diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..c7dfc55e --- /dev/null +++ b/.clang-format @@ -0,0 +1,60 @@ +# Base style configuration +# Inherit all formatting rules from the LLVM style as a starting point. +BasedOnStyle: LLVM + +# Indentation settings +# - IndentWidth: number of spaces per indent level. +# - TabWidth: visual width of tab characters. +# - UseTab: whether to emit real tab characters. +# - NamespaceIndentation: ident namespaces +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +NamespaceIndentation: All + +# Include directives +# - SortIncludes: controls ordering of #include statements. +# - IncludeBlocks: allow empty lines for includes. +SortIncludes: true +IncludeBlocks: Preserve + +# Short constructs on single lines +# Disallow collapsing blocks, loops, cases, ifs, and functions into a single line. +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false + +# Access modifiers formatting +# Shift class access specifiers (public/protected/private) relative to class indent. +AccessModifierOffset: -2 + +# Brace placement +# Attach opening braces to the control statement or declaration line. +BreakBeforeBraces: Attach + +# Spacing around parentheses and casts +# ControlStatements: space before parens in if/for/while only. +# No extra spaces inside parentheses, angles, container literals, or square brackets. +SpaceBeforeParens: ControlStatements +SpaceAfterCStyleCast: false +SpacesInParentheses: false +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false + +# Template keyword spacing +# Always place a space after the 'template' keyword. +SpaceAfterTemplateKeyword: true + +# Line length +# ColumnLimit: 0 disables automatic wrapping; allows unlimited line length. +ColumnLimit: 0 + +# Pointer alignment +# DerivePointerAlignment: ignore inference, use PointerAlignment setting. +# PointerAlignment: align the '*' or '&' to the left with the type. +DerivePointerAlignment: false +PointerAlignment: Left diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..5daec749 --- /dev/null +++ b/.flake8 @@ -0,0 +1,24 @@ +[flake8] + +### Rules ### +# F401 - module imported but unused +# F403 - ‘from module import *’ used; unable to detect undefined names +exclude = + __init__.py::F401,F403 + +# E124 - closing bracket does not match visual indentation +ignore = + E124 + + +### File patterns ### +exclude = + .git, + __pycache__ + +filename = + *.py + +### Options ### +max-line-length = 150 +max-doc-length = 150 \ No newline at end of file diff --git a/.github/workflows/distro-ci.yml b/.github/workflows/distro-ci.yml index 247af7e0..0a938baf 100644 --- a/.github/workflows/distro-ci.yml +++ b/.github/workflows/distro-ci.yml @@ -37,15 +37,17 @@ jobs: - run: | set -e DISTRO=$( cat /etc/*-release | tr [:upper:] [:lower:] | grep -Poi '(debian|ubuntu|fedora|gentoo|alpine)' | uniq ) - if [ "$DISTRO" == "gentoo" ]; then source /etc/profile; fi + if [ "$DISTRO" == "gentoo" ]; then + source /etc/profile + fi git clone https://github.com/${{ github.event.pull_request.head.repo.full_name }}.git && cd HyperCPU git checkout ${{ github.event.pull_request.head.sha }} git submodule update --init --recursive - cd dist/pog && git pull origin master && cd ../.. - cmake -S. -Bbuild -DHCPU_COMPILER=clang -DHCPU_LTO=ON -DHCPU_SANITIZERS=OFF -DCMAKE_BUILD_TYPE=Release - cmake --build build --target run-all-tests-github -j8 - cmake --build build --target default -j8 - rm -rf build - cmake -S. -Bbuild -DHCPU_COMPILER=gcc -DHCPU_LTO=ON -DHCPU_SANITIZERS=OFF -DCMAKE_BUILD_TYPE=Release - cmake --build build --target run-all-tests-github -j8 - cmake --build build --target default -j8 + + python3 scripts/build.py -icb \ + --config Release \ + --config Debug \ + --build-dir build \ + -D CMAKE_CXX_COMPILER:STRING=g++ \ + -D CMAKE_CXX_COMPILER:STRING=gcc \ + --profiles-detection diff --git a/.github/workflows/docker-autobuild.yml b/.github/workflows/docker-autobuild.yml index 656cd057..db5b838e 100644 --- a/.github/workflows/docker-autobuild.yml +++ b/.github/workflows/docker-autobuild.yml @@ -10,7 +10,6 @@ on: permissions: contents: read - jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/run-tests-feature-branch.yml b/.github/workflows/run-tests-feature-branch.yml index f6c2a64c..07630e22 100644 --- a/.github/workflows/run-tests-feature-branch.yml +++ b/.github/workflows/run-tests-feature-branch.yml @@ -17,19 +17,26 @@ jobs: - name: Build and test with GCC on Release profile run: | - cmake -S. -Bbuild -DHCPU_COMPILER=gcc -DCMAKE_BUILD_TYPE=Release - cmake --build build --target build-all-tests-github -j4 - build/modular_testing --gtest_brief - build/integration_testing --gtest_brief - rm -rf build + python3 scripts/build.py -icb \ + --config Release \ + --build-dir build \ + -D CMAKE_CXX_COMPILER:STRING=g++ \ + -D CMAKE_C_COMPILER:STRING=gcc \ + --profiles-detection + cd build/Release && ctest -V && cd ../.. + python3 scripts/build.py -r --config Release --build-dir build - name: Build and test with LLVM on Release profile run: | - cmake -S. -Bbuild -DHCPU_COMPILER=clang -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_BUILD_TYPE=Release - cmake --build build --target build-all-tests-github -j4 - build/modular_testing --gtest_brief - build/integration_testing --gtest_brief - - - name: Clean up + python3 scripts/build.py -icb \ + --config Release \ + --build-dir build \ + -D CMAKE_CXX_COMPILER:STRING=clang++-19 \ + -D CMAKE_C_COMPILER:STRING=clang-19 \ + --profiles-detection + cd build/Release && ctest -V && cd ../.. + python3 scripts/build.py -r --config Release --build-dir build + + - name: Cleanup (Conan cache + leftovers in build) run: | - rm -rf build + rm -r build/ diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index d3bd2a88..90a44b1e 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -19,32 +19,48 @@ jobs: - name: Build and test with GCC on Debug profile run: | - cmake -S. -Bbuild -DHCPU_COMPILER=gcc -DCMAKE_BUILD_TYPE=Debug - cmake --build build --target build-all-tests-github -j4 - build/modular_testing --gtest_brief - build/integration_testing --gtest_brief - rm -rf build + python3 scripts/build.py -icb \ + --config Debug \ + --build-dir build \ + -D CMAKE_CXX_COMPILER:STRING=g++ \ + -D CMAKE_C_COMPILER:STRING=gcc \ + --profiles-detection + cd build/Debug && ctest -V && cd ../.. + python3 scripts/build.py -r --config Debug --build-dir build - name: Build and test with GCC on Release profile run: | - cmake -S. -Bbuild -DHCPU_COMPILER=gcc -DCMAKE_BUILD_TYPE=Release - cmake --build build --target build-all-tests-github -j4 - build/modular_testing --gtest_brief - build/integration_testing --gtest_brief - rm -rf build + python3 scripts/build.py -icb \ + --config Release \ + --build-dir build \ + -D CMAKE_CXX_COMPILER:STRING=g++ \ + -D CMAKE_C_COMPILER:STRING=gcc \ + --profiles-detection + cd build/Release && ctest -V && cd ../.. + python3 scripts/build.py -r --config Release --build-dir build - name: Build and test with LLVM on Debug profile run: | - cmake -S. -Bbuild -DHCPU_COMPILER=clang -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_BUILD_TYPE=Release - cmake --build build --target build-all-tests-github -j4 - build/modular_testing --gtest_brief - build/integration_testing --gtest_brief - rm -rf build + python3 scripts/build.py -icb \ + --config Debug \ + --build-dir build \ + -D CMAKE_CXX_COMPILER:STRING=clang++-19 \ + -D CMAKE_C_COMPILER:STRING=clang-19 \ + --profiles-detection + cd build/Debug && ctest -V && cd ../.. + python3 scripts/build.py -r --config Debug --build-dir build - name: Build and test with LLVM on Release profile run: | - cmake -S. -Bbuild -DHCPU_COMPILER=clang -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_BUILD_TYPE=Release - cmake --build build --target build-all-tests-github -j4 - build/modular_testing --gtest_brief - build/integration_testing --gtest_brief - rm -rf build + python3 scripts/build.py -icb \ + --config Release \ + --build-dir build \ + -D CMAKE_CXX_COMPILER:STRING=clang++-19 \ + -D CMAKE_C_COMPILER:STRING=clang-19 \ + --profiles-detection + cd build/Release && ctest -V && cd ../.. + python3 scripts/build.py -r --config Release --build-dir build + + - name: Cleanup (Conan cache + leftovers in build) + run: | + rm -r build/ diff --git a/.gitignore b/.gitignore index 9e12b3b3..87371c2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,93 @@ +# Build directory +build/ + +# Binaries +*.o +*.os +*.so +*.obj +*.bc +*.pyc +*.dblite +*.pdb +*.lib +*.config +*.creator +*.creator.user +*.files +*.includes +*.idb +*.exp + +# Other stuff +*.log + +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +*.qm +*.prl + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* +*_qmlcache.qrc + +# Auto-generated Conan Presets +CMakeUserPresets* + +# Clang language server cache +.cache + +# JetBrains IDE settings .idea + +# VS Code settings .vscode -.cache -compile_commands.json -build -dist/** + +# Distribution packages +dist/* +!dist/CMakeLists.txt diff --git a/.gitmodules b/.gitmodules index 1ca87879..14ab5e2b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,27 +1,6 @@ -[submodule "dist/googletest"] - path = dist/googletest - url = https://github.com/google/googletest -[submodule "dist/HBench"] - path = dist/HBench - url = https://github.com/randommfs/HBench -[submodule "dist/argparse"] - path = dist/argparse - url = https://github.com/p-ranav/argparse -[submodule "dist/pog"] - path = dist/pog - url = https://github.com/HyperWinX/HPog -[submodule "dist/eternal"] - path = dist/eternal - url = https://github.com/mapbox/eternal [submodule "dist/HPool"] path = dist/HPool url = https://github.com/randommfs/HPool -[submodule "dist/benchmark"] - path = dist/benchmark - url = https://github.com/google/benchmark -[submodule "dist/libbacktrace"] - path = dist/libbacktrace - url = https://github.com/ianlancetaylor/libbacktrace [submodule "dist/libunwind"] path = dist/libunwind url = https://github.com/libunwind/libunwind diff --git a/CMakeLists.txt b/CMakeLists.txt index 67707944..2a7e3ea5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,27 +1,102 @@ -cmake_minimum_required(VERSION 3.25) - -include(cmake/Configuration.cmake) -detect_compilers() +cmake_minimum_required(VERSION 3.22..3.25) project(HyperCPU CXX) -set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(EXPORT_COMPILE_COMMANDS ON) +option(HCPU_LTO "HCPU: Enable LTO if possible" OFF) +option(HCPU_NATIVE_OPTIMIZATIONS "HCPU: Optimize for current ISA implementation" OFF) +option(HCPU_SANITIZERS "HCPU: Enable sanitizers" OFF) +option(HCPU_BUILD_TESTS "HCPU: Build tests" ON) +option(HCPU_BUILD_BENCHMARKS "HCPU: Build benchmarks" OFF) + +find_package(GTest 1.14.0 REQUIRED) # GoogleTest +find_package(benchmark 1.9.1 REQUIRED) # Google Benchmark +find_package(absl 20240116.1 REQUIRED) # Abseil (CMake target: absl::absl) +find_package(libbacktrace CONFIG REQUIRED) # libbacktrace (CMake target: libbacktrace::libbacktrace) +find_package(argparse 3.2 REQUIRED) # argparse.cpp (CMake target: argparse::argparse) +find_package(eternal 1.0.1 REQUIRED) # Eternal (CMake target: eternal::eternal) +find_package(RE2 20230801 REQUIRED) # RE2 (CMake target: re2::re2) +find_package(fmt 11.1.4 REQUIRED) # fmtlib (CMake target: fmt::fmt) +find_package(libunwind 1.8.1 REQUIRED) # libunwind (CMake target: libunwind::libunwind) +find_package(Threads REQUIRED) # pthread support (Threads::Threads) +find_package(Boost 1.87.0 REQUIRED) # Boost + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(ROOT_DIR ${HyperCPU_SOURCE_DIR}) -set(BENCHMARK_ENABLE_GTEST_TESTS OFF) # Disable gtest requirement for google/benchmark -set(FMT_SYSTEM_HEADER ON) # Fix -Wstrinop-overflow warning -set_compile_flags() +# FIXME: remove later +# retained for backwards comparability, should be supplied in CLI or presets +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Fix -Wstrinop-overflow warning +set(FMT_SYSTEM_HEADER ON) + +# FIXME: patch hpool to enable -Werror +add_compile_options( + -Wall + -Wextra + # -Werror + -Wno-pointer-arith + -Wno-unused-const-variable + -Wno-missing-field-initializers + -Wno-stringop-overflow +) -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +add_compile_options( + $<$:-ggdb3> + $<$:-D__HCPU_DEBUG> + $<$:-O3> +) -message(STATUS "Generating source files list") -include(cmake/SourceListGenerator.cmake) -add_subdirectory(dist/argparse) -add_subdirectory(dist/pog) -add_subdirectory(bench) +# FIXME: enable this by default when this becomes operational +if(HCPU_LTO) + check_ipo_supported(RESULT result OUTPUT output) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + message(STATUS "HCPU: Enabled LTO") + else() + message(WARNING "HCPU: LTO is not supported: ${output}") + endif() +else() + message(STATUS "HCPU: Skipping LTO") +endif() + +if(HCPU_NATIVE_OPTIMIZATIONS) + add_compile_options(-march=native) + message(STATUS "HCPU: Enabled optimizations for current ISA implementation") +else() + message(STATUS "HCPU: Skipping current ISA optimizations") +endif() + +if(HCPU_SANITIZERS) + add_compile_options(-fsanitize=address,leak) + add_link_options(-fsanitize=address,leak) + message(STATUS "HCPU: Enabling sanitizers") +else() + message(STATUS "HCPU: Skipping sanitizers") +endif() + +include(CTest) +enable_testing() + +add_library(hcpu_unwind INTERFACE) +target_link_libraries( + hcpu_unwind + INTERFACE + libunwind::libunwind libbacktrace::libbacktrace +) +target_compile_definitions( + hcpu_unwind + INTERFACE + HCPU_ENABLE_LIBUNWIND +) + +add_subdirectory(dist) add_subdirectory(src) -add_subdirectory(test) + +if(HCPU_BUILD_BENCHMARKS) + add_subdirectory(bench) +endif() + +if(HCPU_BUILD_TESTS) + add_subdirectory(tests) +endif() \ No newline at end of file diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 9311d8fc..1f1a5119 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -9,4 +9,4 @@ ## For other contributors -1. Almost the same thing as for collabolators, BUT - you have to fork the repository, and work there. Notice, that you probably have to disable GitHub Actions, or connect the self-hosted runner (no guarantee that HyperCPU workflows will execute successfully on custom runner). When you are ready to send your changes - make a PR - and there goes the same cycle, until the PR gets merged:) \ No newline at end of file +1. Almost the same thing as for collaborators, BUT - you have to fork the repository, and work there. Notice, that you probably have to disable GitHub Actions, or connect the self-hosted runner (no guarantee that HyperCPU workflows will execute successfully on custom runner). When you are ready to send your changes - make a PR - and there goes the same cycle, until the PR gets merged:) \ No newline at end of file diff --git a/cmake/Configuration.cmake b/cmake/Configuration.cmake deleted file mode 100644 index e236d5c8..00000000 --- a/cmake/Configuration.cmake +++ /dev/null @@ -1,122 +0,0 @@ -include(cmake/Variables.cmake) - -function(append_compile_flags flag) - set(TMP_CXX_FLAGS "${TMP_CXX_FLAGS} ${flag}") -endfunction() - -function(append_link_flags flag) - set(TMP_LINK_FLAGS "${TMP_LINK_FLAGS} ${flag}") -endfunction() - -function(set_compile_flags) - if ((NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Release") AND (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") AND (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "RelWWithDebInfo")) - message(FATAL_ERROR "Unknown CMAKE_BUILD_TYPE specified") - endif() - - set(TMP_CXX_FLAGS "" PARENT_SCOPE) - set(TMP_LINK_FLAGS "" PARENT_SCOPE) - - if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") - set(TMP_CXX_FLAGS "${FAST_COMPILE_FLAGS}") - elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - set(TMP_CXX_FLAGS "${DEBUG_COMPILE_FLAGS}") - elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") - set(TMP_CXX_FLAGS "${FAST_COMPILE_FLAGS} -ggdb3") - endif() - - get_property(CAN_USE_LLVM_LTO GLOBAL PROPERTY CAN_USE_LLVM_LTO) - - if ("${HCPU_LTO}" STREQUAL "ON" AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - append_compile_flags("-flto") - append_link_flags("-flto") - message(STATUS "Enabled LTO") - elseif("${HCPU_LTO}" STREQUAL "ON" AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND "${CAN_USE_LLVM_LTO}" STREQUAL "YES") - append_compile_flags("-flto=thin") - append_link_flags("-flto=thin") - message(STATUS "Enabled LTO") - else() - message(STATUS "LTO wasn't enabled") - endif() - - string(TOLOWER "${HCPU_MARCH_NATIVE}" HCPU_MARCH_NATIVE) - if ("${HCPU_MARCH_NATIVE}" STREQUAL "on") - append_compile_flags("-march=native") - message(STATUS "Enabled -march=native flag") - endif() - - string(TOLOWER "${HCPU_X86_32}" HCPU_X86_32) - if ("${HCPU_X86_32}" STREQUAL "on") - append_compile_flags("-m32") - message(STATUS "Enabled x86_32 building mode") - endif() - - string(TOLOWER "${HCPU_SANITIZERS}" HCPU_SANITIZERS) - if (NOT "${HCPU_SANITIZERS}" STREQUAL "off") - append_compile_flags("-fsanitize=address,leak") - append_link_flags("-fsanitize=address,leak") - message(STATUS "Enabling sanitizers") - endif() - - find_library(LIBUNWIND unwind) - set(LIBUNWIND ${LIBUNWIND} PARENT_SCOPE) - if (LIBUNWIND) - message(STATUS "Found libunwind") - append_compile_flags(-DHCPU_ENABLE_LIBUNWIND) - append_link_flags(-L${ROOT_DIR}/dist/libbacktrace -lunwind -lbacktrace) - endif() - set_property(GLOBAL PROPERTY HCPU_COMPILE_FLAGS "${TMP_CXX_FLAGS}") - set_property(GLOBAL PROPERTY HCPU_LINK_FLAGS "${TMP_LINK_FLAGS}") -endfunction() - -function(detect_compilers) - find_program(WHICH_AVAILABLE "which") - if ("${WHICH_AVAILABLE}" STREQUAL WHICH_AVAILABLE-NOT_FOUND) - message(WARNING "which binary is not found - using CMake compiler autodetection") - set(HCPU_COMPILER auto) - endif() - - string(TOLOWER "${HCPU_COMPILER}" HCPU_COMPILER) - string(TOLOWER "${HCPU_LTO}" HCPU_LTO) - string(SUBSTRING "${CMAKE_C_COMPILER}" 0 1 IS_ABS) - - if ("${HCPU_COMPILER}" STREQUAL "auto" OR "${HCPU_COMPILER}" STREQUAL "") - message(STATUS "Using CMake compiler autodetection") - elseif ("${HCPU_COMPILER}" STREQUAL "clang") - if ("${CMAKE_C_COMPILER}" STREQUAL "" OR "${IS_ABS}" STREQUAL "/") - message(STATUS "Searching for clang") - execute_process(COMMAND which clang OUTPUT_VARIABLE CMAKE_C_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND which clang++ OUTPUT_VARIABLE CMAKE_CXX_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) - set(CMAKE_C_COMPILER ${CMAKE_C_COMPILER} CACHE INTERNAL "CMAKE_C_COMPILER") - set(CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} CACHE INTERNAL "CMAKE_CXX_COMPILER") - elseif (NOT "${IS_ABS}" STREQUAL "/") - message(STATUS "Searching for ${CMAKE_C_COMPILER}") - execute_process(COMMAND which ${CMAKE_C_COMPILER} OUTPUT_VARIABLE CMAKE_C_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND which ${CMAKE_CXX_COMPILER} OUTPUT_VARIABLE CMAKE_CXX_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) - set(CMAKE_C_COMPILER ${CMAKE_C_COMPILER} CACHE INTERNAL "CMAKE_C_COMPILER") - set(CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} CACHE INTERNAL "CMAKE_CXX_COMPILER") - endif() - - find_program(LLD_AVAILABLE "ld.lld") - if ("${LLD_AVAILABLE}" STREQUAL LLD_AVAILABLE-NOT_FOUND AND "${HCPU_LTO}" STREQUAL "yes") - message(WARNING "LLD not found: LLVM LTO will be disabled") - set(CAN_USE_LLVM_LTO "NO" PARENT_SCOPE) - else() - execute_process(COMMAND which ld.lld OUTPUT_VARIABLE CMAKE_LINKER OUTPUT_STRIP_TRAILING_WHITESPACE) - message(STATUS "Found LLD") - set_property(GLOBAL PROPERTY CAN_USE_LLVM_LTO "YES") - endif() - elseif ("${HCPU_COMPILER}" STREQUAL "gcc") - if ("${CMAKE_C_COMPILER}" STREQUAL "" OR "${IS_ABS}" STREQUAL "/") - message(STATUS "Searching for gcc") - execute_process(COMMAND which gcc OUTPUT_VARIABLE CMAKE_C_COMPILER) - execute_process(COMMAND which g++ OUTPUT_VARIABLE CMAKE_CXX_COMPILER) - elseif (NOT "${IS_ABS}" STREQUAL "/") - message(STATUS "Searching for ${CMAKE_C_COMPILER}") - execute_process(COMMAND which ${CMAKE_C_COMPILER} OUTPUT_VAR -IABLE CMAKE_C_COMPILER) - execute_process(COMMAND which ${CMAKE_CXX_COMPILER} OUTPUT_VARIABLE CMAKE_CXX_COMPILER) - endif() - else() - message(FATAL_ERROR "HCPU_COMPILER not specified: cannot proceed. Please specify one of: auto, clang, gcc.") - endif() -endfunction() diff --git a/cmake/SourceListGenerator.cmake b/cmake/SourceListGenerator.cmake deleted file mode 100644 index 28091458..00000000 --- a/cmake/SourceListGenerator.cmake +++ /dev/null @@ -1,16 +0,0 @@ -include(${ROOT_DIR}/cmake/TargetAndFolderAssoc.cmake) - -foreach(pair IN ZIP_LISTS __TARGETS_LIST __DIRECTORIES_LIST) - # Execute find and get list of source files - execute_process( - COMMAND find ${pair_1} -type f -name "*.cpp" - OUTPUT_VARIABLE ENUMERATED_SRC - ) - - ##string(REGEX MATCHALL "[^a-zA-Z]\n[^a-zA-Z]$" SOURCES_${pair_0} ${ENUMERATED_SRC}) - #string(REGEX REPLACE "\n" ";" SOURCES_${pair_0} "${ENUMERATED_SRC}") - set(SOURCES_${pair_0} ${ENUMERATED_SRC}) - string(REPLACE "\n" ";" SOURCES_${pair_0} ${ENUMERATED_SRC}) - list(LENGTH SOURCES_${pair_0} CNT_${pair_0}) - message(STATUS "Found ${CNT_${pair_0}} source files for target ${pair_0}") -endforeach() diff --git a/cmake/TargetAndFolderAssoc.cmake b/cmake/TargetAndFolderAssoc.cmake deleted file mode 100644 index ee27762c..00000000 --- a/cmake/TargetAndFolderAssoc.cmake +++ /dev/null @@ -1,38 +0,0 @@ -set(__TARGETS_LIST -# HyperCPU core sources - emulator-core - emulator-main - - assembler-core - assembler-main - - backtrace-provider - - -# Testing - modular_testing - integration_testing -) - -set(__DIRECTORIES_LIST -# emulator-core target - ${ROOT_DIR}/src/Emulator/Core - -# emulator-main target - ${ROOT_DIR}/src/Emulator/Main - -# assembler-core target - ${ROOT_DIR}/src/Assembler/Core - -# assembler-main target - ${ROOT_DIR}/src/Assembler/Main - -# backtrace-provider target - ${ROOT_DIR}/src/BacktraceProvider - -# modulartesting_src target - ${ROOT_DIR}/test/Modular - -# integrationtesting_src - ${ROOT_DIR}/test/Integration -) diff --git a/cmake/Variables.cmake b/cmake/Variables.cmake deleted file mode 100644 index 1d185b4f..00000000 --- a/cmake/Variables.cmake +++ /dev/null @@ -1,2 +0,0 @@ -set(DEBUG_COMPILE_FLAGS -Wall -Wextra -Werror -Wno-pointer-arith -O0 -ggdb3 -Wno-unused-const-variable -Wno-missing-field-initializers -Wno-stringop-overflow -Wno-unknown-warning-option -D__HCPU_DEBUG) -set(FAST_COMPILE_FLAGS -Wall -Wextra -Werror -Wno-pointer-arith -O3 -Wno-unused-const-variable -Wno-missing-field-initializers -Wno-stringop-overflow -Wno-unknown-warning-option) diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 00000000..1fe452f0 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,35 @@ +from typing import Self, List +from functools import lru_cache + +from conan import ConanFile +from conan.tools.cmake import cmake_layout + + +class HyperCPU(ConanFile): + generators: List[str] = ['CMakeToolchain', 'CMakeDeps'] + settings: List[str] = ['os', 'compiler', 'build_type', 'arch'] + + def __init__(self: Self, display_name: str = '') -> None: + self.name = 'HyperCPU' + + self.__requirements = { + 'gtest': '1.14.0', + 'benchmark': '1.9.1', + 'abseil': '20240116.1', + 'libbacktrace': 'cci.20210118', + 'argparse': '3.2', + 'eternal': '1.0.1', + 're2': '20230801', + 'fmt': '11.1.4', + 'libunwind': '1.8.1', + 'boost': '1.87.0' + } + super().__init__(display_name) + + @lru_cache + def requirements(self: Self) -> None: + for req, version in self.__requirements.items(): + self.requires(f'{req}/{version}') + + def layout(self): + cmake_layout(self) diff --git a/dist/CMakeLists.txt b/dist/CMakeLists.txt new file mode 100644 index 00000000..b765f1c9 --- /dev/null +++ b/dist/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.25) + +# hpool +set(HPOOL_BUILD_TESTS OFF CACHE BOOL "Whether to include test target into configuration" FORCE) +set(HPOOL_BUILD_BENCHMARKS OFF CACHE BOOL "Whether to include benchmark target into configuration" FORCE) +add_subdirectory(HPool) \ No newline at end of file diff --git a/dist/HBench b/dist/HBench deleted file mode 160000 index adae2e68..00000000 --- a/dist/HBench +++ /dev/null @@ -1 +0,0 @@ -Subproject commit adae2e6831283f8675062a8123ac21c72dcc52a9 diff --git a/dist/HPool b/dist/HPool index 5d09b643..6b165437 160000 --- a/dist/HPool +++ b/dist/HPool @@ -1 +1 @@ -Subproject commit 5d09b643d2cd43081fae8af5a7c313272485c387 +Subproject commit 6b1654378a084f4e7cc7324670543df86d03b7fa diff --git a/dist/argparse b/dist/argparse deleted file mode 160000 index d924b84e..00000000 --- a/dist/argparse +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d924b84eba1f0f0adf38b20b7b4829f6f65b6570 diff --git a/dist/benchmark b/dist/benchmark deleted file mode 160000 index 1bc59dce..00000000 --- a/dist/benchmark +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1bc59dce278b9145f18ef88d31d373c4ba939dc4 diff --git a/dist/eternal b/dist/eternal deleted file mode 160000 index dd2f5b9f..00000000 --- a/dist/eternal +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dd2f5b9ff38fcd36b59efd9d289127fa73efc6cb diff --git a/dist/googletest b/dist/googletest deleted file mode 160000 index 2b6b042a..00000000 --- a/dist/googletest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2b6b042a77446ff322cd7522ca068d9f2a21c1d1 diff --git a/dist/libbacktrace b/dist/libbacktrace deleted file mode 160000 index 79392187..00000000 --- a/dist/libbacktrace +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 793921876c981ce49759114d7bb89bb89b2d3a2d diff --git a/dist/pog b/dist/pog deleted file mode 160000 index d62f0d05..00000000 --- a/dist/pog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d62f0d05c1cbb638ef2d4d37084ab8b54e3bfc29 diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile index 5c199bb1..52918024 100644 --- a/docker/alpine/Dockerfile +++ b/docker/alpine/Dockerfile @@ -3,5 +3,5 @@ FROM alpine:latest RUN apk update && \ apk add --no-cache python3 py3-pip clang gcc git cmake make \ - ninja nodejs grep \ + ninja nodejs grep g++ linux-headers \ && pip3 install --no-cache-dir --break-system-packages conan \ No newline at end of file diff --git a/scripts/build.py b/scripts/build.py new file mode 100755 index 00000000..24cf7d52 --- /dev/null +++ b/scripts/build.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python3 + +import os +import sys +import json +import errno +import shutil +import typing +import inspect +import logging +import argparse +import subprocess +import multiprocessing + +from pathlib import Path +from collections import OrderedDict +from dataclasses import dataclass, field +from typing import Callable, Iterable, Union, Tuple, Collection, Optional + +from conan.api.conan_api import ConanAPI, ConanException +# this is not part of external API, but those formatters are really cool +from conan.api.output import ConanOutput +from conan.cli.printers import print_profiles +from conan.cli.printers.graph import print_graph_basic, print_graph_packages + + +logger = logging.getLogger(__file__ if '__file__' in vars() else 'build.helper') + + +DESCRIPTION = ''' + CLI build helper. +''' + + +@dataclass +class Config: + build_directory: Path = field(default_factory=lambda: Path.cwd().joinpath('build')) + cores: int = multiprocessing.cpu_count() + # CMake-specific + build_configs: Iterable[str] = field(default_factory=lambda: ['Debug']) + generator: Optional[str] = None + defines: Collection[Tuple[str, str, str]] = field(default_factory=lambda: []) + # Conan-specific + # allow conan to detect profile (might be inaccurate) + allow_profile_detection: bool = False + # build profile specifies target environment + build_profile: str = 'default' + # host profile describes host environment (where build happens) + host_profile: str = 'default' + + +def routine(priority: int) -> Callable: + def factory(f: Callable) -> Callable: + f.priority = priority + f.is_routine = True + return f + + return factory + + +class Routines: + def __init__(self, params: Config): + self.__params = params + + def routines(self) -> Iterable[Tuple[str, Callable]]: + def key(pair): + _, member = pair + return member.priority if hasattr(member, 'priority') else 0 + + members = inspect.getmembers(self, predicate=inspect.ismethod) + for name, method in sorted(members, key=key, reverse=True): + if hasattr(method, 'is_routine'): + yield name, method + + @routine(5) + def remove(self): + for config in self.__params.build_configs: + directory = self.__params.build_directory.joinpath(config) + if directory.is_dir(): + logger.info(f'removing directory for CMake build config "{config}"') + shutil.rmtree(directory) + else: + logger.error(f'build directory for config "{config}" was not found or was not a directory') + + @routine(4) + def conan_install(self): + api = ConanAPI(str(self.__params.build_directory / 'conan2')) + + detect_profiles = False + profiles = api.profiles.list() + if len(profiles) > 0: + logger.debug(f'detected profiles: ' + ', '.join(profiles)) + else: + if not self.__params.allow_profile_detection: + raise FileNotFoundError( + 'no profiles exist and autodetection is turned off' + ) + detect_profiles = True + + logger.info( + 'please notice, this script modifies "build_type" in incoming profile, to match running params' + ) + remotes = api.remotes.list() + + for config in self.__params.build_configs: + if not self._conan_install_for_config(config, api, remotes, detect_profiles=detect_profiles): + raise RuntimeError( + f'failed to install dependencies for config: {config}, see error from conan above' + ) + + @routine(3) + def configure(self): + if not self.__params.build_directory.is_dir(): + self.__params.build_directory.mkdir() + + use_presets = Path.cwd().joinpath('CMakeUserPresets.json').is_file() + if use_presets: + logger.info('found CMake presets') + + logger.info('configuring for CMake build configs: ' + ', '.join(self.__params.build_configs)) + for config in self.__params.build_configs: + source = Path.cwd() + binary = self.__params.build_directory.joinpath(config) + + # TODO: allow multiconfigs + command = [ + 'cmake', + '--preset', f'conan-{config.lower()}', + '-B', str(binary), + '-S', str(source), + self._decorate_cmake_variable('CMAKE_EXPORT_COMPILE_COMMANDS', 'ON', 'BOOL'), + self._decorate_cmake_variable('CMAKE_BUILD_TYPE', config) + ] if use_presets else [ + 'cmake', + '-B', str(binary), + '-S', str(source), + self._decorate_cmake_variable('CMAKE_EXPORT_COMPILE_COMMANDS', 'ON', 'BOOL'), + self._decorate_cmake_variable('CMAKE_BUILD_TYPE', config) + ] + + if self.__params.generator is not None: + command.extend(['-G', self.__params.generator]) + + if len(self.__params.defines) > 0: + for key, var_type, value in self.__params.defines: + command.append(self._decorate_cmake_variable(key, value, var_type)) + + logger.info(f'running configure command for CMake config {config}') + self._run(command) + + @routine(2) + def build(self): + if not self.__params.build_directory.is_dir(): + raise FileNotFoundError(f'build directory \'{self.__params.build_directory}\' was not found') + + logger.info(f'using {self.__params.cores} threads') + for config in self.__params.build_configs: + directory = self.__params.build_directory.joinpath(config) + logger.info(f'building for CMake configuration \'{config}\'') + self._run([ + 'cmake', + '--build', str(directory), + '--parallel', str(self.__params.cores) + ]) + + @routine(1) + def symlink_compile_commands(self: 'Routines') -> None: + path = None + if 'Debug' in self.__params.build_configs: + path = self.__params.build_directory.joinpath('Debug').joinpath('compile_commands.json') + logger.info(f'creating symlink for path \'{path}\'') + + if 'Release' in self.__params.build_configs: + path = self.__params.build_directory.joinpath('Release').joinpath('compile_commands.json') + logger.info(f'creating symlink for path \'{path}\'') + + if path is None: + raise ValueError('no supported CMake configs detected') + + symlink = Path.cwd().joinpath('compile_commands.json') + if symlink.is_symlink(): + symlink.unlink() + + Path.cwd().joinpath('compile_commands.json').symlink_to(path) + + def _conan_install_for_config( + self, + config: str, + api: ConanAPI, + remotes: list, + detect_profiles: bool = False + ) -> bool: + out = ConanOutput() + try: + if detect_profiles: + build_profile = host_profile = api.profiles.detect() + else: + build_profile = api.profiles.get_profile([self.__params.build_profile]) + host_profile = api.profiles.get_profile([self.__params.host_profile]) + + mixin = OrderedDict({'build_type': config}) + for profile in (build_profile, host_profile): + profile.update_settings(mixin) + if profile.processed_settings is None: + profile.process_settings(api.config.settings_yml) + + if detect_profiles: + out.title('Autodetected profiles (be careful using them!)') + print_profiles(host_profile, build_profile) + + except ConanException as conan_error: + raise RuntimeError( + f'getting requested profiles failed: {(self.__params.build_profile, self.__params.host_profile)}' + ) from conan_error + + logger.info(f'computing dependency graph for config: {config}') + try: + graph = api.graph.load_graph_consumer( + path=api.local.get_conanfile_path('.', Path.cwd(), py=True), + # these 4 are provided by recipe + name=None, + version=None, + user=None, + channel=None, + # ============================== + profile_build=build_profile, + profile_host=host_profile, + lockfile=None, + remotes=remotes, + update=True, + check_updates=True + ) + print_graph_basic(graph) + graph.report_graph_error() + api.graph.analyze_binaries(graph, build_mode=['missing'], remotes=remotes, update=True) + print_graph_packages(graph) + + except ConanException as conan_error: + out.error(str(conan_error)) + return False + + # make sure to define cmake layout in conanfile.py + try: + api.install.install_binaries(graph, remotes) + api.install.install_consumer(graph, source_folder=Path.cwd()) + except ConanException as conan_error: + out.error(str(conan_error)) + return False + + return True + + def _run(self: 'Routines', command: typing.List[str]) -> None: + logger.info('running command: ' + ' '.join(command)) + code = subprocess.run(command, encoding='UTF-8', stderr=subprocess.STDOUT, env=os.environ).returncode + if code: + sys.exit(f'error: subprocess failed: {errno.errorcode[code]} (code: {code})') + + def _decorate_cmake_variable(self: 'Routines', var: str, value: str, var_type: Union[str, None] = None) -> str: + if var_type is not None: + return f'-D{var.upper()}:{var_type}={value}' + return f'-D{var.upper()}={value}' + + +def resolve_profiles(config: Config, args: argparse.Namespace): + if args.profile_all is not None: + config.build_profile = config.host_profile = args.profile_all + logger.info(f'using specified "all" (build and host) profile: {args.profile_all}') + return + + if args.profile_host is not None: + config.host_profile = args.profile_host + logger.info(f'using specified "host" profile: {args.profile_host}') + + if args.profile_build is not None: + config.build_profile = args.profile_build + logger.info(f'using specified "host" profile: {args.profile_build}') + + if all(profile is None for profile in (args.profile_all, args.profile_host, args.profile_build)): + logger.info('no profiles were specified. Using default for both "host" and "build"') + + +def resolve_defines(config: Config, args: argparse.Namespace): + for define in args.defines: + name, value = define.split('=') + var_name, var_type = name.split(':') + config.defines.append((var_name, var_type, value)) + + +def parse_cli_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter) + # Routines + parser.add_argument('-b', '--build', action='store_true', dest='build') + parser.add_argument('-c', '--configure', action='store_true', dest='configure') + parser.add_argument('-i', '--conan-install', action='store_true', dest='conan_install') + parser.add_argument('-r', '--remove', action='store_true', dest='remove') + parser.add_argument('-l', '--symlink-compile-commands', action='store_true', dest='symlink_compile_commands') + # Environment + parser.add_argument('--build-dir', action='store', dest='build_directory') + # TODO: allow more configs + parser.add_argument('--config', action='append', dest='configs', choices=['Debug', 'Release']) + parser.add_argument('--parallel', action='store', dest='cores') + parser.add_argument('--generator', action='store', dest='generator') + # Profiles + parser.add_argument('--pr:a', action='store', dest='profile_all') + parser.add_argument('--pr:h', action='store', dest='profile_host') + parser.add_argument('--pr:b', action='store', dest='profile_build') + parser.add_argument('--profiles-detection', action='store_true', dest='profile_detect') + # Arbitrary CMake flags + parser.add_argument('-D', metavar='KEY:TYPE=VALUE', action='append', default=[], dest='defines') + return parser.parse_args() + + +def main(): + try: + from rich.logging import RichHandler + from rich.traceback import install + + install(show_locals=True) + handler = RichHandler( + level=logging.DEBUG, + rich_tracebacks=True, + show_path=False, + show_time=False, + show_level=True, + ) + except ImportError: + handler = logging.StreamHandler(sys.stdout) + + logging.root.addHandler(handler) + logging.root.setLevel(logging.INFO) + + args = parse_cli_args() + + params = Config() + if getattr(args, 'build_directory') is not None: + params.build_directory = Path.cwd().joinpath(args.build_directory) + logger.info(f'config: user-provided build directory: "{params.build_directory}"') + if getattr(args, 'configs') is not None: + params.build_configs = args.configs + logger.info(f'config: user-provided build configs: {params.build_configs}') + if getattr(args, 'cores') is not None: + params.cores = int(args.cores) + logger.info(f'config: user-provided threads, that will be run in parallel: "{params.cores}"') + if getattr(args, 'generator') is not None: + params.generator = args.generator + logger.info(f'config: user-provided generator: "{params.generator}"') + params.allow_profile_detection = args.profile_detect + + resolve_profiles(params, args) + resolve_defines(params, args) + + r = Routines(params) + for routine, f in r.routines(): + if not hasattr(args, routine): + logger.info(f'routine \'{routine}\' is not configured for CLI, skipping') + continue + if getattr(args, routine): + logger.info(f'running {routine}') + f() + + logger.info('done!') + + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print('exited by user') \ No newline at end of file diff --git a/src/Assembler/CMakeLists.txt b/src/Assembler/CMakeLists.txt new file mode 100644 index 00000000..cb0ab849 --- /dev/null +++ b/src/Assembler/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.25) + + +add_library( + assembler-core + STATIC + Core/StatementCompilers.cpp + Core/Parsers.cpp + Core/Tokenizers.cpp + Core/Compiler.cpp + Core/BinaryTransformer.cpp + Utils/Extension.hpp + Core/BinaryTransformer.hpp + Core/OpcodeNameAssoc.hpp + Core/RegNameAssoc.hpp + Core/Compiler.hpp + Core/ModeNameAssoc.hpp +) +target_include_directories( + assembler-core + PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} +) +target_link_libraries( + assembler-core + PUBLIC + fmt::fmt + argparse::argparse + eternal::eternal + re2::re2 + pch + pog + hpool +) + +add_executable(hcasm Main/Main.cpp) +target_link_libraries(hcasm PRIVATE assembler-core) \ No newline at end of file diff --git a/src/Assembler/Core/BinaryTransformer.cpp b/src/Assembler/Core/BinaryTransformer.cpp index 9499da93..81e9656b 100644 --- a/src/Assembler/Core/BinaryTransformer.cpp +++ b/src/Assembler/Core/BinaryTransformer.cpp @@ -1,3 +1,6 @@ +#include +#include + #include #include #include @@ -5,6 +8,7 @@ #include #include + HyperCPU::OperandTypes HCAsm::BinaryTransformer::DetermineOperandTypes(Operand& op1, Operand& op2) { Op1T tp1; Op2T tp2; diff --git a/src/Assembler/Core/BinaryTransformer.hpp b/src/Assembler/Core/BinaryTransformer.hpp index 509e3986..3cbe1c81 100644 --- a/src/Assembler/Core/BinaryTransformer.hpp +++ b/src/Assembler/Core/BinaryTransformer.hpp @@ -1,10 +1,9 @@ #pragma once +#include + #include #include -#include - -#include namespace HCAsm { diff --git a/src/Assembler/Core/Compiler.cpp b/src/Assembler/Core/Compiler.cpp index 281c1d34..3df0b693 100644 --- a/src/Assembler/Core/Compiler.cpp +++ b/src/Assembler/Core/Compiler.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -7,9 +8,6 @@ #include #include -#include -#include - using HyperCPU::LogLevel; diff --git a/src/Assembler/Core/Compiler.hpp b/src/Assembler/Core/Compiler.hpp index 3e3ec031..1886a8ee 100644 --- a/src/Assembler/Core/Compiler.hpp +++ b/src/Assembler/Core/Compiler.hpp @@ -1,18 +1,14 @@ #pragma once +#include #include #include #include -#include -#include #include #include #include -#include -#include - #include diff --git a/src/Assembler/Core/ModeNameAssoc.hpp b/src/Assembler/Core/ModeNameAssoc.hpp index 0359d1de..6ae6d56d 100644 --- a/src/Assembler/Core/ModeNameAssoc.hpp +++ b/src/Assembler/Core/ModeNameAssoc.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include diff --git a/src/Assembler/Core/OpcodeNameAssoc.hpp b/src/Assembler/Core/OpcodeNameAssoc.hpp index 5679d136..0d7705f6 100644 --- a/src/Assembler/Core/OpcodeNameAssoc.hpp +++ b/src/Assembler/Core/OpcodeNameAssoc.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include diff --git a/src/Assembler/Core/Parsers.cpp b/src/Assembler/Core/Parsers.cpp index 8998a0cd..b2f8f144 100644 --- a/src/Assembler/Core/Parsers.cpp +++ b/src/Assembler/Core/Parsers.cpp @@ -1,9 +1,10 @@ +#include +#include + #include #include #include -#include - using HCAsm::Value; Value HCAsm::ParseOperand1(pog::Parser& parser, std::vector>&& args) { diff --git a/src/Assembler/Core/RegNameAssoc.hpp b/src/Assembler/Core/RegNameAssoc.hpp index 97687a2c..698261d5 100644 --- a/src/Assembler/Core/RegNameAssoc.hpp +++ b/src/Assembler/Core/RegNameAssoc.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include diff --git a/src/Assembler/Core/StatementCompilers.cpp b/src/Assembler/Core/StatementCompilers.cpp index 608289a7..3f0fa948 100644 --- a/src/Assembler/Core/StatementCompilers.cpp +++ b/src/Assembler/Core/StatementCompilers.cpp @@ -1,5 +1,6 @@ -#include "pog/line_spec.h" -#include "pog/parser.h" +#include +#include + #include #include #include diff --git a/src/Assembler/Core/Tokenizers.cpp b/src/Assembler/Core/Tokenizers.cpp index 3054ec97..024f1a18 100644 --- a/src/Assembler/Core/Tokenizers.cpp +++ b/src/Assembler/Core/Tokenizers.cpp @@ -1,3 +1,6 @@ +#include +#include + #include using HCAsm::Value; diff --git a/src/Assembler/Main/Main.cpp b/src/Assembler/Main/Main.cpp index 723737fd..320960b8 100644 --- a/src/Assembler/Main/Main.cpp +++ b/src/Assembler/Main/Main.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -5,7 +6,6 @@ #include #include #include -#include #include #include diff --git a/src/BacktraceProvider/BacktraceProvider.cpp b/src/BacktraceProvider/BacktraceProvider.cpp index e4e8b5c2..da889253 100644 --- a/src/BacktraceProvider/BacktraceProvider.cpp +++ b/src/BacktraceProvider/BacktraceProvider.cpp @@ -1,6 +1,8 @@ #ifdef HCPU_ENABLE_LIBUNWIND #define UNW_LOCAL_ONLY +#include + #include #include diff --git a/src/BacktraceProvider/CMakeLists.txt b/src/BacktraceProvider/CMakeLists.txt new file mode 100644 index 00000000..1b2ddd51 --- /dev/null +++ b/src/BacktraceProvider/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.25) + + +add_library( + backtrace-provider + STATIC + BacktraceProvider.cpp + BacktraceProvider.hpp +) +target_include_directories( + backtrace-provider + PUBLIC + ${CMAKE_SOURCE_DIR}/src +) +target_link_libraries( + backtrace-provider + PUBLIC + fmt::fmt + argparse::argparse + pog + eternal::eternal + hcpu_unwind + pch +) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ee5dc8e..798be99a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,68 +1,24 @@ -include(ExternalProject) +# TODO: refactor dependencies for each target, there is something useless there for sure +# TODO: refactor all includes relevant to CMAKE_SOURCE_DIR, like #include -set(GENERIC_INCLUDE_DIR - ${ROOT_DIR}/src - ${ROOT_DIR}/dist/argparse/include - ${ROOT_DIR}/dist/pog/include - ${ROOT_DIR}/dist/eternal/include - ${ROOT_DIR}/dist/HPool) - -ExternalProject_Add( - libbacktrace - SOURCE_DIR - ${ROOT_DIR}/dist/libbacktrace - BUILD_IN_SOURCE - 0 - CONFIGURE_COMMAND - cd ${ROOT_DIR}/dist/libbacktrace && ./configure - BUILD_COMMAND - cd ${ROOT_DIR}/dist/libbacktrace && make -j$(nproc) - INSTALL_COMMAND - "" - BUILD_BYPRODUCTS - ${ROOT_DIR}/dist/libbacktrace/libbacktrace.la +add_library( + pch INTERFACE + pch.hpp ) +target_precompile_headers(pch INTERFACE pch.hpp) -get_property(HCPU_CXX_FLAGS GLOBAL PROPERTY HCPU_CXX_FLAGS) -get_property(HCPU_LINK_FLAGS GLOBAL PROPERTY HCPU_LINK_FLAGS) - -add_library(emulator-core STATIC ${SOURCES_emulator-core}) -target_compile_options(emulator-core PUBLIC "${HCPU_CXX_FLAGS}") -target_include_directories(emulator-core PUBLIC ${GENERIC_INCLUDE_DIR} ${ROOT_DIR}/src/Emulator) -target_link_libraries(emulator-core ${LD_FLAGS} fmt) -target_precompile_headers(emulator-core PRIVATE pch.hpp) - -add_library(assembler-core STATIC ${SOURCES_assembler-core}) -target_compile_options(assembler-core PUBLIC "${HCPU_CXX_FLAGS}") -target_include_directories(assembler-core PUBLIC ${GENERIC_INCLUDE_DIR} ${ROOT_DIR}/src/Assembler) -target_link_libraries(assembler-core pog) -target_precompile_headers(assembler-core PRIVATE pch.hpp) - -add_library(backtrace-provider STATIC ${SOURCES_backtrace-provider}) -target_compile_options(backtrace-provider PUBLIC "${HCPU_CXX_FLAGS}") -target_include_directories(backtrace-provider PUBLIC ${GENERIC_INCLUDE_DIR}) -add_dependencies(backtrace-provider libbacktrace) -target_precompile_headers(backtrace-provider PRIVATE pch.hpp) - -add_executable(hcasm ${SOURCES_assembler-main}) -target_compile_options(hcasm PUBLIC "${HCPU_CXX_FLAGS}") -target_include_directories(hcasm PUBLIC ${GENERIC_INCLUDE_DIR} ${ROOT_DIR}/src/Assembler) -target_link_libraries(hcasm assembler-core pog) -target_precompile_headers(hcasm PRIVATE pch.hpp) - -add_executable(hcemul ${SOURCES_emulator-main}) -target_compile_options(hcemul PUBLIC "${HCPU_CXX_FLAGS}") -target_include_directories(hcemul PUBLIC ${GENERIC_INCLUDE_DIR} ${ROOT_DIR}/src/Emulator) -target_link_libraries(hcemul emulator-core assembler-core) -target_precompile_headers(hcemul PRIVATE pch.hpp) - -if (LIBUNWIND) - target_link_libraries(hcasm backtrace-provider -L${ROOT_DIR}/dist/libbacktrace backtrace) - target_link_libraries(hcemul backtrace-provider -L${ROOT_DIR}/dist/libbacktrace backtrace) -endif() - -add_custom_target(default - DEPENDS - hcasm - hcemul +add_library( + pog INTERFACE + Pog/Pog.hpp ) +target_precompile_headers(pog INTERFACE Pog/Pog.hpp) +target_include_directories( + pog + INTERFACE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/Pog +) + +add_subdirectory(Emulator) +add_subdirectory(Assembler) +add_subdirectory(BacktraceProvider) diff --git a/src/Emulator/CMakeLists.txt b/src/Emulator/CMakeLists.txt new file mode 100644 index 00000000..f194c8d4 --- /dev/null +++ b/src/Emulator/CMakeLists.txt @@ -0,0 +1,97 @@ +cmake_minimum_required(VERSION 3.25) + + +add_library( + emulator-core + STATIC + Core/CPU/Instructions/AllowedFlags.cpp + Core/CPU/Interrupts/InterruptHandler.cpp + Core/CPU/CPU.cpp + Core/CPU/InstructionsImpl/DIV.cpp + Core/CPU/InstructionsImpl/CALLL.cpp + Core/CPU/InstructionsImpl/MUL.cpp + Core/CPU/InstructionsImpl/READ.cpp + Core/CPU/InstructionsImpl/ADC.cpp + Core/CPU/InstructionsImpl/ADD.cpp + Core/CPU/InstructionsImpl/DEC.cpp + Core/CPU/InstructionsImpl/SHFL.cpp + Core/CPU/InstructionsImpl/CUDF.cpp + Core/CPU/InstructionsImpl/PUSH.cpp + Core/CPU/InstructionsImpl/OR.cpp + Core/CPU/InstructionsImpl/POP.cpp + Core/CPU/InstructionsImpl/CALLGR.cpp + Core/CPU/InstructionsImpl/CCRF.cpp + Core/CPU/InstructionsImpl/JMP.cpp + Core/CPU/InstructionsImpl/AND.cpp + Core/CPU/InstructionsImpl/HID.cpp + Core/CPU/InstructionsImpl/CALL.cpp + Core/CPU/InstructionsImpl/INC.cpp + Core/CPU/InstructionsImpl/INTR.cpp + Core/CPU/InstructionsImpl/SHFR.cpp + Core/CPU/InstructionsImpl/CALLE.cpp + Core/CPU/InstructionsImpl/JML.cpp + Core/CPU/InstructionsImpl/SUB.cpp + Core/CPU/InstructionsImpl/ANDN.cpp + Core/CPU/InstructionsImpl/LOIVT.cpp + Core/CPU/InstructionsImpl/HALT.cpp + Core/CPU/InstructionsImpl/MOV.cpp + Core/CPU/InstructionsImpl/CMP.cpp + Core/CPU/InstructionsImpl/WRITE.cpp + Core/CPU/InstructionsImpl/BSWAP.cpp + Core/CPU/InstructionsImpl/JME.cpp + Core/CPU/InstructionsImpl/COVF.cpp + Core/CPU/InstructionsImpl/JMGR.cpp + Core/CPU/Decoders/StdDecoder.cpp + Core/CPU/Stack.cpp + Core/CPU/OperandsEvaluation.cpp + Core/CPU/IO/Simple.cpp + Core/MemoryController/MemoryControllerST.hpp + Core/MemoryController/IMemoryController.hpp + Core/CPU/Instructions/AllowedFlags.hpp + Core/CPU/Instructions/Opcodes.hpp + Core/CPU/Instructions/Flags.hpp + Core/CPU/Instructions/Registers.hpp + Core/CPU/Interrupts/ReservedInterrupts.hpp + Core/CPU/CPU.hpp + Core/CPU/ALU.hpp + Core/CPU/Decoders/StdDecoder.hpp + Core/CPU/Decoders/IDecoder.hpp + Core/CPU/Assert.hpp + Core/CPU/Version.hpp + Core/CPU/Util.hpp + Core/CPU/IO/Simple.hpp + Misc/byteswap.hpp + Misc/overflow.hpp + Misc/deref.hpp + Misc/bit_cast.hpp + Misc/unreachable.hpp + Misc/underflow.hpp + Misc/print.hpp +) + +target_include_directories( + emulator-core + PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries( + emulator-core + PUBLIC + fmt::fmt + argparse::argparse + pog + eternal::eternal + pch + hpool +) + +add_executable( + hcemul + Main/Main.hpp + Main/Main.cpp + Main/ExceptionHandling.cpp + Main/ExceptionHandling.hpp +) +target_link_libraries(hcemul PRIVATE emulator-core) \ No newline at end of file diff --git a/src/Emulator/Core/CPU/CPU.cpp b/src/Emulator/Core/CPU/CPU.cpp index 6bb4aecb..db773b45 100644 --- a/src/Emulator/Core/CPU/CPU.cpp +++ b/src/Emulator/Core/CPU/CPU.cpp @@ -1,12 +1,12 @@ -#include "Core/CPU/Instructions/Opcodes.hpp" -#include "Logger/Logger.hpp" #include #include -#include +#include #include +#include #include + HyperCPU::CPU::CPU(std::uint16_t core_count, std::uint64_t mem_size, char* binary, std::uint64_t binary_size) : mem_controller(dynamic_cast(new MemoryControllerST(mem_size, this))), logger(LogLevel::ERROR), diff --git a/src/Emulator/Core/CPU/CPU.hpp b/src/Emulator/Core/CPU/CPU.hpp index 18cb9d01..b39538e0 100644 --- a/src/Emulator/Core/CPU/CPU.hpp +++ b/src/Emulator/Core/CPU/CPU.hpp @@ -1,6 +1,5 @@ #pragma once -#include "Logger/Logger.hpp" #include #include @@ -8,7 +7,8 @@ #include #include #include -#include +#include + #define DECLARE_INSTR(name) void Exec##name(const IInstruction& instr, OperandContainer op1, OperandContainer op2) diff --git a/src/Emulator/Core/CPU/Decoders/IDecoder.hpp b/src/Emulator/Core/CPU/Decoders/IDecoder.hpp index 786506e2..0bf51c75 100644 --- a/src/Emulator/Core/CPU/Decoders/IDecoder.hpp +++ b/src/Emulator/Core/CPU/Decoders/IDecoder.hpp @@ -1,10 +1,5 @@ #pragma once -#include -#include -#include - - namespace HyperCPU { struct IInstruction; diff --git a/src/Emulator/Core/CPU/Decoders/StdDecoder.cpp b/src/Emulator/Core/CPU/Decoders/StdDecoder.cpp index 1b3ec5ae..85ffe863 100644 --- a/src/Emulator/Core/CPU/Decoders/StdDecoder.cpp +++ b/src/Emulator/Core/CPU/Decoders/StdDecoder.cpp @@ -6,9 +6,6 @@ #include #include #include -#include -#include -#include #include #include diff --git a/src/Emulator/Core/CPU/Decoders/StdDecoder.hpp b/src/Emulator/Core/CPU/Decoders/StdDecoder.hpp index 3741a485..8d8c8deb 100644 --- a/src/Emulator/Core/CPU/Decoders/StdDecoder.hpp +++ b/src/Emulator/Core/CPU/Decoders/StdDecoder.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include diff --git a/src/Emulator/Core/CPU/IO/Simple.hpp b/src/Emulator/Core/CPU/IO/Simple.hpp index 1e5e892e..78cc4e57 100644 --- a/src/Emulator/Core/CPU/IO/Simple.hpp +++ b/src/Emulator/Core/CPU/IO/Simple.hpp @@ -1,8 +1,6 @@ #include #include -#include - namespace HyperCPU { class SimpleIOImpl { diff --git a/src/Emulator/Core/CPU/InstructionsImpl/ADC.cpp b/src/Emulator/Core/CPU/InstructionsImpl/ADC.cpp index fb8e257a..3ff6a94f 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/ADC.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/ADC.cpp @@ -1,10 +1,12 @@ +#include + #include #include -#include #include #include + using namespace HyperALU; void HyperCPU::CPU::ExecADC(const IInstruction& instr, OperandContainer op1, OperandContainer op2) { diff --git a/src/Emulator/Core/CPU/InstructionsImpl/ADD.cpp b/src/Emulator/Core/CPU/InstructionsImpl/ADD.cpp index 832db1aa..df52d676 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/ADD.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/ADD.cpp @@ -1,7 +1,8 @@ +#include + #include #include -#include #include #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/AND.cpp b/src/Emulator/Core/CPU/InstructionsImpl/AND.cpp index 99dffd26..2ed7d08b 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/AND.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/AND.cpp @@ -1,9 +1,10 @@ +#include + #include #include -#include #include -#include + using namespace HyperALU; diff --git a/src/Emulator/Core/CPU/InstructionsImpl/ANDN.cpp b/src/Emulator/Core/CPU/InstructionsImpl/ANDN.cpp index ba6ee7f7..a04dea5f 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/ANDN.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/ANDN.cpp @@ -1,9 +1,10 @@ +#include + #include #include -#include #include -#include + using namespace HyperALU; diff --git a/src/Emulator/Core/CPU/InstructionsImpl/BSWAP.cpp b/src/Emulator/Core/CPU/InstructionsImpl/BSWAP.cpp index de2e73d4..c8931954 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/BSWAP.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/BSWAP.cpp @@ -1,10 +1,7 @@ -#include +#include #include -#include -#include -#include #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/CALL.cpp b/src/Emulator/Core/CPU/InstructionsImpl/CALL.cpp index 1276cb66..7751e572 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/CALL.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/CALL.cpp @@ -1,11 +1,11 @@ +#include + #include #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/CALLE.cpp b/src/Emulator/Core/CPU/InstructionsImpl/CALLE.cpp index 3077884e..c70a1dc7 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/CALLE.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/CALLE.cpp @@ -1,11 +1,11 @@ +#include + #include #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/CALLGR.cpp b/src/Emulator/Core/CPU/InstructionsImpl/CALLGR.cpp index 7bc060e7..08e0aeac 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/CALLGR.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/CALLGR.cpp @@ -1,11 +1,11 @@ +#include + #include #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/CALLL.cpp b/src/Emulator/Core/CPU/InstructionsImpl/CALLL.cpp index f3d2cae3..49414d2e 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/CALLL.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/CALLL.cpp @@ -1,11 +1,11 @@ +#include + #include #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/CCRF.cpp b/src/Emulator/Core/CPU/InstructionsImpl/CCRF.cpp index 3b935b48..7415e6f9 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/CCRF.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/CCRF.cpp @@ -1,3 +1,5 @@ +#include + #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/CMP.cpp b/src/Emulator/Core/CPU/InstructionsImpl/CMP.cpp index b9f4f733..dccafe94 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/CMP.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/CMP.cpp @@ -1,5 +1,7 @@ -#include "Core/CPU/ALU.hpp" +#include + #include +#include #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/COVF.cpp b/src/Emulator/Core/CPU/InstructionsImpl/COVF.cpp index ffc0f62e..41e6787f 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/COVF.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/COVF.cpp @@ -1,3 +1,5 @@ +#include + #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/CUDF.cpp b/src/Emulator/Core/CPU/InstructionsImpl/CUDF.cpp index 3b7ee95b..2f4b1cc5 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/CUDF.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/CUDF.cpp @@ -1,3 +1,5 @@ +#include + #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/DEC.cpp b/src/Emulator/Core/CPU/InstructionsImpl/DEC.cpp index 1c50ab7e..4561af36 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/DEC.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/DEC.cpp @@ -1,8 +1,6 @@ -#include +#include -#include -#include -#include +#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/DIV.cpp b/src/Emulator/Core/CPU/InstructionsImpl/DIV.cpp index 9165ba8d..4b76b4ab 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/DIV.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/DIV.cpp @@ -1,10 +1,9 @@ +#include + #include #include #include -#include -#include -#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" diff --git a/src/Emulator/Core/CPU/InstructionsImpl/HALT.cpp b/src/Emulator/Core/CPU/InstructionsImpl/HALT.cpp index a445b20c..beb5ae16 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/HALT.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/HALT.cpp @@ -1,3 +1,5 @@ +#include + #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/HID.cpp b/src/Emulator/Core/CPU/InstructionsImpl/HID.cpp index 0cb55eb0..4fd64f49 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/HID.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/HID.cpp @@ -1,11 +1,8 @@ +#include + #include #include -#include -#include -#include - - #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" diff --git a/src/Emulator/Core/CPU/InstructionsImpl/INC.cpp b/src/Emulator/Core/CPU/InstructionsImpl/INC.cpp index c7aea1da..1c5c7298 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/INC.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/INC.cpp @@ -1,8 +1,6 @@ -#include +#include -#include -#include -#include +#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/INTR.cpp b/src/Emulator/Core/CPU/InstructionsImpl/INTR.cpp index 36101995..71cb764e 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/INTR.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/INTR.cpp @@ -1,9 +1,9 @@ +#include + #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/JME.cpp b/src/Emulator/Core/CPU/InstructionsImpl/JME.cpp index 8c41e6e2..fec958f7 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/JME.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/JME.cpp @@ -1,11 +1,11 @@ +#include + #include #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/JMGR.cpp b/src/Emulator/Core/CPU/InstructionsImpl/JMGR.cpp index b8d45b30..6878635c 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/JMGR.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/JMGR.cpp @@ -1,11 +1,11 @@ +#include + #include #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/JML.cpp b/src/Emulator/Core/CPU/InstructionsImpl/JML.cpp index 6ac2e875..4e7419c5 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/JML.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/JML.cpp @@ -1,11 +1,11 @@ +#include + #include #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/JMP.cpp b/src/Emulator/Core/CPU/InstructionsImpl/JMP.cpp index 44ce86ae..d70137bd 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/JMP.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/JMP.cpp @@ -1,11 +1,11 @@ +#include + #include #include #include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/LOIVT.cpp b/src/Emulator/Core/CPU/InstructionsImpl/LOIVT.cpp index 5f34e3c4..4e5a35c0 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/LOIVT.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/LOIVT.cpp @@ -1,9 +1,9 @@ -#include "Exit.hpp" +#include + #include +#include -#include #include -#include #pragma GCC diagnostic push diff --git a/src/Emulator/Core/CPU/InstructionsImpl/MOV.cpp b/src/Emulator/Core/CPU/InstructionsImpl/MOV.cpp index 393723ab..6a094886 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/MOV.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/MOV.cpp @@ -1,3 +1,5 @@ +#include + #include #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/MUL.cpp b/src/Emulator/Core/CPU/InstructionsImpl/MUL.cpp index 6ca04a7c..ac710823 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/MUL.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/MUL.cpp @@ -1,12 +1,14 @@ +#include + #include #include #include -#include #include #include + using namespace HyperALU; void HyperCPU::CPU::ExecMUL(const IInstruction& instr, OperandContainer op1, OperandContainer op2) { diff --git a/src/Emulator/Core/CPU/InstructionsImpl/OR.cpp b/src/Emulator/Core/CPU/InstructionsImpl/OR.cpp index bfd92f8e..460d87f3 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/OR.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/OR.cpp @@ -1,9 +1,10 @@ +#include + #include #include -#include #include -#include + using namespace HyperALU; diff --git a/src/Emulator/Core/CPU/InstructionsImpl/POP.cpp b/src/Emulator/Core/CPU/InstructionsImpl/POP.cpp index afca8c11..ae74fb70 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/POP.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/POP.cpp @@ -1,7 +1,7 @@ +#include + #include -#include #include -#include #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/PUSH.cpp b/src/Emulator/Core/CPU/InstructionsImpl/PUSH.cpp index 29b8f5db..b2fcd438 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/PUSH.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/PUSH.cpp @@ -1,7 +1,6 @@ -#include +#include #include -#include #include #include #include diff --git a/src/Emulator/Core/CPU/InstructionsImpl/READ.cpp b/src/Emulator/Core/CPU/InstructionsImpl/READ.cpp index 04a478a2..7de544ac 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/READ.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/READ.cpp @@ -1,9 +1,11 @@ +#include + #include -#include #include #include #include + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" diff --git a/src/Emulator/Core/CPU/InstructionsImpl/SHFL.cpp b/src/Emulator/Core/CPU/InstructionsImpl/SHFL.cpp index 90df5fe3..6969e596 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/SHFL.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/SHFL.cpp @@ -1,8 +1,8 @@ +#include + #include -#include #include -#include void HyperCPU::CPU::ExecSHFL(const IInstruction& instr, OperandContainer op1, OperandContainer op2) { diff --git a/src/Emulator/Core/CPU/InstructionsImpl/SHFR.cpp b/src/Emulator/Core/CPU/InstructionsImpl/SHFR.cpp index b51c0f4d..c97183ac 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/SHFR.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/SHFR.cpp @@ -1,9 +1,9 @@ -#include "Core/CPU/Instructions/Flags.hpp" +#include + +#include #include -#include #include -#include void HyperCPU::CPU::ExecSHFR(const IInstruction& instr, OperandContainer op1, OperandContainer op2) { diff --git a/src/Emulator/Core/CPU/InstructionsImpl/SUB.cpp b/src/Emulator/Core/CPU/InstructionsImpl/SUB.cpp index 79841f99..f3a8f8e1 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/SUB.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/SUB.cpp @@ -1,10 +1,12 @@ +#include + #include #include -#include #include #include + using namespace HyperALU; void HyperCPU::CPU::ExecSUB(const IInstruction& instr, OperandContainer op1, OperandContainer op2) { diff --git a/src/Emulator/Core/CPU/InstructionsImpl/WRITE.cpp b/src/Emulator/Core/CPU/InstructionsImpl/WRITE.cpp index de573bb6..ae38520e 100644 --- a/src/Emulator/Core/CPU/InstructionsImpl/WRITE.cpp +++ b/src/Emulator/Core/CPU/InstructionsImpl/WRITE.cpp @@ -1,11 +1,11 @@ -#include +#include #include -#include #include #include #include + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" diff --git a/src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp b/src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp index 6df98bd9..da072fb3 100644 --- a/src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp +++ b/src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp @@ -1,6 +1,7 @@ +#include + #include #include -#include #include #include #include diff --git a/src/Emulator/Core/CPU/Interrupts/ReservedInterrupts.hpp b/src/Emulator/Core/CPU/Interrupts/ReservedInterrupts.hpp index 8e6d1ae8..e1b48bfb 100644 --- a/src/Emulator/Core/CPU/Interrupts/ReservedInterrupts.hpp +++ b/src/Emulator/Core/CPU/Interrupts/ReservedInterrupts.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace HyperCPU { diff --git a/src/Emulator/Core/CPU/OperandsEvaluation.cpp b/src/Emulator/Core/CPU/OperandsEvaluation.cpp index b775f9b0..03e6e8af 100644 --- a/src/Emulator/Core/CPU/OperandsEvaluation.cpp +++ b/src/Emulator/Core/CPU/OperandsEvaluation.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include diff --git a/src/Emulator/Core/CPU/Stack.cpp b/src/Emulator/Core/CPU/Stack.cpp index 66908ddc..739a6932 100644 --- a/src/Emulator/Core/CPU/Stack.cpp +++ b/src/Emulator/Core/CPU/Stack.cpp @@ -1,3 +1,5 @@ +#include + #include #include diff --git a/src/Emulator/Core/MemoryController/IMemoryController.hpp b/src/Emulator/Core/MemoryController/IMemoryController.hpp index b8a7d6ab..22b36a34 100644 --- a/src/Emulator/Core/MemoryController/IMemoryController.hpp +++ b/src/Emulator/Core/MemoryController/IMemoryController.hpp @@ -2,7 +2,6 @@ #include -#include #define mem_ctlr_assert(expr) \ do { \ diff --git a/src/Emulator/Core/MemoryController/MemoryControllerST.hpp b/src/Emulator/Core/MemoryController/MemoryControllerST.hpp index 51577c28..5beeb57c 100644 --- a/src/Emulator/Core/MemoryController/MemoryControllerST.hpp +++ b/src/Emulator/Core/MemoryController/MemoryControllerST.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include diff --git a/src/Emulator/Main/ExceptionHandling.cpp b/src/Emulator/Main/ExceptionHandling.cpp index e57bc395..83308482 100644 --- a/src/Emulator/Main/ExceptionHandling.cpp +++ b/src/Emulator/Main/ExceptionHandling.cpp @@ -1,8 +1,11 @@ -#include "Logger/Logger.hpp" +#include + #include
+#include -#include #include +#include + [[noreturn]] void HyperCPU::ThrowError(CPU* cpu, std::string message) { cpu->GetLogger().Log(HyperCPU::LogLevel::ERROR, message); diff --git a/src/Emulator/Main/Main.cpp b/src/Emulator/Main/Main.cpp index 7eb6036f..7271cf04 100644 --- a/src/Emulator/Main/Main.cpp +++ b/src/Emulator/Main/Main.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include #include #include diff --git a/src/Logger/Logger.hpp b/src/Logger/Logger.hpp index 3600f664..6a5d0cac 100644 --- a/src/Logger/Logger.hpp +++ b/src/Logger/Logger.hpp @@ -2,71 +2,77 @@ #include -#include #include #include #include + namespace HyperCPU { - enum class LogLevel : std::uint_fast8_t { - DEBUG = 0, - INFO = 1, - WARNING = 2, - ERROR = 3 - }; + enum class LogLevel : std::uint_fast8_t { DEBUG = 0, INFO = 1, WARNING = 2, ERROR = 3 }; + + class Logger { + private: + LogLevel _loglevel; - class Logger { - private: - LogLevel _loglevel; + constexpr char DefineMsgChar(HyperCPU::LogLevel lvl) const noexcept { + switch (lvl) { + case HyperCPU::LogLevel::DEBUG: + return '-'; + case HyperCPU::LogLevel::INFO: + return '*'; + case HyperCPU::LogLevel::WARNING: + return '='; + case HyperCPU::LogLevel::ERROR: + return '!'; + default: + UNREACHABLE(); + } + } - constexpr char DefineMsgChar(HyperCPU::LogLevel lvl) const noexcept { - switch (lvl) { - case HyperCPU::LogLevel::DEBUG: return '-'; - case HyperCPU::LogLevel::INFO: return '*'; - case HyperCPU::LogLevel::WARNING: return '='; - case HyperCPU::LogLevel::ERROR: return '!'; - default: - UNREACHABLE(); - } - } + constexpr const char* DefineBoldColor(HyperCPU::LogLevel lvl) const noexcept { + switch (lvl) { + case HyperCPU::LogLevel::DEBUG: + case HyperCPU::LogLevel::INFO: + return RESET; + case HyperCPU::LogLevel::WARNING: + return B_YELLOW; + case HyperCPU::LogLevel::ERROR: + return B_RED; + default: + UNREACHABLE(); + } + } - constexpr const char* DefineBoldColor(HyperCPU::LogLevel lvl) const noexcept { - switch (lvl) { - case HyperCPU::LogLevel::DEBUG: - case HyperCPU::LogLevel::INFO: return RESET; - case HyperCPU::LogLevel::WARNING: return B_YELLOW; - case HyperCPU::LogLevel::ERROR: return B_RED; - default: - UNREACHABLE(); - } - } + constexpr const char* DefineColor(HyperCPU::LogLevel lvl) const noexcept { + switch (lvl) { + case HyperCPU::LogLevel::DEBUG: + case HyperCPU::LogLevel::INFO: + return RESET; + case HyperCPU::LogLevel::WARNING: + return YELLOW; + case HyperCPU::LogLevel::ERROR: + return RED; + default: + UNREACHABLE(); + } + } - constexpr const char* DefineColor(HyperCPU::LogLevel lvl) const noexcept { - switch (lvl) { - case HyperCPU::LogLevel::DEBUG: - case HyperCPU::LogLevel::INFO: return RESET; - case HyperCPU::LogLevel::WARNING: return YELLOW; - case HyperCPU::LogLevel::ERROR: return RED; - default: - UNREACHABLE(); - } - } + public: + Logger(LogLevel default_loglevel) : _loglevel(default_loglevel) { + } - public: - Logger(LogLevel default_loglevel) : _loglevel(default_loglevel) { } + template + void Log(LogLevel lvl, std::string_view fmt, Args&&... args) const noexcept { + if (static_cast(lvl) < static_cast(_loglevel)) { + return; + } - template - void Log(LogLevel lvl, std::string_view fmt, Args&&... args) const noexcept { - if (static_cast(lvl) < static_cast(_loglevel)) { - return; - } - - auto ch = DefineMsgChar(lvl); - auto bold_col = DefineBoldColor(lvl); - auto col = DefineColor(lvl); + auto ch = DefineMsgChar(lvl); + auto bold_col = DefineBoldColor(lvl); + auto col = DefineColor(lvl); - print("{}[{}]{} {}", bold_col, ch, RESET, col); - print("{}{}\n", fmt::vformat(fmt, fmt::make_format_args(std::forward(args)...)), RESET); - } - }; -} + print("{}[{}]{} {}", bold_col, ch, RESET, col); + print("{}{}\n", fmt::vformat(fmt, fmt::make_format_args(std::forward(args)...)), RESET); + } + }; +} // namespace HyperCPU diff --git a/src/Pog/Action.hpp b/src/Pog/Action.hpp new file mode 100644 index 00000000..26ba034a --- /dev/null +++ b/src/Pog/Action.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include +#include + +namespace pog { + +template +struct Shift +{ + const State* state; +}; + +template +struct Reduce +{ + const Rule* rule; +}; + +struct Accept {}; + +template +using Action = std::variant, Reduce, Accept>; + +} // namespace pog diff --git a/src/Pog/Automaton.hpp b/src/Pog/Automaton.hpp new file mode 100644 index 00000000..731129c5 --- /dev/null +++ b/src/Pog/Automaton.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include +#include + +namespace pog { + +template +class Automaton +{ +public: + using GrammarType = Grammar; + using ItemType = Item; + using StateType = State; + using SymbolType = Symbol; + + using StateAndSymbolType = StateAndSymbol; + + Automaton(const GrammarType* grammar) : _grammar(grammar), _states(), _state_to_index() {} + + const std::vector>& get_states() const { return _states; } + + const StateType* get_state(std::size_t index) const + { + assert(index < _states.size() && "Accessing state index out of bounds"); + return _states[index].get(); + } + + template + std::pair add_state(StateT&& state) + { + auto itr = _state_to_index.find(&state); + if (itr != _state_to_index.end()) + return {_states[itr->second].get(), false}; + + _states.push_back(std::make_unique(std::forward(state))); + _state_to_index.emplace(_states.back().get(), _states.size() - 1); + return {_states.back().get(), true}; + } + + void closure(StateType& state) + { + std::deque to_process; + for (const auto& item : state) + to_process.push_back(item.get()); + + while (!to_process.empty()) + { + const auto* current_item = to_process.front(); + to_process.pop_front(); + + const auto* next_symbol = current_item->get_read_symbol(); + auto rules = _grammar->get_rules_of_symbol(next_symbol); + for (const auto* rule : rules) + { + auto new_item = Item{rule}; + auto result = state.add_item(std::move(new_item)); + if (result.second) + to_process.push_back(result.first); + } + } + } + + void construct_states() + { + StateType initial_state; + initial_state.add_item(ItemType{_grammar->get_start_rule()}); + initial_state.set_index(0); + closure(initial_state); + auto result = add_state(std::move(initial_state)); + + std::deque to_process{result.first}; + while (!to_process.empty()) + { + auto* state = to_process.front(); + to_process.pop_front(); + + std::map> prepared_states; + for (const auto& item : *state) + { + if (item->is_final()) + continue; + + auto next_sym = item->get_read_symbol(); + if (next_sym->is_end()) + continue; + + auto new_item = Item{*item}; + new_item.step(); + + auto itr = prepared_states.find(next_sym); + if (itr == prepared_states.end()) + std::tie(itr, std::ignore) = prepared_states.emplace(next_sym, StateType{}); + itr->second.add_item(std::move(new_item)); + } + + for (auto&& [symbol, prepared_state] : prepared_states) + { + prepared_state.set_index(static_cast(_states.size())); + auto result = add_state(std::move(prepared_state)); + auto* target_state = result.first; + if (result.second) + { + // We calculate closure only if it's new state introduced in the automaton. + // States can be compared only with their kernel items so it's better to just do it + // once for each state. + closure(*target_state); + to_process.push_back(target_state); + } + state->add_transition(symbol, target_state); + target_state->add_back_transition(symbol, state); + } + } + } + + std::string generate_graph() const + { + std::vector states_str(_states.size()); + std::transform(_states.begin(), _states.end(), states_str.begin(), [](const auto& state) { + std::vector items_str(state->size()); + std::transform(state->begin(), state->end(), items_str.begin(), [](const auto& item) { + return item->to_string("→", "ε", "•"); + }); + return fmt::format("{} [label=\"{}\\l\", xlabel=\"{}\"]", state->get_index(), fmt::join(items_str.begin(), items_str.end(), "\\l"), state->get_index()); + }); + std::vector edges_str; + for (const auto& state : _states) + { + for (const auto& [sym, dest] : state->get_transitions()) + { + edges_str.push_back(fmt::format("{} -> {} [label=\"{}\"]", state->get_index(), dest->get_index(), sym->get_name())); + } + } + return fmt::format(R"(digraph Automaton {{ +node [shape=rect]; + +{} + +{} +}})", + fmt::join(states_str.begin(), states_str.end(), "\n"), + fmt::join(edges_str.begin(), edges_str.end(), "\n") + ); + } + +private: + const GrammarType* _grammar; + std::vector> _states; + std::unordered_map, StateKernelEquals> _state_to_index; +}; + +} // namespace pog diff --git a/src/Pog/DigraphAlgo.hpp b/src/Pog/DigraphAlgo.hpp new file mode 100644 index 00000000..085ded10 --- /dev/null +++ b/src/Pog/DigraphAlgo.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +namespace pog { + +namespace detail { + +template +void digraph_traverse(const NodeT& x, std::deque& stack, std::unordered_map& depths, const R& rel, BaseF& base_f, F& f) +{ + stack.push_back(x); // push x + std::size_t current_depth = stack.size(); // d <- depth of stack + depths.insert_or_assign(x, current_depth); // N[x] <- d + f.insert_or_assign(x, base_f[x]); // F(x) <- F'(x) + + auto rel_with = rel.find(x); + if (rel_with) + { + for (const auto& y : *rel_with) // for each y such that xRy + { + auto include_itr = depths.find(y); + if (include_itr == depths.end()) // if N[y] == 0 + digraph_traverse(y, stack, depths, rel, base_f, f); // recursive call Traverse(y) + + include_itr = depths.find(y); // possible iterator invalidation + include_itr->second = std::min(depths[x], include_itr->second); // N[y] <- min(N[x], N[y]) + auto& fx = f[x]; + auto& fy = f[y]; + std::copy(fy.begin(), fy.end(), std::inserter(fx, fx.begin())); // F(x) <- F(x) union F(y) + } + } + + if (depths[x] == current_depth) // if N[x] == d + { + auto top_x = std::move(stack.back()); + stack.pop_back(); + depths[top_x] = std::numeric_limits::max(); // N(top of stack) <- Infinity + if (top_x != x) + f[top_x] = f[x]; // F(top of stack) <- F(x) + + while (top_x != x) // while top of stack != x + { + top_x = std::move(stack.back()); + stack.pop_back(); + depths[top_x] = std::numeric_limits::max(); // N(top of stack) <- Infinity + if (top_x != x) + f[top_x] = f[x]; // F(top of stack) <- F(x) + } + } +} + +} // namespace detail + +/** + * Digraph algorithm for finding SCCs (Strongly Connected Components). It is used for + * computation of function F(x) using base function F'(x) over directed graph. It first + * computes F'(x) as F(x) for each node x and then perform unions of F(x) over edges + * of directed graph. Finding SCC is a crucial part to not get into infinite loops and properly + * propagate F(x) in looped relations. + * + * You can specify custom relation R which specifies edges of the directed graph, base function + * F'(x) which needs to be already precomunted. The output is operation F(x) which will + * be computed along the way. + * + * TODO: base_f should not be non-const but we require operator[] right now + */ +template +void digraph_algo(const R& rel, BaseF& base_f, F& f) +{ + std::unordered_map depths; + std::deque stack; + for (const auto& x : rel) + { + detail::digraph_traverse(x.first, stack, depths, rel, base_f, f); + } +} + +} // namespace pog diff --git a/src/Pog/Errors.hpp b/src/Pog/Errors.hpp new file mode 100644 index 00000000..5b517005 --- /dev/null +++ b/src/Pog/Errors.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include + +namespace pog { + +class Error : public std::exception +{ +public: + Error() : _msg() {} + template + Error(T&& msg) noexcept : _msg(std::forward(msg)) {} + Error(const Error& o) noexcept : _msg(o._msg) {} + virtual ~Error() noexcept {} + + virtual const char* what() const noexcept override { return _msg.c_str(); } + +protected: + std::string _msg; +}; + +class SyntaxError : public Error +{ +public: + template + SyntaxError(const Symbol* unexpected_symbol, const std::vector*>& expected_symbols) : Error() + { + std::vector expected_symbols_str(expected_symbols.size()); + std::transform(expected_symbols.begin(), expected_symbols.end(), expected_symbols_str.begin(), [](const auto& sym) { + return sym->get_description(); + }); + + _msg = fmt::format( + "Syntax error: Unexpected {}, expected one of {}", + unexpected_symbol->get_description(), + fmt::join(expected_symbols_str.begin(), expected_symbols_str.end(), ", ") + ); + } + + template + SyntaxError(const std::vector*>& expected_symbols) : Error() + { + std::vector expected_symbols_str(expected_symbols.size()); + std::transform(expected_symbols.begin(), expected_symbols.end(), expected_symbols_str.begin(), [](const auto& sym) { + return sym->get_description(); + }); + + _msg = fmt::format( + "Syntax error: Unknown symbol on input, expected one of {}", + fmt::join(expected_symbols_str.begin(), expected_symbols_str.end(), ", ") + ); + } +}; + +} // namespace pog diff --git a/src/Pog/FilterView.hpp b/src/Pog/FilterView.hpp new file mode 100644 index 00000000..d5b53d68 --- /dev/null +++ b/src/Pog/FilterView.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include + +#include + +template +class FilterView +{ +public: + using ValueType = typename std::iterator_traits::value_type; + + class iterator + { + public: + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = std::forward_iterator_tag; + + iterator(const FilterView* parent) : _parent(parent), _itr(_parent->_begin) { _find_next(); } + iterator(const FilterView* parent, const It& itr) : _parent(parent), _itr(itr) {} + iterator(const iterator&) = default; + iterator(iterator&&) noexcept = default; + + iterator& operator=(const iterator&) = default; + iterator& operator=(iterator&&) noexcept = default; + + reference operator*() const { return *_itr; } + pointer operator->() const { return &*_itr; } + + iterator& operator++() + { + ++_itr; + _find_next(); + return *this; + } + + iterator operator++(int) + { + auto tmp = *this; + ++_itr; + _find_next(); + return tmp; + } + + bool operator==(const iterator& rhs) const { return _itr == rhs._itr; } + bool operator!=(const iterator& rhs) const { return !(*this == rhs); } + + private: + void _find_next() + { + while (_itr != _parent->_end && !_parent->_filter(*_itr)) + ++_itr; + } + + const FilterView* _parent; + It _itr; + }; + + template + FilterView(I&& begin, I&& end, F&& filter) + : _begin(std::forward(begin)), _end(std::forward(end)), _filter(std::forward(filter)) {} + + FilterView(const FilterView&) = default; + FilterView(FilterView&&) noexcept = default; + + FilterView& operator=(const FilterView&) = default; + FilterView& operator=(FilterView&&) noexcept = default; + + auto begin() const { return iterator{this}; } + auto end() const { return iterator{this, _end}; } + +private: + It _begin, _end; + std::function _filter; +}; + +template +FilterView(It&&, It&&, Filter&&) -> FilterView; diff --git a/src/Pog/Grammar.hpp b/src/Pog/Grammar.hpp new file mode 100644 index 00000000..6cdc4a54 --- /dev/null +++ b/src/Pog/Grammar.hpp @@ -0,0 +1,288 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace pog { + +template +class Grammar +{ +public: + using RuleType = Rule; + using SymbolType = Symbol; + using TokenType = Token; + + Grammar() : _rules(), _symbols(), _name_to_symbol(), _internal_start_symbol(nullptr), _internal_end_of_input(nullptr), + _start_rule(nullptr), _empty_table(), _first_table(), _follow_table() + { + _internal_start_symbol = add_symbol(SymbolKind::Nonterminal, "@start"); + _internal_end_of_input = add_symbol(SymbolKind::End, "@end"); + } + + const std::vector>& get_symbols() const { return _symbols; } + const std::vector>& get_rules() const { return _rules; } + + const SymbolType* get_end_of_input_symbol() const { return _internal_end_of_input; } + const RuleType* get_start_rule() const { return _start_rule; } + + std::vector get_terminal_symbols() const + { + std::vector result; + transform_if(_symbols.begin(), _symbols.end(), std::back_inserter(result), + [](const auto& s) { return s->is_terminal() || s->is_end(); }, + [](const auto& s) { return s.get(); } + ); + return result; + } + + std::vector get_nonterminal_symbols() const + { + std::vector result; + transform_if(_symbols.begin(), _symbols.end(), std::back_inserter(result), + [](const auto& s) { return s->is_nonterminal(); }, + [](const auto& s) { return s.get(); } + ); + return result; + } + + SymbolType* get_symbol(const std::string& name) const + { + auto itr = _name_to_symbol.find(name); + if (itr == _name_to_symbol.end()) + return nullptr; + + return itr->second; + } + + // TODO: return filter_view<> + std::vector get_rules_of_symbol(const SymbolType* sym) const + { + std::vector result; + transform_if(_rules.begin(), _rules.end(), std::back_inserter(result), [&](const auto& rule) { + return sym == rule->get_lhs(); + }, [](const auto& rule) { + return rule.get(); + }); + return result; + } + + // TODO: return filter_view<> + std::vector get_rules_with_symbol(const SymbolType* sym) const + { + std::vector result; + transform_if(_rules.begin(), _rules.end(), std::back_inserter(result), [&](const auto& rule) { + return std::find(rule->get_rhs().begin(), rule->get_rhs().end(), sym) != rule->get_rhs().end(); + }, [](const auto& rule) { + return rule.get(); + }); + return result; + } + + void set_start_symbol(const SymbolType* symbol) + { + auto start_rule = add_rule(_internal_start_symbol, std::vector{symbol, _internal_end_of_input}, [](Parser&, std::vector>&& args) { + return std::move(args[0].value); + }); + start_rule->set_start_rule(true); + _start_rule = start_rule; + } + + SymbolType* add_symbol(SymbolKind kind, const std::string& name) + { + if (auto itr = _name_to_symbol.find(name); itr != _name_to_symbol.end()) + return itr->second; + + _symbols.push_back(std::make_unique(static_cast(_symbols.size()), kind, name)); + _name_to_symbol.emplace(_symbols.back()->get_name(), _symbols.back().get()); + return _symbols.back().get(); + } + + template + RuleType* add_rule(const SymbolType* lhs, const std::vector& rhs, CallbackT&& action) + { + _rules.push_back(std::make_unique(static_cast(_rules.size()), lhs, rhs, std::forward(action))); + return _rules.back().get(); + } + + bool empty(const SymbolType* sym) const + { + std::unordered_set visited_lhss; + auto result = empty(sym, visited_lhss); + _empty_table[sym] = result; + return result; + } + + bool empty(const std::vector& seq) const + { + std::unordered_set visited_lhss; + auto result = empty(seq, visited_lhss); + return result; + } + + std::unordered_set first(const SymbolType* sym) const + { + std::unordered_set visited_lhss; + auto result = first(sym, visited_lhss); + _first_table[sym] = result; + return result; + } + + std::unordered_set first(const std::vector& seq) const + { + std::unordered_set visited_lhss; + auto result = first(seq, visited_lhss); + return result; + } + + std::unordered_set follow(const SymbolType* sym) + { + std::unordered_set visited; + auto result = follow(sym, visited); + _follow_table[sym] = result; + return result; + } + + bool empty(const SymbolType* sym, std::unordered_set& visited_lhss) const + { + if (auto itr = _empty_table.find(sym); itr != _empty_table.end()) + return itr->second; + + // Empty of terminal or end of input marker '$' remains the same + if (sym->is_terminal() || sym->is_end()) + return false; + + // In case of nonterminal A, there exist rules A -> ... and we need to inspect them all + auto rules = get_rules_of_symbol(sym); + for (const auto* rule : rules) + { + visited_lhss.insert(rule->get_lhs()); + if (empty(rule->get_rhs(), visited_lhss)) + return true; + } + + return false; + } + + bool empty(const std::vector& seq, std::unordered_set& visited_lhss) const + { + for (const auto* sym : seq) + { + if (auto itr = visited_lhss.find(sym); itr == visited_lhss.end()) + { + if (!empty(sym, visited_lhss)) + return false; + } + else + return false; + } + + return true; + } + + std::unordered_set first(const SymbolType* sym, std::unordered_set& visited_lhss) const + { + if (auto itr = _first_table.find(sym); itr != _first_table.end()) + return itr->second; + + if (sym->is_terminal() || sym->is_end()) + return {sym}; + else if (sym->is_nonterminal()) + { + std::unordered_set result; + + if (auto itr = visited_lhss.find(sym); itr == visited_lhss.end()) + { + visited_lhss.insert(sym); + auto rules = get_rules_of_symbol(sym); + for (const auto* rule : rules) + { + auto tmp = first(rule->get_rhs(), visited_lhss); + std::copy(tmp.begin(), tmp.end(), std::inserter(result, result.begin())); + } + } + + return result; + } + + return {}; + } + + std::unordered_set first(const std::vector& seq, std::unordered_set& visited_lhss) const + { + std::unordered_set result; + + for (const auto* sym : seq) + { + auto tmp = first(sym, visited_lhss); + std::copy(tmp.begin(), tmp.end(), std::inserter(result, result.begin())); + if (!empty(sym)) + break; + } + + return result; + } + + std::unordered_set follow(const SymbolType* sym, std::unordered_set& visited) const + { + if (auto itr = _follow_table.find(sym); itr != _follow_table.end()) + return itr->second; + + std::unordered_set result; + if (visited.find(sym) != visited.end()) + return result; + + visited.insert(sym); + auto rules = get_rules_with_symbol(sym); + for (const auto* rule : rules) + { + for (auto itr = rule->get_rhs().begin(), end = rule->get_rhs().end(); itr != end; ++itr) + { + if (*itr == sym) + { + bool can_be_last_in_production = true; + // If we have a production A -> a B b and we are doing Follow(B), we need to inspect everything + // right of B (so in this case 'b') whether there exist production b =>* eps. + // If so, B can be last in production and and therefore we need to add Follow(A) to Follow(B) + if (itr + 1 != end) + { + std::vector tail(itr + 1, end); + auto tmp = first(tail); + std::copy(tmp.begin(), tmp.end(), std::inserter(result, result.begin())); + can_be_last_in_production = empty(tail); + } + + // There exists production b =>* eps so add Follow(A) to Follow(B) + if (can_be_last_in_production) + { + auto tmp = follow(rule->get_lhs(), visited); + std::copy(tmp.begin(), tmp.end(), std::inserter(result, result.begin())); + } + } + } + } + + return result; + } + +private: + std::vector> _rules; + std::vector> _symbols; + std::unordered_map _name_to_symbol; + const SymbolType* _internal_start_symbol; + const SymbolType* _internal_end_of_input; + const RuleType* _start_rule; + + mutable std::unordered_map _empty_table; + mutable std::unordered_map> _first_table; + mutable std::unordered_map> _follow_table; +}; + +} // namespace pog diff --git a/src/Pog/HTMLReport.hpp b/src/Pog/HTMLReport.hpp new file mode 100644 index 00000000..fc2edd40 --- /dev/null +++ b/src/Pog/HTMLReport.hpp @@ -0,0 +1,284 @@ +#pragma once + +#include + +#include + +namespace pog { + +template +class HtmlReport +{ +public: + using ParserType = Parser; + + using ShiftActionType = Shift; + using ReduceActionType = Reduce; + + HtmlReport(const ParserType& parser) : _parser(parser) {} + + void save(const std::string& file_path) + { + static const std::string html_page_template = R"( + + + + + + + + + + +
+ {issues} + {parsing_table} + {states} + {automaton} +
+ Generated: {generated_at} +
+ + + + + + + +)"; + + using namespace fmt; + + std::ofstream file(file_path, std::ios::out | std::ios::trunc); + if (file.is_open()) + file << fmt::format(html_page_template, + "issues"_a = build_issues(), + "parsing_table"_a = build_parsing_table(), + "states"_a = build_states(), + "automaton"_a = generate_automaton_graph(), + "generated_at"_a = current_time("%Y-%m-%d %H:%M:%S %Z") + ); + file.close(); + } + +private: + std::string build_issues() + { + using namespace fmt; + + if (_parser._report) + return std::string{}; + + std::vector issues(_parser._report.number_of_issues()); + std::transform(_parser._report.begin(), _parser._report.end(), issues.begin(), [](const auto& issue) { + return fmt::format("
  • {}
  • ", visit_with(issue, + [&](const ShiftReduceConflict& sr) { return sr.to_string("→", "ε"); }, + [&](const ReduceReduceConflict& rr) { return rr.to_string("→", "ε"); } + )); + }); + + return fmt::format( + R"(
    +
    +

    Issues

    +
      + {issues} +
    +
    +
    )", + "issues"_a = fmt::join(issues.begin(), issues.end(), "") + ); + } + + std::string build_parsing_table() + { + using namespace fmt; + + auto terminal_symbols = _parser._grammar.get_terminal_symbols(); + auto nonterminal_symbols = _parser._grammar.get_nonterminal_symbols(); + + std::vector symbol_headers(terminal_symbols.size() + nonterminal_symbols.size()); + std::transform(terminal_symbols.begin(), terminal_symbols.end(), symbol_headers.begin(), [](const auto& s) { + return fmt::format("{}", s->get_name()); + }); + std::transform(nonterminal_symbols.begin(), nonterminal_symbols.end(), symbol_headers.begin() + terminal_symbols.size(), [](const auto& s) { + return fmt::format("{}", s->get_name()); + }); + + std::vector rows(_parser._automaton.get_states().size()); + for (const auto& state : _parser._automaton.get_states()) + { + std::vector row; + row.push_back(fmt::format( + "{}", + state->to_string("→", "ε", "•", "
    "), + state->get_index() + )); + for (const auto& sym : terminal_symbols) + { + auto action = _parser._parsing_table.get_action(state.get(), sym); + if (!action) + { + row.push_back(""); + continue; + } + + row.push_back(visit_with(action.value(), + [](const ShiftActionType& shift) { + return fmt::format( + "s{state_id}", + "state_str"_a = shift.state->to_string("→", "ε", "•", "
    "), + "state_id"_a = shift.state->get_index() + ); + }, + [](const ReduceActionType& reduce) { + return fmt::format( + "r{}", + reduce.rule->to_string("→", "ε"), + reduce.rule->get_index() + ); + }, + [](const Accept&) -> std::string { return ""; } + )); + } + + for (const auto& sym : nonterminal_symbols) + { + auto go_to = _parser._parsing_table.get_transition(state.get(), sym); + if (!go_to) + { + row.push_back(""); + continue; + } + + row.push_back(fmt::format( + "{state_id}", + "state_str"_a = go_to.value()->to_string("→", "ε", "•", "
    "), + "state_id"_a = go_to.value()->get_index() + )); + } + + row.push_back(""); + rows.push_back(fmt::format("{}", fmt::join(row.begin(), row.end(), ""))); + } + + return fmt::format( + R"(
    +

    Parsing Table

    +
    +
    + + + + + + + + + {symbols} + + + + {rows} + +
    StateActionGoto
    +
    +
    )", + "number_of_terminals"_a = terminal_symbols.size(), + "number_of_nonterminals"_a = nonterminal_symbols.size(), + "symbols"_a = fmt::join(symbol_headers.begin(), symbol_headers.end(), ""), + "rows"_a = fmt::join(rows.begin(), rows.end(), "") + ); + } + + std::string build_states() + { + using namespace fmt; + + static const std::string single_state_template = + R"(
    + + + + + + {rows} + +
    State {id}
    +
    )"; + + std::vector states; + for (const auto& state : _parser._automaton.get_states()) + { + std::vector cols(state->size()); + std::transform(state->begin(), state->end(), cols.begin(), [](const auto& item) { + return fmt::format("{}", item->to_string("→", "ε", "•")); + }); + states.push_back(fmt::format( + single_state_template, + "id"_a = state->get_index(), + "rows"_a = fmt::join(cols.begin(), cols.end(), "") + )); + } + + return fmt::format( + R"(
    +

    States

    +
    + {states} +
    )", + "states"_a = fmt::join(states.begin(), states.end(), "") + ); + } + + std::string generate_automaton_graph() + { + using namespace fmt; + + return fmt::format(R"(
    +

    Automaton (graphviz)

    +
    + +
    +
    + +
    +
    )", + "automaton"_a = _parser._automaton.generate_graph() + ); + } + + const ParserType& _parser; +}; + +template +HtmlReport(const Parser&) -> HtmlReport; + +} // namespace pog diff --git a/src/Pog/Item.hpp b/src/Pog/Item.hpp new file mode 100644 index 00000000..3d860010 --- /dev/null +++ b/src/Pog/Item.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +namespace pog { + +template +class Item +{ +public: + using RuleType = Rule; + using SymbolType = Symbol; + + Item(const RuleType* rule, std::size_t read_pos = 0) + : _rule(rule), _read_pos(read_pos) {} + Item(const Item&) = default; + Item(Item&&) noexcept = default; + + const RuleType* get_rule() const { return _rule; } + std::size_t get_read_pos() const { return _read_pos; } + + const SymbolType* get_previous_symbol() const + { + return _read_pos == 0 ? nullptr : _rule->get_rhs()[_read_pos - 1]; + } + + const SymbolType* get_read_symbol() const + { + return is_final() ? nullptr : _rule->get_rhs()[_read_pos]; + } + + std::vector get_left_side_without_read_symbol() + { + if (_read_pos == 0) + return {}; + + // TODO: return just iterator range + std::vector result(_read_pos); + std::copy(_rule->get_rhs().begin(), _rule->get_rhs().begin() + _read_pos, result.begin()); + return result; + } + + std::vector get_right_side_without_read_symbol() + { + if (is_final()) + { + assert(false && "Shouldn't call get_right_side_without_read_symbol() on final item"); + return {}; + } + + auto rest_size = _rule->get_rhs().size() - _read_pos - 1; + if (rest_size == 0) + return {}; + + // TODO: possibly just return iterator range? + std::vector result(rest_size); + std::copy(_rule->get_rhs().begin() + _read_pos + 1, _rule->get_rhs().end(), result.begin()); + return result; + } + + void step() + { + if (!is_final()) + _read_pos++; + } + + void step_back() + { + if (_read_pos > 0) + _read_pos--; + } + + bool is_kernel() const + { + return _read_pos > 0 || _rule->is_start_rule(); + } + + bool is_final() const + { + return _read_pos == _rule->get_rhs().size(); + } + + bool is_accepting() const + { + return !is_final() && get_read_symbol()->is_end(); + } + + std::string to_string(std::string_view arrow = "->", std::string_view eps = "", std::string_view sep = "<*>") const + { + const auto& rhs = _rule->get_rhs(); + std::vector left_of_read_pos(_read_pos); + std::vector right_of_read_pos(rhs.size() - _read_pos); + std::transform(rhs.begin(), rhs.begin() + _read_pos, left_of_read_pos.begin(), [](const auto* sym) { + return sym->get_name(); + }); + std::transform(rhs.begin() + _read_pos, rhs.end(), right_of_read_pos.begin(), [](const auto* sym) { + return sym->get_name(); + }); + + std::vector parts; + if (!left_of_read_pos.empty()) + parts.push_back(fmt::format("{}", fmt::join(left_of_read_pos.begin(), left_of_read_pos.end(), " "))); + parts.push_back(std::string{sep}); + if (!right_of_read_pos.empty()) + parts.push_back(fmt::format("{}", fmt::join(right_of_read_pos.begin(), right_of_read_pos.end(), " "))); + + if (parts.size() == 1) + parts.push_back(std::string{eps}); + + return fmt::format("{} {} {}", _rule->get_lhs()->get_name(), arrow, fmt::join(parts.begin(), parts.end(), " ")); + } + + bool operator==(const Item& rhs) const + { + return get_rule()->get_index() == rhs.get_rule()->get_index() && get_read_pos() == rhs.get_read_pos(); + } + + bool operator!=(const Item& rhs) const + { + return !(*this == rhs); + } + + bool operator<(const Item& rhs) const + { + return std::tuple{is_kernel() ? 0 : 1, _rule->get_index(), _read_pos} < std::tuple{rhs.is_kernel() ? 0 : 1, rhs._rule->get_index(), rhs._read_pos}; + } + +private: + const RuleType* _rule; + std::size_t _read_pos; +}; + +} // namespace pog diff --git a/src/Pog/LineSpecialization.hpp b/src/Pog/LineSpecialization.hpp new file mode 100644 index 00000000..c8dcea80 --- /dev/null +++ b/src/Pog/LineSpecialization.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +namespace HCAsm { + +struct CompilerState; + +} + +namespace pog { + +struct LineSpecialization { + LineSpecialization(std::uint32_t line, std::uint16_t offset, std::uint16_t length) + : line(line) + , offset(offset) + , length(length) { } + LineSpecialization() + : line(1) + , offset(0) + , length(0) { } + + std::uint32_t line; + std::uint16_t offset; + std::uint16_t length; +}; + +template +struct TokenWithLineSpec { + TokenWithLineSpec() = default; + TokenWithLineSpec(ValueT& val, LineSpecialization spec) : value(val), line_spec(spec) { } + + ValueT value; + LineSpecialization line_spec; +}; + +} \ No newline at end of file diff --git a/src/Pog/Operations/Follow.hpp b/src/Pog/Operations/Follow.hpp new file mode 100644 index 00000000..34dd6bc2 --- /dev/null +++ b/src/Pog/Operations/Follow.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace pog { + +/** + * Follow operations maps (q,x) where q is state and x is symbol to set of symbols. + * + * Formal definition for what Follow() represents is + * Follow(q, A) = Read(q, A) union (union { Follow(p, B) | (q, A) includes (p, B) }) + * So Follow(q, A) represents Read(q, A) with union of all Follow sets of (p, B) such that + * (q, A) is in include relation with (p, B). + * + * To put it simply by individual parts: + * 1. Read(q, A) means what symbols can directly follow A in state q. See Read() operation for + * more information. + * 2. Includes relation of (q, A) with (p, B) means that when we are about to read A in state q + * and everything after A can be empty string, meaning that what can follow B in state p can + * also follow A in state q. + * + * So Follow(q, A) represents what symbols can follow A while in state q. It is constructed from + * generated follow using Read(q, A) and propagated follow using include relation. + */ +template +class Follow : public Operation, const Symbol*> +{ +public: + using Parent = Operation, const Symbol*>; + + using AutomatonType = Automaton; + using GrammarType = Grammar; + + using StateAndSymbolType = StateAndSymbol; + + Follow(const AutomatonType* automaton, const GrammarType* grammar, const Includes& includes, Read& read_op) + : Parent(automaton, grammar), _includes(includes), _read_op(read_op) {} + Follow(const Follow&) = delete; + Follow(Follow&&) noexcept = default; + + virtual void calculate() override + { + // We use digraph algorithm which calculates Follow() for us. See digraph_algo() for more information. + digraph_algo(_includes, _read_op, Parent::_operation); + } + +private: + const Includes& _includes; + Read& _read_op; +}; + +} // namespace pog diff --git a/src/Pog/Operations/Lookahead.hpp b/src/Pog/Operations/Lookahead.hpp new file mode 100644 index 00000000..4de7c0fc --- /dev/null +++ b/src/Pog/Operations/Lookahead.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include + +namespace pog { + +/** + * Lookahead operation maps (q, R), where q is state and R is rule from grammar, to set of symbols. + * + * Formal definition for Lookahead(q, A -> x) is + * Lookahead(q, A -> x) = union { Follow(p, B) | (q, A -> x) lookback (p, B) } + * So it's union of all Follow sets of state p and symbol B such that (q, A -> x) is in lookback + * relation with (p, B). + * + * To put it simply: + * 1. Follow set of (p, B) represents what symbols can follow symbol B when we are in the state B. + * 2. Lookback relation represents that in order to preform some reduction A -> x in state q, we first + * had to go through some other state p and use what follows A in B -> a A b to know when to perform + * reduction. + * So we'll take all rules A -> x and find in which state they can be reduced (there is an item A -> x <*>). + * We'll then union all Follow() sets according to lookback relation and for each state and rule, we now + * know what symbols need to follow in order to perform reductions by such rule in that particular state. + */ +template +class Lookahead : public Operation, const Symbol*> +{ +public: + using Parent = Operation, const Symbol*>; + + using AutomatonType = Automaton; + using GrammarType = Grammar; + + using StateAndRuleType = StateAndRule; + + // TODO: Follow<> should not be non-const but we need it for operator[] + Lookahead(const AutomatonType* automaton, const GrammarType* grammar, const Lookback& lookback, Follow& follow_op) + : Parent(automaton, grammar), _lookback(lookback), _follow_op(follow_op) {} + Lookahead(const Lookahead&) = delete; + Lookahead(Lookahead&&) noexcept = default; + + virtual void calculate() override + { + // Iterate over all rules in grammar + for (const auto& rule : Parent::_grammar->get_rules()) + { + for (const auto& state : Parent::_automaton->get_states()) + { + // Find lookback of the current state and rule + auto sr = StateAndRuleType{state.get(), rule.get()}; + auto lookback_with = _lookback.find(sr); + if (!lookback_with) + continue; + + // Union all Follow() sets of the current state and rule to compute Lookahead() + for (const auto& ss : *lookback_with) + { + if (auto itr = Parent::_operation.find(sr); itr == Parent::_operation.end()) + Parent::_operation.emplace(std::move(sr), _follow_op[ss]); + else if (auto follow_res = _follow_op.find(ss); follow_res) + std::copy(follow_res->begin(), follow_res->end(), std::inserter(itr->second, itr->second.begin())); + } + } + } + + } + +private: + const Lookback& _lookback; + Follow& _follow_op; +}; + +} // namespace pog diff --git a/src/Pog/Operations/Operation.hpp b/src/Pog/Operations/Operation.hpp new file mode 100644 index 00000000..c4f323ab --- /dev/null +++ b/src/Pog/Operations/Operation.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include +#include + +namespace pog { + +template +class Operation +{ +public: + using AutomatonType = Automaton; + using GrammarType = Grammar; + + Operation(const AutomatonType* automaton, const GrammarType* grammar) : _automaton(automaton), _grammar(grammar) {} + Operation(const Operation&) = delete; + Operation(Operation&&) noexcept = default; + virtual ~Operation() = default; + + virtual void calculate() = 0; + + auto& operator[](const ArgT& key) { return _operation[key]; } + auto& operator[](ArgT& key) { return _operation[key]; } + + template + std::unordered_set* find(const T& key) + { + auto itr = _operation.find(key); + if (itr == _operation.end()) + return nullptr; + + return &itr->second; + } + + template + const std::unordered_set* find(const T& key) const + { + auto itr = _operation.find(key); + if (itr == _operation.end()) + return nullptr; + + return &itr->second; + } + +protected: + const AutomatonType* _automaton; + const GrammarType* _grammar; + std::unordered_map> _operation; +}; + +} // namespace pog diff --git a/src/Pog/Operations/Read.hpp b/src/Pog/Operations/Read.hpp new file mode 100644 index 00000000..dfb8c610 --- /dev/null +++ b/src/Pog/Operations/Read.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +namespace pog { + +/** + * Read operations maps (q,x) where q is state and x is symbol into set of symbols. + * + * Let's take state Q and non-final item A -> a <*> B b. Read(Q, B) represents set + * of symbols we can possibly read after reading B while in state Q. Originally, + * in all papers you see this proposed to be calculated using DirectRead() function + * and reads relation but in the end, it's the same as if we do First(b). First(b) + * incorporates situation if some symbol in sequence b can be reduced to empty string. + * + * So to put it shortly, for state Q and item A -> a <*> B b, Read(Q, B) = First(b). + */ +template +class Read : public Operation, const Symbol*> +{ +public: + using Parent = Operation, const Symbol*>; + + using AutomatonType = Automaton; + using GrammarType = Grammar; + using SymbolType = Symbol; + + using StateAndSymbolType = StateAndSymbol; + + Read(const AutomatonType* automaton, const GrammarType* grammar) : Parent(automaton, grammar) {} + Read(const Read&) = delete; + Read(Read&&) noexcept = default; + + virtual void calculate() override + { + // Iterate over all states of LR automaton + for (const auto& state : Parent::_automaton->get_states()) + { + for (const auto& item : *state.get()) + { + // We don't care about final items, only those in form A -> a <*> B b + if (item->is_final()) + continue; + + // Symbol right to <*> needs to be nonterminal + auto next_symbol = item->get_read_symbol(); + if (!next_symbol->is_nonterminal()) + continue; + + // Observe everything right of B, so in this case 'b' and calculate First() + auto right_rest = item->get_right_side_without_read_symbol(); + auto symbols = Parent::_grammar->first(right_rest); + + // Insert operation result + auto ss = StateAndSymbolType{state.get(), next_symbol}; + auto itr = Parent::_operation.find(ss); + if (itr == Parent::_operation.end()) + Parent::_operation.emplace(std::move(ss), std::move(symbols)); + else + { + // TODO: std::vector + std::set_union or std::unordered_set::extract + std::copy(symbols.begin(), symbols.end(), std::inserter(itr->second, itr->second.begin())); + } + } + } + } +}; + +} // namespace pog diff --git a/src/Pog/Parser.hpp b/src/Pog/Parser.hpp new file mode 100644 index 00000000..2a7243e2 --- /dev/null +++ b/src/Pog/Parser.hpp @@ -0,0 +1,331 @@ +#pragma once + +#include +#include +#include + +#include +#ifdef POG_DEBUG +#define POG_DEBUG_PARSER 1 +#endif + +#ifdef POG_DEBUG_PARSER +#define debug_parser(...) fmt::print(stderr, "[parser] {}\n", fmt::format(__VA_ARGS__)) +#else +#define debug_parser(...) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace HCAsm { + +class HCAsmCompiler; + +} + +namespace pog { + +template +class HtmlReport; + +template +class Parser +{ +public: + friend class HtmlReport; + + using ActionType = Action; + using ShiftActionType = Shift; + using ReduceActionType = Reduce; + + using BacktrackingInfoType = BacktrackingInfo; + using ItemType = Item; + using ParserReportType = ParserReport; + using RuleBuilderType = RuleBuilder; + using RuleType = Rule; + using StateType = State; + using SymbolType = Symbol; + using StateAndRuleType = StateAndRule; + using StateAndSymbolType = StateAndSymbol; + using TokenBuilderType = TokenBuilder; + using TokenMatchType = TokenMatch; + using TokenType = Token; + using TokenizerType = Tokenizer; + + Parser() : _grammar(), _tokenizer(&_grammar), _automaton(&_grammar), _includes(&_automaton, &_grammar), + _lookback(&_automaton, &_grammar), _read_operation(&_automaton, &_grammar), _follow_operation(&_automaton, &_grammar, _includes, _read_operation), + _lookahead_operation(&_automaton, &_grammar, _lookback, _follow_operation), _parsing_table(&_automaton, &_grammar, _lookahead_operation) + { + static_assert(std::is_default_constructible_v, "Value type needs to be default constructible"); + } + + Parser(const Parser&) = delete; + Parser(Parser&&) noexcept = default; + + const ParserReportType& prepare() + { + for (auto& tb : _token_builders) + tb.done(); + for (auto& rb : _rule_builders) + rb.done(); + _automaton.construct_states(); + _includes.calculate(); + _lookback.calculate(); + _read_operation.calculate(); + _follow_operation.calculate(); + _lookahead_operation.calculate(); + _parsing_table.calculate(_report); + _tokenizer.prepare(); + return _report; + } + + TokenBuilderType& token(const std::string& pattern) + { + _token_builders.emplace_back(&_grammar, &_tokenizer, pattern); + return _token_builders.back(); + } + + TokenBuilderType& end_token() + { + _token_builders.emplace_back(&_grammar, &_tokenizer); + return _token_builders.back(); + } + + RuleBuilderType& rule(const std::string& lhs) + { + _rule_builders.emplace_back(&_grammar, lhs); + return _rule_builders.back(); + } + + void set_start_symbol(const std::string& name) + { + _grammar.set_start_symbol(_grammar.add_symbol(SymbolKind::Nonterminal, name)); + } + + void enter_tokenizer_state(const std::string& state_name) + { + _tokenizer.enter_state(state_name); + } + + void push_input_stream(std::string& input) + { + _tokenizer.push_input_stream(input); + } + + void pop_input_stream() + { + _tokenizer.pop_input_stream(); + } + + void global_tokenizer_action(typename TokenizerType::CallbackType&& global_action) + { + _tokenizer.global_action(std::move(global_action)); + } + + std::string& get_top_file() + { + return files.back(); + } + + void set_compiler_state(HCAsm::CompilerState* state) + { + _state = state; + } + + HCAsm::CompilerState* get_compiler_state() + { + return _state; + } + + void reset_line_offset() + { + _tokenizer._reset_line_offset(); + } + + std::uint32_t& get_line_counter() + { + return _tokenizer._get_line_counter(); + } + + std::uint16_t& get_line_offset() { + return _tokenizer._get_line_offset(); + } + + std::optional parse(std::string& contents) + { + files.push(contents); + + _tokenizer.enter_state(std::string{decltype(_tokenizer)::DefaultState}); + + std::optional token; + _tokenizer.clear_input_streams(); + _tokenizer.push_input_stream(contents); + + std::deque>>> stack; + stack.emplace_back(0, std::nullopt); + + while (!stack.empty()) + { + // Check if we remember token from the last iteration because we did reduction + // so the token was not "consumed" from the input. + if (!token) + { + token = _tokenizer.next_token(); + if (!token) [[unlikely]] + { + auto expected_symbols = _parsing_table.get_expected_symbols_from_state(_automaton.get_state(stack.back().first)); + throw SyntaxError(expected_symbols); + } + + debug_parser("Tokenizer returned new token with symbol \'{}\'", token.value().symbol->get_name()); + } + else { + debug_parser("Reusing old token with symbol \'{}\'", token.value().symbol->get_name()); + } + + debug_parser("Top of the stack is state {}", stack.back().first); + + const auto* next_symbol = token.value().symbol; + auto maybe_action = _parsing_table.get_action(_automaton.get_state(stack.back().first), next_symbol); + if (!maybe_action) + { + auto expected_symbols = _parsing_table.get_expected_symbols_from_state(_automaton.get_state(stack.back().first)); + throw SyntaxError(next_symbol, expected_symbols); + } + + // TODO: use visit + auto action = maybe_action.value(); + if (std::holds_alternative(action)) + { + const auto& reduce = std::get(action); + debug_parser("Reducing by rule \'{}\'", reduce.rule->to_string()); + + // Each symbol on right-hand side of the rule should have record on the stack + // We'll pop them out and put them in reverse order so user have them available + // left-to-right and not right-to-left. + std::vector> action_arg; + action_arg.reserve(reduce.rule->get_number_of_required_arguments_for_action()); + assert(stack.size() >= action_arg.capacity() && "Stack is too small"); + + for (std::size_t i = 0; i < action_arg.capacity(); ++i) + { + // Notice how std::move() is only around optional itself and not the whole expressions + // We need to do this in order to perform move together with value_or() + // See: https://en.cppreference.com/w/cpp/utility/optional/value_or + // std::move(*this) is performed only when value_or() is called from r-value + // + // Also do not pop from stack here because midrule actions can still return us arguments back + action_arg.insert(action_arg.begin(), std::move(stack[stack.size() - i - 1].second).value_or(TokenWithLineSpec{})); + } + + // What left on the stack now determines what state we get into now + // We use size of RHS to determine stack top because midrule actions might have only borrowed something from stack so the + // real stack top is not the actual top. Midrule actions have 0 RHS size even though they borrow items. Other rules + // have same size of RHS and what they take out of stack. + auto maybe_next_state = _parsing_table.get_transition(_automaton.get_state(stack[stack.size() - reduce.rule->get_rhs().size() - 1].first), reduce.rule->get_lhs()); + if (!maybe_next_state) + { + assert(false && "Reduction happened but corresponding GOTO table record is empty"); + return std::nullopt; + } + + auto action_result = reduce.rule->has_action() ? reduce.rule->perform_action(*this, std::move(action_arg)) : ValueT{}; + // Midrule actions only borrowed arguments and it is returning them back + if (reduce.rule->is_midrule()) + { + for (std::size_t i = 0; i < action_arg.size(); ++i) + stack[stack.size() - i - 1].second = std::move(action_arg[action_arg.size() - i - 1]); + } + // Non-midrule actions actually consumed those arguments so pop them out + else + { + for (std::size_t i = 0; i < action_arg.size(); ++i) + stack.pop_back(); + } + + debug_parser("Pushing state {}", maybe_next_state.value()->get_index()); + + stack.emplace_back( + maybe_next_state.value()->get_index(), + std::optional { TokenWithLineSpec { action_result, {} } } + ); + } + else if (std::holds_alternative(action)) + { + const auto& shift = std::get(action); + debug_parser("Shifting state {}", shift.state->get_index()); + + // Notice how std::move() is only around optional itself and not the whole expressions + // We need to do this in order to perform move together with value() + // See: https://en.cppreference.com/w/cpp/utility/optional/value + // Return by rvalue is performed only when value() is called from r-value + stack.emplace_back( + shift.state->get_index(), + std::optional { TokenWithLineSpec { token.value().value, token.value().line_spec } } + ); + + // We did shift so the token value is moved onto stack, "forget" the token + token.reset(); + } + else if (std::holds_alternative(action)) + { + debug_parser("Accept"); + // Notice how std::move() is only around optional itself and not the whole expressions + // We need to do this in order to perform move together with value() + // See: https://en.cppreference.com/w/cpp/utility/optional/value + // Return by rvalue is performed only when value() is called from r-value + return { std::move(stack.back().second).value().value }; + } + } + + assert(false && "Stack was emptied too early"); + return std::nullopt; + } + + std::string generate_automaton_graph() + { + return _automaton.generate_graph(); + } + + std::string generate_includes_relation_graph() + { + return _includes.generate_relation_graph(); + } + +private: + Grammar _grammar; + Tokenizer _tokenizer; + Automaton _automaton; + Includes _includes; + Lookback _lookback; + Read _read_operation; + Follow _follow_operation; + Lookahead _lookahead_operation; + ParsingTable _parsing_table; + + std::vector _rule_builders; + std::vector _token_builders; + + ParserReportType _report; + HCAsm::CompilerState* _state; + std::queue files; +}; + +} // namespace pog diff --git a/src/Pog/ParserReport.hpp b/src/Pog/ParserReport.hpp new file mode 100644 index 00000000..ebe8fa98 --- /dev/null +++ b/src/Pog/ParserReport.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace pog { + +template +struct ShiftReduceConflict +{ + const State* state; + const Symbol* symbol; + const Rule* rule; + + std::string to_string(std::string_view arrow = "->", std::string_view eps = "") const + { + return fmt::format("Shift-reduce conflict of symbol \'{}\' and rule \'{}\' in state {}", symbol->get_name(), rule->to_string(arrow, eps), state->get_index()); + } +}; + +template +struct ReduceReduceConflict +{ + const State* state; + const Rule* rule1; + const Rule* rule2; + + std::string to_string(std::string_view arrow = "->", std::string_view eps = "") const + { + return fmt::format("Reduce-reduce conflict of rule \'{}\' and rule \'{}\' in state {}", rule1->to_string(arrow, eps), rule2->to_string(arrow, eps), state->get_index()); + } +}; + +template +using Issue = std::variant, ReduceReduceConflict>; + +template +class ParserReport +{ +public: + using IssueType = Issue; + using RuleType = Rule; + using StateType = State; + using SymbolType = Symbol; + + using ReduceReduceConflictType = ReduceReduceConflict; + using ShiftReduceConflictType = ShiftReduceConflict; + + bool ok() const { return _issues.empty(); } + operator bool() const { return ok(); } + + std::size_t number_of_issues() const { return _issues.size(); } + auto begin() { return _issues.begin(); } + auto end() { return _issues.end(); } + auto begin() const { return _issues.begin(); } + auto end() const { return _issues.end(); } + + void add_shift_reduce_conflict(const StateType* state, const SymbolType* symbol, const RuleType* rule) + { + _issues.push_back(ShiftReduceConflictType{state, symbol, rule}); + } + + void add_reduce_reduce_conflict(const StateType* state, const RuleType* rule1, const RuleType* rule2) + { + _issues.push_back(ReduceReduceConflictType{state, rule1, rule2}); + } + + std::string to_string(std::string_view arrow = "->", std::string_view eps = "") const + { + std::vector issues_str(_issues.size()); + std::transform(_issues.begin(), _issues.end(), issues_str.begin(), [&](const auto& issue) { + return visit_with(issue, + [&](const ShiftReduceConflictType& sr) { return sr.to_string(arrow, eps); }, + [&](const ReduceReduceConflictType& rr) { return rr.to_string(arrow, eps); } + ); + }); + return fmt::format("{}", fmt::join(issues_str.begin(), issues_str.end(), "\n")); + } + +private: + std::vector _issues; +}; + +} // namespace pog diff --git a/src/Pog/ParsingTable.hpp b/src/Pog/ParsingTable.hpp new file mode 100644 index 00000000..8abc1fc0 --- /dev/null +++ b/src/Pog/ParsingTable.hpp @@ -0,0 +1,170 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pog { + +template +class ParsingTable +{ +public: + using ActionType = Action; + using ShiftActionType = Shift; + using ReduceActionType = Reduce; + + using AutomatonType = Automaton; + using GrammarType = Grammar; + using RuleType = Rule; + using StateType = State; + using SymbolType = Symbol; + + using StateAndRuleType = StateAndRule; + using StateAndSymbolType = StateAndSymbol; + + // TODO: Lookahead<> should be non-const but we need it for operator[] + ParsingTable(const AutomatonType* automaton, const GrammarType* grammar, Lookahead& lookahead_op) + : _automaton(automaton), _grammar(grammar), _lookahead_op(lookahead_op) {} + + void calculate(ParserReport& report) + { + for (const auto& state : _automaton->get_states()) + { + if (state->is_accepting()) + add_accept(state.get(), _grammar->get_end_of_input_symbol()); + + for (const auto& [sym, dest_state] : state->get_transitions()) + add_state_transition(report, state.get(), sym, dest_state); + + for (const auto& item : state->get_production_items()) + { + for (const auto& sym : _lookahead_op[StateAndRuleType{state.get(), item->get_rule()}]) + add_reduction(report, state.get(), sym, item->get_rule()); + } + } + } + + void add_accept(const StateType* state, const SymbolType* symbol) + { + auto ss = StateAndSymbolType{state, symbol}; + auto itr = _action_table.find(ss); + if (itr != _action_table.end()) + assert(false && "Conflict happened in placing accept but this shouldn't happen"); + + _action_table.emplace(std::move(ss), Accept{}); + } + + void add_state_transition(ParserReport& report, const StateType* src_state, const SymbolType* symbol, const StateType* dest_state) + { + auto ss = StateAndSymbolType{src_state, symbol}; + if (symbol->is_terminal()) + { + auto itr = _action_table.find(ss); + if (itr != _action_table.end()) + { + if (std::holds_alternative(itr->second)) + report.add_shift_reduce_conflict(ss.state, ss.symbol, std::get(itr->second).rule); + } + else + _action_table.emplace(std::move(ss), ShiftActionType{dest_state}); + } + else if (symbol->is_nonterminal()) + { + auto itr = _goto_table.find(ss); + if (itr != _goto_table.end()) + assert(false && "Conflict happened in filling GOTO table but this shouldn't happen"); + + _goto_table.emplace(std::move(ss), dest_state); + } + } + + void add_reduction(ParserReport& report, const StateType* state, const SymbolType* symbol, const RuleType* rule) + { + auto ss = StateAndSymbolType{state, symbol}; + auto itr = _action_table.find(ss); + if (itr != _action_table.end()) + { + std::optional stack_prec; + if (rule->has_precedence()) + stack_prec = rule->get_precedence(); + else if (auto op_symbol = rule->get_rightmost_terminal(); op_symbol && op_symbol->has_precedence()) + stack_prec = op_symbol->get_precedence(); + + if (stack_prec.has_value() && symbol->has_precedence()) + { + const auto& input_prec = symbol->get_precedence(); + // Stack symbol precedence is lower, keep shift in the table + if (stack_prec < input_prec) + { + return; + } + // Stack symbol precedence is greater, prefer reduce + else if (stack_prec > input_prec) + { + itr->second = ReduceActionType{rule}; + return; + } + } + + if (std::holds_alternative(itr->second)) + report.add_reduce_reduce_conflict(ss.state, std::get(itr->second).rule, rule); + else if (std::holds_alternative(itr->second)) + report.add_shift_reduce_conflict(ss.state, ss.symbol, rule); + } + else + _action_table.emplace(std::move(ss), ReduceActionType{rule}); + } + + std::optional get_action(const StateType* state, const SymbolType* symbol) const + { + auto action_itr = _action_table.find(StateAndSymbolType{state, symbol}); + if (action_itr == _action_table.end()) + return std::nullopt; + + return action_itr->second; + } + + std::optional get_transition(const StateType* state, const SymbolType* symbol) const + { + auto goto_itr = _goto_table.find(StateAndSymbolType{state, symbol}); + if (goto_itr == _goto_table.end()) + return std::nullopt; + + return goto_itr->second; + } + + std::vector get_expected_symbols_from_state(const StateType* state) const + { + std::vector result; + for (const auto& sym : _grammar->get_symbols()) + { + if (sym->is_nonterminal()) + continue; + + StateAndSymbolType ss{state, sym.get()}; + if (auto itr = _action_table.find(ss); itr != _action_table.end()) + result.push_back(sym.get()); + } + + return result; + } + +private: + const AutomatonType* _automaton; + const GrammarType* _grammar; + std::unordered_map _action_table; + std::unordered_map _goto_table; + Lookahead& _lookahead_op; +}; + +} // namespace pog diff --git a/src/Pog/Pog.hpp b/src/Pog/Pog.hpp new file mode 100644 index 00000000..3651c84d --- /dev/null +++ b/src/Pog/Pog.hpp @@ -0,0 +1,6 @@ +#pragma once + +#define POG_VERSION "0.5.3" + +#include +#include diff --git a/src/Pog/Precedence.hpp b/src/Pog/Precedence.hpp new file mode 100644 index 00000000..bd16eb1f --- /dev/null +++ b/src/Pog/Precedence.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +namespace pog { + +enum class Associativity +{ + Left, + Right +}; + +struct Precedence +{ + std::uint32_t level; + Associativity assoc; + + bool operator==(const Precedence& rhs) const { return level == rhs.level && assoc == rhs.assoc; } + bool operator!=(const Precedence& rhs) const { return !(*this == rhs); } + + bool operator<(const Precedence& rhs) const + { + if (level < rhs.level) + return true; + else if (level == rhs.level) + { + if (assoc == Associativity::Right) + return true; + } + + return false; + } + + bool operator>(const Precedence& rhs) const + { + if (level > rhs.level) + return true; + else if (level == rhs.level) + { + if (assoc == Associativity::Left) + return true; + } + + return false; + } +}; + +} // namespace pog diff --git a/src/Pog/Relations/Includes.hpp b/src/Pog/Relations/Includes.hpp new file mode 100644 index 00000000..191b2db2 --- /dev/null +++ b/src/Pog/Relations/Includes.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace pog { + +/** + * Includes is a relation on tuples of (q,x) where q is state and x is symbol. + * Basically it's a relation on transitions over symbols coming out from states. + * + * Let's imagine that state Q contains item A -> a <*> B b where a and b are sequences + * of terminals and nonterminals. We had to get to this state Q from state P which + * contains item A -> <*> a B b. If and only if b can be reduced down to empty string (b =>* eps), + * we say that (Q, B) includes (P, A). + * + * To put this into human-friendly language, if we are in a state with item A -> a <*> B b, + * that means we are about to read B from the input. If the sequence b can possibly be empty, + * then we say that the state with item A -> a <*> B b and symbol B includes state with + * item A -> <*> a B b and symbol A. + * + * This is useful for construction of Follow sets because if b can be completely empty, then + * what can follow A can also follow B (with respect to states they are in). + */ +template +class Includes : public Relation> +{ +public: + using Parent = Relation>; + + using AutomatonType = Automaton; + using BacktrackingInfoType = BacktrackingInfo; + using GrammarType = Grammar; + using StateType = State; + using SymbolType = Symbol; + + using StateAndSymbolType = StateAndSymbol; + + Includes(const AutomatonType* automaton, const GrammarType* grammar) : Parent(automaton, grammar) {} + Includes(const Includes&) = delete; + Includes(Includes&&) noexcept = default; + + virtual void calculate() override + { + // Iterate over all states in the LR automaton + for (const auto& state : Parent::_automaton->get_states()) + { + for (const auto& item : *state.get()) + { + // We are looking for items in form A -> a <*> B b so we are not intersted in final items + if (item->is_final()) + continue; + + // Get the symbol right next to <*> in an item + auto next_symbol = item->get_read_symbol(); + + // If the next symbol is not nonterminal then we are again not interested + if (!next_symbol->is_nonterminal()) + continue; + + StateAndSymbolType src_ss{state.get(), next_symbol}; + Parent::_relation.emplace(src_ss, std::unordered_set{}); + + // Get the 'b' out of A -> a <*> B b + // If b can't be reduced down to empty string - Empty(b) - then we are not interested + auto right_rest = item->get_right_side_without_read_symbol(); + if (!right_rest.empty() && !Parent::_grammar->empty(right_rest)) + continue; + + // Now we'll start backtracking through LR automaton using backtransitions. + // We'll basically just go in the different direction of arrows in the automata. + // We know of what symbols 'a' in A -> a <*> B b is made of so we exactly know which + // backtransitions to take. There can be multiple transitions through the same symbol + // going into current state so we'll put them into queue and process until queue is empty. + std::unordered_set visited_states; + std::deque to_process; + // Let's insert the current state and item A -> a <*> B b into the queue as a starting point + to_process.push_back(BacktrackingInfoType{state.get(), *item.get()}); + while (!to_process.empty()) + { + auto backtracking_info = std::move(to_process.front()); + to_process.pop_front(); + + // If we've reached state with item A -> <*> a B b, we've reached our destination + if (backtracking_info.item.get_read_pos() == 0) + { + // Insert relation + StateAndSymbolType dest_ss{backtracking_info.state, backtracking_info.item.get_rule()->get_lhs()}; + auto itr = Parent::_relation.find(src_ss); + if (itr == Parent::_relation.end()) + assert(false && "This shouldn't happen"); + itr->second.insert(std::move(dest_ss)); + continue; + } + + // Observe backtransitions over the symbol left to the <*> in an item + const auto& back_trans = backtracking_info.state->get_back_transitions(); + auto itr = back_trans.find(backtracking_info.item.get_previous_symbol()); + if (itr == back_trans.end()) + assert(false && "This shouldn't happen"); + + // Perform step back of an item so that <*> in an item is moved one symbol to the left + backtracking_info.item.step_back(); + for (const auto& dest_state : itr->second) + { + if (visited_states.find(dest_state) == visited_states.end()) + { + // Put non-visited states from backtransitions into the queue + to_process.push_back(BacktrackingInfoType{dest_state, backtracking_info.item}); + visited_states.emplace(dest_state); + } + } + } + } + } + } + + std::string generate_relation_graph() + { + std::vector states_str, edges_str; + for (const auto& [ss, dests] : Parent::_relation) + { + states_str.push_back(fmt::format("n_{}_{} [label=\"({}, {})\"]", ss.state->get_index(), ss.symbol->get_index(), ss.state->get_index(), ss.symbol->get_name())); + for (const auto& dest_ss : dests) + { + states_str.push_back(fmt::format("n_{}_{} [label=\"({}, {})\"]", dest_ss.state->get_index(), dest_ss.symbol->get_index(), dest_ss.state->get_index(), dest_ss.symbol->get_name())); + edges_str.push_back(fmt::format("n_{}_{} -> n_{}_{}", ss.state->get_index(), ss.symbol->get_index(), dest_ss.state->get_index(), dest_ss.symbol->get_index())); + } + } + + return fmt::format(R"(digraph Includes {{ +node [shape=circle]; + +{} + +{} +}})", + fmt::join(states_str.begin(), states_str.end(), "\n"), + fmt::join(edges_str.begin(), edges_str.end(), "\n") + ); + } +}; + +} // namespace pog diff --git a/src/Pog/Relations/Lookback.hpp b/src/Pog/Relations/Lookback.hpp new file mode 100644 index 00000000..925edc01 --- /dev/null +++ b/src/Pog/Relations/Lookback.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include + +#include +#include +#include + +namespace pog { + +/** + * Lookback is a relation of tuples (q,R) where q is state and R is a rule from grammar and + * tuples of (p,x) where p is state and x is symbol. Basically it's a relation on state and rule + * with state and symbol. + * + * Let's imagine that state Q contains final item A -> x <*> where x is a sequence of terminals and + * nonterminals. That means we need to perform production of rule A -> x and reduce x on the stack + * into A. That also means that there is some state P with item B -> a <*> A b through which we had to + * go into state Q. If it happens then (Q, A -> x) lookbacks (P, A). + * + * To put it simply, in order to get to state with final item A -> x <*>, we first had to go through + * state with item B -> a <*> A b. We just simply put state with item A -> x <*> and rule A -> x into relation + * with the origin state with item B -> a <*> A b and symbol A. + * + * This is useful for so-called propagation of lookaheads. If we know that rule A -> x is being used + * and it all originated in certain state where rule B -> a A b is being processed, we can use what + * can possible follow A in B -> a A b to know whether to use production of A -> x. + */ +template +class Lookback : public Relation, StateAndSymbol> +{ +public: + using Parent = Relation, StateAndSymbol>; + + using AutomatonType = Automaton; + using BacktrackingInfoType = BacktrackingInfo; + using GrammarType = Grammar; + using StateType = State; + using SymbolType = Symbol; + + using StateAndSymbolType = StateAndSymbol; + using StateAndRuleType = StateAndRule; + + Lookback(const AutomatonType* automaton, const GrammarType* grammar) : Parent(automaton, grammar) {} + Lookback(const Lookback&) = delete; + Lookback(Lookback&&) noexcept = default; + + virtual void calculate() override + { + // Iterate over all states of LR automaton + for (const auto& state : Parent::_automaton->get_states()) + { + for (const auto& item : *state.get()) + { + // We are not interested in items other than in form A -> x <*> + if (!item->is_final()) + continue; + + // Get left-hand side symbol of a rule + auto prod_symbol = item->get_rule()->get_lhs(); + + // Now we'll start backtracking through LR automaton using backtransitions. + // We'll basically just go in the different direction of arrows in the automata. + // We know that we have item A -> x <*> so we know which backtransitions to take (those contained in sequence x). + // There can be multiple transitions through the same symbol + // going into current state so we'll put them into queue and process until queue is empty. + std::unordered_set visited_states; + std::deque to_process; + // Let's insert the current state and item A -> x <*> into the queue as a starting point + to_process.push_back(BacktrackingInfoType{state.get(), *item.get()}); + while (!to_process.empty()) + { + auto backtracking_info = std::move(to_process.front()); + to_process.pop_front(); + + // If the state has transition over the symbol A, that means there is an item B -> a <*> A b + if (backtracking_info.state->get_transitions().find(prod_symbol) != backtracking_info.state->get_transitions().end()) + { + // Insert relation + StateAndRuleType src_sr{state.get(), item->get_rule()}; + StateAndSymbolType dest_ss{backtracking_info.state, prod_symbol}; + auto itr = Parent::_relation.find(src_sr); + if (itr == Parent::_relation.end()) + Parent::_relation.emplace(std::move(src_sr), std::unordered_set{std::move(dest_ss)}); + else + itr->second.insert(std::move(dest_ss)); + } + + // We've reached item with <*> at the start so we are no longer interested in it + if (backtracking_info.item.get_read_pos() == 0) + continue; + + // Observe backtransitions over the symbol left to the <*> in an item + const auto& back_trans = backtracking_info.state->get_back_transitions(); + auto itr = back_trans.find(backtracking_info.item.get_previous_symbol()); + if (itr == back_trans.end()) + assert(false && "This shouldn't happen"); + + // Perform step back of an item so that <*> in an item is moved one symbol to the left + backtracking_info.item.step_back(); + for (const auto& dest_state : itr->second) + { + if (visited_states.find(dest_state) == visited_states.end()) + { + // Put non-visited states from backtransitions into the queue + to_process.push_back(BacktrackingInfoType{dest_state, backtracking_info.item}); + visited_states.emplace(dest_state); + } + } + } + } + } + } +}; + +} // namespace pog diff --git a/src/Pog/Relations/Relation.hpp b/src/Pog/Relations/Relation.hpp new file mode 100644 index 00000000..d4313b5c --- /dev/null +++ b/src/Pog/Relations/Relation.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +#include +#include + +namespace pog { + +template +struct BacktrackingInfo +{ + const State* state; + Item item; +}; + +template +class Relation +{ +public: + using AutomatonType = Automaton; + using GrammarType = Grammar; + + Relation(const AutomatonType* automaton, const GrammarType* grammar) : _automaton(automaton), _grammar(grammar) {} + Relation(const Relation&) = delete; + Relation(Relation&&) noexcept = default; + virtual ~Relation() = default; + + virtual void calculate() = 0; + + auto begin() { return _relation.begin(); } + auto end() { return _relation.end(); } + + auto begin() const { return _relation.begin(); } + auto end() const { return _relation.end(); } + + template + std::unordered_set* find(const T& key) + { + auto itr = _relation.find(key); + if (itr == _relation.end()) + return nullptr; + + return &itr->second; + } + + template + const std::unordered_set* find(const T& key) const + { + auto itr = _relation.find(key); + if (itr == _relation.end()) + return nullptr; + + return &itr->second; + } + +protected: + const AutomatonType* _automaton; + const GrammarType* _grammar; + std::unordered_map> _relation; +}; + +} // namespace pog diff --git a/src/Pog/Rule.hpp b/src/Pog/Rule.hpp new file mode 100644 index 00000000..01fe7f9c --- /dev/null +++ b/src/Pog/Rule.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace pog { + +template +class Parser; + +template +class Rule +{ +public: + using SymbolType = Symbol; + using CallbackType = std::function&, std::vector>&&)>; + + Rule(std::uint32_t index, const SymbolType* lhs, const std::vector& rhs) + : _index(index), _lhs(lhs), _rhs(rhs), _action(), _midrule_size(std::nullopt), _start(false) {} + + template + Rule(std::uint32_t index, const SymbolType* lhs, const std::vector& rhs, CallbackT&& action) + : _index(index), _lhs(lhs), _rhs(rhs), _action(std::forward(action)), _midrule_size(std::nullopt), _start(false) {} + + std::uint32_t get_index() const { return _index; } + const SymbolType* get_lhs() const { return _lhs; } + const std::vector& get_rhs() const { return _rhs; } + + bool has_precedence() const { return static_cast(_precedence); } + const Precedence& get_precedence() const { return _precedence.value(); } + void set_precedence(std::uint32_t level, Associativity assoc) { _precedence = Precedence{level, assoc}; } + + std::size_t get_number_of_required_arguments_for_action() const + { + return is_midrule() ? get_midrule_size() : get_rhs().size(); + } + + const SymbolType* get_rightmost_terminal() const + { + auto itr = std::find_if(_rhs.rbegin(), _rhs.rend(), [](const auto& symbol) { + return symbol->is_terminal(); + }); + + return itr != _rhs.rend() ? *itr : nullptr; + } + + std::string to_string(std::string_view arrow = "->", std::string_view eps = "") const + { + std::vector rhs_strings(_rhs.size()); + std::transform(_rhs.begin(), _rhs.end(), rhs_strings.begin(), [](const SymbolType* s) { + return s->get_name(); + }); + + if (rhs_strings.empty()) + rhs_strings.push_back(std::string{eps}); + + return fmt::format("{} {} {}", _lhs->get_name(), arrow, fmt::join(rhs_strings.begin(), rhs_strings.end(), " ")); + } + + bool has_action() const { return static_cast(_action); } + bool is_start_rule() const { return _start; } + + void set_start_rule(bool set) { _start = set; } + void set_midrule(std::size_t size) { _midrule_size = size; } + bool is_midrule() const { return static_cast(_midrule_size); } + std::size_t get_midrule_size() const { return _midrule_size.value(); } + + template + ValueT perform_action(Args&&... args) const { return _action(std::forward(args)...); } + + bool operator==(const Rule& rhs) const { return _index == rhs._index; } + bool operator!=(const Rule& rhs) const { return !(*this == rhs); } + +private: + std::uint32_t _index; + const SymbolType* _lhs; + std::vector _rhs; + CallbackType _action; + std::optional _precedence; + std::optional _midrule_size; + bool _start; +}; + +} // namespace pog diff --git a/src/Pog/RuleBuilder.hpp b/src/Pog/RuleBuilder.hpp new file mode 100644 index 00000000..813d953b --- /dev/null +++ b/src/Pog/RuleBuilder.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include + +namespace pog { + +template +class RuleBuilder +{ +public: + using GrammarType = Grammar; + using RuleType = Rule; + using SymbolType = Symbol; + + struct SymbolsAndAction + { + std::vector symbols; + typename RuleType::CallbackType action; + }; + + struct RightHandSide + { + std::vector symbols_and_action; + std::optional precedence; + }; + + RuleBuilder(GrammarType* grammar, const std::string& lhs) : _grammar(grammar), _lhs(lhs), _rhss() {} + + void done() + { + if (_rhss.empty()) + return; + + const auto* lhs_symbol = _grammar->add_symbol(SymbolKind::Nonterminal, _lhs); + + std::size_t rhs_counter = 0; + for (auto&& rhs : _rhss) + { + assert(!rhs.symbols_and_action.empty() && "No symbols and action associated to right-hand side of the rule. This shouldn't happen"); + + std::vector rhs_symbols; + for (std::size_t i = 0; i < rhs.symbols_and_action.size(); ++i) + { + auto&& symbols_and_action = rhs.symbols_and_action[i]; + + std::transform(symbols_and_action.symbols.begin(), symbols_and_action.symbols.end(), std::back_inserter(rhs_symbols), [this](const auto& sym_name) { + return _grammar->add_symbol(SymbolKind::Nonterminal, sym_name); + }); + + // There are multple actions (mid-rule actions) so we need to create new symbol and epsilon rule + // for each midrule action. Midrule symbols will be inserted into the original rule. + // + // If you have rule A -> B C D and you want to perform action after B, then we'll create rules + // A -> B X C D + // X -> + // where X -> will have assigned the midrule action. + if (i < rhs.symbols_and_action.size() - 1) + { + // Create unique nonterminal for midrule action + auto midsymbol = _grammar->add_symbol( + SymbolKind::Nonterminal, + fmt::format("_{}#{}.{}", _lhs, rhs_counter, i) + ); + + // Create rule to which midrule action can be assigned and set midrule size. + // Midrule size is number of symbols preceding the midrule symbol. It represents how many + // items from stack we need to borrow for action arguments. + auto rule = _grammar->add_rule(midsymbol, std::vector{}, std::move(symbols_and_action.action)); + rule->set_midrule(rhs_symbols.size()); + rhs_symbols.push_back(midsymbol); + } + // This is the last action so do not mark it as midrule + else + { + auto rule = _grammar->add_rule(lhs_symbol, rhs_symbols, std::move(symbols_and_action.action)); + if (rule && rhs.precedence) + { + const auto& prec = rhs.precedence.value(); + rule->set_precedence(prec.level, prec.assoc); + } + } + } + + rhs_counter++; + } + } + + template + RuleBuilder& production(Args&&... args) + { + _rhss.push_back(RightHandSide{ + std::vector{ + SymbolsAndAction{ + std::vector{}, + {} + } + }, + std::nullopt + }); + _production(_rhss.back().symbols_and_action, std::forward(args)...); + return *this; + } + + RuleBuilder& precedence(std::uint32_t level, Associativity assoc) + { + _rhss.back().precedence = Precedence{level, assoc}; + return *this; + } + +private: + void _production(std::vector&) {} + + template + void _production(std::vector& sa, const std::string& symbol, Args&&... args) + { + sa.back().symbols.push_back(symbol); + _production(sa, std::forward(args)...); + } + + template + void _production(std::vector& sa, typename RuleType::CallbackType&& action, Args&&... args) + { + sa.back().action = std::move(action); + // We have ran into action so create new record in symbols and actions vector + // but only if it isn't the very last thing in the production + if constexpr (sizeof...(args) > 0) + sa.push_back(SymbolsAndAction{ + std::vector{}, + {} + }); + _production(sa, std::forward(args)...); + } + + GrammarType* _grammar; + std::string _lhs; + std::vector _rhss; +}; + +} // namespace pog diff --git a/src/Pog/State.hpp b/src/Pog/State.hpp new file mode 100644 index 00000000..ad9ba829 --- /dev/null +++ b/src/Pog/State.hpp @@ -0,0 +1,157 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace pog { + +template +class State +{ +public: + using ItemType = Item; + using SymbolType = Symbol; + + State() : _index(std::numeric_limits::max()) {} + State(std::uint32_t index) : _index(index) {} + + std::uint32_t get_index() const { return _index; } + void set_index(std::uint32_t index) { _index = index; } + + std::size_t size() const { return _items.size(); } + auto begin() const { return _items.begin(); } + auto end() const { return _items.end(); } + + template + std::pair add_item(T&& item) + { + auto itr = std::lower_bound(_items.begin(), _items.end(), item, [](const auto& left, const auto& needle) { + return *left.get() < needle; + }); + + if (itr == _items.end() || *itr->get() != item) + { + auto new_itr = _items.insert(itr, std::make_unique(std::forward(item))); + return {new_itr->get(), true}; + } + else + return {itr->get(), false}; + } + + void add_transition(const SymbolType* symbol, const State* state) + { + _transitions.emplace(symbol, state); + } + + void add_back_transition(const SymbolType* symbol, const State* state) + { + auto itr = _back_transitions.find(symbol); + if (itr == _back_transitions.end()) + { + _back_transitions.emplace(symbol, std::vector{state}); + return; + } + + auto state_itr = std::lower_bound(itr->second.begin(), itr->second.end(), state->get_index(), [](const auto& left, const auto& needle) { + return left->get_index() < needle; + }); + + if (state_itr == itr->second.end() || (*state_itr)->get_index() != state->get_index()) + itr->second.insert(state_itr, state); + } + + bool is_accepting() const + { + return std::count_if(_items.begin(), _items.end(), [](const auto& item) { + return item->is_accepting(); + }) == 1; + } + + std::string to_string(std::string_view arrow = "->", std::string_view eps = "", std::string_view sep = "<*>", const std::string& newline = "\n") const + { + std::vector item_strings(_items.size()); + std::transform(_items.begin(), _items.end(), item_strings.begin(), [&](const auto& item) { + return item->to_string(arrow, eps, sep); + }); + return fmt::format("{}", fmt::join(item_strings.begin(), item_strings.end(), newline)); + } + + std::vector get_production_items() const + { + std::vector result; + transform_if(_items.begin(), _items.end(), std::back_inserter(result), + [](const auto& item) { + return item->is_final(); + }, + [](const auto& item) { + return item.get(); + } + ); + return result; + } + + auto get_kernel() const + { + return FilterView{_items.begin(), _items.end(), [](const auto& item) { + return item->is_kernel(); + }}; + } + + bool contains(const ItemType& item) const + { + auto itr = std::lower_bound(_items.begin(), _items.end(), item, [](const auto& left, const auto& needle) { + return *left.get() < needle; + }); + return itr != _items.end() && *itr->get() == item; + } + + bool operator==(const State& rhs) const + { + auto lhs_kernel = get_kernel(); + auto rhs_kernel = rhs.get_kernel(); + return std::equal(lhs_kernel.begin(), lhs_kernel.end(), rhs_kernel.begin(), rhs_kernel.end(), [](const auto& left, const auto& right) { + return *left.get() == *right.get(); + }); + } + + bool operator !=(const State& rhs) const + { + return !(*this == rhs); + } + + const std::map>& get_transitions() const { return _transitions; } + const std::map, SymbolLess>& get_back_transitions() const { return _back_transitions; } + +private: + std::uint32_t _index; + std::vector> _items; + std::map> _transitions; + std::map, SymbolLess> _back_transitions; +}; + +template +struct StateKernelHash +{ + std::size_t operator()(const State* state) const + { + std::size_t kernel_hash = 0; + for (const auto& item : state->get_kernel()) + hash_combine(kernel_hash, item->get_rule()->get_index(), item->get_read_pos()); + return kernel_hash; + } +}; + +template +struct StateKernelEquals +{ + bool operator()(const State* state1, const State* state2) const + { + return *state1 == *state2; + } +}; + +} // namespace pog diff --git a/src/Pog/Symbol.hpp b/src/Pog/Symbol.hpp new file mode 100644 index 00000000..3f913902 --- /dev/null +++ b/src/Pog/Symbol.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace pog { + +enum class SymbolKind +{ + End, + Nonterminal, + Terminal +}; + +template +class Symbol +{ +public: + Symbol(std::uint32_t index, SymbolKind kind, const std::string& name) : _index(index), _kind(kind), _name(name) {} + + std::uint32_t get_index() const { return _index; } + const Precedence& get_precedence() const { return _precedence.value(); } + const std::string& get_name() const { return _name; } + const std::string& get_description() const { return _description.has_value() ? *_description : _name; } + + bool has_precedence() const { return static_cast(_precedence); } + bool is_end() const { return _kind == SymbolKind::End; } + bool is_nonterminal() const { return _kind == SymbolKind::Nonterminal; } + bool is_terminal() const { return _kind == SymbolKind::Terminal; } + + void set_precedence(std::uint32_t level, Associativity assoc) { _precedence = Precedence{level, assoc}; } + void set_description(const std::string& description) { _description = description; } + +private: + std::uint32_t _index; + SymbolKind _kind; + std::string _name; + std::optional _description; + std::optional _precedence; +}; + + +template +struct SymbolLess +{ + bool operator()(const Symbol* lhs, const Symbol* rhs) const + { + return lhs->get_index() < rhs->get_index(); + } +}; + +} // namespace pog diff --git a/src/Pog/Token.hpp b/src/Pog/Token.hpp new file mode 100644 index 00000000..0134d04f --- /dev/null +++ b/src/Pog/Token.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include + +namespace pog { + +template +class Token +{ +public: + using SymbolType = Symbol; + using CallbackType = std::function; + + template + Token(std::uint32_t index, const std::string& pattern, StatesT&& active_in_states) : Token(index, pattern, std::forward(active_in_states), nullptr) {} + + template + Token(std::uint32_t index, const std::string& pattern, StatesT&& active_in_states, const SymbolType* symbol) + : _index(index), _pattern(pattern), _symbol(symbol), _regexp(std::make_unique(_pattern)), _action(), + _enter_state(), _active_in_states(std::forward(active_in_states)) {} + + std::uint32_t get_index() const { return _index; } + const std::string& get_pattern() const { return _pattern; } + const SymbolType* get_symbol() const { return _symbol; } + const re2::RE2* get_regexp() const { return _regexp.get(); } + + bool has_symbol() const { return _symbol != nullptr; } + bool has_action() const { return static_cast(_action); } + bool has_transition_to_state() const { return static_cast(_enter_state); } + + template + void set_action(CallbackT&& action) + { + _action = std::forward(action); + } + + template + ValueT perform_action(Args&&... args) const + { + return _action(std::forward(args)...); + } + + void set_transition_to_state(const std::string& state) + { + _enter_state = state; + } + + const std::string& get_transition_to_state() const + { + return _enter_state.value(); + } + + template + void add_active_in_state(StrT&& state) + { + _active_in_states.push_back(std::forward(state)); + } + + const std::vector& get_active_in_states() const + { + return _active_in_states; + } + +private: + std::uint32_t _index; + std::string _pattern; + const SymbolType* _symbol; + std::unique_ptr _regexp; + CallbackType _action; + std::optional _enter_state; + std::vector _active_in_states; +}; + +} // namespace pog diff --git a/src/Pog/TokenBuilder.hpp b/src/Pog/TokenBuilder.hpp new file mode 100644 index 00000000..a4c01c66 --- /dev/null +++ b/src/Pog/TokenBuilder.hpp @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include + +namespace pog { + +template +class TokenBuilder +{ +public: + using GrammarType = Grammar; + using SymbolType = Symbol; + using TokenType = Token; + using TokenizerType = Tokenizer; + + TokenBuilder(GrammarType* grammar, TokenizerType* tokenizer) : _grammar(grammar), _tokenizer(tokenizer), _pattern("$"), + _symbol_name(), _precedence(), _action(), _fullword(false), _end_token(true), _in_states{std::string{TokenizerType::DefaultState}}, _enter_state() {} + + TokenBuilder(GrammarType* grammar, TokenizerType* tokenizer, const std::string& pattern) : _grammar(grammar), _tokenizer(tokenizer), _pattern(pattern), + _symbol_name(), _precedence(), _action(), _fullword(false), _end_token(false), _in_states{std::string{TokenizerType::DefaultState}}, _enter_state() {} + + void done() + { + TokenType* token; + if (!_end_token) + { + auto* symbol = !_symbol_name.empty() ? _grammar->add_symbol(SymbolKind::Terminal, _symbol_name) : nullptr; + token = _tokenizer->add_token(_fullword ? fmt::format("{}(\\b|$)", _pattern) : _pattern, symbol, std::move(_in_states)); + if (symbol && _precedence) + { + const auto& prec = _precedence.value(); + symbol->set_precedence(prec.level, prec.assoc); + } + + if(symbol && _description.size() != 0) + symbol->set_description(_description); + + if (_enter_state) + token->set_transition_to_state(_enter_state.value()); + } + else + { + token = _tokenizer->get_end_token(); + for (auto&& state : _in_states) + token->add_active_in_state(std::move(state)); + } + + if (_action) + token->set_action(std::move(_action)); + } + + TokenBuilder& symbol(const std::string& symbol_name) + { + _symbol_name = symbol_name; + return *this; + } + + TokenBuilder& precedence(std::uint32_t level, Associativity assoc) + { + _precedence = Precedence{level, assoc}; + return *this; + } + + TokenBuilder& description(const std::string& text) + { + _description = text; + return *this; + } + + template + TokenBuilder& action(CallbackT&& action) + { + _action = std::forward(action); + return *this; + } + + TokenBuilder& fullword() + { + _fullword = true; + return *this; + } + + template + TokenBuilder& states(Args&&... args) + { + _in_states = {std::forward(args)...}; + return *this; + } + + TokenBuilder& enter_state(const std::string& state) + { + _enter_state = state; + return *this; + } + +private: + GrammarType* _grammar; + TokenizerType* _tokenizer; + std::string _description; + std::string _pattern; + std::string _symbol_name; + std::optional _precedence; + typename TokenType::CallbackType _action; + bool _fullword; + bool _end_token; + std::vector _in_states; + std::optional _enter_state; +}; + +} // namespace pog diff --git a/src/Pog/Tokenizer.hpp b/src/Pog/Tokenizer.hpp new file mode 100644 index 00000000..d61265b0 --- /dev/null +++ b/src/Pog/Tokenizer.hpp @@ -0,0 +1,285 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#ifdef POG_DEBUG +#define POG_DEBUG_TOKENIZER 1 +#endif + +#ifdef POG_DEBUG_TOKENIZER +#define debug_tokenizer(...) fmt::print(stderr, "[tokenizer] {}\n", fmt::format(__VA_ARGS__)) +#else +#define debug_tokenizer(...) +#endif + +#include +#include +#include + +namespace pog { + +template +struct TokenMatch +{ + TokenMatch(const Symbol* sym) : symbol(sym), value(), match_length(0) {} + template + TokenMatch(const Symbol* sym, T&& v, std::size_t len, LineSpecialization spec) : symbol(sym), line_spec(spec), value(std::forward(v)), match_length(len) {} + TokenMatch(const TokenMatch&) = default; + TokenMatch(TokenMatch&&) noexcept = default; + + TokenMatch& operator=(const TokenMatch&) = default; + TokenMatch& operator=(TokenMatch&&) noexcept = default; + + const Symbol* symbol; + LineSpecialization line_spec; + ValueT value; + std::size_t match_length; +}; + +struct InputStream +{ + std::unique_ptr content; + re2::StringPiece stream; + bool at_end; +}; + +template +struct StateInfo +{ + std::string name; + std::unique_ptr re_set; + std::vector*> tokens; +}; + +template +class Tokenizer +{ +public: + using CallbackType = std::function; + + static constexpr std::string_view DefaultState = "@default"; + + using GrammarType = Grammar; + using StateInfoType = StateInfo; + using SymbolType = Symbol; + using TokenType = Token; + using TokenMatchType = TokenMatch; + + Tokenizer(const GrammarType* grammar) : _grammar(grammar), _tokens(), _state_info(), _input_stack(), _current_state(nullptr), _global_action() + { + _current_line = 1; + _current_offset = 0; + _current_state = get_or_make_state_info(std::string{DefaultState}); + add_token("$", nullptr, std::vector{std::string{DefaultState}}); + } + + void prepare() + { + std::string error; + + for (const auto& token : _tokens) + { + for (const auto& state : token->get_active_in_states()) + { + error.clear(); + auto* state_info = get_or_make_state_info(state); + state_info->re_set->Add(token->get_pattern(), &error); + state_info->tokens.push_back(token.get()); + assert(1); + assert(error.empty() && "Error when compiling token regexp"); + } + } + + for (auto&& [name, info] : _state_info) + info.re_set->Compile(); + } + + const std::vector>& get_tokens() const + { + return _tokens; + } + + TokenType* get_end_token() const + { + return _tokens[0].get(); + } + + TokenType* add_token(const std::string& pattern, const SymbolType* symbol, const std::vector& states) + { + _tokens.push_back(std::make_unique(static_cast(_tokens.size()), pattern, states, symbol)); + return _tokens.back().get(); + } + + void push_input_stream(std::string& stream) + { + _input_stack.emplace_back(InputStream{std::make_unique(std::move(stream)), re2::StringPiece{}, false}); + _input_stack.back().stream = re2::StringPiece{_input_stack.back().content->c_str()}; + } + + void pop_input_stream() + { + _input_stack.pop_back(); + } + + void clear_input_streams() + { + _input_stack.clear(); + } + + void global_action(CallbackType&& global_action) + { + _global_action = std::move(global_action); + } + + void _reset_line_offset() + { + _current_offset = 0; + } + + std::uint32_t& _get_line_counter() + { + return _current_line; + } + + std::uint16_t& _get_line_offset() { + return _current_offset; + } + + std::optional next_token() + { + bool repeat = true; + while (repeat) + { + // We've emptied the stack so that means return end symbol to parser + if (_input_stack.empty()) + { + debug_tokenizer("Input stack empty - returing end of input"); + return _grammar->get_end_of_input_symbol(); + } + + auto& current_input = _input_stack.back(); + if (!current_input.at_end) + { + // Matched patterns doesn't have to be sorted (used to be in older re2 versions) but we shouldn't count on that + std::vector matched_patterns; + _current_state->re_set->Match(current_input.stream, &matched_patterns); + + // Haven't matched anything, tokenization failure, we will get into endless loop + if (matched_patterns.empty()) [[unlikely]] + { + debug_tokenizer("Nothing matched on the current input"); + return std::nullopt; + } + + re2::StringPiece submatch; + const TokenType* best_match = nullptr; + int longest_match = -1; + for (auto pattern_index : matched_patterns) + { + _current_state->tokens[pattern_index]->get_regexp()->Match(current_input.stream, 0, current_input.stream.size(), re2::RE2::Anchor::ANCHOR_START, &submatch, 1); + if (longest_match < static_cast(submatch.size())) + { + best_match = _current_state->tokens[pattern_index]; + longest_match = static_cast(submatch.size()); + } + // In case of equal matches, index of tokens chooses which one is it (lower index has higher priority) + else if (longest_match == static_cast(submatch.size())) + { + if (!best_match || best_match->get_index() > static_cast(pattern_index)) + best_match = _current_state->tokens[pattern_index]; + } + } + + if (current_input.stream.size() == 0) + { + debug_tokenizer("Reached end of input"); + current_input.at_end = true; + } + + if (best_match->has_transition_to_state()) + { + enter_state(best_match->get_transition_to_state()); + debug_tokenizer("Entered state \'{}\'", best_match->get_transition_to_state()); + } + + std::string_view token_str{current_input.stream.data(), static_cast(longest_match)}; + _current_offset += longest_match; + current_input.stream.remove_prefix(longest_match); + debug_tokenizer("Matched \'{}\' with token \'{}\' (index {})", token_str, best_match->get_pattern(), best_match->get_index()); + + if (_global_action) + _global_action(token_str); + + ValueT value{}; + if (best_match->has_action()) + value = best_match->perform_action(token_str); + + if (!best_match->has_symbol()) + continue; + + return TokenMatchType { + best_match->get_symbol(), + std::move(value), + static_cast(longest_match), + LineSpecialization { + _current_line, + static_cast(_current_offset - longest_match), + static_cast(longest_match) + } + }; + } + else { + debug_tokenizer("At the end of input"); + } + + // There is still something on stack but we've reached the end and noone popped it so return end symbol to parser + return _grammar->get_end_of_input_symbol(); + } + + return std::nullopt; + } + + void enter_state(const std::string& state) + { + _current_state = get_state_info(state); + assert(_current_state && "Transition to unknown state in tokenizer"); + } + +private: + StateInfoType* get_or_make_state_info(const std::string& name) + { + auto itr = _state_info.find(name); + if (itr == _state_info.end()) + std::tie(itr, std::ignore) = _state_info.emplace(name, StateInfoType{ + name, + std::make_unique(re2::RE2::DefaultOptions, re2::RE2::Anchor::ANCHOR_START), + std::vector{} + }); + return &itr->second; + } + + StateInfoType* get_state_info(const std::string& name) + { + auto itr = _state_info.find(name); + if (itr == _state_info.end()) + return nullptr; + return &itr->second; + } + + const GrammarType* _grammar; + std::vector> _tokens; + + std::unordered_map _state_info; + std::vector _input_stack; + StateInfoType* _current_state; + CallbackType _global_action; + std::uint32_t _current_line; + std::uint16_t _current_offset; +}; + +} // namespace pog diff --git a/src/Pog/Types/StateAndRule.hpp b/src/Pog/Types/StateAndRule.hpp new file mode 100644 index 00000000..2a8cc2a2 --- /dev/null +++ b/src/Pog/Types/StateAndRule.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +namespace pog { + +template +struct StateAndRule +{ + const State* state; + const Rule* rule; + + bool operator==(const StateAndRule& rhs) const + { + return state->get_index() == rhs.state->get_index() && rule->get_index() == rhs.rule->get_index(); + } + + bool operator!=(const StateAndRule& rhs) const + { + return !(*this == rhs); + } +}; + +} // namespace pog + +namespace std { + +template +struct hash> +{ + std::size_t operator()(const pog::StateAndRule& sr) const + { + return pog::hash_combine(sr.state->get_index(), sr.rule->get_index()); + } +}; + +} diff --git a/src/Pog/Types/StateAndSymbol.hpp b/src/Pog/Types/StateAndSymbol.hpp new file mode 100644 index 00000000..271124f6 --- /dev/null +++ b/src/Pog/Types/StateAndSymbol.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +namespace pog { + +template +struct StateAndSymbol +{ + const State* state; + const Symbol* symbol; + + bool operator==(const StateAndSymbol& rhs) const + { + return state->get_index() == rhs.state->get_index() && symbol->get_index() == rhs.symbol->get_index(); + } + + bool operator!=(const StateAndSymbol& rhs) const + { + return !(*this == rhs); + } +}; + +} // namespace pog + +namespace std { + +template +struct hash> +{ + std::size_t operator()(const pog::StateAndSymbol& ss) const + { + return pog::hash_combine(ss.state->get_index(), ss.symbol->get_index()); + } +}; + +} diff --git a/src/Pog/Utils.hpp b/src/Pog/Utils.hpp new file mode 100644 index 00000000..f637ba17 --- /dev/null +++ b/src/Pog/Utils.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace pog { + +template +OutputIterator transform_if(InputIterator first1, InputIterator last1, OutputIterator result, Pred pred, UnaryOperator op) +{ + while (first1 != last1) + { + if (pred(*first1)) { + *result = op(*first1); + ++result; + } + ++first1; + } + return result; +} + +template +T accumulate_if(InputIterator first, InputIterator last, T init, Pred pred, BinaryOperation op) +{ + for (; first != last; ++first) + { + if (pred(*first)) + init = op(std::move(init), *first); + } + return init; +} + +inline void hash_combine(std::size_t&) { } + +template +inline void hash_combine(std::size_t& seed, const T& v, const Rest&... rest) { + seed ^= std::hash{}(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + hash_combine(seed, rest...); +} + +template +inline std::size_t hash_combine(const Rest&... rest) +{ + std::size_t seed = 0; + hash_combine(seed, rest...); + return seed; +} + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +template +auto visit_with(Variant& v, Fs&&... fs) +{ + return std::visit(overloaded{ + std::forward(fs)... + }, v); +} + +template +inline std::string current_time(FormatT&& format) +{ + auto now = std::time(nullptr); + auto tm = std::localtime(&now); + + std::ostringstream ss; + ss << std::put_time(tm, std::forward(format)); + return ss.str(); +} + +} // namespace pog diff --git a/src/pch.hpp b/src/pch.hpp index 55951a1d..07388a4e 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -35,3 +35,4 @@ #include #include #include +#include diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index 32a90565..00000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -include(../cmake/Variables.cmake) - -set(TESTS_INCLUDE_DIR - ${ROOT_DIR}/dist/eternal/include - ${ROOT_DIR}/src/Emulator - ${ROOT_DIR}/test) - -add_executable(modular_testing ${ROOT_DIR}/test/main.cpp ${SOURCES_modular_testing}) -target_link_libraries(modular_testing emulator-core assembler-core gtest pthread) -target_include_directories(modular_testing PUBLIC ${TESTS_INCLUDE_DIR}) -target_precompile_headers(modular_testing PRIVATE pch.hpp) - -add_executable(integration_testing ${ROOT_DIR}/test/main.cpp ${SOURCES_integration_testing}) -target_link_libraries(integration_testing emulator-core assembler-core gtest pthread) -target_include_directories(integration_testing PUBLIC ${TESTS_INCLUDE_DIR}) -target_precompile_headers(integration_testing PRIVATE pch.hpp) - -add_custom_target(run-all-tests-github - ${CMAKE_BINARY_DIR}/modular_testing --gtest_brief=1 - COMMAND - ${CMAKE_BINARY_DIR}/integration_testing --gtest_brief=1 - DEPENDS - build-all-tests-github -) - -add_custom_target(build-all-tests-github - DEPENDS - modular_testing - integration_testing -) diff --git a/test/Integration/EmulatorCore/CPU/CPU_CALLE.cpp b/test/Integration/EmulatorCore/CPU/CPU_CALLE.cpp deleted file mode 100644 index c8d0f436..00000000 --- a/test/Integration/EmulatorCore/CPU/CPU_CALLE.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include - -#include - - -TEST_F(CPU_TEST, INSTR_CALLE_R_TRUE) { - cpu.mem_controller->Load16(*cpu.xip, HyperCPU::Opcode::CALLE); - cpu.mem_controller->Load8(*cpu.xip + 2, (HyperCPU::Mode::b64 << 4) | HyperCPU::OperandTypes::R); - cpu.mem_controller->Load8(*cpu.xip + 3, HyperCPU::Registers::X0); - cpu.mem_controller->Load16(1536, HyperCPU::Opcode::HALT); - cpu.mem_controller->Load8(1538, HyperCPU::OperandTypes::NONE); - *cpu.xbp = 512; - *cpu.xsp = *cpu.xbp; - *cpu.x0 = 1536; - cpu.zrf = 1; - - - cpu.Run(); - - ASSERT_EQ(*cpu.xip, 1539); -} - -TEST_F(CPU_TEST, INSTR_CALLE_R_FALSE) { - cpu.mem_controller->Load16(*cpu.xip, HyperCPU::Opcode::CALLE); - cpu.mem_controller->Load8(*cpu.xip + 2, (HyperCPU::Mode::b64 << 4) | HyperCPU::OperandTypes::R); - cpu.mem_controller->Load8(*cpu.xip + 3, HyperCPU::Registers::X0); - cpu.mem_controller->Load16(*cpu.xip + 4, HyperCPU::Opcode::HALT); - cpu.mem_controller->Load8(*cpu.xip + 6, HyperCPU::OperandTypes::NONE); - cpu.mem_controller->Load16(1536, HyperCPU::Opcode::HALT); - cpu.mem_controller->Load8(1538, HyperCPU::OperandTypes::NONE); - *cpu.xbp = 512; - *cpu.xsp = *cpu.xbp; - *cpu.x0 = 1536; - - - cpu.Run(); - - ASSERT_EQ(*cpu.xip, 7); -} - -TEST_F(CPU_TEST, INSTR_CALLE_IMM_TRUE) { - cpu.mem_controller->Load16(*cpu.xip, HyperCPU::Opcode::CALLE); - cpu.mem_controller->Load8(*cpu.xip + 2, (HyperCPU::Mode::b64 << 4) | HyperCPU::OperandTypes::IMM); - cpu.mem_controller->Load64(*cpu.xip + 3, 1536); - cpu.mem_controller->Load16(1536, HyperCPU::Opcode::HALT); - cpu.mem_controller->Load8(1538, HyperCPU::OperandTypes::NONE); - *cpu.xbp = 512; - *cpu.xsp = *cpu.xbp; - cpu.zrf = 1; - - cpu.Run(); - - ASSERT_EQ(*cpu.xip, 1539); -} - -TEST_F(CPU_TEST, INSTR_CALLE_IMM_FALSE) { - cpu.mem_controller->Load16(*cpu.xip, HyperCPU::Opcode::CALLE); - cpu.mem_controller->Load8(*cpu.xip + 2, (HyperCPU::Mode::b64 << 4) | HyperCPU::OperandTypes::IMM); - cpu.mem_controller->Load64(*cpu.xip + 3, 1536); - cpu.mem_controller->Load16(*cpu.xip + 11, HyperCPU::Opcode::HALT); - cpu.mem_controller->Load8(*cpu.xip + 13, HyperCPU::OperandTypes::NONE); - cpu.mem_controller->Load16(1536, HyperCPU::Opcode::HALT); - cpu.mem_controller->Load8(1538, HyperCPU::OperandTypes::NONE); - *cpu.xbp = 512; - *cpu.xsp = *cpu.xbp; - - cpu.Run(); - - ASSERT_EQ(*cpu.xip, 14); -} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp deleted file mode 100644 index 7c30f193..00000000 --- a/test/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} diff --git a/test/pch.hpp b/test/pch.hpp deleted file mode 100644 index 2dacb8ab..00000000 --- a/test/pch.hpp +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Precompiled headers - * ! Only add headers that will not be modified ! - */ -#include -#include -#include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..bb1fd19a --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +# FIXME: Add separate precompiled headers here, too +cmake_minimum_required(VERSION 3.25) + + +include(GoogleTest) + +add_subdirectory(Integration) +add_subdirectory(Modular) +add_subdirectory(Pog) +add_subdirectory(Python/Buildsystem) diff --git a/test/Integration/AssemblerCore/AssemblerFail.cpp b/tests/Integration/AssemblerCore/AssemblerFail.cpp similarity index 100% rename from test/Integration/AssemblerCore/AssemblerFail.cpp rename to tests/Integration/AssemblerCore/AssemblerFail.cpp diff --git a/test/Integration/AssemblerCore/AssemblerSuccess.cpp b/tests/Integration/AssemblerCore/AssemblerSuccess.cpp similarity index 100% rename from test/Integration/AssemblerCore/AssemblerSuccess.cpp rename to tests/Integration/AssemblerCore/AssemblerSuccess.cpp diff --git a/test/Integration/AssemblerCore/FullAssembler.cpp b/tests/Integration/AssemblerCore/FullAssembler.cpp similarity index 100% rename from test/Integration/AssemblerCore/FullAssembler.cpp rename to tests/Integration/AssemblerCore/FullAssembler.cpp diff --git a/test/Integration/AssemblerCore/TwoOperandsSuccess.cpp b/tests/Integration/AssemblerCore/TwoOperandsSuccess.cpp similarity index 100% rename from test/Integration/AssemblerCore/TwoOperandsSuccess.cpp rename to tests/Integration/AssemblerCore/TwoOperandsSuccess.cpp diff --git a/tests/Integration/CMakeLists.txt b/tests/Integration/CMakeLists.txt new file mode 100644 index 00000000..f45fabc2 --- /dev/null +++ b/tests/Integration/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.25) + + +add_executable( + integration_testing + ${CMAKE_SOURCE_DIR}/tests/main.cpp + EmulatorCore/CPU/CPU_CALLE.cpp + EmulatorCore/CPU/CPU_HID.cpp + EmulatorCore/CPU/CPU_READ.cpp + EmulatorCore/CPU/CPU_AND.cpp + EmulatorCore/CPU/CPU_INC.cpp + EmulatorCore/CPU/CPU_COVF.cpp + EmulatorCore/CPU/CPU_CALLL.cpp + EmulatorCore/CPU/CPU_ADC.cpp + EmulatorCore/CPU/CPU_ADD.cpp + EmulatorCore/CPU/CPU_MOV.cpp + EmulatorCore/CPU/CPU_CMP.cpp + EmulatorCore/CPU/CPU_OR.cpp + EmulatorCore/CPU/CPU_SHFL.cpp + EmulatorCore/CPU/CPU_POP.cpp + EmulatorCore/CPU/CPU_DEC.cpp + EmulatorCore/CPU/CPU_JML.cpp + EmulatorCore/CPU/CPU_SHFR.cpp + EmulatorCore/CPU/CPU_CALL.cpp + EmulatorCore/CPU/CPU_JMP.cpp + EmulatorCore/CPU/CPU_INTR.cpp + EmulatorCore/CPU/CPU_WRITE.cpp + EmulatorCore/CPU/CPU_IRET.cpp + EmulatorCore/CPU/CPU_MUL.cpp + EmulatorCore/CPU/CPU_JME.cpp + EmulatorCore/CPU/CPU_CALLGR.cpp + EmulatorCore/CPU/CPU_ANDN.cpp + EmulatorCore/CPU/CPU_CCRF.cpp + EmulatorCore/CPU/CPU_LOIVT.cpp + EmulatorCore/CPU/CPU_SUB.cpp + EmulatorCore/CPU/CPU_PUSH.cpp + EmulatorCore/CPU/CPU_DIV.cpp + EmulatorCore/CPU/OperandsEvaluation.cpp + EmulatorCore/CPU/CPU_JMGR.cpp + EmulatorCore/CPU/CPU_CUDF.cpp + EmulatorCore/CPU/CPU_BSWAP.cpp + EmulatorCore/Exceptions/Exceptions.cpp + AssemblerCore/TwoOperandsSuccess.cpp + AssemblerCore/AssemblerSuccess.cpp + AssemblerCore/FullAssembler.cpp + AssemblerCore/AssemblerFail.cpp +) +target_link_libraries( + integration_testing + PRIVATE + emulator-core + assembler-core + GTest::gtest + pthread +) +target_include_directories( + integration_testing + PRIVATE + ${CMAKE_SOURCE_DIR}/tests +) + +gtest_add_tests( + TARGET + integration_testing + ARGS + --gtest_brief=1 +) \ No newline at end of file diff --git a/test/Integration/EmulatorCore/CPU/CPU_ADC.cpp b/tests/Integration/EmulatorCore/CPU/CPU_ADC.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_ADC.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_ADC.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_ADD.cpp b/tests/Integration/EmulatorCore/CPU/CPU_ADD.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_ADD.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_ADD.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_AND.cpp b/tests/Integration/EmulatorCore/CPU/CPU_AND.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_AND.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_AND.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_ANDN.cpp b/tests/Integration/EmulatorCore/CPU/CPU_ANDN.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_ANDN.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_ANDN.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_BSWAP.cpp b/tests/Integration/EmulatorCore/CPU/CPU_BSWAP.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_BSWAP.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_BSWAP.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_CALL.cpp b/tests/Integration/EmulatorCore/CPU/CPU_CALL.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_CALL.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_CALL.cpp diff --git a/tests/Integration/EmulatorCore/CPU/CPU_CALLE.cpp b/tests/Integration/EmulatorCore/CPU/CPU_CALLE.cpp new file mode 100644 index 00000000..ba8194a8 --- /dev/null +++ b/tests/Integration/EmulatorCore/CPU/CPU_CALLE.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include + +TEST_F(CPU_TEST, INSTR_CALLE_R_TRUE) { + cpu.mem_controller->Load16(*cpu.xip, HyperCPU::Opcode::CALLE); + cpu.mem_controller->Load8(*cpu.xip + 2, (HyperCPU::Mode::b64 << 4) | HyperCPU::OperandTypes::R); + cpu.mem_controller->Load8(*cpu.xip + 3, HyperCPU::Registers::X0); + cpu.mem_controller->Load16(1536, HyperCPU::Opcode::HALT); + cpu.mem_controller->Load8(1538, HyperCPU::OperandTypes::NONE); + *cpu.xbp = 512; + *cpu.xsp = *cpu.xbp; + *cpu.x0 = 1536; + cpu.zrf = 1; + + cpu.Run(); + + ASSERT_EQ(*cpu.xip, 1539); +} + +TEST_F(CPU_TEST, INSTR_CALLE_R_FALSE) { + cpu.mem_controller->Load16(*cpu.xip, HyperCPU::Opcode::CALLE); + cpu.mem_controller->Load8(*cpu.xip + 2, (HyperCPU::Mode::b64 << 4) | HyperCPU::OperandTypes::R); + cpu.mem_controller->Load8(*cpu.xip + 3, HyperCPU::Registers::X0); + cpu.mem_controller->Load16(*cpu.xip + 4, HyperCPU::Opcode::HALT); + cpu.mem_controller->Load8(*cpu.xip + 6, HyperCPU::OperandTypes::NONE); + cpu.mem_controller->Load16(1536, HyperCPU::Opcode::HALT); + cpu.mem_controller->Load8(1538, HyperCPU::OperandTypes::NONE); + *cpu.xbp = 512; + *cpu.xsp = *cpu.xbp; + *cpu.x0 = 1536; + + cpu.Run(); + + ASSERT_EQ(*cpu.xip, 7); +} + +TEST_F(CPU_TEST, INSTR_CALLE_IMM_TRUE) { + cpu.mem_controller->Load16(*cpu.xip, HyperCPU::Opcode::CALLE); + cpu.mem_controller->Load8(*cpu.xip + 2, (HyperCPU::Mode::b64 << 4) | HyperCPU::OperandTypes::IMM); + cpu.mem_controller->Load64(*cpu.xip + 3, 1536); + cpu.mem_controller->Load16(1536, HyperCPU::Opcode::HALT); + cpu.mem_controller->Load8(1538, HyperCPU::OperandTypes::NONE); + *cpu.xbp = 512; + *cpu.xsp = *cpu.xbp; + cpu.zrf = 1; + + cpu.Run(); + + ASSERT_EQ(*cpu.xip, 1539); +} + +TEST_F(CPU_TEST, INSTR_CALLE_IMM_FALSE) { + cpu.mem_controller->Load16(*cpu.xip, HyperCPU::Opcode::CALLE); + cpu.mem_controller->Load8(*cpu.xip + 2, (HyperCPU::Mode::b64 << 4) | HyperCPU::OperandTypes::IMM); + cpu.mem_controller->Load64(*cpu.xip + 3, 1536); + cpu.mem_controller->Load16(*cpu.xip + 11, HyperCPU::Opcode::HALT); + cpu.mem_controller->Load8(*cpu.xip + 13, HyperCPU::OperandTypes::NONE); + cpu.mem_controller->Load16(1536, HyperCPU::Opcode::HALT); + cpu.mem_controller->Load8(1538, HyperCPU::OperandTypes::NONE); + *cpu.xbp = 512; + *cpu.xsp = *cpu.xbp; + + cpu.Run(); + + ASSERT_EQ(*cpu.xip, 14); +} \ No newline at end of file diff --git a/test/Integration/EmulatorCore/CPU/CPU_CALLGR.cpp b/tests/Integration/EmulatorCore/CPU/CPU_CALLGR.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_CALLGR.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_CALLGR.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_CALLL.cpp b/tests/Integration/EmulatorCore/CPU/CPU_CALLL.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_CALLL.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_CALLL.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_CCRF.cpp b/tests/Integration/EmulatorCore/CPU/CPU_CCRF.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_CCRF.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_CCRF.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_CMP.cpp b/tests/Integration/EmulatorCore/CPU/CPU_CMP.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_CMP.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_CMP.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_COVF.cpp b/tests/Integration/EmulatorCore/CPU/CPU_COVF.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_COVF.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_COVF.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_CUDF.cpp b/tests/Integration/EmulatorCore/CPU/CPU_CUDF.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_CUDF.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_CUDF.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_DEC.cpp b/tests/Integration/EmulatorCore/CPU/CPU_DEC.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_DEC.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_DEC.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_DIV.cpp b/tests/Integration/EmulatorCore/CPU/CPU_DIV.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_DIV.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_DIV.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_HID.cpp b/tests/Integration/EmulatorCore/CPU/CPU_HID.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_HID.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_HID.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_INC.cpp b/tests/Integration/EmulatorCore/CPU/CPU_INC.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_INC.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_INC.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_INTR.cpp b/tests/Integration/EmulatorCore/CPU/CPU_INTR.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_INTR.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_INTR.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_IRET.cpp b/tests/Integration/EmulatorCore/CPU/CPU_IRET.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_IRET.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_IRET.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_JME.cpp b/tests/Integration/EmulatorCore/CPU/CPU_JME.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_JME.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_JME.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_JMGR.cpp b/tests/Integration/EmulatorCore/CPU/CPU_JMGR.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_JMGR.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_JMGR.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_JML.cpp b/tests/Integration/EmulatorCore/CPU/CPU_JML.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_JML.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_JML.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_JMP.cpp b/tests/Integration/EmulatorCore/CPU/CPU_JMP.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_JMP.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_JMP.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_LOIVT.cpp b/tests/Integration/EmulatorCore/CPU/CPU_LOIVT.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_LOIVT.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_LOIVT.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_MOV.cpp b/tests/Integration/EmulatorCore/CPU/CPU_MOV.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_MOV.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_MOV.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_MUL.cpp b/tests/Integration/EmulatorCore/CPU/CPU_MUL.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_MUL.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_MUL.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_OR.cpp b/tests/Integration/EmulatorCore/CPU/CPU_OR.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_OR.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_OR.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_POP.cpp b/tests/Integration/EmulatorCore/CPU/CPU_POP.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_POP.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_POP.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_PUSH.cpp b/tests/Integration/EmulatorCore/CPU/CPU_PUSH.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_PUSH.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_PUSH.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_READ.cpp b/tests/Integration/EmulatorCore/CPU/CPU_READ.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_READ.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_READ.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_SHFL.cpp b/tests/Integration/EmulatorCore/CPU/CPU_SHFL.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_SHFL.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_SHFL.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_SHFR.cpp b/tests/Integration/EmulatorCore/CPU/CPU_SHFR.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_SHFR.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_SHFR.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_SUB.cpp b/tests/Integration/EmulatorCore/CPU/CPU_SUB.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_SUB.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_SUB.cpp diff --git a/test/Integration/EmulatorCore/CPU/CPU_WRITE.cpp b/tests/Integration/EmulatorCore/CPU/CPU_WRITE.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/CPU_WRITE.cpp rename to tests/Integration/EmulatorCore/CPU/CPU_WRITE.cpp diff --git a/test/Integration/EmulatorCore/CPU/OperandsEvaluation.cpp b/tests/Integration/EmulatorCore/CPU/OperandsEvaluation.cpp similarity index 100% rename from test/Integration/EmulatorCore/CPU/OperandsEvaluation.cpp rename to tests/Integration/EmulatorCore/CPU/OperandsEvaluation.cpp diff --git a/test/Integration/EmulatorCore/Exceptions/Exceptions.cpp b/tests/Integration/EmulatorCore/Exceptions/Exceptions.cpp similarity index 100% rename from test/Integration/EmulatorCore/Exceptions/Exceptions.cpp rename to tests/Integration/EmulatorCore/Exceptions/Exceptions.cpp diff --git a/test/Modular/AssemblerCore/Parser/Operands.cpp b/tests/Modular/AssemblerCore/Parser/Operands.cpp similarity index 100% rename from test/Modular/AssemblerCore/Parser/Operands.cpp rename to tests/Modular/AssemblerCore/Parser/Operands.cpp diff --git a/test/Modular/AssemblerCore/Parser/Statements.cpp b/tests/Modular/AssemblerCore/Parser/Statements.cpp similarity index 100% rename from test/Modular/AssemblerCore/Parser/Statements.cpp rename to tests/Modular/AssemblerCore/Parser/Statements.cpp diff --git a/test/Modular/AssemblerCore/Parser/Tokens.cpp b/tests/Modular/AssemblerCore/Parser/Tokens.cpp similarity index 99% rename from test/Modular/AssemblerCore/Parser/Tokens.cpp rename to tests/Modular/AssemblerCore/Parser/Tokens.cpp index b64e2378..9e3cfbd2 100644 --- a/test/Modular/AssemblerCore/Parser/Tokens.cpp +++ b/tests/Modular/AssemblerCore/Parser/Tokens.cpp @@ -1,4 +1,4 @@ -#include "pog/line_spec.h" +#include #include TEST_F(ASM_PARSER_TEST, TOKEN_STRING) { diff --git a/tests/Modular/CMakeLists.txt b/tests/Modular/CMakeLists.txt new file mode 100644 index 00000000..8ef88049 --- /dev/null +++ b/tests/Modular/CMakeLists.txt @@ -0,0 +1,141 @@ +cmake_minimum_required(VERSION 3.25) + + +add_executable( + modular_testing + ${CMAKE_SOURCE_DIR}/tests/main.cpp + EmulatorCore/Decoding/CMPInstr/R_R.cpp + EmulatorCore/Decoding/CMPInstr/RM_IMM.cpp + EmulatorCore/Decoding/CMPInstr/RM_M.cpp + EmulatorCore/Decoding/CMPInstr/M_R.cpp + EmulatorCore/Decoding/CMPInstr/RM_R.cpp + EmulatorCore/Decoding/CMPInstr/Unsupported.cpp + EmulatorCore/Decoding/CMPInstr/R_RM.cpp + EmulatorCore/Decoding/CMPInstr/R_IMM.cpp + EmulatorCore/Decoding/CMPInstr/R_M.cpp + EmulatorCore/Decoding/SHFLInstr/R_R.cpp + EmulatorCore/Decoding/SHFLInstr/Unsupported.cpp + EmulatorCore/Decoding/SHFLInstr/R_IMM.cpp + EmulatorCore/Decoding/SHFRInstr/R_R.cpp + EmulatorCore/Decoding/SHFRInstr/Unsupported.cpp + EmulatorCore/Decoding/SHFRInstr/R_IMM.cpp + EmulatorCore/Decoding/JMLInstr/test_decoder_r.cpp + EmulatorCore/Decoding/JMLInstr/test_decoder_imm.cpp + EmulatorCore/Decoding/JMLInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/BSWAPInstr/R.cpp + EmulatorCore/Decoding/BSWAPInstr/Unsupported.cpp + EmulatorCore/Decoding/CALLGRInstr/test_decoder_r.cpp + EmulatorCore/Decoding/CALLGRInstr/test_decoder_imm.cpp + EmulatorCore/Decoding/CALLGRInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/MULInstr/R_R.cpp + EmulatorCore/Decoding/MULInstr/Unsupported.cpp + EmulatorCore/Decoding/MULInstr/R_RM.cpp + EmulatorCore/Decoding/MULInstr/R_IMM.cpp + EmulatorCore/Decoding/MULInstr/R_M.cpp + EmulatorCore/Decoding/WRITEInstr/R_R.cpp + EmulatorCore/Decoding/WRITEInstr/Unsupported.cpp + EmulatorCore/Decoding/WRITEInstr/R_IMM.cpp + EmulatorCore/Decoding/DECInstr/R.cpp + EmulatorCore/Decoding/DECInstr/Unsupported.cpp + EmulatorCore/Decoding/CUDFInstr/Unsupported.cpp + EmulatorCore/Decoding/CUDFInstr/None.cpp + EmulatorCore/Decoding/CALLEInstr/test_decoder_r.cpp + EmulatorCore/Decoding/CALLEInstr/test_decoder_imm.cpp + EmulatorCore/Decoding/CALLEInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/INCInstr/R.cpp + EmulatorCore/Decoding/INCInstr/Unsupported.cpp + EmulatorCore/Decoding/COVFInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/COVFInstr/test_decoder_none.cpp + EmulatorCore/Decoding/JMGRInstr/test_decoder_r.cpp + EmulatorCore/Decoding/JMGRInstr/test_decoder_imm.cpp + EmulatorCore/Decoding/JMGRInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/JMPInstr/test_decoder_r.cpp + EmulatorCore/Decoding/JMPInstr/test_decoder_imm.cpp + EmulatorCore/Decoding/JMPInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/SUBInstr/R_R.cpp + EmulatorCore/Decoding/SUBInstr/Unsupported.cpp + EmulatorCore/Decoding/SUBInstr/R_RM.cpp + EmulatorCore/Decoding/SUBInstr/R_IMM.cpp + EmulatorCore/Decoding/SUBInstr/R_M.cpp + EmulatorCore/Decoding/ANDInstr/R_R.cpp + EmulatorCore/Decoding/ANDInstr/Unsupported.cpp + EmulatorCore/Decoding/ANDInstr/R_RM.cpp + EmulatorCore/Decoding/ANDInstr/R_IMM.cpp + EmulatorCore/Decoding/ANDInstr/R_M.cpp + EmulatorCore/Decoding/MOVInstr/R_R.cpp + EmulatorCore/Decoding/MOVInstr/RM_IMM.cpp + EmulatorCore/Decoding/MOVInstr/RM_M.cpp + EmulatorCore/Decoding/MOVInstr/M_R.cpp + EmulatorCore/Decoding/MOVInstr/RM_R.cpp + EmulatorCore/Decoding/MOVInstr/Unsupported.cpp + EmulatorCore/Decoding/MOVInstr/R_RM.cpp + EmulatorCore/Decoding/MOVInstr/R_IMM.cpp + EmulatorCore/Decoding/MOVInstr/R_M.cpp + EmulatorCore/Decoding/ADDInstr/R_R.cpp + EmulatorCore/Decoding/ADDInstr/Unsupported.cpp + EmulatorCore/Decoding/ADDInstr/R_RM.cpp + EmulatorCore/Decoding/ADDInstr/R_IMM.cpp + EmulatorCore/Decoding/ADDInstr/R_M.cpp + EmulatorCore/Decoding/HIDInstr/Unsupported.cpp + EmulatorCore/Decoding/HIDInstr/None.cpp + EmulatorCore/Decoding/JMEInstr/test_decoder_r.cpp + EmulatorCore/Decoding/JMEInstr/test_decoder_imm.cpp + EmulatorCore/Decoding/JMEInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/ANDNInstr/R_R.cpp + EmulatorCore/Decoding/ANDNInstr/Unsupported.cpp + EmulatorCore/Decoding/ANDNInstr/R_RM.cpp + EmulatorCore/Decoding/ANDNInstr/R_IMM.cpp + EmulatorCore/Decoding/ANDNInstr/R_M.cpp + EmulatorCore/Decoding/DIVInstr/R.cpp + EmulatorCore/Decoding/DIVInstr/Unsupported.cpp + EmulatorCore/Decoding/CALLLInstr/test_decoder_r.cpp + EmulatorCore/Decoding/CALLLInstr/test_decoder_imm.cpp + EmulatorCore/Decoding/CALLLInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/CALLInstr/test_decoder_r.cpp + EmulatorCore/Decoding/CALLInstr/test_decoder_imm.cpp + EmulatorCore/Decoding/CALLInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/ADCInstr/R_R.cpp + EmulatorCore/Decoding/ADCInstr/Unsupported.cpp + EmulatorCore/Decoding/ADCInstr/R_RM.cpp + EmulatorCore/Decoding/ADCInstr/R_IMM.cpp + EmulatorCore/Decoding/ADCInstr/R_M.cpp + EmulatorCore/Decoding/Features/AddressAddition.cpp + EmulatorCore/Decoding/HALTInstr/Unsupported.cpp + EmulatorCore/Decoding/HALTInstr/None.cpp + EmulatorCore/Decoding/ORInstr/R_R.cpp + EmulatorCore/Decoding/ORInstr/Unsupported.cpp + EmulatorCore/Decoding/ORInstr/R_RM.cpp + EmulatorCore/Decoding/ORInstr/R_IMM.cpp + EmulatorCore/Decoding/ORInstr/R_M.cpp + EmulatorCore/Decoding/READInstr/IMM.cpp + EmulatorCore/Decoding/READInstr/Unsupported.cpp + EmulatorCore/Decoding/CCRFInstr/test_decoder_unsupported.cpp + EmulatorCore/Decoding/CCRFInstr/test_decoder_none.cpp + EmulatorCore/MemoryControllers/ST.cpp + EmulatorCore/CPUInit/CPUInit.cpp + EmulatorCore/Stack/Stack.cpp + AssemblerCore/Parser/Statements.cpp + AssemblerCore/Parser/Operands.cpp + AssemblerCore/Parser/Tokens.cpp +) +target_link_libraries( + modular_testing + PRIVATE + emulator-core + assembler-core + GTest::gtest + pthread + pch +) +target_include_directories( + modular_testing + PRIVATE + ${CMAKE_SOURCE_DIR}/tests +) + +gtest_add_tests( + TARGET + modular_testing + EXTRA_ARGS + --gtest_brief=1 +) diff --git a/test/Modular/EmulatorCore/CPUInit/CPUInit.cpp b/tests/Modular/EmulatorCore/CPUInit/CPUInit.cpp similarity index 98% rename from test/Modular/EmulatorCore/CPUInit/CPUInit.cpp rename to tests/Modular/EmulatorCore/CPUInit/CPUInit.cpp index 90b36c9d..57ee3cc3 100644 --- a/test/Modular/EmulatorCore/CPUInit/CPUInit.cpp +++ b/tests/Modular/EmulatorCore/CPUInit/CPUInit.cpp @@ -1,5 +1,5 @@ #include "Logger/Logger.hpp" -#include +#include #define private public #include diff --git a/test/Modular/EmulatorCore/Decoding/ADCInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/ADCInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADCInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/ADCInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADCInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/ADCInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADCInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/ADCInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADCInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/ADCInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADCInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/ADCInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADCInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/ADCInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADCInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/ADCInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADCInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/ADCInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADCInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/ADCInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADDInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/ADDInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADDInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/ADDInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADDInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/ADDInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADDInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/ADDInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADDInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/ADDInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADDInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/ADDInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADDInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/ADDInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADDInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/ADDInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ADDInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/ADDInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ADDInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/ADDInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/ANDInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/ANDInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/ANDInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/ANDInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/ANDInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDNInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/ANDNInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDNInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDNInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDNInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/ANDNInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDNInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDNInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDNInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/ANDNInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDNInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDNInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDNInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/ANDNInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDNInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDNInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ANDNInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/ANDNInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ANDNInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/ANDNInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/BSWAPInstr/R.cpp b/tests/Modular/EmulatorCore/Decoding/BSWAPInstr/R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/BSWAPInstr/R.cpp rename to tests/Modular/EmulatorCore/Decoding/BSWAPInstr/R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/BSWAPInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/BSWAPInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/BSWAPInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/BSWAPInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_imm.cpp b/tests/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_imm.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_imm.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_imm.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_r.cpp b/tests/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_r.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_r.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_r.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLEInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_imm.cpp b/tests/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_imm.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_imm.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_imm.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_r.cpp b/tests/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_r.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_r.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_r.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLGRInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_imm.cpp b/tests/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_imm.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_imm.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_imm.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_r.cpp b/tests/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_r.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_r.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_r.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_imm.cpp b/tests/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_imm.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_imm.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_imm.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_r.cpp b/tests/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_r.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_r.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_r.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/CALLLInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CCRFInstr/test_decoder_none.cpp b/tests/Modular/EmulatorCore/Decoding/CCRFInstr/test_decoder_none.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CCRFInstr/test_decoder_none.cpp rename to tests/Modular/EmulatorCore/Decoding/CCRFInstr/test_decoder_none.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CCRFInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/CCRFInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CCRFInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/CCRFInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/M_R.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/M_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/M_R.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/M_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/RM_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/RM_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/RM_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/RM_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/RM_M.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/RM_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/RM_M.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/RM_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/RM_R.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/RM_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/RM_R.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/RM_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CMPInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/CMPInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CMPInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/CMPInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/COVFInstr/test_decoder_none.cpp b/tests/Modular/EmulatorCore/Decoding/COVFInstr/test_decoder_none.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/COVFInstr/test_decoder_none.cpp rename to tests/Modular/EmulatorCore/Decoding/COVFInstr/test_decoder_none.cpp diff --git a/test/Modular/EmulatorCore/Decoding/COVFInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/COVFInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/COVFInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/COVFInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CUDFInstr/None.cpp b/tests/Modular/EmulatorCore/Decoding/CUDFInstr/None.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CUDFInstr/None.cpp rename to tests/Modular/EmulatorCore/Decoding/CUDFInstr/None.cpp diff --git a/test/Modular/EmulatorCore/Decoding/CUDFInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/CUDFInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/CUDFInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/CUDFInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/DECInstr/R.cpp b/tests/Modular/EmulatorCore/Decoding/DECInstr/R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/DECInstr/R.cpp rename to tests/Modular/EmulatorCore/Decoding/DECInstr/R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/DECInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/DECInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/DECInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/DECInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/DIVInstr/R.cpp b/tests/Modular/EmulatorCore/Decoding/DIVInstr/R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/DIVInstr/R.cpp rename to tests/Modular/EmulatorCore/Decoding/DIVInstr/R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/DIVInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/DIVInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/DIVInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/DIVInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/Features/AddressAddition.cpp b/tests/Modular/EmulatorCore/Decoding/Features/AddressAddition.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/Features/AddressAddition.cpp rename to tests/Modular/EmulatorCore/Decoding/Features/AddressAddition.cpp diff --git a/test/Modular/EmulatorCore/Decoding/HALTInstr/None.cpp b/tests/Modular/EmulatorCore/Decoding/HALTInstr/None.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/HALTInstr/None.cpp rename to tests/Modular/EmulatorCore/Decoding/HALTInstr/None.cpp diff --git a/test/Modular/EmulatorCore/Decoding/HALTInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/HALTInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/HALTInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/HALTInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/HIDInstr/None.cpp b/tests/Modular/EmulatorCore/Decoding/HIDInstr/None.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/HIDInstr/None.cpp rename to tests/Modular/EmulatorCore/Decoding/HIDInstr/None.cpp diff --git a/test/Modular/EmulatorCore/Decoding/HIDInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/HIDInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/HIDInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/HIDInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/INCInstr/R.cpp b/tests/Modular/EmulatorCore/Decoding/INCInstr/R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/INCInstr/R.cpp rename to tests/Modular/EmulatorCore/Decoding/INCInstr/R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/INCInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/INCInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/INCInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/INCInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_imm.cpp b/tests/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_imm.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_imm.cpp rename to tests/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_imm.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_r.cpp b/tests/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_r.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_r.cpp rename to tests/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_r.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/JMEInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_imm.cpp b/tests/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_imm.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_imm.cpp rename to tests/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_imm.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_r.cpp b/tests/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_r.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_r.cpp rename to tests/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_r.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/JMGRInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_imm.cpp b/tests/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_imm.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_imm.cpp rename to tests/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_imm.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_r.cpp b/tests/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_r.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_r.cpp rename to tests/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_r.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/JMLInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_imm.cpp b/tests/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_imm.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_imm.cpp rename to tests/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_imm.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_r.cpp b/tests/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_r.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_r.cpp rename to tests/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_r.cpp diff --git a/test/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/JMPInstr/test_decoder_unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/M_R.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/M_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/M_R.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/M_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/RM_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/RM_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/RM_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/RM_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/RM_M.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/RM_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/RM_M.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/RM_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/RM_R.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/RM_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/RM_R.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/RM_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MOVInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/MOVInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MOVInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/MOVInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MULInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/MULInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MULInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/MULInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MULInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/MULInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MULInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/MULInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MULInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/MULInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MULInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/MULInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MULInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/MULInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MULInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/MULInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/MULInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/MULInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/MULInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/MULInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ORInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/ORInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ORInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/ORInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ORInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/ORInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ORInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/ORInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ORInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/ORInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ORInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/ORInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ORInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/ORInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ORInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/ORInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/ORInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/ORInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/ORInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/ORInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/READInstr/IMM.cpp b/tests/Modular/EmulatorCore/Decoding/READInstr/IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/READInstr/IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/READInstr/IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/READInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/READInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/READInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/READInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SHFLInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/SHFLInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SHFLInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/SHFLInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SHFLInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/SHFLInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SHFLInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/SHFLInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SHFLInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/SHFLInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SHFLInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/SHFLInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SHFRInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/SHFRInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SHFRInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/SHFRInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SHFRInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/SHFRInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SHFRInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/SHFRInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SHFRInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/SHFRInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SHFRInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/SHFRInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SUBInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/SUBInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SUBInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/SUBInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SUBInstr/R_M.cpp b/tests/Modular/EmulatorCore/Decoding/SUBInstr/R_M.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SUBInstr/R_M.cpp rename to tests/Modular/EmulatorCore/Decoding/SUBInstr/R_M.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SUBInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/SUBInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SUBInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/SUBInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SUBInstr/R_RM.cpp b/tests/Modular/EmulatorCore/Decoding/SUBInstr/R_RM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SUBInstr/R_RM.cpp rename to tests/Modular/EmulatorCore/Decoding/SUBInstr/R_RM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/SUBInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/SUBInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/SUBInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/SUBInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/Decoding/WRITEInstr/R_IMM.cpp b/tests/Modular/EmulatorCore/Decoding/WRITEInstr/R_IMM.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/WRITEInstr/R_IMM.cpp rename to tests/Modular/EmulatorCore/Decoding/WRITEInstr/R_IMM.cpp diff --git a/test/Modular/EmulatorCore/Decoding/WRITEInstr/R_R.cpp b/tests/Modular/EmulatorCore/Decoding/WRITEInstr/R_R.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/WRITEInstr/R_R.cpp rename to tests/Modular/EmulatorCore/Decoding/WRITEInstr/R_R.cpp diff --git a/test/Modular/EmulatorCore/Decoding/WRITEInstr/Unsupported.cpp b/tests/Modular/EmulatorCore/Decoding/WRITEInstr/Unsupported.cpp similarity index 100% rename from test/Modular/EmulatorCore/Decoding/WRITEInstr/Unsupported.cpp rename to tests/Modular/EmulatorCore/Decoding/WRITEInstr/Unsupported.cpp diff --git a/test/Modular/EmulatorCore/MemoryControllers/ST.cpp b/tests/Modular/EmulatorCore/MemoryControllers/ST.cpp similarity index 100% rename from test/Modular/EmulatorCore/MemoryControllers/ST.cpp rename to tests/Modular/EmulatorCore/MemoryControllers/ST.cpp diff --git a/test/Modular/EmulatorCore/Stack/Stack.cpp b/tests/Modular/EmulatorCore/Stack/Stack.cpp similarity index 100% rename from test/Modular/EmulatorCore/Stack/Stack.cpp rename to tests/Modular/EmulatorCore/Stack/Stack.cpp diff --git a/tests/Pog/Automaton.cpp b/tests/Pog/Automaton.cpp new file mode 100644 index 00000000..02eaca91 --- /dev/null +++ b/tests/Pog/Automaton.cpp @@ -0,0 +1,180 @@ +#include + +#include + +#include + +using namespace pog; + +class TestAutomaton : public ::testing::Test +{ +public: + using SetupGrammarTuple = std::tuple; + + TestAutomaton() : grammar() {} + + template + State new_state(const Args&... args) + { + State state; + _new_state(state, args...); + return state; + } + + void _new_state(State& state, const std::string& lhs, const std::vector& left_rhs, const std::vector& right_rhs) + { + _add_item_to_state(state, lhs, left_rhs, right_rhs); + } + + template + void _new_state(State& state, const std::string& lhs, const std::vector& left_rhs, const std::vector& right_rhs, const Args&... args) + { + _add_item_to_state(state, lhs, left_rhs, right_rhs); + _new_state(state, args...); + } + + void _add_item_to_state(State& state, const std::string& lhs, const std::vector& left_rhs, const std::vector& right_rhs) + { + auto lhs_sym = grammar.add_symbol(SymbolKind::Nonterminal, lhs); + auto sym_transform = [this](const auto& name) { + return grammar.add_symbol(std::islower(name[0]) ? SymbolKind::Terminal : SymbolKind::Nonterminal, name); + }; + + std::vector*> left_rhs_syms(left_rhs.size()); + std::vector*> right_rhs_syms(right_rhs.size()); + std::vector*> rhs_syms(left_rhs.size() + right_rhs.size()); + + std::transform(left_rhs.begin(), left_rhs.end(), left_rhs_syms.begin(), sym_transform); + std::transform(right_rhs.begin(), right_rhs.end(), right_rhs_syms.begin(), sym_transform); + std::copy(left_rhs_syms.begin(), left_rhs_syms.end(), rhs_syms.begin()); + std::copy(right_rhs_syms.begin(), right_rhs_syms.end(), rhs_syms.begin() + left_rhs.size()); + + Rule* rule = nullptr; + for (auto& r : grammar.get_rules()) + { + bool rhs_equal = std::equal(r->get_rhs().begin(), r->get_rhs().end(), rhs_syms.begin(), rhs_syms.end(), [](const auto* sym1, const auto* sym2) { + return sym1->get_index() == sym2->get_index(); + }); + if (r->get_lhs() == lhs_sym && rhs_equal) + { + rule = r.get(); + break; + } + } + + if (!rule) + rule = grammar.add_rule(lhs_sym, rhs_syms, [](auto&&...) -> int { return 0; }); + + state.add_item(Item{rule, left_rhs.size()}); + } + + Grammar grammar; +}; + +TEST_F(TestAutomaton, +AddState) { + auto state = new_state( + "S", std::vector{}, std::vector{"a", "S", "b"}, + "S", std::vector{}, std::vector{} + ); + + Automaton a(&grammar); + auto result = a.add_state(std::move(state)); + + EXPECT_TRUE(result.second); + EXPECT_EQ(result.first->to_string(), "S -> <*> a S b\nS -> <*> "); +} + +TEST_F(TestAutomaton, +AddStateUnique) { + auto state1 = new_state( + "S", std::vector{}, std::vector{"a", "S", "b"}, + "S", std::vector{}, std::vector{} + ); + auto state2 = new_state( + "S", std::vector{"a"}, std::vector{"S", "b"} + ); + + Automaton a(&grammar); + auto result1 = a.add_state(std::move(state1)); + auto result2 = a.add_state(std::move(state2)); + + EXPECT_TRUE(result1.second); + EXPECT_EQ(result1.first->to_string(), "S -> <*> a S b\nS -> <*> "); + EXPECT_TRUE(result2.second); + EXPECT_EQ(result2.first->to_string(), "S -> a <*> S b"); +} + +TEST_F(TestAutomaton, +AddStateDuplicate) { + auto state1 = new_state( + "S", std::vector{}, std::vector{"a", "S", "b"}, + "S", std::vector{}, std::vector{} + ); + auto state2 = new_state( + "S", std::vector{}, std::vector{"a", "S", "b"}, + "S", std::vector{}, std::vector{} + ); + + Automaton a(&grammar); + auto result1 = a.add_state(std::move(state1)); + auto result2 = a.add_state(std::move(state2)); + + EXPECT_TRUE(result1.second); + EXPECT_EQ(result1.first->to_string(), "S -> <*> a S b\nS -> <*> "); + EXPECT_FALSE(result2.second); + EXPECT_EQ(result1.first, result2.first); +} + +TEST_F(TestAutomaton, +GetState) { + auto state = new_state( + "S", std::vector{}, std::vector{"a", "S", "b"}, + "S", std::vector{}, std::vector{} + ); + + Automaton a(&grammar); + a.add_state(std::move(state)); + + state = new_state( + "S", std::vector{}, std::vector{"a", "S", "b"}, + "S", std::vector{}, std::vector{} + ); + + EXPECT_EQ(*a.get_state(0), state); +} + +TEST_F(TestAutomaton, +Closure) { + auto state = new_state( + "S", std::vector{"A"}, std::vector{"S", "b"} + ); + new_state( + "A", std::vector{"a"}, std::vector{"A"}, + "A", std::vector{}, std::vector{} + ); + + Automaton a(&grammar); + a.closure(state); + + EXPECT_EQ(state.to_string(), "S -> A <*> S b\nS -> <*> A S b\nA -> <*> a A\nA -> <*> "); +} + +TEST_F(TestAutomaton, +ConstructStates) { + grammar.set_start_symbol(grammar.add_symbol(SymbolKind::Nonterminal, "S")); + new_state( + "S", std::vector{}, std::vector{"a", "S", "b"}, + "S", std::vector{}, std::vector{} + ); + + Automaton a(&grammar); + a.construct_states(); + + EXPECT_EQ(a.get_states().size(), 5u); + EXPECT_EQ(a.get_states()[0]->to_string(), "@start -> <*> S @end\nS -> <*> a S b\nS -> <*> "); + EXPECT_EQ(a.get_states()[1]->to_string(), "@start -> S <*> @end"); + EXPECT_EQ(a.get_states()[2]->to_string(), "S -> a <*> S b\nS -> <*> a S b\nS -> <*> "); + EXPECT_EQ(a.get_states()[3]->to_string(), "S -> a S <*> b"); + EXPECT_EQ(a.get_states()[4]->to_string(), "S -> a S b <*>"); +} diff --git a/tests/Pog/CMakeLists.txt b/tests/Pog/CMakeLists.txt new file mode 100644 index 00000000..c0cc31fe --- /dev/null +++ b/tests/Pog/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.25) + +add_executable( + pog_testing + Automaton.cpp + FilterView.cpp + Grammar.cpp + Item.cpp + Parser.cpp + ParsingTable.cpp + PogTests.cpp + Precedence.cpp + Rule.cpp + RuleBuilder.cpp + State.cpp + Symbol.cpp + Token.cpp + TokenBuilder.cpp + Tokenizer.cpp + Utils.cpp +) + +target_link_libraries( + pog_testing + PRIVATE + emulator-core + assembler-core + GTest::gtest + pthread + pch + pog +) +target_include_directories( + pog_testing + PRIVATE + ${CMAKE_SOURCE_DIR}/tests +) + +gtest_add_tests( + TARGET + pog_testing + EXTRA_ARGS + --gtest_brief=1 +) diff --git a/tests/Pog/FilterView.cpp b/tests/Pog/FilterView.cpp new file mode 100644 index 00000000..5791d4de --- /dev/null +++ b/tests/Pog/FilterView.cpp @@ -0,0 +1,83 @@ +#include + +#include + +class TestFilterView : public ::testing::Test {}; + +TEST_F(TestFilterView, +EmptyContainer) { + std::vector v; + + FilterView fv(v.begin(), v.end(), [](int x) { return x & 1; }); + + std::vector actual; + for (auto x : fv) + actual.push_back(x); + + EXPECT_EQ(actual, (std::vector{})); +} + +TEST_F(TestFilterView, +BasicFilter) { + std::vector v = {0, 1, 2, 3, 4, 5, 6}; + + FilterView fv(v.begin(), v.end(), [](int x) { return x & 1; }); + + std::vector actual; + for (auto x : fv) + actual.push_back(x); + + EXPECT_EQ(actual, (std::vector{1, 3, 5})); +} + +TEST_F(TestFilterView, +SharedEnd) { + std::vector v = {0, 1, 2, 3, 4, 5}; + + FilterView fv(v.begin(), v.end(), [](int x) { return x & 1; }); + + std::vector actual; + for (auto x : fv) + actual.push_back(x); + + EXPECT_EQ(actual, (std::vector{1, 3, 5})); +} + +TEST_F(TestFilterView, +SharedBegin) { + std::vector v = {1, 2, 3, 4, 5, 6}; + + FilterView fv(v.begin(), v.end(), [](int x) { return x & 1; }); + + std::vector actual; + for (auto x : fv) + actual.push_back(x); + + EXPECT_EQ(actual, (std::vector{1, 3, 5})); +} + +TEST_F(TestFilterView, +SharedBeginAndEnd) { + std::vector v = {1, 2, 3, 4, 5}; + + FilterView fv(v.begin(), v.end(), [](int x) { return x & 1; }); + + std::vector actual; + for (auto x : fv) + actual.push_back(x); + + EXPECT_EQ(actual, (std::vector{1, 3, 5})); +} + +TEST_F(TestFilterView, +NoElements) { + std::vector v = {0, 1, 2, 3, 4, 5, 6}; + + FilterView fv(v.begin(), v.end(), [](int x) { return x > 10; }); + + std::vector actual; + for (auto x : fv) + actual.push_back(x); + + EXPECT_EQ(actual, (std::vector{})); +} diff --git a/tests/Pog/Grammar.cpp b/tests/Pog/Grammar.cpp new file mode 100644 index 00000000..0c1239e9 --- /dev/null +++ b/tests/Pog/Grammar.cpp @@ -0,0 +1,187 @@ +#include + +#include + +using namespace pog; + +class TestGrammar : public ::testing::Test {}; + +TEST_F(TestGrammar, +DefaultGrammar) { + Grammar g; + + EXPECT_EQ(g.get_symbols().size(), 2u); + EXPECT_EQ(g.get_rules().size(), 0u); + + EXPECT_TRUE(g.get_symbols()[0]->is_nonterminal()); + EXPECT_EQ(g.get_symbols()[0]->get_name(), "@start"); + + EXPECT_TRUE(g.get_symbols()[1]->is_end()); + EXPECT_EQ(g.get_symbols()[1]->get_name(), "@end"); + + EXPECT_EQ(g.get_end_of_input_symbol(), g.get_symbols()[1].get()); + EXPECT_EQ(g.get_start_rule(), nullptr); +} + +TEST_F(TestGrammar, +AddSymbol) { + Grammar g; + + g.add_symbol(SymbolKind::Nonterminal, "A"); + g.add_symbol(SymbolKind::Nonterminal, "B"); + + EXPECT_EQ(g.get_symbols().size(), 4u); +} + +TEST_F(TestGrammar, +AddSymbolDuplicate) { + Grammar g; + + g.add_symbol(SymbolKind::Nonterminal, "A"); + g.add_symbol(SymbolKind::Terminal, "A"); + + EXPECT_EQ(g.get_symbols().size(), 3u); +} + +TEST_F(TestGrammar, +GetSymbol) { + Grammar g; + + auto sym = g.add_symbol(SymbolKind::Nonterminal, "A"); + EXPECT_EQ(g.get_symbol("A"), sym); + EXPECT_EQ(g.get_symbol("B"), nullptr); +} + +TEST_F(TestGrammar, +AddRule) { + Grammar g; + + auto s1 = g.add_symbol(SymbolKind::Nonterminal, "A"); + auto s2 = g.add_symbol(SymbolKind::Nonterminal, "B"); + auto s3 = g.add_symbol(SymbolKind::Nonterminal, "C"); + + auto result = g.add_rule(s1, std::vector*>{s2, s3}, [](auto&&...) -> int { return 0; }); + EXPECT_EQ(g.get_rules().size(), 1u); + EXPECT_EQ(result->get_lhs(), s1); + EXPECT_EQ(result->get_rhs(), (std::vector*>{s2, s3})); +} + +TEST_F(TestGrammar, +GetRulesOfSymbol) { + Grammar g; + + auto s1 = g.add_symbol(SymbolKind::Nonterminal, "A"); + auto s2 = g.add_symbol(SymbolKind::Nonterminal, "B"); + auto s3 = g.add_symbol(SymbolKind::Nonterminal, "C"); + + auto r1 = g.add_rule(s1, std::vector*>{s2, s3}, [](auto&&...) -> int { return 0; }); + auto r2 = g.add_rule(s1, std::vector*>{}, [](auto&&...) -> int { return 0; }); + auto r3 = g.add_rule(s2, std::vector*>{s1, s3}, [](auto&&...) -> int { return 0; }); + + EXPECT_EQ(g.get_rules().size(), 3u); + EXPECT_EQ(g.get_rules_of_symbol(s1), (std::vector*>{r1, r2})); + EXPECT_EQ(g.get_rules_of_symbol(s2), (std::vector*>{r3})); + EXPECT_EQ(g.get_rules_of_symbol(s3), (std::vector*>{})); +} + +TEST_F(TestGrammar, +GetRulesWithSymbol) { + Grammar g; + + auto s1 = g.add_symbol(SymbolKind::Nonterminal, "A"); + auto s2 = g.add_symbol(SymbolKind::Nonterminal, "B"); + auto s3 = g.add_symbol(SymbolKind::Nonterminal, "C"); + + auto r1 = g.add_rule(s1, std::vector*>{s2, s3}, [](auto&&...) -> int { return 0; }); + auto r2 = g.add_rule(s1, std::vector*>{}, [](auto&&...) -> int { return 0; }); + auto r3 = g.add_rule(s2, std::vector*>{s1, s3}, [](auto&&...) -> int { return 0; }); + static_cast(r2); + + EXPECT_EQ(g.get_rules().size(), 3u); + EXPECT_EQ(g.get_rules_with_symbol(s1), (std::vector*>{r3})); + EXPECT_EQ(g.get_rules_with_symbol(s2), (std::vector*>{r1})); + EXPECT_EQ(g.get_rules_with_symbol(s3), (std::vector*>{r1, r3})); +} + +TEST_F(TestGrammar, +StartSymbol) { + Grammar g; + + auto s = g.add_symbol(SymbolKind::Nonterminal, "A"); + g.set_start_symbol(s); + + EXPECT_EQ(g.get_rules().size(), 1u); + EXPECT_EQ(g.get_rules()[0]->to_string(), "@start -> A @end"); + EXPECT_EQ(g.get_rules()[0].get(), g.get_start_rule()); +} + +TEST_F(TestGrammar, +Empty) { + Grammar g; + + auto a = g.add_symbol(SymbolKind::Terminal, "a"); + auto b = g.add_symbol(SymbolKind::Terminal, "b"); + auto S = g.add_symbol(SymbolKind::Nonterminal, "S"); + auto A = g.add_symbol(SymbolKind::Nonterminal, "A"); + + g.add_rule(S, std::vector*>{a, S, b}, [](auto&&...) -> int { return 0; }); + g.add_rule(S, std::vector*>{a, b}, [](auto&&...) -> int { return 0; }); + g.add_rule(A, std::vector*>{a}, [](auto&&...) -> int { return 0; }); + g.add_rule(A, std::vector*>{}, [](auto&&...) -> int { return 0; }); + + EXPECT_FALSE(g.empty(a)); + EXPECT_FALSE(g.empty(b)); + EXPECT_FALSE(g.empty(S)); + EXPECT_TRUE(g.empty(A)); + + EXPECT_TRUE(g.empty(std::vector*>{A, A, A})); + EXPECT_FALSE(g.empty(std::vector*>{A, A, A, S})); +} + +TEST_F(TestGrammar, +First) { + Grammar g; + + auto a = g.add_symbol(SymbolKind::Terminal, "a"); + auto b = g.add_symbol(SymbolKind::Terminal, "b"); + auto S = g.add_symbol(SymbolKind::Nonterminal, "S"); + auto A = g.add_symbol(SymbolKind::Nonterminal, "A"); + + g.add_rule(S, std::vector*>{a, S, b}, [](auto&&...) -> int { return 0; }); + g.add_rule(S, std::vector*>{a, b}, [](auto&&...) -> int { return 0; }); + g.add_rule(S, std::vector*>{b}, [](auto&&...) -> int { return 0; }); + g.add_rule(A, std::vector*>{a}, [](auto&&...) -> int { return 0; }); + g.add_rule(A, std::vector*>{}, [](auto&&...) -> int { return 0; }); + + EXPECT_EQ(g.first(a), (std::unordered_set*>{a})); + EXPECT_EQ(g.first(b), (std::unordered_set*>{b})); + EXPECT_EQ(g.first(S), (std::unordered_set*>{a, b})); + EXPECT_EQ(g.first(A), (std::unordered_set*>{a})); + + EXPECT_EQ(g.first(std::vector*>{A, A, A}), (std::unordered_set*>{a})); + EXPECT_EQ(g.first(std::vector*>{A, A, A, S}), (std::unordered_set*>{a, b})); + EXPECT_EQ(g.first(std::vector*>{b, A, A, S}), (std::unordered_set*>{b})); +} + +TEST_F(TestGrammar, +Follow) { + Grammar g; + + auto a = g.add_symbol(SymbolKind::Terminal, "a"); + auto b = g.add_symbol(SymbolKind::Terminal, "b"); + auto S = g.add_symbol(SymbolKind::Nonterminal, "S"); + auto A = g.add_symbol(SymbolKind::Nonterminal, "A"); + + g.add_rule(S, std::vector*>{a, S, b}, [](auto&&...) -> int { return 0; }); + g.add_rule(S, std::vector*>{a, b}, [](auto&&...) -> int { return 0; }); + g.add_rule(S, std::vector*>{b}, [](auto&&...) -> int { return 0; }); + g.add_rule(A, std::vector*>{a, A}, [](auto&&...) -> int { return 0; }); + g.add_rule(A, std::vector*>{}, [](auto&&...) -> int { return 0; }); + + EXPECT_EQ(g.follow(S), (std::unordered_set*>{b})); + EXPECT_EQ(g.follow(A), (std::unordered_set*>{})); + + // To test out caching + EXPECT_EQ(g.follow(S), (std::unordered_set*>{b})); + EXPECT_EQ(g.follow(A), (std::unordered_set*>{})); +} diff --git a/tests/Pog/Item.cpp b/tests/Pog/Item.cpp new file mode 100644 index 00000000..ea16c301 --- /dev/null +++ b/tests/Pog/Item.cpp @@ -0,0 +1,236 @@ +#include + +#include + +using namespace pog; + +class TestItem : public ::testing::Test {}; + +TEST_F(TestItem, +SimpleItem) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule); + + EXPECT_EQ(item.get_rule(), &rule); + EXPECT_EQ(item.get_read_pos(), 0u); + EXPECT_EQ(item.get_previous_symbol(), nullptr); + EXPECT_EQ(item.get_read_symbol(), &s2); + + EXPECT_FALSE(item.is_kernel()); + EXPECT_FALSE(item.is_final()); + EXPECT_FALSE(item.is_accepting()); +} + +TEST_F(TestItem, +SimpleItemWithReadPosShifted) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 1); + + EXPECT_EQ(item.get_rule(), &rule); + EXPECT_EQ(item.get_read_pos(), 1u); + EXPECT_EQ(item.get_previous_symbol(), &s2); + EXPECT_EQ(item.get_read_symbol(), &s3); + + EXPECT_TRUE(item.is_kernel()); + EXPECT_FALSE(item.is_final()); + EXPECT_FALSE(item.is_accepting()); +} + +TEST_F(TestItem, +SimpleItemWithReadPosAtTheEnd) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 2); + + EXPECT_EQ(item.get_rule(), &rule); + EXPECT_EQ(item.get_read_pos(), 2u); + EXPECT_EQ(item.get_previous_symbol(), &s3); + EXPECT_EQ(item.get_read_symbol(), nullptr); + + EXPECT_TRUE(item.is_kernel()); + EXPECT_TRUE(item.is_final()); + EXPECT_FALSE(item.is_accepting()); +} + +TEST_F(TestItem, +SimpleAcceptingItem) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::End, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 1); + + EXPECT_EQ(item.get_rule(), &rule); + EXPECT_EQ(item.get_read_pos(), 1u); + EXPECT_EQ(item.get_previous_symbol(), &s2); + EXPECT_EQ(item.get_read_symbol(), &s3); + + EXPECT_TRUE(item.is_kernel()); + EXPECT_FALSE(item.is_final()); + EXPECT_TRUE(item.is_accepting()); +} + +TEST_F(TestItem, +LeftSideWithoutReadSymbol) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 1); + + EXPECT_EQ(item.get_left_side_without_read_symbol(), std::vector*>{&s2}); +} + +TEST_F(TestItem, +LeftSideWithoutReadSymbolWhenReadPosAtStart) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 0); + + EXPECT_EQ(item.get_left_side_without_read_symbol(), std::vector*>{}); +} + +TEST_F(TestItem, +RightSideWithoutReadSymbol) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 0); + + EXPECT_EQ(item.get_right_side_without_read_symbol(), std::vector*>{&s3}); +} + +TEST_F(TestItem, +RightSideWithoutReadSymbolWhenNothingIsReturned) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 1); + + EXPECT_EQ(item.get_right_side_without_read_symbol(), std::vector*>{}); +} + +TEST_F(TestItem, +Step) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 0); + + item.step(); + EXPECT_EQ(item.get_read_pos(), 1u); + item.step(); + EXPECT_EQ(item.get_read_pos(), 2u); + item.step(); + EXPECT_EQ(item.get_read_pos(), 2u); +} + +TEST_F(TestItem, +StepBack) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 2); + + item.step_back(); + EXPECT_EQ(item.get_read_pos(), 1u); + item.step_back(); + EXPECT_EQ(item.get_read_pos(), 0u); + item.step_back(); + EXPECT_EQ(item.get_read_pos(), 0u); +} + +TEST_F(TestItem, +ToString) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 1); + + EXPECT_EQ(item.to_string(), "1 -> 2 <*> 3"); +} + +TEST_F(TestItem, +EpsilonToString) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Rule rule(42, &s1, std::vector*>{}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item(&rule, 0); + + EXPECT_EQ(item.to_string(), "1 -> <*> "); +} + +TEST_F(TestItem, +Equality) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Rule rule2(42, &s1, std::vector*>{&s2}, [](Parser&, std::vector>&&) -> int { return 0; }); + Rule rule3(43, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item1(&rule1, 1); + Item item2(&rule2, 1); + Item item3(&rule3, 1); + + EXPECT_TRUE(item1 == item2); + EXPECT_FALSE(item1 == item3); + + EXPECT_FALSE(item1 != item2); + EXPECT_TRUE(item1 != item3); + + item1.step(); + EXPECT_FALSE(item1 == item2); +} + +TEST_F(TestItem, +LessThanDifferentRule) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Rule rule2(41, &s1, std::vector*>{&s2}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item1(&rule1, 0); + Item item2(&rule2, 0); + + EXPECT_FALSE(item1 < item2); +} + +TEST_F(TestItem, +LessThanDifferentReadPos) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Rule rule2(42, &s1, std::vector*>{&s2}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item1(&rule1, 0); + Item item2(&rule2, 1); + + EXPECT_FALSE(item1 < item2); +} + +TEST_F(TestItem, +LessThanWithKernelItemPriority) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, std::vector>&&) -> int { return 0; }); + Rule rule2(41, &s1, std::vector*>{&s2}, [](Parser&, std::vector>&&) -> int { return 0; }); + Item item1(&rule1, 1); + Item item2(&rule2, 0); + + EXPECT_TRUE(item1 < item2); +} diff --git a/tests/Pog/Parser.cpp b/tests/Pog/Parser.cpp new file mode 100644 index 00000000..ed119afe --- /dev/null +++ b/tests/Pog/Parser.cpp @@ -0,0 +1,919 @@ +#include + +#include + +using namespace pog; +using namespace ::testing; + +class TestParser : public ::testing::Test {}; + +TEST_F(TestParser, +RepeatingAs) { + Parser p; + + p.token("a").symbol("a"); + + p.set_start_symbol("A"); + p.rule("A") + .production("A", "a", [](Parser&, auto&& args) { + return 1 + args[0].value; + }) + .production("a", [](Parser&, auto&&) { + return 1; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("a"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 1); + + std::string input2("aaaa"); + result = p.parse(input2); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 4); + + try + { + std::string input3("aa aaa"); + p.parse(input3); + FAIL() << "Expected syntax error"; + } + catch (const SyntaxError& e) + { + EXPECT_STREQ(e.what(), "Syntax error: Unknown symbol on input, expected one of @end, a"); + } +} + +TEST_F(TestParser, +RepeatingAsWithIgnoringWhitespaces) { + Parser p; + + p.token(R"(\s+)"); + p.token("a").symbol("a"); + + p.set_start_symbol("A"); + p.rule("A") + .production("A", "a", [](Parser&, auto&& args) { + return 1 + args[0].value; + }) + .production("a", [](Parser&, auto&&) { + return 1; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("a"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 1); + + std::string input2("aaaa"); + result = p.parse(input2); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 4); + + std::string input3("aa aaa"); + result = p.parse(input3); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 5); +} + +TEST_F(TestParser, +SameNumberOfAsAndBs) { + Parser p; + + p.token("a").symbol("a"); + p.token("b").symbol("b"); + + p.set_start_symbol("S"); + p.rule("S") + .production("a", "S", "b", [](Parser&, auto&& args) { + return 1 + args[1].value; + }) + .production("a", "b", [](Parser&, auto&&) { + return 1; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("ab"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 1); + + std::string input2("aaabbb"); + result = p.parse(input2); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 3); + + try + { + std::string input3("aabbb"); + p.parse(input3); + FAIL() << "Expected syntax error"; + } + catch (const SyntaxError& e) + { + EXPECT_STREQ(e.what(), "Syntax error: Unexpected b, expected one of @end"); + } + + try + { + std::string input4("aaabb"); + p.parse(input4); + FAIL() << "Expected syntax error"; + } + catch (const SyntaxError& e) + { + EXPECT_STREQ(e.what(), "Syntax error: Unexpected @end, expected one of b"); + } +} + +TEST_F(TestParser, +SymbolDescriptionInErrorMessages) { + Parser p; + + p.token("a").symbol("a"); + p.token("b").symbol("b").description("symbol_b"); + + p.set_start_symbol("S"); + p.rule("S") + .production("a", "S", "b", [](Parser&, auto&& args) { + return 1 + args[1].value; + }) + .production("a", "b", [](Parser&, auto&&) { + return 1; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("ab"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 1); + + std::string input2("aaabbb"); + result = p.parse(input2); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 3); + + try + { + std::string input3("aabbb"); + p.parse(input3); + FAIL() << "Expected syntax error"; + } + catch (const SyntaxError& e) + { + EXPECT_STREQ(e.what(), "Syntax error: Unexpected symbol_b, expected one of @end"); + } + + try + { + std::string input4("aaabb"); + p.parse(input4); + FAIL() << "Expected syntax error"; + } + catch (const SyntaxError& e) + { + EXPECT_STREQ(e.what(), "Syntax error: Unexpected @end, expected one of symbol_b"); + } +} + +TEST_F(TestParser, +LalrButNotLrNorNqlalr) { + Parser p; + + p.token("a").symbol("a"); + p.token("b").symbol("b"); + p.token("c").symbol("c"); + p.token("d").symbol("d"); + p.token("g").symbol("g"); + + p.set_start_symbol("S"); + p.rule("S") + .production("a", "g", "d") + .production("a", "A", "c") + .production("b", "A", "d") + .production("b", "g", "c"); + p.rule("A") + .production("B"); + p.rule("B") + .production("g"); + EXPECT_TRUE(p.prepare()); + + std::string input("agc"); + auto result = p.parse(input); + EXPECT_TRUE(result); +} + +TEST_F(TestParser, +Precedence) { + Parser p; + + p.token(R"(\s+)"); + p.token(R"(\+)").symbol("+").precedence(0, Associativity::Left); + p.token(R"(-)").symbol("-").precedence(0, Associativity::Left); + p.token(R"(\*)").symbol("*").precedence(1, Associativity::Left); + p.token("[0-9]+").symbol("int").action([](std::string_view str) { + return std::stoi(std::string{str}); + }); + + p.set_start_symbol("E"); + p.rule("E") + .production("E", "+", "E", [](Parser&, auto&& args) { + return args[0].value + args[2].value; + }) + .production("E", "-", "E", [](Parser&, auto&& args) { + return args[0].value - args[2].value; + }) + .production("E", "*", "E", [](Parser&, auto&& args) { + return args[0].value * args[2].value; + }) + .production("-", "E", [](Parser&, auto&& args) { + return -args[1].value; + }).precedence(2, Associativity::Right) + .production("int", [](Parser&, auto&& args) { + return args[0].value; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("2 + 3 * 4 + 5"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 19); + + std::string input2("-5 - 3 - -10"); + result = p.parse(input2); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 2); + + std::string input3("5 + -3 * 10"); + result = p.parse(input3); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), -25); +} + +TEST_F(TestParser, +Conflicts1) { + Parser p; + + p.token("a").symbol("a"); + + p.set_start_symbol("sequence"); + p.rule("sequence") + .production("sequence", "a") + .production("maybea") + .production(); + p.rule("maybea") + .production("a") + .production(); + + auto report = p.prepare(); + EXPECT_FALSE(report); + EXPECT_EQ(report.number_of_issues(), 3u); + EXPECT_EQ(report.to_string(), + "Shift-reduce conflict of symbol 'a' and rule 'sequence -> ' in state 0\n" + "Reduce-reduce conflict of rule 'sequence -> ' and rule 'maybea -> ' in state 0\n" + "Shift-reduce conflict of symbol 'a' and rule 'maybea -> ' in state 0" + ); +} + +TEST_F(TestParser, +Conflicts2) { + Parser p; + + p.token("b").symbol("b"); + p.token("c").symbol("c"); + + p.set_start_symbol("Y"); + p.rule("Y") + .production("c", "c", "Z", "b"); + p.rule("Z") + .production("c", "Z", "b") + .production("c", "Z") + .production(); + + auto report = p.prepare(); + EXPECT_FALSE(report); + EXPECT_EQ(report.number_of_issues(), 1u); + EXPECT_EQ(report.to_string(), "Shift-reduce conflict of symbol 'b' and rule 'Z -> c Z' in state 6"); +} + +TEST_F(TestParser, +Conflicts3) { + Parser> p; + + p.token("\\(").symbol("("); + p.token("\\)").symbol(")"); + p.token("a").symbol("a"); + + p.set_start_symbol("E"); + p.rule("E") + .production("(", "E", ")", [](Parser>&, auto&& args) { + args[1].value.push_back("E -> ( E )"); + return std::move(args[1].value); + }) + .production("PE", [](Parser>&, auto&& args) { + args[0].value.push_back("E -> PE"); + return std::move(args[0].value); + }); + p.rule("PE") + .production("(", "PE", ")", [](Parser>&, auto&& args) { + args[1].value.push_back("PE -> ( PE )"); + return std::move(args[1].value); + }) + .production("a", [](Parser>&, auto&&) { + return std::vector{"PE -> a"}; + }); + + auto report = p.prepare(); + EXPECT_FALSE(report); + EXPECT_EQ(report.number_of_issues(), 1u); + EXPECT_EQ(report.to_string(), "Shift-reduce conflict of symbol ')' and rule 'E -> PE' in state 6"); + + std::string input1("(((a)))"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), (std::vector{ + "PE -> a", + "PE -> ( PE )", + "PE -> ( PE )", + "PE -> ( PE )", + "E -> PE" + })); +} + +TEST_F(TestParser, +ResolveConflictWithPrecedence) { + Parser> p; + + p.token("\\(").symbol("("); + p.token("\\)").symbol(")").precedence(0, Associativity::Left); + p.token("a").symbol("a"); + + p.set_start_symbol("E"); + p.rule("E") + .production("(", "E", ")", [](Parser>&, auto&& args) { + args[1].value.push_back("E -> ( E )"); + return std::move(args[1].value); + }) + .production("PE", [](Parser>&, auto&& args) { + args[0].value.push_back("E -> PE"); + return std::move(args[0].value); + }).precedence(1, Associativity::Left); + p.rule("PE") + .production("(", "PE", ")", [](Parser>&, auto&& args) { + args[1].value.push_back("PE -> ( PE )"); + return std::move(args[1].value); + }) + .production("a", [](Parser>&, auto&&) { + return std::vector{"PE -> a"}; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("(((a)))"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), (std::vector{ + "PE -> a", + "E -> PE", + "E -> ( E )", + "E -> ( E )", + "E -> ( E )" + })); +} + +TEST_F(TestParser, +EndTokenAction) { + int end_call_count = 0; + Parser p; + + p.token("a").symbol("a"); + p.end_token().action([&](std::string_view) { + end_call_count++; + return 0; + }); + + p.set_start_symbol("A"); + p.rule("A") + .production("A", "a", [](Parser&, auto&& args) { + return 1 + args[0].value; + }) + .production("a", [](Parser&, auto&&) { + return 1; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("aaaa"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 4); + EXPECT_EQ(end_call_count, 1); +} + +TEST_F(TestParser, +TokenActionsCalledOnce) { + int a_call_count = 0; + Parser p; + + p.token("a").symbol("a").action([&](std::string_view) { + a_call_count++; + return 0; + }); + + p.set_start_symbol("A"); + p.rule("A") + .production("B", [](Parser&, auto&& args) { + return args[0].value; + }); + p.rule("B") + .production("A", "a", [](Parser&, auto&& args) { + return 1 + args[0].value; + }) + .production("a", [](Parser&, auto&&) { + return 1; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("aaaa"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 4); + EXPECT_EQ(a_call_count, 4); +} + +TEST_F(TestParser, +MultistateTokenizer) { + using Value = std::variant< + std::string, + std::pair, + std::vector> + >; + + Parser p; + std::string built_string; + + p.token("\\s+"); + p.token("=").symbol("="); + p.token("[a-zA-Z_][a-zA-Z0-9_]*").symbol("id").action([](std::string_view str) -> Value { + return std::string{str}; + }); + + p.token(R"(")").enter_state("string").action([&](std::string_view) -> Value { + built_string.clear(); + return {}; + }); + p.token(R"(\\n)").states("string").action([&](std::string_view) -> Value { + built_string += '\n'; + return {}; + }); + p.token(R"(\\t)").states("string").action([&](std::string_view) -> Value { + built_string += '\t'; + return {}; + }); + p.token(R"(\\r)").states("string").action([&](std::string_view) -> Value { + built_string += '\r'; + return {}; + }); + p.token(R"(\\x[0-9a-fA-F]{2})").states("string").action([&](std::string_view str) -> Value { + auto s = std::string{str.begin() + 2, str.end()}; + built_string += static_cast(std::stoi(s, nullptr, 16)); + return {}; + }); + p.token(R"([^\\"]+)").states("string").action([&](std::string_view str) -> Value { + built_string += str; + return {}; + }); + p.token(R"(")").states("string").enter_state("@default").symbol("string_literal").action([&](std::string_view) -> Value { + return built_string; + }); + + p.set_start_symbol("root"); + p.rule("root") + .production("strings", [](Parser&, auto&& args) -> Value { + return std::move(args[0].value); + }) + .production([](Parser&, auto&&) -> Value { + return std::vector>{}; + }); + p.rule("strings") + .production("strings", "string", [](Parser&, auto&& args) -> Value { + std::get>>(args[0].value).push_back( + std::get>(args[1].value) + ); + return std::move(args[0].value); + }) + .production("string", [](Parser&, auto&& args) -> Value { + return std::vector>{ + std::get>(args[0].value) + }; + }); + p.rule("string") + .production("id", "=", "string_literal", [](Parser&, auto&& args) -> Value { + return std::make_pair( + std::get(args[0].value), + std::get(args[2].value) + ); + }); + EXPECT_TRUE(p.prepare()); + + std::string input( + "abc = \"xyz\"\n" + "x = \"ab\\n\\t\\r\\x20cd\"" + ); + auto result = p.parse(input); + EXPECT_TRUE(result); + auto strings = std::get>>(result.value()); + EXPECT_EQ(strings.size(), 2u); + EXPECT_THAT(strings[0], Pair(Eq("abc"), Eq("xyz"))); + EXPECT_THAT(strings[1], Pair(Eq("x"), Eq("ab\n\t\r cd"))); +} + +TEST_F(TestParser, +MidruleActionsToCheckRedefinition) { + using Value = std::variant< + int, + std::string + >; + + Parser p; + + p.token("\\s+"); + p.token("=").symbol("="); + p.token(";").symbol(";"); + p.token("\\{").symbol("{"); + p.token("\\}").symbol("}"); + p.token("function").symbol("function"); + p.token("var").symbol("var"); + p.token("[_a-zA-Z][_a-zA-Z0-9]*").symbol("id").action([](std::string_view str) -> Value { + return std::string{str}; + }); + p.token("[0-9]+").symbol("num").action([](std::string_view str) -> Value { + return std::stoi(std::string{str}); + }); + + std::unordered_set defs, redefs; + + p.set_start_symbol("prog"); + p.rule("prog") + .production("funcs") + .production(); + p.rule("funcs") + .production("funcs", "func") + .production("func"); + p.rule("func") + .production( + "function", "id", [&](Parser&, auto&& args) -> Value { + auto func_name = std::get(args[1].value); + auto [itr, inserted] = defs.insert(func_name); + if (!inserted) + redefs.insert(func_name); + return {}; + }, + "{", "func_body", "}" + ); + p.rule("func_body") + .production("stmts") + .production(); + p.rule("stmts") + .production("stmts", "stmt") + .production("stmt"); + p.rule("stmt") + .production( + "var", "id", [&](Parser&, auto&& args) -> Value { + auto var_name = std::get(args[1].value); + auto [itr, inserted] = defs.insert(var_name); + if (!inserted) + redefs.insert(var_name); + return {}; + }, + "=", "num", ";" + ); + EXPECT_TRUE(p.prepare()); + + std::string input1( +R"(function x { + var y = 5; + var z = 10; +})" + ); + auto result1 = p.parse(input1); + EXPECT_TRUE(result1); + EXPECT_EQ(defs, (std::unordered_set{"x", "y", "z"})); + EXPECT_EQ(redefs, (std::unordered_set{})); + + defs.clear(); + redefs.clear(); + + std::string input2( +R"(function x { + var y = 5; + var x = 10; +})" + ); + auto result2 = p.parse(input2); + EXPECT_TRUE(result2); + EXPECT_EQ(defs, (std::unordered_set{"x", "y"})); + EXPECT_EQ(redefs, (std::unordered_set{"x"})); + + defs.clear(); + redefs.clear(); + + std::string input3( +R"(function x { + var y = 5; + var z = 10; +} + +function z { + var a = 1; +})" + ); + auto result3 = p.parse(input3); + EXPECT_TRUE(result3); + EXPECT_EQ(defs, (std::unordered_set{"x", "y", "z", "a"})); + EXPECT_EQ(redefs, (std::unordered_set{"z"})); +} + +TEST_F(TestParser, +InputStreamStackManipulation) { + static std::vector input_streams = { + "10", + "include 0", + "30", + "40" + }; + + std::vector> inputs; + + Parser p; + + p.token("\\s+"); + p.token("\\+").symbol("+").precedence(1, Associativity::Left); + p.token("\\*").symbol("*").precedence(2, Associativity::Left); + p.token("include [0-9]+").action([&](std::string_view str) { + auto stream_idx = std::stoi(std::string{str.begin() + 8, str.end()}); + inputs.emplace_back(std::make_unique(input_streams[stream_idx])); + p.push_input_stream(*inputs.back().get()); + return 0; + }); + p.token("[0-9]+").symbol("number").action([](std::string_view str) { + return std::stoi(std::string{str}); + }); + p.end_token().action([&](std::string_view) { + p.pop_input_stream(); + return 0; + }); + + p.set_start_symbol("E"); + p.rule("E") + .production("E", "+", "E", [](Parser&, auto&& args) { return args[0].value + args[2].value; }) + .production("E", "*", "E", [](Parser&, auto&& args) { return args[0].value * args[2].value; }) + .production("number", [](Parser&, auto&& args) { return args[0].value; }); + + EXPECT_TRUE(p.prepare()); + + std::string input("include 1 + include 2 * include 3 + 5"); + auto result = p.parse(input); + + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 1215); +} + +TEST_F(TestParser, +RuleActionsAccessingDataBeforeMidruleAction) { + Parser p; + + p.token("\\s+"); + p.token("a").symbol("a").action([](std::string_view) { + return 1; + }); + p.token("function").symbol("func").action([](std::string_view) { + return 10; + }); + p.token("[a-zA-Z_]+").symbol("id").action([](std::string_view) { + return 100; + }); + + std::vector all_values; + + p.set_start_symbol("S"); + p.rule("S") + .production("func", "id", [](Parser&, auto&& args) { + return args[0].value + args[1].value; + }, + "A", [&](Parser&, auto&& args) { + for (const auto& arg : args) + all_values.push_back(arg.value); + return args[2].value + args[3].value; + }); + p.rule("A") + .production("A", "a", [](Parser&, auto&& args) { return args[0].value + args[1].value; }) + .production("a", [](Parser&, auto&& args) { return args[0].value; }); + + + EXPECT_TRUE(p.prepare()); + + std::string input("function abc a a a a a"); + auto result = p.parse(input); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 115); + EXPECT_EQ(all_values, (std::vector{10, 100, 110, 5})); +} + +TEST_F(TestParser, +MultistateTokenizerWithExplicitCalls) { + using Value = std::variant< + std::string, + std::pair, + std::vector> + >; + + Parser p; + std::string built_string; + + p.token("\\s+"); + p.token("=").symbol("="); + p.token("[a-zA-Z_][a-zA-Z0-9_]*").symbol("id").action([](std::string_view str) -> Value { + return std::string{str}; + }); + + p.token(R"(")").action([&](std::string_view) -> Value { + p.enter_tokenizer_state("string"); + built_string.clear(); + return {}; + }); + p.token(R"(\\n)").states("string").action([&](std::string_view) -> Value { + built_string += '\n'; + return {}; + }); + p.token(R"(\\t)").states("string").action([&](std::string_view) -> Value { + built_string += '\t'; + return {}; + }); + p.token(R"(\\r)").states("string").action([&](std::string_view) -> Value { + built_string += '\r'; + return {}; + }); + p.token(R"(\\x[0-9a-fA-F]{2})").states("string").action([&](std::string_view str) -> Value { + auto s = std::string{str.begin() + 2, str.end()}; + built_string += static_cast(std::stoi(s, nullptr, 16)); + return {}; + }); + p.token(R"([^\\"]+)").states("string").action([&](std::string_view str) -> Value { + built_string += str; + return {}; + }); + p.token(R"(")").states("string").symbol("string_literal").action([&](std::string_view) -> Value { + p.enter_tokenizer_state("@default"); + return built_string; + }); + + p.set_start_symbol("root"); + p.rule("root") + .production("strings", [](Parser&, auto&& args) -> Value { + return std::move(args[0].value); + }) + .production([](Parser&, auto&&) -> Value { + return std::vector>{}; + }); + p.rule("strings") + .production("strings", "string", [](Parser&, auto&& args) -> Value { + std::get>>(args[0].value).push_back( + std::get>(args[1].value) + ); + return std::move(args[0].value); + }) + .production("string", [](Parser&, auto&& args) -> Value { + return std::vector>{ + std::get>(args[0].value) + }; + }); + p.rule("string") + .production("id", "=", "string_literal", [](Parser&, auto&& args) -> Value { + return std::make_pair( + std::get(args[0].value), + std::get(args[2].value) + ); + }); + EXPECT_TRUE(p.prepare()); + + std::string input( + "abc = \"xyz\"\n" + "x = \"ab\\n\\t\\r\\x20cd\"" + ); + auto result = p.parse(input); + EXPECT_TRUE(result); + auto strings = std::get>>(result.value()); + EXPECT_EQ(strings.size(), 2u); + EXPECT_THAT(strings[0], Pair(Eq("abc"), Eq("xyz"))); + EXPECT_THAT(strings[1], Pair(Eq("x"), Eq("ab\n\t\r cd"))); +} + +TEST_F(TestParser, +EndInputInNonDefaultTokenizerState) { + Parser p; + + p.token("a").symbol("a").enter_state("special"); + p.token("b").symbol("b").enter_state("@default"); + p.end_token().states("@default", "special"); + + p.set_start_symbol("A"); + p.rule("A") + .production("A", "a", [](Parser&, auto&& args) { + return 1 + args[0].value; + }) + .production("A", "b", [](Parser&, auto&& args) { + return 1 + args[0].value; + }) + .production("a", [](Parser&, auto&&) { + return 1; + }) + .production("b", [](Parser&, auto&&) { + return 1; + }); + EXPECT_TRUE(p.prepare()); + + std::string input1("a"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 1); + + std::string input2("bbbba"); + result = p.parse(input2); + EXPECT_TRUE(result); + EXPECT_EQ(result.value(), 5); + + try + { + std::string input3("bbbbab"); + p.parse(input3); + FAIL() << "Expected syntax error"; + } + catch (const SyntaxError& e) + { + EXPECT_STREQ(e.what(), "Syntax error: Unknown symbol on input, expected one of @end, a, b"); + } +} + +TEST_F(TestParser, +IncludesRelationCalulcatedCorrectlyForSameRightHandSifePrefix) { + Parser p; + + p.token("a").symbol("a"); + p.token("b").symbol("b"); + + p.set_start_symbol("S"); + p.rule("S") + .production("A"); + p.rule("A") + .production("B", "b") + .production("B"); + p.rule("B") + .production("a"); + EXPECT_TRUE(p.prepare()); + + std::string input1("a"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + + std::string input2("ab"); + result = p.parse(input2); + EXPECT_TRUE(result); +} + +TEST_F(TestParser, +GlobalTokenizerAction) { + Parser p; + + std::pair location = {1, 0}; + p.global_tokenizer_action([&](std::string_view str) { + location.second += str.length(); + }); + + p.token(R"(\n)").action([&](std::string_view) { + location.first++; + location.second = 0; + return 0; + }); + p.token(R"([ \t\v\r]+)"); + p.token("a+").symbol("As"); + p.token("b{3}").symbol("Bs"); + + p.set_start_symbol("S"); + p.rule("S") + .production("S", "As") + .production("S", "Bs") + .production(); + EXPECT_TRUE(p.prepare()); + + std::string input1("aaaaa"); + auto result = p.parse(input1); + EXPECT_TRUE(result); + EXPECT_THAT(location, Pair(Eq(1), Eq(5))); + + location = {1, 0}; + std::string input2("aaa \nbbb bbb\n \n\na"); + result = p.parse(input2); + EXPECT_TRUE(result); + EXPECT_THAT(location, Pair(Eq(5), Eq(1))); +} diff --git a/tests/Pog/ParsingTable.cpp b/tests/Pog/ParsingTable.cpp new file mode 100644 index 00000000..b59db1fa --- /dev/null +++ b/tests/Pog/ParsingTable.cpp @@ -0,0 +1,12 @@ +#include + +#include + +using namespace pog; + +class TestParsingTable : public ::testing::Test {}; + +TEST_F(TestParsingTable, +AddAccept) { + //ParsingTable pt; +} diff --git a/tests/Pog/PogTests.cpp b/tests/Pog/PogTests.cpp new file mode 100644 index 00000000..5ff23f2f --- /dev/null +++ b/tests/Pog/PogTests.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/Pog/Precedence.cpp b/tests/Pog/Precedence.cpp new file mode 100644 index 00000000..4bd379d3 --- /dev/null +++ b/tests/Pog/Precedence.cpp @@ -0,0 +1,81 @@ +#include + +#include + +class TestPrecedence : public ::testing::Test {}; + +using namespace pog; + +TEST_F(TestPrecedence, +Equality) { + Precedence p1{1, Associativity::Left}; + Precedence p2{1, Associativity::Left}; + Precedence p3{1, Associativity::Right}; + Precedence p4{0, Associativity::Left}; + Precedence p5{2, Associativity::Left}; + + EXPECT_EQ(p1, p2); + EXPECT_NE(p1, p3); + EXPECT_NE(p1, p4); + EXPECT_NE(p1, p5); +} + +TEST_F(TestPrecedence, +SameLevelLeftAssociative) { + EXPECT_FALSE( + (Precedence{1, Associativity::Left}) < (Precedence{1, Associativity::Left}) + ); + EXPECT_TRUE( + (Precedence{1, Associativity::Left}) > (Precedence{1, Associativity::Left}) + ); +} + +TEST_F(TestPrecedence, +SameLevelRightAssociative) { + EXPECT_TRUE( + (Precedence{1, Associativity::Right}) < (Precedence{1, Associativity::Right}) + ); + EXPECT_FALSE( + (Precedence{1, Associativity::Right}) > (Precedence{1, Associativity::Right}) + ); +} + +TEST_F(TestPrecedence, +LowerLevelLeftAssociative) { + EXPECT_TRUE( + (Precedence{0, Associativity::Left}) < (Precedence{1, Associativity::Left}) + ); + EXPECT_FALSE( + (Precedence{0, Associativity::Left}) > (Precedence{1, Associativity::Left}) + ); +} + +TEST_F(TestPrecedence, +LowerLevelRightAssociative) { + EXPECT_TRUE( + (Precedence{0, Associativity::Right}) < (Precedence{1, Associativity::Right}) + ); + EXPECT_FALSE( + (Precedence{0, Associativity::Right}) > (Precedence{1, Associativity::Right}) + ); +} + +TEST_F(TestPrecedence, +HigherLevelLeftAssociative) { + EXPECT_FALSE( + (Precedence{2, Associativity::Left}) < (Precedence{1, Associativity::Left}) + ); + EXPECT_TRUE( + (Precedence{2, Associativity::Left}) > (Precedence{1, Associativity::Left}) + ); +} + +TEST_F(TestPrecedence, +HigherLevelRightAssociative) { + EXPECT_FALSE( + (Precedence{2, Associativity::Right}) < (Precedence{1, Associativity::Right}) + ); + EXPECT_TRUE( + (Precedence{2, Associativity::Right}) > (Precedence{1, Associativity::Right}) + ); +} diff --git a/tests/Pog/Rule.cpp b/tests/Pog/Rule.cpp new file mode 100644 index 00000000..6318b568 --- /dev/null +++ b/tests/Pog/Rule.cpp @@ -0,0 +1,92 @@ +#include + +#include + +class TestRule : public ::testing::Test {}; + +using namespace pog; + +TEST_F(TestRule, +SimpleRule) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + EXPECT_EQ(rule.get_index(), 42u); + EXPECT_EQ(rule.get_lhs(), &s1); + EXPECT_EQ(rule.get_rhs(), (std::vector*>{&s2, &s3})); + EXPECT_FALSE(rule.has_precedence()); +} + +TEST_F(TestRule, +Precedence) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + rule.set_precedence(1, Associativity::Right); + + EXPECT_EQ(rule.get_index(), 42u); + EXPECT_EQ(rule.get_lhs(), &s1); + EXPECT_EQ(rule.get_rhs(), (std::vector*>{&s2, &s3})); + EXPECT_TRUE(rule.has_precedence()); + EXPECT_EQ(rule.get_precedence(), (Precedence{1, Associativity::Right})); +} + +TEST_F(TestRule, +RightmostTerminalWhileThereIsNone) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + EXPECT_EQ(rule.get_rightmost_terminal(), nullptr); +} + +TEST_F(TestRule, +RightmostTerminal) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Terminal, "2"); + Symbol s3(3, SymbolKind::Terminal, "3"); + Symbol s4(4, SymbolKind::Nonterminal, "4"); + Rule rule(42, &s1, std::vector*>{&s2, &s3, &s4}, [](Parser&, auto&&) -> int { return 0; }); + + EXPECT_EQ(rule.get_rightmost_terminal(), &s3); +} + +TEST_F(TestRule, +ToString) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Terminal, "2"); + Symbol s3(3, SymbolKind::Terminal, "3"); + Symbol s4(4, SymbolKind::Nonterminal, "4"); + Rule rule(42, &s1, std::vector*>{&s2, &s3, &s4}, [](Parser&, auto&&) -> int { return 0; }); + + EXPECT_EQ(rule.to_string(), "1 -> 2 3 4"); +} + +TEST_F(TestRule, +EpsilonToString) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Rule rule(42, &s1, std::vector*>{}, [](Parser&, auto&&) -> int { return 0; }); + + EXPECT_EQ(rule.to_string(), "1 -> "); +} + +TEST_F(TestRule, +Equality) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Terminal, "2"); + Symbol s3(3, SymbolKind::Terminal, "3"); + Symbol s4(4, SymbolKind::Nonterminal, "4"); + Rule rule1(42, &s1, std::vector*>{&s2, &s3, &s4}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(42, &s1, std::vector*>{}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule3(43, &s1, std::vector*>{&s2, &s3, &s4}, [](Parser&, auto&&) -> int { return 0; }); + + EXPECT_TRUE(rule1 == rule2); + EXPECT_FALSE(rule1 == rule3); + + EXPECT_FALSE(rule1 != rule2); + EXPECT_TRUE(rule1 != rule3); +} diff --git a/tests/Pog/RuleBuilder.cpp b/tests/Pog/RuleBuilder.cpp new file mode 100644 index 00000000..fe33cd29 --- /dev/null +++ b/tests/Pog/RuleBuilder.cpp @@ -0,0 +1,68 @@ +#include + +#include + +using namespace pog; + +class TestRuleBuilder : public ::testing::Test +{ +public: + Grammar grammar; +}; + +TEST_F(TestRuleBuilder, +Initialization) { + RuleBuilder rb(&grammar, "A"); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); // start and end symbol + EXPECT_TRUE(grammar.get_rules().empty()); +} + +TEST_F(TestRuleBuilder, +NoProductions) { + RuleBuilder rb(&grammar, "A"); + rb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); + EXPECT_TRUE(grammar.get_rules().empty()); +} + +TEST_F(TestRuleBuilder, +SingleProductionWithoutAction) { + RuleBuilder rb(&grammar, "A"); + rb.production("a"); + rb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 4u); + EXPECT_EQ(grammar.get_rules().size(), 1u); + EXPECT_EQ(grammar.get_rules()[0]->to_string(), "A -> a"); + EXPECT_FALSE(grammar.get_rules()[0]->has_action()); +} + +TEST_F(TestRuleBuilder, +SingleProductionWithAction) { + RuleBuilder rb(&grammar, "A"); + rb.production("a", [](Parser&, auto&&) { return 42; }); + rb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 4u); + EXPECT_EQ(grammar.get_rules().size(), 1u); + EXPECT_EQ(grammar.get_rules()[0]->to_string(), "A -> a"); + EXPECT_TRUE(grammar.get_rules()[0]->has_action()); +} + +TEST_F(TestRuleBuilder, +MultipleProductionsWithActions) { + RuleBuilder rb(&grammar, "A"); + rb.production("A", "a", [](Parser&, auto&&) { return 42; }) + .production("a", [](Parser&, auto&&) { return 42; }); + rb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 4u); + EXPECT_EQ(grammar.get_rules().size(), 2u); + EXPECT_EQ(grammar.get_rules()[0]->to_string(), "A -> A a"); + EXPECT_EQ(grammar.get_rules()[1]->to_string(), "A -> a"); + EXPECT_TRUE(grammar.get_rules()[0]->has_action()); + EXPECT_TRUE(grammar.get_rules()[1]->has_action()); +} + diff --git a/tests/Pog/State.cpp b/tests/Pog/State.cpp new file mode 100644 index 00000000..526e8d36 --- /dev/null +++ b/tests/Pog/State.cpp @@ -0,0 +1,278 @@ +#include + +#include + +using namespace pog; +using namespace ::testing; + +class TestState : public ::testing::Test {}; + +TEST_F(TestState, +DefultState) { + State state; + + EXPECT_EQ(state.get_index(), std::numeric_limits::max()); +} + +TEST_F(TestState, +SimpleState) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + + EXPECT_EQ(state.get_index(), 1u); + EXPECT_EQ(state.size(), 0u); +} + +TEST_F(TestState, +SetIndex) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + state.set_index(2); + + EXPECT_EQ(state.get_index(), 2u); + EXPECT_EQ(state.size(), 0u); +} + +TEST_F(TestState, +AddItem) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(43, &s1, std::vector*>{&s2}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + auto result1 = state.add_item(Item{&rule1, 0}); + auto result2 = state.add_item(Item{&rule2, 0}); + EXPECT_EQ(state.size(), 2u); + EXPECT_THAT(result1, Pair(An*>(), Eq(true))); + EXPECT_THAT(result2, Pair(An*>(), Eq(true))); + EXPECT_NE(result1.first, result2.first); +} + +TEST_F(TestState, +AddItemAlreadyExists) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + auto result1 = state.add_item(Item{&rule1, 0}); + auto result2 = state.add_item(Item{&rule2, 0}); + EXPECT_EQ(state.size(), 1u); + EXPECT_THAT(result1, Pair(An*>(), Eq(true))); + EXPECT_THAT(result2, Pair(An*>(), Eq(false))); + EXPECT_EQ(result1.first, result2.first); +} + +TEST_F(TestState, +ItemsAreSorted) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(44, &s1, std::vector*>{}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(43, &s1, std::vector*>{&s2}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule3(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + state.add_item(Item{&rule1, 0}); + state.add_item(Item{&rule2, 0}); + state.add_item(Item{&rule3, 0}); + EXPECT_EQ(state.size(), 3u); + EXPECT_EQ(state.begin()->get()->get_rule()->get_index(), 42u); + EXPECT_EQ((state.begin() + 1)->get()->get_rule()->get_index(), 43u); + EXPECT_EQ((state.begin() + 2)->get()->get_rule()->get_index(), 44u); +} + +TEST_F(TestState, +ItemAreIterable) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(44, &s1, std::vector*>{}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(43, &s1, std::vector*>{&s2}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule3(42, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + auto result1 = state.add_item(Item{&rule1, 0}); + auto result2 = state.add_item(Item{&rule2, 0}); + auto result3 = state.add_item(Item{&rule3, 0}); + + auto expected = std::vector*>{result3.first, result2.first, result1.first}; + std::size_t i = 0; + for (const auto& item : state) + EXPECT_EQ(item.get(), expected[i++]); +} + +TEST_F(TestState, +AddTransition) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(42, &s1, std::vector*>{}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(43, &s1, std::vector*>{&s2}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule3(44, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state1(1); + state1.add_item(Item{&rule1, 0}); + State state2(2); + state2.add_item(Item{&rule2, 0}); + + state1.add_transition(&s1, &state2); + + EXPECT_EQ(state1.get_transitions().size(), 1u); + auto itr = state1.get_transitions().find(&s1); + EXPECT_NE(itr, state1.get_transitions().end()); + EXPECT_EQ(itr->second, &state2); +} + +TEST_F(TestState, +AddBackTransition) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::Nonterminal, "3"); + Rule rule1(42, &s1, std::vector*>{}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(43, &s1, std::vector*>{&s2}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule3(44, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state1(1); + state1.add_item(Item{&rule1, 0}); + State state2(30); + state2.add_item(Item{&rule2, 0}); + State state3(20); + state2.add_item(Item{&rule3, 0}); + + state1.add_back_transition(&s1, &state2); + state1.add_back_transition(&s1, &state3); + + EXPECT_EQ(state1.get_back_transitions().size(), 1u); + auto itr = state1.get_back_transitions().find(&s1); + EXPECT_NE(itr, state1.get_back_transitions().end()); + EXPECT_EQ(itr->second, (std::vector*>{&state3, &state2})); +} + +TEST_F(TestState, +IsAccepting) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::End, "3"); + Rule rule1(42, &s1, std::vector*>{}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(43, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state1(1); + state1.add_item(Item{&rule1, 0}); + State state2(2); + state2.add_item(Item{&rule2, 0}); + State state3(3); + state3.add_item(Item{&rule2, 1}); + + EXPECT_FALSE(state1.is_accepting()); + EXPECT_FALSE(state2.is_accepting()); + EXPECT_TRUE(state3.is_accepting()); +} + +TEST_F(TestState, +ToString) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::End, "3"); + Rule rule1(42, &s1, std::vector*>{}, [](Parser&, auto&&) -> int { return 0; }); + Rule rule2(43, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + state.add_item(Item{&rule1, 0}); + state.add_item(Item{&rule2, 0}); + state.add_item(Item{&rule2, 1}); + + EXPECT_EQ(state.to_string(), "1 -> 2 <*> 3\n1 -> <*> \n1 -> <*> 2 3"); +} + +TEST_F(TestState, +GetProductionItems) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::End, "3"); + Rule rule(43, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + state.add_item(Item{&rule, 0}); + state.add_item(Item{&rule, 1}); + + auto expected = std::vector*>{}; + EXPECT_EQ(state.get_production_items(), expected); + + auto i = state.add_item(Item{&rule, 2}); + expected.push_back(i.first); + EXPECT_EQ(state.get_production_items(), expected); +} + +TEST_F(TestState, +Contains) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::End, "3"); + Rule rule(43, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + state.add_item(Item{&rule, 0}); + state.add_item(Item{&rule, 1}); + + EXPECT_TRUE(state.contains(Item{&rule, 0})); + EXPECT_TRUE(state.contains(Item{&rule, 1})); + EXPECT_FALSE(state.contains(Item{&rule, 2})); +} + +TEST_F(TestState, +Equality) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::End, "3"); + Rule rule(43, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state1(1); + state1.add_item(Item{&rule, 0}); + state1.add_item(Item{&rule, 1}); + + State state2(2); + state2.add_item(Item{&rule, 0}); + state2.add_item(Item{&rule, 1}); + + State state3(3); + state3.add_item(Item{&rule, 0}); + state3.add_item(Item{&rule, 2}); + + EXPECT_TRUE(state1 == state2); + EXPECT_FALSE(state1 == state3); + + EXPECT_FALSE(state1 != state2); + EXPECT_TRUE(state1 != state3); +} + +TEST_F(TestState, +Kernel) { + Symbol s1(1, SymbolKind::Nonterminal, "1"); + Symbol s2(2, SymbolKind::Nonterminal, "2"); + Symbol s3(3, SymbolKind::End, "3"); + Rule rule(43, &s1, std::vector*>{&s2, &s3}, [](Parser&, auto&&) -> int { return 0; }); + + State state(1); + state.add_item(Item{&rule, 0}); + state.add_item(Item{&rule, 1}); + state.add_item(Item{&rule, 2}); + + std::vector kernel; + for (const auto& item : state.get_kernel()) + kernel.push_back(item->to_string()); + + EXPECT_EQ(kernel, (std::vector{"1 -> 2 <*> 3", "1 -> 2 3 <*>"})); +} diff --git a/tests/Pog/Symbol.cpp b/tests/Pog/Symbol.cpp new file mode 100644 index 00000000..cabccb75 --- /dev/null +++ b/tests/Pog/Symbol.cpp @@ -0,0 +1,57 @@ +#include + +#include + +class TestSymbol : public ::testing::Test {}; + +using namespace pog; + +TEST_F(TestSymbol, +Nonterminal) { + Symbol symbol(42, SymbolKind::Nonterminal, "testing_nonterminal"); + + EXPECT_EQ(symbol.get_index(), 42u); + EXPECT_EQ(symbol.get_name(), "testing_nonterminal"); + EXPECT_FALSE(symbol.is_end()); + EXPECT_TRUE(symbol.is_nonterminal()); + EXPECT_FALSE(symbol.is_terminal()); + EXPECT_FALSE(symbol.has_precedence()); +} + +TEST_F(TestSymbol, +Terminal) { + Symbol symbol(42, SymbolKind::Terminal, "testing_terminal"); + + EXPECT_EQ(symbol.get_index(), 42u); + EXPECT_EQ(symbol.get_name(), "testing_terminal"); + EXPECT_FALSE(symbol.is_end()); + EXPECT_FALSE(symbol.is_nonterminal()); + EXPECT_TRUE(symbol.is_terminal()); + EXPECT_FALSE(symbol.has_precedence()); +} + +TEST_F(TestSymbol, +End) { + Symbol symbol(42, SymbolKind::End, "testing_end"); + + EXPECT_EQ(symbol.get_index(), 42u); + EXPECT_EQ(symbol.get_name(), "testing_end"); + EXPECT_TRUE(symbol.is_end()); + EXPECT_FALSE(symbol.is_nonterminal()); + EXPECT_FALSE(symbol.is_terminal()); + EXPECT_FALSE(symbol.has_precedence()); +} + +TEST_F(TestSymbol, +Precedence) { + Symbol symbol(42, SymbolKind::Terminal, "testing_terminal"); + symbol.set_precedence(1, Associativity::Right); + + EXPECT_EQ(symbol.get_index(), 42u); + EXPECT_EQ(symbol.get_name(), "testing_terminal"); + EXPECT_FALSE(symbol.is_end()); + EXPECT_FALSE(symbol.is_nonterminal()); + EXPECT_TRUE(symbol.is_terminal()); + EXPECT_TRUE(symbol.has_precedence()); + EXPECT_EQ(symbol.get_precedence(), (Precedence{1, Associativity::Right})); +} diff --git a/tests/Pog/Token.cpp b/tests/Pog/Token.cpp new file mode 100644 index 00000000..5c362ca0 --- /dev/null +++ b/tests/Pog/Token.cpp @@ -0,0 +1,81 @@ +#include + +#include + +using namespace pog; +using namespace ::testing; + +class TestToken : public ::testing::Test {}; + +TEST_F(TestToken, +SimpleTokenWithoutSymbol) { + Token t(1, "abc", std::vector{"s1", "s2"}); + + EXPECT_EQ(t.get_index(), 1u); + EXPECT_EQ(t.get_pattern(), "abc"); + EXPECT_EQ(t.get_active_in_states(), (std::vector{"s1", "s2"})); + EXPECT_EQ(t.get_symbol(), nullptr); + EXPECT_THAT(t.get_regexp(), A()); + + EXPECT_FALSE(t.has_symbol()); + EXPECT_FALSE(t.has_action()); + EXPECT_FALSE(t.has_transition_to_state()); +} + +TEST_F(TestToken, +SimpleTokenWithSymbol) { + Symbol s(1, SymbolKind::Nonterminal, "a"); + Token t(1, "abc", std::vector{"s1", "s2"}, &s); + + EXPECT_EQ(t.get_index(), 1u); + EXPECT_EQ(t.get_pattern(), "abc"); + EXPECT_EQ(t.get_active_in_states(), (std::vector{"s1", "s2"})); + EXPECT_EQ(t.get_symbol(), &s); + EXPECT_THAT(t.get_regexp(), A()); + + EXPECT_TRUE(t.has_symbol()); + EXPECT_FALSE(t.has_action()); + EXPECT_FALSE(t.has_transition_to_state()); +} + +TEST_F(TestToken, +TransitionToState) { + Token t(1, "abc", std::vector{"s1", "s2"}); + t.set_transition_to_state("dest_state"); + + EXPECT_EQ(t.get_index(), 1u); + EXPECT_EQ(t.get_pattern(), "abc"); + EXPECT_EQ(t.get_active_in_states(), (std::vector{"s1", "s2"})); + EXPECT_EQ(t.get_symbol(), nullptr); + EXPECT_THAT(t.get_regexp(), A()); + + EXPECT_FALSE(t.has_symbol()); + EXPECT_FALSE(t.has_action()); + EXPECT_TRUE(t.has_transition_to_state()); + EXPECT_EQ(t.get_transition_to_state(), "dest_state"); +} + +TEST_F(TestToken, +AddActiveInState) { + Token t(1, "abc", std::vector{"s1", "s2"}); + + t.add_active_in_state("s3"); + + EXPECT_EQ(t.get_active_in_states(), (std::vector{"s1", "s2", "s3"})); +} + +TEST_F(TestToken, +Action) { + bool called = false; + + Token t(1, "abc", std::vector{"s1", "s2"}); + t.set_action([&](std::string_view str) -> int { + called = true; + return static_cast(str.length()); + }); + + EXPECT_EQ(t.get_index(), 1u); + EXPECT_TRUE(t.has_action()); + EXPECT_EQ(t.perform_action("abcdef"), 6); + EXPECT_TRUE(called); +} diff --git a/tests/Pog/TokenBuilder.cpp b/tests/Pog/TokenBuilder.cpp new file mode 100644 index 00000000..b29601a5 --- /dev/null +++ b/tests/Pog/TokenBuilder.cpp @@ -0,0 +1,145 @@ +#include + +#include + +using namespace pog; + +class TestTokenBuilder : public ::testing::Test +{ +public: + TestTokenBuilder() : grammar(), tokenizer(&grammar) {} + + Grammar grammar; + Tokenizer tokenizer; +}; + +TEST_F(TestTokenBuilder, +Initialization) { + TokenBuilder tb(&grammar, &tokenizer); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); + EXPECT_EQ(tokenizer.get_tokens().size(), 1u); +} + +TEST_F(TestTokenBuilder, +NoTokens) { + TokenBuilder tb(&grammar, &tokenizer); + tb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); + EXPECT_EQ(tokenizer.get_tokens().size(), 1u); +} + +TEST_F(TestTokenBuilder, +SingleTokenWithoutAnything) { + TokenBuilder tb(&grammar, &tokenizer, "abc"); + tb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); + EXPECT_EQ(tokenizer.get_tokens().size(), 2u); + + EXPECT_EQ(tokenizer.get_tokens()[1]->get_pattern(), "abc"); +} + +TEST_F(TestTokenBuilder, +SingleTokenWithSymbol) { + TokenBuilder tb(&grammar, &tokenizer, "abc"); + tb.symbol("ABC"); + tb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 3u); + EXPECT_EQ(tokenizer.get_tokens().size(), 2u); + + EXPECT_EQ(grammar.get_symbols()[2]->get_name(), "ABC"); + EXPECT_EQ(tokenizer.get_tokens()[1]->get_pattern(), "abc"); +} + +TEST_F(TestTokenBuilder, +SingleTokenWithAction) { + TokenBuilder tb(&grammar, &tokenizer, "abc"); + tb.action([](std::string_view) { return 42; }); + tb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); + EXPECT_EQ(tokenizer.get_tokens().size(), 2u); + + EXPECT_EQ(tokenizer.get_tokens()[1]->get_pattern(), "abc"); + EXPECT_TRUE(tokenizer.get_tokens()[1]->has_action()); + EXPECT_EQ(tokenizer.get_tokens()[1]->perform_action("xyz"), 42); +} + +TEST_F(TestTokenBuilder, +SingleTokenWithFullwordSpecifier) { + TokenBuilder tb(&grammar, &tokenizer, "abc"); + tb.fullword(); + tb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); + EXPECT_EQ(tokenizer.get_tokens().size(), 2u); + + EXPECT_EQ(tokenizer.get_tokens()[1]->get_pattern(), "abc(\\b|$)"); +} + +TEST_F(TestTokenBuilder, +SingleTokenWithStates) { + TokenBuilder tb(&grammar, &tokenizer, "abc"); + tb.states("state1", "state2"); + tb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); + EXPECT_EQ(tokenizer.get_tokens().size(), 2u); + + EXPECT_EQ(tokenizer.get_tokens()[1]->get_pattern(), "abc"); +} + +TEST_F(TestTokenBuilder, +SingleTokenWithTransitionToState) { + TokenBuilder tb(&grammar, &tokenizer, "abc"); + tb.enter_state("state1"); + tb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 2u); + EXPECT_EQ(tokenizer.get_tokens().size(), 2u); + + EXPECT_EQ(tokenizer.get_tokens()[1]->get_pattern(), "abc"); + EXPECT_TRUE(tokenizer.get_tokens()[1]->has_transition_to_state()); + EXPECT_EQ(tokenizer.get_tokens()[1]->get_transition_to_state(), "state1"); +} + +TEST_F(TestTokenBuilder, +SingleTokenWithPrecedence) { + TokenBuilder tb(&grammar, &tokenizer, "abc"); + tb.symbol("ABC"); + tb.precedence(1, Associativity::Left); + tb.done(); + + EXPECT_EQ(grammar.get_symbols().size(), 3u); + EXPECT_EQ(tokenizer.get_tokens().size(), 2u); + + EXPECT_EQ(tokenizer.get_tokens()[1]->get_pattern(), "abc"); + EXPECT_TRUE(grammar.get_symbols()[2]->has_precedence()); + EXPECT_EQ(grammar.get_symbols()[2]->get_precedence(), (Precedence{1, Associativity::Left})); +} + +TEST_F(TestTokenBuilder, +MultipleTokensDescription) { + TokenBuilder tb1(&grammar, &tokenizer, "abc"); + tb1.symbol("ABC"); + tb1.description("abc token"); + tb1.done(); + + TokenBuilder tb2(&grammar, &tokenizer, "def"); + tb2.symbol("DEF"); + tb2.done(); + + + EXPECT_EQ(grammar.get_symbols().size(), 4u); + EXPECT_EQ(tokenizer.get_tokens().size(), 3u); + + EXPECT_EQ(grammar.get_symbols()[2]->get_name(), "ABC"); + EXPECT_EQ(grammar.get_symbols()[2]->get_description(), "abc token"); + EXPECT_EQ(grammar.get_symbols()[3]->get_name(), "DEF"); + EXPECT_EQ(grammar.get_symbols()[3]->get_description(), "DEF"); + EXPECT_EQ(tokenizer.get_tokens()[1]->get_pattern(), "abc"); + EXPECT_EQ(tokenizer.get_tokens()[2]->get_pattern(), "def"); +} diff --git a/tests/Pog/Tokenizer.cpp b/tests/Pog/Tokenizer.cpp new file mode 100644 index 00000000..ad2197b1 --- /dev/null +++ b/tests/Pog/Tokenizer.cpp @@ -0,0 +1,295 @@ +#include + +#include + +using namespace pog; + +class TestTokenizer : public ::testing::Test +{ +public: + Grammar grammar; +}; + +TEST_F(TestTokenizer, +Initialization) { + Tokenizer t(&grammar); + + EXPECT_EQ(t.get_tokens().size(), 1u); + EXPECT_EQ(t.get_tokens()[0].get(), t.get_end_token()); + EXPECT_FALSE(t.get_tokens()[0]->has_symbol()); +} + +TEST_F(TestTokenizer, +AddToken) { + auto a = grammar.add_symbol(SymbolKind::Terminal, "a"); + auto b = grammar.add_symbol(SymbolKind::Terminal, "b"); + + Tokenizer t(&grammar); + + t.add_token("aaa", a, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("bbb", b, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("ccc", nullptr, std::vector{std::string{decltype(t)::DefaultState}}); + + EXPECT_EQ(t.get_tokens().size(), 4u); + EXPECT_EQ(t.get_tokens()[1]->get_pattern(), "aaa"); + EXPECT_TRUE(t.get_tokens()[1]->has_symbol()); + EXPECT_EQ(t.get_tokens()[1]->get_symbol(), a); + EXPECT_EQ(t.get_tokens()[2]->get_pattern(), "bbb"); + EXPECT_TRUE(t.get_tokens()[2]->has_symbol()); + EXPECT_EQ(t.get_tokens()[2]->get_symbol(), b); + EXPECT_EQ(t.get_tokens()[3]->get_pattern(), "ccc"); + EXPECT_FALSE(t.get_tokens()[3]->has_symbol()); +} + +TEST_F(TestTokenizer, +NextToken) { + auto a = grammar.add_symbol(SymbolKind::Terminal, "a"); + auto b = grammar.add_symbol(SymbolKind::Terminal, "b"); + + Tokenizer t(&grammar); + + t.add_token("aaa", a, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("bbb", b, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("ccc", nullptr, std::vector{std::string{decltype(t)::DefaultState}}); + t.prepare(); + + std::string input("aaacccbbb"); + t.push_input_stream(input); + + auto result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, a); + + result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, b); + + result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, grammar.get_end_of_input_symbol()); +} + +TEST_F(TestTokenizer, +NextTokenWithUnknownToken) { + auto a = grammar.add_symbol(SymbolKind::Terminal, "a"); + auto b = grammar.add_symbol(SymbolKind::Terminal, "b"); + + Tokenizer t(&grammar); + + t.add_token("aaa", a, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("bbb", b, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("ccc", nullptr, std::vector{std::string{decltype(t)::DefaultState}}); + t.prepare(); + + std::string input("aaaccbbb"); + t.push_input_stream(input); + + auto result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, a); + + result = t.next_token(); + EXPECT_FALSE(result); +} + +TEST_F(TestTokenizer, +NextTokenLongestMatchWins) { + auto a1 = grammar.add_symbol(SymbolKind::Terminal, "a1"); + auto a2 = grammar.add_symbol(SymbolKind::Terminal, "a3"); + auto a3 = grammar.add_symbol(SymbolKind::Terminal, "a3"); + + Tokenizer t(&grammar); + + t.add_token("a", a1, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("aaa", a3, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("aa", a2, std::vector{std::string{decltype(t)::DefaultState}}); + t.prepare(); + + std::string input("aaaaa"); + t.push_input_stream(input); + + auto result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, a3); +} + +TEST_F(TestTokenizer, +NextTokenIndexWinsInCaseOfEqualMatch) { + auto a3 = grammar.add_symbol(SymbolKind::Terminal, "a3"); + auto an = grammar.add_symbol(SymbolKind::Terminal, "an"); + + Tokenizer t(&grammar); + + t.add_token("aaa", a3, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("a*", an, std::vector{std::string{decltype(t)::DefaultState}}); + t.prepare(); + + std::string input("aaa"); + t.push_input_stream(input); + + auto result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, a3); +} + +TEST_F(TestTokenizer, +TokenActionsPerformed) { + auto a = grammar.add_symbol(SymbolKind::Terminal, "a"); + auto b = grammar.add_symbol(SymbolKind::Terminal, "b"); + + Tokenizer t(&grammar); + + std::vector matches; + + auto a_t = t.add_token("a+", a, std::vector{std::string{decltype(t)::DefaultState}}); + a_t->set_action([&](std::string_view str) { + matches.push_back(std::string{str}); + return 0; + }); + auto b_t = t.add_token("b+", b, std::vector{std::string{decltype(t)::DefaultState}}); + b_t->set_action([&](std::string_view str) { + matches.push_back(std::string{str}); + return 0; + }); + t.get_end_token()->set_action([&](std::string_view str) { + matches.push_back(std::string{str}); + return 0; + }); + t.prepare(); + + std::string input("aabbbbaaaaabb"); + t.push_input_stream(input); + + for (auto i = 0; i < 5; ++i) + t.next_token(); + + EXPECT_EQ(matches.size(), 5u); + EXPECT_EQ(matches, (std::vector{"aa", "bbbb", "aaaaa", "bb", ""})); +} + +TEST_F(TestTokenizer, +NextTokenGlobalActionPerformed) { + auto a = grammar.add_symbol(SymbolKind::Terminal, "a"); + auto b = grammar.add_symbol(SymbolKind::Terminal, "b"); + + Tokenizer t(&grammar); + + std::vector matches; + + t.global_action([&](std::string_view str) { + matches.push_back(fmt::format("global:{}", str)); + }); + auto a_t = t.add_token("a+", a, std::vector{std::string{decltype(t)::DefaultState}}); + a_t->set_action([&](std::string_view str) { + matches.push_back(std::string{str}); + return 0; + }); + t.add_token("b+", b, std::vector{std::string{decltype(t)::DefaultState}}); + t.prepare(); + + std::string input("aabbbbaaaaabb"); + t.push_input_stream(input); + + for (auto i = 0; i < 5; ++i) + t.next_token(); + + EXPECT_EQ(matches.size(), 7u); + EXPECT_EQ(matches, (std::vector{"global:aa", "aa", "global:bbbb", "global:aaaaa", "aaaaa", "global:bb", "global:"})); +} + +TEST_F(TestTokenizer, +InputStreamStackManipulation) { + auto a = grammar.add_symbol(SymbolKind::Terminal, "a"); + auto b = grammar.add_symbol(SymbolKind::Terminal, "b"); + + Tokenizer t(&grammar); + + t.add_token("aaa", a, std::vector{std::string{decltype(t)::DefaultState}}); + t.add_token("bbb", b, std::vector{std::string{decltype(t)::DefaultState}}); + t.prepare(); + + std::string input("aaabbb"); + t.push_input_stream(input); + + auto result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, a); + + std::string input2("aaaaaa"); + t.push_input_stream(input2); + + result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, a); + + result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, a); + + result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, grammar.get_end_of_input_symbol()); + + t.pop_input_stream(); + + result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, b); + + result = t.next_token(); + EXPECT_TRUE(result); + EXPECT_EQ(result.value().symbol, grammar.get_end_of_input_symbol()); +} + +TEST_F(TestTokenizer, +StatesAndTransitions) { + auto a = grammar.add_symbol(SymbolKind::Terminal, "a"); + auto b = grammar.add_symbol(SymbolKind::Terminal, "b"); + + Tokenizer t(&grammar); + + auto a_t = t.add_token("aaa", a, std::vector{std::string{decltype(t)::DefaultState}}); + auto b_t = t.add_token("bbb", b, std::vector{"state1"}); + a_t->set_transition_to_state("state1"); + b_t->set_transition_to_state(std::string{decltype(t)::DefaultState}); + t.prepare(); + + std::string input("aaabbb"); + t.push_input_stream(input); + EXPECT_TRUE(t.next_token()); + EXPECT_TRUE(t.next_token()); + EXPECT_TRUE(t.next_token()); + t.pop_input_stream(); + + std::string input2("aaaaaa"); + t.push_input_stream(input2); + EXPECT_TRUE(t.next_token()); + EXPECT_FALSE(t.next_token()); + t.pop_input_stream(); +} + +TEST_F(TestTokenizer, +EnterState) { + auto a = grammar.add_symbol(SymbolKind::Terminal, "a"); + auto b = grammar.add_symbol(SymbolKind::Terminal, "b"); + + Tokenizer t(&grammar); + + [[maybe_unused]] auto a_t = t.add_token("aaa", a, std::vector{std::string{decltype(t)::DefaultState}}); + [[maybe_unused]] auto b_t = t.add_token("bbb", b, std::vector{"state1"}); + t.prepare(); + + t.enter_state("state1"); + + std::string input("aaabbb"); + t.push_input_stream(input); + EXPECT_FALSE(t.next_token()); + t.pop_input_stream(); + + std::string input2("bbbbbb"); + t.push_input_stream(input2); + EXPECT_TRUE(t.next_token()); + EXPECT_TRUE(t.next_token()); + EXPECT_FALSE(t.next_token()); + t.pop_input_stream(); +} diff --git a/tests/Pog/Utils.cpp b/tests/Pog/Utils.cpp new file mode 100644 index 00000000..6a207247 --- /dev/null +++ b/tests/Pog/Utils.cpp @@ -0,0 +1,62 @@ +#include + +#include + +class TestUtils : public ::testing::Test {}; + +TEST_F(TestUtils, +transform_if) { + std::vector v{0, 1, 2, 3, 4, 5, 6}; + + std::vector result; + pog::transform_if(v.begin(), v.end(), std::back_inserter(result), + [](auto i) { return i % 2 == 0; }, + [](auto i) { return i + 10; } + ); + EXPECT_EQ(result, (std::vector{10, 12, 14, 16})); + + result.clear(); + pog::transform_if(v.begin(), v.end(), std::back_inserter(result), + [](auto i) { return i < 100; }, + [](auto i) { return i + 10; } + ); + EXPECT_EQ(result, (std::vector{10, 11, 12, 13, 14, 15, 16})); + + result.clear(); + pog::transform_if(v.begin(), v.end(), std::back_inserter(result), + [](auto i) { return i > 100; }, + [](auto i) { return i + 10; } + ); + EXPECT_EQ(result, (std::vector{})); +} + +TEST_F(TestUtils, +accumulate_if) { + std::vector v{1, 2, 3, 4, 5, 6}; + + auto result = pog::accumulate_if(v.begin(), v.end(), 0, + [](auto i) { return i % 2 == 0; }, + [](auto res, auto i) { return res + i; } + ); + EXPECT_EQ(result, 12); + + result = pog::accumulate_if(v.begin(), v.end(), 0, + [](auto i) { return i < 100; }, + [](auto res, auto i) { return res + i; } + ); + EXPECT_EQ(result, 21); + + result = pog::accumulate_if(v.begin(), v.end(), 0, + [](auto i) { return i > 100; }, + [](auto res, auto i) { return res + i; } + ); + EXPECT_EQ(result, 0); +} + +TEST_F(TestUtils, +hash_combine) { + EXPECT_EQ(pog::hash_combine(1, 2), pog::hash_combine(1, 2)); + EXPECT_NE(pog::hash_combine(1, 2), pog::hash_combine(1, 3)); + EXPECT_NE(pog::hash_combine(1, 2), pog::hash_combine(2, 1)); + EXPECT_NE(pog::hash_combine(1, 2), pog::hash_combine(1, 2, 3)); +} diff --git a/tests/Python/Buildsystem/CMakeLists.txt b/tests/Python/Buildsystem/CMakeLists.txt new file mode 100644 index 00000000..5a097f07 --- /dev/null +++ b/tests/Python/Buildsystem/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.25) + + +add_custom_target(python_build_copy_tests ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/DummyProject" + "${CMAKE_CURRENT_BINARY_DIR}/DummyProject" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/test_build.py" + "${CMAKE_CURRENT_BINARY_DIR}/test_build.py" + COMMENT "Copy python tests and their artifacts to build directory" +) + +add_test( + NAME TestBuildsystem + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E env + PYTHONPATH=${CMAKE_SOURCE_DIR} + python3 + "${CMAKE_CURRENT_BINARY_DIR}/test_build.py" +) \ No newline at end of file diff --git a/tests/Python/Buildsystem/DummyProject/CMakeLists.txt b/tests/Python/Buildsystem/DummyProject/CMakeLists.txt new file mode 100644 index 00000000..930b3cbc --- /dev/null +++ b/tests/Python/Buildsystem/DummyProject/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.25) +project(dummy) + +find_package(plog 1.0.0 REQUIRED) + +if(NOT DEFINED DUMMY_MESSAGE OR DUMMY_MESSAGE STREQUAL "") + message(FATAL_ERROR "Dummy: expected DUMMY_MESSAGE to be defined") +endif() + +add_executable(dummy main.cpp) + +target_compile_definitions(dummy + PRIVATE + # The backslashes escape the quote into the generated -D flag + DUMMY_MESSAGE=\"${DUMMY_MESSAGE}\" +) + +target_link_libraries(dummy + PRIVATE + plog::plog +) \ No newline at end of file diff --git a/tests/Python/Buildsystem/DummyProject/conanfile.py b/tests/Python/Buildsystem/DummyProject/conanfile.py new file mode 100644 index 00000000..b6f90766 --- /dev/null +++ b/tests/Python/Buildsystem/DummyProject/conanfile.py @@ -0,0 +1,20 @@ +from typing import Self, List + +from conan import ConanFile +from conan.tools.cmake import cmake_layout + + +class Dummy(ConanFile): + generators: List[str] = ['CMakeToolchain', 'CMakeDeps'] + settings: List[str] = ['os', 'compiler', 'build_type', 'arch'] + + def __init__(self: Self, display_name: str = '') -> None: + self.name = 'Dummy' + self.version = '1.0.0' + super().__init__(display_name) + + def requirements(self: Self) -> None: + self.requires('plog/1.1.10') + + def layout(self): + cmake_layout(self) \ No newline at end of file diff --git a/tests/Python/Buildsystem/DummyProject/main.cpp b/tests/Python/Buildsystem/DummyProject/main.cpp new file mode 100644 index 00000000..6d30eee3 --- /dev/null +++ b/tests/Python/Buildsystem/DummyProject/main.cpp @@ -0,0 +1,13 @@ +#include + +// compilation will fail if plog is not available +#include + +#ifndef DUMMY_MESSAGE +#error "Expected macro DUMMY_MESSAGE in this compilation unit" +#endif + +int main() { + std::cout << DUMMY_MESSAGE; + return 0; +} \ No newline at end of file diff --git a/tests/Python/Buildsystem/test_build.py b/tests/Python/Buildsystem/test_build.py new file mode 100644 index 00000000..e07439e2 --- /dev/null +++ b/tests/Python/Buildsystem/test_build.py @@ -0,0 +1,99 @@ +import os +import logging +import unittest + +from pathlib import Path +from scripts.build import Routines, Config + +# TODO: make a single test runner +# TODO: add more tests + run resulting executable +class BuildsystemTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.dummy_message = 'Hello, HyperCPU!' + cls.orig_cwd = Path.cwd() + cls.dummy_dir = cls.orig_cwd / 'DummyProject' + + os.chdir(cls.dummy_dir) + + @classmethod + def tearDownClass(cls): + os.chdir(cls.orig_cwd) + + def setUp(self): + self.config = Config() + self.routines = Routines(self.config) + + def test_01_remove_no_dirs(self): + self.config.build_configs = ['Debug', 'Release'] + self.routines.remove() + + def test_01_remove_a_single_dir(self): + path = self.config.build_directory / 'Debug' + path.mkdir(parents=True, exist_ok=True) + + self.config.build_configs = ['Debug'] + self.routines.remove() + + self.assertFalse(path.exists()) + + def test_01_remove_selected_dir(self): + build_path = self.config.build_directory + paths = [build_path / 'Debug', build_path / 'Release'] + for path in paths: + path.mkdir(parents=True) + + self.config.build_configs = ['Debug'] + self.routines.remove() + + debug, release = paths + self.assertTrue(build_path.exists()) + self.assertFalse(debug.exists()) + self.assertTrue(release.exists()) + + def test_01_remove_all_dirs(self): + build_path = self.config.build_directory + paths = [build_path / 'Debug', build_path / 'Release'] + for path in paths: + path.mkdir(parents=True, exist_ok=True) + + self.config.build_configs = ['Debug', 'Release'] + self.routines.remove() + + debug, release = paths + # it is important to preserve top-level "build" dir + self.assertTrue(build_path.exists()) + self.assertFalse(debug.exists()) + self.assertFalse(release.exists()) + + def test_02_conan_install(self): + # it just has to work + self.config.allow_profile_detection = True + self.config.build_configs = ['Debug', 'Release'] + self.routines.conan_install() + + build_path = self.config.build_directory + debug, release = build_path / 'Debug', build_path / 'Release' + self.assertTrue(build_path.exists()) + self.assertTrue(debug.exists()) + self.assertTrue(release.exists()) + + def test_03_configure(self): + self.config.defines.append( + ('DUMMY_MESSAGE', 'STRING', self.dummy_message) + ) + self.config.build_configs = ['Debug', 'Release'] + self.routines.configure() + + def test_04_build(self): + self.config.build_configs = ['Debug', 'Release'] + self.routines.build() + pass + + def test_05_symlink_debug_compile_commands(self): + pass + pass + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) + unittest.main() \ No newline at end of file diff --git a/test/fixtures.hpp b/tests/fixtures.hpp similarity index 99% rename from test/fixtures.hpp rename to tests/fixtures.hpp index 03137514..404be3d1 100644 --- a/test/fixtures.hpp +++ b/tests/fixtures.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #define private public #include diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 00000000..836659c0 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,8 @@ +#include +#include + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}