diff --git a/.github/workflows/cpp-build-test.yml b/.github/workflows/cpp-build-test.yml new file mode 100644 index 0000000..0538a65 --- /dev/null +++ b/.github/workflows/cpp-build-test.yml @@ -0,0 +1,56 @@ +name: Cpp Build and Test + +on: + push: + branches: [main] + paths: + - "Cpp/**" + - "grammar/*.g4" + pull_request: + branches: [main] + paths: + - "Cpp/**" + - "grammar/*.g4" + +jobs: + build-and-test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, windows-latest] + fail-fast: false + env: + DEMO_DIR: demo/${{ matrix.os == 'windows-latest' && 'Win32/Debug' || 'Mac' }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: "3.x" + + - name: Generate ANTLR4 files (Windows) + if: matrix.os == 'windows-latest' + run: .\generate.ps1 + + - name: Generate ANTLR4 files (macOS) + if: matrix.os == 'macos-latest' + run: sh ./generate.sh + shell: bash + + - name: Configure CMake + run: | + mkdir -p Cpp/build + cd Cpp/build + cmake .. -DWITH_TEST=true + + - name: Build + run: | + cd Cpp/build + cmake --build . + + - name: Run tests + run: | + cd Cpp/build/${{ env.DEMO_DIR }} + ls + ./ArcscriptTest diff --git a/.gitignore b/.gitignore index 413023d..7e2e3c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store - +.idea /grammar/**/.antlr !/grammar/*.g4 diff --git a/Cpp/.gitignore b/Cpp/.gitignore index 21bc544..d1860aa 100644 --- a/Cpp/.gitignore +++ b/Cpp/.gitignore @@ -1 +1,7 @@ -src/Generated \ No newline at end of file +src/Generated +.idea +build +cmake-build-debug-visual-studio +cmake-build-debug +cmake-build-release-visual-studio +cmake-build-release diff --git a/Cpp/CMakeLists.txt b/Cpp/CMakeLists.txt new file mode 100755 index 0000000..72bbd5c --- /dev/null +++ b/Cpp/CMakeLists.txt @@ -0,0 +1,175 @@ +# minimum required CMAKE version +CMAKE_MINIMUM_REQUIRED(VERSION 3.10 FATAL_ERROR) + +message(STATUS "CMAKE_CXX_COMPILER_ID=${CMAKE_CXX_COMPILER_ID}") +message(STATUS "CMake version: ${CMAKE_VERSION}") + +set(PROJECT_VERSION "0.1.0") +project(ArcscriptTranspiler VERSION ${PROJECT_VERSION}) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +message(STATUS "Generator: ${CMAKE_GENERATOR}") +message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message(STATUS "CMAKE_CONFIGURATION_TYPES: ${CMAKE_CONFIGURATION_TYPES}") + +# compiler must be 17 +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +# required if linking to static library +#add_definitions(-DANTLR4CPP_STATIC) + +# using /MD flag for antlr4_runtime (for Visual C++ compilers only) +set(ANTLR4_WITH_STATIC_CRT OFF) + +# Specify the version of the antlr4 library needed for this project. +# By default the latest version of antlr4 will be used. You can specify a +# specific, stable version by setting a repository tag value or a link +# to a zip file containing the libary source. + set(ANTLR4_TAG 8e6fd91) +# set(ANTLR4_ZIP_REPOSITORY https://github.com/antlr/antlr4/archive/refs/tags/4.13.2.zip) + +# add external build for antlrcpp +include(ExternalAntlr4Cpp) +# add antlr4cpp artifacts to project environment +include_directories(${ANTLR4_INCLUDE_DIRS}) + +include_directories(src/Generated/ArcscriptLexer) +include_directories(src/Generated/ArcscriptParser) +include_directories(src) + +add_library(ArcscriptTranspiler SHARED) + +target_sources(ArcscriptTranspiler PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptTranspiler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptParserBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptVisitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptOutputs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptFunctions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptExpression.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptErrorListener.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Generated/ArcscriptLexer/ArcscriptLexer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Generated/ArcscriptParser/ArcscriptParser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Generated/ArcscriptParser/ArcscriptParserBaseVisitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Generated/ArcscriptParser/ArcscriptParserVisitor.cpp +) + +target_sources(ArcscriptTranspiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptTranspiler.h) + +set(ARCSCRIPT_INCLUDE_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptTranspiler.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptHelpers.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptErrorExceptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/ArcscriptOutputs.h +) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # For MSVC, we need to link against the shared library + target_compile_options(ArcscriptTranspiler PRIVATE -Wall -Wextra) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # For other compilers, we can link against the static library + target_compile_definitions(ArcscriptTranspiler PRIVATE WIN_EXPORT) +endif() + +target_link_libraries(ArcscriptTranspiler PRIVATE antlr4_shared) + +if (WIN32 OR WIN64) + set(LIBRARY_NAME "${PROJECT_NAME}.lib") + set(DLL_NAME "${PROJECT_NAME}.dll") + if (WIN64) + set(PLATFORM "Win64") + else() + set(PLATFORM "Win32") + endif() +elseif (APPLE) + set(DLL_NAME "lib${PROJECT_NAME}.dylib") + set(PLATFORM "Mac") +elseif (UNIX) + set(DLL_NAME "lib${PROJECT_NAME}.so") + set(PLATFORM "Linux") +endif() + +set_target_properties(ArcscriptTranspiler PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib/${PLATFORM}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib/${PLATFORM}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib/${PLATFORM}" + OUTPUT_NAME ${PROJECT_NAME} +) + +if (APPLE) + add_custom_command(TARGET ArcscriptTranspiler PRE_BUILD + COMMAND install_name_tool -id @rpath/libantlr4-runtime.dylib ${ANTLR4_RUNTIME_LIBRARIES}) +endif () + +# Copy the antlr4 runtime library to the output directory +add_custom_command(TARGET ArcscriptTranspiler PRE_BUILD + COMMAND ${CMAKE_COMMAND} + -E copy ${ANTLR4_RUNTIME_LIBRARIES} "$" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +# Copy the generated headers to the output directory +if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/lib/include") + add_custom_command(TARGET ArcscriptTranspiler POST_BUILD + COMMAND ${CMAKE_COMMAND} + -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/lib/include") +endif() + +add_custom_command(TARGET ArcscriptTranspiler POST_BUILD + COMMAND ${CMAKE_COMMAND} + -E copy ${ARCSCRIPT_INCLUDE_HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/lib/include" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +if (WITH_TEST) + add_executable(ArcscriptTest ${CMAKE_CURRENT_SOURCE_DIR}/demo/ArcscriptTest.cpp) + + include(FetchContent) + FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz) + FetchContent_MakeAvailable(json) + + target_link_libraries(ArcscriptTest PRIVATE nlohmann_json::nlohmann_json) + target_link_libraries(ArcscriptTest PRIVATE ArcscriptTranspiler) + + set_target_properties(ArcscriptTest PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/demo/${PLATFORM}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/demo/${PLATFORM}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/demo/${PLATFORM}" + OUTPUT_NAME ArcscriptTest + ) + + file(GLOB TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/demo/tests/*.json") + + if (NOT EXISTS "$/tests") + add_custom_command(TARGET ArcscriptTest POST_BUILD + COMMAND ${CMAKE_COMMAND} + -E make_directory "$/tests") + endif() + + add_custom_command(TARGET ArcscriptTest POST_BUILD + COMMAND ${CMAKE_COMMAND} + -E copy ${TEST_SOURCES} "$/tests" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + if (WIN32 OR WIN64) + set(ARCSCRIPT_TRANSPILER_LIB_FILES + "$/${DLL_NAME}" + "$/${LIBRARY_NAME}" + "$/${PROJECT_NAME}.exp" + "$/${PROJECT_NAME}.pdb" + ) + elseif (APPLE) + set(ARCSCRIPT_TRANSPILER_LIB_FILES + "$/${DLL_NAME}" + "$/${LIBRARY_NAME}" + ) + endif() + + add_custom_command(TARGET ArcscriptTest POST_BUILD + COMMAND ${CMAKE_COMMAND} + -E copy ${ANTLR4_RUNTIME_LIBRARIES} "$" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + add_custom_command(TARGET ArcscriptTest POST_BUILD + COMMAND ${CMAKE_COMMAND} + -E copy ${ARCSCRIPT_TRANSPILER_LIB_FILES} "$" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +endif (WITH_TEST) \ No newline at end of file diff --git a/Cpp/README.md b/Cpp/README.md new file mode 100644 index 0000000..2418ce5 --- /dev/null +++ b/Cpp/README.md @@ -0,0 +1,20 @@ +# Arcscript C++ Transpiler + +## Prerequisites + +- CMake 3.10 or higher +- A C++ compiler that supports C++17 or higher + +## Building the Project + +### Windows & Linux + +1. After generating the ANTLR4 parser files from the top root folder of this repository, run the following commands to build the project: + + ```bash + mkdir build + cd build + cmake .. + cmake --build . + ``` +2. The generated libraries will be located in the `build/Debug` or `build/Release` directory, depending on your build configuration. The folder also includes the `include` directory with the header files needed to use the transpiler. \ No newline at end of file diff --git a/Cpp/cmake/ExternalAntlr4Cpp.cmake b/Cpp/cmake/ExternalAntlr4Cpp.cmake new file mode 100755 index 0000000..620b2c8 --- /dev/null +++ b/Cpp/cmake/ExternalAntlr4Cpp.cmake @@ -0,0 +1,177 @@ +cmake_minimum_required(VERSION 3.10) + +if(POLICY CMP0114) + cmake_policy(SET CMP0114 NEW) +endif() + +include(ExternalProject) + +set(ANTLR4_ROOT ${CMAKE_CURRENT_BINARY_DIR}/antlr4_runtime/src/antlr4_runtime) +set(ANTLR4_INCLUDE_DIRS ${ANTLR4_ROOT}/runtime/Cpp/runtime/src) +set(ANTLR4_GIT_REPOSITORY https://github.com/antlr/antlr4.git) +if(NOT DEFINED ANTLR4_TAG) + # Set to branch name to keep library updated at the cost of needing to rebuild after 'clean' + # Set to commit hash to keep the build stable and does not need to rebuild after 'clean' + set(ANTLR4_TAG master) +endif() + +# Ensure that the include dir already exists at configure time (to avoid cmake erroring +# on non-existent include dirs) +file(MAKE_DIRECTORY "${ANTLR4_INCLUDE_DIRS}") + +if(${CMAKE_GENERATOR} MATCHES "Visual Studio.*") + set(ANTLR4_OUTPUT_DIR ${ANTLR4_ROOT}/runtime/Cpp/runtime/$(Configuration)) +elseif(${CMAKE_GENERATOR} MATCHES "Xcode.*") + set(ANTLR4_OUTPUT_DIR ${ANTLR4_ROOT}/runtime/Cpp/runtime/$(CONFIGURATION)) +else() + set(ANTLR4_OUTPUT_DIR ${ANTLR4_ROOT}/runtime/Cpp/runtime) +endif() + +if(MSVC) + set(ANTLR4_STATIC_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/antlr4-runtime-static.lib) + set(ANTLR4_SHARED_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/antlr4-runtime.lib) + set(ANTLR4_RUNTIME_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/antlr4-runtime.dll) +else() + set(ANTLR4_STATIC_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/libantlr4-runtime.a) + if(MINGW) + set(ANTLR4_SHARED_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/libantlr4-runtime.dll.a) + set(ANTLR4_RUNTIME_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/libantlr4-runtime.dll) + elseif(CYGWIN) + set(ANTLR4_SHARED_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/libantlr4-runtime.dll.a) + set(ANTLR4_RUNTIME_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/cygantlr4-runtime-4.13.2.dll) + elseif(APPLE) + set(ANTLR4_RUNTIME_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/libantlr4-runtime.dylib) + else() + set(ANTLR4_RUNTIME_LIBRARIES + ${ANTLR4_OUTPUT_DIR}/libantlr4-runtime.so) + endif() +endif() + +if(${CMAKE_GENERATOR} MATCHES ".* Makefiles") + # This avoids + # 'warning: jobserver unavailable: using -j1. Add '+' to parent make rule.' + set(ANTLR4_BUILD_COMMAND $(MAKE)) +elseif(${CMAKE_GENERATOR} MATCHES "Visual Studio.*") + set(ANTLR4_BUILD_COMMAND + ${CMAKE_COMMAND} + --build . + --config $(Configuration) + --target) +elseif(${CMAKE_GENERATOR} MATCHES "Xcode.*") + set(ANTLR4_BUILD_COMMAND + ${CMAKE_COMMAND} + --build . + --config $(CONFIGURATION) + --target) +else() + set(ANTLR4_BUILD_COMMAND + ${CMAKE_COMMAND} + --build . + --target) +endif() + +if(NOT DEFINED ANTLR4_WITH_STATIC_CRT) + set(ANTLR4_WITH_STATIC_CRT ON) +endif() + +if(ANTLR4_ZIP_REPOSITORY) + ExternalProject_Add( + antlr4_runtime + PREFIX antlr4_runtime + URL ${ANTLR4_ZIP_REPOSITORY} + DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR} + BUILD_COMMAND "" + BUILD_IN_SOURCE 1 + SOURCE_DIR ${ANTLR4_ROOT} + SOURCE_SUBDIR runtime/Cpp + CMAKE_CACHE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DWITH_STATIC_CRT:BOOL=${ANTLR4_WITH_STATIC_CRT} + -DDISABLE_WARNINGS:BOOL=ON + # -DCMAKE_CXX_STANDARD:STRING=17 # if desired, compile the runtime with a different C++ standard + # -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} # alternatively, compile the runtime with the same C++ standard as the outer project + INSTALL_COMMAND "" + EXCLUDE_FROM_ALL 1) +else() + ExternalProject_Add( + antlr4_runtime + PREFIX antlr4_runtime + GIT_REPOSITORY ${ANTLR4_GIT_REPOSITORY} + GIT_TAG ${ANTLR4_TAG} + DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR} + BUILD_COMMAND "" + BUILD_IN_SOURCE 1 + SOURCE_DIR ${ANTLR4_ROOT} + SOURCE_SUBDIR runtime/Cpp + CMAKE_CACHE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DWITH_STATIC_CRT:BOOL=${ANTLR4_WITH_STATIC_CRT} + -DDISABLE_WARNINGS:BOOL=ON + # -DCMAKE_CXX_STANDARD:STRING=17 # if desired, compile the runtime with a different C++ standard + # -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} # alternatively, compile the runtime with the same C++ standard as the outer project + INSTALL_COMMAND "" + EXCLUDE_FROM_ALL 1) +endif() + +# Separate build step as rarely people want both +set(ANTLR4_BUILD_DIR ${ANTLR4_ROOT}) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14.0") + # CMake 3.14 builds in above's SOURCE_SUBDIR when BUILD_IN_SOURCE is true + set(ANTLR4_BUILD_DIR ${ANTLR4_ROOT}/runtime/Cpp) +endif() + +ExternalProject_Add_Step( + antlr4_runtime + build_static + COMMAND ${ANTLR4_BUILD_COMMAND} antlr4_static + # Depend on target instead of step (a custom command) + # to avoid running dependent steps concurrently + DEPENDS antlr4_runtime + BYPRODUCTS ${ANTLR4_STATIC_LIBRARIES} + EXCLUDE_FROM_MAIN 1 + WORKING_DIRECTORY ${ANTLR4_BUILD_DIR}) +ExternalProject_Add_StepTargets(antlr4_runtime build_static) + +add_library(antlr4_static STATIC IMPORTED) +add_dependencies(antlr4_static antlr4_runtime-build_static) +set_target_properties(antlr4_static PROPERTIES + IMPORTED_LOCATION ${ANTLR4_STATIC_LIBRARIES}) +target_include_directories(antlr4_static + INTERFACE + ${ANTLR4_INCLUDE_DIRS} +) + +ExternalProject_Add_Step( + antlr4_runtime + build_shared + COMMAND ${ANTLR4_BUILD_COMMAND} antlr4_shared + # Depend on target instead of step (a custom command) + # to avoid running dependent steps concurrently + DEPENDS antlr4_runtime + BYPRODUCTS ${ANTLR4_SHARED_LIBRARIES} ${ANTLR4_RUNTIME_LIBRARIES} + EXCLUDE_FROM_MAIN 1 + WORKING_DIRECTORY ${ANTLR4_BUILD_DIR}) +ExternalProject_Add_StepTargets(antlr4_runtime build_shared) + +add_library(antlr4_shared SHARED IMPORTED) +add_dependencies(antlr4_shared antlr4_runtime-build_shared) +set_target_properties(antlr4_shared PROPERTIES + IMPORTED_LOCATION ${ANTLR4_RUNTIME_LIBRARIES}) +target_include_directories(antlr4_shared + INTERFACE + ${ANTLR4_INCLUDE_DIRS} +) + +if(ANTLR4_SHARED_LIBRARIES) + set_target_properties(antlr4_shared PROPERTIES + IMPORTED_IMPLIB ${ANTLR4_SHARED_LIBRARIES}) +endif() \ No newline at end of file diff --git a/Cpp/cmake/FindANTLR.cmake b/Cpp/cmake/FindANTLR.cmake new file mode 100755 index 0000000..77f4f7b --- /dev/null +++ b/Cpp/cmake/FindANTLR.cmake @@ -0,0 +1,124 @@ +find_package(Java QUIET COMPONENTS Runtime) + +if(NOT ANTLR_EXECUTABLE) + find_program(ANTLR_EXECUTABLE + NAMES antlr.jar antlr4.jar antlr-4.jar antlr-4.13.2-complete.jar) +endif() + +if(ANTLR_EXECUTABLE AND Java_JAVA_EXECUTABLE) + execute_process( + COMMAND ${Java_JAVA_EXECUTABLE} -jar ${ANTLR_EXECUTABLE} + OUTPUT_VARIABLE ANTLR_COMMAND_OUTPUT + ERROR_VARIABLE ANTLR_COMMAND_ERROR + RESULT_VARIABLE ANTLR_COMMAND_RESULT + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(ANTLR_COMMAND_RESULT EQUAL 0) + string(REGEX MATCH "Version [0-9]+(\\.[0-9]+)*" ANTLR_VERSION ${ANTLR_COMMAND_OUTPUT}) + string(REPLACE "Version " "" ANTLR_VERSION ${ANTLR_VERSION}) + else() + message( + SEND_ERROR + "Command '${Java_JAVA_EXECUTABLE} -jar ${ANTLR_EXECUTABLE}' " + "failed with the output '${ANTLR_COMMAND_ERROR}'") + endif() + + macro(ANTLR_TARGET Name InputFile) + set(ANTLR_OPTIONS LEXER PARSER LISTENER VISITOR) + set(ANTLR_ONE_VALUE_ARGS PACKAGE OUTPUT_DIRECTORY DEPENDS_ANTLR) + set(ANTLR_MULTI_VALUE_ARGS COMPILE_FLAGS DEPENDS) + cmake_parse_arguments(ANTLR_TARGET + "${ANTLR_OPTIONS}" + "${ANTLR_ONE_VALUE_ARGS}" + "${ANTLR_MULTI_VALUE_ARGS}" + ${ARGN}) + + set(ANTLR_${Name}_INPUT ${InputFile}) + + get_filename_component(ANTLR_INPUT ${InputFile} NAME_WE) + + if(ANTLR_TARGET_OUTPUT_DIRECTORY) + set(ANTLR_${Name}_OUTPUT_DIR ${ANTLR_TARGET_OUTPUT_DIRECTORY}) + else() + set(ANTLR_${Name}_OUTPUT_DIR + ${CMAKE_CURRENT_BINARY_DIR}/antlr4cpp_generated_src/${ANTLR_INPUT}) + endif() + + unset(ANTLR_${Name}_CXX_OUTPUTS) + + if((ANTLR_TARGET_LEXER AND NOT ANTLR_TARGET_PARSER) OR + (ANTLR_TARGET_PARSER AND NOT ANTLR_TARGET_LEXER)) + list(APPEND ANTLR_${Name}_CXX_OUTPUTS + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}.h + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}.cpp) + set(ANTLR_${Name}_OUTPUTS + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}.interp + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}.tokens) + else() + list(APPEND ANTLR_${Name}_CXX_OUTPUTS + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Lexer.h + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Lexer.cpp + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Parser.h + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Parser.cpp) + list(APPEND ANTLR_${Name}_OUTPUTS + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Lexer.interp + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Lexer.tokens) + endif() + + if(ANTLR_TARGET_LISTENER) + list(APPEND ANTLR_${Name}_CXX_OUTPUTS + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}BaseListener.h + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}BaseListener.cpp + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Listener.h + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Listener.cpp) + list(APPEND ANTLR_TARGET_COMPILE_FLAGS -listener) + endif() + + if(ANTLR_TARGET_VISITOR) + list(APPEND ANTLR_${Name}_CXX_OUTPUTS + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}BaseVisitor.h + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}BaseVisitor.cpp + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Visitor.h + ${ANTLR_${Name}_OUTPUT_DIR}/${ANTLR_INPUT}Visitor.cpp) + list(APPEND ANTLR_TARGET_COMPILE_FLAGS -visitor) + endif() + + if(ANTLR_TARGET_PACKAGE) + list(APPEND ANTLR_TARGET_COMPILE_FLAGS -package ${ANTLR_TARGET_PACKAGE}) + endif() + + list(APPEND ANTLR_${Name}_OUTPUTS ${ANTLR_${Name}_CXX_OUTPUTS}) + + if(ANTLR_TARGET_DEPENDS_ANTLR) + if(ANTLR_${ANTLR_TARGET_DEPENDS_ANTLR}_INPUT) + list(APPEND ANTLR_TARGET_DEPENDS + ${ANTLR_${ANTLR_TARGET_DEPENDS_ANTLR}_INPUT}) + list(APPEND ANTLR_TARGET_DEPENDS + ${ANTLR_${ANTLR_TARGET_DEPENDS_ANTLR}_OUTPUTS}) + else() + message(SEND_ERROR + "ANTLR target '${ANTLR_TARGET_DEPENDS_ANTLR}' not found") + endif() + endif() + + add_custom_command( + OUTPUT ${ANTLR_${Name}_OUTPUTS} + COMMAND ${Java_JAVA_EXECUTABLE} -jar ${ANTLR_EXECUTABLE} + ${InputFile} + -o ${ANTLR_${Name}_OUTPUT_DIR} + -no-listener + -Dlanguage=Cpp + ${ANTLR_TARGET_COMPILE_FLAGS} + DEPENDS ${InputFile} + ${ANTLR_TARGET_DEPENDS} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Building ${Name} with ANTLR ${ANTLR_VERSION}") + endmacro(ANTLR_TARGET) + +endif(ANTLR_EXECUTABLE AND Java_JAVA_EXECUTABLE) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + ANTLR + REQUIRED_VARS ANTLR_EXECUTABLE Java_JAVA_EXECUTABLE + VERSION_VAR ANTLR_VERSION) \ No newline at end of file diff --git a/Cpp/cmake/antlr4-generator.cmake.in b/Cpp/cmake/antlr4-generator.cmake.in new file mode 100755 index 0000000..9672d58 --- /dev/null +++ b/Cpp/cmake/antlr4-generator.cmake.in @@ -0,0 +1,181 @@ +set(ANTLR_VERSION @ANTLR_VERSION@) + +@PACKAGE_INIT@ + +if (NOT ANTLR4_CPP_GENERATED_SRC_DIR) + set(ANTLR4_GENERATED_SRC_DIR ${CMAKE_BINARY_DIR}/antlr4_generated_src) +endif() + +FIND_PACKAGE(Java COMPONENTS Runtime REQUIRED) + +# +# The ANTLR generator will output the following files given the input file f.g4 +# +# Input -> f.g4 +# Output -> f.h +# -> f.cpp +# +# the following files will only be produced if there is a parser contained +# Flag -visitor active +# Output -> BaseVisitor.h +# -> BaseVisitor.cpp +# -> Visitor.h +# -> Visitor.cpp +# +# Flag -listener active +# Output -> BaseListener.h +# -> BaseListener.cpp +# -> Listener.h +# -> Listener.cpp +# +# See documentation in markup +# +function(antlr4_generate + Antlr4_ProjectTarget + Antlr4_InputFile + Antlr4_GeneratorType + ) + + set( Antlr4_GeneratedSrcDir ${ANTLR4_GENERATED_SRC_DIR}/${Antlr4_ProjectTarget} ) + + get_filename_component(Antlr4_InputFileBaseName ${Antlr4_InputFile} NAME_WE ) + + list( APPEND Antlr4_GeneratorStatusMessage "Common Include-, Source- and Tokenfiles" ) + + if ( ${Antlr4_GeneratorType} STREQUAL "LEXER") + set(Antlr4_LexerBaseName "${Antlr4_InputFileBaseName}") + set(Antlr4_ParserBaseName "") + else() + if ( ${Antlr4_GeneratorType} STREQUAL "PARSER") + set(Antlr4_LexerBaseName "") + set(Antlr4_ParserBaseName "${Antlr4_InputFileBaseName}") + else() + if ( ${Antlr4_GeneratorType} STREQUAL "BOTH") + set(Antlr4_LexerBaseName "${Antlr4_InputFileBaseName}Lexer") + set(Antlr4_ParserBaseName "${Antlr4_InputFileBaseName}Parser") + else() + message(FATAL_ERROR "The third parameter must be LEXER, PARSER or BOTH") + endif () + endif () + endif () + + # Prepare list of generated targets + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}.tokens" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}.interp" ) + list( APPEND DependentTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}.tokens" ) + + if ( NOT ${Antlr4_LexerBaseName} STREQUAL "" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_LexerBaseName}.h" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_LexerBaseName}.cpp" ) + endif () + + if ( NOT ${Antlr4_ParserBaseName} STREQUAL "" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_ParserBaseName}.h" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_ParserBaseName}.cpp" ) + endif () + + # process optional arguments ... + + if ( ( ARGC GREATER_EQUAL 4 ) AND ARGV3 ) + set(Antlr4_BuildListenerOption "-listener") + + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}BaseListener.h" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}BaseListener.cpp" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}Listener.h" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}Listener.cpp" ) + + list( APPEND Antlr4_GeneratorStatusMessage ", Listener Include- and Sourcefiles" ) + else() + set(Antlr4_BuildListenerOption "-no-listener") + endif () + + if ( ( ARGC GREATER_EQUAL 5 ) AND ARGV4 ) + set(Antlr4_BuildVisitorOption "-visitor") + + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}BaseVisitor.h" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}BaseVisitor.cpp" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}Visitor.h" ) + list( APPEND Antlr4_GeneratedTargets "${Antlr4_GeneratedSrcDir}/${Antlr4_InputFileBaseName}Visitor.cpp" ) + + list( APPEND Antlr4_GeneratorStatusMessage ", Visitor Include- and Sourcefiles" ) + else() + set(Antlr4_BuildVisitorOption "-no-visitor") + endif () + + if ( (ARGC GREATER_EQUAL 6 ) AND (NOT ${ARGV5} STREQUAL "") ) + set(Antlr4_NamespaceOption "-package;${ARGV5}") + + list( APPEND Antlr4_GeneratorStatusMessage " in Namespace ${ARGV5}" ) + else() + set(Antlr4_NamespaceOption "") + endif () + + if ( (ARGC GREATER_EQUAL 7 ) AND (NOT ${ARGV6} STREQUAL "") ) + set(Antlr4_AdditionalDependencies ${ARGV6}) + else() + set(Antlr4_AdditionalDependencies "") + endif () + + if ( (ARGC GREATER_EQUAL 8 ) AND (NOT ${ARGV7} STREQUAL "") ) + set(Antlr4_LibOption "-lib;${ARGV7}") + + list( APPEND Antlr4_GeneratorStatusMessage " using Library ${ARGV7}" ) + else() + set(Antlr4_LibOption "") + endif () + + if(NOT Java_FOUND) + message(FATAL_ERROR "Java is required to process grammar or lexer files! - Use 'FIND_PACKAGE(Java COMPONENTS Runtime REQUIRED)'") + endif() + + if(NOT EXISTS "${ANTLR4_JAR_LOCATION}") + message(FATAL_ERROR "Unable to find antlr tool. ANTLR4_JAR_LOCATION:${ANTLR4_JAR_LOCATION}") + endif() + + # The call to generate the files + add_custom_command( + OUTPUT ${Antlr4_GeneratedTargets} + # Remove target directory + COMMAND + ${CMAKE_COMMAND} -E remove_directory ${Antlr4_GeneratedSrcDir} + # Create target directory + COMMAND + ${CMAKE_COMMAND} -E make_directory ${Antlr4_GeneratedSrcDir} + COMMAND + # Generate files + "${Java_JAVA_EXECUTABLE}" -jar "${ANTLR4_JAR_LOCATION}" -Werror -Dlanguage=Cpp ${Antlr4_BuildListenerOption} ${Antlr4_BuildVisitorOption} ${Antlr4_LibOption} ${ANTLR4_GENERATED_OPTIONS} -o "${Antlr4_GeneratedSrcDir}" ${Antlr4_NamespaceOption} "${Antlr4_InputFile}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + MAIN_DEPENDENCY "${Antlr4_InputFile}" + DEPENDS ${Antlr4_AdditionalDependencies} + ) + + # set output variables in parent scope + set( ANTLR4_INCLUDE_DIR_${Antlr4_ProjectTarget} ${Antlr4_GeneratedSrcDir} PARENT_SCOPE) + set( ANTLR4_SRC_FILES_${Antlr4_ProjectTarget} ${Antlr4_GeneratedTargets} PARENT_SCOPE) + set( ANTLR4_TOKEN_FILES_${Antlr4_ProjectTarget} ${DependentTargets} PARENT_SCOPE) + set( ANTLR4_TOKEN_DIRECTORY_${Antlr4_ProjectTarget} ${Antlr4_GeneratedSrcDir} PARENT_SCOPE) + + # export generated cpp files into list + foreach(generated_file ${Antlr4_GeneratedTargets}) + + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set_source_files_properties( + ${generated_file} + PROPERTIES + COMPILE_FLAGS -Wno-overloaded-virtual + ) + endif () + + if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set_source_files_properties( + ${generated_file} + PROPERTIES + COMPILE_FLAGS -wd4251 + ) + endif () + + endforeach(generated_file) + +message(STATUS "Antlr4 ${Antlr4_ProjectTarget} - Building " ${Antlr4_GeneratorStatusMessage} ) + +endfunction() \ No newline at end of file diff --git a/Cpp/cmake/antlr4-runtime.cmake.in b/Cpp/cmake/antlr4-runtime.cmake.in new file mode 100755 index 0000000..c120494 --- /dev/null +++ b/Cpp/cmake/antlr4-runtime.cmake.in @@ -0,0 +1,13 @@ +set(ANTLR_VERSION @ANTLR_VERSION@) + +@PACKAGE_INIT@ + +set_and_check(ANTLR4_INCLUDE_DIR "@PACKAGE_ANTLR4_INCLUDE_DIR@") +set_and_check(ANTLR4_LIB_DIR "@PACKAGE_ANTLR4_LIB_DIR@") + +include(CMakeFindDependencyMacro) +find_dependency(Threads) + +include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) + +check_required_components(antlr) \ No newline at end of file diff --git a/Cpp/demo/ArcscriptTest.cpp b/Cpp/demo/ArcscriptTest.cpp new file mode 100644 index 0000000..7fd8f53 --- /dev/null +++ b/Cpp/demo/ArcscriptTest.cpp @@ -0,0 +1,330 @@ +// ArcscriptTranspilerTest.cpp: This file contains the 'main' function. Program execution begins and ends there. +// + +#include +#include +#include +#include +#include +#include +#include +#include "ArcscriptTranspiler.h" + +#ifdef _WIN64 +#define strdup _strdup +#endif + +using json = nlohmann::json; + +using namespace Arcweave; + +UVariable* getInitialVars(json initialVarsJson) { + auto initVars = new UVariable[initialVarsJson.size()]; + int i = 0; + for (json::iterator it = initialVarsJson.begin(); it != initialVarsJson.end(); ++it) { + std::string id = it.value()["id"].get(); + std::string name = it.value()["name"].get(); + std::string type = it.value()["type"].get(); + + initVars[i].id = strdup(id.c_str()); + initVars[i].name = strdup(name.c_str()); + initVars[i].type = VariableType::AW_ANY; + if (type == "string") { + initVars[i].type = VariableType::AW_STRING; + } + else if (type == "integer") { + initVars[i].type = VariableType::AW_INTEGER; + } + else if (type == "double") { + initVars[i].type = VariableType::AW_DOUBLE; + } + else if (type == "boolean") { + initVars[i].type = VariableType::AW_BOOLEAN; + } + + if (initVars[i].type == VariableType::AW_STRING) { + initVars[i].string_val = strdup(it.value()["value"].get().c_str()); + } + else if (initVars[i].type == VariableType::AW_INTEGER) { + initVars[i].int_val = it.value()["value"].get(); + } + else if (initVars[i].type == VariableType::AW_DOUBLE) { + initVars[i].double_val= it.value()["value"].get(); + } + else if (initVars[i].type == VariableType::AW_BOOLEAN) { + initVars[i].bool_val = it.value()["value"].get(); + } + i += 1; + } + + return initVars; +} + +std::map getExpectedChanges(json changes) { + std::map expectedChanges; + + for (json::iterator it = changes.begin(); it != changes.end(); ++it) { + const std::string& varId = it.key(); + const json& value = it.value(); + + if (value.type() == json::value_t::string) { + expectedChanges[varId] = value.get(); + } + else if (value.type() == json::value_t::number_float || value.type() == json::value_t::number_integer) { + expectedChanges[varId] = value.get(); + } + else if (value.type() == json::value_t::boolean) { + expectedChanges[varId] = value.get(); + } + } + return expectedChanges; +} + +UVisit* getVisits(json initVisits) { + if (initVisits.empty()) return nullptr; + auto visits = new UVisit[initVisits.size()]; + int i = 0; + for (json::iterator it = initVisits.begin(); it != initVisits.end(); ++it) { + visits[i].elId = strdup(it.key().c_str()); + visits[i].visits = it.value().get(); + i += 1; + } + return visits; +} + +std::string test(json testCase, size_t caseIndex, UVariable* initVars, size_t initVarLen) { + std::stringstream errorOutput; + + const char* code = strdup(testCase["code"].get().c_str()); + UVisit* visits = nullptr; + size_t visitsLen = 0; + const char* currentElement = nullptr; + if (testCase.contains("elementId")) { + currentElement = strdup(testCase["elementId"].get().c_str()); + } + else { + currentElement = strdup("TestElement"); + } + if (testCase.contains("visits")) { + visits = getVisits(testCase["visits"]); + visitsLen = testCase["visits"].size(); + } + + bool hasError = false; + std::string errorType; + if (testCase.contains("error")) { + hasError = true; + errorType = testCase["error"].get(); + } + UTranspilerOutput* result = nullptr; + try { + result = runScriptExport(code, currentElement, initVars, initVarLen, visits, visitsLen); + } catch (RuntimeErrorException &e) { + if (!hasError) { + errorOutput << "Unexpected Runtime Error: " << e.what() << std::endl; + } else { + if (errorType != "runtime") { + errorOutput << "Received Runtime Error: " << e.what() << std::endl; + } + } + } catch (ParseErrorException &e) { + if (!hasError) { + errorOutput << "Unexpected Parse Error: " << e.what() << std::endl; + } else { + if (errorType != "parse") { + errorOutput << "Received Parse Error: " << e.what() << std::endl; + } + } + } catch (std::exception &e) { + if (!hasError) { + errorOutput << "Unexpected Exception: " << e.what() << std::endl; + } else { + if (errorType != "exception") { + errorOutput << "Received Exception: " << e.what() << std::endl; + } + } + } + + if (result == nullptr) { + if (!errorOutput.str().empty()) { + std::stringstream temp; + temp << "Test case " << caseIndex << " failed: \"" << code << "\"" << std::endl << errorOutput.rdbuf(); + errorOutput.swap(temp); + } + return errorOutput.str(); + } + + if (hasError) { + errorOutput << "Test case " << caseIndex << " failed: \"" << code << "\"" << std::endl; + errorOutput << "Expected error of type: " << errorType << " but no error thrown." << std::endl; + + return errorOutput.str(); + } + + if (testCase.contains("output")) + { + std::string output = testCase["output"].get(); + if (output != result->output) + { + errorOutput << "Different Text Output" << std::endl; + errorOutput << "EXPECTED:\t\"" << output << "\"" << std::endl << "ACTUAL:\t\t\"" << result->output << "\"" << std::endl; + } + } + + if (testCase.contains("changes")) { + json changes = testCase["changes"]; + + for (json::iterator it = changes.begin(); it != changes.end(); ++it) { + const std::string& expectedChangeKey = it.key(); + const json& expectedChangeValue = it.value(); + UVariableChange change; + bool found = false; + for (int i = 0; i < result->changesLen; i++) { + if (result->changes[i].varId == expectedChangeKey) { + change = result->changes[i]; + found = true; + break; + } + } + if (!found) { + errorOutput << "Variable change not found: " << expectedChangeKey << ": " << expectedChangeValue << std::endl; + continue; + } + + if (change.type == VariableType::AW_STRING) { + std::string expectedValue = expectedChangeValue.get(); + if (expectedValue != change.string_result) { + errorOutput << "Variable change mismatch for " << expectedChangeKey << ": expected \"" << expectedValue << "\", got \"" << change.string_result << "\"" << std::endl; + } + } else if (change.type == VariableType::AW_INTEGER) { + int expectedValue = expectedChangeValue.get(); + if (expectedValue != change.int_result) { + errorOutput << "Variable change mismatch for " << expectedChangeKey << ": expected " << expectedValue << ", got " << change.int_result << std::endl; + } + } else if (change.type == VariableType::AW_DOUBLE) { + double expectedValue = expectedChangeValue.get(); + if (expectedValue != change.double_result) { + errorOutput << "Variable change mismatch for " << expectedChangeKey << ": expected " << expectedValue << ", got " << change.double_result << std::endl; + } + } else if (change.type == VariableType::AW_BOOLEAN) { + bool expectedValue = expectedChangeValue.get(); + if (expectedValue != change.bool_result) { + errorOutput << "Variable change mismatch for " << expectedChangeKey << ": expected " << expectedValue << ", got " << change.bool_result << std::endl; + } + } + } + } + + if (testCase.contains("result")) { + bool expectedResult = testCase["result"].get(); + if (result->conditionResult != expectedResult) { + errorOutput << "Condition result mismatch: expected " << expectedResult << ", got " << result->conditionResult << std::endl; + } + } + + if (visits != nullptr) { + for (int i = 0; i < visitsLen; i++) { + free(const_cast(visits[i].elId)); + } + delete visits; + } + free(const_cast(currentElement)); + + deallocateOutput(result); + + if (!errorOutput.str().empty()) { + std::stringstream temp; + temp << "Test case " << caseIndex << " failed: \"" << code << "\"" << std::endl << errorOutput.rdbuf(); + errorOutput.swap(temp); + } + + free(const_cast(code)); + + return errorOutput.str(); +} + +int testFile(const std::filesystem::path& path, int testIndex = -1) { + std::ifstream f(path); + json data = json::parse(f); + + std::cout << "Testing file: " << path; + + json initVarsJson = data["initialVars"]; + UVariable* initVars = getInitialVars(initVarsJson); + size_t initVarLen = initVarsJson.size(); + size_t caseIndex = 0; + + if (testIndex >= 0) { + std::string errorOutput = test(data["cases"][testIndex], testIndex, initVars, initVarLen); + + if (!errorOutput.empty()) { + std::cout << errorOutput << std::endl; + return 1; + } + + return 0; + } + + bool fileError = false; + for (const auto& testCase : data["cases"]) { + + std::string errorOutput = test(testCase, caseIndex, initVars, initVarLen); + + if (!errorOutput.empty()) { + fileError = true; + std::cout << std::endl << errorOutput; + } + caseIndex += 1; + } + + if (!fileError) { + std::cout << "\t\t\tSuccess" << std::endl; + } else { + std::cout << std::endl; + } + + + + for (int j = 0; j < initVarLen; j++) { + free(const_cast(initVars[j].id)); + free(const_cast(initVars[j].name)); + if (initVars[j].type == VariableType::AW_STRING) { + free(const_cast(initVars[j].string_val)); + } + } + delete initVars; + + if (fileError) { + return 1; + } + + return 0; +} + + + +int main(int argc, char* argv[]) +{ + // Create an array of the test path files + std::vector testPaths = { + "./tests/valid.json", + "./tests/conditions.json", + "./tests/stringConcat.json", + "./tests/runtimeErrors.json", + "./tests/parseErrors.json", + }; + bool hasErrors = false; + for (const auto& path : testPaths) { + if (!std::filesystem::exists(path)) { + std::cout << "File not found: " << path << std::endl; + continue; + } + auto result = testFile(path); + if (result != 0) { + hasErrors = true; + } + } + if (hasErrors) { + return 1; + } +} diff --git a/Cpp/demo/tests/conditions.json b/Cpp/demo/tests/conditions.json new file mode 100644 index 0000000..88331f1 --- /dev/null +++ b/Cpp/demo/tests/conditions.json @@ -0,0 +1,100 @@ +{ + "initialVars": { + "var1": { + "id": "var1", + "name": "x", + "type": "integer", + "value": 14 + }, + "var2": { + "id": "var2", + "name": "y", + "type": "integer", + "value": 15 + }, + "var3": { + "id": "var3", + "name": "z", + "type": "integer", + "value": 0 + }, + "var4": { + "id": "var4", + "name": "w", + "type": "string", + "value": "Dummy text" + }, + "var5": { + "id": "var5", + "name": "$c5", + "type": "integer", + "value": 0 + }, + "var6": { + "id": "var6", + "name": "_a", + "type": "boolean", + "value": false + }, + "var7": { + "id": "var7", + "name": "xy", + "type": "integer", + "value": -1 + } + }, + "cases": [ + { + "code": "
x >= 14
", + "result": true + }, + { + "code": "
x == 14
", + "result": true + }, + { + "code": "
x is 14
", + "result": true + }, + { + "code": "
x == 45
", + "result": false + }, + { + "code": "
x is 45
", + "result": false + }, + { + "code": "
x == 14 && y == 15
", + "result": true + }, + { + "code": "
x == 14 and y == 15
", + "result": true + }, + { + "code": "
x == 3 || y != 0
", + "result": true + }, + { + "code": "
x == 3 or y != 0
", + "result": true + }, + { + "code": "
w == \"Dummy text\"
", + "result": true + }, + { + "code": "
w != \"Dummy text\"
", + "result": false + }, + { + "code": "
w is not \"Dummy text\"
", + "result": false + }, + { + "code": "
x is not \"Dummy text\"
", + "result": true + } + ] +} \ No newline at end of file diff --git a/Cpp/demo/tests/parseErrors.json b/Cpp/demo/tests/parseErrors.json new file mode 100644 index 0000000..07d79d9 --- /dev/null +++ b/Cpp/demo/tests/parseErrors.json @@ -0,0 +1,96 @@ +{ + "initialVars": { + "var1": { + "id": "var1", + "name": "x", + "type": "integer", + "value": 14 + }, + "var2": { + "id": "var2", + "name": "y", + "type": "integer", + "value": 15 + }, + "var3": { + "id": "var3", + "name": "z", + "type": "integer", + "value": 0 + }, + "var4": { + "id": "var4", + "name": "w", + "type": "string", + "value": "Dummy text" + }, + "var5": { + "id": "var5", + "name": "$c5", + "type": "integer", + "value": 0 + }, + "var6": { + "id": "var6", + "name": "_a", + "type": "boolean", + "value": false + }, + "var7": { + "id": "var7", + "name": "xy", + "type": "integer", + "value": -1 + } + }, + "cases": [ + { + "code": "
p = 1
", + "error": "parse" + }, + { + "code": "
x = p
", + "error": "parse" + }, + { + "code": "
show(x, y, z, abc)
", + "error": "parse" + }, + { + "code": "
testFunction(x, y)
", + "error": "parse" + }, + { + "code": "
x = 1;
", + "error": "parse" + }, + { + "code": "
We want to be friends
", + "error": "parse" + }, + { + "code": "
x = sqr()
", + "error": "parse" + }, + { + "code": "
x = random(2)
", + "error": "parse" + }, + { + "code": "
y = 33
reset(13)
", + "error": "parse" + }, + { + "code": "
ελληνικά
", + "error": "parse" + }, + { + "code": "
visits(Untitled Comp) == 9
", + "error": "parse" + }, + { + "code": "

Test

if true

inside

endif

this

", + "error": "parse" + } + ] +} \ No newline at end of file diff --git a/Cpp/demo/tests/runtimeErrors.json b/Cpp/demo/tests/runtimeErrors.json new file mode 100644 index 0000000..2f4924a --- /dev/null +++ b/Cpp/demo/tests/runtimeErrors.json @@ -0,0 +1,72 @@ +{ + "initialVars": { + "var1": { + "id": "var1", + "name": "x", + "type": "integer", + "value": 14 + }, + "var2": { + "id": "var2", + "name": "y", + "type": "integer", + "value": 15 + }, + "var3": { + "id": "var3", + "name": "z", + "type": "integer", + "value": 0 + }, + "var4": { + "id": "var4", + "name": "w", + "type": "string", + "value": "Dummy text" + }, + "var5": { + "id": "var5", + "name": "$c5", + "type": "integer", + "value": 0 + }, + "var6": { + "id": "var6", + "name": "_a", + "type": "boolean", + "value": false + }, + "var7": { + "id": "var7", + "name": "xy", + "type": "integer", + "value": -1 + } + }, + "cases": [ + { + "code": "
x = sqrt(-3)
", + "error": "runtime" + }, + { + "code": "
x = 3 / 0
", + "error": "runtime" + }, + { + "code": "
x = roll(\"2\")
", + "error": "runtime" + }, + { + "code": "
w = \"2\" - \"test\"
", + "error": "runtime" + }, + { + "code": "
w = w - \"test\"
", + "error": "runtime" + }, + { + "code": "
w -= _a
", + "error": "runtime" + } + ] +} \ No newline at end of file diff --git a/Cpp/demo/tests/stringConcat.json b/Cpp/demo/tests/stringConcat.json new file mode 100644 index 0000000..d9b3503 --- /dev/null +++ b/Cpp/demo/tests/stringConcat.json @@ -0,0 +1,132 @@ +{ + "initialVars": { + "var1": { + "id": "var1", + "name": "x", + "type": "integer", + "value": 14 + }, + "var2": { + "id": "var2", + "name": "y", + "type": "integer", + "value": 15 + }, + "var3": { + "id": "var3", + "name": "z", + "type": "integer", + "value": 0 + }, + "var4": { + "id": "var4", + "name": "w", + "type": "string", + "value": "Dummy text" + }, + "var5": { + "id": "var5", + "name": "$c5", + "type": "integer", + "value": 0 + }, + "var6": { + "id": "var6", + "name": "_a", + "type": "boolean", + "value": false + }, + "var7": { + "id": "var7", + "name": "xy", + "type": "integer", + "value": -1 + }, + "var8": { + "id": "var8", + "name": "man", + "type": "string", + "value": "Different Text" + } + }, + "cases": [ + { + "code": "
w = \"test\"
w = w + \"ing\"
", + "changes": { + "var4": "testing" + } + }, + { + "code": "
w = \"test\" + \"ing\"
", + "changes": { + "var4": "testing" + } + }, + { + "code": "
w = 0.42 + \"ing\"
", + "changes": { + "var4": "0.42ing" + } + }, + { + "code": "
w = 0.42 + \"\"
", + "changes": { + "var4": "0.42" + } + }, + { + "code": "
w = \"test\" + 44
", + "changes": { + "var4": "test44" + } + }, + { + "code": "
w = \"test\" + false
", + "changes": { + "var4": "testfalse" + } + }, + { + "code": "
w = \"test\" + true + true
", + "changes": { + "var4": "testtruetrue" + } + }, + { + "code": "
w = true + true + \"test\"
", + "changes": { + "var4": "2test" + } + }, + { + "code": "
w += \" test\"
", + "changes": { + "var4": "Dummy text test" + } + }, + { + "code": "
x += \"test\"
", + "changes": { + "var1": "14test" + } + }, + { + "code": "
w += 42
", + "changes": { + "var4": "Dummy text42" + } + }, + { + "code": "
w += _a
", + "changes": { + "var4": "Dummy textfalse" + } + }, + { + "code": "
w += man
", + "changes": { + "var4": "Dummy textDifferent Text" + } + } + ] +} \ No newline at end of file diff --git a/Cpp/demo/tests/valid.json b/Cpp/demo/tests/valid.json new file mode 100644 index 0000000..6df3bb8 --- /dev/null +++ b/Cpp/demo/tests/valid.json @@ -0,0 +1,478 @@ +{ + "initialVars": { + "var1": { + "id": "var1", + "name": "x", + "type": "integer", + "value": 14 + }, + "var2": { + "id": "var2", + "name": "y", + "type": "integer", + "value": 15 + }, + "var3": { + "id": "var3", + "name": "z", + "type": "integer", + "value": 0 + }, + "var4": { + "id": "var4", + "name": "w", + "type": "string", + "value": "Dummy text" + }, + "var5": { + "id": "var5", + "name": "$c5", + "type": "integer", + "value": 0 + }, + "var6": { + "id": "var6", + "name": "_a", + "type": "boolean", + "value": false + }, + "var7": { + "id": "var7", + "name": "xy", + "type": "integer", + "value": -1 + }, + "var8": { + "id": "var8", + "name": "man", + "type": "string", + "value": "Different Text" + } + }, + "cases": [ + { + "code": "
x=5
", + "changes": { + "var1": 5 + } + }, + { + "code": "
x=-5
", + "changes": { + "var1": -5 + } + }, + { + "code": "
x=5.23
", + "changes": { + "var1": 5.23 + } + }, + { + "code": "
x=3
y=2
", + "changes": { + "var1": 3, + "var2": 2 + } + }, + { + "code": "
x=\"test\"
", + "changes": { + "var1": "test" + } + }, + { + "code": "
x=true
", + "changes": { + "var1": true + } + }, + { + "code": "
x = y
", + "changes": { + "var1": 15 + } + }, + { + "code": "
x = 5 + 3
", + "changes": { + "var1": 8 + } + }, + { + "code": "
x = 3 / 4 * 5
", + "changes": { + "var1": 3.75 + } + }, + { + "code": "
x = 3 + 4 * 5
", + "changes": { + "var1": 23 + } + }, + { + "code": "
x = 3 * 4 + 5
", + "changes": { + "var1": 17 + } + }, + { + "code": "
x = 3 / 4 / 5
", + "changes": { + "var1": 0.15 + } + }, + { + "code": "
x = sqr(2)
", + "changes": { + "var1": 4 + } + }, + { + "code": "
x = 3 > 2
", + "changes": { + "var1": true + } + }, + { + "code": "
x = 3 >= 2
", + "changes": { + "var1": true + } + }, + { + "code": "
x = 3 < 2
", + "changes": { + "var1": false + } + }, + { + "code": "
x = 3 <= 2
", + "changes": { + "var1": false + } + }, + { + "code": "
x = 3 == 2
", + "changes": { + "var1": false + } + }, + { + "code": "
x = 3 != 2
", + "changes": { + "var1": true + } + }, + { + "code": "
_a = !(3 == 2)
", + "changes": { + "var6": true + } + }, + { + "code": "
_a = not (3 == 2)
", + "changes": { + "var6": true + } + }, + { + "code": "
x = !0
", + "changes": { + "var1": true + } + }, + { + "code": "
x = true || false
", + "changes": { + "var1": true + } + }, + { + "code": "
x = true && false
", + "changes": { + "var1": false + } + }, + { + "code": "
x = -3
y = y + 3
z = sqr(x)
", + "changes": { + "var1": -3, + "var2": 18, + "var3": 9 + } + }, + { + "code": "
x = sqrt(y + 2)
", + "changes": { + "var1": 4.123105625617661 + } + }, + { + "code": "
x = z + 12 == 4 * 3
", + "changes": { + "var1": true + } + }, + { + "code": "
x = y == 15 && !(z == 1)
", + "changes": { + "var1": true + } + }, + { + "code": "
x = (x*x + 3)/y
", + "changes": { + "var1": 13.266666666666667 + } + }, + { + "code": "
x = x*(x + 1)/y
", + "changes": { + "var1": 14 + } + }, + { + "code": "
x += 2
", + "changes": { + "var1": 16 + } + }, + { + "code": "
x -= 2
", + "changes": { + "var1": 12 + } + }, + { + "code": "
x *= 2
", + "changes": { + "var1": 28 + } + }, + { + "code": "
x /= 2
", + "changes": { + "var1": 7 + } + }, + { + "code": "
x = abs(-6)
", + "changes": { + "var1": 6 + } + }, + { + "code": "
x = min(5, -y, 2.3)
", + "changes": { + "var1": -15 + } + }, + { + "code": "
x = max(5, -y, 2.3)
", + "changes": { + "var1": 5 + } + }, + { + "code": "
x = random()
", + "changes": {} + }, + { + "code": "
x = roll(6)
", + "changes": {} + }, + { + "code": "
x = roll(6, 5)
", + "changes": {} + }, + { + "code": "
x = roll(y)
", + "changes": {} + }, + { + "code": "
x = round(2.65)
", + "changes": { + "var1": 3 + } + }, + { + "code": "
show(\"x is \", x)
", + "changes": {}, + "output": "

x is 14

" + }, + { + "code": "
y = 33
reset(y)
", + "changes": { + "var2": 15 + } + }, + { + "code": "
x = 16
y = 33
resetAll(y)
", + "changes": { + "var1": 14, + "var2": 33, + "var3": 0, + "var4": "Dummy text", + "var5": 0, + "var6": false, + "var7": -1, + "var8": "Different Text" + } + }, + { + "code": "
$c5 = x
", + "changes": { + "var5": 14 + } + }, + { + "code": "
if (x==14) 
x=5
endif
", + "changes": { + "var1": 5 + } + }, + { + "code": "
if (x==11)
x=5
else
x=7
endif
", + "changes": { + "var1": 7 + } + }, + { + "code": "
if x==3
x=5
elseif x==14
x=2
else
x = 0
endif
", + "changes": { + "var1": 2 + } + }, + { + "code": "

Mein gott!

x = 1

What is that?

", + "changes": { + "var1": 1 + }, + "output": "

Mein gott! What is that?

" + }, + { + "code": "

Hello my dear

if x == 1

We might ride to the mountains.

endif
", + "changes": {}, + "output": "

Hello my dear

" + }, + { + "code": "

You carry

if x == 12

a small knife

elseif y == 12

a large sword

elseif z == 1

just a rotten tomato

else

nothing really

endif
", + "changes": {}, + "output": "

You carry nothing really

" + }, + { + "code": "

How are you?

x = 1

I am fine, thank you.

", + "changes": {}, + "output": "

How are you?

I am fine, thank you.

" + }, + { + "code": "
show(\"Hello friends:\", x)
", + "changes": {}, + "output": "

Hello friends:14

" + }, + { + "code": "
_a = true
if (((_a)))
x = 1
endif
", + "changes": { + "var1": 1, + "var6": true + } + }, + { + "code": "
visits() == 1
", + "visits": { + "a": 1, + "b": 1, + "c": 0 + }, + "elementId": "b", + "result": true + }, + { + "code": "
visits(Untitled Comp) == 9
", + "visits": { + "a": 1, + "b": 1, + "c": 0 + }, + "elementId": "b", + "result": false + }, + { + "code": "

this

text

must

not

be

in

one

paragraph

", + "output": "

this

text

must

not

be

in

one

paragraph

" + }, + { + "code": "

three

separate

lines

just

show(\"one\")
show(\"and\")
show(\"single\")

line

three

more

lines

", + "output": "

three

separate

lines

just one and single line

three

more

lines

" + }, + { + "code": "

This is a blockquote

if x

This is a separate line

endif

This is another blockquote

", + "output": "

This is a blockquote

This is a separate line

This is another blockquote

" + }, + { + "code": "

This is a blockquote

show(x)

test

", + "output": "

This is a blockquote 14

test

" + }, + { + "code": "

This is a blockquote

show(x)

test

", + "output": "

This is a blockquote 14 test

" + }, + { + "code": "

one

if true

two

endif

three

", + "output": "

one

two

three

" + }, + { + "code": "

one

if true

two

endif

three

", + "output": "

one two

three

" + }, + { + "code": "

one

if true

two

endif

three

", + "output": "

one two

three

" + }, + { + "code": "

one

if true
show(x)
endif

three

", + "output": "

one 14

three

" + }, + { + "code": "

one

if true

two

test

four

endif

three

", + "output": "

one two

test

four

three

" + }, + { + "code": "

one

if true

two

test

show(\"four\")
endif

three

", + "output": "

one two

test four

three

" + }, + { + "code": "

one

if false

two

endif

three

", + "output": "

one

three

" + }, + { + "code": "

This is a blockquote

if true

Second blockquote

Separate Line

endif

Outside blockquote

", + "output": "

This is a blockquote

Second blockquote

Separate Line

Outside blockquote

" + }, + { + "code": "

Test

_a = true

this

", + "output": "

Test this

", + "changes": { + "var6": true + } + }, + { + "code": "

Test

show(x)

this

", + "output": "

Test 14 this

" + }, + { + "code": "

this is a test

show(x)

continue

testing

", + "output": "

this is a test

14 continue

testing

" + }, + { + "code": "

This is a test

Manolis

inside blockquotes

if true

John

endif
", + "output": "

This is a test

Manolis

inside blockquotes

John

" + }, + { + "code": "
w = \"String with \\\"quotes\\\" \\t tabs and \\n new lines\"
show(w)
", + "output": "

String with \"quotes\" \t tabs and \n new lines

" + } + ] +} \ No newline at end of file diff --git a/Cpp/src/ArcscriptErrorExceptions.h b/Cpp/src/ArcscriptErrorExceptions.h new file mode 100755 index 0000000..8e5c559 --- /dev/null +++ b/Cpp/src/ArcscriptErrorExceptions.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef _WIN64 +#define strdup _strdup +#endif + +namespace Arcweave { + class RuntimeErrorException : public std::exception { + public: + std::string message; + size_t line = 0; + size_t charPositionInLine = 0; + RuntimeErrorException(std::string msg) { + message = msg; + }; + RuntimeErrorException(std::string msg, size_t _line, size_t _charPositionInLine) { + message = msg; + line = _line; + charPositionInLine = _charPositionInLine; + }; + char const* what() const noexcept override { + if (line > 0) { + std::ostringstream oss; + oss << "line " << line << ":" << charPositionInLine << " " << message << std::endl; + return strdup(oss.str().c_str()); + } + return message.c_str(); + } + }; + + class ParseErrorException : public std::exception { + public: + std::string message; + size_t line = 0; + size_t charPositionInLine = 0; + ParseErrorException(std::string msg) { + message = msg; + }; + ParseErrorException(std::string msg, size_t _line, size_t _charPositionInLine) { + message = msg; + line = _line; + charPositionInLine = _charPositionInLine; + }; + char const* what() const noexcept override { + if (line > 0) { + std::ostringstream oss; + oss << "line " << line << ":" << charPositionInLine << " " << message << std::endl; + return strdup(oss.str().c_str()); + } + return message.c_str(); + } + }; +} + diff --git a/Cpp/src/ArcscriptErrorListener.cpp b/Cpp/src/ArcscriptErrorListener.cpp new file mode 100755 index 0000000..d6bf9fc --- /dev/null +++ b/Cpp/src/ArcscriptErrorListener.cpp @@ -0,0 +1,12 @@ +#include "ArcscriptErrorListener.h" +#include "ArcscriptErrorExceptions.h" + +using namespace antlr4; +using namespace Arcweave; + +ErrorListener ErrorListener::INSTANCE; + +void ErrorListener::syntaxError(Recognizer * /*recognizer*/, Token * /*offendingSymbol*/, + size_t line, size_t charPositionInLine, const std::string &msg, std::exception_ptr /*e*/) { + throw ParseErrorException(msg, line, charPositionInLine); +} diff --git a/Cpp/src/ArcscriptErrorListener.h b/Cpp/src/ArcscriptErrorListener.h new file mode 100755 index 0000000..894a736 --- /dev/null +++ b/Cpp/src/ArcscriptErrorListener.h @@ -0,0 +1,14 @@ +#pragma once + +#include "BaseErrorListener.h" + +using namespace antlr4; + +namespace Arcweave { + class ErrorListener : public BaseErrorListener { + public: + static ErrorListener INSTANCE; + void syntaxError(Recognizer *recognizer, Token *offendingSymbol, size_t line, + size_t charPositionInLine, const std::string &msg, std::exception_ptr e); + }; +} diff --git a/Cpp/src/ArcscriptExpression.cpp b/Cpp/src/ArcscriptExpression.cpp new file mode 100755 index 0000000..ca13bfb --- /dev/null +++ b/Cpp/src/ArcscriptExpression.cpp @@ -0,0 +1,342 @@ +#include "ArcscriptExpression.h" +#include + +#include "ArcscriptErrorExceptions.h" + +namespace Arcweave { + +std::string Expression::valueToString(std::any value) +{ + if (value.type() == typeid(std::string)) + { + return std::any_cast(value); + } + if (value.type() == typeid(bool)) + { + return std::any_cast(value) ? "true" : "false"; + } + if (value.type() == typeid(int)) + { + return std::to_string(std::any_cast(value)); + } + if (value.type() == typeid(double)) + { + std::stringstream ss; + ss << std::any_cast(value); + return ss.str(); + } + + return NULL; +} + + +Expression::NumberValues Expression::doubleValues(std::any value1, std::any value2) { + int intValue1, intValue2; + double dblValue1, dblValue2; + bool isDouble = false; + if (value1.type() == typeid(int)) { + intValue1 = std::any_cast(value1); + dblValue1 = intValue1; + } else if (value1.type() == typeid(double)){ // type double; + isDouble = true; + dblValue1 = std::any_cast(value1); + } else if (value1.type() == typeid(bool)) { + bool boolVal = std::any_cast(value1); + if (boolVal) { + dblValue1 = 1; + } else { + dblValue1 = 0; + } + } + if (value2.type() == typeid(int)) { + intValue2 = std::any_cast(value2); + dblValue2 = intValue2; + } else if (value2.type() == typeid(double)) { + isDouble = true; + dblValue2 = std::any_cast(value2); + } else if (value2.type() == typeid(bool)) { + bool boolVal = std::any_cast(value1); + if (boolVal) { + dblValue2 = 1; + } else { + dblValue2 = 0; + } + } + NumberValues returnVal; + returnVal.value1 = dblValue1; + returnVal.value2 = dblValue2; + returnVal.hasDoubles = isDouble; + return returnVal; +} + +bool Expression::valueToBool(std::any value) { + if (value.type() == typeid(int)) { + return (std::any_cast(value) > 0); + } + if (value.type() == typeid(double)) { + return (std::any_cast(value) > 0); + } + if (value.type() == typeid(std::string)) { + return (std::any_cast(value) != ""); + } + return (std::any_cast(value)); +} + +Expression Expression::operator+ (const Expression &other) { + if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) + { + return Expression(valueToString(value) + valueToString(other.value)); + } + NumberValues values = doubleValues(value, other.value); + Expression* result; + if (!values.hasDoubles) { + int intValue = static_cast(values.value1 + values.value2); + result = new Expression(intValue); + } else { + result = new Expression(values.value1 + values.value2); + } + return *result; +} + +Expression Expression::operator- (const Expression &other) { + if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) { + throw RuntimeErrorException("Cannot subtract strings"); + } + NumberValues values = doubleValues(value, other.value); + Expression* result; + if (!values.hasDoubles) { + int intValue = static_cast(values.value1 - values.value2); + result = new Expression(intValue); + } else { + result = new Expression(values.value1 - values.value2); + } + return *result; +} + +Expression Expression::operator* (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + Expression* result; + if (!values.hasDoubles) { + int intValue = static_cast(values.value1 * values.value2); + result = new Expression(intValue); + } else { + result = new Expression(values.value1 * values.value2); + } + return *result; +} + +Expression Expression::operator* (const int other) { + NumberValues values = doubleValues(value, other); + Expression* result; + if (!values.hasDoubles) { + int intValue = static_cast(values.value1 * values.value2); + result = new Expression(intValue); + } else { + result = new Expression(values.value1 * values.value2); + } + return *result; +} + +Expression Expression::operator/ (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + Expression* result; + + if (values.value2 == 0) { + throw RuntimeErrorException("Division by zero is not allowed."); + } + + result = new Expression(values.value1 / values.value2); + + return *result; +} + +Expression Expression::operator+= (const Expression &other) { + if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) { + auto val1 = valueToString(value); + auto val2 = valueToString(other.value); + if (val1.empty() && val2.empty()) { + value = std::string(); + } else if (val1.empty()) { + value = val2; + } else if (val2.empty()) { + value = val1; + } else { + value = val1 + val2; + } + return *this; + } + + NumberValues values = doubleValues(value, other.value); + if (!values.hasDoubles) { + int intValue = static_cast(values.value1 + values.value2); + value = intValue; + } else { + value = values.value1 + values.value2; + } + + return *this; +} + +Expression Expression::operator-= (const Expression &other) { + if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) { + throw RuntimeErrorException("Cannot subtract strings"); + } + NumberValues values = doubleValues(value, other.value); + if (!values.hasDoubles) { + int intValue = static_cast(values.value1 - values.value2); + value = intValue; + } else { + value = values.value1 - values.value2; + } + return *this; +} + +Expression Expression::operator*= (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + if (!values.hasDoubles) { + int intValue = static_cast(values.value1 * values.value2); + value = intValue; + } else { + value = values.value1 * values.value2; + } + return *this; +} + +Expression Expression::operator/= (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + if (values.value2 == 0) { + throw RuntimeErrorException("Division by zero is not allowed."); + } + if (!values.hasDoubles) { + int intValue = static_cast(values.value1 / values.value2); + value = intValue; + } else { + value = values.value1 / values.value2; + } + return *this; +} + +bool Expression::operator== (const Expression &other) { + if (value.type() == typeid(int) || value.type() == typeid(double)) { + NumberValues values = doubleValues(value, other.value); + return values.value1 == values.value2; + } + if (value.type() == typeid(bool)) { + return std::any_cast(value) == std::any_cast(other.value); + } + return std::any_cast(value) == std::any_cast(other.value); +} + +bool Expression::operator== (double other) { + NumberValues values = doubleValues(value, other); + return values.value1 == values.value2; +} + +bool Expression::operator== (int other) { + NumberValues values = doubleValues(value, other); + return values.value1 == values.value2; +} + +bool Expression::operator== (std::string other) { + return std::any_cast(value) == other; +} + +bool Expression::operator== (bool other) { + return valueToBool(value) == other; +} + +bool Expression::operator!= (const Expression &other) { + if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) { + if (value.type() != other.value.type()) { + return true; // Different types, cannot be equal + } else { + return std::any_cast(value) != std::any_cast(other.value); + } + } + if (value.type() == typeid(int) || value.type() == typeid(double)) { + NumberValues values = doubleValues(value, other.value); + return values.value1 != values.value2; + } + if (value.type() == typeid(bool)) { + return std::any_cast(value) != std::any_cast(other.value); + } + return std::any_cast(value) != std::any_cast(other.value); +} + +bool Expression::operator!= (double other) { + NumberValues values = doubleValues(value, other); + return values.value1 != values.value2; +} + +bool Expression::operator!= (int other) { + NumberValues values = doubleValues(value, other); + return values.value1 != values.value2; +} + +bool Expression::operator!= (std::string other) { + return std::any_cast(value) != other; +} + +bool Expression::operator!= (const char other[]) { + return strcmp(std::any_cast(value).c_str(), other) == 0; +} + +bool Expression::operator> (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + return values.value1 > values.value2; +} + +bool Expression::operator> (int other) { + NumberValues values = doubleValues(value, other); + return values.value1 > values.value2; +} + +bool Expression::operator> (double other) { + NumberValues values = doubleValues(value, other); + return values.value1 > values.value2; +} + +bool Expression::operator>= (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + return values.value1 >= values.value2; +} + +bool Expression::operator< (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + return values.value1 < values.value2; +} + +bool Expression::operator<= (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + return values.value1 <= values.value2; +} + +bool Expression::operator! () { + return !(valueToBool(value)); +} + +bool Expression::operator&& (const Expression &other) { + return valueToBool(value) && valueToBool(other.value); +} + +bool Expression::operator|| (const Expression &other) { + return valueToBool(value) || valueToBool(other.value); +} +} + +std::ostream& operator<< (std::ostream& out, const Arcweave::Expression &e) { + std::any value = e.value; + if (value.type() == typeid(int)) { + return out << std::any_cast(value); + } + if (value.type() == typeid(double)) { + return out << std::any_cast(value); + } + if (value.type() == typeid(std::string)) { + return out << std::any_cast(value); + } + if (value.type() == typeid(bool)) { + return out << std::any_cast(value); + } + return out; +} diff --git a/Cpp/src/ArcscriptExpression.h b/Cpp/src/ArcscriptExpression.h new file mode 100755 index 0000000..09d5c00 --- /dev/null +++ b/Cpp/src/ArcscriptExpression.h @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +namespace Arcweave { + +class Expression { +private: + struct NumberValues { + double value1; + double value2; + bool hasDoubles = false; + }; + + static NumberValues doubleValues(std::any value1, std::any value2) ; + + static std::string valueToString(std::any value); + static bool valueToBool(std::any value); +public: + std::any value; + Expression() { + value = std::any(); + } + Expression(std::string _value) { + value = _value; + } + Expression(bool _value) { + value = _value; + } + Expression(int _value) { + value = _value; + } + Expression(double _value) { + value = _value; + } + Expression(const Expression &e) { + value = e.value; + } + + void setValue(std::any _value) { + value = _value; + } + + const std::type_info& type() { + return value.type(); + } + + Expression operator+ (const Expression &other); + Expression operator- (const Expression &other); + Expression operator* (const Expression &other); + Expression operator* (const int other); + Expression operator/ (const Expression &other); + Expression operator+= (const Expression &other); + Expression operator-= (const Expression &other); + Expression operator*= (const Expression &other); + Expression operator/= (const Expression &other); + + bool operator== (const Expression &other); + bool operator== (double other); + bool operator== (int other); + bool operator== (std::string other); + bool operator== (bool other); + bool operator!= (const Expression &other); + bool operator!= (double other); + bool operator!= (int other); + bool operator!= (std::string other); + bool operator!= (const char other[]); + bool operator> (const Expression &other); + bool operator> (int other); + bool operator> (double other); + bool operator>= (const Expression &other); + bool operator< (const Expression &other); + bool operator<= (const Expression &other); + bool operator! (); + bool operator&& (const Expression &other); + bool operator|| (const Expression &other); +}; + +} +std::ostream& operator<< (std::ostream& out, const Arcweave::Expression &e); diff --git a/Cpp/src/ArcscriptFunctions.cpp b/Cpp/src/ArcscriptFunctions.cpp new file mode 100755 index 0000000..476c153 --- /dev/null +++ b/Cpp/src/ArcscriptFunctions.cpp @@ -0,0 +1,268 @@ +#include "ArcscriptFunctions.h" +#include "ArcscriptExpression.h" +#include +#include +#include +#include + +#include "ArcscriptErrorExceptions.h" + +namespace Arcweave { + std::string replaceEscapes(std::string str) { + std::string result; + for (size_t i = 0; i < str.length(); i++) { + if (str[i] == '\\' && i + 1 < str.length()) { + switch (str[i + 1]) { + case 'a': + result += '\x07'; + break; + case 'b': + result += '\b'; + break; + case 'f': + result += '\f'; + case 'n': + result += '\n'; + break; + case 'r': + result += '\r'; + break; + case 't': + result += '\t'; + break; + case 'v': + result += '\v'; + break; + case '\'': + result += '\''; + break; + case '\\': + result += '\\'; + break; + case '"': + result += '"'; + break; + default: + result += str[i]; + } + i++; + } else { + result += str[i]; + } + } + return result; + } + + std::map ArcscriptFunctions::functions = { + { "abs", { 1, 1 } }, + { "max", { 2, -1 }}, + { "min", { 2, -1 }}, + { "random", { 0, 0 }}, + { "reset", { 1, -1 }}, + { "resetAll", { 0, -1} }, + { "roll", { 1, 2 } }, + { "round", { 1, 1} }, + { "show", { 1, -1 } }, + { "sqr", { 1, 1 } }, + { "sqrt", { 1, 1 } }, + { "visits", { 0, 1 } }, + }; + + std::any ArcscriptFunctions::Call(std::string functionName, std::vector _args) { + std::vector args; + for (std::any arg : _args) { + if (arg.type() == typeid(Expression)) { + args.push_back(std::any_cast(arg).value); + } + else { + args.push_back(arg); + } + } + std::any result; + if (functionName == "sqrt") { + result = this->Sqrt(args); + } + else if (functionName == "sqr") { + result = this->Sqr(args); + } + else if (functionName == "abs") { + result = this->Abs(args); + } + else if (functionName == "random") { + result = this->Random(args); + } + else if (functionName == "roll") { + result = this->Roll(args); + } + else if (functionName == "show") { + result = this->Show(args); + } + else if (functionName == "reset") { + result = this->Reset(args); + } + else if (functionName == "resetAll") { + result = this->ResetAll(args); + } + else if (functionName == "round") { + result = this->Round(args); + } + else if (functionName == "min") { + result = this->Min(args); + } + else if (functionName == "max") { + result = this->Max(args); + } + else if (functionName == "visits") { + result = this->Visits(args); + } + return result; + } + + std::any ArcscriptFunctions::Sqrt(std::vector args) { + if (args[0].type() == typeid(int)) { + const int arg = std::any_cast(args[0]); + if (arg < 0) { + throw RuntimeErrorException("Cannot calculate square root of a negative number."); + } + return sqrt(arg); + } + if (args[0].type() != typeid(double)) { + throw RuntimeErrorException("The argument of sqrt must be a number."); + } + const double arg = std::any_cast(args[0]); + if (arg < 0) { + throw RuntimeErrorException("Cannot calculate square root of a negative number."); + } + return sqrt(arg); + } + + std::any ArcscriptFunctions::Sqr(std::vector args) { + if (args[0].type() == typeid(int)) { + int n = std::any_cast(args[0]); + return n * n; + } + double n = std::any_cast(args[0]); + return n * n; + } + + std::any ArcscriptFunctions::Abs(std::vector args) { + if (args[0].type() == typeid(int)) { + return abs(std::any_cast(args[0])); + } + double n = std::any_cast(args[0]); + return abs(n); + } + + std::any ArcscriptFunctions::Random(std::vector args) { + srand(static_cast(time(NULL))); + return ((double)rand() / (RAND_MAX)); + } + + std::any ArcscriptFunctions::Roll(std::vector args) { + if (args[0].type() != typeid(int)) { + throw RuntimeErrorException("The argument of roll must be an integer."); + } + int maxRoll = std::any_cast(args[0]); + int numRolls = 1; + if (args.size() == 2) { + numRolls = std::any_cast(args[1]); + } + int sum = 0; + for (int i = 0; i < numRolls; i++) { + int oneRoll = rand() % maxRoll + 1; + sum += oneRoll; + } + return sum; + } + + std::any ArcscriptFunctions::Show(std::vector args) { + std::string result; + for (int i = 0; i < args.size(); i++) { + std::any arg = args[i]; + if (arg.type() == typeid(int)) { + result += std::to_string(std::any_cast(arg)); + } + else if (arg.type() == typeid(double)) { + result += std::to_string(std::any_cast(arg)); + } + else if (arg.type() == typeid(bool)) { + result += std::to_string(std::any_cast(arg)); + } + else if (arg.type() == typeid(std::string)) { + result += std::any_cast(arg); + } + } + result = replaceEscapes(result); + _state->outputs.AddScriptOutput(result); + return std::any(); + } + + std::any ArcscriptFunctions::Round(std::vector args) { + if (args[0].type() == typeid(int)) { + return round(std::any_cast(args[0])); + } + double n = std::any_cast(args[0]); + return round(n); + } + + std::any ArcscriptFunctions::Min(std::vector args) { + std::vector casted; + for (std::any arg : args) { + double val; + if (arg.type() == typeid(int)) { + val = std::any_cast(arg); + } + else { + val = std::any_cast(arg); + } + casted.push_back(val); + } + return *min_element(casted.begin(), casted.end()); + } + + std::any ArcscriptFunctions::Max(std::vector args) { + std::vector casted; + for (std::any arg : args) { + double val; + if (arg.type() == typeid(int)) { + val = std::any_cast(arg); + } + else { + val = std::any_cast(arg); + } + casted.push_back(val); + } + return *max_element(casted.begin(), casted.end()); + } + + std::any ArcscriptFunctions::Reset(std::vector args) { + std::vector variables; + for (std::any arg : args) { + variables.push_back(std::any_cast(arg)); + } + _state->resetVars(variables); + return std::any(); + } + + std::any ArcscriptFunctions::ResetAll(std::vector args) { + std::vector except; + for (std::any arg : args) { + except.push_back(std::any_cast(arg)); + } + _state->resetAllVars(except); + return std::any(); + } + + std::any ArcscriptFunctions::Visits(std::vector args) { + std::string nodeId = _state->currentElement; + if (args.size() > 0) { + Mention mention = std::any_cast(args[0]); + + if (mention.attrs.count("data-id")) { + nodeId = mention.attrs["data-id"]; + } + } + + return _state->visits[nodeId]; + } +} diff --git a/Cpp/src/ArcscriptFunctions.h b/Cpp/src/ArcscriptFunctions.h new file mode 100755 index 0000000..cc2dc59 --- /dev/null +++ b/Cpp/src/ArcscriptFunctions.h @@ -0,0 +1,38 @@ +#pragma once + +#include "ArcscriptHelpers.h" +#include + +namespace Arcweave { +class ArcscriptFunctions { +private: + ArcscriptState *_state; + +public: + struct FunctionInfo { + int minArgs; + int maxArgs; + }; + + static std::map functions; + + ArcscriptFunctions(ArcscriptState* state) : _state(state) { + + } + + std::any Call(std::string functionName, std::vector args); + + std::any Abs(std::vector args); + std::any Max(std::vector args); + std::any Min(std::vector args); + std::any Random(std::vector args); + std::any Reset(std::vector args); + std::any ResetAll(std::vector args); + std::any Roll(std::vector args); + std::any Round(std::vector args); + std::any Show(std::vector args); + std::any Sqr(std::vector args); + std::any Sqrt(std::vector args); + std::any Visits(std::vector args); +}; +} diff --git a/Cpp/src/ArcscriptHelpers.h b/Cpp/src/ArcscriptHelpers.h new file mode 100755 index 0000000..be5576c --- /dev/null +++ b/Cpp/src/ArcscriptHelpers.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "ArcscriptOutputs.h" + +namespace Arcweave { + +enum VariableType { + AW_STRING, + AW_INTEGER, + AW_DOUBLE, + AW_BOOLEAN, + AW_ANY +}; + +struct Variable { + std::string id; + std::string name; + VariableType type; + std::any value; +}; + +class ArcscriptState { +public: + std::map variableChanges; + std::map variableValues; + std::map varNameToID; + ArcscriptOutputs outputs; + std::string currentElement; + std::map visits; + + ArcscriptState(std::string elementId, std::map varValues, std::map _visits) { + currentElement = elementId; + variableValues = varValues; + for(const auto var : variableValues) { + varNameToID[var.second.name] = var.first; + } + visits = _visits; + }; + + inline Variable getVar(std::string name) { + std::string varId = varNameToID[name]; + return variableValues[varId]; + } + + inline std::any getVarValue(std::string name) { + std::string varId = varNameToID[name]; + if(variableChanges.count(varId)) { + return variableChanges[varId]; + } + return variableValues[varId].value; + } + inline VariableType getVarType(std::string name) { + return variableValues[varNameToID[name]].type; + } + inline void setVarValue(std::string name, std::any value) { + std::string varId = varNameToID[name]; + variableChanges[varId] = value; + } + inline void setVarValues(std::vector names, std::vector values) { + for (int i = 0; i < names.size(); i++) { + variableChanges[names[i]] = values[i]; + } + } + + inline void resetVars(std::vector vars) { + for (Variable var : vars) { + variableChanges[var.id] = var.value; + } + } + + inline void resetAllVars(std::vector except) { + std::set exceptVariableIds; + for (Variable var : except) { + exceptVariableIds.insert(var.id); + } + std::map::iterator it = variableValues.begin(); + while (it != variableValues.end()) + { + if (exceptVariableIds.find(it->first) == exceptVariableIds.end()) { // not in except vars + variableChanges[it->first] = it->second.value; + } + it++; + } + } +}; + +class Mention { +public: + std::string label; + std::map attrs; + Mention(std::string _label, std::map _attrs) { + label = _label; + attrs = _attrs; + } + Mention(const Mention &m) { + label = m.label; + attrs = m.attrs; + } +}; +} diff --git a/Cpp/src/ArcscriptOutputs.cpp b/Cpp/src/ArcscriptOutputs.cpp new file mode 100755 index 0000000..32845fe --- /dev/null +++ b/Cpp/src/ArcscriptOutputs.cpp @@ -0,0 +1,79 @@ +#include "ArcscriptOutputs.h" + +void Arcweave::ArcscriptOutputs::AppendParagraph(std::string text) +{ + outputs_.push_back(std::make_unique(text)); +} + +void Arcweave::ArcscriptOutputs::AddParagraph(std::string text) +{ + if (current_node_ == nullptr) + { + current_node_ = this; + } + + if (added_script_) + { + if (!outputs_.empty() && dynamic_cast(outputs_.back().get()) && dynamic_cast(current_node_) == nullptr) + { + AppendParagraph(text); + } else + { + AddScriptOutput(text); + } + added_script_ = false; + return; + } + current_node_->AppendParagraph(text); +} + +void Arcweave::ArcscriptOutputs::AddBlockquote() +{ + if (added_script_ && !outputs_.empty()) + { + IOutputNode* n = outputs_.back().get(); + if (dynamic_cast(n)) + { + current_node_ = dynamic_cast(n); + return; + } + } + Blockquote* b = new Blockquote(); + outputs_.push_back(std::unique_ptr(b)); + current_node_ = b; +} + +void Arcweave::ArcscriptOutputs::AddScript() +{ + added_script_ = true; +} + + +void Arcweave::ArcscriptOutputs::AddScriptOutput(std::string text) +{ + added_script_ = true; + if (outputs_.empty()) + { + outputs_.push_back(std::make_unique(text)); + return; + } + outputs_.back()->MergeScriptOutput(text); +} + +void Arcweave::ArcscriptOutputs::ExitBlockquote() +{ + current_node_ = this; +} + +std::string Arcweave::ArcscriptOutputs::GetText() +{ + std::string output; + for (const auto& value : outputs_) + { + output += value->GetText(); + } + + return output; +} + + diff --git a/Cpp/src/ArcscriptOutputs.h b/Cpp/src/ArcscriptOutputs.h new file mode 100755 index 0000000..42edc19 --- /dev/null +++ b/Cpp/src/ArcscriptOutputs.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include + +namespace Arcweave +{ + class IOutputNode + { + public: + virtual ~IOutputNode() = default; + + virtual std::string GetText() = 0; + virtual void MergeScriptOutput(std::string text) = 0; + }; + + class IHasParagraphs + { + public: + virtual ~IHasParagraphs() = default; + + virtual void AppendParagraph(std::string text) = 0; + }; + + class Paragraph: public IOutputNode + { + private: + std::string text_; + + public: + + explicit Paragraph(const std::string &text) + { + text_ = text; + } + + inline void MergeScriptOutput(std::string text) override + { + if (!text.empty()) { + if (!text_.empty()) + { + text_ += ' ' + text; + } else { + text_ = text; + } + } + } + + inline std::string GetText() override + { + return "

