diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b04a8c9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +Dockerfile +.dockerignore +docs +examples +Jenkinsfile +cmake-build-* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d0d9b58..7c44d3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.18) project(Yerbacon VERSION 0.0.1 LANGUAGES C CXX) set(EXENAME "ybcon") +string(TOLOWER "${PROJECT_NAME}" LOWERCASE_PROJECT_NAME) if (NOT DEFINED CODENAME) set(CODENAME "Unknown") @@ -13,7 +14,8 @@ set(EXEDESC "Transpiler for the yerbacon language.") string(TIMESTAMP SHORT_BUILD_TIMESTAMP "%Y%m") string(SUBSTRING ${SHORT_BUILD_TIMESTAMP} 2 4 SHORT_BUILD_TIMESTAMP) string(ASCII 169 CopyrightCharacter) -string(TIMESTAMP LEGALCOPYRIGHT "Copyright ${CopyrightCharacter} 2020-%Y. Available under the MPL-2.0 license.") +set(CPACK_RPM_PACKAGE_LICENSE "MPL-2.0") +string(TIMESTAMP LEGAL-COPYRIGHT "Copyright ${CopyrightCharacter} 2020-%Y. Available under the ${CPACK_RPM_PACKAGE_LICENSE} license.") file(COPY "resources/${PROJECT_NAME}.ico" DESTINATION "processed") configure_file("resources/${PROJECT_NAME}.manifest" "processed/${PROJECT_NAME}.manifest" @ONLY) configure_file("resources/${PROJECT_NAME}.rc" "processed/${PROJECT_NAME}.rc" @ONLY) @@ -21,20 +23,33 @@ configure_file("resources/${PROJECT_NAME}.rc" "processed/${PROJECT_NAME}.rc" @ON set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +if (NOT DEFINED CMAKE_UNITY_BUILD) + set(CMAKE_UNITY_BUILD TRUE) +endif() include(CheckPIESupported) check_pie_supported(LANGUAGES CXX) +set(USER_DEFINED_PIE (DEFINED CMAKE_POSITION_INDEPENDENT_CODE)) if (CMAKE_CXX_LINK_PIE_SUPPORTED) - set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) -else() - message(NOTICE "Position-Independent Code (PIC) is not supported by the current toolchain") + if (NOT USER_DEFINED_PIE) + set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) + endif() +elseif(USER_DEFINED_PIE AND CMAKE_POSITION_INDEPENDENT_CODE) + message(NOTICE "-- Could NOT manually enable Position-Independent Code (PIC) because it is not supported by the current toolchain") +endif() +if (DEFINED CXX_TARGET) + set(CMAKE_SYSTEM_PROCESSOR "${CXX_TARGET}") +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "") + set(CMAKE_SYSTEM_PROCESSOR "${CMAKE_HOST_SYSTEM_PROCESSOR}") endif() set(CMAKE_CXX_FLAGS "-Wall") set(CMAKE_CXX_FLAGS_RELEASE "-Os") +set(CMAKE_COLOR_DIAGNOSTICS ON) set(MINIMAL_GNU "11.0") -set(MINIMAL_CLANG "13.0") -set(MINIMAL_MSVC "19.25") -set(IS_GNU (${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)) -set(IS_CLANG (${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)) +set(MINIMAL_CLANG "14.0.1") +set(MINIMAL_MSVC "19.30") +option(IGNORE_MINIMAL_COMPILER_VERSION "Whether or not to ignore the minimal compiler versions") +set(IS_GNU (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")) +set(IS_CLANG (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) option(NO_CCACHE "Disables CCache") if (NOT NO_CCACHE) @@ -49,13 +64,30 @@ find_package(Threads) if (${IS_GNU} OR ${IS_CLANG}) set(THREADS_PREFER_PTHREAD_FLAG TRUE) if (Threads_FOUND AND NOT MINGW) - include(FindOpenMP) - if (OpenMP_CXX_FOUND) - set(CMAKE_CXX_FLAGS "${OpenMP_CXX_FLAGS} ${CMAKE_CXX_FLAGS}") - add_definitions(-D_GLIBCXX_PARALLEL) + if (NOT ("${CMAKE_EXE_LINKER_FLAGS}" MATCHES -static)) + include(FindOpenMP) + if (OpenMP_CXX_FOUND) + set(CMAKE_CXX_FLAGS "${OpenMP_CXX_FLAGS} ${CMAKE_CXX_FLAGS}") + add_definitions(-D_GLIBCXX_PARALLEL) + endif() endif() endif() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstrict-enums -pipe -fstack-protector-strong -fstack-clash-protection -funwind-tables -fasynchronous-unwind-tables -frtti -fexceptions") + if (NOT DEFINED EMSCRIPTEN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong -fstack-clash-protection") + else() + if (NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Android") # See https://github.com/WebAssembly/binaryen/issues/4519 + set(IS_HOST_NOT_ANDROID 1) + else() + set(IS_HOST_NOT_ANDROID 0) + endif() + set(CMAKE_EXE_LINKER_FLAGS "--closure 1 -sWASM=0 --memory-init-file 0 -sEXPORTED_FUNCTIONS=_main -sEXIT_RUNTIME=1 -sSINGLE_FILE=1 -sSAFE_HEAP=${IS_HOST_NOT_ANDROID} -sABORTING_MALLOC=0 -sJS_MATH=1 -sENVIRONMENT='web,webview,worker,node,shell' -sNODEJS_CATCH_EXIT=0 -sSTRICT=1 -sMINIMAL_RUNTIME=0 -sDISABLE_EXCEPTION_CATCHING=0 -sALLOW_MEMORY_GROWTH=1 -Wno-pthreads-mem-growth -sMEMORY_GROWTH_GEOMETRIC_STEP=0 -sDECLARE_ASM_MODULE_EXPORTS=0 ${CMAKE_EXE_LINKER_FLAGS}") + if (CMAKE_USE_PTHREADS_INIT) + set(CMAKE_CXX_FLAGS "-pthread ${CMAKE_CXX_FLAGS}") # Emscripten requires the -pthread flag + set(CMAKE_EXE_LINKER_FLAGS "--closure-args=\"--compilation_level SIMPLE\" -sPROXY_TO_PTHREAD=1 ${CMAKE_EXE_LINKER_FLAGS}") # See https://github.com/emscripten-core/emscripten/issues/16706 + endif() + endif() + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-s") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstrict-enums -pipe -funwind-tables -fasynchronous-unwind-tables -frtti -fexceptions") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffunction-sections -fdata-sections -fmerge-all-constants -ftree-vectorize -fno-math-errno") include(CheckCXXCompilerFlag) set(CF_PROTECTION "-fcf-protection") @@ -65,37 +97,49 @@ if (${IS_GNU} OR ${IS_CLANG}) endif() endif() -include(CheckIPOSupported) -check_ipo_supported(RESULT CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE LANGUAGES CXX) +if (NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE) + set(USER_DEFINED_INTERPROCEDURAL_OPTIMIZATION_RELEASE NO) + include(CheckIPOSupported) + check_ipo_supported(RESULT CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE LANGUAGES CXX) +else() + set(USER_DEFINED_INTERPROCEDURAL_OPTIMIZATION_RELEASE YES) +endif() if (${IS_GNU}) - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${MINIMAL_GNU}) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${MINIMAL_GNU} AND NOT ${IGNORE_MINIMAL_COMPILER_VERSION}) message(FATAL_ERROR "G++ ${MINIMAL_GNU} or higher is required.") endif() if (Threads_FOUND) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ftree-parallelize-loops=2") endif() - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fvect-cost-model=unlimited -foptimize-strlen -fsched-pressure -flive-range-shrinkage -fpredictive-commoning -ftree-partial-pre -fzero-call-used-regs=used-gpr-arg -fira-loop-pressure -ftree-loop-distribution -floop-interchange -fsplit-paths -fgcse-las -fgcse-sm -fipa-pta -fstdarg-opt -fivopts") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fvect-cost-model=unlimited -foptimize-strlen -fsched-pressure -flive-range-shrinkage -fpredictive-commoning -ftree-partial-pre -fzero-call-used-regs=used-gpr-arg -fira-loop-pressure -ftree-loop-distribution -floop-interchange -fsplit-paths -fgcse-las -fgcse-sm -fipa-pta -fstdarg-opt -fivopts -fweb") if (${CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE}) - set(CMAKE_CXX_FLAGS_RELEASE "-fwhole-program ${CMAKE_CXX_FLAGS_RELEASE}") + set(CMAKE_CXX_FLAGS_RELEASE "-fwhole-program -flto-partition=none ${CMAKE_CXX_FLAGS_RELEASE}") endif() elseif(${IS_CLANG}) - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${MINIMAL_CLANG}) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${MINIMAL_CLANG} AND NOT ${IGNORE_MINIMAL_COMPILER_VERSION}) message(FATAL_ERROR "Clang ${MINIMAL_CLANG} or higher is required.") endif() - if (${CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE} OR MINGW) # CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE is false on llvm-mingw toolchains even though it is supported - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto=full -fwhole-program-vtables") + if (MINGW AND NOT USER_DEFINED_INTERPROCEDURAL_OPTIMIZATION_RELEASE) # CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE is false on llvm-mingw toolchains even though it is supported + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE YES) endif() + if (CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto=thin") + if (NOT DEFINED EMSCRIPTEN) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fwhole-program-vtables") + endif() + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-warning-option -Wno-unqualified-std-cast-call -Wno-reinterpret-base-class") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fstrict-vtable-pointers") elseif(MSVC) - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${MINIMAL_MSVC}) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${MINIMAL_MSVC} AND NOT ${IGNORE_MINIMAL_COMPILER_VERSION}) message(FATAL_ERROR "MSVC ${MINIMAL_MSVC} or higher is required") endif() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR /W3 /Zc:__cplusplus /Zc:preprocessor /Zc:throwingNew /Zc:inline") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive- /Zc:twoPhase- /EHsc /GR /W3 /Zc:__cplusplus /Zc:preprocessor /Zc:throwingNew /Zc:inline") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" UPPERCASE_BUILD_TYPE) -if (UPPERCASE_BUILD_TYPE STREQUAL RELEASE) +if (UPPERCASE_BUILD_TYPE STREQUAL "RELEASE") add_definitions(-DYBCON_FLAGS="${CMAKE_CXX_FLAGS_RELEASE}") endif() add_definitions(-DYBCON_COMPILER="${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") @@ -119,39 +163,77 @@ set(CPACK_VERBATIM_VARIABLES TRUE) string(TIMESTAMP TIME "%Y.%m+%d") set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}-${TIME}") set(CPACK_PACKAGE_VENDOR "Contributor(s)") -set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/Yerbacon.png") +set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/${PROJECT_NAME}.png") set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/misc/desc.txt") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${EXEDESC}") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") set(CPACK_PACKAGE_CONTACT "Username404 ") -set(CPACK_PACKAGE_INSTALL_DIRECTORY "Yerbacon ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}") -set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${CMAKE_PROJECT_VERSION}-${TIME}") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}") +set(CPACK_PACKAGE_FILE_NAME "${LOWERCASE_PROJECT_NAME}-${PROJECT_VERSION}-${TIME}.${CMAKE_SYSTEM_PROCESSOR}") include_directories(${CMAKE_CURRENT_LIST_DIR}) -add_executable(${EXENAME} src/main.cpp ${CMAKE_CURRENT_BINARY_DIR}/processed/${PROJECT_NAME}.rc src/etc/filefuncs.cpp src/etc/lexer.cpp src/headers/lex.hpp src/headers/misc.hpp src/headers/parsing/ParseComponents.hpp src/headers/transpiler/Target.hpp src/headers/transpiler/implementations/Lua.hpp src/headers/transpiler/implementations/Js.hpp src/headers/transpiler/implementations/Py.hpp src/headers/parsing/Parser.hpp src/headers/arguments.hpp) +add_executable(${EXENAME} src/main.cpp ${CMAKE_CURRENT_BINARY_DIR}/processed/${PROJECT_NAME}.rc src/etc/filefuncs.cpp src/etc/lexer.cpp src/headers/lex.hpp src/headers/misc.hpp src/headers/parsing/ParseComponents.hpp src/headers/transpiler/Target.hpp src/headers/transpiler/implementations/Lua.hpp src/headers/transpiler/implementations/Js.hpp src/headers/transpiler/implementations/Py.hpp src/headers/parsing/Parser.hpp src/headers/arguments.hpp src/headers/parsing/ReservedIdentifiers.hpp) target_compile_definitions(${EXENAME} PRIVATE YBCON_VERSION="${CODENAME} ${PROJECT_VERSION}") target_precompile_headers(${EXENAME} PRIVATE src/headers/Yerbacon.hpp) if (Threads_FOUND) target_link_libraries(${EXENAME} PRIVATE Threads::Threads) endif() -# lpkg = linux package, wpkg = windows package -set(PNAME ${PROJECT_NAME}-${CODENAME}-${TIME}) -if (UNIX AND NOT (MINGW OR CMAKE_HOST_WIN32)) +option(NO_SELF_PACKER "Disables usage of a self-packer") +if (NOT (UPPERCASE_BUILD_TYPE STREQUAL "DEBUG" OR UPPERCASE_BUILD_TYPE STREQUAL "RELWITHDEBINFO" OR NO_SELF_PACKER OR DEFINED EMSCRIPTEN)) + include(FindSelfPackers) + if (SELF_PACKER_FOR_EXECUTABLE MATCHES upx) # UPX version d61edc9 or higher is required when using a cross-compiler to target the musl C library + if (CMAKE_CXX_LINK_NO_PIE_SUPPORTED OR MINGW) # MINGW does not support PIE, yet the variable is set to NO. + set(SELF_PACKER_FOR_EXECUTABLE_FLAGS ${SELF_PACKER_FOR_EXECUTABLE_FLAGS} --ultra-brute --best) + if (USER_DEFINED_PIE AND CMAKE_POSITION_INDEPENDENT_CODE) + message(NOTICE "-- Could NOT manually enable PIE (UPX is in use)") + endif() + set(CMAKE_POSITION_INDEPENDENT_CODE FALSE) + set_target_properties(${EXENAME} PROPERTIES POSITION_INDEPENDENT_CODE ${CMAKE_POSITION_INDEPENDENT_CODE}) + else() + set(SELF_PACKER_FOR_EXECUTABLE "SELF_PACKER_FOR_EXECUTABLE-NOTFOUND") + message(NOTICE "UPX usage has been disabled because position-dependent executables are unsupported by the current compiler") + endif() + endif() + if (NOT SELF_PACKER_FOR_EXECUTABLE STREQUAL "SELF_PACKER_FOR_EXECUTABLE-NOTFOUND") + add_custom_command(TARGET ${EXENAME} POST_BUILD COMMAND "${SELF_PACKER_FOR_EXECUTABLE}" ${SELF_PACKER_FOR_EXECUTABLE_FLAGS} $ VERBATIM) + endif() +endif() + +if (EMSCRIPTEN) + find_program(NPM_PRESENT npm) + find_program(YARN_PRESENT yarn) + if (YARN_PRESENT) + set(NODE_PACKAGE_PROGRAM "${YARN_PRESENT}") + elseif(NPM_PRESENT) + set(NODE_PACKAGE_PROGRAM "${NPM_PRESENT}") + endif() + if (DEFINED NODE_PACKAGE_PROGRAM) + configure_file("resources/package.json" "processed/package.json" @ONLY) + file(COPY "${CMAKE_CURRENT_BINARY_DIR}/processed/package.json" DESTINATION ".") + file(COPY "resources/.npmrc" DESTINATION ".") + add_custom_target(vercel_pkg COMMAND ${NODE_PACKAGE_PROGRAM} install) + add_dependencies(vercel_pkg ${EXENAME}) + # Warning: https://github.com/sbingner/ldid has to be on the PATH, before packaging, for the arm64 macOS executable to work + add_custom_command(TARGET vercel_pkg POST_BUILD COMMAND node_modules/.bin/pkg --config processed/package.json -C Brotli $ --no-bytecode --public-packages "\"*\"" --public --output "${EXENAME}-mac" VERBATIM) + endif() +endif() + +if (UNIX AND NOT (MINGW OR CMAKE_HOST_WIN32 OR EMSCRIPTEN)) include(GNUInstallDirs) set(CMAKE_INSTALL_PREFIX "/usr") set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") - set(CPACK_PACKAGE_FILE_NAME "${PNAME}_lpkg") set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scripts/postinst" "processed/postinst" @ONLY) - set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/processed/postinst") + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/processed/postinst") set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA ${CPACK_RPM_POST_INSTALL_SCRIPT_FILE}) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT") set(CPACK_DEBIAN_COMPRESSION_TYPE xz) set(CPACK_RPM_COMPRESSION_TYPE ${CPACK_DEBIAN_COMPRESSION_TYPE}) set(CPACK_RPM_PACKAGE_AUTOREQ YES) - set(CPACK_RPM_PACKAGE_LICENSE "MPL-2.0") + set(CPACK_RPM_FILE_NAME "RPM-DEFAULT") set(CPACK_RPM_CHANGELOG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/changelog") if (CMAKE_VERSION STREQUAL "3.21.1") message(WARNING "CPack ${CMAKE_VERSION} has a bug which causes rpm scripts to be unusable; please update or downgrade") @@ -200,14 +282,14 @@ elseif(MINGW OR MSVC) set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CODENAME}) set(CPACK_NSIS_INSTALL_ROOT "${CMAKE_INSTALL_PREFIX}") if (MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-command-line-argument") set(CMAKE_EXE_LINKER_FLAGS "-static -static-libstdc++ -static-libgcc ${CMAKE_EXE_LINKER_FLAGS}") endif() - set(CPACK_PACKAGE_FILE_NAME "${PNAME}_wpkg") set(CPACK_NSIS_COMPRESSOR "/SOLID lzma") set(CPACK_NSIS_MANIFEST_DPI_AWARE TRUE) set(CPACK_NSIS_PACKAGE_NAME "${PROJECT_NAME} ${CODENAME}-${PROJECT_VERSION}") set(CPACK_NSIS_WELCOME_TITLE_3LINES ON) - set(CPACK_NSIS_URL_INFO_ABOUT "https://gits.username404.fr/Username404-59/Yerbacon") + set(CPACK_NSIS_URL_INFO_ABOUT "https://gits.username404.fr/Username404-59/${PROJECT_NAME}") set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/${PROJECT_NAME}.ico") set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/${PROJECT_NAME}.ico") set(CPACK_NSIS_DEFINES "RequestExecutionLevel highest") @@ -217,7 +299,7 @@ elseif(MINGW OR MSVC) set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) if (CMAKE_HOST_WIN32) # The two following variables require CMake 3.20 or higher - set(CPACK_NSIS_BRANDING_TEXT "NSIS Installer for the yerbacon compiler") + set(CPACK_NSIS_BRANDING_TEXT "NSIS Installer for ${PROJECT_NAME}") set(CPACK_NSIS_BRANDING_TEXT_TRIM_POSITION CENTER) endif() # CMake 3.22+ is required to ignore the NSIS license page @@ -225,8 +307,4 @@ elseif(MINGW OR MSVC) set(CPACK_GENERATOR ZIP;NSIS) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${EXENAME}.exe DESTINATION bin) endif() -if (NOT DEFINED CXX_TARGET) - set(CXX_TARGET ${CMAKE_HOST_SYSTEM_PROCESSOR}) -endif() -set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}.${CXX_TARGET}") include(CPack) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7201374 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM alpine:edge +RUN apk add --no-cache gcc g++ musl-dev libc-dev cmake git samurai coreutils rpm dpkg dpkg-dev xz file + +RUN mkdir -p /usr/src/yerbacon +WORKDIR /usr/src/ +ARG BRANCH="devel4" +ADD https://api.github.com/repos/upx/upx/git/refs/heads/$BRANCH version.json +RUN git clone --quiet -n --branch $BRANCH https://github.com/upx/upx/ +ARG CMAKE_BUILD_TYPE="Release" +WORKDIR ./upx +RUN git checkout --quiet `git tag | sort -V | tail -1` +RUN git submodule --quiet update --init +RUN mkdir -p ./build/release +WORKDIR ./build/release +RUN cmake -G Ninja ../.. +RUN cmake --build . --parallel `nproc` + +WORKDIR /usr/src/yerbacon +COPY . ./ +RUN cmake -G Ninja -S . -B ./cmake-build-release -DCMAKE_EXE_LINKER_FLAGS="-static" -DSELF_PACKER_FOR_EXECUTABLE=/usr/src/upx/build/release/upx +RUN cmake --build ./cmake-build-release --parallel `nproc` +WORKDIR ./cmake-build-release +RUN cpack \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 01879d2..8066c55 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,29 +1,29 @@ -def buildTarget(String path, String rpmArch = 'noarch', String debArch = 'noarch', boolean isPackageArchDeb = true, String suffix = '') { - final String packageArch = isPackageArchDeb ? debArch : rpmArch; - final String system_name = !path.contains('mingw') ? sh(returnStdout: true, script: 'uname -s').trim() : 'Windows' - // Note: CMake 3.20 or higher is needed - cmakeBuild buildDir: "cmake-build-${packageArch}${suffix}", buildType: 'release', cleanBuild: true, installation: 'Latest', - cmakeArgs: "--no-warn-unused-cli -DCMAKE_SYSTEM_NAME=\"${system_name}\" -DCMAKE_C_COMPILER=/usr/bin/${path}-gcc -DCMAKE_CXX_COMPILER=/usr/bin/${path}-g++ -DCMAKE_LINKER=/usr/bin/${path}-ld.gold -DCMAKE_AR=/usr/bin/${path}-ar -DCMAKE_RC_COMPILER=/usr/bin/${path}-windres -DCPACK_RPM_PACKAGE_ARCHITECTURE=${rpmArch} -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=${debArch} -DCXX_TARGET=${packageArch} -DCPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS=/usr/${path}/lib/ -DNO_CCACHE=ON", - generator: fileExists('/usr/bin/ninja') ? 'Ninja' : 'Unix Makefiles' - cmake arguments: "--build ./cmake-build-${packageArch}${suffix} --target ybcon", installation: 'Latest' - sh "/usr/bin/${path}-strip -s ./cmake-build-${packageArch}${suffix}/ybcon*" - cpack installation: 'Latest', workingDir: "cmake-build-${packageArch}${suffix}" -} - -final String windowsSuffix = '-windows' +String cmake_generator() { return fileExists('/usr/bin/ninja') ? 'Ninja' : 'Unix Makefiles' } +def clean_workspace() { cleanWs(cleanWhenNotBuilt: false, deleteDirs: true, disableDeferredWipeout: false, notFailBuild: true, skipWhenFailed: false) } +boolean use_yarn_or_npm() { return fileExists('/usr/bin/yarn') || fileExists('/usr/bin/npm') } /* Required Plugins: - CMake - Sidebar Link - Workspace Cleanup + Required Compilers: + - G++ cross-compiler for riscv64 + - Musl cross-compiling toolchains (archives ending in "-cross" from https://musl.cc/#binaries) for x86_64, i686, armel, armhf and aarch64, unzipped into /usr/, along with soft links from /usr/bin/ to the binaries + - Emscripten Clang (https://github.com/emscripten-core/emsdk) in /usr/share/ + - MinGW32 G++/Clang (https://github.com/mstorsjo/llvm-mingw) for x86_64, i686, armhf and aarch64, (unzip the content of the folder in the tar.xz file) in /usr/, along with soft links from /usr/bin/ to the binaries + Optional Tools: + - Ninja + - UPX (d61edc9 or higher) + - Vercel PKG (https://github.com/vercel/pkg) + - LDID (https://github.com/sbingner/ldid) */ -pipeline { // Multi-branch pipeline script for Yerbacon. +pipeline { agent any options { buildDiscarder(logRotator(numToKeepStr: '48', artifactNumToKeepStr: '96')) - timeout(time: 8, unit: 'MINUTES') + timeout(time: 25, unit: 'MINUTES') sidebarLinks([ [displayName: 'Gitea Repository', iconFileName: '/userContent/Yerbacon.png', urlName: 'https://gits.username404.fr/Username404-59/Yerbacon/src/branch/' + env.BRANCH_NAME] ]) @@ -33,39 +33,70 @@ pipeline { // Multi-branch pipeline script for Yerbacon. } stages { stage('Build') { - steps { - echo "Building the ${env.BRANCH_NAME} branch.." - buildTarget('x86_64-linux-gnu', 'x86_64', 'amd64') - buildTarget('i686-linux-gnu', 'i386', 'i386') - buildTarget('arm-linux-gnueabi', 'armv4l', 'armel') - buildTarget('arm-linux-gnueabihf', 'armv7hl', 'armhf') - buildTarget('aarch64-linux-gnu', 'aarch64', 'arm64', false) - buildTarget('riscv64-linux-gnu', 'riscv64', 'riscv64') - } - } - stage('Build for other platforms') { - steps { - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { - buildTarget('x86_64-w64-mingw32', 'x86_64', 'amd64', true, windowsSuffix) - buildTarget('i686-w64-mingw32', 'i386', 'i386', true, windowsSuffix) - buildTarget('armv7-w64-mingw32', 'armv7hl', 'armhf', true, windowsSuffix) - buildTarget('aarch64-w64-mingw32', 'aarch64', 'arm64', false, windowsSuffix) + matrix { + agent any + axes { + axis { + name 'TARGET' + values( + 'x86_64-linux-musl (RPM=x86_64, DEB=amd64)', + 'i686-linux-musl (RPM=i386, DEB=i386)', + 'armel-linux-musleabi (RPM=armv4l, DEB=armel)', + 'armv7l-linux-musleabihf (RPM=armv7hl, DEB=armhf)', + 'aarch64-linux-musl (RPM=aarch64, DEB=arm64)', + 'riscv64-linux-gnu (RPM=riscv64, DEB=riscv64)', + 'x86_64-w64-mingw32 (RPM=x86_64, DEB=amd64)', + 'i686-w64-mingw32 (RPM=i386, DEB=i386)', + 'armv7-w64-mingw32 (RPM=armv7hl, DEB=armhf)', + 'aarch64-w64-mingw32 (RPM=aarch64, DEB=arm64)', + '/usr/share/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake' + ) + } } - } - } - stage('Deploy') { - steps { - echo 'Deploying....' - archiveArtifacts artifacts: 'cmake-build-*/*.deb, cmake-build-*/*.rpm, cmake-build-*/*.tar.gz, cmake-build-*/*.sh', fingerprint: false - catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') { - archiveArtifacts artifacts: 'cmake-build-*/*wpkg.*.exe, cmake-build-*/*.zip' + stages { + stage('Compile') { + steps { + script { + final boolean use_toolchain = "${TARGET}".endsWith('.cmake') + final String path = use_toolchain ? "${TARGET}" : "${TARGET}".substring(0, "${TARGET}".indexOf(' (')) + final String rpmArch = use_toolchain ? 'noarch' : "${TARGET}".substring("${TARGET}".indexOf('(RPM=') + 5, "${TARGET}".indexOf(',')) + final String debArch = use_toolchain ? 'noarch' : "${TARGET}".substring("${TARGET}".indexOf('DEB=') + 4, "${TARGET}".indexOf(')')) + final String system_name = !path.contains('mingw') ? sh(returnStdout: true, script: 'uname -s').trim() : 'Windows' + final String linker = "/usr/bin/${path}-ld" + final boolean not_packer_compatible = path.contains('riscv') || ((path.contains('aarch64') || path.contains('arm')) && path.contains('mingw')) + final String build_directory = "cmake-build-${use_toolchain ? path.substring(path.lastIndexOf('/') + 1, path.length() - 6) : path}".toLowerCase() + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + // Note: CMake 3.20 or higher is needed + cmakeBuild buildDir: build_directory, buildType: 'release', cleanBuild: true, installation: 'Latest', + cmakeArgs: "--no-warn-unused-cli ${use_toolchain ? "-DCMAKE_TOOLCHAIN_FILE=${path}" : "-DCMAKE_SYSTEM_NAME=\"${system_name}\" -DCXX_TARGET=\"${use_toolchain ? 'noarch' : debArch}\" -DCPACK_RPM_PACKAGE_ARCHITECTURE=${rpmArch} -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=${debArch} -DCMAKE_SYSTEM_PROCESSOR=\"${path.substring(0, path.indexOf('-'))}\" -DCMAKE_C_COMPILER=/usr/bin/${path}-gcc -DCMAKE_CXX_COMPILER=/usr/bin/${path}-g++ -DCMAKE_LINKER=${fileExists("${linker}.gold") ? "${linker}.gold" : linker} -DCMAKE_AR=/usr/bin/${path}-ar -DCMAKE_RC_COMPILER=/usr/bin/${path}-windres -DCMAKE_EXE_LINKER_FLAGS=-static"} -DNO_SELF_PACKER=${!(not_packer_compatible || use_toolchain) ? "OFF" : "ON"} -DIGNORE_MINIMAL_COMPILER_VERSION=ON -DNO_CCACHE=ON -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON${path.endsWith('armv7-w64-mingw32') ? ' -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=NO' : ''}", + generator: cmake_generator() + cmake arguments: "--build ./$build_directory --target ${!("${TARGET}".endsWith('Emscripten.cmake') && use_yarn_or_npm()) ? 'ybcon' : 'vercel_pkg'}", installation: 'Latest' + } + } + } + } + stage('Package') { + when { equals expected: true, actual: !"${TARGET}".endsWith('.cmake') } + steps { + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + script { + cpack installation: 'Latest', workingDir: "cmake-build-${"${TARGET}".substring(0, "${TARGET}".indexOf(' ('))}" + } + } + } + } + stage('Deploy') { + steps { + echo 'Deploying....' + catchError(buildResult: "${TARGET}".endsWith('.cmake') ? 'FAILURE' : 'UNSTABLE', stageResult: 'FAILURE') { + archiveArtifacts artifacts: "cmake-build-*/*.deb, cmake-build-*/*.rpm, cmake-build-*/*.tar.gz, cmake-build-*/*.sh, cmake-build-*/*.js,${use_yarn_or_npm() ? 'cmake-build-*/*-mac-*,' : ''} cmake-build-*/*.*.exe, cmake-build-*/*.zip", fingerprint: false + } + } + } } + post { always { clean_workspace() } } } } } - post { - always { - cleanWs(cleanWhenNotBuilt: false, deleteDirs: true, disableDeferredWipeout: false, notFailBuild: true, skipWhenFailed: false) - } - } + post { always { clean_workspace() } } } \ No newline at end of file diff --git a/README.md b/README.md index 1a76cfa..80c13ef 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -# Yerbacon +# Yerbacon -[![Build Status](https://ci.username404.fr/buildStatus/icon?style=plastic)](https://ci.username404.fr/job/Yerbacon/job/stable/) +Yerbacon logo -Yerbacon logo -Aka Yer Bacon, - -- #### A language that transpiles into lua, javascript (ES6) or python code. +### ► A language that transpiles into another programming language, like Lua, JavaScript (ES6) or Python. ``` -main #=> { +main >> { print_line("Hello, World!") } +main() ``` +[![Build Status](https://ci.username404.fr/buildStatus/icon?style=plastic)](https://ci.username404.fr/job/Yerbacon/job/stable/) + ### Planned features: - Type inference @@ -23,14 +23,11 @@ main #=> { ## Build requirements -CMake 3.18 or higher is needed. +1. CMake 3.18 *or higher* -### Linux target +2. A compiler from the following list: + - GCC 11+ + - Clang 14.0.1+ with llvm-gold + - MSVC 19.30+ -- GCC 11+ or Clang 13+ with llvm-gold - -### Windows target - - - MinGW32 (on debian you will need to use the `i686-w64-mingw32-gcc-posix` executable provided by the `gcc-mingw-w64-i686-posix-runtime` package) - -MSVC could also be used instead if you are on Windows. \ No newline at end of file +Note that to build for Windows targets with a mingw toolchain on debian, you will need to use the `i686-w64-mingw32-gcc-posix` executable provided by the `gcc-mingw-w64-i686-posix-runtime` package. \ No newline at end of file diff --git a/docs/gettingstarted.md b/docs/gettingstarted.md index 6fb4a38..4358eba 100644 --- a/docs/gettingstarted.md +++ b/docs/gettingstarted.md @@ -7,33 +7,23 @@ helloworld #= "Hello, World!" ## 2 - (Anonymous) Functions You can make an anonymous functions using the following syntax: ``` -getHelloWorld: ~String => { +getHelloWorld: ~String #= { return "Hello, World!" } ``` The type (`~String` here, which basically means "an anonymous function that returns a String") can be omitted, see [#3](#3---types). -Note that you can make `getHelloWorld` final by replacing `=>` with `#=>`. - -To define a **named** (and mandatorily final) function, replace `=>` with `>>`: +To define a **named** (and mandatorily final) function, replace `#=` with `>>`: ``` getHelloWorld: String >> { return "Hello, World!" } ``` -### 2.1 - Main -Main can be a variable named `main` that contains an anonymous function, a function named `main` or a function that has the `as_main` attribute: -``` -main #=> {} -main >> {} -as_main helloWorld >> {} -``` - -### 2.2 - Function parameters +### 2.1 - Function parameters Parameters can be added to an anonymous function by specifying the types as follows: ``` -addIntToString (int, String) => { a, b; +addIntToString: (int, String)~ #= { a, b; print_line(b + a) } ``` @@ -45,7 +35,7 @@ addIntToString(a: int, b: String) => { } ``` -### 2.3 - Function calls +### 2.2 - Function calls A function can be simply invoked like this if it has no parameters: ``` helloWorld() @@ -67,7 +57,7 @@ Types are *inferred*, which means that specifying types of variables or returned Note: While primitives types (`String`, `int`, `double`, `boolean`, `float`) will be transpiled to their equivalents for the target of the transpiler, this is not the case for other types. -Every variable has a static type by default; it is possible to make a **non-final** variable dynamic by adding `dyn`/`dynamic` to the attributes or making it the return type: +Every variable has a static type by default at compile time; it is possible to make a **non-final** variable dynamic by adding `dyn`/`dynamic` to the attributes or making it the return type: ``` dyn helloWorld = 0 # helloWorld: dyn = 0 would also work helloWorld = "Hello, World!" @@ -99,7 +89,5 @@ class Program { } } -main >> { - Program("World") -} +program #= Program("World") ``` \ No newline at end of file diff --git a/examples/HelloWorld.ybcon b/examples/HelloWorld.ybcon index 17413c2..086af0b 100644 --- a/examples/HelloWorld.ybcon +++ b/examples/HelloWorld.ybcon @@ -1,3 +1,5 @@ -main #= [] -> { +main >> { print_line("Hello, World!") } + +main() \ No newline at end of file diff --git a/resources/.npmrc b/resources/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/resources/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/resources/Yerbacon.rc b/resources/Yerbacon.rc index 9da11b5..5325fe8 100644 --- a/resources/Yerbacon.rc +++ b/resources/Yerbacon.rc @@ -14,7 +14,7 @@ BEGIN VALUE "FileDescription", "@EXEDESC@" VALUE "FileVersion", "@CMAKE_PROJECT_VERSION@.@SHORT_BUILD_TIMESTAMP@" VALUE "InternalName", "@EXENAME@" - VALUE "LegalCopyright", "@LEGALCOPYRIGHT@" + VALUE "LegalCopyright", "@LEGAL-COPYRIGHT@" VALUE "OriginalName", "@EXENAME@.exe" VALUE "ProductName", "@PROJECT_NAME@" VALUE "ProductVersion", "@CODENAME@ @CMAKE_PROJECT_VERSION@" diff --git a/resources/package.json b/resources/package.json new file mode 100644 index 0000000..c475403 --- /dev/null +++ b/resources/package.json @@ -0,0 +1,13 @@ +{ + "name": "@EXENAME@", + "bin": "@EXENAME@.js", + "license": "@CPACK_RPM_PACKAGE_LICENSE@", + "devDependencies": { "pkg": "^5.8.0" }, + "pkg": { + "assets": "../@EXENAME@.worker.js", + "targets": [ + "latest-macos-x64", + "latest-macos-arm64" + ] + } +} \ No newline at end of file diff --git a/scripts/TestYbcon b/scripts/TestYbcon deleted file mode 100644 index 42c763b..0000000 --- a/scripts/TestYbcon +++ /dev/null @@ -1 +0,0 @@ -cd ./cmake-build-release/ && clear && ./ybcon --printresult ../examples/HelloWorld.ybcon diff --git a/scripts/completions/bash-completion.sh b/scripts/completions/bash-completion.sh index 520c8c5..fa1546e 100644 --- a/scripts/completions/bash-completion.sh +++ b/scripts/completions/bash-completion.sh @@ -8,7 +8,7 @@ _ybconAutoComplete() { COMPREPLY=() current="${COMP_WORDS[COMP_CWORD]}" previous="${COMP_WORDS[COMP_CWORD-1]}" - options='-h -p --help --parallel --target= --newlines= --printresult --version --buildInfo' + options='-h -p -t --help --parallel --target= --newlines= --printresult --text --version --buildInfo' if [[ "${current}" == -* ]]; then YCompReply "$(compgen -W "$options" -- "$current")" return 0 diff --git a/scripts/completions/fish-completion.fish b/scripts/completions/fish-completion.fish index 9977067..b88b157 100644 --- a/scripts/completions/fish-completion.fish +++ b/scripts/completions/fish-completion.fish @@ -7,4 +7,5 @@ complete -c ybcon -x -l buildInfo -d "Print informations about how the ybcon exe complete -c ybcon -l parallel -k -d "Transpile files in parallel mode" complete -c ybcon -l target -k -f -a 'lua js py' -d "Set the transpilation target" complete -c ybcon -l newlines -k -f -a 'on off' -d "Enable or disable new lines" -complete -c ybcon -s p -l printresult -d "Enable printing the transpilation result to stdout" \ No newline at end of file +complete -c ybcon -s p -l printresult -d "Enable printing the transpilation result to stdout" +complete -c ybcon -x -s t -l text -d "Transpile text provided after this argument (implies -p)" \ No newline at end of file diff --git a/scripts/completions/zsh-completion.zsh b/scripts/completions/zsh-completion.zsh index ea58753..4d227d7 100644 --- a/scripts/completions/zsh-completion.zsh +++ b/scripts/completions/zsh-completion.zsh @@ -9,6 +9,7 @@ _ybcon() { --target='[Set the transpilation target]:language:(lua js py)' \ --newlines='[Enable or disable new lines]:state:(on off)' \ {-p,--printresult}'[Enable printing the transpilation result to stdout]' \ + {-t,--text}'[Transpile text provided after this argument (implies -p)]' \ "*:$completeyfile" return 0 } diff --git a/scripts/ybcon b/scripts/ybcon index f086ed0..86a6ee8 100755 --- a/scripts/ybcon +++ b/scripts/ybcon @@ -11,7 +11,7 @@ defaultBinLocation="$scriptDir/../libexec/ybcon" usage() { if [ "$1" = false ]; then echo "Invalid arguments, usage:"; fi - echo "$EXENAME [--version] [--buildInfo] [-h|--help] [--parallel] [--target=] [--newlines=on/off] [-p|--printresult] " + echo "$EXENAME [--version] [--buildInfo] [-h|--help] [--parallel] [--target=] [--newlines=on/off] [-p|--printresult] [-t|--text] " if [ "$1" = true ]; then echo " --version Print the version" echo " --buildInfo Print informations about how the ybcon executable was built" @@ -20,6 +20,7 @@ usage() { echo " --target= Set the transpilation target" echo " --newlines=on/off Enable or disable new lines" echo " -p or --printresult Enable printing the transpilation result to stdout" + echo " -t or --text Transpile text provided after this argument (implies -p)" printf "\n" fi } @@ -29,57 +30,51 @@ usageExit() { exit 0 }; helpExit() { usage true; exit 0; } -args=""; +arguments=""; run=false; -newArgs() { - if [ "$args" = "" ]; then - args="$args$1" - else - args="$args $1" - fi -} - if [ "$#" != 0 ]; then if [ "$#" = 1 ]; then case "$1" in -h | --help ) helpExit ;; - --version | --buildInfo ) - run=true; args="$1" ;; - *.ybcon ) - newArgs "$1"; run=true ;; + --version | --buildInfo | *.ybcon ) + run=true ;; * ) usageExit ;; esac else + text_provided=false for it in "$@" do case "$it" in -p | --printresult | --parallel | --target=* | --newlines=on | --newlines=off ) - if test "${args#*$it}" = "$args"; then - newArgs "$it" + if [ "${arguments#*$it}" != "$arguments" ] && [ "${arguments%*-$it}" = "${arguments#*-$it}" ]; then + usageExit + fi ;; + -t | --text ) + text_provided=true ;; + * ) + if [ $text_provided = true ] || [ "${it%*.ybcon}" != "$it" ]; then + run=true else usageExit fi ;; - *.ybcon ) - newArgs "$it" - run=true ;; - * ) - usageExit ;; esac + if [ "$arguments" != "" ]; then + arguments="$arguments " + fi + arguments="$arguments$it" done - if test "${args#*".ybcon"}" = "$args" ; then usageExit; fi + if test $run = false ; then usageExit; fi fi else usage false fi -runIt() { eval "$1" " $args"; } - if [ "$run" = true ]; then if [ -f "$defaultBinLocation" ]; then - runIt "$defaultBinLocation" + exec "$defaultBinLocation" "$@" else echo "Yerbacon executable not found at $defaultBinLocation" fi diff --git a/src/etc/filefuncs.cpp b/src/etc/filefuncs.cpp index b7f44f7..d379fb5 100644 --- a/src/etc/filefuncs.cpp +++ b/src/etc/filefuncs.cpp @@ -1,6 +1,7 @@ #include #include #include +#include "../headers/Yerbacon.hpp" using namespace std; string getFileContent(const string& file) diff --git a/src/etc/lexer.cpp b/src/etc/lexer.cpp index 09024da..b11187d 100644 --- a/src/etc/lexer.cpp +++ b/src/etc/lexer.cpp @@ -9,20 +9,26 @@ tok::type getIdentifierCharType(const char& Char) { else return UNEXPECTED; } -vector lex(const string& in) +vector lex(const string_view& in) { vector resVal; unsigned long lineNumber = 1; - for (unsigned int i = 0; i <= in.size(); ++i) { + for (unsigned int i = 0; i < in.size(); ++i) { const char& current = in[i]; switch (current) { + case LPAR: case LBRACE: case LBRACKET: { + const auto reversedCharacter = static_cast(tok::inverseLCharacter(current)); + if (find_corresponding(in.cbegin() + i + 1, in.cend(), current, reversedCharacter) != in.cend()) { + goto insertToken; + } else throw tok::LexerException(string("Missing \"") + reversedCharacter + "\" character", lineNumber); + } case DIVIDE: if (in[i + 1] == current) i += 2; else goto insertToken; case TAG: { if (current == TAG && in[i + 1] == DEFINE) { // See the IDENTIFIER case in Parser.hpp goto insertToken; } else { - while (not (in[i + 1] == EOF_ || in[i + 1] == '\n')) { + while (not (i == in.size() || in[i + 1] == '\n')) { ++i; } break; @@ -42,13 +48,12 @@ vector lex(const string& in) break; } else goto insertToken; } - [[unlikely]] case EOF_: --lineNumber; - case DEFINE: case LPAR: case RPAR: - case LBRACE: case RBRACE: case LBRACKET: case RBRACKET: + case DEFINE: case RPAR: case COMMA: + case RBRACE: case RBRACKET: case SEMICOLON: case PLUS: case HYPHEN: case LCOMP: case RCOMP: case DOT: case DOLLAR_SIGN: case SQUOTE: insertToken: resVal.emplace_back(static_cast(current), lineNumber); - [[likely]] case ' ': case '\t': case '\r': case ';': break; + [[likely]] case ' ': case '\t': case '\r': break; [[likely]] case '\n': ++lineNumber; break; default: { const tok::type type = getIdentifierCharType(current); diff --git a/src/headers/Yerbacon.hpp b/src/headers/Yerbacon.hpp index adb5abd..801deed 100644 --- a/src/headers/Yerbacon.hpp +++ b/src/headers/Yerbacon.hpp @@ -22,9 +22,6 @@ #if not defined(__cpp_lib_concepts) || not defined(__cpp_lib_integer_comparison_functions) #error "The current standard library is incomplete" #endif -#ifndef __cpp_using_enum -#error "The "using enum" syntax is not supported by this compiler" -#endif #define token_expansion(X) #X #define make_string(X) token_expansion(X) @@ -44,14 +41,14 @@ namespace Yerbacon { " Compiler: " YBCON_COMPILER "\n" " C++ standard: " make_string(__cplusplus); } - static void exit(const std::initializer_list reason) { + static void fail(const std::initializer_list reason) { std::for_each(reason.begin(), reason.end(), [](const char* current_string){ std::cout << current_string; }); std::cout << std::endl; std::exit(EXIT_FAILURE); } - inline void exit(const char* reason) { exit({reason}); } + inline void fail(const char* reason) { fail({reason}); } class Exception: public std::exception { std::string exceptionCause; public: @@ -65,10 +62,6 @@ namespace Yerbacon { }; } -#include -template -constexpr T& pointerAs(const std::unique_ptr& ptr) { return reinterpret_cast(*ptr); } - #undef YBCON_VERSION #undef YBCON_FLAGS #undef YBCON_COMPILER diff --git a/src/headers/lex.hpp b/src/headers/lex.hpp index 6f97672..5aab51c 100644 --- a/src/headers/lex.hpp +++ b/src/headers/lex.hpp @@ -5,34 +5,44 @@ #include "Yerbacon.hpp" #include #include +#include struct tok { typedef Yerbacon::Exception LexerException; enum type: const unsigned short { UNEXPECTED = std::numeric_limits::max() + 1, IDENTIFIER, NUMBER, ALPHACHAR, - EOF_ = '\0', DEFINE = '=', TAG = '#', DOLLAR_SIGN = '$', DOT = '.', + DEFINE = '=', TAG = '#', DOLLAR_SIGN = '$', DOT = '.', COMMA = ',', SEMICOLON = ';', LPAR = '(', LBRACE = '{', LBRACKET = '[', RPAR = ')', RBRACE = '}', RBRACKET = ']', PLUS = '+', HYPHEN = '-', DIVIDE = '/', LCOMP = '>', RCOMP = '<', SQUOTE = '\'', ASTERISK = '*', STRING = '"', }; - const type toktype; - const std::string toktext; - const unsigned long line; - tok& operator=(const tok& other) { - if (this != &other) { - const_cast(toktype) = other.toktype; - const_cast(toktext) = other.toktext; - const_cast(line) = other.line; - } - return *this; - } - tok(): toktype(UNEXPECTED), toktext(), line(0) {}; - tok(const type& Type, std::string_view Text, const decltype(line)& line = 0): toktype(Type), toktext(Text), line(line) {} - explicit tok(const type& Type, const decltype(line)& line = 0): tok(Type, std::string(1, Type), line) {}; + static auto inverseLCharacter(const unsigned char& character) { + return static_cast(((character + 2) - (character == tok::LPAR))); + }; + type toktype; + std::string toktext; + unsigned long line; + tok() = delete; + tok(const type& Type, std::string_view Text, const decltype(line)& line = 1): toktype(Type), toktext(Text), line(line) {} + explicit tok(const type& Type, const decltype(line)& line = 1): tok(Type, std::string(1, Type), line) {}; + operator char() const { return static_cast(toktype); } friend std::ostream& operator<<(std::ostream& output, const tok& it) { return output << it.toktext; } }; -std::vector lex(const std::string& in); + +auto find_corresponding(std::input_iterator auto begin, std::input_iterator auto end, const unsigned char open, const unsigned char close) { + unsigned short occurrences = 1; + return std::find_if(begin, end, [&open, &close, &occurrences](const char& it){ + if (it == open) { + ++occurrences; + } else if (it == close) { + return --occurrences == 0; + } + return false; + }); +} + +std::vector lex(const std::string_view& in); #endif //YERBACON_TEST_H \ No newline at end of file diff --git a/src/headers/misc.hpp b/src/headers/misc.hpp index 0962dee..ac29321 100644 --- a/src/headers/misc.hpp +++ b/src/headers/misc.hpp @@ -6,6 +6,6 @@ void outputFileContent(const string& file, string_view content); #include "lex.hpp" #include "parsing/Parser.hpp" -inline auto parseString(const string& toParse) { return Parser::parseVector(lex(toParse)); } +inline auto parseString(const string_view& toParse) { return Parser::parse(lex(toParse)); } #endif //YERBACON_MISC_HPP \ No newline at end of file diff --git a/src/headers/parsing/ParseComponents.hpp b/src/headers/parsing/ParseComponents.hpp index 225839e..3919b82 100644 --- a/src/headers/parsing/ParseComponents.hpp +++ b/src/headers/parsing/ParseComponents.hpp @@ -9,6 +9,7 @@ #include #include #include +#include "ReservedIdentifiers.hpp" using namespace std; #include "../lex.hpp" @@ -19,66 +20,63 @@ struct ParseComponent { [[nodiscard]] inline const type_info& getId() const { return typeid(*this); } virtual ~ParseComponent() = default; }; - -namespace StandardComponents { - struct NamedIdentifier: ParseComponent { - const string name; - explicit NamedIdentifier(string_view nameText): name(nameText) {} - }; - struct Define: NamedIdentifier { - const bool final; - explicit Define(const bool& isFinal, string_view nameText): NamedIdentifier(nameText), final(isFinal) {} - explicit Define(string_view nameText): Define(false, nameText) {} - }; - struct Reference: NamedIdentifier { - using NamedIdentifier::NamedIdentifier; - }; - - struct Class: NamedIdentifier { - using NamedIdentifier::NamedIdentifier; - }; - namespace types { - struct String: ParseComponent { - const string content; - explicit String(const char* string): content(string) {} - }; +template +struct NamedIdentifier: virtual ParseComponent { + struct identifier_reserved_exception: exception {}; + const string name; + explicit NamedIdentifier(const string_view nameText): name(nameText) { + if (disallow_reserved and reserved(name)) { + throw identifier_reserved_exception(); + } } -} + NamedIdentifier() = delete; +}; + +typedef unique_ptr component_ptr; #define IS_PARSECOMPONENT IS(ParseComponent) -class ParseTree { - mutable vector> subComponents; +#define IS_IDENTIFIER enable_if_t, T> or is_base_of_v, T>, T> +class ParseTree: public virtual ParseComponent { + mutable vector subComponents; using array_type = decltype(subComponents); using iterator = array_type::iterator; using constant_iterator = array_type::const_iterator; protected: IS_PARSECOMPONENT - void addComponent(const T& component) const - { subComponents.emplace_back(new T(component)); }; + void addComponent(const T& component) const { + if constexpr(is_copy_constructible_v) { + subComponents.emplace_back(new T(component)); + } else { + static_assert(is_move_constructible_v, "T is not copy-constructible or move-constructible"); + subComponents.emplace_back(new T(move(const_cast(component)))); + } + }; IS_PARSECOMPONENT void addAllComponents( const initializer_list& components ) const { - subComponents.reserve(components.size()); + subComponents.reserve(subComponents.size() + components.size()); for (const T& current: components) addComponent(current); } public: - inline iterator begin() const noexcept { return subComponents.begin(); } + inline size_t size() const { return subComponents.size(); } + inline bool empty() const { return size() == 0; } + inline iterator begin() noexcept { return subComponents.begin(); } inline constant_iterator cbegin() const noexcept { return subComponents.cbegin(); } - inline iterator end() const noexcept { return subComponents.end(); } + inline iterator end() noexcept { return subComponents.end(); } inline constant_iterator cend() const noexcept { return subComponents.cend(); } IS_PARSECOMPONENT vector findById() const { vector filteredComponents; - for_each(cbegin(), cend(), [&filteredComponents](const unique_ptr& it) { + for_each(cbegin(), cend(), [&filteredComponents](const component_ptr& it) { if (it->getId() == typeid(T)) { - filteredComponents.push_back(reinterpret_cast(it.get())); + filteredComponents.push_back(dynamic_cast(to_address(it))); } }); return filteredComponents; } - IS(StandardComponents::NamedIdentifier) - optional> findReferenceByName(const string& name) const { + template + optional> findReferenceByName(const string& name) const { const vector identifiers = findById(); for (T* identifier: identifiers) { if (identifier->getId() == typeid(T) && identifier->name == name) { @@ -87,17 +85,61 @@ public: } return optional>(); }; - inline decltype(*subComponents.data()) operator[](const unsigned int& index) const { return subComponents[index]; } - IS(StandardComponents::NamedIdentifier) - inline auto operator[](const string& key) const { return findReferenceByName(key); } - inline size_t getCompCount() const { return subComponents.size(); } + inline component_ptr& operator[](const unsigned int& index) const { return subComponents[index]; } + inline component_ptr& at(const unsigned int& index) const { return subComponents.at(index); } + template + inline auto operator[](const string& key) const { return findReferenceByName(key); } IS_PARSECOMPONENT inline void add(const T& component) { addComponent(component); } IS_PARSECOMPONENT inline void addAll(const initializer_list& components) { addAllComponents(components); } IS_PARSECOMPONENT inline ParseTree& operator<<(const T& component) { add(component); return *this; } + ParseTree& operator<<(const string&); ParseTree(): subComponents() {}; - IS_PARSECOMPONENT constexpr explicit ParseTree(const T& element): ParseTree() { addComponent(element); } - IS_PARSECOMPONENT constexpr ParseTree(const initializer_list& elements): ParseTree() { addAllComponents(elements); } + IS_PARSECOMPONENT inline explicit ParseTree(const T& element): ParseTree() { addComponent(element); } + IS_PARSECOMPONENT inline ParseTree(const initializer_list& elements): ParseTree() { addAllComponents(elements); } + ParseTree& operator=(ParseTree&& parseTree) noexcept { subComponents = move(parseTree.subComponents); return *this; } + ParseTree(ParseTree&& parseTree) noexcept: subComponents(move(parseTree.subComponents)) {} + ParseTree(const ParseTree& parseTree) = delete; }; #undef IS_PARSECOMPONENT +namespace StandardComponents { + struct Define: NamedIdentifier { + const bool final; + ParseTree content; + explicit Define(const bool& isFinal, string_view nameText, ParseTree&& content): NamedIdentifier(nameText), final(isFinal), content(move(content)) {} + explicit Define(string_view nameText, ParseTree&& content): Define(false, nameText, move(content)) {} + Define(Define&& define) noexcept: Define(define.final, define.name, move(define.content)) {} + }; + struct Reference: NamedIdentifier { + using NamedIdentifier::NamedIdentifier; + }; + namespace types { + struct Integer: ParseComponent { + typedef uint_fast8_t precision_type; + const long double value; + const precision_type precision; + Integer(const long double& value, const precision_type& precision): value(value), precision(precision) {} + }; + struct String: ParseComponent { + const string content; + explicit String(string content_string): content(move(content_string)) {} + }; + } + struct Call: ParseTree, Reference { + using Reference::Reference; + }; + + struct Function: NamedIdentifier, ParseTree { + ParseTree parameters; + using NamedIdentifier::NamedIdentifier; + }; + struct Class: NamedIdentifier, ParseTree { + using NamedIdentifier::NamedIdentifier; + }; +} + +ParseTree& ParseTree::operator<<(const string& text) { + return *this << StandardComponents::types::String(text); +} + #endif //YERBACON_PARSECOMPONENTS_HPP \ No newline at end of file diff --git a/src/headers/parsing/Parser.hpp b/src/headers/parsing/Parser.hpp index 99875f2..3020ad5 100644 --- a/src/headers/parsing/Parser.hpp +++ b/src/headers/parsing/Parser.hpp @@ -5,6 +5,10 @@ #include "ParseComponents.hpp" #include "../Yerbacon.hpp" #include +#include +#include +#include +#include "ReservedIdentifiers.hpp" namespace Parser { typedef Yerbacon::Exception ParsingException; @@ -15,15 +19,38 @@ namespace Parser { const tok& token, const string& text, const bool& quoteTokenText = false ) { error(token, text, token.line, quoteTokenText); } - ParseTree parseVector(const vector& lexed) { - ParseTree parseTree; + + void filter_comma_list(vector& tokens) { + if (tokens.size() >= 2) { + for (auto iterator = tokens.begin(); iterator->toktype != tok::COMMA && iterator < tokens.end() - 1; ++iterator) { + const auto nextIterator = iterator + 1; + if (nextIterator->toktype == tok::COMMA) { + tokens.erase(nextIterator); + } else if (nextIterator->toktype != tok::DOT && iterator->toktype != tok::DOT) { + throw ParsingException("Missing comma after \"" + iterator->toktext + '"'); + } + } + } + } + vector filter_comma_list(input_iterator auto begin, input_iterator auto end) { + vector tokens(begin, end); + filter_comma_list(tokens); + return tokens; + } + + IS(ParseTree) + inline T parse(const input_iterator auto&, const input_iterator auto&); + + IS(ParseTree) + T parse(const span&& lexed) { + T parseTree; using namespace StandardComponents; using enum tok::type; unsigned int i = 0; - const auto nextAre = [&i, &lexed] T>(const initializer_list& nextValues) -> bool { + const auto nextAre = [&i, &lexed] Y>(const initializer_list& nextValues) -> bool { unsigned int j = 1; - for (const T& nextValue: nextValues) { - if (!cmp_greater(lexed.size() - i, nextValues.size()) || lexed[i + j].toktype != nextValue) { + for (const Y& nextValue: nextValues) { + if (cmp_less(lexed.size() - i, nextValues.size()) || lexed[i + j].toktype != nextValue) { return false; } ++j; @@ -31,39 +58,118 @@ namespace Parser { return true; }; for (;i < lexed.size(); ++i) { - const tok& current = lexed[i], next = (current.toktype != EOF_) ? lexed[i + 1] : current; + const bool hasNext = (i + 1) < lexed.size(); + const tok& current = lexed[i], next = hasNext ? lexed[i + 1] : tok(UNEXPECTED, current.line); - switch (current.toktype) { - case STRING: parseTree << types::String(current.toktext.data()); break; - case IDENTIFIER: { - if (current.toktext == "class" || current.toktext == "structure") { - if (next.toktype == IDENTIFIER) { - parseTree << Class(next.toktext); ++i; - } else { - const bool isNotBlank = (not (next.toktext.empty() or next.toktype == tok::EOF_)); - parsingError(next, isNotBlank ? " is not a valid class identifier" : "A class identifier is required", isNotBlank); + try { + switch (current.toktype) { + case NUMBER: { + long double v = stoul(current.toktext); + if (i != 0 && lexed[i - 1].toktype == HYPHEN) v = -v; + types::Integer::precision_type p = 0; + if (nextAre({DOT, NUMBER})) { + i += 2; + const string& right = lexed[i].toktext; + p = min(static_cast(right.size()), numeric_limits::digits10); + v += copysign(stold(right.substr(0, p)) / powl(deca::num, p), v); } - } else { - bool isFinalDefine = nextAre({TAG, DEFINE}); - if (isFinalDefine || next.toktype == DEFINE) { - const optional previousDefinition = parseTree.findReferenceByName(current.toktext); - if (previousDefinition.has_value()) { - if (previousDefinition.value().get().final || isFinalDefine) { - parsingError(current, previousDefinition->get().final ? " cannot be redefined as it is final" : " cannot be made final after it has been declared", true); - } + parseTree << types::Integer(v, p); + break; + } + case STRING: parseTree << current.toktext; break; + case IDENTIFIER: { + using enum ReservedIdentifier; + if (current.toktext == ID(CLASS) || current.toktext == ID(STRUCT)) { + if (next.toktype == IDENTIFIER) { + parseTree << Class(next.toktext); ++i; + } else { + parsingError(next, hasNext ? " is not a valid class identifier" : "A class identifier is required", hasNext); } - parseTree << Define(isFinalDefine, current.toktext); - i += 1 + isFinalDefine; } else { - parseTree << Reference(current.toktext); + unsigned int parametersDistance = 0; + if (next.toktype == LPAR) { + const auto closing = find_if(lexed.begin() + i, lexed.end(), [](const tok& token){ return token.toktype == RPAR; }); + parametersDistance = distance(lexed.begin() + i, closing); + i += parametersDistance; + } + if (nextAre({LCOMP, LCOMP, LBRACE})) { + Function function(current.toktext); + if (parametersDistance > 2) + function.parameters = parse(filter_comma_list(lexed.begin() + ((i + 2) - parametersDistance), lexed.begin() + i)); + parseTree << function; + i += 2; + break; + } else i -= parametersDistance; + + bool isFinalDefine = nextAre({TAG, DEFINE}); + if (isFinalDefine || next.toktype == DEFINE) { + const optional previousDefinition = parseTree.template findReferenceByName(current.toktext); + if (previousDefinition.has_value() && (previousDefinition.value().get().final || isFinalDefine)) + parsingError(current, previousDefinition->get().final ? " cannot be redefined as it is final" : " cannot be made final after it has been declared", true); + const unsigned increment = 2 + isFinalDefine; + const auto beginning = lexed.begin() + i + increment; + const auto end = find_if(beginning, lexed.end(), [¤t](const tok& it){ + return it.toktype == SEMICOLON || it.line != current.line; + }); + parseTree << Define(isFinalDefine, current.toktext, parse(beginning, end)); + i += 1 + isFinalDefine + distance(beginning, end); + } else { + const bool method = nextAre({DOT, IDENTIFIER, LPAR}); + const bool property = not method && nextAre({DOT, IDENTIFIER}); + const string name = property or method ? current.toktext + '.' + lexed[i + 2].toktext : current.toktext; + if (method or next.toktype == LPAR) { + parseTree << Call(name); + } else if (property) { + parseTree << Reference(name); + } + if (property or method) i += 2; + } } } + case SEMICOLON: break; + case LPAR: case LBRACE: case LBRACKET: { + const auto closingCharacter = find_corresponding(lexed.begin() + i + 1, lexed.end(), current.toktype, tok::inverseLCharacter(current.toktype)); + vector subTokens(lexed.begin() + i + 1, closingCharacter); + if (current.toktype == LPAR || current.toktype == LBRACKET) filter_comma_list(subTokens); + if (not parseTree.empty()) { + try { + auto& previous = dynamic_cast(*parseTree.at(parseTree.size() - 1)); + if (find_if(reverse_iterator(lexed.begin() + i), lexed.rend(), [](const tok& token){ + return token.toktype != SEMICOLON; + })->toktype != *closingCharacter) { + previous = parse(subTokens); + i = distance(lexed.begin(), closingCharacter); + break; + } + } catch (const out_of_range&) {} catch (const bad_cast&) {} + } + } + default: parsingError(current, " \u27F5 Unexpected character", true); + } + } catch (const NamedIdentifier::identifier_reserved_exception&) { + parsingError(current, " is a reserved identifier", true); + } + if (not parseTree.empty()) { + const auto& last = parseTree.cend() - 1; + const type_info& lastId = last->get()->getId(); + const auto* last_identifier = dynamic_cast*>(last->get()); + if (last_identifier != nullptr) { + if (lastId != typeid(Define) and + any_of(parseTree.cbegin(), last, [&last_identifier](const component_ptr& pointer){ + try { + return dynamic_cast&>(*pointer).name == last_identifier->name; + } catch (const bad_cast&) { + return false; + } + })) + { parsingError(current, " has already been defined previously", true); } } - default: break; } } return parseTree; } + template T> + inline T parse(const input_iterator auto& begin, const input_iterator auto& end) { return parse(span(begin, end)); } } #endif //YERBACON_PARSER_HPP \ No newline at end of file diff --git a/src/headers/parsing/ReservedIdentifiers.hpp b/src/headers/parsing/ReservedIdentifiers.hpp new file mode 100644 index 0000000..3bc29d1 --- /dev/null +++ b/src/headers/parsing/ReservedIdentifiers.hpp @@ -0,0 +1,25 @@ +#ifndef YERBACON_RESERVEDIDENTIFIERS_HPP +#define YERBACON_RESERVEDIDENTIFIERS_HPP + +#include +#include +#include "../Yerbacon.hpp" +#include + +static constinit const array identifiers { + "class", "structure", "print", "print_line" +}; + +enum class ReservedIdentifier: size_t { + CLASS, STRUCT, PRINT_FUNCTION, PRINT_LINE_FUNCTION +}; + +inline const char* ID(const ReservedIdentifier& identifier) { + return identifiers.at(reinterpret_cast(identifier)); +} + +bool reserved(const string_view words) { + return find(identifiers.cbegin(), identifiers.cend(), words) != identifiers.cend(); +} + +#endif //YERBACON_RESERVEDIDENTIFIERS_HPP diff --git a/src/headers/transpiler/Target.hpp b/src/headers/transpiler/Target.hpp index cb26d57..96dcd6c 100644 --- a/src/headers/transpiler/Target.hpp +++ b/src/headers/transpiler/Target.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef __GNUC__ #include #endif @@ -19,7 +20,7 @@ class Target { constexpr static const char* const interpolationString = "${"; constexpr static const char* const interpolationCloseString = "}"; protected: - virtual constexpr bool supportsOneLine() { return true; }; + inline bool supportsOneLine() { return uniqueLineSeparator().has_value(); }; std::stringstream output; inline void stringInterpolation(const char* multiline, const string& view) { stringInterpolation(view, multiline, multiline); } void stringInterpolation(string view, const char* openMultiline = "", const char* closeMultiline = "", const char* concatenationCharacters = "+") { @@ -65,84 +66,112 @@ protected: } else output << openCharacters << view << closeCharacters; } typedef function task; - #define make_task_base(type, captures, function_body) make_pair(type_index(typeid(type)), [captures](const ParseTree& parsedTree, unsigned int& index) { const type& parseComponent = pointerAs(parsedTree[index]); function_body }) - #define make_task(T, F) make_task_base(T, this, F) + #define make_task_base(start, type, captures, function_body) make_pair(type_index(typeid(type)), [captures](const ParseTree& parsedTree, unsigned int& index) { start; function_body }) + #define make_task_base_R(T, C, F) make_task_base(const T& parseComponent = reinterpret_cast(*parsedTree[index]), T, C, F) + #define make_task(T, F) make_task_base_R(T, this, F) + #define make_task_noR(T, F) make_task_base(,T, this, F) + #define make_nonlocal_task(T, F) make_task_base_R(T, , F) typedef unordered_map unordered_task_map; + typedef pair print_functions_pair; virtual unordered_task_map getTaskMap() = 0; -public: - const unordered_task_map& getTaskMapInstance() { - static unordered_task_map staticMap = getTaskMap(); - // Default / Shared tasks: - staticMap.merge(unordered_task_map({ - make_task(StandardComponents::Reference, - output << parseComponent.name; - ) - })); - return staticMap; - }; - static shared_ptr forName(string_view name, bool newLines); - const bool newLines; - string transpileWithTree(const ParseTree& tree) { - const unordered_task_map& taskMap = getTaskMapInstance(); - if (not newLines && !supportsOneLine()) { - throw Yerbacon::Exception("--newlines=off is not supported by the current target"); - } - output.str(string()); - for (unsigned int i = 0; i < tree.getCompCount(); ++i) { - const unique_ptr& component = tree[i]; - const type_info& id = component->getId(); - try { - const task& currentTask = taskMap.at(id); - currentTask(tree, i); - } catch (const out_of_range&) { - throw Yerbacon::Exception(string( - #ifndef __GNUC__ - id.name() - #else - abi::__cxa_demangle(id.name(), nullptr, nullptr, nullptr) - #endif - ) += " is not supported by the current target"); + virtual print_functions_pair printFunctions() = 0; + IS(ParseTree) + void transpileTree(const T& parseTree, const unsigned short& indentationLevel = 0, const function&& postInsertionFunction = [](auto&){}) { + if (not parseTree.empty()) { + const auto added_size = indentationLevel * strlen(indentation); + if (newLines) { + separator.reserve(separator.size() + added_size); + for (unsigned short level = 0; level < indentationLevel; ++level) { + separator.append(indentation); + } } + unsigned int subIndex = 0; + for (auto pointer_iterator = parseTree.cbegin(); pointer_iterator < parseTree.cend(); ++pointer_iterator) { + const type_info& id = pointer_iterator->get()->getId(); + try { + getTaskMapInstance().at(id)(parseTree, subIndex); ++subIndex; + } catch (const out_of_range&) { + throw Yerbacon::Exception(string( + #ifndef __GNUC__ + id.name() + #else + abi::__cxa_demangle(id.name(), nullptr, nullptr, nullptr) + #endif + ) += " is not supported by the current target"); + } + postInsertionFunction(pointer_iterator); + } + if (newLines) separator.erase(separator.size() - added_size); } + } + IS(ParseTree) + void separate_transpileTree(const T& parseTree, const unsigned short& indentationLevel = 0) { + transpileTree(parseTree, indentationLevel, [this, &parseTree](const auto& iterator){ + if ((newLines || iterator + 1 == parseTree.cend()) && use_uniqueLineSeparator()) output << uniqueLineSeparator().value(); + if (iterator + 1 != parseTree.cend()) output << separator; + }); + } + IS(ParseTree) + void separate_transpileTree(const T& parseTree, const string_view separation_characters) { + transpileTree(parseTree, 0, [this, &parseTree, &separation_characters](const auto& iterator){ + if (iterator + 1 != parseTree.cend()) { output << separation_characters; } + }); + } + typedef optional optional_string; + virtual optional_string uniqueLineSeparator() { return ";"; }; + virtual bool use_uniqueLineSeparator() { return false; }; + const bool newLines; + string separator; + static constexpr const char* indentation = " "; +public: + unordered_task_map getTaskMapInstance() { + unordered_task_map fullMap = getTaskMap(); + // Default / Shared tasks: + fullMap.merge(unordered_task_map({ + make_task(ParseTree, transpileTree(parseComponent);), + make_task(StandardComponents::Reference, output << parseComponent.name;), + make_task(StandardComponents::Call, + const auto print_functions = printFunctions(); + using enum ReservedIdentifier; + output << ((parseComponent.name == ID(PRINT_FUNCTION)) ? print_functions.first : (parseComponent.name == ID(PRINT_LINE_FUNCTION)) ? print_functions.second : parseComponent.name) << '('; + separate_transpileTree(parseComponent, ", "); + output << ')'; + ), + make_task(StandardComponents::types::Integer, output << setprecision(parseComponent.precision) << fixed << parseComponent.value;) + })); + return fullMap; + }; + static unique_ptr forName(string_view name, bool newLines); + string transpileWithTree(const ParseTree& tree) { + separator = newLines ? "\n" : (supportsOneLine() ? uniqueLineSeparator().value() : throw Yerbacon::Exception("--newlines=off is not supported by the current target")); + output.str(string()); + separate_transpileTree(tree); return output.str() + '\n'; }; - explicit Target(const bool newLines): newLines(newLines) {}; + explicit Target(const bool& newLines): output(), newLines(newLines), separator() {}; + Target() = delete; virtual ~Target() = default; }; #include "implementations/Lua.hpp" #include "implementations/Js.hpp" #include "implementations/Py.hpp" +#include "implementations/GodotScript.hpp" -enum LANGUAGE: signed short { NONE = -1, LUA, JS, PY }; -constinit const array languages { ".lua", ".js", ".py" }; - -shared_ptr Target::forName(string_view name, const bool newLines = true) { - LANGUAGE selected = NONE; - for (unsigned int i = 0; i < languages.size(); ++i) { - if (name == languages[i]) { - selected = static_cast(i); - break; - } - } - shared_ptr target; - #define ADDTARGET(X) target = shared_ptr(new X(newLines)) - switch (selected) { - #ifdef LUA_HPP - case LUA: ADDTARGET(LUA_HPP); break; - #endif - #ifdef JS_HPP - case JS: ADDTARGET(JS_HPP); break; - #endif - #ifdef PY_HPP - case PY: ADDTARGET(PY_HPP); break; - #endif - case NONE: - default: Yerbacon::exit({"\"", string(1, (char) toupper(name.at(1))).data(), name.substr(2).data(), "\" is not a valid target."}); - } +unique_ptr Target::forName(string_view name, const bool newLines = true) { + #define ADDTARGET(X, target_class) if (name == X) return unique_ptr(new target_class(newLines)) + ADDTARGET("lua", LuaTarget); + ADDTARGET("js", JsTarget); + ADDTARGET("py", PyTarget); + ADDTARGET("gd", GsTarget); #undef ADDTARGET + #undef make_nonlocal_task + #undef make_task_noR #undef make_task - return target; + #undef make_task_base_R + #undef make_task_base + Yerbacon::fail({"\"", string(1, (char) toupper(name.at(1))).data(), name.substr(2).data(), "\" is not a valid target."}); + return nullptr; } #endif //YERBACON_TARGET_HPP \ No newline at end of file diff --git a/src/headers/transpiler/implementations/GodotScript.hpp b/src/headers/transpiler/implementations/GodotScript.hpp new file mode 100644 index 0000000..d658f71 --- /dev/null +++ b/src/headers/transpiler/implementations/GodotScript.hpp @@ -0,0 +1,25 @@ +#ifndef GODOTSCRIPT_HPP +#define GODOTSCRIPT_HPP + +struct GsTarget: Target { + print_functions_pair printFunctions() final { return make_pair("printraw", "print"); } + unordered_task_map getTaskMap() final { + return { + make_task(Define, + output << (parseComponent.final ? "const " : "var ") << parseComponent.name << " = "; + transpileTree(parseComponent.content); + ), + make_task(types::String, stringInterpolation(R"(""")", parseComponent.content);), + make_task(Function, + output << "func " << parseComponent.name << '('; + separate_transpileTree(parseComponent.parameters, ", "); + output << "):" << separator << indentation; + if (parseComponent.empty()) output << "pass"; + separate_transpileTree(parseComponent, 1); + ) + }; + } + using Target::Target; +}; + +#endif \ No newline at end of file diff --git a/src/headers/transpiler/implementations/Js.hpp b/src/headers/transpiler/implementations/Js.hpp index 59181f8..ce8f5c7 100644 --- a/src/headers/transpiler/implementations/Js.hpp +++ b/src/headers/transpiler/implementations/Js.hpp @@ -1,15 +1,26 @@ #ifndef JS_HPP -#define JS_HPP JsTarget +#define JS_HPP using namespace StandardComponents; struct JsTarget: Target { + print_functions_pair printFunctions() final { return make_pair("util.write", "console.log"); } + bool use_uniqueLineSeparator() final { return true; } unordered_task_map getTaskMap() final { return { make_task(Define, output << (parseComponent.final ? "const " : "let ") << parseComponent.name << " = "; - output << "ah"; + transpileTree(parseComponent.content); ), - make_task(types::String, stringInterpolation(parseComponent.content);) + make_task(types::String, stringInterpolation(parseComponent.content);), + make_task(Function, + output << "function " << parseComponent.name << '('; + separate_transpileTree(parseComponent.parameters, ", "); + output << ") {"; + if (newLines and not parseComponent.empty()) output << separator << indentation; + separate_transpileTree(parseComponent, 1); + if (newLines and not parseComponent.empty()) output << separator; + output << '}'; + ) }; } using Target::Target; diff --git a/src/headers/transpiler/implementations/Lua.hpp b/src/headers/transpiler/implementations/Lua.hpp index 556c95b..84939ad 100644 --- a/src/headers/transpiler/implementations/Lua.hpp +++ b/src/headers/transpiler/implementations/Lua.hpp @@ -1,8 +1,10 @@ #ifndef LUA_HPP -#define LUA_HPP LuaTarget +#define LUA_HPP using namespace StandardComponents; struct LuaTarget: Target { + print_functions_pair printFunctions() final { return make_pair("io.write", "print"); } + optional_string uniqueLineSeparator() final { return " "; } unordered_task_map getTaskMap() final { return { make_task(Define, @@ -10,8 +12,21 @@ struct LuaTarget: Target { output << parseComponent.name; if (parseComponent.final) output << " "; // TODO Find an alternative to for lua <5.4 output << " = "; + transpileTree(parseComponent.content); ), - make_task(types::String, stringInterpolation(parseComponent.content, "[[", "]]", "..");) + make_task(types::String, stringInterpolation(parseComponent.content, "[[", "]]", "..");), + make_task(Function, + output << "function " << parseComponent.name << '('; + separate_transpileTree(parseComponent.parameters, ", "); + output << ')'; + if (not parseComponent.empty()) { + output << separator; + if (newLines) output << indentation; + } else output << ' '; + separate_transpileTree(parseComponent, 1); + if (not parseComponent.empty()) output << separator; + output << "end"; + ) }; } using Target::Target; diff --git a/src/headers/transpiler/implementations/Py.hpp b/src/headers/transpiler/implementations/Py.hpp index dc2bf5d..16081a7 100644 --- a/src/headers/transpiler/implementations/Py.hpp +++ b/src/headers/transpiler/implementations/Py.hpp @@ -1,13 +1,21 @@ #ifndef PY_HPP -#define PY_HPP PyTarget +#define PY_HPP using namespace StandardComponents; struct PyTarget: Target { - bool supportsOneLine() final { return false; } + print_functions_pair printFunctions() final { return make_pair("sys.stdout.write", "print"); } + optional_string uniqueLineSeparator() final { return {}; } unordered_task_map getTaskMap() final { return { - make_task(Define, output << parseComponent.name << " = ";), - make_task(types::String, stringInterpolation(R"(""")", parseComponent.content);) + make_task(Define, output << parseComponent.name << " = "; transpileTree(parseComponent.content);), + make_task(types::String, stringInterpolation(R"(""")", parseComponent.content);), + make_task(Function, + output << "def " << parseComponent.name << '('; + separate_transpileTree(parseComponent.parameters, ", "); + output << "):" << separator << indentation; + if (parseComponent.empty()) output << "pass"; + separate_transpileTree(parseComponent, 1); + ), }; } using Target::Target; diff --git a/src/main.cpp b/src/main.cpp index 0b249ba..a96e7e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include "headers/Yerbacon.hpp" #include #include @@ -11,79 +11,71 @@ using namespace std; int main(int argc, char* argv[]) { setlocale(LC_ALL, ""); - string target = ".lua"; + string target = "lua"; bool printResult = false, parallel = false, - newLines = true; - set files; + newLines = true, + text_provided = false; + using unit_result = pair>; + using unit = future; + map Units; for (signed int i = 1; i < argc; ++i) { - const string_view currentArg (argv[i]); - if (currentArg == ArgumentShort("printresult")) printResult = true; - else if (currentArg == ArgumentAssignable("target")) { - const string_view value = ArgumentAssignable::getValueFor(currentArg); - if (not value.empty()) (target = '.') += value; - else Yerbacon::exit("No target was provided."); + const string_view currentArgument (argv[i]); + if (currentArgument == ArgumentShort("printresult")) printResult = true; + else if (currentArgument == ArgumentAssignable("target")) { + const string_view value = ArgumentAssignable::getValueFor(currentArgument); + if (not value.empty()) target = value; + else Yerbacon::fail("No target was provided."); } - else if (currentArg == Argument("parallel")) parallel = true; - else if (currentArg == ArgumentAssignable("newlines")) { - const string_view enabled = ArgumentAssignable::getValueFor(currentArg); + else if (currentArgument == Argument("parallel")) parallel = true; + else if (currentArgument == ArgumentAssignable("newlines")) { + const string_view enabled = ArgumentAssignable::getValueFor(currentArgument); if (enabled == "off") { newLines = false; } else if (enabled == "on") { newLines = true; } else goto invalid_argument; } - else if (currentArg.ends_with(".ybcon")) files.insert(currentArg); - else { - if (argc == 2) { - if (currentArg == Argument("version")) { - cout << Yerbacon::getVersion(); - } else if (currentArg == Argument("buildInfo")) { - cout << Yerbacon::getBuildInfo(); - } else goto invalid_argument; - cout << '\n'; exit(EXIT_SUCCESS); - } else invalid_argument: Yerbacon::exit({"\"", currentArg.data(), "\" is not a valid argument."}); - } - } - const auto currentTarget = Target::forName(target, newLines); - const auto compile = [&target, ¤tTarget](string_view name) -> string { - string transpiledString = currentTarget->transpileWithTree(parseString(getFileContent(name.data()))); - name.remove_suffix(6); - string outputFile; - (outputFile = name).append(target); - outputFileContent(outputFile, transpiledString); - return transpiledString; - }; - int8_t exit_code = EXIT_SUCCESS; - if (!files.empty()) { - using unit = future>>; - vector Units(files.size()); - const launch& Policy = not parallel ? launch::deferred : launch::async; - transform(files.cbegin(), files.cend(), Units.begin(), [&Policy, &compile](const string_view& fileName){ - return async(Policy, [&fileName, &compile]() { - pair> resultingPair; + else if (currentArgument == ArgumentShort("text")) printResult = text_provided = true; + else if (text_provided || currentArgument.ends_with(".ybcon")) + Units.insert_or_assign(currentArgument, async(not parallel ? launch::deferred : launch::async, [currentArgument, &text_provided, &target, &newLines]() { + unit_result resultingPair; try { - resultingPair.first = compile(fileName); + resultingPair.first = Target::forName(target, newLines)->transpileWithTree( + parseString(text_provided ? currentArgument : getFileContent(currentArgument.data())) + ); + if (not text_provided) outputFileContent(string(currentArgument.substr(0, currentArgument.size() - 6)) + '.' + target, resultingPair.first); } catch (const Yerbacon::Exception& error) { size_t lastSlash = 0; - const size_t position1 = fileName.find_last_of('/'); + const size_t position1 = currentArgument.find_last_of('/'); if (cmp_not_equal(position1, string_view::npos)) lastSlash = position1; if constexpr(filesystem::path::preferred_separator != '/') { - const size_t position2 = fileName.find_last_of(filesystem::path::preferred_separator); + const size_t position2 = currentArgument.find_last_of(filesystem::path::preferred_separator); if (cmp_not_equal(position2, string_view::npos)) { lastSlash = max(lastSlash, position2); } } - resultingPair.first = fileName.substr(lastSlash + 1); + resultingPair.first = currentArgument.substr(lastSlash + 1); resultingPair.second.emplace(error); } return resultingPair; - }); - }); + })); + else { + if (argc == 2) { + if (currentArgument == Argument("version")) { + cout << Yerbacon::getVersion(); + } else if (currentArgument == Argument("buildInfo")) { + cout << Yerbacon::getBuildInfo(); + } else goto invalid_argument; + cout << '\n'; exit(EXIT_SUCCESS); + } else invalid_argument: Yerbacon::fail({"\"", currentArgument.data(), "\" is not a valid argument."}); + } + } + if (!Units.empty()) { if (printResult) cout << "~~~~[Yerbacon compilation result]~~~~\n\n"; - if (any_of(Units.rbegin(), Units.rend(), [&printResult](unit& currentFuture) -> bool { - const pair result = currentFuture.get(); + for (auto entry_iterator = Units.rbegin(); entry_iterator != Units.rend(); ++entry_iterator) { + const pair result = entry_iterator->second.get(); const bool is_exception = result.second.has_value(); if (not is_exception) { if (printResult && !(result.first.empty() || all_of(result.first.begin(), result.first.end(), [](const char& character){ @@ -92,13 +84,11 @@ int main(int argc, char* argv[]) { cout << result.first << '\n'; } } else { - cout << "Compilation of " << result.first << " has failed with the following error:" << endl; + cout << "Compilation"; if (not text_provided) cout << " of " << result.first; cout << " has failed with the following error:" << endl; cerr << result.second.value().what() << '\n'; + return EXIT_FAILURE; } - return is_exception; - })) { - exit_code = EXIT_FAILURE; } - } else cout << "No valid file provided.\n"; - return exit_code; + } else cout << (!text_provided ? "No valid file provided" : "No text was provided") << ".\n"; + return EXIT_SUCCESS; } \ No newline at end of file