" + text_ + "

"; + } + }; + + class Blockquote: public IOutputNode, public IHasParagraphs + { + public: + std::vector> paragraphs; + + inline void AppendParagraph(std::string text) override + { + paragraphs.push_back(std::make_unique(text)); + } + + inline void MergeScriptOutput(std::string text) override + { + if (paragraphs.empty()) + { + AppendParagraph(text); + return; + } + paragraphs.back()->MergeScriptOutput(text); + } + + inline std::string GetText() override + { + std::string output; + for (const auto& value : paragraphs) + { + output += value->GetText(); + } + + return "
" + output + "
"; + } + }; + + class ArcscriptOutputs: public IHasParagraphs + { + private: + std::vector> outputs_; + IHasParagraphs* current_node_ = nullptr; + bool added_script_ = false; + public: + void AppendParagraph(std::string text) override; + void AddParagraph(std::string text); + void AddBlockquote(); + void AddScript(); + void AddScriptOutput(std::string text); + void ExitBlockquote(); + std::string GetText(); + }; + +} diff --git a/Cpp/src/ArcscriptParserBase.cpp b/Cpp/src/ArcscriptParserBase.cpp new file mode 100755 index 0000000..1ae9582 --- /dev/null +++ b/Cpp/src/ArcscriptParserBase.cpp @@ -0,0 +1,102 @@ +#include "ArcscriptParser.h" +#include "ArcscriptLexer.h" +#include "ArcscriptParserBase.h" +#include "ArcscriptExpression.h" +#include "ArcscriptErrorExceptions.h" +#include "ArcscriptFunctions.h" + +using namespace antlr4; + +bool ArcscriptParserBase::assertVariable(antlr4::Token *variable) { + std::string variableName = variable->getText(); + if (_state->varNameToID.count(variableName) > 0) { + return true; + } + throw Arcweave::ParseErrorException("Unknown variable \"" + variableName + "\""); + return false; +} + +inline bool ends_with(std::string const & value, std::string const & ending) +{ + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +bool ArcscriptParserBase::assertMention(std::any attrCtxList) { + std::map attrs; + ParserRuleContext *ctx = this->getContext(); + std::vector ctxList = ctx->getRuleContexts(); + for (auto attrCtx : ctxList) { + tree::TerminalNode *nameNode = attrCtx->getToken(Arcweave::ArcscriptParser::ATTR_NAME, 0); + std::string attrName = ""; + if (nameNode) { + attrName = nameNode->getText(); + } + tree::TerminalNode *valueNode = attrCtx->getToken(Arcweave::ArcscriptParser::ATTR_VALUE, 0); + std::string attrValue = ""; + if (nameNode) { + attrValue = valueNode->getText(); + } + + if ((attrValue.rfind("\"", 0) == 0 && ends_with(attrValue, "\"")) || + attrValue.rfind("'", 0) == 0 && ends_with(attrValue, "'")) { + + attrValue = attrValue.substr(1, attrValue.size() - 2); + + } + attrs[attrName] = attrValue; + } + std::stringstream classList(attrs["class"]); + std::string className; + bool classFound = false; + while(classList >> className){ + if (className == "mention") { + classFound = true; + break; + } + } + if (!classFound) { + throw Arcweave::ParseErrorException("Invalid mention type"); + return false; + } + if (attrs["data-type"] != "element") { + throw Arcweave::ParseErrorException("Invalid mention type"); + return false; + } + if (_state->visits.count(attrs["data-id"]) == 0) { + throw Arcweave::ParseErrorException("Invalid element mention"); + } + + return true; +} + +bool ArcscriptParserBase::assertFunctionArguments(Token *fname, std::any argumentList) { + int argListLength = 0; + std::string functionName = fname->getText(); + int min = Arcweave::ArcscriptFunctions::functions[functionName].minArgs; + int max = Arcweave::ArcscriptFunctions::functions[functionName].maxArgs; + if (argumentList.type() == typeid(Arcweave::ArcscriptParser::Argument_listContext*)) { + Arcweave::ArcscriptParser::Argument_listContext *argumentListCtx = std::any_cast(argumentList); + if (argumentListCtx != NULL) { + argListLength = static_cast(argumentListCtx->argument().size()); + } + } + if (argumentList.type() == typeid(Arcweave::ArcscriptParser::Variable_listContext*)) { + Arcweave::ArcscriptParser::Variable_listContext *variableListCtx = std::any_cast(argumentList); + if (variableListCtx != NULL) { + argListLength = static_cast(variableListCtx->VARIABLE().size()); + } + } + + if ((min != -1 && argListLength < min) || (max != -1 && argListLength > max)) { + throw Arcweave::ParseErrorException("Wrong number of arguments in \""+ functionName + "\"."); + return false; + } + + return true; +} + +void ArcscriptParserBase::setLineStart(antlr4::Token* token) +{ + openTagEndPos = token->getStartIndex() + token->getText().length(); +} diff --git a/Cpp/src/ArcscriptParserBase.h b/Cpp/src/ArcscriptParserBase.h new file mode 100755 index 0000000..83e0af4 --- /dev/null +++ b/Cpp/src/ArcscriptParserBase.h @@ -0,0 +1,18 @@ +#pragma once + +#include "antlr4-runtime.h" +#include "ArcscriptHelpers.h" + +class ArcscriptParserBase : public antlr4::Parser { +private: + Arcweave::ArcscriptState* _state; + size_t openTagEndPos; +public: + int currentLine = 0; + ArcscriptParserBase(antlr4::TokenStream *input) : Parser(input) { } + inline void setArcscriptState(Arcweave::ArcscriptState *state) { _state = state; }; + bool assertVariable(antlr4::Token *variable); + bool assertMention(std::any attrCtxList); + bool assertFunctionArguments(antlr4::Token *fname, std::any argumentList); + void setLineStart(antlr4::Token *token); +}; diff --git a/Cpp/src/ArcscriptTranspiler.cpp b/Cpp/src/ArcscriptTranspiler.cpp new file mode 100755 index 0000000..55cb545 --- /dev/null +++ b/Cpp/src/ArcscriptTranspiler.cpp @@ -0,0 +1,175 @@ +#include "ArcscriptTranspiler.h" +#include "ArcscriptErrorListener.h" +#include "antlr4-runtime.h" +#include "ArcscriptVisitor.h" +#include "ArcscriptLexer.h" +#include "ArcscriptParser.h" +#include + +#define _CRT_SECURE_NO_WARNINGS + +#ifdef _WIN64 +#define strdup _strdup +#endif + +using namespace Arcweave; +using namespace antlr4; + +TranspilerOutput ArcscriptTranspiler::runScript(std::string code) { + ANTLRInputStream input(code); + ArcscriptLexer lexer(&input); + + TranspilerOutput result; + + ErrorListener lexerErrorListener; + lexer.removeErrorListeners(); + lexer.addErrorListener(&lexerErrorListener); + + CommonTokenStream tokens(&lexer); + + // Run the lexer + tokens.fill(); + + ArcscriptParser parser(&tokens); + parser.setArcscriptState(&state); + ErrorListener parserErrorListener; + parser.removeErrorListeners(); + parser.addErrorListener(&parserErrorListener); + + ArcscriptParser::InputContext *tree; + + // Run the parser + tree = parser.input(); + + ArcscriptVisitor visitor(&state); + + // Run the visitor + std::any res(visitor.visitInput(tree)); + + result.changes = visitor.state->variableChanges; + + result.output = visitor.state->outputs.GetText(); + result.result = res; + + if (tree->script() != NULL) { + result.type = SCRIPT; + } else { + result.type = CONDITION; + } + + return result; +} + +UTranspilerOutput* runScriptExport(const char* code, const char* elId, UVariable* variables, size_t varLength, UVisit* visits, size_t visitsLength) +{ + Arcweave::TranspilerOutput transpilerOutput; + + transpilerOutput.type = InputType::CONDITION; + + std::string sCode(code); + std::string sElId(elId); + + std::map initVars; + for (size_t i = 0; i < varLength; i++) { + Variable var; + var.id = std::string(variables[i].id); + var.name = std::string(variables[i].name); + var.type = variables[i].type; + + if (var.type == VariableType::AW_STRING) { + var.value = std::string(variables[i].string_val); + } + else if (var.type == VariableType::AW_INTEGER) { + var.value = variables[i].int_val; + } + else if (var.type == VariableType::AW_DOUBLE) { + var.value = variables[i].double_val; + } + else if (var.type == VariableType::AW_BOOLEAN) { + var.value = variables[i].bool_val; + } + initVars[variables[i].id] = var; + } + + std::map initVisits; + for (size_t i = 0; i < visitsLength; i++) { + initVisits[std::string(visits[i].elId)] = visits[i].visits; + } + + Arcweave::ArcscriptTranspiler transpiler(sElId, initVars, initVisits); + transpilerOutput = transpiler.runScript(sCode); + + UTranspilerOutput* uTranspilerOutput = new UTranspilerOutput(); + uTranspilerOutput->output = strdup(transpilerOutput.output.c_str()); + uTranspilerOutput->type = transpilerOutput.type; + + if (transpilerOutput.type == InputType::CONDITION) { + uTranspilerOutput->conditionResult = std::any_cast(transpilerOutput.result); + } + + size_t changesLen = transpilerOutput.changes.size(); + + UVariableChange* variableChanges = new UVariableChange[changesLen]; + size_t i = 0; + for (auto change : transpilerOutput.changes) { + UVariableChange uChange; + uChange.varId = strdup(change.first.c_str()); + + if (change.second.type() == typeid(std::string)) { + uChange.type = VariableType::AW_STRING; + std::string string_result = std::any_cast(change.second); + uChange.string_result = strdup(string_result.c_str()); + } + else if (change.second.type() == typeid(int)) { + uChange.type = VariableType::AW_INTEGER; + uChange.int_result = std::any_cast(change.second); + } + else if (change.second.type() == typeid(double)) { + uChange.type = VariableType::AW_DOUBLE; + uChange.double_result = std::any_cast(change.second); + } + else if (change.second.type() == typeid(bool)) { + uChange.type = VariableType::AW_BOOLEAN; + uChange.bool_result = std::any_cast(change.second); + } + variableChanges[i] = uChange; + i++; + } + uTranspilerOutput->changes = variableChanges; + uTranspilerOutput->changesLen = changesLen; + + return uTranspilerOutput; + //std::cout << code << std::endl; + //std::cout << elId << std::endl; + //std::cout << _visits["test"] << std::endl; + /*try { + Arcweave::ArcscriptTranspiler transpiler(elId, initVars, _visits); + + try { + transpilerOutput = transpiler.runScript(code); + } + catch (std::exception& e) { + std::cerr << "Arcscript Transpiler failed during runScript: " << std::endl; + std::cerr << e.what() << std::endl; + return false; + } + } + catch (std::exception &e) { + std::cerr << "Arcscript Transpiler failed during init: " << std::endl; + std::cerr << e.what() << std::endl; + return false; + } + return true;*/ +} + +void deallocateOutput(UTranspilerOutput* output) { + for (size_t i = 0; i < output->changesLen; i++) { + free(output->changes[i].varId); + if (output->changes[i].type == VariableType::AW_STRING) { + free(output->changes[i].string_result); + } + } + delete[] output->changes; + free(output->output); + delete output; +} diff --git a/Cpp/src/ArcscriptTranspiler.h b/Cpp/src/ArcscriptTranspiler.h new file mode 100755 index 0000000..0aebda6 --- /dev/null +++ b/Cpp/src/ArcscriptTranspiler.h @@ -0,0 +1,153 @@ +#pragma once +#include "ArcscriptHelpers.h" +#include "ArcscriptErrorExceptions.h" + +#if defined _WIN64 + #ifdef WIN_EXPORT + #ifdef __GNUC__ + #define EXPORTED __attribute__ ((dllexport)) + #else + #define EXPORTED __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define EXPORTED __attribute__ ((dllimport)) + #else + #define EXPORTED __declspec(dllimport) + #endif + #endif + #define NOT_EXPORTED +#else + #if __GNUC__ >= 4 + #define EXPORTED __attribute__ ((visibility("default"))) + #define NOT_EXPORTED __attribute__ ((visibility("hidden"))) + #else + #define EXPORTED + #define NOT_EXPORTED + #endif +#endif + +namespace Arcweave +{ + /** + * @enum InputType + * The arscript code types + */ + enum InputType { + /// @brief A condition (results to bool) code type + CONDITION, + /// @brief A script code type + SCRIPT + }; + + /** + * @struct TranspilerOutput + * @brief The structure contains info that the ArcscriptTranspiler returns + * @var TranspilerOutput::output + * Member 'output' contains the text output when a codeblock is run + * @var TranspilerOutput::changes + * Member 'changes' contains the variables that are going to be changed from the codeblock + * @var TranspilerOutput::result + * Member 'result' contains the result of the ArcscriptTranspiler code. Useful in condition blocks + * @var TranspilerOutput::type + * Member 'type' contains the type of the code block i.e. CONDITION or SCRIPT + */ + struct TranspilerOutput { + std::string output; + std::map changes; + std::any result; + InputType type; + }; + + + // The following types starting with a U* are the types that the exported DLL function accepts and returns + extern "C" struct UVariableChange { + char* varId; + VariableType type; + int int_result; + double double_result; + char* string_result; + bool bool_result; + UVariableChange() { + varId = nullptr; + type = VariableType::AW_ANY; + int_result = 0; + double_result = 0.0; + string_result = nullptr; + bool_result = false; + } + }; + + extern "C" struct UTranspilerOutput { + char* output; + InputType type; + UVariableChange* changes; + size_t changesLen = 0; + bool conditionResult = false; + + UTranspilerOutput() { + output = nullptr; + type = InputType::SCRIPT; + changes = nullptr; + changesLen = 0; + conditionResult = false; + } + }; + + + struct UVariable { + const char* id; + const char* name; + VariableType type; + int int_val; + double double_val; + const char* string_val; + bool bool_val; + + UVariable() { + id = nullptr; + name = nullptr; + type = VariableType::AW_ANY; + int_val = 0; + double_val = 0; + string_val = nullptr; + bool_val = false; + } + }; + + struct UVisit { + const char* elId; + int visits; + + UVisit() { + elId = nullptr; + visits = 0; + } + }; + + /** + * Implementation of the Arcscript Transpiler in C++. Uses the ANTLR4 runtime library + * to run the code and it needs the initial variables and the current element ID it is + * transpiling. + * + * + */ + class ArcscriptTranspiler { + public: + + ArcscriptState state; + + ArcscriptTranspiler(std::string elId, std::map initVars, std::map _visits) : state(elId, initVars, _visits) { }; + + /** + * Runs the arcscript code and returns it's results. + * @param code The code block that we need to parse + * @return The result of the ran script + */ + TranspilerOutput runScript(std::string code); + + //ARCSCRIPTTRANSPILER_API UTranspilerOutput URunScript(char* code); + }; +}; +EXPORTED Arcweave::UTranspilerOutput* runScriptExport(const char* code, const char* elId, Arcweave::UVariable* variables, size_t varLength, Arcweave::UVisit* visits, size_t visitsLength); +EXPORTED void deallocateOutput(Arcweave::UTranspilerOutput* output); diff --git a/Cpp/src/ArcscriptVisitor.cpp b/Cpp/src/ArcscriptVisitor.cpp new file mode 100755 index 0000000..f849517 --- /dev/null +++ b/Cpp/src/ArcscriptVisitor.cpp @@ -0,0 +1,466 @@ +#include "antlr4-runtime.h" +#include "ArcscriptVisitor.h" +#include "ArcscriptParser.h" +#include "ArcscriptExpression.h" +#include "ArcscriptErrorExceptions.h" + +using namespace Arcweave; + +std::any ArcscriptVisitor::visitInput(ArcscriptParser::InputContext * ctx) +{ + if (ctx->script() != NULL) { + return visitScript(ctx->script()); + } + // Condition + Expression comp_cond = std::any_cast(visitCompound_condition_or(ctx->compound_condition_or())); + return comp_cond.value; +} + +std::any ArcscriptVisitor::visitScript_section(ArcscriptParser::Script_sectionContext *ctx) { + if (ctx == NULL) { + return std::any(); + } + + if (const auto blockquote_contexts = ctx->blockquote(); !blockquote_contexts.empty()) + { + std::vector result; + for (const auto blockquote_context : blockquote_contexts) + { + result.push_back(visitBlockquote(blockquote_context)); + } + return result; + } + + if (const auto paragraph_contexts = ctx->paragraph(); !paragraph_contexts.empty()) + { + std::vector result; + for (const auto paragraph_context : paragraph_contexts) + { + result.push_back(visitParagraph(paragraph_context)); + } + return result; + } + + return visitChildren(ctx); +} + +std::any ArcscriptVisitor::visitBlockquote(ArcscriptParser::BlockquoteContext* context) +{ + state->outputs.AddBlockquote(); + visitChildren(context); + state->outputs.ExitBlockquote(); + return context->getText(); +} + +std::any ArcscriptVisitor::visitParagraph(ArcscriptParser::ParagraphContext* context) +{ + auto paragraph_end = context->PARAGRAPHEND()->getText(); + auto paragraph_content = paragraph_end.substr(0, paragraph_end.size() - 4); // size of "

" + state->outputs.AddParagraph(paragraph_content); + return context->getText(); +} + + +std::any ArcscriptVisitor::visitAssignment_segment(ArcscriptParser::Assignment_segmentContext *ctx) { + state->outputs.AddScript(); + return visitStatement_assignment(ctx->statement_assignment()); +} + +std::any ArcscriptVisitor::visitFunction_call_segment(ArcscriptParser::Function_call_segmentContext *ctx) { + state->outputs.AddScript(); + return visitStatement_function_call(ctx->statement_function_call()); +} + +std::any ArcscriptVisitor::visitConditional_section(ArcscriptParser::Conditional_sectionContext *ctx) { + state->outputs.AddScript(); + ConditionalSection ifSection = std::any_cast(visitIf_section(ctx->if_section())); + if (ifSection.clause) { + state->outputs.AddScript(); + return ifSection.script; + } + for (ArcscriptParser::Else_if_sectionContext *else_if_section : ctx->else_if_section()) { + ConditionalSection elif_section = std::any_cast(visitElse_if_section(else_if_section)); + if (elif_section.clause) { + return elif_section.script; + } + } + if (ctx->else_section() != NULL) { + ConditionalSection elseSection = std::any_cast(visitElse_section(ctx->else_section())); + state->outputs.AddScript(); + return elseSection.script; + } + state->outputs.AddScript(); + return std::any(); +} + +std::any ArcscriptVisitor::visitIf_section(ArcscriptParser::If_sectionContext *ctx) { + Expression result = std::any_cast(visitIf_clause(ctx->if_clause())); + ConditionalSection ifSection; + ifSection.clause = false; + if (result == true) { + ifSection.clause = true; + ifSection.script = visitScript(ctx->script()); + } + return ifSection; +} + +std::any ArcscriptVisitor::visitElse_if_section(ArcscriptParser::Else_if_sectionContext *ctx) { + Expression result = std::any_cast(visitElse_if_clause(ctx->else_if_clause())); + ConditionalSection elseSection; + elseSection.clause = false; + if (result == true) { + elseSection.clause = true; + elseSection.script = visitScript(ctx->script()); + } + + return elseSection; +} + +std::any ArcscriptVisitor::visitElse_section(ArcscriptParser::Else_sectionContext *ctx) { + ConditionalSection elseIfSection; + elseIfSection.clause = true; + elseIfSection.script = visitScript(ctx->script()); + return elseIfSection; +} + +std::any ArcscriptVisitor::visitIf_clause(ArcscriptParser::If_clauseContext *ctx) { + return visitCompound_condition_or(ctx->compound_condition_or()); +} + +std::any ArcscriptVisitor::visitElse_if_clause(ArcscriptParser::Else_if_clauseContext *ctx) { + return visitCompound_condition_or(ctx->compound_condition_or()); +} + +std::any ArcscriptVisitor::visitStatement_assignment(ArcscriptParser::Statement_assignmentContext *ctx) { + std::string variableName = ctx->VARIABLE()->getText(); + Expression compound_condition_or = std::any_cast(visitCompound_condition_or(ctx->compound_condition_or())); + if (ctx->ASSIGN() != NULL) { + state->setVarValue(variableName, compound_condition_or.value); + return std::any(); + } + + Expression varValue; + varValue.setValue(state->getVarValue(variableName)); + + if (ctx->ASSIGNADD() != NULL) { + varValue += compound_condition_or; + } + if (ctx->ASSIGNSUB() != NULL) { + varValue -= compound_condition_or; + } + if (ctx->ASSIGNMUL() != NULL) { + varValue *= compound_condition_or; + } + if (ctx->ASSIGNDIV() != NULL) { + varValue /= compound_condition_or; + } + + state->setVarValue(variableName, varValue.value); + return std::any(); +} + +std::any ArcscriptVisitor::visitCompound_condition_or(ArcscriptParser::Compound_condition_orContext *ctx) { + std::any cond_any = visitCompound_condition_and(ctx->compound_condition_and()); + Expression compound_condition_and = std::any_cast(cond_any); + if (ctx->compound_condition_or() != NULL) { + Expression compound_condition_or = std::any_cast(visitCompound_condition_or(ctx->compound_condition_or())); + Expression result(compound_condition_and || compound_condition_or); + return result; + } + return compound_condition_and; +} + +std::any ArcscriptVisitor::visitCompound_condition_and(ArcscriptParser::Compound_condition_andContext *ctx) { + std::any cond_any = visitNegated_unary_condition(ctx->negated_unary_condition()); + Expression negated_unary_condition = std::any_cast(cond_any); + if (ctx->compound_condition_and() != NULL) { + Expression compound_condition_and = std::any_cast(visitCompound_condition_and(ctx->compound_condition_and())); + Expression result(negated_unary_condition && compound_condition_and); + return result; + } + return negated_unary_condition; +} + +std::any ArcscriptVisitor::visitNegated_unary_condition(ArcscriptParser::Negated_unary_conditionContext *ctx) { + Expression unary_condition = std::any_cast(visitUnary_condition(ctx->unary_condition())); + if (ctx->NEG() != NULL || ctx->NOTKEYWORD() != NULL) { + return Expression(!unary_condition); + } + return unary_condition; +} + +std::any ArcscriptVisitor::visitCondition(ArcscriptParser::ConditionContext *ctx) { + if (ctx->expression().size() == 1) { + return visitExpression(ctx->expression()[0]); + + // Expression expr = std::any_cast(exprAny); + // if (expr.type() == typeid(double)) { + // return expr > 0.0; + // } + // else if (expr.type() == typeid(int)) { + // return expr > 0; + // } + // else if (expr.type() == typeid(std::string)) { + // return expr != ""; + // } + // return std::any_cast(expr.value); + } + + ArcscriptParser::Conditional_operatorContext *cond_operator_ctx = ctx->conditional_operator(); + Expression exp0 = std::any_cast(visitExpression(ctx->expression()[0])); + Expression exp1 = std::any_cast(visitExpression(ctx->expression()[1])); + bool result = false; + if (cond_operator_ctx->GT() != NULL) { + result = exp0 > exp1; + } + else if (cond_operator_ctx->GE() != NULL) { + result = exp0 >= exp1; + } + else if (cond_operator_ctx->LT() != NULL) { + result = exp0 < exp1; + } + else if (cond_operator_ctx->LE() != NULL) { + result = exp0 <= exp1; + } + else if (cond_operator_ctx->EQ() != NULL) { + result = exp0 == exp1; + } + else if (cond_operator_ctx->NE() != NULL) { + result = exp0 != exp1; + } + else if (cond_operator_ctx->ISKEYWORD() != NULL) { + if (cond_operator_ctx->NOTKEYWORD() != NULL) { + result = exp0 != exp1; + } else { + result = exp0 == exp1; + } + } + return Expression(result); +} + +std::any ArcscriptVisitor::visitExpression(ArcscriptParser::ExpressionContext *ctx) { + if (ctx->STRING() != NULL) { + std::string result = ctx->STRING()->getText(); + result = result.substr(1, result.size() - 2); + return Expression(result); + } + if (ctx->BOOLEAN() != NULL) { + return Expression(ctx->BOOLEAN()->getText() == "true"); + } + + return visitAdditive_numeric_expression(ctx->additive_numeric_expression()); +} + +std::any ArcscriptVisitor::visitAdditive_numeric_expression(ArcscriptParser::Additive_numeric_expressionContext *ctx) { + if (ctx->additive_numeric_expression() != NULL) { + Expression result = std::any_cast(visitAdditive_numeric_expression(ctx->additive_numeric_expression())); + Expression mult_num_expr = std::any_cast(visitMultiplicative_numeric_expression(ctx->multiplicative_numeric_expression())); + + if (ctx->ADD() != NULL) { + mult_num_expr = result + mult_num_expr; + } + else if (ctx->SUB() != NULL) { + mult_num_expr = result - mult_num_expr; + } + return mult_num_expr; + } + return std::any_cast(visitMultiplicative_numeric_expression(ctx->multiplicative_numeric_expression())); +} + +std::any ArcscriptVisitor::visitMultiplicative_numeric_expression(ArcscriptParser::Multiplicative_numeric_expressionContext *ctx) { + if (ctx->multiplicative_numeric_expression() != NULL) { + Expression result = std::any_cast(visitMultiplicative_numeric_expression(ctx->multiplicative_numeric_expression())); + Expression signed_unary_num_expr = std::any_cast(visitSigned_unary_numeric_expression(ctx->signed_unary_numeric_expression())); + + if (ctx->MUL() != NULL) { + signed_unary_num_expr = result * signed_unary_num_expr; + } + else if (ctx->DIV() != NULL) { + signed_unary_num_expr = result / signed_unary_num_expr; + } + return signed_unary_num_expr; + } + + return std::any_cast(visitSigned_unary_numeric_expression(ctx->signed_unary_numeric_expression())); +} + +std::any ArcscriptVisitor::visitSigned_unary_numeric_expression(ArcscriptParser::Signed_unary_numeric_expressionContext *ctx) { + std::any expr_any = visitUnary_numeric_expression(ctx->unary_numeric_expression()); + Expression unary_num_expr = std::any_cast(expr_any); + ArcscriptParser::SignContext *sign = ctx->sign(); + + if (sign != NULL) { + if (sign->ADD() != NULL) { + return unary_num_expr; + } + // Else MINUS + unary_num_expr = unary_num_expr * (-1); + } + return unary_num_expr; +} + +std::any ArcscriptVisitor::visitUnary_numeric_expression(ArcscriptParser::Unary_numeric_expressionContext *ctx) { + if (ctx->FLOAT() != NULL) { + Expression result(std::stod(ctx->FLOAT()->getText())); + return result; + } + if (ctx->INTEGER() != NULL) { + Expression result(std::stoi(ctx->INTEGER()->getText())); + return result; + } + if (ctx->STRING() != NULL) { + std::string result = ctx->STRING()->getText(); + result = result.substr(1, result.size() - 2); + return Expression(result); + } + if (ctx->BOOLEAN() != NULL) { + return Expression(ctx->BOOLEAN()->getText() == "true"); + } + if (ctx->VARIABLE() != NULL) { + std::string variableName = ctx->VARIABLE()->getText(); + std::any varValue = state->getVarValue(variableName); + if (varValue.type() == typeid(std::string)) { + Expression result(std::any_cast(varValue)); + return result; + } + if (varValue.type() == typeid(bool)) { + Expression result(std::any_cast(varValue)); + return result; + } + if (varValue.type() == typeid(int)) { + Expression result(std::any_cast(varValue)); + return result; + } + if (varValue.type() == typeid(double)) { + Expression result(std::any_cast(varValue)); + return result; + } + } + if (ctx->function_call() != NULL) { + std::any resultValue = visitFunction_call(ctx->function_call()); + if (resultValue.type() == typeid(std::string)) { + Expression result(std::any_cast(resultValue)); + return result; + } + if (resultValue.type() == typeid(bool)) { + Expression result(std::any_cast(resultValue)); + return result; + } + if (resultValue.type() == typeid(int)) { + Expression result(std::any_cast(resultValue)); + return result; + } + if (resultValue.type() == typeid(double)) { + Expression result(std::any_cast(resultValue)); + return result; + } + throw Arcweave::RuntimeErrorException("Unknown result type from function call: " + ctx->function_call()->getText() + "\nType: " + resultValue.type().name()); + } + return visitCompound_condition_or(ctx->compound_condition_or()); +} + +std::any ArcscriptVisitor::visitVoid_function_call(ArcscriptParser::Void_function_callContext *ctx) { + std::string fname = ""; + std::any result; + if (ctx->VFNAME() != NULL) { + fname = ctx->VFNAME()->getText(); + std::vector argument_list_result; + if (ctx->argument_list() != NULL) { + argument_list_result = std::any_cast>(visitArgument_list(ctx->argument_list())); + } + result = functions->Call(fname, argument_list_result); + } + + if (ctx->VFNAMEVARS() != NULL) { + fname = ctx->VFNAMEVARS()->getText(); + std::vector variable_list_result; + + if (ctx->variable_list() != NULL) { + variable_list_result = std::any_cast>(visitVariable_list(ctx->variable_list())); + } + result = functions->Call(fname, variable_list_result); + } + // std::any result = std::invoke(functions->functions[fname], functions, argument_list_result); + + return result; +} + +std::any ArcscriptVisitor::visitFunction_call(ArcscriptParser::Function_callContext *ctx) { + std::vector argument_list_result; + if (ctx->argument_list() != NULL) { + argument_list_result = std::any_cast>(visitArgument_list(ctx->argument_list())); + } + std::string fname = ctx->FNAME()->getText(); + + // std::any result = std::invoke(functions->functions[fname], functions, argument_list_result); + std::any result = functions->Call(fname, argument_list_result); + return result; +} + +std::any ArcscriptVisitor::visitVariable_list(ArcscriptParser::Variable_listContext *ctx) { + std::vector variables; + for (antlr4::tree::TerminalNode *variable : ctx->VARIABLE()) { + variables.push_back(state->getVar(variable->getText())); + } + return variables; +} + +std::any ArcscriptVisitor::visitArgument_list(ArcscriptParser::Argument_listContext *ctx) { + std::vector arguments; + for (ArcscriptParser::ArgumentContext *argument : ctx->argument()) { + arguments.push_back(visitArgument(argument)); + } + return arguments; +} + +std::any ArcscriptVisitor::visitArgument(ArcscriptParser::ArgumentContext *ctx) { + if (ctx->STRING() != NULL) { + std::string result = ctx->STRING()->getText(); + result = result.substr(1, result.size() - 2); + return Expression(result); + } + if (ctx->mention() != NULL) { + Mention mention_result = std::any_cast(visitMention(ctx->mention())); + return mention_result; + } + + return visitAdditive_numeric_expression(ctx->additive_numeric_expression()); +} + +std::any ArcscriptVisitor::visitMention(ArcscriptParser::MentionContext *ctx) { + std::map attrs; + for (ArcscriptParser::Mention_attributesContext *attr : ctx->mention_attributes()) { + std::map res = std::any_cast>(visitMention_attributes(attr)); + attrs[res["name"]] = res["value"]; + } + std::string label = ""; + if (ctx->MENTION_LABEL() != NULL) { + label = ctx->MENTION_LABEL()->getText(); + } + Mention mention(label, attrs); + return mention; +} + +inline bool ends_with(std::string const & value, std::string const & ending) +{ + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +std::any ArcscriptVisitor::visitMention_attributes(ArcscriptParser::Mention_attributesContext *ctx) { + std::string name = ctx->ATTR_NAME()->getText(); + antlr4::tree::TerminalNode *valueNode = ctx->ATTR_VALUE(); + std::string value(name); + + if (valueNode != NULL) { + std::string strvalue = valueNode->getText(); + if ((strvalue.rfind("\"", 0) == 0 && ends_with(strvalue, "\"")) || + strvalue.rfind("'", 0) == 0 && ends_with(strvalue, "'")) { + + strvalue = strvalue.substr(1, strvalue.size() - 2); + + } + value = strvalue; + } + return std::map { {"name", name }, {"value", value } }; +} diff --git a/Cpp/src/ArcscriptVisitor.h b/Cpp/src/ArcscriptVisitor.h new file mode 100755 index 0000000..2b07166 --- /dev/null +++ b/Cpp/src/ArcscriptVisitor.h @@ -0,0 +1,51 @@ +#pragma once + +#include "ArcscriptParserBaseVisitor.h" +#include "ArcscriptFunctions.h" + +using namespace Arcweave; + +class ArcscriptVisitor : public ArcscriptParserBaseVisitor { +public: + struct ConditionalSection { + bool clause; + std::any script; + }; + + ArcscriptState *state; + ArcscriptFunctions *functions; + + ArcscriptVisitor(ArcscriptState* _state) : state(_state) { + functions = new ArcscriptFunctions(state); + } + + std::any visitInput(ArcscriptParser::InputContext *ctx) override; + std::any visitScript_section(ArcscriptParser::Script_sectionContext *ctx) override; + std::any visitAssignment_segment(ArcscriptParser::Assignment_segmentContext *ctx) override; + std::any visitFunction_call_segment(ArcscriptParser::Function_call_segmentContext * ctx) override; + std::any visitConditional_section(ArcscriptParser::Conditional_sectionContext *ctx) override; + std::any visitIf_section(ArcscriptParser::If_sectionContext *ctx) override; + std::any visitElse_if_section(ArcscriptParser::Else_if_sectionContext *ctx) override; + std::any visitElse_section(ArcscriptParser::Else_sectionContext *ctx) override; + std::any visitIf_clause(ArcscriptParser::If_clauseContext *ctx) override; + std::any visitElse_if_clause(ArcscriptParser::Else_if_clauseContext *ctx) override; + std::any visitStatement_assignment(ArcscriptParser::Statement_assignmentContext *ctx) override; + std::any visitCompound_condition_or(ArcscriptParser::Compound_condition_orContext *ctx) override; + std::any visitCompound_condition_and(ArcscriptParser::Compound_condition_andContext *ctx) override; + std::any visitNegated_unary_condition(ArcscriptParser::Negated_unary_conditionContext *ctx) override; + std::any visitCondition(ArcscriptParser::ConditionContext *ctx) override; + std::any visitExpression(ArcscriptParser::ExpressionContext *ctx) override; + std::any visitAdditive_numeric_expression(ArcscriptParser::Additive_numeric_expressionContext *ctx) override; + std::any visitMultiplicative_numeric_expression(ArcscriptParser::Multiplicative_numeric_expressionContext *ctx) override; + std::any visitSigned_unary_numeric_expression(ArcscriptParser::Signed_unary_numeric_expressionContext *ctx) override; + std::any visitUnary_numeric_expression(ArcscriptParser::Unary_numeric_expressionContext *ctx) override; + std::any visitVoid_function_call(ArcscriptParser::Void_function_callContext *ctx) override; + std::any visitFunction_call(ArcscriptParser::Function_callContext *ctx) override; + std::any visitVariable_list(ArcscriptParser::Variable_listContext *ctx) override; + std::any visitArgument_list(ArcscriptParser::Argument_listContext *ctx) override; + std::any visitArgument(ArcscriptParser::ArgumentContext *ctx) override; + std::any visitMention(ArcscriptParser::MentionContext *ctx) override; + std::any visitMention_attributes(ArcscriptParser::Mention_attributesContext *ctx) override; + std::any visitParagraph(ArcscriptParser::ParagraphContext* context) override; + std::any visitBlockquote(ArcscriptParser::BlockquoteContext* context) override; +}; diff --git a/README.md b/README.md index 4351dfc..8e82f34 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,16 @@ We are using the JavaScript parser to interpret arcscript in our arcweave app. W You can find more info in [JavaScript](JavaScript) folder. -### C# & C++ +### C# -The implementation on these two languages are not yet ready to be published. +Use the C# code along with the antlr4 NuGet package in your project. + +Find more info in the [CSharp](CSharp) folder. + +### C++ + +Building the C++ solution will create the include headers and the shared libaries (.dll for Windows and .dylib for macOS) to be included in your project +Find more info in the [Cpp](Cpp) folder. ## Links & References diff --git a/generate.ps1 b/generate.ps1 index 593c942..d325749 100644 --- a/generate.ps1 +++ b/generate.ps1 @@ -28,6 +28,6 @@ Set-Location .. # CSharp generation java -Xmx500M -cp ../antlr4.jar org.antlr.v4.Tool -Dlanguage=CSharp ArcscriptLexer.g4 ArcscriptParser.g4 -visitor -no-listener -o ./CSharp -package Arcweave.Interpreter -New-Item -ItemType Directory -Force -Path ../CSharp/src/Generated | Out-Null -Copy-Item CSharp\*.cs ../CSharp/src/Generated\ +New-Item -ItemType Directory -Force -Path ../CSharp/Interpreter/Generated | Out-Null +Copy-Item CSharp\*.cs ../CSharp/Interpreter/Generated\ Remove-Item -Recurse -Force ./CSharp \ No newline at end of file diff --git a/generate.sh b/generate.sh index 5c253ec..e76b1ac 100644 --- a/generate.sh +++ b/generate.sh @@ -1,3 +1,6 @@ +#!/usr/bin/env bash +cd "$(dirname "$0")" + if [ ! -f ./antlr4.jar ] then curl https://www.antlr.org/download/antlr-4.13.1-complete.jar -o antlr4.jar