diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..def0fc55 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['https://afdian.net/@suhuiw4123'] diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 00000000..642e361b --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,292 @@ +name: CMake + +on: [push, pull_request] + +jobs: + build-windows: + env: + BUILD_TYPE: Release + strategy: + fail-fast: false + matrix: + include: + - type: Win32 + triplet: x86-windows-static + - type: x64 + triplet: x64-windows-static + + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Cache + uses: actions/cache@v2.1.4 + with: + # A list of files, directories, and wildcard patterns to cache and restore + path: ${{github.workspace}}/build/vcpkg_installed + # An explicit key for restoring and saving the cache + key: ${{ runner.os }}-${{ matrix.triplet }}-${{ hashFiles('**/vcpkg.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.triplet }}- + + - name: Setup Vcpkg + shell: cmd + working-directory: ${{github.workspace}}/vcpkg + run: bootstrap-vcpkg.bat + + - name: Configure CMake + shell: bash + working-directory: ${{github.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -A ${{ matrix.type }} -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} + + - name: Build + shell: bash + working-directory: ${{github.workspace}}/build + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE + + - name: Test + shell: bash + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C $BUILD_TYPE + + - name: Upload + uses: actions/upload-artifact@v2.2.2 + with: + # Artifact name + name: ${{ matrix.triplet }} + # A file, directory or wildcard pattern that describes what to upload + path: "**/w4123.Dice.dll" + + build-macos: + env: + BUILD_TYPE: Release + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + triplet: x64-osx + - arch: arm64 + triplet: arm64-osx + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Cache + uses: actions/cache@v2.1.4 + with: + # A list of files, directories, and wildcard patterns to cache and restore + path: ${{github.workspace}}/build/vcpkg_installed + # An explicit key for restoring and saving the cache + key: ${{ runner.os }}-${{ matrix.triplet }}-${{ hashFiles('**/vcpkg.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.triplet }}- + + - name: Setup Vcpkg + shell: bash + working-directory: ${{github.workspace}}/vcpkg + run: ./bootstrap-vcpkg.sh + + - name: Configure CMake + working-directory: ${{github.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake ${{github.workspace}} -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} + + - name: Build + working-directory: ${{github.workspace}}/build + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C $BUILD_TYPE + + - name: Upload + uses: actions/upload-artifact@v2.2.2 + with: + # Artifact name + name: ${{ matrix.triplet }} + # A file, directory or wildcard pattern that describes what to upload + path: "**/w4123.Dice.dll" + + build-linux: + env: + BUILD_TYPE: Release + strategy: + fail-fast: false + matrix: + include: + - apt: g++-8 + cc: gcc-8 + cxx: g++-8 + triplet: x64-linux + - path: /usr/i686-linux-gnu + apt: g++-8-i686-linux-gnu + cc: i686-linux-gnu-gcc-8 + cxx: i686-linux-gnu-g++-8 + triplet: x86-linux + - path: /usr/aarch64-linux-gnu + apt: g++-8-aarch64-linux-gnu g++-aarch64-linux-gnu + cc: aarch64-linux-gnu-gcc-8 + cxx: aarch64-linux-gnu-g++-8 + triplet: arm64-linux + - path: /usr/arm-linux-gnueabihf + apt: g++-8-arm-linux-gnueabihf g++-arm-linux-gnueabihf + cc: arm-linux-gnueabihf-gcc-8 + cxx: arm-linux-gnueabihf-g++-8 + triplet: arm-linux + + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Install Cross Compiler + run: sudo apt-get install ${{ matrix.apt }} + + - name: Cache + uses: actions/cache@v2.1.4 + with: + # A list of files, directories, and wildcard patterns to cache and restore + path: ${{github.workspace}}/build/vcpkg_installed + # An explicit key for restoring and saving the cache + key: ${{ runner.os }}-${{ matrix.triplet }}-${{ hashFiles('**/vcpkg.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.triplet }}- + + - name: Setup Vcpkg + shell: bash + working-directory: ${{github.workspace}}/vcpkg + run: ./bootstrap-vcpkg.sh + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{github.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: CC=${{ matrix.cc }} CXX=${{ matrix.cxx }} cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_OVERLAY_TRIPLETS=${{ github.workspace }}/triplets -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} -DCMAKE_PREFIX_PATH=${{ matrix.path }} + + - name: Build + working-directory: ${{github.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE + + - name: Test + working-directory: ${{github.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C $BUILD_TYPE + + - name: Upload + uses: actions/upload-artifact@v2.2.2 + with: + # Artifact name + name: ${{ matrix.triplet }} + # A file, directory or wildcard pattern that describes what to upload + path: "**/w4123.Dice.dll" + + build-android: + env: + ANDROID_NDK: $ANDROID_NDK_HOME + BUILD_TYPE: Release + strategy: + fail-fast: false + matrix: + include: + - abi: armeabi-v7a + triplet: arm-android-static + - abi: arm64-v8a + triplet: arm64-android-static + - abi: x86 + triplet: x86-android-static + - abi: x86_64 + triplet: x64-android-static + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Cache + uses: actions/cache@v2.1.4 + with: + # A list of files, directories, and wildcard patterns to cache and restore + path: ${{github.workspace}}/build/vcpkg_installed + # An explicit key for restoring and saving the cache + key: ${{ runner.os }}-${{ matrix.triplet }}-${{ hashFiles('**/vcpkg.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.triplet }}- + + - name: Setup Vcpkg + shell: bash + working-directory: ${{github.workspace}}/vcpkg + run: ./bootstrap-vcpkg.sh + + - name: Configure CMake + shell: bash + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake -DVCPKG_OVERLAY_TRIPLETS=${{ github.workspace }}/triplets -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} -DANDROID_ABI=${{ matrix.abi }} -DANDROID_PLATFORM=21 + + - name: Build + working-directory: ${{github.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE + + - name: Test + working-directory: ${{github.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C $BUILD_TYPE + + - name: Upload + uses: actions/upload-artifact@v2.2.2 + with: + # Artifact name + name: ${{ matrix.triplet }} + # A file, directory or wildcard pattern that describes what to upload + path: "**/w4123.Dice.dll" + diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..5446eebf --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,69 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ Shiki ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ Shiki ] + schedule: + - cron: '17 7 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + submodules: 'recursive' + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # 鈩癸笍 Command-line programs to run using the OS shell. + # 馃摎 https://git.io/JvXDl + + # 鉁忥笍 If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore index 9c11a4ff..2de31c91 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,10 @@ /packages /docs/html /Debug +/Win32 /x64 /Dice-cppcheck-build-dir +/w4123.Dice.dir + +/CMakeFiles +/vcpkg_installed \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..c3b43781 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "vcpkg"] + path = vcpkg + url = https://github.com/w4123/vcpkg +[submodule "libiconv"] + path = libiconv + url = https://github.com/w4123/libiconv-android-prebuilt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..93b2cfca --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.15) +cmake_policy(SET CMP0091 NEW) +set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake + CACHE STRING "Vcpkg toolchain file") +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" + CACHE STRING "Minimum OS X deployment version") +project(Dice) + +include_directories(CQSDK Dice Lua QQAPI) + +aux_source_directory(Dice Dice_SRC) +aux_source_directory(Lua Lua_SRC) +aux_source_directory(CQSDKCPP CQSDK_SRC) +aux_source_directory(QQAPI QQAPI_SRC) + +add_library(w4123.Dice SHARED ${Dice_SRC} ${Lua_SRC} ${CQSDK_SRC} ${QQAPI_SRC}) + +set_property(TARGET w4123.Dice PROPERTY CXX_STANDARD 17) +set_property(TARGET w4123.Dice PROPERTY CXX_STANDARD_REQUIRED ON) +set_property(TARGET w4123.Dice PROPERTY PREFIX "") +set_property(TARGET w4123.Dice PROPERTY SUFFIX ".dll") + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "-fpermissive ${CMAKE_CXX_FLAGS}") + target_link_libraries(w4123.Dice PRIVATE $<$,9.0>:stdc++fs>) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + set(CMAKE_CXX_FLAGS "-fpermissive -Wno-invalid-source-encoding ${CMAKE_CXX_FLAGS}") +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "-fpermissive -Wno-invalid-source-encoding ${CMAKE_CXX_FLAGS}") + target_link_libraries(w4123.Dice PRIVATE $<$,9.0>:c++fs>) + if(CMAKE_SYSTEM_NAME STREQUAL Android) + set(CMAKE_SHARED_LINKER_FLAGS "-static-libstdc++ ${CMAKE_SHARED_LINKER_FLAGS}") + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + string(REGEX MATCH static IS_STATIC ${VCPKG_TARGET_TRIPLET}) + if(IS_STATIC STREQUAL static) + set_property(TARGET w4123.Dice PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() +endif() + +include(CheckCSourceCompiles) + +list(APPEND CMAKE_REQUIRED_LIBRARIES "-Wl,-Bsymbolic-functions -Wl,-Bsymbolic") + +check_c_source_compiles( +[=[ +int main () +{ + return 0; +} +]=] +BSYMBOLIC_WORKS +) +list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "-Wl,-Bsymbolic-functions -Wl,-Bsymbolic") + +if (BSYMBOLIC_WORKS) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-Bsymbolic-functions -Wl,-Bsymbolic") +endif() + +include(CheckIPOSupported) +check_ipo_supported(RESULT supported) + +if(supported) + set_property(TARGET w4123.Dice PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + if(CMAKE_SYSTEM_NAME STREQUAL Android) + STRING(REGEX REPLACE "-fuse-ld=gold" "" CMAKE_CXX_LINK_OPTIONS_IPO ${CMAKE_CXX_LINK_OPTIONS_IPO}) + endif() +endif() + + +if(NOT ((CMAKE_SYSTEM_NAME STREQUAL Windows) OR (CMAKE_SYSTEM_NAME STREQUAL Android))) + find_package(CURL REQUIRED) + target_link_libraries(w4123.Dice PRIVATE CURL::libcurl) + find_package(Iconv REQUIRED) + target_link_libraries(w4123.Dice PRIVATE Iconv::Iconv) +endif() + +find_package(OpenSSL REQUIRED) +target_link_libraries(w4123.Dice PRIVATE OpenSSL::SSL OpenSSL::Crypto) +find_package(AWSSDK CONFIG COMPONENTS core s3 REQUIRED) +target_include_directories(w4123.Dice PRIVATE ${AWSSDK_INCLUDE_DIRS}) +target_link_libraries(w4123.Dice PRIVATE ${AWSSDK_LIBRARIES}) + +set_target_properties(w4123.Dice PROPERTIES CXX_VISIBILITY_PRESET hidden) +set_target_properties(w4123.Dice PROPERTIES C_VISIBILITY_PRESET hidden) + +if(CMAKE_SYSTEM_NAME STREQUAL Android) + target_include_directories(w4123.Dice PRIVATE ${CMAKE_SOURCE_DIR}/libiconv/${ANDROID_ABI}/include) + target_link_libraries(w4123.Dice PRIVATE ${CMAKE_SOURCE_DIR}/libiconv/${ANDROID_ABI}/lib/libiconv.a) +endif() + +if(NOT ((CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"))) + set_target_properties(w4123.Dice PROPERTIES LINK_FLAGS_RELEASE "-s -Wl,--exclude-libs,ALL") +endif() diff --git a/CQSDK/CQAPI.h b/CQSDK/CQAPI.h deleted file mode 100644 index dbf2febe..00000000 --- a/CQSDK/CQAPI.h +++ /dev/null @@ -1,266 +0,0 @@ -/* -CoolQ SDK for VS2017 -Api Version 9.13 -Written by MukiPy2001 & Thanks for the help of orzFly and Coxxs -*/ -#pragma once -#include "cqdefine.h" -#ifdef _MSC_VER -#define CQAPI(NAME,ReturnType) extern "C" __declspec(dllimport) ReturnType __stdcall NAME // NOLINT -#else -#define CQAPI(NAME,ReturnType) extern "C" __attribute__((dllimport)) ReturnType __attribute__((__stdcall__)) NAME // NOLINT -#endif /*_MSC_VER*/ - -namespace CQ -{ - // 获取调用api所需的AuthCode - int getAuthCode() noexcept; - - //发送好友消息 - //Auth=106 失败返回负值,成功返回消息ID - CQAPI(CQ_sendPrivateMsg, int)( - int AuthCode, // - long long QQID, // 目标QQ - const char* msg // 消息内容 - ) noexcept; - - //发送群消息 - //Auth=101 失败返回负值,成功返回消息ID - CQAPI(CQ_sendGroupMsg, int)( - int AuthCode, // - long long GroupID, // 目标群 - const char* msg // 消息内容 - ) noexcept; - - //发送讨论组消息 - //Auth=103 失败返回负值,成功返回消息ID - CQAPI(CQ_sendDiscussMsg, int)( - int AuthCode, // - long long DiscussID, // 目标讨论组 - const char* msg // 消息内容 - ) noexcept; - - //发送赞 Auth=110 - CQAPI(CQ_sendLike, int)( - int AuthCode, // - long long QQID // 目标QQ - ) noexcept; - - //发送赞V2 Auth=110 - CQAPI(CQ_sendLikeV2, int)( - int AuthCode, // - long long QQID, // 目标QQ - int times // 赞的次数,最多10次 - ) noexcept; - - //取Cookies (慎用,此接口需要严格授权) - //Auth=20 慎用,此接口需要严格授权 - CQAPI(CQ_getCookiesV2, const char *)( - int AuthCode, // - const char* Domain - ) noexcept; - - //接收语音 - CQAPI(CQ_getRecordV2, const char *)( - int AuthCode, // - const char* file, // 收到消息中的语音文件名 (file) - const char* outformat // 应用所需的格式 mp3,amr,wma,m4a,spx,ogg,wav,flac - ) noexcept; - - //取CsrfToken (慎用,此接口需要严格授权) - //Auth=20 即QQ网页用到的bkn/g_tk等 慎用,此接口需要严格授权 - CQAPI(CQ_getCsrfToken, int)( - int AuthCode // - ) noexcept; - - //取应用目录 - //返回的路径末尾带"\" - CQAPI(CQ_getAppDirectory, const char *)( - int AuthCode // - ) noexcept; - - //取登录QQ - CQAPI(CQ_getLoginQQ, long long)( - int AuthCode // - ) noexcept; - - //取登录昵称 - CQAPI(CQ_getLoginNick, const char *)( - int AuthCode // - ) noexcept; - - //置群员移除 Auth=120 - CQAPI(CQ_setGroupKick, int)( - int AuthCode, // - long long GroupID, // 目标群 - long long QQID, // 目标QQ - CQBOOL RefuseForever // 如果为真,则“不再接收此人加群申请”,请慎用 - ) noexcept; - - //置群员禁言 Auth=121 - CQAPI(CQ_setGroupBan, int)( - int AuthCode, // - long long GroupID, // 目标群 - long long QQID, // 目标QQ - long long Time // 禁言的时间,单位为秒。如果要解禁,这里填写0 - ) noexcept; - - //置群管理员 Auth=122 - CQAPI(CQ_setGroupAdmin, int)( - int AuthCode, // - long long GroupID, // 目标群 - long long QQID, // 被设置的QQ - CQBOOL setAdmin // 真/设置管理员 假/取消管理员 - ) noexcept; - - //置群成员专属头衔 Auth=128 需群主权限 - CQAPI(CQ_setGroupSpecialTitle, int)( - int AuthCode, // - long long GroupID, // 目标群 - long long QQID, // 目标QQ - const char* Title, // 如果要删除,这里填空 - long long ExpireTime // 专属头衔有效期,单位为秒。如果永久有效,这里填写-1 - ) noexcept; - - //置全群禁言 Auth=123 - CQAPI(CQ_setGroupWholeBan, int)( - int AuthCode, // - long long GroupID, // 目标群 - CQBOOL EnableWholeBan // 真/开启 假/关闭 - ) noexcept; - - //置匿名群员禁言 Auth=124 - CQAPI(CQ_setGroupAnonymousBan, int)( - int AuthCode, // - long long GroupID, // 目标群 - const char* AnonymousID, // 群消息事件收到的“匿名”参数 - long long time // 禁言的时间,单位为秒。不支持解禁 - ) noexcept; - - //置群匿名设置 Auth=125 - CQAPI(CQ_setGroupAnonymous, int)( - int AuthCode, // - long long GroupID, // - CQBOOL EnableAnonymous // - ) noexcept; - - //置群成员名片 Auth=126 - CQAPI(CQ_setGroupCard, int)( - int AuthCode, // - long long GroupID, // 目标群 - long long QQID, // 被设置的QQ - const char* NewGroupCarkNick // - ) noexcept; - - //置群退出 Auth=127 慎用,此接口需要严格授权 - CQAPI(CQ_setGroupLeave, int)( - int AuthCode, // - long long GroupID, // 目标群 - CQBOOL isDismiss // 真/解散本群 (群主) 假/退出本群 (管理、群成员) - ) noexcept; - - //置讨论组退出 Auth=140 - CQAPI(CQ_setDiscussLeave, int)( - int AuthCode, // - long long DiscussID // 目标讨论组 - ) noexcept; - - //置好友添加请求 Auth=150 - CQAPI(CQ_setFriendAddRequest, int)( - int AuthCode, // - const char* ResponseToken, // 请求事件收到的“反馈标识”参数 - int ResponseType, // #请求_通过 或 #请求_拒绝 - const char* Remarks // 添加后的好友备注 - ) noexcept; - - //置群添加请求 Auth=151 - CQAPI(CQ_setGroupAddRequest, int)( - int AuthCode, // - const char* ResponseToken, // 请求事件收到的“反馈标识”参数 - int RequestType, // 根据请求事件的子类型区分 #请求_群添加 或 #请求_群邀请 - int ResponseType // #请求_通过 或 #请求_拒绝 - ) noexcept; - - //置群添加请求 Auth=151 - CQAPI(CQ_setGroupAddRequestV2, int)( - int AuthCode, // - const char* RequestToken, // 请求事件收到的“反馈标识”参数 - int RequestType, // 根据请求事件的子类型区分 #请求_群添加 或 #请求_群邀请 - int ResponseType, // #请求_通过 或 #请求_拒绝 - const char* Reason // 操作理由,仅 #请求_群添加 且 #请求_拒绝 时可用 - ) noexcept; - - //增加运行日志 - CQAPI(CQ_addLog, int)( - int AuthCode, // - int Priorty, // #Log_ 开头常量 - const char* Type, // - const char* Content // - ) noexcept; - - //置致命错误提示 - CQAPI(CQ_setFatal, int)( - int AuthCode, // - const char* ErrorMsg // - ) noexcept; - - //取群成员信息 (旧版,请用CQ_getGroupMemberInfoV2) Auth=130 - CQAPI(CQ_getGroupMemberInfo, const char *)( - int AuthCode, // - long long GroupID, // 目标QQ所在群 - long long QQID // 目标QQ - ) noexcept; - //取群成员信息 (支持缓存) Auth=130 - CQAPI(CQ_getGroupMemberInfoV2, const char *)( - int AuthCode, // - long long GroupID, // 目标QQ所在群 - long long QQID, // 目标QQ - CQBOOL DisableCache - ) noexcept; - //取陌生人信息 (支持缓存) Auth=131 - CQAPI(CQ_getStrangerInfo, const char *)( - int AuthCode, // - long long QQID, // 目标QQ - CQBOOL DisableCache - ) noexcept; - //取群信息 (支持缓存) Auth=132 - CQAPI(CQ_getGroupInfo, const char*)( - int AuthCode, // - long long GroupID, // 目标群 - CQBOOL DisableCache - ) noexcept; - - //取群成员列表 Auth=160 - CQAPI(CQ_getGroupMemberList, const char *)( - int AuthCode, // - long long GroupID // 目标QQ所在群 - ) noexcept; - //取群列表 Auth=161 - CQAPI(CQ_getGroupList, const char *)( - int AuthCode - ) noexcept; - //取好友列表 Auth=162 - CQAPI(CQ_getFriendList, const char*)( - int AuthCode, - CQBOOL Reserved = false // 保持为false - ) noexcept; - //撤回消息 Auth=180 - CQAPI(CQ_deleteMsg, int)( - int AuthCode, - long long MsgId - ) noexcept; - - //是否支持发送图片,返回大于 0 为支持,等于 0 为不支持 - CQAPI(CQ_canSendImage, int)( - int AuthCode - ) noexcept; - //是否支持发送语音,返回大于 0 为支持,等于 0 为不支持 - CQAPI(CQ_canSendRecord, int)( - int AuthCode - ) noexcept; - //接收图片,并返回图片文件绝对路径 - CQAPI(CQ_getImage, const char*)( - int AuthCode, - const char* file//收到消息中的图片文件名(file) - ) noexcept; -} diff --git a/CQSDK/CQAPI_EX.h b/CQSDK/CQAPI_EX.h deleted file mode 100644 index 4e677ae4..00000000 --- a/CQSDK/CQAPI_EX.h +++ /dev/null @@ -1,259 +0,0 @@ -#pragma once - -#include "cqdefine.h" - -#include -#include -#include - -class Unpack; - -namespace CQ -{ - //增加运行日志 - int addLog(int Priorty, const char* Type, const char* Content) noexcept; - - //发送好友消息 - //Auth=106 失败返回负值,成功返回消息ID - int sendPrivateMsg(long long QQ, const char* msg) noexcept; - //发送好友消息 - //Auth=106 失败返回负值,成功返回消息ID - int sendPrivateMsg(long long QQ, const std::string& msg) noexcept; - - //发送群消息 - //Auth=101 失败返回负值,成功返回消息ID - int sendGroupMsg(long long GroupID, const char* msg) noexcept; - //发送群消息 - //Auth=101 失败返回负值,成功返回消息ID - int sendGroupMsg(long long GroupID, const std::string& msg) noexcept; - - - //发送讨论组消息 - //Auth=103 失败返回负值,成功返回消息ID - int sendDiscussMsg(long long DiscussID, const char* msg) noexcept; - //发送讨论组消息 - //Auth=103 失败返回负值,成功返回消息ID - int sendDiscussMsg(long long DiscussID, const std::string& msg) noexcept; - - //发送赞 Auth=110 - int sendLike(long long QQID, int times) noexcept; - - //取Cookies (慎用,此接口需要严格授权) - //Auth=20 - const char* getCookies(const char* Domain) noexcept; - - //取Cookies (慎用,此接口需要严格授权) - //Auth=20 - const char* getCookies(const std::string& Domain) noexcept; - - //接收语音 - const char* getRecord( - const char* file, // 收到消息中的语音文件名 (file) - const char* outformat // 应用所需的格式 mp3,amr,wma,m4a,spx,ogg,wav,flac - ) noexcept; - //接收语音 - std::string getRecord( - const std::string& file, // 收到消息中的语音文件名 (file) - const std::string& outformat // 应用所需的格式 mp3,amr,wma,m4a,spx,ogg,wav,flac - ) noexcept; - - //取CsrfToken (慎用,此接口需要严格授权) - //Auth=20 即QQ网页用到的bkn/g_tk等 - int getCsrfToken() noexcept; - - //取应用目录 - //返回的路径末尾带"\" - const char* getAppDirectory() noexcept; - - //取登录QQ - long long getLoginQQ() noexcept; - - //取登录昵称 - const char* getLoginNick() noexcept; - - //置群员移除 Auth=120 - int setGroupKick( - long long GroupID, long long QQID, - CQBOOL RefuseForever = false // 如果为真,则“不再接收此人加群申请”,请慎用 - ) noexcept; - - //置群员禁言 Auth=121 - int setGroupBan( - long long GroupID, long long QQID, - long long Time = 60 // 禁言的时间,单位为秒。如果要解禁,这里填写0 - ) noexcept; - - //置群管理员 Auth=122 - int setGroupAdmin( - long long GroupID, long long QQID, - CQBOOL isAdmin = true // 真/设置管理员 假/取消管理员 - ) noexcept; - - //置群成员专属头衔 Auth=128 需群主权限 - int setGroupSpecialTitle( - long long GroupID, long long QQID, - const char* Title, // 如果要删除,这里填空 - long long ExpireTime = -1 // 专属头衔有效期,单位为秒。如果永久有效,这里填写-1 - ) noexcept; - //置群成员专属头衔 Auth=128 需群主权限 - int setGroupSpecialTitle( - long long GroupID, long long QQID, - const std::string& Title, // 如果要删除,这里填空 - long long ExpireTime = -1 // 专属头衔有效期,单位为秒。如果永久有效,这里填写-1 - ) noexcept; - - //置全群禁言 Auth=123 - int setGroupWholeBan( - long long GroupID, - CQBOOL isBan = true // 真/开启 假/关闭 - ) noexcept; - - //置匿名群员禁言 Auth=124 - int setGroupAnonymousBan( - long long GroupID, - const char* AnonymousToken, // 群消息事件收到的“匿名”参数 - long long banTime = 60 // 禁言的时间,单位为秒。不支持解禁 - ) noexcept; - - //置群匿名设置 Auth=125 - int setGroupAnonymous(long long GroupID, CQBOOL enableAnonymous = true) noexcept; - - //置群成员名片 Auth=126 - int setGroupCard(long long GroupID, long long QQID, const char* newGroupNick) noexcept; - - //置群成员名片 Auth=126 - int setGroupCard(long long GroupID, long long QQID, const std::string& newGroupNick) noexcept; - - //置群退出 Auth=127 慎用,此接口需要严格授权 - int setGroupLeave( - long long GroupID, - CQBOOL isDismiss = false // 真/解散本群 (群主) 假/退出本群 (管理、群成员) - ) noexcept; - - //置讨论组退出 Auth=140 - int setDiscussLeave( - long long DiscussID - ) noexcept; - - //置好友添加请求 Auth=150 - int setFriendAddRequest( - const char* RequestToken, // 请求事件收到的“反馈标识”参数 - int ReturnType, // #请求_通过 或 #请求_拒绝 - const char* Remarks // 添加后的好友备注 - ) noexcept; - - //置群添加请求 Auth=151 - int setGroupAddRequest( - const char* RequestToken, // 请求事件收到的“反馈标识”参数 - int RequestType, // 根据请求事件的子类型区分 #请求_群添加 或 #请求_群邀请 - int ReturnType, // #请求_通过 或 #请求_拒绝 - const char* Reason // 操作理由,仅 #请求_群添加 且 #请求_拒绝 时可用 - ) noexcept; - - //置致命错误提示,暂时不知道干什么用的 - int setFatal(const char* ErrorMsg) noexcept; - - - class GroupMemberInfo; - //取群成员信息 (支持缓存) Auth=130 - GroupMemberInfo getGroupMemberInfo(long long GroupID, long long QQID, CQBOOL DisableCache = false) noexcept; - - class StrangerInfo; - //取陌生人信息 (支持缓存) Auth=131 - StrangerInfo getStrangerInfo(long long QQID, CQBOOL DisableCache = false) noexcept; - - //取群成员列表 Auth=160 - std::vector getGroupMemberList(long long GroupID); - - //取群列表 Auth=161 - std::map getGroupList(bool disableCache = false); - - class FriendInfo; - //取好友列表 Auth=162 - std::map getFriendList(bool disableCache = false); - - //是否支持发送图片,返回true为支持,返回false为不支持 - bool canSendImage() noexcept; - - //是否支持发送语音,返回大于 0 为支持,等于 0 为不支持 - bool canSendRecord() noexcept; - - //接收图片,并返回图片文件绝对路径 - const char* getImage(const char* file) noexcept; - - //接收图片,并返回图片文件绝对路径 - const char* getImage(const std::string& file) noexcept; - - //撤回消息 Auth=180 - int deleteMsg(long long MsgId) noexcept; - - const char* getlasterrmsg() noexcept; - // 群信息 - class GroupInfo final { - void setdata(Unpack& u); - public: - long long llGroup{}; - std::string strGroupName{}; - int nGroupSize = 0; // 群人数 - int nMaxGroupSize = 0;//群规模 - int nFriendCnt = 0;//好友数 - - explicit GroupInfo(long long group); - GroupInfo() = default; - [[nodiscard]] std::string tostring() const; - }; - // 群成员信息 - class GroupMemberInfo final - { - void setdata(Unpack& u); - public: - long long Group{}; - long long QQID{}; - std::string Nick{}; - std::string GroupNick{}; - int Gender{}; // 0/男性 1/女性 - int Age{}; - std::string Region{}; - int AddGroupTime{}; - int LastMsgTime{}; - std::string LevelName{}; - int permissions{}; //1/成员 2/管理员 3/群主 - std::string Title{}; - int ExpireTime{}; // -1 代表不过期 - CQBOOL NaughtyRecord{}; - CQBOOL canEditGroupNick{}; - - explicit GroupMemberInfo(Unpack& msg); - explicit GroupMemberInfo(const char* msg); //从API解码 - explicit GroupMemberInfo(const std::vector& data); //从Unpack解码 - GroupMemberInfo() = default; - - [[nodiscard]] std::string tostring() const; - }; - // 好友信息 - class FriendInfo final - { - public: - long long QQID = 0; - std::string nick; //昵称 - std::string remark; //备注 - - explicit FriendInfo(Unpack p); - FriendInfo() = default; - [[nodiscard]] std::string tostring() const; - }; - // 陌生人信息 - class StrangerInfo final - { - public: - long long QQID = 0; - std::string nick = ""; //昵称 - int sex = 255; //0/男性 1/女性 255/未知 - int age = -1; //年龄 - - explicit StrangerInfo(const char* msg); - StrangerInfo() = default; - - [[nodiscard]] std::string tostring() const; - }; -} diff --git a/CQSDK/CQEVE.h b/CQSDK/CQEVE.h deleted file mode 100644 index 94e06eb7..00000000 --- a/CQSDK/CQEVE.h +++ /dev/null @@ -1,289 +0,0 @@ -/* -CoolQ SDK for VS2017 -Api Version 9.10 -Written by MukiPy2001 & Thanks for the help of orzFly and Coxxs -*/ -#pragma once -#ifdef _MSC_VER -#define CQEVENT(ReturnType, Name, Size) __pragma(comment(linker, "/EXPORT:" #Name "=_" #Name "@" #Size))\ - extern "C" __declspec(dllexport) ReturnType __stdcall Name -#else -#define CQEVENT(ReturnType, Name, Size)\ - extern "C" __attribute__((dllexport)) ReturnType __attribute__((__stdcall__)) Name -#endif /*_MSC_VER*/ -/* -返回应用的ApiVer、Appid,打包后将不会调用 -*/ -#define MUST_AppInfo CQEVENT(const char*, AppInfo, 0)() - -/* -返回应用的ApiVer、Appid,打包后将不会调用 -*/ -#define MUST_AppInfo_RETURN(CQAPPID) MUST_AppInfo{return CQAPIVERTEXT "," CQAPPID;} - -/* -本宏已失效,请使用 getAuthCode(); 直接获取, 此函数在CQAPI.h中 - -应用AuthCode接收 - -请保存 AuthCode ,此值是调用CQAPI的凭证 - -请不要在本函数添加其他代码 -*/ -//#define MUST_Initialize CQEVENT(int, Initialize, 4)(int AuthCode) - - -///////////////////////////////// 事件 相关内容 ///////////////////////////////// -//extern "C" __declspec(dllexport) void __stdcall Int32(int a){}//@4 -//extern "C" __declspec(dllexport) void __stdcall Char(const char* a){}//@4 -//extern "C" __declspec(dllexport) void __stdcall Int64(long long a){}//@8 - -/* -酷Q启动(Type=1001) - -本子程序会在酷Q【主线程】中被调用。 -无论本应用是否被启用,本函数都会在酷Q启动后执行一次,请在这里执行插件初始化代码。 -请务必尽快返回本子程序,否则会卡住其他插件以及主程序的加载。 -请固定返回 0 - -名字如果使用下划线开头需要改成双下划线 -*/ -#define EVE_Startup(Name) CQEVENT(int, Name, 0)() - -/* -酷Q退出(Type=1002) - -本子程序会在酷Q【主线程】中被调用。 -无论本应用是否被启用,本函数都会在酷Q退出前执行一次,请在这里执行插件关闭代码。 -本函数调用完毕后,酷Q将很快关闭,请不要再通过线程等方式执行其他代码 - -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Exit(Name) CQEVENT(int, Name, 0)() - -/* -应用已被启用(Type=1003) - -当应用被启用后,将收到此事件。 -如果酷Q载入时应用已被启用,则在 EVE_Startup(Type=1001,酷Q启动) 被调用后,本函数也将被调用一次。 -如非必要,不建议在这里加载窗口。(可以添加菜单,让用户手动打开窗口) - -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Enable(Name) CQEVENT(int, Name, 0)() - -/* -应用将被停用(Type=1004) - -当应用被停用前,将收到此事件。 -如果酷Q载入时应用已被停用,则本函数【不会】被调用。 -无论本应用是否被启用,酷Q关闭前本函数都【不会】被调用。 - -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Disable(Name) CQEVENT(int, Name, 0)() - -/* -私聊消息(Type=21) - -此函数具有以下参数 -subType 子类型,11/来自好友 1/来自在线状态 2/来自群 3/来自讨论组 -msgId 消息ID -fromQQ 来源QQ -msg 消息内容 -font 字体 - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_PrivateMsg(Name) CQEVENT(int, Name, 24)(int subType, int msgId, long long fromQQ, const char* msg, int font) - -/* -群消息(Type=2) - -subType 子类型,目前固定为1 -msgId 消息ID -fromGroup 来源群号 -fromQQ 来源QQ号 -fromAnonymous 来源匿名者 -msg 消息内容 -font 字体 - -如果消息来自匿名者,fromQQ 固定为 80000000,可使用工具将 fromAnonymous 转换为 匿名者信息 - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_GroupMsg(Name) CQEVENT(int, Name, 36)(int subType, int msgId, long long fromGroup, long long fromQQ, const char* fromAnonymous, const char* msg, int font) - -/* -讨论组消息(Type=4) - -subtype 子类型,目前固定为1 -msgId 消息ID -fromDiscuss 来源讨论组 -fromQQ 来源QQ号 -msg 消息内容 -font 字体 - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_DiscussMsg(Name) CQEVENT(int, Name, 32)(int subType, int msgId, long long fromDiscuss, long long fromQQ, const char* msg, int font) - -/* -群文件上传事件(Type=11) - -subType 子类型,目前固定为1 -sendTime 发送时间(时间戳) -fromGroup 来源群号 -fromQQ 来源QQ号 -file 上传文件信息,使用 <其他_转换_文本到群文件> 将本参数转换为有效数据,待编辑 - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_GroupUpload(Name) CQEVENT(int, Name, 28)(int subType, int sendTime, long long fromGroup,long long fromQQ, const char* file) -/*增强的版本,待补充 -#define EVE_GroupUpload_EX(Name) CQEVENT(int, Name, 28)(int subType, int sendTime, long long fromGroup,long long fromQQ, const char* file)\ -{\ -int Name(int subType, int sendTime, long long fromGroup,long long fromQQ, File file);\ -return Name(subType,sendTime,fromGroup,fromQQ,ToFile(file));\ -}\ -int Name(int subType, int sendTime, long long fromGroup,long long fromQQ, File file) -*/ - -/* -群事件-管理员变动(Type=101) - -subtype 子类型,1/被取消管理员 2/被设置管理员 -sendTime 发送时间(时间戳) -fromGroup 来源群号 -beingOperateQQ 被操作QQ - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_System_GroupAdmin(Name) CQEVENT(int, Name, 24)(int subType, int msgId, long long fromGroup, long long beingOperateQQ) - -/* -群事件-群成员减少(Type=102) - -subtype 子类型,1/群员离开 2/群员被踢 3/自己(即登录号)被踢 -sendTime 发送时间(时间戳) -fromGroup 来源群号 -fromQQ 操作者QQ(仅子类型为2、3时存在) -beingOperateQQ 被操作QQ - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_System_GroupMemberDecrease(Name) CQEVENT(int, Name, 32)(int subType, int msgId, long long fromGroup, long long fromQQ, long long beingOperateQQ) - -/* -群事件-群成员增加(Type=103) - -subtype 子类型,1/管理员已同意 2/管理员邀请 -sendTime 发送时间(时间戳) -fromGroup 来源群号 -fromQQ 操作者QQ(即管理员QQ) -beingOperateQQ 被操作QQ(即加群的QQ) - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_System_GroupMemberIncrease(Name) CQEVENT(int, Name, 32)(int subType, int msgId, long long fromGroup, long long fromQQ, long long beingOperateQQ) - -/* -群事件-群禁言(Type=104) - -subtype 子类型,1/被解禁 2/被禁言 -msgId 消息id -fromGroup 来源群号 -fromQQ 操作者QQ(管理员QQ) -beingOperateQQ 被操作QQ(若为全群禁言则为0) -duration 禁言时长(单位:秒,仅子类型为2时可用) -*/ -#define EVE_System_GroupBan(Name) CQEVENT(int, Name, 40)(int subType, int msgId, long long fromGroup, long long fromQQ, long long beingOperateQQ,long long duration) - -/* -好友事件-好友已添加(Type=201) - -subtype 子类型,目前固定为1 -sendTime 发送时间(时间戳) -fromQQ 来源QQ - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Friend_Add(Name) CQEVENT(int, Name, 16)(int subType, int msgId, long long fromQQ) - -/* -请求-好友添加(Type=301) - -subtype 子类型,目前固定为1 -sendTime 发送时间(时间戳) -fromQQ 来源QQ -msg 附言 -responseFlag 反馈标识(处理请求用) - -置好友添加请求 (responseFlag, #请求_通过) - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Request_AddFriend(Name) CQEVENT(int, Name, 24)(int subType, int msgId, long long fromQQ, const char* msg, const char* responseFlag) - -/* -请求-群添加(Type=302) - -subtype 子类型,1/他人申请入群 2/自己(即登录号)受邀入群 -sendTime 发送时间(时间戳) -fromGroup 来源群号 -fromQQ 来源QQ -msg 附言 -responseFlag 反馈标识(处理请求用) - -如果 subtype = 1 -置群添加请求 (responseFlag, #请求_群添加, #请求_通过) -如果 subtype = 2 -置群添加请求 (responseFlag, #请求_群邀请, #请求_通过) - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Request_AddGroup(Name) CQEVENT(int, Name, 32)(int subType, int sendTime, long long fromGroup, long long fromQQ, const char* msg, const char* responseFlag) - -/* -菜单 - -可在 .json 文件中设置菜单数目、函数名 -如果不使用菜单,请在 .json 及此处删除无用菜单 -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Menu(Name) CQEVENT(int, Name, 0)() - -/* -悬浮窗 - -请使用EX版本 -emmm,因为一些原因,悬浮窗暂时不可用... - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize)。 -名字如果使用下划线开头需要改成双下划线 -*/ -#define EVE_Status(Name) CQEVENT(const char*, Name, 0)() diff --git a/CQSDK/CQEVEBasic.h b/CQSDK/CQEVEBasic.h deleted file mode 100644 index f8f90404..00000000 --- a/CQSDK/CQEVEBasic.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "CQconstant.h" - -namespace CQ -{ - // 事件基类 - struct EVE - { - //不对消息做任何动作 - //如果之前拦截了消息,这里将重新放行本条消息 - void message_ignore() noexcept; - //拦截本条消息 - void message_block() noexcept; - - int _EVEret = Msg_Ignored; - - virtual ~EVE() - { - } - }; - - - inline void EVE::message_ignore() noexcept { _EVEret = Msg_Ignored; } - inline void EVE::message_block() noexcept { _EVEret = Msg_Blocked; } - -} diff --git a/CQSDK/CQEVEMsg.h b/CQSDK/CQEVEMsg.h deleted file mode 100644 index ae377f0f..00000000 --- a/CQSDK/CQEVEMsg.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include "CQEVEBasic.h" - -#include -#include - -namespace CQ -{ - - //正则消息 - class regexMsg final - { - //消息 - std::map regexMap{}; - public: - regexMsg(const std::string& msg); - std::string get(const std::string&); - std::string operator [](const std::string&); - }; - - class msg; - - //消息事件基类 - struct EVEMsg : EVE - { - //子类型 - int subType; - //消息ID - int msgId; - //来源QQ - long long fromQQ; - //消息 - std::string message; - //字体 - int font; - - EVEMsg(int subType, int msgId, long long fromQQ, std::string message, int font) noexcept; - - //真实用户 - bool isUser() const noexcept; - //是否是系统用户 - bool isSystem() const noexcept; - - virtual int sendMsg(const char*) const noexcept = 0; - virtual int sendMsg(const std::string&) const noexcept = 0; - virtual msg sendMsg() const noexcept = 0; - }; - - inline bool EVEMsg::isSystem() const noexcept { return fromQQ == 1000000; } -} diff --git a/CQSDK/CQEVERequest.h b/CQSDK/CQEVERequest.h deleted file mode 100644 index 2cb53b4d..00000000 --- a/CQSDK/CQEVERequest.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include "CQEVEBasic.h" - -namespace CQ -{ - struct EVERequest : EVE - { - int sendTime; // 发送时间(时间戳) - long long fromQQ; // 来源QQ - const char* msg; // 附言 - const char* responseFlag; // 反馈标识(处理请求用) - - EVERequest(int sendTime, long long fromQQ, const char* msg, const char* responseFlag) noexcept; - virtual void pass(const std::string& msg) const noexcept = 0; //通过此请求 - virtual void fail(const std::string& msg) const noexcept = 0; //拒绝此请求 - }; -} diff --git a/CQSDK/CQEVE_ALL.h b/CQSDK/CQEVE_ALL.h deleted file mode 100644 index 7ff894dd..00000000 --- a/CQSDK/CQEVE_ALL.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "CQEVE.h" -#include "CQEVEBasic.h" - -#include "CQEVEMsg.h" -#include "CQEVERequest.h" -#include "CQAPI_EX.h" -#include "CQEVE_Status.h" -#include "CQEVE_GroupMsg.h" -#include "CQEVE_PrivateMsg.h" -#include "CQEVE_DiscussMsg.h" -#include "CQEVE_RequestAddFriend.h" -#include "CQEVE_RequestAddGroup.h" -#include "CQEVE_FriendAdd.h" diff --git a/CQSDK/CQEVE_DiscussMsg.h b/CQSDK/CQEVE_DiscussMsg.h deleted file mode 100644 index 933f840f..00000000 --- a/CQSDK/CQEVE_DiscussMsg.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "CQMsgSend.h" -#include "CQEVEMsg.h" -/* -讨论组消息(Type=4) - -subtype 子类型,目前固定为1 -msgId 消息ID -fromDiscuss 来源讨论组 -fromQQ 来源QQ号 -msg 消息内容 -font 字体 - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_DiscussMsg_EX(Name) \ - void Name(CQ::EVEDiscussMsg & eve); \ - EVE_DiscussMsg(Name) \ - { \ - CQ::EVEDiscussMsg tep(subType, msgId, fromDiscuss, fromQQ, msg, font); \ - Name(tep);\ - return tep._EVEret; \ - } \ - void Name(CQ::EVEDiscussMsg & eve) - - -namespace CQ -{ - struct EVEDiscussMsg : EVEMsg - { - long long fromDiscuss; //讨论组号 - - EVEDiscussMsg(int subType, int msgId, long long fromDiscuss, long long fromQQ, const char* msg, int font) noexcept; - - bool leave() const noexcept; //退出讨论组 - - // 通过 EVEMsg 继承 - msg sendMsg() const noexcept override; - int sendMsg(const char*) const noexcept override; - int sendMsg(const std::string&) const noexcept override; - }; -} diff --git a/CQSDK/CQEVE_FriendAdd.h b/CQSDK/CQEVE_FriendAdd.h deleted file mode 100644 index 7d655e7e..00000000 --- a/CQSDK/CQEVE_FriendAdd.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include "CQEVEBasic.h" - -/* -好友事件-好友已添加(Type=201) - -subtype 子类型,目前固定为1 -msgId 消息ID -fromQQ 来源QQ - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Friend_Add_EX(Name) \ - void Name(CQ::EVERequestAddFriend & eve);\ - EVE_Friend_Add(Name)\ - {\ - CQ::EVEFriendAdd tep(subType, msgId, fromGroup, fromQQ, msg, responseFlag);\ - Name(tep);\ - return tep._EVEret;\ - }\ - void Name(CQ::EVEFriendAdd & eve) - -namespace CQ -{ - struct EVEFriendAdd final : EVE - { - }; -} diff --git a/CQSDK/CQEVE_GroupMsg.h b/CQSDK/CQEVE_GroupMsg.h deleted file mode 100644 index f3341697..00000000 --- a/CQSDK/CQEVE_GroupMsg.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once -#include "CQEVEMsg.h" - -#include -/* -群消息(Type=2) - -subType 子类型,目前固定为1 -msgId 消息ID -fromGroup 来源群号 -fromQQ 来源QQ号 -fromAnonymous 来源匿名者 -msg 消息内容 -font 字体 - -如果消息来自匿名者,isAnonymous() 返回 true, 可使用 getFromAnonymousInfo() 获取 匿名者信息 - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_GroupMsg_EX(Name) \ - void Name(CQ::EVEGroupMsg & eve); \ - EVE_GroupMsg(Name) \ - { \ - CQ::EVEGroupMsg tep(subType, msgId, fromGroup, fromQQ, fromAnonymous, msg, font); \ - Name(tep); \ - return tep._EVEret; \ - } \ - void Name(CQ::EVEGroupMsg & eve) - -namespace CQ -{ - class GroupMemberInfo; - - // 群匿名信息 - struct AnonymousInfo final - { - long long AID = 0; - std::string AnonymousNick = ""; - - explicit AnonymousInfo(const char* msg) noexcept; - AnonymousInfo() noexcept = default; - }; - - //群事件 - struct EVEGroupMsg final : public EVEMsg - { - private: - AnonymousInfo* fromAnonymousInfo; - public: - //群号 - long long fromGroup; - //禁言用的令牌 - const char* fromAnonymousToken; - EVEGroupMsg(int subType, int msgId, long long fromGroup, long long fromQQ, const char* fromAnonymous, - const char* msg, int font) noexcept; - - virtual ~EVEGroupMsg() noexcept; - - bool isAnonymous() const noexcept; - - // 通过 EVEMsg 继承 - int sendMsg(const char*) const noexcept override; - int sendMsg(const std::string&) const noexcept override; - msg sendMsg() const noexcept override; - - //获取匿名者信息 - AnonymousInfo& getFromAnonymousInfo() noexcept(false); - - //置群员移除 - bool setGroupKick(bool refusedAddAgain = false) const noexcept; - //置群员禁言 - //自动判断是否是匿名 - bool setGroupBan(long long banTime = 60) const noexcept; - //置群管理员 - bool setGroupAdmin(bool isAdmin) const noexcept; - //置群成员专属头衔 - bool setGroupSpecialTitle(const std::string& Title, long long ExpireTime = -1) const noexcept; - - //置全群禁言 - bool setGroupWholeBan(bool enableBan = true) const noexcept; - //置群匿名设置 - bool setGroupAnonymous(bool enableAnonymous) const noexcept; - //置群成员名片 - bool setGroupCard(const std::string& newGroupNick) const noexcept; - //置群退出 - bool setGroupLeave(bool isDismiss) const noexcept; - //取群成员信息 (支持缓存) - GroupMemberInfo getGroupMemberInfo(bool disableCache = false) const noexcept; - //取群成员列表 - std::vector getGroupMemberList() const noexcept; - }; -} diff --git a/CQSDK/CQEVE_PrivateMsg.h b/CQSDK/CQEVE_PrivateMsg.h deleted file mode 100644 index a2184b42..00000000 --- a/CQSDK/CQEVE_PrivateMsg.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once -#include "CQMsgSend.h" -#include "CQEVEMsg.h" - -/* -私聊消息(Type=21) - -此函数具有以下参数 -subType 子类型,11/来自好友 1/来自在线状态 2/来自群 3/来自讨论组 -msgId 消息ID -fromQQ 来源QQ -msg 消息内容 -font 字体 - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_PrivateMsg_EX(Name) \ - void Name(CQ::EVEPrivateMsg & eve); \ - EVE_PrivateMsg(Name) \ - { \ - CQ::EVEPrivateMsg tep(subType, msgId, fromQQ, msg, font); \ - Name(tep); \ - return tep._EVEret; \ - } \ - void Name(CQ::EVEPrivateMsg & eve) - - -namespace CQ -{ - struct EVEPrivateMsg final : EVEMsg - { - EVEPrivateMsg(int subType, int msgId, long long fromQQ, const char* msg, int font) noexcept; - - //来自好友 - [[nodiscard]] bool fromPrivate() const noexcept; - - //来自在线状态 - [[nodiscard]] bool fromOnlineStatus() const noexcept; - - //来自群临时 - [[nodiscard]] bool fromGroup() const noexcept; - - //来自讨论组临时 - [[nodiscard]] bool fromDiscuss() const noexcept; - - // 通过 EVEMsg 继承 - [[nodiscard]] msg sendMsg() const noexcept override; - - int sendMsg(const char*) const noexcept override; - - [[nodiscard]] int sendMsg(const std::string&) const noexcept override; - }; -} diff --git a/CQSDK/CQEVE_RequestAddFriend.h b/CQSDK/CQEVE_RequestAddFriend.h deleted file mode 100644 index 66835aeb..00000000 --- a/CQSDK/CQEVE_RequestAddFriend.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include -#include "CQEVERequest.h" - -/* -请求-好友添加(Type=301) - -subtype 子类型,目前固定为1 -sendTime 发送时间(时间戳) -fromQQ 来源QQ -msg 附言 -responseFlag 反馈标识(处理请求用) - -置好友添加请求 (responseFlag, #请求_通过) - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Request_AddFriend_EX(Name) \ - void Name(CQ::EVERequestAddFriend & eve);\ - EVE_Request_AddFriend(Name)\ - {\ - CQ::EVERequestAddFriend tep(subType, sendTime, fromGroup, fromQQ, msg, responseFlag);\ - Name(tep);\ - return tep._EVEret;\ - }\ - void Name(CQ::EVERequestAddFriend & eve) - -namespace CQ -{ - struct EVERequestAddFriend final : EVERequest - { - //子类型 - //1:固定为1 - int subType; - long long fromGroup; // 来源群号 - EVERequestAddFriend(int subType, int sendTime, long long fromQQ, const char* msg, const char* responseFlag) noexcept; - void pass(const std::string& msg = "") const noexcept override; //通过此请求 - void fail(const std::string& msg = "您由于不满足某些要求被拒绝!") const noexcept override; //拒绝此请求 - }; -} diff --git a/CQSDK/CQEVE_RequestAddGroup.h b/CQSDK/CQEVE_RequestAddGroup.h deleted file mode 100644 index bda60a73..00000000 --- a/CQSDK/CQEVE_RequestAddGroup.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -#include -#include "CQEVERequest.h" - -/* -请求-群添加(Type=302) - -subtype 子类型,1/他人申请入群 2/自己(即登录号)受邀入群 -sendTime 发送时间(时间戳) -fromGroup 来源群号 -fromQQ 来源QQ -msg 附言 -responseFlag 反馈标识(处理请求用) - -如果 subtype = 1 -置群添加请求 (responseFlag, #请求_群添加, #请求_通过) -如果 subtype = 2 -置群添加请求 (responseFlag, #请求_群邀请, #请求_通过) - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize) -名字如果使用下划线开头需要改成双下划线 -返回非零值,消息将被拦截,最高优先不可拦截 -*/ -#define EVE_Request_AddGroup_EX(Name) \ - void Name(CQ::EVERequestAddGroup & eve);\ - EVE_Request_AddGroup(Name)\ - {\ - CQ::EVERequestAddGroup tep(subType, sendTime, fromGroup, fromQQ, msg, responseFlag);\ - Name(tep);\ - return tep._EVEret;\ - }\ - void Name(CQ::EVERequestAddGroup & eve) - -namespace CQ -{ - struct EVERequestAddGroup final : EVERequest - { - //子类型 - //1:他人申请入群 - //2:自己(即登录号)受邀入群 - int subType; - long long fromGroup; // 来源群号 - EVERequestAddGroup(int subType, int sendTime, long long fromGroup, long long fromQQ, const char* msg, - const char* responseFlag) noexcept; - void pass(const std::string& msg = "") const noexcept override; //通过此请求 - void fail(const std::string& msg = "您由于不满足某些要求被拒绝!") const noexcept override; //拒绝此请求 - }; -} diff --git a/CQSDK/CQEVE_Status.h b/CQSDK/CQEVE_Status.h deleted file mode 100644 index 4dfee8e7..00000000 --- a/CQSDK/CQEVE_Status.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -/* -悬浮窗 - -请设置 eve.data eve.dataf eve.color 其他函数保持不动即可 - -本子程序会在酷Q【线程】中被调用,请注意使用对象等需要初始化(CoInitialize,CoUninitialize)。 -名字如果使用下划线开头需要改成双下划线 -*/ - -#define EVE_Status_EX(Name) \ - void Name(CQ::EVEStatus & eve);\ - EVE_Status(Name)\ - {\ - CQ::EVEStatus tep;\ - Name(tep);\ - static std::string ret;\ - ret = CQ::statusEVEreturn(tep);\ - return ret.c_str();\ - }\ - void Name(CQ::EVEStatus & eve) - -namespace CQ -{ - struct EVEStatus final - { - std::string - //数据 - data, - //数据单位 - dataf; - int - // 1 : 绿 - // 2 : 橙 - // 3 : 红 - // 4 : 深红 - // 5 : 黑 - // 6 : 灰 - color = 4; - // 1 : 绿 - void color_green() noexcept; - // 2 : 橙 - void color_orange() noexcept; - // 3 : 红 - void color_red() noexcept; - // 4 : 深红 - void color_crimson() noexcept; - // 5 : 黑 - void color_black() noexcept; - // 6 : 灰 - void color_gray() noexcept; - }; - - std::string statusEVEreturn(EVEStatus& eve) noexcept; -} diff --git a/CQSDK/CQLogger.h b/CQSDK/CQLogger.h deleted file mode 100644 index 47176750..00000000 --- a/CQSDK/CQLogger.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once -#include "bufstream.h" -#include - -#define DEBUGINFO "文件:" << __FILE__ << ",行数:" << __LINE__ << ",输出:" - -namespace CQ -{ - class logstream : public CQstream - { - int flag; - std::string title; - public: - logstream(std::string title, int Log_flag) noexcept; - - // 通过 CQstream 继承 - void send() noexcept override; - }; - - class logger - { - std::string title; - public: - logger(std::string title) noexcept; - void setTitle(std::string title) noexcept; - - void Debug(const std::string& msg) const noexcept; - void Info(const std::string& msg) const noexcept; - void InfoSuccess(const std::string& msg) const noexcept; - void InfoRecv(const std::string& msg) const noexcept; - void InfoSend(const std::string& msg) const noexcept; - void Warning(const std::string& msg) const noexcept; - void Error(const std::string& msg) const noexcept; - void Fatal(const std::string& msg) const noexcept; - - void Debug(const char* msg) const noexcept; - void Info(const char* msg) const noexcept; - void InfoSuccess(const char* msg) const noexcept; - void InfoRecv(const char* msg) const noexcept; - void InfoSend(const char* msg) const noexcept; - void Warning(const char* msg) const noexcept; - void Error(const char* msg) const noexcept; - void Fatal(const char* msg) const noexcept; - - [[nodiscard]] logstream Debug() const noexcept; - [[nodiscard]] logstream Info() const noexcept; - [[nodiscard]] logstream InfoSuccess() const noexcept; - [[nodiscard]] logstream InfoRecv() const noexcept; - [[nodiscard]] logstream InfoSend() const noexcept; - [[nodiscard]] logstream Warning() const noexcept; - [[nodiscard]] logstream Error() const noexcept; - [[nodiscard]] logstream Fatal() const noexcept; - }; -} diff --git a/CQSDK/CQMsgCode.h b/CQSDK/CQMsgCode.h deleted file mode 100644 index 943e8947..00000000 --- a/CQSDK/CQMsgCode.h +++ /dev/null @@ -1,150 +0,0 @@ -#pragma once -#include "CQface.h" - -#include -#include -#include -#include -//#include - -namespace CQ { - //cq消息参数 - struct OneCodeMsg { size_t key, keylen = 0, value = 0; OneCodeMsg(size_t key); }; - //一条cq消息或者普通消息 - struct CodeMsg : std::vector { public: bool isCode; size_t key, keylen = 0; CodeMsg(bool isCode, size_t key); }; - - class CodeMsgs; - struct CodeMsgsFor { - CodeMsgs&t; - size_t pos; - CQ::CodeMsgs&operator*(); - CQ::CodeMsgsFor&operator++(); - bool operator!=(CQ::CodeMsgsFor&); - CodeMsgsFor(CodeMsgs&t, int pos); - }; - //消息解析 - class CodeMsgs { - std::vector msglist; - std::string txt; - size_t thismsg=0;//指针 - void decod();//解码 - bool find(std::string &s,int); - bool is(std::string &s, int); - public: - CodeMsgs(std::string); - - //char* at(int); - - //定位到指定段 - CQ::CodeMsgs&operator[](size_t); - CQ::CodeMsgs&operator++(int); - CQ::CodeMsgs&operator++(); - CQ::CodeMsgs&operator--(int); - CQ::CodeMsgs&operator--(); - CQ::CodeMsgs&operator-(size_t); - CQ::CodeMsgs&operator+(size_t); - //返回指针当前位置 - int pos(); - - //从当前位置开始搜索指定cq码 - //如果存在,定位到指定段 - //否则返回null,并且不会移动指针 - bool find(std::string s); - - //从当前位置开始反向搜索指定cq码 - //如果存在,定位到指定段 - //否则返回null,并且不会移动指针 - bool lastfind(std::string); - - - //判断是否是CQ码 - bool isCQcode(); - - //判断是否为指定CQ码 - bool is(std::string); - - //如果是CQ码,返回CQ码类型 - //如果不是,返回消息 - std::string get(); - - //如果是CQ码,返回键对应的值 - //如果找不到键,则返回空字符 - //如果不是,返回空字符 - std::string get(std::string key); - std::vector keys(); - - CQ::CodeMsgsFor begin(); - CQ::CodeMsgsFor end(); - - void debug(); - - }; - - struct code { - //[CQ:image,file={1}] - 发送自定义图片 - //文件以 酷Q目录\data\image\ 为基础 - static std::string image(std::string fileurl); - - //[CQ:record,file={1},magic={2}] - 发送语音 - //文件以 酷Q目录\data\record\ 为基础 - static std::string record(std::string fileurl, bool magic); - - //[CQ:face,id={1}] - QQ表情 - static std::string face(int faceid); - - //[CQ:face,id={1}] - QQ表情 - //static std::string face(CQ::face face); - - //[CQ:at,qq={1}] - @某人 - static std::string at(long long QQ); - - //[CQ:effect,type=art,id=2003,content=小吉] - 魔法字体 - static std::string effect(std::string type, int id, std::string content); - - //[CQ:sign,title=晒幸福,image=http://pub.idqqimg.com/pc/misc/files/20170825/cc9103d0db0b4dcbb7a17554d227f4d7.jpg] - 签到 - - //[CQ:hb, title = 恭喜发财] - 红包(只限收,不能发) - - //[CQ:shake, id = 1] - 戳一戳(原窗口抖动,仅支持好友消息使用) - - //[CQ:sface,id={1}] - 小表情 - - //[CQ:bface,id={1}] - 原创表情 - - //[CQ:emoji,id={1}] - emoji表情 - - //[CQ:rps,type={1}] - 发送猜拳魔法表情 - //发送不支持自定义type - //1 为 石头 - //2 为 剪刀 - //3 为 布 - - //[CQ:dice,type={1}] - 发送掷骰子魔法表情 - //发送不支持自定义type - //type为骰子点数 - - //[CQ:anonymous,ignore={1}] - 匿名发消息(仅支持群消息使用) - //必须在消息最开头 - //ignore为true时,如果发送失败则转为普通消息 - - //[CQ:music,type={1},id={2}] - 发送音乐 - //type为音乐平台,支持qq、163、xiami - //id即为音乐id - - //[CQ:music,type=custom,url={1},audio={2},title={3},content={4},image={5}] - 发送音乐自定义分享 - //url为分享链接,即点击分享后进入的音乐页面(如歌曲介绍页) - //audio为音频链接(如mp3链接) - //title为音乐的标题,建议12字以内 - //content为音乐的简介,建议30字以内。该参数可被忽略 - //image为音乐的封面图片链接。若参数为空或被忽略,则显示默认图片 - //!音乐自定义分享只能作为单独的一条消息发送 - - //[CQ:share,url={1},title={2},content={3},image={4}] - 发送链接分享 - //url为分享链接。 - //title为分享的标题,建议12字以内。 - //content为分享的简介,建议30字以内。该参数可被忽略。 - //image为分享的图片链接。若参数为空或被忽略,则显示默认图片。 - //!链接分享只能作为单独的一条消息发送 - - }; -} \ No newline at end of file diff --git a/CQSDK/CQMsgSend.h b/CQSDK/CQMsgSend.h deleted file mode 100644 index 6aa52a1d..00000000 --- a/CQSDK/CQMsgSend.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include "bufstream.h" - -namespace CQ -{ - enum class msgtype : int { Private = 0, Group = 1, Discuss = 2 }; - - class msg final : public CQstream - { - long long ID; - int subType = 0; - - public: - /* - Type: - 0=msgtype::好友 - 1=msgtype::群 - 2=msgtype::讨论组 - */ - msg(long long GroupID_Or_QQID, msgtype Type) noexcept; - /* - Type: - 0=好友 - 1=群 - 2=讨论组 - */ - msg(long long GroupID_Or_QQID, int Type) noexcept; - - void send() noexcept override; - }; -} diff --git a/CQSDK/CQconstant.h b/CQSDK/CQconstant.h deleted file mode 100644 index dd1bdabe..00000000 --- a/CQSDK/CQconstant.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -// 调试 灰色 -#define Log_Debug 0 -// 信息 黑色 -#define Log_Info 10 -// 信息(成功) 紫色 -#define Log_InfoSuccess 11 -// 信息(接收) 蓝色 -#define Log_InfoRecv 12 -// 信息(发送) 绿色 -#define Log_InfoSend 13 -// 警告 橙色 -#define Log_Warning 20 -// 错误 红色 -#define Log_Error 30 -// 致命错误 深红 -#define Log_Fatal 40 - -// 拦截此条消息,不再传递给其他应用 -//注意:应用优先级设置为最高(10000)时,不得使用本返回值 -#define Msg_Blocked 1 -// 将此消息继续传递给其他应用 -#define Msg_Ignored 0 - -#define FloatingWindows_Green 1 -#define FloatingWindows_Orange 2 -#define FloatingWindows_Red 3 -#define FloatingWindows_DeepRed 4 -#define FloatingWindows_Black 5 -#define FloatingWindows_Grey 6 - -#define RequestAccepted 1 -#define RequestRefused 2 - -#define RequestGroupAdd 1 -#define RequestGroupInvite 2 diff --git a/CQSDK/cqdefine.h b/CQSDK/cqdefine.h deleted file mode 100644 index ed600447..00000000 --- a/CQSDK/cqdefine.h +++ /dev/null @@ -1,12 +0,0 @@ -/* -CoolQ SDK for VS2017 -Api Version 9.10 -Written by MukiPy2001 & Thanks for the help of orzFly and Coxxs -*/ -#pragma once - - -#define CQAPIVER 9 -#define CQAPIVERTEXT "9" - -typedef int CQBOOL; diff --git a/CQSDKCPP/CQAPI.cpp b/CQSDKCPP/CQAPI.cpp deleted file mode 100644 index d83edda8..00000000 --- a/CQSDKCPP/CQAPI.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "CQAPI.h" -#include "CQEVE.h" - -int AuthCode; -CQEVENT(int, Initialize, 4)(const int AuthCode) -{ - ::AuthCode = AuthCode; - return 0; -} - -int getAuthCode() noexcept -{ - return AuthCode; -} - -int CQ::getAuthCode() noexcept -{ - return AuthCode; -} diff --git a/CQSDKCPP/CQAPI_EX.cpp b/CQSDKCPP/CQAPI_EX.cpp deleted file mode 100644 index 7e5be953..00000000 --- a/CQSDKCPP/CQAPI_EX.cpp +++ /dev/null @@ -1,502 +0,0 @@ -#include "CQAPI_EX.h" - -#include - - -#include "CQAPI.h" -#include "Unpack.h" -#include "CQEVE_GroupMsg.h" -#include "CQTools.h" - -using namespace CQ; -using namespace std; -int lasterr; - -StrangerInfo::StrangerInfo(const char* msg) -{ - if (msg != nullptr && msg[0] != '\0') - { - Unpack p(base64_decode(msg)); - QQID = p.getLong(); - nick = p.getstring(); - sex = p.getInt(); - age = p.getInt(); - } -} - -string StrangerInfo::tostring() const -{ - return string("{") - + "QQ:" + to_string(QQID) - + " ,昵称:" + nick - + " ,性别:" + (sex == 255 ? "未知" : sex == 1 ? "男" : "女") - + " ,年龄:" + to_string(age) - + "}"; -} - -void GroupInfo::setdata(Unpack& u) -{ - llGroup = u.getLong(); - strGroupName = u.getstring(); - nGroupSize = u.getInt(); // 群人数 - nMaxGroupSize = u.getInt(); //群规模 - nFriendCnt = u.getInt(); //好友数 -} - -GroupInfo::GroupInfo(long long group) -{ - Unpack pack(base64_decode(CQ_getGroupInfo(getAuthCode(), group, true))); - if (!pack.len())return; - setdata(pack); -} - -std::string GroupInfo::tostring() const -{ - return strGroupName + "(" + std::to_string(llGroup) + ")[" + std::to_string(nGroupSize) + "/" + - std::to_string(nMaxGroupSize) + "]"; -} - -void GroupMemberInfo::setdata(Unpack& u) -{ - Group = u.getLong(); - QQID = u.getLong(); - Nick = u.getstring(); - GroupNick = u.getstring(); - Gender = u.getInt(); - Age = u.getInt(); - Region = u.getstring(); - AddGroupTime = u.getInt(); - LastMsgTime = u.getInt(); - LevelName = u.getstring(); - permissions = u.getInt(); - NaughtyRecord = u.getInt() == 1; - Title = u.getstring(); - ExpireTime = u.getInt(); - canEditGroupNick = u.getInt() == 1; -} - -GroupMemberInfo::GroupMemberInfo(Unpack& msg) { setdata(msg); } - -GroupMemberInfo::GroupMemberInfo(const char* msg) -{ - if (msg != nullptr && msg[0] != '\0') - { - Unpack u(base64_decode(msg)); - setdata(u); - } -} - -GroupMemberInfo::GroupMemberInfo(const vector& data) -{ - if (!data.empty()) - { - Unpack u(data); - setdata(u); - } -} - -string GroupMemberInfo::tostring() const -{ - string s = "{"; - s += "群号:"; - s += to_string(Group); - s += " ,QQ号:"; - s += to_string(QQID); - s += " ,昵称:"; - s += Nick; - s += " ,名片:"; - s += GroupNick; - s += " ,性别:"; - s += (Gender == 255 ? "未知" : Gender == 1 ? "男" : "女"); - s += " ,年龄:"; - s += to_string(Age); - s += " ,地区:"; - s += Region; - s += " ,加群时间:"; - s += to_string(AddGroupTime); - s += " ,最后发言:"; - s += to_string(LastMsgTime); - s += " ,等级_名称:"; - s += LevelName; - s += " ,管理权限:"; - s += (permissions == 3 ? "群主" : permissions == 2 ? "管理员" : "群员"); - s += "("; - s += to_string(permissions); - s += ")"; - s += " ,不良记录成员:"; - s += to_string(NaughtyRecord); - s += " ,专属头衔:"; - s += Title; - s += " ,专属头衔过期时间:"; - s += to_string(ExpireTime); - s += " ,允许修改名片:"; - s += to_string(canEditGroupNick); - s += "}"; - return s; -} - -FriendInfo::FriendInfo(Unpack p) -{ - QQID = p.getLong(); - nick = p.getstring(); - remark = p.getstring(); -} - -std::string FriendInfo::tostring() const -{ - return remark + '(' + std::to_string(QQID) + ')' + ((remark == nick) ? "" : "(" + nick + ")"); -} - -//增加运行日志 -int CQ::addLog(const int Priorty, const char* Type, const char* Content) noexcept -{ - return lasterr = CQ_addLog(getAuthCode(), Priorty, Type, Content); -} - -//发送好友消息 -int CQ::sendPrivateMsg(const long long QQ, const char* msg) noexcept -{ - return CQ_sendPrivateMsg(getAuthCode(), QQ, msg); -} - -//发送好友消息 -int CQ::sendPrivateMsg(const long long QQ, const std::string& msg) noexcept { return sendPrivateMsg(QQ, msg.c_str()); } - -//发送群消息 -int CQ::sendGroupMsg(const long long GroupID, const char* msg) noexcept -{ - return CQ_sendGroupMsg(getAuthCode(), GroupID, msg); -} - -//发送群消息 -int CQ::sendGroupMsg(const long long GroupID, const std::string& msg) noexcept -{ - return sendGroupMsg(GroupID, msg.c_str()); -} - -int CQ::sendDiscussMsg(const long long DiscussID, const char* msg) noexcept -{ - return CQ_sendDiscussMsg(getAuthCode(), DiscussID, msg); -} - -//发送讨论组消息 -int CQ::sendDiscussMsg(const long long DiscussID, const std::string& msg) noexcept -{ - return sendDiscussMsg(DiscussID, msg.c_str()); -} - -//发送赞 -int CQ::sendLike(const long long QQID, const int times) noexcept -{ - return lasterr = CQ_sendLikeV2(getAuthCode(), QQID, times); -} - - -//取Cookies (慎用,此接口需要严格授权) -const char* CQ::getCookies(const char* Domain) noexcept { return CQ_getCookiesV2(getAuthCode(), Domain); } - -//取Cookies (慎用,此接口需要严格授权) -const char* CQ::getCookies(const std::string& Domain) noexcept { return getCookies(Domain.c_str()); } - -//接收语音 -const char* CQ::getRecord(const char* file, const char* outformat) noexcept -{ - return CQ_getRecordV2(getAuthCode(), file, outformat); -} - -//接收语音 -std::string CQ::getRecord(const std::string& file, const std::string& outformat) noexcept -{ - return getRecord(file.c_str(), outformat.c_str()); -} - -//取CsrfToken (慎用,此接口需要严格授权) -int CQ::getCsrfToken() noexcept { return CQ_getCsrfToken(getAuthCode()); } - -//取应用目录 -const char* CQ::getAppDirectory() noexcept { return CQ_getAppDirectory(getAuthCode()); } - -//取登录QQ -long long CQ::getLoginQQ() noexcept { return CQ_getLoginQQ(getAuthCode()); } - -//取登录昵称 -const char* CQ::getLoginNick() noexcept { return CQ_getLoginNick(getAuthCode()); } - -//置群员移除 -int CQ::setGroupKick(const long long GroupID, const long long QQID, const CQBOOL refuseForever) noexcept -{ - return lasterr = CQ_setGroupKick(getAuthCode(), GroupID, QQID, refuseForever); -} - -//置群员禁言 -int CQ::setGroupBan(const long long GroupID, const long long QQID, const long long banTime) noexcept -{ - return lasterr = CQ_setGroupBan(getAuthCode(), GroupID, QQID, banTime); -} - -//置群管理员 -int CQ::setGroupAdmin(const long long GroupID, const long long QQID, const CQBOOL isAdmin) noexcept -{ - return lasterr = CQ_setGroupAdmin(getAuthCode(), GroupID, QQID, isAdmin); -} - -//置群成员专属头衔 -int CQ::setGroupSpecialTitle(const long long GroupID, const long long QQID, const char* Title, - const long long ExpireTime) noexcept -{ - return lasterr = CQ_setGroupSpecialTitle(getAuthCode(), GroupID, QQID, Title, ExpireTime); -} - -//置群成员专属头衔 -int CQ::setGroupSpecialTitle(const long long GroupID, const long long QQID, const std::string& Title, - const long long ExpireTime) noexcept -{ - return setGroupSpecialTitle(GroupID, QQID, Title.c_str(), ExpireTime); -} - -//置全群禁言 -int CQ::setGroupWholeBan(const long long GroupID, const CQBOOL isBan) noexcept -{ - return lasterr = CQ_setGroupWholeBan(getAuthCode(), GroupID, isBan); -} - -//置Anonymous群员禁言 -int CQ::setGroupAnonymousBan(const long long GroupID, const char* AnonymousToken, const long long banTime) noexcept -{ - return lasterr = CQ_setGroupAnonymousBan(getAuthCode(), GroupID, AnonymousToken, banTime); -} - -//置群Anonymous设置 -int CQ::setGroupAnonymous(const long long GroupID, const CQBOOL enableAnonymous) noexcept -{ - return lasterr = CQ_setGroupAnonymous(getAuthCode(), GroupID, enableAnonymous); -} - -//置群成员名片 -int CQ::setGroupCard(const long long GroupID, const long long QQID, const char* newGroupNick) noexcept -{ - return lasterr = CQ_setGroupCard(getAuthCode(), GroupID, QQID, newGroupNick); -} - -//置群成员名片 -int CQ::setGroupCard(const long long GroupID, const long long QQID, const std::string& newGroupNick) noexcept -{ - return setGroupCard(GroupID, QQID, newGroupNick.c_str()); -} - -//置群退出 -int CQ::setGroupLeave(const long long GroupID, const CQBOOL isDismiss) noexcept -{ - return lasterr = CQ_setGroupLeave(getAuthCode(), GroupID, isDismiss); -} - -//置讨论组退出 -int CQ::setDiscussLeave(const long long DiscussID) noexcept -{ - return lasterr = CQ_setDiscussLeave(getAuthCode(), DiscussID); -} - -//置好友添加请求 -int CQ::setFriendAddRequest(const char* RequestToken, const int ReturnType, const char* Remarks) noexcept -{ - return lasterr = CQ_setFriendAddRequest(getAuthCode(), RequestToken, ReturnType, Remarks); -} - -//置群添加请求 -int CQ::setGroupAddRequest(const char* RequestToken, const int RequestType, const int ReturnType, - const char* Reason) noexcept -{ - return lasterr = CQ_setGroupAddRequestV2(getAuthCode(), RequestToken, RequestType, ReturnType, Reason); -} - -//置致命错误提示 -int CQ::setFatal(const char* ErrorMsg) noexcept { return lasterr = CQ_setFatal(getAuthCode(), ErrorMsg); } - -//取群成员信息 (支持缓存) -GroupMemberInfo CQ::getGroupMemberInfo(const long long GroupID, const long long QQID, - const CQBOOL disableCache) noexcept -{ - return GroupMemberInfo(CQ_getGroupMemberInfoV2(getAuthCode(), GroupID, QQID, disableCache)); -} - -//取陌生人信息 (支持缓存) -StrangerInfo CQ::getStrangerInfo(const long long QQID, const CQBOOL DisableCache) noexcept -{ - return StrangerInfo(CQ_getStrangerInfo(getAuthCode(), QQID, DisableCache)); -} - -//取群成员列表 -std::vector CQ::getGroupMemberList(const long long GroupID) -{ - const char* ret = CQ_getGroupMemberList(getAuthCode(), GroupID); - if (!ret || ret[0] == '\0') return {}; - const string data(base64_decode(ret)); - if (data.empty())return {}; - vector infovector; - Unpack u(data); - auto i = u.getInt(); - while (--i && u.len() > 0) - { - auto tmp = u.getUnpack(); - infovector.emplace_back(tmp); - } - - return infovector; -} - -#include -//取群列表 -std::map CQ::getGroupList(bool disableCache) -{ - static std::map ret; - static time_t lastUpdateTime = 0; - - const time_t timeNow = time(nullptr); - if (!disableCache && timeNow - lastUpdateTime < 600 && !ret.empty()) - { - return ret; - } - - ret.clear(); - lastUpdateTime = timeNow; - - const char* src = CQ_getGroupList(getAuthCode()); - if (!src || src[0] == '\0') return {}; - const auto data(base64_decode(src)); // 解码 - if (data.empty())return {}; - Unpack pack(data); // 转换为Unpack - - pack.getInt(); //获取总群数, 返回值在此并没有用 - while (pack.len() > 0) - { - //如果还有剩余数据,就继续读取 - auto tep = pack.getUnpack(); //读取下一个群 - auto ID = tep.getLong(); //读取GroupID - const auto name = tep.getstring(); //读取群名称 - ret[ID] = name; //写入map - } - return ret; -} - -//取好友列表 -std::map CQ::getFriendList(bool disableCache) -{ - static std::map ret; - static time_t lastUpdateTime = 0; - - const time_t timeNow = time(nullptr); - if (!disableCache && timeNow - lastUpdateTime < 600 && !ret.empty()) - { - return ret; - } - - ret.clear(); - lastUpdateTime = timeNow; - - const char* src = CQ_getFriendList(getAuthCode(), false); - if (!src || src[0] == '\0') return {}; - const auto data(base64_decode(src)); // 解码 - if (data.empty())return {}; - Unpack pack(data); // 获取原始数据转换为Unpack - int Cnt = pack.getInt(); //获取总数 - while (Cnt--) - { - FriendInfo info(pack.getUnpack()); //读取 - ret[info.QQID] = info; //写入map - } - return ret; -} - -bool CQ::canSendImage() noexcept -{ - return CQ_canSendImage(getAuthCode()) > 0; -} - -bool CQ::canSendRecord() noexcept -{ - return CQ_canSendRecord(getAuthCode()) > 0; -} - -const char* CQ::getImage(const char* file) noexcept -{ - return CQ_getImage(getAuthCode(), file); -} - -const char* CQ::getImage(const std::string& file) noexcept -{ - return getImage(file.c_str()); -} - -int CQ::deleteMsg(const long long MsgId) noexcept -{ - return lasterr = CQ_deleteMsg(getAuthCode(), MsgId); -} - -const char* CQ::getlasterrmsg() noexcept -{ - switch (lasterr) - { - case 0: return "操作成功"; - case -1: return "请求发送失败"; - case -2: return "未收到服务器回复,可能未发送成功"; - case -3: return "消息过长或为空"; - case -4: return "消息解析过程异常"; - case -5: return "日志功能未启用"; - case -6: return "日志优先级错误"; - case -7: return "数据入库失败"; - case -8: return "不支持对系统帐号操作"; - case -9: return "帐号不在该群内,消息无法发送"; - case -10: return "该用户不存在/不在群内"; - case -11: return "数据错误,无法请求发送"; - case -12: return "不支持对Anonymous成员解除禁言"; - case -13: return "无法解析要禁言的Anonymous成员数据"; - case -14: return "由于未知原因,操作失败"; - case -15: return "群未开启Anonymous发言功能,或Anonymous帐号被禁言"; - case -16: return "帐号不在群内或网络错误,无法退出/解散该群"; - case -17: return "帐号为群主,无法退出该群"; - case -18: return "帐号非群主,无法解散该群"; - case -19: return "临时消息已失效或未建立"; - case -20: return "参数错误"; - case -21: return "临时消息已失效或未建立"; - case -22: return "获取QQ信息失败"; - case -23: return "找不到与目标QQ的关系,消息无法发送"; - case -99: return "您调用的功能无法在此版本上实现"; - case -101: return "应用过大"; - case -102: return "不是合法的应用"; - case -103: return "不是合法的应用"; - case -104: return "应用不存在公开的Information函数"; - case -105: return "无法载入应用信息"; - case -106: return "文件名与应用ID不同"; - case -107: return "返回信息解析错误"; - case -108: return "AppInfo返回的Api版本不支持直接加载,仅支持Api版本为9(及以上)的应用直接加载"; - case -109: return "AppInfo返回的AppID错误"; - case -110: return "缺失AppInfo返回的AppID对应的[Appid].json文件"; - case -111: return "[Appid].json文件内的AppID与其文件名不同"; - case -120: return "无Api授权接收函数(Initialize)"; - case -121: return "Api授权接收函数(Initialize)返回值非0"; - case -122: return "尝试恶意修改酷Q配置文件,将取消加载并关闭酷Q"; - case -150: return "无法载入应用信息"; - case -151: return "应用信息Json串解析失败,请检查Json串是否正确"; - case -152: return "Api版本过旧或过新"; - case -153: return "应用信息错误或存在缺失"; - case -154: return "Appid不合法"; - case -160: return "事件类型(Type)错误或缺失"; - case -161: return "事件函数(Function)错误或缺失"; - case -162: return "应用优先级不为10000、20000、30000、40000中的一个"; - case -163: return "事件类型(Api)不支持应用Api版本"; - case -164: return "应用Api版本大于8,但使用了新版本已停用的事件类型(Type):1(好友消息)、3(临时消息)"; - case -165: return "事件类型为2(群消息)、4(讨论组消息)、21(私聊消息),但缺少正则表达式(regex)的表达式部分(expression)"; - case -166: return "存在为空的正则表达式(regex)的key"; - case -167: return "存在为空的正则表达式(regex)的表达式部分(expression)"; - case -168: return "应用事件(event)id参数不存在或为0"; - case -169: return "应用事件(event)id参数有重复"; - case -180: return "应用状态(status)id参数不存在或为0"; - case -181: return "应用状态(status)period参数不存在或设置错误"; - case -182: return "应用状态(status)id参数有重复"; - case -201: return "无法载入应用,可能是应用文件已损坏"; - case -202: return "Api版本过旧或过新"; - case -997: return "应用未启用"; - case -998: return "应用调用在Auth声明之外的 酷Q Api。"; - default: return "未知错误"; - } -} diff --git a/CQSDKCPP/CQEVE.cpp b/CQSDKCPP/CQEVE.cpp deleted file mode 100644 index 4d279128..00000000 --- a/CQSDKCPP/CQEVE.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/* -此文件是所有EX事件的实现 -*/ -#include -#include -#include "CQEVE_ALL.h" - -#include "CQAPI_EX.h" -#include "CQTools.h" -#include "Unpack.h" - -#define WIN32_LEAN_AND_MEAN -#include - -using namespace CQ; - - -EVEMsg::EVEMsg(const int subType, const int msgId, const long long fromQQ, std::string message, const int font) noexcept - : subType(subType), msgId(msgId), fromQQ(fromQQ), message(move(message)), font(font) -{ -} - -//真实用户 -bool EVEMsg::isUser() const noexcept -{ - switch (fromQQ) - { - case 1000000: // 系统提示 - case 80000000: // 匿名 - return false; - default: - return true; - } -} - -EVEGroupMsg::EVEGroupMsg(const int subType, const int msgId, const long long fromGroup, const long long fromQQ, - const char* fromAnonymous, - const char* msg, const int font) noexcept - : EVEMsg(subType, msgId, fromQQ, msg, font), fromAnonymousInfo(), fromGroup(fromGroup), - fromAnonymousToken(fromAnonymous) -{ -} - -EVEGroupMsg::~EVEGroupMsg() noexcept { delete fromAnonymousInfo; } - - -bool EVEGroupMsg::isAnonymous() const noexcept { return fromQQ == 80000000; } - - -AnonymousInfo& EVEGroupMsg::getFromAnonymousInfo() noexcept(false) -{ - if (isAnonymous()) - return - fromAnonymousInfo != nullptr - ? *fromAnonymousInfo - : *(fromAnonymousInfo = new AnonymousInfo(fromAnonymousToken)); - throw std::logic_error("Trying to Get Anonymous Info from Non-anonymous User"); -} - -bool EVEGroupMsg::setGroupKick(const bool refusedAddAgain) const noexcept -{ - return !CQ::setGroupKick(fromGroup, fromQQ, refusedAddAgain); -} - -bool EVEGroupMsg::setGroupBan(const long long banTime) const noexcept -{ - if (isAnonymous()) - { - return !setGroupAnonymousBan(fromGroup, fromAnonymousToken, banTime); - } - return !CQ::setGroupBan(fromGroup, fromQQ, banTime); -} - -bool EVEGroupMsg::setGroupAdmin(const bool isAdmin) const noexcept -{ - return !CQ::setGroupAdmin(fromGroup, fromQQ, isAdmin); -} - -bool EVEGroupMsg::setGroupSpecialTitle(const std::string& Title, const long long ExpireTime) const noexcept -{ - return !CQ::setGroupSpecialTitle(fromGroup, fromQQ, Title, ExpireTime); -} - -bool EVEGroupMsg::setGroupWholeBan(const bool isBan) const noexcept -{ - return CQ::setGroupWholeBan(fromGroup, isBan) != 0; -} - -bool EVEGroupMsg::setGroupAnonymous(const bool enableAnonymous) const noexcept -{ - return CQ::setGroupAnonymous(fromGroup, enableAnonymous) != 0; -} - -bool EVEGroupMsg::setGroupCard(const std::string& newGroupNick) const noexcept -{ - return CQ::setGroupCard(fromGroup, fromQQ, newGroupNick) != 0; -} - -bool EVEGroupMsg::setGroupLeave(const bool isDismiss) const noexcept -{ - return CQ::setGroupLeave(fromGroup, isDismiss) != 0; -} - -GroupMemberInfo EVEGroupMsg::getGroupMemberInfo(const bool disableCache) const noexcept -{ - return CQ::getGroupMemberInfo(fromGroup, fromQQ, disableCache); -} - -std::vector EVEGroupMsg::getGroupMemberList() const noexcept -{ - return CQ::getGroupMemberList(fromGroup); -} - -EVEPrivateMsg::EVEPrivateMsg(const int subType, const int msgId, const long long fromQQ, const char* msg, - const int font) noexcept - : EVEMsg(subType, msgId, fromQQ, msg, font) -{ -} - -//来自好友 -bool EVEPrivateMsg::fromPrivate() const noexcept { return subType == 11; } - -//来自在线状态 -bool EVEPrivateMsg::fromOnlineStatus() const noexcept { return subType == 1; } - -//来自群临时 -bool EVEPrivateMsg::fromGroup() const noexcept { return subType == 2; } - -//来自讨论组临时 -bool EVEPrivateMsg::fromDiscuss() const noexcept { return subType == 3; } - -msg EVEPrivateMsg::sendMsg() const noexcept { return msg(fromQQ, msgtype::Private); } -msg EVEGroupMsg::sendMsg() const noexcept { return msg(fromGroup, msgtype::Group); } -msg EVEDiscussMsg::sendMsg() const noexcept { return msg(fromQQ, msgtype::Discuss); } - -int EVEPrivateMsg::sendMsg(const char* msg) const noexcept { return sendPrivateMsg(fromQQ, msg); } -int EVEPrivateMsg::sendMsg(const std::string& msg) const noexcept { return sendPrivateMsg(fromQQ, msg); } -int EVEGroupMsg::sendMsg(const char* msg) const noexcept { return sendGroupMsg(fromGroup, msg); } -int EVEGroupMsg::sendMsg(const std::string& msg) const noexcept { return sendGroupMsg(fromGroup, msg); } -int EVEDiscussMsg::sendMsg(const char* msg) const noexcept { return sendDiscussMsg(fromDiscuss, msg); } -int EVEDiscussMsg::sendMsg(const std::string& msg) const noexcept { return sendDiscussMsg(fromDiscuss, msg); } - -EVEDiscussMsg::EVEDiscussMsg(const int subType, const int msgId, const long long fromDiscuss, const long long fromQQ, - const char* msg, const int font) noexcept - : EVEMsg(subType, msgId, fromQQ, msg, font), fromDiscuss(fromDiscuss) -{ -} - -bool EVEDiscussMsg::leave() const noexcept { return !setDiscussLeave(fromDiscuss); } - -void EVEStatus::color_green() noexcept { color = 1; } -void EVEStatus::color_orange() noexcept { color = 2; } -void EVEStatus::color_red() noexcept { color = 3; } -void EVEStatus::color_crimson() noexcept { color = 4; } -void EVEStatus::color_black() noexcept { color = 5; } -void EVEStatus::color_gray() noexcept { color = 6; } - -std::string CQ::statusEVEreturn(EVEStatus& eve) noexcept -{ - Unpack pack; - std::string _ret = pack.add(eve.data).add(eve.dataf).add(eve.color).getAll(); - _ret = base64_encode(_ret); - return _ret; -} - -EVERequest::EVERequest(const int sendTime, const long long fromQQ, const char* msg, const char* responseFlag) noexcept - : sendTime(sendTime), fromQQ(fromQQ), msg(msg), responseFlag(responseFlag) -{ -} - -EVERequestAddFriend::EVERequestAddFriend(const int subType, const int sendTime, const long long fromQQ, const char* msg, - const char* responseFlag) noexcept - : EVERequest(sendTime, fromQQ, msg, responseFlag), subType(subType), fromGroup(0) -{ -} - -void EVERequestAddFriend::pass(const std::string& msg) const noexcept -{ - setFriendAddRequest(responseFlag, RequestAccepted, msg.c_str()); -} - -void EVERequestAddFriend::fail(const std::string& msg) const noexcept -{ - setFriendAddRequest(responseFlag, RequestRefused, msg.c_str()); -} - -EVERequestAddGroup::EVERequestAddGroup(const int subType, const int sendTime, const long long fromGroup, - const long long fromQQ, - const char* const msg, const char* const responseFlag) noexcept - : EVERequest(sendTime, fromQQ, msg, responseFlag), subType(subType), fromGroup(fromGroup) -{ -} - -void EVERequestAddGroup::pass(const std::string& msg) const noexcept -{ - setGroupAddRequest(responseFlag, subType, RequestAccepted, msg.c_str()); -} - -void EVERequestAddGroup::fail(const std::string& msg) const noexcept -{ - setGroupAddRequest(responseFlag, subType, RequestRefused, msg.c_str()); -} - -AnonymousInfo::AnonymousInfo(const char* msg) noexcept -{ - if (msg != nullptr && msg[0] != '\0') - { - Unpack p(base64_decode(msg)); - AID = p.getLong(); - AnonymousNick = p.getstring(); - } -} - -regexMsg::regexMsg(const std::string& msg) -{ - Unpack msgs(base64_decode(msg)); - auto len = msgs.getInt(); //获取参数数量 - while (len-- > 0) - { - auto tep = msgs.getUnpack(); - const auto key = tep.getstring(); - const auto value = tep.getstring(); - if (key.empty()) - { - return; - } - regexMap[key] = value; - } -} - -std::string regexMsg::get(const std::string& key) -{ - return regexMap[key]; -} - -std::string regexMsg::operator[](const std::string& key) -{ - return regexMap[key]; -} diff --git a/CQSDKCPP/CQTools.cpp b/CQSDKCPP/CQTools.cpp index b2287fd2..c1262ba9 100644 --- a/CQSDKCPP/CQTools.cpp +++ b/CQSDKCPP/CQTools.cpp @@ -138,5 +138,6 @@ std::string& msg_decode(std::string& s, const bool isCQ) msg_replace(s, "]", "]"); msg_replace(s, ",", "\t"); msg_replace(s, "&", "&"); + //msg_replace(s, " ", " "); return s; } diff --git a/CQSDKCPP/CQstream.cpp b/CQSDKCPP/CQstream.cpp deleted file mode 100644 index 3de9fcf0..00000000 --- a/CQSDKCPP/CQstream.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/*此文件是下面三个头文件的实现*/ -#include -#include -#include "CQAPI_EX.h" -#include "bufstream.h" -#include "CQLogger.h" -#include "CQMsgSend.h" -#include "CQconstant.h" - -using namespace CQ; -using namespace std; - -logger::logger(std::string title) noexcept : title(std::move(title)) -{ -} - -void logger::setTitle(std::string title) noexcept { this->title = std::move(title); } - -void logger::Debug(const std::string& msg) const noexcept { Debug(msg.c_str()); } - -void logger::Info(const std::string& msg) const noexcept { Info(msg.c_str()); } - -void logger::InfoSuccess(const std::string& msg) const noexcept { InfoSuccess(msg.c_str()); } - -void logger::InfoRecv(const std::string& msg) const noexcept { InfoRecv(msg.c_str()); } - -void logger::InfoSend(const std::string& msg) const noexcept { InfoSend(msg.c_str()); } - -void logger::Warning(const std::string& msg) const noexcept { Warning(msg.c_str()); } - -void logger::Error(const std::string& msg) const noexcept { Error(msg.c_str()); } - -void logger::Fatal(const std::string& msg) const noexcept { Fatal(msg.c_str()); } - -void logger::Debug(const char* const msg) const noexcept { addLog(Log_Debug, title.c_str(), msg); } - -void logger::Info(const char* const msg) const noexcept { addLog(Log_Info, title.c_str(), msg); } - -void logger::InfoSuccess(const char* const msg) const noexcept { addLog(Log_InfoSuccess, title.c_str(), msg); } - -void logger::InfoRecv(const char* const msg) const noexcept { addLog(Log_InfoRecv, title.c_str(), msg); } - -void logger::InfoSend(const char* const msg) const noexcept { addLog(Log_InfoSend, title.c_str(), msg); } - -void logger::Warning(const char* const msg) const noexcept { addLog(Log_Warning, title.c_str(), msg); } - -void logger::Error(const char* const msg) const noexcept { addLog(Log_Error, title.c_str(), msg); } - -void logger::Fatal(const char* const msg) const noexcept { addLog(Log_Fatal, title.c_str(), msg); } - -logstream logger::Debug() const noexcept { return logstream(title, Log_Debug); } - -logstream logger::Info() const noexcept { return logstream(title, Log_Info); } - -logstream logger::InfoSuccess() const noexcept { return logstream(title, Log_InfoSuccess); } - -logstream logger::InfoRecv() const noexcept { return logstream(title, Log_InfoRecv); } - -logstream logger::InfoSend() const noexcept { return logstream(title, Log_InfoSend); } - -logstream logger::Warning() const noexcept { return logstream(title, Log_Warning); } - -logstream logger::Error() const noexcept { return logstream(title, Log_Error); } - -logstream logger::Fatal() const noexcept { return logstream(title, Log_Fatal); } - -void CQ::send(CQstream& log) noexcept -{ - log.send(); - log.clear(); -} - -void CQ::flush(CQstream& log) noexcept { log.flush(); } -void CQ::endl(CQstream& log) noexcept { log << "\r\n"; } - -void CQstream::clear() noexcept { buf.clear(); } - -CQstream& CQstream::append(const string& s) noexcept -{ - buf += s; - return *this; -} - -CQstream& CQstream::operator<<(const string& s) noexcept { return append(s); } - -CQstream& CQstream::append(const int& i) noexcept -{ - buf += to_string(i); - return *this; -} - -CQstream& CQstream::operator<<(const int& i) noexcept { return append(i); } - -CQstream& CQstream::append(const size_t& i) noexcept -{ - buf += to_string(i); - return *this; -} - -CQstream& CQstream::operator<<(const size_t& i) noexcept { return append(i); } - -CQstream& CQstream::append(const long long& l) noexcept -{ - buf += to_string(l); - return *this; -} - -CQstream& CQstream::operator<<(const long long& l) noexcept { return append(l); } - -CQstream& CQstream::append(const char* const c) noexcept -{ - buf += c; - return *this; -} - -CQstream& CQstream::operator<<(const char* const c) noexcept { return append(c); } - -CQstream& CQstream::operator<<(void (*control)(CQstream&)) -{ - control(*this); - return *this; -} - -void CQstream::flush() noexcept { send(); } - -inline CQstream::~CQstream() noexcept = default; - -inline logstream::logstream(std::string title, const int Log_flag) noexcept : flag(Log_flag), title(std::move(title)) -{ -} - -void logstream::send() noexcept -{ - if (buf.empty())return; - addLog(flag, title.c_str(), buf.c_str()); -} - -msg::msg(const long long GroupID_Or_QQID, const msgtype Type) noexcept : ID(GroupID_Or_QQID), - subType(static_cast(Type)) -{ -} - -msg::msg(const long long GroupID_Or_QQID, const int Type) noexcept : ID(GroupID_Or_QQID), subType(Type) -{ -} - -void msg::send() noexcept -{ - if (buf.empty())return; - switch (subType) - { - case static_cast(msgtype::Private): //好友 - sendPrivateMsg(ID, buf); - break; - case static_cast(msgtype::Group): //群 - sendGroupMsg(ID, buf); - break; - case static_cast(msgtype::Discuss): //讨论组 - sendDiscussMsg(ID, buf); - break; - default: - assert(false); - break; - } -} diff --git a/Dice.cppcheck b/Dice.cppcheck deleted file mode 100644 index 837da140..00000000 --- a/Dice.cppcheck +++ /dev/null @@ -1,17 +0,0 @@ - - - Dice-cppcheck-build-dir - win32A - Dice.sln - false - true - false - 10 - - Debug - Release - - - windows - - diff --git a/Dice.sln b/Dice.sln deleted file mode 100644 index c9314f5b..00000000 --- a/Dice.sln +++ /dev/null @@ -1,31 +0,0 @@ -锘 -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27428.2015 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "com.w4123.dice", "Dice\Dice.vcxproj", "{BA051175-B8E8-4104-9DD9-B9E225738C42}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BA051175-B8E8-4104-9DD9-B9E225738C42}.Debug|x64.ActiveCfg = Debug|x64 - {BA051175-B8E8-4104-9DD9-B9E225738C42}.Debug|x64.Build.0 = Debug|x64 - {BA051175-B8E8-4104-9DD9-B9E225738C42}.Debug|x86.ActiveCfg = Debug|Win32 - {BA051175-B8E8-4104-9DD9-B9E225738C42}.Debug|x86.Build.0 = Debug|Win32 - {BA051175-B8E8-4104-9DD9-B9E225738C42}.Release|x64.ActiveCfg = Release|x64 - {BA051175-B8E8-4104-9DD9-B9E225738C42}.Release|x64.Build.0 = Release|x64 - {BA051175-B8E8-4104-9DD9-B9E225738C42}.Release|x86.ActiveCfg = Release|Win32 - {BA051175-B8E8-4104-9DD9-B9E225738C42}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {A83189AF-8958-4DC7-A9D3-3C639C8AEC79} - EndGlobalSection -EndGlobal diff --git a/Dice.sln.DotSettings b/Dice.sln.DotSettings deleted file mode 100644 index 07531f05..00000000 --- a/Dice.sln.DotSettings +++ /dev/null @@ -1,4 +0,0 @@ -锘 - True - True - True \ No newline at end of file diff --git a/Dice.sln.DotSettings.user b/Dice.sln.DotSettings.user deleted file mode 100644 index 99e3685b..00000000 --- a/Dice.sln.DotSettings.user +++ /dev/null @@ -1,3 +0,0 @@ -锘 - True - True \ No newline at end of file diff --git a/Dice/APPINFO.h b/Dice/APPINFO.h index 24c9bab6..4cc99450 100644 --- a/Dice/APPINFO.h +++ b/Dice/APPINFO.h @@ -1,3 +1,5 @@ +#pragma once + /* * _______ ________ ________ ________ __ * | __ \ |__ __| | _____| | _____| | | @@ -24,7 +26,6 @@ // 此文件用于酷Q获取对应的APPINFO // 请勿修改 -#pragma once #ifndef DICE_APPINFO #define DICE_APPINFO diff --git a/Dice/BlackListManager.cpp b/Dice/BlackListManager.cpp index 3de43428..2165e984 100644 --- a/Dice/BlackListManager.cpp +++ b/Dice/BlackListManager.cpp @@ -11,12 +11,12 @@ #include "BlackListManager.h" #include "Jsonio.h" #include "STLExtern.hpp" +#include "DDAPI.h" #include "DiceEvent.h" #include "DiceConsole.h" #include "DiceNetwork.h" using namespace std; -using namespace CQ; using namespace nlohmann; using Mark = DDBlackMark; @@ -27,7 +27,7 @@ using Factory = DDBlackMarkFactory; int isReliable(long long QQID) { if (!QQID)return 0; - if (QQID == console.master())return 5; + if (QQID == console.master())return 255; if (trustedQQ(QQID) > 2)return trustedQQ(QQID) - 1; if (mDiceList.count(QQID)) { @@ -46,24 +46,29 @@ void checkGroupWithBlackQQ(const DDBlackMark& mark, long long llQQ) string strNotice; for (auto& [id, grp] : ChatList) { - if (grp.isset("已退") || grp.isset("忽略") || !grp.isGroup)continue; - if (getGroupMemberInfo(id, llQQ).QQID == llQQ) - { + int authSelf; + if (grp.isset("已退") || grp.isset("未进") || grp.isset("忽略") || !grp.isGroup + || !(authSelf = DD::getGroupAuth(id, console.DiceMaid, 0)))continue; + if (DD::isGroupMember(grp.ID, llQQ, false)) { strNotice = printGroup(id); - if (grp.isset("免黑")) - { - if (mark.isSource(console.DiceMaid) && !mark.isType("local"))sendGroupMsg(id, mark.warning()); + if (grp.isset("协议无效")) { + strNotice += "群协议无效"; + } + else if (grp.isset("免黑")) { + if (mark.isSource(console.DiceMaid) && !mark.isType("local"))DD::sendGroupMsg(id, mark.warning()); strNotice += "群免黑"; } - else if (getGroupMemberInfo(id, llQQ).permissions < getGroupMemberInfo(id, getLoginQQ()).permissions) - { + else if (int authBlack{ DD::getGroupAuth(id,llQQ,0) }; authBlack < 1 || authSelf < 1) { + strNotice += "群权限获取失败"; + } + else if (authBlack < authSelf) { if (mark.isSource(console.DiceMaid && !mark.isType("local")))AddMsgToQueue( mark.warning(), id, msgtype::Group); strNotice += "对方群权限较低"; } - else if (getGroupMemberInfo(id, llQQ).permissions > getGroupMemberInfo(id, getLoginQQ()).permissions) + else if (authSelf > authBlack) { - sendGroupMsg(id, mark.warning()); + DD::sendGroupMsg(id, mark.warning()); grp.leave("发现新增黑名单管理员" + printQQ(llQQ) + "\n" + GlobalMsg["strSelfName"] + "将预防性退群"); strNotice += "对方群权限较高,已退群"; this_thread::sleep_for(1s); @@ -76,7 +81,7 @@ void checkGroupWithBlackQQ(const DDBlackMark& mark, long long llQQ) } else if (console["LeaveBlackQQ"]) { - sendGroupMsg(id, mark.warning()); + DD::sendGroupMsg(id, mark.warning()); grp.leave("发现新增黑名单成员" + printQQ(llQQ) + "(同等群权限)\n" + GlobalMsg["strSelfName"] + "将预防性退群"); strNotice += "已退群"; this_thread::sleep_for(1s); @@ -98,6 +103,8 @@ std::queue warningQueue; // 消息发送队列锁 mutex warningMutex; +bool isLoadingExtern = false; + void AddWarning(const string& msg, long long DiceQQ, long long fromGroup) { lock_guard lock_queue(warningMutex); @@ -186,6 +193,13 @@ DDBlackMark::DDBlackMark(void* pJson) else if (!credit_limit.count(j["type"].get())) { return; } else type = j["type"].get(); } + if (j.count("wid")) { + j["wid"].get_to(wid); + if (j.count("isErased")) { + isClear = j["isErased"].get(); + isAdd = !isClear; + } + } if (j.count("fromGroup"))fromGroup = {j["fromGroup"].get(), isAdd}; if (j.count("fromQQ"))fromQQ = {j["fromQQ"].get(), isAdd}; if (j.count("inviterQQ"))inviterQQ = {j["inviterQQ"].get(), isAdd}; @@ -194,12 +208,14 @@ DDBlackMark::DDBlackMark(void* pJson) if (j.count("DiceMaid"))DiceMaid = j["DiceMaid"].get(); if (j.count("masterQQ"))masterQQ = j["masterQQ"].get(); - if (j.count("wid"))wid = j["wid"].get(); if (j.count("danger")) { danger = j["danger"].get(); if (danger < 1)return; } + else { + danger = (type == "ruler") ? 3 : 2; + } if (j.count("time"))time = UTF8toGBK(j["time"].get()); if (j.count("note"))note = UTF8toGBK(j["note"].get()); @@ -221,7 +237,7 @@ DDBlackMark::DDBlackMark(void* pJson) } catch (...) { - console.log("解析黑名单json失败!", 0b10, printSTNow()); + console.log("解析黑名单json失败!", 0, printSTNow()); return; } } @@ -235,7 +251,7 @@ DDBlackMark::DDBlackMark(const string& strWarning) } catch (...) { - console.log("解析黑名单json失败!", 0b10, printSTNow()); + console.log("解析黑名单json失败!", 0, printSTNow()); return; } } @@ -317,7 +333,7 @@ void DDBlackMark::fill_note() } else if (type == "spam") { - note = time + " " + (fromQQ.first ? printQQ(fromQQ.first) : "") + "对" + printQQ(DiceMaid) + "刷屏"; + note = time + " " + (fromQQ.first ? printQQ(fromQQ.first) : "") + "对" + printQQ(DiceMaid) + "高频发送指令"; } } @@ -370,14 +386,12 @@ bool DDBlackMark::isSource(long long qq) const { return DiceMaid == qq || masterQQ == qq; } - -bool DDBlackMark::is_remit() const +void DDBlackMark::check_remit() { - if (fromGroup.first && groupset(fromGroup.first, "免黑") > 0)return true; - if (fromQQ.first && trustedQQ(fromQQ.first) > 1)return true; - if (inviterQQ.first && trustedQQ(inviterQQ.first) > 1)return true; - if (ownerQQ.first && trustedQQ(ownerQQ.first) > 1)return true; - return false; + if (fromGroup.first && groupset(fromGroup.first, "免黑") > 0)erase(); + if (fromQQ.first && trustedQQ(fromQQ.first) > 1)erase(); + if (inviterQQ.first && trustedQQ(inviterQQ.first) > 1)inviterQQ = { 0,false }; + if (ownerQQ.first && trustedQQ(ownerQQ.first) > 1)ownerQQ = { 0,false }; } void DDBlackMark::erase() @@ -388,13 +402,17 @@ void DDBlackMark::erase() void DDBlackMark::upload() { - std::string frmdata = "fromQQ=" + std::to_string(fromQQ.first) + "&fromGroup=" + std::to_string(fromGroup.first) + - "&DiceMaid=" + std::to_string(DiceMaid) + "&masterQQ=" + std::to_string(masterQQ) + "&type=" + type + "&time=" + - time + "¬e=" + UrlEncode(GBKtoUTF8(note)); - string temp; - Network::POST("shiki.stringempty.xyz", "/DiceCloud/warning_upload.php", 80, frmdata.data(), temp); - if (isdigit(static_cast(temp[0])))wid = stoi(temp); - else if (temp == "denied")erase(); + std::string info = "&masterQQ=" + std::to_string(masterQQ) + "&time=" + time + "¬e=" + UrlEncode(GBKtoUTF8(note)); + int res{ DD::uploadBlack(console.DiceMaid, fromQQ.first, fromGroup.first, type, info) }; + if (!res) { + erase(); + } + else if (res < 0) { + console.log("上传不良记录失败:" + info, 0b1); + } + else { + wid = res; + } } int DDBlackMark::check_cloud() @@ -408,7 +426,13 @@ int DDBlackMark::check_cloud() console.log("云记录访问失败" + temp, 0); return -2; } - if (temp[0] == '+') + if (temp[0] == '?') + { + console.log("匹配到未确认云记录:wid" + temp, 0); + wid = stoi(temp.substr(1)); + return 1; + } + else if (temp[0] == '+') { console.log("匹配到未注销云记录:wid" + temp, 0); wid = stoi(temp.substr(1)); @@ -425,350 +449,357 @@ int DDBlackMark::check_cloud() int DDBlackManager::find(const DDBlackMark& mark) { - std::lock_guard lock_queue(blacklistMutex); - unordered_set sRange; - if (mark.wid && mCloud.count(mark.wid)) return mCloud[mark.wid]; - if (!mark.time.empty()) - { - unordered_set sTimeRange = sTimeEmpty; - for (auto& [key,id] : multi_range(mTimeIndex, mark.time)) + std::lock_guard lock_queue(blacklistMutex); + unordered_set sRange; + if (mark.wid && mCloud.count(mark.wid)) return mCloud[mark.wid]; + if (mark.wid){ + if (mCloud.count(mark.wid)) return mCloud[mark.wid]; + sRange = sIDEmpty; + } + if (mark.time.length() == 19) + { + unordered_set sTimeRange = sTimeEmpty; + for (auto& [key,id] : multi_range(mTimeIndex, mark.time)) { - sTimeRange.insert(id); - } - if (sRange.empty()) + sTimeRange.insert(id); + } + if (sRange.empty()) { - sRange.swap(sTimeRange); - } - else + sRange.swap(sTimeRange); + } + else { - unordered_set sInter; + unordered_set sInter; - for (const auto& ele : sRange) - { - if (sTimeRange.count(ele)) - { - sInter.insert(ele); + for (const auto& ele : sRange) + { + if (sTimeRange.count(ele)) + { + sInter.insert(ele); } } - //std::set_intersection(sRange.begin(), sRange.end(), sTimeRange.begin(), sTimeRange.end(), std::inserter(sInter, sInter.begin())); - if (sInter.empty())return -1; - sRange.swap(sInter); - } - } - if (mark.fromGroup.first) - { - unordered_set sGroupRange = sGroupEmpty; - for (auto& [key, id] : multi_range(mGroupIndex, mark.fromGroup.first)) + //std::set_intersection(sRange.begin(), sRange.end(), sTimeRange.begin(), sTimeRange.end(), std::inserter(sInter, sInter.begin())); + if (sInter.empty())return -1; + sRange.swap(sInter); + } + } + if (mark.fromGroup.first) + { + unordered_set sGroupRange = sGroupEmpty; + for (auto& [key, id] : multi_range(mGroupIndex, mark.fromGroup.first)) { - sGroupRange.insert(id); - } - if (sGroupRange.empty())return -1; - if (sRange.empty()) + sGroupRange.insert(id); + } + if (sGroupRange.empty())return -1; + if (sRange.empty()) { - sRange.swap(sGroupRange); - } - else + sRange.swap(sGroupRange); + } + else { - unordered_set sInter; - - // O(n) - for (const auto& ele : sRange) - { - if (sGroupRange.count(ele)) - { - sInter.insert(ele); + unordered_set sInter; + + // O(n) + for (const auto& ele : sRange) + { + if (sGroupRange.count(ele)) + { + sInter.insert(ele); } } - //std::set_intersection(sRange.begin(), sRange.end(), sGroupRange.begin(), sGroupRange.end(), std::inserter(sInter, sInter.begin())); - if (sInter.empty())return -1; - sRange.swap(sInter); - } - } - if (mark.fromQQ.first) - { - unordered_set sQQRange = sQQEmpty; - for (auto& [key, id] : multi_range(mQQIndex, mark.fromQQ.first)) + //std::set_intersection(sRange.begin(), sRange.end(), sGroupRange.begin(), sGroupRange.end(), std::inserter(sInter, sInter.begin())); + if (sInter.empty())return -1; + sRange.swap(sInter); + } + } + if (mark.fromQQ.first) + { + unordered_set sQQRange = sQQEmpty; + for (auto& [key, id] : multi_range(mQQIndex, mark.fromQQ.first)) { - if (Enabled)console.log("匹配用户记录" + to_string(id), 0); - sQQRange.insert(id); - } - if (sQQRange.empty())return -1; - if (sRange.empty()) + if (Enabled)console.log("匹配用户记录" + to_string(id), 0); + sQQRange.insert(id); + } + if (sQQRange.empty())return -1; + if (sRange.empty()) { - sRange.swap(sQQRange); - } - else + sRange.swap(sQQRange); + } + else { - unordered_set sInter; + unordered_set sInter; - for (const auto& ele : sRange) - { - if (sQQRange.count(ele)) - { - sInter.insert(ele); + for (const auto& ele : sRange) + { + if (sQQRange.count(ele)) + { + sInter.insert(ele); } } - //std::set_intersection(sRange.begin(), sRange.end(), sQQRange.begin(), sQQRange.end(), std::inserter(sInter, sInter.begin())); - if (sInter.empty())return -1; - sRange.swap(sInter); - } - } - for (auto i : sRange) - { - if (vBlackList[i].isSame(mark))return i; - } - return -1; + //std::set_intersection(sRange.begin(), sRange.end(), sQQRange.begin(), sQQRange.end(), std::inserter(sInter, sInter.begin())); + if (sInter.empty())return -1; + sRange.swap(sInter); + } + } + for (auto i : sRange) + { + if (vBlackList[i].isSame(mark))return i; + } + return -1; } DDBlackMark& DDBlackMark::operator<<(const DDBlackMark& mark) { - // int delta_danger = mark.danger - danger; - if (type == "null" && mark.type != "null")type = mark.type; - if (time.empty() && !mark.time.empty()) - { - time = mark.time; - } - if (note != mark.note && - (note.empty() || count_char(note, '?') > count_char(mark.note, '?') || note.length() < mark.note.length()) - ) - { - note = mark.note; - } - if (mark.fromGroup.first) - { - fromGroup = mark.fromGroup; - } - if (mark.fromQQ.first) - { - fromQQ = mark.fromQQ; - } - if (mark.inviterQQ.first) - { - inviterQQ = mark.inviterQQ; - } - if (mark.ownerQQ.first) - { - ownerQQ = mark.ownerQQ; - } - if (!DiceMaid && mark.DiceMaid)DiceMaid = mark.DiceMaid; - if (!masterQQ && mark.masterQQ)masterQQ = mark.masterQQ; - //save comment if the mark changed at this update - if (!mark.comment.empty()) - { - comment = mark.comment; - } - return *this; + // int delta_danger = mark.danger - danger; + if (type == "null" && mark.type != "null")type = mark.type; + if (time.empty() && !mark.time.empty()) + { + time = mark.time; + } + if (note != mark.note && + (note.empty() || count_char(note, '?') > count_char(mark.note, '?') || note.length() < mark.note.length()) + ) + { + note = mark.note; + } + if (mark.fromGroup.first) + { + fromGroup = mark.fromGroup; + } + if (mark.fromQQ.first) + { + fromQQ = mark.fromQQ; + } + if (mark.inviterQQ.first) + { + inviterQQ = mark.inviterQQ; + } + if (mark.ownerQQ.first) + { + ownerQQ = mark.ownerQQ; + } + if (!DiceMaid && mark.DiceMaid)DiceMaid = mark.DiceMaid; + if (!masterQQ && mark.masterQQ)masterQQ = mark.masterQQ; + //save comment if the mark changed at this update + if (!mark.comment.empty()) + { + comment = mark.comment; + } + return *this; } -void DDBlackManager::insert(DDBlackMark& ex_mark) +bool DDBlackManager::insert(DDBlackMark& ex_mark) { - std::lock_guard lock_queue(blacklistMutex); - unsigned id = vBlackList.size(); - vBlackList.push_back(ex_mark); - DDBlackMark& mark(vBlackList[id]); - if (mark.wid)mCloud[mark.wid] = id; - else sIDEmpty.insert(id); - if (mark.time.empty())sTimeEmpty.insert(id); - else mTimeIndex.emplace(mark.time, id); - if (mark.fromGroup.first) - { - mGroupIndex.emplace(mark.fromGroup.first, id); - if (mark.fromGroup.second) + std::lock_guard lock_queue(blacklistMutex); + unsigned id = vBlackList.size(); + vBlackList.push_back(ex_mark); + DDBlackMark& mark(vBlackList[id]); + if (mark.wid)mCloud[mark.wid] = id; + else sIDEmpty.insert(id); + if (mark.time.length() == 19)mTimeIndex.emplace(mark.time, id); + else sTimeEmpty.insert(id); + if (mark.fromGroup.first) + { + mGroupIndex.emplace(mark.fromGroup.first, id); + if (mark.fromGroup.second) { - up_group_danger(mark.fromGroup.first, mark); - } - } - else + up_group_danger(mark.fromGroup.first, mark); + } + } + else { - sGroupEmpty.insert(id); - } - if (mark.fromQQ.first) + sGroupEmpty.insert(id); + } + if (mark.fromQQ.first) { - mQQIndex.emplace(mark.fromQQ.first, id); - if (mark.fromQQ.second) + mQQIndex.emplace(mark.fromQQ.first, id); + if (mark.fromQQ.second) { - up_qq_danger(mark.fromQQ.first, mark); - } - } - else + up_qq_danger(mark.fromQQ.first, mark); + } + } + else { - sQQEmpty.insert(id); - } - if (mark.inviterQQ.first) + sQQEmpty.insert(id); + } + if (mark.inviterQQ.first) { - mQQIndex.emplace(mark.inviterQQ.first, id); - if (mark.inviterQQ.second) + mQQIndex.emplace(mark.inviterQQ.first, id); + if (mark.inviterQQ.second) { - up_qq_danger(mark.inviterQQ.first, mark); - } - } - if (mark.ownerQQ.first) + up_qq_danger(mark.inviterQQ.first, mark); + } + } + if (mark.ownerQQ.first) { - mQQIndex.emplace(mark.ownerQQ.first, id); - if (mark.ownerQQ.second) + mQQIndex.emplace(mark.ownerQQ.first, id); + if (mark.ownerQQ.second) { - up_qq_danger(mark.ownerQQ.first, mark); - } - } - if (Enabled)blacklist->saveJson(DiceDir + "\\conf\\BlackList.json"); + up_qq_danger(mark.ownerQQ.first, mark); + } + } + if (Enabled && !isLoadingExtern)blacklist->saveJson(DiceDir / "conf" / "BlackList.json"); + return !mark.isClear; } bool DDBlackManager::update(DDBlackMark& mark, unsigned int id, int credit = 5) { - std::lock_guard lock_queue(blacklistMutex); - DDBlackMark& old_mark = vBlackList[id]; - int delta_danger = mark.danger - old_mark.danger; - bool isUpdated = false; - if (delta_danger) - { - old_mark.danger = mark.danger; - isUpdated = true; - } - if (mark.wid && !old_mark.wid) - { - sIDEmpty.erase(id); - old_mark.wid = mark.wid; - mCloud.emplace(old_mark.wid, id); - isUpdated = true; - } - if (old_mark.type == "null" && mark.type != "null")old_mark.type = mark.type; - if (old_mark.time.length() < mark.time.length()) - { - old_mark.time = mark.time; - sTimeEmpty.erase(id); - mTimeIndex.emplace(old_mark.time, id); - } - if (old_mark.note != mark.note && - (old_mark.note.empty() || count_char(old_mark.note, '?') > count_char(mark.note, '?') || old_mark.note.length() + std::lock_guard lock_queue(blacklistMutex); + DDBlackMark& old_mark = vBlackList[id]; + int delta_danger = mark.danger - old_mark.danger; + bool isUpdated = false; + if (delta_danger) + { + old_mark.danger = mark.danger; + isUpdated = true; + } + if (mark.wid && !old_mark.wid) + { + sIDEmpty.erase(id); + old_mark.wid = mark.wid; + mCloud.emplace(old_mark.wid, id); + isUpdated = true; + } + if (old_mark.type == "null" && mark.type != "null")old_mark.type = mark.type; + if (old_mark.time.length() < mark.time.length() && mark.time.length() <= 19) { + old_mark.time = mark.time; + sTimeEmpty.erase(id); + if (mark.time.length() == 19)mTimeIndex.emplace(old_mark.time, id); + } + if (old_mark.note != mark.note && + (old_mark.note.empty() || count_char(old_mark.note, '?') > count_char(mark.note, '?') || old_mark.note.length() < mark.note.length()) - ) + ) { - old_mark.note = mark.note; - } - if (mark.fromGroup.first) + old_mark.note = mark.note; + } + if (mark.fromGroup.first) { - if (!old_mark.fromGroup.first) + if (!old_mark.fromGroup.first) { - sGroupEmpty.erase(id); - mGroupIndex.emplace(mark.fromGroup.first, id); - if (mark.fromGroup.second) + sGroupEmpty.erase(id); + mGroupIndex.emplace(mark.fromGroup.first, id); + if (mark.fromGroup.second) { - up_group_danger(mark.fromGroup.first, mark); - } - old_mark.fromGroup = mark.fromGroup; - isUpdated = true; - } - else if (mark.fromGroup.second != old_mark.fromGroup.second) + up_group_danger(mark.fromGroup.first, mark); + } + old_mark.fromGroup = mark.fromGroup; + isUpdated = true; + } + else if (mark.fromGroup.second != old_mark.fromGroup.second) { - if (old_mark.fromGroup.second) + if (old_mark.fromGroup.second) { - old_mark.fromGroup.second = false; - reset_group_danger(mark.fromGroup.first); - isUpdated = true; - } - else if (delta_danger > 0 || credit > 2) + old_mark.fromGroup.second = false; + reset_group_danger(mark.fromGroup.first); + isUpdated = true; + } + else if (delta_danger > 0 || credit > 2) { - old_mark.fromGroup.second = true; - up_group_danger(mark.fromGroup.first, mark); - isUpdated = true; - } - } - } - if (mark.fromQQ.first) - { - if (!old_mark.fromQQ.first) + old_mark.fromGroup.second = true; + up_group_danger(mark.fromGroup.first, mark); + isUpdated = true; + } + } + } + if (mark.fromQQ.first) + { + if (!old_mark.fromQQ.first) { - sQQEmpty.erase(id); - mQQIndex.emplace(mark.fromQQ.first, id); - old_mark.fromQQ = mark.fromQQ; - if (mark.fromQQ.second) + sQQEmpty.erase(id); + mQQIndex.emplace(mark.fromQQ.first, id); + old_mark.fromQQ = mark.fromQQ; + if (mark.fromQQ.second) { - up_qq_danger(mark.fromQQ.first, mark); - } - isUpdated = true; - } - else if (mark.fromQQ.second != old_mark.fromQQ.second) + up_qq_danger(mark.fromQQ.first, mark); + } + isUpdated = true; + } + else if (mark.fromQQ.second != old_mark.fromQQ.second) { - if (old_mark.fromQQ.second) + if (old_mark.fromQQ.second) { - old_mark.fromQQ.second = false; - reset_qq_danger(mark.fromQQ.first); - isUpdated = true; - } - else if (delta_danger > 0 || credit > 2) + old_mark.fromQQ.second = false; + reset_qq_danger(mark.fromQQ.first); + isUpdated = true; + } + else if (delta_danger > 0 || credit > 2) { - old_mark.fromQQ.second = true; - up_qq_danger(mark.fromQQ.first, mark); - isUpdated = true; - } - } - } - if (mark.inviterQQ.first) - { - if (!old_mark.inviterQQ.first) + old_mark.fromQQ.second = true; + up_qq_danger(mark.fromQQ.first, mark); + isUpdated = true; + } + } + } + if (mark.inviterQQ.first) + { + if (!old_mark.inviterQQ.first) { - mQQIndex.emplace(mark.inviterQQ.first, id); - old_mark.inviterQQ = mark.inviterQQ; - if (mark.inviterQQ.second) + mQQIndex.emplace(mark.inviterQQ.first, id); + old_mark.inviterQQ = mark.inviterQQ; + if (mark.inviterQQ.second) { - up_qq_danger(mark.inviterQQ.first, mark); - } - isUpdated = true; - } - else if (mark.inviterQQ.second != old_mark.inviterQQ.second) + up_qq_danger(mark.inviterQQ.first, mark); + } + isUpdated = true; + } + else if (mark.inviterQQ.second != old_mark.inviterQQ.second) { - if (old_mark.inviterQQ.second) + if (old_mark.inviterQQ.second) { - old_mark.inviterQQ.second = false; - reset_qq_danger(mark.inviterQQ.first); - isUpdated = true; - } - else if (delta_danger > 0 || credit > 2) + old_mark.inviterQQ.second = false; + reset_qq_danger(mark.inviterQQ.first); + isUpdated = true; + } + else if (delta_danger > 0 || credit > 2) { - old_mark.inviterQQ.second = true; - up_qq_danger(mark.inviterQQ.first, mark); - isUpdated = true; - } - } - } - if (mark.ownerQQ.first) - { - if (!old_mark.ownerQQ.first) + old_mark.inviterQQ.second = true; + up_qq_danger(mark.inviterQQ.first, mark); + isUpdated = true; + } + } + } + if (mark.ownerQQ.first) + { + if (!old_mark.ownerQQ.first) { - sQQEmpty.erase(id); - mQQIndex.emplace(mark.ownerQQ.first, id); - old_mark.ownerQQ = mark.ownerQQ; - if (mark.ownerQQ.second) + sQQEmpty.erase(id); + mQQIndex.emplace(mark.ownerQQ.first, id); + old_mark.ownerQQ = mark.ownerQQ; + if (mark.ownerQQ.second) { - up_qq_danger(mark.ownerQQ.first, mark); - } - isUpdated = true; - } - else if (mark.ownerQQ.second != old_mark.ownerQQ.second) + up_qq_danger(mark.ownerQQ.first, mark); + } + isUpdated = true; + } + else if (mark.ownerQQ.second != old_mark.ownerQQ.second) { - if (old_mark.ownerQQ.second) + if (old_mark.ownerQQ.second) { - old_mark.ownerQQ.second = false; - reset_qq_danger(mark.ownerQQ.first); - isUpdated = true; - } - else if (delta_danger > 0 || credit > 2) + old_mark.ownerQQ.second = false; + reset_qq_danger(mark.ownerQQ.first); + isUpdated = true; + } + else if (delta_danger > 0 || credit > 2) { - old_mark.ownerQQ.second = true; - up_qq_danger(mark.ownerQQ.first, mark); - isUpdated = true; - } + old_mark.ownerQQ.second = true; + up_qq_danger(mark.ownerQQ.first, mark); + isUpdated = true; + } + } + } + if (!old_mark.DiceMaid && mark.DiceMaid)old_mark.DiceMaid = mark.DiceMaid; + if (!old_mark.masterQQ && mark.masterQQ)old_mark.masterQQ = mark.masterQQ; + //save comment if the mark changed at this update + if (isUpdated) + { + if (!mark.comment.empty())old_mark.comment = mark.comment; + else if (old_mark.comment.empty()) { + old_mark.comment = printSTNow() + " 更新"; } - } - if (!old_mark.DiceMaid && mark.DiceMaid)old_mark.DiceMaid = mark.DiceMaid; - if (!old_mark.masterQQ && mark.masterQQ)old_mark.masterQQ = mark.masterQQ; - //save comment if the mark changed at this update - if (isUpdated) - { - if (!mark.comment.empty())old_mark.comment = mark.comment; - if (Enabled)blacklist->saveJson(DiceDir + "\\conf\\BlackList.json"); - } - return isUpdated; + if (Enabled && !isLoadingExtern)blacklist->saveJson(DiceDir / "conf" / "BlackList.json"); + } + return isUpdated; } void DDBlackManager::reset_group_danger(long long llgroup) @@ -787,7 +818,7 @@ void DDBlackManager::reset_group_danger(long long llgroup) else { mGroupDanger.erase(llgroup); - if (Enabled)console.log("已消除" + printGroup(llgroup) + "的危险等级", 0b10, printSTNow()); + if (Enabled && !isLoadingExtern)console.log("已消除" + printGroup(llgroup) + "的危险等级", 0b10, printSTNow()); } } @@ -814,7 +845,7 @@ void DDBlackManager::reset_qq_danger(long long llqq) mQQDanger.erase(llqq); if (Enabled) { - console.log("已消除" + printQQ(llqq) + "的危险等级", 0b10, printSTNow()); + if (!isLoadingExtern)console.log("已消除" + printQQ(llqq) + "的危险等级", 0b10, printSTNow()); if (UserList.count(llqq))AddMsgToQueue(getMsg("strBlackQQDelNotice", {{"user_nick", getName(llqq)}}), llqq); } } @@ -828,14 +859,14 @@ bool DDBlackManager::up_group_danger(long long llgroup, DDBlackMark& mark) return false; } if (mGroupDanger.count(llgroup) && mGroupDanger[llgroup] >= mark.danger)return false; + mGroupDanger[llgroup] = mark.danger; if (Enabled) { - if (console["LeaveBlackGroup"] && ChatList.count(llgroup) && !chat(llgroup).isset("已退"))chat(llgroup).leave( + if (ChatList.count(llgroup) && !chat(llgroup).isset("已退"))chat(llgroup).leave( mark.warning()); - console.log(GlobalMsg["strSelfName"] + "已将" + printGroup(llgroup) + "危险等级提升至" + to_string(mark.danger), 0b10, + if(!isLoadingExtern)console.log(GlobalMsg["strSelfName"] + "已将" + printGroup(llgroup) + "危险等级提升至" + to_string(mark.danger), 0b10, printSTNow()); } - mGroupDanger[llgroup] = mark.danger; return true; } @@ -843,12 +874,13 @@ bool DDBlackManager::up_qq_danger(long long llqq, DDBlackMark& mark) { if (trustedQQ(llqq) > 1 || llqq == console.master() || llqq == console.DiceMaid) { - if (mark.fromQQ.first == llqq)mark.fromQQ.second = false; + if (mark.fromQQ.first == llqq)mark.erase(); if (mark.inviterQQ.first == llqq)mark.inviterQQ.second = false; if (mark.ownerQQ.first == llqq)mark.ownerQQ.second = false; return false; } if (mQQDanger.count(llqq) && mQQDanger[llqq] >= mark.danger)return false; + mQQDanger[llqq] = mark.danger; if (Enabled && mark.danger > 1) { if (!mQQDanger.count(llqq) && UserList.count(llqq) && mark.danger == 2) @@ -857,11 +889,12 @@ bool DDBlackManager::up_qq_danger(long long llqq, DDBlackMark& mark) : AddMsgToQueue(getMsg("strBlackQQAddNoticeReason", { {"0", mark.note}, {"reason", mark.note}, {"user_nick", getName(llqq)} }), llqq); - console.log(GlobalMsg["strSelfName"] + "已将" + printQQ(llqq) + "危险等级提升至" + to_string(mark.danger), 0b10, - printSTNow()); - checkGroupWithBlackQQ(mark, llqq); + if (!isLoadingExtern) { + console.log(GlobalMsg["strSelfName"] + "已将" + printQQ(llqq) + "危险等级提升至" + to_string(mark.danger), 0b10, + printSTNow()); + checkGroupWithBlackQQ(mark, llqq); + } } - mQQDanger[llqq] = mark.danger; return true; } @@ -873,7 +906,8 @@ short DDBlackManager::get_group_danger(long long id) const short DDBlackManager::get_qq_danger(long long id) const { - if (auto it = mQQDanger.find(id); it != mQQDanger.end())return it->second; + if (auto it = mQQDanger.find(id); it != mQQDanger.end()) + return it->second; return 0; } @@ -888,6 +922,7 @@ void DDBlackManager::rm_black_group(long long llgroup, FromMsg* msg) if (mGroupDanger[llgroup] >= msg->trusted && msg->fromQQ != console.master()) { msg->reply("你注销目标黑名单的权限不足×"); + return; } for (auto [key,index] : multi_range(mGroupIndex, llgroup)) { @@ -895,7 +930,7 @@ void DDBlackManager::rm_black_group(long long llgroup, FromMsg* msg) } mGroupDanger.erase(llgroup); msg->note("已注销" + printGroup(llgroup) + "的黑名单记录√"); - blacklist->saveJson(DiceDir + "\\conf\\BlackList.json"); + blacklist->saveJson(DiceDir / "conf" / "BlackList.json"); } void DDBlackManager::rm_black_qq(long long llqq, FromMsg* msg) @@ -909,6 +944,7 @@ void DDBlackManager::rm_black_qq(long long llqq, FromMsg* msg) if (mQQDanger[llqq] >= msg->trusted && msg->fromQQ != console.master()) { msg->reply("你注销目标黑名单的权限不足×"); + return; } for (auto [key, index] : multi_range(mQQIndex, llqq)) { @@ -918,7 +954,7 @@ void DDBlackManager::rm_black_qq(long long llqq, FromMsg* msg) } reset_qq_danger(llqq); msg->note("已注销" + printQQ(llqq) + "的黑名单记录√"); - blacklist->saveJson(DiceDir + "\\conf\\BlackList.json"); + blacklist->saveJson(DiceDir / "conf" / "BlackList.json"); } void DDBlackManager::isban(FromMsg* msg) @@ -1025,7 +1061,10 @@ void DDBlackManager::add_black_group(long long llgroup, FromMsg* msg) DDBlackMark mark{0, llgroup}; mark.danger = 1; mark.note = msg->strVar["note"]; - if (!mark.note.empty())mark.danger = 2; + if (!mark.note.empty()) { + mark.danger = 2; + mark.type = "other"; + } if (mark.danger < get_qq_danger(llgroup)) { msg->reply(GlobalMsg["strSelfName"] + "已拉黑群" + to_string(llgroup) + "!"); @@ -1034,6 +1073,7 @@ void DDBlackManager::add_black_group(long long llgroup, FromMsg* msg) mark.time = msg->strVar["time"]; mark.DiceMaid = console.DiceMaid; mark.masterQQ = console.masterQQ; + mark.comment = printSTNow() + " 由" + printQQ(msg->fromQQ) + "拉黑"; insert(mark); msg->note("已添加" + printGroup(llgroup) + "的黑名单记录√"); } @@ -1048,7 +1088,10 @@ void DDBlackManager::add_black_qq(long long llqq, FromMsg* msg) DDBlackMark mark{llqq, 0}; mark.danger = 1; mark.note = msg->strVar["note"]; - if (!mark.note.empty())mark.danger = 2; + if (!mark.note.empty() && !msg->strVar.count("user")) { + mark.danger = 2; + mark.type = "other"; + } if (mark.danger < get_qq_danger(llqq)) { msg->reply(GlobalMsg["strSelfName"] + "已拉黑用户" + printQQ(llqq) + "!"); @@ -1057,194 +1100,201 @@ void DDBlackManager::add_black_qq(long long llqq, FromMsg* msg) mark.time = msg->strVar["time"]; mark.DiceMaid = console.DiceMaid; mark.masterQQ = console.masterQQ; + mark.comment = printSTNow() + " 由" + printQQ(msg->fromQQ) + "拉黑"; insert(mark); msg->note("已添加" + printQQ(llqq) + "的本地黑名单记录√"); } -void DDBlackManager::verify(void* pJson, long long operateQQ) +void DDBlackManager::verify(void* pJson, long long operatorQQ) { - DDBlackMark mark{pJson}; - if (!mark.isValid)return; - int credit = isReliable(operateQQ); - //数据库是否有记录:-1=不存在;0=已注销;1=未注销; - int is_cloud = -1; - if (console["CloudBlackShare"]) - { - if (mark.wid) + DDBlackMark mark{pJson}; + if (!mark.isValid)return; + int credit = isReliable(operatorQQ); + //数据库是否有记录:-1=不存在;0=已注销;1=未确认;2=已确认; + int is_cloud = -1; + if (console["CloudBlackShare"]) + { + if (mark.wid) { - string strInfo; - if ((is_cloud = getCloudBlackMark(mark.wid, strInfo)) > -1) + string strInfo; + if ((is_cloud = getCloudBlackMark(mark.wid, strInfo)) > -1) { - try + try { - json j = json::parse(strInfo); - if (j["isErased"].get())is_cloud = 0; - if (mark.fromQQ.first) + nlohmann::json j = nlohmann::json::parse(strInfo); + if (j["isErased"].get())is_cloud = 0; + else if (j["isCheck"].get())is_cloud = 2; + if (mark.fromQQ.first) { - if (mark.fromQQ.first != j["fromQQ"].get())return; - } - else + if (mark.fromQQ.first != j["fromQQ"].get())return; + } + else { - if (!mark.isClear)mark.fromQQ = {j["fromQQ"].get(), true}; - } - if (mark.fromGroup.first) + if (!mark.isClear)mark.fromQQ = {j["fromQQ"].get(), true}; + } + if (mark.fromGroup.first) { - if (mark.fromGroup.first != j["fromGroup"].get())return; - } - else + if (mark.fromGroup.first != j["fromGroup"].get())return; + } + else { - if (!mark.isClear)mark.fromGroup = {j["fromGroup"].get(), true}; - } - mark.DiceMaid = j["DiceMaid"].get(); - mark.masterQQ = j["masterQQ"].get(); - mark.type = j["type"].get(); - mark.time = j["time"].get(); - if (mark.note.empty()) + if (!mark.isClear)mark.fromGroup = {j["fromGroup"].get(), true}; + } + mark.DiceMaid = j["DiceMaid"].get(); + mark.masterQQ = j["masterQQ"].get(); + mark.type = j["type"].get(); + mark.time = j["time"].get(); + if (mark.note.empty()) { - if (j.count("note") && !j["note"].get().empty())mark.note = UTF8toGBK( - j["note"].get()); - else mark.fill_note(); - } - if (credit < 3 || !mark.danger)mark.danger = 2; - } - catch (...) + if (j.count("note") && !j["note"].get().empty())mark.note = UTF8toGBK( + j["note"].get()); + else mark.fill_note(); + } + if (credit < 3 || !mark.danger)mark.danger = 2; + } + catch (...) { - console.log("云端数据同步失败:wid=" + to_string(mark.wid), 0); - //mark.wid = 0; - } - } - else + console.log("云端数据同步失败:wid=" + to_string(mark.wid), 0); + //mark.wid = 0; + } + } + else { - console.log("云端核验失败:wid=" + to_string(mark.wid), 0); - mark.wid = 0; - } - } - else + console.log("云端核验失败:wid=" + to_string(mark.wid), 0); + mark.wid = 0; + } + } + else { - is_cloud = mark.check_cloud(); - } - } - else + is_cloud = mark.check_cloud(); + } + } + else { - mark.wid = 0; - } - if (credit < 3) + mark.wid = 0; + } + if (credit < 3) { - if ((mark.isType("kick") && !console["ListenGroupKick"]) || (mark.isType("ban") && !console["ListenGroupBan"]) + if ((mark.isType("kick") && !console["ListenGroupKick"]) || (mark.isType("ban") && !console["ListenGroupBan"]) || (mark.isType("spam") && !console["ListenSpam"]))return; - if (mark.type == "local" || mark.type == "other") - { - if (credit > 0)console.log( - getName(operateQQ) + "已通知" + GlobalMsg["strSelfName"] + "不良记录(未采用):\n!warning" + UTF8toGBK( + if (mark.type == "local" || mark.type == "other" || mark.isSource(console.DiceMaid)) { + if (credit > 0)console.log( + getName(operatorQQ) + "已通知" + GlobalMsg["strSelfName"] + "不良记录(未采用):\n!warning" + UTF8toGBK( static_cast(pJson)->dump()), 1, printSTNow()); - return; - } - } - else if (credit < 5 && mark.danger > credit) mark.danger = credit; - if (!mark.danger)mark.danger = 2; - if (mark.is_remit())mark.erase(); - int index = find(mark); - //新记录 - if (index < 0) - { - if (!mark.isType())return; - if (credit < 0)return; - if (is_cloud < 0) + return; + } + } + else if (credit < 5 && mark.danger > credit) mark.danger = credit; + if (!mark.danger || credit < 3)mark.danger = (mark.type == "ruler" ? 3 : 2); + mark.check_remit(); + int index = find(mark); + //新记录 + if (index < 0) + { + //发送者或当事人任一有黑名单 + if (credit < 0 || get_qq_danger(mark.DiceMaid) || get_qq_danger(mark.masterQQ))return; + if (!mark.isType())return; + //无云端确认记录且不受信任 + if (is_cloud < 0 || is_cloud == 1) { - if (mark.type == "ruler" || credit == 0)return; - } - else if (is_cloud == 0) + if (mark.type == "ruler" || credit == 0)return; + } + //云端注销,则同步注销 + else if (is_cloud == 0) { - if (!mark.isClear && credit < 3)mark.erase(); - } - if (credit < 3) + if (!mark.isClear && credit < 3)mark.erase(); + } + if (credit < 3) { - if (is_cloud < 1 && mark.type == "extern")return; - if (mark.type != "ruler") - { - if (mark.danger > 2)mark.danger = 2; - } - else - { - if (mark.danger > 3)mark.danger = 3; - } - } - else + if (is_cloud < 1 && mark.type == "extern")return; + } + else { - if (mark.type == "local" && credit < 4)return; - } - if (get_qq_danger(mark.DiceMaid) || get_qq_danger(mark.masterQQ))return; - if (mark.fromGroup.first && groupset(mark.fromGroup.first, "忽略") > 0)return; - insert(mark); - console.log( - getName(operateQQ) + "已通知" + GlobalMsg["strSelfName"] + "不良记录" + to_string(vBlackList.size() - 1) + - ":\n!warning" + UTF8toGBK(static_cast(pJson)->dump()), 1, printSTNow()); - } - else - { + if (mark.type == "local" && credit < 4)return; + } + if (mark.fromGroup.first && (groupset(mark.fromGroup.first, "忽略") > 0 || groupset(mark.fromGroup.first, "协议无效") > 0 || ExceptGroups.count(mark.fromGroup.first)))return; + insert(mark); + console.log(getName(operatorQQ) + "已通知" + GlobalMsg["strSelfName"] + "不良记录" + to_string(vBlackList.size() - 1) + ":\n!warning" + UTF8toGBK(((json*)pJson)->dump()), 1, printSTNow()); + } + else + { //已有记录 - DDBlackMark& old_mark = vBlackList[index]; - bool isSource = operateQQ == old_mark.DiceMaid || operateQQ == old_mark.masterQQ; - if (credit < 1) - { - if (old_mark.danger != 2)return; - if (is_cloud < 0 && !isSource)return; - if (is_cloud && (old_mark.isClear || mark.isClear))return; - mark.inviterQQ = {0, false}; - mark.ownerQQ = {0, false}; - } - else if (credit < 3) - { - if (old_mark.danger > 2)return; - if (mark.danger > 2)mark.danger = 2; - } - if (mark.danger != old_mark.danger) - { - if (credit < 3)return; - if (credit < 5 && credit < old_mark.danger)return; - } - if (update(mark, index, credit))console.log( - getName(operateQQ) + "已更新" + GlobalMsg["strSelfName"] + "不良记录" + to_string(index) + ":\n!warning" + - UTF8toGBK(static_cast(pJson)->dump()), 1, printSTNow()); - } + DDBlackMark& old_mark = vBlackList[index]; + bool isSource = operatorQQ == old_mark.DiceMaid || operatorQQ == old_mark.masterQQ; + //低于危险等级无权修改 + if (old_mark.danger > credit && credit < 255) { + if (old_mark.danger != 2)return; + //当事人权利 + if (isSource) { + mark.fromQQ.second &= old_mark.fromQQ.second; + mark.fromGroup.second &= old_mark.fromGroup.second; + } + //同步云端注销 + else if (!is_cloud) { + mark.erase(); + } + else return; + } + //无信任 + if (credit < 1) { + mark.inviterQQ = { 0,false }; + mark.ownerQQ = { 0,false }; + } + //无权调整危险等级 + if (mark.danger != old_mark.danger && credit < 3) { + mark.danger = old_mark.danger; + } + if(update(mark,index,credit))console.log(getName(operatorQQ) + "已更新" + GlobalMsg["strSelfName"] + "不良记录" + to_string(index) + ":\n!warning" + UTF8toGBK(((json*)pJson)->dump()), 1, printSTNow()); + } } -void DDBlackManager::create(DDBlackMark& mark) +void DDBlackManager::create(DDBlackMark& mark) { - if (mark.is_remit())return; - if (console["CloudBlackShare"] && mark.isSource(console.DiceMaid))mark.upload(); - console.log(mark.warning(), 0b100000); - insert(mark); + if (mark.check_remit(); mark.isClear)return; + if (console["CloudBlackShare"] && mark.isSource(console.DiceMaid))mark.upload(); + console.log(mark.warning(), 0b100000); + insert(mark); } -int DDBlackManager::loadJson(string strPath) +int DDBlackManager::loadJson(const std::filesystem::path& fpPath, bool isExtern) { - json j = freadJson(strPath); + json j = freadJson(fpPath); if (j.is_null())return -1; if (j.size() > vBlackList.capacity())vBlackList.reserve(j.size() * 2); + int cnt(0); for (auto& item : j) { DDBlackMark mark{&item}; if (!mark.isValid)continue; if (!mark.danger)mark.danger = 2; //新插入或更新 + isLoadingExtern = true; if (int res = find(mark); res < 0) { - insert(mark); + if(insert(mark))cnt++; } else { - update(mark, res); + if (isExtern) { + if (mark.isSource(console.DiceMaid))continue; //涉及自身的不处理 + if (mark.danger != vBlackList[res].danger)continue; //危险等级特别改动的不处理 + } + if (update(mark, res))cnt++; } + isLoadingExtern = false; } - return vBlackList.size(); + if (isExtern) { + filesystem::remove(fpPath); + console.log("更新外源不良记录条目" + to_string(cnt) + "条", 1, printSTNow()); + blacklist->saveJson(DiceDir / "conf" / "BlackList.json"); + } + return cnt; } -int DDBlackManager::loadHistory(const string& strLoc) +int DDBlackManager::loadHistory(const std::filesystem::path& fpLoc) { - console.log("开始初始化历史黑名单", 0, printSTNow()); long long id; - std::ifstream fgroup(strLoc + "BlackGroup.RDconf"); + std::ifstream fgroup(fpLoc / "BlackGroup.RDconf"); if (fgroup) { while (fgroup >> id) @@ -1255,7 +1305,7 @@ int DDBlackManager::loadHistory(const string& strLoc) } } fgroup.close(); - std::ifstream fqq(strLoc + "BlackQQ.RDconf"); + std::ifstream fqq(fpLoc / "BlackQQ.RDconf"); if (fqq) { while (fqq >> id) @@ -1269,9 +1319,9 @@ int DDBlackManager::loadHistory(const string& strLoc) return vBlackList.size(); } -void DDBlackManager::saveJson(const string& strPath) const +void DDBlackManager::saveJson(const std::filesystem::path& fpPath) const { - std::ofstream fout(strPath); + std::ofstream fout(fpPath); JsonList jary; for (auto& mark : vBlackList) { diff --git a/Dice/BlackListManager.h b/Dice/BlackListManager.h index bc15314a..da09e24f 100644 --- a/Dice/BlackListManager.h +++ b/Dice/BlackListManager.h @@ -1,9 +1,11 @@ +#pragma once + /** * 黑名单明细 * 更数据库式的管理 * Copyright (C) 2019-2020 String.Empty */ -#pragma once + #include #include #include @@ -12,6 +14,7 @@ #include #include #include +#include using std::pair; using std::string; @@ -70,7 +73,7 @@ class DDBlackMark [[nodiscard]] bool isType(const string& strType) const; [[nodiscard]] bool isSame(const DDBlackMark&) const; [[nodiscard]] bool isSource(long long) const; - [[nodiscard]] bool is_remit() const; + void check_remit(); DDBlackMark& operator<<(const DDBlackMark&); //bool operator<(const DDBlackMark&)const; }; @@ -89,7 +92,7 @@ class DDBlackManager //发现所指相同的记录 int find(const DDBlackMark&); //更新记录 - void insert(DDBlackMark&); + bool insert(DDBlackMark&); bool update(DDBlackMark&, unsigned int, int); void reset_group_danger(long long); void reset_qq_danger(long long); @@ -116,10 +119,10 @@ class DDBlackManager void verify(void*, long long); void create(DDBlackMark&); //读取json格式黑名单记录 - int loadJson(string strPath); + int loadJson(const std::filesystem::path& fpPath, bool isExtern = false); //读取旧版本黑名单列表 - int loadHistory(const string& strLoc); - void saveJson(const string& strPath) const; + int loadHistory(const std::filesystem::path& fpLoc); + void saveJson(const std::filesystem::path& fpPath) const; }; extern std::unique_ptr blacklist; @@ -152,6 +155,12 @@ class DDBlackMarkFactory return *this; } + Factory& comment(string strNote) + { + mark.comment = strNote; + return *this; + } + Factory& inviterQQ(long long qq) { mark.inviterQQ = {qq, true}; diff --git a/Dice/CQP.lib b/Dice/CQP.lib deleted file mode 100644 index 8af05707..00000000 Binary files a/Dice/CQP.lib and /dev/null differ diff --git a/Dice/CardDeck.cpp b/Dice/CardDeck.cpp index fd7ae4b9..45221b05 100644 --- a/Dice/CardDeck.cpp +++ b/Dice/CardDeck.cpp @@ -32,6 +32,7 @@ namespace CardDeck } }, {"天干", {"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"}}, + {"地支", {"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"}}, { "first_name_cn", { @@ -358,6 +359,21 @@ namespace CardDeck } }, {"硬币", {"正", "反"}}, + { "扑克牌", + { + "红桃A","红桃2","红桃3","红桃4","红桃5","红桃6","红桃7","红桃8","红桃9","红桃10","红桃J","红桃Q","红桃K", + "黑桃A","黑桃2","黑桃3","黑桃4","黑桃5","黑桃6","黑桃7","黑桃8","黑桃9","黑桃10","黑桃J","黑桃Q","黑桃K", + "方片A","方片2","方片3","方片4","方片5","方片6","方片7","方片8","方片9","方片10","方片J","方片Q","方片K", + "梅花A","梅花2","梅花3","梅花4","梅花5","梅花6","梅花7","梅花8","梅花9","梅花10","梅花J","梅花Q","梅花K", + "小王","大王"} + }, + { "麻将牌", + { + "::4::一万","::4::二万","::4::三万","::4::四万","::4::五万","::4::六万","::4::七万","::4::八万","::4::九万", + "::4::一筒","::4::二筒","::4::三筒","::4::四筒","::4::五筒","::4::六筒","::4::七筒","::4::八筒","::4::九筒", + "::4::一条","::4::二条","::4::三条","::4::四条","::4::五条","::4::六条","::4::七条","::4::八条","::4::九条", + "::4::东风","::4::南风","::4::西风","::4::北风","::4::中","::4::发","::4::白"} + }, {"性别", {"男", "女"}}, { "人偶暗示", @@ -1014,14 +1030,6 @@ namespace CardDeck std::map, less_ci> mReplyDeck = { {".dissmiss", {"diss!diss!diss!会英语吗在那diss本骰娘?那叫!dismiss!"}} }; - //群聊牌堆 - std::map> mGroupDeck; - //群聊临时牌堆 - std::map> mGroupDeckTmp; - //私人牌堆 - std::map> mPrivateDeck; - //私人临时牌堆 - std::map> mPrivateDeckTmp; std::map PublicComplexDeck{ //{"调查员背景","个人描述:{个人描述}\n思想信念:{思想信念}\n重要之人:{重要之人}\n重要之人理由:{重要之人理由}\n意义非凡之地:{意义非凡之地}\n宝贵之物:{宝贵之物}\n特点:{调查员特点}"} }; @@ -1096,9 +1104,7 @@ namespace CardDeck sortedIndex.push_back(sortedIndex[sortedIndex.size() - 1] + cnt); } } - - - + std::string strReply; if (TempDeck.empty())return ""; if (TempDeck.size() == 1) @@ -1146,6 +1152,11 @@ namespace CardDeck int lq = 0, rq = 0; while ((lq = strExp.find('{', intCnt)) != std::string::npos && (rq = strExp.find('}', lq)) != std::string::npos) { + if (lq > 0 && strExp[lq - 1] == '\\') { + strExp.erase(strExp.begin() + lq - 1); + intCnt = rq; + continue; + } bool isTmpBack = false; string strTempName = strExp.substr(lq + 1, rq - lq - 1); if (strTempName[0] == '%') @@ -1169,6 +1180,11 @@ namespace CardDeck intCnt = 0; while ((lq = strExp.find('[', intCnt)) != std::string::npos && (rq = strExp.find(']', lq)) != std::string::npos) { + if (lq > 0 && strExp[lq - 1] == '\\') { + strExp.erase(strExp.begin() + lq - 1); + intCnt = rq; + continue; + } string strRoll = strExp.substr(lq + 1, rq - lq - 1); intCnt = rq + 1; RD RDroll(strRoll); diff --git a/Dice/CardDeck.h b/Dice/CardDeck.h index 85dc44ad..22dfcaa9 100644 --- a/Dice/CardDeck.h +++ b/Dice/CardDeck.h @@ -1,9 +1,11 @@ +#pragma once + /* * 牌堆抽卡功能 * 因为发展方向与Dice!插件并不相合,所以更新会止步在某种程度 * Copyright (C) 2019 String.Empty */ -#pragma once + #ifndef CARD_DECK #define CARD_DECK #include @@ -16,14 +18,6 @@ namespace CardDeck extern std::map, less_ci> mPublicDeck; extern std::map, less_ci> mExternPublicDeck; extern std::map, less_ci> mReplyDeck; - //群聊牌堆 - extern std::map> mGroupDeck; - //群聊临时牌堆 - extern std::map> mGroupDeckTmp; - //私人牌堆 - extern std::map> mPrivateDeck; - //私人临时牌堆 - extern std::map> mPrivateDeckTmp; extern std::map PublicComplexDeck; int findDeck(std::string strDeckName); std::string drawCard(std::vector& TempDeck, bool boolBack = false); diff --git a/Dice/CharacterCard.cpp b/Dice/CharacterCard.cpp index 0a1a5eb2..f269f914 100644 --- a/Dice/CharacterCard.cpp +++ b/Dice/CharacterCard.cpp @@ -1,30 +1,203 @@ /* * 玩家人物卡 - * Copyright (C) 2019-2020 String.Empty + * Copyright (C) 2019-2021 String.Empty */ #include "CharacterCard.h" -map& getmCardTemplet() +/** + * 错误返回值 + * -1:PcCardFull 角色卡已满 + * -2:PcTempInvalid 模板无效(已弃用) + * -3:PcNameEmpty 卡名为空 + * -4:PCNameExist 卡名已存在 + * -5:PcNameNotExist 卡名不存在 + * -6:PCNameInvalid 卡名无效 + * -7:PcInitDelErr 删除初始卡 + */ + + +string CardTemp::show() { + ResList res; + if (!mVariable.empty()) { + ResList resVar; + for (const auto& [key, val] : mVariable) { + resVar << key; + } + res << "动态变量:" + resVar.show(); + } + if (!mExpression.empty()) { + ResList resExp; + for (const auto& [key, val] : mExpression) { + resExp << key; + } + res << "掷骰公式:" + resExp.show(); + } + return "pc模板:" + type + res.show(); +} + +CardTemp& getCardTemplet(const string& type) { - static map mCardTemplet = { - { - "COC7", { - "COC7", SkillNameReplace, BasicCOC7, InfoCOC7, AutoFillCOC7, mVariableCOC7, ExpressionCOC7, - SkillDefaultVal, { - {"", CardBuild({BuildCOC7}, CardDeck::mPublicDeck["随机姓名"], {})}, - { - "bg", CardBuild({ - {"性别", "{性别}"}, {"年龄", "7D6+8"}, {"职业", "{调查员职业}"}, {"个人描述", "{个人描述}"}, - {"重要之人", "{重要之人}"}, {"思想信念", "{思想信念}"}, {"意义非凡之地", "{意义非凡之地}"}, - {"宝贵之物", "{宝贵之物}"}, {"特质", "{调查员特点}"} - }, CardDeck::mPublicDeck["随机姓名"], {}) - } - } + if (type.empty() || !mCardTemplet.count(type))return mCardTemplet["BRP"]; + return mCardTemplet[type]; +} + +void CharaCard::setName(const string& strName) { + Name = strName; + Info["__Name"] = strName; +} +void CharaCard::writeb(std::ofstream& fout) const { + fwrite(fout, string("Name")); + fwrite(fout, Name); + if (!Attr.empty()) { + fwrite(fout, string("Attr")); + fwrite(fout, Attr); + } + if (!Info.empty()) { + fwrite(fout, string("Info")); + fwrite(fout, Info); + } + if (!DiceExp.empty()) { + fwrite(fout, string("DiceExp")); + fwrite(fout, DiceExp); + } + if (!Note.empty()) { + fwrite(fout, string("Note")); + fwrite(fout, Note); + } + fwrite(fout, string("END")); +} +int CharaCard::setInfo(const string& key, const string& s) { + if (key.empty() || s.length() > 255)return -1; + Info[key] = s; + if (key == "__Type") + pTemplet = &getCardTemplet(s); + return 0; +} + +int CharaCard::show(string key, string& val) const { + if (Info.count(key)) { + val = Info.find(key)->second; + return 3; + } + if (key == "note") { + val = Note; + return 2; + } + if (DiceExp.count(key)) { + val = DiceExp.find(key)->second; + return 1; + } + key = standard(key); + if (Attr.count(key)) { + val = to_string(Attr.find(key)->second); + return 0; + } + return -1; +} + +bool CharaCard::count(const string& strKey) const { + if (Attr.count(strKey))return true; + string key{ standard(strKey) }; + return Attr.count(key) || DiceExp.count(key) || pTemplet->mAutoFill.count(key) || pTemplet->mVariable.count(key) + || pTemplet->defaultSkill.count(key); +} +short& CharaCard::operator[](const string& strKey) { + if (Attr.count(strKey))return Attr[strKey]; + string key{ standard(strKey) }; + if (!Attr.count(key)) { + if (pTemplet->mAutoFill.count(key))Attr[key] = cal(pTemplet->mAutoFill.find(key)->second); + if (pTemplet->defaultSkill.count(key))Attr[key] = pTemplet->defaultSkill.find(key)->second; + } + return Attr[key]; +} + +void CharaCard::clear() { + Attr.clear(); + map info_new{ {"__Type",Info["__Type"]},{"__Name",Info["__Name"]} }; + Info.swap(info_new); + DiceExp.clear(); + Note.clear(); +} +[[nodiscard]] string CharaCard::show(bool isWhole) const { + std::set sDefault; + ResList Res; + for (const auto& list : pTemplet->vBasicList) { + ResList subList; + string strVal; + for (const auto& it : list) { + switch (show(it, strVal)) { + case 0: + sDefault.insert(it); + subList << it + ":" + strVal; + break; + case 1: + sDefault.insert(it); + subList << "&" + it + "=" + strVal; + break; + case 3: + sDefault.insert(it); + subList.dot("\t"); + subList << it + ":" + strVal; + break; + default: + continue; } - }, - {"BRP", {}} - }; - return mCardTemplet; + } + Res << subList.show(); + } + string strAttrRest; + for (const auto& [key,val] : Attr) { + if (sDefault.count(key) || + (key[0] == '_' && (key.length() < 2 || key[1] != '_' || !isWhole)))continue; + strAttrRest += key + ":" + to_string(val) + " "; + } + Res << strAttrRest; + if (isWhole && !Info.empty()) + for (const auto& it : Info) { + if (sDefault.count(it.first) || it.first[0] == '_')continue; + Res << it.first + ":" + it.second; + } + if (isWhole && !DiceExp.empty()) + for (const auto& it : DiceExp) { + if (sDefault.count(it.first) || it.first[0] == '_')continue; + Res << "&" + it.first + "=" + it.second; + } + if (isWhole && !Note.empty())Res << "====================\n" + Note; + return Res.show(); +} +void CharaCard::readb(std::ifstream& fin) { + string tag = fread(fin); + while (tag != "END") { + switch (mCardTag[tag]) { + case 1: + setName(fread(fin)); + break; + case 2: + Info["__Type"] = fread(fin); + break; + case 11: + fread(fin, Attr); + Attr.erase(""); + break; + case 21: + fread(fin, DiceExp); + DiceExp.erase(""); + break; + case 102: + fread(fin, Info); + Info.erase(""); + scanImage(Info, sReferencedImage); + break; + case 101: + Note = fread(fin); + scanImage(Note, sReferencedImage); + break; + default: + break; + } + tag = fread(fin); + } + pTemplet = &getCardTemplet(Info["__Type"]); } Player& getPlayer(long long qq) @@ -32,9 +205,29 @@ Player& getPlayer(long long qq) if (!PList.count(qq))PList[qq] = {}; return PList[qq]; } +int Player::renameCard(const string& name, const string& name_new) { + std::lock_guard lock_queue(cardMutex); + if (name_new.empty())return -3; + if (mNameIndex.count(name_new))return -4; + if (name_new.find(":") != string::npos)return -6; + const int i = mNameIndex[name_new] = mNameIndex[name]; + mNameIndex.erase(name); + mCardList[i].setName(name_new); + return 0; +} +string Player::listCard() { + ResList Res; + for (auto& [idx, pc] : mCardList) { + Res << "[" + to_string(idx) + "]<" + getCardTemplet(pc.Info["__Type"]).type + ">" + pc.getName(); + } + Res << "default:" + (*this)[0].getName(); + return Res.show(); +} -string getPCName(long long qq, long long group) +void getPCName(FromMsg& msg) { - if (PList.count(qq) && PList[qq][group].Name != "角色卡")return PList[qq][group].Name; - return getName(qq, group); + msg["pc"] = (PList.count(msg.fromQQ) && PList[msg.fromQQ][msg.fromGroup].getName() != "角色卡") + ? PList[msg.fromQQ][msg.fromGroup].getName() + : ((!msg.strVar.count("nick") || msg["nick"].empty()) + ? msg["nick"] = getName(msg.fromQQ, msg.fromGroup) : msg["nick"]); } diff --git a/Dice/CharacterCard.h b/Dice/CharacterCard.h index 9185090c..0c9fafdf 100644 --- a/Dice/CharacterCard.h +++ b/Dice/CharacterCard.h @@ -1,8 +1,10 @@ +#pragma once + /* * 玩家人物卡 - * Copyright (C) 2019 String.Empty + * Copyright (C) 2019-2021 String.Empty */ -#pragma once + #include #include #include @@ -13,12 +15,15 @@ #include #include "CQTools.h" #include "Unpack.h" +#include "RDConstant.h" #include "RD.h" #include "DiceXMLTree.h" #include "DiceFile.hpp" #include "ManagerSystem.h" #include "MsgFormat.h" #include "CardDeck.h" +#include "DiceEvent.h" + using std::string; using std::to_string; using std::vector; @@ -113,31 +118,21 @@ class CardTemp CardTemp(string type, map replace, vector> basic, set info, map autofill, map dynamic, map exp, - map skill, map option) : type( - std::move( - type)), replaceName( - std::move( - replace)), vBasicList( - std::move( - basic)), sInfoList( - std::move( - info)), mAutoFill( - std::move( - autofill)), mVariable( - std::move( - dynamic)), mExpression( - std::move( - exp)), defaultSkill( - std::move( - skill)), mBuildOption( - std::move( - option)) + map skill, map option) : type(std::move(type)), + replaceName(std::move(replace)), + vBasicList(std::move(basic)), + sInfoList(std::move(info)), + mAutoFill(std::move(autofill)), + mVariable(std::move(dynamic)), + mExpression(std::move(exp)), + defaultSkill(std::move(skill)), + mBuildOption(std::move(option)) { } - CardTemp(DDOM d) + CardTemp(const DDOM& d) { - readt(std::move(d)); + readt(d); } void readt(const DDOM& d) @@ -164,6 +159,7 @@ class CardTemp { sInfoList.insert(sub); } + break; case 22: readini(node.strValue, mAutoFill); break; @@ -199,30 +195,37 @@ class CardTemp if (strItem.empty())return type; return type + "[" + strItem + "]"; } + string show(); }; +// 由于依赖于其他全局变量,为了避免全局变量初始化顺序冲突,这个变量会在eventEnable中被初始化 +inline map mCardTemplet; +CardTemp& getCardTemplet(const string& type); -map& getmCardTemplet(); - - +struct lua_State; class CharaCard { private: -public: string Name = "角色卡"; - string Type = "COC7"; - map Attr{}; - map Info{}; - map DiceExp{}; +public: + const string& getName()const { return Name; } + void setName(const string&); + //string Type = "COC7"; + map Attr{}; + map Info{ {"__Type","COC7"} }; + map DiceExp{}; string Note; - const CardTemp* pTemplet = &getmCardTemplet()[Type]; + CardTemp* pTemplet{ nullptr }; - CharaCard() - { + CharaCard(){ + pTemplet = &getCardTemplet("BRP"); } - CharaCard(string name, string type = "COC7") : Name(std::move(name)), Type(std::move(type)) + CharaCard(const string& name, const string& type = "COC7") : Name(name) { + Info["__Type"] = type; + Info["__Name"] = name; + pTemplet = &getCardTemplet(type); } short call(string& key) @@ -346,7 +349,7 @@ class CharaCard { std::stack vOption; int Cnt; - vOption.push(""); + vOption.push("_default"); while ((Cnt = para.rfind(':')) != string::npos) { vOption.push(para.substr(Cnt + 1)); @@ -361,7 +364,7 @@ class CharaCard } } - [[nodiscard]] string standard(string key) const + [[nodiscard]] string standard(const string& key) const { if (pTemplet->replaceName.count(key))return pTemplet->replaceName.find(key)->second; return key; @@ -369,6 +372,7 @@ class CharaCard int set(string key, short val) { + if (key.empty())return -1; key = standard(key); if (pTemplet->defaultSkill.count(key) && val == pTemplet->defaultSkill.find(key)->second) { @@ -379,16 +383,11 @@ class CharaCard return 0; } - int setInfo(const string& key, const string& s) - { - if (s.length() > 48)return -1; - Info[key] = s; - return 0; - } + int setInfo(const string& key, const string& s); int setExp(const string& key, const string& exp) { - if (exp.length() > 48)return -1; + if (key.empty() || exp.length() > 48)return -1; DiceExp[key] = exp; return 0; } @@ -433,105 +432,13 @@ class CharaCard return false; } - void clear() - { - Attr.clear(); - Info.clear(); - DiceExp.clear(); - Note.clear(); - } + void clear(); - int show(string key, string& val) const - { - if (pTemplet->sInfoList.count(key)) - { - if (Info.count(key)) - { - val = Info.find(key)->second; - return 3; - } - return -1; - } - if (key == "note") - { - val = Note; - return 2; - } - if (DiceExp.count(key)) - { - val = DiceExp.find(key)->second; - return 1; - } - key = standard(key); - if (Attr.count(key)) - { - val = to_string(Attr.find(key)->second); - return 0; - } - return -1; - } + int show(string key, string& val) const; - [[nodiscard]] string show(bool isWhole) const - { - std::set sDefault; - ResList Res; - for (const auto& list : pTemplet->vBasicList) - { - ResList subList; - string strVal; - for (const auto& it : list) - { - switch (show(it, strVal)) - { - case 0: - sDefault.insert(it); - subList << it + ":" + strVal; - break; - case 1: - sDefault.insert(it); - subList << "&" + it + "=" + strVal; - break; - case 3: - sDefault.insert(it); - subList.dot("\t"); - subList << it + ":" + strVal; - break; - default: - continue; - } - } - Res << subList.show(); - } - string strAttrRest; - for (const auto& it : Attr) - { - if (sDefault.count(it.first))continue; - strAttrRest += it.first + ":" + to_string(it.second) + " "; - } - Res << strAttrRest; - if (isWhole && !Info.empty()) - for (const auto& it : Info) - { - if (sDefault.count(it.first))continue; - Res << it.first + ":" + it.second; - } - if (isWhole && !DiceExp.empty()) - for (const auto& it : DiceExp) - { - if (sDefault.count(it.first))continue; - Res << "&" + it.first + "=" + it.second; - } - if (isWhole && !Note.empty())Res << "====================\n" + Note; - return Res.show(); - } + [[nodiscard]] string show(bool isWhole) const; - bool count(string& key) const - { - if (Attr.count(key))return true; - key = standard(key); - return Attr.count(key) || DiceExp.count(key) || pTemplet->mAutoFill.count(key) || pTemplet->mVariable.count(key) - || pTemplet->defaultSkill.count(key); - } + bool count(const string& key) const; bool stored(string& key) const { @@ -539,16 +446,7 @@ class CharaCard return Attr.count(key) || pTemplet->mAutoFill.count(key) || pTemplet->defaultSkill.count(key); } - short& operator[](string& key) - { - key = standard(key); - if (!Attr.count(key)) - { - if (pTemplet->mAutoFill.count(key))Attr[key] = cal(pTemplet->mAutoFill.find(key)->second); - if (pTemplet->defaultSkill.count(key))Attr[key] = pTemplet->defaultSkill.find(key)->second; - } - return Attr[key]; - } + short& operator[](const string& key); void operator<<(const CharaCard& card) { @@ -557,69 +455,12 @@ class CharaCard Name = name; } - void writeb(std::ofstream& fout) const - { - fwrite(fout, string("Name")); - fwrite(fout, Name); - fwrite(fout, string("Type")); - fwrite(fout, Type); - if (!Attr.empty()) - { - fwrite(fout, string("Attr")); - fwrite(fout, Attr); - } - if (!Info.empty()) - { - fwrite(fout, string("Info")); - fwrite(fout, Info); - } - if (!DiceExp.empty()) - { - fwrite(fout, string("DiceExp")); - fwrite(fout, DiceExp); - } - if (!Note.empty()) - { - fwrite(fout, string("Note")); - fwrite(fout, Note); - } - fwrite(fout, string("END")); - } + void writeb(std::ofstream& fout) const; - void readb(std::ifstream& fin) - { - string tag = fread(fin); - while (tag != "END") - { - switch (mCardTag[tag]) - { - case 1: - Name = fread(fin); - break; - case 2: - Type = fread(fin); - break; - case 11: - Attr = fread(fin); - break; - case 21: - DiceExp = fread(fin); - break; - case 102: - Info = fread(fin); - scanImage(Note, sReferencedImage); - break; - case 101: - Note = fread(fin); - scanImage(Note, sReferencedImage); - break; - default: - break; - } - tag = fread(fin); - } - pTemplet = getmCardTemplet().count(Type) ? &getmCardTemplet()[Type] : &getmCardTemplet()["COC7"]; - } + void readb(std::ifstream& fin); + + void pushTable(lua_State*); + void toCard(lua_State*); }; class Player @@ -678,7 +519,7 @@ class Player s.erase(s.begin(), s.begin() + Cnt + 1); if (type == "COC")type = "COC7"; } - else if (getmCardTemplet().count(s)) + else if (mCardTemplet.count(s)) { type = s; s.clear(); @@ -688,8 +529,8 @@ class Player vOption.push(type.substr(Cnt + 1)); type.erase(type.begin() + Cnt, type.end()); } - //无效模板 - if (!getmCardTemplet().count(type))return -2; + //无效模板不再报错 + //if (!getmCardTemplet().count(type))return -2; if (mNameIndex.count(s))return -4; if (s.find("=") != string::npos)return -6; mCardList[++indexMax] = CharaCard(s, type); @@ -700,37 +541,37 @@ class Player string para = vOption.top(); vOption.pop(); card.build(para); - if (card.Name.empty()) + if (card.getName().empty()) { - std::vector list = getmCardTemplet()[type].mBuildOption[para].vNameList; + std::vector list = getCardTemplet(type).mBuildOption[para].vNameList; while (!list.empty()) { s = CardDeck::draw(list[0]); if (mNameIndex.count(s))list.erase(list.begin()); else { - card.Name = s; + card.setName(s); break; } } } } - if (card.Name.empty()) + if (card.getName().empty()) { - std::vector list = getmCardTemplet()[type].mBuildOption[""].vNameList; + std::vector list = getCardTemplet(type).mBuildOption["_default"].vNameList; while (!list.empty()) { s = CardDeck::draw(list[0]); if (mNameIndex.count(s))list.erase(list.begin()); else { - card.Name = s; + card.setName(s); break; } } - if (card.Name.empty())card.Name = to_string(indexMax + 1); + if (card.getName().empty())card.setName(to_string(indexMax + 1)); } - s = card.Name; + s = card.getName(); mNameIndex[s] = indexMax; mGroupIndex[group] = indexMax; return 0; @@ -749,12 +590,12 @@ class Player if (!strName.empty() && !mNameIndex.count(strName)) { if (const int res = newCard(name, group))return res; - name = getCard(strName, group).Name; + name = getCard(strName, group).getName(); (*this)[name].buildv(); } else { - name = getCard(strName, group).Name; + name = getCard(strName, group).getName(); if (isClear)(*this)[name].clear(); (*this)[name].buildv(strType); } @@ -778,9 +619,17 @@ class Player std::lock_guard lock_queue(cardMutex); if (!mNameIndex.count(name))return -5; if (!mNameIndex[name])return -7; - for (auto it : mGroupIndex) + auto it = mGroupIndex.cbegin(); + while (it != mGroupIndex.cend()) { - if (it.second == mNameIndex[name])mGroupIndex.erase(it.first); + if (it->second == mNameIndex[name]) + { + it = mGroupIndex.erase(it); + } + else + { + ++it; + } } mCardList.erase(mNameIndex[name]); while (!mCardList.count(indexMax))indexMax--; @@ -788,16 +637,7 @@ class Player return 0; } - int renameCard(const string& name, const string& name_new) - { - std::lock_guard lock_queue(cardMutex); - if (mNameIndex.count(name_new))return -4; - if (name_new.find(":") != string::npos)return -6; - const int i = mNameIndex[name_new] = mNameIndex[name]; - mNameIndex.erase(name); - mCardList[i].Name = name_new; - return 0; - } + int renameCard(const string& name, const string& name_new); int copyCard(const string& name1, const string& name2, long long group = 0) { @@ -816,24 +656,15 @@ class Player return 0; } - string listCard() - { - ResList Res; - for (const auto& it : mCardList) - { - Res << "[" + to_string(it.first) + "]" + it.second.Name; - } - Res << "default:" + (*this)[0].Name; - return Res.show(); - } + string listCard(); string listMap() { ResList Res; - for (auto it : mGroupIndex) + for (const auto& it : mGroupIndex) { - if (!it.first)Res << "default:" + mCardList[it.second].Name; - else Res << "(" + to_string(it.first) + ")" + mCardList[it.second].Name; + if (!it.first)Res << "default:" + mCardList[it.second].getName(); + else Res << "(" + to_string(it.first) + ")" + mCardList[it.second].getName(); } return Res.show(); } @@ -901,8 +732,7 @@ class Player { Unpack card; card.add(it.first); - card.add(it.second.Name); - card.add(it.second.Type); + card.add(it.second.getName()); Unpack skills; for (const auto& skill : it.second.Attr) { @@ -915,7 +745,7 @@ class Player } pack.add(cards); Unpack groups; - for (auto it : mGroupIndex) + for (const auto& it : mGroupIndex) { groups.add(static_cast(it.first)); groups.add(it.second); @@ -924,7 +754,7 @@ class Player fout << base64_encode(pack.getAll()); } - void writeb(std::ofstream& fout) + void writeb(std::ofstream& fout) const { fwrite(fout, indexMax); fwrite(fout, mCardList); @@ -937,7 +767,7 @@ class Player mCardList = fread(fin); for (const auto& card : mCardList) { - mNameIndex[card.second.Name] = card.first; + mNameIndex[card.second.getName()] = card.first; } mGroupIndex = fread(fin); } @@ -947,4 +777,4 @@ inline map PList; Player& getPlayer(long long qq); -string getPCName(long long qq, long long group); +void getPCName(FromMsg&); diff --git a/Dice/CustomMsg.h b/Dice/CustomMsg.h index f4c6802a..87151dd2 100644 --- a/Dice/CustomMsg.h +++ b/Dice/CustomMsg.h @@ -1,3 +1,5 @@ +#pragma once + /* * _______ ________ ________ ________ __ * | __ \ |__ __| | _____| | _____| | | @@ -20,9 +22,11 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#pragma once + #ifndef DICE_CUSTOM_MSG #define DICE_CUSTOM_MSG +#include +#include void ReadCustomMsg(std::ifstream& in); void SaveCustomMsg(std::string strPath); diff --git a/Dice/Dice.cpp b/Dice/Dice.cpp index 11ff63c5..609584a2 100644 --- a/Dice/Dice.cpp +++ b/Dice/Dice.cpp @@ -21,8 +21,6 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#define WIN32_LEAN_AND_MEAN -#include #include #include #include @@ -32,11 +30,13 @@ #include #include #include +#include #include "APPINFO.h" #include "DiceFile.hpp" #include "Jsonio.h" -#include "CQEVE_ALL.h" +#include "QQEvent.h" +#include "DDAPI.h" #include "ManagerSystem.h" #include "DiceMod.h" #include "DiceMsgSend.h" @@ -49,47 +49,55 @@ #include "DiceEvent.h" #include "DiceSession.h" #include "DiceGUI.h" +#include "S3PutObject.h" +#include "DiceCensor.h" +#include "EncodingConvert.h" + +#ifndef _WIN32 +#include +#endif #pragma warning(disable:4996) #pragma warning(disable:6031) using namespace std; -using namespace CQ; unordered_map UserList{}; -map mLinkedList; -multimap mFwdList; ThreadFactory threads; -string strFileLoc; +std::filesystem::path fpFileLoc; + +constexpr auto msgInit{ R"(欢迎使用Dice!掷骰机器人! +请发送.system gui开启骰娘的后台面板 +开启Master模式通过认主后即可成为我的主人~ +可发送.help查看帮助 +参考文档参看.help链接)" }; //加载数据 void loadData() { - mkDir(DiceDir); - string strLog; - loadDir(loadXML, string(DiceDir + "\\CardTemp\\"), getmCardTemplet(), strLog, true); - if (loadJMap(DiceDir + "\\conf\\CustomReply.json", CardDeck::mReplyDeck) < 0 && loadJMap( - strFileLoc + "ReplyDeck.json", CardDeck::mReplyDeck) > 0) + std::error_code ec; + std::filesystem::create_directory(DiceDir, ec); + ResList logList; + loadDir(loadXML, DiceDir / "CardTemp", mCardTemplet, logList, true); + if (loadJMap(DiceDir / "conf" / "CustomReply.json", CardDeck::mReplyDeck) < 0 && loadJMap( + fpFileLoc / "ReplyDeck.json", CardDeck::mReplyDeck) > 0) { - console.log("迁移自定义回复" + to_string(CardDeck::mReplyDeck.size()) + "条", 1); - saveJMap(DiceDir + "\\conf\\CustomReply.json", CardDeck::mReplyDeck); + logList << "迁移自定义回复" + to_string(CardDeck::mReplyDeck.size()) + "条"; + saveJMap(DiceDir / "conf" / "CustomReply.json", CardDeck::mReplyDeck); } - fmt->set_help("回复列表", "回复触发词列表:" + listKey(CardDeck::mReplyDeck)); - if (loadDir(loadJMap, string(DiceDir + "\\PublicDeck\\"), CardDeck::mExternPublicDeck, strLog) < 1) + fmt->set_help("回复列表", "回复触发词列表:{list_reply_deck}"); + if (loadDir(loadJMap, DiceDir / "PublicDeck", CardDeck::mExternPublicDeck, logList) < 1) { - loadJMap(strFileLoc + "PublicDeck.json", CardDeck::mExternPublicDeck); - loadJMap(strFileLoc + "ExternDeck.json", CardDeck::mExternPublicDeck); + loadJMap(fpFileLoc / "PublicDeck.json", CardDeck::mExternPublicDeck); + loadJMap(fpFileLoc / "ExternDeck.json", CardDeck::mExternPublicDeck); } map_merge(CardDeck::mPublicDeck, CardDeck::mExternPublicDeck); - fmt->set_help("扩展牌堆", listKey(CardDeck::mExternPublicDeck)); - fmt->set_help("全牌堆列表", listKey(CardDeck::mPublicDeck)); - fmt->set_help("master", printQQ(console.master())); //读取帮助文档 - fmt->load(strLog); - if (int cnt; (cnt = loadJMap(DiceDir + "\\conf\\CustomHelp.json", CustomHelp)) < 0) + fmt->load(&logList); + if (int cnt; (cnt = loadJMap(DiceDir / "conf" / "CustomHelp.json", CustomHelp)) < 0) { - if (cnt == -1)console.log("自定义帮助文件json解析失败!", 1); - ifstream ifstreamHelpDoc(strFileLoc + "HelpDoc.txt"); + if (cnt == -1)logList << UTF8toGBK((DiceDir / "conf" / "CustomHelp.json").u8string()) + "解析失败!"; + ifstream ifstreamHelpDoc(fpFileLoc / "HelpDoc.txt"); if (ifstreamHelpDoc) { string strName, strMsg; @@ -102,17 +110,21 @@ void loadData() } if (!CustomHelp.empty()) { - saveJMap(DiceDir + "\\conf\\CustomHelp.json", CustomHelp); - console.log("初始化自定义帮助词条" + to_string(CustomHelp.size()) + "条", 1); + saveJMap(DiceDir / "conf" / "CustomHelp.json", CustomHelp); + logList << "初始化自定义帮助词条" + to_string(CustomHelp.size()) + "条"; } } ifstreamHelpDoc.close(); } map_merge(fmt->helpdoc, CustomHelp); - if (!strLog.empty()) + //读取敏感词库 + loadDir(load_words, DiceDir / "conf" / "censor", censor, logList, true); + loadJMap(DiceDir / "conf" / "CustomCensor.json", censor.CustomWords); + censor.build(); + if (!logList.empty()) { - strLog += "扩展配置读取完毕√"; - console.log(strLog, 1, printSTNow()); + logList << "扩展配置读取完毕√"; + console.log(logList.show(), 1, printSTNow()); } } @@ -123,13 +135,14 @@ void dataInit() if (gm->load() < 0) { multimap ObserveGroup; - loadFile(strFileLoc + "ObserveDiscuss.RDconf", ObserveGroup); - loadFile(strFileLoc + "ObserveGroup.RDconf", ObserveGroup); + loadFile(fpFileLoc / "ObserveDiscuss.RDconf", ObserveGroup); + loadFile(fpFileLoc / "ObserveGroup.RDconf", ObserveGroup); for (auto [grp, qq] : ObserveGroup) { gm->session(grp).sOB.insert(qq); + gm->session(grp).update(); } - ifstream ifINIT(strFileLoc + "INIT.DiceDB"); + ifstream ifINIT(fpFileLoc / "INIT.DiceDB"); if (ifINIT) { long long Group(0); @@ -138,65 +151,124 @@ void dataInit() while (ifINIT >> Group >> nickname >> value) { gm->session(Group).mTable["先攻"].emplace(base64_decode(nickname), value); + gm->session(Group).update(); } } ifINIT.close(); - console.log("初始化旁观与先攻记录" + to_string(gm->mSession.size()) + "条", 1); - gm->save(); + if(gm->mSession.size())console.log("初始化旁观与先攻记录" + to_string(gm->mSession.size()) + "条", 1); } + today = make_unique(DiceDir / "user" / "DiceToday.json"); } //备份数据 void dataBackUp() { - mkDir(DiceDir + "\\conf"); - mkDir(DiceDir + "\\user"); - mkDir(DiceDir + "\\audit"); - //保存卡牌 - saveJMap(strFileLoc + "GroupDeck.json", CardDeck::mGroupDeck); - saveJMap(strFileLoc + "GroupDeckTmp.json", CardDeck::mGroupDeckTmp); - saveJMap(strFileLoc + "PrivateDeck.json", CardDeck::mPrivateDeck); - saveJMap(strFileLoc + "PrivateDeckTmp.json", CardDeck::mPrivateDeckTmp); + std::error_code ec; + std::filesystem::create_directory(DiceDir / "conf", ec); + std::filesystem::create_directory(DiceDir / "user", ec); + std::filesystem::create_directory(DiceDir / "audit", ec); //备份列表 - saveBFile(DiceDir + "\\user\\PlayerCards.RDconf", PList); - saveFile(DiceDir + "\\user\\ChatList.txt", ChatList); - saveBFile(DiceDir + "\\user\\ChatConf.RDconf", ChatList); - clearUser(); - saveFile(DiceDir + "\\user\\UserList.txt", UserList); - saveBFile(DiceDir + "\\user\\UserConf.RDconf", UserList); + saveBFile(DiceDir / "user" / "PlayerCards.RDconf", PList); + saveFile(DiceDir / "user" / "ChatList.txt", ChatList); + saveBFile(DiceDir / "user" / "ChatConf.RDconf", ChatList); + saveFile(DiceDir / "user" / "UserList.txt", UserList); + saveBFile(DiceDir / "user" / "UserConf.RDconf", UserList); } +bool isIniting{ false }; EVE_Enable(eventEnable) { - llStartTime = clock(); - char path[MAX_PATH]; - GetModuleFileNameA(nullptr, path, MAX_PATH); - std::string pathStr(path); - strModulePath = pathStr; - pathStr = pathStr.substr(pathStr.rfind("\\") + 1); - std::transform(pathStr.begin(), pathStr.end(), pathStr.begin(), [](unsigned char c) { return tolower(c); }); - if (pathStr.substr(0, 4) == "java") - { - Mirai = true; - Dice_Full_Ver_For = Dice_Full_Ver + " For Mirai]"; - DiceDir = "Dice" + to_string(getLoginQQ()); - this_thread::sleep_for(3s); //确保Mirai异步信息加载执行完毕 - } - console.setPath(DiceDir + "\\conf\\Console.xml"); - strFileLoc = getAppDirectory(); - mkDir(strFileLoc); // Mirai不会自动创建文件夹 - console.DiceMaid = getLoginQQ(); - GlobalMsg["strSelfName"] = getLoginNick(); - if (GlobalMsg["strSelfName"].empty() && Mirai) - { - GlobalMsg["strSelfName"] = "骰娘[" + toString(console.DiceMaid % 1000, 4) + "]"; - } - mkDir(DiceDir + "\\conf"); - mkDir(DiceDir + "\\user"); - mkDir(DiceDir + "\\audit"); + if (isIniting || Enabled)return; + isIniting = true; + llStartTime = time(nullptr); + #ifndef _WIN32 + CURLcode err; + err = curl_global_init(CURL_GLOBAL_DEFAULT); + if (err != CURLE_OK) + { + console.log("错误: 加载libcurl失败!", 1); + } +#endif + std::string RootDir = DD::getRootDir(); + if (RootDir.empty()) { +#ifdef _WIN32 + char path[MAX_PATH]; + GetModuleFileNameA(nullptr, path, MAX_PATH); + dirExe = std::filesystem::path(path).parent_path(); +#endif + } + else + { + dirExe = RootDir; + } + Dice_Full_Ver_On = Dice_Full_Ver + " on\n" + DD::getDriVer(); + DD::debugLog(Dice_Full_Ver_On); + mCardTemplet = { + { + "COC7", { + "COC7", SkillNameReplace, BasicCOC7, InfoCOC7, AutoFillCOC7, mVariableCOC7, ExpressionCOC7, + SkillDefaultVal, { + {"_default", CardBuild({BuildCOC7}, {"{随机姓名}"}, {})}, + { + "bg", CardBuild({ + {"性别", "{性别}"}, {"年龄", "7D6+8"}, {"职业", "{调查员职业}"}, {"个人描述", "{个人描述}"}, + {"重要之人", "{重要之人}"}, {"思想信念", "{思想信念}"}, {"意义非凡之地", "{意义非凡之地}"}, + {"宝贵之物", "{宝贵之物}"}, {"特质", "{调查员特点}"} + }, {"{随机姓名}"}, {}) + } + } + } + }, + {"BRP", { + "BRP", {}, {}, {}, {}, {}, {}, { + {"__DefaultDice",100} + }, { + {"_default", CardBuild({}, {"{随机姓名}"}, {})}, + { + "bg", CardBuild({ + {"性别", "{性别}"}, {"年龄", "7D6+8"}, {"职业", "{调查员职业}"}, {"个人描述", "{个人描述}"}, + {"重要之人", "{重要之人}"}, {"思想信念", "{思想信念}"}, {"意义非凡之地", "{意义非凡之地}"}, + {"宝贵之物", "{宝贵之物}"}, {"特质", "{调查员特点}"} + }, {"{随机姓名}"}, {}) + } + } + }}, + {"DND", { + "DND", {}, {}, {}, {}, {}, {}, { + {"__DefaultDice",20} + }, { + {"_default", CardBuild({}, {"{随机姓名}"}, {})}, + { + "bg", CardBuild({ + {"性别", "{性别}"}, + }, {"{随机姓名}"}, {}) + } + } + }}, + }; + if ((console.DiceMaid = DD::getLoginQQ())) + { + DiceDir = dirExe / ("Dice" + to_string(console.DiceMaid)); + if (!exists(DiceDir)) { + filesystem::path DiceDirOld(dirExe / "DiceData"); + if (exists(DiceDirOld))rename(DiceDirOld, DiceDir); + else filesystem::create_directory(DiceDir); + } + } + console.setPath(DiceDir / "conf" / "Console.xml"); + fpFileLoc = DiceDir / "com.w4123.dice"; + GlobalMsg["strSelfName"] = DD::getLoginNick(); + if (GlobalMsg["strSelfName"].empty()) + { + GlobalMsg["strSelfName"] = "骰娘[" + toString(console.DiceMaid % 10000, 4) + "]"; + } + std::error_code ec; + std::filesystem::create_directory(DiceDir / "conf", ec); + std::filesystem::create_directory(DiceDir / "user", ec); + std::filesystem::create_directory(DiceDir / "audit", ec); if (!console.load()) { - ifstream ifstreamMaster(strFileLoc + "Master.RDconf"); + ifstream ifstreamMaster(fpFileLoc / "Master.RDconf"); if (ifstreamMaster) { std::pair ClockToWork{}, ClockOffWork{}; @@ -209,197 +281,195 @@ EVE_Enable(eventEnable) console.set("Private", iPrivate); console.set("DisabledJrrp", iDisabledJrrp); console.set("LeaveDiscuss", iLeaveDiscuss); - console.setClock(ClockToWork, ClockEvent::on); - console.setClock(ClockOffWork, ClockEvent::off); + console.setClock(ClockToWork, "on"); + console.setClock(ClockOffWork, "off"); } else { - sendPrivateMsg(console.DiceMaid, - R"(欢迎使用Dice!exp版掷骰机器人! -右键点击酷Q->【应用管理】->【菜单】->【Master模式切换】可开启Master模式,开启对骰娘的后台功能 -炼骰手册:http://shiki.stringempty.xyz/download/DiceMaid_CookBook.html -更多文件参看.help链接 -)"); + DD::sendPrivateMsg(console.DiceMaid, msgInit); } ifstreamMaster.close(); std::map boolConsole; - loadJMap(strFileLoc + "boolConsole.json", boolConsole); + loadJMap(fpFileLoc / "boolConsole.json", boolConsole); for (auto& [key, val] : boolConsole) { console.set(key, val); } - console.setClock({4, 4}, ClockEvent::save); - console.setClock({5, 5}, ClockEvent::clear); + console.setClock({ 11, 45 }, "clear"); console.loadNotice(); console.save(); } //读取聊天列表 - if (loadBFile(DiceDir + "\\user\\UserConf.RDconf", UserList) < 1) + if (loadBFile(DiceDir / "user"/ "UserConf.RDconf", UserList) < 1) { map DefaultDice; - if (loadFile(strFileLoc + "Default.RDconf", DefaultDice) > 0) + if (loadFile(fpFileLoc / "Default.RDconf", DefaultDice) > 0) for (auto p : DefaultDice) { getUser(p.first).create(NEWYEAR).intConf["默认骰"] = p.second; } map DefaultRule; - if (loadFile(strFileLoc + "DefaultRule.RDconf", DefaultRule) > 0) + if (loadFile(fpFileLoc / "DefaultRule.RDconf", DefaultRule) > 0) for (auto p : DefaultRule) { if (isdigit(static_cast(p.second[0])))break; getUser(p.first).create(NEWYEAR).strConf["默认规则"] = p.second; } - ifstream ifName(strFileLoc + "Name.dicedb"); + ifstream ifName(fpFileLoc / "Name.dicedb"); if (ifName) { long long GroupID = 0, QQ = 0; string name; while (ifName >> GroupID >> QQ >> name) { - getUser(QQ).create(NEWYEAR).setNick(GroupID, base64_decode(name)); + name = base64_decode(name); + getUser(QQ).create(NEWYEAR).setNick(GroupID, name); } } } - if (loadFile(DiceDir + "\\user\\UserList.txt", UserList) < 1) + if (loadFile(DiceDir / "user" / "UserList.txt", UserList) < 1) { set WhiteQQ; - if (loadFile(strFileLoc + "WhiteQQ.RDconf", WhiteQQ) > 0) + if (loadFile(fpFileLoc / "WhiteQQ.RDconf", WhiteQQ) > 0) for (auto qq : WhiteQQ) { getUser(qq).create(NEWYEAR).trust(1); } //读取管理员列表 set AdminQQ; - if (loadFile(strFileLoc + "AdminQQ.RDconf", AdminQQ) > 0) + if (loadFile(fpFileLoc / "AdminQQ.RDconf", AdminQQ) > 0) for (auto qq : AdminQQ) { getUser(qq).create(NEWYEAR).trust(4); } if (console.master())getUser(console.master()).create(NEWYEAR).trust(5); - console.log("初始化用户记录" + to_string(UserList.size()) + "条", 1); + if (UserList.size()){ + console.log("初始化用户记录" + to_string(UserList.size()) + "条", 1); + saveFile(DiceDir / "user" / "UserList.txt", UserList); + } } - if (loadBFile(DiceDir + "\\user\\ChatConf.RDconf", ChatList) < 1) + if (loadBFile(DiceDir / "user" / "ChatConf.RDconf", ChatList) < 1) { set GroupList; - if (loadFile(strFileLoc + "DisabledDiscuss.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledDiscuss.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).discuss().set("停用指令"); } GroupList.clear(); - if (loadFile(strFileLoc + "DisabledJRRPDiscuss.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledJRRPDiscuss.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).discuss().set("禁用jrrp"); } GroupList.clear(); - if (loadFile(strFileLoc + "DisabledMEGroup.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledMEGroup.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).discuss().set("禁用me"); } GroupList.clear(); - if (loadFile(strFileLoc + "DisabledHELPDiscuss.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledHELPDiscuss.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).discuss().set("禁用help"); } GroupList.clear(); - if (loadFile(strFileLoc + "DisabledOBDiscuss.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledOBDiscuss.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).discuss().set("禁用ob"); } GroupList.clear(); map mDefault; - if (loadFile(strFileLoc + "DefaultCOC.MYmap", mDefault) > 0) - for (auto it : mDefault) + if (loadFile(fpFileLoc / "DefaultCOC.MYmap", mDefault) > 0) + for (const auto& it : mDefault) { if (it.first.second == msgtype::Private)getUser(it.first.first) .create(NEWYEAR).setConf("rc房规", it.second); else chat(it.first.first).create(NEWYEAR).setConf("rc房规", it.second); } - if (loadFile(strFileLoc + "DisabledGroup.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledGroup.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).group().set("停用指令"); } GroupList.clear(); - if (loadFile(strFileLoc + "DisabledJRRPGroup.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledJRRPGroup.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).group().set("禁用jrrp"); } GroupList.clear(); - if (loadFile(strFileLoc + "DisabledMEDiscuss.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledMEDiscuss.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).group().set("禁用me"); } GroupList.clear(); - if (loadFile(strFileLoc + "DisabledHELPGroup.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledHELPGroup.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).group().set("禁用help"); } GroupList.clear(); - if (loadFile(strFileLoc + "DisabledOBGroup.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "DisabledOBGroup.RDconf", GroupList) > 0) for (auto p : GroupList) { chat(p).group().set("禁用ob"); } GroupList.clear(); map WelcomeMsg; - if (loadFile(strFileLoc + "WelcomeMsg.RDconf", WelcomeMsg) > 0) + if (loadFile(fpFileLoc / "WelcomeMsg.RDconf", WelcomeMsg) > 0) { for (const auto& p : WelcomeMsg) { chat(p.first).group().setText("入群欢迎", p.second); } } - if (loadFile(strFileLoc + "WhiteGroup.RDconf", GroupList) > 0) + if (loadFile(fpFileLoc / "WhiteGroup.RDconf", GroupList) > 0) { for (auto g : GroupList) { chat(g).group().set("许可使用").set("免清"); } } + saveBFile(DiceDir / "user" / "ChatConf.RDconf", ChatList); } - if (loadFile(DiceDir + "\\user\\ChatList.txt", ChatList) < 1) + if (loadFile(DiceDir / "user" / "ChatList.txt", ChatList) < 1) { - map mLastMsgList; - for (auto it : mLastMsgList) - { - if (it.first.second == msgtype::Private)getUser(it.first.first).create(it.second); - else chat(it.first.first).create(it.second).lastmsg(it.second).isGroup = 2 - static_cast(it - .first.second - ); - } std::map mGroupInviter; - if (loadFile(strFileLoc + "GroupInviter.RDconf", mGroupInviter) < 1) + if (loadFile(fpFileLoc / "GroupInviter.RDconf", mGroupInviter) < 1) { - for (auto it : mGroupInviter) + for (const auto& it : mGroupInviter) { chat(it.first).group().inviter = it.second; } } - console.log("初始化群记录" + to_string(ChatList.size()) + "条", 1); + if(ChatList.size()){ + console.log("初始化群记录" + to_string(ChatList.size()) + "条", 1); + saveFile(DiceDir / "user" / "ChatList.txt", ChatList); + } } - for (auto& [gid,gname] : getGroupList()) + for (auto gid : DD::getGroupIDList()) { - chat(gid).group().name(gname).reset("已退"); + chat(gid).group().reset("未进").reset("已退"); } blacklist = make_unique(); - if (blacklist->loadJson(DiceDir + "\\conf\\BlackList.json") < 0) + if (blacklist->loadJson(DiceDir / "conf" / "BlackList.json") < 0) { - blacklist->loadJson(strFileLoc + "BlackMarks.json"); - int cnt = blacklist->loadHistory(strFileLoc); - blacklist->saveJson(DiceDir + "\\conf\\BlackList.json"); - console.log("初始化不良记录" + to_string(cnt) + "条", 1); + blacklist->loadJson(fpFileLoc / "BlackMarks.json"); + int cnt = blacklist->loadHistory(fpFileLoc); + if (cnt) { + blacklist->saveJson(DiceDir / "conf" / "BlackList.json"); + console.log("初始化不良记录" + to_string(cnt) + "条", 1); + } + } + else { + blacklist->loadJson(DiceDir / "conf" / "BlackListEx.json", true); } - fmt = make_unique(); - if (loadJMap(DiceDir + "\\conf\\CustomMsg.json", EditedMsg) < 0)loadJMap(strFileLoc + "CustomMsg.json", EditedMsg); + if (loadJMap(DiceDir / "conf" / "CustomMsg.json", EditedMsg) < 0)loadJMap(fpFileLoc / "CustomMsg.json", EditedMsg); //预修改出场回复文本 if (EditedMsg.count("strSelfName"))GlobalMsg["strSelfName"] = EditedMsg["strSelfName"]; for (auto it : EditedMsg) @@ -408,10 +478,11 @@ EVE_Enable(eventEnable) GlobalMsg["strSelfName"]); GlobalMsg[it.first] = it.second; } + DD::debugLog("Dice.loadData"); loadData(); - if (loadBFile(DiceDir + "\\user\\PlayerCards.RDconf", PList) < 1) + if (loadBFile(DiceDir / "user" / "PlayerCards.RDconf", PList) < 1) { - ifstream ifstreamCharacterProp(strFileLoc + "CharacterProp.RDconf"); + ifstream ifstreamCharacterProp(fpFileLoc / "CharacterProp.RDconf"); if (ifstreamCharacterProp) { long long QQ, GrouporDiscussID; @@ -430,26 +501,23 @@ EVE_Enable(eventEnable) { if (!UserList.count(pl.first))getUser(pl.first).create(NEWYEAR); } - //读取卡牌 - loadJMap(strFileLoc + "GroupDeck.json", CardDeck::mGroupDeck); - loadJMap(strFileLoc + "GroupDeckTmp.json", CardDeck::mGroupDeckTmp); - loadJMap(strFileLoc + "PrivateDeck.json", CardDeck::mPrivateDeck); - loadJMap(strFileLoc + "PrivateDeckTmp.json", CardDeck::mPrivateDeckTmp); dataInit(); // 确保线程执行结束 - while (msgSendThreadRunning)Sleep(10); + while (msgSendThreadRunning)this_thread::sleep_for(10ms); + Aws::InitAPI(options); Enabled = true; threads(SendMsg); threads(ConsoleTimer); threads(warningHandler); threads(frqHandler); + sch.start(); + console.log(GlobalMsg["strSelfName"] + "初始化完成,用时" + to_string(time(nullptr) - llStartTime) + "秒", 0b1, + printSTNow()); //骰娘网络 getDiceList(); - Cloud::update(); - console.log(GlobalMsg["strSelfName"] + "初始化完成,用时" + to_string((clock() - llStartTime) / 1000) + "秒", 0b1, - printSTNow()); - llStartTime = clock(); - return 0; + getExceptGroup(); + llStartTime = time(nullptr); + isIniting = false; } mutex GroupAddMutex; @@ -458,18 +526,24 @@ bool eve_GroupAdd(Chat& grp) { { unique_lock lock_queue(GroupAddMutex); - if (grp.isset("未进") || grp.isset("已退"))grp.reset("未进").reset("已退"); - else if (time(nullptr) - grp.tCreated > 1)return false; - lock_queue.unlock(); + if (grp.lastmsg(time(nullptr)).isset("未进") || grp.isset("已退"))grp.reset("未进").reset("已退"); + else return false; + if (ChatList.size() == 1 && !console.isMasterMode)DD::sendGroupMsg(grp.ID, msgInit); } - if (!console["ListenGroupAdd"] || grp.isset("忽略"))return false; long long fromGroup = grp.ID; + if (grp.Name.empty()) + grp.Name = DD::getGroupName(fromGroup); + Size gsize(DD::getGroupSize(fromGroup)); + if (console["GroupInvalidSize"] > 0 && grp.boolConf.empty() && gsize.siz > (size_t)console["GroupInvalidSize"]) { + grp.set("协议无效"); + } + if (!console["ListenGroupAdd"] || grp.isset("忽略"))return 0; string strNow = printSTNow(); string strMsg(GlobalMsg["strSelfName"]); - try + try { - strMsg += "新加入" + GroupInfo(fromGroup).tostring(); - if (blacklist->get_group_danger(fromGroup)) + strMsg += "新加入:" + DD::printGroupInfo(grp.ID); + if (blacklist->get_group_danger(fromGroup)) { grp.leave(blacklist->list_group_warning(fromGroup)); strMsg += "为黑名单群,已退群"; @@ -477,66 +551,93 @@ bool eve_GroupAdd(Chat& grp) return true; } if (grp.isset("使用许可"))strMsg += "(已获使用许可)"; - if (grp.inviter) - { + else if(grp.isset("协议无效"))strMsg += "(默认协议无效)"; + if (grp.inviter) { strMsg += ",邀请者" + printQQ(chat(fromGroup).inviter); } console.log(strMsg, 0, strNow); int max_trust = 0; + float ave_trust(0); //int max_danger = 0; + long long ownerQQ = 0; ResList blacks; - std::vector list = getGroupMemberList(fromGroup); + std::set list = DD::getGroupMemberList(fromGroup); if (list.empty()) { strMsg += ",群员名单未加载;"; } - else + else { - long long ownerQQ = 0; - - for (auto& each : list) + size_t cntUser{ 0 }, cntMember{ 0 }, cntDiceMaid{ 0 }; + for (auto& each : list) { - if (each.QQID == console.DiceMaid)continue; - if (each.permissions > 1) + if (each == console.DiceMaid)continue; + cntMember++; + if (UserList.count(each)) + { + cntUser++; + ave_trust += getUser(each).nTrust; + } + if (DD::isGroupAdmin(fromGroup, each, false)) { - max_trust |= (1 << trustedQQ(each.QQID)); - if (blacklist->get_qq_danger(each.QQID) > 1) + max_trust |= (1 << trustedQQ(each)); + if (blacklist->get_qq_danger(each) > 1) { - strMsg += ",发现黑名单管理员" + printQQ(each.QQID); + strMsg += ",发现黑名单管理员" + printQQ(each); if (grp.isset("免黑")) { strMsg += "(群免黑)"; } else { - sendGroupMsg(fromGroup, blacklist->list_qq_warning(each.QQID)); - grp.leave("发现黑名单管理员" + printQQ(each.QQID) + "将预防性退群"); + DD::sendGroupMsg(fromGroup, blacklist->list_qq_warning(each)); + grp.leave("发现黑名单管理员" + printQQ(each) + "将预防性退群"); strMsg += ",已退群"; console.log(strMsg, 0b10, strNow); return true; } } - if (each.permissions == 3) + if (DD::isGroupOwner(fromGroup, each, false)) + { + ownerQQ = each; + ave_trust += (gsize.siz - 1) * trustedQQ(each); + strMsg += ",群主" + printQQ(each) + ";"; + } + else { - ownerQQ = each.QQID; - strMsg += ",群主" + printQQ(each.QQID) + ";"; + ave_trust += (gsize.siz - 10) * trustedQQ(each) / 10; } } - else if (blacklist->get_qq_danger(each.QQID) > 1) + else if (blacklist->get_qq_danger(each) > 1) { //max_trust |= 1; - blacks << printQQ(each.QQID); - if (blacklist->get_qq_danger(each.QQID)) + blacks << printQQ(each); + if (blacklist->get_qq_danger(each)) { - AddMsgToQueue(blacklist->list_self_qq_warning(each.QQID), fromGroup, msgtype::Group); + AddMsgToQueue(blacklist->list_self_qq_warning(each), fromGroup, msgtype::Group); } } + if (DD::isDiceMaid(each)) { + ++cntDiceMaid; + } } if (!chat(fromGroup).inviter && list.size() == 2 && ownerQQ) { chat(fromGroup).inviter = ownerQQ; strMsg += "邀请者" + printQQ(ownerQQ); } + if (!cntMember) + { + strMsg += ",群员名单未加载;"; + } + else + { + ave_trust /= cntMember; + strMsg += "\n用户浓度" + to_string(cntUser * 100 / cntMember) + "% (" + to_string(cntUser) + "/" + to_string(cntMember) + "), 信任度" + toString(ave_trust); + } + if (cntDiceMaid) { + strMsg += "\n可识别同系Dice!" + to_string(cntDiceMaid) + "位"; + } } if (!blacks.empty()) { @@ -544,16 +645,16 @@ bool eve_GroupAdd(Chat& grp) strMsg += strNote; } if (console["Private"] && !grp.isset("许可使用")) - { + { //避免小群绕过邀请没加上白名单 - if (max_trust > 1) + if (max_trust > 1 || ave_trust > 0.5) { grp.set("许可使用"); - strMsg += "已自动追加使用许可"; + strMsg += "\n已自动追加使用许可"; } - else + else { - strMsg += "无许可使用,已退群"; + strMsg += "\n无许可使用,已退群"; console.log(strMsg, 1, strNow); grp.leave(getMsg("strPreserve")); return true; @@ -567,6 +668,7 @@ bool eve_GroupAdd(Chat& grp) console.log(strMsg + "\n群" + to_string(fromGroup) + "信息获取失败!", 0b1, printSTNow()); return true; } + if (grp.isset("协议无效")) return 0; if (!GlobalMsg["strAddGroup"].empty()) { this_thread::sleep_for(2s); @@ -576,140 +678,126 @@ bool eve_GroupAdd(Chat& grp) { grp.set("未审核"); this_thread::sleep_for(2s); - AddMsgToQueue(getMsg("strAddGroupNoLicense"), {fromGroup, msgtype::Group}); + AddMsgToQueue(getMsg("strGroupLicenseDeny"), {fromGroup, msgtype::Group}); } return false; } //处理骰子指令 -EVE_PrivateMsg_EX(eventPrivateMsg) +EVE_PrivateMsg(eventPrivateMsg) { - if (!Enabled)return; - FromMsg Msg(eve.message, eve.fromQQ); - if (Msg.DiceFilter())eve.message_block(); - Msg.FwdMsg(eve.message); + if (!Enabled)return 0; + if (fromQQ == console.DiceMaid && !console["ListenSelfEcho"])return 0; + shared_ptr Msg(make_shared(message, fromQQ)); + return Msg->DiceFilter(); } -EVE_GroupMsg_EX(eventGroupMsg) +EVE_GroupMsg(eventGroupMsg) { - if (!Enabled)return; - if (eve.isAnonymous())return; - if (eve.isSystem())return; - Chat& grp = chat(eve.fromGroup).group().lastmsg(time(nullptr)); + if (!Enabled)return 0; + Chat& grp = chat(fromGroup).group().lastmsg(time(nullptr)); + if (fromQQ == console.DiceMaid && !console["ListenGroupEcho"])return 0; if (grp.isset("未进") || grp.isset("已退"))eve_GroupAdd(grp); if (!grp.isset("忽略")) { - FromMsg Msg(eve.message, eve.fromGroup, msgtype::Group, eve.fromQQ); - if (Msg.DiceFilter())eve.message_block(); - Msg.FwdMsg(eve.message); + shared_ptr Msg(make_shared(message, fromGroup, msgtype::Group, fromQQ)); + return Msg->DiceFilter(); } - if (grp.isset("拦截消息"))eve.message_block(); + return grp.isset("拦截消息"); } -EVE_DiscussMsg_EX(eventDiscussMsg) +EVE_DiscussMsg(eventDiscussMsg) { - if (!Enabled)return; + if (!Enabled)return 0; //time_t tNow = time(NULL); if (console["LeaveDiscuss"]) { - sendDiscussMsg(eve.fromDiscuss, getMsg("strLeaveDiscuss")); - Sleep(1000); - setDiscussLeave(eve.fromDiscuss); - return; + DD::sendDiscussMsg(fromDiscuss, getMsg("strLeaveDiscuss")); + this_thread::sleep_for(1000ms); + DD::setDiscussLeave(fromDiscuss); + return 1; } - Chat& grp = chat(eve.fromDiscuss).discuss().lastmsg(time(nullptr)); - if (blacklist->get_qq_danger(eve.fromQQ) && console["AutoClearBlack"]) + Chat& grp = chat(fromDiscuss).discuss().lastmsg(time(nullptr)); + if (blacklist->get_qq_danger(fromQQ) && console["AutoClearBlack"]) { - const string strMsg = "发现黑名单用户" + printQQ(eve.fromQQ) + ",自动执行退群"; - console.log(printChat({eve.fromDiscuss, msgtype::Discuss}) + strMsg, 0b10, printSTNow()); + const string strMsg = "发现黑名单用户" + printQQ(fromQQ) + ",自动执行退群"; + console.log(printChat({fromDiscuss, msgtype::Discuss}) + strMsg, 0b10, printSTNow()); grp.leave(strMsg); - return; + return 1; } - FromMsg Msg(eve.message, eve.fromDiscuss, msgtype::Discuss, eve.fromQQ); - if (Msg.DiceFilter() || grp.isset("拦截消息"))eve.message_block(); - Msg.FwdMsg(eve.message); + shared_ptr Msg(make_shared(message, fromDiscuss, msgtype::Discuss, fromQQ)); + return Msg->DiceFilter() || grp.isset("拦截消息"); } -EVE_System_GroupMemberIncrease(eventGroupMemberIncrease) +EVE_GroupMemberIncrease(eventGroupMemberAdd) { + if (!Enabled)return 0; Chat& grp = chat(fromGroup); if (grp.isset("忽略"))return 0; - if (beingOperateQQ != console.DiceMaid) + if (fromQQ != console.DiceMaid) { if (chat(fromGroup).strConf.count("入群欢迎")) { string strReply = chat(fromGroup).strConf["入群欢迎"]; while (strReply.find("{at}") != string::npos) { - strReply.replace(strReply.find("{at}"), 4, "[CQ:at,qq=" + to_string(beingOperateQQ) + "]"); + strReply.replace(strReply.find("{at}"), 4, "[CQ:at,qq=" + to_string(fromQQ) + "]"); } while (strReply.find("{@}") != string::npos) { - strReply.replace(strReply.find("{@}"), 3, "[CQ:at,qq=" + to_string(beingOperateQQ) + "]"); + strReply.replace(strReply.find("{@}"), 3, "[CQ:at,qq=" + to_string(fromQQ) + "]"); } while (strReply.find("{nick}") != string::npos) { - strReply.replace(strReply.find("{nick}"), 6, getStrangerInfo(beingOperateQQ).nick); - } - while (strReply.find("{age}") != string::npos) - { - strReply.replace(strReply.find("{age}"), 5, to_string(getStrangerInfo(beingOperateQQ).age)); - } - while (strReply.find("{sex}") != string::npos) - { - strReply.replace(strReply.find("{sex}"), 5, - getStrangerInfo(beingOperateQQ).sex == 0 - ? "男" - : getStrangerInfo(beingOperateQQ).sex == 1 - ? "女" - : "未知"); + strReply.replace(strReply.find("{nick}"), 6, DD::getQQNick(fromQQ)); } while (strReply.find("{qq}") != string::npos) { - strReply.replace(strReply.find("{qq}"), 4, to_string(beingOperateQQ)); + strReply.replace(strReply.find("{qq}"), 4, to_string(fromQQ)); } grp.update(time(nullptr)); AddMsgToQueue(strReply, fromGroup, msgtype::Group); } - if (blacklist->get_qq_danger(beingOperateQQ)) + if (blacklist->get_qq_danger(fromQQ)) { const string strNow = printSTNow(); string strNote = printGroup(fromGroup) + "发现" + GlobalMsg["strSelfName"] + "的黑名单用户" + printQQ( - beingOperateQQ) + "入群"; - AddMsgToQueue(blacklist->list_self_qq_warning(beingOperateQQ), fromGroup, msgtype::Group); + fromQQ) + "入群"; + AddMsgToQueue(blacklist->list_self_qq_warning(fromQQ), fromGroup, msgtype::Group); if (grp.isset("免清"))strNote += "(群免清)"; else if (grp.isset("免黑"))strNote += "(群免黑)"; - else if (getGroupMemberInfo(fromGroup, console.DiceMaid).permissions > 1)strNote += "(群内有权限)"; + else if (grp.isset("协议无效"))strNote += "(群协议无效)"; + else if (DD::isGroupAdmin(fromGroup, console.DiceMaid, false))strNote += "(群内有权限)"; else if (console["LeaveBlackQQ"]) { strNote += "(已退群)"; - grp.leave("发现黑名单用户" + printQQ(beingOperateQQ) + "入群,将预防性退群"); + grp.leave("发现黑名单用户" + printQQ(fromQQ) + "入群,将预防性退群"); } console.log(strNote, 0b10, strNow); } } - else - { - return eve_GroupAdd(grp.set("未进")); + else{ + if (!grp.inviter)grp.inviter = operatorQQ; + if (!grp.tLastMsg)grp.set("未进"); + return eve_GroupAdd(grp); } return 0; } -EVE_System_GroupMemberDecrease(eventGroupMemberDecrease) -{ +EVE_GroupMemberKicked(eventGroupMemberKicked){ if (fromQQ == 0)return 0; // 考虑Mirai在机器人自行退群时也会调用一次这个函数 Chat& grp = chat(fromGroup); if (beingOperateQQ == console.DiceMaid) { grp.set("已退"); - if (!console || trustedQQ(fromQQ) > 1 || grp.isset("忽略"))return 0; + if (!console || grp.isset("忽略"))return 0; string strNow = printSTime(stNow); - string strNote = printQQ(fromQQ) + "将" + printQQ(beingOperateQQ) + "移出了群" + to_string(fromGroup); + string strNote = printQQ(fromQQ) + "将" + printQQ(beingOperateQQ) + "移出了" + printChat(grp); console.log(strNote, 0b1000, strNow); - if (!console["ListenGroupKick"] || trustedQQ(fromQQ) > 1 || grp.isset("免黑"))return 0; + if (!console["ListenGroupKick"] || trustedQQ(fromQQ) > 1 || grp.isset("免黑") || grp.isset("协议无效") || ExceptGroups.count(fromGroup)) return 0; DDBlackMarkFactory mark{fromQQ, fromGroup}; - mark.sign().type("kick").time(strNow).note(strNow + " " + strNote); + mark.sign().type("kick").time(strNow).note(strNow + " " + strNote).comment(getMsg("strSelfCall") + "原生记录"); if (grp.inviter && trustedQQ(grp.inviter) < 2) { strNote += ";入群邀请者:" + printQQ(grp.inviter); @@ -718,28 +806,27 @@ EVE_System_GroupMemberDecrease(eventGroupMemberDecrease) grp.reset("许可使用").reset("免清"); blacklist->create(mark.product()); } - else if (mDiceList.count(beingOperateQQ) && subType == 2 && console["ListenGroupKick"]) + else if (mDiceList.count(beingOperateQQ) && console["ListenGroupKick"]) { if (!console || grp.isset("忽略"))return 0; string strNow = printSTime(stNow); - string strNote = printQQ(fromQQ) + "将" + printQQ(beingOperateQQ) + "移出了群" + to_string(fromGroup); + string strNote = printQQ(fromQQ) + "将" + printQQ(beingOperateQQ) + "移出了" + printChat(grp); console.log(strNote, 0b1000, strNow); - if (trustedQQ(fromQQ) > 1 || grp.isset("免黑"))return 0; + if (trustedQQ(fromQQ) > 1 || grp.isset("免黑") || grp.isset("协议无效") || ExceptGroups.count(fromGroup)) return 0; DDBlackMarkFactory mark{fromQQ, fromGroup}; - mark.type("kick").time(strNow).note(strNow + " " + strNote).DiceMaid(beingOperateQQ).masterQQ( - mDiceList[beingOperateQQ]); + mark.type("kick").time(strNow).note(strNow + " " + strNote).DiceMaid(beingOperateQQ).masterQQ(mDiceList[beingOperateQQ]).comment(strNow + " " + printQQ(console.DiceMaid) + "目击"); grp.reset("许可使用").reset("免清"); blacklist->create(mark.product()); } return 0; } -EVE_System_GroupBan(eventGroupBan) +EVE_GroupBan(eventGroupBan) { Chat& grp = chat(fromGroup); if (grp.isset("忽略") || (beingOperateQQ != console.DiceMaid && !mDiceList.count(beingOperateQQ)) || !console[ "ListenGroupBan"])return 0; - if (subType == 1) + if (!duration || !duration[0]) { if (beingOperateQQ == console.DiceMaid) { @@ -751,38 +838,35 @@ EVE_System_GroupBan(eventGroupBan) { string strNow = printSTNow(); long long llOwner = 0; - string strNote = "在" + printGroup(fromGroup) + "中," + printQQ(beingOperateQQ) + "被" + printQQ(fromQQ) + "禁言" + - to_string(duration) + "秒"; - if (trustedQQ(fromQQ) > 1 || grp.isset("免黑")) + string strNote = "在" + printGroup(fromGroup) + "中," + printQQ(beingOperateQQ) + "被" + printQQ(operatorQQ) + "禁言" + duration; + if (!console["ListenGroupBan"] || trustedQQ(operatorQQ) > 1 || grp.isset("免黑") || grp.isset("协议无效") || ExceptGroups.count(fromGroup)) { console.log(strNote, 0b10, strNow); return 1; } - DDBlackMarkFactory mark{fromQQ, fromGroup}; + DDBlackMarkFactory mark{operatorQQ, fromGroup}; mark.type("ban").time(strNow).note(strNow + " " + strNote); - if (mDiceList.count(fromQQ))mark.fromQQ(0); + if (mDiceList.count(operatorQQ))mark.fromQQ(0); if (beingOperateQQ == console.DiceMaid) { if (!console)return 0; - mark.sign(); + mark.sign().comment(getMsg("strSelfCall") + "原生记录"); } else { - mark.DiceMaid(beingOperateQQ).masterQQ(mDiceList[beingOperateQQ]); + mark.DiceMaid(beingOperateQQ).masterQQ(mDiceList[beingOperateQQ]).comment(strNow + " " + printQQ(console.DiceMaid) + "目击"); } //统计群内管理 int intAuthCnt = 0; string strAuthList; - vector list = getGroupMemberList(fromGroup); - for (auto& member : list) + for (auto admin : DD::getGroupAdminList(fromGroup)) { - if (member.permissions == 3) - { - llOwner = member.QQID; + if (DD::isGroupOwner(fromGroup, admin, false)) { + llOwner = admin; } - else if (member.permissions == 2) + else { - strAuthList += '\n' + member.Nick + "(" + to_string(member.QQID) + ")"; + strAuthList += '\n' + printQQ(admin); intAuthCnt++; } } @@ -802,49 +886,65 @@ EVE_System_GroupBan(eventGroupBan) return 0; } -EVE_Request_AddGroup(eventGroupInvited) +EVE_GroupInvited(eventGroupInvited) { if (!console["ListenGroupRequest"])return 0; - if (subType == 2 && groupset(fromGroup, "忽略") < 1) + if (groupset(fromGroup, "忽略") < 1) { this_thread::sleep_for(3s); const string strNow = printSTNow(); - string strMsg = "群添加请求,来自:" + getStrangerInfo(fromQQ).nick + "(" + to_string(fromQQ) + "),群:" + - to_string(fromGroup) + "。"; - if (blacklist->get_group_danger(fromGroup)) + string strMsg = "群添加请求,来自:" + printQQ(fromQQ) + ",群:" + + DD::printGroupInfo(fromGroup); + if (ExceptGroups.count(fromGroup)) { + strMsg += "\n已忽略(默认协议无效)"; + console.log(strMsg, 0b10, strNow); + DD::answerGroupInvited(fromGroup, 3); + } + else if (blacklist->get_group_danger(fromGroup)) { strMsg += "\n已拒绝(群在黑名单中)"; console.log(strMsg, 0b10, strNow); - setGroupAddRequest(responseFlag, 2, 2, ""); + DD::answerGroupInvited(fromGroup, 2); } else if (blacklist->get_qq_danger(fromQQ)) { strMsg += "\n已拒绝(用户在黑名单中)"; console.log(strMsg, 0b10, strNow); - setGroupAddRequest(responseFlag, 2, 2, ""); + DD::answerGroupInvited(fromGroup, 2); } - else if (Chat& grp = chat(fromGroup).group(); grp.isset("许可使用")) - { - grp.set("许可使用").set("未进"); + else if (Chat& grp = chat(fromGroup).group(); grp.isset("许可使用")) { + grp.set("未进"); grp.inviter = fromQQ; - strMsg += "\n已同意(群已许可使用)" + grp.listBoolConf(); + strMsg += "\n已同意(群已许可使用)"; console.log(strMsg, 1, strNow); - setGroupAddRequest(responseFlag, 2, 1, ""); + DD::answerGroupInvited(fromGroup, 1); } else if (trustedQQ(fromQQ)) { - grp.set("许可使用").set("未进"); + grp.set("许可使用").set("未进").reset("未审核").reset("协议无效"); grp.inviter = fromQQ; strMsg += "\n已同意(受信任用户)"; console.log(strMsg, 1, strNow); - setGroupAddRequest(responseFlag, 2, 1, ""); + DD::answerGroupInvited(fromGroup, 1); + } + else if (grp.isset("协议无效")) { + grp.set("未进"); + strMsg += "\n已忽略(协议无效)"; + console.log(strMsg, 0b10, strNow); + DD::answerGroupInvited(fromGroup, 3); + } + else if (console["GroupInvalidSize"] > 0 && DD::getGroupSize(grp.ID).siz > (size_t)console["GroupInvalidSize"]) { + grp.set("协议无效"); + strMsg += "\n已忽略(大群默认协议无效)"; + console.log(strMsg, 0b10, strNow); + DD::answerGroupInvited(fromGroup, 3); } else if (console && console["Private"]) { - sendPrivateMsg(fromQQ, getMsg("strPreserve")); + DD::sendPrivateMsg(fromQQ, getMsg("strPreserve")); strMsg += "\n已拒绝(当前在私用模式)"; console.log(strMsg, 1, strNow); - setGroupAddRequest(responseFlag, 2, 2, ""); + DD::answerGroupInvited(fromGroup, 2); } else { @@ -853,12 +953,51 @@ EVE_Request_AddGroup(eventGroupInvited) strMsg += "已同意"; this_thread::sleep_for(2s); console.log(strMsg, 1, strNow); - setGroupAddRequest(responseFlag, 2, 1, ""); + DD::answerGroupInvited(fromGroup, true); } return 1; } return 0; } +EVE_FriendRequest(eventFriendRequest) { + if (!console["ListenFriendRequest"])return 0; + string strMsg = "好友添加请求,来自 " + printQQ(fromQQ) + ":" + message; + this_thread::sleep_for(3s); + if (blacklist->get_qq_danger(fromQQ)) { + strMsg += "\n已拒绝(用户在黑名单中)"; + DD::answerFriendRequest(fromQQ, 2, ""); + console.log(strMsg, 0b10, printSTNow()); + } + else if (trustedQQ(fromQQ)) { + strMsg += "\n已同意(受信任用户)"; + DD::answerFriendRequest(fromQQ, 1, getMsg("strAddFriendWhiteQQ")); + console.log(strMsg, 1, printSTNow()); + } + else if (console["AllowStranger"] < 2 && !UserList.count(fromQQ)) { + strMsg += "\n已拒绝(无用户记录)"; + DD::answerFriendRequest(fromQQ, 2, getMsg("strFriendDenyNotUser")); + console.log(strMsg, 1, printSTNow()); + } + else if (console["AllowStranger"] < 1) { + strMsg += "\n已拒绝(非信任用户)"; + DD::answerFriendRequest(fromQQ, 2, getMsg("strFriendDenyNoTrust")); + console.log(strMsg, 1, printSTNow()); + } + else { + strMsg += "\n已同意"; + DD::answerFriendRequest(fromQQ, 1, getMsg("strAddFriend")); + console.log(strMsg, 1, printSTNow()); + } + return 1; +} +EVE_FriendAdded(eventFriendAdd) { + if (!console["ListenFriendAdd"])return 0; + this_thread::sleep_for(3s); + GlobalMsg["strAddFriendWhiteQQ"].empty() + ? AddMsgToQueue(getMsg("strAddFriend"), fromQQ) + : AddMsgToQueue(getMsg("strAddFriendWhiteQQ"), fromQQ); + return 0; +} EVE_Menu(eventMasterMode) { @@ -866,27 +1005,33 @@ EVE_Menu(eventMasterMode) { console.isMasterMode = false; console.killMaster(); +#ifdef _WIN32 MessageBoxA(nullptr, "Master模式已关闭√\nmaster已清除", "Master模式切换", MB_OK | MB_ICONINFORMATION); +#endif } else { console.isMasterMode = true; console.save(); +#ifdef _WIN32 MessageBoxA(nullptr, "Master模式已开启√\n认主请对骰娘发送.master public/private", "Master模式切换", MB_OK | MB_ICONINFORMATION); +#endif } return 0; } +#ifdef _WIN32 EVE_Menu(eventGUI) { return GUIMain(); } +#endif -EVE_Disable(eventDisable) -{ +void global_exit() { Enabled = false; - threads = {}; dataBackUp(); + sch.end(); + censor = {}; fmt.reset(); gm.reset(); PList.clear(); @@ -895,15 +1040,36 @@ EVE_Disable(eventDisable) console.reset(); EditedMsg.clear(); blacklist.reset(); - return 0; + Aws::ShutdownAPI(options); +#ifndef _WIN32 + curl_global_cleanup(); +#endif + threads.exit(); +} + +EVE_Disable(eventDisable) +{ + global_exit(); } EVE_Exit(eventExit) { - if (!Enabled) - return 0; - dataBackUp(); - return 0; + if (Enabled)global_exit(); } -MUST_AppInfo_RETURN(CQAPPID); +EVE_Menu(eventGlobalSwitch) { + if (console["DisabledGlobal"]) { + console.set("DisabledGlobal", 0); +#ifdef _WIN32 + MessageBoxA(nullptr, "骰娘已结束静默√", "全局开关", MB_OK | MB_ICONINFORMATION); +#endif + } + else { + console.set("DisabledGlobal", 1); +#ifdef _WIN32 + MessageBoxA(nullptr, "骰娘已全局静默√", "全局开关", MB_OK | MB_ICONINFORMATION); +#endif + } + + return 0; +} \ No newline at end of file diff --git a/Dice/Dice.vcxproj b/Dice/Dice.vcxproj deleted file mode 100644 index 90bedd84..00000000 --- a/Dice/Dice.vcxproj +++ /dev/null @@ -1,346 +0,0 @@ -锘 - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {BA051175-B8E8-4104-9DD9-B9E225738C42} - Win32Proj - Dice - 10.0 - app - - - - DynamicLibrary - true - v142 - MultiByte - - - DynamicLibrary - false - true - MultiByte - v142 - - - DynamicLibrary - true - v142 - MultiByte - - - DynamicLibrary - false - true - MultiByte - v142 - - - - - - - - - - - - - - - - - - - - - false - MultiThreaded - static - static - $(SolutionDir)$(Configuration)\ - $(Configuration)\ - - - false - - - true - MultiThreadedDebug - static - static - false - - - true - MultiThreadedDebug - static - static - false - - - false - MultiThreaded - static - static - false - - - - NotUsing - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;DICE_EXPORTS;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) - true - SyncCThrow - true - MultiThreaded - AnySuitable - Speed - true - true - true - Precise - ..\CQSDK;%(AdditionalIncludeDirectories) - true - stdcpp17 - - - Windows - true - true - false - false - UseLinkTimeCodeGeneration - %(ForceSymbolReferences) - NotSet - %(AdditionalLibraryDirectories) - - - - - - - copy /Y $(TargetPath) "D:\閰稱 Air\dev\com.w4123.dice" - - - - - NotUsing - Level3 - Disabled - true - WIN32;_DEBUG;DICE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - SyncCThrow - ..\CQSDK;%(AdditionalIncludeDirectories) - MultiThreadedDebug - stdcpp17 - - - Windows - true - %(AdditionalLibraryDirectories) - - - - - - - copy /Y $(TargetPath) "D:\閰稱 Air\dev\com.w4123.dice" - - - - - NotUsing - Level3 - Disabled - false - _DEBUG;DICE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - SyncCThrow - ..\CQSDK;%(AdditionalIncludeDirectories) - MultiThreadedDebug - stdcpp17 - - - Windows - true - %(AdditionalLibraryDirectories) - - - - - - - copy /Y $(TargetPath) "D:\閰稱 Air\dev\com.w4123.dice" - - - - - NotUsing - Level3 - MaxSpeed - true - true - true - NDEBUG;DICE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - SyncCThrow - true - true - MultiThreaded - Precise - ..\CQSDK;%(AdditionalIncludeDirectories) - true - AnySuitable - Speed - true - stdcpp17 - - - Windows - true - true - false - false - UseLinkTimeCodeGeneration - NotSet - %(AdditionalLibraryDirectories) - - - - - - - copy /Y $(TargetPath) "D:\閰稱 Air\dev\com.w4123.dice" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Dice/Dice.vcxproj.DotSettings b/Dice/Dice.vcxproj.DotSettings deleted file mode 100644 index 21022da7..00000000 --- a/Dice/Dice.vcxproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ -锘 - Cpp17 \ No newline at end of file diff --git a/Dice/Dice.vcxproj.filters b/Dice/Dice.vcxproj.filters deleted file mode 100644 index 66a4e30a..00000000 --- a/Dice/Dice.vcxproj.filters +++ /dev/null @@ -1,299 +0,0 @@ -锘 - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {264cbd68-17fb-46f0-95a7-e30bd02878ac} - - - {9991a6c1-3689-4b27-a14b-c09c80989141} - - - {911e9211-0dd0-42ff-82bc-c6ae90928af6} - - - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠禱CQCPP - - - 婧愭枃浠禱CQCPP - - - 婧愭枃浠禱CQCPP - - - 婧愭枃浠禱CQCPP - - - 婧愭枃浠禱CQCPP - - - 婧愭枃浠禱CQCPP - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - 婧愭枃浠 - - - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠禱CQHead - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠禱nlohmann - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - 澶存枃浠 - - - - - 璧勬簮鏂囦欢 - - - - - 璧勬簮鏂囦欢 - - - - - 璧勬簮鏂囦欢 - - - - - 璧勬簮鏂囦欢 - - - 璧勬簮鏂囦欢 - - - \ No newline at end of file diff --git a/Dice/Dice.vcxproj.user b/Dice/Dice.vcxproj.user deleted file mode 100644 index 7e04c94d..00000000 --- a/Dice/Dice.vcxproj.user +++ /dev/null @@ -1,6 +0,0 @@ -锘 - - - false - - \ No newline at end of file diff --git a/Dice/DiceCensor.cpp b/Dice/DiceCensor.cpp new file mode 100644 index 00000000..0a60df09 --- /dev/null +++ b/Dice/DiceCensor.cpp @@ -0,0 +1,99 @@ +#include "DiceCensor.h" +#include "SHKTrie.h" +#include "DiceFile.hpp" +#include "Jsonio.h" +#include "STLExtern.hpp" +#include "DiceConsole.h" +#include "EncodingConvert.h" +#include +#include +#include + +TrieG wordG; + +enumap sens{ "Ignore","Notice","Caution","Warning","Danger" }; + +int load_words(const std::filesystem::path& path, Censor& cens) { + int cnt(0); + ifstream fin(path); + if (!fin)return -1; + bool isUTF8{ false }; + string word; + Censor::Level danger = Censor::Level::Warning; + while (getline(fin, word)) { + if (word.empty())break; + if (word[0] == '#') { + word = word.substr(1); + //注释敏感等级 + if (sens.count(word)) { + danger = (Censor::Level)sens[word]; + } + //注释文件编码 + else if (word == "UTF8") { + isUTF8 = true; + } + } + else { + if (isUTF8)word = UTF8toGBK(word); + cens.insert(word, danger); + cnt++; + } + } + return cnt; +} + +void Censor::insert(const string& word, Level danger = Level::Warning) { + words[word] = danger; +} +void Censor::add_word(const string& word, Level danger = Level::Warning) { + CustomWords[word] = danger; + if (!words.count(word))wordG.insert(word); + words[word] = danger; + save(); +} +bool Censor::rm_word(const string& word) { + if (!CustomWords.count(word)) { + //无可移除 + if (!words.count(word)) { + return false; + } + //无自定义,但词库包含 + else { + words[word] = Level::Ignore; + CustomWords[word] = Level::Ignore; + } + } + else{ + words[word] = Level::Ignore; + CustomWords.erase(word); + } + save(); + return true; +} + +Censor::Level Censor::get_level(const string& lv) { + if (!sens.count(lv))return Level::Warning; + return (Censor::Level)sens[lv]; +} + +void Censor::build() { + map_merge(words, CustomWords); + wordG.build(words); +} +void Censor::save() { + saveJMap(DiceDir / "conf" / "CustomCensor.json", censor.CustomWords); +} + +int Censor::search(const string& text, unordered_set& res) { + std::bitset<6> sens; + wordG.search(text, res); + for (auto& word : res) { + sens.set((size_t)words[word]); + } + return sens[5] ? 5 + : sens[4] ? 4 + : sens[3] ? 3 + : sens[2] ? 2 + : sens[1] ? 1 + : 0; +} \ No newline at end of file diff --git a/Dice/DiceCensor.h b/Dice/DiceCensor.h new file mode 100644 index 00000000..323bd30f --- /dev/null +++ b/Dice/DiceCensor.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include "STLExtern.hpp" +#include +using std::string; +using std::unordered_set; + +class Censor { + string dirWords; +public: + enum class Level :size_t { + Ignore, //无视 + Notice, //仅0级通知 + Caution, //仅1级通知 + Warning, //警告用户且拒绝称呼,并1级通知 + Danger, //警告用户且拒绝指令,并3级通知 + Critical, //仅占位,不启用 + }; + map words; + map CustomWords; + Level get_level(const string&); + void insert(const string& word, Level); + void add_word(const string& word, Level); + bool rm_word(const string& word); + void build(); + void save(); + size_t size()const { return words.size(); } + int search(const string& text, unordered_set& res); +}; + +inline Censor censor; + +int load_words(const std::filesystem::path& path, Censor& cens); \ No newline at end of file diff --git a/Dice/DiceCloud.cpp b/Dice/DiceCloud.cpp index 30d0dbb0..23d3b1b9 100644 --- a/Dice/DiceCloud.cpp +++ b/Dice/DiceCloud.cpp @@ -2,55 +2,49 @@ * 骰娘网络 * Copyright (C) 2019 String.Empty */ -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include +#include +#include #include "json.hpp" #include "DiceCloud.h" #include "GlobalVar.h" #include "EncodingConvert.h" -#include "CQAPI_EX.h" #include "DiceNetwork.h" #include "DiceConsole.h" #include "DiceMsgSend.h" #include "DiceEvent.h" +#include "DDAPI.h" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include #pragma comment(lib, "urlmon.lib") +#endif using namespace std; using namespace nlohmann; namespace Cloud { - void update() + void heartbeat() { const string strVer = GBKtoUTF8(string(Dice_Ver)); - const string data = "DiceQQ=" + to_string(console.DiceMaid) + "&masterQQ=" + to_string(console.master()) + "&Ver=" + + const string data = "&masterQQ=" + to_string(console.master()) + "&Ver=" + strVer + "&isGlobalOn=" + to_string(!console["DisabledGlobal"]) + "&isPublic=" + to_string(!console["Private"]) + "&isVisible=" + to_string(console["CloudVisible"]); - char* frmdata = new char[data.length() + 1]; - strcpy_s(frmdata, data.length() + 1, data.c_str()); - string temp; - Network::POST("shiki.stringempty.xyz", "/DiceCloud/update.php", 80, frmdata, temp); - //AddMsgToQueue(temp, masterQQ); - delete[] frmdata; - } - - void upWarning(const char* warning) - { - char* frmdata = new char[strlen(warning) + 1]; - strcpy_s(frmdata, strlen(warning) + 1, warning); - string temp; - Network::POST("shiki.stringempty.xyz", "/DiceCloud/warning_upload.php", 80, frmdata, temp); - delete[] frmdata; + DD::heartbeat(data); } int checkWarning(const char* warning) { char* frmdata = new char[strlen(warning) + 1]; +#ifdef _MSC_VER strcpy_s(frmdata, strlen(warning) + 1, warning); +#else + strcpy(frmdata, warning); +#endif string temp; Network::POST("shiki.stringempty.xyz", "/DiceCloud/warning_check.php", 80, frmdata, temp); delete[] frmdata; @@ -65,12 +59,29 @@ namespace Cloud return 0; } - int DownloadFile(const char* url, const char* downloadPath) + [[deprecated]] int DownloadFile(const char* url, const char* downloadPath) { +#ifdef _WIN32 DeleteUrlCacheEntryA(url); if (URLDownloadToFileA(nullptr, url, downloadPath, 0, nullptr) != S_OK) return -1; if (_access(downloadPath, 0))return -2; return 0; +#else + return -1; +#endif + } + + int DownloadFile(const char* url, const std::filesystem::path& downloadPath) + { +#ifdef _WIN32 + DeleteUrlCacheEntryA(url); + if (URLDownloadToFileA(nullptr, url, downloadPath.string().c_str(), 0, nullptr) != S_OK) return -1; + std::error_code ec; + if (!std::filesystem::exists(downloadPath, ec) || ec)return -2; + return 0; +#else + return -1; +#endif } int checkUpdate(FromMsg* msg) diff --git a/Dice/DiceCloud.h b/Dice/DiceCloud.h index 40d1fa3f..126e5f22 100644 --- a/Dice/DiceCloud.h +++ b/Dice/DiceCloud.h @@ -1,16 +1,19 @@ +#pragma once + /* * 骰娘网络 * Copyright (C) 2019 String.Empty */ -#pragma once + +#include class FromMsg; namespace Cloud { - void update(); - void upWarning(const char* warning); + void heartbeat(); int checkWarning(const char* warning); - int DownloadFile(const char* url, const char* downloadPath); + [[deprecated]] int DownloadFile(const char* url, const char* downloadPath); + int DownloadFile(const char* url, const std::filesystem::path& downloadPath); int checkUpdate(FromMsg* msg); } diff --git a/Dice/DiceConsole.cpp b/Dice/DiceConsole.cpp index 131a36e9..d48a45ef 100644 --- a/Dice/DiceConsole.cpp +++ b/Dice/DiceConsole.cpp @@ -7,7 +7,8 @@ * |_______/ |________| |________| |________| |__| * * Dice! QQ Dice Robot for TRPG - * Copyright (C) 2018-2019 w4123溯洄 + * Copyright (C) 2018-2021 w4123溯洄 + * Copyright (C) 2019-2021 String.Empty * * This program is free software: you can redistribute it and/or modify it under the terms * of the GNU Affero General Public License as published by the Free Software Foundation, @@ -31,36 +32,46 @@ #include "DiceCloud.h" #include "Jsonio.h" #include "BlackListManager.h" +#include "DiceSchedule.h" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif +#include "DDAPI.h" using namespace std; -using namespace CQ; -const std::map Console::intDefault{ - {"DisabledGlobal", 0}, {"DisabledBlock", 0}, {"DisabledListenAt", 1}, - {"DisabledMe", 1}, {"DisabledJrrp", 0}, {"DisabledDeck", 1}, {"DisabledDraw", 0}, {"DisabledSend", 0}, - {"Private", 0}, {"CheckGroupLicense", 0}, {"LeaveDiscuss", 0}, - {"ListenGroupRequest", 1}, {"ListenGroupAdd", 1}, - {"ListenFriendRequest", 1}, {"ListenFriendAdd", 1}, {"AllowStranger", 1}, - {"AutoClearBlack", 1}, {"LeaveBlackQQ", 0}, - {"ListenGroupKick", 1}, {"ListenGroupBan", 1}, {"ListenSpam", 1}, - {"BannedLeave", 0}, {"BannedBanInviter", 0}, - {"KickedBanInviter", 0}, - {"CloudBlackShare", 1}, {"BelieveDiceList", 0}, {"CloudVisible", 1}, - {"SystemAlarmCPU", 90}, {"SystemAlarmRAM", 90}, - {"SendIntervalIdle", 500}, {"SendIntervalBusy", 100} +const std::mapConsole::intDefault{ +{"DisabledGlobal",0},{"DisabledBlock",0},{"DisabledListenAt",1}, +{"DisabledMe",1},{"DisabledJrrp",0},{"DisabledDeck",1},{"DisabledDraw",0},{"DisabledSend",0}, +{"Private",0},{"CheckGroupLicense",0},{"LeaveDiscuss",0}, +{"ListenGroupRequest",1},{"ListenGroupAdd",1}, +{"ListenFriendRequest",1},{"ListenFriendAdd",1},{"AllowStranger",1}, +{"AutoClearBlack",1},{"LeaveBlackQQ",0}, +{"ListenGroupKick",1},{"ListenGroupBan",1},{"ListenSpam",1}, +{"BannedBanInviter",0},{"KickedBanInviter",0}, +{"GroupInvalidSize",500},{"GroupClearLimit",20}, +{"CloudBlackShare",1},{"BelieveDiceList",0},{"CloudVisible",1}, +{"SystemAlarmCPU",90},{"SystemAlarmRAM",90},{"SystemAlarmDisk",90}, +{"SendIntervalIdle",500},{"SendIntervalBusy",100}, +//自动保存事件间隔[min],自动图片清理间隔[h],自动重启框架间隔[h] +{"AutoSaveInterval",5},{"AutoClearImage",0},{"AutoFrameRemake",0}, +//用户记录清理期限[Day],群记录清理期限[Day] +{"InactiveUserLine",360},{"InactiveGroupLine",360}, +//接收群内自己的消息,接受自己私聊消息 +{"ListenGroupEcho",0},{"ListenSelfEcho",0}, }; -const enumap Console::mClockEvent{"off", "on", "save", "clear"}; +const enumap Console::mClockEvent{ "off", "on", "save", "clear" }; -int Console::setClock(Clock c, ClockEvent e) +int Console::setClock(Clock c, const string& e) { if (c.first > 23 || c.second > 59)return -1; - if (static_cast(e) > 3)return -2; mWorkClock.emplace(c, e); save(); return 0; } -int Console::rmClock(Clock c, ClockEvent e) +int Console::rmClock(Clock c, const string& e) { if (const auto it = match(mWorkClock, c, e); it != mWorkClock.end()) { @@ -76,23 +87,7 @@ ResList Console::listClock() const ResList list; for (const auto& [clock, eve] : mWorkClock) { - string strClock = printClock(clock); - switch (eve) - { - case ClockEvent::on: - strClock += " 定时开启"; - break; - case ClockEvent::off: - strClock += " 定时关闭"; - break; - case ClockEvent::save: - strClock += " 定时保存"; - break; - case ClockEvent::clear: - strClock += " 定时清群"; - break; - default: break; - } + string strClock = printClock(clock) + " " + eve; list << strClock; } return list; @@ -140,7 +135,7 @@ void Console::rmNotice(chatType ct) int Console::log(const std::string& strMsg, int note_lv, const string& strTime) { - ofstream fout(string(DiceDir + "\\audit\\log") + to_string(DiceMaid) + "_" + printDate() + ".txt", + ofstream fout(DiceDir / "audit" / ("log" + to_string(DiceMaid) + "_" + printDate() + ".txt"), ios::out | ios::app); fout << strTime << "\t" << note_lv << "\t" << printLine(strMsg) << std::endl; fout.close(); @@ -152,20 +147,22 @@ int Console::log(const std::string& strMsg, int note_lv, const string& strTime) { if (!(level & note_lv))continue; AddMsgToQueue(note, ct); - Cnt++; + Cnt++; + if(strTime.empty())this_thread::sleep_for(chrono::milliseconds(console["SendIntervalIdle"])); } - if (!Cnt)sendPrivateMsg(DiceMaid, note); + if (!Cnt)DD::sendPrivateMsg(DiceMaid, note); } + else DD::debugLog(note); return Cnt; -} - +} void Console::newMaster(long long qq) { - masterQQ = qq; - getUser(qq).trust(5); + masterQQ = qq; + if (trustedQQ(qq) < 5)getUser(qq).trust(5); setNotice({qq, msgtype::Private}, 0b111111); - save(); + save(); AddMsgToQueue(getMsg("strNewMaster"), qq); + AddMsgToQueue(intConf["Private"] ? getMsg("strNewMasterPrivate") : getMsg("strNewMasterPublic"), qq); } void Console::reset() @@ -175,18 +172,40 @@ void Console::reset() NoticeList.clear(); } +bool Console::load() { + string s; + //DSens.build({ {"nn老公",2 } }); + if (!rdbuf(fpPath, s))return false; + DDOM xml(s); + if (xml.count("mode"))isMasterMode = stoi(xml["mode"].strValue); + if (xml.count("master"))masterQQ = stoll(xml["master"].strValue); + if (xml.count("clock")) + for (auto& child : xml["clock"].vChild) { + mWorkClock.insert({ + scanClock(child.strValue), child.tag + }); + } + if (xml.count("conf")) + for (auto& child : xml["conf"].vChild) { + std::pair conf; + readini(child.strValue, conf); + if (intDefault.count(conf.first))intConf.insert(conf); + } + loadNotice(); + return true; +} void Console::loadNotice() { - if (loadFile(DiceDir + "\\conf\\NoticeList.txt", NoticeList) < 1) + if (loadFile(DiceDir / "conf" / "NoticeList.txt", NoticeList) < 1) { std::set sChat; - if (loadFile(static_cast(getAppDirectory()) + "MonitorList.RDconf", sChat) > 0) + if (loadFile(DiceDir / "com.w4123.dice" / "MonitorList.RDconf", sChat) > 0) for (const auto& it : sChat) { console.setNotice(it, 0b100000); } sChat.clear(); - if (loadFile(DiceDir + "\\conf\\RecorderList.RDconf", sChat) > 0) + if (loadFile(DiceDir / "conf" / "RecorderList.RDconf", sChat) > 0) for (const auto& it : sChat) { console.setNotice(it, 0b11011); @@ -206,39 +225,51 @@ void Console::loadNotice() void Console::saveNotice() const { - saveFile(DiceDir + "\\conf\\NoticeList.txt", NoticeList); + saveFile(DiceDir / "conf" / "NoticeList.txt", NoticeList); } Console console; //DiceModManager modules{}; - +//除外群列表 +std::set ExceptGroups; //骰娘列表 std::map mDiceList; //程序启动时间 -long long llStartTime = clock(); +long long llStartTime = time(nullptr); //当前时间 -SYSTEMTIME stNow{}; -SYSTEMTIME stTmp{}; +tm stNow{}; std::string printSTNow() { - GetLocalTime(&stNow); + time_t tt = time(nullptr); +#ifdef _MSC_VER + localtime_s(&stNow, &tt); +#else + localtime_r(&tt, &stNow); +#endif return printSTime(stNow); } std::string printDate() { - return to_string(stNow.wYear) + "-" + (stNow.wMonth < 10 ? "0" : "") + to_string(stNow.wMonth) + "-" + ( - stNow.wDay < 10 ? "0" : "") + to_string(stNow.wDay); + return to_string(stNow.tm_year + 1900) + "-" + (stNow.tm_mon + 1 < 10 ? "0" : "") + to_string(stNow.tm_mon + 1) + "-" + ( + stNow.tm_mday < 10 ? "0" : "") + to_string(stNow.tm_mday); } std::string printDate(time_t tt) { tm t{}; - if (!tt || localtime_s(&t, &tt))return R"(????-??-??)"; + if(!tt) return R"(????-??-??)"; +#ifdef _MSC_VER + auto ret = localtime_s(&t, &tt); + if(ret) return R"(????-??-??)"; +#else + auto ret = localtime_r(&tt, &t); + if(!ret) return R"(????-??-??)"; +#endif return to_string(t.tm_year + 1900) + "-" + to_string(t.tm_mon + 1) + "-" + to_string(t.tm_mday); } @@ -256,366 +287,72 @@ string printClock(std::pair clock) return strClock; } -std::string printSTime(const SYSTEMTIME st) +std::string printSTime(const tm st) { - return to_string(st.wYear) + "-" + (st.wMonth < 10 ? "0" : "") + to_string(st.wMonth) + "-" + ( - st.wDay < 10 ? "0" : "") + to_string(st.wDay) + " " + (st.wHour < 10 ? "0" : "") + to_string(st.wHour) + ":" + return to_string(st.tm_year + 1900) + "-" + (st.tm_mon + 1 < 10 ? "0" : "") + to_string(st.tm_mon + 1) + "-" + ( + st.tm_mday < 10 ? "0" : "") + to_string(st.tm_mday) + " " + (st.tm_hour < 10 ? "0" : "") + to_string(st.tm_hour) + ":" + ( - st.wMinute < 10 ? "0" : "") + to_string(st.wMinute) + ":" + (st.wSecond < 10 ? "0" : "") + - to_string(st.wSecond); + st.tm_min < 10 ? "0" : "") + to_string(st.tm_min) + ":" + (st.tm_sec < 10 ? "0" : "") + + to_string(st.tm_sec); } - -//打印用户昵称QQ -string printQQ(long long llqq) -{ - string nick = getStrangerInfo(llqq).nick; - string::size_type i; - while ((i = nick.find(' ')) != string::npos) + //打印用户昵称QQ + string printQQ(long long llqq) { - nick.erase(nick.begin() + i); + string nick = DD::getQQNick(llqq); + if (nick.empty())return getMsg("stranger") + "[" + to_string(llqq) + "]"; + return nick + "(" + to_string(llqq) + ")"; } - return nick + "(" + to_string(llqq) + ")"; -} - -//打印QQ群号 -string printGroup(long long llgroup) -{ - if (!llgroup)return "私聊"; - const auto GroupList = getGroupList(); - if (GroupList.count(llgroup)) + //打印QQ群号 + string printGroup(long long llgroup) { - return GroupList.at(llgroup) + "(" + to_string(llgroup) + ")"; + if (!llgroup)return "私聊"; + if (ChatList.count(llgroup))return printChat(ChatList[llgroup]); + if (string name{ DD::getGroupName(llgroup) };!name.empty())return "[" + name + "](" + to_string(llgroup) + ")"; + return "群(" + to_string(llgroup) + ")"; } - return "群聊(" + to_string(llgroup) + ")"; -} - -//打印聊天窗口 -string printChat(chatType ct) -{ - switch (ct.second) + //打印聊天窗口 + string printChat(chatType ct) { - case msgtype::Private: - return printQQ(ct.first); - case msgtype::Group: - return printGroup(ct.first); - case msgtype::Discuss: - return "讨论组(" + to_string(ct.first) + ")"; - default: - break; + switch (ct.second) + { + case msgtype::Private: + return printQQ(ct.first); + case msgtype::Group: + return printGroup(ct.first); + case msgtype::Discuss: + return "讨论组(" + to_string(ct.first) + ")"; + default: + break; + } + return ""; } - return ""; -} - //获取骰娘列表 void getDiceList() { +} +//获取骰娘列表 +void getExceptGroup() { std::string list; - if (!Network::GET("shiki.stringempty.xyz", "/DiceList/", 80, list) && mDiceList.empty()) - { - console.log("获取骰娘列表时遇到错误: \n" + list, 1, printSTNow()); - return; - } - readJson(list, mDiceList); + if (Network::GET("shiki.stringempty.xyz", "/DiceCloud/except_group.json", 80, list)) + readJson(list, ExceptGroups); } -bool operator==(const SYSTEMTIME& st, const Console::Clock clock) +bool operator==(const tm& st, const Console::Clock clock) { - return st.wHour == clock.first && st.wHour == clock.second; + return st.tm_hour == clock.first && st.tm_hour == clock.second; } -bool operator<(const Console::Clock clock, const SYSTEMTIME& st) +bool operator<(const Console::Clock clock, const tm& st) { - return st.wHour == clock.first && st.wHour == clock.second; + return st.tm_hour == clock.first && st.tm_hour == clock.second; } -//简易计时器 -void ConsoleTimer() -{ - Console::Clock clockNow{stNow.wHour, stNow.wMinute}; - long long perLastCPU = 0; - long long perLastRAM = 0; - while (Enabled) - { - GetLocalTime(&stNow); - //分钟时点变动 - if (stTmp.wMinute != stNow.wMinute) - { - stTmp = stNow; - clockNow = {stNow.wHour, stNow.wMinute}; - for (const auto& [clock,eve_type] : multi_range(console.mWorkClock, clockNow)) - { - switch (eve_type) - { - case ClockEvent::on: - if (console["DisabledGlobal"]) - { - console.set("DisabledGlobal", 0); - console.log(getMsg("strClockToWork"), 0b10000, ""); - } - break; - case ClockEvent::off: - if (!console["DisabledGlobal"]) - { - console.set("DisabledGlobal", 1); - console.log(getMsg("strClockOffWork"), 0b10000, ""); - } - break; - case ClockEvent::save: - dataBackUp(); - console.log(GlobalMsg["strSelfName"] + "定时保存完成√", 1, printSTime(stTmp)); - break; - case ClockEvent::clear: - if (console && console["AutoClearBlack"] && clearGroup("black")) - console.log(GlobalMsg["strSelfName"] + "定时清群完成√", 1, printSTNow()); - break; - default: break; - } - } - //整点事件 - if (stNow.wMinute % 10 == 0) - { - Cloud::update(); - } - if (stNow.wMinute % 30 == 0) - { - if (console["SystemAlarmCPU"]) - { - const long long perCPU = getWinCpuUsage(); - if (perCPU > console["SystemAlarmCPU"] && perCPU > perLastCPU)console.log( - "警告:" + GlobalMsg["strSelfName"] + "所在系统CPU占用达" + to_string(perCPU) + "%", 0b1001, - printSTime(stNow)); - else if (perLastCPU > console["SystemAlarmCPU"] && perCPU < console["SystemAlarmCPU"])console.log( - "提醒:" + GlobalMsg["strSelfName"] + "所在系统CPU占用降至" + to_string(perCPU) + "%", 0b11, - printSTime(stNow)); - perLastCPU = perCPU; - } - if (console["SystemAlarmRAM"]) - { - const long long perRAM = getRamPort(); - if (perRAM > console["SystemAlarmRAM"] && perRAM > perLastRAM)console.log( - "警告:" + GlobalMsg["strSelfName"] + "所在系统内存占用达" + to_string(perRAM) + "%", 0b1001, - printSTime(stNow)); - else if (perLastRAM > console["SystemAlarmRAM"] && perRAM < console["SystemAlarmRAM"])console.log( - "提醒:" + GlobalMsg["strSelfName"] + "所在系统内存占用降至" + to_string(perRAM) + "%", 0b11, - printSTime(stNow)); - perLastRAM = perRAM; - } - } + void ThreadFactory::exit() { + rear = 0; + for (auto& th : vTh) { + if (th.joinable())th.join(); } - this_thread::sleep_for(1000ms); + vTh = {}; } -} -//一键清退 -int clearGroup(string strPara, long long fromQQ) -{ - int intCnt = 0; - string strReply; - ResList res; - std::map strVar; - if (strPara == "unpower" || strPara.empty()) - { - for (auto& [id, grp] : ChatList) - { - if (grp.isset("忽略") || grp.isset("已退") || grp.isset("未进") || grp.isset("免清"))continue; - if (grp.isGroup && getGroupMemberInfo(id, console.DiceMaid).permissions == 1) - { - res << printGroup(id); - grp.leave(getMsg("strLeaveNoPower")); - intCnt++; - this_thread::sleep_for(3s); - } - } - strReply = GlobalMsg["strSelfName"] + "筛除无群权限群聊" + to_string(intCnt) + "个:" + res.show(); - console.log(strReply, 0b10, printSTNow()); - } - else if (isdigit(static_cast(strPara[0]))) - { - const int intDayLim = stoi(strPara); - const string strDayLim = to_string(intDayLim); - const time_t tNow = time(nullptr); - for (auto& [id, grp] : ChatList) - { - if (grp.isset("忽略") || grp.isset("已退") || grp.isset("未进") || grp.isset("免清"))continue; - time_t tLast = grp.tLastMsg; - if (grp.isGroup) - { - const int tLMT = getGroupMemberInfo(id, console.DiceMaid).LastMsgTime; - if (tLMT > 0) - tLast = tLMT; - } - if (!tLast)continue; - const int intDay = static_cast(tNow - tLast) / 86400; - if (intDay > intDayLim) - { - strVar["day"] = to_string(intDay); - res << printGroup(id) + ":" + to_string(intDay) + "天\n"; - grp.leave(getMsg("strLeaveUnused", strVar)); - intCnt++; - this_thread::sleep_for(2s); - } - } - strReply += GlobalMsg["strSelfName"] + "已筛除潜水" + strDayLim + "天群聊" + to_string(intCnt) + "个√" + res.show(); - console.log(strReply, 0b10, printSTNow()); - } - else if (strPara == "black") - { - try - { - for (auto& [id, grp_name] : getGroupList()) - { - Chat& grp = chat(id).group().name(grp_name); - if (grp.isset("忽略") || grp.isset("已退") || grp.isset("未进") || grp.isset("免清") || grp.isset("免黑"))continue - ; - if (blacklist->get_group_danger(id)) - { - res << printGroup(id) + ":" + "黑名单群"; - if (console["LeaveBlackGroup"])grp.leave(getMsg("strBlackGroup")); - } - vector MemberList = getGroupMemberList(id); - for (const auto& eachQQ : MemberList) - { - if (blacklist->get_qq_danger(eachQQ.QQID) > 1) - { - if (eachQQ.permissions < getGroupMemberInfo(id, getLoginQQ()).permissions) - { - continue; - } - if (eachQQ.permissions > getGroupMemberInfo(id, getLoginQQ()).permissions) - { - res << printChat(grp) + ":" + printQQ(eachQQ.QQID) + "对方群权限较高"; - grp.leave("发现黑名单管理员" + printQQ(eachQQ.QQID) + "\n" + GlobalMsg["strSelfName"] + "将预防性退群"); - intCnt++; - break; - } - if (console["LeaveBlackQQ"]) - { - res << printChat(grp) + ":" + printQQ(eachQQ.QQID); - grp.leave("发现黑名单成员" + printQQ(eachQQ.QQID) + "\n" + GlobalMsg["strSelfName"] + "将预防性退群"); - intCnt++; - break; - } - } - } - } - } - catch (...) - { - console.log(strReply, 0b10, "提醒:" + GlobalMsg["strSelfName"] + "清查黑名单群聊时出错!"); - } - if (intCnt) - { - strReply = GlobalMsg["strSelfName"] + "已按黑名单清查群聊" + to_string(intCnt) + "个:" + res.show(); - console.log(strReply, 0b10, printSTNow()); - } - else if (fromQQ) - { - console.log(strReply, 1, printSTNow()); - } - } - else if (strPara == "preserve") - { - for (auto& [id,grp] : ChatList) - { - if (grp.isset("忽略") || grp.isset("已退") || grp.isset("未进") || grp.isset("使用许可") || grp.isset("免清"))continue; - if (grp.isGroup && getGroupMemberInfo(id, console.master()).permissions) - { - grp.set("使用许可"); - continue; - } - res << printChat(grp); - grp.leave(getMsg("strPreserve")); - intCnt++; - this_thread::sleep_for(3s); - } - strReply = GlobalMsg["strSelfName"] + "筛除无许可群聊" + to_string(intCnt) + "个:" + res.show(); - console.log(strReply, 1, printSTNow()); - } - else - AddMsgToQueue("无法识别筛选参数×", fromQQ); - return intCnt; -} - - -EVE_Menu(eventClearGroupUnpower) -{ - const int intGroupCnt = clearGroup("unpower"); - const string strReply = "已清退无权限群聊" + to_string(intGroupCnt) + "个√"; - MessageBoxA(nullptr, strReply.c_str(), "一键清退", MB_OK | MB_ICONINFORMATION); - return 0; -} - -EVE_Menu(eventClearGroup30) -{ - const int intGroupCnt = clearGroup("30"); - const string strReply = "已清退30天未使用群聊" + to_string(intGroupCnt) + "个√"; - MessageBoxA(nullptr, strReply.c_str(), "一键清退", MB_OK | MB_ICONINFORMATION); - return 0; -} - -EVE_Menu(eventGlobalSwitch) -{ - if (console["DisabledGlobal"]) - { - console.set("DisabledGlobal", 0); - MessageBoxA(nullptr, "骰娘已结束静默√", "全局开关", MB_OK | MB_ICONINFORMATION); - } - else - { - console.set("DisabledGlobal", 1); - MessageBoxA(nullptr, "骰娘已全局静默√", "全局开关", MB_OK | MB_ICONINFORMATION); - } - - return 0; -} - -EVE_Request_AddFriend(eventAddFriend) -{ - if (!console["ListenFriendRequest"])return 0; - string strMsg = "好友添加请求,来自:" + printQQ(fromQQ); - this_thread::sleep_for(3s); - if (blacklist->get_qq_danger(fromQQ)) - { - strMsg += ",已拒绝(用户在黑名单中)"; - setFriendAddRequest(responseFlag, 2, ""); - console.log(strMsg, 0b10, printSTNow()); - } - else if (trustedQQ(fromQQ)) - { - strMsg += ",已同意(受信任用户)"; - setFriendAddRequest(responseFlag, 1, ""); - AddMsgToQueue(getMsg("strAddFriendWhiteQQ"), fromQQ); - console.log(strMsg, 1, printSTNow()); - } - else if (console["AllowStranger"] < 2 && !UserList.count(fromQQ)) - { - strMsg += ",已拒绝(无用户记录)"; - setFriendAddRequest(responseFlag, 2, ""); - console.log(strMsg, 1, printSTNow()); - } - else if (console["AllowStranger"] < 1) - { - strMsg += ",已拒绝(非信任用户)"; - setFriendAddRequest(responseFlag, 2, ""); - console.log(strMsg, 1, printSTNow()); - } - else - { - strMsg += ",已同意"; - setFriendAddRequest(responseFlag, 1, ""); - AddMsgToQueue(getMsg("strAddFriend"), fromQQ); - console.log(strMsg, 1, printSTNow()); - } - return 1; -} - -EVE_Friend_Add(eventFriendAdd) -{ - if (!console["ListenFriendAdd"])return 0; - this_thread::sleep_for(3s); - GlobalMsg["strAddFriendWhiteQQ"].empty() - ? AddMsgToQueue(getMsg("strAddFriend"), fromQQ) - : AddMsgToQueue(getMsg("strAddFriendWhiteQQ"), fromQQ); - return 0; -} diff --git a/Dice/DiceConsole.h b/Dice/DiceConsole.h index e2a00753..f6aaa769 100644 --- a/Dice/DiceConsole.h +++ b/Dice/DiceConsole.h @@ -1,28 +1,33 @@ +#pragma once + /* - * Copyright (C) 2019-2020 String.Empty + * Copyright (C) 2019-2021 String.Empty */ -#pragma once + #ifndef Dice_Console #define Dice_Console +#include #include #include #include #include #include -#include #include #include +#include #include "STLExtern.hpp" #include "DiceXMLTree.h" #include "DiceFile.hpp" #include "MsgFormat.h" #include "DiceMsgSend.h" -#include "CQEVE_ALL.h" using namespace std::literals::chrono_literals; using std::string; using std::to_string; -enum class ClockEvent { off, on, save, clear }; +extern std::filesystem::path dirExe; +extern std::filesystem::path DiceDir; + +//enum class ClockEvent { off, on, save, clear }; class Console { @@ -30,8 +35,10 @@ class Console bool isMasterMode = false; long long masterQQ = 0; long long DiceMaid = 0; + bool is_self(long long qq)const { return masterQQ == qq || DiceMaid == qq; } friend void ConsoleTimer(); friend class FromMsg; + friend class DiceJob; //DiceSens DSens; using Clock = std::pair; static const enumap mClockEvent; @@ -63,7 +70,7 @@ class Console void killMaster() { - rmNotice({masterQQ, CQ::msgtype::Private}); + rmNotice({masterQQ, msgtype::Private}); masterQQ = 0; save(); } @@ -75,12 +82,12 @@ class Console return 0; } - int setClock(Clock c, ClockEvent e); - int rmClock(Clock c, ClockEvent e); + int setClock(Clock c, const string&); + int rmClock(Clock c, const string&); [[nodiscard]] ResList listClock() const; [[nodiscard]] ResList listNotice() const; [[nodiscard]] int showNotice(chatType ct) const; - void setPath(std::string path) { strPath = std::move(path); } + void setPath(const std::filesystem::path& path) { fpPath = path; } void set(const std::string& key, int val) { @@ -94,35 +101,12 @@ class Console void rmNotice(chatType ct); void reset(); - bool load() + bool load(); + void save() { - string s; - //DSens.build({ {"nn老公",2 } }); - if (!rdbuf(strPath, s))return false; - DDOM xml(s); - if (xml.count("mode"))isMasterMode = stoi(xml["mode"].strValue); - if (xml.count("master"))masterQQ = stoll(xml["master"].strValue); - if (xml.count("clock")) - for (auto& child : xml["clock"].vChild) - { - if (mClockEvent.count(child.tag))mWorkClock.insert({ - scanClock(child.strValue), static_cast(mClockEvent[child.tag]) - }); - } - if (xml.count("conf")) - for (auto& child : xml["conf"].vChild) - { - std::pair conf; - readini(child.strValue, conf); - if (intDefault.count(conf.first))intConf.insert(conf); - } - loadNotice(); - return true; - } - - void save() - { - DDOM xml("console", ""); + std::error_code ec; + std::filesystem::create_directories(DiceDir / "conf", ec); + DDOM xml("console",""); xml.push(DDOM("mode", to_string(isMasterMode))); xml.push(DDOM("master", to_string(masterQQ))); if (!mWorkClock.empty()) @@ -130,7 +114,7 @@ class Console DDOM clocks("clock", ""); for (auto& [clock, type] : mWorkClock) { - clocks.push(DDOM(mClockEvent[static_cast(type)], printClock(clock))); + clocks.push(DDOM(type, printClock(clock))); } xml.push(clocks); } @@ -143,76 +127,72 @@ class Console } xml.push(conf); } - std::ofstream fout(strPath); - fout << xml.dump(); + std::ofstream fout(fpPath); + if (fout) fout << xml.dump(); } void loadNotice(); void saveNotice() const; private: - string strPath; + std::filesystem::path fpPath; std::map intConf; - std::multimap mWorkClock{}; + std::multimap mWorkClock{}; std::map NoticeList{}; }; + extern Console console; + //extern DiceModManager modules; -extern Console console; -//extern DiceModManager modules; -//骰娘列表 -extern std::map mDiceList; -//获取骰娘列表 -void getDiceList(); - -struct fromMsg -{ - std::string strMsg; - long long fromQQ = 0; - long long fromGroup = 0; - fromMsg() = default; +extern std::set ExceptGroups; +void getExceptGroup(); + //骰娘列表 + extern std::map mDiceList; + //获取骰娘列表 + void getDiceList(); - fromMsg(std::string msg, long long QQ, long long Group) : strMsg(std::move(msg)), fromQQ(QQ), fromGroup(Group) + struct fromMsg { + std::string strMsg; + long long fromQQ = 0; + long long fromGroup = 0; + fromMsg() = default; + + fromMsg(std::string msg, long long QQ, long long Group) : strMsg(std::move(msg)), fromQQ(QQ), fromGroup(Group) + { + }; }; -}; -//通知 -//一键清退 -extern int clearGroup(std::string strPara = "unpower", long long fromQQ = 0); -//连接的聊天窗口 -extern std::map mLinkedList; -//单向转发列表 -extern std::multimap mFwdList; -//程序启动时间 -extern long long llStartTime; -//当前时间 -extern SYSTEMTIME stNow; -std::string printClock(std::pair clock); -std::string printSTime(SYSTEMTIME st); -std::string printSTNow(); -std::string printDate(); -std::string printDate(time_t tt); -std::string printQQ(long long); -std::string printGroup(long long); -std::string printChat(chatType); + //程序启动时间 + extern long long llStartTime; + //当前时间 + extern tm stNow; + std::string printClock(std::pair clock); + std::string printSTime(tm st); + std::string printSTNow(); + std::string printDate(); + std::string printDate(time_t tt); + std::string printQQ(long long); + std::string printGroup(long long); + std::string printChat(chatType); void ConsoleTimer(); class ThreadFactory { public: - ThreadFactory() - { - } + ThreadFactory() {} int rear = 0; - std::thread vTh[4]; + std::array vTh; - void operator()(void (*func)()) - { - std::thread th(func); - th.detach(); + void operator()(void (*func)()) { + std::thread th{ [func]() {try { func(); } catch (...) { return; }} }; vTh[rear] = std::move(th); + //vTh[rear].detach(); rear++; } + void exit(); + ~ThreadFactory() { + exit(); + } }; extern ThreadFactory threads; diff --git a/Dice/DiceEvent.cpp b/Dice/DiceEvent.cpp index f11d70ae..057b1160 100644 --- a/Dice/DiceEvent.cpp +++ b/Dice/DiceEvent.cpp @@ -1,36 +1,112 @@ -#include -#include -#include +#include "DDAPI.h" #include "DiceEvent.h" #include "Jsonio.h" #include "MsgFormat.h" +#include "DiceCensor.h" #include "DiceMod.h" #include "ManagerSystem.h" #include "BlackListManager.h" #include "CharacterCard.h" #include "DiceSession.h" #include "GetRule.h" -#include "CQAPI.h" #include "DiceNetwork.h" #include "DiceCloud.h" - -//#pragma warning(disable:28159) +#include "DiceGUI.h" +#include +#include using namespace std; -using namespace CQ; -void FromMsg::FwdMsg(const string& message) +FromMsg& FromMsg::initVar(const std::initializer_list& replace_str) { + int index = 0; + for (const auto& s : replace_str) { + strVar[to_string(index++)] = s; + } + return *this; +} +void FromMsg::formatReply() { + if (!strVar.count("nick") || strVar["nick"].empty())strVar["nick"] = getName(fromQQ, fromGroup); + if (!strVar.count("pc") || strVar["pc"].empty())getPCName(*this); + if (!strVar.count("at") || strVar["at"].empty())strVar["at"] = fromChat.second != msgtype::Private ? "[CQ:at,qq=" + to_string(fromQQ) + "]" : strVar["nick"]; + strReply = format(strReply, GlobalMsg, strVar); +} + +void FromMsg::reply(const std::string& msgReply, bool isFormat) { + strReply = msgReply; + reply(isFormat); +} + +void FromMsg::reply(const std::string& msgReply, const std::initializer_list& replace_str) { + initVar(replace_str); + strReply = msgReply; + reply(); +} + +void FromMsg::reply(bool isFormat) { + if (isVirtual && fromQQ == console.DiceMaid && fromChat.second == msgtype::Private)return; + isAns = true; + while (isspace(static_cast(strReply[0]))) + strReply.erase(strReply.begin()); + if (isFormat) + formatReply(); + AddMsgToQueue(strReply, fromChat); + if (LogList.count(fromSession) && gm->session(fromSession).is_logging()) { + filter_CQcode(strReply, fromGroup); + ofstream logout(gm->session(fromSession).log_path(), ios::out | ios::app); + logout << GBKtoUTF8(getMsg("strSelfName")) + "(" + to_string(console.DiceMaid) + ") " + printTTime(fromTime) << endl + << GBKtoUTF8(strReply) << endl << endl; + } +} + +void FromMsg::replyHidden(const std::string& msgReply) { + strReply = msgReply; + replyHidden(); +} +void FromMsg::replyHidden() { + isAns = true; + while (isspace(static_cast(strReply[0]))) + strReply.erase(strReply.begin()); + formatReply(); + if (LogList.count(fromSession) && gm->session(fromSession).is_logging() + && (fromChat.second == msgtype::Private || !console["ListenGroupEcho"])) { + filter_CQcode(strReply, fromGroup); + ofstream logout(gm->session(fromSession).log_path(), ios::out | ios::app); + logout << GBKtoUTF8(getMsg("strSelfName")) + "(" + to_string(console.DiceMaid) + ") " + printTTime(fromTime) << endl + << '*' << GBKtoUTF8(strReply) << endl << endl; + } + strReply = "在" + printChat(fromChat) + "中 " + strReply; + AddMsgToQueue(strReply, fromQQ); + if (gm->has_session(fromSession)) { + for (auto qq : gm->session(fromSession).get_ob()) { + if (qq != fromQQ) { + AddMsgToQueue(strReply, qq); + } + } + } +} + +void FromMsg::fwdMsg() { - if (mFwdList.count(fromChat) && !isLinkOrder) + if (LinkList.count(fromSession) && LinkList[fromSession].second && fromQQ != console.DiceMaid && strLowerMessage.find(".link") != 0) { - const auto range = mFwdList.equal_range(fromChat); string strFwd; if (trusted < 5)strFwd += printFrom(); - strFwd += message; - for (auto it = range.first; it != range.second; ++it) - { - AddMsgToQueue(strFwd, it->second.first, it->second.second); + strFwd += strMsg; + if (long long aim = LinkList[fromSession].first;aim < 0) { + AddMsgToQueue(strFwd, ~aim); + } + else if (ChatList.count(aim)) { + AddMsgToQueue(strFwd, aim, chat(aim).isGroup ? msgtype::Group : msgtype::Discuss); } } + if (LogList.count(fromSession) && strLowerMessage.find(".log") != 0) { + string msg = strMsg; + filter_CQcode(msg, fromGroup); + ofstream logout(gm->session(fromSession).log_path(), ios::out | ios::app); + if (!strVar.count("nick") || strVar["nick"].empty())strVar["nick"] = getName(fromQQ, fromGroup); + if (!strVar.count("pc") || strVar["pc"].empty())getPCName(*this); + logout << GBKtoUTF8(strVar["pc"]) + "(" + to_string(fromQQ) + ") " + printTTime(fromTime) << endl + << GBKtoUTF8(msg) << endl << endl; + } } int FromMsg::AdminEvent(const string& strOption) @@ -55,13 +131,15 @@ int FromMsg::AdminEvent(const string& strOption) if (console["DisabledDraw"])res << "全局禁用.draw"; if (console["DisabledSend"])res << "全局禁用.send"; if (trusted > 3) - res << "所在群聊数:" + to_string(getGroupList().size()) - << "群记录数:" + to_string(ChatList.size()) - << "好友数:" + to_string(getFriendList().size()) - << "用户记录数:" + to_string(UserList.size()) - << (!PList.empty() ? "角色卡记录数:" + to_string(PList.size()) : "无角色卡记录") - << "黑名单用户数:" + to_string(blacklist->mQQDanger.size()) - << "黑名单群数:" + to_string(blacklist->mGroupDanger.size()); + res << "所在群聊数:" + to_string(DD::getGroupIDList().size()) + << "群记录数:" + to_string(ChatList.size()) + << "好友数:" + to_string(DD::getFriendQQList().size()) + << "用户记录数:" + to_string(UserList.size()) + << "今日用户量:" + to_string(today->cnt()) + << (!PList.empty() ? "角色卡记录数:" + to_string(PList.size()) : "") + << "黑名单用户数:" + to_string(blacklist->mQQDanger.size()) + << "黑名单群数:" + to_string(blacklist->mGroupDanger.size()) + << (censor.size() ? "敏感词库规模:" + to_string(censor.size()) : ""); reply(GlobalMsg["strSelfName"] + "的当前情况" + res.show()); return 1; } @@ -125,13 +203,60 @@ int FromMsg::AdminEvent(const string& strOption) { getDiceList(); strReply = "当前骰娘列表:"; - for (auto it : mDiceList) + for (auto& [diceQQ, masterQQ] : mDiceList) { - strReply += "\n" + printQQ(it.first); + strReply += "\n" + printQQ(diceQQ); } reply(); return 1; } + if (strOption == "censor") { + readSkipSpace(); + if (strMsg[intMsgCnt] == '+') { + intMsgCnt++; + strVar["danger_level"] = readToColon(); + Censor::Level danger_level = censor.get_level(strVar["danger_level"]); + readSkipColon(); + ResList res; + while (intMsgCnt != strMsg.length()) { + string item = readItem(); + if (!item.empty()) { + censor.add_word(item, danger_level); + res << item; + } + } + if (res.empty()) { + reply("{nick}未输入待添加敏感词!"); + } + else { + note("{nick}已添加{danger_level}级敏感词" + to_string(res.size()) + "个:" + res.show(), 1); + } + } + else if (strMsg[intMsgCnt] == '-') { + intMsgCnt++; + ResList res,resErr; + while (intMsgCnt != strMsg.length()) { + string item = readItem(); + if (!item.empty()) { + if (censor.rm_word(item)) + res << item; + else + resErr << item; + } + } + if (res.empty()) { + reply("{nick}未输入待移除敏感词!"); + } + else { + note("{nick}已移除敏感词" + to_string(res.size()) + "个:" + res.show(), 1); + } + if (!resErr.empty()) + reply("{nick}移除不存在敏感词" + to_string(resErr.size()) + "个:" + resErr.show()); + } + else + reply(fmt->get_help("censor")); + return 1; + } if (strOption == "only") { if (console["Private"]) @@ -172,7 +297,7 @@ int FromMsg::AdminEvent(const string& strOption) intMsgCnt++; } string strType = readPara(); - if (strType.empty() || !Console::mClockEvent.count(strType)) + if (strType.empty()) { reply(GlobalMsg["strSelfName"] + "的定时列表:" + console.listClock().show()); return 1; @@ -183,13 +308,13 @@ int FromMsg::AdminEvent(const string& strOption) case 0: if (isErase) { - if (console.rmClock(cc, ClockEvent(Console::mClockEvent[strType])))reply( + if (console.rmClock(cc, strType))reply( GlobalMsg["strSelfName"] + "无此定时项目"); else note("已移除" + GlobalMsg["strSelfName"] + "在" + printClock(cc) + "的定时" + strType, 0b10); } else { - console.setClock(cc, ClockEvent(Console::mClockEvent[strType])); + console.setClock(cc, strType); note("已设置" + GlobalMsg["strSelfName"] + "在" + printClock(cc) + "的定时" + strType, 0b10); } break; @@ -237,7 +362,7 @@ int FromMsg::AdminEvent(const string& strOption) bool isReduce = strMsg[intMsgCnt] == '-'; string strNum = readDigit(); if (strNum.empty() || strNum.length() > 1)break; - if (int intNum = stoi(strNum); intNum > 5)continue; + if (int intNum = stoi(strNum); intNum > 9)continue; else { if (isReduce)intReduce |= (1 << intNum); @@ -365,13 +490,9 @@ int FromMsg::AdminEvent(const string& strOption) if (strOption == "blackfriend") { ResList res; - Unpack pack(base64_decode(CQ_getFriendList(getAuthCode()))); //获取原始数据转换为Unpack - int Cnt = pack.getInt(); //获取总数 - while (Cnt--) - { - FriendInfo info(pack.getUnpack()); //读取 - if (blacklist->get_qq_danger(info.QQID)) - res << info.tostring(); + for(long long qq: DD::getFriendQQList()){ + if (blacklist->get_qq_danger(qq)) + res << printQQ(qq); } if (res.empty()) { @@ -444,164 +565,166 @@ int FromMsg::AdminEvent(const string& strOption) reply("当前总指令频度" + to_string(FrqMonitor::getFrqTotal())); return 1; } - bool boolErase = false; - strVar["note"] = readPara(); - if (strMsg[intMsgCnt] == '-') - { - boolErase = true; - intMsgCnt++; - } - if (strMsg[intMsgCnt] == '+') { intMsgCnt++; } - long long llTargetID = readID(); - if (strOption == "dismiss") + else { - if (ChatList.count(llTargetID)) - { - note("已令" + GlobalMsg["strSelfName"] + "退出" + printChat(chat(llTargetID)), 0b10); - chat(llTargetID).reset("免清").leave(); - } - else + bool boolErase = false; + strVar["note"] = readPara(); + if (strMsg[intMsgCnt] == '-') { - reply(GlobalMsg["strGroupGetErr"]); + boolErase = true; + intMsgCnt++; } - return 1; - } - if (strOption == "boton") - { - if (getGroupList().count(llTargetID)) + if (strMsg[intMsgCnt] == '+') { intMsgCnt++; } + long long llTargetID = readID(); + if (strOption == "dismiss") { - if (groupset(llTargetID, "停用指令") > 0) + if (ChatList.count(llTargetID)) { - chat(llTargetID).reset("停用指令"); - note("已令" + GlobalMsg["strSelfName"] + "在" + printGroup(llTargetID) + "启用指令√"); + note("已令" + GlobalMsg["strSelfName"] + "退出" + printChat(chat(llTargetID)), 0b10); + chat(llTargetID).reset("免清").leave(); } - else reply(GlobalMsg["strSelfName"] + "已在该群启用指令!"); - } - else - { - reply(GlobalMsg["strGroupGetErr"]); - } - } - else if (strOption == "botoff") - { - if (groupset(llTargetID, "停用指令") < 1) - { - chat(llTargetID).set("停用指令"); - note("已令" + GlobalMsg["strSelfName"] + "在" + printGroup(llTargetID) + "停用指令√", 0b1); - } - else reply(GlobalMsg["strSelfName"] + "已在该群停用指令!"); - return 1; - } - else if (strOption == "blackgroup") - { - if (llTargetID == 0) - { - strReply = "当前黑名单群列表:"; - for (auto [each, danger] : blacklist->mGroupDanger) + else { - strReply += "\n" + to_string(each); + reply(GlobalMsg["strGroupGetErr"]); } - reply(); return 1; } - strVar["time"] = printSTNow(); - do + else if (strOption == "boton") { - if (boolErase) + if (ChatList.count(llTargetID)) { - blacklist->rm_black_group(llTargetID, this); + if (groupset(llTargetID, "停用指令") > 0) + { + chat(llTargetID).reset("停用指令"); + note("已令" + GlobalMsg["strSelfName"] + "在" + printGroup(llTargetID) + "启用指令√"); + } + else reply(GlobalMsg["strSelfName"] + "已在该群启用指令!"); } else { - blacklist->add_black_group(llTargetID, this); + reply(GlobalMsg["strGroupGetErr"]); } } - while ((llTargetID = readID())); - return 1; - } - else if (strOption == "whiteqq") - { - if (llTargetID == 0) + else if (strOption == "botoff") { - strReply = "当前白名单用户列表:"; - for (auto& [qq, user] : UserList) + if (groupset(llTargetID, "停用指令") < 1) { - if (user.nTrust)strReply += "\n" + printQQ(qq) + ":" + to_string(user.nTrust); + chat(llTargetID).set("停用指令"); + note("已令" + GlobalMsg["strSelfName"] + "在" + printGroup(llTargetID) + "停用指令√", 0b1); } - reply(); + else reply(GlobalMsg["strSelfName"] + "已在该群停用指令!"); return 1; } - do + else if (strOption == "blackgroup") { - if (boolErase) + if (llTargetID == 0) { - if (trustedQQ(llTargetID)) - { - if (trusted <= trustedQQ(llTargetID)) - { - reply(GlobalMsg["strUserTrustDenied"]); - } - else - { - getUser(llTargetID).trust(0); - note("已收回" + GlobalMsg["strSelfName"] + "对" + printQQ(llTargetID) + "的信任√", 0b1); - } - } - else - { - reply(printQQ(llTargetID) + "并不在" + GlobalMsg["strSelfName"] + "的白名单!"); + ResList res; + for (auto [each, danger] : blacklist->mGroupDanger) { + res << printGroup(each) + ":" + to_string(danger); } + reply(res.show(), false); + return 1; } - else + strVar["time"] = printSTNow(); + do { - if (trustedQQ(llTargetID)) + if (boolErase) { - reply(printQQ(llTargetID) + "已加入" + GlobalMsg["strSelfName"] + "的白名单!"); + blacklist->rm_black_group(llTargetID, this); } else { - getUser(llTargetID).trust(1); - note("已添加" + GlobalMsg["strSelfName"] + "对" + printQQ(llTargetID) + "的信任√", 0b1); - strVar["user_nick"] = getName(llTargetID); - AddMsgToQueue(format(GlobalMsg["strWhiteQQAddNotice"], GlobalMsg, strVar), llTargetID); + blacklist->add_black_group(llTargetID, this); } - } + } + while ((llTargetID = readID())); + return 1; } - while ((llTargetID = readID())); - return 1; - } - else if (strOption == "blackqq") - { - if (llTargetID == 0) + else if (strOption == "whiteqq") { - strReply = "当前黑名单用户列表:"; - for (auto [each, danger] : blacklist->mQQDanger) + if (llTargetID == 0) { - strReply += "\n" + printQQ(each); + strReply = "当前白名单用户列表:"; + for (auto& [qq, user] : UserList) + { + if (user.nTrust)strReply += "\n" + printQQ(qq) + ":" + to_string(user.nTrust); + } + reply(); + return 1; } - reply(); + do + { + if (boolErase) + { + if (trustedQQ(llTargetID)) + { + if (trusted <= trustedQQ(llTargetID)) + { + reply(GlobalMsg["strUserTrustDenied"]); + } + else + { + getUser(llTargetID).trust(0); + note("已收回" + GlobalMsg["strSelfName"] + "对" + printQQ(llTargetID) + "的信任√", 0b1); + } + } + else + { + reply(printQQ(llTargetID) + "并不在" + GlobalMsg["strSelfName"] + "的白名单!"); + } + } + else + { + if (trustedQQ(llTargetID)) + { + reply(printQQ(llTargetID) + "已加入" + GlobalMsg["strSelfName"] + "的白名单!"); + } + else + { + getUser(llTargetID).trust(1); + note("已添加" + GlobalMsg["strSelfName"] + "对" + printQQ(llTargetID) + "的信任√", 0b1); + strVar["user_nick"] = getName(llTargetID); + AddMsgToQueue(format(GlobalMsg["strWhiteQQAddNotice"], GlobalMsg, strVar), llTargetID); + } + } + } + while ((llTargetID = readID())); return 1; } - strVar["time"] = printSTNow(); - do + else if (strOption == "blackqq") { - if (boolErase) + if (llTargetID == 0) { - blacklist->rm_black_qq(llTargetID, this); + ResList res; + for (auto [each, danger] : blacklist->mQQDanger) + { + res << printQQ(each) + ":" + to_string(danger); + } + reply(res.show(), false); + return 1; } - else + strVar["time"] = printSTNow(); + do { - blacklist->add_black_qq(llTargetID, this); + if (boolErase) + { + blacklist->rm_black_qq(llTargetID, this); + } + else + { + blacklist->add_black_qq(llTargetID, this); + } } + while ((llTargetID = readID())); + return 1; } - while ((llTargetID = readID())); - return 1; + else reply(GlobalMsg["strAdminOptionEmpty"]); + return 0; } - else reply(GlobalMsg["strAdminOptionEmpty"]); - return 0; } -int FromMsg::MasterSet() +int FromMsg::MasterSet() { const std::string strOption = readPara(); if (strOption.empty()) @@ -611,8 +734,9 @@ int FromMsg::MasterSet() } if (strOption == "groupclr") { - const std::string strPara = readRest(); - clearGroup(strPara, fromQQ); + strVar["clear_mode"] = readRest(); + cmd_key = "clrgroup"; + sch.push_job(*this); return 1; } if (strOption == "delete") @@ -626,7 +750,7 @@ int FromMsg::MasterSet() console.killMaster(); return 1; } - if (strOption == "reset") + else if (strOption == "reset") { if (console.master() != fromQQ) { @@ -636,7 +760,7 @@ int FromMsg::MasterSet() const string strMaster = readDigit(); if (strMaster.empty() || stoll(strMaster) == console.master()) { - reply("Master不要消遣于我!"); + reply("Master不要消遣{strSelfCall}!"); } else { @@ -697,20 +821,17 @@ int FromMsg::MasterSet() return AdminEvent(strOption); } -int FromMsg::DiceReply() +int FromMsg::BasicOrder() { if (strMsg[0] != '.')return 0; intMsgCnt++; - int intT = static_cast(fromType); + int intT = static_cast(fromChat.second); while (isspace(static_cast(strMsg[intMsgCnt]))) intMsgCnt++; - strVar["nick"] = getName(fromQQ, fromGroup); - strVar["pc"] = getPCName(fromQQ, fromGroup); - strVar["at"] = intT ? "[CQ:at,qq=" + to_string(fromQQ) + "]" : strVar["nick"]; - isAuth = trusted > 3 || fromType != msgtype::Group || getGroupMemberInfo(fromGroup, fromQQ).permissions > 1; - strLowerMessage = strMsg; - std::transform(strLowerMessage.begin(), strLowerMessage.end(), strLowerMessage.begin(), - [](unsigned char c) { return tolower(c); }); + //strVar["nick"] = getName(fromQQ, fromGroup); + //getPCName(*this); + //strVar["at"] = intT ? "[CQ:at,qq=" + to_string(fromQQ) + "]" : strVar["nick"]; + isAuth = trusted > 3 || intT != GroupT || DD::isGroupAdmin(fromGroup, fromQQ, true) || pGrp->inviter == fromQQ; //指令匹配 if (strLowerMessage.substr(intMsgCnt, 9) == "authorize") { @@ -729,10 +850,10 @@ int FromMsg::DiceReply() return 1; } } - if (pGrp->isset("许可使用") && !pGrp->isset("未审核"))return 0; + if (pGrp->isset("许可使用") && !pGrp->isset("未审核") && !pGrp->isset("协议无效"))return 0; if (trusted > 0) { - pGrp->set("许可使用").reset("未审核"); + pGrp->set("许可使用").reset("未审核").reset("协议无效"); note("已授权" + printGroup(pGrp->ID) + "许可使用", 1); AddMsgToQueue(getMsg("strGroupAuthorized", strVar), pGrp->ID, msgtype::Group); } @@ -746,8 +867,9 @@ int FromMsg::DiceReply() } return 1; } - if (strLowerMessage.substr(intMsgCnt, 7) == "dismiss") + else if (strLowerMessage.substr(intMsgCnt, 7) == "dismiss") { + intMsgCnt += 7; if (!intT) { string QQNum = readDigit(); @@ -766,81 +888,67 @@ int FromMsg::DiceReply() if (grp.isset("已退") || grp.isset("未进")) { reply(GlobalMsg["strGroupAway"]); - return 1; } - if (trustedQQ(fromQQ) > 2 || getGroupMemberInfo(llGroup, fromQQ).permissions > 1) - { - grp.leave(GlobalMsg["strDismiss"]); + if (trustedQQ(fromQQ) > 2) { + grp.leave(getMsg("strAdminDismiss", strVar)); reply(GlobalMsg["strGroupExit"]); - return 1; } - reply(GlobalMsg["strPermissionDeniedErr"]); + else if(DD::isGroupAdmin(llGroup, fromQQ, true) || (grp.inviter == fromQQ)) + { + reply(GlobalMsg["strDismiss"]); + } + else + { + reply(GlobalMsg["strPermissionDeniedErr"]); + } return 1; } - intMsgCnt += 7; - while (isspace(static_cast(strLowerMessage[intMsgCnt]))) - intMsgCnt++; string QQNum = readDigit(); - if (QQNum.empty() || QQNum == to_string(console.DiceMaid) || (QQNum.length() == 4 && stoll(QQNum) == - getLoginQQ() % 10000)) - { - if (!isAuth && trusted < 3) + if (QQNum.empty() || QQNum == to_string(console.DiceMaid) || (QQNum.length() == 4 && stoll(QQNum) == DD::getLoginQQ() % 10000)){ + if (trusted > 2) + { + pGrp->leave(getMsg("strAdminDismiss", strVar)); + return 1; + } + if (pGrp->isset("协议无效"))return 0; + if (isAuth) { - if (pGrp->isset("停用指令") && GroupInfo(fromGroup).nGroupSize > 200)AddMsgToQueue( - getMsg("strPermissionDeniedErr", strVar), fromQQ); + pGrp->leave(GlobalMsg["strDismiss"]); + } + else + { + if (!isCalled && (pGrp->isset("停用指令") || DD::getGroupSize(fromGroup).siz > 200))AddMsgToQueue(getMsg("strPermissionDeniedErr", strVar), fromQQ); else reply(GlobalMsg["strPermissionDeniedErr"]); - return -1; } - chat(fromGroup).leave(GlobalMsg["strDismiss"]); + return 1; } return 1; } - if (strLowerMessage.substr(intMsgCnt, 7) == "warning") + else if (strLowerMessage.substr(intMsgCnt, 7) == "warning") { intMsgCnt += 7; string strWarning = readRest(); AddWarning(strWarning, fromQQ, fromGroup); return 1; } - if (strLowerMessage.substr(intMsgCnt, 6) == "master" && console.isMasterMode) + else if (strLowerMessage.substr(intMsgCnt, 6) == "master" && console.isMasterMode) { intMsgCnt += 6; if (!console.master()) { - console.newMaster(fromQQ); - strReply = "请认真阅读当前版本Master手册以履行职责。最新发布版:shiki.stringempty.xyz/download/Shiki_Master_Manual.pdf"; - strReply += "\n用户手册:shiki.stringempty.xyz/download/Shiki_User_Manual.pdf"; - strReply += "\n如要添加较多没有单群开关的插件,推荐开启DisabledBlock保证群内的静默;"; - strReply += "\n默认开启对群移出、禁言、刷屏事件的监听,如要关闭请手动调整;"; string strOption = readRest(); - if (strOption == "public") - { - strReply += "\n开启公骰模式:"; + if (strOption == "public"){ console.set("BelieveDiceList", 1); - strReply += "\n自动开启BelieveDiceList响应来自骰娘列表的warning;"; console.set("AllowStranger", 1); - strReply += "\n公骰模式默认同意陌生人的好友邀请;"; console.set("LeaveBlackQQ", 1); console.set("BannedLeave", 1); - strReply += "\n已开启黑名单自动清理,拉黑时及每日定时会自动清理与黑名单用户的共同群聊,黑名单用户群权限不低于自己时自动退群;"; console.set("BannedBanInviter", 1); console.set("KickedBanInviter", 1); - strReply += "\n拉黑群时会连带邀请人,可以手动关闭;"; - console.set("DisabledSend", 0); - strReply += "\n已启用send功能;"; } - else - { + else{ console.set("Private", 1); - strReply += "\n默认开启私骰模式:"; - strReply += "\n默认拒绝陌生人的好友邀请,如要同意请开启AllowStranger;"; - strReply += "\n默认拒绝陌生人的群邀请,只同意来自管理员、白名单的邀请;"; - strReply += "\n已开启黑名单自动清理,拉黑时及每日定时会自动清理与黑名单用户的共同群聊,黑名单用户群权限高于自己时自动退群;"; - strReply += "\n.me功能默认不可用,需要手动开启;"; - strReply += "\n切换公用请使用.admin public,但不会初始化响应设置;"; - strReply += "\n可在.master delete后使用.master public来重新初始化;"; } - reply(); + console.newMaster(fromQQ); } else if (trusted > 4 || console.master() == fromQQ) { @@ -853,26 +961,28 @@ int FromMsg::DiceReply() } return 1; } + else if (intT != PrivateT && pGrp->isset("协议无效")){ + return 1; + } if (blacklist->get_qq_danger(fromQQ) || (intT != PrivateT && blacklist->get_group_danger(fromGroup))) { - return 0; + return 1; } if (strLowerMessage.substr(intMsgCnt, 3) == "bot") { intMsgCnt += 3; string Command = readPara(); string QQNum = readDigit(); - if (QQNum.empty() || QQNum == to_string(getLoginQQ()) || (QQNum.length() == 4 && stoll(QQNum) == getLoginQQ() % + if (QQNum.empty() || QQNum == to_string(DD::getLoginQQ()) || (QQNum.length() == 4 && stoll(QQNum) == DD::getLoginQQ() % 10000)) { if (Command == "on") { if (console["DisabledGlobal"])reply(GlobalMsg["strGlobalOff"]); - else if (intT == GroupT && ((console["CheckGroupLicense"] && pGrp->isset("未审核")) || (console[ - "CheckGroupLicense"] == 2 && !pGrp->isset("许可使用"))))reply(GlobalMsg["strGroupLicenseDeny"]); + else if (intT == GroupT && ((console["CheckGroupLicense"] && pGrp->isset("未审核")) || (console["CheckGroupLicense"] == 2 && !pGrp->isset("许可使用"))))reply(GlobalMsg["strGroupLicenseDeny"]); else if (intT) { - if (isAuth) + if (isAuth || trusted >2) { if (groupset(fromGroup, "停用指令") > 0) { @@ -886,7 +996,7 @@ int FromMsg::DiceReply() } else { - if (groupset(fromGroup, "停用指令") > 0 && GroupInfo(fromGroup).nGroupSize > 100)AddMsgToQueue( + if (groupset(fromGroup, "停用指令") > 0 && DD::getGroupSize(fromGroup).siz > 200)AddMsgToQueue( getMsg("strPermissionDeniedErr", strVar), fromQQ); else reply(GlobalMsg["strPermissionDeniedErr"]); } @@ -894,46 +1004,42 @@ int FromMsg::DiceReply() } else if (Command == "off") { - if (fromType == msgtype(intT)) + if (isAuth || trusted > 2) { - if (isAuth) + if (groupset(fromGroup, "停用指令")) { - if (groupset(fromGroup, "停用指令")) - { - if (!isCalled && QQNum.empty() && pGrp->isGroup && GroupInfo(fromGroup).nGroupSize > 200) - AddMsgToQueue(getMsg("strBotOffAlready", strVar), fromQQ); - else reply(GlobalMsg["strBotOffAlready"]); - } - else - { - chat(fromGroup).set("停用指令"); - reply(GlobalMsg["strBotOff"]); - } + if (!isCalled && QQNum.empty() && pGrp->isGroup && DD::getGroupSize(fromGroup).siz > 200)AddMsgToQueue(getMsg("strBotOffAlready", strVar), fromQQ); + else reply(GlobalMsg["strBotOffAlready"]); } - else + else { - if (groupset(fromGroup, "停用指令"))AddMsgToQueue(getMsg("strPermissionDeniedErr", strVar), fromQQ); - else reply(GlobalMsg["strPermissionDeniedErr"]); + chat(fromGroup).set("停用指令"); + reply(GlobalMsg["strBotOff"]); } } + else + { + if (groupset(fromGroup, "停用指令"))AddMsgToQueue(getMsg("strPermissionDeniedErr", strVar), fromQQ); + else reply(GlobalMsg["strPermissionDeniedErr"]); + } } else if (!Command.empty() && !isCalled && pGrp->isset("停用指令")) { return 0; } - else if (intT == GroupT && pGrp->isset("停用指令") && GroupInfo(fromGroup).nGroupSize >= 500 && !isCalled) + else if (intT == GroupT && pGrp->isset("停用指令") && DD::getGroupSize(fromGroup).siz > 500 && !isCalled) { - AddMsgToQueue(Dice_Full_Ver_For + getMsg("strBotMsg"), fromQQ); + AddMsgToQueue(Dice_Full_Ver_On + getMsg("strBotMsg"), fromQQ); } else { this_thread::sleep_for(1s); - reply(Dice_Full_Ver_For + GlobalMsg["strBotMsg"]); + reply(Dice_Full_Ver_On + GlobalMsg["strBotMsg"]); } } return 1; } - if (isBotOff) + if (isDisabled || (!isCalled || !console["DisabledListenAt"]) && (groupset(fromGroup, "停用指令") > 0)) { if (intT == PrivateT) { @@ -942,15 +1048,6 @@ int FromMsg::DiceReply() } return 0; } - /*switch(console.DSens.find(strLowerMessage, fromQQ, fromChat)) { - case 0:break; - case 1: - reply(GlobalMsg["strSensNote"]); - break; - case 2: - reply(GlobalMsg["strSensWarn"]); - return 1; - }*/ if (strLowerMessage.substr(intMsgCnt, 7) == "helpdoc" && trusted > 3) { intMsgCnt += 7; @@ -980,7 +1077,7 @@ int FromMsg::DiceReply() fmt->set_help(strVar["key"], strHelpdoc); reply(format(GlobalMsg["strHlpSet"], {strVar["key"]})); } - saveJMap(DiceDir + "\\conf\\CustomHelp.json", CustomHelp); + saveJMap(DiceDir / "conf" / "CustomHelp.json", CustomHelp); return true; } if (strLowerMessage.substr(intMsgCnt, 4) == "help") @@ -988,16 +1085,16 @@ int FromMsg::DiceReply() intMsgCnt += 4; while (strLowerMessage[intMsgCnt] == ' ') intMsgCnt++; - const string strOption = readRest(); + strVar["help_word"] = readRest(); if (intT) { - if (!isAuth && (strOption == "on" || strOption == "off")) + if (!isAuth && (strVar["help_word"] == "on" || strVar["help_word"] == "off")) { reply(GlobalMsg["strPermissionDeniedErr"]); return 1; } strVar["option"] = "禁用help"; - if (strOption == "off") + if (strVar["help_word"] == "off") { if (groupset(fromGroup, strVar["option"]) < 1) { @@ -1010,7 +1107,7 @@ int FromMsg::DiceReply() } return 1; } - if (strOption == "on") + if (strVar["help_word"] == "on") { if (groupset(fromGroup, strVar["option"]) > 0) { @@ -1029,85 +1126,93 @@ int FromMsg::DiceReply() return 1; } } - if (strOption.empty()) - { - reply(string(Dice_Short_Ver) + "\n" + GlobalMsg["strHlpMsg"]); - } - else - { - reply(fmt->get_help(strOption)); - } + std::thread th(&DiceModManager::_help, fmt.get(), shared_from_this()); + th.detach(); return true; } - if (intT == GroupT && ((console["CheckGroupLicense"] && pGrp->isset("未审核")) || (console["CheckGroupLicense"] == 2 && - !pGrp->isset("许可使用")))) - { + return 0; +} + +int FromMsg::InnerOrder() { + if (strMsg[0] != '.')return 0; + if (WordCensor()) { + return 1; + } + if (strLowerMessage.substr(intMsgCnt, 8) == "setreply") { return 0; } - if (strLowerMessage.substr(intMsgCnt, 7) == "welcome") - { - if (intT != GroupT) - { + else if (strLowerMessage.substr(intMsgCnt, 7) == "welcome") { + if (fromChat.second != msgtype::Group) { reply(GlobalMsg["strWelcomePrivate"]); return 1; } intMsgCnt += 7; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; - if (isAuth) - { + if (isAuth) { string strWelcomeMsg = strMsg.substr(intMsgCnt); - if (strWelcomeMsg.empty()) - { - if (chat(fromGroup).strConf.count("入群欢迎")) - { + if (strWelcomeMsg.empty()) { + if (chat(fromGroup).strConf.count("入群欢迎")) { chat(fromGroup).rmText("入群欢迎"); reply(GlobalMsg["strWelcomeMsgClearNotice"]); } - else - { + else { reply(GlobalMsg["strWelcomeMsgClearErr"]); } } - else if (strWelcomeMsg == "show") - { + else if (strWelcomeMsg == "show") { reply(chat(fromGroup).strConf["入群欢迎"]); } - else - { + else { chat(fromGroup).setText("入群欢迎", strWelcomeMsg); reply(GlobalMsg["strWelcomeMsgUpdateNotice"]); } } - else - { + else { reply(GlobalMsg["strPermissionDeniedErr"]); } return 1; } - if (strLowerMessage.substr(intMsgCnt, 6) == "setcoc") - { - if (!isAuth) - { + else if (strLowerMessage.substr(intMsgCnt, 6) == "groups") { + if (trusted < 4) { + reply(GlobalMsg["strNotAdmin"]); + return 1; + } + intMsgCnt += 6; + string strOption = readPara(); + if (strOption == "list") { + strVar["list_mode"] = readPara(); + cmd_key = "lsgroup"; + sch.push_job(*this); + } + else if (strOption == "clr") { + if (trusted < 5) { + reply(GlobalMsg["strNotMaster"]); + return 1; + } + int cnt = clearGroup(); + note("已清理过期群记录" + to_string(cnt) + "条", 0b10); + return 1; + } + } + else if (strLowerMessage.substr(intMsgCnt, 6) == "setcoc") { + if (!isAuth) { reply(GlobalMsg["strPermissionDeniedErr"]); return 1; } string strRule = readDigit(); - if (strRule.empty()) - { - if (intT)chat(fromGroup).rmConf("rc房规"); + if (strRule.empty()) { + if (fromChat.second != msgtype::Private)chat(fromGroup).rmConf("rc房规"); else getUser(fromQQ).rmIntConf("rc房规"); reply(GlobalMsg["strDefaultCOCClr"]); return 1; } - if (strRule.length() > 1) - { + if (strRule.length() > 1) { reply(GlobalMsg["strDefaultCOCNotFound"]); return 1; } int intRule = stoi(strRule); - switch (intRule) - { + switch (intRule) { case 0: reply(GlobalMsg["strDefaultCOCSet"] + "0 规则书\n出1大成功\n不满50出96-100大失败,满50出100大失败"); break; @@ -1126,172 +1231,115 @@ int FromMsg::DiceReply() case 5: reply(GlobalMsg["strDefaultCOCSet"] + "5\n出1-2且<五分之一大成功\n不满50出96-100大失败,满50出99-100大失败"); break; + case 6: + reply(GlobalMsg["strDefaultCOCSet"] + "6\n绿色三角洲\n出1或两骰相同<=成功率大成功\n出100或两骰相同>成功率大失败"); + break; default: reply(GlobalMsg["strDefaultCOCNotFound"]); return 1; } - if (intT)chat(fromGroup).setConf("rc房规", intRule); + if (fromChat.second != msgtype::Private)chat(fromGroup).setConf("rc房规", intRule); else getUser(fromQQ).setConf("rc房规", intRule); return 1; } - if (strLowerMessage.substr(intMsgCnt, 6) == "system") - { + else if (strLowerMessage.substr(intMsgCnt, 6) == "system") { intMsgCnt += 6; - if (trusted < 4) - { + if (console && trusted < 4) { reply(GlobalMsg["strNotAdmin"]); return -1; } string strOption = readPara(); - if (strOption == "save") - { +#ifdef _WIN32 + if (strOption == "gui") { + thread th(GUIMain); + th.detach(); + return 1; + } +#endif + if (strOption == "save") { dataBackUp(); note("已手动保存数据√", 0b1); return 1; } - if (strOption == "load") - { + if (strOption == "load") { loadData(); note("已手动加载数据√", 0b1); return 1; } if (strOption == "state") { - GetLocalTime(&stNow); - strReply = "本地时间:" + printSTime(stNow) + "\n"; - strReply += "内存占用:" + to_string(getRamPort()) + "%\n"; - strReply += "CPU占用:" + to_string(getWinCpuUsage()) + "%\n"; - //strReply += "CPU占用:" + to_string(getWinCpuUsage()) + "% / 进程占用:" + to_string(getProcessCpu() / 100.0) + "%\n"; - //strReply += "本机运行时间:" + std::to_string(clock()) + " 启动时间:" + std::to_string(llStartTime) + "\n"; - strReply += "运行时长:"; - long long llDuration = (clock() - llStartTime) / 1000; - if (llDuration < 0) - { - strReply += "N/A"; - } - else if (llDuration < 60 * 2) - { - strReply += std::to_string(llDuration) + "秒"; - } - else if (llDuration < 60 * 60 * 2) - { - strReply += std::to_string(llDuration / 60) + "分" + std::to_string(llDuration % 60) + "秒"; - } - else if (llDuration < 60 * 60 * 24 * 2) - { - strReply += std::to_string(llDuration / 60 / 60) + "小时" + std::to_string(llDuration / 60 % 60) + "分"; - } - else if (llDuration < 60 * 60 * 24 * 10) - { - strReply += std::to_string(llDuration / 60 / 60 / 24) + "天" + std::to_string(llDuration / 60 / 60 % 24) - + "小时"; - } - else - { - strReply += std::to_string(llDuration / 60 / 60 / 24) + "天"; + time_t tt = time(nullptr); +#ifdef _MSC_VER + localtime_s(&stNow, &tt); +#else + localtime_r(&tt, &stNow); +#endif +#ifdef _WIN32 + double mbFreeBytes = 0, mbTotalBytes = 0; + long long milDisk(getDiskUsage(mbFreeBytes, mbTotalBytes)); +#endif + ResList res; + res << "本地时间:" + printSTime(stNow) +#ifdef _WIN32 + << "内存占用:" + to_string(getRamPort()) + "%" + << "CPU占用:" + toString(getWinCpuUsage() / 10.0) + "%" + << "硬盘占用:" + toString(milDisk / 10.0) + "%(空余:" + toString(mbFreeBytes) + "GB/ " + toString(mbTotalBytes) + "GB)" +#endif + << "运行时长:" + printDuringTime(time(nullptr) - llStartTime) + << "今日指令量:" + to_string(today->get("frq")) + << "启动后指令量:" + to_string(FrqMonitor::sumFrqTotal); + reply(res.show()); + return 1; + } + if (strOption == "clrimg") { + reply("非酷Q框架不需要此功能"); + return -1; + } + else if (strOption == "reload") { + if (trusted < 5 && fromQQ != console.master()) { + reply(GlobalMsg["strNotMaster"]); + return -1; } - reply(); + cmd_key = "reload"; + sch.push_job(*this); return 1; } - if (strOption == "clrimg") - { - if (Mirai) - { - reply("Mirai不支持此功能"); + else if (strOption == "remake") { + + if (trusted < 5 && fromQQ != console.master()) { + reply(GlobalMsg["strNotMaster"]); return -1; } - if (trusted < 5) - { + cmd_key = "remake"; + sch.push_job(*this); + return 1; + } + else if (strOption == "die") { + if (trusted < 5 && fromQQ != console.master()) { reply(GlobalMsg["strNotMaster"]); return -1; } - int Cnt = clearImage(); - note("已清理image文件" + to_string(Cnt) + "项", 0b1); + cmd_key = "die"; + sch.push_job(*this); return 1; } - if (strOption == "reload") + if (strOption == "rexplorer") { - if (Mirai) +#ifdef _WIN32 + if (trusted < 5 && fromQQ != console.master()) { - reply("Mirai不支持此功能"); - return -1; - } - if (trusted < 5) - { - reply(GlobalMsg["strNotMaster"]); - return -1; - } - - string strSelfName; - int pid = _getpid(); - PROCESSENTRY32 pe32; - pe32.dwSize = sizeof(pe32); - HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap == INVALID_HANDLE_VALUE) - { - note("重启失败:进程快照创建失败!", 1); - return false; - } - BOOL bResult = Process32First(hProcessSnap, &pe32); - int ppid(0); - while (bResult) - { - if (pe32.th32ProcessID == pid) - { - ppid = pe32.th32ParentProcessID; - reply("确认进程" + strModulePath + "\n本进程id:" + to_string(pe32.th32ProcessID) + "\n父进程id:" + to_string( - pe32.th32ParentProcessID)); - strSelfName = pe32.szExeFile; - break; - } - bResult = Process32Next(hProcessSnap, &pe32); - } - if (!ppid) - { - note("重启失败:未找到进程!", 1); - return -1; - } - string command = "taskkill /f /pid " + to_string(ppid) + "\nstart .\\" + strSelfName + " /account " + - to_string(getLoginQQ()); - //string command = "taskkill /f /pid " + to_string(ppid) + "\ntaskkill /f /pid " + to_string(pid) + "\nstart " + strSelfPath + " /account " + to_string(getLoginQQ()) + "\ntimeout /t 60\ndel %0"; - ofstream fout("reload.bat"); - fout << command << std::endl; - fout.close(); - note(command, 0); - this_thread::sleep_for(5s); - Enabled = false; - dataBackUp(); - switch (UINT res = -1;res = WinExec(".\\reload.bat", SW_SHOW)) - { - case 0: - note("重启失败:内存或资源已耗尽!", 1); - break; - case ERROR_FILE_NOT_FOUND: - note("重启失败:指定的文件未找到!", 1); - break; - case ERROR_PATH_NOT_FOUND: - note("重启失败:指定的路径未找到!", 1); - break; - default: - if (res > 31)note("重启成功" + to_string(res), 0); - else note("重启失败:未知错误" + to_string(res), 0); - break; - } - return 1; - } - if (strOption == "rexplorer") - { - if (trusted < 5) - { - reply(GlobalMsg["strNotMaster"]); + reply(GlobalMsg["strNotMaster"]); return -1; } system(R"(taskkill /f /fi "username eq %username%" /im explorer.exe)"); system(R"(start %SystemRoot%\explorer.exe)"); + this_thread::sleep_for(3s); note("已重启资源管理器√\n当前内存占用:" + to_string(getRamPort()) + "%"); +#endif } else if (strOption == "cmd") { +#ifdef _WIN32 if (fromQQ != console.master()) { reply(GlobalMsg["strNotMaster"]); @@ -1301,94 +1349,73 @@ int FromMsg::DiceReply() system(strCMD.c_str()); reply("已启动命令行√"); return 1; +#endif } } - else if (strLowerMessage.substr(intMsgCnt, 5) == "admin") - { + else if (strLowerMessage.substr(intMsgCnt, 5) == "admin") { intMsgCnt += 5; return AdminEvent(readPara()); } - else if (strLowerMessage.substr(intMsgCnt, 5) == "cloud") - { + else if (strLowerMessage.substr(intMsgCnt, 5) == "cloud") { intMsgCnt += 5; string strOpt = readPara(); - if (trusted < 4 && fromQQ != console.master()) - { + if (trusted < 4 && fromQQ != console.master()) { reply(GlobalMsg["strNotAdmin"]); return 1; } - if (strOpt == "update") - { - string strPara = readPara(); - if (strPara.empty()) - { + if (strOpt == "update") { + strVar["ver"] = readPara(); + if (strVar["ver"].empty()) { Cloud::checkUpdate(this); } - else if (strPara == "dev" || strPara == "release") - { - string strAppPath(strModulePath); - strAppPath = strAppPath.substr(0, strAppPath.find_last_of('\\')) + "\\app\\com.w4123.dice.cpk"; - - switch (Cloud::DownloadFile( - ("http://shiki.stringempty.xyz/DiceVer/" + strPara + "?" + to_string(fromTime)).c_str(), - strAppPath.c_str())) - { - case -1: - reply("下载失败:" + strAppPath); - break; - case -2: - reply("文件未找到:" + strAppPath); - break; - case 0: - note("更新开发版成功√\n可reload应用更新"); - } + else if (strVar["ver"] == "dev" || strVar["ver"] == "release") { + cmd_key = "update"; + sch.push_job(*this); } return 1; } + else if (strOpt == "black") { + cmd_key = "cloudblack"; + sch.push_job(*this); + return 1; + } } - else if (strLowerMessage.substr(intMsgCnt, 5) == "coc7d" || strLowerMessage.substr(intMsgCnt, 4) == "cocd") - { - strReply = strVar["nick"]; - COC7D(strReply); - reply(strReply); + else if (strLowerMessage.substr(intMsgCnt, 5) == "coc7d" || strLowerMessage.substr(intMsgCnt, 4) == "cocd") { + COC7D(strVar["res"]); + reply(GlobalMsg["strCOCBuild"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 5) == "coc6d") - { - strReply = strVar["nick"]; - COC6D(strReply); - reply(strReply); + else if (strLowerMessage.substr(intMsgCnt, 5) == "coc6d") { + COC6D(strVar["res"]); + reply(GlobalMsg["strCOCBuild"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 5) == "group") - { + else if (strLowerMessage.substr(intMsgCnt, 5) == "group") { intMsgCnt += 5; long long llGroup(fromGroup); readSkipSpace(); - if (strLowerMessage.substr(intMsgCnt, 3) == "all") - { - if (trusted < 5) - { + if (strMsg.length() == intMsgCnt) { + reply(fmt->get_help("group")); + return 1; + } + if (strLowerMessage.substr(intMsgCnt, 3) == "all") { + if (trusted < 5) { reply(GlobalMsg["strNotMaster"]); return 1; } intMsgCnt += 3; readSkipSpace(); - if (strMsg[intMsgCnt] == '+' || strMsg[intMsgCnt] == '-') - { + if (strMsg[intMsgCnt] == '+' || strMsg[intMsgCnt] == '-') { bool isSet = strMsg[intMsgCnt] == '+'; intMsgCnt++; string strOption = strVar["option"] = readRest(); - if (!mChatConf.count(strVar["option"])) - { + if (!mChatConf.count(strVar["option"])) { reply(GlobalMsg["strGroupSetNotExist"]); return 1; } int Cnt = 0; - if (isSet) - { - for (auto& [id, grp] : ChatList) - { + if (isSet) { + for (auto& [id, grp] : ChatList) { if (grp.isset(strOption))continue; grp.set(strOption); Cnt++; @@ -1396,10 +1423,8 @@ int FromMsg::DiceReply() strVar["cnt"] = to_string(Cnt); note(GlobalMsg["strGroupSetAll"], 0b100); } - else - { - for (auto& [id, grp] : ChatList) - { + else { + for (auto& [id, grp] : ChatList) { if (!grp.isset(strOption))continue; grp.reset(strOption); Cnt++; @@ -1410,685 +1435,493 @@ int FromMsg::DiceReply() } return 1; } - if (string strGroup = readDigit(false); !strGroup.empty()) - { + if (string& strGroup = strVar["group_id"] = readDigit(false); !strGroup.empty()) { llGroup = stoll(strGroup); - if (!ChatList.count(llGroup)) - { + if (!ChatList.count(llGroup)) { reply(GlobalMsg["strGroupNotFound"]); return 1; } - if (getGroupAuth(llGroup) < 0) - { + if (getGroupAuth(llGroup) < 0) { reply(GlobalMsg["strGroupDenied"]); return 1; } } - else if (intT != GroupT)return 0; + else if (fromChat.second != msgtype::Group)return 0; + else strVar["group_id"] = to_string(fromGroup); Chat& grp = chat(llGroup); while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; - if (strMsg[intMsgCnt] == '+' || strMsg[intMsgCnt] == '-') - { - bool isSet = strMsg[intMsgCnt] == '+'; - intMsgCnt++; - strVar["option"] = readRest(); - if (!mChatConf.count(strVar["option"])) - { - reply(GlobalMsg["strGroupSetDenied"]); - return 1; - } - if (getGroupAuth(llGroup) >= get(mChatConf, strVar["option"], 0)) - { - if (isSet) - { - if (groupset(llGroup, strVar["option"]) < 1) - { - chat(llGroup).set(strVar["option"]); - reply(GlobalMsg["strGroupSetOn"]); - if (strVar["Option"] == "许可使用") - { - AddMsgToQueue(getMsg("strGroupAuthorized", strVar), fromQQ, msgtype::Group); + if(strMsg[intMsgCnt] == '+' || strMsg[intMsgCnt] == '-'){ + int cntSet{ 0 }; + bool isSet{ strMsg[intMsgCnt] == '+' }; + do{ + isSet = strMsg[intMsgCnt] == '+'; + intMsgCnt++; + strVar["option"] = readPara(); + readSkipSpace(); + if (!mChatConf.count(strVar["option"])) { + reply(GlobalMsg["strGroupSetInvalid"]); + continue; + } + if (getGroupAuth(llGroup) >= get(mChatConf, strVar["option"], 0)) { + if (isSet) { + if (groupset(llGroup, strVar["option"]) < 1) { + chat(llGroup).set(strVar["option"]); + ++cntSet; + if (strVar["Option"] == "许可使用") { + AddMsgToQueue(getMsg("strGroupAuthorized", strVar), fromQQ, msgtype::Group); + } + } + else { + reply(GlobalMsg["strGroupSetOnAlready"]); } } - else - { - reply(GlobalMsg["strGroupSetOnAlready"]); + else if (grp.isset(strVar["option"])) { + ++cntSet; + chat(llGroup).reset(strVar["option"]); + } + else { + reply(GlobalMsg["strGroupSetOffAlready"]); } - return 1; - } - if (grp.isset(strVar["option"])) - { - chat(llGroup).reset(strVar["option"]); - reply(GlobalMsg["strGroupSetOff"]); } - else - { - reply(GlobalMsg["strGroupSetOffAlready"]); + else { + reply(GlobalMsg["strGroupSetDenied"]); } - return 1; + } while (strMsg[intMsgCnt] == '+' || strMsg[intMsgCnt] == '-'); + if (cntSet == 1) { + isSet ? reply(GlobalMsg["strGroupSetOn"]) : reply(GlobalMsg["strGroupSetOff"]); + } + else if(cntSet > 1) { + strVar["opt_list"] = grp.listBoolConf(); + reply(GlobalMsg["strGroupMultiSet"]); } - reply(GlobalMsg["strGroupSetDenied"]); return 1; } - GroupInfo grpinfo; - bool isInGroup = getGroupList().count(llGroup); - if (isInGroup)grpinfo = GroupInfo(llGroup); + bool isInGroup{ fromGroup == llGroup || DD::isGroupMember(llGroup,console.DiceMaid,true) }; string Command = readPara(); - - if (Command == "state") - { - time_t tNow = time(nullptr); - const int intTMonth = 30 * 24 * 60 * 60; - set sInact; - set sBlackQQ; - if (isInGroup) - for (const auto& each : getGroupMemberList(llGroup)) - { - if (!each.LastMsgTime || tNow - each.LastMsgTime > intTMonth) - { - sInact.insert(each.GroupNick + "(" + to_string(each.QQID) + ")"); - } - if (blacklist->get_qq_danger(each.QQID)) - { - sBlackQQ.insert(each.GroupNick + "(" + to_string(each.QQID) + ")"); - } - } + strVar["group"] = DD::printGroupInfo(llGroup); + if (Command.empty()) { + reply(fmt->get_help("group")); + return 1; + } + else if (Command == "state") { ResList res; - strVar["group"] = grpinfo.llGroup ? grpinfo.tostring() : printGroup(llGroup); res << "在{group}:"; res << grp.listBoolConf(); - for (const auto& it : grp.intConf) - { + for (const auto& it : grp.intConf) { res << it.first + ":" + to_string(it.second); } res << "记录创建:" + printDate(grp.tCreated); res << "最后记录:" + printDate(grp.tUpdated); if (grp.inviter)res << "邀请者:" + printQQ(grp.inviter); - if (isInGroup) - { - res << string("入群欢迎:") + (grp.isset("入群欢迎") ? "已设置" : "未设置"); - res << (!sInact.empty() ? "\n30天不活跃群员数:" + to_string(sInact.size()) : ""); - if (!sBlackQQ.empty()) - { - if (sBlackQQ.size() > 8) - res << GlobalMsg["strSelfName"] + "的黑名单成员" + to_string(sBlackQQ.size()) + "名"; - else - { - res << GlobalMsg["strSelfName"] + "的黑名单成员:{blackqq}"; - ResList blacks; - for (const auto& each : sBlackQQ) - { - blacks << each; - } - strVar["blackqq"] = blacks.show(); - } - } - else - { - res << "无{self}的黑名单成员"; - } - } + res << string("入群欢迎:") + (grp.isset("入群欢迎") ? "已设置" : "无"); reply(GlobalMsg["strSelfName"] + res.show()); return 1; } - if (!isInGroup && (intT != GroupT || fromGroup != llGroup)) - { + if (!grp.isGroup || (fromGroup == llGroup && fromChat.second != msgtype::Group)) { + reply(GlobalMsg["strGroupNot"]); + return 1; + } + else if (Command == "info") { + reply(DD::printGroupInfo(llGroup), false); + return 1; + } + else if (!isInGroup) { reply(GlobalMsg["strGroupNotIn"]); return 1; } - if (Command == "info") - { - reply(grpinfo.tostring(), false); + else if (Command == "survey") { + int cntDiver(0); + long long dayMax(0); + set sBlackQQ; + int cntUser(0); + size_t cntDice(0); + time_t tNow = time(nullptr); + const int intTDay = 24 * 60 * 60; + int cntSize(0); + set list{ DD::getGroupMemberList(llGroup) }; + if (list.empty()) { + reply("{self}加载成员列表失败!"); + return 1; + } + for (auto each : list) { + if (each == console.DiceMaid)continue; + if (long long lst{ DD::getGroupLastMsg(llGroup,each) }; lst > 0) { + long long dayDive((tNow - lst) / intTDay); + if (dayDive > dayMax)dayMax = dayDive; + if (dayDive > 30)++cntDiver; + } + if (blacklist->get_qq_danger(each) > 1) { + sBlackQQ.insert(printQQ(each)); + } + if (UserList.count(each))++cntUser; + if (DD::isDiceMaid(each))++cntDice; + ++cntSize; + } + ResList res; + res << "在{group}内" + << "{self}用户占比: " + to_string(cntUser * 100 / (cntSize)) + "%" + << (cntDice ? "同系骰娘: " + to_string(cntDice) : "") + << (cntDiver ? "30天潜水群员: " + to_string(cntDiver) : ""); + if (!sBlackQQ.empty()) { + if (sBlackQQ.size() > 8) + res << GlobalMsg["strSelfName"] + "的黑名单成员" + to_string(sBlackQQ.size()) + "名"; + else { + res << GlobalMsg["strSelfName"] + "的黑名单成员:{blackqq}"; + ResList blacks; + for (const auto& each : sBlackQQ) { + blacks << each; + } + strVar["blackqq"] = blacks.show(); + } + } + reply(res.show()); return 1; } - if (Command == "diver") - { + else if (Command == "diver") { + bool bForKick = false; + if (strLowerMessage.substr(intMsgCnt, 5) == "4kick") { + bForKick = true; + intMsgCnt += 5; + } std::priority_queue> qDiver; time_t tNow = time(nullptr); const int intTDay = 24 * 60 * 60; - int intSize = grpinfo.nGroupSize; - for (auto& each : getGroupMemberList(llGroup)) - { - time_t intLastMsg = (tNow - each.LastMsgTime) / intTDay; - if (!each.LastMsgTime || intLastMsg > 30) - { - qDiver.emplace(intLastMsg, each.Nick + "(" + to_string(each.QQID) + ")"); + int cntSize(0); + for (auto each : DD::getGroupMemberList(llGroup)) { + long long lst{ DD::getGroupLastMsg(llGroup,each) }; + time_t intLastMsg = (tNow - lst) / intTDay; + if (lst > 0 || intLastMsg > 30) { + qDiver.emplace(intLastMsg, (bForKick ? to_string(each) + : printQQ(each))); } + ++cntSize; } - if (qDiver.empty()) - { - reply("{self}未发现潜水成员或成员列表加载失败!"); + if (!cntSize) { + reply("{self}加载成员列表失败!"); + return 1; + } + else if (qDiver.empty()) { + reply("{self}未发现潜水群成员!"); return 1; } int intCnt(0); ResList res; - while (!qDiver.empty()) - { - res << qDiver.top().second + to_string(qDiver.top().first) + "天"; - if (++intCnt > 15 && intCnt > intSize / 80)break; + while (!qDiver.empty()) { + res << (bForKick ? qDiver.top().second + : (qDiver.top().second + to_string(qDiver.top().first) + "天")); + if (++intCnt > 15 && intCnt > cntSize / 80)break; qDiver.pop(); } - reply("潜水成员列表:" + res.show()); + bForKick ? reply("(.group " + to_string(llGroup) + " kick " + res.show(1)) + : reply("潜水成员列表:" + res.show(1)); return 1; } - if (int intPms = getGroupMemberInfo(llGroup, fromQQ).permissions; Command == "pause") - { - if (intPms < 2 && trusted < 4) - { + if (bool isAdmin = DD::isGroupAdmin(llGroup, fromQQ, true); Command == "pause") { + if (!isAdmin && trusted < 4) { reply(GlobalMsg["strPermissionDeniedErr"]); return 1; } - if (setGroupWholeBan(llGroup))reply(GlobalMsg["strGroupWholeBanErr"]); - else reply(GlobalMsg["strGroupWholeBan"]); + int secDuring(-1); + string strDuring{ readDigit() }; + if (!strDuring.empty())secDuring = stoi(strDuring); + DD::setGroupWholeBan(llGroup, secDuring); + reply(GlobalMsg["strGroupWholeBan"]); return 1; } - else - { - if (Command == "restart") - { - if (intPms < 2 && trusted < 4) - { - reply(GlobalMsg["strPermissionDeniedErr"]); - return 1; - } - if (!setGroupWholeBan(llGroup, 0))reply(GlobalMsg["strGroupWholeUnban"]); - return 1; - } - if (Command == "card") - { - if (long long llqq = readID()) - { - if (trusted < 4 && intPms < 2 && llqq != fromQQ) - { - reply(GlobalMsg["strPermissionDeniedErr"]); - return 1; - } - if (getGroupMemberInfo(llGroup, console.DiceMaid).permissions < 2) - { - reply(GlobalMsg["strSelfPermissionErr"]); - return 1; - } - while (!isspace(static_cast(strMsg[intMsgCnt])) && intMsgCnt != strMsg.length()) - intMsgCnt++; - while (isspace(static_cast(strMsg[intMsgCnt])))intMsgCnt++; - strVar["card"] = readRest(); - strVar["target"] = getName(llqq, llGroup); - if (setGroupCard(llGroup, llqq, strVar["card"])) - { - reply(GlobalMsg["strGroupCardSetErr"]); - } - else - { - reply(GlobalMsg["strGroupCardSet"]); - } - } - else - { - reply(GlobalMsg["strQQIDEmpty"]); - } - return 1; - } - if ((intPms < 2 && (getGroupMemberInfo(llGroup, console.DiceMaid).permissions < 3 || trusted < 5))) - { + else if (Command == "restart") { + if (!isAdmin && trusted < 4) { reply(GlobalMsg["strPermissionDeniedErr"]); return 1; } - if (Command == "ban") - { - if (trusted < 4) - { - reply(GlobalMsg["strNotAdmin"]); - return -1; - } - if (getGroupMemberInfo(llGroup, console.DiceMaid).permissions < 2) - { - reply(GlobalMsg["strSelfPermissionErr"]); + DD::setGroupWholeBan(llGroup, 0); + reply(GlobalMsg["strGroupWholeUnban"]); + return 1; + } + else if (Command == "card") { + if (long long llqq = readID()) { + if (trusted < 4 && !isAdmin && llqq != fromQQ) { + reply(GlobalMsg["strPermissionDeniedErr"]); return 1; } - string QQNum = readDigit(); - if (QQNum.empty()) - { - reply(GlobalMsg["strQQIDEmpty"]); - return -1; - } - long long llMemberQQ = stoll(QQNum); - GroupMemberInfo Member = getGroupMemberInfo(llGroup, llMemberQQ); - if (Member.QQID == llMemberQQ) - { - strVar["member"] = getName(Member.QQID, llGroup); - if (Member.permissions > 1) - { - reply(GlobalMsg["strSelfPermissionErr"]); - return 1; - } - string strMainDice = readDice(); - if (strMainDice.empty()) - { - reply(GlobalMsg["strValueErr"]); - return -1; - } - const int intDefaultDice = get(getUser(fromQQ).intConf, string("默认骰"), 100); - RD rdMainDice(strMainDice, intDefaultDice); - rdMainDice.Roll(); - long long intDuration = rdMainDice.intTotal; - strVar["res"] = rdMainDice.FormCompleteString(); - if (setGroupBan(llGroup, llMemberQQ, intDuration * 60) == 0) - if (intDuration <= 0) - reply(GlobalMsg["strGroupUnban"]); - else reply(GlobalMsg["strGroupBan"]); - else reply(GlobalMsg["strGroupBanErr"]); - } - else reply("{self}查无此群员×"); - } - else if (Command == "kick") - { - if (trusted < 4) - { - reply(GlobalMsg["strNotAdmin"]); - return -1; - } - if (getGroupMemberInfo(llGroup, console.DiceMaid).permissions < 2) - { + if (!DD::isGroupAdmin(llGroup, console.DiceMaid, true)) { reply(GlobalMsg["strSelfPermissionErr"]); return 1; } - long long llMemberQQ = readID(); - if (!llMemberQQ) - { - reply(GlobalMsg["strQQIDEmpty"]); - return -1; - } - ResList resKicked, resDenied, resNotFound; - GroupMemberInfo Member; - do - { - Member = getGroupMemberInfo(llGroup, llMemberQQ); - if (Member.QQID == llMemberQQ) - { - if (Member.permissions > 1) - { - resDenied << Member.Nick + "(" + to_string(Member.QQID) + ")"; - continue; - } - if (setGroupKick(llGroup, llMemberQQ, false) == 0) - { - resKicked << Member.Nick + "(" + to_string(Member.QQID) + ")"; - } - else resDenied << Member.Nick + "(" + to_string(Member.QQID) + ")"; - } - else resNotFound << to_string(llMemberQQ); - } - while ((llMemberQQ = readID())); - strReply = GlobalMsg["strSelfName"]; - if (!resKicked.empty())strReply += "已移出群员:" + resKicked.show() + "\n"; - if (!resDenied.empty())strReply += "移出失败:" + resDenied.show() + "\n"; - if (!resNotFound.empty())strReply += "找不到对象:" + resNotFound.show(); - reply(); + while (!isspace(static_cast(strMsg[intMsgCnt])) && intMsgCnt != strMsg.length()) + intMsgCnt++; + while (isspace(static_cast(strMsg[intMsgCnt])))intMsgCnt++; + strVar["card"] = readRest(); + strVar["target"] = getName(llqq, llGroup); + DD::setGroupCard(llGroup, llqq, strVar["card"]); + reply(GlobalMsg["strGroupCardSet"]); + } + else { + reply(GlobalMsg["strQQIDEmpty"]); + } + return 1; + } + else if ((!isAdmin && (!DD::isGroupOwner(llGroup, console.DiceMaid,true) || trusted < 5))) { + reply(GlobalMsg["strPermissionDeniedErr"]); + return 1; + } + else if (Command == "ban") { + if (trusted < 4) { + reply(GlobalMsg["strNotAdmin"]); + return -1; + } + if (!DD::isGroupAdmin(llGroup, console.DiceMaid, true)) { + reply(GlobalMsg["strSelfPermissionErr"]); return 1; } - else if (Command == "title") - { - if (getGroupMemberInfo(llGroup, console.DiceMaid).permissions < 3) - { - reply(GlobalMsg["strSelfPermissionErr"]); - return 1; - } - if (long long llqq = readID()) - { - while (!isspace(static_cast(strMsg[intMsgCnt])) && intMsgCnt != strMsg.length()) - intMsgCnt++; - while (isspace(static_cast(strMsg[intMsgCnt])))intMsgCnt++; - strVar["title"] = readRest(); - if (setGroupSpecialTitle(llGroup, llqq, strVar["title"])) - { - reply(GlobalMsg["strGroupTitleSetErr"]); - } - else - { - strVar["target"] = getName(llqq, llGroup); - reply(GlobalMsg["strGroupTitleSet"]); + string& QQNum = strVar["ban_qq"] = readDigit(); + if (QQNum.empty()) { + reply(GlobalMsg["strQQIDEmpty"]); + return -1; + } + long long llMemberQQ = stoll(QQNum); + strVar["member"] = getName(llMemberQQ, llGroup); + string strMainDice = readDice(); + if (strMainDice.empty()) { + reply(GlobalMsg["strValueErr"]); + return -1; + } + const int intDefaultDice = get(getUser(fromQQ).intConf, string("默认骰"), 100); + RD rdMainDice(strMainDice, intDefaultDice); + rdMainDice.Roll(); + int intDuration{ rdMainDice.intTotal }; + strVar["res"] = rdMainDice.FormShortString(); + DD::setGroupBan(llGroup, llMemberQQ, intDuration * 60); + if (intDuration <= 0) + reply(GlobalMsg["strGroupUnban"]); + else reply(GlobalMsg["strGroupBan"]); + } + else if (Command == "kick") { + if (trusted < 4) { + reply(GlobalMsg["strNotAdmin"]); + return -1; + } + if (!DD::isGroupAdmin(llGroup, console.DiceMaid, true)) { + reply(GlobalMsg["strSelfPermissionErr"]); + return 1; + } + long long llMemberQQ = readID(); + if (!llMemberQQ) { + reply(GlobalMsg["strQQIDEmpty"]); + return -1; + } + ResList resKicked, resDenied, resNotFound; + do { + if (int auth{ DD::getGroupAuth(llGroup, llMemberQQ,0) }) { + if (auth > 1) { + resDenied << printQQ(llMemberQQ); + continue; } - } - else - { - reply(GlobalMsg["strQQIDEmpty"]); - } + DD::setGroupKick(llGroup, llMemberQQ); + resKicked << printQQ(llMemberQQ); + } + else resNotFound << printQQ(llMemberQQ); + } while ((llMemberQQ = readID())); + strReply = GlobalMsg["strSelfName"]; + if (!resKicked.empty())strReply += "已移出群员:" + resKicked.show() + "\n"; + if (!resDenied.empty())strReply += "移出失败:" + resDenied.show() + "\n"; + if (!resNotFound.empty())strReply += "找不到对象:" + resNotFound.show(); + reply(); + return 1; + } + else if (Command == "title") { + if (!DD::isGroupOwner(llGroup, console.DiceMaid,true)) { + reply(GlobalMsg["strSelfPermissionErr"]); return 1; } + if (long long llqq = readID()) { + while (!isspace(static_cast(strMsg[intMsgCnt])) && intMsgCnt != strMsg.length()) + intMsgCnt++; + while (isspace(static_cast(strMsg[intMsgCnt])))intMsgCnt++; + strVar["title"] = readRest(); + DD::setGroupTitle(llGroup, llqq, strVar["title"]); + strVar["target"] = getName(llqq, llGroup); + reply(GlobalMsg["strGroupTitleSet"]); + } + else { + reply(GlobalMsg["strQQIDEmpty"]); + } + return 1; } return 1; } - else if (strLowerMessage.substr(intMsgCnt, 5) == "reply") - { + else if (strLowerMessage.substr(intMsgCnt, 5) == "reply") { intMsgCnt += 5; - if (trusted < 4) - { + if (trusted < 4) { reply(GlobalMsg["strNotAdmin"]); return -1; } strVar["key"] = readUntilSpace(); - vector* Deck = nullptr; - if (strVar["key"].empty()) - { + if (strVar["key"].empty()) { reply(GlobalMsg["strParaEmpty"]); return -1; } + vector* Deck = nullptr; CardDeck::mReplyDeck[strVar["key"]] = {}; Deck = &CardDeck::mReplyDeck[strVar["key"]]; - while (intMsgCnt != strMsg.length()) - { + while (intMsgCnt != strMsg.length()) { string item = readItem(); if (!item.empty())Deck->push_back(item); } - if (Deck->empty()) - { - reply(GlobalMsg["strReplyDel"], {strVar["key"]}); + if (Deck->empty()) { + reply(GlobalMsg["strReplyDel"], { strVar["key"] }); CardDeck::mReplyDeck.erase(strVar["key"]); } else reply(GlobalMsg["strReplySet"], {strVar["key"]}); - saveJMap(DiceDir + "\\conf\\CustomReply.json", CardDeck::mReplyDeck); + saveJMap(DiceDir / "conf" / "CustomReply.json", CardDeck::mReplyDeck); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 5) == "rules") - { + else if (strLowerMessage.substr(intMsgCnt, 5) == "rules") { intMsgCnt += 5; while (isspace(static_cast(strMsg[intMsgCnt]))) intMsgCnt++; - if (strLowerMessage.substr(intMsgCnt, 3) == "set") - { + if (strMsg.length() == intMsgCnt) { + reply(fmt->get_help("rules")); + return 1; + } + if (strLowerMessage.substr(intMsgCnt, 3) == "set") { intMsgCnt += 3; while (isspace(static_cast(strMsg[intMsgCnt])) || strMsg[intMsgCnt] == ':') intMsgCnt++; string strDefaultRule = strMsg.substr(intMsgCnt); - if (strDefaultRule.empty()) - { + if (strDefaultRule.empty()) { getUser(fromQQ).rmStrConf("默认规则"); reply(GlobalMsg["strRuleReset"]); } - else - { + else { for (auto& n : strDefaultRule) n = toupper(static_cast(n)); getUser(fromQQ).setConf("默认规则", strDefaultRule); reply(GlobalMsg["strRuleSet"]); } } - else - { + else { string strSearch = strMsg.substr(intMsgCnt); for (auto& n : strSearch) n = toupper(static_cast(n)); string strReturn; - if (getUser(fromQQ).strConf.count("默认规则") && strSearch.find(':') == string::npos && - GetRule::get(getUser(fromQQ).strConf["默认规则"], strSearch, strReturn)) - { + if (getUser(fromQQ).strConf.count("默认规则") && strSearch.find(':') == string::npos && + GetRule::get(getUser(fromQQ).strConf["默认规则"], strSearch, strReturn)) { reply(strReturn); } - else if (GetRule::analyze(strSearch, strReturn)) - { + else if (GetRule::analyze(strSearch, strReturn)) { reply(strReturn); } - else - { + else { reply(GlobalMsg["strRuleErr"] + strReturn); } } return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "coc6") - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "coc6") { intMsgCnt += 4; if (strLowerMessage[intMsgCnt] == 's') intMsgCnt++; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; string strNum; - while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) - { + while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) { strNum += strLowerMessage[intMsgCnt]; intMsgCnt++; } - if (strNum.length() > 2) - { + if (strNum.length() > 2) { reply(GlobalMsg["strCharacterTooBig"]); return 1; } const int intNum = stoi(strNum.empty() ? "1" : strNum); - if (intNum > 10) - { + if (intNum > 10) { reply(GlobalMsg["strCharacterTooBig"]); return 1; } - if (intNum == 0) - { + if (intNum == 0) { reply(GlobalMsg["strCharacterCannotBeZero"]); return 1; } - strReply = strVar["nick"]; - COC6(strReply, intNum); - reply(strReply); + COC6(strVar["res"], intNum); + reply(GlobalMsg["strCOCBuild"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "deck") - { - if (trusted < 4 && console["DisabledDeck"]) - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "deck") { + if (trusted < 4 && console["DisabledDeck"]) { reply(GlobalMsg["strDisabledDeckGlobal"]); return 1; } intMsgCnt += 4; - readSkipSpace(); + string strRoom = readDigit(false); + long long llRoom = strRoom.empty() ? fromSession : stoll(strRoom); + if (llRoom == 0)llRoom = fromSession; + if (strMsg.length() == intMsgCnt) { + reply(fmt->get_help("deck")); + return 1; + } string strPara = readPara(); - vector *DeckPro = nullptr, *DeckTmp = nullptr; - if (intT != PrivateT && CardDeck::mGroupDeck.count(fromGroup)) - { - DeckPro = &CardDeck::mGroupDeck[fromGroup]; - DeckTmp = &CardDeck::mGroupDeckTmp[fromGroup]; + if (strPara == "show") { + if (gm->has_session(llRoom)) + gm->session(llRoom).deck_show(this); + else reply(GlobalMsg["strDeckListEmpty"]); } - else - { - if (CardDeck::mPrivateDeck.count(fromQQ)) - { - DeckPro = &CardDeck::mPrivateDeck[fromQQ]; - DeckTmp = &CardDeck::mPrivateDeckTmp[fromQQ]; - } - //haichayixiangpanding + else if ((!isAuth || llRoom != fromSession) && !trusted) { + reply(GlobalMsg["strWhiteQQDenied"]); } - if (strPara == "show") - { - if (!DeckTmp) - { - reply(GlobalMsg["strDeckTmpNotFound"]); - return 1; - } - if (DeckTmp->empty()) - { - reply(GlobalMsg["strDeckTmpEmpty"]); - return 1; - } - string strReply = GlobalMsg["strDeckTmpShow"] + "\n"; - for (const auto& it : *DeckTmp) - { - it.length() > 10 ? strReply += it + "\n" : strReply += it + "|"; - } - strReply.erase(strReply.end() - 1); - reply(strReply); - return 1; + else if (strPara == "set") { + gm->session(llRoom).deck_set(this); } - if (!intT && !isAuth && !trusted) - { - reply(GlobalMsg["strPermissionDeniedErr"]); - return 1; + else if (strPara == "reset") { + gm->session(llRoom).deck_reset(this); } - if (strPara == "set") - { - strVar["deck_name"] = readAttrName(); - if (strVar["deck_name"].empty())strVar["deck_name"] = readDigit(); - if (strVar["deck_name"].empty()) - { - reply(GlobalMsg["strDeckNameEmpty"]); - return 1; - } - vector DeckSet = {}; - if ((strVar["deck_name"] == "群成员" || strVar["deck_name"] == "member") && intT == GroupT) - { - vector list = getGroupMemberList(fromGroup); - for (auto& each : list) - { - DeckSet.push_back( - (each.GroupNick.empty() ? each.Nick : each.GroupNick) + "(" + to_string(each.QQID) + ")"); - } - CardDeck::mGroupDeck[fromGroup] = DeckSet; - CardDeck::mGroupDeckTmp.erase(fromGroup); - reply(GlobalMsg["strDeckProSet"], {strVar["deck_name"]}); - return 1; - } - switch (CardDeck::findDeck(strVar["deck_name"])) - { - case 1: - if (strVar["deck_name"][0] == '_') - reply(GlobalMsg["strDeckNotFound"]); - else - DeckSet = CardDeck::mPublicDeck[strVar["deck_name"]]; - break; - case 2: - { - int intSize = stoi(strVar["deck_name"]) + 1; - if (intSize == 0) - { - reply(GlobalMsg["strNumCannotBeZero"]); - return 1; - } - strVar["deck_name"] = "数列1至" + strVar["deck_name"]; - while (--intSize) { - DeckSet.push_back(to_string(intSize)); - } - break; - } - case 0: - default: - reply(GlobalMsg["strDeckNotFound"]); - return 1; - } - if (intT == PrivateT) - { - CardDeck::mPrivateDeck[fromQQ] = DeckSet; - } - else - { - CardDeck::mGroupDeck[fromGroup] = DeckSet; - } - reply(GlobalMsg["strDeckProSet"], { strVar["deck_name"] }); - return 1; + else if (strPara == "del") { + gm->session(llRoom).deck_del(this); } - if (strPara == "reset") - { - *DeckTmp = vector(*DeckPro); - reply(GlobalMsg["strDeckTmpReset"]); - return 1; + else if (strPara == "clr") { + gm->session(llRoom).deck_clr(this); } - if (strPara == "clr") - { - if (intT == PrivateT) - { - if (CardDeck::mPrivateDeck.count(fromQQ) == 0) - { - reply(GlobalMsg["strDeckProNull"]); - return 1; - } - CardDeck::mPrivateDeck.erase(fromQQ); - if (DeckTmp)DeckTmp->clear(); - reply(GlobalMsg["strDeckProClr"]); - } - else - { - if (CardDeck::mGroupDeck.count(fromGroup) == 0) - { - reply(GlobalMsg["strDeckProNull"]); - return 1; - } - CardDeck::mGroupDeck.erase(fromGroup); - if (DeckTmp)DeckTmp->clear(); - reply(GlobalMsg["strDeckProClr"]); - } - return 1; - } - if (strPara == "new") - { - if (intT != PrivateT && groupset(fromGroup, "许可使用") == 0) - { - reply(GlobalMsg["strWhiteGroupDenied"]); - return 1; - } - if (intT == PrivateT && trustedQQ(fromQQ) == 0) - { - reply(GlobalMsg["strWhiteQQDenied"]); - return 1; - } - if (intT == PrivateT) - { - CardDeck::mPrivateDeck[fromQQ] = {}; - DeckPro = &CardDeck::mPrivateDeck[fromQQ]; - } - else - { - CardDeck::mGroupDeck[fromGroup] = {}; - DeckPro = &CardDeck::mGroupDeck[fromGroup]; - } - while (intMsgCnt != strMsg.length()) - { - string item = readItem(); - if (!item.empty())DeckPro->push_back(item); - } - reply(GlobalMsg["strDeckProNew"]); - return 1; + else if (strPara == "new") { + gm->session(llRoom).deck_new(this); } + return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "draw") - { - if (trusted < 4 && console["DisabledDraw"]) - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "draw") { + if (trusted < 4 && console["DisabledDraw"]) { reply(GlobalMsg["strDisabledDrawGlobal"]); return 1; } strVar["option"] = "禁用draw"; - if (intT && groupset(fromGroup, strVar["option"]) > 0) - { + if (fromChat.second != msgtype::Private && groupset(fromGroup, strVar["option"]) > 0) { reply(GlobalMsg["strGroupSetOnAlready"]); return 1; } intMsgCnt += 4; + bool isPrivate(false); + if (strMsg[intMsgCnt] == 'h' && isspace(static_cast(strMsg[intMsgCnt + 1]))) { + strVar["hidden"]; + isPrivate = true; + ++intMsgCnt; + } while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; vector ProDeck; vector* TempDeck = nullptr; - strVar["deck_name"] = readPara(); - if (strVar["deck_name"].empty()) - { - if (intT != PrivateT && CardDeck::mGroupDeck.count(fromGroup)) - { - if (CardDeck::mGroupDeckTmp.count(fromGroup) == 0 || CardDeck::mGroupDeckTmp[fromGroup].empty()) - CardDeck::mGroupDeckTmp[fromGroup] = vector(CardDeck::mGroupDeck[fromGroup]); - TempDeck = &CardDeck::mGroupDeckTmp[fromGroup]; - } - else if (CardDeck::mPrivateDeck.count(fromQQ)) - { - if (CardDeck::mPrivateDeckTmp.count(fromQQ) == 0 || CardDeck::mPrivateDeckTmp[fromQQ].empty()) - CardDeck::mPrivateDeckTmp[fromQQ] = vector(CardDeck::mPrivateDeck[fromQQ]); - TempDeck = &CardDeck::mPrivateDeckTmp[fromQQ]; - } - else - { - reply(GlobalMsg["strDeckNameEmpty"]); + string& key{ strVar["deck_name"] = readAttrName() }; + while (!strVar["deck_name"].empty() && strVar["deck_name"][0] == '_') { + isPrivate = true; + strVar["hidden"]; + strVar["deck_name"].erase(strVar["deck_name"].begin()); + } + if (strVar["deck_name"].empty()) { + reply(fmt->get_help("draw")); + return 1; + } + else { + if (gm->has_session(fromSession) && gm->session(fromSession).has_deck(key)) { + gm->session(fromSession)._draw(this); return 1; } - } - else - { - //int intFoundRes = CardDeck::findDeck(strVar["deck_name"]); - if (strVar["deck_name"][0] == '_' || CardDeck::findDeck(strVar["deck_name"]) == 0) - { + else if (CardDeck::findDeck(strVar["deck_name"]) == 0) { strReply = GlobalMsg["strDeckNotFound"]; reply(strReply); return 1; @@ -2097,11 +1930,9 @@ int FromMsg::DiceReply() TempDeck = &ProDeck; } int intCardNum = 1; - switch (readNum(intCardNum)) - { + switch (readNum(intCardNum)) { case 0: - if (intCardNum == 0) - { + if (intCardNum == 0) { reply(GlobalMsg["strNumCannotBeZero"]); return 1; } @@ -2110,46 +1941,62 @@ int FromMsg::DiceReply() case -2: reply(GlobalMsg["strParaIllegal"]); console.log("提醒:" + printQQ(fromQQ) + "对" + GlobalMsg["strSelfName"] + "使用了非法指令参数\n" + strMsg, 1, - printSTNow()); + printSTNow()); return 1; } ResList Res; - while (intCardNum--) - { + while (intCardNum--) { Res << CardDeck::drawCard(*TempDeck); if (TempDeck->empty())break; } strVar["res"] = Res.dot("|").show(); - reply(GlobalMsg["strDrawCard"], {strVar["pc"], strVar["res"]}); - if (intCardNum > 0) - { + strVar["cnt"] = to_string(Res.size()); + strVar["nick"] = getName(fromQQ, fromGroup); + getPCName(*this); + initVar({ strVar["pc"], strVar["res"] }); + if (isPrivate) { + reply(GlobalMsg["strDrawHidden"]); + replyHidden(GlobalMsg["strDrawCard"]); + } + else + reply(GlobalMsg["strDrawCard"]); + if (intCardNum > 0) { reply(GlobalMsg["strDeckEmpty"]); return 1; } return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "init" && intT) - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "init") { intMsgCnt += 4; - while (isspace(static_cast(strLowerMessage[intMsgCnt])))intMsgCnt++; - if (strLowerMessage.substr(intMsgCnt, 3) == "clr") - { - if (gm->session(fromGroup).table_clr("先攻")) - reply("成功清除先攻记录!"); + strVar["table_name"] = "先攻"; + string strCmd = readPara(); + if (strCmd.empty()|| fromChat.second == msgtype::Private) { + reply(fmt->get_help("init")); + } + else if (!gm->has_session(fromSession) || !gm->session(fromSession).table_count("先攻")) { + reply(GlobalMsg["strGMTableNotExist"]); + } + else if (strCmd == "show" || strCmd == "list") { + strVar["res"] = gm->session(fromSession).table_prior_show("先攻"); + reply(GlobalMsg["strGMTableShow"]); + } + else if (strCmd == "del") { + strVar["table_item"] = readRest(); + if (strVar["table_item"].empty()) + reply(GlobalMsg["strGMTableItemEmpty"]); + else if (gm->session(fromSession).table_del("先攻", strVar["table_item"])) + reply(GlobalMsg["strGMTableItemDel"]); else - reply("列表为空!"); - return 1; + reply(GlobalMsg["strGMTableItemNotFound"]); + } + else if (strCmd == "clr") { + gm->session(fromSession).table_clr("先攻"); + reply(GlobalMsg["strGMTableClr"]); } - strVar["table_name"] = "先攻"; - if (gm->session(fromGroup).table_count("先攻")) - reply(GlobalMsg["strGMTableShow"] + gm->session(fromGroup).table_prior_show("先攻")); - else reply(GlobalMsg["strGMTableNotExist"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "jrrp") - { - if (console["DisabledJrrp"]) - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "jrrp") { + if (console["DisabledJrrp"]) { reply("&strDisabledJrrpGlobal"); return 1; } @@ -2157,267 +2004,175 @@ int FromMsg::DiceReply() while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; const string Command = strLowerMessage.substr(intMsgCnt, strMsg.find(' ', intMsgCnt) - intMsgCnt); - if (intT == GroupT) - { - if (Command == "on") - { - if (isAuth) - { - if (groupset(fromGroup, "禁用jrrp") > 0) - { + if (fromChat.second == msgtype::Group) { + if (Command == "on") { + if (isAuth) { + if (groupset(fromGroup, "禁用jrrp") > 0) { chat(fromGroup).reset("禁用jrrp"); reply("成功在本群中启用JRRP!"); } - else - { + else { reply("在本群中JRRP没有被禁用!"); } } - else - { + else { reply(GlobalMsg["strPermissionDeniedErr"]); } return 1; } - if (Command == "off") - { - if (getGroupMemberInfo(fromGroup, fromQQ).permissions >= 2) - { - if (groupset(fromGroup, "禁用jrrp") < 1) - { + if (Command == "off") { + if (isAuth) { + if (groupset(fromGroup, "禁用jrrp") < 1) { chat(fromGroup).set("禁用jrrp"); reply("成功在本群中禁用JRRP!"); } - else - { + else { reply("在本群中JRRP没有被启用!"); } } - else - { + else { reply(GlobalMsg["strPermissionDeniedErr"]); } return 1; } - if (groupset(fromGroup, "禁用jrrp") > 0) - { + if (groupset(fromGroup, "禁用jrrp") > 0) { reply("在本群中JRRP功能已被禁用"); return 1; } } - else if (intT == DiscussT) - { - if (Command == "on") - { - if (groupset(fromGroup, "禁用jrrp") > 0) - { + else if (fromChat.second != msgtype::Discuss) { + if (Command == "on") { + if (groupset(fromGroup, "禁用jrrp") > 0) { chat(fromGroup).reset("禁用jrrp"); reply("成功在此多人聊天中启用JRRP!"); } - else - { + else { reply("在此多人聊天中JRRP没有被禁用!"); } return 1; } - if (Command == "off") - { - if (groupset(fromGroup, "禁用jrrp") < 1) - { + if (Command == "off") { + if (groupset(fromGroup, "禁用jrrp") < 1) { chat(fromGroup).set("禁用jrrp"); reply("成功在此多人聊天中禁用JRRP!"); } - else - { + else { reply("在此多人聊天中JRRP没有被启用!"); } return 1; } - if (groupset(fromGroup, "禁用jrrp") > 0) - { + if (groupset(fromGroup, "禁用jrrp") > 0) { reply("在此多人聊天中JRRP已被禁用!"); return 1; } } - string data = "QQ=" + to_string(getLoginQQ()) + "&v=20190114" + "&QueryQQ=" + to_string(fromQQ); - char* frmdata = new char[data.length() + 1]; - strcpy_s(frmdata, data.length() + 1, data.c_str()); - bool res = Network::POST("api.kokona.tech", "/jrrp", 5555, frmdata, strVar["res"]); - delete[] frmdata; - if (res) - { - reply(GlobalMsg["strJrrp"], {strVar["nick"], strVar["res"]}); - } - else - { - reply(GlobalMsg["strJrrpErr"], {strVar["res"]}); - } + strVar["nick"] = getName(fromQQ, fromGroup); + strVar["res"] = to_string(today->getJrrp(fromQQ)); + reply(GlobalMsg["strJrrp"], { strVar["nick"], strVar["res"] }); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "link") - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "link") { intMsgCnt += 4; - if (trusted < 3) - { + if (trusted < 3) { reply(GlobalMsg["strNotAdmin"]); return true; } - isLinkOrder = true; - string strOption = readPara(); - if (strOption == "close") - { - if (mLinkedList.count(fromChat)) - { - chatType ToChat = mLinkedList[fromChat]; - mLinkedList.erase(fromChat); - auto Range = mFwdList.equal_range(fromChat); - for (auto it = Range.first; it != Range.second;) - { - if (it->second == ToChat) - { - it = mFwdList.erase(it); - } - else - { - ++it; - } - } - Range = mFwdList.equal_range(ToChat); - for (auto it = Range.first; it != Range.second;) - { - if (it->second == fromChat) - { - it = mFwdList.erase(it); - } - else - { - ++it; - } - } - reply(GlobalMsg["strLinkLoss"]); - return 1; - } - return 1; - } - string strType = readPara(); - chatType ToChat; - string strID = readDigit(); - if (strID.empty()) - { - reply(GlobalMsg["strLinkNotFound"]); - return 1; - } - ToChat.first = stoll(strID); - if (strType == "qq") - { - ToChat.second = msgtype::Private; - } - else if (strType == "group") - { - ToChat.second = msgtype::Group; - } - else if (strType == "discuss") - { - ToChat.second = msgtype::Discuss; - } - else - { - reply(GlobalMsg["strLinkNotFound"]); - return 1; - } - if (mLinkedList.count(fromChat) && mFwdList.count(mLinkedList[fromChat])) - { - mFwdList.erase(mLinkedList[fromChat]); + strVar["option"] = readPara(); + if (strVar["option"] == "close") { + gm->session(fromSession).link_close(this); } - if (strOption == "with") - { - mLinkedList[fromChat] = ToChat; - mFwdList.insert({fromChat, ToChat}); - mFwdList.insert({ToChat, fromChat}); + else if (strVar["option"] == "start") { + gm->session(fromSession).link_start(this); } - else if (strOption == "from") - { - mLinkedList[fromChat] = ToChat; - mFwdList.insert({ToChat, fromChat}); + else if (strVar["option"] == "with" || strVar["option"] == "from" || strVar["option"] == "to") { + gm->session(fromSession).link_new(this); } - else if (strOption == "to") - { - mLinkedList[fromChat] = ToChat; - mFwdList.insert({fromChat, ToChat}); + else { + reply(fmt->get_help("link")); } - else return 1; - if (ChatList.count(ToChat.first) || UserList.count(ToChat.first))reply(GlobalMsg["strLinked"]); - else reply(GlobalMsg["strLinkWarning"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "name") - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "name") { intMsgCnt += 4; while (isspace(static_cast(strMsg[intMsgCnt]))) intMsgCnt++; string type = readPara(); string strNum = readDigit(); - if (strNum.length() > 1 && strNum != "10") - { + if (strNum.length() > 1 && strNum != "10") { reply(GlobalMsg["strNameNumTooBig"]); return 1; } int intNum = strNum.empty() ? 1 : stoi(strNum); - if (intNum == 0) - { + if (intNum == 0) { reply(GlobalMsg["strNameNumCannotBeZero"]); return 1; } string strDeckName = (!type.empty() && CardDeck::mPublicDeck.count("随机姓名_" + type)) ? "随机姓名_" + type : "随机姓名"; vector TempDeck(CardDeck::mPublicDeck[strDeckName]); ResList Res; - while (intNum--) - { + while (intNum--) { Res << CardDeck::drawCard(TempDeck, true); } strVar["res"] = Res.dot("、").show(); reply(GlobalMsg["strNameGenerator"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "send") - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "send") { intMsgCnt += 4; readSkipSpace(); + if (strMsg.length() == intMsgCnt) { + reply(fmt->get_help("send")); + return 1; + } //先考虑Master带参数向指定目标发送 - if (trusted > 2) - { + if (trusted > 2) { chatType ct; - if (!readChat(ct, true)) - { + if (!readChat(ct, true)) { readSkipColon(); string strFwd(readRest()); - if (strFwd.empty()) - { + if (strFwd.empty()) { reply(GlobalMsg["strSendMsgEmpty"]); } - else - { + else { AddMsgToQueue(strFwd, ct); reply(GlobalMsg["strSendMsg"]); } return 1; } + else if (strLowerMessage.substr(intMsgCnt, 6) == "notice" && trusted > 3) { + intMsgCnt += 6; + int intLv = 0; + string strNum{ readDigit(false) }; + while (!strNum.empty()){ + if (strNum.length() > 1)break; + if (int intNum = stoi(strNum); intNum > 9)continue; + else { + intLv |= (1 << intNum); + } + if (strLowerMessage[intMsgCnt] == '+')++intMsgCnt; + strNum = readDigit(false); + } + string strNotice(readRest()); + if (intLv && !strNotice.empty()){ + console.log(strNotice, intLv); + reply(GlobalMsg["strSendMsg"]); + } + else reply(GlobalMsg["strParaEmpty"]); + return 1; + } readSkipColon(); } - else if (!console) - { + else if (!console) { reply(GlobalMsg["strSendMsgInvalid"]); return 1; } - else if (console["DisabledSend"] && trusted < 3) - { + else if (console["DisabledSend"] && trusted < 3) { reply(GlobalMsg["strDisabledSendGlobal"]); return 1; } string strInfo = readRest(); - if (strInfo.empty()) - { + if (strInfo.empty()) { reply(GlobalMsg["strSendMsgEmpty"]); return 1; } @@ -2426,13 +2181,11 @@ int FromMsg::DiceReply() reply(GlobalMsg["strSendMasterMsg"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 4) == "user") - { + else if (strLowerMessage.substr(intMsgCnt, 4) == "user") { intMsgCnt += 4; string strOption = readPara(); if (strOption.empty())return 0; - if (strOption == "state") - { + if (strOption == "state") { User& user = getUser(fromQQ); strVar["user"] = printQQ(fromQQ); ResList rep; @@ -2444,31 +2197,25 @@ int FromMsg::DiceReply() reply("{user}" + rep.show()); return 1; } - if (strOption == "trust") - { - if (trusted < 4 && fromQQ != console.master()) - { + if (strOption == "trust") { + if (trusted < 4 && fromQQ != console.master()) { reply(GlobalMsg["strNotAdmin"]); return 1; } string strTarget = readDigit(); - if (strTarget.empty()) - { + if (strTarget.empty()) { reply(GlobalMsg["strQQIDEmpty"]); return 1; } long long llTarget = stoll(strTarget); - if (trustedQQ(llTarget) >= trusted && fromQQ != console.master()) - { + if (trustedQQ(llTarget) >= trusted && !console.is_self(fromQQ) && fromQQ != llTarget) { reply(GlobalMsg["strUserTrustDenied"]); return 1; } strVar["user"] = printQQ(llTarget); strVar["trust"] = readDigit(); - if (strVar["trust"].empty()) - { - if (!UserList.count(llTarget)) - { + if (strVar["trust"].empty()) { + if (!UserList.count(llTarget)) { reply(GlobalMsg["strUserNotFound"]); return 1; } @@ -2478,34 +2225,48 @@ int FromMsg::DiceReply() } User& user = getUser(llTarget); if (short intTrust = stoi(strVar["trust"]); intTrust < 0 || intTrust > 255 || (intTrust >= trusted && fromQQ - != console.master())) - { + != console.master())) { reply(GlobalMsg["strUserTrustIllegal"]); return 1; } - else - { + else { user.trust(intTrust); } reply(GlobalMsg["strUserTrusted"]); return 1; } - if (strOption == "kill") - { - if (trusted < 4 && fromQQ != console.master()) - { + if (strOption == "diss") { + if (trusted < 4 && fromQQ != console.master()) { + reply(GlobalMsg["strNotAdmin"]); + return 1; + } + strVar["note"] = readPara(); + long long llTargetID(readID()); + if (!llTargetID) { + reply(GlobalMsg["strQQIDEmpty"]); + } + else if (trustedQQ(llTargetID) >= trusted) { + reply(GlobalMsg["strUserTrustDenied"]); + } + else { + blacklist->add_black_qq(llTargetID, this); + UserList.erase(llTargetID); + PList.erase(llTargetID); + } + return 1; + } + if (strOption == "kill") { + if (trusted < 4 && fromQQ != console.master()) { reply(GlobalMsg["strNotAdmin"]); return 1; } long long llTarget = readID(); - if (trustedQQ(llTarget) >= trusted && fromQQ != console.master()) - { + if (trustedQQ(llTarget) >= trusted && fromQQ != console.master()) { reply(GlobalMsg["strUserTrustDenied"]); return 1; } strVar["user"] = printQQ(llTarget); - if (!llTarget || !UserList.count(llTarget)) - { + if (!llTarget || !UserList.count(llTarget)) { reply(GlobalMsg["strUserNotFound"]); return 1; } @@ -2513,20 +2274,17 @@ int FromMsg::DiceReply() reply("已抹除{user}的用户记录"); return 1; } - if (strOption == "clr") - { - if (trusted < 5) - { + if (strOption == "clr") { + if (trusted < 5) { reply(GlobalMsg["strNotMaster"]); return 1; } int cnt = clearUser(); - note("已清理无效用户记录" + to_string(cnt) + "条", 0b10); + note("已清理无效或过期用户记录" + to_string(cnt) + "条", 0b10); return 1; } } - else if (strLowerMessage.substr(intMsgCnt, 3) == "coc") - { + else if (strLowerMessage.substr(intMsgCnt, 3) == "coc") { intMsgCnt += 3; if (strLowerMessage[intMsgCnt] == '7') intMsgCnt++; @@ -2535,155 +2293,166 @@ int FromMsg::DiceReply() while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; string strNum; - while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) - { + while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) { strNum += strLowerMessage[intMsgCnt]; intMsgCnt++; } - if (strNum.length() > 1 && strNum != "10") - { + if (strNum.length() > 1 && strNum != "10") { reply(GlobalMsg["strCharacterTooBig"]); return 1; } const int intNum = stoi(strNum.empty() ? "1" : strNum); - if (intNum == 0) - { + if (intNum == 0) { reply(GlobalMsg["strCharacterCannotBeZero"]); return 1; } - string strReply = strVar["pc"]; - COC7(strReply, intNum); - reply(strReply); + COC7(strVar["res"], intNum); + reply(GlobalMsg["strCOCBuild"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 3) == "dnd") - { + else if (strLowerMessage.substr(intMsgCnt, 3) == "dnd") { intMsgCnt += 3; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; string strNum; - while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) - { + while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) { strNum += strLowerMessage[intMsgCnt]; intMsgCnt++; } - if (strNum.length() > 1 && strNum != "10") - { + if (strNum.length() > 1 && strNum != "10") { reply(GlobalMsg["strCharacterTooBig"]); return 1; } const int intNum = stoi(strNum.empty() ? "1" : strNum); - if (intNum == 0) - { + if (intNum == 0) { reply(GlobalMsg["strCharacterCannotBeZero"]); return 1; } - string strReply = strVar["pc"]; - DND(strReply, intNum); - reply(strReply); + DND(strVar["res"], intNum); + reply(GlobalMsg["strDNDBuild"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 3) == "nnn") - { + else if (strLowerMessage.substr(intMsgCnt, 3) == "log") { + intMsgCnt += 3; + string strPara = readPara(); + if (strPara.empty()) { + reply(fmt->get_help("log")); + } + else if (DiceSession& game = gm->session(fromSession); strPara == "new") { + game.log_new(this); + } + else if (strPara == "on") { + game.log_on(this); + } + else if (strPara == "off") { + game.log_off(this); + } + else if (strPara == "end") { + game.log_end(this); + } + else { + reply(fmt->get_help("log")); + } + return 1; + } + else if (strLowerMessage.substr(intMsgCnt, 3) == "nnn") { intMsgCnt += 3; while (isspace(static_cast(strMsg[intMsgCnt]))) intMsgCnt++; string type = readPara(); string strDeckName = (!type.empty() && CardDeck::mPublicDeck.count("随机姓名_" + type)) ? "随机姓名_" + type : "随机姓名"; + strVar["nick"] = getName(fromQQ, fromGroup); strVar["new_nick"] = strip(CardDeck::drawCard(CardDeck::mPublicDeck[strDeckName], true)); getUser(fromQQ).setNick(fromGroup, strVar["new_nick"]); - const string strReply = format(GlobalMsg["strNameSet"], {strVar["nick"], strVar["new_nick"]}); + const string strReply = format(GlobalMsg["strNameSet"], { strVar["nick"], strVar["new_nick"] }); reply(strReply); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 3) == "set") - { + else if (strLowerMessage.substr(intMsgCnt, 3) == "set") { intMsgCnt += 3; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; - string strDefaultDice = strLowerMessage.substr(intMsgCnt, strLowerMessage.find(' ', intMsgCnt) - intMsgCnt); - while (strDefaultDice[0] == '0') - strDefaultDice.erase(strDefaultDice.begin()); - if (strDefaultDice.empty()) - strDefaultDice = "100"; - for (auto charNumElement : strDefaultDice) - if (!isdigit(static_cast(charNumElement))) - { + strVar["default"] = readDigit(); + while (strVar["default"][0] == '0') + strVar["default"].erase(strVar["default"].begin()); + if (strVar["default"].empty()) + strVar["default"] = "100"; + for (auto charNumElement : strVar["default"]) + if (!isdigit(static_cast(charNumElement))) { reply(GlobalMsg["strSetInvalid"]); return 1; } - if (strDefaultDice.length() > 4) - { + if (strVar["default"].length() > 4) { reply(GlobalMsg["strSetTooBig"]); return 1; } - const int intDefaultDice = stoi(strDefaultDice); + strVar["nick"] = getName(fromQQ, fromGroup); + getPCName(*this); + const int intDefaultDice = stoi(strVar["default"]); + if (PList.count(fromQQ)) { + PList[fromQQ][fromGroup]["__DefaultDice"] = intDefaultDice; + reply("已将" + strVar["pc"] + "的默认骰类型更改为D" + strVar["default"]); + return 1; + } if (intDefaultDice == 100) getUser(fromQQ).rmIntConf("默认骰"); else getUser(fromQQ).setConf("默认骰", intDefaultDice); - const string strSetSuccessReply = "已将" + strVar["pc"] + "的默认骰类型更改为D" + strDefaultDice; - reply(strSetSuccessReply); + + reply("已将" + strVar["nick"] + "的默认骰类型更改为D" + strVar["default"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 3) == "str" && trusted > 3) - { + else if (strLowerMessage.substr(intMsgCnt, 3) == "str" && trusted > 3) { string strName; while (!isspace(static_cast(strLowerMessage[intMsgCnt])) && intMsgCnt != strLowerMessage.length() - ) - { + ) { strName += strMsg[intMsgCnt]; intMsgCnt++; } while (strMsg[intMsgCnt] == ' ')intMsgCnt++; - if (intMsgCnt == strMsg.length() || strMsg.substr(intMsgCnt) == "show") - { + if (intMsgCnt == strMsg.length() || strMsg.substr(intMsgCnt) == "show") { AddMsgToQueue(GlobalMsg[strName], fromChat); return 1; } string strMessage = strMsg.substr(intMsgCnt); - if (strMessage == "reset") - { + if (strMessage == "reset") { EditedMsg.erase(strName); GlobalMsg[strName] = ""; note("已清除" + strName + "的自定义,将在下次重启后恢复默认设置。", 0b1); } - else - { + else { if (strMessage == "NULL")strMessage = ""; EditedMsg[strName] = strMessage; GlobalMsg[strName] = strMessage; note("已自定义" + strName + "的文本", 0b1); } - saveJMap(DiceDir + "\\conf\\CustomMsg.json", EditedMsg); + saveJMap(DiceDir / "conf" / "CustomMsg.json", EditedMsg); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "en") - { + else if (strLowerMessage.substr(intMsgCnt, 2) == "en") { intMsgCnt += 2; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; + if (strMsg.length() == intMsgCnt) { + reply(fmt->get_help("en")); + return 1; + } strVar["attr"] = readAttrName(); string strCurrentValue = readDigit(false); short nCurrentVal; short* pVal = &nCurrentVal; //获取技能原值 - if (strCurrentValue.empty()) - { - if (PList.count(fromQQ) && !strVar["attr"].empty() && (getPlayer(fromQQ)[fromGroup].stored(strVar["attr"]))) - { + if (strCurrentValue.empty()) { + if (PList.count(fromQQ) && !strVar["attr"].empty() && (getPlayer(fromQQ)[fromGroup].stored(strVar["attr"]))) { pVal = &getPlayer(fromQQ)[fromGroup][strVar["attr"]]; } - else - { + else { reply(GlobalMsg["strEnValEmpty"]); return 1; } } - else - { - if (strCurrentValue.length() > 3) - { + else { + if (strCurrentValue.length() > 3) { reply(GlobalMsg["strEnValInvalid"]); return 1; } @@ -2695,12 +2464,10 @@ int FromMsg::DiceReply() string strEnFail; string strEnSuc = "1D10"; //以加减号做开头确保与技能值相区分 - if (strLowerMessage[intMsgCnt] == '+' || strLowerMessage[intMsgCnt] == '-') - { + if (strLowerMessage[intMsgCnt] == '+' || strLowerMessage[intMsgCnt] == '-') { strEnChange = strLowerMessage.substr(intMsgCnt, strMsg.find(' ', intMsgCnt) - intMsgCnt); //没有'/'时默认成功变化值 - if (strEnChange.find('/') != std::string::npos) - { + if (strEnChange.find('/') != std::string::npos) { strEnFail = strEnChange.substr(0, strEnChange.find('/')); strEnSuc = strEnChange.substr(strEnChange.find('/') + 1); } @@ -2710,18 +2477,15 @@ int FromMsg::DiceReply() const int intTmpRollRes = RandomGenerator::Randint(1, 100); strVar["res"] = "1D100=" + to_string(intTmpRollRes) + "/" + to_string(*pVal) + " "; - if (intTmpRollRes <= *pVal && intTmpRollRes <= 95) - { - if (strEnFail.empty()) - { + if (intTmpRollRes <= *pVal && intTmpRollRes <= 95) { + if (strEnFail.empty()) { strVar["res"] += GlobalMsg["strFailure"]; reply(GlobalMsg["strEnRollNotChange"]); return 1; } strVar["res"] += GlobalMsg["strFailure"]; RD rdEnFail(strEnFail); - if (rdEnFail.Roll()) - { + if (rdEnFail.Roll()) { reply(GlobalMsg["strValueErr"]); return 1; } @@ -2735,8 +2499,7 @@ int FromMsg::DiceReply() } strVar["res"] += GlobalMsg["strSuccess"]; RD rdEnSuc(strEnSuc); - if (rdEnSuc.Roll()) - { + if (rdEnSuc.Roll()) { reply(GlobalMsg["strValueErr"]); return 1; } @@ -2748,142 +2511,114 @@ int FromMsg::DiceReply() reply(GlobalMsg["strEnRollSuccess"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "li") - { - string strAns = strVar["pc"] + "的疯狂发作-总结症状:\n"; + else if (strLowerMessage.substr(intMsgCnt, 2) == "li") { + string strAns = "{pc}的疯狂发作-总结症状:\n"; LongInsane(strAns); reply(strAns); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "me") - { - if (trusted < 4 && console["DisabledMe"]) - { + else if (strLowerMessage.substr(intMsgCnt, 2) == "me") { + if (trusted < 4 && console["DisabledMe"]) { reply(GlobalMsg["strDisabledMeGlobal"]); return 1; } intMsgCnt += 2; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; - if (intT == 0) - { + if (fromChat.second == msgtype::Private) { string strGroupID = readDigit(); - if (strGroupID.empty()) - { + if (strGroupID.empty()) { reply(GlobalMsg["strGroupIDEmpty"]); return 1; } const long long llGroupID = stoll(strGroupID); - if (groupset(llGroupID, "停用指令") && trusted < 4) - { + if (groupset(llGroupID, "停用指令") && trusted < 4) { reply(GlobalMsg["strDisabledErr"]); return 1; } - if (groupset(llGroupID, "禁用me") && trusted < 5) - { + if (groupset(llGroupID, "禁用me") && trusted < 5) { reply(GlobalMsg["strMEDisabledErr"]); return 1; } while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; string strAction = strip(readRest()); - if (strAction.empty()) - { + if (strAction.empty()) { reply(GlobalMsg["strActionEmpty"]); return 1; } string strReply = getName(fromQQ, llGroupID) + strAction; - const int intSendRes = sendGroupMsg(llGroupID, strReply); - if (intSendRes < 0) - { - reply(GlobalMsg["strSendErr"]); - } - else - { - reply(GlobalMsg["strSendSuccess"]); - } + DD::sendGroupMsg(llGroupID, strReply); + reply(GlobalMsg["strSendSuccess"]); return 1; } string strAction = strLowerMessage.substr(intMsgCnt); - if (!isAuth && (strAction == "on" || strAction == "off")) - { + if (!isAuth && (strAction == "on" || strAction == "off")) { reply(GlobalMsg["strPermissionDeniedErr"]); return 1; } - if (strAction == "off") - { - if (groupset(fromGroup, "禁用me") < 1) - { + if (strAction == "off") { + if (groupset(fromGroup, "禁用me") < 1) { chat(fromGroup).set("禁用me"); reply(GlobalMsg["strMeOff"]); } - else - { + else { reply(GlobalMsg["strMeOffAlready"]); } return 1; } - if (strAction == "on") - { - if (groupset(fromGroup, "禁用me") > 0) - { + if (strAction == "on") { + if (groupset(fromGroup, "禁用me") > 0) { chat(fromGroup).reset("禁用me"); reply(GlobalMsg["strMeOn"]); } - else - { + else { reply(GlobalMsg["strMeOnAlready"]); } return 1; } - if (groupset(fromGroup, "禁用me")) - { + if (groupset(fromGroup, "禁用me")) { reply(GlobalMsg["strMEDisabledErr"]); return 1; } strAction = strip(readRest()); - if (strAction.empty()) - { + if (strAction.empty()) { reply(GlobalMsg["strActionEmpty"]); return 1; } + strVar["nick"] = getName(fromQQ, fromGroup); + getPCName(*this); trusted > 4 ? reply(strAction) : reply(strVar["pc"] + strAction); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "nn") - { + else if (strLowerMessage.substr(intMsgCnt, 2) == "nn") { intMsgCnt += 2; while (isspace(static_cast(strMsg[intMsgCnt]))) intMsgCnt++; + strVar["nick"] = getName(fromQQ, fromGroup); strVar["new_nick"] = strip(strMsg.substr(intMsgCnt)); filter_CQcode(strVar["new_nick"]); - if (strVar["new_nick"].length() > 50) - { + if (strVar["new_nick"].length() > 50) { reply(GlobalMsg["strNameTooLongErr"]); return 1; } if (!strVar["new_nick"].empty()) { getUser(fromQQ).setNick(fromGroup, strVar["new_nick"]); - reply(GlobalMsg["strNameSet"], {strVar["nick"], strVar["new_nick"]}); + reply(GlobalMsg["strNameSet"], { strVar["nick"], strVar["new_nick"] }); } - else - { - if (getUser(fromQQ).rmNick(fromGroup)) - { - reply(GlobalMsg["strNameClr"], {strVar["nick"]}); + else { + if (getUser(fromQQ).rmNick(fromGroup)) { + reply(GlobalMsg["strNameClr"], { strVar["nick"] }); } - else - { + else { reply(GlobalMsg["strNameDelEmpty"]); } } return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "ob") - { - if (intT == PrivateT) - { - reply(GlobalMsg["strObPrivate"]); + else if (strLowerMessage.substr(intMsgCnt, 2) == "ob") { + if (fromChat.second == msgtype::Private) { + reply(fmt->get_help("ob")); return 1; } intMsgCnt += 2; @@ -2891,79 +2626,66 @@ int FromMsg::DiceReply() intMsgCnt++; const string strOption = strLowerMessage.substr(intMsgCnt, strMsg.find(' ', intMsgCnt) - intMsgCnt); - if (!isAuth && (strOption == "on" || strOption == "off")) - { + if (!isAuth && (strOption == "on" || strOption == "off")) { reply(GlobalMsg["strPermissionDeniedErr"]); return 1; } strVar["option"] = "禁用ob"; - if (strOption == "off") - { - if (groupset(fromGroup, strVar["option"]) < 1) - { + if (strOption == "off") { + if (groupset(fromGroup, strVar["option"]) < 1) { chat(fromGroup).set(strVar["option"]); - gm->session(fromGroup).clear_ob(); + gm->session(fromSession).clear_ob(); reply(GlobalMsg["strObOff"]); } - else - { + else { reply(GlobalMsg["strObOffAlready"]); } return 1; } - if (strOption == "on") - { - if (groupset(fromGroup, strVar["option"]) > 0) - { + if (strOption == "on") { + if (groupset(fromGroup, strVar["option"]) > 0) { chat(fromGroup).reset(strVar["option"]); reply(GlobalMsg["strObOn"]); } - else - { + else { reply(GlobalMsg["strObOnAlready"]); } return 1; } - if (groupset(fromGroup, strVar["option"]) > 0) - { + if (groupset(fromGroup, strVar["option"]) > 0) { reply(GlobalMsg["strObOffAlready"]); return 1; } - if (strOption == "list") - { - gm->session(fromGroup).ob_list(this); + if (strOption == "list") { + gm->session(fromSession).ob_list(this); } - else if (strOption == "clr") - { - if (intT == DiscussT || getGroupMemberInfo(fromGroup, fromQQ).permissions >= 2) - { - gm->session(fromGroup).ob_clr(this); + else if (strOption == "clr") { + if (isAuth) { + gm->session(fromSession).ob_clr(this); } - else - { + else { reply(GlobalMsg["strPermissionDeniedErr"]); } } - else if (strOption == "exit") - { - gm->session(fromGroup).ob_exit(this); + else if (strOption == "exit") { + gm->session(fromSession).ob_exit(this); } - else - { - gm->session(fromGroup).ob_enter(this); + else { + gm->session(fromSession).ob_enter(this); } return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "pc") - { + else if (strLowerMessage.substr(intMsgCnt, 2) == "pc") { intMsgCnt += 2; string strOption = readPara(); + if (strOption.empty()) { + reply(fmt->get_help("pc")); + return 1; + } Player& pl = getPlayer(fromQQ); - if (strOption == "tag") - { + if (strOption == "tag") { strVar["char"] = readRest(); - switch (pl.changeCard(strVar["char"], fromGroup)) - { + switch (pl.changeCard(strVar["char"], fromGroup)) { case 1: reply(GlobalMsg["strPcCardReset"]); break; @@ -2979,23 +2701,20 @@ int FromMsg::DiceReply() } return 1; } - if (strOption == "show") - { + if (strOption == "show") { string strName = readRest(); - strVar["char"] = pl.getCard(strName, fromGroup).Name; - strVar["type"] = pl.getCard(strName, fromGroup).Type; + strVar["char"] = pl.getCard(strName, fromGroup).getName(); + strVar["type"] = pl.getCard(strName, fromGroup).Info["__Type"]; strVar["show"] = pl[strVar["char"]].show(true); reply(GlobalMsg["strPcCardShow"]); return 1; } - if (strOption == "new") - { + if (strOption == "new") { strVar["char"] = strip(readRest()); filter_CQcode(strVar["char"]); - switch (pl.newCard(strVar["char"], fromGroup)) - { + switch (pl.newCard(strVar["char"], fromGroup)) { case 0: - strVar["type"] = pl[fromGroup].Type; + strVar["type"] = pl[fromGroup].Info["__Type"]; strVar["show"] = pl[fromGroup].show(true); if (strVar["show"].empty())reply(GlobalMsg["strPcNewEmptyCard"]); else reply(GlobalMsg["strPcNewCardShow"]); @@ -3003,14 +2722,11 @@ int FromMsg::DiceReply() case -1: reply(GlobalMsg["strPcCardFull"]); break; - case -2: - reply(GlobalMsg["strPcTempInvalid"]); - break; case -4: - reply(GlobalMsg["strPCNameExist"]); + reply(GlobalMsg["strPcNameExist"]); break; case -6: - reply(GlobalMsg["strPCNameInvalid"]); + reply(GlobalMsg["strPcNameInvalid"]); break; default: reply(GlobalMsg["strUnknownErr"]); @@ -3018,12 +2734,10 @@ int FromMsg::DiceReply() } return 1; } - if (strOption == "build") - { + if (strOption == "build") { strVar["char"] = strip(readRest()); filter_CQcode(strVar["char"]); - switch (pl.buildCard(strVar["char"], false, fromGroup)) - { + switch (pl.buildCard(strVar["char"], false, fromGroup)) { case 0: strVar["show"] = pl[strVar["char"]].show(true); reply(GlobalMsg["strPcCardBuild"]); @@ -3043,27 +2757,22 @@ int FromMsg::DiceReply() } return 1; } - if (strOption == "list") - { + if (strOption == "list") { strVar["show"] = pl.listCard(); reply(GlobalMsg["strPcCardList"]); return 1; } - if (strOption == "nn") - { + if (strOption == "nn") { strVar["new_name"] = strip(readRest()); - filter_CQcode(strVar["char"]); - if (strVar["new_name"].empty()) - { - reply(GlobalMsg["strPCNameEmpty"]); - return 1; - } - strVar["old_name"] = pl[fromGroup].Name; - switch (pl.renameCard(strVar["old_name"], strVar["new_name"])) - { + filter_CQcode(strVar["new_name"]); + strVar["old_name"] = pl[fromGroup].getName(); + switch (pl.renameCard(strVar["old_name"], strVar["new_name"])) { case 0: reply(GlobalMsg["strPcCardRename"]); break; + case -3: + reply(GlobalMsg["strPCNameEmpty"]); + break; case -4: reply(GlobalMsg["strPCNameExist"]); break; @@ -3076,11 +2785,9 @@ int FromMsg::DiceReply() } return 1; } - if (strOption == "del") - { + if (strOption == "del") { strVar["char"] = strip(readRest()); - switch (pl.removeCard(strVar["char"])) - { + switch (pl.removeCard(strVar["char"])) { case 0: reply(GlobalMsg["strPcCardDel"]); break; @@ -3096,30 +2803,26 @@ int FromMsg::DiceReply() } return 1; } - if (strOption == "redo") - { + if (strOption == "redo") { strVar["char"] = strip(readRest()); pl.buildCard(strVar["char"], true, fromGroup); strVar["show"] = pl[strVar["char"]].show(true); reply(GlobalMsg["strPcCardRedo"]); return 1; } - if (strOption == "grp") - { + if (strOption == "grp") { strVar["show"] = pl.listMap(); reply(GlobalMsg["strPcGroupList"]); return 1; } - if (strOption == "cpy") - { + if (strOption == "cpy") { string strName = strip(readRest()); filter_CQcode(strName); strVar["char1"] = strName.substr(0, strName.find('=')); - strVar["char2"] = (strVar["char1"].length() < strName.length() - 1) - ? strip(strName.substr(strVar["char1"].length() + 1)) - : pl[fromGroup].Name; - switch (pl.copyCard(strVar["char1"], strVar["char2"], fromGroup)) - { + strVar["char2"] = (strVar["char1"].length() + 1 < strName.length()) + ? strip(strName.substr(strVar["char1"].length() + 1)) + : pl[fromGroup].getName(); + switch (pl.copyCard(strVar["char1"], strVar["char2"], fromGroup)) { case 0: reply(GlobalMsg["strPcCardCpy"]); break; @@ -3138,29 +2841,58 @@ int FromMsg::DiceReply() } return 1; } - if (strOption == "clr") - { + if (strOption == "clr") { PList.erase(fromQQ); reply(GlobalMsg["strPcClr"]); return 1; } + if (strOption == "type") { + strVar["new_type"] = strip(readRest()); + if (strVar["new_type"].empty()) { + strVar["attr"] = "模板类"; + strVar["val"] = pl[fromGroup].Info["__Type"]; + reply(GlobalMsg["strProp"]); + } + else { + pl[fromGroup].setInfo("__Type", strVar["new_type"]); + reply(GlobalMsg["strSetPropSuccess"]); + } + return 1; + } + else if (strOption == "temp") { + CardTemp& temp{ *pl[fromGroup].pTemplet}; + reply(temp.show()); + return 1; + } + reply(fmt->get_help("pc")); + return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "ra" || strLowerMessage.substr(intMsgCnt, 2) == "rc") - { + else if (strLowerMessage.substr(intMsgCnt, 2) == "ra" || strLowerMessage.substr(intMsgCnt, 2) == "rc") { intMsgCnt += 2; - readSkipSpace(); - int intRule = intT - ? get(chat(fromGroup).intConf, string("rc房规"), 0) - : get(getUser(fromQQ).intConf, string("rc房规"), 0); + if (strMsg.length() == intMsgCnt) { + reply(fmt->get_help("rc")); + return 1; + } + int intRule = fromChat.second != msgtype::Private + ? get(chat(fromGroup).intConf, string("rc房规"), 0) + : get(getUser(fromQQ).intConf, string("rc房规"), 0); int intTurnCnt = 1; - if (strMsg.find('#') != string::npos) - { + bool isHidden(false); + if (strMsg[intMsgCnt] == 'h' && isspace(static_cast(strMsg[intMsgCnt + 1]))) { + isHidden = true; + ++intMsgCnt; + } + else if (readSkipSpace(); strMsg[intMsgCnt] == '_') { + isHidden = true; + ++intMsgCnt; + } + readSkipSpace(); + if (strMsg.find('#') != string::npos) { string strTurnCnt = strMsg.substr(intMsgCnt, strMsg.find('#') - intMsgCnt); //#能否识别有效 if (strTurnCnt.empty())intMsgCnt++; else if ((strTurnCnt.length() == 1 && isdigit(static_cast(strTurnCnt[0]))) || strTurnCnt == - "10") - { + "10") { intMsgCnt += strTurnCnt.length() + 1; intTurnCnt = stoi(strTurnCnt); } @@ -3177,190 +2909,164 @@ int FromMsg::DiceReply() int intSkillDivisor = 1; //自动成功 bool isAutomatic = false; - if ((strLowerMessage[intMsgCnt] == 'p' || strLowerMessage[intMsgCnt] == 'b') && strLowerMessage[intMsgCnt - 1] != ' ') - { + if ((strLowerMessage[intMsgCnt] == 'p' || strLowerMessage[intMsgCnt] == 'b') && strLowerMessage[intMsgCnt - 1] != ' ') { strMainDice = strLowerMessage[intMsgCnt]; intMsgCnt++; - while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) - { + while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) { strMainDice += strLowerMessage[intMsgCnt]; intMsgCnt++; } } readSkipSpace(); - if (strMsg.length() == intMsgCnt) - { + if (strMsg[intMsgCnt] == '_') { + isHidden = true; + ++intMsgCnt; + } + if (strMsg.length() == intMsgCnt) { strVar["attr"] = GlobalMsg["strEnDefaultName"]; - reply(GlobalMsg["strUnknownPropErr"], {strVar["attr"]}); + reply(GlobalMsg["strUnknownPropErr"], { strVar["attr"] }); return 1; } strVar["attr"] = strMsg.substr(intMsgCnt); if (PList.count(fromQQ) && PList[fromQQ][fromGroup].count(strVar["attr"]))intMsgCnt = strMsg.length(); else strVar["attr"] = readAttrName(); - if (strVar["attr"].find("自动成功") == 0) - { + if (strVar["attr"].find("自动成功") == 0) { strDifficulty = strVar["attr"].substr(0, 8); strVar["attr"] = strVar["attr"].substr(8); isAutomatic = true; } - if (strVar["attr"].find("困难") == 0 || strVar["attr"].find("极难") == 0) - { + if (strVar["attr"].find("困难") == 0 || strVar["attr"].find("极难") == 0) { strDifficulty += strVar["attr"].substr(0, 4); intDifficulty = (strVar["attr"].substr(0, 4) == "困难") ? 2 : 5; strVar["attr"] = strVar["attr"].substr(4); } - if (strLowerMessage[intMsgCnt] == '*' && isdigit(strLowerMessage[intMsgCnt + 1])) - { + if (strLowerMessage[intMsgCnt] == '*' && isdigit(strLowerMessage[intMsgCnt + 1])) { intMsgCnt++; readNum(intSkillMultiple); } while ((strLowerMessage[intMsgCnt] == '+' || strLowerMessage[intMsgCnt] == '-') && isdigit( - strLowerMessage[intMsgCnt + 1])) - { + strLowerMessage[intMsgCnt + 1])) { if (!readNum(intSkillModify))strSkillModify = to_signed_string(intSkillModify); } - if (strLowerMessage[intMsgCnt] == '/' && isdigit(strLowerMessage[intMsgCnt + 1])) - { + if (strLowerMessage[intMsgCnt] == '/' && isdigit(strLowerMessage[intMsgCnt + 1])) { intMsgCnt++; readNum(intSkillDivisor); - if (intSkillDivisor == 0) - { + if (intSkillDivisor == 0) { reply(GlobalMsg["strValueErr"]); return 1; } } while (isspace(static_cast(strLowerMessage[intMsgCnt])) || strLowerMessage[intMsgCnt] == '=' || - strLowerMessage[intMsgCnt] == - ':') + strLowerMessage[intMsgCnt] == + ':') intMsgCnt++; string strSkillVal; - while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) - { + while (isdigit(static_cast(strLowerMessage[intMsgCnt]))) { strSkillVal += strLowerMessage[intMsgCnt]; intMsgCnt++; } - while (isspace(static_cast(strLowerMessage[intMsgCnt]))) - { + while (isspace(static_cast(strLowerMessage[intMsgCnt]))) { intMsgCnt++; } strVar["reason"] = readRest(); int intSkillVal; - if (strSkillVal.empty()) - { - if (PList.count(fromQQ) && PList[fromQQ][fromGroup].count(strVar["attr"])) - { + if (strSkillVal.empty()) { + if (PList.count(fromQQ) && PList[fromQQ][fromGroup].count(strVar["attr"])) { intSkillVal = PList[fromQQ][fromGroup].call(strVar["attr"]); } - else - { - if (!PList.count(fromQQ) && SkillNameReplace.count(strVar["attr"])) - { + else { + if (!PList.count(fromQQ) && SkillNameReplace.count(strVar["attr"])) { strVar["attr"] = SkillNameReplace[strVar["attr"]]; } - if (!PList.count(fromQQ) && SkillDefaultVal.count(strVar["attr"])) - { + if (!PList.count(fromQQ) && SkillDefaultVal.count(strVar["attr"])) { intSkillVal = SkillDefaultVal[strVar["attr"]]; } - else - { - reply(GlobalMsg["strUnknownPropErr"], {strVar["attr"]}); + else { + reply(GlobalMsg["strUnknownPropErr"], { strVar["attr"] }); return 1; } } } - else if (strSkillVal.length() > 3) - { + else if (strSkillVal.length() > 3) { reply(GlobalMsg["strPropErr"]); return 1; } - else - { + else { intSkillVal = stoi(strSkillVal); } int intFianlSkillVal = (intSkillVal * intSkillMultiple + intSkillModify) / intSkillDivisor / intDifficulty; - if (intFianlSkillVal < 0 || intFianlSkillVal > 1000) - { + if (intFianlSkillVal < 0 || intFianlSkillVal > 1000) { reply(GlobalMsg["strSuccessRateErr"]); return 1; } RD rdMainDice(strMainDice); const int intFirstTimeRes = rdMainDice.Roll(); - if (intFirstTimeRes == ZeroDice_Err) - { + if (intFirstTimeRes == ZeroDice_Err) { reply(GlobalMsg["strZeroDiceErr"]); return 1; } - if (intFirstTimeRes == DiceTooBig_Err) - { + if (intFirstTimeRes == DiceTooBig_Err) { reply(GlobalMsg["strDiceTooBigErr"]); return 1; } strVar["attr"] = strDifficulty + strVar["attr"] + ( (intSkillMultiple != 1) ? "×" + to_string(intSkillMultiple) : "") + strSkillModify + ((intSkillDivisor != 1) - ? "/" + to_string( - intSkillDivisor) - : ""); - if (strVar["reason"].empty()) - { - strReply = format(GlobalMsg["strRollSkill"], {strVar["pc"], strVar["attr"]}); - } - else strReply = format(GlobalMsg["strRollSkillReason"], {strVar["pc"], strVar["attr"], strVar["reason"]}); + ? "/" + to_string( + intSkillDivisor) + : ""); + strVar["nick"] = getName(fromQQ, fromGroup); + getPCName(*this); + if (strVar["reason"].empty()) { + strReply = format(GlobalMsg["strRollSkill"], { strVar["pc"], strVar["attr"] }); + } + else strReply = format(GlobalMsg["strRollSkillReason"], { strVar["pc"], strVar["attr"], strVar["reason"] }); ResList Res; string strAns; - if (intTurnCnt == 1) - { + if (intTurnCnt == 1) { rdMainDice.Roll(); strAns = rdMainDice.FormCompleteString() + "/" + to_string(intFianlSkillVal) + " "; int intRes = RollSuccessLevel(rdMainDice.intTotal, intFianlSkillVal, intRule); - switch (intRes) - { + switch (intRes) { case 0: strAns += GlobalMsg["strRollFumble"]; break; case 1: strAns += isAutomatic ? GlobalMsg["strRollRegularSuccess"] : GlobalMsg["strRollFailure"]; break; case 5: strAns += GlobalMsg["strRollCriticalSuccess"]; break; - case 4: if (intDifficulty == 1) - { - strAns += GlobalMsg["strRollExtremeSuccess"]; - break; - } - case 3: if (intDifficulty == 1) - { - strAns += GlobalMsg["strRollHardSuccess"]; - break; - } + case 4: if (intDifficulty == 1) { + strAns += GlobalMsg["strRollExtremeSuccess"]; + break; + } + case 3: if (intDifficulty == 1) { + strAns += GlobalMsg["strRollHardSuccess"]; + break; + } case 2: strAns += GlobalMsg["strRollRegularSuccess"]; break; } strReply += strAns; } - else - { + else { Res.dot("\n"); - while (intTurnCnt--) - { + while (intTurnCnt--) { rdMainDice.Roll(); strAns = rdMainDice.FormCompleteString() + "/" + to_string(intFianlSkillVal) + " "; int intRes = RollSuccessLevel(rdMainDice.intTotal, intFianlSkillVal, intRule); - switch (intRes) - { + switch (intRes) { case 0: strAns += GlobalMsg["strFumble"]; break; case 1: strAns += isAutomatic ? GlobalMsg["strSuccess"] : GlobalMsg["strFailure"]; break; - case 5: strAns += GlobalMsg["strCriticalSuccess"]; + case 5: strAns += GlobalMsg["strCriticalSuccess"]; + break; + case 4: if (intDifficulty == 1) { + strAns += GlobalMsg["strExtremeSuccess"]; + break; + } + case 3: if (intDifficulty == 1) { + strAns += GlobalMsg["strHardSuccess"]; break; - case 4: if (intDifficulty == 1) - { - strAns += GlobalMsg["strExtremeSuccess"]; - break; - } - case 3: if (intDifficulty == 1) - { - strAns += GlobalMsg["strHardSuccess"]; - break; - } + } case 2: strAns += GlobalMsg["strSuccess"]; break; } @@ -3368,141 +3074,134 @@ int FromMsg::DiceReply() } strReply += Res.show(); } - reply(); + if (isHidden) { + replyHidden(); + reply(GlobalMsg["strRollSkillHidden"]); + } + else + reply(); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "ri" && intT) - { + else if (strLowerMessage.substr(intMsgCnt, 2) == "ri") { + if (fromChat.second == msgtype::Private) { + reply(fmt->get_help("ri")); + return 1; + } intMsgCnt += 2; - readSkipSpace(); string strinit = "D20"; - if (strLowerMessage[intMsgCnt] == '+' || strLowerMessage[intMsgCnt] == '-') - { + if (strLowerMessage[intMsgCnt] == '+' || strLowerMessage[intMsgCnt] == '-') { strinit += readDice(); } - else if (isRollDice()) - { + else if (isRollDice()) { strinit = readDice(); } readSkipSpace(); - string strname = strMsg.substr(intMsgCnt); - if (strname.empty()) + string strname = strip(strMsg.substr(intMsgCnt)); + if (strname.empty()) { + + if (!strVar.count("pc") || strVar["pc"].empty()) { + strVar["nick"] = getName(fromQQ, fromGroup); + getPCName(*this); + } strname = strVar["pc"]; - else - strname = strip(strname); + } RD initdice(strinit, 20); const int intFirstTimeRes = initdice.Roll(); - if (intFirstTimeRes == Value_Err) - { + if (intFirstTimeRes == Value_Err) { reply(GlobalMsg["strValueErr"]); return 1; } - if (intFirstTimeRes == Input_Err) - { + if (intFirstTimeRes == Input_Err) { reply(GlobalMsg["strInputErr"]); return 1; } - if (intFirstTimeRes == ZeroDice_Err) - { + if (intFirstTimeRes == ZeroDice_Err) { reply(GlobalMsg["strZeroDiceErr"]); return 1; } - if (intFirstTimeRes == ZeroType_Err) - { + if (intFirstTimeRes == ZeroType_Err) { reply(GlobalMsg["strZeroTypeErr"]); return 1; } - if (intFirstTimeRes == DiceTooBig_Err) - { + if (intFirstTimeRes == DiceTooBig_Err) { reply(GlobalMsg["strDiceTooBigErr"]); return 1; } - if (intFirstTimeRes == TypeTooBig_Err) - { + if (intFirstTimeRes == TypeTooBig_Err) { reply(GlobalMsg["strTypeTooBigErr"]); return 1; } - if (intFirstTimeRes == AddDiceVal_Err) - { + if (intFirstTimeRes == AddDiceVal_Err) { reply(GlobalMsg["strAddDiceValErr"]); return 1; } - if (intFirstTimeRes != 0) - { + if (intFirstTimeRes != 0) { reply(GlobalMsg["strUnknownErr"]); return 1; } - gm->session(fromGroup).table_add("先攻", initdice.intTotal, strname); + gm->session(fromSession).table_add("先攻", initdice.intTotal, strname); const string strReply = strname + "的先攻骰点:" + initdice.FormCompleteString(); reply(strReply); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "sc") - { + else if (strLowerMessage.substr(intMsgCnt, 2) == "sc") { intMsgCnt += 2; string SanCost = readUntilSpace(); while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; - if (SanCost.empty() || SanCost.find('/') == string::npos) - { + if (SanCost.empty()) { + reply(fmt->get_help("sc")); + return 1; + } + if (SanCost.find('/') == string::npos) { reply(GlobalMsg["strSanCostInvalid"]); return 1; } string attr = "理智"; int intSan = 0; auto* pSan = (short*)&intSan; - if (readNum(intSan)) - { - if (PList.count(fromQQ) && getPlayer(fromQQ)[fromGroup].count(attr)) - { + if (readNum(intSan)) { + if (PList.count(fromQQ) && getPlayer(fromQQ)[fromGroup].count(attr)) { pSan = &getPlayer(fromQQ)[fromGroup][attr]; } - else - { + else { reply(GlobalMsg["strSanEmpty"]); return 1; } } string strSanCostSuc = SanCost.substr(0, SanCost.find('/')); string strSanCostFail = SanCost.substr(SanCost.find('/') + 1); - for (const auto& character : strSanCostSuc) - { + for (const auto& character : strSanCostSuc) { if (!isdigit(static_cast(character)) && character != 'D' && character != 'd' && character != - '+' && character != '-') - { + '+' && character != '-') { reply(GlobalMsg["strSanCostInvalid"]); return 1; } } - for (const auto& character : SanCost.substr(SanCost.find('/') + 1)) - { + for (const auto& character : SanCost.substr(SanCost.find('/') + 1)) { if (!isdigit(static_cast(character)) && character != 'D' && character != 'd' && character != - '+' && character != '-') - { + '+' && character != '-') { reply(GlobalMsg["strSanCostInvalid"]); return 1; } } RD rdSuc(strSanCostSuc); RD rdFail(strSanCostFail); - if (rdSuc.Roll() != 0 || rdFail.Roll() != 0) - { + if (rdSuc.Roll() != 0 || rdFail.Roll() != 0) { reply(GlobalMsg["strSanCostInvalid"]); return 1; } - if (*pSan == 0) - { + if (*pSan == 0) { reply(GlobalMsg["strSanInvalid"]); return 1; } const int intTmpRollRes = RandomGenerator::Randint(1, 100); strVar["res"] = "1D100=" + to_string(intTmpRollRes) + "/" + to_string(*pSan) + " "; //调用房规 - int intRule = intT - ? get(chat(fromGroup).intConf, string("rc房规"), 0) - : get(getUser(fromQQ).intConf, string("rc房规"), 0); - switch (RollSuccessLevel(intTmpRollRes, *pSan, intRule)) - { + int intRule = fromGroup + ? get(chat(fromGroup).intConf, string("rc房规"), 0) + : get(getUser(fromQQ).intConf, string("rc房规"), 0); + switch (RollSuccessLevel(intTmpRollRes, *pSan, intRule)) { case 5: case 4: case 3: @@ -3527,75 +3226,64 @@ int FromMsg::DiceReply() reply(GlobalMsg["strSanRollRes"]); return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "st") - { + else if (strLowerMessage.substr(intMsgCnt, 2) == "st") { intMsgCnt += 2; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; - if (intMsgCnt == strLowerMessage.length()) - { - reply(GlobalMsg["strStErr"]); + if (intMsgCnt == strLowerMessage.length()) { + reply(fmt->get_help("st")); return 1; } - if (strLowerMessage.substr(intMsgCnt, 3) == "clr") - { - if (!PList.count(fromQQ)) - { + strVar["nick"] = getName(fromQQ, fromGroup); + getPCName(*this); + if (strLowerMessage.substr(intMsgCnt, 3) == "clr") { + if (!PList.count(fromQQ)) { reply(getMsg("strPcNotExistErr")); return 1; } getPlayer(fromQQ)[fromGroup].clear(); - strVar["char"] = getPlayer(fromQQ)[fromGroup].Name; - reply(GlobalMsg["strPropCleared"], {strVar["char"]}); + strVar["char"] = getPlayer(fromQQ)[fromGroup].getName(); + reply(GlobalMsg["strPropCleared"], { strVar["char"] }); return 1; } - if (strLowerMessage.substr(intMsgCnt, 3) == "del") - { - if (!PList.count(fromQQ)) - { + if (strLowerMessage.substr(intMsgCnt, 3) == "del") { + if (!PList.count(fromQQ)) { reply(getMsg("strPcNotExistErr")); return 1; } intMsgCnt += 3; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; - if (strMsg[intMsgCnt] == '&') - { + if (strMsg[intMsgCnt] == '&') { intMsgCnt++; } strVar["attr"] = readAttrName(); - if (getPlayer(fromQQ)[fromGroup].erase(strVar["attr"])) - { - reply(GlobalMsg["strPropDeleted"], {strVar["pc"], strVar["attr"]}); + if (getPlayer(fromQQ)[fromGroup].erase(strVar["attr"])) { + reply(GlobalMsg["strPropDeleted"], { strVar["pc"], strVar["attr"] }); } - else - { - reply(GlobalMsg["strPropNotFound"], {strVar["attr"]}); + else { + reply(GlobalMsg["strPropNotFound"], { strVar["attr"] }); } return 1; } CharaCard& pc = getPlayer(fromQQ)[fromGroup]; - if (strLowerMessage.substr(intMsgCnt, 4) == "show") - { + if (strLowerMessage.substr(intMsgCnt, 4) == "show") { intMsgCnt += 4; while (isspace(static_cast(strLowerMessage[intMsgCnt]))) intMsgCnt++; strVar["attr"] = readAttrName(); - if (strVar["attr"].empty()) - { - strVar["char"] = pc.Name; - strVar["type"] = pc.Type; + if (strVar["attr"].empty()) { + strVar["char"] = pc.getName(); + strVar["type"] = pc.Info["__Type"]; strVar["show"] = pc.show(false); reply(GlobalMsg["strPropList"]); return 1; } - if (pc.show(strVar["attr"], strVar["val"]) > -1) - { - reply(format(GlobalMsg["strProp"], {strVar["pc"], strVar["attr"], strVar["val"]})); + if (pc.show(strVar["attr"], strVar["val"]) > -1) { + reply(format(GlobalMsg["strProp"], { strVar["pc"], strVar["attr"], strVar["val"] })); } - else - { - reply(GlobalMsg["strPropNotFound"], {strVar["attr"]}); + else { + reply(GlobalMsg["strPropNotFound"], { strVar["attr"] }); } return 1; } @@ -3603,44 +3291,40 @@ int FromMsg::DiceReply() bool isDetail = false; bool isModify = false; //循环录入 - while (intMsgCnt != strLowerMessage.length()) - { + while (intMsgCnt != strLowerMessage.length()) { //读取属性名 readSkipSpace(); - if (strMsg[intMsgCnt] == '&') - { + if (strMsg[intMsgCnt] == '&') { intMsgCnt++; - strVar["attr"] = readToColon(); - if (pc.setExp(strVar["attr"], readExp())) - { + strVar["attr"] = readToColon(); + if (strVar["attr"].empty()) { + continue; + } + if (pc.setExp(strVar["attr"], readExp())) { reply(GlobalMsg["strPcTextTooLong"]); return 1; } continue; } string strSkillName = readAttrName(); - if (strSkillName.empty()) - { - strVar["val"] = readDigit(); + if (strSkillName.empty()) { + strVar["val"] = readDigit(false); continue; } - if (pc.pTemplet->sInfoList.count(strSkillName)) - { + strSkillName = pc.standard(strSkillName); + if (pc.pTemplet->sInfoList.count(strSkillName)) { while (isspace(static_cast(strLowerMessage[intMsgCnt])) || strLowerMessage[intMsgCnt] == - '=' || strLowerMessage[intMsgCnt] == ':')intMsgCnt++; - if (pc.setInfo(strSkillName, readUntilTab())) - { + '=' || strLowerMessage[intMsgCnt] == ':')intMsgCnt++; + if (pc.setInfo(strSkillName, readUntilTab())) { reply(GlobalMsg["strPcTextTooLong"]); return 1; } continue; } - if (strSkillName == "note") - { + if (strSkillName == "note") { while (isspace(static_cast(strLowerMessage[intMsgCnt])) || strLowerMessage[intMsgCnt] == - '=' || strLowerMessage[intMsgCnt] == ':')intMsgCnt++; - if (pc.setNote(readRest())) - { + '=' || strLowerMessage[intMsgCnt] == ':')intMsgCnt++; + if (pc.setNote(readRest())) { reply(GlobalMsg["strPcNoteTooLong"]); return 1; } @@ -3648,36 +3332,31 @@ int FromMsg::DiceReply() } //读取属性值 readSkipSpace(); - if ((strLowerMessage[intMsgCnt] == '-' || strLowerMessage[intMsgCnt] == '+')) - { + if ((strLowerMessage[intMsgCnt] == '-' || strLowerMessage[intMsgCnt] == '+')) { isDetail = true; isModify = true; short& nVal = pc[strSkillName]; RD Mod((nVal == 0 ? "" : to_string(nVal)) + readDice()); - if (Mod.Roll()) - { + if (Mod.Roll()) { reply(GlobalMsg["strValueErr"]); return 1; } strReply += "\n" + strSkillName + ":" + Mod.FormCompleteString(); - if (Mod.intTotal < -32767) - { + if (Mod.intTotal < -32767) { strReply += "→-32767"; nVal = -32767; } - else if (Mod.intTotal > 32767) - { + else if (Mod.intTotal > 32767) { strReply += "→32767"; nVal = 32767; } else nVal = Mod.intTotal; while (isspace(static_cast(strLowerMessage[intMsgCnt])) || strLowerMessage[intMsgCnt] == - '|')intMsgCnt++; + '|')intMsgCnt++; continue; } string strSkillVal = readDigit(); - if (strSkillName.empty() || strSkillVal.empty() || strSkillVal.length() > 5) - { + if (strSkillName.empty() || strSkillVal.empty() || strSkillVal.length() > 5) { boolError = true; break; } @@ -3687,144 +3366,120 @@ int FromMsg::DiceReply() while (isspace(static_cast(strLowerMessage[intMsgCnt])) || strLowerMessage[intMsgCnt] == '|') intMsgCnt++; } - if (boolError) - { + if (boolError) { reply(GlobalMsg["strPropErr"]); } - else if (isModify) - { - reply(format(GlobalMsg["strStModify"], {strVar["pc"]}) + strReply); + else if (isModify) { + reply(format(GlobalMsg["strStModify"], { strVar["pc"] }) + strReply); } - else - { + else { reply(GlobalMsg["strSetPropSuccess"]); } return 1; } - else if (strLowerMessage.substr(intMsgCnt, 2) == "ti") - { - string strAns = strVar["pc"] + "的疯狂发作-临时症状:\n"; + else if (strLowerMessage.substr(intMsgCnt, 2) == "ti") { + string strAns = "{pc}的疯狂发作-临时症状:\n"; TempInsane(strAns); reply(strAns); return 1; } - else if (strLowerMessage[intMsgCnt] == 'w') - { + else if (strLowerMessage[intMsgCnt] == 'w') { intMsgCnt++; bool boolDetail = false; - if (strLowerMessage[intMsgCnt] == 'w') - { + if (strLowerMessage[intMsgCnt] == 'w') { intMsgCnt++; boolDetail = true; } bool isHidden = false; - if (strLowerMessage[intMsgCnt] == 'h') - { + if (strLowerMessage[intMsgCnt] == 'h') { isHidden = true; intMsgCnt += 1; } - if (intT == 0)isHidden = false; + strVar["nick"] = getName(fromQQ, fromGroup); + getPCName(*this); + if (!fromGroup)isHidden = false; string strMainDice = readDice(); readSkipSpace(); strVar["reason"] = strMsg.substr(intMsgCnt); int intTurnCnt = 1; const int intDefaultDice = get(getUser(fromQQ).intConf, string("默认骰"), 100); - if (strMainDice.find('#') != string::npos) - { + if (strMainDice.find('#') != string::npos) { string strTurnCnt = strMainDice.substr(0, strMainDice.find('#')); if (strTurnCnt.empty()) strTurnCnt = "1"; strMainDice = strMainDice.substr(strMainDice.find('#') + 1); RD rdTurnCnt(strTurnCnt, intDefaultDice); const int intRdTurnCntRes = rdTurnCnt.Roll(); - if (intRdTurnCntRes != 0) - { - if (intRdTurnCntRes == Value_Err) - { + if (intRdTurnCntRes != 0) { + if (intRdTurnCntRes == Value_Err) { reply(GlobalMsg["strValueErr"]); return 1; } - if (intRdTurnCntRes == Input_Err) - { + if (intRdTurnCntRes == Input_Err) { reply(GlobalMsg["strInputErr"]); return 1; } - if (intRdTurnCntRes == ZeroDice_Err) - { + if (intRdTurnCntRes == ZeroDice_Err) { reply(GlobalMsg["strZeroDiceErr"]); return 1; } - if (intRdTurnCntRes == ZeroType_Err) - { + if (intRdTurnCntRes == ZeroType_Err) { reply(GlobalMsg["strZeroTypeErr"]); return 1; } - if (intRdTurnCntRes == DiceTooBig_Err) - { + if (intRdTurnCntRes == DiceTooBig_Err) { reply(GlobalMsg["strDiceTooBigErr"]); return 1; } - if (intRdTurnCntRes == TypeTooBig_Err) - { + if (intRdTurnCntRes == TypeTooBig_Err) { reply(GlobalMsg["strTypeTooBigErr"]); return 1; } - if (intRdTurnCntRes == AddDiceVal_Err) - { + if (intRdTurnCntRes == AddDiceVal_Err) { reply(GlobalMsg["strAddDiceValErr"]); return 1; } reply(GlobalMsg["strUnknownErr"]); return 1; } - if (rdTurnCnt.intTotal > 10) - { + if (rdTurnCnt.intTotal > 10) { reply(GlobalMsg["strRollTimeExceeded"]); return 1; } - if (rdTurnCnt.intTotal <= 0) - { + if (rdTurnCnt.intTotal <= 0) { reply(GlobalMsg["strRollTimeErr"]); return 1; } intTurnCnt = rdTurnCnt.intTotal; - if (strTurnCnt.find('d') != string::npos) - { + if (strTurnCnt.find('d') != string::npos) { string strTurnNotice = strVar["pc"] + "的掷骰轮数: " + rdTurnCnt.FormShortString() + "轮"; - if (!isHidden || intT == PrivateT) - { + if (!isHidden) { reply(strTurnNotice); } - else - { + else { strTurnNotice = "在" + printChat(fromChat) + "中 " + strTurnNotice; AddMsgToQueue(strTurnNotice, fromQQ, msgtype::Private); - for (auto qq : gm->session(fromGroup).get_ob()) - { - if (qq != fromQQ) - { + for (auto qq : gm->session(fromSession).get_ob()) { + if (qq != fromQQ) { AddMsgToQueue(strTurnNotice, qq, msgtype::Private); } } } } } - if (strMainDice.empty()) - { + if (strMainDice.empty()) { reply(GlobalMsg["strEmptyWWDiceErr"]); return 1; } string strFirstDice = strMainDice.substr(0, strMainDice.find('+') < strMainDice.find('-') - ? strMainDice.find('+') - : strMainDice.find('-')); + ? strMainDice.find('+') + : strMainDice.find('-')); strFirstDice = strFirstDice.substr(0, strFirstDice.find('x') < strFirstDice.find('*') - ? strFirstDice.find('x') - : strFirstDice.find('*')); + ? strFirstDice.find('x') + : strFirstDice.find('*')); bool boolAdda10 = true; - for (auto i : strFirstDice) - { - if (!isdigit(static_cast(i))) - { + for (auto i : strFirstDice) { + if (!isdigit(static_cast(i))) { boolAdda10 = false; break; } @@ -3834,54 +3489,44 @@ int FromMsg::DiceReply() RD rdMainDice(strMainDice, intDefaultDice); const int intFirstTimeRes = rdMainDice.Roll(); - if (intFirstTimeRes != 0) - { - if (intFirstTimeRes == Value_Err) - { + if (intFirstTimeRes != 0) { + if (intFirstTimeRes == Value_Err) { reply(GlobalMsg["strValueErr"]); return 1; } - if (intFirstTimeRes == Input_Err) - { + if (intFirstTimeRes == Input_Err) { reply(GlobalMsg["strInputErr"]); return 1; } - if (intFirstTimeRes == ZeroDice_Err) - { + if (intFirstTimeRes == ZeroDice_Err) { reply(GlobalMsg["strZeroDiceErr"]); return 1; } - if (intFirstTimeRes == ZeroType_Err) - { + if (intFirstTimeRes == ZeroType_Err) { reply(GlobalMsg["strZeroTypeErr"]); return 1; } - if (intFirstTimeRes == DiceTooBig_Err) - { + if (intFirstTimeRes == DiceTooBig_Err) { reply(GlobalMsg["strDiceTooBigErr"]); return 1; } - if (intFirstTimeRes == TypeTooBig_Err) - { + if (intFirstTimeRes == TypeTooBig_Err) { reply(GlobalMsg["strTypeTooBigErr"]); return 1; } - if (intFirstTimeRes == AddDiceVal_Err) - { + if (intFirstTimeRes == AddDiceVal_Err) { reply(GlobalMsg["strAddDiceValErr"]); return 1; } reply(GlobalMsg["strUnknownErr"]); return 1; } - if (!boolDetail && intTurnCnt != 1) - { + if (!boolDetail && intTurnCnt != 1) { if (strVar["reason"].empty())strReply = GlobalMsg["strRollMuiltDice"]; else strReply = GlobalMsg["strRollMuiltDiceReason"]; vector vintExVal; strVar["res"] = "{ "; - while (intTurnCnt--) - { + while (intTurnCnt--) { // 此处返回值无用 // ReSharper disable once CppExpressionWithoutSideEffects rdMainDice.Roll(); @@ -3889,107 +3534,95 @@ int FromMsg::DiceReply() if (intTurnCnt != 0) strVar["res"] = ","; if ((rdMainDice.strDice == "D100" || rdMainDice.strDice == "1D100") && (rdMainDice.intTotal <= 5 || - rdMainDice.intTotal >= 96)) + rdMainDice.intTotal >= 96)) vintExVal.push_back(rdMainDice.intTotal); } strVar["res"] += " }"; - if (!vintExVal.empty()) - { + if (!vintExVal.empty()) { strVar["res"] += ",极值: "; - for (auto it = vintExVal.cbegin(); it != vintExVal.cend(); ++it) - { + for (auto it = vintExVal.cbegin(); it != vintExVal.cend(); ++it) { strVar["res"] += to_string(*it); if (it != vintExVal.cend() - 1)strVar["res"] += ","; } } - if (!isHidden || intT == PrivateT) - { + if (!isHidden) { reply(); } - else - { + else { strReply = format(strReply, GlobalMsg, strVar); strReply = "在" + printChat(fromChat) + "中 " + strReply; AddMsgToQueue(strReply, fromQQ, msgtype::Private); - for (auto qq : gm->session(fromGroup).get_ob()) - { - if (qq != fromQQ) - { + for (auto qq : gm->session(fromSession).get_ob()) { + if (qq != fromQQ) { AddMsgToQueue(strReply, qq, msgtype::Private); } } } } - else - { - while (intTurnCnt--) - { + else { + while (intTurnCnt--) { // 此处返回值无用 // ReSharper disable once CppExpressionWithoutSideEffects rdMainDice.Roll(); strVar["res"] = boolDetail ? rdMainDice.FormCompleteString() : rdMainDice.FormShortString(); if (strVar["reason"].empty()) - strReply = format(GlobalMsg["strRollDice"], {strVar["pc"], strVar["res"]}); - else strReply = format(GlobalMsg["strRollDiceReason"], {strVar["pc"], strVar["res"], strVar["reason"]}); - if (!isHidden || intT == PrivateT) - { + strReply = format(GlobalMsg["strRollDice"], { strVar["pc"], strVar["res"] }); + else strReply = format(GlobalMsg["strRollDiceReason"], { strVar["pc"], strVar["res"], strVar["reason"] }); + if (!isHidden) { reply(); } - else - { + else { strReply = format(strReply, GlobalMsg, strVar); strReply = "在" + printChat(fromChat) + "中 " + strReply; AddMsgToQueue(strReply, fromQQ, msgtype::Private); - for (auto qq : gm->session(fromGroup).get_ob()) - { - if (qq != fromQQ) - { + for (auto qq : gm->session(fromSession).get_ob()) { + if (qq != fromQQ) { AddMsgToQueue(strReply, qq, msgtype::Private); } } } } } - if (isHidden) - { - reply(GlobalMsg["strRollHidden"], {strVar["pc"]}); + if (isHidden) { + reply(GlobalMsg["strRollHidden"], { strVar["pc"] }); } return 1; } - else if (strLowerMessage[intMsgCnt] == 'r' || strLowerMessage[intMsgCnt] == 'h') - { + else if (strLowerMessage[intMsgCnt] == 'r' || strLowerMessage[intMsgCnt] == 'h') { + strVar["nick"] = getName(fromQQ, fromGroup); + getPCName(*this); bool isHidden = false; if (strLowerMessage[intMsgCnt] == 'h') isHidden = true; intMsgCnt += 1; bool boolDetail = true; - if (strMsg[intMsgCnt] == 's') - { + if (strMsg[intMsgCnt] == 's') { boolDetail = false; intMsgCnt++; } - if (strLowerMessage[intMsgCnt] == 'h') - { + if (strLowerMessage[intMsgCnt] == 'h') { isHidden = true; intMsgCnt += 1; } - if (intT == 0)isHidden = false; + if (!fromGroup)isHidden = false; while (isspace(static_cast(strMsg[intMsgCnt]))) intMsgCnt++; string strMainDice; strVar["reason"] = strMsg.substr(intMsgCnt); - if (PList.count(fromQQ) && getPlayer(fromQQ)[fromGroup].countExp(strMsg.substr(intMsgCnt))) - { + if (strVar["reason"].empty()) { + string key{ "__DefaultDiceExp" }; + if (PList.count(fromQQ) && getPlayer(fromQQ)[fromGroup].countExp(strVar[key])) { + strMainDice = getPlayer(fromQQ)[fromGroup].getExp(key); + } + } + if (PList.count(fromQQ) && getPlayer(fromQQ)[fromGroup].countExp(strVar["reason"])) { strMainDice = getPlayer(fromQQ)[fromGroup].getExp(strVar["reason"]); } - else - { + else { strMainDice = readDice(); bool isExp = false; - for (auto ch : strMainDice) - { - if (!isdigit(ch)) - { + for (auto ch : strMainDice) { + if (!isdigit(ch)) { isExp = true; break; } @@ -3998,17 +3631,17 @@ int FromMsg::DiceReply() else strMainDice.clear(); } int intTurnCnt = 1; - const int intDefaultDice = get(getUser(fromQQ).intConf, string("默认骰"), 100); - if (strMainDice.find('#') != string::npos) - { + const int intDefaultDice = (PList.count(fromQQ) && PList[fromQQ][fromGroup].count("__DefaultDice")) + ? PList[fromQQ][fromGroup]["__DefaultDice"] + : get(getUser(fromQQ).intConf, string("默认骰"), 100); + if (strMainDice.find('#') != string::npos) { strVar["turn"] = strMainDice.substr(0, strMainDice.find('#')); if (strVar["turn"].empty()) strVar["turn"] = "1"; strMainDice = strMainDice.substr(strMainDice.find('#') + 1); RD rdTurnCnt(strVar["turn"], intDefaultDice); const int intRdTurnCntRes = rdTurnCnt.Roll(); - switch (intRdTurnCntRes) - { + switch (intRdTurnCntRes) { case 0: break; case Value_Err: reply(GlobalMsg["strValueErr"]); @@ -4035,47 +3668,32 @@ int FromMsg::DiceReply() reply(GlobalMsg["strUnknownErr"]); return 1; } - if (rdTurnCnt.intTotal > 10) - { + if (rdTurnCnt.intTotal > 10) { reply(GlobalMsg["strRollTimeExceeded"]); return 1; } - if (rdTurnCnt.intTotal <= 0) - { + if (rdTurnCnt.intTotal <= 0) { reply(GlobalMsg["strRollTimeErr"]); return 1; } intTurnCnt = rdTurnCnt.intTotal; - if (strVar["turn"].find('d') != string::npos) - { + if (strVar["turn"].find('d') != string::npos) { strVar["turn"] = rdTurnCnt.FormShortString(); - if (!isHidden) - { - reply(GlobalMsg["strRollTurn"], {strVar["pc"], strVar["turn"]}); + if (!isHidden) { + reply(GlobalMsg["strRollTurn"], { strVar["pc"], strVar["turn"] }); } - else - { - strReply = format("在" + printChat(fromChat) + "中 " + GlobalMsg["strRollTurn"], GlobalMsg, strVar); - AddMsgToQueue(strReply, fromQQ, msgtype::Private); - for (auto qq : gm->session(fromGroup).get_ob()) - { - if (qq != fromQQ) - { - AddMsgToQueue(strReply, qq, msgtype::Private); - } - } + else { + replyHidden(GlobalMsg["strRollTurn"]); } } } - if (strMainDice.empty() && PList.count(fromQQ) && getPlayer(fromQQ)[fromGroup].countExp(strVar["reason"])) - { + if (strMainDice.empty() && PList.count(fromQQ) && getPlayer(fromQQ)[fromGroup].countExp(strVar["reason"])) { strMainDice = getPlayer(fromQQ)[fromGroup].getExp(strVar["reason"]); } RD rdMainDice(strMainDice, intDefaultDice); const int intFirstTimeRes = rdMainDice.Roll(); - switch (intFirstTimeRes) - { + switch (intFirstTimeRes) { case 0: break; case Value_Err: reply(GlobalMsg["strValueErr"]); @@ -4104,15 +3722,13 @@ int FromMsg::DiceReply() } strVar["dice_exp"] = rdMainDice.strDice; string strType = (intTurnCnt != 1 - ? (strVar["reason"].empty() ? "strRollMultiDice" : "strRollMultiDiceReason") - : (strVar["reason"].empty() ? "strRollDice" : "strRollDiceReason")); - if (!boolDetail && intTurnCnt != 1) - { + ? (strVar["reason"].empty() ? "strRollMultiDice" : "strRollMultiDiceReason") + : (strVar["reason"].empty() ? "strRollDice" : "strRollDiceReason")); + if (!boolDetail && intTurnCnt != 1) { strReply = GlobalMsg[strType]; vector vintExVal; strVar["res"] = "{ "; - while (intTurnCnt--) - { + while (intTurnCnt--) { // 此处返回值无用 // ReSharper disable once CppExpressionWithoutSideEffects rdMainDice.Roll(); @@ -4120,49 +3736,32 @@ int FromMsg::DiceReply() if (intTurnCnt != 0) strVar["res"] += ","; if ((rdMainDice.strDice == "D100" || rdMainDice.strDice == "1D100") && (rdMainDice.intTotal <= 5 || - rdMainDice.intTotal >= 96)) + rdMainDice.intTotal >= 96)) vintExVal.push_back(rdMainDice.intTotal); } strVar["res"] += " }"; - if (!vintExVal.empty()) - { + if (!vintExVal.empty()) { strVar["res"] += ",极值: "; - for (auto it = vintExVal.cbegin(); it != vintExVal.cend(); ++it) - { + for (auto it = vintExVal.cbegin(); it != vintExVal.cend(); ++it) { strVar["res"] += to_string(*it); if (it != vintExVal.cend() - 1) strVar["res"] += ","; } } - if (!isHidden) - { + if (!isHidden) { reply(); } - else - { - strReply = format(strReply, GlobalMsg, strVar); - strReply = "在" + printChat(fromChat) + "中 " + strReply; - AddMsgToQueue(strReply, fromQQ, msgtype::Private); - for (auto qq : gm->session(fromGroup).get_ob()) - { - if (qq != fromQQ) - { - AddMsgToQueue(strReply, qq, msgtype::Private); - } - } + else { + replyHidden(strReply); } } - else - { + else { ResList dices; - if (intTurnCnt > 1) - { - while (intTurnCnt--) - { + if (intTurnCnt > 1) { + while (intTurnCnt--) { rdMainDice.Roll(); string strForm = to_string(rdMainDice.intTotal); - if (boolDetail) - { + if (boolDetail) { string strCombined = rdMainDice.FormStringCombined(); string strSeparate = rdMainDice.FormStringSeparate(); if (strCombined != strForm)strForm = strCombined + "=" + strForm; @@ -4174,28 +3773,16 @@ int FromMsg::DiceReply() strVar["res"] = dices.dot(", ").line(7).show(); } else strVar["res"] = boolDetail ? rdMainDice.FormCompleteString() : rdMainDice.FormShortString(); - strReply = format(GlobalMsg[strType], {strVar["pc"], strVar["res"], strVar["reason"]}); - if (!isHidden) - { + strReply = format(GlobalMsg[strType], { strVar["pc"], strVar["res"], strVar["reason"] }); + if (!isHidden) { reply(); } - else - { - strReply = format(strReply, GlobalMsg, strVar); - strReply = "在" + printChat(fromChat) + "中 " + strReply; - AddMsgToQueue(strReply, fromQQ, msgtype::Private); - for (auto qq : gm->session(fromGroup).get_ob()) - { - if (qq != fromQQ) - { - AddMsgToQueue(strReply, qq, msgtype::Private); - } - } + else { + replyHidden(strReply); } } - if (isHidden) - { - reply(GlobalMsg["strRollHidden"], {strVar["pc"]}); + if (isHidden) { + reply(GlobalMsg["strRollHidden"], { strVar["pc"] }); } return 1; } @@ -4206,16 +3793,14 @@ int FromMsg::CustomReply() { const string strKey = readRest(); if (auto deck = CardDeck::mReplyDeck.find(strKey); deck != CardDeck::mReplyDeck.end() - || (!isBotOff && (deck = CardDeck::mReplyDeck.find(strMsg)) != CardDeck::mReplyDeck.end())) + || (deck = CardDeck::mReplyDeck.find(strMsg)) != CardDeck::mReplyDeck.end()) { - if (strVar.empty()) - { - strVar["nick"] = getName(fromQQ, fromGroup); - strVar["pc"] = getPCName(fromQQ, fromGroup); - strVar["at"] = fromType != msgtype::Private ? "[CQ:at,qq=" + to_string(fromQQ) + "]" : strVar["nick"]; - } - reply(CardDeck::drawCard(deck->second, true)); - AddFrq(fromQQ, fromTime, fromChat); + string strAns(CardDeck::drawCard(deck->second, true)); + if (fromQQ == console.DiceMaid && strAns == strKey)return 0; + reply(strAns); + if (!isVirtual)AddFrq(fromQQ, fromTime, fromChat, strMsg); + else + AddFrq(0, fromTime, fromChat, strMsg); return 1; } return 0; @@ -4228,7 +3813,7 @@ bool FromMsg::DiceFilter() strMsg.erase(strMsg.begin()); init(strMsg); bool isOtherCalled = false; - string strAt = CQ_AT + to_string(getLoginQQ()) + "]"; + string strAt = CQ_AT + to_string(DD::getLoginQQ()) + "]"; while (strMsg.find(CQ_AT) == 0) { if (strMsg.find(strAt) == 0) @@ -4249,29 +3834,112 @@ bool FromMsg::DiceFilter() while (isspace(static_cast(strMsg[0]))) strMsg.erase(strMsg.begin()); } - if (isOtherCalled && !isCalled)return false; init2(strMsg); - if (fromType == msgtype::Private) isCalled = true; + strLowerMessage = strMsg; + std::transform(strLowerMessage.begin(), strLowerMessage.end(), strLowerMessage.begin(), + [](unsigned char c) { return tolower(c); }); trusted = trustedQQ(fromQQ); - isBotOff = (console["DisabledGlobal"] && (trusted < 4 || !isCalled)) || (!(isCalled && console["DisabledListenAt"]) - && (groupset(fromGroup, "停用指令") > 0)); - if (DiceReply()) + fwdMsg(); + if (isOtherCalled && !isCalled)return false; + if (fromChat.second == msgtype::Private) isCalled = true; + isDisabled = ((console["DisabledGlobal"] && trusted < 4) || groupset(fromGroup, "协议无效") > 0); + if (BasicOrder()) { - AddFrq(fromQQ, fromTime, fromChat); - if (isAns)getUser(fromQQ).update(fromTime); - if (fromType != msgtype::Private)chat(fromGroup).update(fromTime); - return true; + if (isAns) { + if (!isVirtual) { + AddFrq(fromQQ, fromTime, fromChat, strMsg); + getUser(fromQQ).update(fromTime); + if (fromChat.second != msgtype::Private)chat(fromGroup).update(fromTime); + } + else { + AddFrq(0, fromTime, fromChat, strMsg); + } + } + return 1; + } + if (fromChat.second == msgtype::Group && ((console["CheckGroupLicense"] > 0 && pGrp->isset("未审核")) + || (console["CheckGroupLicense"] == 2 && !pGrp->isset("许可使用")) + || blacklist->get_group_danger(fromGroup))) { + isDisabled = true; + } + if (blacklist->get_qq_danger(fromQQ))isDisabled = true; + if (!isDisabled && (isCalled || !pGrp->isset("停用指令"))) { + if (fmt->listen_order(this) || InnerOrder()) { + if (!isVirtual) { + AddFrq(fromQQ, fromTime, fromChat, strMsg); + getUser(fromQQ).update(fromTime); + if (fromChat.second != msgtype::Private)chat(fromGroup).update(fromTime); + } + else { + AddFrq(0, fromTime, fromChat, strMsg); + } + return true; + } + } + if (!isDisabled && (isCalled || !pGrp->isset("禁用回复")) && CustomReply())return true; + if (isDisabled)return console["DisabledBlock"]; + return false; +} +bool FromMsg::WordCensor() { + //信任小于4的用户进行敏感词检测 + if (trusted < 4) { + unordered_setsens_words; + switch (int danger = censor.search(strMsg, sens_words) - 1) { + case 3: + if (trusted < danger++) { + console.log("警告:" + printQQ(fromQQ) + "对" + GlobalMsg["strSelfName"] + "发送了含敏感词指令:\n" + strMsg, 0b1000, + printTTime(fromTime)); + reply(GlobalMsg["strCensorDanger"]); + return 1; + } + case 2: + if (trusted < danger++) { + console.log("警告:" + printQQ(fromQQ) + "对" + GlobalMsg["strSelfName"] + "发送了含敏感词指令:\n" + strMsg, 0b10, + printTTime(fromTime)); + reply(GlobalMsg["strCensorWarning"]); + break; + } + case 1: + if (trusted < danger++) { + console.log("提醒:" + printQQ(fromQQ) + "对" + GlobalMsg["strSelfName"] + "发送了含敏感词指令:\n" + strMsg, 0b10, + printTTime(fromTime)); + reply(GlobalMsg["strCensorCaution"]); + break; + } + case 0: + console.log("提醒:" + printQQ(fromQQ) + "对" + GlobalMsg["strSelfName"] + "发送了含敏感词指令:\n" + strMsg, 1, + printTTime(fromTime)); + break; + default: + break; + } } - if (groupset(fromGroup, "禁用回复") < 1 && CustomReply())return true; - if (isBotOff)return console["DisabledBlock"]; return false; } +void FromMsg::operator()() { + isVirtual = true; + isCalled = true; + DiceFilter(); + delete this; +} + +int FromMsg::getGroupAuth(long long group) { + if (trusted > 0)return trusted; + if (ChatList.count(group)) { + return DD::isGroupAdmin(group, fromQQ, true) ? 0 : -1; + } + return -2; +} +void FromMsg::readSkipColon() { + readSkipSpace(); + while (intMsgCnt < strMsg.length() && (strMsg[intMsgCnt] == ':' || strMsg[intMsgCnt] == '='))intMsgCnt++; +} + int FromMsg::readNum(int& num) { string strNum; - while (intMsgCnt < strMsg.length() && !isdigit(static_cast(strMsg[intMsgCnt])) && strMsg[intMsgCnt] - != '-')intMsgCnt++; + while (intMsgCnt < strMsg.length() && !isdigit(static_cast(strMsg[intMsgCnt])) && strMsg[intMsgCnt] != '-')intMsgCnt++; if (strMsg[intMsgCnt] == '-') { strNum += '-'; @@ -4284,6 +3952,7 @@ int FromMsg::readNum(int& num) intMsgCnt++; } if (strNum.length() > 9)return -2; + if (strNum.empty() || strNum == "-")return -3; num = stoi(strNum); return 0; } @@ -4296,30 +3965,27 @@ int FromMsg::readChat(chatType& ct, bool isReroll) ct = {fromQQ, msgtype::Private}; return 0; } + else if (strT == "this") + { + ct = fromChat; + return 0; + } + else if (strT == "qq") + { + ct.second = msgtype::Private; + } + else if (strT == "group") + { + ct.second = msgtype::Group; + } + else if (strT == "discuss") + { + ct.second = msgtype::Discuss; + } else { - if (strT == "this") - { - ct = fromChat; - return 0; - } - if (strT == "qq") - { - ct.second = msgtype::Private; - } - else if (strT == "group") - { - ct.second = msgtype::Group; - } - else if (strT == "discuss") - { - ct.second = msgtype::Discuss; - } - else - { - if (isReroll)intMsgCnt = intFormor; - return -1; - } + if (isReroll)intMsgCnt = intFormor; + return -1; } if (const long long llID = readID(); llID) { @@ -4329,3 +3995,15 @@ int FromMsg::readChat(chatType& ct, bool isReroll) if (isReroll)intMsgCnt = intFormor; return -2; } + +void FromMsg::readItems(vector& vItem) { + while (intMsgCnt != strMsg.length()) { + string strItem; + while (isspace(static_cast(strMsg[intMsgCnt])) || strMsg[intMsgCnt] == '|')intMsgCnt++; + while (strMsg[intMsgCnt] != '|' && intMsgCnt != strMsg.length()) { + strItem += strMsg[intMsgCnt]; + intMsgCnt++; + } + vItem.push_back(strItem); + } +} \ No newline at end of file diff --git a/Dice/DiceEvent.h b/Dice/DiceEvent.h index 71724f49..8b315273 100644 --- a/Dice/DiceEvent.h +++ b/Dice/DiceEvent.h @@ -1,3 +1,5 @@ +#pragma once + /* * 消息处理 * Copyright (C) 2019 String.Empty @@ -7,93 +9,56 @@ #include #include #include -#include "CQAPI_EX.h" +#include #include "MsgMonitor.h" +#include "DiceSchedule.h" #include "DiceMsgSend.h" #include "GlobalVar.h" using std::string; //打包待处理消息 -class FromMsg -{ +class FromMsg : public DiceJobDetail { public: - std::string strMsg; string strLowerMessage; - long long fromID = 0; - CQ::msgtype fromType = CQ::msgtype::Private; - long long fromQQ = 0; long long fromGroup = 0; + long long fromSession; Chat* pGrp = nullptr; - chatType fromChat; - time_t fromTime = time(nullptr); string strReply; - //临时变量库 - map strVar = {}; - - FromMsg(std::string message, long long fromNum) : strMsg(std::move(message)), fromID(fromNum), fromQQ(fromNum) - { - fromChat = {fromID, CQ::msgtype::Private}; + FromMsg(std::string message, long long qq) :DiceJobDetail(qq, { qq,msgtype::Private }, message){ + fromSession = ~fromQQ; } - - FromMsg(std::string message, long long fromGroup, CQ::msgtype msgType, long long fromNum) : strMsg(std::move(message)), - fromID(fromGroup), - fromType(msgType), - fromQQ(fromNum), - fromGroup(fromGroup), - fromChat({ - fromGroup, - fromType - }) - { + FromMsg(std::string message, long long fromGroup, msgtype msgType, long long qq) :DiceJobDetail(qq, { fromGroup,msgType }, message), fromGroup(fromGroup), fromSession(fromGroup){ pGrp = &chat(fromGroup); } bool isBlock = false; - void reply(const std::string& strReply, bool isFormat) - { - isAns = true; - if (isFormat) - AddMsgToQueue(format(strReply, GlobalMsg, strVar), fromID, fromType); - else AddMsgToQueue(strReply, fromID, fromType); - } + void formatReply(); - void reply(const std::string& strReply, const std::initializer_list replace_str = {}, - bool isFormat = true) - { - isAns = true; - if (!isFormat) - { - AddMsgToQueue(strReply, fromID, fromType); - return; - } - int index = 0; - for (const auto& s : replace_str) - { - strVar[to_string(index++)] = s; - } - AddMsgToQueue(format(strReply, GlobalMsg, strVar), fromID, fromType); - } + void reply(const std::string& strReply, bool isFormat = true)override; - void reply() - { - reply(strReply); - } + FromMsg& initVar(const std::initializer_list& replace_str); + void reply(const std::string& strReply, const std::initializer_list& replace_str); + void replyHidden(const std::string& strReply); + + void reply(bool isFormat = true); + + void replyHidden(); //通知 void note(std::string strMsg, int note_lv = 0b1) { strMsg = format(strMsg, GlobalMsg, strVar); - ofstream fout(string(DiceDir + "\\audit\\log") + to_string(console.DiceMaid) + "_" + printDate() + ".txt", + ofstream fout(DiceDir / "audit" / ("log" + to_string(console.DiceMaid) + "_" + printDate() + ".txt"), ios::out | ios::app); fout << printSTNow() << "\t" << note_lv << "\t" << printLine(strMsg) << std::endl; fout.close(); reply(strMsg); const string note = getName(fromQQ) + strMsg; - for (const auto& [ct,level] : console.NoticeList) + for (const auto& [ct,level] : console.NoticeList) { - if (!(level & note_lv) || pair(fromQQ, CQ::msgtype::Private) == ct || ct == fromChat)continue; + if (!(level & note_lv) || pair(fromQQ, msgtype::Private) == ct || ct == fromChat)continue; AddMsgToQueue(note, ct); } } @@ -102,60 +67,49 @@ class FromMsg std::string printFrom() { std::string strFwd; - if (fromType == CQ::msgtype::Group)strFwd += "[群:" + to_string(fromGroup) + "]"; - if (fromType == CQ::msgtype::Discuss)strFwd += "[讨论组:" + to_string(fromGroup) + "]"; + if (fromChat.second == msgtype::Group)strFwd += "[群:" + to_string(fromGroup) + "]"; + if (fromChat.second == msgtype::Discuss)strFwd += "[讨论组:" + to_string(fromGroup) + "]"; strFwd += getName(fromQQ, fromGroup) + "(" + to_string(fromQQ) + "):"; return strFwd; } //转发消息 - void FwdMsg(const string& message); + void fwdMsg(); int AdminEvent(const string& strOption); int MasterSet(); - int DiceReply(); + int BasicOrder(); + int InnerOrder(); + //int CustomOrder(); int CustomReply(); //判断是否响应 bool DiceFilter(); + bool WordCensor(); + void operator()(); short trusted = 0; private: + bool isVirtual = false; //是否响应 bool isAns = false; - unsigned int intMsgCnt = 0; - bool isBotOff = false; + bool isDisabled = false; bool isCalled = false; bool isAuth = false; - bool isLinkOrder = false; - - short getGroupAuth(long long group = 0) - { - if (trusted > 0)return trusted; - if (ChatList.count(group)) - { - const int per = CQ::getGroupMemberInfo(group, fromQQ).permissions; - if (per > 1)return 0; - if (per)return -1; - } - return -2; - } + int getGroupAuth(long long group = 0); +public: + unsigned int intMsgCnt = 0; //跳过空格 void readSkipSpace() { - while (intMsgCnt < strMsg.length() && isspace(static_cast(strLowerMessage[intMsgCnt])))intMsgCnt - ++; + while (intMsgCnt < strMsg.length() && isspace(static_cast(strLowerMessage[intMsgCnt])))intMsgCnt++; } - void readSkipColon() - { - readSkipSpace(); - while (intMsgCnt < strMsg.length() && strMsg[intMsgCnt] == ':')intMsgCnt++; - } + void readSkipColon(); string readUntilSpace() { string strPara; - readSkipSpace(); + readSkipSpace(); while (intMsgCnt < strMsg.length() && !isspace(static_cast(strLowerMessage[intMsgCnt]))) { strPara += strMsg[intMsgCnt]; @@ -191,15 +145,12 @@ class FromMsg string readPara() { string strPara; - while (intMsgCnt < strMsg.length() && isspace(static_cast(strLowerMessage[intMsgCnt])))intMsgCnt - ++; - while (intMsgCnt < strMsg.length() && !isspace(static_cast(strLowerMessage[intMsgCnt])) && ! - isdigit(static_cast(strLowerMessage[intMsgCnt])) - && (strLowerMessage[intMsgCnt] != '-') && (strLowerMessage[intMsgCnt] != '+') && (strLowerMessage[intMsgCnt] - != '[') && (strLowerMessage[intMsgCnt] != ']') && (strLowerMessage[intMsgCnt] != '=') && ( - strLowerMessage[intMsgCnt] != ':') - && intMsgCnt != strLowerMessage.length()) - { + while (intMsgCnt < strMsg.length() && isspace(static_cast(strLowerMessage[intMsgCnt])))intMsgCnt++; + while (intMsgCnt < strMsg.length() && !isspace(static_cast(strLowerMessage[intMsgCnt])) && !isdigit(static_cast(strLowerMessage[intMsgCnt])) + && (strLowerMessage[intMsgCnt] != '-') && (strLowerMessage[intMsgCnt] != '+') + && (strLowerMessage[intMsgCnt] != '[') && (strLowerMessage[intMsgCnt] != ']') + && (strLowerMessage[intMsgCnt] != '=') && (strLowerMessage[intMsgCnt] != ':') + && intMsgCnt != strLowerMessage.length()) { strPara += strLowerMessage[intMsgCnt]; intMsgCnt++; } @@ -210,13 +161,12 @@ class FromMsg string readDigit(bool isForce = true) { string strMum; - if (isForce) - while (intMsgCnt < strMsg.length() && !isdigit(static_cast(strMsg[intMsgCnt]))) - { - if (strMsg[intMsgCnt] < 0)intMsgCnt++; - intMsgCnt++; - } - else while (intMsgCnt < strMsg.length() && isspace(static_cast(strMsg[intMsgCnt])))intMsgCnt++; + if (isForce)while (intMsgCnt < strMsg.length() && !isdigit(static_cast(strMsg[intMsgCnt]))) + { + if (strMsg[intMsgCnt] < 0)intMsgCnt++; + intMsgCnt++; + } + else while(intMsgCnt < strMsg.length() && isspace(static_cast(strMsg[intMsgCnt])))intMsgCnt++; while (intMsgCnt < strMsg.length() && isdigit(static_cast(strMsg[intMsgCnt]))) { strMum += strMsg[intMsgCnt]; @@ -392,6 +342,7 @@ class FromMsg } return strMum; } + void readItems(vector&); }; #endif /*DICE_EVENT*/ diff --git a/Dice/DiceFile.cpp b/Dice/DiceFile.cpp index 1ea800f4..88ff0d2c 100644 --- a/Dice/DiceFile.cpp +++ b/Dice/DiceFile.cpp @@ -18,7 +18,7 @@ int mkDir(const std::string& dir) return -2;*/ } -int clrDir(const std::string& dir, const std::set& exceptList) +int clrDir(const std::string& dir, const std::unordered_set& exceptList) { int nCnt = 0; std::error_code err; @@ -26,7 +26,7 @@ int clrDir(const std::string& dir, const std::set& exceptList) { if (p.is_regular_file()) { - std::string path = convert_w2a(p.path().filename().wstring().c_str()); + std::string path = convert_w2a(p.path().filename().u16string().c_str()); if (path.length() >= 36 && !exceptList.count(path)) { std::error_code err2; @@ -102,7 +102,7 @@ bool fscan(std::ifstream& fin, std::string& t) return false; } -bool rdbuf(const string& strPath, string& s) +[[deprecated]] bool rdbuf(const string& strPath, string& s) { const std::ifstream fin(strPath); if (!fin)return false; @@ -112,6 +112,16 @@ bool rdbuf(const string& strPath, string& s) return true; } +bool rdbuf(const std::filesystem::path& fpPath, string& s) +{ + const std::ifstream fin(fpPath); + if (!fin)return false; + stringstream ss; + ss << fin.rdbuf(); + s = ss.str(); + return true; +} + void fprint(std::ofstream& fout, std::string s) { while (s.find(' ') != std::string::npos)s.replace(s.find(' '), 1, "{space}"); @@ -127,6 +137,31 @@ void fwrite(ofstream& fout, const std::string& s) fout.write(reinterpret_cast(&len), sizeof(short)); fout.write(s.c_str(), sizeof(char) * s.length()); } +void fwrite(ofstream& fout, const var& val) { + short idx(-1); + switch (val.index()) { + case 0: + fout.write(reinterpret_cast(&idx), sizeof(short)); + break; + case 1: { + idx = -2; + fout.write(reinterpret_cast(&idx), sizeof(short)); + int i = std::get(val); + fout.write(reinterpret_cast(&i), sizeof(int)); + break; + } + case 2: { + idx = -3; + fout.write(reinterpret_cast(&idx), sizeof(short)); + double f = std::get(val); + fout.write(reinterpret_cast(&f), sizeof(double)); + break; + } + case 3: + fwrite(fout, std::get(val)); + break; + } +} void readini(ifstream& fin, std::string& s) { @@ -141,7 +176,7 @@ void readini(ifstream& fin, std::string& s) using namespace std; -std::ifstream& operator>>(std::ifstream& fin, CQ::msgtype& t) +std::ifstream& operator>>(std::ifstream& fin, msgtype& t) { fin >> reinterpret_cast(t); return fin; @@ -151,7 +186,7 @@ std::ifstream& operator>>(std::ifstream& fin, chatType& ct) { int t; fin >> ct.first >> t; - ct.second = static_cast(t); + ct.second = static_cast(t); return fin; } @@ -187,7 +222,7 @@ ofstream& operator<<(ofstream& fout, const Chat& grp) } template -int _listDir(const string& dir, vector& files) +[[deprecated]] int _listDir(const string& dir, vector& files) { int intFile = 0; std::error_code err; @@ -202,7 +237,32 @@ int _listDir(const string& dir, vector& files) return err ? -1 : intFile; } -int listDir(const string& dir, vector& files, bool isSub) +template +int _listDir(const std::filesystem::path& dir, vector& files) +{ + int intFile = 0; + std::error_code err; + for (const auto& file : T(dir, err)) + { + if (file.is_regular_file() && file.path().filename().string().substr(0, 1) != ".") + { + intFile++; + files.push_back(file.path()); + } + } + return err ? -1 : intFile; +} + +[[deprecated]] int listDir(const string& dir, vector& files, bool isSub) +{ + if (isSub) + { + return _listDir(dir, files); + } + return _listDir(dir, files); +} + +int listDir(const std::filesystem::path& dir, vector& files, bool isSub) { if (isSub) { diff --git a/Dice/DiceFile.hpp b/Dice/DiceFile.hpp index 11153afc..9d3298e0 100644 --- a/Dice/DiceFile.hpp +++ b/Dice/DiceFile.hpp @@ -1,8 +1,10 @@ +#pragma once + /* * 文件读写 * Copyright (C) 2019-2020 String.Empty */ -#pragma once + #include #include #include @@ -10,14 +12,16 @@ #include #include #include +#include #include #include -#include -#include +#include #include #include "DiceXMLTree.h" #include "StrExtern.hpp" - +#include "DiceMsgSend.h" +#include "MsgFormat.h" +#include "EncodingConvert.h" using std::ifstream; using std::ofstream; @@ -32,7 +36,7 @@ using std::unordered_map; int mkDir(const std::string& dir); -int clrDir(const std::string& dir, const std::set& exceptList); +int clrDir(const std::string& dir, const unordered_set& exceptList); template void map_merge(map& m1, const map& m2) @@ -77,6 +81,8 @@ bool fscan(std::ifstream& fin, C& obj) return false; } +using var = std::variant; + // 读取二进制文件——基础类型重载 template std::enable_if_t, T> fread(ifstream& fin) @@ -97,6 +103,25 @@ std::enable_if_t, T> fread(ifstream& fin) delete[] buff; return s; } +template +std::enable_if_t, T> fread(ifstream& fin) { + const short len = fread(fin); + if (len >= 0) { + char* buff = new char[len]; + fin.read(buff, sizeof(char) * len); + std::string s(buff, len); + delete[] buff; + return s; + } + switch (len) { + case -1: + return fread(fin); + case -2: + return fread(fin); + default: + return {}; + } +} // 读取二进制文件——含readb函数类重载 template @@ -113,6 +138,7 @@ std::map fread(ifstream& fin) { std::map m; short len = fread(fin); + if (len < 0)return m; while (len--) { T1 key = fread(fin); @@ -121,12 +147,23 @@ std::map fread(ifstream& fin) } return m; } +template +void fread(ifstream& fin, std::map& dir) { + short len = fread(fin); + if (len < 0)return; + while (len--) { + T1 key = fread(fin); + T2 val = fread(fin); + dir[key] = val; + } +} // 读取二进制文件——std::set重载 template std::set fread(ifstream& fin) { short len = fread(fin); + if (len < 0)return {}; set s{}; while (len--) { @@ -177,7 +214,7 @@ void readini(string s, std::vector& v) } template -int loadFile(std::string strPath, std::set& setTmp) +[[deprecated]] int loadFile(const std::string& strPath, std::set& setTmp) { std::ifstream fin(strPath); if (fin) @@ -196,7 +233,26 @@ int loadFile(std::string strPath, std::set& setTmp) } template -int loadFile(std::string strPath, std::unordered_set& setTmp) +int loadFile(const std::filesystem::path& fpPath, std::set& setTmp) +{ + std::ifstream fin(fpPath); + if (fin) + { + int Cnt = 0; + T item; + while (fscan(fin, item)) + { + setTmp.insert(item); + Cnt++; + } + return Cnt; + } + fin.close(); + return -1; +} + +template +[[deprecated]] int loadFile(const std::string& strPath, std::unordered_set& setTmp) { std::ifstream fin(strPath); if (fin) @@ -214,8 +270,27 @@ int loadFile(std::string strPath, std::unordered_set& setTmp) return -1; } +template +int loadFile(const std::filesystem::path& fpPath, std::unordered_set& setTmp) +{ + std::ifstream fin(fpPath); + if (fin) + { + int Cnt = 0; + T item; + while (fscan(fin, item)) + { + setTmp.insert(item); + Cnt++; + } + return Cnt; + } + fin.close(); + return -1; +} + template -int loadFile(std::string strPath, std::map& mapTmp) +[[deprecated]] int loadFile(const std::string& strPath, std::map& mapTmp) { std::ifstream fin(strPath); if (fin) @@ -234,7 +309,26 @@ int loadFile(std::string strPath, std::map& mapTmp) } template -int loadFile(std::string strPath, std::unordered_map& mapTmp) +int loadFile(const std::filesystem::path& fpPath, std::map& mapTmp) +{ + std::ifstream fin(fpPath); + if (fin) + { + int Cnt = 0; + T1 key; + while (fin >> key) + { + fscan(fin, mapTmp[key]); + Cnt++; + } + return Cnt; + } + fin.close(); + return -1; +} + +template +[[deprecated]] int loadFile(const std::string& strPath, std::unordered_map& mapTmp) { std::ifstream fin(strPath); if (fin) @@ -253,7 +347,26 @@ int loadFile(std::string strPath, std::unordered_map& mapTmp) } template -void loadFile(std::string strPath, std::multimap& mapTmp) +int loadFile(const std::filesystem::path& fpPath, std::unordered_map& mapTmp) +{ + std::ifstream fin(fpPath); + if (fin) + { + int Cnt = 0; + T1 key; + while (fin >> key) + { + fscan(fin, mapTmp[key]); + Cnt++; + } + return Cnt; + } + fin.close(); + return -1; +} + +template +[[deprecated]] void loadFile(const std::string& strPath, std::multimap& mapTmp) { std::ifstream fin(strPath); if (fin) @@ -268,8 +381,24 @@ void loadFile(std::string strPath, std::multimap& mapTmp) fin.close(); } +template +void loadFile(const std::filesystem::path& fpPath, std::multimap& mapTmp) +{ + std::ifstream fin(fpPath); + if (fin) + { + T1 key; + T2 Val; + while (fin >> key >> Val) + { + mapTmp.insert({key, Val}); + } + } + fin.close(); +} + template -int loadBFile(std::string strPath, std::map& m) +[[deprecated]] int loadBFile(const std::string& strPath, std::map& m) { std::ifstream fin(strPath, std::ios::in | std::ios::binary); if (!fin)return -1; @@ -287,7 +416,25 @@ int loadBFile(std::string strPath, std::map& m) } template -int loadBFile(std::string strPath, std::unordered_map& m) +int loadBFile(const std::filesystem::path& fpPath, std::map& m) +{ + std::ifstream fin(fpPath, std::ios::in | std::ios::binary); + if (!fin)return -1; + const int len = fread(fin); + int Cnt = 0; + T key; + C val; + while (fin.peek() != EOF && len > Cnt++) + { + key = fread(fin); + m[key].readb(fin); + } + fin.close(); + return Cnt; +} + +template +[[deprecated]] int loadBFile(const std::string& strPath, std::unordered_map& m) { std::ifstream fin(strPath, std::ios::in | std::ios::binary); if (!fin)return -1; @@ -304,9 +451,27 @@ int loadBFile(std::string strPath, std::unordered_map& m) return Cnt; } +template +int loadBFile(const std::filesystem::path& fpPath, std::unordered_map& m) +{ + std::ifstream fin(fpPath, std::ios::in | std::ios::binary); + if (!fin)return -1; + const int len = fread(fin); + int Cnt = 0; + T key; + C val; + while (fin.peek() != EOF && len > Cnt++) + { + key = fread(fin); + m[key].readb(fin); + } + fin.close(); + return Cnt; +} + //读取伪ini template -int loadINI(std::string strPath, std::map& m) +[[deprecated]] int loadINI(const std::string& strPath, std::map& m) { std::ifstream fin(strPath, std::ios::in | std::ios::binary); if (!fin)return -1; @@ -320,11 +485,27 @@ int loadINI(std::string strPath, std::map& m) return 1; } -bool rdbuf(const string& strPath, string& s); +template +int loadINI(const std::filesystem::path& fpPath, std::map& m) +{ + std::ifstream fin(fpPath, std::ios::in | std::ios::binary); + if (!fin)return -1; + std::string s, name; + C val; + getline(fin, s); + readini(fin, name); + val.readi(fin); + m[name] = val; + fin.close(); + return 1; +} + +[[deprecated]] bool rdbuf(const string& strPath, string& s); +bool rdbuf(const std::filesystem::path& fpPath, string& s); //读取伪xml template -int loadXML(const std::string& strPath, std::map& m) +[[deprecated]] int loadXML(const std::string& strPath, std::map& m) { string s; if (!rdbuf(strPath, s))return -1; @@ -334,12 +515,24 @@ int loadXML(const std::string& strPath, std::map& m) return 1; } +template +int loadXML(const std::filesystem::path& fpPath, std::map& m) +{ + string s; + if (!rdbuf(fpPath, s))return -1; + DDOM xml(s); + C obj(xml); + m[obj.getName()].readt(xml); + return 1; +} + //遍历文件夹 -int listDir(const string& dir, vector& files, bool isSub = false); +[[deprecated]] int listDir(const string& dir, vector& files, bool isSub = false); +int listDir(const std::filesystem::path& dir, vector& files, bool isSub = false); template -int _loadDir(int (*load)(const std::string&, T2&), const std::string& strDir, T2& tmp, int& intFile, int& intFailure, - int& intItem, std::vector& files) +[[deprecated]] int _loadDir(int (*load)(const std::string&, T2&), const std::string& strDir, T2& tmp, int& intFile, int& intFailure, + int& intItem, std::vector& failureFiles) { std::error_code err; for (const auto& p : T1(strDir, err)) @@ -347,11 +540,34 @@ int _loadDir(int (*load)(const std::string&, T2&), const std::string& strDir, T2 if (p.is_regular_file()) { intFile++; - string path = convert_w2a(p.path().filename().wstring().c_str()); + string path = convert_w2a(p.path().filename().u16string().c_str()); const int Cnt = load(strDir + path, tmp); if (Cnt < 0) { - files.push_back(path); + failureFiles.push_back(path); + intFailure++; + } + else intItem += Cnt; + } + } + if (err) return -1; + return 0; +} + +template +int _loadDir(int (*load)(const std::filesystem::path&, T2&), const std::filesystem::path& fpDir, T2& tmp, int& intFile, int& intFailure, + int& intItem, std::vector& failureFiles) +{ + std::error_code err; + for (const auto& p : T1(fpDir, err)) + { + if (p.is_regular_file() && p.path().filename().string().substr(0, 1) != ".") + { + intFile++; + const int Cnt = load(p, tmp); + if (Cnt < 0) + { + failureFiles.push_back(UTF8toGBK(p.path().filename().u8string())); intFailure++; } else intItem += Cnt; @@ -363,7 +579,7 @@ int _loadDir(int (*load)(const std::string&, T2&), const std::string& strDir, T2 //读取文件夹 template -int loadDir(int (*load)(const std::string&, T&), const std::string& strDir, T& tmp, std::string& strLog, +[[deprecated]] int loadDir(int (*load)(const std::string&, T&), const std::string& strDir, T& tmp, ResList& logList, bool isSubdir = false) { int intFile = 0, intFailure = 0, intItem = 0; @@ -380,13 +596,44 @@ int loadDir(int (*load)(const std::string&, T&), const std::string& strDir, T& t } if (!intFile)return 0; - strLog += "读取" + strDir + "中的" + std::to_string(intFile) + "个文件, 共" + std::to_string(intItem) + "个条目\n"; + logList << "读取" + strDir + "中的" + std::to_string(intFile) + "个文件, 共" + std::to_string(intItem) + "个条目"; if (intFailure) { - strLog += "读取失败" + std::to_string(intFailure) + "个:\n"; + logList << "读取失败" + std::to_string(intFailure) + "个:"; for (auto& it : files) { - strLog += it + "\n"; + logList << it; + } + } + return intFile; +} + + +template +int loadDir(int (*load)(const std::filesystem::path&, T&), const std::filesystem::path& fpDir, T& tmp, ResList& logList, + bool isSubdir = false) +{ + int intFile = 0, intFailure = 0, intItem = 0; + std::vector files; + if (isSubdir) + { + if (_loadDir(load, fpDir, tmp, intFile, intFailure, intItem, + files) == -1) return 0; + } + else + { + if (_loadDir(load, fpDir, tmp, intFile, intFailure, intItem, files) == -1) + return 0; + } + + if (!intFile)return 0; + logList << "读取" + UTF8toGBK(fpDir.u8string()) + "中的" + std::to_string(intFile) + "个文件, 共" + std::to_string(intItem) + "个条目"; + if (intFailure) + { + logList << "读取失败" + std::to_string(intFailure) + "个:"; + for (auto& it : files) + { + logList << it; } } return intFile; @@ -416,7 +663,7 @@ void fprint(std::ofstream& fout, std::pair t) } template -bool clrEmpty(std::string strPath, const T& tmp) +[[deprecated]] bool clrEmpty(const std::string& strPath, const T& tmp) { if (tmp.empty()) { @@ -431,6 +678,22 @@ bool clrEmpty(std::string strPath, const T& tmp) return false; } +template +bool clrEmpty(const std::filesystem::path& fpPath, const T& tmp) +{ + if (tmp.empty()) + { + std::ifstream fin(fpPath); + if (fin) + { + fin.close(); + remove(fpPath); + } + return true; + } + return false; +} + template typename std::enable_if::value, void>::type fwrite(ofstream& fout, T t) { @@ -440,6 +703,13 @@ typename std::enable_if::value, void>::type fwrite(ofstream& f void fwrite(ofstream& fout, const std::string& s); +void fwrite(ofstream& fout, const var& var); + +template +void fwrite(ofstream& fout, const C& obj) +{ + obj.writeb(fout); +} template void fwrite(ofstream& fout, C& obj) @@ -447,8 +717,9 @@ void fwrite(ofstream& fout, C& obj) obj.writeb(fout); } -template -void fwrite(ofstream& fout, const std::map& m) + +template +void fwrite(ofstream& fout, const std::map& m) { const auto len = static_cast(m.size()); fwrite(fout, len); @@ -471,7 +742,7 @@ void fwrite(ofstream& fout, const std::set& s) } template -void saveFile(std::string strPath, const T& setTmp) +[[deprecated]] void saveFile(const std::string& strPath, const T& setTmp) { if (clrEmpty(strPath, setTmp))return; std::ofstream fout(strPath); @@ -483,8 +754,21 @@ void saveFile(std::string strPath, const T& setTmp) fout.close(); } +template +void saveFile(const std::filesystem::path& fpPath, const T& setTmp) +{ + if (clrEmpty(fpPath, setTmp))return; + std::ofstream fout(fpPath); + for (const auto& it : setTmp) + { + fprint(fout, it); + fout << std::endl; + } + fout.close(); +} + template -void saveFile(std::string strPath, const map& mTmp) +[[deprecated]] void saveFile(const std::string& strPath, const map& mTmp) { if (clrEmpty(strPath, mTmp))return; std::ofstream fout(strPath); @@ -497,9 +781,23 @@ void saveFile(std::string strPath, const map& mTmp) fout.close(); } +template +void saveFile(const std::filesystem::path& fpPath, const map& mTmp) +{ + if (clrEmpty(fpPath, mTmp))return; + std::ofstream fout(fpPath); + for (const auto& [key,val] : mTmp) + { + fout << key << "\t"; + fprint(fout, val); + fout << std::endl; + } + fout.close(); +} + template -void saveFile(std::string strPath, const unordered_map& mTmp) +[[deprecated]] void saveFile(const std::string& strPath, const unordered_map& mTmp) { if (clrEmpty(strPath, mTmp))return; std::ofstream fout(strPath); @@ -512,8 +810,22 @@ void saveFile(std::string strPath, const unordered_map& mTmp) fout.close(); } -template -void saveBFile(std::string strPath, std::map& m) +template +void saveFile(const std::filesystem::path& fpPath, const unordered_map& mTmp) +{ + if (clrEmpty(fpPath, mTmp))return; + std::ofstream fout(fpPath); + for (const auto& [key, val] : mTmp) + { + fout << key << "\t"; + fprint(fout, val); + fout << std::endl; + } + fout.close(); +} + +template +[[deprecated]] void saveBFile(const std::string& strPath, std::map& m) { if (clrEmpty(strPath, m))return; std::ofstream fout(strPath, ios::out | ios::trunc | ios::binary); @@ -527,8 +839,23 @@ void saveBFile(std::string strPath, std::map& m) fout.close(); } +template +void saveBFile(const std::filesystem::path& fpPath, std::map& m) +{ + if (clrEmpty(fpPath, m))return; + std::ofstream fout(fpPath, ios::out | ios::trunc | ios::binary); + const int len = m.size(); + fwrite(fout, len); + for (auto& [key,val] : m) + { + fwrite(fout, key); + fwrite(fout, val); + } + fout.close(); +} + template -void saveBFile(std::string strPath, std::unordered_map& m) +[[deprecated]] void saveBFile(const std::string& strPath, std::map& m) { if (clrEmpty(strPath, m))return; std::ofstream fout(strPath, ios::out | ios::trunc | ios::binary); @@ -542,10 +869,92 @@ void saveBFile(std::string strPath, std::unordered_map& m) fout.close(); } +template +void saveBFile(const std::filesystem::path& fpPath, std::map& m) +{ + if (clrEmpty(fpPath, m))return; + std::ofstream fout(fpPath, ios::out | ios::trunc | ios::binary); + const int len = m.size(); + fwrite(fout, len); + for (auto& [key, val] : m) + { + fwrite(fout, key); + fwrite(fout, val); + } + fout.close(); +} + +template +[[deprecated]] void saveBFile(const std::string& strPath, std::unordered_map& m) +{ + if (clrEmpty(strPath, m))return; + std::ofstream fout(strPath, ios::out | ios::trunc | ios::binary); + const int len = m.size(); + fwrite(fout, len); + for (auto& [key, val] : m) + { + fwrite(fout, key); + fwrite(fout, val); + } + fout.close(); +} + +template +void saveBFile(const std::filesystem::path& fpPath, std::unordered_map& m) +{ + if (clrEmpty(fpPath, m))return; + std::ofstream fout(fpPath, ios::out | ios::trunc | ios::binary); + const int len = m.size(); + fwrite(fout, len); + for (auto& [key, val] : m) + { + fwrite(fout, key); + fwrite(fout, val); + } + fout.close(); +} + +template +[[deprecated]] void saveBFile(const std::string& strPath, std::unordered_map& m) +{ + if (clrEmpty(strPath, m))return; + std::ofstream fout(strPath, ios::out | ios::trunc | ios::binary); + const int len = m.size(); + fwrite(fout, len); + for (auto& [key, val] : m) + { + fwrite(fout, key); + fwrite(fout, val); + } + fout.close(); +} + +template +void saveBFile(const std::filesystem::path& fpPath, std::unordered_map& m) +{ + if (clrEmpty(fpPath, m))return; + std::ofstream fout(fpPath, ios::out | ios::trunc | ios::binary); + const int len = m.size(); + fwrite(fout, len); + for (auto& [key, val] : m) + { + fwrite(fout, key); + fwrite(fout, val); + } + fout.close(); +} + //读取伪xml template -void saveXML(std::string strPath, C& obj) +void saveXML(const std::string& strPath, C& obj) { std::ofstream fout(strPath); fout << obj.writet(); } + +template +void saveXML(const std::filesystem::path& fpPath, C& obj) +{ + std::ofstream fout(fpPath); + fout << obj.writet(); +} diff --git a/Dice/DiceGUI.cpp b/Dice/DiceGUI.cpp index df3064ee..9ff6a80a 100644 --- a/Dice/DiceGUI.cpp +++ b/Dice/DiceGUI.cpp @@ -1,4 +1,5 @@ #include "DiceGUI.h" +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include @@ -14,6 +15,7 @@ #include "GlobalVar.h" #include "Jsonio.h" #include "resource.h" +#include "DDAPI.h" #pragma comment(lib, "comctl32.lib") @@ -634,7 +636,7 @@ LRESULT DiceGUI::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) GlobalMsg[curr] = str; EditedMsg[curr] = str; ListViewCustomMsg.SetItemText(str, ListViewCustomMsgCurrentActivated, 1); - saveJMap(DiceDir + "\\conf\\CustomMsg.json", EditedMsg); + saveJMap(DiceDir / "conf" / "CustomMsg.json", EditedMsg); } return 0; case ID_MASTER_BUTTONMASTER: @@ -668,9 +670,8 @@ LRESULT DiceGUI::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) console.newMaster(qq); } } - else - { - MessageBoxA(nullptr, "Master模式已开启√", "Master模式切换", MB_OK | MB_ICONINFORMATION); + else { + MessageBoxA(nullptr, console["Private"] ? getMsg("strNewMasterPrivate").c_str() : getMsg("strNewMasterPublic").c_str(), "Master模式初始化", MB_OK | MB_ICONINFORMATION); console.newMaster(qq); console.isMasterMode = true; } @@ -727,7 +728,7 @@ LRESULT DiceGUI::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) if (ret == -1) { - string nickname = CQ::getStrangerInfo(qq).nick; + string nickname = DD::getQQNick(qq); ListViewUserTrust.AddTextRow({str, nickname, trust}); } else @@ -1007,7 +1008,7 @@ LRESULT DiceGUI::CreateAboutPage() StaticImageDiceLogo.Create(nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP | SS_REALSIZECONTROL, 0, 40, 40, 300, 300, m_hwnd); - StaticVersionInfo.Create(Dice_Full_Ver_For.c_str(), WS_CHILD | WS_VISIBLE, 0, + StaticVersionInfo.Create(Dice_Full_Ver_On.c_str(), WS_CHILD | WS_VISIBLE, 0, 40, 350, 300, 50, m_hwnd); StaticAuthorInfo.Create("主要作者: 溯洄 Shiki\r\n本程序于AGPLv3协议下开源", WS_CHILD | WS_VISIBLE, 0, @@ -1118,18 +1119,13 @@ int WINAPI GUIMain() SendMessageA(progress, PBM_SETSTEP, static_cast(1), 0); ShowWindow(progress, SW_SHOWDEFAULT); - const std::map FriendMp = CQ::getFriendList(); + const std::set FriendMp{ DD::getFriendQQList() }; // 获取Nickname for (const auto& item : UserList) { - if (FriendMp.count(item.first)) - { - nicknameMp[item.first] = FriendMp.at(item.first).nick; - } - else if (LoadStranger) - { - nicknameMp[item.first] = CQ::getStrangerInfo(item.first).nick; + if (FriendMp.count(item.first) || LoadStranger) { + nicknameMp[item.first] = DD::getQQNick(item.first); } SendMessageA(progress, PBM_STEPIT, 0, 0); MSG msg; @@ -1163,3 +1159,4 @@ int WINAPI GUIMain() return 0; } +#endif diff --git a/Dice/DiceGUI.h b/Dice/DiceGUI.h index e01b411f..abc5da2a 100644 --- a/Dice/DiceGUI.h +++ b/Dice/DiceGUI.h @@ -1,7 +1,9 @@ #pragma once #ifndef DICE_GUI #define DICE_GUI +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include int WINAPI GUIMain(); #endif +#endif diff --git a/Dice/DiceJob.cpp b/Dice/DiceJob.cpp new file mode 100644 index 00000000..2b409328 --- /dev/null +++ b/Dice/DiceJob.cpp @@ -0,0 +1,459 @@ +#include "DiceJob.h" +#include "DiceConsole.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#endif + +#include "StrExtern.hpp" +#include "DDAPI.h" +#include "ManagerSystem.h" +#include "DiceCloud.h" +#include "BlackListManager.h" +#include "GlobalVar.h" +#include "CardDeck.h" +#include "DiceMod.h" +#include "DiceNetwork.h" +#include "DiceSession.h" +#include "S3PutObject.h" +#pragma warning(disable:28159) + +using namespace std; + +int sendSelf(const string& msg) { + static long long selfQQ = DD::getLoginQQ(); + DD::sendPrivateMsg(selfQQ, msg); + return 0; +} + +void cq_exit(DiceJob& job) { +#ifdef _WIN32 + job.note("已令" + getMsg("self") + "在5秒后自杀", 1); + std::this_thread::sleep_for(5s); + dataBackUp(); + DD::killme(); +#endif +} + +#ifdef _WIN32 +inline PROCESSENTRY32 getProcess(int pid) { + PROCESSENTRY32 pe32; + pe32.dwSize = sizeof(pe32); + HANDLE hParentProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + Process32First(hParentProcess, &pe32); + return pe32; +} +#endif + +void frame_restart(DiceJob& job) { +#ifdef _WIN32 + if (!job.fromQQ) { + if (console["AutoFrameRemake"] <= 0) { + sch.add_job_for(60 * 60, job); + return; + } + else if (int tWait{ console["AutoFrameRemake"] * 60 * 60 - int(time(nullptr) - llStartTime) }; tWait > 0) { + sch.add_job_for(tWait, job); + return; + } + } + Enabled = false; + dataBackUp(); + std::this_thread::sleep_for(3s); + DD::remake(); +#endif +} + +void frame_reload(DiceJob& job) { + if (DD::reload()) + job.note("重载" + getMsg("self") + "完成√", 1); + else + job.note("重载" + getMsg("self") + "失败×", 0b10); +} + +void check_system(DiceJob& job) { + console.log("检查系统负载", 0, printSTNow()); +#ifdef _WIN32 + static int perRAM(0), perLastRAM(0); + static double perLastCPU(0), perLastDisk(0), + perCPU(0), perDisk(0); + static bool isAlarmRAM(false), isAlarmCPU(false), isAlarmDisk(false); + static double mbFreeBytes = 0, mbTotalBytes = 0; + //内存检测 + if (console["SystemAlarmRAM"] > 0) { + perRAM = getRamPort(); + if (perRAM > console["SystemAlarmRAM"] && perRAM > perLastRAM) { + console.log("警告:" + GlobalMsg["strSelfName"] + "所在系统内存占用达" + to_string(perRAM) + "%", 0b1000, printSTime(stNow)); + perLastRAM = perRAM; + isAlarmRAM = true; + } + else if (perLastRAM > console["SystemAlarmRAM"] && perRAM < console["SystemAlarmRAM"]) { + console.log("提醒:" + GlobalMsg["strSelfName"] + "所在系统内存占用降至" + to_string(perRAM) + "%", 0b10, printSTime(stNow)); + perLastRAM = perRAM; + isAlarmRAM = false; + } + } + //CPU检测 + if (console["SystemAlarmCPU"] > 0) { + perCPU = getWinCpuUsage() / 10.0; + if (perCPU > 99.9) { + this_thread::sleep_for(10s); + perCPU = getWinCpuUsage() / 10.0; + } + if (perCPU > console["SystemAlarmCPU"] && (!isAlarmCPU || perCPU > perLastCPU + 1)) { + console.log("警告:" + GlobalMsg["strSelfName"] + "所在系统CPU占用达" + toString(perCPU) + "%", 0b1000, printSTime(stNow)); + perLastCPU = perCPU; + isAlarmCPU = true; + } + else if (perLastCPU > console["SystemAlarmCPU"] && perCPU < console["SystemAlarmCPU"]) { + console.log("提醒:" + GlobalMsg["strSelfName"] + "所在系统CPU占用降至" + toString(perCPU) + "%", 0b10, printSTime(stNow)); + perLastCPU = perCPU; + isAlarmCPU = false; + } + } + //硬盘检测 + if (console["SystemAlarmRAM"] > 0) { + perDisk = getDiskUsage(mbFreeBytes, mbTotalBytes) / 10.0; + if (perDisk > console["SystemAlarmDisk"] && (!isAlarmDisk || perDisk > perLastDisk + 1)) { + console.log("警告:" + GlobalMsg["strSelfName"] + "所在系统硬盘占用达" + toString(perDisk) + "%", 0b1000, printSTime(stNow)); + perLastDisk = perDisk; + isAlarmDisk = true; + } + else if (perLastDisk > console["SystemAlarmDisk"] && perDisk < console["SystemAlarmDisk"]) { + console.log("提醒:" + GlobalMsg["strSelfName"] + "所在系统硬盘占用降至" + toString(perDisk) + "%", 0b10, printSTime(stNow)); + perLastDisk = perDisk; + isAlarmDisk = false; + } + } + if (isAlarmRAM || isAlarmCPU || isAlarmDisk) { + sch.add_job_for(5 * 60, job); + } + else { + sch.add_job_for(30 * 60, job); + } +#endif +} + + + +void auto_save(DiceJob& job) { + if (sch.is_job_cold("autosave"))return; + console.log("自动保存", 0, printSTNow()); + dataBackUp(); + //console.log(GlobalMsg["strSelfName"] + "已自动保存", 0, printSTNow()); + if (console["AutoSaveInterval"] > 0) { + sch.refresh_cold("autosave", time(NULL) + console["AutoSaveInterval"] * (time_t)60); + sch.add_job_for(console["AutoSaveInterval"] * 60, "autosave"); + } +} + +//被引用的图片列表 +void clear_image(DiceJob& job) { + if (!job.fromQQ) { + if (sch.is_job_cold("clrimage"))return; + if (console["AutoClearImage"] <= 0) { + sch.add_job_for(60 * 60, job); + return; + } + } + scanImage(GlobalMsg, sReferencedImage); + scanImage(HelpDoc, sReferencedImage); + scanImage(CardDeck::mPublicDeck, sReferencedImage); + scanImage(CardDeck::mReplyDeck, sReferencedImage); + for (auto it : ChatList) { + scanImage(it.second.strConf, sReferencedImage); + } + job.note("整理" + GlobalMsg["strSelfName"] + "被引用图片" + to_string(sReferencedImage.size()) + "项", 0b0); + int cnt = clrDir("data/image/", sReferencedImage); + job.note("已清理image文件" + to_string(cnt) + "项", 1); + if (console["AutoClearImage"] > 0) { + sch.refresh_cold("clrimage", time(NULL) + console["AutoClearImage"]); + sch.add_job_for(console["AutoClearImage"] * 60 * 60, "clrimage"); + } +} + +void clear_group(DiceJob& job) { + console.log("开始清查群聊", 0, printSTNow()); + int intCnt = 0; + ResList res; + vector GrpDelete; + time_t grpline{ console["InactiveGroupLine"] > 0 ? (tNow - console["InactiveGroupLine"] * (time_t)86400) : 0 }; + if (job.strVar["clear_mode"] == "unpower") { + for (auto& [id, grp] : ChatList) { + if (grp.isset("忽略") || grp.isset("已退") || grp.isset("未进") || grp.isset("免清") || grp.isset("协议无效"))continue; + if (grp.isGroup && !DD::isGroupAdmin(id, console.DiceMaid, true)) { + res << printGroup(id); + time_t tLast{ grp.tUpdated }; + if (gm->has_session(id) && gm->session(id).tUpdate > grp.tUpdated)tLast = gm->session(id).tUpdate; + if (tLast < grpline)GrpDelete.push_back(id); + grp.leave(getMsg("strLeaveNoPower")); + intCnt++; + if (console["GroupClearLimit"] > 0 && intCnt >= console["GroupClearLimit"])break; + this_thread::sleep_for(3s); + } + } + job.note(GlobalMsg["strSelfName"] + "筛除无群权限群聊" + to_string(intCnt) + "个:" + res.show(), 0b10); + } + else if (isdigit(static_cast(job.strVar["clear_mode"][0]))) { + int intDayLim = stoi(job.strVar["clear_mode"]); + string strDayLim = to_string(intDayLim); + time_t tNow = time(NULL); + for (auto& [id, grp] : ChatList) { + if (grp.isset("忽略") || grp.isset("免清") || grp.isset("协议无效"))continue; + time_t tLast{ grp.tUpdated }; + if (long long tLMT; grp.isGroup && ((tLMT = DD::getGroupLastMsg(id, console.DiceMaid)) > 0 && tLMT > tLast))tLast = tLMT; + if (gm->has_session(id) && gm->session(id).tUpdate > tLast)tLast = gm->session(id).tUpdate; + if (!tLast)continue; + if (tLast < grpline && !grp.isset("免黑"))GrpDelete.push_back(id); + if (grp.isset("已退") || grp.isset("未进"))continue; + int intDay = (int)(tNow - tLast) / 86400; + if (intDay > intDayLim) { + job["day"] = to_string(intDay); + res << printGroup(id) + ":" + to_string(intDay) + "天\n"; + grp.leave(getMsg("strLeaveUnused", job.strVar)); + intCnt++; + if (console["GroupClearLimit"] > 0 && intCnt >= console["GroupClearLimit"])break; + this_thread::sleep_for(3s); + } + } + job.note(GlobalMsg["strSelfName"] + "已筛除潜水" + strDayLim + "天群聊" + to_string(intCnt) + "个√" + res.show(), 0b10); + } + else if (job.strVar["clear_mode"] == "black") { + try { + for (auto id : DD::getGroupIDList()) { + Chat& grp = chat(id).group().name(DD::getGroupName(id)); + if (grp.isset("忽略") || grp.isset("免清") || grp.isset("免黑") || grp.isset("协议无效"))continue; + if (blacklist->get_group_danger(id)) { + time_t tLast{ grp.tUpdated }; + if (gm->has_session(id) && gm->session(id).tUpdate > grp.tUpdated)tLast = gm->session(id).tUpdate; + if (tLast < grpline)GrpDelete.push_back(id); + res << printGroup(id) + ":黑名单群"; + if (console["LeaveBlackGroup"])grp.leave(getMsg("strBlackGroup")); + } + set MemberList{ DD::getGroupMemberList(id) }; + int authSelf{ DD::getGroupAuth(id, console.DiceMaid, 1) }; + for (auto eachQQ : MemberList) { + if (blacklist->get_qq_danger(eachQQ) > 1) { + if (auto authBlack{ DD::getGroupAuth(id, eachQQ, 1) }; authBlack < authSelf) { + continue; + } + else if (authBlack > authSelf) { + if (grp.tUpdated < grpline)GrpDelete.push_back(id); + res << printChat(grp) + ":" + printQQ(eachQQ) + "对方群权限较高"; + grp.leave("发现黑名单管理员" + printQQ(eachQQ) + "\n" + GlobalMsg["strSelfName"] + "将预防性退群"); + intCnt++; + break; + } + else if (console["GroupClearLimit"] > 0 && intCnt >= console["GroupClearLimit"]) { + if(intCnt == console["GroupClearLimit"])res << "*单次清退已达上限*"; + res << printChat(grp) + ":" + printQQ(eachQQ); + } + else if (console["LeaveBlackQQ"]) { + if (grp.tUpdated < grpline)GrpDelete.push_back(id); + res << printChat(grp) + ":" + printQQ(eachQQ); + grp.leave("发现黑名单成员" + printQQ(eachQQ) + "\n" + GlobalMsg["strSelfName"] + "将预防性退群"); + intCnt++; + break; + } + } + } + } + } catch (...) { + console.log("提醒:" + GlobalMsg["strSelfName"] + "清查黑名单群聊时出错!", 0b10, printSTNow()); + } + if (intCnt) { + job.note("已按" + getMsg("strSelfName") + "黑名单清查群聊" + to_string(intCnt) + "个:" + res.show(), 0b10); + } + else if (job.fromQQ) { + job.echo(getMsg("strSelfName") + "按黑名单未发现待清查群聊"); + } + } + else if (job["clear_mode"] == "preserve") { + for (auto& [id, grp] : ChatList) { + if (grp.isset("忽略") || grp.isset("已退") || grp.isset("未进") || grp.isset("使用许可") || grp.isset("免清") || grp.isset("协议无效"))continue; + if (grp.isGroup && DD::isGroupAdmin(id, console.master(), false)) { + grp.set("使用许可"); + continue; + } + time_t tLast{ grp.tUpdated }; + if (gm->has_session(id) && gm->session(id).tUpdate > grp.tUpdated)tLast = gm->session(id).tUpdate; + if (tLast < grpline)GrpDelete.push_back(id); + res << printChat(grp); + grp.leave(getMsg("strPreserve")); + intCnt++; + if (console["GroupClearLimit"] > 0 && intCnt >= console["GroupClearLimit"])break; + this_thread::sleep_for(3s); + } + job.note(GlobalMsg["strSelfName"] + "筛除无许可群聊" + to_string(intCnt) + "个:" + res.show(), 1); + } + else + job.echo("无法识别筛选参数×"); + if (!GrpDelete.empty()) { + for (const auto& id : GrpDelete) { + ChatList.erase(id); + if (gm->has_session(id))gm->session_end(id); + } + job.note("清查群聊时回收不活跃记录" + to_string(GrpDelete.size()) + "条", 0b1); + } +} +void list_group(DiceJob& job) { + console.log("遍历群列表", 0, printSTNow()); + if (job["list_mode"].empty()) { + job.reply(fmt->get_help("groups_list")); + } + if (mChatConf.count(job["list_mode"])) { + ResList res; + for (auto& [id, grp] : ChatList) { + if (grp.isset(job["list_mode"])) { + res << printChat(grp); + } + } + job.reply("{self}含词条" + job["list_mode"] + "群记录" + to_string(res.size()) + "条" + res.head(":").show()); + } + else if (set grps(DD::getGroupIDList()); job["list_mode"] == "idle") { + std::priority_queue> qDiver; + time_t tNow = time(NULL); + for (auto& [id, grp] : ChatList) { + if (grp.isGroup && !grps.empty() && !grps.count(id))grp.set("已退"); + if (grp.isset("已退") || grp.isset("未进"))continue; + time_t tLast = grp.tUpdated; + if (long long tLMT; grp.isGroup && (tLMT = DD::getGroupLastMsg(grp.ID, console.DiceMaid)) > 0 && tLMT > tLast)tLast = tLMT; + if (!tLast)continue; + int intDay = (int)(tNow - tLast) / 86400; + qDiver.emplace(intDay, printGroup(id)); + } + if (qDiver.empty()) { + job.reply("{self}无群聊或群信息加载失败!"); + } + size_t intCnt(0); + ResList res; + while (!qDiver.empty()) { + res << qDiver.top().second + to_string(qDiver.top().first) + "天"; + qDiver.pop(); + if (++intCnt > 32 || qDiver.top().first < 7)break; + } + job.reply("{self}所在闲置群列表:" + res.show(1)); + } + else if (job["list_mode"] == "size") { + std::priority_queue> qSize; + time_t tNow = time(NULL); + for (auto& [id, grp] : ChatList) { + if (grp.isGroup && !grps.empty() && !grps.count(id))grp.set("已退"); + if (grp.isset("已退") || grp.isset("未进") || !grp.isGroup)continue; + Size size(DD::getGroupSize(id)); + if (!size.siz)continue; + qSize.emplace(size.siz, DD::printGroupInfo(id)); + } + if (qSize.empty()) { + job.reply("{self}无群聊或群信息加载失败!"); + } + size_t intCnt(0); + ResList res; + while (!qSize.empty()) { + res << qSize.top().second; + qSize.pop(); + if (++intCnt > 32 || qSize.top().first < 7)break; + } + job.reply("{self}所在大群列表:" + res.show(1)); + } +} + +//心跳检测 +void cloud_beat(DiceJob& job) { + Cloud::heartbeat(); + sch.add_job_for(5 * 60, job); +} + +void dice_update(DiceJob& job) { + job.note("开始更新Dice\n版本:" + job.strVar["ver"], 1); + string ret; + if (DD::updateDice(job.strVar["ver"], ret)) { + job.note("更新Dice!" + job.strVar["ver"] + "版成功√", 1); + } + else { + job.echo("更新失败:" + ret); + } +} + +//获取云不良记录 +void dice_cloudblack(DiceJob& job) { + bool isSuccess(false); + job.note("开始获取云不良记录", 0); + string strURL("https://shiki.stringempty.xyz/blacklist/checked.json?" + to_string(job.fromTime)); + switch (Cloud::DownloadFile(strURL.c_str(), DiceDir / "conf" / "CloudBlackList.json")) { + case -1: { + string des; + if (Network::GET("shiki.stringempty.xyz", "/blacklist/checked.json", 80, des)) { + ofstream fout(DiceDir / "conf" / "CloudBlackList.json"); + fout << des << endl; + isSuccess = true; + } + else + job.echo("同步云不良记录同步失败:" + des); + } + break; + case -2: + job.echo("同步云不良记录同步失败!文件未找到"); + break; + case 0: + default: + break; + } + if (isSuccess) { + if (job.fromQQ)job.note("同步云不良记录成功," + getMsg("self") + "开始读取", 1); + blacklist->loadJson(DiceDir / "conf" / "CloudBlackList.json", true); + } + if (console["CloudBlackShare"]) + sch.add_job_for(24 * 60 * 60, "cloudblack"); +} + +void log_put(DiceJob& job) { + job["ret"] = put_s3_object("dicelogger", + job.strVar["log_file"].c_str(), + job.strVar["log_path"].c_str(), + "ap-southeast-1"); + if (job["ret"] == "SUCCESS") { + job.echo(getMsg("strLogUpSuccess", job.strVar)); + } + else if (job.cntExec++ > 2) { + job.echo(getMsg("strLogUpFailureEnd", job.strVar)); + } + else { + job["retry"] = to_string(job.cntExec); + job.echo(getMsg("strLogUpFailure", job.strVar)); + console.log(getMsg("strLogUpFailure", job.strVar), 1); + sch.add_job_for(2 * 60, job); + } +} + + +string print_master() { + if (!console.master())return "(无主)"; + return printQQ(console.master()); +} + +string list_deck() { + return listKey(CardDeck::mPublicDeck); +} +string list_extern_deck() { + return listKey(CardDeck::mExternPublicDeck); +} +string list_order_ex() { + return fmt->list_order(); +} +string list_dice_sister() { + std::setlist{ DD::getDiceSisters() }; + if (list.size() <= 1)return {}; + else { + list.erase(console.DiceMaid); + ResList li; + li << printQQ(console.DiceMaid) + "的姐妹骰:"; + for (auto dice : list) { + li << printQQ(dice); + } + return li.show(); + } +} \ No newline at end of file diff --git a/Dice/DiceJob.h b/Dice/DiceJob.h new file mode 100644 index 00000000..a645ae43 --- /dev/null +++ b/Dice/DiceJob.h @@ -0,0 +1,36 @@ +#pragma once +#include "DiceSchedule.h" + +inline time_t tNow = time(NULL); + +int sendSelf(const string& msg); + +void cq_exit(DiceJob& job); +void frame_restart(DiceJob& job); +void frame_reload(DiceJob& job); + +void auto_save(DiceJob& job); + +void check_system(DiceJob& job); + + +void clear_image(DiceJob& job); + +void clear_group(DiceJob& job); + +void list_group(DiceJob& job); + +void cloud_beat(DiceJob& job); +void dice_update(DiceJob& job); +void dice_cloudblack(DiceJob& job); + +void log_put(DiceJob& job); +void global_exit(); + + +string print_master(); + +string list_deck(); +string list_extern_deck(); +string list_order_ex(); +string list_dice_sister(); \ No newline at end of file diff --git a/Dice/DiceL5R.h b/Dice/DiceL5R.h index f74515fd..a40eebd9 100644 --- a/Dice/DiceL5R.h +++ b/Dice/DiceL5R.h @@ -25,7 +25,7 @@ class DiceL5R int Randint(int lowest, int highest) { std::mt19937 gen(static_cast(RandomGenerator::GetCycleCount())); - const std::uniform_int_distribution dis(lowest, highest); + std::uniform_int_distribution dis(lowest, highest); return dis(gen); } public: diff --git a/Dice/DiceLua.cpp b/Dice/DiceLua.cpp new file mode 100644 index 00000000..0a88edbf --- /dev/null +++ b/Dice/DiceLua.cpp @@ -0,0 +1,566 @@ +//#pragma comment(lib, "lua.lib") +extern "C"{ +#include +#include +#include +#include +}; +#include "ManagerSystem.h" +#include "DiceEvent.h" +#include "RandomGenerator.h" +#include "CharacterCard.h" +#include "CardDeck.h" +#include "DiceSession.h" +#include "DDAPI.h" + +unordered_set UTF8Luas; + +class LuaState { + lua_State* state; + //bool isValid; +public: + LuaState(const char* file); + //operator bool()const { return isValid; } + operator lua_State* () { return state; } + ~LuaState() { + if(state)lua_close(state); + UTF8Luas.erase(state); + } + void regist(); +}; +double lua_to_number(lua_State* L, int idx) { + return luaL_checknumber(L, idx); +} +long long lua_to_int(lua_State* L, int idx) { + return luaL_checkinteger(L, idx); +} + +// Return a GB18030 string +string lua_to_gb18030_string(lua_State* L, int idx) { + const char* str{ luaL_checkstring(L, idx) }; + if (!str) return {}; + return UTF8toGBK(str, true); +} + +// Return a GB18030 string on Windows and a UTF-8 string on other platforms +string lua_to_string(lua_State* L, int idx) { + const char* str{ luaL_checkstring(L, idx) }; + if (!str) return {}; +#ifdef _WIN32 + return UTF8toGBK(str, true); +#else + return GBKtoUTF8(str, true); +#endif +} + + +void lua_push_string(lua_State* L, const string& str) { + if (UTF8Luas.count(L)) + lua_pushstring(L, GBKtoUTF8(str, true).c_str()); + else + lua_pushstring(L, UTF8toGBK(str, true).c_str()); +} +void lua_set_field(lua_State* L, int idx, const string& str) { + if (UTF8Luas.count(L)) + lua_setfield(L, idx, GBKtoUTF8(str, true).c_str()); + else + lua_setfield(L, idx, UTF8toGBK(str, true).c_str()); +} + +void lua_push_msg(lua_State* L, FromMsg* msg) { + lua_newtable(L); + //lua_pushnumber(L, (double)msg->fromQQ); + lua_push_string(L, to_string(msg->fromQQ)); + lua_setfield(L, -2, "fromQQ"); + //lua_pushnumber(L, (double)msg->fromGroup); + lua_push_string(L, to_string(msg->fromGroup)); + lua_setfield(L, -2, "fromGroup"); + lua_push_string(L, msg->strMsg); + lua_setfield(L, -2, "fromMsg"); +} +void CharaCard::pushTable(lua_State* L) { + lua_newtable(L); + for (auto& [key, val] : Info) { + lua_push_string(L, val); + lua_set_field(L, -2, key.c_str()); + } + for (auto& [key, val] : Attr) { + lua_pushnumber(L, val); + lua_set_field(L, -2, key.c_str()); + } +} +void CharaCard::toCard(lua_State* L) { + +} +//读取指定lua文件的函数,file为原生系统编码 +bool lua_msg_order(FromMsg* msg, const char* file, const char* func) { +#ifndef _WIN32 + // 转换separator + string fileStr(file); + for (auto& c : fileStr) + { + if (c == '\\') c = '/'; + } + file = fileStr.c_str(); +#endif + LuaState L(file); + if (!L)return false; +#ifdef _WIN32 + // 转换为GB18030 + string fileGB18030(file); +#else + string fileGB18030(UTF8toGBK(file, true)); +#endif + lua_getglobal(L, func); + lua_push_msg(L, msg); + if (lua_pcall(L, 1, 2, 0)) { + string pErrorMsg = lua_to_gb18030_string(L, -1); + console.log(GlobalMsg["strSelfName"] + "调用" + fileGB18030 + "函数" + func + "失败!\n" + pErrorMsg, 1); + msg->reply(GlobalMsg["strOrderLuaErr"]); + return false; + } + if (lua_gettop(L) && lua_type(L, 1) != LUA_TNIL) { + if (!lua_isstring(L, 1)) { + console.log(GlobalMsg["strSelfName"] + "调用" + fileGB18030 + "函数" + func + "返回值格式错误!", 1); + msg->reply(GlobalMsg["strOrderLuaErr"]); + return false; + } + if (!(msg->strVar["msg_reply"] = lua_to_gb18030_string(L, 1)).empty()) { + msg->reply(msg->strVar["msg_reply"]); + } + if (lua_type(L, 2) != LUA_TNIL) { + if (!lua_isstring(L, 2)) { + console.log(GlobalMsg["strSelfName"] + "调用" + fileGB18030 + "函数" + func + "返回值格式错误!", 1); + return false; + } + if (!(msg->strVar["msg_hidden"] = lua_to_gb18030_string(L, 2)).empty()) { + msg->replyHidden(msg->strVar["msg_hidden"]); + } + } + } + return true; +} +bool lua_call_task(const char* file, const char* func) { +#ifndef _WIN32 + // 转换separator + string fileStr(file); + for (auto& c : fileStr) + { + if (c == '\\') c = '/'; + } + file = fileStr.c_str(); +#endif + LuaState L(file); + if (!L)return false; + lua_getglobal(L, func); +#ifdef _WIN32 + // 转换为GB18030 + string fileGB18030(file); +#else + string fileGB18030(UTF8toGBK(file, true)); +#endif + if (lua_pcall(L, 0, 0, 0)) { + string pErrorMsg = lua_to_gb18030_string(L, -1); + console.log(GlobalMsg["strSelfName"] + "调用" + fileGB18030 + "函数" + func + "失败!\n" + pErrorMsg, 1); + return false; + } + return true; +} + +/** + * 供lua调用的函数 + */ + + //加载其他lua脚本 +int loadLua(lua_State* L) { + string nameFile{ lua_to_string(L, 1) }; +#ifndef _WIN32 + // 转换separator + for (auto& c : nameFile) + { + if (c == '\\') c = '/'; + } +#endif + std::filesystem::path pathFile{ nameFile }; + if (pathFile.extension() != ".lua")pathFile = nameFile + ".lua"; + if (pathFile.is_relative())pathFile = DiceDir / "plugin" / pathFile; + if (!std::filesystem::exists(pathFile) && nameFile.find('\\') == string::npos && nameFile.find('/') == string::npos) + pathFile = DiceDir / "plugin" / nameFile / "init.lua"; + if (luaL_loadfile(L, pathFile.string().c_str())) { + string pErrorMsg = lua_to_gb18030_string(L, -1); + console.log(GlobalMsg["strSelfName"] + "读取lua文件" + UTF8toGBK(pathFile.u8string()) + "失败:"+ pErrorMsg, 0b10); + return 0; + } + if (lua_pcall(L, 0, 1, 0)) { + string pErrorMsg = lua_to_gb18030_string(L, -1); + console.log(GlobalMsg["strSelfName"] + "运行lua文件" + UTF8toGBK(pathFile.u8string()) + "失败:"+ pErrorMsg, 0b10); + return 1; + } + return 1; +} + //获取DiceMaid +int getDiceQQ(lua_State* L) { + lua_push_string(L, to_string(console.DiceMaid)); + return 1; +} +//获取DiceDir存档目录 +int getDiceDir(lua_State* L) { + lua_push_string(L, DiceDir.u8string()); + return 1; +} +int mkDirs(lua_State* L) { + string dir{ lua_to_string(L, 1) }; + mkDir(dir); + return 0; +} +int getGroupConf(lua_State* L) { + long long id{ lua_to_int(L, 1) }; + string item{ lua_to_gb18030_string(L, 2) }; + if (!id || item.empty())return 0; + if (lua_gettop(L) > 3)lua_settop(L, 3); + if (item == "size") { + lua_pushnumber(L, (double)DD::getGroupSize(id).siz); + } + else if (item == "maxsize") { + lua_pushnumber(L, (double)DD::getGroupSize(id).cap); + } + else if (ChatList.count(id)) { + Chat& grp{ chat(id) }; + if (item == "name") { + if (grp.Name.empty())lua_push_string(L, grp.Name = DD::getGroupName(id)); + else lua_push_string(L, grp.Name); + } + else if (item == "firstCreate") { + lua_pushnumber(L, (double)grp.tCreated); + } + else if (item == "lastUpdate") { + lua_pushnumber(L, (double)grp.tUpdated); + } + else if (mChatConf.count(item)) { + lua_pushboolean(L, grp.boolConf.count(item)); + } + else if (grp.intConf.count(item)) { + lua_pushnumber(L, (double)grp.intConf[item]); + } + else if (grp.strConf.count(item)) { + lua_push_string(L, grp.strConf[item]); + } + } + else if (item == "name") { + lua_push_string(L, DD::getGroupName(id)); + } + else { + lua_pushnil(L); + lua_insert(L, 3); + } + return 1; +} +int setGroupConf(lua_State* L) { + long long id{ lua_to_int(L, 1) }; + string item{ lua_to_gb18030_string(L, 2) }; + if (!id || item.empty())return 0; + Chat& grp{ chat(id) }; + if (mChatConf.count(item)) { + lua_toboolean(L, 3) ? grp.set(item) : grp.reset(item); + } + else if (lua_isnumber(L, 3)) { + grp.setConf(item, (int)lua_tonumber(L, 3)); + } + else if (lua_isstring(L, 3)) { + grp.setText(item, lua_to_gb18030_string(L, 3)); + } + return 0; +} +int getUserConf(lua_State* L) { + long long qq{ lua_to_int(L, 1) }; + if (!qq)return 0; + if (lua_gettop(L) > 3)lua_settop(L, 3); + string item{ lua_to_gb18030_string(L, 2) }; + if (item.empty())return 0; + if (item == "nick" ) { + lua_push_string(L, getName(qq)); + } + else if (item == "trust") { + lua_pushnumber(L, trustedQQ(qq)); + } + else if (UserList.count(qq)) { + User& user{ getUser(qq) }; + if (item == "firstCreate") { + lua_pushnumber(L, (double)user.tCreated); + } + else if (item == "lastUpdate") { + lua_pushnumber(L, (double)user.tUpdated); + } + else if (item == "nn") { + string nick; + user.getNick(nick, qq); + lua_push_string(L, nick); + } + else if (user.intConf.count(item)) { + lua_pushnumber(L, (double)user.intConf[item]); + } + else if (user.strConf.count(item)) { + lua_push_string(L, user.strConf[item]); + } + } + else { + lua_pushnil(L); + lua_insert(L, 3); + } + return 1; +} +int setUserConf(lua_State* L) { + long long qq{ lua_to_int(L, 1) }; + if (!qq)return 0; + string item{ lua_to_gb18030_string(L, 2) }; + if (item.empty())return 0; + if (lua_isnumber(L, 3)) { + getUser(qq).setConf(item, (int)lua_tonumber(L, 3)); + } + else if (lua_isstring(L, 3)) { + getUser(qq).setConf(item, lua_to_gb18030_string(L, 3)); + } + return 0; +} +int getUserToday(lua_State* L) { + long long qq{ lua_to_int(L, 1) }; + if (!qq)return 0; + string item{ lua_to_gb18030_string(L, 2) }; + if (item.empty())return 0; + if (item == "jrrp") + lua_pushnumber(L, today->getJrrp(qq)); + else + lua_pushnumber(L, today->get(qq, item)); + return 1; +} +int setUserToday(lua_State* L) { + long long qq{ lua_to_int(L, 1) }; + if (!qq)return 0; + string item{ lua_to_gb18030_string(L, 2) }; + if (item.empty())return 0; + int val{ (int)lua_to_number(L, 3) }; + today->set(qq, item, val); + return 0; +} + +int getPlayerCardAttr(lua_State* L) { + long long plQQ{ lua_to_int(L, 1) }; + long long group{ lua_to_int(L, 2) }; + string key{ lua_to_gb18030_string(L, 3) }; + if (!plQQ || key.empty())return 0; + CharaCard& pc = getPlayer(plQQ)[group]; + if (pc.Info.count(key)) { + lua_push_string(L, pc.Info.find(key)->second); + return 3; + } + else if (key == "note") { + lua_push_string(L, pc.Note); + return 2; + } + else if (pc.DiceExp.count(key)) { + lua_push_string(L, pc.DiceExp.find(key)->second); + } + else if (key = pc.standard(key); pc.Attr.count(key)) { + lua_pushnumber(L, (double)pc.Attr.find(key)->second); + } + else { + lua_pushnil(L); + lua_insert(L, 4); + } + return 1; +} +int getPlayerCard(lua_State* L) { + long long plQQ{ lua_to_int(L, 1) }; + if (!plQQ)return 0; + long long group{ lua_to_int(L, 2) }; + if (PList.count(plQQ)) { + getPlayer(plQQ)[group].pushTable(L); + return 1; + } + return 0; +} +int setPlayerCardAttr(lua_State* L) { + long long plQQ{ lua_to_int(L, 1) }; + long long group{ lua_to_int(L, 2) }; + string item{ lua_to_gb18030_string(L, 3) }; + if (!plQQ || item.empty())return 0; + //参数4为空则视为删除,__Name除外 + CharaCard& pc = getPlayer(plQQ)[group]; + if (item == "__Name") { + getPlayer(plQQ).renameCard(pc.getName(), lua_to_gb18030_string(L, 4)); + } + else if (lua_isnoneornil(L, 4)) { + pc.erase(item); + } + else if (lua_isnumber(L, 4)) { + pc.set(item, (int)lua_tonumber(L, -1)); + } + else if (lua_isstring(L, 4)) { + if (item[0] == '&')pc.setExp(item.substr(1), lua_to_gb18030_string(L, -1)); + else pc.setInfo(item, lua_to_gb18030_string(L, -1)); + } + return 0; +} + +//取随机数 +int ranint(lua_State* L) { + int l{ (int)lua_to_int(L, 1) }; + int r{ (int)lua_to_int(L, 2) }; + lua_pushnumber(L, RandomGenerator::Randint(l,r)); + return 1; +} +//线程等待 +int sleepTime(lua_State* L) { + int ms{ (int)lua_to_int(L, 1) }; + if (ms <= 0)return 0; + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + return 0; +} + +int drawDeck(lua_State* L) { + long long fromGroup{ lua_to_int(L, 1) }; + long long fromQQ{ lua_to_int(L, 2) }; + if (!fromGroup && !fromQQ)return 0; + string nameDeck{ lua_to_gb18030_string(L, 3) }; + if (nameDeck.empty())return 0; + long long fromSession{ fromGroup ? fromGroup : ~fromQQ }; + if (gm->has_session(fromSession)) { + lua_push_string(L, gm->session(fromSession).deck_draw(nameDeck)); + } + else if (CardDeck::findDeck(nameDeck)) { + vector& deck = CardDeck::mPublicDeck[nameDeck]; + lua_push_string(L, CardDeck::draw(deck[RandomGenerator::Randint(0, deck.size() - 1)])); + } + else { + lua_push_string(L, "{" + nameDeck + "}"); + } + return 1; +} + +int sendMsg(lua_State* L) { + string fromMsg{ lua_to_gb18030_string(L, 1) }; + long long fromGroup{ lua_to_int(L, 2) }; + long long fromQQ{ lua_to_int(L, 3) }; + if (!fromGroup && !fromQQ)return 0; + msgtype type{ fromGroup ? + chat(fromGroup).isGroup ? msgtype::Group : msgtype::Discuss + : msgtype::Private }; + AddMsgToQueue(format(fromMsg, GlobalMsg), + fromGroup ? fromGroup : fromQQ, type); + return 0; +} +int eventMsg(lua_State* L) { + string fromMsg{ lua_to_gb18030_string(L, 1) }; + long long fromGroup{ lua_to_int(L, 2) }; + long long fromQQ{ lua_to_int(L, 3) }; + msgtype type{ fromGroup ? + chat(fromGroup).isGroup ? msgtype::Group : msgtype::Discuss + : msgtype::Private }; + FromMsg* Msg = new FromMsg(fromGroup ? FromMsg(fromMsg, fromGroup, type, fromQQ) + : FromMsg(fromMsg, fromQQ)); + std::thread th(*Msg); + th.detach(); + return 0; +} + +void LuaState::regist() { + const luaL_Reg Dicelibs[] = { + {"loadLua", loadLua}, + {"getDiceQQ", getDiceQQ}, + {"getDiceDir", getDiceDir}, + {"mkDirs", mkDirs}, + {"getGroupConf", getGroupConf}, + {"setGroupConf", setGroupConf}, + {"getUserConf", getUserConf}, + {"setUserConf", setUserConf}, + {"getUserToday", getUserToday}, + {"setUserToday", setUserToday}, + {"getPlayerCardAttr", getPlayerCardAttr}, + {"setPlayerCardAttr", setPlayerCardAttr}, + {"getPlayerCard", getPlayerCard}, + {"ranint", ranint}, + {"sleepTime", sleepTime}, + {"drawDeck", drawDeck}, + {"sendMsg", sendMsg}, + {"eventMsg", eventMsg}, + {nullptr, nullptr}, + }; + for (const luaL_Reg* lib = Dicelibs; lib->func; lib++) { + lua_register(state, lib->name, lib->func); + } + lua_getglobal(state, "package"); + lua_getfield(state, -1, "path"); + string strPath((DiceDir / "plugin" / "?.lua").u8string() + lua_tostring(state, -1)); + lua_pushstring(state, strPath.c_str()); + lua_setfield(state, -3, "path"); + lua_pop(state, 2); +} + +LuaState::LuaState(const char* file) {//:isValid(false) { +#ifndef _WIN32 + // 转换separator + string fileStr(file); + for (auto& c : fileStr) + { + if (c == '\\') c = '/'; + } + file = fileStr.c_str(); +#endif + state = luaL_newstate(); + if (!state)return; + if (luaL_loadfile(state, file)) { + string pErrorMsg = lua_to_gb18030_string(state, -1); + console.log(GlobalMsg["strSelfName"] + "读取lua文件" + file + "失败:" + pErrorMsg, 0b10); + lua_close(state); + state = nullptr; + return; + } + luaL_openlibs(state); + regist(); + ifstream fs(file); + if (checkUTF8(fs))UTF8Luas.insert(state); + fs.close(); + if (lua_pcall(state, 0, 0, 0)) { + string pErrorMsg = lua_to_gb18030_string(state, -1); + console.log(GlobalMsg["strSelfName"] + "运行lua文件" + file + "失败:" + pErrorMsg, 0b10); + lua_close(state); + state = nullptr; + return; + } +} + +int lua_readStringTable(const char* file, const char* var, std::unordered_map& tab) { +#ifndef _WIN32 + // 转换separator + string fileStr(file); + for (auto& c : fileStr) + { + if (c == '\\') c = '/'; + } + file = fileStr.c_str(); +#endif + LuaState L(file); + if (!L)return -1; + lua_getglobal(L, var); + if (lua_type(L, 1) == LUA_TNIL) { + return 0; + } + if (lua_type(L, 1) != LUA_TTABLE) { + return -2; + } + try { + lua_pushnil(L); + while (lua_next(L, -2)) { + if (!lua_isstring(L, -1) || !lua_isstring(L, -2)) { + return -1; + } + string key = lua_to_gb18030_string(L, -2); + string value = lua_to_gb18030_string(L, -1); + tab[key] = value; + lua_pop(L, 1); + } + return tab.size(); + } catch (...) { + return -3; + } +} \ No newline at end of file diff --git a/Dice/DiceLua.h b/Dice/DiceLua.h new file mode 100644 index 00000000..4aed1ac8 --- /dev/null +++ b/Dice/DiceLua.h @@ -0,0 +1,14 @@ +/** + * lua脚本嵌入 + * 用于自定义前缀指令等 + * Copyright (C) 2020 String.Empty + */ +#pragma once +#include +#include + +class Lua_State; +class FromMsg; +bool lua_msg_order(FromMsg*, const char*, const char*); +bool lua_call_task(const char*, const char*); +int lua_readStringTable(const char*, const char*, std::unordered_map&); \ No newline at end of file diff --git a/Dice/DiceMod.cpp b/Dice/DiceMod.cpp index e2ed2518..249c3cb7 100644 --- a/Dice/DiceMod.cpp +++ b/Dice/DiceMod.cpp @@ -1,16 +1,61 @@ +/* + * _______ ________ ________ ________ __ + * | __ \ |__ __| | _____| | _____| | | + * | | | | | | | | | |_____ | | + * | | | | | | | | | _____| |__| + * | |__| | __| |__ | |_____ | |_____ __ + * |_______/ |________| |________| |________| |__| + * + * Dice! QQ Dice Robot for TRPG + * Copyright (C) 2018-2021 w4123溯洄 + * Copyright (C) 2019-2021 String.Empty + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this + * program. If not, see . + */ #include #include "DiceMod.h" #include "GlobalVar.h" #include "ManagerSystem.h" #include "Jsonio.h" #include "DiceFile.hpp" +#include "DiceEvent.h" +#include "DiceLua.h" +#include "RandomGenerator.h" using std::set; +bool DiceMsgOrder::exec(FromMsg* msg) { + if (type == OrderType::Lua) { + //std::thread th(lua_msg_order, msg, fileLua.c_str(), funcLua.c_str()); + //th.detach(); + lua_msg_order(msg, fileLua.c_str(), funcLua.c_str()); + return true; + } + return false; +} +bool DiceMsgOrder::exec() { + if (type == OrderType::Lua) { + std::thread th(lua_call_task, fileLua.c_str(), funcLua.c_str()); + th.detach(); + //lua_call_task(fileLua.c_str(), funcLua.c_str()); + return true; + } + return false; +} + DiceModManager::DiceModManager() : helpdoc(HelpDoc) { } -string DiceModManager::format(string s, const map& dict, const char* mod_name = "") const +string DiceModManager::format(string s, const map& dict, const char* mod_name) const { //直接重定向 if (s[0] == '&') @@ -32,14 +77,32 @@ string DiceModManager::format(string s, const map& dict s.replace(l - 1, 1, ""); continue; } - string key = s.substr(l + 1, r - l - 1); - auto it = dict.find(key); - if (it != dict.end()) + string key = s.substr(l + 1, r - l - 1), val; + if (key.find("help:") == 0) { + key = key.substr(5); + val = fmt->format(fmt->get_help(key), dict); + } + else if (key.find("sample:") == 0) { + key = key.substr(7); + vector samples{ split(key,"|") }; + if (samples.empty())val = ""; + else + val = fmt->format(samples[RandomGenerator::Randint(0, samples.size() - 1)], dict); + } + else if (auto it = dict.find(key); it != dict.end()){ + val = format(it->second, dict, mod_name); + } + //局部屏蔽全局 + else if ((it = GlobalChar.find(key)) != GlobalChar.end()) { + val = it->second; + } + else if (auto func = strFuncs.find(key); func != strFuncs.end()) { - s.replace(l, r - l + 1, format(it->second, dict, mod_name)); - r += s.length() - len + 1; - len = s.length(); + val = func->second(); } + else continue; + s.replace(l, r - l + 1, val); + r = l + val.length(); //调用本mod词条 } return s; @@ -51,11 +114,61 @@ string DiceModManager::get_help(const string& key) const { return format(it->second, helpdoc); } - return "{strHlpNotFound}"; + return "{strHelpNotFound}"; +} + +struct help_sorter { + bool operator()(const string& _Left, const string& _Right) const { + if (fmt->cntHelp.count(_Right) && !fmt->cntHelp.count(_Left)) + return true; + else if (fmt->cntHelp.count(_Left) && !fmt->cntHelp.count(_Right)) + return false; + else if (fmt->cntHelp.count(_Left) && fmt->cntHelp.count(_Right) && fmt->cntHelp[_Left] != fmt->cntHelp[_Right]) + return fmt->cntHelp[_Left] < fmt->cntHelp[_Right]; + else if (_Left.length() != _Right.length()) { + return _Left.length() > _Right.length(); + } + else return _Left > _Right; + } +}; + +void DiceModManager::_help(const shared_ptr& job) { + if ((*job)["help_word"].empty()) { + job->reply(string(Dice_Short_Ver) + "\n" + GlobalMsg["strHlpMsg"]); + return; + } + else if (const auto it = helpdoc.find((*job)["help_word"]); it != helpdoc.end()) { + job->reply(format(it->second, helpdoc)); + } + else if (unordered_set keys = querier.search((*job)["help_word"]);!keys.empty()) { + if (keys.size() == 1) { + (*job)["redirect_key"] = *keys.begin(); + (*job)["redirect_res"] = get_help(*keys.begin()); + job->reply("{strHelpRedirect}"); + } + else { + std::priority_queue, help_sorter> qKey; + for (auto key : keys) { + qKey.emplace(".help " + key); + } + ResList res; + while (!qKey.empty()) { + res << qKey.top(); + qKey.pop(); + if (res.size() > 20)break; + } + (*job)["res"] = res.dot("/").show(1); + job->reply("{strHelpSuggestion}"); + } + } + else job->reply("{strHelpNotFound}"); + cntHelp[(*job)["help_word"]] += 1; + saveJMap(DiceDir / "user" / "HelpStatic.json",cntHelp); } void DiceModManager::set_help(const string& key, const string& val) { + if (!helpdoc.count(key))querier.insert(key); helpdoc[key] = val; } @@ -64,39 +177,121 @@ void DiceModManager::rm_help(const string& key) helpdoc.erase(key); } -int DiceModManager::load(string& strLog) +bool DiceModManager::listen_order(DiceJobDetail* msg) { + string nameOrder; + if(!gOrder.match_head(msg->strMsg, nameOrder))return false; + if (((FromMsg*)msg)->WordCensor()) { + return true; + } + msgorder[nameOrder].exec((FromMsg*)msg); + return true; +} +string DiceModManager::list_order() { + return msgorder.empty() ? "" : "扩展指令:" + listKey(msgorder); +} + +bool DiceModManager::call_task(const string& task) { + return taskcall[task].exec(); +} + +int DiceModManager::load(ResList* resLog) { - vector sFile; - vector sFileErr; - const int cntFile = listDir(DiceDir + "\\mod\\", sFile, true); + vector sModFile; + //读取mod + vector sModErr; + int cntFile = listDir(DiceDir / "mod" , sModFile); int cntItem{0}; - if (cntFile <= 0)return cntFile; - for (auto& filename : sFile) - { - nlohmann::json j = freadJson(filename); - if (j.is_null()) - { - sFileErr.push_back(filename.filename().string()); + if (cntFile > 0) { + for (auto& pathFile : sModFile) { + nlohmann::json j = freadJson(pathFile); + if (j.is_null()) { + sModErr.push_back(pathFile.filename().string()); + continue; + } + if (j.count("dice_build")) { + if (j["dice_build"] > Dice_Build) { + sModErr.push_back(pathFile.filename().string() + "(Dice版本过低)"); + continue; + } + } + if (j.count("helpdoc")) { + cntItem += readJMap(j["helpdoc"], helpdoc); + } + if (j.count("global_char")) { + cntItem += readJMap(j["global_char"], GlobalChar); + } + } + *resLog << "读取/mod/中的" + std::to_string(cntFile) + "个文件, 共" + std::to_string(cntItem) + "个条目"; + if (!sModErr.empty()) { + *resLog << "读取失败" + std::to_string(sModErr.size()) + "个:"; + for (auto& it : sModErr) { + *resLog << it; + } + } + } + //读取plugin + vector sLuaFile; + int cntLuaFile = listDir(DiceDir / "plugin", sLuaFile); + int cntOrder{ 0 }; + if (cntLuaFile <= 0)return cntLuaFile; + vector sLuaErr; + msgorder.clear(); + for (auto& pathFile : sLuaFile) { + string fileLua = pathFile.string(); + if (fileLua.rfind(".lua") != fileLua.length() - 4) { + sLuaErr.push_back(pathFile.filename().string()); continue; } - if (j.count("helpdoc")) - { - cntItem += readJMap(j["helpdoc"], helpdoc); + std::unordered_map mOrder; + int cnt = lua_readStringTable(fileLua.c_str(), "msg_order", mOrder); + if (cnt > 0) { + for (auto& [key, func] : mOrder) { + msgorder[::format(key, GlobalMsg)] = { fileLua,func }; + } + cntOrder += mOrder.size(); + } + else if (cnt < 0) { + sLuaErr.push_back(pathFile.filename().string()); + } + std::unordered_map mJob; + cnt = lua_readStringTable(fileLua.c_str(), "task_call", mJob); + if (cnt > 0) { + for (auto& [key, func] : mJob) { + taskcall[key] = { fileLua,func }; + } + cntOrder += mJob.size(); + } + else if (cnt < 0 + && *sLuaErr.rbegin() != pathFile.filename().string()) { + sLuaErr.push_back(pathFile.filename().string()); } } - strLog += "读取" + DiceDir + "\\mod\\中的" + std::to_string(cntFile) + "个文件, 共" + std::to_string(cntItem) + "个条目\n"; - if (!sFileErr.empty()) - { - strLog += "读取失败" + std::to_string(sFileErr.size()) + "个:\n"; - for (auto& it : sFileErr) - { - strLog += it + "\n"; + *resLog << "读取/plugin/中的" + std::to_string(cntLuaFile) + "个脚本, 共" + std::to_string(cntOrder) + "个指令"; + if (!sLuaErr.empty()) { + *resLog << "读取失败" + std::to_string(sLuaErr.size()) + "个:"; + for (auto& it : sLuaErr) { + *resLog << it; } } + std::thread factory(&DiceModManager::init,this); + factory.detach(); + if (cntHelp.empty()) { + cntHelp.reserve(helpdoc.size()); + loadJMap(DiceDir / "user" / "HelpStatic.json", cntHelp); + } return cntFile; } - +void DiceModManager::init() { + isIniting = true; + for (auto& [key, word] : helpdoc) { + querier.insert(key); + } + gOrder.build(msgorder); + isIniting = false; +} void DiceModManager::clear() { helpdoc.clear(); + msgorder.clear(); + taskcall.clear(); } diff --git a/Dice/DiceMod.h b/Dice/DiceMod.h index e118a88e..e433fc71 100644 --- a/Dice/DiceMod.h +++ b/Dice/DiceMod.h @@ -1,26 +1,31 @@ +#pragma once + /* * 资源模块 - * Copyright (C) 2019 String.Empty + * Copyright (C) 2019-2020 String.Empty */ -#pragma once + #include #include #include #include #include #include "STLExtern.hpp" +#include "SHKQuerier.h" +#include "SHKTrie.h" +#include "DiceSchedule.h" using std::string; using std::vector; using std::map; class DiceGenerator { - //冷却时间 - //int cold_time; - //单次抽取上限 - //int draw_limit = 1; - string expression; - //string cold_msg = "冷却时间中×"; + //冷却时间 + //int cold_time; + //单次抽取上限 + //int draw_limit = 1; + string expression; + //string cold_msg = "冷却时间中×"; public: string getExp() { return expression; } }; @@ -31,39 +36,83 @@ class BaseDeck vector cards; }; +class FromMsg; +class DiceMsgOrder { + enum class OrderType{Nil,Lua}; + //仅支持lua + OrderType type{ OrderType::Nil }; + string fileLua; + string funcLua; +public: + DiceMsgOrder() = default; + DiceMsgOrder(const string& file, const string& func): fileLua(file), funcLua(func){ + type = OrderType::Lua; + } + bool exec(FromMsg*); + bool exec(); +}; + class DiceMod { - string mod_name; - map m_helpdoc; - /*map> m_private_deck; - map> m_public_deck; - map m_generator;*/ +protected: + string mod_name; + string mod_author; + string mod_ver; + unsigned int mod_build{ 0 }; + unsigned int mod_Dice_build{ 0 }; + using dir = map; + dir mod_helpdoc; + map> mod_public_deck; + using orders = map; + orders mod_msg_order; + /*map m_generator;*/ public: + DiceMod() = default; + friend class DiceModFactory; +}; - DiceMod(string name, - map helpdoc /*, - map> private_deck, - map> public_deck, - map generator*/ - ) : mod_name(std::move(name)), m_helpdoc(std::move(helpdoc)) - /*,m_private_deck(private_deck),m_public_deck(public_deck),m_generator(generator)*/ - { - } +#define MOD_BUILD(TYPE, MEM) DiceModFactory& MEM(const TYPE& val){ \ + mod_##MEM = val; \ + return *this; \ + } +class DiceModFactory :public DiceMod { +public: + DiceModFactory() {} + MOD_BUILD(string, name) + MOD_BUILD(string, author) + MOD_BUILD(string, ver) + MOD_BUILD(unsigned int, build) + MOD_BUILD(unsigned int, Dice_build) + MOD_BUILD(dir, helpdoc) + MOD_BUILD(orders, msg_order) }; +class ResList; class DiceModManager { map mNameIndex; map helpdoc; + map msgorder; + map taskcall; + WordQuerier querier; + TrieG gOrder; public: DiceModManager(); friend void loadData(); - string format(string, const map&, const char*) const; + bool isIniting{ false }; + string format(string, const map&, const char* mod_name = "") const; + unordered_mapcntHelp; [[nodiscard]] string get_help(const string&) const; + void _help(const shared_ptr&); void set_help(const string&, const string&); void rm_help(const string&); - int load(string&); + + bool listen_order(DiceJobDetail*); + bool call_task(const string&); + string list_order(); + int load(ResList*); + void init(); void clear(); }; -inline std::unique_ptr fmt; +inline std::shared_ptr fmt; diff --git a/Dice/DiceMsgSend.cpp b/Dice/DiceMsgSend.cpp index c95e058c..562847f6 100644 --- a/Dice/DiceMsgSend.cpp +++ b/Dice/DiceMsgSend.cpp @@ -24,13 +24,13 @@ #include #include #include -#include "CQAPI_EX.h" +#include "DDAPI.h" #include "DiceMsgSend.h" #include "MsgFormat.h" #include "GlobalVar.h" #include "DiceConsole.h" using namespace std; -using namespace CQ; + // 消息发送存储结构体 struct msg_t @@ -80,17 +80,25 @@ void SendMsg() } if (!msg.msg.empty()) { + if (int pos = msg.msg.find_first_not_of(" \t\r\n"); pos && pos != string::npos) { + msg.msg = msg.msg.substr(pos); + } + if (int pos = msg.msg.find('\f'); pos != string::npos) + { + AddMsgToQueue(msg.msg.substr(pos + 1), msg.target_id, msg.msg_type); + msg.msg = msg.msg.substr(0, pos); + } if (msg.msg_type == msgtype::Private) { - sendPrivateMsg(msg.target_id, msg.msg); + DD::sendPrivateMsg(msg.target_id, msg.msg); } else if (msg.msg_type == msgtype::Group) { - sendGroupMsg(msg.target_id, msg.msg); + DD::sendGroupMsg(msg.target_id, msg.msg); } else { - sendDiscussMsg(msg.target_id, msg.msg); + DD::sendDiscussMsg(msg.target_id, msg.msg); } } if (msgQueue.size() > 2)this_thread::sleep_for(chrono::milliseconds(console["SendIntervalBusy"])); diff --git a/Dice/DiceMsgSend.h b/Dice/DiceMsgSend.h index 8bdabc7e..c4b7583b 100644 --- a/Dice/DiceMsgSend.h +++ b/Dice/DiceMsgSend.h @@ -1,3 +1,5 @@ +#pragma once + /* * _______ ________ ________ ________ __ * | __ \ |__ __| | _____| | _____| | | @@ -20,14 +22,14 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#pragma once + #ifndef DICE_MSG_SEND #define DICE_MSG_SEND #include -#include "CQMsgSend.h" -using chatType = std::pair; -std::ifstream& operator>>(std::ifstream& fin, CQ::msgtype& t); +enum class msgtype : int { Private = 0, Group = 1, Discuss = 2 }; +using chatType = std::pair; +std::ifstream& operator>>(std::ifstream& fin, msgtype& t); std::ifstream& operator>>(std::ifstream& fin, chatType& ct); std::ofstream& operator<<(std::ofstream& fout, const chatType& ct); /* @@ -37,7 +39,7 @@ std::ofstream& operator<<(std::ofstream& fout, const chatType& ct); * long long target_id 目标ID(QQ,群号或讨论组uin) * MsgType msg_type 消息类型 */ -void AddMsgToQueue(const std::string& msg, long long target_id, CQ::msgtype msg_type = CQ::msgtype::Private); +void AddMsgToQueue(const std::string& msg, long long target_id, msgtype msg_type = msgtype::Private); void AddMsgToQueue(const std::string& msg, chatType ct); /* diff --git a/Dice/DiceNetwork.cpp b/Dice/DiceNetwork.cpp index 56d3ac43..6e00411a 100644 --- a/Dice/DiceNetwork.cpp +++ b/Dice/DiceNetwork.cpp @@ -20,13 +20,14 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include - -#ifdef _MSC_VER #pragma comment(lib, "WinInet.lib") -#endif /*_MSC_VER*/ +#else +#include +#endif #include #include "GlobalVar.h" @@ -36,8 +37,19 @@ namespace Network { +#ifndef _WIN32 + CURLcode lastError; + + size_t curlWriteToString(void *contents, size_t size, size_t nmemb, std::string *s) + { + size_t newLength = size*nmemb; + s->append((char*)contents, newLength); + return newLength; + } +#endif std::string getLastErrorMsg() { +#ifdef _WIN32 DWORD dwError = GetLastError(); if (dwError == ERROR_INTERNET_EXTENDED_ERROR) { @@ -83,20 +95,24 @@ namespace Network return ret; } return GlobalMsg["strUnableToGetErrorMsg"]; +#else + return curl_easy_strerror(lastError); +#endif } bool POST(const char* const serverName, const char* const objectName, const unsigned short port, char* const frmdata, std::string& des) { +#ifdef _WIN32 const char* acceptTypes[] = {"*/*", nullptr}; const char* header = "Content-Type: application/x-www-form-urlencoded"; const HINTERNET hInternet = InternetOpenA(DiceRequestHeader, INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0); - const HINTERNET hConnect = InternetConnectA(hInternet, serverName, port, nullptr, nullptr, - INTERNET_SERVICE_HTTP, 0, 0); - const HINTERNET hRequest = HttpOpenRequestA(hConnect, "POST", objectName, "HTTP/1.1", nullptr, acceptTypes, 0, - 0); + const HINTERNET hConnect = InternetConnectA(hInternet, serverName, port, nullptr, nullptr, + INTERNET_SERVICE_HTTP, 0, 0); + const HINTERNET hRequest = HttpOpenRequestA(hConnect, "POST", objectName, "HTTP/1.1", nullptr, acceptTypes, 0, + 0); const BOOL res = HttpSendRequestA(hRequest, header, strlen(header), frmdata, strlen(frmdata)); @@ -188,17 +204,40 @@ namespace Network InternetCloseHandle(hConnect); InternetCloseHandle(hInternet); return false; +#else + CURL *curl; + curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_URL, (std::string("http://") + serverName + ":" + std::to_string(port) + objectName).c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, frmdata); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteToString); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &des); + + lastError = curl_easy_perform(curl); + if (lastError != CURLE_OK) + { + des = getLastErrorMsg(); + } + + curl_easy_cleanup(curl); + return lastError == CURLE_OK; + } + return false; +#endif } bool GET(const char* const serverName, const char* const objectName, const unsigned short port, std::string& des) { +#ifdef _WIN32 const char* acceptTypes[] = {"*/*", nullptr}; const HINTERNET hInternet = InternetOpenA(DiceRequestHeader, INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0); - const HINTERNET hConnect = InternetConnectA(hInternet, serverName, port, nullptr, nullptr, - INTERNET_SERVICE_HTTP, 0, 0); + const HINTERNET hConnect = InternetConnectA(hInternet, serverName, port, nullptr, nullptr, + INTERNET_SERVICE_HTTP, 0, 0); const HINTERNET hRequest = HttpOpenRequestA(hConnect, "GET", objectName, "HTTP/1.1", nullptr, acceptTypes, - INTERNET_FLAG_NO_CACHE_WRITE, 0); + INTERNET_FLAG_NO_CACHE_WRITE, 0); const BOOL res = HttpSendRequestA(hRequest, nullptr, 0, nullptr, 0); @@ -290,5 +329,27 @@ namespace Network InternetCloseHandle(hConnect); InternetCloseHandle(hInternet); return false; +#else + CURL *curl; + curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_URL, (std::string("http://") + serverName + ":" + std::to_string(port) + objectName).c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteToString); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &des); + + lastError = curl_easy_perform(curl); + if (lastError != CURLE_OK) + { + des = getLastErrorMsg(); + } + + curl_easy_cleanup(curl); + return lastError == CURLE_OK; + } + return false; +#endif } + } diff --git a/Dice/DiceNetwork.h b/Dice/DiceNetwork.h index 62e09c61..ab4670ab 100644 --- a/Dice/DiceNetwork.h +++ b/Dice/DiceNetwork.h @@ -1,3 +1,5 @@ +#pragma once + /* * _______ ________ ________ ________ __ * | __ \ |__ __| | _____| | _____| | | @@ -20,7 +22,7 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#pragma once + #ifndef DICE_NETWORK #define DICE_NETWORK #include diff --git a/Dice/DiceSchedule.cpp b/Dice/DiceSchedule.cpp new file mode 100644 index 00000000..b2047cb9 --- /dev/null +++ b/Dice/DiceSchedule.cpp @@ -0,0 +1,313 @@ +#include +#include +#include +#include +#include "DDAPI.h" +#include "GlobalVar.h" +#include "DiceJob.h" +#include "ManagerSystem.h" +#include "Jsonio.h" +#include "DiceSchedule.h" +#include "DiceNetwork.h" +#include "DiceMod.h" +#include "RandomGenerator.h" +#include +#include + +unordered_map mCommand = { + {"syscheck",check_system}, + {"autosave",auto_save}, + {"clrimage",clear_image}, + {"clrgroup",clear_group}, + {"lsgroup",list_group}, + {"reload",frame_reload}, + {"remake",frame_restart}, + {"die",cq_exit}, + {"heartbeat",cloud_beat}, + {"update",dice_update}, + {"cloudblack",dice_cloudblack}, + {"uplog",log_put} +}; + +DiceJobDetail::DiceJobDetail(const char* cmd, bool isFromSelf, unordered_map vars) :cmd_key(cmd), strVar(vars) { + if (isFromSelf)fromQQ = console.DiceMaid; +} + +void DiceJob::exec() { + if (auto it = mCommand.find(cmd_key); it != mCommand.end()) { + it->second(*this); + } + else return; +} +void DiceJob::echo(const std::string& msg) { + if (!fromChat.first)return; + switch (fromChat.second) { + case msgtype::Private: + DD::sendPrivateMsg(fromQQ, msg); + break; + case msgtype::Group: + DD::sendGroupMsg(fromChat.first, msg); + break; + case msgtype::Discuss: + DD::sendDiscussMsg(fromChat.first, msg); + break; + } +} +void DiceJob::reply(const std::string& msg) { + AddMsgToQueue(format(msg, GlobalMsg, strVar), fromChat); +} +void DiceJob::note(const std::string& strMsg, int note_lv = 0b1) { + ofstream fout(DiceDir / "audit" / ("log" + to_string(console.DiceMaid) + "_" + printDate() + ".txt"), ios::out | ios::app); + fout << printSTNow() << "\t" << note_lv << "\t" << printLine(strMsg) << std::endl; + fout.close(); + echo(strMsg); + string note = fromQQ ? getName(fromQQ) + strMsg : strMsg; + for (const auto& [ct, level] : console.NoticeList) { + if (!(level & note_lv) || pair(fromQQ, msgtype::Private) == ct || ct == fromChat)continue; + AddMsgToQueue(note, ct); + } +} + +// 待处理任务队列 +std::queue queueJob; +std::mutex mtQueueJob; +//std::condition_variable cvJob; +//std::condition_variable cvJobWaited; +//延时任务队列 +using waited_job = pair; +std::priority_queue,std::greater> queueJobWaited; +std::mutex mtJobWaited; + +void jobHandle() { + while (Enabled) { + //监听作业队列 + if (std::unique_lock lock_queue(mtQueueJob); !queueJob.empty()) { + DiceJob job(queueJob.front()); + queueJob.pop(); + lock_queue.unlock(); + job.exec(); + //cvJobWaited.notify_one(); + } + else{ + //cvJob.wait_for(lock_queue, 2s, []() {return !Enabled || !queueJob.empty(); }); + std::this_thread::sleep_for(1s); + } + } +} +void jobWait() { + while (Enabled) { + //检查定时作业 + if (std::unique_lock lock_queue(mtJobWaited); !queueJobWaited.empty() && queueJobWaited.top().first <= time(NULL)) { + sch.push_job(queueJobWaited.top().second); + queueJobWaited.pop(); + } + else { + //cvJobWaited.wait_for(lock_queue, 1s); + std::this_thread::sleep_for(1s); + } + today->daily_clear(); + } +} + +//将任务加入执行队列 +void DiceScheduler::push_job(const DiceJobDetail& job) { + if (!Enabled)return; + { + std::unique_lock lock_queue(mtQueueJob); + queueJob.push(job); + } + //cvJob.notify_one(); +} +void DiceScheduler::push_job(const char* job_name, bool isSelf, unordered_mapvars) { + if (!Enabled)return; + { + std::unique_lock lock_queue(mtQueueJob); + queueJob.emplace(job_name, isSelf, vars); + } + //cvJob.notify_one(); +} +//将任务加入等待队列 +void DiceScheduler::add_job_for(unsigned int waited, const DiceJobDetail& job) { + if (!Enabled)return; + std::unique_lock lock_queue(mtJobWaited); + queueJobWaited.emplace(time(nullptr) + waited, job); +} +void DiceScheduler::add_job_for(unsigned int waited, const char* job_name) { + if (!Enabled)return; + std::unique_lock lock_queue(mtJobWaited); + queueJobWaited.emplace(time(nullptr) + waited, job_name); +} + +void DiceScheduler::add_job_until(time_t cloc, const DiceJobDetail& job) { + if (!Enabled)return; + std::unique_lock lock_queue(mtJobWaited); + queueJobWaited.emplace(cloc, job); +} +void DiceScheduler::add_job_until(time_t cloc, const char* job_name) { + if (!Enabled)return; + std::unique_lock lock_queue(mtJobWaited); + queueJobWaited.emplace(cloc, job_name); +} + +bool DiceScheduler::is_job_cold(const char* cmd) { + return untilJobs[cmd] > time(NULL); +} +void DiceScheduler::refresh_cold(const char* cmd, time_t until) { + untilJobs[cmd] = until; +} + +void DiceScheduler::start() { + threads(jobHandle); + threads(jobWait); + push_job("heartbeat"); + add_job_for(60, "syscheck"); + if (console["AutoSaveInterval"] > 0)add_job_for(console["AutoSaveInterval"] * 60, "autosave"); + if (console["AutoFrameRemake"] > 0) + add_job_for(console["AutoFrameRemake"] * 60 * 60, "remake"); + else add_job_for(60 * 60, "remake"); + if (console["CloudBlackShare"] > 0) + add_job_for(12 * 60 * 60, "cloudblack"); +} +void DiceScheduler::end() { +} + +int DiceToday::getJrrp(long long qq) { + if (cntUser.count(qq) && cntUser[qq].count("jrrp")) + return cntUser[qq]["jrrp"]; + string frmdata = "QQ=" + to_string(console.DiceMaid) + "&v=20190114" + "&QueryQQ=" + to_string(qq); + string res; + if (Network::POST("api.kokona.tech", "/jrrp", 5555, frmdata.data(), res)) { + return cntUser[qq]["jrrp"] = stoi(res); + } + else { + if (!cntUser[qq].count("jrrp_local")) { + cntUser[qq]["jrrp_local"] = RandomGenerator::Randint(1, 100); + console.log(getMsg("strJrrpErr", + { {"res", res} } + ), 1); + } + return cntUser[qq]["jrrp_local"]; + } +} + +void DiceToday::daily_clear() { + time_t tt = time(nullptr); +#ifdef _MSC_VER + localtime_s(&stNow, &tt); +#else + localtime_r(&tt, &stNow); +#endif + if (stToday.tm_mday != stNow.tm_mday) { + stToday.tm_mday = stNow.tm_mday; + cntGlobal.clear(); + cntUser.clear(); + } +} + +void DiceToday::save() { + json jFile; + try { + jFile["date"] = { stToday.tm_year + 1900,stToday.tm_mon + 1,stToday.tm_mday }; + jFile["global"] = GBKtoUTF8(cntGlobal); + jFile["user_cnt"] = GBKtoUTF8(cntUser); + fwriteJson(pathFile, jFile); + } catch (...) { + console.log("每日记录保存失败:json错误!", 0b10); + } +} +void DiceToday::load() { + json jFile = freadJson(pathFile); + if (jFile.is_null()) { + time_t tt = time(nullptr); +#ifdef _MSC_VER + localtime_s(&stToday, &tt); +#else + localtime_r(&tt, &stToday); +#endif + return; + } + if (jFile.count("date")) { + jFile["date"][0].get_to(stToday.tm_year); + stToday.tm_year -= 1900; + jFile["date"][1].get_to(stToday.tm_mon); + stToday.tm_mon -= 1; + jFile["date"][2].get_to(stToday.tm_mday); + } + if (jFile.count("global")) { + jFile["global"].get_to(cntGlobal); + cntGlobal = UTF8toGBK(cntGlobal); + } + if (jFile.count("user_cnt")) { + jFile["user_cnt"].get_to(cntUser); + cntUser = UTF8toGBK(cntUser); + } +} + +string printTTime(time_t tt) { + char tm_buffer[20]; + tm t{}; + if(!tt) return "1970-00-00 00:00:00"; +#ifdef _MSC_VER + auto ret = localtime_s(&t, &tt); + if(ret) return "1970-00-00 00:00:00"; +#else + auto ret = localtime_r(&tt, &t); + if(!ret) return "1970-00-00 00:00:00"; +#endif + strftime(tm_buffer, 20, "%Y-%m-%d %H:%M:%S", &t); + return tm_buffer; +} + +//简易计时器 +tm stTmp{}; +void ConsoleTimer() { + Console::Clock clockNow{ stNow.tm_hour,stNow.tm_min }; + while (Enabled) { + time_t tt = time(nullptr); +#ifdef _MSC_VER + localtime_s(&stNow, &tt); +#else + localtime_r(&tt, &stNow); +#endif + //分钟时点变动 + if (stTmp.tm_min != stNow.tm_min) { + stTmp = stNow; + clockNow = { stNow.tm_hour, stNow.tm_min }; + for (const auto& [clock, eve_type] : multi_range(console.mWorkClock, clockNow)){ + switch (Console::mClockEvent[eve_type]) { + case 1: + if (console["DisabledGlobal"]) { + console.set("DisabledGlobal", 0); + console.log(getMsg("strClockToWork"), 0b10000, ""); + } + break; + case 0: + if (!console["DisabledGlobal"]) { + console.set("DisabledGlobal", 1); + console.log(getMsg("strClockOffWork"), 0b10000, ""); + } + break; + case 2: + dataBackUp(); + console.log(GlobalMsg["strSelfName"] + "定时保存完成√", 1, printSTime(stTmp)); + break; + case 3: + sch.push_job("clrgroup", true, { + {"clear_mode","black"} + }); + if (int cnt{ clearGroup() }) { + console.log("已清理过期群记录" + to_string(cnt) + "条", 1, printSTime(stTmp)); + } + if (int cnt{ clearUser() }) { + console.log("已清理无效或过期用户记录" + to_string(cnt) + "条", 1, printSTime(stTmp)); + } + break; + default: + fmt->call_task(eve_type); + break; + } + } + } + std::this_thread::sleep_for(100ms); + } +} \ No newline at end of file diff --git a/Dice/DiceSchedule.h b/Dice/DiceSchedule.h new file mode 100644 index 00000000..5af7d93e --- /dev/null +++ b/Dice/DiceSchedule.h @@ -0,0 +1,96 @@ +#pragma once + +/* + * Copyright (C) 2019-2020 String.Empty + * 处理定时事件 + * 处理不能即时完成的指令 + */ + +#include +#include +#include +#include "DiceMsgSend.h" +#include "Jsonio.h" +#include "json.hpp" + +using std::string; +using std::map; +using std::unordered_map; +using std::shared_ptr; + +struct DiceJobDetail : public std::enable_shared_from_this { + long long fromQQ = 0; + chatType fromChat; + string cmd_key; + string strMsg; + time_t fromTime = time(nullptr); + size_t cntExec{ 0 }; + //临时变量库 + unordered_map strVar = {}; + DiceJobDetail(const char* cmd, bool isFromSelf = false, unordered_map vars = {}); + DiceJobDetail(long long qq, chatType ct, std::string msg = "", const char* cmd = "") + :fromQQ(qq), fromChat(ct), strMsg(msg),cmd_key(cmd) { + } + virtual void reply(const string&, bool = true) {} + string& operator[](const char* key){ + return strVar[key]; + } + bool operator<(const DiceJobDetail& other)const { + return cmd_key < other.cmd_key; + } +}; + +class DiceJob : public DiceJobDetail { + enum class Renum { NIL, Retry_For, Retry_Until }; +public: + DiceJob(DiceJobDetail detail) :DiceJobDetail(detail) {} + Renum ren = Renum::NIL; + void exec(); + void echo(const std::string&); + void reply(const std::string&); + void note(const std::string&, int); +}; + +class DiceScheduler { + //事件冷却期 + unordered_map untilJobs; +public: + void start(); + void end(); + void push_job(const DiceJobDetail&); + void push_job(const char*, bool = false, unordered_map = {}); + void add_job_for(unsigned int, const DiceJobDetail&); + void add_job_for(unsigned int, const char*); + void add_job_until(time_t, const DiceJobDetail&); + void add_job_until(time_t, const char*); + bool is_job_cold(const char*); + void refresh_cold(const char*, time_t); +}; +inline DiceScheduler sch; + +typedef void (*cmd)(DiceJob&); + +//今日记录 +class DiceToday { + tm stToday; + std::filesystem::path pathFile; + unordered_mapcntGlobal; + unordered_map>cntUser; +public: + DiceToday(const std::filesystem::path& path) :pathFile(path) { + load(); + } + void load(); + void save(); + void set(long long qq, const string& key, int cnt) { cntUser[qq][key] = cnt; save(); } + void inc(const string& key) { cntGlobal[key]++; save(); } + void inc(long long qq, const string& key, int cnt = 1) { cntUser[qq][key] += cnt; save(); } + int& get(const string& key) { return cntGlobal[key]; } + int& get(long long qq, const string& key) { return cntUser[qq][key]; } + int getJrrp(long long qq); + size_t cnt(const string& key = "") { return cntUser.size(); } + void daily_clear(); +}; +inline std::unique_ptr today; + +string printTTime(time_t tt); \ No newline at end of file diff --git a/Dice/DiceSession.cpp b/Dice/DiceSession.cpp index cd432fb8..3c7d4146 100644 --- a/Dice/DiceSession.cpp +++ b/Dice/DiceSession.cpp @@ -4,27 +4,38 @@ * Copyright (C) 2019-2020 String.Empty */ #include +#include #include "Jsonio.h" #include "DiceSession.h" #include "MsgFormat.h" #include "EncodingConvert.h" #include "DiceEvent.h" +#include "CardDeck.h" +#include "RandomGenerator.h" +#include "DDAPI.h" std::shared_mutex sessionMutex; -int DiceSession::table_add(string key, int prior, string item) +bool DiceSession::table_del(const string& tab, const string& item) { + if (!mTable.count(tab) || !mTable[tab].count(item))return false; + mTable[tab].erase(item); + update(); + return true; +} + +int DiceSession::table_add(const string& key, int prior, const string& item) { - mTable[key].emplace(item, prior); + mTable[key][item] = prior; update(); return 0; } -string DiceSession::table_prior_show(string key) const +string DiceSession::table_prior_show(const string& key) const { return PriorList(mTable.find(key)->second).show(); } -bool DiceSession::table_clr(string key) +bool DiceSession::table_clr(const string& key) { if (const auto it = mTable.find(key); it != mTable.end()) { @@ -35,7 +46,7 @@ bool DiceSession::table_clr(string key) return false; } -int DiceSession::ob_enter(FromMsg* msg) +void DiceSession::ob_enter(FromMsg* msg) { if (sOB.count(msg->fromQQ)) { @@ -45,12 +56,11 @@ int DiceSession::ob_enter(FromMsg* msg) { sOB.insert(msg->fromQQ); msg->reply(GlobalMsg["strObEnter"]); + update(); } - update(); - return 0; } -int DiceSession::ob_exit(FromMsg* msg) +void DiceSession::ob_exit(FromMsg* msg) { if (sOB.count(msg->fromQQ)) { @@ -62,10 +72,9 @@ int DiceSession::ob_exit(FromMsg* msg) msg->reply(GlobalMsg["strObExitAlready"]); } update(); - return 0; } -int DiceSession::ob_list(FromMsg* msg) const +void DiceSession::ob_list(FromMsg* msg) const { if (sOB.empty())msg->reply(GlobalMsg["strObListEmpty"]); else @@ -77,10 +86,9 @@ int DiceSession::ob_list(FromMsg* msg) const } msg->reply(GlobalMsg["strObList"] + res.linebreak().show()); } - return 0; } -int DiceSession::ob_clr(FromMsg* msg) +void DiceSession::ob_clr(FromMsg* msg) { if (sOB.empty())msg->reply(GlobalMsg["strObListEmpty"]); else @@ -89,23 +97,416 @@ int DiceSession::ob_clr(FromMsg* msg) msg->reply(GlobalMsg["strObListClr"]); } update(); - return 0; } -void DiceSession::save() const -{ - mkDir(DiceDir + "\\user\\session"); - ofstream fout(DiceDir + R"(\user\session\)" + to_string(room) + ".json"); - if (!fout) - { - console.log("群开团信息保存失败:" + DiceDir + R"(\user\session\)" + to_string(room), 1); +void DiceSession::log_new(FromMsg* msg) { + std::error_code ec; + std::filesystem::create_directory(DiceDir / logger.dirLog, ec); + logger.tStart = time(nullptr); + logger.isLogging = true; + logger.fileLog = (type == "solo") + ? ("qq_" + to_string(msg->fromQQ) + "_" + to_string(logger.tStart) + ".txt") + : ("group_" + to_string(msg->fromGroup) + "_" + to_string(logger.tStart) + ".txt"); + logger.pathLog = DiceDir / logger.dirLog / logger.fileLog; + //先发消息后插入 + msg->reply(GlobalMsg["strLogNew"]); + LogList.insert(room); + update(); +} +void DiceSession::log_on(FromMsg* msg) { + if (!logger.tStart) { + log_new(msg); + return; + } + if (logger.isLogging) { + msg->reply(GlobalMsg["strLogOnAlready"]); + return; + } + logger.isLogging = true; + msg->reply(GlobalMsg["strLogOn"]); + LogList.insert(room); + update(); +} +void DiceSession::log_off(FromMsg* msg) { + if (!logger.tStart) { + msg->reply(GlobalMsg["strLogNullErr"]); + return; + } + if (!logger.isLogging) { + msg->reply(GlobalMsg["strLogOffAlready"]); + return; + } + logger.isLogging = false; + //先擦除后发消息 + LogList.erase(room); + msg->reply(GlobalMsg["strLogOff"]); + update(); +} +void DiceSession::log_end(FromMsg* msg) { + if (!logger.tStart) { + msg->reply(GlobalMsg["strLogNullErr"]); + return; + } + LogList.erase(room); + logger.isLogging = false; + logger.tStart = 0; + if (std::filesystem::path pathFile(log_path()); !std::filesystem::exists(pathFile)) { + msg->reply(GlobalMsg["strLogEndEmpty"]); + return; + } + msg->strVar["log_file"] = logger.fileLog; + msg->strVar["log_path"] = UTF8toGBK(log_path().u8string()); + msg->reply(GlobalMsg["strLogEnd"]); + update(); + msg->cmd_key = "uplog"; + sch.push_job(*msg); +} +std::filesystem::path DiceSession::log_path()const { + return logger.pathLog; +} + + +void DiceSession::link_new(FromMsg* msg) { + string strType = msg->readPara(); + string strID = msg->readDigit(); + if (strID.empty()) { + msg->reply(GlobalMsg["strLinkNotFound"]); + return; + } + long long id = stoll(strID); + if (strType == "qq" || strType == "q") { + id = ~id; + } + else if (!ChatList.count(id)) { + msg->reply(GlobalMsg["strLinkNotFound"]); + return; + } + linker.linkFwd = id; + //重置已存在的链接 + if (linker.isLinking) { + LinkList.erase(room); + LinkList.erase(linker.linkFwd); + } + //占线不可用 + if (LinkList.count(room)) { + msg->reply(GlobalMsg["strLinkedAlready"]); + } + else if (LinkList.count(linker.linkFwd)) { + msg->reply(GlobalMsg["strLinkBusy"]); + } + else { + linker.typeLink = msg->strVar["option"]; + LinkList[room] = { linker.linkFwd ,linker.typeLink != "from" }; + LinkList[linker.linkFwd] = { room ,linker.typeLink != "to" }; + linker.isLinking = true; + msg->reply(GlobalMsg["strLinked"]); + update(); + } +} +void DiceSession::link_start(FromMsg* msg) { + if (linker.linkFwd) { + if (LinkList.count(room)) { + msg->reply(GlobalMsg["strLinkingAlready"]); + } + else if (LinkList.count(linker.linkFwd)) { + msg->reply(GlobalMsg["strLinkBusy"]); + } + else { + LinkList[room] = { linker.linkFwd ,linker.typeLink != "from" }; + LinkList[linker.linkFwd] = { room ,linker.typeLink != "to" }; + linker.isLinking = true; + msg->reply(GlobalMsg["strLinked"]); + update(); + } + } + else { + msg->reply(GlobalMsg["strLinkNotFound"]); + } +} +void DiceSession::link_close(FromMsg* msg) { + if (auto link = LinkList.find(room); link != LinkList.end()) { + linker.isLinking = false; + if (gm->mSession.count(link->second.first)) { + gm->session(link->second.first).linker.isLinking = false; + gm->session(link->second.first).update(); + } + LinkList.erase(link->second.first); + LinkList.erase(link); + msg->reply(GlobalMsg["strLinkClose"]); + update(); + } + else { + msg->reply(GlobalMsg["strLinkCloseAlready"]); + } +} + +DeckInfo::DeckInfo(const vector& deck) :meta(deck) { + init(); +} +void DeckInfo::init() { + idxs.reserve(meta.size()); + for (size_t idx = 0; idx < meta.size(); idx++) { + size_t l, r; + size_t cnt(1); + if ((l = meta[idx].find("::")) != string::npos && (r = meta[idx].find("::", l + 2)) != string::npos) { + string strCnt = CardDeck::draw(meta[idx].substr(l + 2, r - l - 2)); + if (strCnt.length() < 4 && !strCnt.empty() && isdigit(static_cast(strCnt[0])) && strCnt != "0") { + meta[idx].erase(meta[idx].begin(), meta[idx].begin() + r + 2); + cnt = stoi(strCnt); + } + } + while (cnt--) { + idxs.push_back(idx); + } + } + sizRes = idxs.size(); +} +void DeckInfo::reset() { + sizRes = idxs.size(); +} +string DeckInfo::draw() { + if (idxs.empty())return{}; + size_t idx = RandomGenerator::Randint(0, --sizRes); + size_t res = idxs[idx]; + idxs[idx] = idxs[sizRes]; + idxs[sizRes] = res; + return CardDeck::draw(meta[res]); +} + +void DiceSession::deck_set(FromMsg* msg) { + string& key{ msg->strVar["deck_name"] = msg->readAttrName() }; + size_t pos = msg->strMsg.find('=', msg->intMsgCnt); + string& strCiteDeck{ msg->strVar["deck_cited"] = pos == string::npos + ? key + : (++msg->intMsgCnt, msg->readAttrName()) }; + if (key.empty()) { + msg->reply(GlobalMsg["strDeckNameEmpty"]); + } + else if (decks.size() > 9 && !decks.count(key)) { + msg->reply(GlobalMsg["strDeckListFull"]); + } + else { + vector DeckSet = {}; + if ((strCiteDeck == "群成员" || (strCiteDeck == "member" && !(strCiteDeck = "群成员").empty())) && msg->fromChat.second == msgtype::Group) { + + if (auto list{ DD::getGroupMemberList(msg->fromGroup) }; list.empty()) { + msg->reply("群成员列表获取失败×"); + } + else for (auto each : list) { + DeckSet.push_back(printQQ(each)); + } + decks[key] = DeckSet; + } + else if (strCiteDeck == "range") { + string strL = msg->readDigit(); + string strR = msg->readDigit(); + string strStep = msg->readDigit(); //步长,不支持反向 + if (strL.empty()) { + msg->reply(GlobalMsg["strRangeEmpty"]); + return; + } + else if (strL.length() > 16 || strR.length() > 16) { + msg->reply(GlobalMsg["strOutRange"]); + return; + } + else { + long long llBegin{ stoll(strL) }; + long long llEnd{ strR.empty() ? 0 : stoll(strR) }; + long long llStep{ strStep.empty() ? 1 : stoll(strStep) }; + if (llBegin == llEnd) { + msg->reply(GlobalMsg["strRangeEmpty"]); + return; + } + else if (llBegin > llEnd) { + long long temp = llBegin; + llBegin = llEnd; + llEnd = temp; + } + if ((llEnd - llBegin) > 1000 * llStep) { + msg->reply(GlobalMsg["strOutRange"]); + return; + } + for (long long card = llBegin; card <= llEnd; card += llStep) { + DeckSet.emplace_back(to_string(card)); + } + decks[key] = DeckSet; + strCiteDeck += to_string(llBegin) + "~" + *(--DeckSet.end()); + } + } + else if (!CardDeck::mPublicDeck.count(strCiteDeck) || strCiteDeck[0] == '_') { + msg->reply(GlobalMsg["strDeckCiteNotFound"]); + return; + } + else { + decks[key] = CardDeck::mPublicDeck[strCiteDeck]; + } + if (key == strCiteDeck)msg->reply(GlobalMsg["strDeckSet"], { msg->strVar["deck_name"] }); + else msg->reply(GlobalMsg["strDeckSetRename"]); + update(); + } +} +void DiceSession::deck_new(FromMsg* msg) { + string& key{ msg->strVar["deck_name"] }; + size_t pos = msg->strMsg.find('=', msg->intMsgCnt); + if (pos == string::npos) { + key = "new"; + } + else { + key = msg->readAttrName(); + msg->intMsgCnt = pos + 1; + } + if (decks.size() > 9 && !decks.count(key)) { + msg->reply(GlobalMsg["strDeckListFull"]); + } + else { + vector DeckSet = {}; + msg->readItems(DeckSet); + if (DeckSet.empty()) { + msg->reply(GlobalMsg["strDeckNewEmpty"]); + return; + } + else if (DeckSet.size() > 256) { + msg->reply(GlobalMsg["strDeckOversize"]); + return; + } + DeckInfo deck{ DeckSet }; + if (deck.idxs.size() > 1024) { + msg->reply(GlobalMsg["strDeckOversize"]); + return; + } + decks[key] = std::move(deck); + msg->reply(GlobalMsg["strDeckNew"]); + update(); + } +} +string DiceSession::deck_draw(const string& key) { + if (decks.count(key)) { + if (!decks[key].sizRes)return getMsg("strDeckRestEmpty", { {"deck_name",key} }); + return decks[key].draw(); + } + else if (CardDeck::mPublicDeck.count(key)) { + vector& deck = CardDeck::mPublicDeck[key]; + return CardDeck::draw(deck[RandomGenerator::Randint(0, deck.size() - 1)]); + } + return "{key}"; +} +void DiceSession::_draw(FromMsg* msg) { + shared_ptr job{ msg->shared_from_this() }; + if ((*job)["deck_name"].empty())(*job)["deck_name"] = msg->readAttrName(); + DeckInfo& deck = decks[(*job)["deck_name"]]; + int intCardNum = 1; + switch (msg->readNum(intCardNum)) { + case 0: + if (intCardNum == 0) { + msg->reply(GlobalMsg["strNumCannotBeZero"]); + return; + } + break; + case -1: break; + case -2: + msg->reply(GlobalMsg["strParaIllegal"]); + console.log("提醒:" + printQQ(msg->fromQQ) + "对" + GlobalMsg["strSelfName"] + "使用了非法指令参数\n" + msg->strMsg, 1, + printSTNow()); + return; + } + ResList Res; + while (deck.sizRes && intCardNum--) { + Res << deck.draw(); + if (!deck.sizRes)break; + } + if(!Res.empty()){ + (*job)["res"] = Res.dot("|").show(); + (*job)["cnt"] = to_string(Res.size()); + msg->initVar({ (*job)["pc"], (*job)["res"] }); + if (msg->strVar.count("hidden")) { + msg->reply(GlobalMsg["strDrawHidden"]); + msg->replyHidden(GlobalMsg["strDrawCard"]); + } + else + msg->reply(GlobalMsg["strDrawCard"]); + update(); + } + if (!deck.sizRes) { + msg->reply(GlobalMsg["strDeckRestEmpty"]); + } +} +void DiceSession::deck_show(FromMsg* msg) { + if (decks.empty()) { + msg->reply(GlobalMsg["strDeckListEmpty"]); return; } + string& strDeckName{ msg->strVar["deck_name"] = msg->readAttrName() }; + //默认列出所有牌堆 + if (strDeckName.empty()) { + ResList res; + for (auto& [key, val] : decks) { + res << key + "[" + to_string(val.sizRes) + "/" + to_string(val.idxs.size()) + "]"; + } + msg->strVar["res"] = res.show(); + msg->reply(GlobalMsg["strDeckListShow"]); + } + else { + if (decks.count(strDeckName)) { + DeckInfo& deck{ decks[strDeckName] }; + ResList residxs; + size_t idx(0); + while (idx < deck.sizRes) { + residxs << deck.meta[deck.idxs[idx++]]; + } + msg->strVar["deck_rest"] = residxs.dot(" | ").show(); + msg->reply(GlobalMsg["strDeckRestShow"]); + } + else { + msg->reply(GlobalMsg["strDeckNotFound"]); + } + } +} +void DiceSession::deck_reset(FromMsg* msg) { + string& key{ msg->strVar["deck_name"] = msg->readAttrName() }; + if (key.empty())key = msg->readDigit(); + if (key.empty()) { + msg->reply(GlobalMsg["strDeckNameEmpty"]); + } + else if (!decks.count(key)) { + msg->reply(GlobalMsg["strDeckNotFound"]); + } + else { + decks[key].reset(); + msg->reply(GlobalMsg["strDeckRestReset"]); + update(); + } +} +void DiceSession::deck_del(FromMsg* msg) { + string& key{ msg->strVar["deck_name"] = msg->readAttrName() }; + if (key.empty())key = msg->readDigit(); + if (key.empty()) { + msg->reply(GlobalMsg["strDeckNameEmpty"]); + } + else if (!decks.count(key)) { + msg->reply(GlobalMsg["strDeckNotFound"]); + } + else { + decks.erase(key); + msg->reply(GlobalMsg["strDeckDelete"]); + update(); + } +} +void DiceSession::deck_clr(FromMsg* msg) { + decks.clear(); + msg->reply(GlobalMsg["strDeckListClr"]); + update(); +} + +std::mutex exSessionSave; + +void DiceSession::save() const +{ + std::error_code ec; + std::filesystem::create_directories(DiceDir / "user" / "session", ec); + std::filesystem::path fpFile = (type == "solo") + ? (DiceDir / "user" / "session" / ("Q" + to_string(~room) + ".json") ) + : (DiceDir / "user" / "session" / (to_string(room) + ".json")); nlohmann::json jData; - jData["type"] = "Simple"; - jData["room"] = room; - jData["create_time"] = tCreate; - jData["update_time"] = tUpdate; if (!sOB.empty())jData["observer"] = sOB; if (!mTable.empty()) for (auto& [key, table] : mTable) @@ -116,15 +517,60 @@ void DiceSession::save() const jData["tables"][strTable][GBKtoUTF8(item)] = val; } } + if (logger.tStart || !logger.fileLog.empty()) { + json jLog; + jLog["start"] = logger.tStart; + jLog["lastMsg"] = logger.tLastMsg; + jLog["file"] = logger.fileLog; + jLog["logging"] = logger.isLogging; + jData["log"] = jLog; + } + if (linker.linkFwd) { + json jLink; + jLink["type"] = linker.typeLink; + jLink["target"] = linker.linkFwd; + jLink["linking"] = linker.isLinking; + jData["link"] = jLink; + } + if (!decks.empty()) { + json jDecks; + for (auto& [key,deck]:decks) { + jDecks[GBKtoUTF8(key)] = { + {"meta",GBKtoUTF8(deck.meta)}, + {"idxs",deck.idxs}, + {"size",deck.sizRes} + }; + } + jData["decks"] = jDecks; + } + std::lock_guard lock(exSessionSave); + if (jData.empty()) { + remove(fpFile); + return; + } + jData["type"] = type; + jData["room"] = room; + jData["create_time"] = tCreate; + jData["update_time"] = tUpdate; + ofstream fout(fpFile); + if (!fout) { + console.log("开团信息保存失败:" + UTF8toGBK(fpFile.u8string()), 1); + return; + } fout << jData.dump(1); } +bool DiceTableMaster::has_session(long long group) { + return mSession.count(group); +} + Session& DiceTableMaster::session(long long group) { if (!mSession.count(group)) { std::unique_lock lock(sessionMutex); - mSession.emplace(group, std::make_shared(group)); + if (group < 0)mSession.emplace(group, std::make_shared(group, "solo")); + else mSession.emplace(group, std::make_shared(group)); } return *mSession[group]; } @@ -132,54 +578,95 @@ Session& DiceTableMaster::session(long long group) void DiceTableMaster::session_end(long long group) { std::unique_lock lock(sessionMutex); - remove((DiceDir + R"(\user\session\)" + to_string(group)).c_str()); + remove(DiceDir / "user" / "session" / to_string(group)); mSession.erase(group); } -const enumap mSMTag{"type", "room", "gm", "player", "observer", "tables"}; +const enumap mSMTag{"type", "room", "gm", "log", "player", "observer", "tables"}; +/* void DiceTableMaster::save() { - mkDir(DiceDir + "\\user\\session"); + mkDir(DiceDir + "/user/session"); std::shared_lock lock(sessionMutex); for (auto [grp, pSession] : mSession) { pSession->save(); } } +*/ -int DiceTableMaster::load() const +int DiceTableMaster::load() { - // string strLog; - std::unique_lock lock(sessionMutex); - vector sFile; - int cnt = listDir(DiceDir + R"(\user\session\)", sFile); - if (cnt <= 0)return cnt; - for (auto& filename : sFile) + string strLog; + std::unique_lock lock(sessionMutex); + vector sFile; + int cnt = listDir(DiceDir / "user" / "session", sFile); + if (cnt <= 0)return cnt; + for (auto& filename : sFile) { - nlohmann::json j = freadJson(filename); - if (j.is_null()) + nlohmann::json j = freadJson(filename); + if (j.is_null()) { - cnt--; - continue; + cnt--; + continue; + } + auto pSession(std::make_shared(j["room"])); + j["type"].get_to(pSession->type); + pSession->create(j["create_time"]).update(j["update_time"]); + if (j.count("log")) { + json& jLog = j["log"]; + jLog["start"].get_to(pSession->logger.tStart); + jLog["lastMsg"].get_to(pSession->logger.tLastMsg); + jLog["file"].get_to(pSession->logger.fileLog); + jLog["logging"].get_to(pSession->logger.isLogging); + pSession->logger.update(); + pSession->logger.pathLog = DiceDir / pSession->logger.dirLog / pSession->logger.fileLog; + if (pSession->logger.isLogging) { + LogList.insert(pSession->room); + } } - if (j["type"] == "simple") + if (j.count("link")) { + json& jLink = j["link"]; + jLink["type"].get_to(pSession->linker.typeLink); + jLink["target"].get_to(pSession->linker.linkFwd); + jLink["linking"].get_to(pSession->linker.isLinking); + if (pSession->linker.isLinking) { + LinkList[pSession->room] = { pSession->linker.linkFwd,pSession->linker.typeLink != "from" }; + LinkList[pSession->linker.linkFwd] = { pSession->room,pSession->linker.typeLink != "to" }; + } + } + if (j.count("decks")) { + json& jDecks = j["decks"]; + for (auto it = jDecks.cbegin(); it != jDecks.cend(); ++it) { + std::string key = UTF8toGBK(it.key()); + pSession->decks[key].meta = UTF8toGBK(it.value()["meta"].get>()); + if (it.value().count("rest")) { + it.value()["rest"].get_to(pSession->decks[key].idxs); + pSession->decks[key].sizRes = pSession->decks[key].meta.size(); + } + else { + it.value()["idxs"].get_to(pSession->decks[key].idxs); + it.value()["size"].get_to(pSession->decks[key].sizRes); + } + } + } + if (j["type"] == "simple") { - auto pSession(std::make_shared(j["room"])); - pSession->create(j["create_time"]).update(j["update_time"]); - if (j.count("observer")) pSession->sOB = j["observer"].get>(); - if (j.count("tables")) + if (j.count("observer")) pSession->sOB = j["observer"].get>(); + if (j.count("tables")) { - for (nlohmann::json::iterator itTable = j["tables"].begin(); itTable != j["tables"].end(); ++itTable) + for (nlohmann::json::iterator itTable = j["tables"].begin(); itTable != j["tables"].end(); ++itTable) { - string strTable = UTF8toGBK(itTable.key()); - for (nlohmann::json::iterator itItem = itTable.value().begin(); itItem != j.end(); ++itItem) + string strTable = UTF8toGBK(itTable.key()); + for (nlohmann::json::iterator itItem = itTable.value().begin(); itItem != itTable.value().end(); ++itItem) { - pSession->mTable[strTable].emplace(UTF8toGBK(itItem.key()), itItem.value()); - } - } - } + pSession->mTable[strTable].emplace(UTF8toGBK(itItem.key()), itItem.value()); + } + } + } } - } - return cnt; -} + mSession[pSession->room] = std::move(pSession); + } + return cnt; +} \ No newline at end of file diff --git a/Dice/DiceSession.h b/Dice/DiceSession.h index 6d821f2e..8790ee6d 100644 --- a/Dice/DiceSession.h +++ b/Dice/DiceSession.h @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include "STLExtern.hpp" using std::pair; using std::string; @@ -16,17 +18,58 @@ using std::set; class FromMsg; class DiceTableMaster; +struct LogInfo{ + const std::filesystem::path dirLog = std::filesystem::path("user") / "log"; + bool isLogging{ false }; + //创建时间,为0则不存在 + time_t tStart{ 0 }; + time_t tLastMsg{ 0 }; + string fileLog; + //路径不保存,初始化时生成 + std::filesystem::path pathLog; + void update() { + tLastMsg = time(nullptr); + } +}; + +struct LinkInfo { + bool isLinking{ false }; + string typeLink; + //对象窗口,为0则不存在 + long long linkFwd{ 0 }; +}; + +struct DeckInfo { + //元表 + vector meta; + //剩余牌 + vector idxs; + size_t sizRes; + DeckInfo() = default; + DeckInfo(const vector& deck); + void init(); + void reset(); + string draw(); +}; + class DiceSession { //数值表 map> mTable; //旁观者 set sOB; + //日志 + LogInfo logger; + //链接 + LinkInfo linker; + //牌堆 + map decks; public: + string type; //群号 long long room; - DiceSession(long long group) : room(group) + DiceSession(long long group, string t = "simple") : room(group),type(t) { tUpdate = tCreate = time(nullptr); } @@ -48,26 +91,27 @@ class DiceSession DiceSession& update(time_t tt) { tUpdate = tt; - save(); return *this; } DiceSession& update() { tUpdate = time(nullptr); + save(); return *this; } - [[nodiscard]] bool table_count(string key) const { return mTable.count(key); } - int table_add(string, int, string); - [[nodiscard]] string table_prior_show(string key) const; - bool table_clr(string key); + [[nodiscard]] bool table_count(const string& key) const { return mTable.count(key); } + bool table_del(const string&, const string&); + int table_add(const string&, int, const string&); + [[nodiscard]] string table_prior_show(const string& key) const; + bool table_clr(const string& key); //旁观指令 - int ob_enter(FromMsg*); - int ob_exit(FromMsg*); - int ob_list(FromMsg*) const; - int ob_clr(FromMsg*); + void ob_enter(FromMsg*); + void ob_exit(FromMsg*); + void ob_list(FromMsg*) const; + void ob_clr(FromMsg*); [[nodiscard]] set get_ob() const { return sOB; } DiceSession& clear_ob() @@ -75,6 +119,31 @@ class DiceSession sOB.clear(); return *this; } + + //log指令 + void log_new(FromMsg*); + void log_on(FromMsg*); + void log_off(FromMsg*); + void log_end(FromMsg*); + [[nodiscard]] std::filesystem::path log_path()const; + [[nodiscard]] bool is_logging() const { return logger.isLogging; } + + //link指令 + void link_new(FromMsg*); + void link_start(FromMsg*); + void link_close(FromMsg*); + [[nodiscard]] bool is_linking() const { return linker.isLinking; } + + //deck指令 + void deck_set(FromMsg*); + string deck_draw(const string&); + void _draw(FromMsg*); + void deck_show(FromMsg*); + void deck_reset(FromMsg*); + void deck_del(FromMsg*); + void deck_clr(FromMsg*); + void deck_new(FromMsg*); + [[nodiscard]] bool has_deck(const string& key) const { return decks.count(key); } void save() const; }; @@ -92,9 +161,13 @@ class DiceTableMaster public: map> mSession; Session& session(long long group); + bool has_session(long long group); void session_end(long long group); - void save(); - int load() const; + //void save(); + int load(); }; inline std::unique_ptr gm; +inline setLogList; +//禁止桥接等花哨操作 +inline map>LinkList; diff --git a/Dice/DiceUpdate.cpp b/Dice/DiceUpdate.cpp deleted file mode 100644 index 6171b1a6..00000000 --- a/Dice/DiceUpdate.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * _______ ________ ________ ________ __ - * | __ \ |__ __| | _____| | _____| | | - * | | | | | | | | | |_____ | | - * | | | | | | | | | _____| |__| - * | |__| | __| |__ | |_____ | |_____ __ - * |_______/ |________| |________| |________| |__| - * - * Dice! QQ Dice Robot for TRPG - * Copyright (C) 2018-2019 w4123溯洄 - * Copyright (C) 2019-2020 String.Empty - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License along with this - * program. If not, see . - */ -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include "CQEVE_ALL.h" -#include "EncodingConvert.h" -#include "DiceNetwork.h" -#include "GlobalVar.h" - - -EVE_Menu(eventDiceUpdate) -{ - std::string ver; - //if (!Network::GET("api.kokona.tech", "/getExpVer", 5555, ver) && !Network::GET("shiki.stringempty.xyz", "/DiceVer", 80, ver)) { - if (!Network::GET("shiki.stringempty.xyz", "/DiceVer", 80, ver)) - { - MessageBoxA(nullptr, ("检查更新时遇到错误: \n" + ver).c_str(), "Dice!更新错误", MB_OK | MB_ICONWARNING); - return -1; - } - - ver = UTF8toGBK(ver); - const std::string serverBuild = ver.substr(ver.find('(') + 1, ver.find(')') - ver.find('(') - 1); - if (Dice_Build >= stoi(serverBuild)) - { - MessageBoxA(nullptr, "您正在使用的插件已为最新版本!", "Dice!更新", MB_OK | MB_ICONINFORMATION); - return 0; - } - - const std::string updateNotice = std::string("发现新版本! \n\n当前版本: ") + std::string(Dice_Ver) + "\n新版本: " + ver + - "\n\n警告: 如果正在使用的Dice!为修改版, 此更新会将其覆盖为原版!\n点击确定键开始更新, 取消键取消更新"; - - if (MessageBoxA(nullptr, updateNotice.c_str(), "Dice!更新", MB_OKCANCEL | MB_ICONINFORMATION) != IDOK) - { - MessageBoxA(nullptr, "更新已取消", "Dice!更新", MB_OK | MB_ICONINFORMATION); - return 0; - } - - char buffer[MAX_PATH]; - const DWORD length = GetModuleFileNameA(nullptr, buffer, sizeof buffer); - - if (length == MAX_PATH || length == 0) - { - MessageBoxA(nullptr, "路径读取失败!", "Dice!更新错误", MB_OK | MB_ICONERROR); - return -1; - } - - std::string filePath(buffer, length); - filePath = filePath.substr(0, filePath.find_last_of("\\")) + "\\app\\com.w4123.dice.cpk"; - - std::string fileContent; - //if (!Network::GET("api.kokona.tech", "/getExpDice", 5555, fileContent) && !Network::GET("shiki.stringempty.xyz", "/download/com.w4123.dice.cpk", 80, fileContent)) - if (!Network::GET("shiki.stringempty.xyz", "/download/com.w4123.dice.cpk", 80, fileContent)) - { - MessageBoxA(nullptr, ("新版本文件下载失败! 请检查您的网络状态! 错误信息: " + fileContent).c_str(), "Dice!更新错误", MB_OK | MB_ICONERROR); - return -1; - } - - std::ofstream streamUpdate(filePath, std::ios::trunc | std::ios::binary | std::ios::out); - if (!streamUpdate) - { - MessageBoxA(nullptr, "文件打开失败!", "Dice!更新错误", MB_OK | MB_ICONERROR); - return -1; - } - - streamUpdate << fileContent; - streamUpdate.close(); - MessageBoxA(nullptr, "Dice!已更新, 请重载应用!", "Dice!更新", MB_OK | MB_ICONINFORMATION); - return 0; -} diff --git a/Dice/DiceXMLTree.h b/Dice/DiceXMLTree.h index bbe3df75..7a79ad80 100644 --- a/Dice/DiceXMLTree.h +++ b/Dice/DiceXMLTree.h @@ -1,9 +1,11 @@ +#pragma once + /* * 树状结构 * Copyright (C) 2019-2020 String.Empty * 实际并非真正意义上的XML格式 */ -#pragma once + #include #include #include diff --git a/Dice/EncodingConvert.cpp b/Dice/EncodingConvert.cpp index 6636e967..13e387e7 100644 --- a/Dice/EncodingConvert.cpp +++ b/Dice/EncodingConvert.cpp @@ -21,21 +21,100 @@ * program. If not, see . */ -#define CP_GB18030 (54936) -#define WIN32_LEAN_AND_MEAN +#define CP_GBK (936) #include "EncodingConvert.h" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN #include +#else +#include +#endif #include #include #include #include +#include -// 事实上是GB18030 -std::string GBKtoUTF8(const std::string& strGBK) +bool checkUTF8(const std::string& strUTF8) { + size_t cntUTF8(0); + int num = 0; + size_t i = 0; + while (i < strUTF8.length()) { + if ((strUTF8[i] & 0x80) == 0x00) { + i++; + continue; + } + else if ((strUTF8[i] & 0xc0) == 0xc0 && (strUTF8[i] & 0xfe) != 0xfe) { + // 110X_XXXX 10XX_XXXX + // 1110_XXXX 10XX_XXXX 10XX_XXXX + // 1111_0XXX 10XX_XXXX 10XX_XXXX 10XX_XXXX + // 1111_10XX 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX + // 1111_110X 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX + unsigned char mask = 0x80; + for (num = 0; num < 8; ++num) { + if ((strUTF8[i] & mask) == mask) { + mask = mask >> 1; + } + else + break; + } + for (int j = 0; j < num - 1; j++) { + if ((strUTF8[++i] & 0xc0) != 0x80) { + return false; + } + } + ++i; + if (++cntUTF8 > 10)return true; + } + else { + return false; + } + } + return cntUTF8; +} +bool checkUTF8(std::ifstream& fin) { + size_t cntUTF8(0); + int num = 0; + char ch{0}; + while (fin >> ch) { + if ((ch & 0x80) == 0x00) { + continue; + } + else if ((ch & 0xc0) == 0xc0 && (ch & 0xfe) != 0xfe) { + // 110X_XXXX 10XX_XXXX + // 1110_XXXX 10XX_XXXX 10XX_XXXX + // 1111_0XXX 10XX_XXXX 10XX_XXXX 10XX_XXXX + // 1111_10XX 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX + // 1111_110X 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX + unsigned char mask = 0x80; + for (num = 0; num < 8; ++num) { + if ((ch & mask) == mask) { + mask = mask >> 1; + } + else + break; + } + for (int j = 0; j < num - 1; j++) { + if ((fin >> ch) && (ch & 0xc0) != 0x80) { + return false; + } + } + if (++cntUTF8 > 10)return true; + } + else { + return false; + } + } + return cntUTF8; +} +// 现在是GBK了 +std::string GBKtoUTF8(const std::string& strGBK, bool isTrial) { - const int UTF16len = MultiByteToWideChar(CP_GB18030, 0, strGBK.c_str(), -1, nullptr, 0); + if (isTrial && checkUTF8(strGBK))return strGBK; +#ifdef _WIN32 + const int UTF16len = MultiByteToWideChar(CP_GBK, 0, strGBK.c_str(), -1, nullptr, 0); auto* const strUTF16 = new wchar_t[UTF16len]; - MultiByteToWideChar(CP_GB18030, 0, strGBK.c_str(), -1, strUTF16, UTF16len); + MultiByteToWideChar(CP_GBK, 0, strGBK.c_str(), -1, strUTF16, UTF16len); const int UTF8len = WideCharToMultiByte(CP_UTF8, 0, strUTF16, -1, nullptr, 0, nullptr, nullptr); auto* const strUTF8 = new char[UTF8len]; WideCharToMultiByte(CP_UTF8, 0, strUTF16, -1, strUTF8, UTF8len, nullptr, nullptr); @@ -43,6 +122,9 @@ std::string GBKtoUTF8(const std::string& strGBK) delete[] strUTF16; delete[] strUTF8; return strOutUTF8; +#else + return ConvertEncoding(strGBK, "gb18030", "utf-8"); +#endif } std::vector GBKtoUTF8(const std::vector& strGBK) @@ -53,18 +135,23 @@ std::vector GBKtoUTF8(const std::vector& strGBK) } // 事实上是GB18030 -std::string UTF8toGBK(const std::string& strUTF8) +std::string UTF8toGBK(const std::string& strUTF8, bool isTrial) { + if (isTrial && !checkUTF8(strUTF8))return strUTF8; +#ifdef _WIN32 const int UTF16len = MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, nullptr, 0); auto* const strUTF16 = new wchar_t[UTF16len]; MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, strUTF16, UTF16len); - const int GBKlen = WideCharToMultiByte(CP_GB18030, 0, strUTF16, -1, nullptr, 0, nullptr, nullptr); + const int GBKlen = WideCharToMultiByte(CP_GBK, 0, strUTF16, -1, nullptr, 0, nullptr, nullptr); auto* const strGBK = new char[GBKlen]; - WideCharToMultiByte(CP_GB18030, 0, strUTF16, -1, strGBK, GBKlen, nullptr, nullptr); + WideCharToMultiByte(CP_GBK, 0, strUTF16, -1, strGBK, GBKlen, nullptr, nullptr); std::string strOutGBK(strGBK); delete[] strUTF16; delete[] strGBK; return strOutGBK; +#else + return ConvertEncoding(strUTF8, "utf-8", "gb18030"); +#endif } std::vector UTF8toGBK(const std::vector& vUTF8) diff --git a/Dice/EncodingConvert.h b/Dice/EncodingConvert.h index 30d9b470..126705d4 100644 --- a/Dice/EncodingConvert.h +++ b/Dice/EncodingConvert.h @@ -1,3 +1,5 @@ +#pragma once + /* * _______ ________ ________ ________ __ * | __ \ |__ __| | _____| | _____| | | @@ -7,7 +9,7 @@ * |_______/ |________| |________| |________| |__| * * Dice! QQ Dice Robot for TRPG - * Copyright (C) 2018-2019 w4123溯洄 + * Copyright (C) 2018-2021 w4123溯洄 * * This program is free software: you can redistribute it and/or modify it under the terms * of the GNU Affero General Public License as published by the Free Software Foundation, @@ -20,33 +22,86 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#pragma once + #ifndef DICE_ENCODING_CONVERT #define DICE_ENCODING_CONVERT #include #include -std::string GBKtoUTF8(const std::string& strGBK); -std::vector GBKtoUTF8(const std::vector& strGBK); +#include +#ifndef _WIN32 +#include +#endif +#include +#include + +bool checkUTF8(const std::string& strUTF8); +bool checkUTF8(std::ifstream&); template && - !std::is_convertible_v>>> -T GBKtoUTF8(T TGBK) -{ - return TGBK; + typename = std::enable_if_t && + !std::is_convertible_v> >> + T GBKtoUTF8(const T& TGBK) { + return TGBK; +} +std::string GBKtoUTF8(const std::string& strGBK, bool isTrial = false); +std::vector GBKtoUTF8(const std::vector& strGBK); +template +std::unordered_map GBKtoUTF8(const std::unordered_map& TGBK) { + std::unordered_map TUTF8; + for (auto& [key, val] : TGBK) { + TUTF8[GBKtoUTF8(key)] = GBKtoUTF8(val); + } + return TUTF8; } -std::string UTF8toGBK(const std::string& strUTF8); -std::vector UTF8toGBK(const std::vector& strUTF8); template && - !std::is_convertible_v>>> -T UTF8toGBK(T TUTF8) -{ + typename = std::enable_if_t && + !std::is_convertible_v> >> + T UTF8toGBK(const T& TUTF8) { return TUTF8; } +std::string UTF8toGBK(const std::string& strUTF8, bool isTrial = false); +std::vector UTF8toGBK(const std::vector& strUTF8); +template +std::unordered_map UTF8toGBK(const std::unordered_map& TUTF8) { + std::unordered_map TGBK; + for (auto& [key, val] : TUTF8) { + TGBK[UTF8toGBK(key)] = UTF8toGBK(val); + } + return TGBK; +} + std::string UrlEncode(const std::string& str); std::string UrlDecode(const std::string& str); +#ifndef _WIN32 +template +std::basic_string ConvertEncoding(const std::basic_string& in, const std::string& InEnc, const std::string& OutEnc, const double CapFac = 4.0) +{ + const auto cd = iconv_open(OutEnc.c_str(), InEnc.c_str()); + if (cd == (iconv_t)-1) + { + return std::basic_string(); + } + size_t in_len = in.size() * sizeof(Q); + size_t out_len = size_t(in_len * CapFac + sizeof(T)); + char* in_ptr = const_cast(reinterpret_cast (in.c_str())); + char* out_ptr = new char[out_len](); + + // As out_ptr would be modified by iconv(), store a copy of it pointing to the beginning of the array + char* out_ptr_copy = out_ptr; + if (iconv(cd, &in_ptr, &in_len, &out_ptr, &out_len) == (size_t)-1) + { + delete[] out_ptr_copy; + iconv_close(cd); + return std::basic_string(); + } + std::basic_string ret(reinterpret_cast(out_ptr_copy), (out_ptr - out_ptr_copy) / sizeof(T)); + delete[] out_ptr_copy; + iconv_close(cd); + return ret; +} +#endif + #endif /*DICE_ENCODING_CONVERT*/ diff --git a/Dice/GetRule.cpp b/Dice/GetRule.cpp index b210ab08..a41c7077 100644 --- a/Dice/GetRule.cpp +++ b/Dice/GetRule.cpp @@ -20,18 +20,12 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#define WIN32_LEAN_AND_MEAN -#include - -#ifdef _MSC_VER -#pragma comment(lib, "Wininet.lib") -#endif /*_MSC_VER*/ - #include +#include +#include "DDAPI.h" #include "GetRule.h" #include "GlobalVar.h" #include "EncodingConvert.h" -#include "CQAPI_EX.h" #include "DiceNetwork.h" @@ -73,13 +67,17 @@ namespace GetRule const string ruleName = GBKtoUTF8(rule); const string itemName = GBKtoUTF8(name); - string data = "Name=" + UrlEncode(itemName) + "&QQ=" + to_string(CQ::getLoginQQ()) + "&v=20190114"; + string data = "Name=" + UrlEncode(itemName) + "&QQ=" + to_string(DD::getLoginQQ()) + "&v=20190114"; if (!ruleName.empty()) { data += "&Type=Rules-" + UrlEncode(ruleName); } char* frmdata = new char[data.length() + 1]; +#ifdef _MSC_VER strcpy_s(frmdata, data.length() + 1, data.c_str()); +#else + strcpy(frmdata, data.c_str()); +#endif string temp; const bool reqRes = Network::POST("api.kokona.tech", "/rules", 5555, frmdata, temp); delete[] frmdata; diff --git a/Dice/GetRule.h b/Dice/GetRule.h index a23c3e55..ff7881f1 100644 --- a/Dice/GetRule.h +++ b/Dice/GetRule.h @@ -1,3 +1,5 @@ +#pragma once + /* * _______ ________ ________ ________ __ * | __ \ |__ __| | _____| | _____| | | @@ -20,7 +22,7 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#pragma once + #ifndef DICE_GET_RULE #define DICE_GET_RULE #include diff --git a/Dice/GlobalVar.cpp b/Dice/GlobalVar.cpp index 70384416..2dedddc8 100644 --- a/Dice/GlobalVar.cpp +++ b/Dice/GlobalVar.cpp @@ -1,581 +1,690 @@ -/* - * _______ ________ ________ ________ __ - * | __ \ |__ __| | _____| | _____| | | - * | | | | | | | | | |_____ | | - * | | | | | | | | | _____| |__| - * | |__| | __| |__ | |_____ | |_____ __ - * |_______/ |________| |________| |________| |__| - * - * Dice! QQ Dice Robot for TRPG - * Copyright (C) 2018-2019 w4123溯洄 - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License along with this - * program. If not, see . - */ -#define WIN32_LEAN_AND_MEAN -#include -#include "CQLogger.h" -#include "GlobalVar.h" -#include "MsgFormat.h" -#include - -bool Enabled = false; - -bool Mirai = false; - -std::string Dice_Full_Ver_For = Dice_Full_Ver + " For CoolQ]"; - -std::string strModulePath; - -HMODULE hDllModule = nullptr; - -bool msgSendThreadRunning = false; - -CQ::logger DiceLogger("Dice!"); - -std::map GlobalMsg -{ - {"strParaEmpty","参数不能为空×"}, //偷懒用万能回复 - {"strParaIllegal","参数非法×"}, //偷懒用万能回复 - {"stranger","用户"}, //{nick}无法获取非空昵称时的称呼 - {"strAdminOptionEmpty","找{self}有什么事么?{nick}"}, // - {"strGMTableShow","{self}记录的{table_name}列表:"}, - {"strGMTableNotExist","{self}没有保存的{table_name}记录"}, - {"strUserTrustShow","{user}在{self}处的信任级别为{trust}"}, - {"strUserTrusted","已将{self}对{user}的信任级别调整为{trust}"}, - {"strUserTrustDenied","{nick}在{self}处无权访问对方的权限×"}, - {"strUserTrustIllegal","将目标权限修改为{trust}是非法的×"}, - {"strUserNotFound","{self}无{user}的用户记录"}, - {"strGroupAuthorized","A roll to the table turns to a dice fumble!\nDice Roller {strSelfName}√\n本群已授权许可,请尽情使用本骰娘√\n请遵守协议使用,服务结束后使用.dismiss送出!" }, - {"strAddGroupNoLicense","本群未获{self}许可使用,将自动在群内静默。\n请先.help协议 阅读并同意协议后向运营方申请许可使用,\n否则请管理员使用!dismiss送出{self}\n可按以下格式填写并发送申请:\n!authorize 申请用途:[理由] 我已了解Dice!基本用法,仔细阅读并保证遵守{strSelfName}的用户协议,如需停用指令使用[指令],用后使用[指令]送出群" }, - {"strGroupLicenseDeny","此群未获{self}许可使用,请先确认协议并申请许可×\n或请管理员使用!dismiss送出{self}" }, - {"strGroupLicenseApply","此群未通过自助授权×\n许可申请已发送√" }, - {"strGroupSetOn","现已开启{self}在此群的“{option}”选项√"}, //群内开关和遥控开关通用此文本 - {"strGroupSetOnAlready","{self}已在此群设置了{option}!"}, - {"strGroupSetOff","现已关闭{self}在此群的“{option}”选项√"}, - {"strGroupSetOffAlready","{self}未在此群设置{option}!"}, - {"strGroupSetAll","{self}已修改记录中{cnt}个群的“{option}”选项√"}, - {"strGroupDenied","{nick}在{self}处无权访问此群的设置×"}, - {"strGroupSetDenied","{nick}在{self}处设置{option}的权限不足×"}, - {"strGroupSetNotExist","{self}无{option}此选项×"}, - {"strGroupWholeUnban","{self}已关闭全局禁言√"}, - {"strGroupWholeBan","{self}已开启全局禁言√"}, - {"strGroupWholeBanErr","{self}开启全局禁言失败×"}, - {"strGroupUnban","{self}裁定:{member}解除禁言√"}, - {"strGroupBan","{self}裁定:{member}禁言{res}分钟√"}, - {"strGroupBanErr","{self}禁言{member}失败×"}, - {"strGroupNotFound","{self}无该群记录×"}, - {"strGroupNotIn","{self}当前不在该群或对象不是群!"}, - {"strGroupExit","{self}已退出该群√"}, - {"strGroupCardSet","{self}已将{target}的群名片修改为{card}√"}, - {"strGroupCardSetErr","{self}设置{target}的群名片失败×"}, - {"strGroupTitleSet","{self}已将{target}的头衔修改为{title}√"}, - {"strGroupTitleSetErr","{self}设置{target}的头衔失败×"}, - {"strPcNewEmptyCard","已为{nick}新建{type}空白卡{char}√"}, - {"strPcNewCardShow","已为{nick}新建{type}卡{char}:{show}"},//由于预生成选项而存在属性 - {"strPcCardSet","已将{nick}当前角色卡绑定为{char}√"},//{nick}-用户昵称 {pc}-原角色卡名 {char}-新角色卡名 - {"strPcCardReset","已解绑{nick}当前的默认卡√"},//{nick}-用户昵称 {pc}-原角色卡名 - {"strPcCardRename","已将{old_name}重命名为{new_name}√"}, - {"strPcCardDel","已将角色卡{char}删除√"}, - {"strPcCardCpy","已将{char2}的属性复制到{char1}√"}, - {"strPcClr","已清空{nick}的角色卡记录√"}, - {"strPcCardList","{nick}的角色列表:{show}"}, - {"strPcCardBuild","{nick}的{char}生成:{show}"}, - {"strPcCardShow","{nick}的<{type}>{char}:{show}"}, //{nick}-用户昵称 {type}-角色卡类型 {char}-角色卡名 - {"strPcCardRedo","{nick}的{char}重新生成:{show}"}, - {"strPcGroupList","{nick}的各群角色列表:{show}"}, - {"strPcNotExistErr","{self}无{nick}的角色卡记录,无法删除×"}, - {"strPcCardFull","{nick}在{self}处的角色卡已达上限,请先清理多余角色卡×"}, - {"strPcTempInvalid","{self}无法识别的角色卡模板×"}, - {"strPcNameEmpty","名称不能为空×"}, - {"strPcNameExist","已存在同名卡×"}, - {"strPcNameNotExist","该名称不存在×"}, - {"strPcNameInvalid","非法的人物卡名(存在冒号)×"}, - {"strPcInitDelErr","初始卡不可删除×"}, - {"strPcNoteTooLong","备注长度不能超过255×"}, - {"strPcTextTooLong","文本长度不能超过48×"}, - {"strSensNote","发现指令中带敏感词,{self}已记录并上报!"}, - {"strSensWarn","发现指令中带敏感词,{self}拒绝响应且已上报!"}, - {"strSpamFirstWarning","你短时间内对{self}指令次数过多!请善用多轮掷骰和复数生成指令(刷屏初次警告)"}, - {"strSpamFinalWarning","请暂停你的一切指令,避免因高频指令被{self}拉黑!(刷屏最终警告)"}, - {"strReplySet","{self}对关键词{key}的回复已设置√"}, - {"strReplyDel","{self}对关键词{key}的回复已清除√"}, - {"strStModify","{self}对已记录{pc}的属性变化:"}, //存在技能值变化情况时,优先使用此文本 - {"strStDetail","{self}对已设置{pc}的属性:"}, //存在掷骰时,使用此文本(暂时无用) - {"strStValEmpty","{self}未记录{attr}原值×"}, //{0}为属性名 - {"strBlackQQAddNotice","{user_nick},你已被{self}加入黑名单,详情请联系Master"}, - {"strBlackQQAddNoticeReason","{user_nick},由于{reason},你已被{self}加入黑名单,申诉解封请联系管理员"}, - {"strBlackQQDelNotice","{user_nick},你已被{self}移出黑名单,现在可以继续使用了"}, - {"strWhiteQQAddNotice","{user_nick},您已获得{self}的信任,请尽情使用{self}√"}, - {"strWhiteQQDenied","你不是{self}信任的用户×"}, - {"strWhiteGroupDenied","本群聊不在白名单中×"}, - {"strDeckProNew","已新建自定义牌堆√"}, - {"strDeckProSet","已将{deck_name}设置为默认牌堆√"}, - {"strDeckProClr","已删除默认牌堆√"}, - {"strDeckProNull","默认牌堆不存在!"}, - {"strDeckTmpReset","已重置卡牌√"}, - {"strDeckTmpShow","当前剩余卡牌:"}, - {"strDeckTmpEmpty","已无剩余卡牌!"}, //剩余卡牌数为0 - {"strDeckTmpNotFound","不存在剩余卡牌×"}, //没有生成过牌堆 - {"strDeckNameEmpty","未指定牌堆名×"}, - {"strRollDice","{pc}掷骰: {res}"}, - {"strRollDiceReason","{pc}掷骰 {reason}: {res}"}, - {"strRollHidden","{pc}进行了一次暗骰"}, - {"strRollTurn","{pc}的掷骰轮数: {turn}轮"}, - {"strRollMultiDice","{pc}掷骰{turn}次: {dice_exp}={res}"}, - {"strRollMultiDiceReason","{pc}掷骰{turn}次{reason}: {dice_exp}={res}"}, - {"strRollSkill","{pc}进行{attr}检定:"}, - {"strRollSkillReason","由于{reason} {pc}进行{attr}检定:"}, - {"strEnRoll","{pc}的{attr}增强或成长检定:\n{res}"},//{attr}在用户省略技能名后替换为{strEnDefaultName} - {"strEnRollNotChange","{strEnRoll}\n{pc}的{attr}值没有变化"}, - {"strEnRollFailure","{strEnRoll}\n{pc}的{attr}变化{change}点,当前为{final}点"}, - {"strEnRollSuccess","{strEnRoll}\n{pc}的{attr}增加{change}点,当前为{final}点"}, - {"strEnDefaultName","属性或技能"},//默认文本 - {"strEnValEmpty", "未对{self}设定待成长属性值,请先.st {attr} 属性值 或查看.help en×"}, - {"strEnValInvalid", "{attr}值输入不正确,请输入1-99范围内的整数!"}, - {"strSendMsg", "{self}已将消息送出√"}, //Master定向发送的回执 - {"strSendMasterMsg", "消息{self}已发送给Master√"}, //向Master发送的回执 - {"strSendMsgEmpty", "发送消息内容为空×"}, - {"strSendMsgInvalid", "{self}没有可以发送的对象×"}, //没有Master - {"strDefaultCOCClr", "默认检定房规已清除√"}, - {"strDefaultCOCNotFound", "默认检定房规不存在×"}, - {"strDefaultCOCSet", "默认检定房规已设置:"}, - {"strLinkLoss", "{self}的时空连接已断开√"}, - {"strLinked", "{self}已创建时空门√"}, - {"strLinkWarning", "尝试创建时空门,但不保证能否连通"}, - {"strLinkNotFound", "时空门要通向不可名状的地方了×"}, - {"strNotMaster", "你不是{self}的master!你想做什么?"}, - {"strNotAdmin", "你不是{self}的管理员×"}, - {"strDismiss", ""}, //.dismiss退群前的回执 - {"strHlpSet", "已为{key}设置词条√"}, - {"strHlpReset", "已清除{key}的词条√"}, - {"strHlpNameEmpty", "Master想要自定义什么词条呀?"}, - {"strHlpNotFound", "{self}未找到指定的帮助信息×"}, - {"strClockToWork", "{self}已按时启用√"}, - {"strClockOffWork", "{self}已按时关闭√"}, - {"strNameGenerator", "{pc}的随机名称:{res}"}, - {"strDrawCard", "来看看{pc}抽到了什么:{res}"}, - {"strMeOn", "成功在这里启用{self}的.me命令√"}, - {"strMeOff", "成功在这里禁用{self}的.me命令√"}, - {"strMeOnAlready", "在这里{self}的.me命令没有被禁用!"}, - {"strMeOffAlready", "在这里{self}的.me命令已经被禁用!"}, - {"strObOn", "成功在这里启用{self}的旁观模式√"}, - {"strObOff", "成功在这里禁用{self}的旁观模式√"}, - {"strObOnAlready", "在这里{self}的旁观模式没有被禁用!"}, - {"strObOffAlready", "在这里{self}的旁观模式已经被禁用!"}, - {"strObList", "当前{self}的旁观者有:"}, - {"strObListEmpty", "当前{self}暂无旁观者"}, - {"strObListClr", "{self}成功删除所有旁观者√"}, - {"strObEnter", "{nick}成功加入{self}的旁观√"}, - {"strObExit", "{nick}成功退出{self}的旁观√"}, - {"strObEnterAlready", "{nick}已经处于{self}的旁观模式!"}, - {"strObExitAlready", "{nick}没有加入{self}的旁观模式!"}, - {"strQQIDEmpty", "QQ号不能为空×"}, - {"strGroupIDEmpty", "群号不能为空×"}, - {"strBlackGroup", "该群在黑名单中,如有疑问请联系master"}, - {"strBotOn", "成功开启{self}√"}, - {"strBotOff", "成功关闭{self}√"}, - {"strBotOnAlready", "{self}已经处于开启状态!"}, - {"strBotOffAlready", "{self}已经处于关闭状态!"}, - {"strRollCriticalSuccess", "大成功!"}, //一般检定用 - {"strRollExtremeSuccess", "极难成功"}, - {"strRollHardSuccess", "困难成功"}, - {"strRollRegularSuccess", "成功"}, - {"strRollFailure", "失败"}, - {"strRollFumble", "大失败!"}, - {"strFumble", "大失败!"}, //多轮检定用,请控制长度 - {"strFailure", "失败"}, - {"strSuccess", "成功"}, - {"strHardSuccess", "困难成功"}, - {"strExtremeSuccess", "极难成功"}, - {"strCriticalSuccess", "大成功!"}, - {"strNumCannotBeZero", "无意义的数目!莫要消遣于我!"}, - {"strDeckNotFound", "是说{deck_name}?{self}没听说过的牌堆名呢……"}, - {"strDeckEmpty", "{self}已经一张也不剩了!"}, - {"strNameNumTooBig", "生成数量过多!请输入1-10之间的数字!"}, - {"strNameNumCannotBeZero", "生成数量不能为零!请输入1-10之间的数字!"}, - {"strSetInvalid", "无效的默认骰!请输入1-9999之间的数字!"}, - {"strSetTooBig", "这面数……让我丢个球啊!请输入1-9999之间的数字!"}, - {"strSetCannotBeZero", "默认骰不能为零!请输入1-9999之间的数字!"}, - {"strCharacterCannotBeZero", "人物作成次数不能为零!请输入1-10之间的数字!"}, - {"strCharacterTooBig", "人物作成次数过多!请输入1-10之间的数字!"}, - {"strCharacterInvalid", "人物作成次数无效!请输入1-10之间的数字!"}, - {"strSanRoll", "{pc}的San Check:\n{res}"}, - {"strSanRollRes", "{strSanRoll}\n{pc}的San值减少{change}点,当前剩余{final}点"}, - {"strSanCostInvalid", "SC表达式输入不正确,格式为成功扣San/失败扣San,如1/1d6!"}, - {"strSanInvalid", "San值输入不正确,请输入1-99范围内的整数!"}, - {"strSanEmpty", "未设定San值,请先.st san 或查看.help sc×"}, - {"strSuccessRateErr", "这成功率还需要检定吗?"}, - {"strGroupIDInvalid", "无效的群号!"}, - {"strSendErr", "消息发送失败!"}, - {"strSendSuccess", "命令执行成功√"}, - {"strDisabledErr", "命令无法执行:机器人已在此群中被关闭!"}, - {"strActionEmpty", "动作不能为空×"}, - {"strMEDisabledErr", "管理员已在此群中禁用.me命令!"}, - {"strDisabledMeGlobal", "恕不提供.me服务×"}, - {"strDisabledJrrpGlobal", "恕不提供.jrrp服务×"}, - {"strDisabledDeckGlobal", "恕不提供.deck服务×"}, - {"strDisabledDrawGlobal", "恕不提供.draw服务×"}, - {"strDisabledSendGlobal", "恕不提供.send服务×"}, - {"strHELPDisabledErr", "管理员已在此群中禁用.help命令!"}, - {"strNameDelEmpty", "{nick}没有设置名称,无法删除!"}, - {"strValueErr", "掷骰表达式输入错误!"}, - {"strInputErr", "命令或掷骰表达式输入错误!"}, - {"strUnknownErr", "发生了未知错误!"}, - {"strUnableToGetErrorMsg", "无法获取错误信息!"}, - {"strDiceTooBigErr", "{self}被你扔出的骰子淹没了×"}, - {"strRequestRetCodeErr", "访问服务器时出现错误! HTTP状态码: {error}"}, - {"strRequestNoResponse", "服务器未返回任何信息×"}, - {"strTypeTooBigErr", "哇!让我数数骰子有多少面先~1...2..."}, - {"strZeroTypeErr", "这是...!!时空裂({self}被骰子产生的时空裂缝卷走了)"}, - {"strAddDiceValErr", "你这样要让{self}扔骰子扔到什么时候嘛~(请输入正确的加骰参数:5-10之内的整数)"}, - {"strZeroDiceErr", "咦?我的骰子呢?"}, - {"strRollTimeExceeded", "掷骰轮数超过了最大轮数限制!"}, - {"strRollTimeErr", "异常的掷骰轮数"}, - {"strObPrivate", "你想看什么呀?"}, - {"strDismissPrivate", "滚!"}, - {"strWelcomePrivate", "你在这欢迎谁呢?"}, - {"strWelcomeMsgClearNotice", "已清除本群的入群欢迎词√"}, - {"strWelcomeMsgClearErr", "没有设置入群欢迎词,清除失败×"}, - {"strWelcomeMsgUpdateNotice", "{self}已更新本群的入群欢迎词√"}, - {"strPermissionDeniedErr", "请让群内管理对{self}发送该指令×"}, - {"strSelfPermissionErr", "{self}权限不够无能为力呢×"}, - {"strNameTooLongErr", "名称过长×(最多为50英文字符)"}, - {"strNameClr", "已将{nick}的名称删除√"}, - {"strNameSet", "已将{nick}的名称更改为{new_nick}√"}, - {"strUnknownPropErr", "未设定{attr}成功率,请先.st {attr} 技能值 或查看.help rc×"}, - {"strEmptyWWDiceErr", "格式错误:正确格式为.w(w)XaY!其中X≥1, 5≤Y≤10"}, - {"strPropErr", "请认真的输入你的属性哦~"}, - {"strSetPropSuccess", "属性设置成功√"}, - {"strPropCleared", "已清空{char}的所有属性√"}, - {"strRuleReset", "已重置默认规则√"}, - {"strRuleSet", "已设置默认规则√"}, - {"strRuleErr", "规则数据获取失败,具体信息:\n"}, - {"strRulesFailedErr", "请求失败,{self}无法连接数据库×"}, - {"strPropDeleted", "已删除{pc}的{attr}√"}, - {"strPropNotFound", "属性{attr}不存在×"}, - {"strRuleNotFound", "{self}未找到对应的规则信息×"}, - {"strProp", "{pc}的{attr}为{val}"}, - {"strPropList", "{nick}的{char}属性列表为:{show}"}, - {"strStErr", "格式错误:请参考.help st获取.st命令的使用方法"}, - {"strRulesFormatErr", "格式错误:正确格式为.rules[规则名称:]规则条目 如.rules COC7:力量"}, - {"strLeaveDiscuss", "{self}现不支持讨论组服务,即将退出"}, - {"strLeaveNoPower", "{self}未获得群管理,即将退群"}, - {"strLeaveUnused", "{self}已经在这里被放置{day}天啦,马上就会离开这里了"}, - {"strGlobalOff", "{self}休假中,暂停服务×"}, - {"strPreserve", "{self}私有私用,勿扰勿怪\n如需申请许可请发送!authorize +[群号] [申请理由]"}, - {"strJrrp", "{nick}今天的人品值是: {res}"}, - {"strJrrpErr", "JRRP获取失败! 错误信息: \n{res}"}, - {"strAddFriendWhiteQQ", "{strAddFriend}"}, //白名单用户添加好友时回复此句 - { - "strAddFriend", - R"(欢迎使用{strSelfName}! -.help协议 确认服务协议 -.help指令 查看指令列表 -.help设定 确认骰娘设定 -.help链接 查看源码文档 -使用服务默认已经同意服务协议)" - }, //同意添加好友时额外发送的语句 - { - "strAddGroup", - R"(欢迎使用{strSelfName}! -请使用.dismiss QQ号(或后四位) 使{self}退群退讨论组 -.bot on/off QQ号(或后四位) //开启或关闭指令 -.group +/-禁用回复 //禁用或启用回复 -.help协议 确认服务协议 -.help指令 查看指令列表 -.help设定 确认骰娘设定 -.help链接 查看源码文档 -邀请入群默认视为同意服务协议,知晓禁言或移出的后果)" - }, - {"strSelfName", ""}, - {"strSelfCall", "&strSelfName"}, - {"self", "&strSelfCall"}, - {"strBotMsg", "\n使用.help更新 查看{self}更新内容"}, - { - "strHlpMsg", - R"(请使用.dismiss QQ号(或后四位) 使{self}退群退讨论组 -.bot on/off QQ号(或后四位) //开启或关闭指令 -.help协议 确认服务协议 -.help指令 查看指令列表 -.help群管 查看群管指令 -.help设定 确认骰娘设定 -.help链接 查看源码文档 -官方论坛:https://forum.kokona.tech/ -官方(水)群: 624807593 941980833 882747577 -Shiki交流群: 1029435374 -私骰分流群: 863062599)" - } -}; - -std::map EditedMsg; -const std::map HelpDoc = { - {"更新", R"(V2.4.0 多功能更新 Dice! GUI 管理面板)"}, - { - "协议", - "0.本协议是Dice!默认服务协议。如果你看到了这句话,意味着Master应用默认协议,请注意。\n1.邀请骰娘、使用掷骰服务和在群内阅读此协议视为同意并承诺遵守此协议,否则请使用.dismiss移出骰娘。\n2.不允许禁言、移出骰娘或刷屏掷骰等对骰娘的不友善行为,这些行为将会提高骰娘被制裁的风险。开关骰娘响应请使用.bot on/off。\n3.骰娘默认邀请行为已事先得到群内同意,因而会自动同意群邀请。因擅自邀请而使骰娘遭遇不友善行为时,邀请者因未履行预见义务而将承担连带责任。\n4.禁止将骰娘用于赌博及其他违法犯罪行为。\n5.对于设置敏感昵称等无法预见但有可能招致言论审查的行为,骰娘可能会出于自我保护而拒绝提供服务\n6.由于技术以及资金原因,我们无法保证机器人100%的时间稳定运行,可能不定时停机维护或遭遇冻结,但是相应情况会及时通过各种渠道进行通知,敬请谅解。临时停机的骰娘不会有任何响应,故而不会影响群内活动,此状态下仍然禁止不友善行为。\n7.对于违反协议的行为,骰娘将视情况终止对用户和所在群提供服务,并将不良记录共享给其他服务提供方。黑名单相关事宜可以与服务提供方协商,但最终裁定权在服务提供方。\n8.本协议内容随时有可能改动。请注意帮助信息、签名、空间、官方群等处的骰娘动态。\n9.骰娘提供掷骰服务是完全免费的,欢迎投食。\n10.本服务最终解释权归服务提供方所有。" - }, - { - "链接", - R"(官方论坛:https://forum.kokona.tech/ -查看源码:https://github.com/mystringEmpty/Dice -插件下载:https://github.com/mystringEmpty/Dice/releases -用户手册:http://shiki.stringempty.xyz/download/Shiki_User_Manual.pdf -骰主手册:http://shiki.stringempty.xyz/download/Shiki_Master_Manual.pdf -炼骰手册:http://shiki.stringempty.xyz/download/DiceMaid_CookBook.html -(在线文档)https://dice.c-j.dev/ -st用COC7人物卡:http://shiki.stringempty.xyz/download/COC7_player_card_shiki.xlsx)" - }, - { - "设定", - "Master:{master}\n.me使用:禁止\n.jrrp使用:允许\n邀请处理:黑名单制,非禁即入\n讨论组使用:允许\n移出反制:拉黑群和操作者\n禁言反制:默认拉黑群和群主\n刷屏反制:警告\n邀请人责任:有限连带\n窥屏可能:有\n其他插件:无\n骰娘个人群:(未设置)\n官方(水)群: 624807593 941980833 882747577\n私骰分流群:863062599\nShiki交流群:1029435374" - }, - {"作者", "Copyright (C) 2018-2020 w4123溯洄\nCopyright (C) 2019-2020 String.Empty\n本应用在AGPLv3下授权"}, - { - "指令", - R"(at骰娘后接指令可以指定骰娘单独响应,如at骰娘.bot off -多数指令需要后接参数,请.help对应指令 获取详细信息 -掷骰指令包括: -.dismiss 退群 -.authorize 授权许可 -.bot 开关 -.welcome 入群欢迎 -.rules 规则速查 -.r 掷骰 -.ob 旁观模式 -.set 设置默认骰 -.name 随机姓名 -.nn 设置昵称 -.coc COC人物作成 -.dnd DND人物作成 -.st 属性记录 -.pc 角色卡 -.rc 检定 -.setcoc 设置检定房规 -.sc 理智检定 -.en 成长检定 -.ri 先攻 -.init 先攻列表 -.ww 骰池 -.me 第三人称动作 -.jrrp 今日人品 -.send 向管理发送消息 -.group 群管 -.draw 抽牌 -为了避免未预料到的指令误判,请尽可能在参数之间使用空格)" - }, - { - "deck", - "该指令可以设置默认牌堆,使用.draw不指定牌堆名时将使用此牌堆。该牌堆不会放回直到抽完最后一张后洗牌。\n.deck set 公共牌堆名 设置默认牌堆\n.deck set 正整数1-100 设置指定长度的数列\n.deck show 查看剩余卡牌\n.deck reset 重置剩余卡牌\n.deck new 自定义牌堆(用空格或|分割)(白名单限定)\n.deck new 有弹|无弹|无弹|无弹|无弹|无弹\n除show外其他群内操作需要管理权限" - }, - {"退群", "&dismiss"}, - {"退群指令", "&dismiss"}, - {"dismiss", "该指令需要群管理员权限,使用后即退出群聊\n!dismiss [目标QQ(完整或末四位)]指名退群\n!dismiss无视内置黑名单和静默状态,只要插件开启总是有效"}, - {"授权许可", "&authoize"}, - {"authorize", "授权许可(非信任用户使用时转为向管理申请许可)\n!authorize (+[群号]) ([申请理由])\n群内原地发送可省略群号,无法自动授权时会连同理由发给管理"}, - {"开关", "&bot"}, - {"bot", ".bot on/off开启/静默骰子(限群管理)\n.bot无视静默状态,只要插件开启且不在黑名单总是有效"}, - {"规则速查", "&rules"}, - {"规则", "&rules"}, - { - "rules ", - "规则速查:.rules ([规则]):[待查词条] 或.rules set [规则]\n.rules 跳跃 //复数规则有相同词条时,择一返回\n.rules COC:大失败 //coc默认搜寻coc7的词条,dnd默认搜寻3r\n.rules dnd:语言\n.rules set dnd //设置后优先查询dnd同名词条,无参数则清除设置" - }, - {"掷骰", "&r"}, - {"rd", "&r"}, - { - "r", - "掷骰:.r [掷骰表达式] ([掷骰原因]) [掷骰表达式]:([掷骰轮数]#)[骰子个数]d骰子面数(p[惩罚骰个数])(k[取点数最大的骰子数])不带参数时视为掷一个默认骰\n合法参数要求掷骰轮数1-10,奖惩骰个数1-9,个数范围1-100,面数范围1-1000\n.r3#d\t//3轮掷骰\n.rh心理学 暗骰\n.rs1D10+1D6+3 沙鹰伤害\t//省略单个骰子的点数,直接给结果\n现版本开头的r均可用o或d代替,但群聊中.ob会被识别为旁观指令" - }, - {"暗骰", "群聊限定,掷骰指令后接h视为暗骰,结果将私发本人和群内ob的用户\n为了保证发送成功,请加骰娘好友"}, - {"旁观", "&ob"}, - {"旁观模式", "&ob"}, - { - "ob", - "旁观模式:.ob (exit/list/clr/on/off)\n.ob\t//加入旁观可以看到他人暗骰结果\n.ob exit\t//退出旁观模式\n.ob list\t//查看群内旁观者\n.ob clr\t//清除所有旁观者\n.ob on\t//全群允许旁观模式\n.ob off\t//禁用旁观模式\n暗骰与旁观仅在群聊中有效" - }, - {"默认骰", "&set"}, - {"set", "当表达式中‘D’之后没有接面数时,视为投掷默认骰\n.set20 将默认骰设置为20\n.set 不带参数视为将默认骰重置为默认的100\n若所用规则判定掷骰形如2D6,推荐使用.st &=2D6"}, - {"个位骰", "个位骰有十面,为0~9十个数字,百面骰的结果为十位骰与个位骰之和(但00+0时视为100)"}, - {"十位骰", "十位骰有十面,为00~90十个数字,百面骰的结果为十位骰与个位骰之和(但00+0时视为100)"}, - {"奖励骰", "&奖励/惩罚骰"}, - {"惩罚骰", "&奖励/惩罚骰"}, - {"奖惩骰", "&奖励/惩罚骰"}, - {"奖励/惩罚骰", "COC中奖励/惩罚骰是额外投掷的十位骰,最终结果选用点数更低/更高的结果(不出现大失败的情况下等价于更小/更大的十位骰)\n.rb2 2个奖励骰\nrcp 射击 1个惩罚骰"}, - {"随机姓名", "&name"}, - { - "name", - "随机姓名:.name (cn/jp/en)([生成数量])\n.name 10\t//默认三类名称随机生成\n.name en\t//后接cn/jp/en则限定生成中文/日文/英文名\n名称生成个数范围1-10,太多容易发现姓名库的寒酸" - }, - {"设置昵称", "&nn"}, - {"昵称", "&nn"}, - { - "nn", - "设置昵称:.nn [昵称] / .nn / .nnn(cn/jp/en) \n.nn kp\t//昵称前的./!等符号会被自动忽略\n.nn\t//视为删除昵称\n.nnn\t//设置为随机昵称\n.nnn jp\t/设置限定随机昵称\n私聊.nn视为操作全局昵称\n优先级:群昵称>全局昵称>群名片>QQ昵称" - }, - {"人物作成", "该版本人物作成支持COC7(.coc、.draw调查员背景/英雄天赋)、COC6(.coc6、.draw煤气灯)、DND(.dnd)、AMGC(.draw AMGC)"}, - { - "coc", - "克苏鲁的呼唤(COC)人物作成:.coc([7/6])(d)([生成数量])\n.coc 10\t//默认生成7版人物\n.coc6d\t//接d为详细作成,一次只能作成一个\n仅用作骰点法人物作成,可应用变体规则,参考.rules创建调查员的其他选项" - }, - {"dnd", "龙与地下城(DND)人物作成:.dnd([生成数量])\n.dnd 5\t//仅作参考,可自行应用变体规则"}, - {"属性记录", "&st"}, - { - "st", - "属性记录:.st (del/clr/show) ([属性名]:[属性值])\n用户默认所有群使用同一张卡,pl如需多开请使用.pc指令切卡\n.st力量:50 体质:55 体型:65 敏捷:45 外貌:70 智力:75 意志:35 教育:65 幸运:75\n.st hp-1 后接+/-时视为从原值上变化\n.st san+1d6 修改属性时可使用掷骰表达式\n.st del kp裁决\t//删除已保存的属性\n.st clr\t//清空当前卡\n.st show 灵感\t//查看指定属性\n.st show\t//无参数时查看所有属性,请使用只st加点过技能的半自动人物卡!\n部分COC属性会被视为同义词,如智力/灵感、理智/san、侦查/侦察" - }, - {"角色卡", "&pc"}, - { - "pc", - R"(角色卡:.pc -每名用户最多可同时保存16张角色卡 -.pc new ([模板]:([生成参数]:))([卡名]) -完全省略参数将生成一张COC7模板的随机姓名卡 -.pc tag ([卡名]) //为当前群绑定指定卡,为空则解绑使用默认卡 -所有群默认使用私聊绑定卡,未绑定则使用0号卡 -.pc show ([卡名]) //展示指定卡所有记录的属性,为空则展示当前卡 -.pc nn [新卡名] //重命名当前卡,不允许重名 -.pc cpy [卡名1]=[卡名2] //将后者属性复制给前者 -.pc del [卡名] //删除指定卡 -.pc list //列出全部角色卡 -.pc grp //列出各群绑定卡 -.pc build ([生成参数]:)(卡名) //根据模板填充生成属性(COC7为9项主属性) -.pc redo ([生成参数]:)(卡名) //清空原有属性后重新生成 -.pc clr //销毁全部角色卡记录 -)" - }, - {"rc", "&rc/ra"}, - {"ra", "&rc/ra"}, - {"检定", "&rc/ra"}, - { - "rc/ra", - "检定指令:.rc/ra [属性名]([成功率])\n角色卡设置了属性时,可省略成功率\n.rc体质*5\t//允许使用+-*/,但顺序要求为乘法>加减>除法\n.rc 困难幸运\t//技能名开头的困难和极难会被视为关键词\n.rc 敏捷-10\t//修正后成功率必须在1-1000内\n.rcp 手枪\t//奖惩骰至多9个\n默认以规则书判定,大成功大失败的房规由.setcoc设置" - }, - { - "setcoc", - "为当前群或讨论组设置COC房规,如.setcoc 1,当前参数0-5\n0 规则书\n出1大成功\n不满50出96 - 100大失败,满50出100大失败\n1\n不满50出1大成功,满50出1 - 5大成功\n不满50出96 - 100大失败,满50出100大失败\n2\n出1 - 5且 <= 成功率大成功\n出100或出96 - 99且 > 成功率大失败\n3\n出1 - 5大成功\n出96 - 100大失败\n4\n出1 - 5且 <= 十分之一大成功\n不满50出 >= 96 + 十分之一大失败,满50出100大失败\n5\n出1 - 2且 < 五分之一大成功\n不满50出96 - 100大失败,满50出99 - 100大失败\n" - }, - {"san check", "&sc"}, - {"理智检定", "&sc"}, - { - "sc", - "San Check指令:.sc[成功损失]/[失败损失] ([当前san值])\n已经.st了理智/san时,可省略最后的参数\n.sc0/1 70\n.sc1d10/1d100 直面外神\n大失败自动失去最大值\n当调用角色卡san时,san会自动更新为sc后的剩余值\n程序上可以损失负数的san,也就是可以用.sc-1d6/-1d6来回复san,但请避免这种奇怪操作" - }, - {"ti", "&ti/li"}, - {"li", "&ti/li"}, - {"疯狂症状", "&ti/li"}, - {"ti/li", "疯狂症状:\n.ti 临时疯狂症状\n.li 总结疯狂症状\n适用coc7版规则,6版请自行用百面骰配合查表\n具体适用哪项参见.rules 疯狂发作"}, - {"成长检定", "&en"}, - {"增强检定", "&en"}, - { - "en", - "成长检定:.en [技能名称]([技能值])(([失败成长值]/)[成功成长值])\n已经.st时,可省略最后的参数\n.en 教育 60 +1D10 教育增强\t//后接成功时成长值\n.en 幸运 +1D3/1D10幸运成长\t//调用人物卡属性时,成长后的值会自动更新\n可变成长值必须以加减号开头,不限制加减" - }, - {"抽牌", "&draw"}, - { - "draw", - "抽牌:.draw [牌堆名称] ([抽牌数量])\t//抽到的牌不放回,抽牌数量不能超过牌堆数量\n当前内置牌堆:硬币/性别/\n调查员职业/调查员背景/英雄天赋/煤气灯/个人描述/思想信念/重要之人/重要之人理由/意义非凡之地/宝贵之物/调查员特点/即时症状/总结症状/恐惧症状/狂躁症状/\n阵营/哈罗花色/冒险点子/\n人偶暗示/人偶宝物/人偶记忆碎片/人偶依恋/\nAMGC/AMGC身材/AMGC专精/AMGC武器/AMGC套装/AMGC才能/AMGC特技1/AMGC特技2/AMGC特技3\n/塔罗牌/正逆/塔罗牌占卜/单张塔罗牌/圣三角牌阵/四要素牌阵/小十字牌阵/六芒星牌阵/凯尔特十字牌阵\n.help审判正位(牌+方向)可获取塔罗牌解读\n扩展牌堆:{扩展牌堆}" - }, - {"先攻", "&ri"}, - {"扩展牌堆", ""}, - {"ri", "先攻(群聊限定):.ri([加值])([昵称])\n.ri -1 某pc\t//自动记入先攻列表\n.ri +5 boss"}, - {"先攻列表", "&init"}, - {"init", "先攻列表:\n.init\t//查看先攻列表\n.init clr\t//清除先攻列表"}, - {"骰池", "&ww"}, - {"ww", "骰池:.w(w) [骰子个数]a[加骰参数]\n.w会直接给出结果而.ww会给出每个骰子的点数\n固定10面骰,每有一个骰子点数达到加骰参数,则加骰一次,最后计算点数达到8的骰子数\n具体用法请参考相关游戏规则"}, - {"第三人称", "&me"}, - {"第三人称动作", "&me"}, - { - "me", - "第三人称动作:.me([群号])[动作].me 笑出了声\t//仅限群聊使用\n.me 941980833 抱群主\t//仅限私聊使用,此命令可伪装成骰子在群里说话\n.me off\t//群内禁用.me\n.me on\t//群内启用.me" - }, - {"今日人品", "&jrrp"}, - { - "jrrp", - "今日人品:.jrrp\n.jrrp off\t//群内禁用jrrp\n.jrrp on\t//群内启用jrrp\n一天一个人品值\n2.3.5版本后随机值为均匀分布\n骰娘只负责从溯洄的服务器搬运结果,请勿无能狂怒\n如何发配所有人的命运,只有孕育万千骰娘生机之母,萌妹吃鱼之神,正五棱双角锥体对的监护人,一切诡秘的窥见者,时空舞台外的逆流者,永转的命运之轮——溯洄本体掌握" - }, - {"send", "发送消息:.send 想对Master说的话\n如果用来调戏Master请做好心理准备"}, - {"入群欢迎", "&welcome"}, - {"入群欢迎词", "&welcome"}, - { - "welcome", - "入群欢迎词:.welcome\n.welcome \\\\{at}欢迎\\\\{nick}入群!\t//\\\\{at}视为at入群者,\\\\{nick}会替换为新人的昵称\n.welcome\t//不带参数时清除欢迎词\n无论开关状态,只要有欢迎词时有人入群,都会响应" - }, - {"group", "&群管"}, - { - "群管", - R"(群管指令.group(群管理员限定) -.group state //查看在群内对骰娘的设置 -.group pause/restart //群全体禁言/全体解除禁言 -.group card [at/用户QQ] [名片] //设置群员名片 -.group title [at/用户QQ] [头衔] //设置群员头衔 -.group diver //查看潜水成员 -.group +/-[群管词条] //为群加减设置,需要对应权限 -例:.group +禁用回复 //关闭本群自定义回复 -群管词条:停用指令/禁用回复/禁用jrrp/禁用draw/禁用me/禁用help/禁用ob/拦截消息/许可使用/免清/免黑)" - }, - {"溯洄", "孕育万千骰娘生机之母,萌妹吃鱼之神,正五棱双角锥体对的监护人,一切诡秘的窥见者,拟人者主宰,时空舞台外的逆流者,永转的命运之轮"}, - {"投食", "投食Shiki,请选择http://shiki.stringempty.xyz/alipay.png\n投食溯洄,可选择https://afdian.net/@suhuiw4123"}, - {"愚者正位", "憧憬自然的地方、毫无目的地前行、喜欢尝试挑战新鲜事物、四处流浪。美好的梦想。"}, - { - "愚者逆位", - "冒险的行动,追求可能性,重视梦想,无视物质的损失,离开家园,过于信赖别人,为出外旅行而烦恼。心情空虚、轻率的恋情、无法长久持续的融洽感、不安的爱情的旅程、对婚姻感到束缚、彼此忽冷忽热、不顾众人反对坠入爱河、为恋人的负心所伤、感情不专一。" - }, - {"魔术师正位", "事情的开始,行动的改变,熟练的技术及技巧,贯彻我的意志,运用自然的力量来达到野心。"}, - {"魔术师逆位", "意志力薄弱,起头难,走入错误的方向,知识不足,被骗和失败。"}, - {"女祭司正位", "开发出内在的神秘潜力,前途将有所变化的预言,深刻地思考,敏锐的洞察力,准确的直觉。"}, - {"女祭司逆位", "过于洁癖,无知,贪心,目光短浅,自尊心过高,偏差的判断,有勇无谋,自命不凡。"}, - {"女皇正位", "幸福,成功,收获,无忧无虑,圆满的家庭生活,良好的环境,美貌,艺术,与大自然接触,愉快的旅行,休闲。"}, - {"女皇逆位", "不活泼,缺乏上进心,散漫的生活习惯,无法解决的事情,不能看到成果,担于享乐,环境险恶,与家人发生纠纷。"}, - {"皇帝正位", "光荣,权力,胜利,握有领导权,坚强的意志,达成目标,父亲的责任,精神上的孤单。"}, - {"皇帝逆位", "幼稚,无力,独裁,撒娇任性,平凡,没有自信,行动力不足,意志薄弱,被支配。"}, - {"教皇正位", "援助,同情,宽宏大量,可信任的人给予的劝告,良好的商量对象,得到精神上的满足,遵守规则,志愿者。"}, - {"教皇逆位", "错误的讯息,恶意的规劝,上当,援助被中断,愿望无法达成,被人利用,被放弃。"}, - {"恋人正位", "撮合,爱情,流行,兴趣,充满希望的未来,魅力,增加朋友。"}, - {"恋人逆位", "禁不起诱惑,纵欲过度,反覆无常,友情变淡,厌倦,争吵,华丽的打扮,优柔寡断。"}, - {"战车正位", "努力而获得成功,胜利,克服障碍,行动力,自立,尝试,自我主张,年轻男子,交通工具,旅行运大吉。"}, - {"战车逆位", "争论失败,发生纠纷,阻滞,违返规则,诉诸暴力,顽固的男子,突然的失败,不良少年,挫折和自私自利。"}, - {"力量正位", "大胆的行动,有勇气的决断,新发展,大转机,异动,以意志力战胜困难,健壮的女人。"}, - {"力量逆位", "胆小,输给强者,经不起诱惑,屈服在权威与常识之下,没有实践便告放弃,虚荣,懦弱,没有耐性。"}, - {"隐者正位", "隐藏的事实,个别的行动,倾听他人的意见,享受孤独,有益的警戒,年长者,避开危险,祖父,乡间生活。"}, - {"隐者逆位", "憎恨孤独,自卑,担心,幼稚思想,过于慎重导致失败,偏差,不宜旅行。"}, - {"命运之轮正位", "关键性的事件,有新的机会,因的潮流,环境的变化,幸运的开端,状况好转,问题解决,幸运之神降临。"}, - {"命运之轮逆位", "挫折,计划泡汤,障碍,无法修正方向,往坏处发展,恶性循环,中断。"}, - {"正义正位", "公正、中立、诚实、心胸坦荡、表里如一、身兼二职、追求合理化、协调者、与法律有关、光明正大的交往、感情和睦。"}, - {"正义逆位", "失衡、偏见、纷扰、诉讼、独断专行、问心有愧、无法两全、表里不一、男女性格不合、情感波折、无视社会道德的恋情。"}, - {"倒吊人正位", "接受考验、行动受限、牺牲、不畏艰辛、不受利诱、有失必有得、吸取经验教训、浴火重生、广泛学习、奉献的爱。"}, - {"倒吊人逆位", "无谓的牺牲、骨折、厄运、不够努力、处于劣势、任性、利己主义者、缺乏耐心、受惩罚、逃避爱情、没有结果的恋情。"}, - {"死神正位", "失败、接近毁灭、生病、失业、维持停滞状态、持续的损害、交易停止、枯燥的生活、别离、重新开始、双方有很深的鸿沟、恋情终止。"}, - {"死神逆位", "抱有一线希望、起死回生、回心转意、摆脱低迷状态、挽回名誉、身体康复、突然改变计划、逃避现实、斩断情丝、与旧情人相逢。"}, - {"节制正位", "单纯、调整、平顺、互惠互利、好感转为爱意、纯爱、深爱。"}, - {"节制逆位", "消耗、下降、疲劳、损失、不安、不融洽、爱情的配合度不佳。"}, - {"恶魔正位", "被束缚、堕落、生病、恶意、屈服、欲望的俘虏、不可抗拒的诱惑、颓废的生活、举债度日、不可告人的秘密、私密恋情。"}, - {"恶魔逆位", "逃离拘束、解除困扰、治愈病痛、告别过去、暂停、别离、拒绝诱惑、舍弃私欲、别离时刻、爱恨交加的恋情。"}, - {"塔正位", "破产、逆境、被开除、急病、致命的打击、巨大的变动、受牵连、信念崩溃、玩火自焚、纷扰不断、突然分离,破灭的爱。"}, - {"塔逆位", "困境、内讧、紧迫的状态、状况不佳、趋于稳定、骄傲自大将付出代价、背水一战、分离的预感、爱情危机。"}, - {"星星正位", "前途光明、充满希望、想象力、创造力、幻想、满足愿望、水准提高、理想的对象、美好的恋情。"}, - {"星星逆位", "挫折、失望、好高骛远、异想天开、仓皇失措、事与愿违、工作不顺心、情况悲观、秘密恋情、缺少爱的生活。"}, - {"月亮正位", "不安、迷惑、动摇、谎言、欺骗、鬼迷心窍、动荡的爱、三角关系。"}, - {"月亮逆位", "逃脱骗局、解除误会、状况好转、预知危险、等待、正视爱情的裂缝。"}, - {"太阳正位", "活跃、丰富的生命力、充满生机、精力充沛、工作顺利、贵人相助、幸福的婚姻、健康的交际。"}, - {"太阳逆位", "消沉、体力不佳、缺乏连续性、意气消沉、生活不安、人际关系不好、感情波动、离婚。"}, - {"审判正位", "复活的喜悦、康复、坦白、好消息、好运气、初露锋芒、复苏的爱、重逢、爱的奇迹。"}, - {"审判逆位", "一蹶不振、幻灭、隐瞒、坏消息、无法决定、缺少目标、没有进展、消除、恋恋不舍。"}, - {"世界正位", "完成、成功、完美无缺、连续不断、精神亢奋、拥有毕生奋斗的目标、完成使命、幸运降临、快乐的结束、模范情侣。"}, - {"世界逆位", "未完成、失败、准备不足、盲目接受、一时不顺利、半途而废、精神颓废、饱和状态、合谋、态度不够融洽、感情受挫。"}, -}; - -std::string getMsg(const std::string& key, const std::map& maptmp) -{ - const auto it = GlobalMsg.find(key); - if (it != GlobalMsg.end())return format(it->second, GlobalMsg, maptmp); - return ""; -} +/* + * _______ ________ ________ ________ __ + * | __ \ |__ __| | _____| | _____| | | + * | | | | | | | | | |_____ | | + * | | | | | | | | | _____| |__| + * | |__| | __| |__ | |_____ | |_____ __ + * |_______/ |________| |________| |________| |__| + * + * Dice! QQ Dice Robot for TRPG + * Copyright (C) 2018-2021 w4123溯洄 + * Copyright (C) 2019-2021 String.Empty + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this + * program. If not, see . + */ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif +#include "GlobalVar.h" +#include "MsgFormat.h" + +bool Enabled = false; + +//QQFrame frame{ QQFrame::CoolQ }; + +std::string Dice_Full_Ver_On; + +//std::string strModulePath; + +#ifdef _WIN32 +HMODULE hDllModule = nullptr; +#endif + +bool msgSendThreadRunning = false; + +std::map GlobalMsg +{ + {"strParaEmpty","参数不能为空×"}, //偷懒用万能回复 + {"strParaIllegal","参数非法×"}, //偷懒用万能回复 + {"stranger","用户"}, //{nick}无法获取非空昵称时的称呼 + {"strAdminOptionEmpty","找{self}有什么事么?{nick}"}, // + {"strLogNew","{self}开始新日志记录√\n请适时用.log off暂停或.log end完成记录"}, + {"strLogOn","{self}开始日志记录√\n可使用.log off暂停记录"}, + {"strLogOnAlready","{self}正在记录中!"}, + {"strLogOff","{self}已暂停日志记录√\n可使用.log on恢复记录"}, + {"strLogOffAlready","{self}已经暂停记录!"}, + {"strLogEnd","{self}已完成日志记录√\n正在上传日志文件{log_file}"}, + {"strLogEndEmpty","{self}已结束记录√\n本次无日志产生"}, + {"strLogNullErr","{self}无日志记录或已结束!"}, + {"strLogUpSuccess","{self}已完成日志上传√\n请访问 https://logpainter.kokona.tech/?s3={log_file} 以查看记录"}, + {"strLogUpFailure","{self}上传日志文件失败,正在第{retry}次重传{log_file}…{ret}"}, + {"strLogUpFailureEnd","很遗憾,{self}无法成功上传日志文件×\n{ret}\n如需获取可联系Master:{master_QQ}\n文件名:{log_file}"}, + {"strGMTableShow","{self}记录的{table_name}列表: {res}"}, + {"strGMTableClr","{self}已清除{table_name}表√"}, + {"strGMTableItemDel","{self}已移除{table_name}表的项目{table_item}√"}, + {"strGMTableNotExist","{self}没有保存{table_name}表×"}, + {"strGMTableItemNotFound","{self}没有找到{table_name}表的项目{table_item}×"}, + {"strGMTableItemEmpty","请告知{self}待移除的{table_name}列表项目×"}, + {"strUserTrustShow","{user}在{self}处的信任级别为{trust}"}, + {"strUserTrusted","已将{self}对{user}的信任级别调整为{trust}"}, + {"strUserTrustDenied","{nick}在{self}处无权访问对方的权限×"}, + {"strUserTrustIllegal","将目标权限修改为{trust}是非法的×"}, + {"strUserNotFound","{self}无{user}的用户记录"}, + {"strGroupAuthorized","A roll to the table turns to a dice fumble!\nDice Roller {strSelfName}√\n本群已授权许可,请尽情使用本骰娘√\n请遵守协议使用,服务结束后使用.dismiss送出!" }, + {"strGroupLicenseDeny","本群未获{self}许可使用,自动在群内静默。\n请先.help协议 阅读并同意协议后向运营方申请许可使用,\n否则请管理员使用!dismiss送出{self}\n可按以下格式填写并发送申请:\n!authorize 申请用途:[ **请写入理由** ] 我已了解Dice!基本用法,仔细阅读并保证遵守{strSelfName}的用户协议,如需停用指令使用[ **请写入指令** ],用后使用[ **请写入指令** ]送出群" }, + {"strGroupLicenseApply","此群未通过自助授权×\n许可申请已发送√" }, + {"strGroupSetOn","现已开启{self}在此群的“{option}”选项√"}, //群内开关和遥控开关通用此文本 + {"strGroupSetOnAlready","{self}已在此群设置了{option}!"}, + {"strGroupSetOff","现已关闭{self}在此群的“{option}”选项√"}, + {"strGroupSetOffAlready","{self}未在此群设置{option}!"}, + {"strGroupMultiSet","{self}已将此群的选项修改为:{opt_list}"}, + {"strGroupSetAll","{self}已修改记录中{cnt}个群的“{option}”选项√"}, + {"strGroupDenied","{nick}在{self}处无权访问此群的设置×"}, + {"strGroupSetDenied","{nick}在{self}处设置{option}的权限不足×"}, + {"strGroupSetInvalid","{nick}尝试设置无效的群词条{option}×"}, + {"strGroupSetNotExist","{self}无{option}此选项×"}, + {"strGroupWholeUnban","{self}已关闭全局禁言√"}, + {"strGroupWholeBan","{self}已开启全局禁言√"}, + {"strGroupWholeBanErr","{self}开启全局禁言失败×"}, + {"strGroupUnban","{self}裁定:{member}解除禁言√"}, + {"strGroupBan","{self}裁定:{member}禁言{res}分钟√"}, + {"strGroupNotFound","{self}无该群记录×"}, + {"strGroupNot","{group}不是群!"}, + {"strGroupNotIn","{self}当前不在{group}内×"}, + {"strGroupExit","{self}已退出该群√"}, + {"strGroupCardSet","{self}已将{target}的群名片修改为{card}√"}, + {"strGroupTitleSet","{self}已将{target}的头衔修改为{title}√"}, + {"strPcNewEmptyCard","已为{nick}新建{type}空白卡{char}√"}, + {"strPcNewCardShow","已为{nick}新建{type}卡{char}:{show}"},//由于预生成选项而存在属性 + {"strPcCardSet","已将{nick}当前角色卡绑定为{char}√"},//{nick}-用户昵称 {pc}-原角色卡名 {char}-新角色卡名 + {"strPcCardReset","已解绑{nick}当前的默认卡√"},//{nick}-用户昵称 {pc}-原角色卡名 + {"strPcCardRename","已将{old_name}重命名为{new_name}√"}, + {"strPcCardDel","已将角色卡{char}删除√"}, + {"strPcCardCpy","已将{char2}的属性复制到{char1}√"}, + {"strPcClr","已清空{nick}的角色卡记录√"}, + {"strPcCardList","{nick}的角色列表:{show}"}, + {"strPcCardBuild","{nick}的{char}生成:{show}"}, + {"strPcCardShow","{nick}的<{type}>{char}:{show}"}, //{nick}-用户昵称 {type}-角色卡类型 {char}-角色卡名 + {"strPcCardRedo","{nick}的{char}重新生成:{show}"}, + {"strPcGroupList","{nick}的各群角色列表:{show}"}, + {"strPcNotExistErr","{self}无{nick}的角色卡记录,无法删除×"}, + {"strPcCardFull","{nick}在{self}处的角色卡已达上限,请先清理多余角色卡×"}, + {"strPcTempInvalid","{self}无法识别的角色卡模板×"}, + {"strPcNameEmpty","名称不能为空×"}, + {"strPcNameExist","{nick}已存在同名卡×"}, + {"strPcNameNotExist","{nick}无该名称角色卡×"}, + {"strPcNameInvalid","非法的角色卡名(存在冒号)×"}, + {"strPcInitDelErr","{nick}的初始卡不可删除×"}, + {"strPcNoteTooLong","备注长度不能超过255×"}, + {"strPcTextTooLong","文本长度不能超过48×"}, + {"strCOCBuild","{pc}的调查员作成:{res}"}, + {"strDNDBuild","{pc}的英雄作成:{res}"}, + {"strCensorCaution","提醒:{nick}的指令包含敏感词,{self}已上报"}, + {"strCensorWarning","警告:{nick}的指令包含敏感词,{self}已记录并上报!"}, + {"strCensorDanger","警告:{nick}的指令包含敏感词,{self}拒绝指令并已上报!"}, + //{"strCensorCritical","警告:{nick}的指令包含敏感词,{self}已记录并上报!"}, + {"strSpamFirstWarning","你短时间内对{self}指令次数过多!请善用多轮掷骰和复数生成指令(刷屏初次警告)"}, + {"strSpamFinalWarning","请暂停你的一切指令,避免因高频指令被{self}拉黑!(刷屏最终警告)"}, + {"strReplySet","{self}对关键词{key}的回复已设置√"}, + {"strReplyDel","{self}对关键词{key}的回复已清除√"}, + {"strStModify","{self}对已记录{pc}的属性变化:"}, //存在技能值变化情况时,优先使用此文本 + {"strStDetail","{self}对已设置{pc}的属性:"}, //存在掷骰时,使用此文本(暂时无用) + {"strStValEmpty","{self}未记录{attr}原值×"}, + {"strBlackQQAddNotice","{user_nick},你已被{self}加入黑名单,详情请联系Master:{master_QQ}"}, + {"strBlackQQAddNoticeReason","{user_nick},由于{reason},你已被{self}加入黑名单,申诉解封请联系管理员。Master:{master_QQ}"}, + {"strBlackQQDelNotice","{user_nick},你已被{self}移出黑名单,现在可以继续使用了"}, + {"strWhiteQQAddNotice","{user_nick},您已获得{self}的信任,请尽情使用{self}√"}, + {"strWhiteQQDenied","你不是{self}信任的用户×"}, + {"strWhiteGroupDenied","本群聊不在白名单中×"}, + {"strDeckNew","{self}已为{nick}自定义新牌堆<{deck_name}>√"}, + {"strDeckSet","{nick}已用<{deck_name}>创建{self}的牌堆实例√"}, + {"strDeckSetRename","{nick}已用<{deck_cited}>创建{self}的牌堆实例{deck_name}√"}, + {"strDeckRestEmpty","牌堆<{deck_name}>已抽空,请使用.deck reset {deck_name}手动重置牌堆"}, + {"strDeckOversize","{nick}定义的牌太多,{self}装不下啦×"}, + {"strDeckRestShow","当前牌堆<{deck_name}>剩余卡牌:{deck_rest}"}, + {"strDeckRestReset","{self}已重置牌堆实例<{deck_name}>√"}, + {"strDeckDelete","{self}已移除牌堆实例<{deck_name}>√"}, + {"strDeckListShow","在{self}处创建的牌堆实例有:{res}"}, + {"strDeckListClr","{nick}已清空{self}处牌堆实例√"}, + {"strDeckListEmpty","{self}处牌堆实例列表为空!"}, + {"strDeckNewEmpty","{self}无法为{nick}新建虚空牌堆×"}, + {"strDeckListFull","{self}处牌堆实例已达上限,请先清理无用实例×"}, + {"strDeckNotFound","{self}找不到牌堆{deck_name}×"}, + {"strDeckCiteNotFound","{self}找不到公共牌堆{deck_cited}×" }, + {"strDeckNameEmpty","未指定牌堆名×"}, + {"strRangeEmpty","{self}没法对着空气数数×" }, + {"strOutRange","{nick}定义的数列超出{self}允许范围×" }, + {"strRollDice","{pc}掷骰: {res}"}, + {"strRollDiceReason","{pc}掷骰 {reason}: {res}"}, + {"strRollHidden","{pc}进行了一次暗骰"}, + {"strRollTurn","{pc}的掷骰轮数: {turn}轮"}, + {"strRollMultiDice","{pc}掷骰{turn}次: {dice_exp}={res}"}, + {"strRollMultiDiceReason","{pc}掷骰{turn}次{reason}: {dice_exp}={res}"}, + {"strRollSkill","{pc}进行{attr}检定:"}, + {"strRollSkillReason","由于{reason} {pc}进行{attr}检定:"}, + {"strRollSkillHidden","{pc}进行了一次暗中{attr}检定√" }, + {"strEnRoll","{pc}的{attr}增强或成长检定:\n{res}"},//{attr}在用户省略技能名后替换为{strEnDefaultName} + {"strEnRollNotChange","{strEnRoll}\n{pc}的{attr}值没有变化"}, + {"strEnRollFailure","{strEnRoll}\n{pc}的{attr}变化{change}点,当前为{final}点"}, + {"strEnRollSuccess","{strEnRoll}\n{pc}的{attr}增加{change}点,当前为{final}点"}, + {"strEnDefaultName","属性或技能"},//默认文本 + {"strEnValEmpty", "未对{self}设定待成长属性值,请先.st {attr} 属性值 或查看.help en×"}, + {"strEnValInvalid", "{attr}值输入不正确,请输入1-99范围内的整数!"}, + {"strSendMsg","{self}已将消息送出√"},//Master定向发送的回执 + {"strSendMasterMsg","消息{self}已发送给Master√"},//向Master发送的回执 + {"strSendMsgEmpty","发送消息内容为空×"}, + {"strSendMsgInvalid","{self}没有可以发送的对象×"},//没有Master + {"strDefaultCOCClr","默认检定房规已清除√"}, + {"strDefaultCOCNotFound","默认检定房规不存在×"}, + {"strDefaultCOCSet","默认检定房规已设置:"}, + {"strLinked","{self}已为对象建立链接√"}, + {"strLinkClose","{self}已断开与对象的链接√" }, + {"strLinkBusy","{nick}的目标已经有对象啦×\n{self}不支持多边关系" }, + {"strLinkedAlready","{self}正在被其他对象链接×\n请{nick}先断绝当前关系" }, + {"strLinkingAlready","{self}已经开启链接啦!" }, + {"strLinkCloseAlready","{self}断开链接失败:{nick}当前本就没有对象!" }, + {"strLinkNotFound","{self}找不到{nick}的对象×"}, + {"strNotMaster","你不是{self}的master!你想做什么?"}, + {"strNotAdmin","你不是{self}的管理员×"}, + {"strAdminDismiss","{strDismiss}"}, //管理员指令退群的回执 + {"strDismiss",""}, //.dismiss退群前的回执 + {"strHlpSet","已为{key}设置词条√"}, + {"strHlpReset","已清除{key}的词条√"}, + {"strHlpNameEmpty","Master想要自定义什么词条呀?"}, + {"strHelpNotFound","{self}未找到【{help_word}】相关的词条×"}, + {"strHelpSuggestion","{self}猜{nick}想要查找的是:{res}"}, + {"strHelpRedirect","{self}仅找到相近词条【{redirect_key}】:\n{redirect_res}" }, + {"strClockToWork","{self}已按时启用√"}, + {"strClockOffWork","{self}已按时关闭√"}, + {"strNameGenerator","{pc}的随机名称:{res}"}, + {"strDrawCard", "来看看{pc}抽到了什么:{res}"}, + {"strDrawHidden", "{pc}抽了{cnt}张手牌√" }, + {"strMeOn", "成功在这里启用{self}的.me命令√"}, + {"strMeOff", "成功在这里禁用{self}的.me命令√"}, + {"strMeOnAlready", "在这里{self}的.me命令没有被禁用!"}, + {"strMeOffAlready", "在这里{self}的.me命令已经被禁用!"}, + {"strObOn", "成功在这里启用{self}的旁观模式√"}, + {"strObOff", "成功在这里禁用{self}的旁观模式√"}, + {"strObOnAlready", "在这里{self}的旁观模式没有被禁用!"}, + {"strObOffAlready", "在这里{self}的旁观模式已经被禁用!"}, + {"strObList", "当前{self}的旁观者有:"}, + {"strObListEmpty", "当前{self}暂无旁观者"}, + {"strObListClr", "{self}成功删除所有旁观者√"}, + {"strObEnter", "{nick}成功加入{self}的旁观√"}, + {"strObExit", "{nick}成功退出{self}的旁观√"}, + {"strObEnterAlready", "{nick}已经处于{self}的旁观模式!"}, + {"strObExitAlready", "{nick}没有加入{self}的旁观模式!"}, + {"strQQIDEmpty", "QQ号不能为空×"}, + {"strGroupIDEmpty", "群号不能为空×"}, + {"strBlackGroup", "该群在黑名单中,如有疑问请联系master"}, + {"strBotOn", "成功开启{self}√"}, + {"strBotOff", "成功关闭{self}√"}, + {"strBotOnAlready", "{self}已经处于开启状态!"}, + {"strBotOffAlready", "{self}已经处于关闭状态!"}, + {"strRollCriticalSuccess", "大成功!"}, //一般检定用 + {"strRollExtremeSuccess", "极难成功"}, + {"strRollHardSuccess", "困难成功"}, + {"strRollRegularSuccess", "成功"}, + {"strRollFailure", "失败"}, + {"strRollFumble", "大失败!"}, + {"strFumble", "大失败!"}, //多轮检定用,请控制长度 + {"strFailure", "失败"}, + {"strSuccess", "成功"}, + {"strHardSuccess", "困难成功"}, + {"strExtremeSuccess", "极难成功"}, + {"strCriticalSuccess", "大成功!"}, + {"strNumCannotBeZero", "无意义的数目!莫要消遣于我!"}, + {"strDeckNotFound", "是说{deck_name}?{self}没听说过的牌堆名呢……"}, + {"strDeckEmpty", "{self}已经一张也不剩了!"}, + {"strNameNumTooBig", "生成数量过多!请输入1-10之间的数字!"}, + {"strNameNumCannotBeZero", "生成数量不能为零!请输入1-10之间的数字!"}, + {"strSetInvalid", "无效的默认骰!请输入1-9999之间的数字!"}, + {"strSetTooBig", "这面数……让我丢个球啊!请输入1-9999之间的数字!"}, + {"strSetCannotBeZero", "默认骰不能为零!请输入1-9999之间的数字!"}, + {"strCharacterCannotBeZero", "人物作成次数不能为零!请输入1-10之间的数字!"}, + {"strCharacterTooBig", "人物作成次数过多!请输入1-10之间的数字!"}, + {"strCharacterInvalid", "人物作成次数无效!请输入1-10之间的数字!"}, + {"strSanRoll", "{pc}的San Check:\n{res}"}, + {"strSanRollRes", "{strSanRoll}\n{pc}的San值减少{change}点,当前剩余{final}点"}, + {"strSanCostInvalid", "SC表达式输入不正确,格式为成功扣San/失败扣San,如1/1d6!"}, + {"strSanInvalid", "San值输入不正确,请输入1-99范围内的整数!"}, + {"strSanEmpty", "未设定San值,请先.st san 或查看.help sc×"}, + {"strSuccessRateErr", "这成功率还需要检定吗?"}, + {"strGroupIDInvalid", "无效的群号!"}, + {"strSendErr", "消息发送失败!"}, + {"strSendSuccess", "命令执行成功√"}, + {"strDisabledErr", "命令无法执行:机器人已在此群中被关闭!"}, + {"strActionEmpty", "动作不能为空×"}, + {"strMEDisabledErr", "管理员已在此群中禁用.me命令!"}, + {"strDisabledMeGlobal", "恕不提供.me服务×"}, + {"strDisabledJrrpGlobal", "恕不提供.jrrp服务×"}, + {"strDisabledDeckGlobal", "恕不提供.deck服务×"}, + {"strDisabledDrawGlobal", "恕不提供.draw服务×"}, + {"strDisabledSendGlobal", "恕不提供.send服务×"}, + {"strHELPDisabledErr", "管理员已在此群中禁用.help命令!"}, + {"strNameDelEmpty", "{nick}没有设置名称,无法删除!"}, + {"strValueErr", "掷骰表达式输入错误!"}, + {"strInputErr", "命令或掷骰表达式输入错误!"}, + {"strUnknownErr", "发生了未知错误!"}, + {"strUnableToGetErrorMsg", "无法获取错误信息!"}, + {"strDiceTooBigErr", "{self}被你扔出的骰子淹没了×"}, + {"strRequestRetCodeErr", "访问服务器时出现错误! HTTP状态码: {error}"}, + {"strRequestNoResponse", "服务器未返回任何信息×"}, + {"strTypeTooBigErr", "哇!让我数数骰子有多少面先~1...2..."}, + {"strZeroTypeErr", "这是...!!时空裂({self}被骰子产生的时空裂缝卷走了)"}, + {"strAddDiceValErr", "你这样要让{self}扔骰子扔到什么时候嘛~(请输入正确的加骰参数:5-10之内的整数)"}, + {"strZeroDiceErr", "咦?我的骰子呢?"}, + {"strRollTimeExceeded", "掷骰轮数超过了最大轮数限制!"}, + {"strRollTimeErr", "异常的掷骰轮数"}, + {"strDismissPrivate", "滚!"}, + {"strWelcomePrivate", "你在这欢迎谁呢?"}, + {"strWelcomeMsgClearNotice", "已清除本群的入群欢迎词√"}, + {"strWelcomeMsgClearErr", "没有设置入群欢迎词,清除失败×"}, + {"strWelcomeMsgUpdateNotice", "{self}已更新本群的入群欢迎词√"}, + {"strPermissionDeniedErr", "请让群内管理对{self}发送该指令×"}, + {"strSelfPermissionErr", "{self}权限不够无能为力呢×"}, + {"strNameTooLongErr", "名称过长×(最多为50英文字符)"}, + {"strNameClr", "已将{nick}的名称删除√"}, + {"strNameSet", "已将{nick}的名称更改为{new_nick}√"}, + {"strUnknownPropErr", "未设定{attr}成功率,请先.st {attr} 技能值 或查看.help rc×"}, + {"strEmptyWWDiceErr", "格式错误:正确格式为.w(w)XaY!其中X≥1, 5≤Y≤10"}, + {"strPropErr", "请认真的输入你的属性哦~"}, + {"strSetPropSuccess", "属性设置成功√"}, + {"strPropCleared", "已清空{char}的所有属性√"}, + {"strRuleReset", "已重置默认规则√"}, + {"strRuleSet", "已设置默认规则√"}, + {"strRuleErr", "规则数据获取失败,具体信息:\n"}, + {"strRulesFailedErr", "请求失败,{self}无法连接数据库×"}, + {"strPropDeleted", "已删除{pc}的{attr}√"}, + {"strPropNotFound", "属性{attr}不存在×"}, + {"strRuleNotFound", "{self}未找到对应的规则信息×"}, + {"strProp", "{pc}的{attr}为{val}"}, + {"strPropList", "{nick}的{char}属性列表为:{show}"}, + {"strStErr", "格式错误:请参考.help st获取.st命令的使用方法"}, + {"strRulesFormatErr", "格式错误:正确格式为.rules[规则名称:]规则条目 如.rules COC7:力量"}, + {"strLeaveDiscuss", "{self}现不支持讨论组服务,即将退出"}, + {"strLeaveNoPower", "{self}未获得群管理,即将退群"}, + {"strLeaveUnused", "{self}已经在这里被放置{day}天啦,马上就会离开这里了"}, + {"strGlobalOff", "{self}休假中,暂停服务×"}, + {"strPreserve", "{self}私有私用,勿扰勿怪\n如需申请许可请发送!authorize +[群号] 申请用途:[ **请写入理由** ] 我已了解Dice!基本用法,仔细阅读并保证遵守{strSelfName}的用户协议,如需停用指令使用[ **请写入指令** ],用后使用[ **请写入指令** ]送出群"}, + {"strJrrp", "{nick}今天的人品值是: {res}"}, + {"strJrrpErr", "JRRP获取失败! 错误信息: \n{res}"}, + { "strFriendDenyNotUser", "很遗憾,你没有对{self}使用指令的记录" }, + { "strFriendDenyNoTrust", "很遗憾,你不是{self}信任的用户,如需使用可联系{master_QQ}" }, + {"strAddFriendWhiteQQ", "{strAddFriend}"}, //白名单用户添加好友时回复此句 + { + "strAddFriend", + R"(欢迎选择{strSelfName}的免费掷骰服务! +.help协议 确认服务协议 +.help指令 查看指令列表 +.help设定 确认骰娘设定 +.help链接 查看源码文档 +使用服务默认已经同意服务协议)" + }, //同意添加好友时额外发送的语句 + { + "strAddGroup", + R"(欢迎选择{strSelfName}的免费掷骰服务! +请使用.dismiss QQ号(或后四位) 使{self}退群退讨论组 +.bot on/off QQ号(或后四位) //开启或关闭指令 +.group +/-禁用回复 //禁用或启用回复 +.help协议 确认服务协议 +.help指令 查看指令列表 +.help设定 确认骰娘设定 +.help链接 查看源码文档 +邀请入群默认视为同意服务协议,知晓禁言或移出的后果)" + }, + { "strNewMaster","试问,你就是{strSelfName}的Master√\n请认真阅读当前版本Master手册以及用户手册。请注意版本号对应: https://v2docs.kokona.tech\f{strSelfName}默认开启对群移出、禁言、刷屏事件的监听,如要关闭请手动调整;\n请注意云黑系统默认开启,如无需此功能请关闭CloudBlackShare;" }, + { "strNewMasterPublic",R"({strSelfName}初始化开启公骰模式: +自动开启BelieveDiceList响应来自骰娘列表的warning; +公骰模式默认同意有掷骰记录用户的好友邀请,如要改为同意任何人请使用.admin AllowStranger=2; +已开启黑名单自动清理,拉黑时及每日定时会自动清理与黑名单用户的共同群聊,黑名单用户群权限不低于自己时自动退群; +已开启拉黑群时连带邀请人; +已启用send功能接收用户发送的消息;)" }, + { "strNewMasterPrivate",R"({strSelfName}默认开启私骰模式: +默认拒绝陌生人的群邀请,只同意来自管理员、受信任用户的邀请; +默认拒绝陌生人的好友邀请,如要同意请开启AllowStranger; +已开启黑名单自动清理,拉黑时及每日定时会自动清理与黑名单用户的共同群聊,黑名单用户群权限高于自己时自动退群; +.me功能默认不可用,需要手动开启; +切换公用请使用.admin public,但不会初始化相应设置; +可在.master delete后使用.master public来重新初始化;)" }, + {"strSelfName", ""}, + {"strSelfCall", "&strSelfName"}, + {"self", "&strSelfCall"}, + {"strBotMsg", "\n使用.help更新 查看{self}更新内容"}, + { + "strHlpMsg", + R"(请使用.dismiss QQ号(或后四位) 使{self}退群退讨论组 +.bot on/off QQ号(或后四位) //开启或关闭指令 +.help协议 确认服务协议 +.help指令 查看指令列表 +.help群管 查看群管指令 +.help设定 确认骰娘设定 +.help链接 查看源码文档 +官方论坛: https://forum.kokona.tech/ +Dice!众筹计划: https://afdian.net/@suhuiw4123)" + } +}; + +std::map EditedMsg; +const std::map HelpDoc = { +{"更新",R"( +580:过期记录回收 +579:允许转义文本多选一 +578:优化群设置读写 +577:窗口广播通知 +576:定时任务脚本 +575:设置自我响应 +574:默认骰机制优化 +573:角色卡机制优化 +572:允许脚本读写角色卡 +571:更新框架,允许多开 +570:允许.lua脚本自定义指令 +569:.rc/.draw暗骰暗抽 +568:.deck自定义牌堆重做 +567:敏感词检测 +566:.help查询建议 +565:.log日志记录 +562:新增GUI +550:允许多轮检定 +549:新增刷屏监测)"}, +{"协议","0.本协议是Dice!默认服务协议。如果你看到了这句话,意味着Master应用默认协议,请注意。\n1.邀请骰娘、使用掷骰服务和在群内阅读此协议视为同意并承诺遵守此协议,否则请使用.dismiss移出骰娘。\n2.不允许禁言、移出骰娘或刷屏掷骰等对骰娘的不友善行为,这些行为将会提高骰娘被制裁的风险。开关骰娘响应请使用.bot on/off。\n3.骰娘默认邀请行为已事先得到群内同意,因而会自动同意群邀请。因擅自邀请而使骰娘遭遇不友善行为时,邀请者因未履行预见义务而将承担连带责任。\n4.禁止将骰娘用于赌博及其他违法犯罪行为。\n5.对于设置敏感昵称等无法预见但有可能招致言论审查的行为,骰娘可能会出于自我保护而拒绝提供服务\n6.由于技术以及资金原因,我们无法保证机器人100%的时间稳定运行,可能不定时停机维护或遭遇冻结,但是相应情况会及时通过各种渠道进行通知,敬请谅解。临时停机的骰娘不会有任何响应,故而不会影响群内活动,此状态下仍然禁止不友善行为。\n7.对于违反协议的行为,骰娘将视情况终止对用户和所在群提供服务,并将不良记录共享给其他服务提供方。黑名单相关事宜可以与服务提供方协商,但最终裁定权在服务提供方。\n8.本协议内容随时有可能改动。请注意帮助信息、签名、空间、官方群等处的骰娘动态。\n9.骰娘提供掷骰服务是完全免费的,欢迎投食。\n10.本服务最终解释权归服务提供方所有。"}, +{"链接","Dice!论坛导航贴: https://kokona.tech \nDice!论坛: https://forum.kokona.tech \nDice!众筹计划: https://afdian.net/@suhuiw4123"}, +{"设定","Master:{master_QQ}\n好友申请:需要使用记录\n入群邀请:黑名单制,非黑即入\n讨论组使用:允许\n移出反制:拉黑群和操作者\n禁言反制:默认拉黑群和群主\n刷屏反制:警告\n邀请人责任:有限连带\n窥屏可能:{窥屏可能}\n其他插件:{其他插件}{姐妹骰}\n骰娘用户群:{骰娘用户群}\n私骰分享群:863062599 192499947\n开发交流群:1029435374"}, +{"骰娘用户群","【未设置】"}, +{"窥屏可能","无"}, +{"其他插件","【未设置】"}, +{"姐妹骰","{list_dice_sister}"}, +{"作者","Copyright (C) 2018-2021 w4123溯洄\nCopyright (C) 2019-2021 String.Empty"}, +{"指令",R"(at骰娘后接指令可以指定骰娘单独响应,如at骰娘.bot off +多数指令需要后接参数,请.help对应指令 获取详细信息,如.help jrrp +控制指令: +.dismiss 退群 +.bot 版本信息 +.bot on 启用指令 +.bot off 停用指令 +.group 群管 +.authorize 授权许可 +.send 向后台发送消息)" +"\f" +R"([第二页]跑团指令 +.rules 规则速查 +.r 掷骰 +.log 日志记录 +.ob 旁观模式 +.set 设置默认骰 +.coc COC人物作成 +.dnd DND人物作成 +.st 属性记录 +.pc 角色卡记录 +.rc 检定 +.setcoc 设置检定房规 +.sc 理智检定 +.en 成长检定 +.ri 先攻 +.init 先攻列表 +.ww 骰池)" +"\f" +R"([第三页]其他指令 +.nn 设置昵称 +.draw 抽牌 +.deck 牌堆实例 +.name 随机姓名 +.jrrp 今日人品 +.welcome 入群欢迎 +.me 第三人称动作 +为了避免未预料到的指令误判,请尽可能在参数之间使用空格)" +"\f" +R"({help:扩展指令})"}, +{"master",R"(当前Master:{master_QQ} +Master拥有最高权限,且可以调整任意信任)"}, +{"log",R"(跑团日志记录 +.log new 新建日志并开始记录 +.log on 开始记录 +.log off 暂停记录 +.log end 完成记录并发送日志文件 +日志上传存在失败可能,届时请联系后台管理索取)"}, +{"deck",R"(牌堆实例.deck +该指令可以在群内自设牌堆,使用.draw时,牌堆实例优先级高于同名公共对象 +抽牌不会放回直到抽空 +每个群的牌堆列表至多保存10个牌堆 +.deck set ([牌堆实例名]=)[公共牌堆名] //从公共牌堆创建实例 +.deck set ([牌堆实例名]=)member //从群成员列表创建实例 +.deck set ([牌堆实例名]=)range [下限] [上限] //创建等差数列作为实例 +.deck show //查看牌堆实例列表 +.deck show [牌堆名] //查看剩余卡牌 +.deck reset [牌堆名] //重置剩余卡牌 +.deck clr //清空所有实例 +.deck new [牌堆名]=[卡面1](...|[卡面n]) //自定义牌堆 +例: +.deck new 俄罗斯轮盘=有弹|无弹|无弹|无弹|无弹|无弹 +除show外其他群内操作需要用户信任或管理权限)"}, +{"退群","&dismiss"}, +{"退群指令","&dismiss"}, +{"dismiss","该指令需要群管理员权限,使用后即退出群聊\n!dismiss [目标QQ(完整或末四位)]指名退群\n!dismiss无视内置黑名单和静默状态,只要插件开启总是有效"}, +{"授权许可","&authoize"}, +{"authorize","授权许可(非信任用户使用时转为向管理申请许可)\n!authorize (+[群号]) ([申请理由])\n群内原地发送可省略群号,无法自动授权时会连同理由发给管理\n默认格式为:!authorize 申请用途:[ **请写入理由** ] 我已了解Dice!基本用法,仔细阅读并保证遵守{strSelfName}的用户协议,如需停用指令使用[ **请写入指令** ],用后使用[ **请写入指令** ]送出群"}, +{"开关","&bot"}, +{"bot",".bot on/off开启/静默骰子(限群管理)\n.bot无视静默状态,只要插件开启且不在黑名单总是有效"}, +{"规则速查","&rules"}, +{"规则","&rules"}, +{"rules","规则速查:.rules ([规则]):[待查词条] 或.rules set [规则]\n.rules 跳跃 //复数规则有相同词条时,择一返回\n.rules COC:大失败 //coc默认搜寻coc7的词条,dnd默认搜寻3r\n.rules dnd:语言\n.rules set dnd //设置后优先查询dnd同名词条,无参数则清除设置"}, +{"掷骰","&r"}, +{"rd","&r"}, +{"r","掷骰:.r [掷骰表达式] ([掷骰原因]) [掷骰表达式]:([掷骰轮数]#)[骰子个数]d骰子面数(p[惩罚骰个数])(k[取点数最大的骰子数])不带参数时视为掷一个默认骰\n合法参数要求掷骰轮数1-10,奖惩骰个数1-9,个数范围1-100,面数范围1-1000\n.r3#d\t//3轮掷骰\n.rh心理学 暗骰\n.rs1D10+1D6+3 沙鹰伤害\t//省略单个骰子的点数,直接给结果\n现版本开头的r均可用o或d代替,但群聊中.ob会被识别为旁观指令"}, +{"暗骰","群聊限定,掷骰指令后接h视为暗骰,结果将私发本人和群内ob的用户\n为了保证发送成功,请加骰娘好友"}, +{"旁观","&ob"}, +{"旁观模式","&ob"}, +{"ob","旁观模式:.ob (exit/list/clr/on/off)\n.ob\t//加入旁观可以看到他人暗骰结果\n.ob exit\t//退出旁观模式\n.ob list\t//查看群内旁观者\n.ob clr\t//清除所有旁观者\n.ob on\t//全群允许旁观模式\n.ob off\t//禁用旁观模式\n暗骰与旁观仅在群聊中有效"}, +{"默认骰","&set"}, +{"set","当表达式中‘D’之后没有接面数时,视为投掷默认骰\n.set20 将默认骰设置为20\n.set 不带参数视为将默认骰重置为默认的100\n若所用规则判定掷骰形如2D6,推荐使用.st &=2D6"}, +{"个位骰","个位骰有十面,为0~9十个数字,百面骰的结果为十位骰与个位骰之和(但00+0时视为100)"}, +{"十位骰","十位骰有十面,为00~90十个数字,百面骰的结果为十位骰与个位骰之和(但00+0时视为100)"}, +{"奖励骰","&奖励/惩罚骰"}, +{"惩罚骰","&奖励/惩罚骰"}, +{"奖惩骰","&奖励/惩罚骰"}, +{"奖励/惩罚骰","COC中奖励/惩罚骰是额外投掷的十位骰,最终结果选用点数更低/更高的结果(不出现大失败的情况下等价于更小/更大的十位骰)\n.rb2 2个奖励骰\nrcp 射击 1个惩罚骰"}, +{"随机姓名","&name"}, +{"name","随机姓名:.name (cn/jp/en)([生成数量])\n.name 10\t//默认4类名称随机生成\n.name en\t//后接cn/jp/en/enzh则限定生成中文/日文/英文/英文中译名\n名称生成个数范围1-10,太多容易发现姓名库的寒酸"}, +{"设置昵称","&nn"}, +{"昵称","&nn"}, +{"nn","设置昵称:.nn [昵称] / .nn / .nnn(cn/jp/en) \n.nn kp\t//昵称前的./!等符号会被自动忽略\n.nn\t//视为删除昵称\n.nnn\t//设置为随机昵称\n.nnn jp\t/设置限定随机昵称\n私聊.nn视为操作全局昵称\n优先级:群昵称>全局昵称>群名片>QQ昵称"}, +{"人物作成","该版本人物作成支持COC7(.coc、.draw调查员背景/英雄天赋)、COC6(.coc6、.draw煤气灯)、DND(.dnd)、AMGC(.draw AMGC)"}, +{"coc","克苏鲁的呼唤(COC)人物作成:.coc([7/6])(d)([生成数量])\n.coc 10\t//默认生成7版人物\n.coc6d\t//接d为详细作成,一次只能作成一个\n仅用作骰点法人物作成,可应用变体规则,参考.rules创建调查员的其他选项"}, +{"dnd","龙与地下城(DND)人物作成:.dnd([生成数量])\n.dnd 5\t//仅作参考,可自行应用变体规则"}, +{"属性记录","&st"}, +{"st","属性记录:.st (del/clr/show) ([属性名]:[属性值])\n用户默认所有群使用同一张卡,pl如需多开请使用.pc指令切卡\n.st力量:50 体质:55 体型:65 敏捷:45 外貌:70 智力:75 意志:35 教育:65 幸运:75\n.st hp-1 后接+/-时视为从原值上变化\n.st san+1d6 修改属性时可使用掷骰表达式\n.st del kp裁决\t//删除已保存的属性\n.st clr\t//清空当前卡\n.st show 灵感\t//查看指定属性\n.st show\t//无参数时查看所有属性,请使用只st加点过技能的半自动人物卡!\n部分COC属性会被视为同义词,如智力/灵感、理智/san、侦查/侦察"}, +{"角色卡","&pc"}, +{"pc",R"(角色卡:.pc +每名用户最多可同时保存16张角色卡 +.pc new ([模板]:([生成参数]:))([卡名]) +完全省略参数将生成一张COC7模板的随机姓名卡 +.pc tag ([卡名]) //为当前群绑定指定卡,为空则解绑使用默认卡 +所有群默认使用私聊绑定卡,未绑定则使用0号卡 +.pc show ([卡名]) //展示指定卡所有记录的属性,为空则展示当前卡 +.pc nn [新卡名] //重命名当前卡,不允许重名 +.pc type [模板] //将切换当前卡模板 +.pc cpy [卡名1]=[卡名2] //将后者属性复制给前者 +.pc del [卡名] //删除指定卡 +.pc list //列出全部角色卡 +.pc grp //列出各群绑定卡 +.pc build ([生成参数]:)(卡名) //根据模板填充生成属性(COC7为9项主属性) +.pc redo ([生成参数]:)(卡名) //清空原有属性后重新生成 +.pc clr //销毁全部角色卡记录 +)" + }, + {"rc", "&rc/ra"}, + {"ra", "&rc/ra"}, + {"检定", "&rc/ra"}, + { + "rc/ra", + "检定指令:.rc/ra (_)([检定次数]#)([难度])[属性名]( [成功率])\n角色卡设置了属性时,可省略成功率\n.rc体质*5\t//允许使用+-*/,但顺序要求为乘法>加减>除法\n.rc 困难幸运\t//技能名开头的困难和极难会被视为关键词\n.rc _心理学50\t//暗骰结果仅本人及旁观者可见\n.rc 敏捷-10\t//修正后成功率必须在1-1000内\n.rcp 手枪\t//奖惩骰至多9个\n默认以规则书判定,大成功大失败的房规由.setcoc设置" + }, + { + "房规", + "Dice!目前只为COC7检定提供房规,指令为.setcoc:{setcoc}" + }, + { + "setcoc", + "为当前群或讨论组设置COC房规,如.setcoc 1,当前参数0-5\n0 规则书\n出1大成功\n不满50出96 - 100大失败,满50出100大失败\n1\n不满50出1大成功,满50出1 - 5大成功\n不满50出96 - 100大失败,满50出100大失败\n2\n出1 - 5且 <= 成功率大成功\n出100或出96 - 99且 > 成功率大失败\n3\n出1 - 5大成功\n出96 - 100大失败\n4\n出1 - 5且 <= 十分之一大成功\n不满50出 >= 96 + 十分之一大失败,满50出100大失败\n5\n出1 - 2且 < 五分之一大成功\n不满50出96 - 100大失败,满50出99 - 100大失败\n6 绿色三角洲\n出1或两骰相同<=成功率大成功\n出100或两骰相同>成功率大失败\n" + R"(如果其他房规可向开发者反馈 +无论如何,群内检定只会调用群内设置,否则后果将是团内成员不对等)" + }, + {"san check", "&sc"}, + {"理智检定", "&sc"}, + { + "sc", + "San Check指令:.sc[成功损失]/[失败损失] ([当前san值])\n已经.st了理智/san时,可省略最后的参数\n.sc0/1 70\n.sc1d10/1d100 直面外神\n大失败自动失去最大值\n当调用角色卡san时,san会自动更新为sc后的剩余值\n程序上可以损失负数的san,也就是可以用.sc-1d6/-1d6来回复san,但请避免这种奇怪操作" + }, + {"ti", "&ti/li"}, + {"li", "&ti/li"}, + {"疯狂症状", "&ti/li"}, + {"ti/li", "疯狂症状:\n.ti 临时疯狂症状\n.li 总结疯狂症状\n适用coc7版规则,6版请自行用百面骰配合查表\n具体适用哪项参见.rules 疯狂发作"}, + {"成长检定", "&en"}, + {"增强检定", "&en"}, + { + "en", + "成长检定:.en [技能名称]([技能值])(([失败成长值]/)[成功成长值])\n已经.st时,可省略最后的参数\n.en 教育 60 +1D10 教育增强\t//后接成功时成长值\n.en 幸运 +1D3/1D10幸运成长\t//调用人物卡属性时,成长后的值会自动更新\n可变成长值必须以加减号开头,不限制加减" + }, + {"抽牌", "&draw"}, + { + "draw", + R"(抽牌:.draw [牌堆名称] ([抽牌数量]) +.draw _[牌堆名称] ([抽牌数量]) //暗抽,结果私聊发送 +.drawh [牌堆名称] ([抽牌数量]) //暗抽,参数h后必须留空格 +.draw _狼人杀 //抽牌结果通过私聊获取,可ob +*牌堆名称优先调用牌堆实例,如未设置则从同名公共牌堆生成临时实例 +*抽到的牌不放回,牌堆抽空后无法继续 +*查看{self}已安装牌堆,可.help 全牌堆列表或.help 扩展牌堆)" + }, + { "扩展牌堆","{list_extern_deck}" }, + { "全牌堆列表","{list_all_deck}" }, + { "扩展指令","{list_extern_order}" }, + {"先攻", "&ri"}, + {"ri", "先攻(群聊限定):.ri([加值])([昵称])\n.ri -1 某pc\t//自动记入先攻列表\n.ri +5 boss"}, + {"先攻列表", "&init"}, + {"init", "先攻列表:\n.init list\t//查看先攻列表\n.init clr\t//清除先攻列表\n.init del [项目名]\t//从先攻列表移除项目"}, + {"骰池", "&ww"}, + {"ww", "骰池:.w(w) [骰子个数]a[加骰参数]\n.w会直接给出结果而.ww会给出每个骰子的点数\n固定10面骰,每有一个骰子点数达到加骰参数,则加骰一次,最后计算点数达到8的骰子数\n具体用法请参考相关游戏规则"}, + {"第三人称", "&me"}, + {"第三人称动作", "&me"}, + { + "me", + "第三人称动作:.me([群号])[动作].me 笑出了声\t//仅限群聊使用\n.me 941980833 抱群主\t//仅限私聊使用,此命令可伪装成骰子在群里说话\n.me off\t//群内禁用.me\n.me on\t//群内启用.me" + }, + {"今日人品", "&jrrp"}, + { + "jrrp", + "今日人品:.jrrp\n.jrrp off\t//群内禁用jrrp\n.jrrp on\t//群内启用jrrp\n一天一个人品值\n2.3.5版本后随机值为均匀分布\n骰娘只负责从溯洄的服务器搬运结果,请勿无能狂怒\n如何发配所有人的命运,只有孕育万千骰娘生机之母,萌妹吃鱼之神,正五棱双角锥体对的监护人,一切诡秘的窥见者,时空舞台外的逆流者,永转的命运之轮——溯洄本体掌握" + }, + {"send", "发送消息:.send 想对Master说的话\n如果用来调戏Master请做好心理准备"}, + {"入群欢迎", "&welcome"}, + {"入群欢迎词", "&welcome"}, + { + "welcome", + "入群欢迎词:.welcome\n.welcome \\\\{at}欢迎\\\\{nick}入群!\t//\\\\{at}视为at入群者,\\\\{nick}会替换为新人的昵称\n.welcome\t//不带参数时清除欢迎词\n无论开关状态,只要有欢迎词时有人入群,都会响应" + }, + {"group", "&群管"}, + { + "群管", + R"(群管指令.group(群管理员限定) +.group state //查看在群内对骰娘的设置 +.group pause/restart //群全体禁言/全体解除禁言 +.group card [at/用户QQ] [名片] //设置群员名片 +.group title [at/用户QQ] [头衔] //设置群员头衔 +.group diver //查看潜水成员 +.group +/-[群管词条] //为群加减设置,需要对应权限 +例:.group +禁用回复 //关闭本群自定义回复 +群管词条:停用指令/禁用回复/禁用jrrp/禁用draw/禁用me/禁用help/禁用ob/拦截消息/许可使用/免清/免黑)" + }, + { "groups_list", "&取群列表" }, + { "取群列表", R"(取群列表.groups list(管理限定) +.groups list idle //按闲置天数降序列出群 +.groups list size //按群规模降序列出群 +.groups list [群管词条] //列出带有词条的群 +群管词条:停用指令/禁用回复/禁用jrrp/禁用draw/禁用me/禁用help/禁用ob/拦截消息/许可使用/免清/免黑)" }, + {"消息链接","&link"}, + {"link",R"(消息链接.link +.link [转发方向] [对象窗口] 建立本窗口与对象窗口的转发 +.link close 关闭链接 +.link start 开启上次关闭的链接 +[转发方向]:to=转发本窗口消息到对象窗口;from=转发对象窗口消息到本窗口;with=双向转发 +[对象窗口]:群/讨论组=[群号];私聊窗口=q[QQ号] +例:.link with q1605271653 //建立双向私聊链接 +.link from 754494359 //接收目标群的消息转发)"}, + { "敏感词检测","&censor" }, + {"censor",R"(敏感词检测.admin censor +.admin censor +([触发等级])=[敏感词0](|[敏感词1]...) //添加敏感词 +.admin censor -[敏感词0](|[敏感词1]...) //移除敏感词 +例:.admin censor +=nmsl //将“nmsl”设置为Warning级 +.admin censor +Danger=nn老公|nn主人 //将“nn老公”、“nn主人”设置为Danger级 +.admin censor -手枪 //移除敏感词“手枪” +# 匹配机制 +骰娘会模糊匹配指令标识符(.)开头的消息,并返回所含敏感词的最高触发等级 +匹配过程自动跳过文本中的特殊符号和空格,且大小写不敏感 +受信任用户会相应降低触发等级,信任4以上用户将不触发检测 +# 触发等级 +Ignore //无视 +Notice //仅在0级窗口通知 +Caution //提醒用户,并在1级窗口提醒 +Warning //【默认等级】警告用户,并在1级窗口提醒 +Danger //警告用户且拒绝指令,并在3级窗口警告 +*请避免为纯字母/数字的敏感词设置较高触发等级,这些字符存在误匹配图片码的可能性 +# 词库批量加载方式见手册)" }, + {"溯洄", "孕育万千骰娘生机之母,萌妹吃鱼之神,正五棱双角锥体对的监护人,一切诡秘的窥见者,拟人者主宰,时空舞台外的逆流者,永转的命运之轮"}, + {"投喂","&投食"}, + {"投食", "投食Shiki,请选择https://afdian.net/@dice_shiki\n投食溯洄,可选择https://afdian.net/@suhuiw4123\n投食{self},可选择……充超会?"}, + {"愚者正位", "憧憬自然的地方、毫无目的地前行、喜欢尝试挑战新鲜事物、四处流浪。美好的梦想。"}, + { + "愚者逆位", + "冒险的行动,追求可能性,重视梦想,无视物质的损失,离开家园,过于信赖别人,为出外旅行而烦恼。心情空虚、轻率的恋情、无法长久持续的融洽感、不安的爱情的旅程、对婚姻感到束缚、彼此忽冷忽热、不顾众人反对坠入爱河、为恋人的负心所伤、感情不专一。" + }, + {"魔术师正位", "事情的开始,行动的改变,熟练的技术及技巧,贯彻我的意志,运用自然的力量来达到野心。"}, + {"魔术师逆位", "意志力薄弱,起头难,走入错误的方向,知识不足,被骗和失败。"}, + {"女祭司正位", "开发出内在的神秘潜力,前途将有所变化的预言,深刻地思考,敏锐的洞察力,准确的直觉。"}, + {"女祭司逆位", "过于洁癖,无知,贪心,目光短浅,自尊心过高,偏差的判断,有勇无谋,自命不凡。"}, + {"女皇正位", "幸福,成功,收获,无忧无虑,圆满的家庭生活,良好的环境,美貌,艺术,与大自然接触,愉快的旅行,休闲。"}, + {"女皇逆位", "不活泼,缺乏上进心,散漫的生活习惯,无法解决的事情,不能看到成果,担于享乐,环境险恶,与家人发生纠纷。"}, + {"皇帝正位", "光荣,权力,胜利,握有领导权,坚强的意志,达成目标,父亲的责任,精神上的孤单。"}, + {"皇帝逆位", "幼稚,无力,独裁,撒娇任性,平凡,没有自信,行动力不足,意志薄弱,被支配。"}, + {"教皇正位", "援助,同情,宽宏大量,可信任的人给予的劝告,良好的商量对象,得到精神上的满足,遵守规则,志愿者。"}, + {"教皇逆位", "错误的讯息,恶意的规劝,上当,援助被中断,愿望无法达成,被人利用,被放弃。"}, + {"恋人正位", "撮合,爱情,流行,兴趣,充满希望的未来,魅力,增加朋友。"}, + {"恋人逆位", "禁不起诱惑,纵欲过度,反覆无常,友情变淡,厌倦,争吵,华丽的打扮,优柔寡断。"}, + {"战车正位", "努力而获得成功,胜利,克服障碍,行动力,自立,尝试,自我主张,年轻男子,交通工具,旅行运大吉。"}, + {"战车逆位", "争论失败,发生纠纷,阻滞,违返规则,诉诸暴力,顽固的男子,突然的失败,不良少年,挫折和自私自利。"}, + {"力量正位", "大胆的行动,有勇气的决断,新发展,大转机,异动,以意志力战胜困难,健壮的女人。"}, + {"力量逆位", "胆小,输给强者,经不起诱惑,屈服在权威与常识之下,没有实践便告放弃,虚荣,懦弱,没有耐性。"}, + {"隐者正位", "隐藏的事实,个别的行动,倾听他人的意见,享受孤独,有益的警戒,年长者,避开危险,祖父,乡间生活。"}, + {"隐者逆位", "憎恨孤独,自卑,担心,幼稚思想,过于慎重导致失败,偏差,不宜旅行。"}, + {"命运之轮正位", "关键性的事件,有新的机会,因的潮流,环境的变化,幸运的开端,状况好转,问题解决,幸运之神降临。"}, + {"命运之轮逆位", "挫折,计划泡汤,障碍,无法修正方向,往坏处发展,恶性循环,中断。"}, + {"正义正位", "公正、中立、诚实、心胸坦荡、表里如一、身兼二职、追求合理化、协调者、与法律有关、光明正大的交往、感情和睦。"}, + {"正义逆位", "失衡、偏见、纷扰、诉讼、独断专行、问心有愧、无法两全、表里不一、男女性格不合、情感波折、无视社会道德的恋情。"}, + {"倒吊人正位", "接受考验、行动受限、牺牲、不畏艰辛、不受利诱、有失必有得、吸取经验教训、浴火重生、广泛学习、奉献的爱。"}, + {"倒吊人逆位", "无谓的牺牲、骨折、厄运、不够努力、处于劣势、任性、利己主义者、缺乏耐心、受惩罚、逃避爱情、没有结果的恋情。"}, + {"死神正位", "失败、接近毁灭、生病、失业、维持停滞状态、持续的损害、交易停止、枯燥的生活、别离、重新开始、双方有很深的鸿沟、恋情终止。"}, + {"死神逆位", "抱有一线希望、起死回生、回心转意、摆脱低迷状态、挽回名誉、身体康复、突然改变计划、逃避现实、斩断情丝、与旧情人相逢。"}, + {"节制正位", "单纯、调整、平顺、互惠互利、好感转为爱意、纯爱、深爱。"}, + {"节制逆位", "消耗、下降、疲劳、损失、不安、不融洽、爱情的配合度不佳。"}, + {"恶魔正位", "被束缚、堕落、生病、恶意、屈服、欲望的俘虏、不可抗拒的诱惑、颓废的生活、举债度日、不可告人的秘密、私密恋情。"}, + {"恶魔逆位", "逃离拘束、解除困扰、治愈病痛、告别过去、暂停、别离、拒绝诱惑、舍弃私欲、别离时刻、爱恨交加的恋情。"}, + {"塔正位", "破产、逆境、被开除、急病、致命的打击、巨大的变动、受牵连、信念崩溃、玩火自焚、纷扰不断、突然分离,破灭的爱。"}, + {"塔逆位", "困境、内讧、紧迫的状态、状况不佳、趋于稳定、骄傲自大将付出代价、背水一战、分离的预感、爱情危机。"}, + {"星星正位", "前途光明、充满希望、想象力、创造力、幻想、满足愿望、水准提高、理想的对象、美好的恋情。"}, + {"星星逆位", "挫折、失望、好高骛远、异想天开、仓皇失措、事与愿违、工作不顺心、情况悲观、秘密恋情、缺少爱的生活。"}, + {"月亮正位", "不安、迷惑、动摇、谎言、欺骗、鬼迷心窍、动荡的爱、三角关系。"}, + {"月亮逆位", "逃脱骗局、解除误会、状况好转、预知危险、等待、正视爱情的裂缝。"}, + {"太阳正位", "活跃、丰富的生命力、充满生机、精力充沛、工作顺利、贵人相助、幸福的婚姻、健康的交际。"}, + {"太阳逆位", "消沉、体力不佳、缺乏连续性、意气消沉、生活不安、人际关系不好、感情波动、离婚。"}, + {"审判正位", "复活的喜悦、康复、坦白、好消息、好运气、初露锋芒、复苏的爱、重逢、爱的奇迹。"}, + {"审判逆位", "一蹶不振、幻灭、隐瞒、坏消息、无法决定、缺少目标、没有进展、消除、恋恋不舍。"}, + {"世界正位", "完成、成功、完美无缺、连续不断、精神亢奋、拥有毕生奋斗的目标、完成使命、幸运降临、快乐的结束、模范情侣。"}, + {"世界逆位", "未完成、失败、准备不足、盲目接受、一时不顺利、半途而废、精神颓废、饱和状态、合谋、态度不够融洽、感情受挫。"}, +}; + +std::string getMsg(const std::string& key, const std::unordered_map& maptmp) +{ + const auto it = GlobalMsg.find(key); + if (it != GlobalMsg.end())return format(it->second, GlobalMsg, maptmp); + return ""; +} diff --git a/Dice/GlobalVar.h b/Dice/GlobalVar.h index 8078acba..216015c9 100644 --- a/Dice/GlobalVar.h +++ b/Dice/GlobalVar.h @@ -1,3 +1,5 @@ +#pragma once + /* * _______ ________ ________ ________ __ * | __ \ |__ __| | _____| | _____| | | @@ -7,8 +9,8 @@ * |_______/ |________| |________| |________| |__| * * Dice! QQ Dice Robot for TRPG - * Copyright (C) 2018-2020 w4123溯洄 - * Copyright (C) 2019-2020 String.Empty + * Copyright (C) 2018-2021 w4123溯洄 + * Copyright (C) 2019-2021 String.Empty * * This program is free software: you can redistribute it and/or modify it under the terms * of the GNU Affero General Public License as published by the Free Software Foundation, @@ -21,13 +23,15 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#pragma once + #ifndef DICE_GLOBAL_VAR #define DICE_GLOBAL_VAR +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include -#include "CQLogger.h" +#endif #include +#include #include "STLExtern.hpp" /* @@ -35,64 +39,64 @@ * 请勿修改Dice_Build, Dice_Ver_Without_Build,DiceRequestHeader以及Dice_Ver常量 * 请修改Dice_Short_Ver或Dice_Full_Ver常量以达到版本自定义 */ -const unsigned short Dice_Build = 563u; -inline const std::string Dice_Ver_Without_Build = "2.4.0beta3"; -constexpr auto DiceRequestHeader = "Dice/2.4.0BETA3"; +const unsigned short Dice_Build = 580u; +inline const std::string Dice_Ver_Without_Build = "2.5.2"; +constexpr auto DiceRequestHeader = "Dice/2.5.2"; inline const std::string Dice_Ver = Dice_Ver_Without_Build + "(" + std::to_string(Dice_Build) + ")"; -inline const std::string Dice_Short_Ver = "Dice! by 溯洄 Shiki Ver " + Dice_Ver; +inline const std::string Dice_Short_Ver = "Dice! by 溯洄 & Shiki Ver " + Dice_Ver; #ifdef __clang__ #ifdef _MSC_VER -const std::string Dice_Full_Ver = Dice_Short_Ver + " [CLANG " + std::to_string(__clang_major__) + "." + std::to_string(__clang_minor__) + "." + std::to_string(__clang_patchlevel__) + " with MSVC " + std::to_string(_MSC_VER) + " " + __DATE__ + " " + __TIME__; +inline const std::string Dice_Full_Ver = Dice_Short_Ver + " [CLANG " + std::to_string(__clang_major__) + "." + std::to_string(__clang_minor__) + "." + std::to_string(__clang_patchlevel__) + " with MSVC " + std::to_string(_MSC_VER) + " " + __DATE__ + " " + __TIME__; #elif defined(__GNUC__) -const std::string Dice_Full_Ver = Dice_Short_Ver + " [CLANG " + std::to_string(__clang_major__) + "." + std::to_string(__clang_minor__) + "." + std::to_string(__clang_patchlevel__) + " with GNUC " + std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__) + " " + __DATE__ + " " + __TIME__; +inline const std::string Dice_Full_Ver = Dice_Short_Ver + " [CLANG " + std::to_string(__clang_major__) + "." + std::to_string(__clang_minor__) + "." + std::to_string(__clang_patchlevel__) + " with GNUC " + std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__) + " " + __DATE__ + " " + __TIME__; #else -const std::string Dice_Full_Ver = Dice_Short_Ver + " [CLANG " + std::to_string(__clang_major__) + "." + std::to_string(__clang_minor__) + "." + std::to_string(__clang_patchlevel__); +inline const std::string Dice_Full_Ver = Dice_Short_Ver + " [CLANG " + std::to_string(__clang_major__) + "." + std::to_string(__clang_minor__) + "." + std::to_string(__clang_patchlevel__); #endif #else #ifdef _MSC_VER -const std::string Dice_Full_Ver = std::string(Dice_Short_Ver) + "[MSVC " + std::to_string(_MSC_VER) + " " + __DATE__ + - " " + __TIME__; +inline const std::string Dice_Full_Ver = std::string(Dice_Short_Ver) + "[MSVC " + std::to_string(_MSC_VER) + " " + __DATE__ + + " " + __TIME__ + "]"; #elif defined(__GNUC__) -const std::string Dice_Full_Ver = Dice_Short_Ver + " [GNUC " + std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__) + " " + __DATE__ + " " + __TIME__; +inline const std::string Dice_Full_Ver = Dice_Short_Ver + " [GNUC " + std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__) + " " + __DATE__ + " " + __TIME__; #else -const std::string Dice_Full_Ver = Dice_Short_Ver + " [UNKNOWN COMPILER"; +inline const std::string Dice_Full_Ver = Dice_Short_Ver + " [UNKNOWN COMPILER"; #endif #endif +#ifdef _WIN32 // DLL hModule extern HMODULE hDllModule; +#endif // 应用是否被启用 extern bool Enabled; -// 是否在Mirai环境中运行 -extern bool Mirai; +// 运行环境 +//enum class QQFrame { CoolQ, Mirai, XianQu }; +//extern QQFrame frame; // Dice最完整的版本字符串 -extern std::string Dice_Full_Ver_For; +extern std::string Dice_Full_Ver_On; // 可执行文件位置 -extern std::string strModulePath; +//extern std::string strModulePath; // 消息发送线程是否正在运行 extern bool msgSendThreadRunning; -// 全局酷QLogger -extern CQ::logger DiceLogger; - // 回复信息, 此内容可以通过CustomMsg功能修改而无需修改源代码 -extern std::map GlobalMsg; +extern std::map GlobalMsg; // 修改后的Global语句 -extern std::map EditedMsg; +extern std::map EditedMsg; // 帮助文档 extern const std::map HelpDoc; // 修改后的帮助文档 inline std::map CustomHelp; -std::string getMsg(const std::string& key, const std::map& tmp = {}); +std::string getMsg(const std::string& key, const std::unordered_map& tmp = {}); #endif /*DICE_GLOBAL_VAR*/ diff --git a/Dice/Jsonio.cpp b/Dice/Jsonio.cpp index 10984cf8..7c60a5e3 100644 --- a/Dice/Jsonio.cpp +++ b/Dice/Jsonio.cpp @@ -1,9 +1,10 @@ #include #include +#include #include "StrExtern.hpp" #include "Jsonio.h" -nlohmann::json freadJson(const std::string& strPath) +[[deprecated]] nlohmann::json freadJson(const std::string& strPath) { std::ifstream fin(strPath); if (!fin)return nlohmann::json(); @@ -21,7 +22,7 @@ nlohmann::json freadJson(const std::string& strPath) nlohmann::json freadJson(const std::filesystem::path& path) { - std::ifstream fin(convert_w2a(path.wstring().c_str())); + std::ifstream fin(path); if (!fin)return nlohmann::json(); nlohmann::json j; try @@ -34,3 +35,16 @@ nlohmann::json freadJson(const std::filesystem::path& path) } return j; } + +[[deprecated]] void fwriteJson(const std::string& strPath, const json& j) +{ + std::ofstream fout(strPath); + fout << std::setw(2) << j; +} + +void fwriteJson(const std::filesystem::path& fpPath, const json& j) +{ + std::ofstream fout(fpPath); + fout << std::setw(2) << j; +} + diff --git a/Dice/Jsonio.h b/Dice/Jsonio.h index 0e4e99d1..ff06873e 100644 --- a/Dice/Jsonio.h +++ b/Dice/Jsonio.h @@ -1,12 +1,17 @@ -// Json信息获取以及写入 #pragma once + +// Json信息获取以及写入 + #include #include #include +#include #include #include "json.hpp" #include "EncodingConvert.h" +using nlohmann::json; + class JsonList { std::vector vRes; @@ -54,25 +59,34 @@ std::enable_if_t, T> readJKey(const std::string& strJson return stoll(strJson); } -nlohmann::json freadJson(const std::string& strPath); +[[deprecated]] nlohmann::json freadJson(const std::string& strPath); nlohmann::json freadJson(const std::filesystem::path& path); +[[deprecated]] void fwriteJson(const std::string& strPath, const json& j); +void fwriteJson(const std::filesystem::path& strPath, const json& j); -template -int readJMap(const nlohmann::json& j, std::map& mapTmp) +template +int readJMap(const nlohmann::json& j, Map& mapTmp) { int intCnt = 0; for (auto it = j.cbegin(); it != j.cend(); ++it) { - T1 tKey = readJKey(it.key()); - T2 tVal = it.value(); - - tVal = UTF8toGBK(tVal); - mapTmp[tKey] = tVal; + std::string key = UTF8toGBK(it.key()); + it.value().get_to(mapTmp[key]); + mapTmp[key] = UTF8toGBK(mapTmp[key]); intCnt++; } return intCnt; } - +template +int readJson(const std::string& strJson, std::set& setTmp) { + try { + nlohmann::json j(nlohmann::json::parse(strJson)); + j.get_to(setTmp); + return j.size(); + } catch (...) { + return -1; + } +} template int readJson(const std::string& strJson, std::map& mapTmp) { @@ -87,54 +101,72 @@ int readJson(const std::string& strJson, std::map& mapTmp) } } -template -int loadJMap(const std::string& strLoc, std::map& mapTmp) -{ - std::ifstream fin(strLoc); - if (fin) +template +[[deprecated]] int loadJMap(const std::string& strLoc, Map& mapTmp) { + nlohmann::json j = freadJson(strLoc); + if (j.is_null())return -2; + try { - try - { - nlohmann::json j; - fin >> j; - fin.close(); - return readJMap(j, mapTmp); - } - catch (...) - { - fin.close(); - return -1; - } + return readJMap(j, mapTmp); + } + catch (...) + { + return -1; } - return -2; } -template -std::string writeJKey(std::enable_if_t, T> strJson) -{ - return GBKtoUTF8(strJson); +template +int loadJMap(const std::filesystem::path& fpLoc, Map& mapTmp) { + nlohmann::json j = freadJson(fpLoc); + if (j.is_null())return -2; + try + { + return readJMap(j, mapTmp); + } + catch (...) + { + return -1; + } } -template -std::string writeJKey(std::enable_if_t, T> llJson) + +//template +template +[[deprecated]] void saveJMap(const std::string& strLoc, const C& mapTmp) { - return std::to_string(llJson); + if (mapTmp.empty()) { + remove(strLoc.c_str()); + return; + } + std::ofstream fout(strLoc); + if (fout) + { + nlohmann::json j; + for (auto& [key,val] : mapTmp) + { + j[GBKtoUTF8(key)] = GBKtoUTF8(val); + } + fout << j.dump(2); + fout.close(); + } } -template -int saveJMap(const std::string& strLoc, std::map mapTmp) +template +void saveJMap(const std::filesystem::path& fpLoc, const C& mapTmp) { - if (mapTmp.empty())return 0; - std::ofstream fout(strLoc); + if (mapTmp.empty()) { + remove(fpLoc); + return; + } + std::ofstream fout(fpLoc); if (fout) { nlohmann::json j; - for (auto it : mapTmp) + for (auto& [key,val] : mapTmp) { - j[writeJKey(it.first)] = GBKtoUTF8(it.second); + j[GBKtoUTF8(key)] = GBKtoUTF8(val); } fout << j.dump(2); fout.close(); } - return 0; } diff --git a/Dice/ManagerSystem.cpp b/Dice/ManagerSystem.cpp index 7e615e33..436e905f 100644 --- a/Dice/ManagerSystem.cpp +++ b/Dice/ManagerSystem.cpp @@ -2,20 +2,28 @@ * 后台系统 * Copyright (C) 2019-2020 String.Empty */ -#include -#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif #include +#include #include "ManagerSystem.h" #include "CardDeck.h" +#include "CharacterCard.h" #include "GlobalVar.h" +#include "DDAPI.h" +#include "CQTools.h" +#include "DiceSession.h" -string DiceDir = "DiceData"; -//被引用的图片列表 -set sReferencedImage; +std::filesystem::path dirExe; +std::filesystem::path DiceDir("DiceData"); + //被引用的图片列表 +unordered_set sReferencedImage; const map mChatConf{ - //0-群管理员,2-白名单2级,3-白名单3级,4-管理员,5-系统操作 + //0-群管理员,2-信任2级,3-信任3级,4-管理员,5-系统操作 {"忽略", 4}, {"拦截消息", 0}, {"停用指令", 0}, @@ -29,6 +37,7 @@ const map mChatConf{ {"未审核", 1}, {"免清", 2}, {"免黑", 4}, + {"协议无效", 3}, {"未进", 5}, {"已退", 5} }; @@ -41,65 +50,113 @@ User& getUser(long long qq) short trustedQQ(long long qq) { if (qq == console.master())return 256; + if (qq == console.DiceMaid)return 255; else if (!UserList.count(qq))return 0; else return UserList[qq].nTrust; } -int clearUser() -{ + +int clearUser() { vector QQDelete; - for (const auto& [qq, user] : UserList) - { - if (user.empty()) - { + time_t tNow{ time(nullptr) }; + time_t userline{ tNow - console["InactiveUserLine"] * (time_t)86400 }; + bool isClearInactive{ console["InactiveUserLine"] > 0 }; + for (const auto& [qq, user] : UserList) { + if (user.nTrust > 0)continue; + if (user.empty()) { + QQDelete.push_back(qq); + } + else if (isClearInactive) { + time_t tLast{ user.tUpdated }; + if (gm->has_session(~qq) && gm->session(~qq).tUpdate > tLast)tLast = gm->session(~qq).tUpdate; + if (tLast >= userline)continue; QQDelete.push_back(qq); + if (PList.count(qq)) { + PList.erase(qq); + } } } - for (const auto& qq : QQDelete) - { + for (const auto& qq : QQDelete) { UserList.erase(qq); + if (gm->has_session(~qq))gm->session_end(~qq); } return QQDelete.size(); } +int clearGroup() { + if (console["InactiveGroupLine"] <= 0)return 0; + vector GrpDelete; + time_t tNow{ time(nullptr) }; + time_t grpline{ tNow - console["InactiveGroupLine"] * (time_t)86400 }; + for (const auto& [id, grp] : ChatList) { + if (grp.is_except() || grp.isset("免清") || grp.isset("忽略"))continue; + time_t tLast{ grp.tUpdated }; + if (gm->has_session(id) && gm->session(id).tUpdate > grp.tUpdated)tLast = gm->session(id).tUpdate; + if (tLast < grpline)GrpDelete.push_back(id); + } + for (const auto& id : GrpDelete) { + ChatList.erase(id); + } + return GrpDelete.size(); + +} string getName(long long QQ, long long GroupID) { + if (QQ == console.DiceMaid)return getMsg("strSelfCall"); string nick; if (UserList.count(QQ) && getUser(QQ).getNick(nick, GroupID))return nick; - if (GroupID && !(nick = strip(CQ::getGroupMemberInfo(GroupID, QQ).GroupNick)).empty())return nick; - if (!(nick = strip(CQ::getStrangerInfo(QQ).nick)).empty())return nick; + if (GroupID && !(nick = DD::getGroupNick(GroupID, QQ)).empty() + && !(nick = strip(msg_decode(nick))).empty())return nick; + if (nick = DD::getQQNick(QQ); !(nick = strip(msg_decode(nick))).empty())return nick; return GlobalMsg["stranger"] + "(" + to_string(QQ) + ")"; } -void filter_CQcode(string& nick, long long fromGroup) { +void filter_CQcode(string& nick, long long fromGroup) +{ size_t posL(0); - while ((posL = nick.find(CQ_AT)) != string::npos) { + while ((posL = nick.find(CQ_AT)) != string::npos) + { //检查at格式 - if (size_t posR = nick.find(']',posL); posR != string::npos) { + if (size_t posR = nick.find(']',posL); posR != string::npos) + { std::string_view stvQQ(nick); stvQQ = stvQQ.substr(posL + 10, posR - posL - 10); //检查QQ号格式 bool isDig = true; - for (auto ch: stvQQ) { - if (!isdigit(static_cast(ch))) { + for (auto ch: stvQQ) + { + if (!isdigit(static_cast(ch))) + { isDig = false; break; } } //转义 - if (isDig && posR - posL < 29) { + if (isDig && posR - posL < 29) + { nick.replace(posL, posR - posL + 1, "@" + getName(stoll(string(stvQQ)), fromGroup)); } - else if (stvQQ == "all") { + else if (stvQQ == "all") + { nick.replace(posL, posR - posL + 1, "@全体成员"); } - else { + else + { nick.replace(posL, posR - posL + 1, "@"); } } else return; } - while ((posL = nick.find("[CQ:")) != string::npos) { + while ((posL = nick.find(CQ_IMAGE)) != string::npos) { + //检查at格式 if (size_t posR = nick.find(']', posL); posR != string::npos) { + nick.replace(posL, posR - posL + 1, "[图片]"); + } + else return; + } + while ((posL = nick.find("[CQ:")) != string::npos) + { + if (size_t posR = nick.find(']', posL); posR != string::npos) + { nick.erase(posL, posR - posL + 1); } else return; @@ -111,23 +168,48 @@ Chat& chat(long long id) if (!ChatList.count(id))ChatList[id].id(id); return ChatList[id]; } +Chat& Chat::id(long long grp) { + ID = grp; + Name = DD::getGroupName(grp); + if (!Enabled)return *this; + if (DD::getGroupIDList().count(grp)) { + isGroup = true; + } + else { + boolConf.insert("未进"); + } + return *this; +} + +void Chat::leave(const string& msg) { + if (!msg.empty()) { + if (isGroup)DD::sendGroupMsg(ID, msg); + else DD::sendDiscussMsg(ID, msg); + std::this_thread::sleep_for(500ms); + } + isGroup ? DD::setGroupLeave(ID) : DD::setDiscussLeave(ID); + set("已退"); +} +bool Chat::is_except()const { + return boolConf.count("免黑") || boolConf.count("协议无效"); +} -int groupset(long long id, string st) +int groupset(long long id, const string& st) { if (!ChatList.count(id))return -1; - return ChatList[id].isset(std::move(st)); + return ChatList[id].isset(st); } string printChat(Chat& grp) { - if (CQ::getGroupList().count(grp.ID))return CQ::getGroupList()[grp.ID] + "(" + to_string(grp.ID) + ")"; - if (grp.isset("群名"))return grp.strConf["群名"] + "(" + to_string(grp.ID) + ")"; - if (grp.isGroup) return "群" + to_string(grp.ID) + ""; - return "讨论组" + to_string(grp.ID) + ""; + string name{ DD::getGroupName(grp.ID) }; + if (!name.empty())return "[" + name + "](" + to_string(grp.ID) + ")"; + if (!grp.Name.empty())return "[" + grp.Name + "](" + to_string(grp.ID) + ")"; + if (grp.isGroup) return "群(" + to_string(grp.ID) + ")"; + return "讨论组(" + to_string(grp.ID) + ")"; } -void scanImage(const string& s, set& list) -{ +void scanImage(const string& s, unordered_set& list) { int l = 0, r = 0; while ((l = s.find('[', r)) != string::npos && (r = s.find(']', l)) != string::npos) { @@ -138,31 +220,14 @@ void scanImage(const string& s, set& list) } } -void scanImage(const vector& v, set& list) -{ +void scanImage(const vector& v, unordered_set& list) { for (const auto& it : v) { scanImage(it, sReferencedImage); } } - -int clearImage() -{ - scanImage(GlobalMsg, sReferencedImage); - scanImage(HelpDoc, sReferencedImage); - scanImage(CardDeck::mPublicDeck, sReferencedImage); - scanImage(CardDeck::mReplyDeck, sReferencedImage); - scanImage(CardDeck::mGroupDeck, sReferencedImage); - scanImage(CardDeck::mPrivateDeck, sReferencedImage); - for (const auto& it : ChatList) - { - scanImage(it.second.strConf, sReferencedImage); - } - const string strLog = "整理" + GlobalMsg["strSelfName"] + "被引用图片" + to_string(sReferencedImage.size()) + "项"; - console.log(strLog, 0b0, printSTNow()); - return clrDir("data\\image\\", sReferencedImage); -} +#ifdef _WIN32 DWORD getRamPort() { @@ -181,16 +246,19 @@ __int64 compareFileTime(const FILETIME& ft1, const FILETIME& ft2) return t1 - t2; } -__int64 getWinCpuUsage() +long long getWinCpuUsage() { + FILETIME preidleTime; + FILETIME prekernelTime; + FILETIME preuserTime; FILETIME idleTime; FILETIME kernelTime; FILETIME userTime; if (!GetSystemTimes(&idleTime, &kernelTime, &userTime)) return -1; - FILETIME preidleTime = idleTime; - FILETIME prekernelTime = kernelTime; - FILETIME preuserTime = userTime; + preidleTime = idleTime; + prekernelTime = kernelTime; + preuserTime = userTime; Sleep(1000); if (!GetSystemTimes(&idleTime, &kernelTime, &userTime)) return -1; @@ -199,11 +267,10 @@ __int64 getWinCpuUsage() const __int64 kernel = compareFileTime(kernelTime, prekernelTime); const __int64 user = compareFileTime(userTime, preuserTime); - const __int64 cpu = (kernel + user - idle) * 100 / (kernel + user); - return cpu; + return (kernel + user - idle) * 1000 / (kernel + user); } -int getProcessCpu() +long long getProcessCpu() { const HANDLE hProcess = GetCurrentProcess(); //if (INVALID_HANDLE_VALUE == hProcess){return -1;} @@ -225,6 +292,32 @@ int getProcessCpu() const __int64 ullKernelTime = compareFileTime(ftKernelTime, ftPreKernelTime); const __int64 ullUserTime = compareFileTime(ftUserTime, ftPreUserTime); log << ullKernelTime << "\n" << ullUserTime << "\n" << iCpuNum; - const __int64 dCpu = (ullKernelTime + ullUserTime) / (iCpuNum * 100); - return static_cast(dCpu); + return (ullKernelTime + ullUserTime) / (iCpuNum * 10); } + +//获取空闲硬盘(千分比) +long long getDiskUsage(double& mbFreeBytes, double& mbTotalBytes){ + /*int sizStr = GetLogicalDriveStrings(0, NULL);//获得本地所有盘符存在Drive数组中 + char* chsDrive = new char[sizStr];//初始化数组用以存储盘符信息 + GetLogicalDriveStrings(sizStr, chsDrive); + int DType; + int si = 0;*/ + BOOL fResult; + unsigned _int64 i64FreeBytesToCaller; + unsigned _int64 i64TotalBytes; + unsigned _int64 i64FreeBytes; + fResult = GetDiskFreeSpaceEx( + NULL, + (PULARGE_INTEGER)&i64FreeBytesToCaller, + (PULARGE_INTEGER)&i64TotalBytes, + (PULARGE_INTEGER)&i64FreeBytes); + //GetDiskFreeSpaceEx函数,可以获取驱动器磁盘的空间状态,函数返回的是个BOOL类型数据 + if (fResult) { + mbTotalBytes = i64TotalBytes * 1000 / 1024 / 1024 / 1024 / 1000.0;//磁盘总容量 + mbFreeBytes = i64FreeBytesToCaller * 1000 / 1024 / 1024 / 1024 / 1000.0;//磁盘剩余空间 + return 1000 - 1000 * i64FreeBytesToCaller / i64TotalBytes; + } + return 0; +} + +#endif \ No newline at end of file diff --git a/Dice/ManagerSystem.h b/Dice/ManagerSystem.h index e5f33a5c..b8b3ef62 100644 --- a/Dice/ManagerSystem.h +++ b/Dice/ManagerSystem.h @@ -1,9 +1,11 @@ +#pragma once + /* * 后台系统 * Copyright (C) 2019-2020 String.Empty * 控制清理用户/群聊记录,清理图片,监控系统 */ -#pragma once + #include #include #include @@ -13,6 +15,12 @@ #include "DiceFile.hpp" #include "DiceConsole.h" #include "MsgFormat.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + using std::string; using std::to_string; using std::set; @@ -21,8 +29,7 @@ using std::vector; using std::unordered_map; constexpr auto CQ_IMAGE = "[CQ:image,file="; constexpr auto CQ_AT = "[CQ:at,qq="; -constexpr time_t NEWYEAR = 1588262400; -extern string DiceDir; +constexpr time_t NEWYEAR = 1609430400; //加载数据 void loadData(); @@ -94,14 +101,15 @@ class User void setConf(const string& key, int val) { + if (key.empty())return; std::lock_guard lock_queue(ex_user); intConf[key] = val; } - void setConf(const string& key, string val) + void setConf(const string& key, const string& val) { std::lock_guard lock_queue(ex_user); - strConf[key] = std::move(val); + strConf[key] = val; } void rmIntConf(const string& key) @@ -168,6 +176,7 @@ extern unordered_map UserList; User& getUser(long long qq); short trustedQQ(long long qq); int clearUser(); +int clearGroup(); string getName(long long QQ, long long GroupID = 0); void filter_CQcode(string&, long long fromGroup = 0); @@ -194,11 +203,7 @@ class Chat map intConf{}; map strConf{}; - Chat& id(long long grp) - { - ID = grp; - return *this; - } + Chat& id(long long grp); Chat& group() { @@ -248,24 +253,15 @@ class Chat return *this; } - void leave(const string& msg = "") - { - if (!msg.empty()) - { - if (isGroup)CQ::sendGroupMsg(ID, msg); - else CQ::sendDiscussMsg(ID, msg); - Sleep(500); - } - set("已退"); - if (isGroup)CQ::setGroupLeave(ID); - else CQ::setDiscussLeave(ID); - } + void leave(const string& msg = ""); [[nodiscard]] bool isset(const string& key) const { return boolConf.count(key) || intConf.count(key) || strConf.count(key); } + bool is_except()const; + void setConf(const string& key, int val) { intConf[key] = val; @@ -353,20 +349,19 @@ class Chat inline unordered_map ChatList; Chat& chat(long long id); -int groupset(long long id, string st); +int groupset(long long id, const string& st); string printChat(Chat& grp); ifstream& operator>>(ifstream& fin, Chat& grp); ofstream& operator<<(ofstream& fout, const Chat& grp); -//被引用的图片列表 -extern set sReferencedImage; +extern unordered_setsReferencedImage; -void scanImage(const string& s, set& list); +void scanImage(const string& s, unordered_set& list); -void scanImage(const vector& v, set& list); +void scanImage(const vector& v, unordered_set& list); template -void scanImage(const map& m, set& list) +void scanImage(const map& m, unordered_set& list) { for (const auto& it : m) { @@ -376,7 +371,7 @@ void scanImage(const map& m, set& list) } template -void scanImage(const map& m, set& list) +void scanImage(const map& m, unordered_set& list) { for (const auto& it : m) { @@ -386,16 +381,15 @@ void scanImage(const map& m, set& list) } template -void scanImage(const map& m, set& list) +void scanImage(const map& m, unordered_set& list) { - for (const auto& it : m) + for (const auto& it : m) { scanImage(it.second, sReferencedImage); } } -int clearImage(); - +#ifdef _WIN32 DWORD getRamPort(); /*static DWORD getRamPort() { @@ -410,6 +404,9 @@ DWORD getRamPort(); __int64 compareFileTime(const FILETIME& ft1, const FILETIME& ft2); //WIN CPU使用情况 -__int64 getWinCpuUsage(); +long long getWinCpuUsage(); + +long long getProcessCpu(); -int getProcessCpu(); +long long getDiskUsage(double&, double&); +#endif \ No newline at end of file diff --git a/Dice/MsgFormat.cpp b/Dice/MsgFormat.cpp index 45852d07..84189b1f 100644 --- a/Dice/MsgFormat.cpp +++ b/Dice/MsgFormat.cpp @@ -23,8 +23,32 @@ #include "MsgFormat.h" #include #include // +#include "DiceJob.h" +#include "DiceMod.h" +#include "EncodingConvert.h" +#include "StrExtern.hpp" +#include "CardDeck.h" +#include "RandomGenerator.h" using std::string; +std::map GlobalChar{ + {"FormFeed","\f"}, + {"Vertical","|"}, + {"LBrace","{"}, + {"RBrace","}"}, + {"LBracket","["}, + {"RBracket","]"}, +}; + +std::map strFuncs{ + {"master_QQ",print_master}, + {"list_extern_deck",list_extern_deck}, + {"list_all_deck",list_deck}, + {"list_reply_deck",[]() {return listKey(CardDeck::mReplyDeck); }}, + {"list_extern_order",list_order_ex}, + {"list_dice_sister",list_dice_sister}, +}; + std::string format(std::string str, const std::initializer_list& replace_str) { auto counter = 0; @@ -41,7 +65,57 @@ std::string format(std::string str, const std::initializer_list& replace_str, + const std::unordered_map& str_tmp) { + if (s[0] == '&') { + string key = s.substr(1); + auto it = replace_str.find(key); + if (it != replace_str.end()) { + return format(it->second, replace_str, str_tmp); + } + if (auto uit = str_tmp.find(key); uit != str_tmp.end()) { + return uit->second; + } + } + int l = 0, r = 0; + while ((l = s.find('{', r)) != string::npos && (r = s.find('}', l)) != string::npos) { + //左括号前加‘\’表示该括号内容不转义 + if (l - 1 >= 0 && s[l - 1] == 0x5c) { + s.replace(l - 1, 1, ""); + continue; + } + string key = s.substr(l + 1, r - l - 1); + string val; + if (key.find("help:") == 0) { + key = key.substr(5); + val = fmt->format(fmt->get_help(key), replace_str); + } + else if (auto uit = str_tmp.find(key); uit != str_tmp.end()) { + if (key == "res")val = format(uit->second, replace_str, str_tmp); + else val = uit->second; + } + else if (key.find("sample:") == 0) { + key = key.substr(7); + vector samples{ split(key,"|") }; + if (samples.empty())val = ""; + else + val = format(samples[RandomGenerator::Randint(0, samples.size() - 1)], replace_str, str_tmp); + } + else if (auto it = replace_str.find(key); it != replace_str.end()) { + val = format(it->second, replace_str, str_tmp); + } + else if ((it = GlobalChar.find(key)) != GlobalChar.end()) { + val = it->second; + } + else if (auto func = strFuncs.find(key); func != strFuncs.end()) { + val = func->second(); + } + else continue; + s.replace(l, r - l + 1, val); + r = l + val.length(); + } + return s; +} string strip(std::string origin) { while (true) @@ -61,9 +135,45 @@ string strip(std::string origin) std::string to_binary(int b) { ResList res; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 14; i++) { if (b & (1 << i))res << std::to_string(i); } return res.dot("+").show(); } + +unsigned int ResList::intPageLen = 512; +ResList& ResList::operator<<(std::string s) { + while (isspace(static_cast(s[0])))s.erase(s.begin()); + if (s.empty())return *this; + vRes.push_back(s); + if (size_t len = wstrlen(s.c_str());len > intMaxLen)intMaxLen = len; + return *this; +} +std::string ResList::show(size_t limPage)const { + if (empty())return {}; + std::string s, strHead, strSepa; + unsigned int lenPage(0), cntPage(0), lenItem(0); + if (intMaxLen > intLineLen || isLineBreak) { + strHead = sHead + "\n"; + strSepa = strLongSepa; + } + else { + strSepa = sDot; + } + for (auto it = vRes.begin(); it != vRes.end(); it++) { + lenItem = wstrlen(it->c_str()); + //超过上限后分页 + if (lenPage + lenItem > intPageLen && !s.empty()) { + if (limPage && limPage <= cntPage + 1) + return s; + if (cntPage++ == 0)s = "\f[第" + std::to_string(cntPage++) + "页]" + (strHead.empty() ? "\n" : "") + s; + s += "\f[第" + std::to_string(cntPage) + "页]\n" + *it; + lenPage = 0; + } + else if (it == vRes.begin())s = strHead + *it; + else s += strSepa + *it; + lenPage += lenItem; + } + return s; +} \ No newline at end of file diff --git a/Dice/MsgFormat.h b/Dice/MsgFormat.h index af352142..93c309c5 100644 --- a/Dice/MsgFormat.h +++ b/Dice/MsgFormat.h @@ -1,3 +1,5 @@ +#pragma once + /* * _______ ________ ________ ________ __ * | __ \ |__ __| | _____| | _____| | | @@ -20,67 +22,34 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#pragma once + #ifndef DICE_MSG_FORMAT #define DICE_MSG_FORMAT #include #include +#include #include #include +#include "STLExtern.hpp" using std::string; + +extern std::map GlobalChar; +typedef string(*GobalTex)(); +extern std::map strFuncs; + std::string format(std::string str, const std::initializer_list& replace_str); -template -std::string format(std::string s, const std::map& replace_str, - const std::map& str_tmp = {}) -{ - if (s[0] == '&') - { - string key = s.substr(1); - auto it = replace_str.find(key); - if (it != replace_str.end()) - { - return format(it->second, replace_str, str_tmp); - } - if ((it = str_tmp.find(key)) != str_tmp.end()) - { - return it->second; - } - } - int l = 0, r = 0; - int len = s.length(); - while ((l = s.find('{', r)) != string::npos && (r = s.find('}', l)) != string::npos) - { - if (l - 1 >= 0 && s[l - 1] == 0x5c) - { - s.replace(l - 1, 1, ""); - continue; - } - string key = s.substr(l + 1, r - l - 1); - auto it = replace_str.find(key); - if (it != replace_str.end()) - { - s.replace(l, r - l + 1, format(it->second, replace_str, str_tmp)); - r += s.length() - len + 1; - len = s.length(); - } - else if ((it = str_tmp.find(key)) != str_tmp.end()) - { - if (key == "res")s.replace(l, r - l + 1, format(it->second, replace_str, str_tmp)); - else s.replace(l, r - l + 1, it->second); - r += s.length() - len + 1; - len = s.length(); - } - } - return s; -} +std::string format(std::string s, const std::map& replace_str, + const std::unordered_map& str_tmp = {}); class ResList { std::vector vRes; unsigned int intMaxLen = 0; bool isLineBreak = false; - unsigned int intLineLen = 16; + unsigned int intLineLen = 10; + static unsigned int intPageLen; + string sHead = ""; string sDot = " "; string strLongSepa = "\n"; public: @@ -92,40 +61,14 @@ class ResList intMaxLen = s.length(); } - ResList& operator<<(std::string s) - { - while (isspace(static_cast(s[0])))s.erase(s.begin()); - if (s.empty())return *this; - vRes.push_back(s); - if (s.length() > intMaxLen)intMaxLen = s.length(); - return *this; - } + ResList& operator<<(std::string s); - std::string show() - { - std::string s; - if (intMaxLen > intLineLen || isLineBreak) - { - for (auto it : vRes) - { - for (auto it2 = vRes.begin(); it2 != vRes.end(); ++it2) - { - if (it2 == vRes.begin())s = "\n" + *it2; - else s += strLongSepa + *it2; - } - } - } - else - { - for (auto it = vRes.begin(); it != vRes.end(); ++it) - { - if (it == vRes.begin())s = *it; - else s += sDot + *it; - } - } - return s; - } + std::string show(size_t = 0)const; + ResList& head(string s) { + sHead = s; + return *this; + } ResList& dot(string s) { sDot = std::move(s); @@ -161,6 +104,21 @@ class ResList } }; +//按属性名输出项目 +class AttrList { + std::unordered_map mItem; + std::vector vKey; +public: + string show() { + string res; + int index = 0; + for (auto& key : vKey) { + res += "\n" + key + "=" + mItem[key]; + } + return res; + } +}; + template std::string listKey(std::map m) { diff --git a/Dice/MsgMonitor.cpp b/Dice/MsgMonitor.cpp index b002d75e..0440db6c 100644 --- a/Dice/MsgMonitor.cpp +++ b/Dice/MsgMonitor.cpp @@ -6,8 +6,12 @@ #include #include #include "MsgMonitor.h" +#include "DiceSchedule.h" +#include "DDAPI.h" +std::atomic FrqMonitor::sumFrqTotal = 0; std::map FrqMonitor::mFrequence = {}; +std::map FrqMonitor::mCntOrder = {}; std::map FrqMonitor::mWarnLevel = {}; std::queue EarlyMsgQueue; @@ -17,13 +21,18 @@ std::queue EarliestMsgQueue; std::set setFrq; std::mutex FrqMutex; -void AddFrq(long long QQ, time_t TT, chatType CT) +void AddFrq(long long QQ, time_t TT, chatType CT, const string& msg) { std::lock_guard lock_queue(FrqMutex); if (setFrq.count(QQ)) return; setFrq.insert(QQ); - auto* newFrq = new FrqMonitor(QQ, TT, CT); + auto* newFrq = new FrqMonitor(QQ, TT, CT, msg); EarlyMsgQueue.push(newFrq); + if (QQ) { + FrqMonitor::sumFrqTotal++; + today->inc("frq"); + today->inc(QQ, "frq"); + } } void frqHandler() @@ -67,37 +76,73 @@ void frqHandler() } } + +FrqMonitor::FrqMonitor(long long QQ, time_t TT, chatType CT, const string& msg) : fromQQ(QQ), fromTime(TT) { + if (mFrequence.count(fromQQ)) { + mFrequence[fromQQ] += 10; + mCntOrder[fromQQ] += 1; + if ((!console["ListenSpam"] || trustedQQ(fromQQ) > 1) && QQ!=console.DiceMaid)return; + if (mFrequence[fromQQ] > 60 && mWarnLevel[fromQQ] < 60 && QQ) { + mWarnLevel[fromQQ] = mFrequence[fromQQ]; + const std::string strMsg = "提醒:\n" + (CT.second != msgtype::Private ? printChat(CT) : "私聊窗口") + + "监测到" + printQQ(fromQQ) + "高频发送指令达" + to_string(mCntOrder[fromQQ]) + + (mCntOrder[fromQQ] > 18 ? "/5min" + : (mCntOrder[fromQQ] > 8 ? "/min" : "/30s")) + + "\n最近指令: " + msg; + if(QQ!=console.DiceMaid)AddMsgToQueue(getMsg("strSpamFirstWarning"), CT); + console.log(strMsg, 1, printSTNow()); + } + else if (mFrequence[fromQQ] > 120 && mWarnLevel[fromQQ] < 120 && QQ) { + mWarnLevel[fromQQ] = mFrequence[fromQQ]; + const std::string strMsg = "警告:\n" + (CT.second != msgtype::Private ? printChat(CT) : "私聊窗口") + + printQQ(fromQQ) + "高频发送指令达" + to_string(mCntOrder[fromQQ]) + + (mCntOrder[fromQQ] > 36 ? "/5min" + : (mCntOrder[fromQQ] > 15 ? "/min" : "/30s")) + + "\n最近指令: " + msg; + if (QQ != console.DiceMaid)AddMsgToQueue(getMsg("strSpamFinalWarning"), CT); + console.log(strMsg, 0b10, printSTNow()); + } + else if (mFrequence[fromQQ] > 200 && mWarnLevel[fromQQ] < 200) { + mWarnLevel[fromQQ] = mFrequence[fromQQ]; + std::string strNow = printSTNow(); + std::string strFrq = to_string(mCntOrder[fromQQ]) + + (mCntOrder[fromQQ] > 60 ? "/5min" + : (mCntOrder[fromQQ] > 25 ? "/min" : "/30s")); + if (!QQ) { + console.log("警告:" + GlobalMsg["strSelfName"] + "高频处理虚拟指令达" + strFrq + + "\n最近指令: " + msg, 0b1000, strNow); + return; + } + std::string strNote = (CT.second != msgtype::Private ? printChat(CT) : "私聊窗口") + "监测到" + + printQQ(fromQQ) + "对" + printQQ(console.DiceMaid) + "高频发送指令达" + strFrq + + "\n最近指令: " + msg; + if (QQ==console.DiceMaid) { + console.set("ListenSelfEcho", 0); + console.set("ListenGroupEcho", 0); + console.log(strNote + "\n已强制停止接收回音", 0b1000, strNow); + } + else if (DD::getDiceSisters().count(fromQQ)) { + console.log(strNote, 0b1000, strNow); + } + else { + DDBlackMarkFactory mark{ fromQQ, 0 }; + mark.sign().type("spam").time(strNow).note(strNow + " " + strNote); + blacklist->create(mark.product()); + } + } + } + else { + mFrequence[fromQQ] = 10; + mWarnLevel[fromQQ] = 0; + } +} + int FrqMonitor::getFrqTotal() { return EarlyMsgQueue.size() + EarlierMsgQueue.size() / 2 + EarliestMsgQueue.size() / 10; } -/*EVE_Status_EX(statusUptime) { - //初始化以来的秒数 - long long llDuration = clock() / 1000; - //long long llDuration = (clock() - llStartTime) / 1000; - if (llDuration < 0) { - eve.data = "N"; - eve.dataf = "/A"; - } - else if (llDuration < 60 * 5) { - eve.data = std::to_string(llDuration); - eve.dataf = "s"; - } - else if (llDuration < 60 * 60 * 5) { - eve.data = std::to_string(llDuration / 60); - eve.dataf = "min"; - } - else if (llDuration < 60 * 60 * 24 * 5) { - eve.data = std::to_string(llDuration / 60 / 60); - eve.dataf = "h"; - } - else{ - eve.data = std::to_string(llDuration / 60 / 60 / 24); - eve.dataf = "day"; - } - eve.color_green(); -}*/ +/* EVE_Status_EX(statusFrq) { if (!Enabled) @@ -108,7 +153,7 @@ EVE_Status_EX(statusFrq) } //平滑到分钟的频度 const int intFrq = FrqMonitor::getFrqTotal(); - //long long llDuration = (clock() - llStartTime) / 1000; + //long long llDuration = time(nullptr) - llStartTime; if (intFrq < 0) { eve.data = "N"; @@ -119,11 +164,11 @@ EVE_Status_EX(statusFrq) { eve.data = std::to_string(intFrq); eve.dataf = "/min"; - if (intFrq < 60) + if (intFrq < 10) { eve.color_green(); } - else if (intFrq < 120) + else if (intFrq < 20) { eve.color_orange(); } @@ -133,3 +178,4 @@ EVE_Status_EX(statusFrq) } } } +*/ \ No newline at end of file diff --git a/Dice/MsgMonitor.h b/Dice/MsgMonitor.h index 6ed2b88d..09a05d9b 100644 --- a/Dice/MsgMonitor.h +++ b/Dice/MsgMonitor.h @@ -5,6 +5,7 @@ #pragma once #include #include +#include #include "DiceConsole.h" #include "ManagerSystem.h" #include "GlobalVar.h" @@ -18,59 +19,15 @@ class FrqMonitor static const long long earlierTime = 60; static const long long earliestTime = 300; //频率记录 - static std::map mFrequence; - static std::map mWarnLevel; + static std::mapmFrequence; + static std::mapmWarnLevel; + static std::mapmCntOrder; + static std::atomic sumFrqTotal; static int getFrqTotal(); long long fromQQ = 0; time_t fromTime = 0; - FrqMonitor(long long QQ, time_t TT, chatType CT): fromQQ(QQ), fromTime(TT) - { - if (mFrequence.count(fromQQ)) - { - mFrequence[fromQQ] += 10; - if (!console["ListenSpam"] || trustedQQ(fromQQ) > 1)return; - if (mFrequence[fromQQ] > 60 && mWarnLevel[fromQQ] < 60) - { - mWarnLevel[fromQQ] = mFrequence[fromQQ]; - const std::string strMsg = "提醒:\n" + (CT.second != CQ::msgtype::Private ? printChat(CT) : "私聊窗口") + - "监测到" + printQQ(fromQQ) + "指令频度达到" + std::to_string(mFrequence[fromQQ] / 10); - AddMsgToQueue(getMsg("strSpamFirstWarning"), CT.first, CT.second); - console.log(strMsg, 1, printSTNow()); - } - else if (mFrequence[fromQQ] > 120 && mWarnLevel[fromQQ] < 120) - { - mWarnLevel[fromQQ] = mFrequence[fromQQ]; - const std::string strMsg = "警告:\n" + (CT.second != CQ::msgtype::Private ? printChat(CT) : "私聊窗口") + - printQQ(fromQQ) + "指令频度达到" + std::to_string(mFrequence[fromQQ] / 10); - AddMsgToQueue(getMsg("strSpamFinalWarning"), CT.first, CT.second); - console.log(strMsg, 0b10, printSTNow()); - } - else if (mFrequence[fromQQ] > 200 && mWarnLevel[fromQQ] < 200) - { - mWarnLevel[fromQQ] = mFrequence[fromQQ]; - std::string strNow = printSTNow(); - std::string strNote = (CT.second != CQ::msgtype::Private ? printChat(CT) : "私聊窗口") + "监测到" + - printQQ(fromQQ) + "对" + printQQ(console.DiceMaid) + "30s发送指令频度达" + std::to_string( - mFrequence[fromQQ] / 10); - if (mDiceList.count(fromQQ)) - { - console.log(strNote, 0b1000, strNow); - } - else - { - DDBlackMarkFactory mark{fromQQ, 0}; - mark.sign().type("spam").time(strNow).note(strNow + " " + strNote); - blacklist->create(mark.product()); - } - } - } - else - { - mFrequence[fromQQ] = 10; - mWarnLevel[fromQQ] = 0; - } - } + FrqMonitor(long long QQ, time_t TT, chatType CT, const string& msg); ~FrqMonitor() { @@ -82,6 +39,7 @@ class FrqMonitor if (time(nullptr) - fromTime > earliestTime) { mFrequence[fromQQ] -= 2; + mCntOrder[fromQQ] -= 1; delete this; return true; } @@ -109,5 +67,5 @@ class FrqMonitor } }; -void AddFrq(long long QQ, time_t TT, chatType CT); +void AddFrq(long long QQ, time_t TT, chatType CT, const string& msg); void frqHandler(); diff --git a/Dice/RD.cpp b/Dice/RD.cpp index 8f0df4ec..cc16a841 100644 --- a/Dice/RD.cpp +++ b/Dice/RD.cpp @@ -20,14 +20,12 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ -#include "CQEVE_ALL.h" #include "CQTools.h" #include #include "RD.h" #include "RDConstant.h" #include "MsgFormat.h" using namespace std; -using namespace CQ; void init(string& msg) { @@ -47,13 +45,16 @@ void init2(string& msg) } if (msg[0] == '!') msg[0] = '.'; + int posFF(0); + while ((posFF = msg.find('\f'))!= string::npos) { + msg[posFF] = '\v'; + } } void COC7D(string& strMAns) { RD rd3D6("3D6"); RD rd2D6p6("2D6+6"); - strMAns += "的人物作成:"; strMAns += '\n'; strMAns += "力量STR=3D6*5="; rd3D6.Roll(); @@ -170,7 +171,6 @@ void COC6D(string& strMAns) RD rd2D6p6("2D6+6"); RD rd3D6p3("3D6+3"); RD rd1D10("1D10"); - strMAns += "的人物作成:"; strMAns += '\n'; strMAns += "力量STR=3D6="; rd3D6.Roll(); @@ -265,7 +265,6 @@ void COC6D(string& strMAns) void COC7(string& strMAns, int intNum) { - strMAns += "的人物作成:"; string strProperty[] = {"力量", "体质", "体型", "敏捷", "外貌", "智力", "意志", "教育", "幸运"}; string strRoll[] = {"3D6", "3D6", "2D6+6", "3D6", "3D6", "2D6+6", "3D6", "2D6+6", "3D6"}; int intAllTotal = 0; @@ -288,7 +287,6 @@ void COC7(string& strMAns, int intNum) void COC6(string& strMAns, int intNum) { - strMAns += "的人物作成:"; string strProperty[] = {"力量", "体质", "体型", "敏捷", "外貌", "智力", "意志", "教育", "资产"}; string strRoll[] = {"3D6", "3D6", "2D6+6", "3D6", "3D6", "2D6+6", "3D6", "3D6+3", "1D10"}; const bool boolAddSpace = intNum != 1; @@ -315,7 +313,6 @@ void COC6(string& strMAns, int intNum) void DND(string& strOutput, int intNum) { - strOutput += "的英雄作成:"; const RD rdDND("4D6K3"); string strDNDName[6] = {"力量", "体质", "敏捷", "智力", "感知", "魅力"}; const bool boolAddSpace = intNum != 1; @@ -449,6 +446,19 @@ int RollSuccessLevel(int res, int rate, int rule) if (rate >= 50 || res < 96)return 1; return 0; break; + case 6: + if (res > rate) { + if (res == 100 || res % 11 == 0) { + return 0; + } + return 1; + } else { + if (res == 1 || res % 11 == 0) { + return 5; + } + return 2; + } + break; default: return -1; } } diff --git a/Dice/RD.h b/Dice/RD.h index c4320a42..185674f6 100644 --- a/Dice/RD.h +++ b/Dice/RD.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "RDConstant.h" #include "RandomGenerator.h" @@ -484,7 +485,10 @@ class RD std::to_string(defaultDice)); while (strDice.find("DX") != std::string::npos) strDice.insert(strDice.find("DX") + 1, - std::to_string(defaultDice)); + std::to_string(defaultDice)); + while (strDice.find("D/") != std::string::npos) + strDice.insert(strDice.find("D/") + 1, + std::to_string(defaultDice)); while (strDice.find("DK") != std::string::npos) strDice.insert(strDice.find("DK") + 1, std::to_string(defaultDice)); @@ -786,12 +790,15 @@ class RD std::string FormShortString() const { std::string strReturnString = strDice; - strReturnString.append("="); - strReturnString.append(FormStringCombined()); - if (FormStringCombined() != std::to_string(intTotal)) + std::string stringCombined{ FormStringCombined() }; + if (strDice != stringCombined) { + strReturnString.append("="); + strReturnString.append(stringCombined); + } + if (std::string strTotal{ std::to_string(intTotal) };stringCombined != strTotal) { strReturnString.append("="); - strReturnString.append(std::to_string(intTotal)); + strReturnString.append(strTotal); } return strReturnString; } diff --git a/Dice/RDConstant.h b/Dice/RDConstant.h index 9ec38531..257aeb9a 100644 --- a/Dice/RDConstant.h +++ b/Dice/RDConstant.h @@ -24,7 +24,9 @@ #ifndef DICE_RD_CONSTANT #define DICE_RD_CONSTANT #include +#include #include +#include //Error Handle #define Value_Err (-1) @@ -51,7 +53,7 @@ typedef int int_errno; -static std::map SkillNameReplace = { +inline std::map SkillNameReplace = { {"str", "力量"}, {"dex", "敏捷"}, {"pow", "意志"}, @@ -65,7 +67,8 @@ static std::map SkillNameReplace = { {"edu", "教育"}, {"mov", "移动力"}, {"san", "理智"}, - {"hp", "体力"}, + {"hp", "生命"}, + {"体力", "生命"}, {"mp", "魔法"}, {"侦察", "侦查"}, {"计算机", "计算机使用"}, @@ -110,19 +113,19 @@ static std::map SkillNameReplace = { {"驾驶:飞行器", "飞行器驾驶"}, {"驾驶(飞行器)", "飞行器驾驶"} }; -static std::vector> BasicCOC7 = { +inline std::vector> BasicCOC7 = { {"职业", "年龄", "性别", "住地", "出身", "时代"}, {"力量", "体质", "体型", "敏捷", "外貌", "智力", "意志", "教育"}, {"生命", "理智", "魔法", "幸运"}, {"身体状态", "精神状态"}, {"DB", "闪避", "护甲"} }; -static std::set InfoCOC7 = { +inline std::set InfoCOC7 = { "职业", "性别", "住地", "出身", "时代", "身体状态", "精神状态", "个人描述", "思想信念", "重要之人", "意义非凡之地", "宝贵之物", "特质", "特点", "伤口疤痕", "恐惧躁狂" }; -static std::vector> BuildCOC7 = { +inline std::vector> BuildCOC7 = { {"力量", "3D6*5"}, {"体质", "3D6*5"}, {"体型", "2D6*5+30"}, @@ -135,14 +138,14 @@ static std::vector> BuildCOC7 = { {"生命", "[体质体型和]/10"}, {"魔法", "[意志]/5"}, }; -static std::map mVariableCOC7 = { +inline std::map mVariableCOC7 = { {"灵感", "&智力"}, {"知识", "&教育"}, {"体质体型和", "[体质]+[体型]"}, {"取悦", "&魅惑"}, }; static std::map ExpressionCOC7 = { - {"", "D100"}, + {"__DefaultDiceExp", "1D100"}, {"医学回复", "1D3"}, {"精神分析回复", "1D3"}, {"徒手伤害", "1D3+[DB]"}, @@ -263,7 +266,7 @@ static std::map ExpressionCOC7 = { {"火焰喷射器", "2D6"}, {"轻型反坦克炮", "8D10"} }; -static std::map AutoFillCOC7 = { +inline std::map AutoFillCOC7 = { {"生命", "[体质体型和]/10"}, {"理智", "&意志"}, {"魔法", "[意志]/5"}, @@ -271,6 +274,7 @@ static std::map AutoFillCOC7 = { {"闪避", "[敏捷]/2"} }; static std::map SkillDefaultVal = { + {"__DefaultDice", 100}, {"会计", 5}, {"人类学", 1}, {"估价", 5}, @@ -370,7 +374,7 @@ static std::map SkillDefaultVal = { {"斗殴", 25} }; -static std::string TempInsanity[11]{ +inline std::string TempInsanity[11]{ "", "失忆:在{0}轮之内,调查员会发现自己只记得最后身处的安全地点,却没有任何来到这里的记忆。", "假性残疾:调查员陷入了心理性的失明、失聪或躯体缺失感中,持续{0}轮。", @@ -384,7 +388,7 @@ static std::string TempInsanity[11]{ "狂躁:调查员由于某种诱因进入狂躁状态,症状持续{0}轮。\n{1}\n具体狂躁症: {2}(KP也可以自行从狂躁症状表中选择其他症状)" }; -static std::string LongInsanity[11]{ +inline std::string LongInsanity[11]{ "", "失忆:在{0}小时后,调查员回过神来,发现自己身处一个陌生的地方,并忘记了自己是谁。记忆会随时间恢复。", "被窃:调查员在{0}小时后恢复清醒,发觉自己被盗,身体毫发无损。", @@ -398,7 +402,7 @@ static std::string LongInsanity[11]{ "狂躁:调查员患上一个新的狂躁症,在{0}小时后恢复理智。在这次疯狂发作中,调查员将完全沉浸于其新的狂躁症状。这是否会被其他人理解(apparent to other people)则取决于守秘人和此调查员。\n{1}\n具体狂躁症: {2}(KP也可以自行从狂躁症状表中选择其他症状)" }; -static std::string strFear[101] = { +inline std::string strFear[101] = { "", "洗澡恐惧症(Ablutophobia):对于洗涤或洗澡的恐惧。", "恐高症(Acrophobia):对于身处高处的恐惧。", @@ -501,7 +505,7 @@ static std::string strFear[101] = { "异域恐惧症(Xenophobia):对陌生人或外国人的恐惧。", "动物恐惧症(Zoophobia):对动物的恐惧。" }; -static std::string strPanic[101] = { +inline std::string strPanic[101] = { "", "沐浴癖(Ablutomania):执着于清洗自己。", "犹豫癖(Aboulomania):病态地犹豫不定。", diff --git a/Dice/RandomGenerator.cpp b/Dice/RandomGenerator.cpp index 94b80c44..e7289193 100644 --- a/Dice/RandomGenerator.cpp +++ b/Dice/RandomGenerator.cpp @@ -24,24 +24,38 @@ #include #include +#if defined(__i386__) || defined(__x86_64__) #ifdef _MSC_VER #include #else #include #endif +#endif namespace RandomGenerator { - inline unsigned long long GetCycleCount() + unsigned long long GetCycleCount() { +#if defined(__i386__) || defined(__x86_64__) return __rdtsc(); - // return static_cast (std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); +#else + return static_cast (std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); +#endif } - + +#if defined(__i386__) || defined(__x86_64__) int Randint(int lowest, int highest) { std::mt19937 gen(static_cast(GetCycleCount())); std::uniform_int_distribution dis(lowest, highest); return dis(gen); } +#else + std::mt19937 gen(static_cast(GetCycleCount())); + int Randint(int lowest, int highest) + { + std::uniform_int_distribution dis(lowest, highest); + return dis(gen); + } +#endif } diff --git a/Dice/RandomGenerator.h b/Dice/RandomGenerator.h index 52450372..d5df4f2a 100644 --- a/Dice/RandomGenerator.h +++ b/Dice/RandomGenerator.h @@ -26,7 +26,7 @@ namespace RandomGenerator { - inline unsigned long long GetCycleCount(); + unsigned long long GetCycleCount(); int Randint(int lowest, int highest); } #endif /*DICE_RANDOM_GENERATOR*/ diff --git a/Dice/S3PutObject.cpp b/Dice/S3PutObject.cpp new file mode 100644 index 00000000..3c3f2016 --- /dev/null +++ b/Dice/S3PutObject.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "S3PutObject.h" + +#include "GlobalVar.h" + +#ifdef _WIN32 +#undef GetMessage +#endif + +// Aws SDK设置 +Aws::SDKOptions options; +Aws::Auth::AWSCredentials awsCredentials("", ""); + +// 判断文件是否存在 +bool file_exists(const std::string& file_name) +{ + std::error_code ec; + return std::filesystem::is_regular_file(file_name, ec); +} + +// 上传文件至S3, 采用S3-accelerate +// 成功时返回"SUCCESS", 否则返回错误信息 +std::string put_s3_object(const Aws::String& s3_bucket_name, + const Aws::String& s3_object_name, + const std::string& file_name, + const Aws::String& region) +{ + // Verify file_name exists + if (!file_exists(file_name)) { + return "ERROR: File Not Found"; + } + // If region is specified, use it + Aws::Client::ClientConfiguration clientConfig; + //if (!region.empty()) + clientConfig.region = region; + clientConfig.verifySSL = false; + clientConfig.endpointOverride = "s3-accelerate.amazonaws.com"; + // Set up request + Aws::S3::S3Client s3_client(awsCredentials, clientConfig); + Aws::S3::Model::PutObjectRequest object_request; + object_request.SetBucket(s3_bucket_name); + object_request.SetKey(s3_object_name); + const std::shared_ptr input_data = + Aws::MakeShared(file_name.c_str(), + file_name.c_str(), + std::ios_base::in | std::ios_base::binary); + object_request.SetBody(input_data); + // Put the object + auto put_object_outcome = s3_client.PutObject(object_request); + if (!put_object_outcome.IsSuccess()) { + const auto& error = put_object_outcome.GetError(); + return std::string("ERROR: ") + error.GetExceptionName().c_str() + ": " + + error.GetMessage().c_str(); + } + return "SUCCESS"; +} \ No newline at end of file diff --git a/Dice/S3PutObject.h b/Dice/S3PutObject.h new file mode 100644 index 00000000..2d4b6870 --- /dev/null +++ b/Dice/S3PutObject.h @@ -0,0 +1,15 @@ +#pragma once +#ifndef TRPGLOGGER_S3PUTOBJECT +#define TRPGLOGGER_S3PUTOBJECT +#include +#include + +extern Aws::SDKOptions options; +// 上传文件至S3, 采用S3-accelerate +// 成功时返回"SUCCESS", 否则返回错误信息 +std::string put_s3_object(const Aws::String& s3_bucket_name, + const Aws::String& s3_object_name, + const std::string& file_name, + const Aws::String& region = ""); + +#endif /*TRPGLOGGER_S3PUTOBJECT*/ \ No newline at end of file diff --git a/Dice/SHKQuerier.h b/Dice/SHKQuerier.h new file mode 100644 index 00000000..6a6b6bd2 --- /dev/null +++ b/Dice/SHKQuerier.h @@ -0,0 +1,101 @@ +/* + * 词条查询器 + * 基于倒排实现对待查询词条的相似匹配 + * Copyright (C) 2020 String.Empty + */ +#pragma once +#include +#include +#include +#include +#include +using std::string; +using std::vector; +using std::unordered_map; +using std::unordered_set; + +struct WordNode { + //该节点的分词,连续数字、连续字母或GBK单字 + //string word; + unordered_setkeys; + unordered_map>next; +}; + +class WordQuerier { + unordered_map word_list; +public: + static vector cutter(const string& title) { + static const char* dot{ "~!@#$%^&*()-=`_+[]\\{}|;':\",./<>?" }; + vector res; + string word; + for (size_t pos = 0; pos < title.length(); pos++) { + if (title[pos] < 0) { + if (!word.empty())res.push_back(word); + res.push_back(title.substr(pos++, 2)); + word.clear(); + } + else if (isspace(title[pos]) || strchr(dot, title[pos])) { + if (!word.empty()) { + res.push_back(word); + word.clear(); + } + } + else { + word += tolower(title[pos]); + } + } + if (!word.empty())res.push_back(word); + return res; + } + void insert(const string& key) { + vector words = cutter(key); + if (words.empty())return; + word_list[words[0]].keys.insert(key); + size_t idx(1); + while (idx < words.size()) { + word_list[words[idx - 1]].next[words[idx]].insert(key); + word_list[words[idx++]].keys.insert(key); + } + } + unordered_set search(const string& key)const{ + vector words = cutter(key); + if (words.empty())return{}; + unordered_set res; + unordered_set sInter; + const WordNode* last_word = nullptr; + for (const auto& word : words) { + //无该词,略过 + if (!word_list.count(word))continue; + //检查连缀 + if (last_word && last_word->next.find(word)!= last_word->next.end()) { + for (const auto& w : last_word->next.find(word)->second) { + if (res.count(w))sInter.insert(w); + } + if (!sInter.empty()) { + res = sInter; + sInter.clear(); + last_word = &word_list.find(word)->second; + continue; + } + } + // + if (res.empty()) { + res = word_list.find(word)->second.keys; + last_word = &word_list.find(word)->second; + continue; + } + for (auto& w : word_list.find(word)->second.keys) { + if (res.count(w))sInter.insert(w); + } + if (!sInter.empty()) { + res = sInter; + sInter.clear(); + last_word = &word_list.find(word)->second; + } + } + return res; + } + void clear() { + word_list.clear(); + } +}; \ No newline at end of file diff --git a/Dice/SHKTrie.h b/Dice/SHKTrie.h new file mode 100644 index 00000000..2e3c0683 --- /dev/null +++ b/Dice/SHKTrie.h @@ -0,0 +1,127 @@ +/* + * 字典树 + * Copyright (C) 2019-2020 String.Empty + */ +#pragma once +#include +#include +#include +#include +using std::map; +using std::unordered_set; +using std::string; + +template +class TrieNode { +public: + map next{}; + TrieNode* fail = nullptr; + bool isleaf = false; + string value{}; +}; + +template +class TrieG { + using Node = TrieNode; + Node root{}; + string chsException; + //是为目标节点的子节点匹配fail + void make_fail(Node& node) { + if (&node == &root) { + for (auto& [ch, kid] : node.next) { + kid.fail = &node; + } + } + else { + for (auto& [ch,kid] : node.next) { + if (auto it = node.fail->next.find(ch); it != node.fail->next.end()) { + kid.fail = &(it->second); + } + else { + kid.fail = &root; + } + } + } + for (auto& [ch, kid] : node.next) { + make_fail(kid); + } + } + static bool ignored(char ch) { + static const char* dot{ "~!@#$%^&*()-=`_+[]\\{}|;':\",./<>?" }; + return isspace(static_cast(ch)) || strchr(dot, ch); + } + void add(const string& s) { + Node* p = &root; + for (auto ch : s) { + if (!p->next.count(ch)) { + p->next[ch] = Node(); + } + p = &(p->next[ch]); + } + p->value = s; + p->isleaf = true; + } +public: + TrieG(){} + template + TrieG(const Con& dir) { + for (const auto& [key,val]: dir) { + add(key); + } + make_fail(root); + } + template + void build(const Con& dir) { + new(this)TrieG(dir); + } + void insert(const string& key) { + add(key); + make_fail(root); + } + //前缀匹配 + bool match_head(const string& s, string& res)const { + const Node* p = &root; + for (const auto& ch : s) { + //if (ignored(ch))continue; + if (!p->next.count(ch))break; + p = &(p->next.find(ch)->second); + if (p->isleaf) { + res = p->value; + } + } + return !res.empty(); + } + bool match_head(const string& s, unordered_set& res)const { + const Node* p = &root; + for (const auto& ch : s) { + if (ignored(ch))continue; + if (!p->next.count(ch))break; + p = &(p->next.find(ch)->second); + if (p->isleaf) { + res.insert(p->value); + } + } + return res.size(); + } + //任意位置字串匹配 + bool search(const string& s, unordered_set& res)const { + const Node* p = &root; + for (const auto& ch : s) { + if (ignored(ch))continue; + while (1) { + if (auto it = p->next.find(ch); it != p->next.end()) { + p = &(it->second); + break; + } + if (p == &root) { + break; + } + p = p->fail; + } + if (p->isleaf) { + res.insert(p->value); + } + } + return res.size(); + } +}; diff --git a/Dice/STLExtern.hpp b/Dice/STLExtern.hpp index 5d9a0295..66bee980 100644 --- a/Dice/STLExtern.hpp +++ b/Dice/STLExtern.hpp @@ -3,6 +3,7 @@ * Copyright (C) 2019-2020 String.Empty */ #pragma once +#include #include #include #include @@ -16,6 +17,9 @@ using std::to_string; struct less_ci { + bool operator()(const char& ch1, const char& ch2) const { + return tolower(static_cast(ch1)) < tolower(static_cast(ch2)); + } bool operator()(const string& str1, const string& str2) const { string::const_iterator it1 = str1.cbegin(), it2 = str2.cbegin(); @@ -53,7 +57,7 @@ class enumap return mVal.find(val) != mVal.end(); } - int operator[](T& val) const + size_t operator[](const T& val) const { if (auto it = mVal.find(val); it != mVal.end())return it->second; return -1; @@ -126,6 +130,3 @@ class PriorList } }; -class FormStep -{ -}; diff --git a/Dice/StorageBase.cpp b/Dice/StorageBase.cpp deleted file mode 100644 index 3787adf9..00000000 --- a/Dice/StorageBase.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * _______ ________ ________ ________ __ - * | __ \ |__ __| | _____| | _____| | | - * | | | | | | | | | |_____ | | - * | | | | | | | | | _____| |__| - * | |__| | __| |__ | |_____ | |_____ __ - * |_______/ |________| |________| |________| |__| - * - * Dice! QQ Dice Robot for TRPG - * Copyright (C) 2018-2019 w4123溯洄 - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License along with this - * program. If not, see . - */ -#include "StorageBase.h" -#include - -StorageBase::StorageBase(std::string FilePath) : FilePath(std::move(FilePath)) -{ -} diff --git a/Dice/StorageBase.h b/Dice/StorageBase.h deleted file mode 100644 index d3051cbb..00000000 --- a/Dice/StorageBase.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * _______ ________ ________ ________ __ - * | __ \ |__ __| | _____| | _____| | | - * | | | | | | | | | |_____ | | - * | | | | | | | | | _____| |__| - * | |__| | __| |__ | |_____ | |_____ __ - * |_______/ |________| |________| |________| |__| - * - * Dice! QQ Dice Robot for TRPG - * Copyright (C) 2018-2019 w4123溯洄 - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License along with this - * program. If not, see . - */ -#pragma once -#ifndef DICE_STORAGE_BASE -#define DICE_STORAGE_BASE -#include - -class StorageBase -{ -protected: - const std::string FilePath; -public: - virtual void read() = 0; - virtual void save() const = 0; - StorageBase(std::string FilePath); - StorageBase(const StorageBase&) = delete; - StorageBase(StorageBase&&) = delete; - virtual ~StorageBase() = default; - virtual StorageBase& operator=(const StorageBase&) = delete; - virtual StorageBase& operator=(StorageBase&&) = delete; -}; -#endif /*DICE_STORAGE_BASE*/ diff --git a/Dice/StrExtern.hpp b/Dice/StrExtern.hpp index 7080f474..bb8e0411 100644 --- a/Dice/StrExtern.hpp +++ b/Dice/StrExtern.hpp @@ -5,24 +5,45 @@ */ #include +#include using std::string; -using std::wstring; +using std::u16string; using std::to_string; +using std::vector; -#define CP_GB18030 (54936) +#define CP_GBK (936) string toString(int num, unsigned short size = 0); -template -string to_signed_string(Dig num) +template +typename std::enable_if_t, string> +toString(F num, unsigned short scale = 2, bool align = false) { - if (num > 0)return "+" + to_string(num); - return to_string(num); + string strNum{ to_string(num) }; + size_t dot(strNum.find('.') + scale + 1); + if (align)return strNum.substr(0, dot); + size_t last(strNum.find_last_not_of('0') + 1); + if (last > dot)last = dot; + if (strNum[last - 1] == '.')last--; + return strNum.substr(0, last); +} + +template +string to_signed_string(Dig num) +{ + if (num > 0)return "+" + to_string(num); + return to_string(num); } int count_char(const string& s, char ch); -string convert_w2a(const wchar_t* wch); +vector split(const string&, const string&); + +string convert_w2a(const char16_t* wch); + +u16string convert_a2w(const char* ch); + +string printDuringTime(long long); -wstring convert_a2w(const char* ch); +size_t wstrlen(const char*); diff --git a/Dice/app.json b/Dice/com.w4123.dice.json similarity index 60% rename from Dice/app.json rename to Dice/com.w4123.dice.json index c7d2e8ec..f996fbd0 100644 --- a/Dice/app.json +++ b/Dice/com.w4123.dice.json @@ -2,10 +2,10 @@ "ret": 1, "apiver": 9, "name": "Dice!", - "version": "2.4.0beta3", - "version_id": 563, - "author": "w4123婧磩 Shiki", - "description": "璺戝洟鐢ㄩ瀛 鏈▼搴忎娇鐢ˋGPLv3寮婧愬崗璁巿鏉 Copyright (c) 2018-2019 w4123婧磩 Shiki", + "version": "2.4.2beta3", + "version_id": 570, + "author": "w4123婧磩 & Shiki", + "description": "璺戝洟鐢ㄩ瀛 鏈▼搴忎娇鐢ˋGPLv3寮婧愬崗璁巿鏉 Copyright (c) 2018-2020 w4123婧磩 & Shiki", "event": [ { "id": 1, @@ -93,22 +93,10 @@ } ], "menu": [ - { - "name": "鏇存柊Dice!", - "function": "eventDiceUpdate" - }, { "name": "Master妯″紡鍒囨崲", "function": "eventMasterMode" }, - { - "name": "娓呴闈炵鐞嗙兢鑱", - "function": "eventClearGroupUnpower" - }, - { - "name": "娓呴涓嶆椿璺冪兢鑱", - "function": "eventClearGroup30" - }, { "name": "鍏ㄥ眬寮鍏", "function": "eventGlobalSwitch" @@ -118,7 +106,7 @@ "function": "eventGUI" } ], - "status": [ // 鎮诞绐楃姸鎬侊紙灏嗗簲鐢ㄨ繍琛岀姸鎬佺瓑鏄剧ず鍦ㄩ叿Q鎮诞绐楋級 + "status": [ { "id": 1, "name": "鎸囦护棰戝害", @@ -128,26 +116,25 @@ } ], "auth": [ - //20, //[鏁忔劅]鍙朇ookies getCookies - 101, //鍙戦佺兢娑堟伅 sendGroupMsg - 103, //鍙戦佽璁虹粍娑堟伅 sendDiscussMsg - 106, //鍙戦佺鑱婃秷鎭 sendPrivateMsg - //110, //鍙戦佽禐 sendLike - 120, //缃兢鎴愬憳绉诲嚭 setGroupKick - 121, //缃兢鎴愬憳绂佽█ setGroupBan - 122, //缃兢绠$悊鍛 setGroupAdmin - 123, //缃叏缇ょ瑷 setGroupWholeBan - 126, //缃兢鎴愬憳鍚嶇墖 setGroupCard - 127, //[鏁忔劅]缃兢閫鍑 setGroupLeave - 128, //缃兢鎴愬憳涓撳睘澶磋 setGroupSpecialTitle - 130, //鍙栫兢鎴愬憳淇℃伅 getGroupMemberInfoV2 / getGroupMemberInfo - 131, //鍙栭檶鐢熶汉淇℃伅 getStrangerInfo - 132, //鍙栫兢淇℃伅 getGroupInfo - 140, //缃璁虹粍閫鍑 setDiscussLeave - 150, //缃ソ鍙嬫坊鍔犺姹 setFriendAddRequest - 151, //缃兢娣诲姞璇锋眰 setGroupAddRequest - 160, //鍙栫兢鎴愬憳鍒楄〃 getGroupMemberList - 161, //鍙栫兢鍒楄〃 getGroupList - 162 //鍙栧ソ鍙嬪垪琛 getFriendList + 101, + 103, + 106, + 110, + 120, + 121, + 122, + 123, + 126, + 127, + 128, + 130, + 131, + 132, + 140, + 150, + 151, + 160, + 161, + 162 ] } \ No newline at end of file diff --git a/Dice/dllmain.cpp b/Dice/dllmain.cpp index 614ce13d..70de116b 100644 --- a/Dice/dllmain.cpp +++ b/Dice/dllmain.cpp @@ -20,6 +20,7 @@ * You should have received a copy of the GNU Affero General Public License along with this * program. If not, see . */ +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include "GlobalVar.h" @@ -32,3 +33,4 @@ BOOL APIENTRY DllMain(HMODULE hModule, hDllModule = hModule; return TRUE; } +#endif \ No newline at end of file diff --git a/Dice/packages.config b/Dice/packages.config new file mode 100644 index 00000000..4265b893 --- /dev/null +++ b/Dice/packages.config @@ -0,0 +1,7 @@ +锘 + + + + + + \ No newline at end of file diff --git a/Dice/strExtern.cpp b/Dice/strExtern.cpp index f3374106..0ce0e5ae 100644 --- a/Dice/strExtern.cpp +++ b/Dice/strExtern.cpp @@ -1,14 +1,21 @@ #include +#include #include +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include +#else +#include +#endif #include "StrExtern.hpp" +#include "EncodingConvert.h" using std::string; -using std::wstring; +using std::u16string; +using std::string_view; using std::to_string; -#define CP_GB18030 (54936) +#define CP_GBK (936) string toString(int num, unsigned short size) { @@ -28,23 +35,72 @@ int count_char(const string& s, char ch) { return std::count(s.begin(), s.end(), ch); } +vector split(const string& str, const string& sep) { + vector res; + size_t l{ 0 }, pos{ str.find(sep) }; + while (pos != string::npos) { + res.push_back(str.substr(l, pos - l)); + l = pos + sep.length(); + pos = str.find(sep, l); + } + if (l < str.length()) res.push_back(str.substr(l)); + return res; +} -string convert_w2a(const wchar_t* wch) +string convert_w2a(const char16_t* wch) { - const int len = WideCharToMultiByte(CP_GB18030, 0, wch, -1, nullptr, 0, nullptr, nullptr); +#ifdef _WIN32 + const int len = WideCharToMultiByte(CP_GBK, 0, (const wchar_t*)wch, -1, nullptr, 0, nullptr, nullptr); char* m_char = new char[len]; - WideCharToMultiByte(CP_GB18030, 0, wch, -1, m_char, len, nullptr, nullptr); + WideCharToMultiByte(CP_GBK, 0, (const wchar_t*)wch, -1, m_char, len, nullptr, nullptr); std::string str(m_char); delete[] m_char; return str; +#else + return ConvertEncoding(wch, "utf-16le", "gb18030"); +#endif } -wstring convert_a2w(const char* ch) +u16string convert_a2w(const char* ch) { - const int len = MultiByteToWideChar(CP_GB18030, 0, ch, -1, nullptr, 0); - auto* m_char = new wchar_t[len]; - MultiByteToWideChar(CP_GB18030, 0, ch, -1, m_char, len); - std::wstring wstr(m_char); - delete[] m_char; - return wstr; +#ifdef _WIN32 + const int len = MultiByteToWideChar(CP_GBK, 0, ch, -1, nullptr, 0); + wchar_t* m_char = new wchar_t[len]; + MultiByteToWideChar(CP_GBK, 0, ch, -1, m_char, len); + std::u16string wstr((char16_t*)m_char); + delete[] m_char; + return wstr; +#else + return ConvertEncoding(ch, "gb18030", "utf-16le"); +#endif +} +size_t wstrlen(const char* ch) { +#ifdef _WIN32 + return MultiByteToWideChar(CP_GBK, 0, ch, -1, nullptr, 0); +#else + return ConvertEncoding(ch, "gb18030", "utf-16le").length(); +#endif +} + +string printDuringTime(long long seconds) +{ + if (seconds < 0) { + return "N/A"; + } + else if (seconds < 60) { + return std::to_string(seconds) + "秒"; + } + int mins = int(seconds / 60); + seconds = seconds % 60; + if (mins < 60) { + return std::to_string(mins) + "分" + std::to_string(seconds) + "秒"; + } + int hours = mins / 60; + mins = mins % 60; + if (hours < 24) { + return std::to_string(hours) + "小时" + std::to_string(mins) + "分" + std::to_string(seconds) + "秒"; + } + int days = hours / 24; + hours = hours % 24; + return std::to_string(days) + "天" + std::to_string(hours) + "小时" + std::to_string(mins) + "分" + std::to_string(seconds) + "秒"; } diff --git a/Lua/lapi.c b/Lua/lapi.c new file mode 100644 index 00000000..c824da27 --- /dev/null +++ b/Lua/lapi.c @@ -0,0 +1,1431 @@ +/* +** $Id: lapi.c $ +** Lua API +** See Copyright Notice in lua.h +*/ + +#define lapi_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lundump.h" +#include "lvm.h" + + + +const char lua_ident[] = + "$LuaVersion: " LUA_COPYRIGHT " $" + "$LuaAuthors: " LUA_AUTHORS " $"; + + + +/* +** Test for a valid index. +** '!ttisnil(o)' implies 'o != &G(L)->nilvalue', so it is not needed. +** However, it covers the most common cases in a faster way. +*/ +#define isvalid(L, o) (!ttisnil(o) || o != &G(L)->nilvalue) + + +/* test for pseudo index */ +#define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) + +/* test for upvalue */ +#define isupvalue(i) ((i) < LUA_REGISTRYINDEX) + + +static TValue *index2value (lua_State *L, int idx) { + CallInfo *ci = L->ci; + if (idx > 0) { + StkId o = ci->func + idx; + api_check(L, idx <= L->ci->top - (ci->func + 1), "unacceptable index"); + if (o >= L->top) return &G(L)->nilvalue; + else return s2v(o); + } + else if (!ispseudo(idx)) { /* negative index */ + api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); + return s2v(L->top + idx); + } + else if (idx == LUA_REGISTRYINDEX) + return &G(L)->l_registry; + else { /* upvalues */ + idx = LUA_REGISTRYINDEX - idx; + api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large"); + if (ttislcf(s2v(ci->func))) /* light C function? */ + return &G(L)->nilvalue; /* it has no upvalues */ + else { + CClosure *func = clCvalue(s2v(ci->func)); + return (idx <= func->nupvalues) ? &func->upvalue[idx-1] : &G(L)->nilvalue; + } + } +} + + +static StkId index2stack (lua_State *L, int idx) { + CallInfo *ci = L->ci; + if (idx > 0) { + StkId o = ci->func + idx; + api_check(L, o < L->top, "unacceptable index"); + return o; + } + else { /* non-positive index */ + api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); + api_check(L, !ispseudo(idx), "invalid index"); + return L->top + idx; + } +} + + +LUA_API int lua_checkstack (lua_State *L, int n) { + int res; + CallInfo *ci; + lua_lock(L); + ci = L->ci; + api_check(L, n >= 0, "negative 'n'"); + if (L->stack_last - L->top > n) /* stack large enough? */ + res = 1; /* yes; check is OK */ + else { /* no; need to grow stack */ + int inuse = cast_int(L->top - L->stack) + EXTRA_STACK; + if (inuse > LUAI_MAXSTACK - n) /* can grow without overflow? */ + res = 0; /* no */ + else /* try to grow stack */ + res = luaD_growstack(L, n, 0); + } + if (res && ci->top < L->top + n) + ci->top = L->top + n; /* adjust frame top */ + lua_unlock(L); + return res; +} + + +LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { + int i; + if (from == to) return; + lua_lock(to); + api_checknelems(from, n); + api_check(from, G(from) == G(to), "moving among independent states"); + api_check(from, to->ci->top - to->top >= n, "stack overflow"); + from->top -= n; + for (i = 0; i < n; i++) { + setobjs2s(to, to->top, from->top + i); + to->top++; /* stack already checked by previous 'api_check' */ + } + lua_unlock(to); +} + + +LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) { + lua_CFunction old; + lua_lock(L); + old = G(L)->panic; + G(L)->panic = panicf; + lua_unlock(L); + return old; +} + + +LUA_API lua_Number lua_version (lua_State *L) { + UNUSED(L); + return LUA_VERSION_NUM; +} + + + +/* +** basic stack manipulation +*/ + + +/* +** convert an acceptable stack index into an absolute index +*/ +LUA_API int lua_absindex (lua_State *L, int idx) { + return (idx > 0 || ispseudo(idx)) + ? idx + : cast_int(L->top - L->ci->func) + idx; +} + + +LUA_API int lua_gettop (lua_State *L) { + return cast_int(L->top - (L->ci->func + 1)); +} + + +LUA_API void lua_settop (lua_State *L, int idx) { + CallInfo *ci; + StkId func; + ptrdiff_t diff; /* difference for new top */ + lua_lock(L); + ci = L->ci; + func = ci->func; + if (idx >= 0) { + api_check(L, idx <= ci->top - (func + 1), "new top too large"); + diff = ((func + 1) + idx) - L->top; + for (; diff > 0; diff--) + setnilvalue(s2v(L->top++)); /* clear new slots */ + } + else { + api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top"); + diff = idx + 1; /* will "subtract" index (as it is negative) */ + } + if (diff < 0 && hastocloseCfunc(ci->nresults)) + luaF_close(L, L->top + diff, LUA_OK); + L->top += diff; /* correct top only after closing any upvalue */ + lua_unlock(L); +} + + +/* +** Reverse the stack segment from 'from' to 'to' +** (auxiliary to 'lua_rotate') +** Note that we move(copy) only the value inside the stack. +** (We do not move additional fields that may exist.) +*/ +static void reverse (lua_State *L, StkId from, StkId to) { + for (; from < to; from++, to--) { + TValue temp; + setobj(L, &temp, s2v(from)); + setobjs2s(L, from, to); + setobj2s(L, to, &temp); + } +} + + +/* +** Let x = AB, where A is a prefix of length 'n'. Then, +** rotate x n == BA. But BA == (A^r . B^r)^r. +*/ +LUA_API void lua_rotate (lua_State *L, int idx, int n) { + StkId p, t, m; + lua_lock(L); + t = L->top - 1; /* end of stack segment being rotated */ + p = index2stack(L, idx); /* start of segment */ + api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), "invalid 'n'"); + m = (n >= 0 ? t - n : p - n - 1); /* end of prefix */ + reverse(L, p, m); /* reverse the prefix with length 'n' */ + reverse(L, m + 1, t); /* reverse the suffix */ + reverse(L, p, t); /* reverse the entire segment */ + lua_unlock(L); +} + + +LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { + TValue *fr, *to; + lua_lock(L); + fr = index2value(L, fromidx); + to = index2value(L, toidx); + api_check(L, isvalid(L, to), "invalid index"); + setobj(L, to, fr); + if (isupvalue(toidx)) /* function upvalue? */ + luaC_barrier(L, clCvalue(s2v(L->ci->func)), fr); + /* LUA_REGISTRYINDEX does not need gc barrier + (collector revisits it before finishing collection) */ + lua_unlock(L); +} + + +LUA_API void lua_pushvalue (lua_State *L, int idx) { + lua_lock(L); + setobj2s(L, L->top, index2value(L, idx)); + api_incr_top(L); + lua_unlock(L); +} + + + +/* +** access functions (stack -> C) +*/ + + +LUA_API int lua_type (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (isvalid(L, o) ? ttype(o) : LUA_TNONE); +} + + +LUA_API const char *lua_typename (lua_State *L, int t) { + UNUSED(L); + api_check(L, LUA_TNONE <= t && t < LUA_NUMTYPES, "invalid type"); + return ttypename(t); +} + + +LUA_API int lua_iscfunction (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (ttislcf(o) || (ttisCclosure(o))); +} + + +LUA_API int lua_isinteger (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return ttisinteger(o); +} + + +LUA_API int lua_isnumber (lua_State *L, int idx) { + lua_Number n; + const TValue *o = index2value(L, idx); + return tonumber(o, &n); +} + + +LUA_API int lua_isstring (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (ttisstring(o) || cvt2str(o)); +} + + +LUA_API int lua_isuserdata (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (ttisfulluserdata(o) || ttislightuserdata(o)); +} + + +LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { + const TValue *o1 = index2value(L, index1); + const TValue *o2 = index2value(L, index2); + return (isvalid(L, o1) && isvalid(L, o2)) ? luaV_rawequalobj(o1, o2) : 0; +} + + +LUA_API void lua_arith (lua_State *L, int op) { + lua_lock(L); + if (op != LUA_OPUNM && op != LUA_OPBNOT) + api_checknelems(L, 2); /* all other operations expect two operands */ + else { /* for unary operations, add fake 2nd operand */ + api_checknelems(L, 1); + setobjs2s(L, L->top, L->top - 1); + api_incr_top(L); + } + /* first operand at top - 2, second at top - 1; result go to top - 2 */ + luaO_arith(L, op, s2v(L->top - 2), s2v(L->top - 1), L->top - 2); + L->top--; /* remove second operand */ + lua_unlock(L); +} + + +LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { + const TValue *o1; + const TValue *o2; + int i = 0; + lua_lock(L); /* may call tag method */ + o1 = index2value(L, index1); + o2 = index2value(L, index2); + if (isvalid(L, o1) && isvalid(L, o2)) { + switch (op) { + case LUA_OPEQ: i = luaV_equalobj(L, o1, o2); break; + case LUA_OPLT: i = luaV_lessthan(L, o1, o2); break; + case LUA_OPLE: i = luaV_lessequal(L, o1, o2); break; + default: api_check(L, 0, "invalid option"); + } + } + lua_unlock(L); + return i; +} + + +LUA_API size_t lua_stringtonumber (lua_State *L, const char *s) { + size_t sz = luaO_str2num(s, s2v(L->top)); + if (sz != 0) + api_incr_top(L); + return sz; +} + + +LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *pisnum) { + lua_Number n = 0; + const TValue *o = index2value(L, idx); + int isnum = tonumber(o, &n); + if (pisnum) + *pisnum = isnum; + return n; +} + + +LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum) { + lua_Integer res = 0; + const TValue *o = index2value(L, idx); + int isnum = tointeger(o, &res); + if (pisnum) + *pisnum = isnum; + return res; +} + + +LUA_API int lua_toboolean (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return !l_isfalse(o); +} + + +LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { + TValue *o; + lua_lock(L); + o = index2value(L, idx); + if (!ttisstring(o)) { + if (!cvt2str(o)) { /* not convertible? */ + if (len != NULL) *len = 0; + lua_unlock(L); + return NULL; + } + luaO_tostring(L, o); + luaC_checkGC(L); + o = index2value(L, idx); /* previous call may reallocate the stack */ + } + if (len != NULL) + *len = vslen(o); + lua_unlock(L); + return svalue(o); +} + + +LUA_API lua_Unsigned lua_rawlen (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + switch (ttypetag(o)) { + case LUA_VSHRSTR: return tsvalue(o)->shrlen; + case LUA_VLNGSTR: return tsvalue(o)->u.lnglen; + case LUA_VUSERDATA: return uvalue(o)->len; + case LUA_VTABLE: return luaH_getn(hvalue(o)); + default: return 0; + } +} + + +LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + if (ttislcf(o)) return fvalue(o); + else if (ttisCclosure(o)) + return clCvalue(o)->f; + else return NULL; /* not a C function */ +} + + +static void *touserdata (const TValue *o) { + switch (ttype(o)) { + case LUA_TUSERDATA: return getudatamem(uvalue(o)); + case LUA_TLIGHTUSERDATA: return pvalue(o); + default: return NULL; + } +} + + +LUA_API void *lua_touserdata (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return touserdata(o); +} + + +LUA_API lua_State *lua_tothread (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (!ttisthread(o)) ? NULL : thvalue(o); +} + + +/* +** Returns a pointer to the internal representation of an object. +** Note that ANSI C does not allow the conversion of a pointer to +** function to a 'void*', so the conversion here goes through +** a 'size_t'. (As the returned pointer is only informative, this +** conversion should not be a problem.) +*/ +LUA_API const void *lua_topointer (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + switch (ttypetag(o)) { + case LUA_VLCF: return cast_voidp(cast_sizet(fvalue(o))); + case LUA_VUSERDATA: case LUA_VLIGHTUSERDATA: + return touserdata(o); + default: { + if (iscollectable(o)) + return gcvalue(o); + else + return NULL; + } + } +} + + + +/* +** push functions (C -> stack) +*/ + + +LUA_API void lua_pushnil (lua_State *L) { + lua_lock(L); + setnilvalue(s2v(L->top)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { + lua_lock(L); + setfltvalue(s2v(L->top), n); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { + lua_lock(L); + setivalue(s2v(L->top), n); + api_incr_top(L); + lua_unlock(L); +} + + +/* +** Pushes on the stack a string with given length. Avoid using 's' when +** 'len' == 0 (as 's' can be NULL in that case), due to later use of +** 'memcmp' and 'memcpy'. +*/ +LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { + TString *ts; + lua_lock(L); + ts = (len == 0) ? luaS_new(L, "") : luaS_newlstr(L, s, len); + setsvalue2s(L, L->top, ts); + api_incr_top(L); + luaC_checkGC(L); + lua_unlock(L); + return getstr(ts); +} + + +LUA_API const char *lua_pushstring (lua_State *L, const char *s) { + lua_lock(L); + if (s == NULL) + setnilvalue(s2v(L->top)); + else { + TString *ts; + ts = luaS_new(L, s); + setsvalue2s(L, L->top, ts); + s = getstr(ts); /* internal copy's address */ + } + api_incr_top(L); + luaC_checkGC(L); + lua_unlock(L); + return s; +} + + +LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, + va_list argp) { + const char *ret; + lua_lock(L); + ret = luaO_pushvfstring(L, fmt, argp); + luaC_checkGC(L); + lua_unlock(L); + return ret; +} + + +LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { + const char *ret; + va_list argp; + lua_lock(L); + va_start(argp, fmt); + ret = luaO_pushvfstring(L, fmt, argp); + va_end(argp); + luaC_checkGC(L); + lua_unlock(L); + return ret; +} + + +LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { + lua_lock(L); + if (n == 0) { + setfvalue(s2v(L->top), fn); + api_incr_top(L); + } + else { + CClosure *cl; + api_checknelems(L, n); + api_check(L, n <= MAXUPVAL, "upvalue index too large"); + cl = luaF_newCclosure(L, n); + cl->f = fn; + L->top -= n; + while (n--) { + setobj2n(L, &cl->upvalue[n], s2v(L->top + n)); + /* does not need barrier because closure is white */ + lua_assert(iswhite(cl)); + } + setclCvalue(L, s2v(L->top), cl); + api_incr_top(L); + luaC_checkGC(L); + } + lua_unlock(L); +} + + +LUA_API void lua_pushboolean (lua_State *L, int b) { + lua_lock(L); + if (b) + setbtvalue(s2v(L->top)); + else + setbfvalue(s2v(L->top)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushlightuserdata (lua_State *L, void *p) { + lua_lock(L); + setpvalue(s2v(L->top), p); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API int lua_pushthread (lua_State *L) { + lua_lock(L); + setthvalue(L, s2v(L->top), L); + api_incr_top(L); + lua_unlock(L); + return (G(L)->mainthread == L); +} + + + +/* +** get functions (Lua -> stack) +*/ + + +static int auxgetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + setobj2s(L, L->top, slot); + api_incr_top(L); + } + else { + setsvalue2s(L, L->top, str); + api_incr_top(L); + luaV_finishget(L, t, s2v(L->top - 1), L->top - 1, slot); + } + lua_unlock(L); + return ttype(s2v(L->top - 1)); +} + + +LUA_API int lua_getglobal (lua_State *L, const char *name) { + Table *reg; + lua_lock(L); + reg = hvalue(&G(L)->l_registry); + return auxgetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name); +} + + +LUA_API int lua_gettable (lua_State *L, int idx) { + const TValue *slot; + TValue *t; + lua_lock(L); + t = index2value(L, idx); + if (luaV_fastget(L, t, s2v(L->top - 1), slot, luaH_get)) { + setobj2s(L, L->top - 1, slot); + } + else + luaV_finishget(L, t, s2v(L->top - 1), L->top - 1, slot); + lua_unlock(L); + return ttype(s2v(L->top - 1)); +} + + +LUA_API int lua_getfield (lua_State *L, int idx, const char *k) { + lua_lock(L); + return auxgetstr(L, index2value(L, idx), k); +} + + +LUA_API int lua_geti (lua_State *L, int idx, lua_Integer n) { + TValue *t; + const TValue *slot; + lua_lock(L); + t = index2value(L, idx); + if (luaV_fastgeti(L, t, n, slot)) { + setobj2s(L, L->top, slot); + } + else { + TValue aux; + setivalue(&aux, n); + luaV_finishget(L, t, &aux, L->top, slot); + } + api_incr_top(L); + lua_unlock(L); + return ttype(s2v(L->top - 1)); +} + + +static int finishrawget (lua_State *L, const TValue *val) { + if (isempty(val)) /* avoid copying empty items to the stack */ + setnilvalue(s2v(L->top)); + else + setobj2s(L, L->top, val); + api_incr_top(L); + lua_unlock(L); + return ttype(s2v(L->top - 1)); +} + + +static Table *gettable (lua_State *L, int idx) { + TValue *t = index2value(L, idx); + api_check(L, ttistable(t), "table expected"); + return hvalue(t); +} + + +LUA_API int lua_rawget (lua_State *L, int idx) { + Table *t; + const TValue *val; + lua_lock(L); + api_checknelems(L, 1); + t = gettable(L, idx); + val = luaH_get(t, s2v(L->top - 1)); + L->top--; /* remove key */ + return finishrawget(L, val); +} + + +LUA_API int lua_rawgeti (lua_State *L, int idx, lua_Integer n) { + Table *t; + lua_lock(L); + t = gettable(L, idx); + return finishrawget(L, luaH_getint(t, n)); +} + + +LUA_API int lua_rawgetp (lua_State *L, int idx, const void *p) { + Table *t; + TValue k; + lua_lock(L); + t = gettable(L, idx); + setpvalue(&k, cast_voidp(p)); + return finishrawget(L, luaH_get(t, &k)); +} + + +LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { + Table *t; + lua_lock(L); + t = luaH_new(L); + sethvalue2s(L, L->top, t); + api_incr_top(L); + if (narray > 0 || nrec > 0) + luaH_resize(L, t, narray, nrec); + luaC_checkGC(L); + lua_unlock(L); +} + + +LUA_API int lua_getmetatable (lua_State *L, int objindex) { + const TValue *obj; + Table *mt; + int res = 0; + lua_lock(L); + obj = index2value(L, objindex); + switch (ttype(obj)) { + case LUA_TTABLE: + mt = hvalue(obj)->metatable; + break; + case LUA_TUSERDATA: + mt = uvalue(obj)->metatable; + break; + default: + mt = G(L)->mt[ttype(obj)]; + break; + } + if (mt != NULL) { + sethvalue2s(L, L->top, mt); + api_incr_top(L); + res = 1; + } + lua_unlock(L); + return res; +} + + +LUA_API int lua_getiuservalue (lua_State *L, int idx, int n) { + TValue *o; + int t; + lua_lock(L); + o = index2value(L, idx); + api_check(L, ttisfulluserdata(o), "full userdata expected"); + if (n <= 0 || n > uvalue(o)->nuvalue) { + setnilvalue(s2v(L->top)); + t = LUA_TNONE; + } + else { + setobj2s(L, L->top, &uvalue(o)->uv[n - 1].uv); + t = ttype(s2v(L->top)); + } + api_incr_top(L); + lua_unlock(L); + return t; +} + + +/* +** set functions (stack -> Lua) +*/ + +/* +** t[k] = value at the top of the stack (where 'k' is a string) +*/ +static void auxsetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + api_checknelems(L, 1); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + luaV_finishfastset(L, t, slot, s2v(L->top - 1)); + L->top--; /* pop value */ + } + else { + setsvalue2s(L, L->top, str); /* push 'str' (to make it a TValue) */ + api_incr_top(L); + luaV_finishset(L, t, s2v(L->top - 1), s2v(L->top - 2), slot); + L->top -= 2; /* pop value and key */ + } + lua_unlock(L); /* lock done by caller */ +} + + +LUA_API void lua_setglobal (lua_State *L, const char *name) { + Table *reg; + lua_lock(L); /* unlock done in 'auxsetstr' */ + reg = hvalue(&G(L)->l_registry); + auxsetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name); +} + + +LUA_API void lua_settable (lua_State *L, int idx) { + TValue *t; + const TValue *slot; + lua_lock(L); + api_checknelems(L, 2); + t = index2value(L, idx); + if (luaV_fastget(L, t, s2v(L->top - 2), slot, luaH_get)) { + luaV_finishfastset(L, t, slot, s2v(L->top - 1)); + } + else + luaV_finishset(L, t, s2v(L->top - 2), s2v(L->top - 1), slot); + L->top -= 2; /* pop index and value */ + lua_unlock(L); +} + + +LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, index2value(L, idx), k); +} + + +LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { + TValue *t; + const TValue *slot; + lua_lock(L); + api_checknelems(L, 1); + t = index2value(L, idx); + if (luaV_fastgeti(L, t, n, slot)) { + luaV_finishfastset(L, t, slot, s2v(L->top - 1)); + } + else { + TValue aux; + setivalue(&aux, n); + luaV_finishset(L, t, &aux, s2v(L->top - 1), slot); + } + L->top--; /* pop value */ + lua_unlock(L); +} + + +static void aux_rawset (lua_State *L, int idx, TValue *key, int n) { + Table *t; + TValue *slot; + lua_lock(L); + api_checknelems(L, n); + t = gettable(L, idx); + slot = luaH_set(L, t, key); + setobj2t(L, slot, s2v(L->top - 1)); + invalidateTMcache(t); + luaC_barrierback(L, obj2gco(t), s2v(L->top - 1)); + L->top -= n; + lua_unlock(L); +} + + +LUA_API void lua_rawset (lua_State *L, int idx) { + aux_rawset(L, idx, s2v(L->top - 2), 2); +} + + +LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) { + TValue k; + setpvalue(&k, cast_voidp(p)); + aux_rawset(L, idx, &k, 1); +} + + +LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { + Table *t; + lua_lock(L); + api_checknelems(L, 1); + t = gettable(L, idx); + luaH_setint(L, t, n, s2v(L->top - 1)); + luaC_barrierback(L, obj2gco(t), s2v(L->top - 1)); + L->top--; + lua_unlock(L); +} + + +LUA_API int lua_setmetatable (lua_State *L, int objindex) { + TValue *obj; + Table *mt; + lua_lock(L); + api_checknelems(L, 1); + obj = index2value(L, objindex); + if (ttisnil(s2v(L->top - 1))) + mt = NULL; + else { + api_check(L, ttistable(s2v(L->top - 1)), "table expected"); + mt = hvalue(s2v(L->top - 1)); + } + switch (ttype(obj)) { + case LUA_TTABLE: { + hvalue(obj)->metatable = mt; + if (mt) { + luaC_objbarrier(L, gcvalue(obj), mt); + luaC_checkfinalizer(L, gcvalue(obj), mt); + } + break; + } + case LUA_TUSERDATA: { + uvalue(obj)->metatable = mt; + if (mt) { + luaC_objbarrier(L, uvalue(obj), mt); + luaC_checkfinalizer(L, gcvalue(obj), mt); + } + break; + } + default: { + G(L)->mt[ttype(obj)] = mt; + break; + } + } + L->top--; + lua_unlock(L); + return 1; +} + + +LUA_API int lua_setiuservalue (lua_State *L, int idx, int n) { + TValue *o; + int res; + lua_lock(L); + api_checknelems(L, 1); + o = index2value(L, idx); + api_check(L, ttisfulluserdata(o), "full userdata expected"); + if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue))) + res = 0; /* 'n' not in [1, uvalue(o)->nuvalue] */ + else { + setobj(L, &uvalue(o)->uv[n - 1].uv, s2v(L->top - 1)); + luaC_barrierback(L, gcvalue(o), s2v(L->top - 1)); + res = 1; + } + L->top--; + lua_unlock(L); + return res; +} + + +/* +** 'load' and 'call' functions (run Lua code) +*/ + + +#define checkresults(L,na,nr) \ + api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na)), \ + "results from function overflow current stack size") + + +LUA_API void lua_callk (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k) { + StkId func; + lua_lock(L); + api_check(L, k == NULL || !isLua(L->ci), + "cannot use continuations inside hooks"); + api_checknelems(L, nargs+1); + api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); + checkresults(L, nargs, nresults); + func = L->top - (nargs+1); + if (k != NULL && yieldable(L)) { /* need to prepare continuation? */ + L->ci->u.c.k = k; /* save continuation */ + L->ci->u.c.ctx = ctx; /* save context */ + luaD_call(L, func, nresults); /* do the call */ + } + else /* no continuation or no yieldable */ + luaD_callnoyield(L, func, nresults); /* just do the call */ + adjustresults(L, nresults); + lua_unlock(L); +} + + + +/* +** Execute a protected call. +*/ +struct CallS { /* data to 'f_call' */ + StkId func; + int nresults; +}; + + +static void f_call (lua_State *L, void *ud) { + struct CallS *c = cast(struct CallS *, ud); + luaD_callnoyield(L, c->func, c->nresults); +} + + + +LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, + lua_KContext ctx, lua_KFunction k) { + struct CallS c; + int status; + ptrdiff_t func; + lua_lock(L); + api_check(L, k == NULL || !isLua(L->ci), + "cannot use continuations inside hooks"); + api_checknelems(L, nargs+1); + api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); + checkresults(L, nargs, nresults); + if (errfunc == 0) + func = 0; + else { + StkId o = index2stack(L, errfunc); + api_check(L, ttisfunction(s2v(o)), "error handler must be a function"); + func = savestack(L, o); + } + c.func = L->top - (nargs+1); /* function to be called */ + if (k == NULL || !yieldable(L)) { /* no continuation or no yieldable? */ + c.nresults = nresults; /* do a 'conventional' protected call */ + status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); + } + else { /* prepare continuation (call is already protected by 'resume') */ + CallInfo *ci = L->ci; + ci->u.c.k = k; /* save continuation */ + ci->u.c.ctx = ctx; /* save context */ + /* save information for error recovery */ + ci->u2.funcidx = cast_int(savestack(L, c.func)); + ci->u.c.old_errfunc = L->errfunc; + L->errfunc = func; + setoah(ci->callstatus, L->allowhook); /* save value of 'allowhook' */ + ci->callstatus |= CIST_YPCALL; /* function can do error recovery */ + luaD_call(L, c.func, nresults); /* do the call */ + ci->callstatus &= ~CIST_YPCALL; + L->errfunc = ci->u.c.old_errfunc; + status = LUA_OK; /* if it is here, there were no errors */ + } + adjustresults(L, nresults); + lua_unlock(L); + return status; +} + + +LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, + const char *chunkname, const char *mode) { + ZIO z; + int status; + lua_lock(L); + if (!chunkname) chunkname = "?"; + luaZ_init(L, &z, reader, data); + status = luaD_protectedparser(L, &z, chunkname, mode); + if (status == LUA_OK) { /* no errors? */ + LClosure *f = clLvalue(s2v(L->top - 1)); /* get newly created function */ + if (f->nupvalues >= 1) { /* does it have an upvalue? */ + /* get global table from registry */ + Table *reg = hvalue(&G(L)->l_registry); + const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS); + /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */ + setobj(L, f->upvals[0]->v, gt); + luaC_barrier(L, f->upvals[0], gt); + } + } + lua_unlock(L); + return status; +} + + +LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) { + int status; + TValue *o; + lua_lock(L); + api_checknelems(L, 1); + o = s2v(L->top - 1); + if (isLfunction(o)) + status = luaU_dump(L, getproto(o), writer, data, strip); + else + status = 1; + lua_unlock(L); + return status; +} + + +LUA_API int lua_status (lua_State *L) { + return L->status; +} + + +/* +** Garbage-collection function +*/ +LUA_API int lua_gc (lua_State *L, int what, ...) { + va_list argp; + int res = 0; + global_State *g; + lua_lock(L); + g = G(L); + va_start(argp, what); + switch (what) { + case LUA_GCSTOP: { + g->gcrunning = 0; + break; + } + case LUA_GCRESTART: { + luaE_setdebt(g, 0); + g->gcrunning = 1; + break; + } + case LUA_GCCOLLECT: { + luaC_fullgc(L, 0); + break; + } + case LUA_GCCOUNT: { + /* GC values are expressed in Kbytes: #bytes/2^10 */ + res = cast_int(gettotalbytes(g) >> 10); + break; + } + case LUA_GCCOUNTB: { + res = cast_int(gettotalbytes(g) & 0x3ff); + break; + } + case LUA_GCSTEP: { + int data = va_arg(argp, int); + l_mem debt = 1; /* =1 to signal that it did an actual step */ + lu_byte oldrunning = g->gcrunning; + g->gcrunning = 1; /* allow GC to run */ + if (data == 0) { + luaE_setdebt(g, 0); /* do a basic step */ + luaC_step(L); + } + else { /* add 'data' to total debt */ + debt = cast(l_mem, data) * 1024 + g->GCdebt; + luaE_setdebt(g, debt); + luaC_checkGC(L); + } + g->gcrunning = oldrunning; /* restore previous state */ + if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */ + res = 1; /* signal it */ + break; + } + case LUA_GCSETPAUSE: { + int data = va_arg(argp, int); + res = getgcparam(g->gcpause); + setgcparam(g->gcpause, data); + break; + } + case LUA_GCSETSTEPMUL: { + int data = va_arg(argp, int); + res = getgcparam(g->gcstepmul); + setgcparam(g->gcstepmul, data); + break; + } + case LUA_GCISRUNNING: { + res = g->gcrunning; + break; + } + case LUA_GCGEN: { + int minormul = va_arg(argp, int); + int majormul = va_arg(argp, int); + res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; + if (minormul != 0) + g->genminormul = minormul; + if (majormul != 0) + setgcparam(g->genmajormul, majormul); + luaC_changemode(L, KGC_GEN); + break; + } + case LUA_GCINC: { + int pause = va_arg(argp, int); + int stepmul = va_arg(argp, int); + int stepsize = va_arg(argp, int); + res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; + if (pause != 0) + setgcparam(g->gcpause, pause); + if (stepmul != 0) + setgcparam(g->gcstepmul, stepmul); + if (stepsize != 0) + g->gcstepsize = stepsize; + luaC_changemode(L, KGC_INC); + break; + } + default: res = -1; /* invalid option */ + } + va_end(argp); + lua_unlock(L); + return res; +} + + + +/* +** miscellaneous functions +*/ + + +LUA_API int lua_error (lua_State *L) { + TValue *errobj; + lua_lock(L); + errobj = s2v(L->top - 1); + api_checknelems(L, 1); + /* error object is the memory error message? */ + if (ttisshrstring(errobj) && eqshrstr(tsvalue(errobj), G(L)->memerrmsg)) + luaM_error(L); /* raise a memory error */ + else + luaG_errormsg(L); /* raise a regular error */ + /* code unreachable; will unlock when control actually leaves the kernel */ + return 0; /* to avoid warnings */ +} + + +LUA_API int lua_next (lua_State *L, int idx) { + Table *t; + int more; + lua_lock(L); + api_checknelems(L, 1); + t = gettable(L, idx); + more = luaH_next(L, t, L->top - 1); + if (more) { + api_incr_top(L); + } + else /* no more elements */ + L->top -= 1; /* remove key */ + lua_unlock(L); + return more; +} + + +LUA_API void lua_toclose (lua_State *L, int idx) { + int nresults; + StkId o; + lua_lock(L); + o = index2stack(L, idx); + nresults = L->ci->nresults; + api_check(L, L->openupval == NULL || uplevel(L->openupval) <= o, + "marked index below or equal new one"); + luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */ + if (!hastocloseCfunc(nresults)) /* function not marked yet? */ + L->ci->nresults = codeNresults(nresults); /* mark it */ + lua_assert(hastocloseCfunc(L->ci->nresults)); + lua_unlock(L); +} + + +LUA_API void lua_concat (lua_State *L, int n) { + lua_lock(L); + api_checknelems(L, n); + if (n > 0) + luaV_concat(L, n); + else { /* nothing to concatenate */ + setsvalue2s(L, L->top, luaS_newlstr(L, "", 0)); /* push empty string */ + api_incr_top(L); + } + luaC_checkGC(L); + lua_unlock(L); +} + + +LUA_API void lua_len (lua_State *L, int idx) { + TValue *t; + lua_lock(L); + t = index2value(L, idx); + luaV_objlen(L, L->top, t); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API lua_Alloc lua_getallocf (lua_State *L, void **ud) { + lua_Alloc f; + lua_lock(L); + if (ud) *ud = G(L)->ud; + f = G(L)->frealloc; + lua_unlock(L); + return f; +} + + +LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { + lua_lock(L); + G(L)->ud = ud; + G(L)->frealloc = f; + lua_unlock(L); +} + + +void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) { + lua_lock(L); + G(L)->ud_warn = ud; + G(L)->warnf = f; + lua_unlock(L); +} + + +void lua_warning (lua_State *L, const char *msg, int tocont) { + lua_lock(L); + luaE_warning(L, msg, tocont); + lua_unlock(L); +} + + + +LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) { + Udata *u; + lua_lock(L); + api_check(L, 0 <= nuvalue && nuvalue < USHRT_MAX, "invalid value"); + u = luaS_newudata(L, size, nuvalue); + setuvalue(L, s2v(L->top), u); + api_incr_top(L); + luaC_checkGC(L); + lua_unlock(L); + return getudatamem(u); +} + + + +static const char *aux_upvalue (TValue *fi, int n, TValue **val, + GCObject **owner) { + switch (ttypetag(fi)) { + case LUA_VCCL: { /* C closure */ + CClosure *f = clCvalue(fi); + if (!(cast_uint(n) - 1u < cast_uint(f->nupvalues))) + return NULL; /* 'n' not in [1, f->nupvalues] */ + *val = &f->upvalue[n-1]; + if (owner) *owner = obj2gco(f); + return ""; + } + case LUA_VLCL: { /* Lua closure */ + LClosure *f = clLvalue(fi); + TString *name; + Proto *p = f->p; + if (!(cast_uint(n) - 1u < cast_uint(p->sizeupvalues))) + return NULL; /* 'n' not in [1, p->sizeupvalues] */ + *val = f->upvals[n-1]->v; + if (owner) *owner = obj2gco(f->upvals[n - 1]); + name = p->upvalues[n-1].name; + return (name == NULL) ? "(no name)" : getstr(name); + } + default: return NULL; /* not a closure */ + } +} + + +LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { + const char *name; + TValue *val = NULL; /* to avoid warnings */ + lua_lock(L); + name = aux_upvalue(index2value(L, funcindex), n, &val, NULL); + if (name) { + setobj2s(L, L->top, val); + api_incr_top(L); + } + lua_unlock(L); + return name; +} + + +LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { + const char *name; + TValue *val = NULL; /* to avoid warnings */ + GCObject *owner = NULL; /* to avoid warnings */ + TValue *fi; + lua_lock(L); + fi = index2value(L, funcindex); + api_checknelems(L, 1); + name = aux_upvalue(fi, n, &val, &owner); + if (name) { + L->top--; + setobj(L, val, s2v(L->top)); + luaC_barrier(L, owner, val); + } + lua_unlock(L); + return name; +} + + +static UpVal **getupvalref (lua_State *L, int fidx, int n, LClosure **pf) { + static const UpVal *const nullup = NULL; + LClosure *f; + TValue *fi = index2value(L, fidx); + api_check(L, ttisLclosure(fi), "Lua function expected"); + f = clLvalue(fi); + if (pf) *pf = f; + if (1 <= n && n <= f->p->sizeupvalues) + return &f->upvals[n - 1]; /* get its upvalue pointer */ + else + return (UpVal**)&nullup; +} + + +LUA_API void *lua_upvalueid (lua_State *L, int fidx, int n) { + TValue *fi = index2value(L, fidx); + switch (ttypetag(fi)) { + case LUA_VLCL: { /* lua closure */ + return *getupvalref(L, fidx, n, NULL); + } + case LUA_VCCL: { /* C closure */ + CClosure *f = clCvalue(fi); + if (1 <= n && n <= f->nupvalues) + return &f->upvalue[n - 1]; + /* else */ + } /* FALLTHROUGH */ + case LUA_VLCF: + return NULL; /* light C functions have no upvalues */ + default: { + api_check(L, 0, "function expected"); + return NULL; + } + } +} + + +LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1, + int fidx2, int n2) { + LClosure *f1; + UpVal **up1 = getupvalref(L, fidx1, n1, &f1); + UpVal **up2 = getupvalref(L, fidx2, n2, NULL); + api_check(L, *up1 != NULL && *up2 != NULL, "invalid upvalue index"); + *up1 = *up2; + luaC_objbarrier(L, f1, *up1); +} + + diff --git a/Lua/lapi.h b/Lua/lapi.h new file mode 100644 index 00000000..41216b27 --- /dev/null +++ b/Lua/lapi.h @@ -0,0 +1,47 @@ +/* +** $Id: lapi.h $ +** Auxiliary functions from Lua API +** See Copyright Notice in lua.h +*/ + +#ifndef lapi_h +#define lapi_h + + +#include "llimits.h" +#include "lstate.h" + + +/* Increments 'L->top', checking for stack overflows */ +#define api_incr_top(L) {L->top++; api_check(L, L->top <= L->ci->top, \ + "stack overflow");} + + +/* +** If a call returns too many multiple returns, the callee may not have +** stack space to accommodate all results. In this case, this macro +** increases its stack space ('L->ci->top'). +*/ +#define adjustresults(L,nres) \ + { if ((nres) <= LUA_MULTRET && L->ci->top < L->top) L->ci->top = L->top; } + + +/* Ensure the stack has at least 'n' elements */ +#define api_checknelems(L,n) api_check(L, (n) < (L->top - L->ci->func), \ + "not enough elements in the stack") + + +/* +** To reduce the overhead of returning from C functions, the presence of +** to-be-closed variables in these functions is coded in the CallInfo's +** field 'nresults', in a way that functions with no to-be-closed variables +** with zero, one, or "all" wanted results have no overhead. Functions +** with other number of wanted results, as well as functions with +** variables to be closed, have an extra check. +*/ + +#define hastocloseCfunc(n) ((n) < LUA_MULTRET) + +#define codeNresults(n) (-(n) - 3) + +#endif diff --git a/Lua/lauxlib.c b/Lua/lauxlib.c new file mode 100644 index 00000000..73504389 --- /dev/null +++ b/Lua/lauxlib.c @@ -0,0 +1,1083 @@ +/* +** $Id: lauxlib.c $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + +#define lauxlib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include + + +/* +** This file uses only the official API of Lua. +** Any function declared here could be written as an application function. +*/ + +#include "lua.h" + +#include "lauxlib.h" + + +#if !defined(MAX_SIZET) +/* maximum value for size_t */ +#define MAX_SIZET ((size_t)(~(size_t)0)) +#endif + + +/* +** {====================================================== +** Traceback +** ======================================================= +*/ + + +#define LEVELS1 10 /* size of the first part of the stack */ +#define LEVELS2 11 /* size of the second part of the stack */ + + + +/* +** Search for 'objidx' in table at index -1. ('objidx' must be an +** absolute index.) Return 1 + string at top if it found a good name. +*/ +static int findfield (lua_State *L, int objidx, int level) { + if (level == 0 || !lua_istable(L, -1)) + return 0; /* not found */ + lua_pushnil(L); /* start 'next' loop */ + while (lua_next(L, -2)) { /* for each pair in table */ + if (lua_type(L, -2) == LUA_TSTRING) { /* ignore non-string keys */ + if (lua_rawequal(L, objidx, -1)) { /* found object? */ + lua_pop(L, 1); /* remove value (but keep name) */ + return 1; + } + else if (findfield(L, objidx, level - 1)) { /* try recursively */ + /* stack: lib_name, lib_table, field_name (top) */ + lua_pushliteral(L, "."); /* place '.' between the two names */ + lua_replace(L, -3); /* (in the slot occupied by table) */ + lua_concat(L, 3); /* lib_name.field_name */ + return 1; + } + } + lua_pop(L, 1); /* remove value */ + } + return 0; /* not found */ +} + + +/* +** Search for a name for a function in all loaded modules +*/ +static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { + int top = lua_gettop(L); + lua_getinfo(L, "f", ar); /* push function */ + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + if (findfield(L, top + 1, 2)) { + const char *name = lua_tostring(L, -1); + if (strncmp(name, LUA_GNAME ".", 3) == 0) { /* name start with '_G.'? */ + lua_pushstring(L, name + 3); /* push name without prefix */ + lua_remove(L, -2); /* remove original name */ + } + lua_copy(L, -1, top + 1); /* copy name to proper place */ + lua_settop(L, top + 1); /* remove table "loaded" and name copy */ + return 1; + } + else { + lua_settop(L, top); /* remove function and global table */ + return 0; + } +} + + +static void pushfuncname (lua_State *L, lua_Debug *ar) { + if (pushglobalfuncname(L, ar)) { /* try first a global name */ + lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); + lua_remove(L, -2); /* remove name */ + } + else if (*ar->namewhat != '\0') /* is there a name from code? */ + lua_pushfstring(L, "%s '%s'", ar->namewhat, ar->name); /* use it */ + else if (*ar->what == 'm') /* main? */ + lua_pushliteral(L, "main chunk"); + else if (*ar->what != 'C') /* for Lua functions, use */ + lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined); + else /* nothing left... */ + lua_pushliteral(L, "?"); +} + + +static int lastlevel (lua_State *L) { + lua_Debug ar; + int li = 1, le = 1; + /* find an upper bound */ + while (lua_getstack(L, le, &ar)) { li = le; le *= 2; } + /* do a binary search */ + while (li < le) { + int m = (li + le)/2; + if (lua_getstack(L, m, &ar)) li = m + 1; + else le = m; + } + return le - 1; +} + + +LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, + const char *msg, int level) { + luaL_Buffer b; + lua_Debug ar; + int last = lastlevel(L1); + int limit2show = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1; + luaL_buffinit(L, &b); + if (msg) { + luaL_addstring(&b, msg); + luaL_addchar(&b, '\n'); + } + luaL_addstring(&b, "stack traceback:"); + while (lua_getstack(L1, level++, &ar)) { + if (limit2show-- == 0) { /* too many levels? */ + int n = last - level - LEVELS2 + 1; /* number of levels to skip */ + lua_pushfstring(L, "\n\t...\t(skipping %d levels)", n); + luaL_addvalue(&b); /* add warning about skip */ + level += n; /* and skip to last levels */ + } + else { + lua_getinfo(L1, "Slnt", &ar); + if (ar.currentline <= 0) + lua_pushfstring(L, "\n\t%s: in ", ar.short_src); + else + lua_pushfstring(L, "\n\t%s:%d: in ", ar.short_src, ar.currentline); + luaL_addvalue(&b); + pushfuncname(L, &ar); + luaL_addvalue(&b); + if (ar.istailcall) + luaL_addstring(&b, "\n\t(...tail calls...)"); + } + } + luaL_pushresult(&b); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Error-report functions +** ======================================================= +*/ + +LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) { + lua_Debug ar; + if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ + return luaL_error(L, "bad argument #%d (%s)", arg, extramsg); + lua_getinfo(L, "n", &ar); + if (strcmp(ar.namewhat, "method") == 0) { + arg--; /* do not count 'self' */ + if (arg == 0) /* error is in the self argument itself? */ + return luaL_error(L, "calling '%s' on bad self (%s)", + ar.name, extramsg); + } + if (ar.name == NULL) + ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?"; + return luaL_error(L, "bad argument #%d to '%s' (%s)", + arg, ar.name, extramsg); +} + + +int luaL_typeerror (lua_State *L, int arg, const char *tname) { + const char *msg; + const char *typearg; /* name for the type of the actual argument */ + if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING) + typearg = lua_tostring(L, -1); /* use the given type name */ + else if (lua_type(L, arg) == LUA_TLIGHTUSERDATA) + typearg = "light userdata"; /* special name for messages */ + else + typearg = luaL_typename(L, arg); /* standard name */ + msg = lua_pushfstring(L, "%s expected, got %s", tname, typearg); + return luaL_argerror(L, arg, msg); +} + + +static void tag_error (lua_State *L, int arg, int tag) { + luaL_typeerror(L, arg, lua_typename(L, tag)); +} + + +/* +** The use of 'lua_pushfstring' ensures this function does not +** need reserved stack space when called. +*/ +LUALIB_API void luaL_where (lua_State *L, int level) { + lua_Debug ar; + if (lua_getstack(L, level, &ar)) { /* check function at level */ + lua_getinfo(L, "Sl", &ar); /* get info about it */ + if (ar.currentline > 0) { /* is there info? */ + lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); + return; + } + } + lua_pushfstring(L, ""); /* else, no information available... */ +} + + +/* +** Again, the use of 'lua_pushvfstring' ensures this function does +** not need reserved stack space when called. (At worst, it generates +** an error with "stack overflow" instead of the given message.) +*/ +LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { + va_list argp; + va_start(argp, fmt); + luaL_where(L, 1); + lua_pushvfstring(L, fmt, argp); + va_end(argp); + lua_concat(L, 2); + return lua_error(L); +} + + +LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { + int en = errno; /* calls to Lua API may change this value */ + if (stat) { + lua_pushboolean(L, 1); + return 1; + } + else { + luaL_pushfail(L); + if (fname) + lua_pushfstring(L, "%s: %s", fname, strerror(en)); + else + lua_pushstring(L, strerror(en)); + lua_pushinteger(L, en); + return 3; + } +} + + +#if !defined(l_inspectstat) /* { */ + +#if defined(LUA_USE_POSIX) + +#include + +/* +** use appropriate macros to interpret 'pclose' return status +*/ +#define l_inspectstat(stat,what) \ + if (WIFEXITED(stat)) { stat = WEXITSTATUS(stat); } \ + else if (WIFSIGNALED(stat)) { stat = WTERMSIG(stat); what = "signal"; } + +#else + +#define l_inspectstat(stat,what) /* no op */ + +#endif + +#endif /* } */ + + +LUALIB_API int luaL_execresult (lua_State *L, int stat) { + if (stat != 0 && errno != 0) /* error with an 'errno'? */ + return luaL_fileresult(L, 0, NULL); + else { + const char *what = "exit"; /* type of termination */ + l_inspectstat(stat, what); /* interpret result */ + if (*what == 'e' && stat == 0) /* successful termination? */ + lua_pushboolean(L, 1); + else + luaL_pushfail(L); + lua_pushstring(L, what); + lua_pushinteger(L, stat); + return 3; /* return true/fail,what,code */ + } +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Userdata's metatable manipulation +** ======================================================= +*/ + +LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { + if (luaL_getmetatable(L, tname) != LUA_TNIL) /* name already in use? */ + return 0; /* leave previous value on top, but return 0 */ + lua_pop(L, 1); + lua_createtable(L, 0, 2); /* create metatable */ + lua_pushstring(L, tname); + lua_setfield(L, -2, "__name"); /* metatable.__name = tname */ + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ + return 1; +} + + +LUALIB_API void luaL_setmetatable (lua_State *L, const char *tname) { + luaL_getmetatable(L, tname); + lua_setmetatable(L, -2); +} + + +LUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) { + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + luaL_getmetatable(L, tname); /* get correct metatable */ + if (!lua_rawequal(L, -1, -2)) /* not the same? */ + p = NULL; /* value is a userdata with wrong metatable */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + } + return NULL; /* value is not a userdata with a metatable */ +} + + +LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { + void *p = luaL_testudata(L, ud, tname); + luaL_argexpected(L, p != NULL, ud, tname); + return p; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Argument check functions +** ======================================================= +*/ + +LUALIB_API int luaL_checkoption (lua_State *L, int arg, const char *def, + const char *const lst[]) { + const char *name = (def) ? luaL_optstring(L, arg, def) : + luaL_checkstring(L, arg); + int i; + for (i=0; lst[i]; i++) + if (strcmp(lst[i], name) == 0) + return i; + return luaL_argerror(L, arg, + lua_pushfstring(L, "invalid option '%s'", name)); +} + + +/* +** Ensures the stack has at least 'space' extra slots, raising an error +** if it cannot fulfill the request. (The error handling needs a few +** extra slots to format the error message. In case of an error without +** this extra space, Lua will generate the same 'stack overflow' error, +** but without 'msg'.) +*/ +LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { + if (!lua_checkstack(L, space)) { + if (msg) + luaL_error(L, "stack overflow (%s)", msg); + else + luaL_error(L, "stack overflow"); + } +} + + +LUALIB_API void luaL_checktype (lua_State *L, int arg, int t) { + if (lua_type(L, arg) != t) + tag_error(L, arg, t); +} + + +LUALIB_API void luaL_checkany (lua_State *L, int arg) { + if (lua_type(L, arg) == LUA_TNONE) + luaL_argerror(L, arg, "value expected"); +} + + +LUALIB_API const char *luaL_checklstring (lua_State *L, int arg, size_t *len) { + const char *s = lua_tolstring(L, arg, len); + if (!s) tag_error(L, arg, LUA_TSTRING); + return s; +} + + +LUALIB_API const char *luaL_optlstring (lua_State *L, int arg, + const char *def, size_t *len) { + if (lua_isnoneornil(L, arg)) { + if (len) + *len = (def ? strlen(def) : 0); + return def; + } + else return luaL_checklstring(L, arg, len); +} + + +LUALIB_API lua_Number luaL_checknumber (lua_State *L, int arg) { + int isnum; + lua_Number d = lua_tonumberx(L, arg, &isnum); + if (!isnum) + tag_error(L, arg, LUA_TNUMBER); + return d; +} + + +LUALIB_API lua_Number luaL_optnumber (lua_State *L, int arg, lua_Number def) { + return luaL_opt(L, luaL_checknumber, arg, def); +} + + +static void interror (lua_State *L, int arg) { + if (lua_isnumber(L, arg)) + luaL_argerror(L, arg, "number has no integer representation"); + else + tag_error(L, arg, LUA_TNUMBER); +} + + +LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int arg) { + int isnum; + lua_Integer d = lua_tointegerx(L, arg, &isnum); + if (!isnum) { + interror(L, arg); + } + return d; +} + + +LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int arg, + lua_Integer def) { + return luaL_opt(L, luaL_checkinteger, arg, def); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +/* userdata to box arbitrary data */ +typedef struct UBox { + void *box; + size_t bsize; +} UBox; + + +static void *resizebox (lua_State *L, int idx, size_t newsize) { + void *ud; + lua_Alloc allocf = lua_getallocf(L, &ud); + UBox *box = (UBox *)lua_touserdata(L, idx); + void *temp = allocf(ud, box->box, box->bsize, newsize); + if (temp == NULL && newsize > 0) { /* allocation error? */ + lua_pushliteral(L, "not enough memory"); + lua_error(L); /* raise a memory error */ + } + box->box = temp; + box->bsize = newsize; + return temp; +} + + +static int boxgc (lua_State *L) { + resizebox(L, 1, 0); + return 0; +} + + +static const luaL_Reg boxmt[] = { /* box metamethods */ + {"__gc", boxgc}, + {"__close", boxgc}, + {NULL, NULL} +}; + + +static void newbox (lua_State *L) { + UBox *box = (UBox *)lua_newuserdatauv(L, sizeof(UBox), 0); + box->box = NULL; + box->bsize = 0; + if (luaL_newmetatable(L, "_UBOX*")) /* creating metatable? */ + luaL_setfuncs(L, boxmt, 0); /* set its metamethods */ + lua_setmetatable(L, -2); +} + + +/* +** check whether buffer is using a userdata on the stack as a temporary +** buffer +*/ +#define buffonstack(B) ((B)->b != (B)->init.b) + + +/* +** Compute new size for buffer 'B', enough to accommodate extra 'sz' +** bytes. +*/ +static size_t newbuffsize (luaL_Buffer *B, size_t sz) { + size_t newsize = B->size * 2; /* double buffer size */ + if (MAX_SIZET - sz < B->n) /* overflow in (B->n + sz)? */ + return luaL_error(B->L, "buffer too large"); + if (newsize < B->n + sz) /* double is not big enough? */ + newsize = B->n + sz; + return newsize; +} + + +/* +** Returns a pointer to a free area with at least 'sz' bytes in buffer +** 'B'. 'boxidx' is the relative position in the stack where the +** buffer's box is or should be. +*/ +static char *prepbuffsize (luaL_Buffer *B, size_t sz, int boxidx) { + if (B->size - B->n >= sz) /* enough space? */ + return B->b + B->n; + else { + lua_State *L = B->L; + char *newbuff; + size_t newsize = newbuffsize(B, sz); + /* create larger buffer */ + if (buffonstack(B)) /* buffer already has a box? */ + newbuff = (char *)resizebox(L, boxidx, newsize); /* resize it */ + else { /* no box yet */ + lua_pushnil(L); /* reserve slot for final result */ + newbox(L); /* create a new box */ + /* move box (and slot) to its intended position */ + lua_rotate(L, boxidx - 1, 2); + lua_toclose(L, boxidx); + newbuff = (char *)resizebox(L, boxidx, newsize); + memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */ + } + B->b = newbuff; + B->size = newsize; + return newbuff + B->n; + } +} + +/* +** returns a pointer to a free area with at least 'sz' bytes +*/ +LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { + return prepbuffsize(B, sz, -1); +} + + +LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { + if (l > 0) { /* avoid 'memcpy' when 's' can be NULL */ + char *b = prepbuffsize(B, l, -1); + memcpy(b, s, l * sizeof(char)); + luaL_addsize(B, l); + } +} + + +LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { + luaL_addlstring(B, s, strlen(s)); +} + + +LUALIB_API void luaL_pushresult (luaL_Buffer *B) { + lua_State *L = B->L; + lua_pushlstring(L, B->b, B->n); + if (buffonstack(B)) { + lua_copy(L, -1, -3); /* move string to reserved slot */ + lua_pop(L, 2); /* pop string and box (closing the box) */ + } +} + + +LUALIB_API void luaL_pushresultsize (luaL_Buffer *B, size_t sz) { + luaL_addsize(B, sz); + luaL_pushresult(B); +} + + +/* +** 'luaL_addvalue' is the only function in the Buffer system where the +** box (if existent) is not on the top of the stack. So, instead of +** calling 'luaL_addlstring', it replicates the code using -2 as the +** last argument to 'prepbuffsize', signaling that the box is (or will +** be) bellow the string being added to the buffer. (Box creation can +** trigger an emergency GC, so we should not remove the string from the +** stack before we have the space guaranteed.) +*/ +LUALIB_API void luaL_addvalue (luaL_Buffer *B) { + lua_State *L = B->L; + size_t len; + const char *s = lua_tolstring(L, -1, &len); + char *b = prepbuffsize(B, len, -2); + memcpy(b, s, len * sizeof(char)); + luaL_addsize(B, len); + lua_pop(L, 1); /* pop string */ +} + + +LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) { + B->L = L; + B->b = B->init.b; + B->n = 0; + B->size = LUAL_BUFFERSIZE; +} + + +LUALIB_API char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) { + luaL_buffinit(L, B); + return prepbuffsize(B, sz, -1); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Reference system +** ======================================================= +*/ + +/* index of free-list header */ +#define freelist 0 + + +LUALIB_API int luaL_ref (lua_State *L, int t) { + int ref; + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* remove from stack */ + return LUA_REFNIL; /* 'nil' has a unique fixed reference */ + } + t = lua_absindex(L, t); + lua_rawgeti(L, t, freelist); /* get first free element */ + ref = (int)lua_tointeger(L, -1); /* ref = t[freelist] */ + lua_pop(L, 1); /* remove it from stack */ + if (ref != 0) { /* any free element? */ + lua_rawgeti(L, t, ref); /* remove it from list */ + lua_rawseti(L, t, freelist); /* (t[freelist] = t[ref]) */ + } + else /* no free elements */ + ref = (int)lua_rawlen(L, t) + 1; /* get a new reference */ + lua_rawseti(L, t, ref); + return ref; +} + + +LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { + if (ref >= 0) { + t = lua_absindex(L, t); + lua_rawgeti(L, t, freelist); + lua_rawseti(L, t, ref); /* t[ref] = t[freelist] */ + lua_pushinteger(L, ref); + lua_rawseti(L, t, freelist); /* t[freelist] = ref */ + } +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Load functions +** ======================================================= +*/ + +typedef struct LoadF { + int n; /* number of pre-read characters */ + FILE *f; /* file being read */ + char buff[BUFSIZ]; /* area for reading file */ +} LoadF; + + +static const char *getF (lua_State *L, void *ud, size_t *size) { + LoadF *lf = (LoadF *)ud; + (void)L; /* not used */ + if (lf->n > 0) { /* are there pre-read characters to be read? */ + *size = lf->n; /* return them (chars already in buffer) */ + lf->n = 0; /* no more pre-read characters */ + } + else { /* read a block from file */ + /* 'fread' can return > 0 *and* set the EOF flag. If next call to + 'getF' called 'fread', it might still wait for user input. + The next check avoids this problem. */ + if (feof(lf->f)) return NULL; + *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); /* read block */ + } + return lf->buff; +} + + +static int errfile (lua_State *L, const char *what, int fnameindex) { + const char *serr = strerror(errno); + const char *filename = lua_tostring(L, fnameindex) + 1; + lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); + lua_remove(L, fnameindex); + return LUA_ERRFILE; +} + + +static int skipBOM (LoadF *lf) { + const char *p = "\xEF\xBB\xBF"; /* UTF-8 BOM mark */ + int c; + lf->n = 0; + do { + c = getc(lf->f); + if (c == EOF || c != *(const unsigned char *)p++) return c; + lf->buff[lf->n++] = c; /* to be read by the parser */ + } while (*p != '\0'); + lf->n = 0; /* prefix matched; discard it */ + return getc(lf->f); /* return next character */ +} + + +/* +** reads the first character of file 'f' and skips an optional BOM mark +** in its beginning plus its first line if it starts with '#'. Returns +** true if it skipped the first line. In any case, '*cp' has the +** first "valid" character of the file (after the optional BOM and +** a first-line comment). +*/ +static int skipcomment (LoadF *lf, int *cp) { + int c = *cp = skipBOM(lf); + if (c == '#') { /* first line is a comment (Unix exec. file)? */ + do { /* skip first line */ + c = getc(lf->f); + } while (c != EOF && c != '\n'); + *cp = getc(lf->f); /* skip end-of-line, if present */ + return 1; /* there was a comment */ + } + else return 0; /* no comment */ +} + + +LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, + const char *mode) { + LoadF lf; + int status, readstatus; + int c; + int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ + if (filename == NULL) { + lua_pushliteral(L, "=stdin"); + lf.f = stdin; + } + else { + lua_pushfstring(L, "@%s", filename); + lf.f = fopen(filename, "r"); + if (lf.f == NULL) return errfile(L, "open", fnameindex); + } + if (skipcomment(&lf, &c)) /* read initial portion */ + lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */ + if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */ + lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ + if (lf.f == NULL) return errfile(L, "reopen", fnameindex); + skipcomment(&lf, &c); /* re-read initial portion */ + } + if (c != EOF) + lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */ + status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode); + readstatus = ferror(lf.f); + if (filename) fclose(lf.f); /* close file (even in case of errors) */ + if (readstatus) { + lua_settop(L, fnameindex); /* ignore results from 'lua_load' */ + return errfile(L, "read", fnameindex); + } + lua_remove(L, fnameindex); + return status; +} + + +typedef struct LoadS { + const char *s; + size_t size; +} LoadS; + + +static const char *getS (lua_State *L, void *ud, size_t *size) { + LoadS *ls = (LoadS *)ud; + (void)L; /* not used */ + if (ls->size == 0) return NULL; + *size = ls->size; + ls->size = 0; + return ls->s; +} + + +LUALIB_API int luaL_loadbufferx (lua_State *L, const char *buff, size_t size, + const char *name, const char *mode) { + LoadS ls; + ls.s = buff; + ls.size = size; + return lua_load(L, getS, &ls, name, mode); +} + + +LUALIB_API int luaL_loadstring (lua_State *L, const char *s) { + return luaL_loadbuffer(L, s, strlen(s), s); +} + +/* }====================================================== */ + + + +LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) { + if (!lua_getmetatable(L, obj)) /* no metatable? */ + return LUA_TNIL; + else { + int tt; + lua_pushstring(L, event); + tt = lua_rawget(L, -2); + if (tt == LUA_TNIL) /* is metafield nil? */ + lua_pop(L, 2); /* remove metatable and metafield */ + else + lua_remove(L, -2); /* remove only metatable */ + return tt; /* return metafield type */ + } +} + + +LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { + obj = lua_absindex(L, obj); + if (luaL_getmetafield(L, obj, event) == LUA_TNIL) /* no metafield? */ + return 0; + lua_pushvalue(L, obj); + lua_call(L, 1, 1); + return 1; +} + + +LUALIB_API lua_Integer luaL_len (lua_State *L, int idx) { + lua_Integer l; + int isnum; + lua_len(L, idx); + l = lua_tointegerx(L, -1, &isnum); + if (!isnum) + luaL_error(L, "object length is not an integer"); + lua_pop(L, 1); /* remove object */ + return l; +} + + +LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { + if (luaL_callmeta(L, idx, "__tostring")) { /* metafield? */ + if (!lua_isstring(L, -1)) + luaL_error(L, "'__tostring' must return a string"); + } + else { + switch (lua_type(L, idx)) { + case LUA_TNUMBER: { + if (lua_isinteger(L, idx)) + lua_pushfstring(L, "%I", (LUAI_UACINT)lua_tointeger(L, idx)); + else + lua_pushfstring(L, "%f", (LUAI_UACNUMBER)lua_tonumber(L, idx)); + break; + } + case LUA_TSTRING: + lua_pushvalue(L, idx); + break; + case LUA_TBOOLEAN: + lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false")); + break; + case LUA_TNIL: + lua_pushliteral(L, "nil"); + break; + default: { + int tt = luaL_getmetafield(L, idx, "__name"); /* try name */ + const char *kind = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : + luaL_typename(L, idx); + lua_pushfstring(L, "%s: %p", kind, lua_topointer(L, idx)); + if (tt != LUA_TNIL) + lua_remove(L, -2); /* remove '__name' */ + break; + } + } + } + return lua_tolstring(L, -1, len); +} + + +/* +** set functions from list 'l' into table at top - 'nup'; each +** function gets the 'nup' elements at the top as upvalues. +** Returns with only the table at the stack. +*/ +LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { + luaL_checkstack(L, nup, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ + if (l->func == NULL) /* place holder? */ + lua_pushboolean(L, 0); + else { + int i; + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + } + lua_setfield(L, -(nup + 2), l->name); + } + lua_pop(L, nup); /* remove upvalues */ +} + + +/* +** ensure that stack[idx][fname] has a table and push that table +** into the stack +*/ +LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { + if (lua_getfield(L, idx, fname) == LUA_TTABLE) + return 1; /* table already there */ + else { + lua_pop(L, 1); /* remove previous result */ + idx = lua_absindex(L, idx); + lua_newtable(L); + lua_pushvalue(L, -1); /* copy to be left at top */ + lua_setfield(L, idx, fname); /* assign new table to field */ + return 0; /* false, because did not find table there */ + } +} + + +/* +** Stripped-down 'require': After checking "loaded" table, calls 'openf' +** to open a module, registers the result in 'package.loaded' table and, +** if 'glb' is true, also registers the result in the global table. +** Leaves resulting module on the top. +*/ +LUALIB_API void luaL_requiref (lua_State *L, const char *modname, + lua_CFunction openf, int glb) { + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_getfield(L, -1, modname); /* LOADED[modname] */ + if (!lua_toboolean(L, -1)) { /* package not already loaded? */ + lua_pop(L, 1); /* remove field */ + lua_pushcfunction(L, openf); + lua_pushstring(L, modname); /* argument to open function */ + lua_call(L, 1, 1); /* call 'openf' to open module */ + lua_pushvalue(L, -1); /* make copy of module (call result) */ + lua_setfield(L, -3, modname); /* LOADED[modname] = module */ + } + lua_remove(L, -2); /* remove LOADED table */ + if (glb) { + lua_pushvalue(L, -1); /* copy of module */ + lua_setglobal(L, modname); /* _G[modname] = module */ + } +} + + +LUALIB_API void luaL_addgsub (luaL_Buffer *b, const char *s, + const char *p, const char *r) { + const char *wild; + size_t l = strlen(p); + while ((wild = strstr(s, p)) != NULL) { + luaL_addlstring(b, s, wild - s); /* push prefix */ + luaL_addstring(b, r); /* push replacement in place of pattern */ + s = wild + l; /* continue after 'p' */ + } + luaL_addstring(b, s); /* push last suffix */ +} + + +LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, + const char *p, const char *r) { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addgsub(&b, s, p, r); + luaL_pushresult(&b); + return lua_tostring(L, -1); +} + + +static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { + (void)ud; (void)osize; /* not used */ + if (nsize == 0) { + free(ptr); + return NULL; + } + else + return realloc(ptr, nsize); +} + + +static int panic (lua_State *L) { + const char *msg = lua_tostring(L, -1); + if (msg == NULL) msg = "error object is not a string"; + lua_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", + msg); + return 0; /* return to Lua to abort */ +} + + +/* +** Warning functions: +** warnfoff: warning system is off +** warnfon: ready to start a new message +** warnfcont: previous message is to be continued +*/ +static void warnfoff (void *ud, const char *message, int tocont); +static void warnfon (void *ud, const char *message, int tocont); +static void warnfcont (void *ud, const char *message, int tocont); + + +/* +** Check whether message is a control message. If so, execute the +** control or ignore it if unknown. +*/ +static int checkcontrol (lua_State *L, const char *message, int tocont) { + if (tocont || *(message++) != '@') /* not a control message? */ + return 0; + else { + if (strcmp(message, "off") == 0) + lua_setwarnf(L, warnfoff, L); /* turn warnings off */ + else if (strcmp(message, "on") == 0) + lua_setwarnf(L, warnfon, L); /* turn warnings on */ + return 1; /* it was a control message */ + } +} + + +static void warnfoff (void *ud, const char *message, int tocont) { + checkcontrol((lua_State *)ud, message, tocont); +} + + +/* +** Writes the message and handle 'tocont', finishing the message +** if needed and setting the next warn function. +*/ +static void warnfcont (void *ud, const char *message, int tocont) { + lua_State *L = (lua_State *)ud; + lua_writestringerror("%s", message); /* write message */ + if (tocont) /* not the last part? */ + lua_setwarnf(L, warnfcont, L); /* to be continued */ + else { /* last part */ + lua_writestringerror("%s", "\n"); /* finish message with end-of-line */ + lua_setwarnf(L, warnfon, L); /* next call is a new message */ + } +} + + +static void warnfon (void *ud, const char *message, int tocont) { + if (checkcontrol((lua_State *)ud, message, tocont)) /* control message? */ + return; /* nothing else to be done */ + lua_writestringerror("%s", "Lua warning: "); /* start a new warning */ + warnfcont(ud, message, tocont); /* finish processing */ +} + + +LUALIB_API lua_State *luaL_newstate (void) { + lua_State *L = lua_newstate(l_alloc, NULL); + if (L) { + lua_atpanic(L, &panic); + lua_setwarnf(L, warnfoff, L); /* default is warnings off */ + } + return L; +} + + +LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) { + lua_Number v = lua_version(L); + if (sz != LUAL_NUMSIZES) /* check numeric types */ + luaL_error(L, "core and library have incompatible numeric types"); + else if (v != ver) + luaL_error(L, "version mismatch: app. needs %f, Lua core provides %f", + (LUAI_UACNUMBER)ver, (LUAI_UACNUMBER)v); +} + diff --git a/Lua/lauxlib.h b/Lua/lauxlib.h new file mode 100644 index 00000000..59fef6af --- /dev/null +++ b/Lua/lauxlib.h @@ -0,0 +1,276 @@ +/* +** $Id: lauxlib.h $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include +#include + +#include "lua.h" + + +/* global table */ +#define LUA_GNAME "_G" + + +typedef struct luaL_Buffer luaL_Buffer; + + +/* extra error code for 'luaL_loadfilex' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +/* key, in the registry, for table of loaded modules */ +#define LUA_LOADED_TABLE "_LOADED" + + +/* key, in the registry, for table of preloaded loaders */ +#define LUA_PRELOAD_TABLE "_PRELOAD" + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + +#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); +#define luaL_checkversion(L) \ + luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) + +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, + lua_Integer def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int arg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); +LUALIB_API int (luaL_execresult) (lua_State *L, int stat); + + +/* predefined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, + const char *mode); + +#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) + +LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, + const char *name, const char *mode); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + +LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); + +LUALIB_API void luaL_addgsub (luaL_Buffer *b, const char *s, + const char *p, const char *r); +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, + const char *p, const char *r); + +LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); + +LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname); + +LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, + const char *msg, int level); + +LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, + lua_CFunction openf, int glb); + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + + +#define luaL_newlibtable(L,l) \ + lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) + +#define luaL_newlib(L,l) \ + (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) + +#define luaL_argcheck(L, cond,arg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (arg), (extramsg)))) + +#define luaL_argexpected(L,cond,arg,tname) \ + ((void)((cond) || luaL_typeerror(L, (arg), (tname)))) + +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) + + +/* push the value used to represent failure/error */ +#define luaL_pushfail(L) lua_pushnil(L) + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +struct luaL_Buffer { + char *b; /* buffer address */ + size_t size; /* buffer size */ + size_t n; /* number of characters in buffer */ + lua_State *L; + union { + LUAI_MAXALIGN; /* ensure maximum alignment for buffer */ + char b[LUAL_BUFFERSIZE]; /* initial buffer */ + } init; +}; + + +#define luaL_bufflen(bf) ((bf)->n) +#define luaL_buffaddr(bf) ((bf)->b) + + +#define luaL_addchar(B,c) \ + ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \ + ((B)->b[(B)->n++] = (c))) + +#define luaL_addsize(B,s) ((B)->n += (s)) + +#define luaL_buffsub(B,s) ((B)->n -= (s)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz); +LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); + +#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE) + +/* }====================================================== */ + + + +/* +** {====================================================== +** File handles for IO library +** ======================================================= +*/ + +/* +** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and +** initial structure 'luaL_Stream' (it may contain other fields +** after that initial structure). +*/ + +#define LUA_FILEHANDLE "FILE*" + + +typedef struct luaL_Stream { + FILE *f; /* stream (NULL for incompletely created streams) */ + lua_CFunction closef; /* to close stream (NULL for closed streams) */ +} luaL_Stream; + +/* }====================================================== */ + +/* +** {================================================================== +** "Abstraction Layer" for basic report of messages and errors +** =================================================================== +*/ + +/* print a string */ +#if !defined(lua_writestring) +#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#endif + +/* print a newline and flush the output */ +#if !defined(lua_writeline) +#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) +#endif + +/* print an error message */ +#if !defined(lua_writestringerror) +#define lua_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) +#endif + +/* }================================================================== */ + + +/* +** {============================================================ +** Compatibility with deprecated conversions +** ============================================================= +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) +#define luaL_optunsigned(L,a,d) \ + ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) + +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) + +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#endif +/* }============================================================ */ + + + +#endif + + diff --git a/Lua/lbaselib.c b/Lua/lbaselib.c new file mode 100644 index 00000000..747fd45a --- /dev/null +++ b/Lua/lbaselib.c @@ -0,0 +1,527 @@ +/* +** $Id: lbaselib.c $ +** Basic library +** See Copyright Notice in lua.h +*/ + +#define lbaselib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +static int luaB_print (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + for (i = 1; i <= n; i++) { /* for each argument */ + size_t l; + const char *s = luaL_tolstring(L, i, &l); /* convert it to string */ + if (i > 1) /* not the first element? */ + lua_writestring("\t", 1); /* add a tab before it */ + lua_writestring(s, l); /* print it */ + lua_pop(L, 1); /* pop result */ + } + lua_writeline(); + return 0; +} + + +/* +** Creates a warning with all given arguments. +** Check first for errors; otherwise an error may interrupt +** the composition of a warning, leaving it unfinished. +*/ +static int luaB_warn (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + luaL_checkstring(L, 1); /* at least one argument */ + for (i = 2; i <= n; i++) + luaL_checkstring(L, i); /* make sure all arguments are strings */ + for (i = 1; i < n; i++) /* compose warning */ + lua_warning(L, lua_tostring(L, i), 1); + lua_warning(L, lua_tostring(L, n), 0); /* close warning */ + return 0; +} + + +#define SPACECHARS " \f\n\r\t\v" + +static const char *b_str2int (const char *s, int base, lua_Integer *pn) { + lua_Unsigned n = 0; + int neg = 0; + s += strspn(s, SPACECHARS); /* skip initial spaces */ + if (*s == '-') { s++; neg = 1; } /* handle sign */ + else if (*s == '+') s++; + if (!isalnum((unsigned char)*s)) /* no digit? */ + return NULL; + do { + int digit = (isdigit((unsigned char)*s)) ? *s - '0' + : (toupper((unsigned char)*s) - 'A') + 10; + if (digit >= base) return NULL; /* invalid numeral */ + n = n * base + digit; + s++; + } while (isalnum((unsigned char)*s)); + s += strspn(s, SPACECHARS); /* skip trailing spaces */ + *pn = (lua_Integer)((neg) ? (0u - n) : n); + return s; +} + + +static int luaB_tonumber (lua_State *L) { + if (lua_isnoneornil(L, 2)) { /* standard conversion? */ + if (lua_type(L, 1) == LUA_TNUMBER) { /* already a number? */ + lua_settop(L, 1); /* yes; return it */ + return 1; + } + else { + size_t l; + const char *s = lua_tolstring(L, 1, &l); + if (s != NULL && lua_stringtonumber(L, s) == l + 1) + return 1; /* successful conversion to number */ + /* else not a number */ + luaL_checkany(L, 1); /* (but there must be some parameter) */ + } + } + else { + size_t l; + const char *s; + lua_Integer n = 0; /* to avoid warnings */ + lua_Integer base = luaL_checkinteger(L, 2); + luaL_checktype(L, 1, LUA_TSTRING); /* no numbers as strings */ + s = lua_tolstring(L, 1, &l); + luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); + if (b_str2int(s, (int)base, &n) == s + l) { + lua_pushinteger(L, n); + return 1; + } /* else not a number */ + } /* else not a number */ + luaL_pushfail(L); /* not a number */ + return 1; +} + + +static int luaB_error (lua_State *L) { + int level = (int)luaL_optinteger(L, 2, 1); + lua_settop(L, 1); + if (lua_type(L, 1) == LUA_TSTRING && level > 0) { + luaL_where(L, level); /* add extra information */ + lua_pushvalue(L, 1); + lua_concat(L, 2); + } + return lua_error(L); +} + + +static int luaB_getmetatable (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_getmetatable(L, 1)) { + lua_pushnil(L); + return 1; /* no metatable */ + } + luaL_getmetafield(L, 1, "__metatable"); + return 1; /* returns either __metatable field (if present) or metatable */ +} + + +static int luaB_setmetatable (lua_State *L) { + int t = lua_type(L, 2); + luaL_checktype(L, 1, LUA_TTABLE); + luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table"); + if (luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL) + return luaL_error(L, "cannot change a protected metatable"); + lua_settop(L, 2); + lua_setmetatable(L, 1); + return 1; +} + + +static int luaB_rawequal (lua_State *L) { + luaL_checkany(L, 1); + luaL_checkany(L, 2); + lua_pushboolean(L, lua_rawequal(L, 1, 2)); + return 1; +} + + +static int luaB_rawlen (lua_State *L) { + int t = lua_type(L, 1); + luaL_argexpected(L, t == LUA_TTABLE || t == LUA_TSTRING, 1, + "table or string"); + lua_pushinteger(L, lua_rawlen(L, 1)); + return 1; +} + + +static int luaB_rawget (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checkany(L, 2); + lua_settop(L, 2); + lua_rawget(L, 1); + return 1; +} + +static int luaB_rawset (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checkany(L, 2); + luaL_checkany(L, 3); + lua_settop(L, 3); + lua_rawset(L, 1); + return 1; +} + + +static int pushmode (lua_State *L, int oldmode) { + lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental" : "generational"); + return 1; +} + + +static int luaB_collectgarbage (lua_State *L) { + static const char *const opts[] = {"stop", "restart", "collect", + "count", "step", "setpause", "setstepmul", + "isrunning", "generational", "incremental", NULL}; + static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, + LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL, + LUA_GCISRUNNING, LUA_GCGEN, LUA_GCINC}; + int o = optsnum[luaL_checkoption(L, 1, "collect", opts)]; + switch (o) { + case LUA_GCCOUNT: { + int k = lua_gc(L, o); + int b = lua_gc(L, LUA_GCCOUNTB); + lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024)); + return 1; + } + case LUA_GCSTEP: { + int step = (int)luaL_optinteger(L, 2, 0); + int res = lua_gc(L, o, step); + lua_pushboolean(L, res); + return 1; + } + case LUA_GCSETPAUSE: + case LUA_GCSETSTEPMUL: { + int p = (int)luaL_optinteger(L, 2, 0); + int previous = lua_gc(L, o, p); + lua_pushinteger(L, previous); + return 1; + } + case LUA_GCISRUNNING: { + int res = lua_gc(L, o); + lua_pushboolean(L, res); + return 1; + } + case LUA_GCGEN: { + int minormul = (int)luaL_optinteger(L, 2, 0); + int majormul = (int)luaL_optinteger(L, 3, 0); + return pushmode(L, lua_gc(L, o, minormul, majormul)); + } + case LUA_GCINC: { + int pause = (int)luaL_optinteger(L, 2, 0); + int stepmul = (int)luaL_optinteger(L, 3, 0); + int stepsize = (int)luaL_optinteger(L, 4, 0); + return pushmode(L, lua_gc(L, o, pause, stepmul, stepsize)); + } + default: { + int res = lua_gc(L, o); + lua_pushinteger(L, res); + return 1; + } + } +} + + +static int luaB_type (lua_State *L) { + int t = lua_type(L, 1); + luaL_argcheck(L, t != LUA_TNONE, 1, "value expected"); + lua_pushstring(L, lua_typename(L, t)); + return 1; +} + + +static int luaB_next (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, 1)) + return 2; + else { + lua_pushnil(L); + return 1; + } +} + + +static int luaB_pairs (lua_State *L) { + luaL_checkany(L, 1); + if (luaL_getmetafield(L, 1, "__pairs") == LUA_TNIL) { /* no metamethod? */ + lua_pushcfunction(L, luaB_next); /* will return generator, */ + lua_pushvalue(L, 1); /* state, */ + lua_pushnil(L); /* and initial value */ + } + else { + lua_pushvalue(L, 1); /* argument 'self' to metamethod */ + lua_call(L, 1, 3); /* get 3 values from metamethod */ + } + return 3; +} + + +/* +** Traversal function for 'ipairs' +*/ +static int ipairsaux (lua_State *L) { + lua_Integer i = luaL_checkinteger(L, 2) + 1; + lua_pushinteger(L, i); + return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2; +} + + +/* +** 'ipairs' function. Returns 'ipairsaux', given "table", 0. +** (The given "table" may not be a table.) +*/ +static int luaB_ipairs (lua_State *L) { + luaL_checkany(L, 1); + lua_pushcfunction(L, ipairsaux); /* iteration function */ + lua_pushvalue(L, 1); /* state */ + lua_pushinteger(L, 0); /* initial value */ + return 3; +} + + +static int load_aux (lua_State *L, int status, int envidx) { + if (status == LUA_OK) { + if (envidx != 0) { /* 'env' parameter? */ + lua_pushvalue(L, envidx); /* environment for loaded function */ + if (!lua_setupvalue(L, -2, 1)) /* set it as 1st upvalue */ + lua_pop(L, 1); /* remove 'env' if not used by previous call */ + } + return 1; + } + else { /* error (message is on top of the stack) */ + luaL_pushfail(L); + lua_insert(L, -2); /* put before error message */ + return 2; /* return fail plus error message */ + } +} + + +static int luaB_loadfile (lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + const char *mode = luaL_optstring(L, 2, NULL); + int env = (!lua_isnone(L, 3) ? 3 : 0); /* 'env' index or 0 if no 'env' */ + int status = luaL_loadfilex(L, fname, mode); + return load_aux(L, status, env); +} + + +/* +** {====================================================== +** Generic Read function +** ======================================================= +*/ + + +/* +** reserved slot, above all arguments, to hold a copy of the returned +** string to avoid it being collected while parsed. 'load' has four +** optional arguments (chunk, source name, mode, and environment). +*/ +#define RESERVEDSLOT 5 + + +/* +** Reader for generic 'load' function: 'lua_load' uses the +** stack for internal stuff, so the reader cannot change the +** stack top. Instead, it keeps its resulting string in a +** reserved slot inside the stack. +*/ +static const char *generic_reader (lua_State *L, void *ud, size_t *size) { + (void)(ud); /* not used */ + luaL_checkstack(L, 2, "too many nested functions"); + lua_pushvalue(L, 1); /* get function */ + lua_call(L, 0, 1); /* call it */ + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* pop result */ + *size = 0; + return NULL; + } + else if (!lua_isstring(L, -1)) + luaL_error(L, "reader function must return a string"); + lua_replace(L, RESERVEDSLOT); /* save string in reserved slot */ + return lua_tolstring(L, RESERVEDSLOT, size); +} + + +static int luaB_load (lua_State *L) { + int status; + size_t l; + const char *s = lua_tolstring(L, 1, &l); + const char *mode = luaL_optstring(L, 3, "bt"); + int env = (!lua_isnone(L, 4) ? 4 : 0); /* 'env' index or 0 if no 'env' */ + if (s != NULL) { /* loading a string? */ + const char *chunkname = luaL_optstring(L, 2, s); + status = luaL_loadbufferx(L, s, l, chunkname, mode); + } + else { /* loading from a reader function */ + const char *chunkname = luaL_optstring(L, 2, "=(load)"); + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, RESERVEDSLOT); /* create reserved slot */ + status = lua_load(L, generic_reader, NULL, chunkname, mode); + } + return load_aux(L, status, env); +} + +/* }====================================================== */ + + +static int dofilecont (lua_State *L, int d1, lua_KContext d2) { + (void)d1; (void)d2; /* only to match 'lua_Kfunction' prototype */ + return lua_gettop(L) - 1; +} + + +static int luaB_dofile (lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + lua_settop(L, 1); + if (luaL_loadfile(L, fname) != LUA_OK) + return lua_error(L); + lua_callk(L, 0, LUA_MULTRET, 0, dofilecont); + return dofilecont(L, 0, 0); +} + + +static int luaB_assert (lua_State *L) { + if (lua_toboolean(L, 1)) /* condition is true? */ + return lua_gettop(L); /* return all arguments */ + else { /* error */ + luaL_checkany(L, 1); /* there must be a condition */ + lua_remove(L, 1); /* remove it */ + lua_pushliteral(L, "assertion failed!"); /* default message */ + lua_settop(L, 1); /* leave only message (default if no other one) */ + return luaB_error(L); /* call 'error' */ + } +} + + +static int luaB_select (lua_State *L) { + int n = lua_gettop(L); + if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') { + lua_pushinteger(L, n-1); + return 1; + } + else { + lua_Integer i = luaL_checkinteger(L, 1); + if (i < 0) i = n + i; + else if (i > n) i = n; + luaL_argcheck(L, 1 <= i, 1, "index out of range"); + return n - (int)i; + } +} + + +/* +** Continuation function for 'pcall' and 'xpcall'. Both functions +** already pushed a 'true' before doing the call, so in case of success +** 'finishpcall' only has to return everything in the stack minus +** 'extra' values (where 'extra' is exactly the number of items to be +** ignored). +*/ +static int finishpcall (lua_State *L, int status, lua_KContext extra) { + if (status != LUA_OK && status != LUA_YIELD) { /* error? */ + lua_pushboolean(L, 0); /* first result (false) */ + lua_pushvalue(L, -2); /* error message */ + return 2; /* return false, msg */ + } + else + return lua_gettop(L) - (int)extra; /* return all results */ +} + + +static int luaB_pcall (lua_State *L) { + int status; + luaL_checkany(L, 1); + lua_pushboolean(L, 1); /* first result if no errors */ + lua_insert(L, 1); /* put it in place */ + status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall); + return finishpcall(L, status, 0); +} + + +/* +** Do a protected call with error handling. After 'lua_rotate', the +** stack will have ; so, the function passes +** 2 to 'finishpcall' to skip the 2 first values when returning results. +*/ +static int luaB_xpcall (lua_State *L) { + int status; + int n = lua_gettop(L); + luaL_checktype(L, 2, LUA_TFUNCTION); /* check error function */ + lua_pushboolean(L, 1); /* first result */ + lua_pushvalue(L, 1); /* function */ + lua_rotate(L, 3, 2); /* move them below function's arguments */ + status = lua_pcallk(L, n - 2, LUA_MULTRET, 2, 2, finishpcall); + return finishpcall(L, status, 2); +} + + +static int luaB_tostring (lua_State *L) { + luaL_checkany(L, 1); + luaL_tolstring(L, 1, NULL); + return 1; +} + + +static const luaL_Reg base_funcs[] = { + {"assert", luaB_assert}, + {"collectgarbage", luaB_collectgarbage}, + {"dofile", luaB_dofile}, + {"error", luaB_error}, + {"getmetatable", luaB_getmetatable}, + {"ipairs", luaB_ipairs}, + {"loadfile", luaB_loadfile}, + {"load", luaB_load}, + {"next", luaB_next}, + {"pairs", luaB_pairs}, + {"pcall", luaB_pcall}, + {"print", luaB_print}, + {"warn", luaB_warn}, + {"rawequal", luaB_rawequal}, + {"rawlen", luaB_rawlen}, + {"rawget", luaB_rawget}, + {"rawset", luaB_rawset}, + {"select", luaB_select}, + {"setmetatable", luaB_setmetatable}, + {"tonumber", luaB_tonumber}, + {"tostring", luaB_tostring}, + {"type", luaB_type}, + {"xpcall", luaB_xpcall}, + /* placeholders */ + {LUA_GNAME, NULL}, + {"_VERSION", NULL}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_base (lua_State *L) { + /* open lib into global table */ + lua_pushglobaltable(L); + luaL_setfuncs(L, base_funcs, 0); + /* set global _G */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, LUA_GNAME); + /* set global _VERSION */ + lua_pushliteral(L, LUA_VERSION); + lua_setfield(L, -2, "_VERSION"); + return 1; +} + diff --git a/Lua/lcode.c b/Lua/lcode.c new file mode 100644 index 00000000..14d41f1a --- /dev/null +++ b/Lua/lcode.c @@ -0,0 +1,1818 @@ +/* +** $Id: lcode.c $ +** Code generator for Lua +** See Copyright Notice in lua.h +*/ + +#define lcode_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "llex.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstring.h" +#include "ltable.h" +#include "lvm.h" + + +/* Maximum number of registers in a Lua function (must fit in 8 bits) */ +#define MAXREGS 255 + + +#define hasjumps(e) ((e)->t != (e)->f) + + +static int codesJ (FuncState *fs, OpCode o, int sj, int k); + + + +/* semantic error */ +l_noret luaK_semerror (LexState *ls, const char *msg) { + ls->t.token = 0; /* remove "near " from final message */ + luaX_syntaxerror(ls, msg); +} + + +/* +** If expression is a numeric constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +static int tonumeral (const expdesc *e, TValue *v) { + if (hasjumps(e)) + return 0; /* not a numeral */ + switch (e->k) { + case VKINT: + if (v) setivalue(v, e->u.ival); + return 1; + case VKFLT: + if (v) setfltvalue(v, e->u.nval); + return 1; + default: return 0; + } +} + + +/* +** Get the constant value from a constant expression +*/ +static TValue *const2val (FuncState *fs, const expdesc *e) { + lua_assert(e->k == VCONST); + return &fs->ls->dyd->actvar.arr[e->u.info].k; +} + + +/* +** If expression is a constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v) { + if (hasjumps(e)) + return 0; /* not a constant */ + switch (e->k) { + case VFALSE: + setbfvalue(v); + return 1; + case VTRUE: + setbtvalue(v); + return 1; + case VNIL: + setnilvalue(v); + return 1; + case VKSTR: { + setsvalue(fs->ls->L, v, e->u.strval); + return 1; + } + case VCONST: { + setobj(fs->ls->L, v, const2val(fs, e)); + return 1; + } + default: return tonumeral(e, v); + } +} + + +/* +** Return the previous instruction of the current code. If there +** may be a jump target between the current instruction and the +** previous one, return an invalid instruction (to avoid wrong +** optimizations). +*/ +static Instruction *previousinstruction (FuncState *fs) { + static const Instruction invalidinstruction = ~(Instruction)0; + if (fs->pc > fs->lasttarget) + return &fs->f->code[fs->pc - 1]; /* previous instruction */ + else + return cast(Instruction*, &invalidinstruction); +} + + +/* +** Create a OP_LOADNIL instruction, but try to optimize: if the previous +** instruction is also OP_LOADNIL and ranges are compatible, adjust +** range of previous instruction instead of emitting a new one. (For +** instance, 'local a; local b' will generate a single opcode.) +*/ +void luaK_nil (FuncState *fs, int from, int n) { + int l = from + n - 1; /* last register to set nil */ + Instruction *previous = previousinstruction(fs); + if (GET_OPCODE(*previous) == OP_LOADNIL) { /* previous is LOADNIL? */ + int pfrom = GETARG_A(*previous); /* get previous range */ + int pl = pfrom + GETARG_B(*previous); + if ((pfrom <= from && from <= pl + 1) || + (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ + if (pfrom < from) from = pfrom; /* from = min(from, pfrom) */ + if (pl > l) l = pl; /* l = max(l, pl) */ + SETARG_A(*previous, from); + SETARG_B(*previous, l - from); + return; + } /* else go through */ + } + luaK_codeABC(fs, OP_LOADNIL, from, n - 1, 0); /* else no optimization */ +} + + +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +static int getjump (FuncState *fs, int pc) { + int offset = GETARG_sJ(fs->f->code[pc]); + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ +} + + +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ +static void fixjump (FuncState *fs, int pc, int dest) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest - (pc + 1); + lua_assert(dest != NO_JUMP); + if (!(-OFFSET_sJ <= offset && offset <= MAXARG_sJ - OFFSET_sJ)) + luaX_syntaxerror(fs->ls, "control structure too long"); + lua_assert(GET_OPCODE(*jmp) == OP_JMP); + SETARG_sJ(*jmp, offset); +} + + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +void luaK_concat (FuncState *fs, int *l1, int l2) { + if (l2 == NO_JUMP) return; /* nothing to concatenate? */ + else if (*l1 == NO_JUMP) /* no original list? */ + *l1 = l2; /* 'l1' points to 'l2' */ + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); /* last element links to 'l2' */ + } +} + + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). +*/ +int luaK_jump (FuncState *fs) { + return codesJ(fs, OP_JMP, NO_JUMP, 0); +} + + +/* +** Code a 'return' instruction +*/ +void luaK_ret (FuncState *fs, int first, int nret) { + OpCode op; + switch (nret) { + case 0: op = OP_RETURN0; break; + case 1: op = OP_RETURN1; break; + default: op = OP_RETURN; break; + } + luaK_codeABC(fs, op, first, nret + 1, 0); +} + + +/* +** Code a "conditional jump", that is, a test or comparison opcode +** followed by a jump. Return jump position. +*/ +static int condjump (FuncState *fs, OpCode op, int A, int B, int C, int k) { + luaK_codeABCk(fs, op, A, B, C, k); + return luaK_jump(fs); +} + + +/* +** returns current 'pc' and marks it as a jump target (to avoid wrong +** optimizations with consecutive instructions not in the same basic block). +*/ +int luaK_getlabel (FuncState *fs) { + fs->lasttarget = fs->pc; + return fs->pc; +} + + +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ +static Instruction *getjumpcontrol (FuncState *fs, int pc) { + Instruction *pi = &fs->f->code[pc]; + if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) + return pi-1; + else + return pi; +} + + +/* +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) +*/ +static int patchtestreg (FuncState *fs, int node, int reg) { + Instruction *i = getjumpcontrol(fs, node); + if (GET_OPCODE(*i) != OP_TESTSET) + return 0; /* cannot patch other instructions */ + if (reg != NO_REG && reg != GETARG_B(*i)) + SETARG_A(*i, reg); + else { + /* no register to put value or register already has the value; + change instruction to simple test */ + *i = CREATE_ABCk(OP_TEST, GETARG_B(*i), 0, 0, GETARG_k(*i)); + } + return 1; +} + + +/* +** Traverse a list of tests ensuring no one produces a value +*/ +static void removevalues (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) + patchtestreg(fs, list, NO_REG); +} + + +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ +static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, + int dtarget) { + while (list != NO_JUMP) { + int next = getjump(fs, list); + if (patchtestreg(fs, list, reg)) + fixjump(fs, list, vtarget); + else + fixjump(fs, list, dtarget); /* jump to default target */ + list = next; + } +} + + +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ +void luaK_patchlist (FuncState *fs, int list, int target) { + lua_assert(target <= fs->pc); + patchlistaux(fs, list, target, NO_REG, target); +} + + +void luaK_patchtohere (FuncState *fs, int list) { + int hr = luaK_getlabel(fs); /* mark "here" as a jump target */ + luaK_patchlist(fs, list, hr); +} + + +/* +** MAXimum number of successive Instructions WiTHout ABSolute line +** information. +*/ +#if !defined(MAXIWTHABS) +#define MAXIWTHABS 120 +#endif + + +/* limit for difference between lines in relative line info. */ +#define LIMLINEDIFF 0x80 + + +/* +** Save line info for a new instruction. If difference from last line +** does not fit in a byte, of after that many instructions, save a new +** absolute line info; (in that case, the special value 'ABSLINEINFO' +** in 'lineinfo' signals the existence of this absolute information.) +** Otherwise, store the difference from last line in 'lineinfo'. +*/ +static void savelineinfo (FuncState *fs, Proto *f, int line) { + int linedif = line - fs->previousline; + int pc = fs->pc - 1; /* last instruction coded */ + if (abs(linedif) >= LIMLINEDIFF || fs->iwthabs++ > MAXIWTHABS) { + luaM_growvector(fs->ls->L, f->abslineinfo, fs->nabslineinfo, + f->sizeabslineinfo, AbsLineInfo, MAX_INT, "lines"); + f->abslineinfo[fs->nabslineinfo].pc = pc; + f->abslineinfo[fs->nabslineinfo++].line = line; + linedif = ABSLINEINFO; /* signal that there is absolute information */ + fs->iwthabs = 0; /* restart counter */ + } + luaM_growvector(fs->ls->L, f->lineinfo, pc, f->sizelineinfo, ls_byte, + MAX_INT, "opcodes"); + f->lineinfo[pc] = linedif; + fs->previousline = line; /* last line saved */ +} + + +/* +** Remove line information from the last instruction. +** If line information for that instruction is absolute, set 'iwthabs' +** above its max to force the new (replacing) instruction to have +** absolute line info, too. +*/ +static void removelastlineinfo (FuncState *fs) { + Proto *f = fs->f; + int pc = fs->pc - 1; /* last instruction coded */ + if (f->lineinfo[pc] != ABSLINEINFO) { /* relative line info? */ + fs->previousline -= f->lineinfo[pc]; /* correct last line saved */ + fs->iwthabs--; /* undo previous increment */ + } + else { /* absolute line information */ + lua_assert(f->abslineinfo[fs->nabslineinfo - 1].pc == pc); + fs->nabslineinfo--; /* remove it */ + fs->iwthabs = MAXIWTHABS + 1; /* force next line info to be absolute */ + } +} + + +/* +** Remove the last instruction created, correcting line information +** accordingly. +*/ +static void removelastinstruction (FuncState *fs) { + removelastlineinfo(fs); + fs->pc--; +} + + +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ +int luaK_code (FuncState *fs, Instruction i) { + Proto *f = fs->f; + /* put new instruction in code array */ + luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction, + MAX_INT, "opcodes"); + f->code[fs->pc++] = i; + savelineinfo(fs, f, fs->ls->lastline); + return fs->pc - 1; /* index of new instruction */ +} + + +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ +int luaK_codeABCk (FuncState *fs, OpCode o, int a, int b, int c, int k) { + lua_assert(getOpMode(o) == iABC); + lua_assert(a <= MAXARG_A && b <= MAXARG_B && + c <= MAXARG_C && (k & ~1) == 0); + return luaK_code(fs, CREATE_ABCk(o, a, b, c, k)); +} + + +/* +** Format and emit an 'iABx' instruction. +*/ +int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { + lua_assert(getOpMode(o) == iABx); + lua_assert(a <= MAXARG_A && bc <= MAXARG_Bx); + return luaK_code(fs, CREATE_ABx(o, a, bc)); +} + + +/* +** Format and emit an 'iAsBx' instruction. +*/ +int luaK_codeAsBx (FuncState *fs, OpCode o, int a, int bc) { + unsigned int b = bc + OFFSET_sBx; + lua_assert(getOpMode(o) == iAsBx); + lua_assert(a <= MAXARG_A && b <= MAXARG_Bx); + return luaK_code(fs, CREATE_ABx(o, a, b)); +} + + +/* +** Format and emit an 'isJ' instruction. +*/ +static int codesJ (FuncState *fs, OpCode o, int sj, int k) { + unsigned int j = sj + OFFSET_sJ; + lua_assert(getOpMode(o) == isJ); + lua_assert(j <= MAXARG_sJ && (k & ~1) == 0); + return luaK_code(fs, CREATE_sJ(o, j, k)); +} + + +/* +** Emit an "extra argument" instruction (format 'iAx') +*/ +static int codeextraarg (FuncState *fs, int a) { + lua_assert(a <= MAXARG_Ax); + return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a)); +} + + +/* +** Emit a "load constant" instruction, using either 'OP_LOADK' +** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX' +** instruction with "extra argument". +*/ +static int luaK_codek (FuncState *fs, int reg, int k) { + if (k <= MAXARG_Bx) + return luaK_codeABx(fs, OP_LOADK, reg, k); + else { + int p = luaK_codeABx(fs, OP_LOADKX, reg, 0); + codeextraarg(fs, k); + return p; + } +} + + +/* +** Check register-stack level, keeping track of its maximum size +** in field 'maxstacksize' +*/ +void luaK_checkstack (FuncState *fs, int n) { + int newstack = fs->freereg + n; + if (newstack > fs->f->maxstacksize) { + if (newstack >= MAXREGS) + luaX_syntaxerror(fs->ls, + "function or expression needs too many registers"); + fs->f->maxstacksize = cast_byte(newstack); + } +} + + +/* +** Reserve 'n' registers in register stack +*/ +void luaK_reserveregs (FuncState *fs, int n) { + luaK_checkstack(fs, n); + fs->freereg += n; +} + + +/* +** Free register 'reg', if it is neither a constant index nor +** a local variable. +) +*/ +static void freereg (FuncState *fs, int reg) { + if (reg >= luaY_nvarstack(fs)) { + fs->freereg--; + lua_assert(reg == fs->freereg); + } +} + + +/* +** Free two registers in proper order +*/ +static void freeregs (FuncState *fs, int r1, int r2) { + if (r1 > r2) { + freereg(fs, r1); + freereg(fs, r2); + } + else { + freereg(fs, r2); + freereg(fs, r1); + } +} + + +/* +** Free register used by expression 'e' (if any) +*/ +static void freeexp (FuncState *fs, expdesc *e) { + if (e->k == VNONRELOC) + freereg(fs, e->u.info); +} + + +/* +** Free registers used by expressions 'e1' and 'e2' (if any) in proper +** order. +*/ +static void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) { + int r1 = (e1->k == VNONRELOC) ? e1->u.info : -1; + int r2 = (e2->k == VNONRELOC) ? e2->u.info : -1; + freeregs(fs, r1, r2); +} + + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). +** Use scanner's table to cache position of constants in constant list +** and try to reuse constants. Because some values should not be used +** as keys (nil cannot be a key, integer keys can collapse with float +** keys), the caller must provide a useful 'key' for indexing the cache. +*/ +static int addk (FuncState *fs, TValue *key, TValue *v) { + lua_State *L = fs->ls->L; + Proto *f = fs->f; + TValue *idx = luaH_set(L, fs->ls->h, key); /* index scanner table */ + int k, oldsize; + if (ttisinteger(idx)) { /* is there an index there? */ + k = cast_int(ivalue(idx)); + /* correct value? (warning: must distinguish floats from integers!) */ + if (k < fs->nk && ttypetag(&f->k[k]) == ttypetag(v) && + luaV_rawequalobj(&f->k[k], v)) + return k; /* reuse index */ + } + /* constant not found; create a new entry */ + oldsize = f->sizek; + k = fs->nk; + /* numerical value does not need GC barrier; + table has no metatable, so it does not need to invalidate cache */ + setivalue(idx, k); + luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); + while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); + setobj(L, &f->k[k], v); + fs->nk++; + luaC_barrier(L, f, v); + return k; +} + + +/* +** Add a string to list of constants and return its index. +*/ +static int stringK (FuncState *fs, TString *s) { + TValue o; + setsvalue(fs->ls->L, &o, s); + return addk(fs, &o, &o); /* use string itself as key */ +} + + +/* +** Add an integer to list of constants and return its index. +** Integers use userdata as keys to avoid collision with floats with +** same value; conversion to 'void*' is used only for hashing, so there +** are no "precision" problems. +*/ +static int luaK_intK (FuncState *fs, lua_Integer n) { + TValue k, o; + setpvalue(&k, cast_voidp(cast_sizet(n))); + setivalue(&o, n); + return addk(fs, &k, &o); +} + +/* +** Add a float to list of constants and return its index. +*/ +static int luaK_numberK (FuncState *fs, lua_Number r) { + TValue o; + setfltvalue(&o, r); + return addk(fs, &o, &o); /* use number itself as key */ +} + + +/* +** Add a false to list of constants and return its index. +*/ +static int boolF (FuncState *fs) { + TValue o; + setbfvalue(&o); + return addk(fs, &o, &o); /* use boolean itself as key */ +} + + +/* +** Add a true to list of constants and return its index. +*/ +static int boolT (FuncState *fs) { + TValue o; + setbtvalue(&o); + return addk(fs, &o, &o); /* use boolean itself as key */ +} + + +/* +** Add nil to list of constants and return its index. +*/ +static int nilK (FuncState *fs) { + TValue k, v; + setnilvalue(&v); + /* cannot use nil as key; instead use table itself to represent nil */ + sethvalue(fs->ls->L, &k, fs->ls->h); + return addk(fs, &k, &v); +} + + +/* +** Check whether 'i' can be stored in an 'sC' operand. Equivalent to +** (0 <= int2sC(i) && int2sC(i) <= MAXARG_C) but without risk of +** overflows in the hidden addition inside 'int2sC'. +*/ +static int fitsC (lua_Integer i) { + return (l_castS2U(i) + OFFSET_sC <= cast_uint(MAXARG_C)); +} + + +/* +** Check whether 'i' can be stored in an 'sBx' operand. +*/ +static int fitsBx (lua_Integer i) { + return (-OFFSET_sBx <= i && i <= MAXARG_Bx - OFFSET_sBx); +} + + +void luaK_int (FuncState *fs, int reg, lua_Integer i) { + if (fitsBx(i)) + luaK_codeAsBx(fs, OP_LOADI, reg, cast_int(i)); + else + luaK_codek(fs, reg, luaK_intK(fs, i)); +} + + +static void luaK_float (FuncState *fs, int reg, lua_Number f) { + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ieq) && fitsBx(fi)) + luaK_codeAsBx(fs, OP_LOADF, reg, cast_int(fi)); + else + luaK_codek(fs, reg, luaK_numberK(fs, f)); +} + + +/* +** Convert a constant in 'v' into an expression description 'e' +*/ +static void const2exp (TValue *v, expdesc *e) { + switch (ttypetag(v)) { + case LUA_VNUMINT: + e->k = VKINT; e->u.ival = ivalue(v); + break; + case LUA_VNUMFLT: + e->k = VKFLT; e->u.nval = fltvalue(v); + break; + case LUA_VFALSE: + e->k = VFALSE; + break; + case LUA_VTRUE: + e->k = VTRUE; + break; + case LUA_VNIL: + e->k = VNIL; + break; + case LUA_VSHRSTR: case LUA_VLNGSTR: + e->k = VKSTR; e->u.strval = tsvalue(v); + break; + default: lua_assert(0); + } +} + + +/* +** Fix an expression to return the number of results 'nresults'. +** 'e' must be a multi-ret expression (function call or vararg). +*/ +void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { + Instruction *pc = &getinstruction(fs, e); + if (e->k == VCALL) /* expression is an open function call? */ + SETARG_C(*pc, nresults + 1); + else { + lua_assert(e->k == VVARARG); + SETARG_C(*pc, nresults + 1); + SETARG_A(*pc, fs->freereg); + luaK_reserveregs(fs, 1); + } +} + + +/* +** Convert a VKSTR to a VK +*/ +static void str2K (FuncState *fs, expdesc *e) { + lua_assert(e->k == VKSTR); + e->u.info = stringK(fs, e->u.strval); + e->k = VK; +} + + +/* +** Fix an expression to return one result. +** If expression is not a multi-ret expression (function call or +** vararg), it already returns one result, so nothing needs to be done. +** Function calls become VNONRELOC expressions (as its result comes +** fixed in the base register of the call), while vararg expressions +** become VRELOC (as OP_VARARG puts its results where it wants). +** (Calls are created returning one result, so that does not need +** to be fixed.) +*/ +void luaK_setoneret (FuncState *fs, expdesc *e) { + if (e->k == VCALL) { /* expression is an open function call? */ + /* already returns 1 value */ + lua_assert(GETARG_C(getinstruction(fs, e)) == 2); + e->k = VNONRELOC; /* result has fixed position */ + e->u.info = GETARG_A(getinstruction(fs, e)); + } + else if (e->k == VVARARG) { + SETARG_C(getinstruction(fs, e), 2); + e->k = VRELOC; /* can relocate its simple result */ + } +} + + +/* +** Ensure that expression 'e' is not a variable (nor a ). +** (Expression still may have jump lists.) +*/ +void luaK_dischargevars (FuncState *fs, expdesc *e) { + switch (e->k) { + case VCONST: { + const2exp(const2val(fs, e), e); + break; + } + case VLOCAL: { /* already in a register */ + e->u.info = e->u.var.sidx; + e->k = VNONRELOC; /* becomes a non-relocatable value */ + break; + } + case VUPVAL: { /* move value to some (pending) register */ + e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); + e->k = VRELOC; + break; + } + case VINDEXUP: { + e->u.info = luaK_codeABC(fs, OP_GETTABUP, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VINDEXI: { + freereg(fs, e->u.ind.t); + e->u.info = luaK_codeABC(fs, OP_GETI, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VINDEXSTR: { + freereg(fs, e->u.ind.t); + e->u.info = luaK_codeABC(fs, OP_GETFIELD, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VINDEXED: { + freeregs(fs, e->u.ind.t, e->u.ind.idx); + e->u.info = luaK_codeABC(fs, OP_GETTABLE, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VVARARG: case VCALL: { + luaK_setoneret(fs, e); + break; + } + default: break; /* there is one value available (somewhere) */ + } +} + + +/* +** Ensure expression value is in register 'reg', making 'e' a +** non-relocatable expression. +** (Expression still may have jump lists.) +*/ +static void discharge2reg (FuncState *fs, expdesc *e, int reg) { + luaK_dischargevars(fs, e); + switch (e->k) { + case VNIL: { + luaK_nil(fs, reg, 1); + break; + } + case VFALSE: { + luaK_codeABC(fs, OP_LOADFALSE, reg, 0, 0); + break; + } + case VTRUE: { + luaK_codeABC(fs, OP_LOADTRUE, reg, 0, 0); + break; + } + case VKSTR: { + str2K(fs, e); + } /* FALLTHROUGH */ + case VK: { + luaK_codek(fs, reg, e->u.info); + break; + } + case VKFLT: { + luaK_float(fs, reg, e->u.nval); + break; + } + case VKINT: { + luaK_int(fs, reg, e->u.ival); + break; + } + case VRELOC: { + Instruction *pc = &getinstruction(fs, e); + SETARG_A(*pc, reg); /* instruction will put result in 'reg' */ + break; + } + case VNONRELOC: { + if (reg != e->u.info) + luaK_codeABC(fs, OP_MOVE, reg, e->u.info, 0); + break; + } + default: { + lua_assert(e->k == VJMP); + return; /* nothing to do... */ + } + } + e->u.info = reg; + e->k = VNONRELOC; +} + + +/* +** Ensure expression value is in a register, making 'e' a +** non-relocatable expression. +** (Expression still may have jump lists.) +*/ +static void discharge2anyreg (FuncState *fs, expdesc *e) { + if (e->k != VNONRELOC) { /* no fixed register yet? */ + luaK_reserveregs(fs, 1); /* get a register */ + discharge2reg(fs, e, fs->freereg-1); /* put value there */ + } +} + + +static int code_loadbool (FuncState *fs, int A, OpCode op) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, op, A, 0, 0); +} + + +/* +** check whether list has any jump that do not produce a value +** or produce an inverted value +*/ +static int need_value (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) { + Instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) return 1; + } + return 0; /* not found */ +} + + +/* +** Ensures final expression result (which includes results from its +** jump lists) is in register 'reg'. +** If expression has jumps, need to patch these jumps either to +** its final position or to "load" instructions (for those tests +** that do not produce values). +*/ +static void exp2reg (FuncState *fs, expdesc *e, int reg) { + discharge2reg(fs, e, reg); + if (e->k == VJMP) /* expression itself is a test? */ + luaK_concat(fs, &e->t, e->u.info); /* put this jump in 't' list */ + if (hasjumps(e)) { + int final; /* position after whole expression */ + int p_f = NO_JUMP; /* position of an eventual LOAD false */ + int p_t = NO_JUMP; /* position of an eventual LOAD true */ + if (need_value(fs, e->t) || need_value(fs, e->f)) { + int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); + p_f = code_loadbool(fs, reg, OP_LFALSESKIP); /* skip next inst. */ + p_t = code_loadbool(fs, reg, OP_LOADTRUE); + /* jump around these booleans if 'e' is not a test */ + luaK_patchtohere(fs, fj); + } + final = luaK_getlabel(fs); + patchlistaux(fs, e->f, final, reg, p_f); + patchlistaux(fs, e->t, final, reg, p_t); + } + e->f = e->t = NO_JUMP; + e->u.info = reg; + e->k = VNONRELOC; +} + + +/* +** Ensures final expression result is in next available register. +*/ +void luaK_exp2nextreg (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + freeexp(fs, e); + luaK_reserveregs(fs, 1); + exp2reg(fs, e, fs->freereg - 1); +} + + +/* +** Ensures final expression result is in some (any) register +** and return that register. +*/ +int luaK_exp2anyreg (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + if (e->k == VNONRELOC) { /* expression already has a register? */ + if (!hasjumps(e)) /* no jumps? */ + return e->u.info; /* result is already in a register */ + if (e->u.info >= luaY_nvarstack(fs)) { /* reg. is not a local? */ + exp2reg(fs, e, e->u.info); /* put final result in it */ + return e->u.info; + } + /* else expression has jumps and cannot change its register + to hold the jump values, because it is a local variable. + Go through to the default case. */ + } + luaK_exp2nextreg(fs, e); /* default: use next available register */ + return e->u.info; +} + + +/* +** Ensures final expression result is either in a register +** or in an upvalue. +*/ +void luaK_exp2anyregup (FuncState *fs, expdesc *e) { + if (e->k != VUPVAL || hasjumps(e)) + luaK_exp2anyreg(fs, e); +} + + +/* +** Ensures final expression result is either in a register +** or it is a constant. +*/ +void luaK_exp2val (FuncState *fs, expdesc *e) { + if (hasjumps(e)) + luaK_exp2anyreg(fs, e); + else + luaK_dischargevars(fs, e); +} + + +/* +** Try to make 'e' a K expression with an index in the range of R/K +** indices. Return true iff succeeded. +*/ +static int luaK_exp2K (FuncState *fs, expdesc *e) { + if (!hasjumps(e)) { + int info; + switch (e->k) { /* move constants to 'k' */ + case VTRUE: info = boolT(fs); break; + case VFALSE: info = boolF(fs); break; + case VNIL: info = nilK(fs); break; + case VKINT: info = luaK_intK(fs, e->u.ival); break; + case VKFLT: info = luaK_numberK(fs, e->u.nval); break; + case VKSTR: info = stringK(fs, e->u.strval); break; + case VK: info = e->u.info; break; + default: return 0; /* not a constant */ + } + if (info <= MAXINDEXRK) { /* does constant fit in 'argC'? */ + e->k = VK; /* make expression a 'K' expression */ + e->u.info = info; + return 1; + } + } + /* else, expression doesn't fit; leave it unchanged */ + return 0; +} + + +/* +** Ensures final expression result is in a valid R/K index +** (that is, it is either in a register or in 'k' with an index +** in the range of R/K indices). +** Returns 1 iff expression is K. +*/ +int luaK_exp2RK (FuncState *fs, expdesc *e) { + if (luaK_exp2K(fs, e)) + return 1; + else { /* not a constant in the right range: put it in a register */ + luaK_exp2anyreg(fs, e); + return 0; + } +} + + +static void codeABRK (FuncState *fs, OpCode o, int a, int b, + expdesc *ec) { + int k = luaK_exp2RK(fs, ec); + luaK_codeABCk(fs, o, a, b, ec->u.info, k); +} + + +/* +** Generate code to store result of expression 'ex' into variable 'var'. +*/ +void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { + switch (var->k) { + case VLOCAL: { + freeexp(fs, ex); + exp2reg(fs, ex, var->u.var.sidx); /* compute 'ex' into proper place */ + return; + } + case VUPVAL: { + int e = luaK_exp2anyreg(fs, ex); + luaK_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0); + break; + } + case VINDEXUP: { + codeABRK(fs, OP_SETTABUP, var->u.ind.t, var->u.ind.idx, ex); + break; + } + case VINDEXI: { + codeABRK(fs, OP_SETI, var->u.ind.t, var->u.ind.idx, ex); + break; + } + case VINDEXSTR: { + codeABRK(fs, OP_SETFIELD, var->u.ind.t, var->u.ind.idx, ex); + break; + } + case VINDEXED: { + codeABRK(fs, OP_SETTABLE, var->u.ind.t, var->u.ind.idx, ex); + break; + } + default: lua_assert(0); /* invalid var kind to store */ + } + freeexp(fs, ex); +} + + +/* +** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). +*/ +void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { + int ereg; + luaK_exp2anyreg(fs, e); + ereg = e->u.info; /* register where 'e' was placed */ + freeexp(fs, e); + e->u.info = fs->freereg; /* base register for op_self */ + e->k = VNONRELOC; /* self expression has a fixed register */ + luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ + codeABRK(fs, OP_SELF, e->u.info, ereg, key); + freeexp(fs, key); +} + + +/* +** Negate condition 'e' (where 'e' is a comparison). +*/ +static void negatecondition (FuncState *fs, expdesc *e) { + Instruction *pc = getjumpcontrol(fs, e->u.info); + lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && + GET_OPCODE(*pc) != OP_TEST); + SETARG_k(*pc, (GETARG_k(*pc) ^ 1)); +} + + +/* +** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond' +** is true, code will jump if 'e' is true.) Return jump position. +** Optimize when 'e' is 'not' something, inverting the condition +** and removing the 'not'. +*/ +static int jumponcond (FuncState *fs, expdesc *e, int cond) { + if (e->k == VRELOC) { + Instruction ie = getinstruction(fs, e); + if (GET_OPCODE(ie) == OP_NOT) { + removelastinstruction(fs); /* remove previous OP_NOT */ + return condjump(fs, OP_TEST, GETARG_B(ie), 0, 0, !cond); + } + /* else go through */ + } + discharge2anyreg(fs, e); + freeexp(fs, e); + return condjump(fs, OP_TESTSET, NO_REG, e->u.info, 0, cond); +} + + +/* +** Emit code to go through if 'e' is true, jump otherwise. +*/ +void luaK_goiftrue (FuncState *fs, expdesc *e) { + int pc; /* pc of new jump */ + luaK_dischargevars(fs, e); + switch (e->k) { + case VJMP: { /* condition? */ + negatecondition(fs, e); /* jump when it is false */ + pc = e->u.info; /* save jump position */ + break; + } + case VK: case VKFLT: case VKINT: case VKSTR: case VTRUE: { + pc = NO_JUMP; /* always true; do nothing */ + break; + } + default: { + pc = jumponcond(fs, e, 0); /* jump when false */ + break; + } + } + luaK_concat(fs, &e->f, pc); /* insert new jump in false list */ + luaK_patchtohere(fs, e->t); /* true list jumps to here (to go through) */ + e->t = NO_JUMP; +} + + +/* +** Emit code to go through if 'e' is false, jump otherwise. +*/ +void luaK_goiffalse (FuncState *fs, expdesc *e) { + int pc; /* pc of new jump */ + luaK_dischargevars(fs, e); + switch (e->k) { + case VJMP: { + pc = e->u.info; /* already jump if true */ + break; + } + case VNIL: case VFALSE: { + pc = NO_JUMP; /* always false; do nothing */ + break; + } + default: { + pc = jumponcond(fs, e, 1); /* jump if true */ + break; + } + } + luaK_concat(fs, &e->t, pc); /* insert new jump in 't' list */ + luaK_patchtohere(fs, e->f); /* false list jumps to here (to go through) */ + e->f = NO_JUMP; +} + + +/* +** Code 'not e', doing constant folding. +*/ +static void codenot (FuncState *fs, expdesc *e) { + switch (e->k) { + case VNIL: case VFALSE: { + e->k = VTRUE; /* true == not nil == not false */ + break; + } + case VK: case VKFLT: case VKINT: case VKSTR: case VTRUE: { + e->k = VFALSE; /* false == not "x" == not 0.5 == not 1 == not true */ + break; + } + case VJMP: { + negatecondition(fs, e); + break; + } + case VRELOC: + case VNONRELOC: { + discharge2anyreg(fs, e); + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, OP_NOT, 0, e->u.info, 0); + e->k = VRELOC; + break; + } + default: lua_assert(0); /* cannot happen */ + } + /* interchange true and false lists */ + { int temp = e->f; e->f = e->t; e->t = temp; } + removevalues(fs, e->f); /* values are useless when negated */ + removevalues(fs, e->t); +} + + +/* +** Check whether expression 'e' is a small literal string +*/ +static int isKstr (FuncState *fs, expdesc *e) { + return (e->k == VK && !hasjumps(e) && e->u.info <= MAXARG_B && + ttisshrstring(&fs->f->k[e->u.info])); +} + +/* +** Check whether expression 'e' is a literal integer. +*/ +int luaK_isKint (expdesc *e) { + return (e->k == VKINT && !hasjumps(e)); +} + + +/* +** Check whether expression 'e' is a literal integer in +** proper range to fit in register C +*/ +static int isCint (expdesc *e) { + return luaK_isKint(e) && (l_castS2U(e->u.ival) <= l_castS2U(MAXARG_C)); +} + + +/* +** Check whether expression 'e' is a literal integer in +** proper range to fit in register sC +*/ +static int isSCint (expdesc *e) { + return luaK_isKint(e) && fitsC(e->u.ival); +} + + +/* +** Check whether expression 'e' is a literal integer or float in +** proper range to fit in a register (sB or sC). +*/ +static int isSCnumber (expdesc *e, int *pi, int *isfloat) { + lua_Integer i; + if (e->k == VKINT) + i = e->u.ival; + else if (e->k == VKFLT && luaV_flttointeger(e->u.nval, &i, F2Ieq)) + *isfloat = 1; + else + return 0; /* not a number */ + if (!hasjumps(e) && fitsC(i)) { + *pi = int2sC(cast_int(i)); + return 1; + } + else + return 0; +} + + +/* +** Create expression 't[k]'. 't' must have its final result already in a +** register or upvalue. Upvalues can only be indexed by literal strings. +** Keys can be literal strings in the constant table or arbitrary +** values in registers. +*/ +void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { + if (k->k == VKSTR) + str2K(fs, k); + lua_assert(!hasjumps(t) && + (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL)); + if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non 'Kstr'? */ + luaK_exp2anyreg(fs, t); /* put it in a register */ + if (t->k == VUPVAL) { + t->u.ind.t = t->u.info; /* upvalue index */ + t->u.ind.idx = k->u.info; /* literal string */ + t->k = VINDEXUP; + } + else { + /* register index of the table */ + t->u.ind.t = (t->k == VLOCAL) ? t->u.var.sidx: t->u.info; + if (isKstr(fs, k)) { + t->u.ind.idx = k->u.info; /* literal string */ + t->k = VINDEXSTR; + } + else if (isCint(k)) { + t->u.ind.idx = cast_int(k->u.ival); /* int. constant in proper range */ + t->k = VINDEXI; + } + else { + t->u.ind.idx = luaK_exp2anyreg(fs, k); /* register */ + t->k = VINDEXED; + } + } +} + + +/* +** Return false if folding can raise an error. +** Bitwise operations need operands convertible to integers; division +** operations cannot have 0 as divisor. +*/ +static int validop (int op, TValue *v1, TValue *v2) { + switch (op) { + case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: + case LUA_OPSHL: case LUA_OPSHR: case LUA_OPBNOT: { /* conversion errors */ + lua_Integer i; + return (tointegerns(v1, &i) && tointegerns(v2, &i)); + } + case LUA_OPDIV: case LUA_OPIDIV: case LUA_OPMOD: /* division by 0 */ + return (nvalue(v2) != 0); + default: return 1; /* everything else is valid */ + } +} + + +/* +** Try to "constant-fold" an operation; return 1 iff successful. +** (In this case, 'e1' has the final result.) +*/ +static int constfolding (FuncState *fs, int op, expdesc *e1, + const expdesc *e2) { + TValue v1, v2, res; + if (!tonumeral(e1, &v1) || !tonumeral(e2, &v2) || !validop(op, &v1, &v2)) + return 0; /* non-numeric operands or not safe to fold */ + luaO_rawarith(fs->ls->L, op, &v1, &v2, &res); /* does operation */ + if (ttisinteger(&res)) { + e1->k = VKINT; + e1->u.ival = ivalue(&res); + } + else { /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */ + lua_Number n = fltvalue(&res); + if (luai_numisnan(n) || n == 0) + return 0; + e1->k = VKFLT; + e1->u.nval = n; + } + return 1; +} + + +/* +** Emit code for unary expressions that "produce values" +** (everything but 'not'). +** Expression to produce final result will be encoded in 'e'. +*/ +static void codeunexpval (FuncState *fs, OpCode op, expdesc *e, int line) { + int r = luaK_exp2anyreg(fs, e); /* opcodes operate only on registers */ + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, op, 0, r, 0); /* generate opcode */ + e->k = VRELOC; /* all those operations are relocatable */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for binary expressions that "produce values" +** (everything but logical operators 'and'/'or' and comparison +** operators). +** Expression to produce final result will be encoded in 'e1'. +*/ +static void finishbinexpval (FuncState *fs, expdesc *e1, expdesc *e2, + OpCode op, int v2, int flip, int line, + OpCode mmop, TMS event) { + int v1 = luaK_exp2anyreg(fs, e1); + int pc = luaK_codeABCk(fs, op, 0, v1, v2, 0); + freeexps(fs, e1, e2); + e1->u.info = pc; + e1->k = VRELOC; /* all those operations are relocatable */ + luaK_fixline(fs, line); + luaK_codeABCk(fs, mmop, v1, v2, event, flip); /* to call metamethod */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for binary expressions that "produce values" over +** two registers. +*/ +static void codebinexpval (FuncState *fs, OpCode op, + expdesc *e1, expdesc *e2, int line) { + int v2 = luaK_exp2anyreg(fs, e2); /* both operands are in registers */ + lua_assert(OP_ADD <= op && op <= OP_SHR); + finishbinexpval(fs, e1, e2, op, v2, 0, line, OP_MMBIN, + cast(TMS, (op - OP_ADD) + TM_ADD)); +} + + +/* +** Code binary operators with immediate operands. +*/ +static void codebini (FuncState *fs, OpCode op, + expdesc *e1, expdesc *e2, int flip, int line, + TMS event) { + int v2 = int2sC(cast_int(e2->u.ival)); /* immediate operand */ + lua_assert(e2->k == VKINT); + finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINI, event); +} + + +/* Try to code a binary operator negating its second operand. +** For the metamethod, 2nd operand must keep its original value. +*/ +static int finishbinexpneg (FuncState *fs, expdesc *e1, expdesc *e2, + OpCode op, int line, TMS event) { + if (!luaK_isKint(e2)) + return 0; /* not an integer constant */ + else { + lua_Integer i2 = e2->u.ival; + if (!(fitsC(i2) && fitsC(-i2))) + return 0; /* not in the proper range */ + else { /* operating a small integer constant */ + int v2 = cast_int(i2); + finishbinexpval(fs, e1, e2, op, int2sC(-v2), 0, line, OP_MMBINI, event); + /* correct metamethod argument */ + SETARG_B(fs->f->code[fs->pc - 1], int2sC(v2)); + return 1; /* successfully coded */ + } + } +} + + +static void swapexps (expdesc *e1, expdesc *e2) { + expdesc temp = *e1; *e1 = *e2; *e2 = temp; /* swap 'e1' and 'e2' */ +} + + +/* +** Code arithmetic operators ('+', '-', ...). If second operand is a +** constant in the proper range, use variant opcodes with K operands. +*/ +static void codearith (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int flip, int line) { + TMS event = cast(TMS, opr + TM_ADD); + if (tonumeral(e2, NULL) && luaK_exp2K(fs, e2)) { /* K operand? */ + int v2 = e2->u.info; /* K index */ + OpCode op = cast(OpCode, opr + OP_ADDK); + finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINK, event); + } + else { /* 'e2' is neither an immediate nor a K operand */ + OpCode op = cast(OpCode, opr + OP_ADD); + if (flip) + swapexps(e1, e2); /* back to original order */ + codebinexpval(fs, op, e1, e2, line); /* use standard operators */ + } +} + + +/* +** Code commutative operators ('+', '*'). If first operand is a +** numeric constant, change order of operands to try to use an +** immediate or K operator. +*/ +static void codecommutative (FuncState *fs, BinOpr op, + expdesc *e1, expdesc *e2, int line) { + int flip = 0; + if (tonumeral(e1, NULL)) { /* is first operand a numeric constant? */ + swapexps(e1, e2); /* change order */ + flip = 1; + } + if (op == OPR_ADD && isSCint(e2)) /* immediate operand? */ + codebini(fs, cast(OpCode, OP_ADDI), e1, e2, flip, line, TM_ADD); + else + codearith(fs, op, e1, e2, flip, line); +} + + +/* +** Code bitwise operations; they are all associative, so the function +** tries to put an integer constant as the 2nd operand (a K operand). +*/ +static void codebitwise (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int line) { + int flip = 0; + int v2; + OpCode op; + if (e1->k == VKINT && luaK_exp2RK(fs, e1)) { + swapexps(e1, e2); /* 'e2' will be the constant operand */ + flip = 1; + } + else if (!(e2->k == VKINT && luaK_exp2RK(fs, e2))) { /* no constants? */ + op = cast(OpCode, opr + OP_ADD); + codebinexpval(fs, op, e1, e2, line); /* all-register opcodes */ + return; + } + v2 = e2->u.info; /* index in K array */ + op = cast(OpCode, opr + OP_ADDK); + lua_assert(ttisinteger(&fs->f->k[v2])); + finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINK, + cast(TMS, opr + TM_ADD)); +} + + +/* +** Emit code for order comparisons. When using an immediate operand, +** 'isfloat' tells whether the original value was a float. +*/ +static void codeorder (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) { + int r1, r2; + int im; + int isfloat = 0; + if (isSCnumber(e2, &im, &isfloat)) { + /* use immediate operand */ + r1 = luaK_exp2anyreg(fs, e1); + r2 = im; + op = cast(OpCode, (op - OP_LT) + OP_LTI); + } + else if (isSCnumber(e1, &im, &isfloat)) { + /* transform (A < B) to (B > A) and (A <= B) to (B >= A) */ + r1 = luaK_exp2anyreg(fs, e2); + r2 = im; + op = (op == OP_LT) ? OP_GTI : OP_GEI; + } + else { /* regular case, compare two registers */ + r1 = luaK_exp2anyreg(fs, e1); + r2 = luaK_exp2anyreg(fs, e2); + } + freeexps(fs, e1, e2); + e1->u.info = condjump(fs, op, r1, r2, isfloat, 1); + e1->k = VJMP; +} + + +/* +** Emit code for equality comparisons ('==', '~='). +** 'e1' was already put as RK by 'luaK_infix'. +*/ +static void codeeq (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { + int r1, r2; + int im; + int isfloat = 0; /* not needed here, but kept for symmetry */ + OpCode op; + if (e1->k != VNONRELOC) { + lua_assert(e1->k == VK || e1->k == VKINT || e1->k == VKFLT); + swapexps(e1, e2); + } + r1 = luaK_exp2anyreg(fs, e1); /* 1st expression must be in register */ + if (isSCnumber(e2, &im, &isfloat)) { + op = OP_EQI; + r2 = im; /* immediate operand */ + } + else if (luaK_exp2RK(fs, e2)) { /* 1st expression is constant? */ + op = OP_EQK; + r2 = e2->u.info; /* constant index */ + } + else { + op = OP_EQ; /* will compare two registers */ + r2 = luaK_exp2anyreg(fs, e2); + } + freeexps(fs, e1, e2); + e1->u.info = condjump(fs, op, r1, r2, isfloat, (opr == OPR_EQ)); + e1->k = VJMP; +} + + +/* +** Apply prefix operation 'op' to expression 'e'. +*/ +void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e, int line) { + static const expdesc ef = {VKINT, {0}, NO_JUMP, NO_JUMP}; + luaK_dischargevars(fs, e); + switch (op) { + case OPR_MINUS: case OPR_BNOT: /* use 'ef' as fake 2nd operand */ + if (constfolding(fs, op + LUA_OPUNM, e, &ef)) + break; + /* else */ /* FALLTHROUGH */ + case OPR_LEN: + codeunexpval(fs, cast(OpCode, op + OP_UNM), e, line); + break; + case OPR_NOT: codenot(fs, e); break; + default: lua_assert(0); + } +} + + +/* +** Process 1st operand 'v' of binary operation 'op' before reading +** 2nd operand. +*/ +void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { + luaK_dischargevars(fs, v); + switch (op) { + case OPR_AND: { + luaK_goiftrue(fs, v); /* go ahead only if 'v' is true */ + break; + } + case OPR_OR: { + luaK_goiffalse(fs, v); /* go ahead only if 'v' is false */ + break; + } + case OPR_CONCAT: { + luaK_exp2nextreg(fs, v); /* operand must be on the stack */ + break; + } + case OPR_ADD: case OPR_SUB: + case OPR_MUL: case OPR_DIV: case OPR_IDIV: + case OPR_MOD: case OPR_POW: + case OPR_BAND: case OPR_BOR: case OPR_BXOR: + case OPR_SHL: case OPR_SHR: { + if (!tonumeral(v, NULL)) + luaK_exp2anyreg(fs, v); + /* else keep numeral, which may be folded with 2nd operand */ + break; + } + case OPR_EQ: case OPR_NE: { + if (!tonumeral(v, NULL)) + luaK_exp2RK(fs, v); + /* else keep numeral, which may be an immediate operand */ + break; + } + case OPR_LT: case OPR_LE: + case OPR_GT: case OPR_GE: { + int dummy, dummy2; + if (!isSCnumber(v, &dummy, &dummy2)) + luaK_exp2anyreg(fs, v); + /* else keep numeral, which may be an immediate operand */ + break; + } + default: lua_assert(0); + } +} + +/* +** Create code for '(e1 .. e2)'. +** For '(e1 .. e2.1 .. e2.2)' (which is '(e1 .. (e2.1 .. e2.2))', +** because concatenation is right associative), merge both CONCATs. +*/ +static void codeconcat (FuncState *fs, expdesc *e1, expdesc *e2, int line) { + Instruction *ie2 = previousinstruction(fs); + if (GET_OPCODE(*ie2) == OP_CONCAT) { /* is 'e2' a concatenation? */ + int n = GETARG_B(*ie2); /* # of elements concatenated in 'e2' */ + lua_assert(e1->u.info + 1 == GETARG_A(*ie2)); + freeexp(fs, e2); + SETARG_A(*ie2, e1->u.info); /* correct first element ('e1') */ + SETARG_B(*ie2, n + 1); /* will concatenate one more element */ + } + else { /* 'e2' is not a concatenation */ + luaK_codeABC(fs, OP_CONCAT, e1->u.info, 2, 0); /* new concat opcode */ + freeexp(fs, e2); + luaK_fixline(fs, line); + } +} + + +/* +** Finalize code for binary operation, after reading 2nd operand. +*/ +void luaK_posfix (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int line) { + luaK_dischargevars(fs, e2); + if (foldbinop(opr) && constfolding(fs, opr + LUA_OPADD, e1, e2)) + return; /* done by folding */ + switch (opr) { + case OPR_AND: { + lua_assert(e1->t == NO_JUMP); /* list closed by 'luaK_infix' */ + luaK_concat(fs, &e2->f, e1->f); + *e1 = *e2; + break; + } + case OPR_OR: { + lua_assert(e1->f == NO_JUMP); /* list closed by 'luaK_infix' */ + luaK_concat(fs, &e2->t, e1->t); + *e1 = *e2; + break; + } + case OPR_CONCAT: { /* e1 .. e2 */ + luaK_exp2nextreg(fs, e2); + codeconcat(fs, e1, e2, line); + break; + } + case OPR_ADD: case OPR_MUL: { + codecommutative(fs, opr, e1, e2, line); + break; + } + case OPR_SUB: { + if (finishbinexpneg(fs, e1, e2, OP_ADDI, line, TM_SUB)) + break; /* coded as (r1 + -I) */ + /* ELSE */ + } /* FALLTHROUGH */ + case OPR_DIV: case OPR_IDIV: case OPR_MOD: case OPR_POW: { + codearith(fs, opr, e1, e2, 0, line); + break; + } + case OPR_BAND: case OPR_BOR: case OPR_BXOR: { + codebitwise(fs, opr, e1, e2, line); + break; + } + case OPR_SHL: { + if (isSCint(e1)) { + swapexps(e1, e2); + codebini(fs, OP_SHLI, e1, e2, 1, line, TM_SHL); /* I << r2 */ + } + else if (finishbinexpneg(fs, e1, e2, OP_SHRI, line, TM_SHL)) { + /* coded as (r1 >> -I) */; + } + else /* regular case (two registers) */ + codebinexpval(fs, OP_SHL, e1, e2, line); + break; + } + case OPR_SHR: { + if (isSCint(e2)) + codebini(fs, OP_SHRI, e1, e2, 0, line, TM_SHR); /* r1 >> I */ + else /* regular case (two registers) */ + codebinexpval(fs, OP_SHR, e1, e2, line); + break; + } + case OPR_EQ: case OPR_NE: { + codeeq(fs, opr, e1, e2); + break; + } + case OPR_LT: case OPR_LE: { + OpCode op = cast(OpCode, (opr - OPR_EQ) + OP_EQ); + codeorder(fs, op, e1, e2); + break; + } + case OPR_GT: case OPR_GE: { + /* '(a > b)' <=> '(b < a)'; '(a >= b)' <=> '(b <= a)' */ + OpCode op = cast(OpCode, (opr - OPR_NE) + OP_EQ); + swapexps(e1, e2); + codeorder(fs, op, e1, e2); + break; + } + default: lua_assert(0); + } +} + + +/* +** Change line information associated with current position, by removing +** previous info and adding it again with new line. +*/ +void luaK_fixline (FuncState *fs, int line) { + removelastlineinfo(fs); + savelineinfo(fs, fs->f, line); +} + + +void luaK_settablesize (FuncState *fs, int pc, int ra, int asize, int hsize) { + Instruction *inst = &fs->f->code[pc]; + int rb = (hsize != 0) ? luaO_ceillog2(hsize) + 1 : 0; /* hash size */ + int extra = asize / (MAXARG_C + 1); /* higher bits of array size */ + int rc = asize % (MAXARG_C + 1); /* lower bits of array size */ + int k = (extra > 0); /* true iff needs extra argument */ + *inst = CREATE_ABCk(OP_NEWTABLE, ra, rb, rc, k); + *(inst + 1) = CREATE_Ax(OP_EXTRAARG, extra); +} + + +/* +** Emit a SETLIST instruction. +** 'base' is register that keeps table; +** 'nelems' is #table plus those to be stored now; +** 'tostore' is number of values (in registers 'base + 1',...) to add to +** table (or LUA_MULTRET to add up to stack top). +*/ +void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { + lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH); + if (tostore == LUA_MULTRET) + tostore = 0; + if (nelems <= MAXARG_C) + luaK_codeABC(fs, OP_SETLIST, base, tostore, nelems); + else { + int extra = nelems / (MAXARG_C + 1); + nelems %= (MAXARG_C + 1); + luaK_codeABCk(fs, OP_SETLIST, base, tostore, nelems, 1); + codeextraarg(fs, extra); + } + fs->freereg = base + 1; /* free registers with list values */ +} + + +/* +** return the final target of a jump (skipping jumps to jumps) +*/ +static int finaltarget (Instruction *code, int i) { + int count; + for (count = 0; count < 100; count++) { /* avoid infinite loops */ + Instruction pc = code[i]; + if (GET_OPCODE(pc) != OP_JMP) + break; + else + i += GETARG_sJ(pc) + 1; + } + return i; +} + + +/* +** Do a final pass over the code of a function, doing small peephole +** optimizations and adjustments. +*/ +void luaK_finish (FuncState *fs) { + int i; + Proto *p = fs->f; + for (i = 0; i < fs->pc; i++) { + Instruction *pc = &p->code[i]; + lua_assert(i == 0 || isOT(*(pc - 1)) == isIT(*pc)); + switch (GET_OPCODE(*pc)) { + case OP_RETURN0: case OP_RETURN1: { + if (!(fs->needclose || p->is_vararg)) + break; /* no extra work */ + /* else use OP_RETURN to do the extra work */ + SET_OPCODE(*pc, OP_RETURN); + } /* FALLTHROUGH */ + case OP_RETURN: case OP_TAILCALL: { + if (fs->needclose) + SETARG_k(*pc, 1); /* signal that it needs to close */ + if (p->is_vararg) + SETARG_C(*pc, p->numparams + 1); /* signal that it is vararg */ + break; + } + case OP_JMP: { + int target = finaltarget(p->code, i); + fixjump(fs, i, target); + break; + } + default: break; + } + } +} diff --git a/Lua/lcode.h b/Lua/lcode.h new file mode 100644 index 00000000..32658244 --- /dev/null +++ b/Lua/lcode.h @@ -0,0 +1,104 @@ +/* +** $Id: lcode.h $ +** Code generator for Lua +** See Copyright Notice in lua.h +*/ + +#ifndef lcode_h +#define lcode_h + +#include "llex.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" + + +/* +** Marks the end of a patch list. It is an invalid value both as an absolute +** address, and as a list link (would link an element to itself). +*/ +#define NO_JUMP (-1) + + +/* +** grep "ORDER OPR" if you change these enums (ORDER OP) +*/ +typedef enum BinOpr { + /* arithmetic operators */ + OPR_ADD, OPR_SUB, OPR_MUL, OPR_MOD, OPR_POW, + OPR_DIV, OPR_IDIV, + /* bitwise operators */ + OPR_BAND, OPR_BOR, OPR_BXOR, + OPR_SHL, OPR_SHR, + /* string operator */ + OPR_CONCAT, + /* comparison operators */ + OPR_EQ, OPR_LT, OPR_LE, + OPR_NE, OPR_GT, OPR_GE, + /* logical operators */ + OPR_AND, OPR_OR, + OPR_NOBINOPR +} BinOpr; + + +/* true if operation is foldable (that is, it is arithmetic or bitwise) */ +#define foldbinop(op) ((op) <= OPR_SHR) + + +#define luaK_codeABC(fs,o,a,b,c) luaK_codeABCk(fs,o,a,b,c,0) + + +typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; + + +/* get (pointer to) instruction of given 'expdesc' */ +#define getinstruction(fs,e) ((fs)->f->code[(e)->u.info]) + + +#define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET) + +#define luaK_jumpto(fs,t) luaK_patchlist(fs, luaK_jump(fs), t) + +LUAI_FUNC int luaK_code (FuncState *fs, Instruction i); +LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx); +LUAI_FUNC int luaK_codeAsBx (FuncState *fs, OpCode o, int A, int Bx); +LUAI_FUNC int luaK_codeABCk (FuncState *fs, OpCode o, int A, + int B, int C, int k); +LUAI_FUNC int luaK_isKint (expdesc *e); +LUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v); +LUAI_FUNC void luaK_fixline (FuncState *fs, int line); +LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); +LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); +LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); +LUAI_FUNC void luaK_int (FuncState *fs, int reg, lua_Integer n); +LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); +LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); +LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_goiffalse (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e); +LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults); +LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_jump (FuncState *fs); +LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret); +LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target); +LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list); +LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2); +LUAI_FUNC int luaK_getlabel (FuncState *fs); +LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v, int line); +LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v); +LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1, + expdesc *v2, int line); +LUAI_FUNC void luaK_settablesize (FuncState *fs, int pc, + int ra, int asize, int hsize); +LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore); +LUAI_FUNC void luaK_finish (FuncState *fs); +LUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *msg); + + +#endif diff --git a/Lua/lcorolib.c b/Lua/lcorolib.c new file mode 100644 index 00000000..c165031d --- /dev/null +++ b/Lua/lcorolib.c @@ -0,0 +1,207 @@ +/* +** $Id: lcorolib.c $ +** Coroutine Library +** See Copyright Notice in lua.h +*/ + +#define lcorolib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +static lua_State *getco (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argexpected(L, co, 1, "thread"); + return co; +} + + +/* +** Resumes a coroutine. Returns the number of results for non-error +** cases or -1 for errors. +*/ +static int auxresume (lua_State *L, lua_State *co, int narg) { + int status, nres; + if (!lua_checkstack(co, narg)) { + lua_pushliteral(L, "too many arguments to resume"); + return -1; /* error flag */ + } + lua_xmove(L, co, narg); + status = lua_resume(co, L, narg, &nres); + if (status == LUA_OK || status == LUA_YIELD) { + if (!lua_checkstack(L, nres + 1)) { + lua_pop(co, nres); /* remove results anyway */ + lua_pushliteral(L, "too many results to resume"); + return -1; /* error flag */ + } + lua_xmove(co, L, nres); /* move yielded values */ + return nres; + } + else { + lua_xmove(co, L, 1); /* move error message */ + return -1; /* error flag */ + } +} + + +static int luaB_coresume (lua_State *L) { + lua_State *co = getco(L); + int r; + r = auxresume(L, co, lua_gettop(L) - 1); + if (r < 0) { + lua_pushboolean(L, 0); + lua_insert(L, -2); + return 2; /* return false + error message */ + } + else { + lua_pushboolean(L, 1); + lua_insert(L, -(r + 1)); + return r + 1; /* return true + 'resume' returns */ + } +} + + +static int luaB_auxwrap (lua_State *L) { + lua_State *co = lua_tothread(L, lua_upvalueindex(1)); + int r = auxresume(L, co, lua_gettop(L)); + if (r < 0) { /* error? */ + int stat = lua_status(co); + if (stat != LUA_OK && stat != LUA_YIELD) /* error in the coroutine? */ + lua_resetthread(co); /* close its tbc variables */ + if (stat != LUA_ERRMEM && /* not a memory error and ... */ + lua_type(L, -1) == LUA_TSTRING) { /* ... error object is a string? */ + luaL_where(L, 1); /* add extra info, if available */ + lua_insert(L, -2); + lua_concat(L, 2); + } + return lua_error(L); /* propagate error */ + } + return r; +} + + +static int luaB_cocreate (lua_State *L) { + lua_State *NL; + luaL_checktype(L, 1, LUA_TFUNCTION); + NL = lua_newthread(L); + lua_pushvalue(L, 1); /* move function to top */ + lua_xmove(L, NL, 1); /* move function from L to NL */ + return 1; +} + + +static int luaB_cowrap (lua_State *L) { + luaB_cocreate(L); + lua_pushcclosure(L, luaB_auxwrap, 1); + return 1; +} + + +static int luaB_yield (lua_State *L) { + return lua_yield(L, lua_gettop(L)); +} + + +#define COS_RUN 0 +#define COS_DEAD 1 +#define COS_YIELD 2 +#define COS_NORM 3 + + +static const char *const statname[] = + {"running", "dead", "suspended", "normal"}; + + +static int auxstatus (lua_State *L, lua_State *co) { + if (L == co) return COS_RUN; + else { + switch (lua_status(co)) { + case LUA_YIELD: + return COS_YIELD; + case LUA_OK: { + lua_Debug ar; + if (lua_getstack(co, 0, &ar)) /* does it have frames? */ + return COS_NORM; /* it is running */ + else if (lua_gettop(co) == 0) + return COS_DEAD; + else + return COS_YIELD; /* initial state */ + } + default: /* some error occurred */ + return COS_DEAD; + } + } +} + + +static int luaB_costatus (lua_State *L) { + lua_State *co = getco(L); + lua_pushstring(L, statname[auxstatus(L, co)]); + return 1; +} + + +static int luaB_yieldable (lua_State *L) { + lua_State *co = lua_isnone(L, 1) ? L : getco(L); + lua_pushboolean(L, lua_isyieldable(co)); + return 1; +} + + +static int luaB_corunning (lua_State *L) { + int ismain = lua_pushthread(L); + lua_pushboolean(L, ismain); + return 2; +} + + +static int luaB_close (lua_State *L) { + lua_State *co = getco(L); + int status = auxstatus(L, co); + switch (status) { + case COS_DEAD: case COS_YIELD: { + status = lua_resetthread(co); + if (status == LUA_OK) { + lua_pushboolean(L, 1); + return 1; + } + else { + lua_pushboolean(L, 0); + lua_xmove(co, L, 1); /* copy error message */ + return 2; + } + } + default: /* normal or running coroutine */ + return luaL_error(L, "cannot close a %s coroutine", statname[status]); + } +} + + +static const luaL_Reg co_funcs[] = { + {"create", luaB_cocreate}, + {"resume", luaB_coresume}, + {"running", luaB_corunning}, + {"status", luaB_costatus}, + {"wrap", luaB_cowrap}, + {"yield", luaB_yield}, + {"isyieldable", luaB_yieldable}, + {"close", luaB_close}, + {NULL, NULL} +}; + + + +LUAMOD_API int luaopen_coroutine (lua_State *L) { + luaL_newlib(L, co_funcs); + return 1; +} + diff --git a/Lua/lctype.c b/Lua/lctype.c new file mode 100644 index 00000000..95422809 --- /dev/null +++ b/Lua/lctype.c @@ -0,0 +1,64 @@ +/* +** $Id: lctype.c $ +** 'ctype' functions for Lua +** See Copyright Notice in lua.h +*/ + +#define lctype_c +#define LUA_CORE + +#include "lprefix.h" + + +#include "lctype.h" + +#if !LUA_USE_CTYPE /* { */ + +#include + + +#if defined (LUA_UCID) /* accept UniCode IDentifiers? */ +/* consider all non-ascii codepoints to be alphabetic */ +#define NONA 0x01 +#else +#define NONA 0x00 /* default */ +#endif + + +LUAI_DDEF const lu_byte luai_ctype_[UCHAR_MAX + 2] = { + 0x00, /* EOZ */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0. */ + 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, /* 2. */ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, /* 3. */ + 0x16, 0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x05, /* 4. */ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 5. */ + 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x05, + 0x04, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x05, /* 6. */ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 7. */ + 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x00, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* 8. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* 9. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* a. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* b. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + 0x00, 0x00, NONA, NONA, NONA, NONA, NONA, NONA, /* c. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* d. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* e. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, 0x00, 0x00, 0x00, /* f. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +#endif /* } */ diff --git a/Lua/lctype.h b/Lua/lctype.h new file mode 100644 index 00000000..864e1901 --- /dev/null +++ b/Lua/lctype.h @@ -0,0 +1,101 @@ +/* +** $Id: lctype.h $ +** 'ctype' functions for Lua +** See Copyright Notice in lua.h +*/ + +#ifndef lctype_h +#define lctype_h + +#include "lua.h" + + +/* +** WARNING: the functions defined here do not necessarily correspond +** to the similar functions in the standard C ctype.h. They are +** optimized for the specific needs of Lua. +*/ + +#if !defined(LUA_USE_CTYPE) + +#if 'A' == 65 && '0' == 48 +/* ASCII case: can use its own tables; faster and fixed */ +#define LUA_USE_CTYPE 0 +#else +/* must use standard C ctype */ +#define LUA_USE_CTYPE 1 +#endif + +#endif + + +#if !LUA_USE_CTYPE /* { */ + +#include + +#include "llimits.h" + + +#define ALPHABIT 0 +#define DIGITBIT 1 +#define PRINTBIT 2 +#define SPACEBIT 3 +#define XDIGITBIT 4 + + +#define MASK(B) (1 << (B)) + + +/* +** add 1 to char to allow index -1 (EOZ) +*/ +#define testprop(c,p) (luai_ctype_[(c)+1] & (p)) + +/* +** 'lalpha' (Lua alphabetic) and 'lalnum' (Lua alphanumeric) both include '_' +*/ +#define lislalpha(c) testprop(c, MASK(ALPHABIT)) +#define lislalnum(c) testprop(c, (MASK(ALPHABIT) | MASK(DIGITBIT))) +#define lisdigit(c) testprop(c, MASK(DIGITBIT)) +#define lisspace(c) testprop(c, MASK(SPACEBIT)) +#define lisprint(c) testprop(c, MASK(PRINTBIT)) +#define lisxdigit(c) testprop(c, MASK(XDIGITBIT)) + + +/* +** In ASCII, this 'ltolower' is correct for alphabetic characters and +** for '.'. That is enough for Lua needs. ('check_exp' ensures that +** the character either is an upper-case letter or is unchanged by +** the transformation, which holds for lower-case letters and '.'.) +*/ +#define ltolower(c) \ + check_exp(('A' <= (c) && (c) <= 'Z') || (c) == ((c) | ('A' ^ 'a')), \ + (c) | ('A' ^ 'a')) + + +/* one entry for each character and for -1 (EOZ) */ +LUAI_DDEC(const lu_byte luai_ctype_[UCHAR_MAX + 2];) + + +#else /* }{ */ + +/* +** use standard C ctypes +*/ + +#include + + +#define lislalpha(c) (isalpha(c) || (c) == '_') +#define lislalnum(c) (isalnum(c) || (c) == '_') +#define lisdigit(c) (isdigit(c)) +#define lisspace(c) (isspace(c)) +#define lisprint(c) (isprint(c)) +#define lisxdigit(c) (isxdigit(c)) + +#define ltolower(c) (tolower(c)) + +#endif /* } */ + +#endif + diff --git a/Lua/ldblib.c b/Lua/ldblib.c new file mode 100644 index 00000000..5a326ade --- /dev/null +++ b/Lua/ldblib.c @@ -0,0 +1,482 @@ +/* +** $Id: ldblib.c $ +** Interface from Lua to its debug API +** See Copyright Notice in lua.h +*/ + +#define ldblib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** The hook table at registry[HOOKKEY] maps threads to their current +** hook function. +*/ +static const char *const HOOKKEY = "_HOOKKEY"; + + +/* +** If L1 != L, L1 can be in any state, and therefore there are no +** guarantees about its stack space; any push in L1 must be +** checked. +*/ +static void checkstack (lua_State *L, lua_State *L1, int n) { + if (L != L1 && !lua_checkstack(L1, n)) + luaL_error(L, "stack overflow"); +} + + +static int db_getregistry (lua_State *L) { + lua_pushvalue(L, LUA_REGISTRYINDEX); + return 1; +} + + +static int db_getmetatable (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_getmetatable(L, 1)) { + lua_pushnil(L); /* no metatable */ + } + return 1; +} + + +static int db_setmetatable (lua_State *L) { + int t = lua_type(L, 2); + luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table"); + lua_settop(L, 2); + lua_setmetatable(L, 1); + return 1; /* return 1st argument */ +} + + +static int db_getuservalue (lua_State *L) { + int n = (int)luaL_optinteger(L, 2, 1); + if (lua_type(L, 1) != LUA_TUSERDATA) + luaL_pushfail(L); + else if (lua_getiuservalue(L, 1, n) != LUA_TNONE) { + lua_pushboolean(L, 1); + return 2; + } + return 1; +} + + +static int db_setuservalue (lua_State *L) { + int n = (int)luaL_optinteger(L, 3, 1); + luaL_checktype(L, 1, LUA_TUSERDATA); + luaL_checkany(L, 2); + lua_settop(L, 2); + if (!lua_setiuservalue(L, 1, n)) + luaL_pushfail(L); + return 1; +} + + +/* +** Auxiliary function used by several library functions: check for +** an optional thread as function's first argument and set 'arg' with +** 1 if this argument is present (so that functions can skip it to +** access their other arguments) +*/ +static lua_State *getthread (lua_State *L, int *arg) { + if (lua_isthread(L, 1)) { + *arg = 1; + return lua_tothread(L, 1); + } + else { + *arg = 0; + return L; /* function will operate over current thread */ + } +} + + +/* +** Variations of 'lua_settable', used by 'db_getinfo' to put results +** from 'lua_getinfo' into result table. Key is always a string; +** value can be a string, an int, or a boolean. +*/ +static void settabss (lua_State *L, const char *k, const char *v) { + lua_pushstring(L, v); + lua_setfield(L, -2, k); +} + +static void settabsi (lua_State *L, const char *k, int v) { + lua_pushinteger(L, v); + lua_setfield(L, -2, k); +} + +static void settabsb (lua_State *L, const char *k, int v) { + lua_pushboolean(L, v); + lua_setfield(L, -2, k); +} + + +/* +** In function 'db_getinfo', the call to 'lua_getinfo' may push +** results on the stack; later it creates the result table to put +** these objects. Function 'treatstackoption' puts the result from +** 'lua_getinfo' on top of the result table so that it can call +** 'lua_setfield'. +*/ +static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) { + if (L == L1) + lua_rotate(L, -2, 1); /* exchange object and table */ + else + lua_xmove(L1, L, 1); /* move object to the "main" stack */ + lua_setfield(L, -2, fname); /* put object into table */ +} + + +/* +** Calls 'lua_getinfo' and collects all results in a new table. +** L1 needs stack space for an optional input (function) plus +** two optional outputs (function and line table) from function +** 'lua_getinfo'. +*/ +static int db_getinfo (lua_State *L) { + lua_Debug ar; + int arg; + lua_State *L1 = getthread(L, &arg); + const char *options = luaL_optstring(L, arg+2, "flnSrtu"); + checkstack(L, L1, 3); + if (lua_isfunction(L, arg + 1)) { /* info about a function? */ + options = lua_pushfstring(L, ">%s", options); /* add '>' to 'options' */ + lua_pushvalue(L, arg + 1); /* move function to 'L1' stack */ + lua_xmove(L, L1, 1); + } + else { /* stack level */ + if (!lua_getstack(L1, (int)luaL_checkinteger(L, arg + 1), &ar)) { + luaL_pushfail(L); /* level out of range */ + return 1; + } + } + if (!lua_getinfo(L1, options, &ar)) + return luaL_argerror(L, arg+2, "invalid option"); + lua_newtable(L); /* table to collect results */ + if (strchr(options, 'S')) { + lua_pushlstring(L, ar.source, ar.srclen); + lua_setfield(L, -2, "source"); + settabss(L, "short_src", ar.short_src); + settabsi(L, "linedefined", ar.linedefined); + settabsi(L, "lastlinedefined", ar.lastlinedefined); + settabss(L, "what", ar.what); + } + if (strchr(options, 'l')) + settabsi(L, "currentline", ar.currentline); + if (strchr(options, 'u')) { + settabsi(L, "nups", ar.nups); + settabsi(L, "nparams", ar.nparams); + settabsb(L, "isvararg", ar.isvararg); + } + if (strchr(options, 'n')) { + settabss(L, "name", ar.name); + settabss(L, "namewhat", ar.namewhat); + } + if (strchr(options, 'r')) { + settabsi(L, "ftransfer", ar.ftransfer); + settabsi(L, "ntransfer", ar.ntransfer); + } + if (strchr(options, 't')) + settabsb(L, "istailcall", ar.istailcall); + if (strchr(options, 'L')) + treatstackoption(L, L1, "activelines"); + if (strchr(options, 'f')) + treatstackoption(L, L1, "func"); + return 1; /* return table */ +} + + +static int db_getlocal (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + int nvar = (int)luaL_checkinteger(L, arg + 2); /* local-variable index */ + if (lua_isfunction(L, arg + 1)) { /* function argument? */ + lua_pushvalue(L, arg + 1); /* push function */ + lua_pushstring(L, lua_getlocal(L, NULL, nvar)); /* push local name */ + return 1; /* return only name (there is no value) */ + } + else { /* stack-level argument */ + lua_Debug ar; + const char *name; + int level = (int)luaL_checkinteger(L, arg + 1); + if (!lua_getstack(L1, level, &ar)) /* out of range? */ + return luaL_argerror(L, arg+1, "level out of range"); + checkstack(L, L1, 1); + name = lua_getlocal(L1, &ar, nvar); + if (name) { + lua_xmove(L1, L, 1); /* move local value */ + lua_pushstring(L, name); /* push name */ + lua_rotate(L, -2, 1); /* re-order */ + return 2; + } + else { + luaL_pushfail(L); /* no name (nor value) */ + return 1; + } + } +} + + +static int db_setlocal (lua_State *L) { + int arg; + const char *name; + lua_State *L1 = getthread(L, &arg); + lua_Debug ar; + int level = (int)luaL_checkinteger(L, arg + 1); + int nvar = (int)luaL_checkinteger(L, arg + 2); + if (!lua_getstack(L1, level, &ar)) /* out of range? */ + return luaL_argerror(L, arg+1, "level out of range"); + luaL_checkany(L, arg+3); + lua_settop(L, arg+3); + checkstack(L, L1, 1); + lua_xmove(L, L1, 1); + name = lua_setlocal(L1, &ar, nvar); + if (name == NULL) + lua_pop(L1, 1); /* pop value (if not popped by 'lua_setlocal') */ + lua_pushstring(L, name); + return 1; +} + + +/* +** get (if 'get' is true) or set an upvalue from a closure +*/ +static int auxupvalue (lua_State *L, int get) { + const char *name; + int n = (int)luaL_checkinteger(L, 2); /* upvalue index */ + luaL_checktype(L, 1, LUA_TFUNCTION); /* closure */ + name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n); + if (name == NULL) return 0; + lua_pushstring(L, name); + lua_insert(L, -(get+1)); /* no-op if get is false */ + return get + 1; +} + + +static int db_getupvalue (lua_State *L) { + return auxupvalue(L, 1); +} + + +static int db_setupvalue (lua_State *L) { + luaL_checkany(L, 3); + return auxupvalue(L, 0); +} + + +/* +** Check whether a given upvalue from a given closure exists and +** returns its index +*/ +static void *checkupval (lua_State *L, int argf, int argnup, int *pnup) { + void *id; + int nup = (int)luaL_checkinteger(L, argnup); /* upvalue index */ + luaL_checktype(L, argf, LUA_TFUNCTION); /* closure */ + id = lua_upvalueid(L, argf, nup); + if (pnup) { + luaL_argcheck(L, id != NULL, argnup, "invalid upvalue index"); + *pnup = nup; + } + return id; +} + + +static int db_upvalueid (lua_State *L) { + void *id = checkupval(L, 1, 2, NULL); + if (id != NULL) + lua_pushlightuserdata(L, id); + else + luaL_pushfail(L); + return 1; +} + + +static int db_upvaluejoin (lua_State *L) { + int n1, n2; + checkupval(L, 1, 2, &n1); + checkupval(L, 3, 4, &n2); + luaL_argcheck(L, !lua_iscfunction(L, 1), 1, "Lua function expected"); + luaL_argcheck(L, !lua_iscfunction(L, 3), 3, "Lua function expected"); + lua_upvaluejoin(L, 1, n1, 3, n2); + return 0; +} + + +/* +** Call hook function registered at hook table for the current +** thread (if there is one) +*/ +static void hookf (lua_State *L, lua_Debug *ar) { + static const char *const hooknames[] = + {"call", "return", "line", "count", "tail call"}; + lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY); + lua_pushthread(L); + if (lua_rawget(L, -2) == LUA_TFUNCTION) { /* is there a hook function? */ + lua_pushstring(L, hooknames[(int)ar->event]); /* push event name */ + if (ar->currentline >= 0) + lua_pushinteger(L, ar->currentline); /* push current line */ + else lua_pushnil(L); + lua_assert(lua_getinfo(L, "lS", ar)); + lua_call(L, 2, 0); /* call hook function */ + } +} + + +/* +** Convert a string mask (for 'sethook') into a bit mask +*/ +static int makemask (const char *smask, int count) { + int mask = 0; + if (strchr(smask, 'c')) mask |= LUA_MASKCALL; + if (strchr(smask, 'r')) mask |= LUA_MASKRET; + if (strchr(smask, 'l')) mask |= LUA_MASKLINE; + if (count > 0) mask |= LUA_MASKCOUNT; + return mask; +} + + +/* +** Convert a bit mask (for 'gethook') into a string mask +*/ +static char *unmakemask (int mask, char *smask) { + int i = 0; + if (mask & LUA_MASKCALL) smask[i++] = 'c'; + if (mask & LUA_MASKRET) smask[i++] = 'r'; + if (mask & LUA_MASKLINE) smask[i++] = 'l'; + smask[i] = '\0'; + return smask; +} + + +static int db_sethook (lua_State *L) { + int arg, mask, count; + lua_Hook func; + lua_State *L1 = getthread(L, &arg); + if (lua_isnoneornil(L, arg+1)) { /* no hook? */ + lua_settop(L, arg+1); + func = NULL; mask = 0; count = 0; /* turn off hooks */ + } + else { + const char *smask = luaL_checkstring(L, arg+2); + luaL_checktype(L, arg+1, LUA_TFUNCTION); + count = (int)luaL_optinteger(L, arg + 3, 0); + func = hookf; mask = makemask(smask, count); + } + if (!luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY)) { + /* table just created; initialize it */ + lua_pushstring(L, "k"); + lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */ + lua_pushvalue(L, -1); + lua_setmetatable(L, -2); /* metatable(hooktable) = hooktable */ + } + checkstack(L, L1, 1); + lua_pushthread(L1); lua_xmove(L1, L, 1); /* key (thread) */ + lua_pushvalue(L, arg + 1); /* value (hook function) */ + lua_rawset(L, -3); /* hooktable[L1] = new Lua hook */ + lua_sethook(L1, func, mask, count); + return 0; +} + + +static int db_gethook (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + char buff[5]; + int mask = lua_gethookmask(L1); + lua_Hook hook = lua_gethook(L1); + if (hook == NULL) { /* no hook? */ + luaL_pushfail(L); + return 1; + } + else if (hook != hookf) /* external hook? */ + lua_pushliteral(L, "external hook"); + else { /* hook table must exist */ + lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY); + checkstack(L, L1, 1); + lua_pushthread(L1); lua_xmove(L1, L, 1); + lua_rawget(L, -2); /* 1st result = hooktable[L1] */ + lua_remove(L, -2); /* remove hook table */ + } + lua_pushstring(L, unmakemask(mask, buff)); /* 2nd result = mask */ + lua_pushinteger(L, lua_gethookcount(L1)); /* 3rd result = count */ + return 3; +} + + +static int db_debug (lua_State *L) { + for (;;) { + char buffer[250]; + lua_writestringerror("%s", "lua_debug> "); + if (fgets(buffer, sizeof(buffer), stdin) == 0 || + strcmp(buffer, "cont\n") == 0) + return 0; + if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") || + lua_pcall(L, 0, 0, 0)) + lua_writestringerror("%s\n", luaL_tolstring(L, -1, NULL)); + lua_settop(L, 0); /* remove eventual returns */ + } +} + + +static int db_traceback (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + const char *msg = lua_tostring(L, arg + 1); + if (msg == NULL && !lua_isnoneornil(L, arg + 1)) /* non-string 'msg'? */ + lua_pushvalue(L, arg + 1); /* return it untouched */ + else { + int level = (int)luaL_optinteger(L, arg + 2, (L == L1) ? 1 : 0); + luaL_traceback(L, L1, msg, level); + } + return 1; +} + + +static int db_setcstacklimit (lua_State *L) { + int limit = (int)luaL_checkinteger(L, 1); + int res = lua_setcstacklimit(L, limit); + lua_pushinteger(L, res); + return 1; +} + + +static const luaL_Reg dblib[] = { + {"debug", db_debug}, + {"getuservalue", db_getuservalue}, + {"gethook", db_gethook}, + {"getinfo", db_getinfo}, + {"getlocal", db_getlocal}, + {"getregistry", db_getregistry}, + {"getmetatable", db_getmetatable}, + {"getupvalue", db_getupvalue}, + {"upvaluejoin", db_upvaluejoin}, + {"upvalueid", db_upvalueid}, + {"setuservalue", db_setuservalue}, + {"sethook", db_sethook}, + {"setlocal", db_setlocal}, + {"setmetatable", db_setmetatable}, + {"setupvalue", db_setupvalue}, + {"traceback", db_traceback}, + {"setcstacklimit", db_setcstacklimit}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_debug (lua_State *L) { + luaL_newlib(L, dblib); + return 1; +} + diff --git a/Lua/ldebug.c b/Lua/ldebug.c new file mode 100644 index 00000000..8cb00e51 --- /dev/null +++ b/Lua/ldebug.c @@ -0,0 +1,852 @@ +/* +** $Id: ldebug.c $ +** Debug Interface +** See Copyright Notice in lua.h +*/ + +#define ldebug_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lapi.h" +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + + +#define noLuaClosure(f) ((f) == NULL || (f)->c.tt == LUA_VCCL) + +/* inverse of 'pcRel' */ +#define invpcRel(pc, p) ((p)->code + (pc) + 1) + +static const char *funcnamefromcode (lua_State *L, CallInfo *ci, + const char **name); + + +static int currentpc (CallInfo *ci) { + lua_assert(isLua(ci)); + return pcRel(ci->u.l.savedpc, ci_func(ci)->p); +} + + +/* +** Get a "base line" to find the line corresponding to an instruction. +** For that, search the array of absolute line info for the largest saved +** instruction smaller or equal to the wanted instruction. A special +** case is when there is no absolute info or the instruction is before +** the first absolute one. +*/ +static int getbaseline (const Proto *f, int pc, int *basepc) { + if (f->sizeabslineinfo == 0 || pc < f->abslineinfo[0].pc) { + *basepc = -1; /* start from the beginning */ + return f->linedefined; + } + else { + unsigned int i; + if (pc >= f->abslineinfo[f->sizeabslineinfo - 1].pc) + i = f->sizeabslineinfo - 1; /* instruction is after last saved one */ + else { /* binary search */ + unsigned int j = f->sizeabslineinfo - 1; /* pc < anchorlines[j] */ + i = 0; /* abslineinfo[i] <= pc */ + while (i < j - 1) { + unsigned int m = (j + i) / 2; + if (pc >= f->abslineinfo[m].pc) + i = m; + else + j = m; + } + } + *basepc = f->abslineinfo[i].pc; + return f->abslineinfo[i].line; + } +} + + +/* +** Get the line corresponding to instruction 'pc' in function 'f'; +** first gets a base line and from there does the increments until +** the desired instruction. +*/ +int luaG_getfuncline (const Proto *f, int pc) { + if (f->lineinfo == NULL) /* no debug information? */ + return -1; + else { + int basepc; + int baseline = getbaseline(f, pc, &basepc); + while (basepc++ < pc) { /* walk until given instruction */ + lua_assert(f->lineinfo[basepc] != ABSLINEINFO); + baseline += f->lineinfo[basepc]; /* correct line */ + } + return baseline; + } +} + + +static int getcurrentline (CallInfo *ci) { + return luaG_getfuncline(ci_func(ci)->p, currentpc(ci)); +} + + +/* +** Set 'trap' for all active Lua frames. +** This function can be called during a signal, under "reasonable" +** assumptions. A new 'ci' is completely linked in the list before it +** becomes part of the "active" list, and we assume that pointers are +** atomic; see comment in next function. +** (A compiler doing interprocedural optimizations could, theoretically, +** reorder memory writes in such a way that the list could be +** temporarily broken while inserting a new element. We simply assume it +** has no good reasons to do that.) +*/ +static void settraps (CallInfo *ci) { + for (; ci != NULL; ci = ci->previous) + if (isLua(ci)) + ci->u.l.trap = 1; +} + + +/* +** This function can be called during a signal, under "reasonable" +** assumptions. +** Fields 'basehookcount' and 'hookcount' (set by 'resethookcount') +** are for debug only, and it is no problem if they get arbitrary +** values (causes at most one wrong hook call). 'hookmask' is an atomic +** value. We assume that pointers are atomic too (e.g., gcc ensures that +** for all platforms where it runs). Moreover, 'hook' is always checked +** before being called (see 'luaD_hook'). +*/ +LUA_API void lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { + if (func == NULL || mask == 0) { /* turn off hooks? */ + mask = 0; + func = NULL; + } + L->hook = func; + L->basehookcount = count; + resethookcount(L); + L->hookmask = cast_byte(mask); + if (mask) + settraps(L->ci); /* to trace inside 'luaV_execute' */ +} + + +LUA_API lua_Hook lua_gethook (lua_State *L) { + return L->hook; +} + + +LUA_API int lua_gethookmask (lua_State *L) { + return L->hookmask; +} + + +LUA_API int lua_gethookcount (lua_State *L) { + return L->basehookcount; +} + + +LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) { + int status; + CallInfo *ci; + if (level < 0) return 0; /* invalid (negative) level */ + lua_lock(L); + for (ci = L->ci; level > 0 && ci != &L->base_ci; ci = ci->previous) + level--; + if (level == 0 && ci != &L->base_ci) { /* level found? */ + status = 1; + ar->i_ci = ci; + } + else status = 0; /* no such level */ + lua_unlock(L); + return status; +} + + +static const char *upvalname (const Proto *p, int uv) { + TString *s = check_exp(uv < p->sizeupvalues, p->upvalues[uv].name); + if (s == NULL) return "?"; + else return getstr(s); +} + + +static const char *findvararg (CallInfo *ci, int n, StkId *pos) { + if (clLvalue(s2v(ci->func))->p->is_vararg) { + int nextra = ci->u.l.nextraargs; + if (n >= -nextra) { /* 'n' is negative */ + *pos = ci->func - nextra - (n + 1); + return "(vararg)"; /* generic name for any vararg */ + } + } + return NULL; /* no such vararg */ +} + + +const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, StkId *pos) { + StkId base = ci->func + 1; + const char *name = NULL; + if (isLua(ci)) { + if (n < 0) /* access to vararg values? */ + return findvararg(ci, n, pos); + else + name = luaF_getlocalname(ci_func(ci)->p, n, currentpc(ci)); + } + if (name == NULL) { /* no 'standard' name? */ + StkId limit = (ci == L->ci) ? L->top : ci->next->func; + if (limit - base >= n && n > 0) { /* is 'n' inside 'ci' stack? */ + /* generic name for any valid slot */ + name = isLua(ci) ? "(temporary)" : "(C temporary)"; + } + else + return NULL; /* no name */ + } + if (pos) + *pos = base + (n - 1); + return name; +} + + +LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { + const char *name; + lua_lock(L); + if (ar == NULL) { /* information about non-active function? */ + if (!isLfunction(s2v(L->top - 1))) /* not a Lua function? */ + name = NULL; + else /* consider live variables at function start (parameters) */ + name = luaF_getlocalname(clLvalue(s2v(L->top - 1))->p, n, 0); + } + else { /* active function; get information through 'ar' */ + StkId pos = NULL; /* to avoid warnings */ + name = luaG_findlocal(L, ar->i_ci, n, &pos); + if (name) { + setobjs2s(L, L->top, pos); + api_incr_top(L); + } + } + lua_unlock(L); + return name; +} + + +LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { + StkId pos = NULL; /* to avoid warnings */ + const char *name; + lua_lock(L); + name = luaG_findlocal(L, ar->i_ci, n, &pos); + if (name) { + setobjs2s(L, pos, L->top - 1); + L->top--; /* pop value */ + } + lua_unlock(L); + return name; +} + + +static void funcinfo (lua_Debug *ar, Closure *cl) { + if (noLuaClosure(cl)) { + ar->source = "=[C]"; + ar->srclen = LL("=[C]"); + ar->linedefined = -1; + ar->lastlinedefined = -1; + ar->what = "C"; + } + else { + const Proto *p = cl->l.p; + if (p->source) { + ar->source = getstr(p->source); + ar->srclen = tsslen(p->source); + } + else { + ar->source = "=?"; + ar->srclen = LL("=?"); + } + ar->linedefined = p->linedefined; + ar->lastlinedefined = p->lastlinedefined; + ar->what = (ar->linedefined == 0) ? "main" : "Lua"; + } + luaO_chunkid(ar->short_src, ar->source, ar->srclen); +} + + +static int nextline (const Proto *p, int currentline, int pc) { + if (p->lineinfo[pc] != ABSLINEINFO) + return currentline + p->lineinfo[pc]; + else + return luaG_getfuncline(p, pc); +} + + +static void collectvalidlines (lua_State *L, Closure *f) { + if (noLuaClosure(f)) { + setnilvalue(s2v(L->top)); + api_incr_top(L); + } + else { + int i; + TValue v; + const Proto *p = f->l.p; + int currentline = p->linedefined; + Table *t = luaH_new(L); /* new table to store active lines */ + sethvalue2s(L, L->top, t); /* push it on stack */ + api_incr_top(L); + setbtvalue(&v); /* boolean 'true' to be the value of all indices */ + for (i = 0; i < p->sizelineinfo; i++) { /* for all lines with code */ + currentline = nextline(p, currentline, i); + luaH_setint(L, t, currentline, &v); /* table[line] = true */ + } + } +} + + +static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { + if (ci == NULL) /* no 'ci'? */ + return NULL; /* no info */ + else if (ci->callstatus & CIST_FIN) { /* is this a finalizer? */ + *name = "__gc"; + return "metamethod"; /* report it as such */ + } + /* calling function is a known Lua function? */ + else if (!(ci->callstatus & CIST_TAIL) && isLua(ci->previous)) + return funcnamefromcode(L, ci->previous, name); + else return NULL; /* no way to find a name */ +} + + +static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, + Closure *f, CallInfo *ci) { + int status = 1; + for (; *what; what++) { + switch (*what) { + case 'S': { + funcinfo(ar, f); + break; + } + case 'l': { + ar->currentline = (ci && isLua(ci)) ? getcurrentline(ci) : -1; + break; + } + case 'u': { + ar->nups = (f == NULL) ? 0 : f->c.nupvalues; + if (noLuaClosure(f)) { + ar->isvararg = 1; + ar->nparams = 0; + } + else { + ar->isvararg = f->l.p->is_vararg; + ar->nparams = f->l.p->numparams; + } + break; + } + case 't': { + ar->istailcall = (ci) ? ci->callstatus & CIST_TAIL : 0; + break; + } + case 'n': { + ar->namewhat = getfuncname(L, ci, &ar->name); + if (ar->namewhat == NULL) { + ar->namewhat = ""; /* not found */ + ar->name = NULL; + } + break; + } + case 'r': { + if (ci == NULL || !(ci->callstatus & CIST_TRAN)) + ar->ftransfer = ar->ntransfer = 0; + else { + ar->ftransfer = ci->u2.transferinfo.ftransfer; + ar->ntransfer = ci->u2.transferinfo.ntransfer; + } + break; + } + case 'L': + case 'f': /* handled by lua_getinfo */ + break; + default: status = 0; /* invalid option */ + } + } + return status; +} + + +LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { + int status; + Closure *cl; + CallInfo *ci; + TValue *func; + lua_lock(L); + if (*what == '>') { + ci = NULL; + func = s2v(L->top - 1); + api_check(L, ttisfunction(func), "function expected"); + what++; /* skip the '>' */ + L->top--; /* pop function */ + } + else { + ci = ar->i_ci; + func = s2v(ci->func); + lua_assert(ttisfunction(func)); + } + cl = ttisclosure(func) ? clvalue(func) : NULL; + status = auxgetinfo(L, what, ar, cl, ci); + if (strchr(what, 'f')) { + setobj2s(L, L->top, func); + api_incr_top(L); + } + if (strchr(what, 'L')) + collectvalidlines(L, cl); + lua_unlock(L); + return status; +} + + +/* +** {====================================================== +** Symbolic Execution +** ======================================================= +*/ + +static const char *getobjname (const Proto *p, int lastpc, int reg, + const char **name); + + +/* +** Find a "name" for the constant 'c'. +*/ +static void kname (const Proto *p, int c, const char **name) { + TValue *kvalue = &p->k[c]; + *name = (ttisstring(kvalue)) ? svalue(kvalue) : "?"; +} + + +/* +** Find a "name" for the register 'c'. +*/ +static void rname (const Proto *p, int pc, int c, const char **name) { + const char *what = getobjname(p, pc, c, name); /* search for 'c' */ + if (!(what && *what == 'c')) /* did not find a constant name? */ + *name = "?"; +} + + +/* +** Find a "name" for a 'C' value in an RK instruction. +*/ +static void rkname (const Proto *p, int pc, Instruction i, const char **name) { + int c = GETARG_C(i); /* key index */ + if (GETARG_k(i)) /* is 'c' a constant? */ + kname(p, c, name); + else /* 'c' is a register */ + rname(p, pc, c, name); +} + + +static int filterpc (int pc, int jmptarget) { + if (pc < jmptarget) /* is code conditional (inside a jump)? */ + return -1; /* cannot know who sets that register */ + else return pc; /* current position sets that register */ +} + + +/* +** Try to find last instruction before 'lastpc' that modified register 'reg'. +*/ +static int findsetreg (const Proto *p, int lastpc, int reg) { + int pc; + int setreg = -1; /* keep last instruction that changed 'reg' */ + int jmptarget = 0; /* any code before this address is conditional */ + if (testMMMode(GET_OPCODE(p->code[lastpc]))) + lastpc--; /* previous instruction was not actually executed */ + for (pc = 0; pc < lastpc; pc++) { + Instruction i = p->code[pc]; + OpCode op = GET_OPCODE(i); + int a = GETARG_A(i); + int change; /* true if current instruction changed 'reg' */ + switch (op) { + case OP_LOADNIL: { /* set registers from 'a' to 'a+b' */ + int b = GETARG_B(i); + change = (a <= reg && reg <= a + b); + break; + } + case OP_TFORCALL: { /* affect all regs above its base */ + change = (reg >= a + 2); + break; + } + case OP_CALL: + case OP_TAILCALL: { /* affect all registers above base */ + change = (reg >= a); + break; + } + case OP_JMP: { /* doesn't change registers, but changes 'jmptarget' */ + int b = GETARG_sJ(i); + int dest = pc + 1 + b; + /* jump does not skip 'lastpc' and is larger than current one? */ + if (dest <= lastpc && dest > jmptarget) + jmptarget = dest; /* update 'jmptarget' */ + change = 0; + break; + } + default: /* any instruction that sets A */ + change = (testAMode(op) && reg == a); + break; + } + if (change) + setreg = filterpc(pc, jmptarget); + } + return setreg; +} + + +/* +** Check whether table being indexed by instruction 'i' is the +** environment '_ENV' +*/ +static const char *gxf (const Proto *p, int pc, Instruction i, int isup) { + int t = GETARG_B(i); /* table index */ + const char *name; /* name of indexed variable */ + if (isup) /* is an upvalue? */ + name = upvalname(p, t); + else + getobjname(p, pc, t, &name); + return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field"; +} + + +static const char *getobjname (const Proto *p, int lastpc, int reg, + const char **name) { + int pc; + *name = luaF_getlocalname(p, reg + 1, lastpc); + if (*name) /* is a local? */ + return "local"; + /* else try symbolic execution */ + pc = findsetreg(p, lastpc, reg); + if (pc != -1) { /* could find instruction? */ + Instruction i = p->code[pc]; + OpCode op = GET_OPCODE(i); + switch (op) { + case OP_MOVE: { + int b = GETARG_B(i); /* move from 'b' to 'a' */ + if (b < GETARG_A(i)) + return getobjname(p, pc, b, name); /* get name for 'b' */ + break; + } + case OP_GETTABUP: { + int k = GETARG_C(i); /* key index */ + kname(p, k, name); + return gxf(p, pc, i, 1); + } + case OP_GETTABLE: { + int k = GETARG_C(i); /* key index */ + rname(p, pc, k, name); + return gxf(p, pc, i, 0); + } + case OP_GETI: { + *name = "integer index"; + return "field"; + } + case OP_GETFIELD: { + int k = GETARG_C(i); /* key index */ + kname(p, k, name); + return gxf(p, pc, i, 0); + } + case OP_GETUPVAL: { + *name = upvalname(p, GETARG_B(i)); + return "upvalue"; + } + case OP_LOADK: + case OP_LOADKX: { + int b = (op == OP_LOADK) ? GETARG_Bx(i) + : GETARG_Ax(p->code[pc + 1]); + if (ttisstring(&p->k[b])) { + *name = svalue(&p->k[b]); + return "constant"; + } + break; + } + case OP_SELF: { + rkname(p, pc, i, name); + return "method"; + } + default: break; /* go through to return NULL */ + } + } + return NULL; /* could not find reasonable name */ +} + + +/* +** Try to find a name for a function based on the code that called it. +** (Only works when function was called by a Lua function.) +** Returns what the name is (e.g., "for iterator", "method", +** "metamethod") and sets '*name' to point to the name. +*/ +static const char *funcnamefromcode (lua_State *L, CallInfo *ci, + const char **name) { + TMS tm = (TMS)0; /* (initial value avoids warnings) */ + const Proto *p = ci_func(ci)->p; /* calling function */ + int pc = currentpc(ci); /* calling instruction index */ + Instruction i = p->code[pc]; /* calling instruction */ + if (ci->callstatus & CIST_HOOKED) { /* was it called inside a hook? */ + *name = "?"; + return "hook"; + } + switch (GET_OPCODE(i)) { + case OP_CALL: + case OP_TAILCALL: + return getobjname(p, pc, GETARG_A(i), name); /* get function name */ + case OP_TFORCALL: { /* for iterator */ + *name = "for iterator"; + return "for iterator"; + } + /* other instructions can do calls through metamethods */ + case OP_SELF: case OP_GETTABUP: case OP_GETTABLE: + case OP_GETI: case OP_GETFIELD: + tm = TM_INDEX; + break; + case OP_SETTABUP: case OP_SETTABLE: case OP_SETI: case OP_SETFIELD: + tm = TM_NEWINDEX; + break; + case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: { + tm = cast(TMS, GETARG_C(i)); + break; + } + case OP_UNM: tm = TM_UNM; break; + case OP_BNOT: tm = TM_BNOT; break; + case OP_LEN: tm = TM_LEN; break; + case OP_CONCAT: tm = TM_CONCAT; break; + case OP_EQ: tm = TM_EQ; break; + case OP_LT: case OP_LE: case OP_LTI: case OP_LEI: + *name = "order"; /* '<=' can call '__lt', etc. */ + return "metamethod"; + case OP_CLOSE: case OP_RETURN: + *name = "close"; + return "metamethod"; + default: + return NULL; /* cannot find a reasonable name */ + } + *name = getstr(G(L)->tmname[tm]) + 2; + return "metamethod"; +} + +/* }====================================================== */ + + + +/* +** The subtraction of two potentially unrelated pointers is +** not ISO C, but it should not crash a program; the subsequent +** checks are ISO C and ensure a correct result. +*/ +static int isinstack (CallInfo *ci, const TValue *o) { + StkId base = ci->func + 1; + ptrdiff_t i = cast(StkId, o) - base; + return (0 <= i && i < (ci->top - base) && s2v(base + i) == o); +} + + +/* +** Checks whether value 'o' came from an upvalue. (That can only happen +** with instructions OP_GETTABUP/OP_SETTABUP, which operate directly on +** upvalues.) +*/ +static const char *getupvalname (CallInfo *ci, const TValue *o, + const char **name) { + LClosure *c = ci_func(ci); + int i; + for (i = 0; i < c->nupvalues; i++) { + if (c->upvals[i]->v == o) { + *name = upvalname(c->p, i); + return "upvalue"; + } + } + return NULL; +} + + +static const char *varinfo (lua_State *L, const TValue *o) { + const char *name = NULL; /* to avoid warnings */ + CallInfo *ci = L->ci; + const char *kind = NULL; + if (isLua(ci)) { + kind = getupvalname(ci, o, &name); /* check whether 'o' is an upvalue */ + if (!kind && isinstack(ci, o)) /* no? try a register */ + kind = getobjname(ci_func(ci)->p, currentpc(ci), + cast_int(cast(StkId, o) - (ci->func + 1)), &name); + } + return (kind) ? luaO_pushfstring(L, " (%s '%s')", kind, name) : ""; +} + + +l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { + const char *t = luaT_objtypename(L, o); + luaG_runerror(L, "attempt to %s a %s value%s", op, t, varinfo(L, o)); +} + + +l_noret luaG_forerror (lua_State *L, const TValue *o, const char *what) { + luaG_runerror(L, "bad 'for' %s (number expected, got %s)", + what, luaT_objtypename(L, o)); +} + + +l_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2) { + if (ttisstring(p1) || cvt2str(p1)) p1 = p2; + luaG_typeerror(L, p1, "concatenate"); +} + + +l_noret luaG_opinterror (lua_State *L, const TValue *p1, + const TValue *p2, const char *msg) { + if (!ttisnumber(p1)) /* first operand is wrong? */ + p2 = p1; /* now second is wrong */ + luaG_typeerror(L, p2, msg); +} + + +/* +** Error when both values are convertible to numbers, but not to integers +*/ +l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2) { + lua_Integer temp; + if (!tointegerns(p1, &temp)) + p2 = p1; + luaG_runerror(L, "number%s has no integer representation", varinfo(L, p2)); +} + + +l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { + const char *t1 = luaT_objtypename(L, p1); + const char *t2 = luaT_objtypename(L, p2); + if (strcmp(t1, t2) == 0) + luaG_runerror(L, "attempt to compare two %s values", t1); + else + luaG_runerror(L, "attempt to compare %s with %s", t1, t2); +} + + +/* add src:line information to 'msg' */ +const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, + int line) { + char buff[LUA_IDSIZE]; + if (src) + luaO_chunkid(buff, getstr(src), tsslen(src)); + else { /* no source available; use "?" instead */ + buff[0] = '?'; buff[1] = '\0'; + } + return luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); +} + + +l_noret luaG_errormsg (lua_State *L) { + if (L->errfunc != 0) { /* is there an error handling function? */ + StkId errfunc = restorestack(L, L->errfunc); + lua_assert(ttisfunction(s2v(errfunc))); + setobjs2s(L, L->top, L->top - 1); /* move argument */ + setobjs2s(L, L->top - 1, errfunc); /* push function */ + L->top++; /* assume EXTRA_STACK */ + luaD_callnoyield(L, L->top - 2, 1); /* call it */ + } + luaD_throw(L, LUA_ERRRUN); +} + + +l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { + CallInfo *ci = L->ci; + const char *msg; + va_list argp; + luaC_checkGC(L); /* error message uses memory */ + va_start(argp, fmt); + msg = luaO_pushvfstring(L, fmt, argp); /* format message */ + va_end(argp); + if (isLua(ci)) /* if Lua function, add source:line information */ + luaG_addinfo(L, msg, ci_func(ci)->p->source, getcurrentline(ci)); + luaG_errormsg(L); +} + + +/* +** Check whether new instruction 'newpc' is in a different line from +** previous instruction 'oldpc'. +*/ +static int changedline (const Proto *p, int oldpc, int newpc) { + if (p->lineinfo == NULL) /* no debug information? */ + return 0; + while (oldpc++ < newpc) { + if (p->lineinfo[oldpc] != 0) + return (luaG_getfuncline(p, oldpc - 1) != luaG_getfuncline(p, newpc)); + } + return 0; /* no line changes between positions */ +} + + +/* +** Traces the execution of a Lua function. Called before the execution +** of each opcode, when debug is on. 'L->oldpc' stores the last +** instruction traced, to detect line changes. When entering a new +** function, 'npci' will be zero and will test as a new line without +** the need for 'oldpc'; so, 'oldpc' does not need to be initialized +** before. Some exceptional conditions may return to a function without +** updating 'oldpc'. In that case, 'oldpc' may be invalid; if so, it is +** reset to zero. (A wrong but valid 'oldpc' at most causes an extra +** call to a line hook.) +*/ +int luaG_traceexec (lua_State *L, const Instruction *pc) { + CallInfo *ci = L->ci; + lu_byte mask = L->hookmask; + const Proto *p = ci_func(ci)->p; + int counthook; + /* 'L->oldpc' may be invalid; reset it in this case */ + int oldpc = (L->oldpc < p->sizecode) ? L->oldpc : 0; + if (!(mask & (LUA_MASKLINE | LUA_MASKCOUNT))) { /* no hooks? */ + ci->u.l.trap = 0; /* don't need to stop again */ + return 0; /* turn off 'trap' */ + } + pc++; /* reference is always next instruction */ + ci->u.l.savedpc = pc; /* save 'pc' */ + counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT)); + if (counthook) + resethookcount(L); /* reset count */ + else if (!(mask & LUA_MASKLINE)) + return 1; /* no line hook and count != 0; nothing to be done now */ + if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ + ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ + return 1; /* do not call hook again (VM yielded, so it did not move) */ + } + if (!isIT(*(ci->u.l.savedpc - 1))) + L->top = ci->top; /* prepare top */ + if (counthook) + luaD_hook(L, LUA_HOOKCOUNT, -1, 0, 0); /* call count hook */ + if (mask & LUA_MASKLINE) { + int npci = pcRel(pc, p); + if (npci == 0 || /* call linehook when enter a new function, */ + pc <= invpcRel(oldpc, p) || /* when jump back (loop), or when */ + changedline(p, oldpc, npci)) { /* enter new line */ + int newline = luaG_getfuncline(p, npci); + luaD_hook(L, LUA_HOOKLINE, newline, 0, 0); /* call line hook */ + } + L->oldpc = npci; /* 'pc' of last call to line hook */ + } + if (L->status == LUA_YIELD) { /* did hook yield? */ + if (counthook) + L->hookcount = 1; /* undo decrement to zero */ + ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ + ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ + luaD_throw(L, LUA_YIELD); + } + return 1; /* keep 'trap' on */ +} + diff --git a/Lua/ldebug.h b/Lua/ldebug.h new file mode 100644 index 00000000..a0a58486 --- /dev/null +++ b/Lua/ldebug.h @@ -0,0 +1,52 @@ +/* +** $Id: ldebug.h $ +** Auxiliary functions from Debug Interface module +** See Copyright Notice in lua.h +*/ + +#ifndef ldebug_h +#define ldebug_h + + +#include "lstate.h" + + +#define pcRel(pc, p) (cast_int((pc) - (p)->code) - 1) + + +/* Active Lua function (given call info) */ +#define ci_func(ci) (clLvalue(s2v((ci)->func))) + + +#define resethookcount(L) (L->hookcount = L->basehookcount) + +/* +** mark for entries in 'lineinfo' array that has absolute information in +** 'abslineinfo' array +*/ +#define ABSLINEINFO (-0x80) + +LUAI_FUNC int luaG_getfuncline (const Proto *f, int pc); +LUAI_FUNC const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, + StkId *pos); +LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o, + const char *opname); +LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o, + const char *what); +LUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_opinterror (lua_State *L, const TValue *p1, + const TValue *p2, + const char *msg); +LUAI_FUNC l_noret luaG_tointerror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...); +LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg, + TString *src, int line); +LUAI_FUNC l_noret luaG_errormsg (lua_State *L); +LUAI_FUNC int luaG_traceexec (lua_State *L, const Instruction *pc); + + +#endif diff --git a/Lua/ldo.c b/Lua/ldo.c new file mode 100644 index 00000000..4b55c31c --- /dev/null +++ b/Lua/ldo.c @@ -0,0 +1,857 @@ +/* +** $Id: ldo.c $ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + +#define ldo_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lundump.h" +#include "lvm.h" +#include "lzio.h" + + + +#define errorstatus(s) ((s) > LUA_YIELD) + + +/* +** {====================================================== +** Error-recovery functions +** ======================================================= +*/ + +/* +** LUAI_THROW/LUAI_TRY define how Lua does exception handling. By +** default, Lua handles errors with exceptions when compiling as +** C++ code, with _longjmp/_setjmp when asked to use them, and with +** longjmp/setjmp otherwise. +*/ +#if !defined(LUAI_THROW) /* { */ + +#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) /* { */ + +/* C++ exceptions */ +#define LUAI_THROW(L,c) throw(c) +#define LUAI_TRY(L,c,a) \ + try { a } catch(...) { if ((c)->status == 0) (c)->status = -1; } +#define luai_jmpbuf int /* dummy variable */ + +#elif defined(LUA_USE_POSIX) /* }{ */ + +/* in POSIX, try _longjmp/_setjmp (more efficient) */ +#define LUAI_THROW(L,c) _longjmp((c)->b, 1) +#define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } +#define luai_jmpbuf jmp_buf + +#else /* }{ */ + +/* ISO C handling with long jumps */ +#define LUAI_THROW(L,c) longjmp((c)->b, 1) +#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } +#define luai_jmpbuf jmp_buf + +#endif /* } */ + +#endif /* } */ + + + +/* chain list of long jump buffers */ +struct lua_longjmp { + struct lua_longjmp *previous; + luai_jmpbuf b; + volatile int status; /* error code */ +}; + + +void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { + switch (errcode) { + case LUA_ERRMEM: { /* memory error? */ + setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ + break; + } + case LUA_ERRERR: { + setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); + break; + } + case CLOSEPROTECT: { + setnilvalue(s2v(oldtop)); /* no error message */ + break; + } + default: { + setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ + break; + } + } + L->top = oldtop + 1; +} + + +l_noret luaD_throw (lua_State *L, int errcode) { + if (L->errorJmp) { /* thread has an error handler? */ + L->errorJmp->status = errcode; /* set status */ + LUAI_THROW(L, L->errorJmp); /* jump to it */ + } + else { /* thread has no error handler */ + global_State *g = G(L); + errcode = luaF_close(L, L->stack, errcode); /* close all upvalues */ + L->status = cast_byte(errcode); /* mark it as dead */ + if (g->mainthread->errorJmp) { /* main thread has a handler? */ + setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */ + luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ + } + else { /* no handler at all; abort */ + if (g->panic) { /* panic function? */ + luaD_seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */ + if (L->ci->top < L->top) + L->ci->top = L->top; /* pushing msg. can break this invariant */ + lua_unlock(L); + g->panic(L); /* call panic function (last chance to jump out) */ + } + abort(); + } + } +} + + +int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { + l_uint32 oldnCcalls = L->nCcalls; + struct lua_longjmp lj; + lj.status = LUA_OK; + lj.previous = L->errorJmp; /* chain new error handler */ + L->errorJmp = &lj; + LUAI_TRY(L, &lj, + (*f)(L, ud); + ); + L->errorJmp = lj.previous; /* restore old error handler */ + L->nCcalls = oldnCcalls; + return lj.status; +} + +/* }====================================================== */ + + +/* +** {================================================================== +** Stack reallocation +** =================================================================== +*/ +static void correctstack (lua_State *L, StkId oldstack, StkId newstack) { + CallInfo *ci; + UpVal *up; + if (oldstack == newstack) + return; /* stack address did not change */ + L->top = (L->top - oldstack) + newstack; + for (up = L->openupval; up != NULL; up = up->u.open.next) + up->v = s2v((uplevel(up) - oldstack) + newstack); + for (ci = L->ci; ci != NULL; ci = ci->previous) { + ci->top = (ci->top - oldstack) + newstack; + ci->func = (ci->func - oldstack) + newstack; + if (isLua(ci)) + ci->u.l.trap = 1; /* signal to update 'trap' in 'luaV_execute' */ + } +} + + +/* some space for error handling */ +#define ERRORSTACKSIZE (LUAI_MAXSTACK + 200) + + +int luaD_reallocstack (lua_State *L, int newsize, int raiseerror) { + int lim = stacksize(L); + StkId newstack = luaM_reallocvector(L, L->stack, + lim + EXTRA_STACK, newsize + EXTRA_STACK, StackValue); + lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE); + if (unlikely(newstack == NULL)) { /* reallocation failed? */ + if (raiseerror) + luaM_error(L); + else return 0; /* do not raise an error */ + } + for (; lim < newsize; lim++) + setnilvalue(s2v(newstack + lim + EXTRA_STACK)); /* erase new segment */ + correctstack(L, L->stack, newstack); + L->stack = newstack; + L->stack_last = L->stack + newsize; + return 1; +} + + +/* +** Try to grow the stack by at least 'n' elements. when 'raiseerror' +** is true, raises any error; otherwise, return 0 in case of errors. +*/ +int luaD_growstack (lua_State *L, int n, int raiseerror) { + int size = stacksize(L); + if (unlikely(size > LUAI_MAXSTACK)) { + /* if stack is larger than maximum, thread is already using the + extra space reserved for errors, that is, thread is handling + a stack error; cannot grow further than that. */ + lua_assert(stacksize(L) == ERRORSTACKSIZE); + if (raiseerror) + luaD_throw(L, LUA_ERRERR); /* error inside message handler */ + return 0; /* if not 'raiseerror', just signal it */ + } + else { + int newsize = 2 * size; /* tentative new size */ + int needed = cast_int(L->top - L->stack) + n; + if (newsize > LUAI_MAXSTACK) /* cannot cross the limit */ + newsize = LUAI_MAXSTACK; + if (newsize < needed) /* but must respect what was asked for */ + newsize = needed; + if (likely(newsize <= LUAI_MAXSTACK)) + return luaD_reallocstack(L, newsize, raiseerror); + else { /* stack overflow */ + /* add extra size to be able to handle the error message */ + luaD_reallocstack(L, ERRORSTACKSIZE, raiseerror); + if (raiseerror) + luaG_runerror(L, "stack overflow"); + return 0; + } + } +} + + +static int stackinuse (lua_State *L) { + CallInfo *ci; + int res; + StkId lim = L->top; + for (ci = L->ci; ci != NULL; ci = ci->previous) { + if (lim < ci->top) lim = ci->top; + } + lua_assert(lim <= L->stack_last); + res = cast_int(lim - L->stack) + 1; /* part of stack in use */ + if (res < LUA_MINSTACK) + res = LUA_MINSTACK; /* ensure a minimum size */ + return res; +} + + +/* +** If stack size is more than 3 times the current use, reduce that size +** to twice the current use. (So, the final stack size is at most 2/3 the +** previous size, and half of its entries are empty.) +** As a particular case, if stack was handling a stack overflow and now +** it is not, 'max' (limited by LUAI_MAXSTACK) will be smaller than +** stacksize (equal to ERRORSTACKSIZE in this case), and so the stack +** will be reduced to a "regular" size. +*/ +void luaD_shrinkstack (lua_State *L) { + int inuse = stackinuse(L); + int nsize = inuse * 2; /* proposed new size */ + int max = inuse * 3; /* maximum "reasonable" size */ + if (max > LUAI_MAXSTACK) { + max = LUAI_MAXSTACK; /* respect stack limit */ + if (nsize > LUAI_MAXSTACK) + nsize = LUAI_MAXSTACK; + } + /* if thread is currently not handling a stack overflow and its + size is larger than maximum "reasonable" size, shrink it */ + if (inuse <= LUAI_MAXSTACK && stacksize(L) > max) + luaD_reallocstack(L, nsize, 0); /* ok if that fails */ + else /* don't change stack */ + condmovestack(L,{},{}); /* (change only for debugging) */ + luaE_shrinkCI(L); /* shrink CI list */ +} + + +void luaD_inctop (lua_State *L) { + luaD_checkstack(L, 1); + L->top++; +} + +/* }================================================================== */ + + +/* +** Call a hook for the given event. Make sure there is a hook to be +** called. (Both 'L->hook' and 'L->hookmask', which trigger this +** function, can be changed asynchronously by signals.) +*/ +void luaD_hook (lua_State *L, int event, int line, + int ftransfer, int ntransfer) { + lua_Hook hook = L->hook; + if (hook && L->allowhook) { /* make sure there is a hook */ + int mask = CIST_HOOKED; + CallInfo *ci = L->ci; + ptrdiff_t top = savestack(L, L->top); + ptrdiff_t ci_top = savestack(L, ci->top); + lua_Debug ar; + ar.event = event; + ar.currentline = line; + ar.i_ci = ci; + if (ntransfer != 0) { + mask |= CIST_TRAN; /* 'ci' has transfer information */ + ci->u2.transferinfo.ftransfer = ftransfer; + ci->u2.transferinfo.ntransfer = ntransfer; + } + luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + if (L->top + LUA_MINSTACK > ci->top) + ci->top = L->top + LUA_MINSTACK; + L->allowhook = 0; /* cannot call hooks inside a hook */ + ci->callstatus |= mask; + lua_unlock(L); + (*hook)(L, &ar); + lua_lock(L); + lua_assert(!L->allowhook); + L->allowhook = 1; + ci->top = restorestack(L, ci_top); + L->top = restorestack(L, top); + ci->callstatus &= ~mask; + } +} + + +/* +** Executes a call hook for Lua functions. This function is called +** whenever 'hookmask' is not zero, so it checks whether call hooks are +** active. +*/ +void luaD_hookcall (lua_State *L, CallInfo *ci) { + int hook = (ci->callstatus & CIST_TAIL) ? LUA_HOOKTAILCALL : LUA_HOOKCALL; + Proto *p; + if (!(L->hookmask & LUA_MASKCALL)) /* some other hook? */ + return; /* don't call hook */ + p = clLvalue(s2v(ci->func))->p; + L->top = ci->top; /* prepare top */ + ci->u.l.savedpc++; /* hooks assume 'pc' is already incremented */ + luaD_hook(L, hook, -1, 1, p->numparams); + ci->u.l.savedpc--; /* correct 'pc' */ +} + + +static StkId rethook (lua_State *L, CallInfo *ci, StkId firstres, int nres) { + ptrdiff_t oldtop = savestack(L, L->top); /* hook may change top */ + int delta = 0; + if (isLuacode(ci)) { + Proto *p = ci_func(ci)->p; + if (p->is_vararg) + delta = ci->u.l.nextraargs + p->numparams + 1; + if (L->top < ci->top) + L->top = ci->top; /* correct top to run hook */ + } + if (L->hookmask & LUA_MASKRET) { /* is return hook on? */ + int ftransfer; + ci->func += delta; /* if vararg, back to virtual 'func' */ + ftransfer = cast(unsigned short, firstres - ci->func); + luaD_hook(L, LUA_HOOKRET, -1, ftransfer, nres); /* call it */ + ci->func -= delta; + } + if (isLua(ci = ci->previous)) + L->oldpc = pcRel(ci->u.l.savedpc, ci_func(ci)->p); /* update 'oldpc' */ + return restorestack(L, oldtop); +} + + +/* +** Check whether 'func' has a '__call' metafield. If so, put it in the +** stack, below original 'func', so that 'luaD_precall' can call it. Raise +** an error if there is no '__call' metafield. +*/ +void luaD_tryfuncTM (lua_State *L, StkId func) { + const TValue *tm = luaT_gettmbyobj(L, s2v(func), TM_CALL); + StkId p; + if (unlikely(ttisnil(tm))) + luaG_typeerror(L, s2v(func), "call"); /* nothing to call */ + for (p = L->top; p > func; p--) /* open space for metamethod */ + setobjs2s(L, p, p-1); + L->top++; /* stack space pre-allocated by the caller */ + setobj2s(L, func, tm); /* metamethod is the new function to be called */ +} + + +/* +** Given 'nres' results at 'firstResult', move 'wanted' of them to 'res'. +** Handle most typical cases (zero results for commands, one result for +** expressions, multiple results for tail calls/single parameters) +** separated. +*/ +static void moveresults (lua_State *L, StkId res, int nres, int wanted) { + StkId firstresult; + int i; + switch (wanted) { /* handle typical cases separately */ + case 0: /* no values needed */ + L->top = res; + return; + case 1: /* one value needed */ + if (nres == 0) /* no results? */ + setnilvalue(s2v(res)); /* adjust with nil */ + else + setobjs2s(L, res, L->top - nres); /* move it to proper place */ + L->top = res + 1; + return; + case LUA_MULTRET: + wanted = nres; /* we want all results */ + break; + default: /* multiple results (or to-be-closed variables) */ + if (hastocloseCfunc(wanted)) { /* to-be-closed variables? */ + ptrdiff_t savedres = savestack(L, res); + luaF_close(L, res, LUA_OK); /* may change the stack */ + res = restorestack(L, savedres); + wanted = codeNresults(wanted); /* correct value */ + if (wanted == LUA_MULTRET) + wanted = nres; + } + break; + } + firstresult = L->top - nres; /* index of first result */ + /* move all results to correct place */ + for (i = 0; i < nres && i < wanted; i++) + setobjs2s(L, res + i, firstresult + i); + for (; i < wanted; i++) /* complete wanted number of results */ + setnilvalue(s2v(res + i)); + L->top = res + wanted; /* top points after the last result */ +} + + +/* +** Finishes a function call: calls hook if necessary, removes CallInfo, +** moves current number of results to proper place. +*/ +void luaD_poscall (lua_State *L, CallInfo *ci, int nres) { + if (L->hookmask) + L->top = rethook(L, ci, L->top - nres, nres); + L->ci = ci->previous; /* back to caller */ + /* move results to proper place */ + moveresults(L, ci->func, nres, ci->nresults); +} + + + +#define next_ci(L) (L->ci->next ? L->ci->next : luaE_extendCI(L)) + + +/* +** Prepare a function for a tail call, building its call info on top +** of the current call info. 'narg1' is the number of arguments plus 1 +** (so that it includes the function itself). +*/ +void luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, int narg1) { + Proto *p = clLvalue(s2v(func))->p; + int fsize = p->maxstacksize; /* frame size */ + int nfixparams = p->numparams; + int i; + for (i = 0; i < narg1; i++) /* move down function and arguments */ + setobjs2s(L, ci->func + i, func + i); + checkstackGC(L, fsize); + func = ci->func; /* moved-down function */ + for (; narg1 <= nfixparams; narg1++) + setnilvalue(s2v(func + narg1)); /* complete missing arguments */ + ci->top = func + 1 + fsize; /* top for new function */ + lua_assert(ci->top <= L->stack_last); + ci->u.l.savedpc = p->code; /* starting point */ + ci->callstatus |= CIST_TAIL; + L->top = func + narg1; /* set top */ +} + + +/* +** Prepares the call to a function (C or Lua). For C functions, also do +** the call. The function to be called is at '*func'. The arguments +** are on the stack, right after the function. Returns the CallInfo +** to be executed, if it was a Lua function. Otherwise (a C function) +** returns NULL, with all the results on the stack, starting at the +** original function position. +*/ +CallInfo *luaD_precall (lua_State *L, StkId func, int nresults) { + lua_CFunction f; + retry: + switch (ttypetag(s2v(func))) { + case LUA_VCCL: /* C closure */ + f = clCvalue(s2v(func))->f; + goto Cfunc; + case LUA_VLCF: /* light C function */ + f = fvalue(s2v(func)); + Cfunc: { + int n; /* number of returns */ + CallInfo *ci; + checkstackGCp(L, LUA_MINSTACK, func); /* ensure minimum stack size */ + L->ci = ci = next_ci(L); + ci->nresults = nresults; + ci->callstatus = CIST_C; + ci->top = L->top + LUA_MINSTACK; + ci->func = func; + lua_assert(ci->top <= L->stack_last); + if (L->hookmask & LUA_MASKCALL) { + int narg = cast_int(L->top - func) - 1; + luaD_hook(L, LUA_HOOKCALL, -1, 1, narg); + } + lua_unlock(L); + n = (*f)(L); /* do the actual call */ + lua_lock(L); + api_checknelems(L, n); + luaD_poscall(L, ci, n); + return NULL; + } + case LUA_VLCL: { /* Lua function */ + CallInfo *ci; + Proto *p = clLvalue(s2v(func))->p; + int narg = cast_int(L->top - func) - 1; /* number of real arguments */ + int nfixparams = p->numparams; + int fsize = p->maxstacksize; /* frame size */ + checkstackGCp(L, fsize, func); + L->ci = ci = next_ci(L); + ci->nresults = nresults; + ci->u.l.savedpc = p->code; /* starting point */ + ci->top = func + 1 + fsize; + ci->func = func; + L->ci = ci; + for (; narg < nfixparams; narg++) + setnilvalue(s2v(L->top++)); /* complete missing arguments */ + lua_assert(ci->top <= L->stack_last); + return ci; + } + default: { /* not a function */ + checkstackGCp(L, 1, func); /* space for metamethod */ + luaD_tryfuncTM(L, func); /* try to get '__call' metamethod */ + goto retry; /* try again with metamethod */ + } + } +} + + +/* +** Call a function (C or Lua) through C. 'inc' can be 1 (increment +** number of recursive invocations in the C stack) or nyci (the same +** plus increment number of non-yieldable calls). +*/ +static void ccall (lua_State *L, StkId func, int nResults, int inc) { + CallInfo *ci; + L->nCcalls += inc; + if (unlikely(getCcalls(L) >= LUAI_MAXCCALLS)) + luaE_checkcstack(L); + if ((ci = luaD_precall(L, func, nResults)) != NULL) { /* Lua function? */ + ci->callstatus = CIST_FRESH; /* mark that it is a "fresh" execute */ + luaV_execute(L, ci); /* call it */ + } + L->nCcalls -= inc; +} + + +/* +** External interface for 'ccall' +*/ +void luaD_call (lua_State *L, StkId func, int nResults) { + ccall(L, func, nResults, 1); +} + + +/* +** Similar to 'luaD_call', but does not allow yields during the call. +*/ +void luaD_callnoyield (lua_State *L, StkId func, int nResults) { + ccall(L, func, nResults, nyci); +} + + +/* +** Completes the execution of an interrupted C function, calling its +** continuation function. +*/ +static void finishCcall (lua_State *L, int status) { + CallInfo *ci = L->ci; + int n; + /* must have a continuation and must be able to call it */ + lua_assert(ci->u.c.k != NULL && yieldable(L)); + /* error status can only happen in a protected call */ + lua_assert((ci->callstatus & CIST_YPCALL) || status == LUA_YIELD); + if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */ + ci->callstatus &= ~CIST_YPCALL; /* continuation is also inside it */ + L->errfunc = ci->u.c.old_errfunc; /* with the same error function */ + } + /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already + handled */ + adjustresults(L, ci->nresults); + lua_unlock(L); + n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation function */ + lua_lock(L); + api_checknelems(L, n); + luaD_poscall(L, ci, n); /* finish 'luaD_call' */ +} + + +/* +** Executes "full continuation" (everything in the stack) of a +** previously interrupted coroutine until the stack is empty (or another +** interruption long-jumps out of the loop). If the coroutine is +** recovering from an error, 'ud' points to the error status, which must +** be passed to the first continuation function (otherwise the default +** status is LUA_YIELD). +*/ +static void unroll (lua_State *L, void *ud) { + CallInfo *ci; + if (ud != NULL) /* error status? */ + finishCcall(L, *(int *)ud); /* finish 'lua_pcallk' callee */ + while ((ci = L->ci) != &L->base_ci) { /* something in the stack */ + if (!isLua(ci)) /* C function? */ + finishCcall(L, LUA_YIELD); /* complete its execution */ + else { /* Lua function */ + luaV_finishOp(L); /* finish interrupted instruction */ + luaV_execute(L, ci); /* execute down to higher C 'boundary' */ + } + } +} + + +/* +** Try to find a suspended protected call (a "recover point") for the +** given thread. +*/ +static CallInfo *findpcall (lua_State *L) { + CallInfo *ci; + for (ci = L->ci; ci != NULL; ci = ci->previous) { /* search for a pcall */ + if (ci->callstatus & CIST_YPCALL) + return ci; + } + return NULL; /* no pending pcall */ +} + + +/* +** Recovers from an error in a coroutine. Finds a recover point (if +** there is one) and completes the execution of the interrupted +** 'luaD_pcall'. If there is no recover point, returns zero. +*/ +static int recover (lua_State *L, int status) { + StkId oldtop; + CallInfo *ci = findpcall(L); + if (ci == NULL) return 0; /* no recovery point */ + /* "finish" luaD_pcall */ + oldtop = restorestack(L, ci->u2.funcidx); + L->ci = ci; + L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ + status = luaF_close(L, oldtop, status); /* may change the stack */ + oldtop = restorestack(L, ci->u2.funcidx); + luaD_seterrorobj(L, status, oldtop); + luaD_shrinkstack(L); /* restore stack size in case of overflow */ + L->errfunc = ci->u.c.old_errfunc; + return 1; /* continue running the coroutine */ +} + + +/* +** Signal an error in the call to 'lua_resume', not in the execution +** of the coroutine itself. (Such errors should not be handled by any +** coroutine error handler and should not kill the coroutine.) +*/ +static int resume_error (lua_State *L, const char *msg, int narg) { + L->top -= narg; /* remove args from the stack */ + setsvalue2s(L, L->top, luaS_new(L, msg)); /* push error message */ + api_incr_top(L); + lua_unlock(L); + return LUA_ERRRUN; +} + + +/* +** Do the work for 'lua_resume' in protected mode. Most of the work +** depends on the status of the coroutine: initial state, suspended +** inside a hook, or regularly suspended (optionally with a continuation +** function), plus erroneous cases: non-suspended coroutine or dead +** coroutine. +*/ +static void resume (lua_State *L, void *ud) { + int n = *(cast(int*, ud)); /* number of arguments */ + StkId firstArg = L->top - n; /* first argument */ + CallInfo *ci = L->ci; + if (L->status == LUA_OK) /* starting a coroutine? */ + ccall(L, firstArg - 1, LUA_MULTRET, 1); /* just call its body */ + else { /* resuming from previous yield */ + lua_assert(L->status == LUA_YIELD); + L->status = LUA_OK; /* mark that it is running (again) */ + luaE_incCstack(L); /* control the C stack */ + if (isLua(ci)) /* yielded inside a hook? */ + luaV_execute(L, ci); /* just continue running Lua code */ + else { /* 'common' yield */ + if (ci->u.c.k != NULL) { /* does it have a continuation function? */ + lua_unlock(L); + n = (*ci->u.c.k)(L, LUA_YIELD, ci->u.c.ctx); /* call continuation */ + lua_lock(L); + api_checknelems(L, n); + } + luaD_poscall(L, ci, n); /* finish 'luaD_call' */ + } + unroll(L, NULL); /* run continuation */ + } +} + +LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, + int *nresults) { + int status; + lua_lock(L); + if (L->status == LUA_OK) { /* may be starting a coroutine */ + if (L->ci != &L->base_ci) /* not in base level? */ + return resume_error(L, "cannot resume non-suspended coroutine", nargs); + else if (L->top - (L->ci->func + 1) == nargs) /* no function? */ + return resume_error(L, "cannot resume dead coroutine", nargs); + } + else if (L->status != LUA_YIELD) /* ended with errors? */ + return resume_error(L, "cannot resume dead coroutine", nargs); + L->nCcalls = (from) ? getCcalls(from) : 0; + luai_userstateresume(L, nargs); + api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); + status = luaD_rawrunprotected(L, resume, &nargs); + /* continue running after recoverable errors */ + while (errorstatus(status) && recover(L, status)) { + /* unroll continuation */ + status = luaD_rawrunprotected(L, unroll, &status); + } + if (likely(!errorstatus(status))) + lua_assert(status == L->status); /* normal end or yield */ + else { /* unrecoverable error */ + L->status = cast_byte(status); /* mark thread as 'dead' */ + luaD_seterrorobj(L, status, L->top); /* push error message */ + L->ci->top = L->top; + } + *nresults = (status == LUA_YIELD) ? L->ci->u2.nyield + : cast_int(L->top - (L->ci->func + 1)); + lua_unlock(L); + return status; +} + + +LUA_API int lua_isyieldable (lua_State *L) { + return yieldable(L); +} + + +LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k) { + CallInfo *ci; + luai_userstateyield(L, nresults); + lua_lock(L); + ci = L->ci; + api_checknelems(L, nresults); + if (unlikely(!yieldable(L))) { + if (L != G(L)->mainthread) + luaG_runerror(L, "attempt to yield across a C-call boundary"); + else + luaG_runerror(L, "attempt to yield from outside a coroutine"); + } + L->status = LUA_YIELD; + if (isLua(ci)) { /* inside a hook? */ + lua_assert(!isLuacode(ci)); + api_check(L, k == NULL, "hooks cannot continue after yielding"); + ci->u2.nyield = 0; /* no results */ + } + else { + if ((ci->u.c.k = k) != NULL) /* is there a continuation? */ + ci->u.c.ctx = ctx; /* save context */ + ci->u2.nyield = nresults; /* save number of results */ + luaD_throw(L, LUA_YIELD); + } + lua_assert(ci->callstatus & CIST_HOOKED); /* must be inside a hook */ + lua_unlock(L); + return 0; /* return to 'luaD_hook' */ +} + + +/* +** Call the C function 'func' in protected mode, restoring basic +** thread information ('allowhook', etc.) and in particular +** its stack level in case of errors. +*/ +int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t old_top, ptrdiff_t ef) { + int status; + CallInfo *old_ci = L->ci; + lu_byte old_allowhooks = L->allowhook; + ptrdiff_t old_errfunc = L->errfunc; + L->errfunc = ef; + status = luaD_rawrunprotected(L, func, u); + if (unlikely(status != LUA_OK)) { /* an error occurred? */ + StkId oldtop = restorestack(L, old_top); + L->ci = old_ci; + L->allowhook = old_allowhooks; + status = luaF_close(L, oldtop, status); + oldtop = restorestack(L, old_top); /* previous call may change stack */ + luaD_seterrorobj(L, status, oldtop); + luaD_shrinkstack(L); /* restore stack size in case of overflow */ + } + L->errfunc = old_errfunc; + return status; +} + + + +/* +** Execute a protected parser. +*/ +struct SParser { /* data to 'f_parser' */ + ZIO *z; + Mbuffer buff; /* dynamic structure used by the scanner */ + Dyndata dyd; /* dynamic structures used by the parser */ + const char *mode; + const char *name; +}; + + +static void checkmode (lua_State *L, const char *mode, const char *x) { + if (mode && strchr(mode, x[0]) == NULL) { + luaO_pushfstring(L, + "attempt to load a %s chunk (mode is '%s')", x, mode); + luaD_throw(L, LUA_ERRSYNTAX); + } +} + + +static void f_parser (lua_State *L, void *ud) { + LClosure *cl; + struct SParser *p = cast(struct SParser *, ud); + int c = zgetc(p->z); /* read first character */ + if (c == LUA_SIGNATURE[0]) { + checkmode(L, p->mode, "binary"); + cl = luaU_undump(L, p->z, p->name); + } + else { + checkmode(L, p->mode, "text"); + cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c); + } + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luaF_initupvals(L, cl); +} + + +int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, + const char *mode) { + struct SParser p; + int status; + incnny(L); /* cannot yield during parsing */ + p.z = z; p.name = name; p.mode = mode; + p.dyd.actvar.arr = NULL; p.dyd.actvar.size = 0; + p.dyd.gt.arr = NULL; p.dyd.gt.size = 0; + p.dyd.label.arr = NULL; p.dyd.label.size = 0; + luaZ_initbuffer(L, &p.buff); + status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc); + luaZ_freebuffer(L, &p.buff); + luaM_freearray(L, p.dyd.actvar.arr, p.dyd.actvar.size); + luaM_freearray(L, p.dyd.gt.arr, p.dyd.gt.size); + luaM_freearray(L, p.dyd.label.arr, p.dyd.label.size); + decnny(L); + return status; +} + + diff --git a/Lua/ldo.h b/Lua/ldo.h new file mode 100644 index 00000000..4d30d072 --- /dev/null +++ b/Lua/ldo.h @@ -0,0 +1,78 @@ +/* +** $Id: ldo.h $ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + +#ifndef ldo_h +#define ldo_h + + +#include "lobject.h" +#include "lstate.h" +#include "lzio.h" + + +/* +** Macro to check stack size and grow stack if needed. Parameters +** 'pre'/'pos' allow the macro to preserve a pointer into the +** stack across reallocations, doing the work only when needed. +** It also allows the running of one GC step when the stack is +** reallocated. +** 'condmovestack' is used in heavy tests to force a stack reallocation +** at every check. +*/ +#define luaD_checkstackaux(L,n,pre,pos) \ + if (L->stack_last - L->top <= (n)) \ + { pre; luaD_growstack(L, n, 1); pos; } \ + else { condmovestack(L,pre,pos); } + +/* In general, 'pre'/'pos' are empty (nothing to save) */ +#define luaD_checkstack(L,n) luaD_checkstackaux(L,n,(void)0,(void)0) + + + +#define savestack(L,p) ((char *)(p) - (char *)L->stack) +#define restorestack(L,n) ((StkId)((char *)L->stack + (n))) + + +/* macro to check stack size, preserving 'p' */ +#define checkstackGCp(L,n,p) \ + luaD_checkstackaux(L, n, \ + ptrdiff_t t__ = savestack(L, p); /* save 'p' */ \ + luaC_checkGC(L), /* stack grow uses memory */ \ + p = restorestack(L, t__)) /* 'pos' part: restore 'p' */ + + +/* macro to check stack size and GC */ +#define checkstackGC(L,fsize) \ + luaD_checkstackaux(L, (fsize), luaC_checkGC(L), (void)0) + + +/* type of protected functions, to be ran by 'runprotected' */ +typedef void (*Pfunc) (lua_State *L, void *ud); + +LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); +LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, + const char *mode); +LUAI_FUNC void luaD_hook (lua_State *L, int event, int line, + int fTransfer, int nTransfer); +LUAI_FUNC void luaD_hookcall (lua_State *L, CallInfo *ci); +LUAI_FUNC void luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, int n); +LUAI_FUNC CallInfo *luaD_precall (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_tryfuncTM (lua_State *L, StkId func); +LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t oldtop, ptrdiff_t ef); +LUAI_FUNC void luaD_poscall (lua_State *L, CallInfo *ci, int nres); +LUAI_FUNC int luaD_reallocstack (lua_State *L, int newsize, int raiseerror); +LUAI_FUNC int luaD_growstack (lua_State *L, int n, int raiseerror); +LUAI_FUNC void luaD_shrinkstack (lua_State *L); +LUAI_FUNC void luaD_inctop (lua_State *L); + +LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode); +LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); + +#endif + diff --git a/Lua/ldump.c b/Lua/ldump.c new file mode 100644 index 00000000..f848b669 --- /dev/null +++ b/Lua/ldump.c @@ -0,0 +1,226 @@ +/* +** $Id: ldump.c $ +** save precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#define ldump_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "lobject.h" +#include "lstate.h" +#include "lundump.h" + + +typedef struct { + lua_State *L; + lua_Writer writer; + void *data; + int strip; + int status; +} DumpState; + + +/* +** All high-level dumps go through dumpVector; you can change it to +** change the endianness of the result +*/ +#define dumpVector(D,v,n) dumpBlock(D,v,(n)*sizeof((v)[0])) + +#define dumpLiteral(D, s) dumpBlock(D,s,sizeof(s) - sizeof(char)) + + +static void dumpBlock (DumpState *D, const void *b, size_t size) { + if (D->status == 0 && size > 0) { + lua_unlock(D->L); + D->status = (*D->writer)(D->L, b, size, D->data); + lua_lock(D->L); + } +} + + +#define dumpVar(D,x) dumpVector(D,&x,1) + + +static void dumpByte (DumpState *D, int y) { + lu_byte x = (lu_byte)y; + dumpVar(D, x); +} + + +/* dumpInt Buff Size */ +#define DIBS ((sizeof(size_t) * 8 / 7) + 1) + +static void dumpSize (DumpState *D, size_t x) { + lu_byte buff[DIBS]; + int n = 0; + do { + buff[DIBS - (++n)] = x & 0x7f; /* fill buffer in reverse order */ + x >>= 7; + } while (x != 0); + buff[DIBS - 1] |= 0x80; /* mark last byte */ + dumpVector(D, buff + DIBS - n, n); +} + + +static void dumpInt (DumpState *D, int x) { + dumpSize(D, x); +} + + +static void dumpNumber (DumpState *D, lua_Number x) { + dumpVar(D, x); +} + + +static void dumpInteger (DumpState *D, lua_Integer x) { + dumpVar(D, x); +} + + +static void dumpString (DumpState *D, const TString *s) { + if (s == NULL) + dumpSize(D, 0); + else { + size_t size = tsslen(s); + const char *str = getstr(s); + dumpSize(D, size + 1); + dumpVector(D, str, size); + } +} + + +static void dumpCode (DumpState *D, const Proto *f) { + dumpInt(D, f->sizecode); + dumpVector(D, f->code, f->sizecode); +} + + +static void dumpFunction(DumpState *D, const Proto *f, TString *psource); + +static void dumpConstants (DumpState *D, const Proto *f) { + int i; + int n = f->sizek; + dumpInt(D, n); + for (i = 0; i < n; i++) { + const TValue *o = &f->k[i]; + int tt = ttypetag(o); + dumpByte(D, tt); + switch (tt) { + case LUA_VNUMFLT: + dumpNumber(D, fltvalue(o)); + break; + case LUA_VNUMINT: + dumpInteger(D, ivalue(o)); + break; + case LUA_VSHRSTR: + case LUA_VLNGSTR: + dumpString(D, tsvalue(o)); + break; + default: + lua_assert(tt == LUA_VNIL || tt == LUA_VFALSE || tt == LUA_VTRUE); + } + } +} + + +static void dumpProtos (DumpState *D, const Proto *f) { + int i; + int n = f->sizep; + dumpInt(D, n); + for (i = 0; i < n; i++) + dumpFunction(D, f->p[i], f->source); +} + + +static void dumpUpvalues (DumpState *D, const Proto *f) { + int i, n = f->sizeupvalues; + dumpInt(D, n); + for (i = 0; i < n; i++) { + dumpByte(D, f->upvalues[i].instack); + dumpByte(D, f->upvalues[i].idx); + dumpByte(D, f->upvalues[i].kind); + } +} + + +static void dumpDebug (DumpState *D, const Proto *f) { + int i, n; + n = (D->strip) ? 0 : f->sizelineinfo; + dumpInt(D, n); + dumpVector(D, f->lineinfo, n); + n = (D->strip) ? 0 : f->sizeabslineinfo; + dumpInt(D, n); + for (i = 0; i < n; i++) { + dumpInt(D, f->abslineinfo[i].pc); + dumpInt(D, f->abslineinfo[i].line); + } + n = (D->strip) ? 0 : f->sizelocvars; + dumpInt(D, n); + for (i = 0; i < n; i++) { + dumpString(D, f->locvars[i].varname); + dumpInt(D, f->locvars[i].startpc); + dumpInt(D, f->locvars[i].endpc); + } + n = (D->strip) ? 0 : f->sizeupvalues; + dumpInt(D, n); + for (i = 0; i < n; i++) + dumpString(D, f->upvalues[i].name); +} + + +static void dumpFunction (DumpState *D, const Proto *f, TString *psource) { + if (D->strip || f->source == psource) + dumpString(D, NULL); /* no debug info or same source as its parent */ + else + dumpString(D, f->source); + dumpInt(D, f->linedefined); + dumpInt(D, f->lastlinedefined); + dumpByte(D, f->numparams); + dumpByte(D, f->is_vararg); + dumpByte(D, f->maxstacksize); + dumpCode(D, f); + dumpConstants(D, f); + dumpUpvalues(D, f); + dumpProtos(D, f); + dumpDebug(D, f); +} + + +static void dumpHeader (DumpState *D) { + dumpLiteral(D, LUA_SIGNATURE); + dumpByte(D, LUAC_VERSION); + dumpByte(D, LUAC_FORMAT); + dumpLiteral(D, LUAC_DATA); + dumpByte(D, sizeof(Instruction)); + dumpByte(D, sizeof(lua_Integer)); + dumpByte(D, sizeof(lua_Number)); + dumpInteger(D, LUAC_INT); + dumpNumber(D, LUAC_NUM); +} + + +/* +** dump Lua function as precompiled chunk +*/ +int luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data, + int strip) { + DumpState D; + D.L = L; + D.writer = w; + D.data = data; + D.strip = strip; + D.status = 0; + dumpHeader(&D); + dumpByte(&D, f->sizeupvalues); + dumpFunction(&D, f, NULL); + return D.status; +} + diff --git a/Lua/lfunc.c b/Lua/lfunc.c new file mode 100644 index 00000000..c4360f09 --- /dev/null +++ b/Lua/lfunc.c @@ -0,0 +1,300 @@ +/* +** $Id: lfunc.c $ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + +#define lfunc_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" + + + +CClosure *luaF_newCclosure (lua_State *L, int nupvals) { + GCObject *o = luaC_newobj(L, LUA_VCCL, sizeCclosure(nupvals)); + CClosure *c = gco2ccl(o); + c->nupvalues = cast_byte(nupvals); + return c; +} + + +LClosure *luaF_newLclosure (lua_State *L, int nupvals) { + GCObject *o = luaC_newobj(L, LUA_VLCL, sizeLclosure(nupvals)); + LClosure *c = gco2lcl(o); + c->p = NULL; + c->nupvalues = cast_byte(nupvals); + while (nupvals--) c->upvals[nupvals] = NULL; + return c; +} + + +/* +** fill a closure with new closed upvalues +*/ +void luaF_initupvals (lua_State *L, LClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) { + GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal)); + UpVal *uv = gco2upv(o); + uv->v = &uv->u.value; /* make it closed */ + setnilvalue(uv->v); + cl->upvals[i] = uv; + luaC_objbarrier(L, cl, uv); + } +} + + +/* +** Create a new upvalue at the given level, and link it to the list of +** open upvalues of 'L' after entry 'prev'. +**/ +static UpVal *newupval (lua_State *L, int tbc, StkId level, UpVal **prev) { + GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal)); + UpVal *uv = gco2upv(o); + UpVal *next = *prev; + uv->v = s2v(level); /* current value lives in the stack */ + uv->tbc = tbc; + uv->u.open.next = next; /* link it to list of open upvalues */ + uv->u.open.previous = prev; + if (next) + next->u.open.previous = &uv->u.open.next; + *prev = uv; + if (!isintwups(L)) { /* thread not in list of threads with upvalues? */ + L->twups = G(L)->twups; /* link it to the list */ + G(L)->twups = L; + } + return uv; +} + + +/* +** Find and reuse, or create if it does not exist, an upvalue +** at the given level. +*/ +UpVal *luaF_findupval (lua_State *L, StkId level) { + UpVal **pp = &L->openupval; + UpVal *p; + lua_assert(isintwups(L) || L->openupval == NULL); + while ((p = *pp) != NULL && uplevel(p) >= level) { /* search for it */ + lua_assert(!isdead(G(L), p)); + if (uplevel(p) == level) /* corresponding upvalue? */ + return p; /* return it */ + pp = &p->u.open.next; + } + /* not found: create a new upvalue after 'pp' */ + return newupval(L, 0, level, pp); +} + + +static void callclose (lua_State *L, void *ud) { + UNUSED(ud); + luaD_callnoyield(L, L->top - 3, 0); +} + + +/* +** Prepare closing method plus its arguments for object 'obj' with +** error message 'err'. (This function assumes EXTRA_STACK.) +*/ +static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) { + StkId top = L->top; + const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); + if (ttisnil(tm)) /* no metamethod? */ + return 0; /* nothing to call */ + setobj2s(L, top, tm); /* will call metamethod... */ + setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */ + setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */ + L->top = top + 3; /* add function and arguments */ + return 1; +} + + +/* +** Raise an error with message 'msg', inserting the name of the +** local variable at position 'level' in the stack. +*/ +static void varerror (lua_State *L, StkId level, const char *msg) { + int idx = cast_int(level - L->ci->func); + const char *vname = luaG_findlocal(L, L->ci, idx, NULL); + if (vname == NULL) vname = "?"; + luaG_runerror(L, msg, vname); +} + + +/* +** Prepare and call a closing method. If status is OK, code is still +** inside the original protected call, and so any error will be handled +** there. Otherwise, a previous error already activated the original +** protected call, and so the call to the closing method must be +** protected here. (A status == CLOSEPROTECT behaves like a previous +** error, to also run the closing method in protected mode). +** If status is OK, the call to the closing method will be pushed +** at the top of the stack. Otherwise, values are pushed after +** the 'level' of the upvalue being closed, as everything after +** that won't be used again. +*/ +static int callclosemth (lua_State *L, StkId level, int status) { + TValue *uv = s2v(level); /* value being closed */ + if (likely(status == LUA_OK)) { + if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */ + callclose(L, NULL); /* call closing method */ + else if (!l_isfalse(uv)) /* non-closable non-false value? */ + varerror(L, level, "attempt to close non-closable variable '%s'"); + } + else { /* must close the object in protected mode */ + ptrdiff_t oldtop; + level++; /* space for error message */ + oldtop = savestack(L, level + 1); /* top will be after that */ + luaD_seterrorobj(L, status, level); /* set error message */ + if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */ + int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0); + if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */ + status = newstatus; /* this will be the new error */ + else { + if (newstatus != LUA_OK) /* suppressed error? */ + luaE_warnerror(L, "__close metamethod"); + /* leave original error (or nil) on top */ + L->top = restorestack(L, oldtop); + } + } + /* else no metamethod; ignore this case and keep original error */ + } + return status; +} + + +/* +** Try to create a to-be-closed upvalue +** (can raise a memory-allocation error) +*/ +static void trynewtbcupval (lua_State *L, void *ud) { + newupval(L, 1, cast(StkId, ud), &L->openupval); +} + + +/* +** Create a to-be-closed upvalue. If there is a memory error +** when creating the upvalue, the closing method must be called here, +** as there is no upvalue to call it later. +*/ +void luaF_newtbcupval (lua_State *L, StkId level) { + TValue *obj = s2v(level); + lua_assert(L->openupval == NULL || uplevel(L->openupval) < level); + if (!l_isfalse(obj)) { /* false doesn't need to be closed */ + int status; + const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); + if (ttisnil(tm)) /* no metamethod? */ + varerror(L, level, "variable '%s' got a non-closable value"); + status = luaD_rawrunprotected(L, trynewtbcupval, level); + if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */ + lua_assert(status == LUA_ERRMEM); + luaD_seterrorobj(L, LUA_ERRMEM, level + 1); /* save error message */ + /* next call must succeed, as object is closable */ + prepclosingmethod(L, s2v(level), s2v(level + 1)); + callclose(L, NULL); /* call closing method */ + luaD_throw(L, LUA_ERRMEM); /* throw memory error */ + } + } +} + + +void luaF_unlinkupval (UpVal *uv) { + lua_assert(upisopen(uv)); + *uv->u.open.previous = uv->u.open.next; + if (uv->u.open.next) + uv->u.open.next->u.open.previous = uv->u.open.previous; +} + + +int luaF_close (lua_State *L, StkId level, int status) { + UpVal *uv; + while ((uv = L->openupval) != NULL && uplevel(uv) >= level) { + TValue *slot = &uv->u.value; /* new position for value */ + lua_assert(uplevel(uv) < L->top); + if (uv->tbc && status != NOCLOSINGMETH) { + /* must run closing method, which may change the stack */ + ptrdiff_t levelrel = savestack(L, level); + status = callclosemth(L, uplevel(uv), status); + level = restorestack(L, levelrel); + } + luaF_unlinkupval(uv); + setobj(L, slot, uv->v); /* move value to upvalue slot */ + uv->v = slot; /* now current value lives here */ + if (!iswhite(uv)) { /* neither white nor dead? */ + nw2black(uv); /* closed upvalues cannot be gray */ + luaC_barrier(L, uv, slot); + } + } + return status; +} + + +Proto *luaF_newproto (lua_State *L) { + GCObject *o = luaC_newobj(L, LUA_VPROTO, sizeof(Proto)); + Proto *f = gco2p(o); + f->k = NULL; + f->sizek = 0; + f->p = NULL; + f->sizep = 0; + f->code = NULL; + f->sizecode = 0; + f->lineinfo = NULL; + f->sizelineinfo = 0; + f->abslineinfo = NULL; + f->sizeabslineinfo = 0; + f->upvalues = NULL; + f->sizeupvalues = 0; + f->numparams = 0; + f->is_vararg = 0; + f->maxstacksize = 0; + f->locvars = NULL; + f->sizelocvars = 0; + f->linedefined = 0; + f->lastlinedefined = 0; + f->source = NULL; + return f; +} + + +void luaF_freeproto (lua_State *L, Proto *f) { + luaM_freearray(L, f->code, f->sizecode); + luaM_freearray(L, f->p, f->sizep); + luaM_freearray(L, f->k, f->sizek); + luaM_freearray(L, f->lineinfo, f->sizelineinfo); + luaM_freearray(L, f->abslineinfo, f->sizeabslineinfo); + luaM_freearray(L, f->locvars, f->sizelocvars); + luaM_freearray(L, f->upvalues, f->sizeupvalues); + luaM_free(L, f); +} + + +/* +** Look for n-th local variable at line 'line' in function 'func'. +** Returns NULL if not found. +*/ +const char *luaF_getlocalname (const Proto *f, int local_number, int pc) { + int i; + for (i = 0; isizelocvars && f->locvars[i].startpc <= pc; i++) { + if (pc < f->locvars[i].endpc) { /* is variable active? */ + local_number--; + if (local_number == 0) + return getstr(f->locvars[i].varname); + } + } + return NULL; /* not found */ +} + diff --git a/Lua/lfunc.h b/Lua/lfunc.h new file mode 100644 index 00000000..8d6f965c --- /dev/null +++ b/Lua/lfunc.h @@ -0,0 +1,69 @@ +/* +** $Id: lfunc.h $ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + +#ifndef lfunc_h +#define lfunc_h + + +#include "lobject.h" + + +#define sizeCclosure(n) (cast_int(offsetof(CClosure, upvalue)) + \ + cast_int(sizeof(TValue)) * (n)) + +#define sizeLclosure(n) (cast_int(offsetof(LClosure, upvals)) + \ + cast_int(sizeof(TValue *)) * (n)) + + +/* test whether thread is in 'twups' list */ +#define isintwups(L) (L->twups != L) + + +/* +** maximum number of upvalues in a closure (both C and Lua). (Value +** must fit in a VM register.) +*/ +#define MAXUPVAL 255 + + +#define upisopen(up) ((up)->v != &(up)->u.value) + + +#define uplevel(up) check_exp(upisopen(up), cast(StkId, (up)->v)) + + +/* +** maximum number of misses before giving up the cache of closures +** in prototypes +*/ +#define MAXMISS 10 + + +/* +** Special "status" for 'luaF_close' +*/ + +/* close upvalues without running their closing methods */ +#define NOCLOSINGMETH (-1) + +/* close upvalues running all closing methods in protected mode */ +#define CLOSEPROTECT (-2) + + +LUAI_FUNC Proto *luaF_newproto (lua_State *L); +LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nupvals); +LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nupvals); +LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); +LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); +LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level); +LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status); +LUAI_FUNC void luaF_unlinkupval (UpVal *uv); +LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); +LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, + int pc); + + +#endif diff --git a/Lua/lgc.c b/Lua/lgc.c new file mode 100644 index 00000000..bab9beb1 --- /dev/null +++ b/Lua/lgc.c @@ -0,0 +1,1716 @@ +/* +** $Id: lgc.c $ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#define lgc_c +#define LUA_CORE + +#include "lprefix.h" + +#include +#include + + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + +/* +** Maximum number of elements to sweep in each single step. +** (Large enough to dissipate fixed overheads but small enough +** to allow small steps for the collector.) +*/ +#define GCSWEEPMAX 100 + +/* +** Maximum number of finalizers to call in each single step. +*/ +#define GCFINMAX 10 + + +/* +** Cost of calling one finalizer. +*/ +#define GCFINALIZECOST 50 + + +/* +** The equivalent, in bytes, of one unit of "work" (visiting a slot, +** sweeping an object, etc.) +*/ +#define WORK2MEM sizeof(TValue) + + +/* +** macro to adjust 'pause': 'pause' is actually used like +** 'pause / PAUSEADJ' (value chosen by tests) +*/ +#define PAUSEADJ 100 + + +/* mask with all color bits */ +#define maskcolors (bitmask(BLACKBIT) | WHITEBITS) + +/* mask with all GC bits */ +#define maskgcbits (maskcolors | AGEBITS) + + +/* macro to erase all color bits then set only the current white bit */ +#define makewhite(g,x) \ + (x->marked = cast_byte((x->marked & ~maskcolors) | luaC_white(g))) + +/* make an object gray (neither white nor black) */ +#define set2gray(x) resetbits(x->marked, maskcolors) + + +/* make an object black (coming from any color) */ +#define set2black(x) \ + (x->marked = cast_byte((x->marked & ~WHITEBITS) | bitmask(BLACKBIT))) + + +#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) + +#define keyiswhite(n) (keyiscollectable(n) && iswhite(gckey(n))) + + +/* +** Protected access to objects in values +*/ +#define gcvalueN(o) (iscollectable(o) ? gcvalue(o) : NULL) + + +#define markvalue(g,o) { checkliveness(g->mainthread,o); \ + if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); } + +#define markkey(g, n) { if keyiswhite(n) reallymarkobject(g,gckey(n)); } + +#define markobject(g,t) { if (iswhite(t)) reallymarkobject(g, obj2gco(t)); } + +/* +** mark an object that can be NULL (either because it is really optional, +** or it was stripped as debug info, or inside an uncompleted structure) +*/ +#define markobjectN(g,t) { if (t) markobject(g,t); } + +static void reallymarkobject (global_State *g, GCObject *o); +static lu_mem atomic (lua_State *L); +static void entersweep (lua_State *L); + + +/* +** {====================================================== +** Generic functions +** ======================================================= +*/ + + +/* +** one after last element in a hash array +*/ +#define gnodelast(h) gnode(h, cast_sizet(sizenode(h))) + + +static GCObject **getgclist (GCObject *o) { + switch (o->tt) { + case LUA_VTABLE: return &gco2t(o)->gclist; + case LUA_VLCL: return &gco2lcl(o)->gclist; + case LUA_VCCL: return &gco2ccl(o)->gclist; + case LUA_VTHREAD: return &gco2th(o)->gclist; + case LUA_VPROTO: return &gco2p(o)->gclist; + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + lua_assert(u->nuvalue > 0); + return &u->gclist; + } + default: lua_assert(0); return 0; + } +} + + +/* +** Link a collectable object 'o' with a known type into the list 'p'. +** (Must be a macro to access the 'gclist' field in different types.) +*/ +#define linkgclist(o,p) linkgclist_(obj2gco(o), &(o)->gclist, &(p)) + +static void linkgclist_ (GCObject *o, GCObject **pnext, GCObject **list) { + lua_assert(!isgray(o)); /* cannot be in a gray list */ + *pnext = *list; + *list = o; + set2gray(o); /* now it is */ +} + + +/* +** Link a generic collectable object 'o' into the list 'p'. +*/ +#define linkobjgclist(o,p) linkgclist_(obj2gco(o), getgclist(o), &(p)) + + + +/* +** Clear keys for empty entries in tables. If entry is empty, mark its +** entry as dead. This allows the collection of the key, but keeps its +** entry in the table: its removal could break a chain and could break +** a table traversal. Other places never manipulate dead keys, because +** its associated empty value is enough to signal that the entry is +** logically empty. +*/ +static void clearkey (Node *n) { + lua_assert(isempty(gval(n))); + if (keyiscollectable(n)) + setdeadkey(n); /* unused key; remove it */ +} + + +/* +** tells whether a key or value can be cleared from a weak +** table. Non-collectable objects are never removed from weak +** tables. Strings behave as 'values', so are never removed too. for +** other objects: if really collected, cannot keep them; for objects +** being finalized, keep them in keys, but not in values +*/ +static int iscleared (global_State *g, const GCObject *o) { + if (o == NULL) return 0; /* non-collectable value */ + else if (novariant(o->tt) == LUA_TSTRING) { + markobject(g, o); /* strings are 'values', so are never weak */ + return 0; + } + else return iswhite(o); +} + + +/* +** Barrier that moves collector forward, that is, marks the white object +** 'v' being pointed by the black object 'o'. In the generational +** mode, 'v' must also become old, if 'o' is old; however, it cannot +** be changed directly to OLD, because it may still point to non-old +** objects. So, it is marked as OLD0. In the next cycle it will become +** OLD1, and in the next it will finally become OLD (regular old). By +** then, any object it points to will also be old. If called in the +** incremental sweep phase, it clears the black object to white (sweep +** it) to avoid other barrier calls for this same object. (That cannot +** be done is generational mode, as its sweep does not distinguish +** whites from deads.) +*/ +void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { + global_State *g = G(L); + lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); + if (keepinvariant(g)) { /* must keep invariant? */ + reallymarkobject(g, v); /* restore invariant */ + if (isold(o)) { + lua_assert(!isold(v)); /* white object could not be old */ + setage(v, G_OLD0); /* restore generational invariant */ + } + } + else { /* sweep phase */ + lua_assert(issweepphase(g)); + if (g->gckind == KGC_INC) /* incremental mode? */ + makewhite(g, o); /* mark 'o' as white to avoid other barriers */ + } +} + + +/* +** barrier that moves collector backward, that is, mark the black object +** pointing to a white object as gray again. +*/ +void luaC_barrierback_ (lua_State *L, GCObject *o) { + global_State *g = G(L); + lua_assert(isblack(o) && !isdead(g, o)); + lua_assert((g->gckind == KGC_GEN) == (isold(o) && getage(o) != G_TOUCHED1)); + if (getage(o) == G_TOUCHED2) /* already in gray list? */ + set2gray(o); /* make it gray to become touched1 */ + else /* link it in 'grayagain' and paint it gray */ + linkobjgclist(o, g->grayagain); + if (isold(o)) /* generational mode? */ + setage(o, G_TOUCHED1); /* touched in current cycle */ +} + + +void luaC_fix (lua_State *L, GCObject *o) { + global_State *g = G(L); + lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */ + set2gray(o); /* they will be gray forever */ + setage(o, G_OLD); /* and old forever */ + g->allgc = o->next; /* remove object from 'allgc' list */ + o->next = g->fixedgc; /* link it to 'fixedgc' list */ + g->fixedgc = o; +} + + +/* +** create a new collectable object (with given type and size) and link +** it to 'allgc' list. +*/ +GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { + global_State *g = G(L); + GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz)); + o->marked = luaC_white(g); + o->tt = tt; + o->next = g->allgc; + g->allgc = o; + return o; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Mark functions +** ======================================================= +*/ + + +/* +** Mark an object. Userdata with no user values, strings, and closed +** upvalues are visited and turned black here. Open upvalues are +** already indirectly linked through their respective threads in the +** 'twups' list, so they don't go to the gray list; nevertheless, they +** are kept gray to avoid barriers, as their values will be revisited +** by the thread or by 'remarkupvals'. Other objects are added to the +** gray list to be visited (and turned black) later. Both userdata and +** upvalues can call this function recursively, but this recursion goes +** for at most two levels: An upvalue cannot refer to another upvalue +** (only closures can), and a userdata's metatable must be a table. +*/ +static void reallymarkobject (global_State *g, GCObject *o) { + switch (o->tt) { + case LUA_VSHRSTR: + case LUA_VLNGSTR: { + set2black(o); /* nothing to visit */ + break; + } + case LUA_VUPVAL: { + UpVal *uv = gco2upv(o); + if (upisopen(uv)) + set2gray(uv); /* open upvalues are kept gray */ + else + set2black(uv); /* closed upvalues are visited here */ + markvalue(g, uv->v); /* mark its content */ + break; + } + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + if (u->nuvalue == 0) { /* no user values? */ + markobjectN(g, u->metatable); /* mark its metatable */ + set2black(u); /* nothing else to mark */ + break; + } + /* else... */ + } /* FALLTHROUGH */ + case LUA_VLCL: case LUA_VCCL: case LUA_VTABLE: + case LUA_VTHREAD: case LUA_VPROTO: { + linkobjgclist(o, g->gray); /* to be visited later */ + break; + } + default: lua_assert(0); break; + } +} + + +/* +** mark metamethods for basic types +*/ +static void markmt (global_State *g) { + int i; + for (i=0; i < LUA_NUMTAGS; i++) + markobjectN(g, g->mt[i]); +} + + +/* +** mark all objects in list of being-finalized +*/ +static lu_mem markbeingfnz (global_State *g) { + GCObject *o; + lu_mem count = 0; + for (o = g->tobefnz; o != NULL; o = o->next) { + count++; + markobject(g, o); + } + return count; +} + + +/* +** For each non-marked thread, simulates a barrier between each open +** upvalue and its value. (If the thread is collected, the value will be +** assigned to the upvalue, but then it can be too late for the barrier +** to act. The "barrier" does not need to check colors: A non-marked +** thread must be young; upvalues cannot be older than their threads; so +** any visited upvalue must be young too.) Also removes the thread from +** the list, as it was already visited. Removes also threads with no +** upvalues, as they have nothing to be checked. (If the thread gets an +** upvalue later, it will be linked in the list again.) +*/ +static int remarkupvals (global_State *g) { + lua_State *thread; + lua_State **p = &g->twups; + int work = 0; /* estimate of how much work was done here */ + while ((thread = *p) != NULL) { + work++; + if (!iswhite(thread) && thread->openupval != NULL) + p = &thread->twups; /* keep marked thread with upvalues in the list */ + else { /* thread is not marked or without upvalues */ + UpVal *uv; + lua_assert(!isold(thread) || thread->openupval == NULL); + *p = thread->twups; /* remove thread from the list */ + thread->twups = thread; /* mark that it is out of list */ + for (uv = thread->openupval; uv != NULL; uv = uv->u.open.next) { + lua_assert(getage(uv) <= getage(thread)); + work++; + if (!iswhite(uv)) { /* upvalue already visited? */ + lua_assert(upisopen(uv) && isgray(uv)); + markvalue(g, uv->v); /* mark its value */ + } + } + } + } + return work; +} + + +static void cleargraylists (global_State *g) { + g->gray = g->grayagain = NULL; + g->weak = g->allweak = g->ephemeron = NULL; +} + + +/* +** mark root set and reset all gray lists, to start a new collection +*/ +static void restartcollection (global_State *g) { + cleargraylists(g); + markobject(g, g->mainthread); + markvalue(g, &g->l_registry); + markmt(g); + markbeingfnz(g); /* mark any finalizing object left from previous cycle */ +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Traverse functions +** ======================================================= +*/ + + +/* +** Check whether object 'o' should be kept in the 'grayagain' list for +** post-processing by 'correctgraylist'. (It could put all old objects +** in the list and leave all the work to 'correctgraylist', but it is +** more efficient to avoid adding elements that will be removed.) Only +** TOUCHED1 objects need to be in the list. TOUCHED2 doesn't need to go +** back to a gray list, but then it must become OLD. (That is what +** 'correctgraylist' does when it finds a TOUCHED2 object.) +*/ +static void genlink (global_State *g, GCObject *o) { + lua_assert(isblack(o)); + if (getage(o) == G_TOUCHED1) { /* touched in this cycle? */ + linkobjgclist(o, g->grayagain); /* link it back in 'grayagain' */ + } /* everything else do not need to be linked back */ + else if (getage(o) == G_TOUCHED2) + changeage(o, G_TOUCHED2, G_OLD); /* advance age */ +} + + +/* +** Traverse a table with weak values and link it to proper list. During +** propagate phase, keep it in 'grayagain' list, to be revisited in the +** atomic phase. In the atomic phase, if table has any white value, +** put it in 'weak' list, to be cleared. +*/ +static void traverseweakvalue (global_State *g, Table *h) { + Node *n, *limit = gnodelast(h); + /* if there is array part, assume it may have white values (it is not + worth traversing it now just to check) */ + int hasclears = (h->alimit > 0); + for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ + else { + lua_assert(!keyisnil(n)); + markkey(g, n); + if (!hasclears && iscleared(g, gcvalueN(gval(n)))) /* a white value? */ + hasclears = 1; /* table will have to be cleared */ + } + } + if (g->gcstate == GCSatomic && hasclears) + linkgclist(h, g->weak); /* has to be cleared later */ + else + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ +} + + +/* +** Traverse an ephemeron table and link it to proper list. Returns true +** iff any object was marked during this traversal (which implies that +** convergence has to continue). During propagation phase, keep table +** in 'grayagain' list, to be visited again in the atomic phase. In +** the atomic phase, if table has any white->white entry, it has to +** be revisited during ephemeron convergence (as that key may turn +** black). Otherwise, if it has any white key, table has to be cleared +** (in the atomic phase). In generational mode, some tables +** must be kept in some gray list for post-processing; this is done +** by 'genlink'. +*/ +static int traverseephemeron (global_State *g, Table *h, int inv) { + int marked = 0; /* true if an object is marked in this traversal */ + int hasclears = 0; /* true if table has white keys */ + int hasww = 0; /* true if table has entry "white-key -> white-value" */ + unsigned int i; + unsigned int asize = luaH_realasize(h); + unsigned int nsize = sizenode(h); + /* traverse array part */ + for (i = 0; i < asize; i++) { + if (valiswhite(&h->array[i])) { + marked = 1; + reallymarkobject(g, gcvalue(&h->array[i])); + } + } + /* traverse hash part; if 'inv', traverse descending + (see 'convergeephemerons') */ + for (i = 0; i < nsize; i++) { + Node *n = inv ? gnode(h, nsize - 1 - i) : gnode(h, i); + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ + else if (iscleared(g, gckeyN(n))) { /* key is not marked (yet)? */ + hasclears = 1; /* table must be cleared */ + if (valiswhite(gval(n))) /* value not marked yet? */ + hasww = 1; /* white-white entry */ + } + else if (valiswhite(gval(n))) { /* value not marked yet? */ + marked = 1; + reallymarkobject(g, gcvalue(gval(n))); /* mark it now */ + } + } + /* link table into proper list */ + if (g->gcstate == GCSpropagate) + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ + else if (hasww) /* table has white->white entries? */ + linkgclist(h, g->ephemeron); /* have to propagate again */ + else if (hasclears) /* table has white keys? */ + linkgclist(h, g->allweak); /* may have to clean white keys */ + else + genlink(g, obj2gco(h)); /* check whether collector still needs to see it */ + return marked; +} + + +static void traversestrongtable (global_State *g, Table *h) { + Node *n, *limit = gnodelast(h); + unsigned int i; + unsigned int asize = luaH_realasize(h); + for (i = 0; i < asize; i++) /* traverse array part */ + markvalue(g, &h->array[i]); + for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ + else { + lua_assert(!keyisnil(n)); + markkey(g, n); + markvalue(g, gval(n)); + } + } + genlink(g, obj2gco(h)); +} + + +static lu_mem traversetable (global_State *g, Table *h) { + const char *weakkey, *weakvalue; + const TValue *mode = gfasttm(g, h->metatable, TM_MODE); + markobjectN(g, h->metatable); + if (mode && ttisstring(mode) && /* is there a weak mode? */ + (cast_void(weakkey = strchr(svalue(mode), 'k')), + cast_void(weakvalue = strchr(svalue(mode), 'v')), + (weakkey || weakvalue))) { /* is really weak? */ + if (!weakkey) /* strong keys? */ + traverseweakvalue(g, h); + else if (!weakvalue) /* strong values? */ + traverseephemeron(g, h, 0); + else /* all weak */ + linkgclist(h, g->allweak); /* nothing to traverse now */ + } + else /* not weak */ + traversestrongtable(g, h); + return 1 + h->alimit + 2 * allocsizenode(h); +} + + +static int traverseudata (global_State *g, Udata *u) { + int i; + markobjectN(g, u->metatable); /* mark its metatable */ + for (i = 0; i < u->nuvalue; i++) + markvalue(g, &u->uv[i].uv); + genlink(g, obj2gco(u)); + return 1 + u->nuvalue; +} + + +/* +** Traverse a prototype. (While a prototype is being build, its +** arrays can be larger than needed; the extra slots are filled with +** NULL, so the use of 'markobjectN') +*/ +static int traverseproto (global_State *g, Proto *f) { + int i; + markobjectN(g, f->source); + for (i = 0; i < f->sizek; i++) /* mark literals */ + markvalue(g, &f->k[i]); + for (i = 0; i < f->sizeupvalues; i++) /* mark upvalue names */ + markobjectN(g, f->upvalues[i].name); + for (i = 0; i < f->sizep; i++) /* mark nested protos */ + markobjectN(g, f->p[i]); + for (i = 0; i < f->sizelocvars; i++) /* mark local-variable names */ + markobjectN(g, f->locvars[i].varname); + return 1 + f->sizek + f->sizeupvalues + f->sizep + f->sizelocvars; +} + + +static int traverseCclosure (global_State *g, CClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ + markvalue(g, &cl->upvalue[i]); + return 1 + cl->nupvalues; +} + +/* +** Traverse a Lua closure, marking its prototype and its upvalues. +** (Both can be NULL while closure is being created.) +*/ +static int traverseLclosure (global_State *g, LClosure *cl) { + int i; + markobjectN(g, cl->p); /* mark its prototype */ + for (i = 0; i < cl->nupvalues; i++) { /* visit its upvalues */ + UpVal *uv = cl->upvals[i]; + markobjectN(g, uv); /* mark upvalue */ + } + return 1 + cl->nupvalues; +} + + +/* +** Traverse a thread, marking the elements in the stack up to its top +** and cleaning the rest of the stack in the final traversal. That +** ensures that the entire stack have valid (non-dead) objects. +** Threads have no barriers. In gen. mode, old threads must be visited +** at every cycle, because they might point to young objects. In inc. +** mode, the thread can still be modified before the end of the cycle, +** and therefore it must be visited again in the atomic phase. To ensure +** these visits, threads must return to a gray list if they are not new +** (which can only happen in generational mode) or if the traverse is in +** the propagate phase (which can only happen in incremental mode). +*/ +static int traversethread (global_State *g, lua_State *th) { + UpVal *uv; + StkId o = th->stack; + if (isold(th) || g->gcstate == GCSpropagate) + linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ + if (o == NULL) + return 1; /* stack not completely built yet */ + lua_assert(g->gcstate == GCSatomic || + th->openupval == NULL || isintwups(th)); + for (; o < th->top; o++) /* mark live elements in the stack */ + markvalue(g, s2v(o)); + for (uv = th->openupval; uv != NULL; uv = uv->u.open.next) + markobject(g, uv); /* open upvalues cannot be collected */ + if (g->gcstate == GCSatomic) { /* final traversal? */ + for (; o < th->stack_last + EXTRA_STACK; o++) + setnilvalue(s2v(o)); /* clear dead stack slice */ + /* 'remarkupvals' may have removed thread from 'twups' list */ + if (!isintwups(th) && th->openupval != NULL) { + th->twups = g->twups; /* link it back to the list */ + g->twups = th; + } + } + else if (!g->gcemergency) + luaD_shrinkstack(th); /* do not change stack in emergency cycle */ + return 1 + stacksize(th); +} + + +/* +** traverse one gray object, turning it to black. +*/ +static lu_mem propagatemark (global_State *g) { + GCObject *o = g->gray; + nw2black(o); + g->gray = *getgclist(o); /* remove from 'gray' list */ + switch (o->tt) { + case LUA_VTABLE: return traversetable(g, gco2t(o)); + case LUA_VUSERDATA: return traverseudata(g, gco2u(o)); + case LUA_VLCL: return traverseLclosure(g, gco2lcl(o)); + case LUA_VCCL: return traverseCclosure(g, gco2ccl(o)); + case LUA_VPROTO: return traverseproto(g, gco2p(o)); + case LUA_VTHREAD: return traversethread(g, gco2th(o)); + default: lua_assert(0); return 0; + } +} + + +static lu_mem propagateall (global_State *g) { + lu_mem tot = 0; + while (g->gray) + tot += propagatemark(g); + return tot; +} + + +/* +** Traverse all ephemeron tables propagating marks from keys to values. +** Repeat until it converges, that is, nothing new is marked. 'dir' +** inverts the direction of the traversals, trying to speed up +** convergence on chains in the same table. +** +*/ +static void convergeephemerons (global_State *g) { + int changed; + int dir = 0; + do { + GCObject *w; + GCObject *next = g->ephemeron; /* get ephemeron list */ + g->ephemeron = NULL; /* tables may return to this list when traversed */ + changed = 0; + while ((w = next) != NULL) { /* for each ephemeron table */ + Table *h = gco2t(w); + next = h->gclist; /* list is rebuilt during loop */ + nw2black(h); /* out of the list (for now) */ + if (traverseephemeron(g, h, dir)) { /* marked some value? */ + propagateall(g); /* propagate changes */ + changed = 1; /* will have to revisit all ephemeron tables */ + } + } + dir = !dir; /* invert direction next time */ + } while (changed); /* repeat until no more changes */ +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Sweep Functions +** ======================================================= +*/ + + +/* +** clear entries with unmarked keys from all weaktables in list 'l' +*/ +static void clearbykeys (global_State *g, GCObject *l) { + for (; l; l = gco2t(l)->gclist) { + Table *h = gco2t(l); + Node *limit = gnodelast(h); + Node *n; + for (n = gnode(h, 0); n < limit; n++) { + if (iscleared(g, gckeyN(n))) /* unmarked key? */ + setempty(gval(n)); /* remove entry */ + if (isempty(gval(n))) /* is entry empty? */ + clearkey(n); /* clear its key */ + } + } +} + + +/* +** clear entries with unmarked values from all weaktables in list 'l' up +** to element 'f' +*/ +static void clearbyvalues (global_State *g, GCObject *l, GCObject *f) { + for (; l != f; l = gco2t(l)->gclist) { + Table *h = gco2t(l); + Node *n, *limit = gnodelast(h); + unsigned int i; + unsigned int asize = luaH_realasize(h); + for (i = 0; i < asize; i++) { + TValue *o = &h->array[i]; + if (iscleared(g, gcvalueN(o))) /* value was collected? */ + setempty(o); /* remove entry */ + } + for (n = gnode(h, 0); n < limit; n++) { + if (iscleared(g, gcvalueN(gval(n)))) /* unmarked value? */ + setempty(gval(n)); /* remove entry */ + if (isempty(gval(n))) /* is entry empty? */ + clearkey(n); /* clear its key */ + } + } +} + + +static void freeupval (lua_State *L, UpVal *uv) { + if (upisopen(uv)) + luaF_unlinkupval(uv); + luaM_free(L, uv); +} + + +static void freeobj (lua_State *L, GCObject *o) { + switch (o->tt) { + case LUA_VPROTO: + luaF_freeproto(L, gco2p(o)); + break; + case LUA_VUPVAL: + freeupval(L, gco2upv(o)); + break; + case LUA_VLCL: { + LClosure *cl = gco2lcl(o); + luaM_freemem(L, cl, sizeLclosure(cl->nupvalues)); + break; + } + case LUA_VCCL: { + CClosure *cl = gco2ccl(o); + luaM_freemem(L, cl, sizeCclosure(cl->nupvalues)); + break; + } + case LUA_VTABLE: + luaH_free(L, gco2t(o)); + break; + case LUA_VTHREAD: + luaE_freethread(L, gco2th(o)); + break; + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + luaM_freemem(L, o, sizeudata(u->nuvalue, u->len)); + break; + } + case LUA_VSHRSTR: { + TString *ts = gco2ts(o); + luaS_remove(L, ts); /* remove it from hash table */ + luaM_freemem(L, ts, sizelstring(ts->shrlen)); + break; + } + case LUA_VLNGSTR: { + TString *ts = gco2ts(o); + luaM_freemem(L, ts, sizelstring(ts->u.lnglen)); + break; + } + default: lua_assert(0); + } +} + + +/* +** sweep at most 'countin' elements from a list of GCObjects erasing dead +** objects, where a dead object is one marked with the old (non current) +** white; change all non-dead objects back to white, preparing for next +** collection cycle. Return where to continue the traversal or NULL if +** list is finished. ('*countout' gets the number of elements traversed.) +*/ +static GCObject **sweeplist (lua_State *L, GCObject **p, int countin, + int *countout) { + global_State *g = G(L); + int ow = otherwhite(g); + int i; + int white = luaC_white(g); /* current white */ + for (i = 0; *p != NULL && i < countin; i++) { + GCObject *curr = *p; + int marked = curr->marked; + if (isdeadm(ow, marked)) { /* is 'curr' dead? */ + *p = curr->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { /* change mark to 'white' */ + curr->marked = cast_byte((marked & ~maskgcbits) | white); + p = &curr->next; /* go to next element */ + } + } + if (countout) + *countout = i; /* number of elements traversed */ + return (*p == NULL) ? NULL : p; +} + + +/* +** sweep a list until a live object (or end of list) +*/ +static GCObject **sweeptolive (lua_State *L, GCObject **p) { + GCObject **old = p; + do { + p = sweeplist(L, p, 1, NULL); + } while (p == old); + return p; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Finalization +** ======================================================= +*/ + +/* +** If possible, shrink string table. +*/ +static void checkSizes (lua_State *L, global_State *g) { + if (!g->gcemergency) { + if (g->strt.nuse < g->strt.size / 4) { /* string table too big? */ + l_mem olddebt = g->GCdebt; + luaS_resize(L, g->strt.size / 2); + g->GCestimate += g->GCdebt - olddebt; /* correct estimate */ + } + } +} + + +/* +** Get the next udata to be finalized from the 'tobefnz' list, and +** link it back into the 'allgc' list. +*/ +static GCObject *udata2finalize (global_State *g) { + GCObject *o = g->tobefnz; /* get first element */ + lua_assert(tofinalize(o)); + g->tobefnz = o->next; /* remove it from 'tobefnz' list */ + o->next = g->allgc; /* return it to 'allgc' list */ + g->allgc = o; + resetbit(o->marked, FINALIZEDBIT); /* object is "normal" again */ + if (issweepphase(g)) + makewhite(g, o); /* "sweep" object */ + else if (getage(o) == G_OLD1) + g->firstold1 = o; /* it is the first OLD1 object in the list */ + return o; +} + + +static void dothecall (lua_State *L, void *ud) { + UNUSED(ud); + luaD_callnoyield(L, L->top - 2, 0); +} + + +static void GCTM (lua_State *L) { + global_State *g = G(L); + const TValue *tm; + TValue v; + lua_assert(!g->gcemergency); + setgcovalue(L, &v, udata2finalize(g)); + tm = luaT_gettmbyobj(L, &v, TM_GC); + if (!notm(tm)) { /* is there a finalizer? */ + int status; + lu_byte oldah = L->allowhook; + int running = g->gcrunning; + L->allowhook = 0; /* stop debug hooks during GC metamethod */ + g->gcrunning = 0; /* avoid GC steps */ + setobj2s(L, L->top++, tm); /* push finalizer... */ + setobj2s(L, L->top++, &v); /* ... and its argument */ + L->ci->callstatus |= CIST_FIN; /* will run a finalizer */ + status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0); + L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */ + L->allowhook = oldah; /* restore hooks */ + g->gcrunning = running; /* restore state */ + if (unlikely(status != LUA_OK)) { /* error while running __gc? */ + luaE_warnerror(L, "__gc metamethod"); + L->top--; /* pops error object */ + } + } +} + + +/* +** Call a few finalizers +*/ +static int runafewfinalizers (lua_State *L, int n) { + global_State *g = G(L); + int i; + for (i = 0; i < n && g->tobefnz; i++) + GCTM(L); /* call one finalizer */ + return i; +} + + +/* +** call all pending finalizers +*/ +static void callallpendingfinalizers (lua_State *L) { + global_State *g = G(L); + while (g->tobefnz) + GCTM(L); +} + + +/* +** find last 'next' field in list 'p' list (to add elements in its end) +*/ +static GCObject **findlast (GCObject **p) { + while (*p != NULL) + p = &(*p)->next; + return p; +} + + +/* +** Move all unreachable objects (or 'all' objects) that need +** finalization from list 'finobj' to list 'tobefnz' (to be finalized). +** (Note that objects after 'finobjold1' cannot be white, so they +** don't need to be traversed. In incremental mode, 'finobjold1' is NULL, +** so the whole list is traversed.) +*/ +static void separatetobefnz (global_State *g, int all) { + GCObject *curr; + GCObject **p = &g->finobj; + GCObject **lastnext = findlast(&g->tobefnz); + while ((curr = *p) != g->finobjold1) { /* traverse all finalizable objects */ + lua_assert(tofinalize(curr)); + if (!(iswhite(curr) || all)) /* not being collected? */ + p = &curr->next; /* don't bother with it */ + else { + if (curr == g->finobjsur) /* removing 'finobjsur'? */ + g->finobjsur = curr->next; /* correct it */ + *p = curr->next; /* remove 'curr' from 'finobj' list */ + curr->next = *lastnext; /* link at the end of 'tobefnz' list */ + *lastnext = curr; + lastnext = &curr->next; + } + } +} + + +/* +** If pointer 'p' points to 'o', move it to the next element. +*/ +static void checkpointer (GCObject **p, GCObject *o) { + if (o == *p) + *p = o->next; +} + + +/* +** Correct pointers to objects inside 'allgc' list when +** object 'o' is being removed from the list. +*/ +static void correctpointers (global_State *g, GCObject *o) { + checkpointer(&g->survival, o); + checkpointer(&g->old1, o); + checkpointer(&g->reallyold, o); + checkpointer(&g->firstold1, o); +} + + +/* +** if object 'o' has a finalizer, remove it from 'allgc' list (must +** search the list to find it) and link it in 'finobj' list. +*/ +void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { + global_State *g = G(L); + if (tofinalize(o) || /* obj. is already marked... */ + gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */ + return; /* nothing to be done */ + else { /* move 'o' to 'finobj' list */ + GCObject **p; + if (issweepphase(g)) { + makewhite(g, o); /* "sweep" object 'o' */ + if (g->sweepgc == &o->next) /* should not remove 'sweepgc' object */ + g->sweepgc = sweeptolive(L, g->sweepgc); /* change 'sweepgc' */ + } + else + correctpointers(g, o); + /* search for pointer pointing to 'o' */ + for (p = &g->allgc; *p != o; p = &(*p)->next) { /* empty */ } + *p = o->next; /* remove 'o' from 'allgc' list */ + o->next = g->finobj; /* link it in 'finobj' list */ + g->finobj = o; + l_setbit(o->marked, FINALIZEDBIT); /* mark it as such */ + } +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Generational Collector +** ======================================================= +*/ + +static void setpause (global_State *g); + + +/* +** Sweep a list of objects to enter generational mode. Deletes dead +** objects and turns the non dead to old. All non-dead threads---which +** are now old---must be in a gray list. Everything else is not in a +** gray list. Open upvalues are also kept gray. +*/ +static void sweep2old (lua_State *L, GCObject **p) { + GCObject *curr; + global_State *g = G(L); + while ((curr = *p) != NULL) { + if (iswhite(curr)) { /* is 'curr' dead? */ + lua_assert(isdead(g, curr)); + *p = curr->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { /* all surviving objects become old */ + setage(curr, G_OLD); + if (curr->tt == LUA_VTHREAD) { /* threads must be watched */ + lua_State *th = gco2th(curr); + linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ + } + else if (curr->tt == LUA_VUPVAL && upisopen(gco2upv(curr))) + set2gray(curr); /* open upvalues are always gray */ + else /* everything else is black */ + nw2black(curr); + p = &curr->next; /* go to next element */ + } + } +} + + +/* +** Sweep for generational mode. Delete dead objects. (Because the +** collection is not incremental, there are no "new white" objects +** during the sweep. So, any white object must be dead.) For +** non-dead objects, advance their ages and clear the color of +** new objects. (Old objects keep their colors.) +** The ages of G_TOUCHED1 and G_TOUCHED2 objects cannot be advanced +** here, because these old-generation objects are usually not swept +** here. They will all be advanced in 'correctgraylist'. That function +** will also remove objects turned white here from any gray list. +*/ +static GCObject **sweepgen (lua_State *L, global_State *g, GCObject **p, + GCObject *limit, GCObject **pfirstold1) { + static const lu_byte nextage[] = { + G_SURVIVAL, /* from G_NEW */ + G_OLD1, /* from G_SURVIVAL */ + G_OLD1, /* from G_OLD0 */ + G_OLD, /* from G_OLD1 */ + G_OLD, /* from G_OLD (do not change) */ + G_TOUCHED1, /* from G_TOUCHED1 (do not change) */ + G_TOUCHED2 /* from G_TOUCHED2 (do not change) */ + }; + int white = luaC_white(g); + GCObject *curr; + while ((curr = *p) != limit) { + if (iswhite(curr)) { /* is 'curr' dead? */ + lua_assert(!isold(curr) && isdead(g, curr)); + *p = curr->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { /* correct mark and age */ + if (getage(curr) == G_NEW) { /* new objects go back to white */ + int marked = curr->marked & ~maskgcbits; /* erase GC bits */ + curr->marked = cast_byte(marked | G_SURVIVAL | white); + } + else { /* all other objects will be old, and so keep their color */ + setage(curr, nextage[getage(curr)]); + if (getage(curr) == G_OLD1 && *pfirstold1 == NULL) + *pfirstold1 = curr; /* first OLD1 object in the list */ + } + p = &curr->next; /* go to next element */ + } + } + return p; +} + + +/* +** Traverse a list making all its elements white and clearing their +** age. In incremental mode, all objects are 'new' all the time, +** except for fixed strings (which are always old). +*/ +static void whitelist (global_State *g, GCObject *p) { + int white = luaC_white(g); + for (; p != NULL; p = p->next) + p->marked = cast_byte((p->marked & ~maskgcbits) | white); +} + + +/* +** Correct a list of gray objects. Return pointer to where rest of the +** list should be linked. +** Because this correction is done after sweeping, young objects might +** be turned white and still be in the list. They are only removed. +** 'TOUCHED1' objects are advanced to 'TOUCHED2' and remain on the list; +** Non-white threads also remain on the list; 'TOUCHED2' objects become +** regular old; they and anything else are removed from the list. +*/ +static GCObject **correctgraylist (GCObject **p) { + GCObject *curr; + while ((curr = *p) != NULL) { + GCObject **next = getgclist(curr); + if (iswhite(curr)) + goto remove; /* remove all white objects */ + else if (getage(curr) == G_TOUCHED1) { /* touched in this cycle? */ + lua_assert(isgray(curr)); + nw2black(curr); /* make it black, for next barrier */ + changeage(curr, G_TOUCHED1, G_TOUCHED2); + goto remain; /* keep it in the list and go to next element */ + } + else if (curr->tt == LUA_VTHREAD) { + lua_assert(isgray(curr)); + goto remain; /* keep non-white threads on the list */ + } + else { /* everything else is removed */ + lua_assert(isold(curr)); /* young objects should be white here */ + if (getage(curr) == G_TOUCHED2) /* advance from TOUCHED2... */ + changeage(curr, G_TOUCHED2, G_OLD); /* ... to OLD */ + nw2black(curr); /* make object black (to be removed) */ + goto remove; + } + remove: *p = *next; continue; + remain: p = next; continue; + } + return p; +} + + +/* +** Correct all gray lists, coalescing them into 'grayagain'. +*/ +static void correctgraylists (global_State *g) { + GCObject **list = correctgraylist(&g->grayagain); + *list = g->weak; g->weak = NULL; + list = correctgraylist(list); + *list = g->allweak; g->allweak = NULL; + list = correctgraylist(list); + *list = g->ephemeron; g->ephemeron = NULL; + correctgraylist(list); +} + + +/* +** Mark black 'OLD1' objects when starting a new young collection. +** Gray objects are already in some gray list, and so will be visited +** in the atomic step. +*/ +static void markold (global_State *g, GCObject *from, GCObject *to) { + GCObject *p; + for (p = from; p != to; p = p->next) { + if (getage(p) == G_OLD1) { + lua_assert(!iswhite(p)); + changeage(p, G_OLD1, G_OLD); /* now they are old */ + if (isblack(p)) + reallymarkobject(g, p); + } + } +} + + +/* +** Finish a young-generation collection. +*/ +static void finishgencycle (lua_State *L, global_State *g) { + correctgraylists(g); + checkSizes(L, g); + g->gcstate = GCSpropagate; /* skip restart */ + if (!g->gcemergency) + callallpendingfinalizers(L); +} + + +/* +** Does a young collection. First, mark 'OLD1' objects. Then does the +** atomic step. Then, sweep all lists and advance pointers. Finally, +** finish the collection. +*/ +static void youngcollection (lua_State *L, global_State *g) { + GCObject **psurvival; /* to point to first non-dead survival object */ + GCObject *dummy; /* dummy out parameter to 'sweepgen' */ + lua_assert(g->gcstate == GCSpropagate); + if (g->firstold1) { /* are there regular OLD1 objects? */ + markold(g, g->firstold1, g->reallyold); /* mark them */ + g->firstold1 = NULL; /* no more OLD1 objects (for now) */ + } + markold(g, g->finobj, g->finobjrold); + markold(g, g->tobefnz, NULL); + atomic(L); + + /* sweep nursery and get a pointer to its last live element */ + g->gcstate = GCSswpallgc; + psurvival = sweepgen(L, g, &g->allgc, g->survival, &g->firstold1); + /* sweep 'survival' */ + sweepgen(L, g, psurvival, g->old1, &g->firstold1); + g->reallyold = g->old1; + g->old1 = *psurvival; /* 'survival' survivals are old now */ + g->survival = g->allgc; /* all news are survivals */ + + /* repeat for 'finobj' lists */ + dummy = NULL; /* no 'firstold1' optimization for 'finobj' lists */ + psurvival = sweepgen(L, g, &g->finobj, g->finobjsur, &dummy); + /* sweep 'survival' */ + sweepgen(L, g, psurvival, g->finobjold1, &dummy); + g->finobjrold = g->finobjold1; + g->finobjold1 = *psurvival; /* 'survival' survivals are old now */ + g->finobjsur = g->finobj; /* all news are survivals */ + + sweepgen(L, g, &g->tobefnz, NULL, &dummy); + finishgencycle(L, g); +} + + +/* +** Clears all gray lists, sweeps objects, and prepare sublists to enter +** generational mode. The sweeps remove dead objects and turn all +** surviving objects to old. Threads go back to 'grayagain'; everything +** else is turned black (not in any gray list). +*/ +static void atomic2gen (lua_State *L, global_State *g) { + cleargraylists(g); + /* sweep all elements making them old */ + g->gcstate = GCSswpallgc; + sweep2old(L, &g->allgc); + /* everything alive now is old */ + g->reallyold = g->old1 = g->survival = g->allgc; + g->firstold1 = NULL; /* there are no OLD1 objects anywhere */ + + /* repeat for 'finobj' lists */ + sweep2old(L, &g->finobj); + g->finobjrold = g->finobjold1 = g->finobjsur = g->finobj; + + sweep2old(L, &g->tobefnz); + + g->gckind = KGC_GEN; + g->lastatomic = 0; + g->GCestimate = gettotalbytes(g); /* base for memory control */ + finishgencycle(L, g); +} + + +/* +** Enter generational mode. Must go until the end of an atomic cycle +** to ensure that all objects are correctly marked and weak tables +** are cleared. Then, turn all objects into old and finishes the +** collection. +*/ +static lu_mem entergen (lua_State *L, global_State *g) { + lu_mem numobjs; + luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */ + luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + numobjs = atomic(L); /* propagates all and then do the atomic stuff */ + atomic2gen(L, g); + return numobjs; +} + + +/* +** Enter incremental mode. Turn all objects white, make all +** intermediate lists point to NULL (to avoid invalid pointers), +** and go to the pause state. +*/ +static void enterinc (global_State *g) { + whitelist(g, g->allgc); + g->reallyold = g->old1 = g->survival = NULL; + whitelist(g, g->finobj); + whitelist(g, g->tobefnz); + g->finobjrold = g->finobjold1 = g->finobjsur = NULL; + g->gcstate = GCSpause; + g->gckind = KGC_INC; + g->lastatomic = 0; +} + + +/* +** Change collector mode to 'newmode'. +*/ +void luaC_changemode (lua_State *L, int newmode) { + global_State *g = G(L); + if (newmode != g->gckind) { + if (newmode == KGC_GEN) /* entering generational mode? */ + entergen(L, g); + else + enterinc(g); /* entering incremental mode */ + } + g->lastatomic = 0; +} + + +/* +** Does a full collection in generational mode. +*/ +static lu_mem fullgen (lua_State *L, global_State *g) { + enterinc(g); + return entergen(L, g); +} + + +/* +** Set debt for the next minor collection, which will happen when +** memory grows 'genminormul'%. +*/ +static void setminordebt (global_State *g) { + luaE_setdebt(g, -(cast(l_mem, (gettotalbytes(g) / 100)) * g->genminormul)); +} + + +/* +** Does a major collection after last collection was a "bad collection". +** +** When the program is building a big structure, it allocates lots of +** memory but generates very little garbage. In those scenarios, +** the generational mode just wastes time doing small collections, and +** major collections are frequently what we call a "bad collection", a +** collection that frees too few objects. To avoid the cost of switching +** between generational mode and the incremental mode needed for full +** (major) collections, the collector tries to stay in incremental mode +** after a bad collection, and to switch back to generational mode only +** after a "good" collection (one that traverses less than 9/8 objects +** of the previous one). +** The collector must choose whether to stay in incremental mode or to +** switch back to generational mode before sweeping. At this point, it +** does not know the real memory in use, so it cannot use memory to +** decide whether to return to generational mode. Instead, it uses the +** number of objects traversed (returned by 'atomic') as a proxy. The +** field 'g->lastatomic' keeps this count from the last collection. +** ('g->lastatomic != 0' also means that the last collection was bad.) +*/ +static void stepgenfull (lua_State *L, global_State *g) { + lu_mem newatomic; /* count of traversed objects */ + lu_mem lastatomic = g->lastatomic; /* count from last collection */ + if (g->gckind == KGC_GEN) /* still in generational mode? */ + enterinc(g); /* enter incremental mode */ + luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + newatomic = atomic(L); /* mark everybody */ + if (newatomic < lastatomic + (lastatomic >> 3)) { /* good collection? */ + atomic2gen(L, g); /* return to generational mode */ + setminordebt(g); + } + else { /* another bad collection; stay in incremental mode */ + g->GCestimate = gettotalbytes(g); /* first estimate */; + entersweep(L); + luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ + setpause(g); + g->lastatomic = newatomic; + } +} + + +/* +** Does a generational "step". +** Usually, this means doing a minor collection and setting the debt to +** make another collection when memory grows 'genminormul'% larger. +** +** However, there are exceptions. If memory grows 'genmajormul'% +** larger than it was at the end of the last major collection (kept +** in 'g->GCestimate'), the function does a major collection. At the +** end, it checks whether the major collection was able to free a +** decent amount of memory (at least half the growth in memory since +** previous major collection). If so, the collector keeps its state, +** and the next collection will probably be minor again. Otherwise, +** we have what we call a "bad collection". In that case, set the field +** 'g->lastatomic' to signal that fact, so that the next collection will +** go to 'stepgenfull'. +** +** 'GCdebt <= 0' means an explicit call to GC step with "size" zero; +** in that case, do a minor collection. +*/ +static void genstep (lua_State *L, global_State *g) { + if (g->lastatomic != 0) /* last collection was a bad one? */ + stepgenfull(L, g); /* do a full step */ + else { + lu_mem majorbase = g->GCestimate; /* memory after last major collection */ + lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul); + if (g->GCdebt > 0 && gettotalbytes(g) > majorbase + majorinc) { + lu_mem numobjs = fullgen(L, g); /* do a major collection */ + if (gettotalbytes(g) < majorbase + (majorinc / 2)) { + /* collected at least half of memory growth since last major + collection; keep doing minor collections */ + setminordebt(g); + } + else { /* bad collection */ + g->lastatomic = numobjs; /* signal that last collection was bad */ + setpause(g); /* do a long wait for next (major) collection */ + } + } + else { /* regular case; do a minor collection */ + youngcollection(L, g); + setminordebt(g); + g->GCestimate = majorbase; /* preserve base value */ + } + } + lua_assert(isdecGCmodegen(g)); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** GC control +** ======================================================= +*/ + + +/* +** Set the "time" to wait before starting a new GC cycle; cycle will +** start when memory use hits the threshold of ('estimate' * pause / +** PAUSEADJ). (Division by 'estimate' should be OK: it cannot be zero, +** because Lua cannot even start with less than PAUSEADJ bytes). +*/ +static void setpause (global_State *g) { + l_mem threshold, debt; + int pause = getgcparam(g->gcpause); + l_mem estimate = g->GCestimate / PAUSEADJ; /* adjust 'estimate' */ + lua_assert(estimate > 0); + threshold = (pause < MAX_LMEM / estimate) /* overflow? */ + ? estimate * pause /* no overflow */ + : MAX_LMEM; /* overflow; truncate to maximum */ + debt = gettotalbytes(g) - threshold; + if (debt > 0) debt = 0; + luaE_setdebt(g, debt); +} + + +/* +** Enter first sweep phase. +** The call to 'sweeptolive' makes the pointer point to an object +** inside the list (instead of to the header), so that the real sweep do +** not need to skip objects created between "now" and the start of the +** real sweep. +*/ +static void entersweep (lua_State *L) { + global_State *g = G(L); + g->gcstate = GCSswpallgc; + lua_assert(g->sweepgc == NULL); + g->sweepgc = sweeptolive(L, &g->allgc); +} + + +/* +** Delete all objects in list 'p' until (but not including) object +** 'limit'. +*/ +static void deletelist (lua_State *L, GCObject *p, GCObject *limit) { + while (p != limit) { + GCObject *next = p->next; + freeobj(L, p); + p = next; + } +} + + +/* +** Call all finalizers of the objects in the given Lua state, and +** then free all objects, except for the main thread. +*/ +void luaC_freeallobjects (lua_State *L) { + global_State *g = G(L); + luaC_changemode(L, KGC_INC); + separatetobefnz(g, 1); /* separate all objects with finalizers */ + lua_assert(g->finobj == NULL); + callallpendingfinalizers(L); + deletelist(L, g->allgc, obj2gco(g->mainthread)); + deletelist(L, g->finobj, NULL); + deletelist(L, g->fixedgc, NULL); /* collect fixed objects */ + lua_assert(g->strt.nuse == 0); +} + + +static lu_mem atomic (lua_State *L) { + global_State *g = G(L); + lu_mem work = 0; + GCObject *origweak, *origall; + GCObject *grayagain = g->grayagain; /* save original list */ + g->grayagain = NULL; + lua_assert(g->ephemeron == NULL && g->weak == NULL); + lua_assert(!iswhite(g->mainthread)); + g->gcstate = GCSatomic; + markobject(g, L); /* mark running thread */ + /* registry and global metatables may be changed by API */ + markvalue(g, &g->l_registry); + markmt(g); /* mark global metatables */ + work += propagateall(g); /* empties 'gray' list */ + /* remark occasional upvalues of (maybe) dead threads */ + work += remarkupvals(g); + work += propagateall(g); /* propagate changes */ + g->gray = grayagain; + work += propagateall(g); /* traverse 'grayagain' list */ + convergeephemerons(g); + /* at this point, all strongly accessible objects are marked. */ + /* Clear values from weak tables, before checking finalizers */ + clearbyvalues(g, g->weak, NULL); + clearbyvalues(g, g->allweak, NULL); + origweak = g->weak; origall = g->allweak; + separatetobefnz(g, 0); /* separate objects to be finalized */ + work += markbeingfnz(g); /* mark objects that will be finalized */ + work += propagateall(g); /* remark, to propagate 'resurrection' */ + convergeephemerons(g); + /* at this point, all resurrected objects are marked. */ + /* remove dead objects from weak tables */ + clearbykeys(g, g->ephemeron); /* clear keys from all ephemeron tables */ + clearbykeys(g, g->allweak); /* clear keys from all 'allweak' tables */ + /* clear values from resurrected weak tables */ + clearbyvalues(g, g->weak, origweak); + clearbyvalues(g, g->allweak, origall); + luaS_clearcache(g); + g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */ + lua_assert(g->gray == NULL); + return work; /* estimate of slots marked by 'atomic' */ +} + + +static int sweepstep (lua_State *L, global_State *g, + int nextstate, GCObject **nextlist) { + if (g->sweepgc) { + l_mem olddebt = g->GCdebt; + int count; + g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX, &count); + g->GCestimate += g->GCdebt - olddebt; /* update estimate */ + return count; + } + else { /* enter next state */ + g->gcstate = nextstate; + g->sweepgc = nextlist; + return 0; /* no work done */ + } +} + + +static lu_mem singlestep (lua_State *L) { + global_State *g = G(L); + switch (g->gcstate) { + case GCSpause: { + restartcollection(g); + g->gcstate = GCSpropagate; + return 1; + } + case GCSpropagate: { + if (g->gray == NULL) { /* no more gray objects? */ + g->gcstate = GCSenteratomic; /* finish propagate phase */ + return 0; + } + else + return propagatemark(g); /* traverse one gray object */ + } + case GCSenteratomic: { + lu_mem work = atomic(L); /* work is what was traversed by 'atomic' */ + entersweep(L); + g->GCestimate = gettotalbytes(g); /* first estimate */; + return work; + } + case GCSswpallgc: { /* sweep "regular" objects */ + return sweepstep(L, g, GCSswpfinobj, &g->finobj); + } + case GCSswpfinobj: { /* sweep objects with finalizers */ + return sweepstep(L, g, GCSswptobefnz, &g->tobefnz); + } + case GCSswptobefnz: { /* sweep objects to be finalized */ + return sweepstep(L, g, GCSswpend, NULL); + } + case GCSswpend: { /* finish sweeps */ + checkSizes(L, g); + g->gcstate = GCScallfin; + return 0; + } + case GCScallfin: { /* call remaining finalizers */ + if (g->tobefnz && !g->gcemergency) { + int n = runafewfinalizers(L, GCFINMAX); + return n * GCFINALIZECOST; + } + else { /* emergency mode or no more finalizers */ + g->gcstate = GCSpause; /* finish collection */ + return 0; + } + } + default: lua_assert(0); return 0; + } +} + + +/* +** advances the garbage collector until it reaches a state allowed +** by 'statemask' +*/ +void luaC_runtilstate (lua_State *L, int statesmask) { + global_State *g = G(L); + while (!testbit(statesmask, g->gcstate)) + singlestep(L); +} + + +/* +** Performs a basic incremental step. The debt and step size are +** converted from bytes to "units of work"; then the function loops +** running single steps until adding that many units of work or +** finishing a cycle (pause state). Finally, it sets the debt that +** controls when next step will be performed. +*/ +static void incstep (lua_State *L, global_State *g) { + int stepmul = (getgcparam(g->gcstepmul) | 1); /* avoid division by 0 */ + l_mem debt = (g->GCdebt / WORK2MEM) * stepmul; + l_mem stepsize = (g->gcstepsize <= log2maxs(l_mem)) + ? ((cast(l_mem, 1) << g->gcstepsize) / WORK2MEM) * stepmul + : MAX_LMEM; /* overflow; keep maximum value */ + do { /* repeat until pause or enough "credit" (negative debt) */ + lu_mem work = singlestep(L); /* perform one single step */ + debt -= work; + } while (debt > -stepsize && g->gcstate != GCSpause); + if (g->gcstate == GCSpause) + setpause(g); /* pause until next cycle */ + else { + debt = (debt / stepmul) * WORK2MEM; /* convert 'work units' to bytes */ + luaE_setdebt(g, debt); + } +} + +/* +** performs a basic GC step if collector is running +*/ +void luaC_step (lua_State *L) { + global_State *g = G(L); + lua_assert(!g->gcemergency); + if (g->gcrunning) { /* running? */ + if(isdecGCmodegen(g)) + genstep(L, g); + else + incstep(L, g); + } +} + + +/* +** Perform a full collection in incremental mode. +** Before running the collection, check 'keepinvariant'; if it is true, +** there may be some objects marked as black, so the collector has +** to sweep all objects to turn them back to white (as white has not +** changed, nothing will be collected). +*/ +static void fullinc (lua_State *L, global_State *g) { + if (keepinvariant(g)) /* black objects? */ + entersweep(L); /* sweep everything to turn them back to white */ + /* finish any pending sweep phase to start a new cycle */ + luaC_runtilstate(L, bitmask(GCSpause)); + luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */ + /* estimate must be correct after a full GC cycle */ + lua_assert(g->GCestimate == gettotalbytes(g)); + luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ + setpause(g); +} + + +/* +** Performs a full GC cycle; if 'isemergency', set a flag to avoid +** some operations which could change the interpreter state in some +** unexpected ways (running finalizers and shrinking some structures). +*/ +void luaC_fullgc (lua_State *L, int isemergency) { + global_State *g = G(L); + lua_assert(!g->gcemergency); + g->gcemergency = isemergency; /* set flag */ + if (g->gckind == KGC_INC) + fullinc(L, g); + else + fullgen(L, g); + g->gcemergency = 0; +} + +/* }====================================================== */ + + diff --git a/Lua/lgc.h b/Lua/lgc.h new file mode 100644 index 00000000..073e2a40 --- /dev/null +++ b/Lua/lgc.h @@ -0,0 +1,189 @@ +/* +** $Id: lgc.h $ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#ifndef lgc_h +#define lgc_h + + +#include "lobject.h" +#include "lstate.h" + +/* +** Collectable objects may have one of three colors: white, which means +** the object is not marked; gray, which means the object is marked, but +** its references may be not marked; and black, which means that the +** object and all its references are marked. The main invariant of the +** garbage collector, while marking objects, is that a black object can +** never point to a white one. Moreover, any gray object must be in a +** "gray list" (gray, grayagain, weak, allweak, ephemeron) so that it +** can be visited again before finishing the collection cycle. (Open +** upvalues are an exception to this rule.) These lists have no meaning +** when the invariant is not being enforced (e.g., sweep phase). +*/ + + +/* +** Possible states of the Garbage Collector +*/ +#define GCSpropagate 0 +#define GCSenteratomic 1 +#define GCSatomic 2 +#define GCSswpallgc 3 +#define GCSswpfinobj 4 +#define GCSswptobefnz 5 +#define GCSswpend 6 +#define GCScallfin 7 +#define GCSpause 8 + + +#define issweepphase(g) \ + (GCSswpallgc <= (g)->gcstate && (g)->gcstate <= GCSswpend) + + +/* +** macro to tell when main invariant (white objects cannot point to black +** ones) must be kept. During a collection, the sweep +** phase may break the invariant, as objects turned white may point to +** still-black objects. The invariant is restored when sweep ends and +** all objects are white again. +*/ + +#define keepinvariant(g) ((g)->gcstate <= GCSatomic) + + +/* +** some useful bit tricks +*/ +#define resetbits(x,m) ((x) &= cast_byte(~(m))) +#define setbits(x,m) ((x) |= (m)) +#define testbits(x,m) ((x) & (m)) +#define bitmask(b) (1<<(b)) +#define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2)) +#define l_setbit(x,b) setbits(x, bitmask(b)) +#define resetbit(x,b) resetbits(x, bitmask(b)) +#define testbit(x,b) testbits(x, bitmask(b)) + + +/* +** Layout for bit use in 'marked' field. First three bits are +** used for object "age" in generational mode. Last bit is used +** by tests. +*/ +#define WHITE0BIT 3 /* object is white (type 0) */ +#define WHITE1BIT 4 /* object is white (type 1) */ +#define BLACKBIT 5 /* object is black */ +#define FINALIZEDBIT 6 /* object has been marked for finalization */ + +#define TESTBIT 7 + + + +#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) + + +#define iswhite(x) testbits((x)->marked, WHITEBITS) +#define isblack(x) testbit((x)->marked, BLACKBIT) +#define isgray(x) /* neither white nor black */ \ + (!testbits((x)->marked, WHITEBITS | bitmask(BLACKBIT))) + +#define tofinalize(x) testbit((x)->marked, FINALIZEDBIT) + +#define otherwhite(g) ((g)->currentwhite ^ WHITEBITS) +#define isdeadm(ow,m) ((m) & (ow)) +#define isdead(g,v) isdeadm(otherwhite(g), (v)->marked) + +#define changewhite(x) ((x)->marked ^= WHITEBITS) +#define nw2black(x) \ + check_exp(!iswhite(x), l_setbit((x)->marked, BLACKBIT)) + +#define luaC_white(g) cast_byte((g)->currentwhite & WHITEBITS) + + +/* object age in generational mode */ +#define G_NEW 0 /* created in current cycle */ +#define G_SURVIVAL 1 /* created in previous cycle */ +#define G_OLD0 2 /* marked old by frw. barrier in this cycle */ +#define G_OLD1 3 /* first full cycle as old */ +#define G_OLD 4 /* really old object (not to be visited) */ +#define G_TOUCHED1 5 /* old object touched this cycle */ +#define G_TOUCHED2 6 /* old object touched in previous cycle */ + +#define AGEBITS 7 /* all age bits (111) */ + +#define getage(o) ((o)->marked & AGEBITS) +#define setage(o,a) ((o)->marked = cast_byte(((o)->marked & (~AGEBITS)) | a)) +#define isold(o) (getage(o) > G_SURVIVAL) + +#define changeage(o,f,t) \ + check_exp(getage(o) == (f), (o)->marked ^= ((f)^(t))) + + +/* Default Values for GC parameters */ +#define LUAI_GENMAJORMUL 100 +#define LUAI_GENMINORMUL 20 + +/* wait memory to double before starting new cycle */ +#define LUAI_GCPAUSE 200 + +/* +** some gc parameters are stored divided by 4 to allow a maximum value +** up to 1023 in a 'lu_byte'. +*/ +#define getgcparam(p) ((p) * 4) +#define setgcparam(p,v) ((p) = (v) / 4) + +#define LUAI_GCMUL 100 + +/* how much to allocate before next GC step (log2) */ +#define LUAI_GCSTEPSIZE 13 /* 8 KB */ + + +/* +** Check whether the declared GC mode is generational. While in +** generational mode, the collector can go temporarily to incremental +** mode to improve performance. This is signaled by 'g->lastatomic != 0'. +*/ +#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0) + +/* +** Does one step of collection when debt becomes positive. 'pre'/'pos' +** allows some adjustments to be done only when needed. macro +** 'condchangemem' is used only for heavy tests (forcing a full +** GC cycle on every opportunity) +*/ +#define luaC_condGC(L,pre,pos) \ + { if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \ + condchangemem(L,pre,pos); } + +/* more often than not, 'pre'/'pos' are empty */ +#define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0) + + +#define luaC_barrier(L,p,v) ( \ + (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \ + luaC_barrier_(L,obj2gco(p),gcvalue(v)) : cast_void(0)) + +#define luaC_barrierback(L,p,v) ( \ + (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \ + luaC_barrierback_(L,p) : cast_void(0)) + +#define luaC_objbarrier(L,p,o) ( \ + (isblack(p) && iswhite(o)) ? \ + luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0)) + +LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); +LUAI_FUNC void luaC_freeallobjects (lua_State *L); +LUAI_FUNC void luaC_step (lua_State *L); +LUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask); +LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency); +LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz); +LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v); +LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o); +LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt); +LUAI_FUNC void luaC_changemode (lua_State *L, int newmode); + + +#endif diff --git a/Lua/linit.c b/Lua/linit.c new file mode 100644 index 00000000..69808f84 --- /dev/null +++ b/Lua/linit.c @@ -0,0 +1,65 @@ +/* +** $Id: linit.c $ +** Initialization of libraries for lua.c and other clients +** See Copyright Notice in lua.h +*/ + + +#define linit_c +#define LUA_LIB + +/* +** If you embed Lua in your program and need to open the standard +** libraries, call luaL_openlibs in your program. If you need a +** different set of libraries, copy this file to your project and edit +** it to suit your needs. +** +** You can also *preload* libraries, so that a later 'require' can +** open the library, which is already linked to the application. +** For that, do the following code: +** +** luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); +** lua_pushcfunction(L, luaopen_modname); +** lua_setfield(L, -2, modname); +** lua_pop(L, 1); // remove PRELOAD table +*/ + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "lualib.h" +#include "lauxlib.h" + + +/* +** these libs are loaded by lua.c and are readily available to any Lua +** program +*/ +static const luaL_Reg loadedlibs[] = { + {LUA_GNAME, luaopen_base}, + {LUA_LOADLIBNAME, luaopen_package}, + {LUA_COLIBNAME, luaopen_coroutine}, + {LUA_TABLIBNAME, luaopen_table}, + {LUA_IOLIBNAME, luaopen_io}, + {LUA_OSLIBNAME, luaopen_os}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + {LUA_UTF8LIBNAME, luaopen_utf8}, + {LUA_DBLIBNAME, luaopen_debug}, + {NULL, NULL} +}; + + +LUALIB_API void luaL_openlibs (lua_State *L) { + const luaL_Reg *lib; + /* "require" functions from 'loadedlibs' and set results to global table */ + for (lib = loadedlibs; lib->func; lib++) { + luaL_requiref(L, lib->name, lib->func, 1); + lua_pop(L, 1); /* remove lib */ + } +} + diff --git a/Lua/liolib.c b/Lua/liolib.c new file mode 100644 index 00000000..60ab1bfa --- /dev/null +++ b/Lua/liolib.c @@ -0,0 +1,821 @@ +/* +** $Id: liolib.c $ +** Standard I/O (and system) library +** See Copyright Notice in lua.h +*/ + +#define liolib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + + + +/* +** Change this macro to accept other modes for 'fopen' besides +** the standard ones. +*/ +#if !defined(l_checkmode) + +/* accepted extensions to 'mode' in 'fopen' */ +#if !defined(L_MODEEXT) +#define L_MODEEXT "b" +#endif + +/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */ +static int l_checkmode (const char *mode) { + return (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && + (*mode != '+' || ((void)(++mode), 1)) && /* skip if char is '+' */ + (strspn(mode, L_MODEEXT) == strlen(mode))); /* check extensions */ +} + +#endif + +/* +** {====================================================== +** l_popen spawns a new process connected to the current +** one through the file streams. +** ======================================================= +*/ + +#if !defined(l_checkmodep) +/* By default, Lua accepts only "r" or "w" as mode */ +#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && m[1] == '\0') +#endif + + +#if !defined(l_popen) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#define l_popen(L,c,m) (fflush(NULL), popen(c,m)) +#define l_pclose(L,file) (pclose(file)) + +#elif defined(LUA_USE_WINDOWS) /* }{ */ + +#define l_popen(L,c,m) (_popen(c,m)) +#define l_pclose(L,file) (_pclose(file)) + +#else /* }{ */ + +/* ISO C definitions */ +#define l_popen(L,c,m) \ + ((void)c, (void)m, \ + luaL_error(L, "'popen' not supported"), \ + (FILE*)0) +#define l_pclose(L,file) ((void)L, (void)file, -1) + +#endif /* } */ + +#endif /* } */ + +/* }====================================================== */ + + +#if !defined(l_getc) /* { */ + +#if defined(LUA_USE_POSIX) +#define l_getc(f) getc_unlocked(f) +#define l_lockfile(f) flockfile(f) +#define l_unlockfile(f) funlockfile(f) +#else +#define l_getc(f) getc(f) +#define l_lockfile(f) ((void)0) +#define l_unlockfile(f) ((void)0) +#endif + +#endif /* } */ + + +/* +** {====================================================== +** l_fseek: configuration for longer offsets +** ======================================================= +*/ + +#if !defined(l_fseek) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#include + +#define l_fseek(f,o,w) fseeko(f,o,w) +#define l_ftell(f) ftello(f) +#define l_seeknum off_t + +#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \ + && defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */ + +/* Windows (but not DDK) and Visual C++ 2005 or higher */ +#define l_fseek(f,o,w) _fseeki64(f,o,w) +#define l_ftell(f) _ftelli64(f) +#define l_seeknum __int64 + +#else /* }{ */ + +/* ISO C definitions */ +#define l_fseek(f,o,w) fseek(f,o,w) +#define l_ftell(f) ftell(f) +#define l_seeknum long + +#endif /* } */ + +#endif /* } */ + +/* }====================================================== */ + + + +#define IO_PREFIX "_IO_" +#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1) +#define IO_INPUT (IO_PREFIX "input") +#define IO_OUTPUT (IO_PREFIX "output") + + +typedef luaL_Stream LStream; + + +#define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE)) + +#define isclosed(p) ((p)->closef == NULL) + + +static int io_type (lua_State *L) { + LStream *p; + luaL_checkany(L, 1); + p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE); + if (p == NULL) + luaL_pushfail(L); /* not a file */ + else if (isclosed(p)) + lua_pushliteral(L, "closed file"); + else + lua_pushliteral(L, "file"); + return 1; +} + + +static int f_tostring (lua_State *L) { + LStream *p = tolstream(L); + if (isclosed(p)) + lua_pushliteral(L, "file (closed)"); + else + lua_pushfstring(L, "file (%p)", p->f); + return 1; +} + + +static FILE *tofile (lua_State *L) { + LStream *p = tolstream(L); + if (isclosed(p)) + luaL_error(L, "attempt to use a closed file"); + lua_assert(p->f); + return p->f; +} + + +/* +** When creating file handles, always creates a 'closed' file handle +** before opening the actual file; so, if there is a memory error, the +** handle is in a consistent state. +*/ +static LStream *newprefile (lua_State *L) { + LStream *p = (LStream *)lua_newuserdatauv(L, sizeof(LStream), 0); + p->closef = NULL; /* mark file handle as 'closed' */ + luaL_setmetatable(L, LUA_FILEHANDLE); + return p; +} + + +/* +** Calls the 'close' function from a file handle. The 'volatile' avoids +** a bug in some versions of the Clang compiler (e.g., clang 3.0 for +** 32 bits). +*/ +static int aux_close (lua_State *L) { + LStream *p = tolstream(L); + volatile lua_CFunction cf = p->closef; + p->closef = NULL; /* mark stream as closed */ + return (*cf)(L); /* close it */ +} + + +static int f_close (lua_State *L) { + tofile(L); /* make sure argument is an open stream */ + return aux_close(L); +} + + +static int io_close (lua_State *L) { + if (lua_isnone(L, 1)) /* no argument? */ + lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT); /* use default output */ + return f_close(L); +} + + +static int f_gc (lua_State *L) { + LStream *p = tolstream(L); + if (!isclosed(p) && p->f != NULL) + aux_close(L); /* ignore closed and incompletely open files */ + return 0; +} + + +/* +** function to close regular files +*/ +static int io_fclose (lua_State *L) { + LStream *p = tolstream(L); + int res = fclose(p->f); + return luaL_fileresult(L, (res == 0), NULL); +} + + +static LStream *newfile (lua_State *L) { + LStream *p = newprefile(L); + p->f = NULL; + p->closef = &io_fclose; + return p; +} + + +static void opencheck (lua_State *L, const char *fname, const char *mode) { + LStream *p = newfile(L); + p->f = fopen(fname, mode); + if (p->f == NULL) + luaL_error(L, "cannot open file '%s' (%s)", fname, strerror(errno)); +} + + +static int io_open (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + LStream *p = newfile(L); + const char *md = mode; /* to traverse/check mode */ + luaL_argcheck(L, l_checkmode(md), 2, "invalid mode"); + p->f = fopen(filename, mode); + return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; +} + + +/* +** function to close 'popen' files +*/ +static int io_pclose (lua_State *L) { + LStream *p = tolstream(L); + errno = 0; + return luaL_execresult(L, l_pclose(L, p->f)); +} + + +static int io_popen (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + LStream *p = newprefile(L); + luaL_argcheck(L, l_checkmodep(mode), 2, "invalid mode"); + p->f = l_popen(L, filename, mode); + p->closef = &io_pclose; + return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; +} + + +static int io_tmpfile (lua_State *L) { + LStream *p = newfile(L); + p->f = tmpfile(); + return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1; +} + + +static FILE *getiofile (lua_State *L, const char *findex) { + LStream *p; + lua_getfield(L, LUA_REGISTRYINDEX, findex); + p = (LStream *)lua_touserdata(L, -1); + if (isclosed(p)) + luaL_error(L, "default %s file is closed", findex + IOPREF_LEN); + return p->f; +} + + +static int g_iofile (lua_State *L, const char *f, const char *mode) { + if (!lua_isnoneornil(L, 1)) { + const char *filename = lua_tostring(L, 1); + if (filename) + opencheck(L, filename, mode); + else { + tofile(L); /* check that it's a valid file handle */ + lua_pushvalue(L, 1); + } + lua_setfield(L, LUA_REGISTRYINDEX, f); + } + /* return current value */ + lua_getfield(L, LUA_REGISTRYINDEX, f); + return 1; +} + + +static int io_input (lua_State *L) { + return g_iofile(L, IO_INPUT, "r"); +} + + +static int io_output (lua_State *L) { + return g_iofile(L, IO_OUTPUT, "w"); +} + + +static int io_readline (lua_State *L); + + +/* +** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit +** in the limit for upvalues of a closure) +*/ +#define MAXARGLINE 250 + +/* +** Auxiliary function to create the iteration function for 'lines'. +** The iteration function is a closure over 'io_readline', with +** the following upvalues: +** 1) The file being read (first value in the stack) +** 2) the number of arguments to read +** 3) a boolean, true iff file has to be closed when finished ('toclose') +** *) a variable number of format arguments (rest of the stack) +*/ +static void aux_lines (lua_State *L, int toclose) { + int n = lua_gettop(L) - 1; /* number of arguments to read */ + luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); + lua_pushvalue(L, 1); /* file */ + lua_pushinteger(L, n); /* number of arguments to read */ + lua_pushboolean(L, toclose); /* close/not close file when finished */ + lua_rotate(L, 2, 3); /* move the three values to their positions */ + lua_pushcclosure(L, io_readline, 3 + n); +} + + +static int f_lines (lua_State *L) { + tofile(L); /* check that it's a valid file handle */ + aux_lines(L, 0); + return 1; +} + + +/* +** Return an iteration function for 'io.lines'. If file has to be +** closed, also returns the file itself as a second result (to be +** closed as the state at the exit of a generic for). +*/ +static int io_lines (lua_State *L) { + int toclose; + if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ + if (lua_isnil(L, 1)) { /* no file name? */ + lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT); /* get default input */ + lua_replace(L, 1); /* put it at index 1 */ + tofile(L); /* check that it's a valid file handle */ + toclose = 0; /* do not close it after iteration */ + } + else { /* open a new file */ + const char *filename = luaL_checkstring(L, 1); + opencheck(L, filename, "r"); + lua_replace(L, 1); /* put file at index 1 */ + toclose = 1; /* close it after iteration */ + } + aux_lines(L, toclose); /* push iteration function */ + if (toclose) { + lua_pushnil(L); /* state */ + lua_pushnil(L); /* control */ + lua_pushvalue(L, 1); /* file is the to-be-closed variable (4th result) */ + return 4; + } + else + return 1; +} + + +/* +** {====================================================== +** READ +** ======================================================= +*/ + + +/* maximum length of a numeral */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + + +/* auxiliary structure used by 'read_number' */ +typedef struct { + FILE *f; /* file being read */ + int c; /* current character (look ahead) */ + int n; /* number of elements in buffer 'buff' */ + char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */ +} RN; + + +/* +** Add current char to buffer (if not out of space) and read next one +*/ +static int nextc (RN *rn) { + if (rn->n >= L_MAXLENNUM) { /* buffer overflow? */ + rn->buff[0] = '\0'; /* invalidate result */ + return 0; /* fail */ + } + else { + rn->buff[rn->n++] = rn->c; /* save current char */ + rn->c = l_getc(rn->f); /* read next one */ + return 1; + } +} + + +/* +** Accept current char if it is in 'set' (of size 2) +*/ +static int test2 (RN *rn, const char *set) { + if (rn->c == set[0] || rn->c == set[1]) + return nextc(rn); + else return 0; +} + + +/* +** Read a sequence of (hex)digits +*/ +static int readdigits (RN *rn, int hex) { + int count = 0; + while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn)) + count++; + return count; +} + + +/* +** Read a number: first reads a valid prefix of a numeral into a buffer. +** Then it calls 'lua_stringtonumber' to check whether the format is +** correct and to convert it to a Lua number. +*/ +static int read_number (lua_State *L, FILE *f) { + RN rn; + int count = 0; + int hex = 0; + char decp[2]; + rn.f = f; rn.n = 0; + decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */ + decp[1] = '.'; /* always accept a dot */ + l_lockfile(rn.f); + do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */ + test2(&rn, "-+"); /* optional sign */ + if (test2(&rn, "00")) { + if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */ + else count = 1; /* count initial '0' as a valid digit */ + } + count += readdigits(&rn, hex); /* integral part */ + if (test2(&rn, decp)) /* decimal point? */ + count += readdigits(&rn, hex); /* fractional part */ + if (count > 0 && test2(&rn, (hex ? "pP" : "eE"))) { /* exponent mark? */ + test2(&rn, "-+"); /* exponent sign */ + readdigits(&rn, 0); /* exponent digits */ + } + ungetc(rn.c, rn.f); /* unread look-ahead char */ + l_unlockfile(rn.f); + rn.buff[rn.n] = '\0'; /* finish string */ + if (lua_stringtonumber(L, rn.buff)) /* is this a valid number? */ + return 1; /* ok */ + else { /* invalid format */ + lua_pushnil(L); /* "result" to be removed */ + return 0; /* read fails */ + } +} + + +static int test_eof (lua_State *L, FILE *f) { + int c = getc(f); + ungetc(c, f); /* no-op when c == EOF */ + lua_pushliteral(L, ""); + return (c != EOF); +} + + +static int read_line (lua_State *L, FILE *f, int chop) { + luaL_Buffer b; + int c; + luaL_buffinit(L, &b); + do { /* may need to read several chunks to get whole line */ + char *buff = luaL_prepbuffer(&b); /* preallocate buffer space */ + int i = 0; + l_lockfile(f); /* no memory errors can happen inside the lock */ + while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n') + buff[i++] = c; /* read up to end of line or buffer limit */ + l_unlockfile(f); + luaL_addsize(&b, i); + } while (c != EOF && c != '\n'); /* repeat until end of line */ + if (!chop && c == '\n') /* want a newline and have one? */ + luaL_addchar(&b, c); /* add ending newline to result */ + luaL_pushresult(&b); /* close buffer */ + /* return ok if read something (either a newline or something else) */ + return (c == '\n' || lua_rawlen(L, -1) > 0); +} + + +static void read_all (lua_State *L, FILE *f) { + size_t nr; + luaL_Buffer b; + luaL_buffinit(L, &b); + do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ + char *p = luaL_prepbuffer(&b); + nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, f); + luaL_addsize(&b, nr); + } while (nr == LUAL_BUFFERSIZE); + luaL_pushresult(&b); /* close buffer */ +} + + +static int read_chars (lua_State *L, FILE *f, size_t n) { + size_t nr; /* number of chars actually read */ + char *p; + luaL_Buffer b; + luaL_buffinit(L, &b); + p = luaL_prepbuffsize(&b, n); /* prepare buffer to read whole block */ + nr = fread(p, sizeof(char), n, f); /* try to read 'n' chars */ + luaL_addsize(&b, nr); + luaL_pushresult(&b); /* close buffer */ + return (nr > 0); /* true iff read something */ +} + + +static int g_read (lua_State *L, FILE *f, int first) { + int nargs = lua_gettop(L) - 1; + int n, success; + clearerr(f); + if (nargs == 0) { /* no arguments? */ + success = read_line(L, f, 1); + n = first + 1; /* to return 1 result */ + } + else { + /* ensure stack space for all results and for auxlib's buffer */ + luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); + success = 1; + for (n = first; nargs-- && success; n++) { + if (lua_type(L, n) == LUA_TNUMBER) { + size_t l = (size_t)luaL_checkinteger(L, n); + success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); + } + else { + const char *p = luaL_checkstring(L, n); + if (*p == '*') p++; /* skip optional '*' (for compatibility) */ + switch (*p) { + case 'n': /* number */ + success = read_number(L, f); + break; + case 'l': /* line */ + success = read_line(L, f, 1); + break; + case 'L': /* line with end-of-line */ + success = read_line(L, f, 0); + break; + case 'a': /* file */ + read_all(L, f); /* read entire file */ + success = 1; /* always success */ + break; + default: + return luaL_argerror(L, n, "invalid format"); + } + } + } + } + if (ferror(f)) + return luaL_fileresult(L, 0, NULL); + if (!success) { + lua_pop(L, 1); /* remove last result */ + luaL_pushfail(L); /* push nil instead */ + } + return n - first; +} + + +static int io_read (lua_State *L) { + return g_read(L, getiofile(L, IO_INPUT), 1); +} + + +static int f_read (lua_State *L) { + return g_read(L, tofile(L), 2); +} + + +/* +** Iteration function for 'lines'. +*/ +static int io_readline (lua_State *L) { + LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); + int i; + int n = (int)lua_tointeger(L, lua_upvalueindex(2)); + if (isclosed(p)) /* file is already closed? */ + return luaL_error(L, "file is already closed"); + lua_settop(L , 1); + luaL_checkstack(L, n, "too many arguments"); + for (i = 1; i <= n; i++) /* push arguments to 'g_read' */ + lua_pushvalue(L, lua_upvalueindex(3 + i)); + n = g_read(L, p->f, 2); /* 'n' is number of results */ + lua_assert(n > 0); /* should return at least a nil */ + if (lua_toboolean(L, -n)) /* read at least one value? */ + return n; /* return them */ + else { /* first result is false: EOF or error */ + if (n > 1) { /* is there error information? */ + /* 2nd result is error message */ + return luaL_error(L, "%s", lua_tostring(L, -n + 1)); + } + if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ + lua_settop(L, 0); /* clear stack */ + lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */ + aux_close(L); /* close it */ + } + return 0; + } +} + +/* }====================================================== */ + + +static int g_write (lua_State *L, FILE *f, int arg) { + int nargs = lua_gettop(L) - arg; + int status = 1; + for (; nargs--; arg++) { + if (lua_type(L, arg) == LUA_TNUMBER) { + /* optimization: could be done exactly as for strings */ + int len = lua_isinteger(L, arg) + ? fprintf(f, LUA_INTEGER_FMT, + (LUAI_UACINT)lua_tointeger(L, arg)) + : fprintf(f, LUA_NUMBER_FMT, + (LUAI_UACNUMBER)lua_tonumber(L, arg)); + status = status && (len > 0); + } + else { + size_t l; + const char *s = luaL_checklstring(L, arg, &l); + status = status && (fwrite(s, sizeof(char), l, f) == l); + } + } + if (status) return 1; /* file handle already on stack top */ + else return luaL_fileresult(L, status, NULL); +} + + +static int io_write (lua_State *L) { + return g_write(L, getiofile(L, IO_OUTPUT), 1); +} + + +static int f_write (lua_State *L) { + FILE *f = tofile(L); + lua_pushvalue(L, 1); /* push file at the stack top (to be returned) */ + return g_write(L, f, 2); +} + + +static int f_seek (lua_State *L) { + static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; + static const char *const modenames[] = {"set", "cur", "end", NULL}; + FILE *f = tofile(L); + int op = luaL_checkoption(L, 2, "cur", modenames); + lua_Integer p3 = luaL_optinteger(L, 3, 0); + l_seeknum offset = (l_seeknum)p3; + luaL_argcheck(L, (lua_Integer)offset == p3, 3, + "not an integer in proper range"); + op = l_fseek(f, offset, mode[op]); + if (op) + return luaL_fileresult(L, 0, NULL); /* error */ + else { + lua_pushinteger(L, (lua_Integer)l_ftell(f)); + return 1; + } +} + + +static int f_setvbuf (lua_State *L) { + static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; + static const char *const modenames[] = {"no", "full", "line", NULL}; + FILE *f = tofile(L); + int op = luaL_checkoption(L, 2, NULL, modenames); + lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); + int res = setvbuf(f, NULL, mode[op], (size_t)sz); + return luaL_fileresult(L, res == 0, NULL); +} + + + +static int io_flush (lua_State *L) { + return luaL_fileresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL); +} + + +static int f_flush (lua_State *L) { + return luaL_fileresult(L, fflush(tofile(L)) == 0, NULL); +} + + +/* +** functions for 'io' library +*/ +static const luaL_Reg iolib[] = { + {"close", io_close}, + {"flush", io_flush}, + {"input", io_input}, + {"lines", io_lines}, + {"open", io_open}, + {"output", io_output}, + {"popen", io_popen}, + {"read", io_read}, + {"tmpfile", io_tmpfile}, + {"type", io_type}, + {"write", io_write}, + {NULL, NULL} +}; + + +/* +** methods for file handles +*/ +static const luaL_Reg meth[] = { + {"read", f_read}, + {"write", f_write}, + {"lines", f_lines}, + {"flush", f_flush}, + {"seek", f_seek}, + {"close", f_close}, + {"setvbuf", f_setvbuf}, + {NULL, NULL} +}; + + +/* +** metamethods for file handles +*/ +static const luaL_Reg metameth[] = { + {"__index", NULL}, /* place holder */ + {"__gc", f_gc}, + {"__close", f_gc}, + {"__tostring", f_tostring}, + {NULL, NULL} +}; + + +static void createmeta (lua_State *L) { + luaL_newmetatable(L, LUA_FILEHANDLE); /* metatable for file handles */ + luaL_setfuncs(L, metameth, 0); /* add metamethods to new metatable */ + luaL_newlibtable(L, meth); /* create method table */ + luaL_setfuncs(L, meth, 0); /* add file methods to method table */ + lua_setfield(L, -2, "__index"); /* metatable.__index = method table */ + lua_pop(L, 1); /* pop metatable */ +} + + +/* +** function to (not) close the standard files stdin, stdout, and stderr +*/ +static int io_noclose (lua_State *L) { + LStream *p = tolstream(L); + p->closef = &io_noclose; /* keep file opened */ + luaL_pushfail(L); + lua_pushliteral(L, "cannot close standard file"); + return 2; +} + + +static void createstdfile (lua_State *L, FILE *f, const char *k, + const char *fname) { + LStream *p = newprefile(L); + p->f = f; + p->closef = &io_noclose; + if (k != NULL) { + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, k); /* add file to registry */ + } + lua_setfield(L, -2, fname); /* add file to module */ +} + + +LUAMOD_API int luaopen_io (lua_State *L) { + luaL_newlib(L, iolib); /* new module */ + createmeta(L); + /* create (and set) default files */ + createstdfile(L, stdin, IO_INPUT, "stdin"); + createstdfile(L, stdout, IO_OUTPUT, "stdout"); + createstdfile(L, stderr, NULL, "stderr"); + return 1; +} + diff --git a/Lua/ljumptab.h b/Lua/ljumptab.h new file mode 100644 index 00000000..8306f250 --- /dev/null +++ b/Lua/ljumptab.h @@ -0,0 +1,112 @@ +/* +** $Id: ljumptab.h $ +** Jump Table for the Lua interpreter +** See Copyright Notice in lua.h +*/ + + +#undef vmdispatch +#undef vmcase +#undef vmbreak + +#define vmdispatch(x) goto *disptab[x]; + +#define vmcase(l) L_##l: + +#define vmbreak vmfetch(); vmdispatch(GET_OPCODE(i)); + + +static const void *const disptab[NUM_OPCODES] = { + +#if 0 +** you can update the following list with this command: +** +** sed -n '/^OP_/\!d; s/OP_/\&\&L_OP_/ ; s/,.*/,/ ; s/\/.*// ; p' lopcodes.h +** +#endif + +&&L_OP_MOVE, +&&L_OP_LOADI, +&&L_OP_LOADF, +&&L_OP_LOADK, +&&L_OP_LOADKX, +&&L_OP_LOADFALSE, +&&L_OP_LFALSESKIP, +&&L_OP_LOADTRUE, +&&L_OP_LOADNIL, +&&L_OP_GETUPVAL, +&&L_OP_SETUPVAL, +&&L_OP_GETTABUP, +&&L_OP_GETTABLE, +&&L_OP_GETI, +&&L_OP_GETFIELD, +&&L_OP_SETTABUP, +&&L_OP_SETTABLE, +&&L_OP_SETI, +&&L_OP_SETFIELD, +&&L_OP_NEWTABLE, +&&L_OP_SELF, +&&L_OP_ADDI, +&&L_OP_ADDK, +&&L_OP_SUBK, +&&L_OP_MULK, +&&L_OP_MODK, +&&L_OP_POWK, +&&L_OP_DIVK, +&&L_OP_IDIVK, +&&L_OP_BANDK, +&&L_OP_BORK, +&&L_OP_BXORK, +&&L_OP_SHRI, +&&L_OP_SHLI, +&&L_OP_ADD, +&&L_OP_SUB, +&&L_OP_MUL, +&&L_OP_MOD, +&&L_OP_POW, +&&L_OP_DIV, +&&L_OP_IDIV, +&&L_OP_BAND, +&&L_OP_BOR, +&&L_OP_BXOR, +&&L_OP_SHL, +&&L_OP_SHR, +&&L_OP_MMBIN, +&&L_OP_MMBINI, +&&L_OP_MMBINK, +&&L_OP_UNM, +&&L_OP_BNOT, +&&L_OP_NOT, +&&L_OP_LEN, +&&L_OP_CONCAT, +&&L_OP_CLOSE, +&&L_OP_TBC, +&&L_OP_JMP, +&&L_OP_EQ, +&&L_OP_LT, +&&L_OP_LE, +&&L_OP_EQK, +&&L_OP_EQI, +&&L_OP_LTI, +&&L_OP_LEI, +&&L_OP_GTI, +&&L_OP_GEI, +&&L_OP_TEST, +&&L_OP_TESTSET, +&&L_OP_CALL, +&&L_OP_TAILCALL, +&&L_OP_RETURN, +&&L_OP_RETURN0, +&&L_OP_RETURN1, +&&L_OP_FORLOOP, +&&L_OP_FORPREP, +&&L_OP_TFORPREP, +&&L_OP_TFORCALL, +&&L_OP_TFORLOOP, +&&L_OP_SETLIST, +&&L_OP_CLOSURE, +&&L_OP_VARARG, +&&L_OP_VARARGPREP, +&&L_OP_EXTRAARG + +}; diff --git a/Lua/llex.c b/Lua/llex.c new file mode 100644 index 00000000..4b8dec99 --- /dev/null +++ b/Lua/llex.c @@ -0,0 +1,578 @@ +/* +** $Id: llex.c $ +** Lexical Analyzer +** See Copyright Notice in lua.h +*/ + +#define llex_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "lctype.h" +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "llex.h" +#include "lobject.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lzio.h" + + + +#define next(ls) (ls->current = zgetc(ls->z)) + + + +#define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') + + +/* ORDER RESERVED */ +static const char *const luaX_tokens [] = { + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "goto", "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while", + "//", "..", "...", "==", ">=", "<=", "~=", + "<<", ">>", "::", "", + "", "", "", "" +}; + + +#define save_and_next(ls) (save(ls, ls->current), next(ls)) + + +static l_noret lexerror (LexState *ls, const char *msg, int token); + + +static void save (LexState *ls, int c) { + Mbuffer *b = ls->buff; + if (luaZ_bufflen(b) + 1 > luaZ_sizebuffer(b)) { + size_t newsize; + if (luaZ_sizebuffer(b) >= MAX_SIZE/2) + lexerror(ls, "lexical element too long", 0); + newsize = luaZ_sizebuffer(b) * 2; + luaZ_resizebuffer(ls->L, b, newsize); + } + b->buffer[luaZ_bufflen(b)++] = cast_char(c); +} + + +void luaX_init (lua_State *L) { + int i; + TString *e = luaS_newliteral(L, LUA_ENV); /* create env name */ + luaC_fix(L, obj2gco(e)); /* never collect this name */ + for (i=0; iextra = cast_byte(i+1); /* reserved word */ + } +} + + +const char *luaX_token2str (LexState *ls, int token) { + if (token < FIRST_RESERVED) { /* single-byte symbols? */ + if (lisprint(token)) + return luaO_pushfstring(ls->L, "'%c'", token); + else /* control character */ + return luaO_pushfstring(ls->L, "'<\\%d>'", token); + } + else { + const char *s = luaX_tokens[token - FIRST_RESERVED]; + if (token < TK_EOS) /* fixed format (symbols and reserved words)? */ + return luaO_pushfstring(ls->L, "'%s'", s); + else /* names, strings, and numerals */ + return s; + } +} + + +static const char *txtToken (LexState *ls, int token) { + switch (token) { + case TK_NAME: case TK_STRING: + case TK_FLT: case TK_INT: + save(ls, '\0'); + return luaO_pushfstring(ls->L, "'%s'", luaZ_buffer(ls->buff)); + default: + return luaX_token2str(ls, token); + } +} + + +static l_noret lexerror (LexState *ls, const char *msg, int token) { + msg = luaG_addinfo(ls->L, msg, ls->source, ls->linenumber); + if (token) + luaO_pushfstring(ls->L, "%s near %s", msg, txtToken(ls, token)); + luaD_throw(ls->L, LUA_ERRSYNTAX); +} + + +l_noret luaX_syntaxerror (LexState *ls, const char *msg) { + lexerror(ls, msg, ls->t.token); +} + + +/* +** creates a new string and anchors it in scanner's table so that +** it will not be collected until the end of the compilation +** (by that time it should be anchored somewhere) +*/ +TString *luaX_newstring (LexState *ls, const char *str, size_t l) { + lua_State *L = ls->L; + TValue *o; /* entry for 'str' */ + TString *ts = luaS_newlstr(L, str, l); /* create new string */ + setsvalue2s(L, L->top++, ts); /* temporarily anchor it in stack */ + o = luaH_set(L, ls->h, s2v(L->top - 1)); + if (isempty(o)) { /* not in use yet? */ + /* boolean value does not need GC barrier; + table is not a metatable, so it does not need to invalidate cache */ + setbtvalue(o); /* t[string] = true */ + luaC_checkGC(L); + } + else { /* string already present */ + ts = keystrval(nodefromval(o)); /* re-use value previously stored */ + } + L->top--; /* remove string from stack */ + return ts; +} + + +/* +** increment line number and skips newline sequence (any of +** \n, \r, \n\r, or \r\n) +*/ +static void inclinenumber (LexState *ls) { + int old = ls->current; + lua_assert(currIsNewline(ls)); + next(ls); /* skip '\n' or '\r' */ + if (currIsNewline(ls) && ls->current != old) + next(ls); /* skip '\n\r' or '\r\n' */ + if (++ls->linenumber >= MAX_INT) + lexerror(ls, "chunk has too many lines", 0); +} + + +void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, + int firstchar) { + ls->t.token = 0; + ls->L = L; + ls->current = firstchar; + ls->lookahead.token = TK_EOS; /* no look-ahead token */ + ls->z = z; + ls->fs = NULL; + ls->linenumber = 1; + ls->lastline = 1; + ls->source = source; + ls->envn = luaS_newliteral(L, LUA_ENV); /* get env name */ + luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ +} + + + +/* +** ======================================================= +** LEXICAL ANALYZER +** ======================================================= +*/ + + +static int check_next1 (LexState *ls, int c) { + if (ls->current == c) { + next(ls); + return 1; + } + else return 0; +} + + +/* +** Check whether current char is in set 'set' (with two chars) and +** saves it +*/ +static int check_next2 (LexState *ls, const char *set) { + lua_assert(set[2] == '\0'); + if (ls->current == set[0] || ls->current == set[1]) { + save_and_next(ls); + return 1; + } + else return 0; +} + + +/* LUA_NUMBER */ +/* +** This function is quite liberal in what it accepts, as 'luaO_str2num' +** will reject ill-formed numerals. Roughly, it accepts the following +** pattern: +** +** %d(%x|%.|([Ee][+-]?))* | 0[Xx](%x|%.|([Pp][+-]?))* +** +** The only tricky part is to accept [+-] only after a valid exponent +** mark, to avoid reading '3-4' or '0xe+1' as a single number. +** +** The caller might have already read an initial dot. +*/ +static int read_numeral (LexState *ls, SemInfo *seminfo) { + TValue obj; + const char *expo = "Ee"; + int first = ls->current; + lua_assert(lisdigit(ls->current)); + save_and_next(ls); + if (first == '0' && check_next2(ls, "xX")) /* hexadecimal? */ + expo = "Pp"; + for (;;) { + if (check_next2(ls, expo)) /* exponent mark? */ + check_next2(ls, "-+"); /* optional exponent sign */ + else if (lisxdigit(ls->current) || ls->current == '.') /* '%x|%.' */ + save_and_next(ls); + else break; + } + if (lislalpha(ls->current)) /* is numeral touching a letter? */ + save_and_next(ls); /* force an error */ + save(ls, '\0'); + if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */ + lexerror(ls, "malformed number", TK_FLT); + if (ttisinteger(&obj)) { + seminfo->i = ivalue(&obj); + return TK_INT; + } + else { + lua_assert(ttisfloat(&obj)); + seminfo->r = fltvalue(&obj); + return TK_FLT; + } +} + + +/* +** read a sequence '[=*[' or ']=*]', leaving the last bracket. If +** sequence is well formed, return its number of '='s + 2; otherwise, +** return 1 if it is a single bracket (no '='s and no 2nd bracket); +** otherwise (an unfinished '[==...') return 0. +*/ +static size_t skip_sep (LexState *ls) { + size_t count = 0; + int s = ls->current; + lua_assert(s == '[' || s == ']'); + save_and_next(ls); + while (ls->current == '=') { + save_and_next(ls); + count++; + } + return (ls->current == s) ? count + 2 + : (count == 0) ? 1 + : 0; +} + + +static void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) { + int line = ls->linenumber; /* initial line (for error message) */ + save_and_next(ls); /* skip 2nd '[' */ + if (currIsNewline(ls)) /* string starts with a newline? */ + inclinenumber(ls); /* skip it */ + for (;;) { + switch (ls->current) { + case EOZ: { /* error */ + const char *what = (seminfo ? "string" : "comment"); + const char *msg = luaO_pushfstring(ls->L, + "unfinished long %s (starting at line %d)", what, line); + lexerror(ls, msg, TK_EOS); + break; /* to avoid warnings */ + } + case ']': { + if (skip_sep(ls) == sep) { + save_and_next(ls); /* skip 2nd ']' */ + goto endloop; + } + break; + } + case '\n': case '\r': { + save(ls, '\n'); + inclinenumber(ls); + if (!seminfo) luaZ_resetbuffer(ls->buff); /* avoid wasting space */ + break; + } + default: { + if (seminfo) save_and_next(ls); + else next(ls); + } + } + } endloop: + if (seminfo) + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep, + luaZ_bufflen(ls->buff) - 2 * sep); +} + + +static void esccheck (LexState *ls, int c, const char *msg) { + if (!c) { + if (ls->current != EOZ) + save_and_next(ls); /* add current to buffer for error message */ + lexerror(ls, msg, TK_STRING); + } +} + + +static int gethexa (LexState *ls) { + save_and_next(ls); + esccheck (ls, lisxdigit(ls->current), "hexadecimal digit expected"); + return luaO_hexavalue(ls->current); +} + + +static int readhexaesc (LexState *ls) { + int r = gethexa(ls); + r = (r << 4) + gethexa(ls); + luaZ_buffremove(ls->buff, 2); /* remove saved chars from buffer */ + return r; +} + + +static unsigned long readutf8esc (LexState *ls) { + unsigned long r; + int i = 4; /* chars to be removed: '\', 'u', '{', and first digit */ + save_and_next(ls); /* skip 'u' */ + esccheck(ls, ls->current == '{', "missing '{'"); + r = gethexa(ls); /* must have at least one digit */ + while (cast_void(save_and_next(ls)), lisxdigit(ls->current)) { + i++; + esccheck(ls, r <= (0x7FFFFFFFu >> 4), "UTF-8 value too large"); + r = (r << 4) + luaO_hexavalue(ls->current); + } + esccheck(ls, ls->current == '}', "missing '}'"); + next(ls); /* skip '}' */ + luaZ_buffremove(ls->buff, i); /* remove saved chars from buffer */ + return r; +} + + +static void utf8esc (LexState *ls) { + char buff[UTF8BUFFSZ]; + int n = luaO_utf8esc(buff, readutf8esc(ls)); + for (; n > 0; n--) /* add 'buff' to string */ + save(ls, buff[UTF8BUFFSZ - n]); +} + + +static int readdecesc (LexState *ls) { + int i; + int r = 0; /* result accumulator */ + for (i = 0; i < 3 && lisdigit(ls->current); i++) { /* read up to 3 digits */ + r = 10*r + ls->current - '0'; + save_and_next(ls); + } + esccheck(ls, r <= UCHAR_MAX, "decimal escape too large"); + luaZ_buffremove(ls->buff, i); /* remove read digits from buffer */ + return r; +} + + +static void read_string (LexState *ls, int del, SemInfo *seminfo) { + save_and_next(ls); /* keep delimiter (for error messages) */ + while (ls->current != del) { + switch (ls->current) { + case EOZ: + lexerror(ls, "unfinished string", TK_EOS); + break; /* to avoid warnings */ + case '\n': + case '\r': + lexerror(ls, "unfinished string", TK_STRING); + break; /* to avoid warnings */ + case '\\': { /* escape sequences */ + int c; /* final character to be saved */ + save_and_next(ls); /* keep '\\' for error messages */ + switch (ls->current) { + case 'a': c = '\a'; goto read_save; + case 'b': c = '\b'; goto read_save; + case 'f': c = '\f'; goto read_save; + case 'n': c = '\n'; goto read_save; + case 'r': c = '\r'; goto read_save; + case 't': c = '\t'; goto read_save; + case 'v': c = '\v'; goto read_save; + case 'x': c = readhexaesc(ls); goto read_save; + case 'u': utf8esc(ls); goto no_save; + case '\n': case '\r': + inclinenumber(ls); c = '\n'; goto only_save; + case '\\': case '\"': case '\'': + c = ls->current; goto read_save; + case EOZ: goto no_save; /* will raise an error next loop */ + case 'z': { /* zap following span of spaces */ + luaZ_buffremove(ls->buff, 1); /* remove '\\' */ + next(ls); /* skip the 'z' */ + while (lisspace(ls->current)) { + if (currIsNewline(ls)) inclinenumber(ls); + else next(ls); + } + goto no_save; + } + default: { + esccheck(ls, lisdigit(ls->current), "invalid escape sequence"); + c = readdecesc(ls); /* digital escape '\ddd' */ + goto only_save; + } + } + read_save: + next(ls); + /* go through */ + only_save: + luaZ_buffremove(ls->buff, 1); /* remove '\\' */ + save(ls, c); + /* go through */ + no_save: break; + } + default: + save_and_next(ls); + } + } + save_and_next(ls); /* skip delimiter */ + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1, + luaZ_bufflen(ls->buff) - 2); +} + + +static int llex (LexState *ls, SemInfo *seminfo) { + luaZ_resetbuffer(ls->buff); + for (;;) { + switch (ls->current) { + case '\n': case '\r': { /* line breaks */ + inclinenumber(ls); + break; + } + case ' ': case '\f': case '\t': case '\v': { /* spaces */ + next(ls); + break; + } + case '-': { /* '-' or '--' (comment) */ + next(ls); + if (ls->current != '-') return '-'; + /* else is a comment */ + next(ls); + if (ls->current == '[') { /* long comment? */ + size_t sep = skip_sep(ls); + luaZ_resetbuffer(ls->buff); /* 'skip_sep' may dirty the buffer */ + if (sep >= 2) { + read_long_string(ls, NULL, sep); /* skip long comment */ + luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */ + break; + } + } + /* else short comment */ + while (!currIsNewline(ls) && ls->current != EOZ) + next(ls); /* skip until end of line (or end of file) */ + break; + } + case '[': { /* long string or simply '[' */ + size_t sep = skip_sep(ls); + if (sep >= 2) { + read_long_string(ls, seminfo, sep); + return TK_STRING; + } + else if (sep == 0) /* '[=...' missing second bracket? */ + lexerror(ls, "invalid long string delimiter", TK_STRING); + return '['; + } + case '=': { + next(ls); + if (check_next1(ls, '=')) return TK_EQ; /* '==' */ + else return '='; + } + case '<': { + next(ls); + if (check_next1(ls, '=')) return TK_LE; /* '<=' */ + else if (check_next1(ls, '<')) return TK_SHL; /* '<<' */ + else return '<'; + } + case '>': { + next(ls); + if (check_next1(ls, '=')) return TK_GE; /* '>=' */ + else if (check_next1(ls, '>')) return TK_SHR; /* '>>' */ + else return '>'; + } + case '/': { + next(ls); + if (check_next1(ls, '/')) return TK_IDIV; /* '//' */ + else return '/'; + } + case '~': { + next(ls); + if (check_next1(ls, '=')) return TK_NE; /* '~=' */ + else return '~'; + } + case ':': { + next(ls); + if (check_next1(ls, ':')) return TK_DBCOLON; /* '::' */ + else return ':'; + } + case '"': case '\'': { /* short literal strings */ + read_string(ls, ls->current, seminfo); + return TK_STRING; + } + case '.': { /* '.', '..', '...', or number */ + save_and_next(ls); + if (check_next1(ls, '.')) { + if (check_next1(ls, '.')) + return TK_DOTS; /* '...' */ + else return TK_CONCAT; /* '..' */ + } + else if (!lisdigit(ls->current)) return '.'; + else return read_numeral(ls, seminfo); + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + return read_numeral(ls, seminfo); + } + case EOZ: { + return TK_EOS; + } + default: { + if (lislalpha(ls->current)) { /* identifier or reserved word? */ + TString *ts; + do { + save_and_next(ls); + } while (lislalnum(ls->current)); + ts = luaX_newstring(ls, luaZ_buffer(ls->buff), + luaZ_bufflen(ls->buff)); + seminfo->ts = ts; + if (isreserved(ts)) /* reserved word? */ + return ts->extra - 1 + FIRST_RESERVED; + else { + return TK_NAME; + } + } + else { /* single-char tokens ('+', '*', '%', '{', '}', ...) */ + int c = ls->current; + next(ls); + return c; + } + } + } + } +} + + +void luaX_next (LexState *ls) { + ls->lastline = ls->linenumber; + if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */ + ls->t = ls->lookahead; /* use this one */ + ls->lookahead.token = TK_EOS; /* and discharge it */ + } + else + ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */ +} + + +int luaX_lookahead (LexState *ls) { + lua_assert(ls->lookahead.token == TK_EOS); + ls->lookahead.token = llex(ls, &ls->lookahead.seminfo); + return ls->lookahead.token; +} + diff --git a/Lua/llex.h b/Lua/llex.h new file mode 100644 index 00000000..389d2f86 --- /dev/null +++ b/Lua/llex.h @@ -0,0 +1,91 @@ +/* +** $Id: llex.h $ +** Lexical Analyzer +** See Copyright Notice in lua.h +*/ + +#ifndef llex_h +#define llex_h + +#include + +#include "lobject.h" +#include "lzio.h" + + +/* +** Single-char tokens (terminal symbols) are represented by their own +** numeric code. Other tokens start at the following value. +*/ +#define FIRST_RESERVED (UCHAR_MAX + 1) + + +#if !defined(LUA_ENV) +#define LUA_ENV "_ENV" +#endif + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER RESERVED" +*/ +enum RESERVED { + /* terminal symbols denoted by reserved words */ + TK_AND = FIRST_RESERVED, TK_BREAK, + TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, + TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, + TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, + /* other terminal symbols */ + TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, + TK_SHL, TK_SHR, + TK_DBCOLON, TK_EOS, + TK_FLT, TK_INT, TK_NAME, TK_STRING +}; + +/* number of reserved words */ +#define NUM_RESERVED (cast_int(TK_WHILE-FIRST_RESERVED + 1)) + + +typedef union { + lua_Number r; + lua_Integer i; + TString *ts; +} SemInfo; /* semantics information */ + + +typedef struct Token { + int token; + SemInfo seminfo; +} Token; + + +/* state of the lexer plus state of the parser when shared by all + functions */ +typedef struct LexState { + int current; /* current character (charint) */ + int linenumber; /* input line counter */ + int lastline; /* line of last token 'consumed' */ + Token t; /* current token */ + Token lookahead; /* look ahead token */ + struct FuncState *fs; /* current function (parser) */ + struct lua_State *L; + ZIO *z; /* input stream */ + Mbuffer *buff; /* buffer for tokens */ + Table *h; /* to avoid collection/reuse strings */ + struct Dyndata *dyd; /* dynamic structures used by the parser */ + TString *source; /* current source name */ + TString *envn; /* environment variable name */ +} LexState; + + +LUAI_FUNC void luaX_init (lua_State *L); +LUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, + TString *source, int firstchar); +LUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l); +LUAI_FUNC void luaX_next (LexState *ls); +LUAI_FUNC int luaX_lookahead (LexState *ls); +LUAI_FUNC l_noret luaX_syntaxerror (LexState *ls, const char *s); +LUAI_FUNC const char *luaX_token2str (LexState *ls, int token); + + +#endif diff --git a/Lua/llimits.h b/Lua/llimits.h new file mode 100644 index 00000000..d0394831 --- /dev/null +++ b/Lua/llimits.h @@ -0,0 +1,369 @@ +/* +** $Id: llimits.h $ +** Limits, basic types, and some other 'installation-dependent' definitions +** See Copyright Notice in lua.h +*/ + +#ifndef llimits_h +#define llimits_h + + +#include +#include + + +#include "lua.h" + + +/* +** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count +** the total memory used by Lua (in bytes). Usually, 'size_t' and +** 'ptrdiff_t' should work, but we use 'long' for 16-bit machines. +*/ +#if defined(LUAI_MEM) /* { external definitions? */ +typedef LUAI_UMEM lu_mem; +typedef LUAI_MEM l_mem; +#elif LUAI_IS32INT /* }{ */ +typedef size_t lu_mem; +typedef ptrdiff_t l_mem; +#else /* 16-bit ints */ /* }{ */ +typedef unsigned long lu_mem; +typedef long l_mem; +#endif /* } */ + + +/* chars used as small naturals (so that 'char' is reserved for characters) */ +typedef unsigned char lu_byte; +typedef signed char ls_byte; + + +/* maximum value for size_t */ +#define MAX_SIZET ((size_t)(~(size_t)0)) + +/* maximum size visible for Lua (must be representable in a lua_Integer) */ +#define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \ + : (size_t)(LUA_MAXINTEGER)) + + +#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)) + +#define MAX_LMEM ((l_mem)(MAX_LUMEM >> 1)) + + +#define MAX_INT INT_MAX /* maximum value of an int */ + + +/* +** floor of the log2 of the maximum signed value for integral type 't'. +** (That is, maximum 'n' such that '2^n' fits in the given signed type.) +*/ +#define log2maxs(t) (sizeof(t) * 8 - 2) + + +/* +** test whether an unsigned value is a power of 2 (or zero) +*/ +#define ispow2(x) (((x) & ((x) - 1)) == 0) + + +/* number of chars of a literal string without the ending \0 */ +#define LL(x) (sizeof(x)/sizeof(char) - 1) + + +/* +** conversion of pointer to unsigned integer: +** this is for hashing only; there is no problem if the integer +** cannot hold the whole pointer value +*/ +#define point2uint(p) ((unsigned int)((size_t)(p) & UINT_MAX)) + + + +/* types of 'usual argument conversions' for lua_Number and lua_Integer */ +typedef LUAI_UACNUMBER l_uacNumber; +typedef LUAI_UACINT l_uacInt; + + +/* +** Internal assertions for in-house debugging +*/ +#if defined LUAI_ASSERT +#undef NDEBUG +#include +#define lua_assert(c) assert(c) +#endif + +#if defined(lua_assert) +#define check_exp(c,e) (lua_assert(c), (e)) +/* to avoid problems with conditions too long */ +#define lua_longassert(c) ((c) ? (void)0 : lua_assert(0)) +#else +#define lua_assert(c) ((void)0) +#define check_exp(c,e) (e) +#define lua_longassert(c) ((void)0) +#endif + +/* +** assertion for checking API calls +*/ +#if !defined(luai_apicheck) +#define luai_apicheck(l,e) ((void)l, lua_assert(e)) +#endif + +#define api_check(l,e,msg) luai_apicheck(l,(e) && msg) + + +/* macro to avoid warnings about unused variables */ +#if !defined(UNUSED) +#define UNUSED(x) ((void)(x)) +#endif + + +/* type casts (a macro highlights casts in the code) */ +#define cast(t, exp) ((t)(exp)) + +#define cast_void(i) cast(void, (i)) +#define cast_voidp(i) cast(void *, (i)) +#define cast_num(i) cast(lua_Number, (i)) +#define cast_int(i) cast(int, (i)) +#define cast_uint(i) cast(unsigned int, (i)) +#define cast_byte(i) cast(lu_byte, (i)) +#define cast_uchar(i) cast(unsigned char, (i)) +#define cast_char(i) cast(char, (i)) +#define cast_charp(i) cast(char *, (i)) +#define cast_sizet(i) cast(size_t, (i)) + + +/* cast a signed lua_Integer to lua_Unsigned */ +#if !defined(l_castS2U) +#define l_castS2U(i) ((lua_Unsigned)(i)) +#endif + +/* +** cast a lua_Unsigned to a signed lua_Integer; this cast is +** not strict ISO C, but two-complement architectures should +** work fine. +*/ +#if !defined(l_castU2S) +#define l_castU2S(i) ((lua_Integer)(i)) +#endif + + +/* +** macros to improve jump prediction (used mainly for error handling) +*/ +#if !defined(likely) + +#if defined(__GNUC__) +#define likely(x) (__builtin_expect(((x) != 0), 1)) +#define unlikely(x) (__builtin_expect(((x) != 0), 0)) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#endif + + +/* +** non-return type +*/ +#if !defined(l_noret) + +#if defined(__GNUC__) +#define l_noret void __attribute__((noreturn)) +#elif defined(_MSC_VER) && _MSC_VER >= 1200 +#define l_noret void __declspec(noreturn) +#else +#define l_noret void +#endif + +#endif + + +/* +** type for virtual-machine instructions; +** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) +*/ +#if LUAI_IS32INT +typedef unsigned int l_uint32; +#else +typedef unsigned long l_uint32; +#endif + +typedef l_uint32 Instruction; + + + +/* +** Maximum length for short strings, that is, strings that are +** internalized. (Cannot be smaller than reserved words or tags for +** metamethods, as these strings must be internalized; +** #("function") = 8, #("__newindex") = 10.) +*/ +#if !defined(LUAI_MAXSHORTLEN) +#define LUAI_MAXSHORTLEN 40 +#endif + + +/* +** Initial size for the string table (must be power of 2). +** The Lua core alone registers ~50 strings (reserved words + +** metaevent keys + a few others). Libraries would typically add +** a few dozens more. +*/ +#if !defined(MINSTRTABSIZE) +#define MINSTRTABSIZE 128 +#endif + + +/* +** Size of cache for strings in the API. 'N' is the number of +** sets (better be a prime) and "M" is the size of each set (M == 1 +** makes a direct cache.) +*/ +#if !defined(STRCACHE_N) +#define STRCACHE_N 53 +#define STRCACHE_M 2 +#endif + + +/* minimum size for string buffer */ +#if !defined(LUA_MINBUFFER) +#define LUA_MINBUFFER 32 +#endif + + +/* +** Maximum depth for nested C calls, syntactical nested non-terminals, +** and other features implemented through recursion in C. (Value must +** fit in a 16-bit unsigned integer. It must also be compatible with +** the size of the C stack.) +*/ +#if !defined(LUAI_MAXCCALLS) +#define LUAI_MAXCCALLS 200 +#endif + + +/* +** macros that are executed whenever program enters the Lua core +** ('lua_lock') and leaves the core ('lua_unlock') +*/ +#if !defined(lua_lock) +#define lua_lock(L) ((void) 0) +#define lua_unlock(L) ((void) 0) +#endif + +/* +** macro executed during Lua functions at points where the +** function can yield. +*/ +#if !defined(luai_threadyield) +#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} +#endif + + +/* +** these macros allow user-specific actions when a thread is +** created/deleted/resumed/yielded. +*/ +#if !defined(luai_userstateopen) +#define luai_userstateopen(L) ((void)L) +#endif + +#if !defined(luai_userstateclose) +#define luai_userstateclose(L) ((void)L) +#endif + +#if !defined(luai_userstatethread) +#define luai_userstatethread(L,L1) ((void)L) +#endif + +#if !defined(luai_userstatefree) +#define luai_userstatefree(L,L1) ((void)L) +#endif + +#if !defined(luai_userstateresume) +#define luai_userstateresume(L,n) ((void)L) +#endif + +#if !defined(luai_userstateyield) +#define luai_userstateyield(L,n) ((void)L) +#endif + + + +/* +** The luai_num* macros define the primitive operations over numbers. +*/ + +/* floor division (defined as 'floor(a/b)') */ +#if !defined(luai_numidiv) +#define luai_numidiv(L,a,b) ((void)L, l_floor(luai_numdiv(L,a,b))) +#endif + +/* float division */ +#if !defined(luai_numdiv) +#define luai_numdiv(L,a,b) ((a)/(b)) +#endif + +/* +** modulo: defined as 'a - floor(a/b)*b'; the direct computation +** using this definition has several problems with rounding errors, +** so it is better to use 'fmod'. 'fmod' gives the result of +** 'a - trunc(a/b)*b', and therefore must be corrected when +** 'trunc(a/b) ~= floor(a/b)'. That happens when the division has a +** non-integer negative result: non-integer result is equivalent to +** a non-zero remainder 'm'; negative result is equivalent to 'a' and +** 'b' with different signs, or 'm' and 'b' with different signs +** (as the result 'm' of 'fmod' has the same sign of 'a'). +*/ +#if !defined(luai_nummod) +#define luai_nummod(L,a,b,m) \ + { (void)L; (m) = l_mathop(fmod)(a,b); \ + if (((m) > 0) ? (b) < 0 : ((m) < 0 && (b) > 0)) (m) += (b); } +#endif + +/* exponentiation */ +#if !defined(luai_numpow) +#define luai_numpow(L,a,b) \ + ((void)L, (b == 2) ? (a)*(a) : l_mathop(pow)(a,b)) +#endif + +/* the others are quite standard operations */ +#if !defined(luai_numadd) +#define luai_numadd(L,a,b) ((a)+(b)) +#define luai_numsub(L,a,b) ((a)-(b)) +#define luai_nummul(L,a,b) ((a)*(b)) +#define luai_numunm(L,a) (-(a)) +#define luai_numeq(a,b) ((a)==(b)) +#define luai_numlt(a,b) ((a)<(b)) +#define luai_numle(a,b) ((a)<=(b)) +#define luai_numgt(a,b) ((a)>(b)) +#define luai_numge(a,b) ((a)>=(b)) +#define luai_numisnan(a) (!luai_numeq((a), (a))) +#endif + + + + + +/* +** macro to control inclusion of some hard tests on stack reallocation +*/ +#if !defined(HARDSTACKTESTS) +#define condmovestack(L,pre,pos) ((void)0) +#else +/* realloc stack keeping its size */ +#define condmovestack(L,pre,pos) \ + { int sz_ = stacksize(L); pre; luaD_reallocstack((L), sz_, 0); pos; } +#endif + +#if !defined(HARDMEMTESTS) +#define condchangemem(L,pre,pos) ((void)0) +#else +#define condchangemem(L,pre,pos) \ + { if (G(L)->gcrunning) { pre; luaC_fullgc(L, 0); pos; } } +#endif + +#endif diff --git a/Lua/lmathlib.c b/Lua/lmathlib.c new file mode 100644 index 00000000..86def470 --- /dev/null +++ b/Lua/lmathlib.c @@ -0,0 +1,763 @@ +/* +** $Id: lmathlib.c $ +** Standard mathematical library +** See Copyright Notice in lua.h +*/ + +#define lmathlib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#undef PI +#define PI (l_mathop(3.141592653589793238462643383279502884)) + + +static int math_abs (lua_State *L) { + if (lua_isinteger(L, 1)) { + lua_Integer n = lua_tointeger(L, 1); + if (n < 0) n = (lua_Integer)(0u - (lua_Unsigned)n); + lua_pushinteger(L, n); + } + else + lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sin (lua_State *L) { + lua_pushnumber(L, l_mathop(sin)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_cos (lua_State *L) { + lua_pushnumber(L, l_mathop(cos)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tan (lua_State *L) { + lua_pushnumber(L, l_mathop(tan)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_asin (lua_State *L) { + lua_pushnumber(L, l_mathop(asin)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_acos (lua_State *L) { + lua_pushnumber(L, l_mathop(acos)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_atan (lua_State *L) { + lua_Number y = luaL_checknumber(L, 1); + lua_Number x = luaL_optnumber(L, 2, 1); + lua_pushnumber(L, l_mathop(atan2)(y, x)); + return 1; +} + + +static int math_toint (lua_State *L) { + int valid; + lua_Integer n = lua_tointegerx(L, 1, &valid); + if (valid) + lua_pushinteger(L, n); + else { + luaL_checkany(L, 1); + luaL_pushfail(L); /* value is not convertible to integer */ + } + return 1; +} + + +static void pushnumint (lua_State *L, lua_Number d) { + lua_Integer n; + if (lua_numbertointeger(d, &n)) /* does 'd' fit in an integer? */ + lua_pushinteger(L, n); /* result is integer */ + else + lua_pushnumber(L, d); /* result is float */ +} + + +static int math_floor (lua_State *L) { + if (lua_isinteger(L, 1)) + lua_settop(L, 1); /* integer is its own floor */ + else { + lua_Number d = l_mathop(floor)(luaL_checknumber(L, 1)); + pushnumint(L, d); + } + return 1; +} + + +static int math_ceil (lua_State *L) { + if (lua_isinteger(L, 1)) + lua_settop(L, 1); /* integer is its own ceil */ + else { + lua_Number d = l_mathop(ceil)(luaL_checknumber(L, 1)); + pushnumint(L, d); + } + return 1; +} + + +static int math_fmod (lua_State *L) { + if (lua_isinteger(L, 1) && lua_isinteger(L, 2)) { + lua_Integer d = lua_tointeger(L, 2); + if ((lua_Unsigned)d + 1u <= 1u) { /* special cases: -1 or 0 */ + luaL_argcheck(L, d != 0, 2, "zero"); + lua_pushinteger(L, 0); /* avoid overflow with 0x80000... / -1 */ + } + else + lua_pushinteger(L, lua_tointeger(L, 1) % d); + } + else + lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1), + luaL_checknumber(L, 2))); + return 1; +} + + +/* +** next function does not use 'modf', avoiding problems with 'double*' +** (which is not compatible with 'float*') when lua_Number is not +** 'double'. +*/ +static int math_modf (lua_State *L) { + if (lua_isinteger(L ,1)) { + lua_settop(L, 1); /* number is its own integer part */ + lua_pushnumber(L, 0); /* no fractional part */ + } + else { + lua_Number n = luaL_checknumber(L, 1); + /* integer part (rounds toward zero) */ + lua_Number ip = (n < 0) ? l_mathop(ceil)(n) : l_mathop(floor)(n); + pushnumint(L, ip); + /* fractional part (test needed for inf/-inf) */ + lua_pushnumber(L, (n == ip) ? l_mathop(0.0) : (n - ip)); + } + return 2; +} + + +static int math_sqrt (lua_State *L) { + lua_pushnumber(L, l_mathop(sqrt)(luaL_checknumber(L, 1))); + return 1; +} + + +static int math_ult (lua_State *L) { + lua_Integer a = luaL_checkinteger(L, 1); + lua_Integer b = luaL_checkinteger(L, 2); + lua_pushboolean(L, (lua_Unsigned)a < (lua_Unsigned)b); + return 1; +} + +static int math_log (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + lua_Number res; + if (lua_isnoneornil(L, 2)) + res = l_mathop(log)(x); + else { + lua_Number base = luaL_checknumber(L, 2); +#if !defined(LUA_USE_C89) + if (base == l_mathop(2.0)) + res = l_mathop(log2)(x); else +#endif + if (base == l_mathop(10.0)) + res = l_mathop(log10)(x); + else + res = l_mathop(log)(x)/l_mathop(log)(base); + } + lua_pushnumber(L, res); + return 1; +} + +static int math_exp (lua_State *L) { + lua_pushnumber(L, l_mathop(exp)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_deg (lua_State *L) { + lua_pushnumber(L, luaL_checknumber(L, 1) * (l_mathop(180.0) / PI)); + return 1; +} + +static int math_rad (lua_State *L) { + lua_pushnumber(L, luaL_checknumber(L, 1) * (PI / l_mathop(180.0))); + return 1; +} + + +static int math_min (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int imin = 1; /* index of current minimum value */ + int i; + luaL_argcheck(L, n >= 1, 1, "value expected"); + for (i = 2; i <= n; i++) { + if (lua_compare(L, i, imin, LUA_OPLT)) + imin = i; + } + lua_pushvalue(L, imin); + return 1; +} + + +static int math_max (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int imax = 1; /* index of current maximum value */ + int i; + luaL_argcheck(L, n >= 1, 1, "value expected"); + for (i = 2; i <= n; i++) { + if (lua_compare(L, imax, i, LUA_OPLT)) + imax = i; + } + lua_pushvalue(L, imax); + return 1; +} + + +static int math_type (lua_State *L) { + if (lua_type(L, 1) == LUA_TNUMBER) + lua_pushstring(L, (lua_isinteger(L, 1)) ? "integer" : "float"); + else { + luaL_checkany(L, 1); + luaL_pushfail(L); + } + return 1; +} + + + +/* +** {================================================================== +** Pseudo-Random Number Generator based on 'xoshiro256**'. +** =================================================================== +*/ + +/* number of binary digits in the mantissa of a float */ +#define FIGS l_floatatt(MANT_DIG) + +#if FIGS > 64 +/* there are only 64 random bits; use them all */ +#undef FIGS +#define FIGS 64 +#endif + + +/* +** LUA_RAND32 forces the use of 32-bit integers in the implementation +** of the PRN generator (mainly for testing). +*/ +#if !defined(LUA_RAND32) && !defined(Rand64) + +/* try to find an integer type with at least 64 bits */ + +#if (ULONG_MAX >> 31 >> 31) >= 3 + +/* 'long' has at least 64 bits */ +#define Rand64 unsigned long + +#elif !defined(LUA_USE_C89) && defined(LLONG_MAX) + +/* there is a 'long long' type (which must have at least 64 bits) */ +#define Rand64 unsigned long long + +#elif (LUA_MAXUNSIGNED >> 31 >> 31) >= 3 + +/* 'lua_Integer' has at least 64 bits */ +#define Rand64 lua_Unsigned + +#endif + +#endif + + +#if defined(Rand64) /* { */ + +/* +** Standard implementation, using 64-bit integers. +** If 'Rand64' has more than 64 bits, the extra bits do not interfere +** with the 64 initial bits, except in a right shift. Moreover, the +** final result has to discard the extra bits. +*/ + +/* avoid using extra bits when needed */ +#define trim64(x) ((x) & 0xffffffffffffffffu) + + +/* rotate left 'x' by 'n' bits */ +static Rand64 rotl (Rand64 x, int n) { + return (x << n) | (trim64(x) >> (64 - n)); +} + +static Rand64 nextrand (Rand64 *state) { + Rand64 state0 = state[0]; + Rand64 state1 = state[1]; + Rand64 state2 = state[2] ^ state0; + Rand64 state3 = state[3] ^ state1; + Rand64 res = rotl(state1 * 5, 7) * 9; + state[0] = state0 ^ state3; + state[1] = state1 ^ state2; + state[2] = state2 ^ (state1 << 17); + state[3] = rotl(state3, 45); + return res; +} + + +/* must take care to not shift stuff by more than 63 slots */ + + +/* +** Convert bits from a random integer into a float in the +** interval [0,1), getting the higher FIG bits from the +** random unsigned integer and converting that to a float. +*/ + +/* must throw out the extra (64 - FIGS) bits */ +#define shift64_FIG (64 - FIGS) + +/* to scale to [0, 1), multiply by scaleFIG = 2^(-FIGS) */ +#define scaleFIG (l_mathop(0.5) / ((Rand64)1 << (FIGS - 1))) + +static lua_Number I2d (Rand64 x) { + return (lua_Number)(trim64(x) >> shift64_FIG) * scaleFIG; +} + +/* convert a 'Rand64' to a 'lua_Unsigned' */ +#define I2UInt(x) ((lua_Unsigned)trim64(x)) + +/* convert a 'lua_Unsigned' to a 'Rand64' */ +#define Int2I(x) ((Rand64)(x)) + + +#else /* no 'Rand64' }{ */ + +/* get an integer with at least 32 bits */ +#if LUAI_IS32INT +typedef unsigned int lu_int32; +#else +typedef unsigned long lu_int32; +#endif + + +/* +** Use two 32-bit integers to represent a 64-bit quantity. +*/ +typedef struct Rand64 { + lu_int32 h; /* higher half */ + lu_int32 l; /* lower half */ +} Rand64; + + +/* +** If 'lu_int32' has more than 32 bits, the extra bits do not interfere +** with the 32 initial bits, except in a right shift and comparisons. +** Moreover, the final result has to discard the extra bits. +*/ + +/* avoid using extra bits when needed */ +#define trim32(x) ((x) & 0xffffffffu) + + +/* +** basic operations on 'Rand64' values +*/ + +/* build a new Rand64 value */ +static Rand64 packI (lu_int32 h, lu_int32 l) { + Rand64 result; + result.h = h; + result.l = l; + return result; +} + +/* return i << n */ +static Rand64 Ishl (Rand64 i, int n) { + lua_assert(n > 0 && n < 32); + return packI((i.h << n) | (trim32(i.l) >> (32 - n)), i.l << n); +} + +/* i1 ^= i2 */ +static void Ixor (Rand64 *i1, Rand64 i2) { + i1->h ^= i2.h; + i1->l ^= i2.l; +} + +/* return i1 + i2 */ +static Rand64 Iadd (Rand64 i1, Rand64 i2) { + Rand64 result = packI(i1.h + i2.h, i1.l + i2.l); + if (trim32(result.l) < trim32(i1.l)) /* carry? */ + result.h++; + return result; +} + +/* return i * 5 */ +static Rand64 times5 (Rand64 i) { + return Iadd(Ishl(i, 2), i); /* i * 5 == (i << 2) + i */ +} + +/* return i * 9 */ +static Rand64 times9 (Rand64 i) { + return Iadd(Ishl(i, 3), i); /* i * 9 == (i << 3) + i */ +} + +/* return 'i' rotated left 'n' bits */ +static Rand64 rotl (Rand64 i, int n) { + lua_assert(n > 0 && n < 32); + return packI((i.h << n) | (trim32(i.l) >> (32 - n)), + (trim32(i.h) >> (32 - n)) | (i.l << n)); +} + +/* for offsets larger than 32, rotate right by 64 - offset */ +static Rand64 rotl1 (Rand64 i, int n) { + lua_assert(n > 32 && n < 64); + n = 64 - n; + return packI((trim32(i.h) >> n) | (i.l << (32 - n)), + (i.h << (32 - n)) | (trim32(i.l) >> n)); +} + +/* +** implementation of 'xoshiro256**' algorithm on 'Rand64' values +*/ +static Rand64 nextrand (Rand64 *state) { + Rand64 res = times9(rotl(times5(state[1]), 7)); + Rand64 t = Ishl(state[1], 17); + Ixor(&state[2], state[0]); + Ixor(&state[3], state[1]); + Ixor(&state[1], state[2]); + Ixor(&state[0], state[3]); + Ixor(&state[2], t); + state[3] = rotl1(state[3], 45); + return res; +} + + +/* +** Converts a 'Rand64' into a float. +*/ + +/* an unsigned 1 with proper type */ +#define UONE ((lu_int32)1) + + +#if FIGS <= 32 + +/* 2^(-FIGS) */ +#define scaleFIG (l_mathop(0.5) / (UONE << (FIGS - 1))) + +/* +** get up to 32 bits from higher half, shifting right to +** throw out the extra bits. +*/ +static lua_Number I2d (Rand64 x) { + lua_Number h = (lua_Number)(trim32(x.h) >> (32 - FIGS)); + return h * scaleFIG; +} + +#else /* 32 < FIGS <= 64 */ + +/* must take care to not shift stuff by more than 31 slots */ + +/* 2^(-FIGS) = 1.0 / 2^30 / 2^3 / 2^(FIGS-33) */ +#define scaleFIG \ + ((lua_Number)1.0 / (UONE << 30) / 8.0 / (UONE << (FIGS - 33))) + +/* +** use FIGS - 32 bits from lower half, throwing out the other +** (32 - (FIGS - 32)) = (64 - FIGS) bits +*/ +#define shiftLOW (64 - FIGS) + +/* +** higher 32 bits go after those (FIGS - 32) bits: shiftHI = 2^(FIGS - 32) +*/ +#define shiftHI ((lua_Number)(UONE << (FIGS - 33)) * 2.0) + + +static lua_Number I2d (Rand64 x) { + lua_Number h = (lua_Number)trim32(x.h) * shiftHI; + lua_Number l = (lua_Number)(trim32(x.l) >> shiftLOW); + return (h + l) * scaleFIG; +} + +#endif + + +/* convert a 'Rand64' to a 'lua_Unsigned' */ +static lua_Unsigned I2UInt (Rand64 x) { + return ((lua_Unsigned)trim32(x.h) << 31 << 1) | (lua_Unsigned)trim32(x.l); +} + +/* convert a 'lua_Unsigned' to a 'Rand64' */ +static Rand64 Int2I (lua_Unsigned n) { + return packI((lu_int32)(n >> 31 >> 1), (lu_int32)n); +} + +#endif /* } */ + + +/* +** A state uses four 'Rand64' values. +*/ +typedef struct { + Rand64 s[4]; +} RanState; + + +/* +** Project the random integer 'ran' into the interval [0, n]. +** Because 'ran' has 2^B possible values, the projection can only be +** uniform when the size of the interval is a power of 2 (exact +** division). Otherwise, to get a uniform projection into [0, n], we +** first compute 'lim', the smallest Mersenne number not smaller than +** 'n'. We then project 'ran' into the interval [0, lim]. If the result +** is inside [0, n], we are done. Otherwise, we try with another 'ran', +** until we have a result inside the interval. +*/ +static lua_Unsigned project (lua_Unsigned ran, lua_Unsigned n, + RanState *state) { + if ((n & (n + 1)) == 0) /* is 'n + 1' a power of 2? */ + return ran & n; /* no bias */ + else { + lua_Unsigned lim = n; + /* compute the smallest (2^b - 1) not smaller than 'n' */ + lim |= (lim >> 1); + lim |= (lim >> 2); + lim |= (lim >> 4); + lim |= (lim >> 8); + lim |= (lim >> 16); +#if (LUA_MAXUNSIGNED >> 31) >= 3 + lim |= (lim >> 32); /* integer type has more than 32 bits */ +#endif + lua_assert((lim & (lim + 1)) == 0 /* 'lim + 1' is a power of 2, */ + && lim >= n /* not smaller than 'n', */ + && (lim >> 1) < n); /* and it is the smallest one */ + while ((ran &= lim) > n) /* project 'ran' into [0..lim] */ + ran = I2UInt(nextrand(state->s)); /* not inside [0..n]? try again */ + return ran; + } +} + + +static int math_random (lua_State *L) { + lua_Integer low, up; + lua_Unsigned p; + RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1)); + Rand64 rv = nextrand(state->s); /* next pseudo-random value */ + switch (lua_gettop(L)) { /* check number of arguments */ + case 0: { /* no arguments */ + lua_pushnumber(L, I2d(rv)); /* float between 0 and 1 */ + return 1; + } + case 1: { /* only upper limit */ + low = 1; + up = luaL_checkinteger(L, 1); + if (up == 0) { /* single 0 as argument? */ + lua_pushinteger(L, I2UInt(rv)); /* full random integer */ + return 1; + } + break; + } + case 2: { /* lower and upper limits */ + low = luaL_checkinteger(L, 1); + up = luaL_checkinteger(L, 2); + break; + } + default: return luaL_error(L, "wrong number of arguments"); + } + /* random integer in the interval [low, up] */ + luaL_argcheck(L, low <= up, 1, "interval is empty"); + /* project random integer into the interval [0, up - low] */ + p = project(I2UInt(rv), (lua_Unsigned)up - (lua_Unsigned)low, state); + lua_pushinteger(L, p + (lua_Unsigned)low); + return 1; +} + + +static void setseed (lua_State *L, Rand64 *state, + lua_Unsigned n1, lua_Unsigned n2) { + int i; + state[0] = Int2I(n1); + state[1] = Int2I(0xff); /* avoid a zero state */ + state[2] = Int2I(n2); + state[3] = Int2I(0); + for (i = 0; i < 16; i++) + nextrand(state); /* discard initial values to "spread" seed */ + lua_pushinteger(L, n1); + lua_pushinteger(L, n2); +} + + +/* +** Set a "random" seed. To get some randomness, use the current time +** and the address of 'L' (in case the machine does address space layout +** randomization). +*/ +static void randseed (lua_State *L, RanState *state) { + lua_Unsigned seed1 = (lua_Unsigned)time(NULL); + lua_Unsigned seed2 = (lua_Unsigned)(size_t)L; + setseed(L, state->s, seed1, seed2); +} + + +static int math_randomseed (lua_State *L) { + RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1)); + if (lua_isnone(L, 1)) { + randseed(L, state); + } + else { + lua_Integer n1 = luaL_checkinteger(L, 1); + lua_Integer n2 = luaL_optinteger(L, 2, 0); + setseed(L, state->s, n1, n2); + } + return 2; /* return seeds */ +} + + +static const luaL_Reg randfuncs[] = { + {"random", math_random}, + {"randomseed", math_randomseed}, + {NULL, NULL} +}; + + +/* +** Register the random functions and initialize their state. +*/ +static void setrandfunc (lua_State *L) { + RanState *state = (RanState *)lua_newuserdatauv(L, sizeof(RanState), 0); + randseed(L, state); /* initialize with a "random" seed */ + lua_pop(L, 2); /* remove pushed seeds */ + luaL_setfuncs(L, randfuncs, 1); +} + +/* }================================================================== */ + + +/* +** {================================================================== +** Deprecated functions (for compatibility only) +** =================================================================== +*/ +#if defined(LUA_COMPAT_MATHLIB) + +static int math_cosh (lua_State *L) { + lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sinh (lua_State *L) { + lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tanh (lua_State *L) { + lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_pow (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + lua_Number y = luaL_checknumber(L, 2); + lua_pushnumber(L, l_mathop(pow)(x, y)); + return 1; +} + +static int math_frexp (lua_State *L) { + int e; + lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); + lua_pushinteger(L, e); + return 2; +} + +static int math_ldexp (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + int ep = (int)luaL_checkinteger(L, 2); + lua_pushnumber(L, l_mathop(ldexp)(x, ep)); + return 1; +} + +static int math_log10 (lua_State *L) { + lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1))); + return 1; +} + +#endif +/* }================================================================== */ + + + +static const luaL_Reg mathlib[] = { + {"abs", math_abs}, + {"acos", math_acos}, + {"asin", math_asin}, + {"atan", math_atan}, + {"ceil", math_ceil}, + {"cos", math_cos}, + {"deg", math_deg}, + {"exp", math_exp}, + {"tointeger", math_toint}, + {"floor", math_floor}, + {"fmod", math_fmod}, + {"ult", math_ult}, + {"log", math_log}, + {"max", math_max}, + {"min", math_min}, + {"modf", math_modf}, + {"rad", math_rad}, + {"sin", math_sin}, + {"sqrt", math_sqrt}, + {"tan", math_tan}, + {"type", math_type}, +#if defined(LUA_COMPAT_MATHLIB) + {"atan2", math_atan}, + {"cosh", math_cosh}, + {"sinh", math_sinh}, + {"tanh", math_tanh}, + {"pow", math_pow}, + {"frexp", math_frexp}, + {"ldexp", math_ldexp}, + {"log10", math_log10}, +#endif + /* placeholders */ + {"random", NULL}, + {"randomseed", NULL}, + {"pi", NULL}, + {"huge", NULL}, + {"maxinteger", NULL}, + {"mininteger", NULL}, + {NULL, NULL} +}; + + +/* +** Open math library +*/ +LUAMOD_API int luaopen_math (lua_State *L) { + luaL_newlib(L, mathlib); + lua_pushnumber(L, PI); + lua_setfield(L, -2, "pi"); + lua_pushnumber(L, (lua_Number)HUGE_VAL); + lua_setfield(L, -2, "huge"); + lua_pushinteger(L, LUA_MAXINTEGER); + lua_setfield(L, -2, "maxinteger"); + lua_pushinteger(L, LUA_MININTEGER); + lua_setfield(L, -2, "mininteger"); + setrandfunc(L); + return 1; +} + diff --git a/Lua/lmem.c b/Lua/lmem.c new file mode 100644 index 00000000..43739bff --- /dev/null +++ b/Lua/lmem.c @@ -0,0 +1,202 @@ +/* +** $Id: lmem.c $ +** Interface to Memory Manager +** See Copyright Notice in lua.h +*/ + +#define lmem_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" + + +#if defined(EMERGENCYGCTESTS) +/* +** First allocation will fail whenever not building initial state +** and not shrinking a block. (This fail will trigger 'tryagain' and +** a full GC cycle at every allocation.) +*/ +static void *firsttry (global_State *g, void *block, size_t os, size_t ns) { + if (ttisnil(&g->nilvalue) && ns > os) + return NULL; /* fail */ + else /* normal allocation */ + return (*g->frealloc)(g->ud, block, os, ns); +} +#else +#define firsttry(g,block,os,ns) ((*g->frealloc)(g->ud, block, os, ns)) +#endif + + + + + +/* +** About the realloc function: +** void *frealloc (void *ud, void *ptr, size_t osize, size_t nsize); +** ('osize' is the old size, 'nsize' is the new size) +** +** - frealloc(ud, p, x, 0) frees the block 'p' and returns NULL. +** Particularly, frealloc(ud, NULL, 0, 0) does nothing, +** which is equivalent to free(NULL) in ISO C. +** +** - frealloc(ud, NULL, x, s) creates a new block of size 's' +** (no matter 'x'). Returns NULL if it cannot create the new block. +** +** - otherwise, frealloc(ud, b, x, y) reallocates the block 'b' from +** size 'x' to size 'y'. Returns NULL if it cannot reallocate the +** block to the new size. +*/ + + + + +/* +** {================================================================== +** Functions to allocate/deallocate arrays for the Parser +** =================================================================== +*/ + +/* +** Minimum size for arrays during parsing, to avoid overhead of +** reallocating to size 1, then 2, and then 4. All these arrays +** will be reallocated to exact sizes or erased when parsing ends. +*/ +#define MINSIZEARRAY 4 + + +void *luaM_growaux_ (lua_State *L, void *block, int nelems, int *psize, + int size_elems, int limit, const char *what) { + void *newblock; + int size = *psize; + if (nelems + 1 <= size) /* does one extra element still fit? */ + return block; /* nothing to be done */ + if (size >= limit / 2) { /* cannot double it? */ + if (unlikely(size >= limit)) /* cannot grow even a little? */ + luaG_runerror(L, "too many %s (limit is %d)", what, limit); + size = limit; /* still have at least one free place */ + } + else { + size *= 2; + if (size < MINSIZEARRAY) + size = MINSIZEARRAY; /* minimum size */ + } + lua_assert(nelems + 1 <= size && size <= limit); + /* 'limit' ensures that multiplication will not overflow */ + newblock = luaM_saferealloc_(L, block, cast_sizet(*psize) * size_elems, + cast_sizet(size) * size_elems); + *psize = size; /* update only when everything else is OK */ + return newblock; +} + + +/* +** In prototypes, the size of the array is also its number of +** elements (to save memory). So, if it cannot shrink an array +** to its number of elements, the only option is to raise an +** error. +*/ +void *luaM_shrinkvector_ (lua_State *L, void *block, int *size, + int final_n, int size_elem) { + void *newblock; + size_t oldsize = cast_sizet((*size) * size_elem); + size_t newsize = cast_sizet(final_n * size_elem); + lua_assert(newsize <= oldsize); + newblock = luaM_saferealloc_(L, block, oldsize, newsize); + *size = final_n; + return newblock; +} + +/* }================================================================== */ + + +l_noret luaM_toobig (lua_State *L) { + luaG_runerror(L, "memory allocation error: block too big"); +} + + +/* +** Free memory +*/ +void luaM_free_ (lua_State *L, void *block, size_t osize) { + global_State *g = G(L); + lua_assert((osize == 0) == (block == NULL)); + (*g->frealloc)(g->ud, block, osize, 0); + g->GCdebt -= osize; +} + + +/* +** In case of allocation fail, this function will call the GC to try +** to free some memory and then try the allocation again. +** (It should not be called when shrinking a block, because then the +** interpreter may be in the middle of a collection step.) +*/ +static void *tryagain (lua_State *L, void *block, + size_t osize, size_t nsize) { + global_State *g = G(L); + if (ttisnil(&g->nilvalue)) { /* is state fully build? */ + luaC_fullgc(L, 1); /* try to free some memory... */ + return (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ + } + else return NULL; /* cannot free any memory without a full state */ +} + + +/* +** Generic allocation routine. +** If allocation fails while shrinking a block, do not try again; the +** GC shrinks some blocks and it is not reentrant. +*/ +void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { + void *newblock; + global_State *g = G(L); + lua_assert((osize == 0) == (block == NULL)); + newblock = firsttry(g, block, osize, nsize); + if (unlikely(newblock == NULL && nsize > 0)) { + if (nsize > osize) /* not shrinking a block? */ + newblock = tryagain(L, block, osize, nsize); + if (newblock == NULL) /* still no memory? */ + return NULL; /* do not update 'GCdebt' */ + } + lua_assert((nsize == 0) == (newblock == NULL)); + g->GCdebt = (g->GCdebt + nsize) - osize; + return newblock; +} + + +void *luaM_saferealloc_ (lua_State *L, void *block, size_t osize, + size_t nsize) { + void *newblock = luaM_realloc_(L, block, osize, nsize); + if (unlikely(newblock == NULL && nsize > 0)) /* allocation failed? */ + luaM_error(L); + return newblock; +} + + +void *luaM_malloc_ (lua_State *L, size_t size, int tag) { + if (size == 0) + return NULL; /* that's all */ + else { + global_State *g = G(L); + void *newblock = firsttry(g, NULL, tag, size); + if (unlikely(newblock == NULL)) { + newblock = tryagain(L, NULL, tag, size); + if (newblock == NULL) + luaM_error(L); + } + g->GCdebt += size; + return newblock; + } +} diff --git a/Lua/lmem.h b/Lua/lmem.h new file mode 100644 index 00000000..8c75a44b --- /dev/null +++ b/Lua/lmem.h @@ -0,0 +1,93 @@ +/* +** $Id: lmem.h $ +** Interface to Memory Manager +** See Copyright Notice in lua.h +*/ + +#ifndef lmem_h +#define lmem_h + + +#include + +#include "llimits.h" +#include "lua.h" + + +#define luaM_error(L) luaD_throw(L, LUA_ERRMEM) + + +/* +** This macro tests whether it is safe to multiply 'n' by the size of +** type 't' without overflows. Because 'e' is always constant, it avoids +** the runtime division MAX_SIZET/(e). +** (The macro is somewhat complex to avoid warnings: The 'sizeof' +** comparison avoids a runtime comparison when overflow cannot occur. +** The compiler should be able to optimize the real test by itself, but +** when it does it, it may give a warning about "comparison is always +** false due to limited range of data type"; the +1 tricks the compiler, +** avoiding this warning but also this optimization.) +*/ +#define luaM_testsize(n,e) \ + (sizeof(n) >= sizeof(size_t) && cast_sizet((n)) + 1 > MAX_SIZET/(e)) + +#define luaM_checksize(L,n,e) \ + (luaM_testsize(n,e) ? luaM_toobig(L) : cast_void(0)) + + +/* +** Computes the minimum between 'n' and 'MAX_SIZET/sizeof(t)', so that +** the result is not larger than 'n' and cannot overflow a 'size_t' +** when multiplied by the size of type 't'. (Assumes that 'n' is an +** 'int' or 'unsigned int' and that 'int' is not larger than 'size_t'.) +*/ +#define luaM_limitN(n,t) \ + ((cast_sizet(n) <= MAX_SIZET/sizeof(t)) ? (n) : \ + cast_uint((MAX_SIZET/sizeof(t)))) + + +/* +** Arrays of chars do not need any test +*/ +#define luaM_reallocvchar(L,b,on,n) \ + cast_charp(luaM_saferealloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char))) + +#define luaM_freemem(L, b, s) luaM_free_(L, (b), (s)) +#define luaM_free(L, b) luaM_free_(L, (b), sizeof(*(b))) +#define luaM_freearray(L, b, n) luaM_free_(L, (b), (n)*sizeof(*(b))) + +#define luaM_new(L,t) cast(t*, luaM_malloc_(L, sizeof(t), 0)) +#define luaM_newvector(L,n,t) cast(t*, luaM_malloc_(L, (n)*sizeof(t), 0)) +#define luaM_newvectorchecked(L,n,t) \ + (luaM_checksize(L,n,sizeof(t)), luaM_newvector(L,n,t)) + +#define luaM_newobject(L,tag,s) luaM_malloc_(L, (s), tag) + +#define luaM_growvector(L,v,nelems,size,t,limit,e) \ + ((v)=cast(t *, luaM_growaux_(L,v,nelems,&(size),sizeof(t), \ + luaM_limitN(limit,t),e))) + +#define luaM_reallocvector(L, v,oldn,n,t) \ + (cast(t *, luaM_realloc_(L, v, cast_sizet(oldn) * sizeof(t), \ + cast_sizet(n) * sizeof(t)))) + +#define luaM_shrinkvector(L,v,size,fs,t) \ + ((v)=cast(t *, luaM_shrinkvector_(L, v, &(size), fs, sizeof(t)))) + +LUAI_FUNC l_noret luaM_toobig (lua_State *L); + +/* not to be called directly */ +LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize, + size_t size); +LUAI_FUNC void *luaM_saferealloc_ (lua_State *L, void *block, size_t oldsize, + size_t size); +LUAI_FUNC void luaM_free_ (lua_State *L, void *block, size_t osize); +LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int nelems, + int *size, int size_elem, int limit, + const char *what); +LUAI_FUNC void *luaM_shrinkvector_ (lua_State *L, void *block, int *nelem, + int final_n, int size_elem); +LUAI_FUNC void *luaM_malloc_ (lua_State *L, size_t size, int tag); + +#endif + diff --git a/Lua/loadlib.c b/Lua/loadlib.c new file mode 100644 index 00000000..c0ec9a13 --- /dev/null +++ b/Lua/loadlib.c @@ -0,0 +1,759 @@ +/* +** $Id: loadlib.c $ +** Dynamic library loader for Lua +** See Copyright Notice in lua.h +** +** This module contains an implementation of loadlib for Unix systems +** that have dlfcn, an implementation for Windows, and a stub for other +** systems. +*/ + +#define loadlib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** LUA_IGMARK is a mark to ignore all before it when building the +** luaopen_ function name. +*/ +#if !defined (LUA_IGMARK) +#define LUA_IGMARK "-" +#endif + + +/* +** LUA_CSUBSEP is the character that replaces dots in submodule names +** when searching for a C loader. +** LUA_LSUBSEP is the character that replaces dots in submodule names +** when searching for a Lua loader. +*/ +#if !defined(LUA_CSUBSEP) +#define LUA_CSUBSEP LUA_DIRSEP +#endif + +#if !defined(LUA_LSUBSEP) +#define LUA_LSUBSEP LUA_DIRSEP +#endif + + +/* prefix for open functions in C libraries */ +#define LUA_POF "luaopen_" + +/* separator for open functions in C libraries */ +#define LUA_OFSEP "_" + + +/* +** key for table in the registry that keeps handles +** for all loaded C libraries +*/ +static const char *const CLIBS = "_CLIBS"; + +#define LIB_FAIL "open" + + +#define setprogdir(L) ((void)0) + + +/* +** Special type equivalent to '(void*)' for functions in gcc +** (to suppress warnings when converting function pointers) +*/ +typedef void (*voidf)(void); + + +/* +** system-dependent functions +*/ + +/* +** unload library 'lib' +*/ +static void lsys_unloadlib (void *lib); + +/* +** load C library in file 'path'. If 'seeglb', load with all names in +** the library global. +** Returns the library; in case of error, returns NULL plus an +** error string in the stack. +*/ +static void *lsys_load (lua_State *L, const char *path, int seeglb); + +/* +** Try to find a function named 'sym' in library 'lib'. +** Returns the function; in case of error, returns NULL plus an +** error string in the stack. +*/ +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym); + + + + +#if defined(LUA_USE_DLOPEN) /* { */ +/* +** {======================================================================== +** This is an implementation of loadlib based on the dlfcn interface. +** The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD, +** NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least +** as an emulation layer on top of native functions. +** ========================================================================= +*/ + +#include + +/* +** Macro to convert pointer-to-void* to pointer-to-function. This cast +** is undefined according to ISO C, but POSIX assumes that it works. +** (The '__extension__' in gnu compilers is only to avoid warnings.) +*/ +#if defined(__GNUC__) +#define cast_func(p) (__extension__ (lua_CFunction)(p)) +#else +#define cast_func(p) ((lua_CFunction)(p)) +#endif + + +static void lsys_unloadlib (void *lib) { + dlclose(lib); +} + + +static void *lsys_load (lua_State *L, const char *path, int seeglb) { + void *lib = dlopen(path, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL)); + if (lib == NULL) lua_pushstring(L, dlerror()); + return lib; +} + + +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = cast_func(dlsym(lib, sym)); + if (f == NULL) lua_pushstring(L, dlerror()); + return f; +} + +/* }====================================================== */ + + + +#elif defined(LUA_DL_DLL) /* }{ */ +/* +** {====================================================================== +** This is an implementation of loadlib for Windows using native functions. +** ======================================================================= +*/ + +#include + + +/* +** optional flags for LoadLibraryEx +*/ +#if !defined(LUA_LLE_FLAGS) +#define LUA_LLE_FLAGS 0 +#endif + + +#undef setprogdir + + +/* +** Replace in the path (on the top of the stack) any occurrence +** of LUA_EXEC_DIR with the executable's path. +*/ +static void setprogdir (lua_State *L) { + char buff[MAX_PATH + 1]; + char *lb; + DWORD nsize = sizeof(buff)/sizeof(char); + DWORD n = GetModuleFileNameA(NULL, buff, nsize); /* get exec. name */ + if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL) + luaL_error(L, "unable to get ModuleFileName"); + else { + *lb = '\0'; /* cut name on the last '\\' to get the path */ + luaL_gsub(L, lua_tostring(L, -1), LUA_EXEC_DIR, buff); + lua_remove(L, -2); /* remove original string */ + } +} + + + + +static void pusherror (lua_State *L) { + int error = GetLastError(); + char buffer[128]; + if (FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, error, 0, buffer, sizeof(buffer)/sizeof(char), NULL)) + lua_pushstring(L, buffer); + else + lua_pushfstring(L, "system error %d\n", error); +} + +static void lsys_unloadlib (void *lib) { + FreeLibrary((HMODULE)lib); +} + + +static void *lsys_load (lua_State *L, const char *path, int seeglb) { + HMODULE lib = LoadLibraryExA(path, NULL, LUA_LLE_FLAGS); + (void)(seeglb); /* not used: symbols are 'global' by default */ + if (lib == NULL) pusherror(L); + return lib; +} + + +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = (lua_CFunction)(voidf)GetProcAddress((HMODULE)lib, sym); + if (f == NULL) pusherror(L); + return f; +} + +/* }====================================================== */ + + +#else /* }{ */ +/* +** {====================================================== +** Fallback for other systems +** ======================================================= +*/ + +#undef LIB_FAIL +#define LIB_FAIL "absent" + + +#define DLMSG "dynamic libraries not enabled; check your Lua installation" + + +static void lsys_unloadlib (void *lib) { + (void)(lib); /* not used */ +} + + +static void *lsys_load (lua_State *L, const char *path, int seeglb) { + (void)(path); (void)(seeglb); /* not used */ + lua_pushliteral(L, DLMSG); + return NULL; +} + + +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { + (void)(lib); (void)(sym); /* not used */ + lua_pushliteral(L, DLMSG); + return NULL; +} + +/* }====================================================== */ +#endif /* } */ + + +/* +** {================================================================== +** Set Paths +** =================================================================== +*/ + +/* +** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment +** variables that Lua check to set its paths. +*/ +#if !defined(LUA_PATH_VAR) +#define LUA_PATH_VAR "LUA_PATH" +#endif + +#if !defined(LUA_CPATH_VAR) +#define LUA_CPATH_VAR "LUA_CPATH" +#endif + + + +/* +** return registry.LUA_NOENV as a boolean +*/ +static int noenv (lua_State *L) { + int b; + lua_getfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); + b = lua_toboolean(L, -1); + lua_pop(L, 1); /* remove value */ + return b; +} + + +/* +** Set a path +*/ +static void setpath (lua_State *L, const char *fieldname, + const char *envname, + const char *dft) { + const char *dftmark; + const char *nver = lua_pushfstring(L, "%s%s", envname, LUA_VERSUFFIX); + const char *path = getenv(nver); /* try versioned name */ + if (path == NULL) /* no versioned environment variable? */ + path = getenv(envname); /* try unversioned name */ + if (path == NULL || noenv(L)) /* no environment variable? */ + lua_pushstring(L, dft); /* use default */ + else if ((dftmark = strstr(path, LUA_PATH_SEP LUA_PATH_SEP)) == NULL) + lua_pushstring(L, path); /* nothing to change */ + else { /* path contains a ";;": insert default path in its place */ + size_t len = strlen(path); + luaL_Buffer b; + luaL_buffinit(L, &b); + if (path < dftmark) { /* is there a prefix before ';;'? */ + luaL_addlstring(&b, path, dftmark - path); /* add it */ + luaL_addchar(&b, *LUA_PATH_SEP); + } + luaL_addstring(&b, dft); /* add default */ + if (dftmark < path + len - 2) { /* is there a suffix after ';;'? */ + luaL_addchar(&b, *LUA_PATH_SEP); + luaL_addlstring(&b, dftmark + 2, (path + len - 2) - dftmark); + } + luaL_pushresult(&b); + } + setprogdir(L); + lua_setfield(L, -3, fieldname); /* package[fieldname] = path value */ + lua_pop(L, 1); /* pop versioned variable name ('nver') */ +} + +/* }================================================================== */ + + +/* +** return registry.CLIBS[path] +*/ +static void *checkclib (lua_State *L, const char *path) { + void *plib; + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_getfield(L, -1, path); + plib = lua_touserdata(L, -1); /* plib = CLIBS[path] */ + lua_pop(L, 2); /* pop CLIBS table and 'plib' */ + return plib; +} + + +/* +** registry.CLIBS[path] = plib -- for queries +** registry.CLIBS[#CLIBS + 1] = plib -- also keep a list of all libraries +*/ +static void addtoclib (lua_State *L, const char *path, void *plib) { + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_pushlightuserdata(L, plib); + lua_pushvalue(L, -1); + lua_setfield(L, -3, path); /* CLIBS[path] = plib */ + lua_rawseti(L, -2, luaL_len(L, -2) + 1); /* CLIBS[#CLIBS + 1] = plib */ + lua_pop(L, 1); /* pop CLIBS table */ +} + + +/* +** __gc tag method for CLIBS table: calls 'lsys_unloadlib' for all lib +** handles in list CLIBS +*/ +static int gctm (lua_State *L) { + lua_Integer n = luaL_len(L, 1); + for (; n >= 1; n--) { /* for each handle, in reverse order */ + lua_rawgeti(L, 1, n); /* get handle CLIBS[n] */ + lsys_unloadlib(lua_touserdata(L, -1)); + lua_pop(L, 1); /* pop handle */ + } + return 0; +} + + + +/* error codes for 'lookforfunc' */ +#define ERRLIB 1 +#define ERRFUNC 2 + +/* +** Look for a C function named 'sym' in a dynamically loaded library +** 'path'. +** First, check whether the library is already loaded; if not, try +** to load it. +** Then, if 'sym' is '*', return true (as library has been loaded). +** Otherwise, look for symbol 'sym' in the library and push a +** C function with that symbol. +** Return 0 and 'true' or a function in the stack; in case of +** errors, return an error code and an error message in the stack. +*/ +static int lookforfunc (lua_State *L, const char *path, const char *sym) { + void *reg = checkclib(L, path); /* check loaded C libraries */ + if (reg == NULL) { /* must load library? */ + reg = lsys_load(L, path, *sym == '*'); /* global symbols if 'sym'=='*' */ + if (reg == NULL) return ERRLIB; /* unable to load library */ + addtoclib(L, path, reg); + } + if (*sym == '*') { /* loading only library (no function)? */ + lua_pushboolean(L, 1); /* return 'true' */ + return 0; /* no errors */ + } + else { + lua_CFunction f = lsys_sym(L, reg, sym); + if (f == NULL) + return ERRFUNC; /* unable to find function */ + lua_pushcfunction(L, f); /* else create new function */ + return 0; /* no errors */ + } +} + + +static int ll_loadlib (lua_State *L) { + const char *path = luaL_checkstring(L, 1); + const char *init = luaL_checkstring(L, 2); + int stat = lookforfunc(L, path, init); + if (stat == 0) /* no errors? */ + return 1; /* return the loaded function */ + else { /* error; error message is on stack top */ + luaL_pushfail(L); + lua_insert(L, -2); + lua_pushstring(L, (stat == ERRLIB) ? LIB_FAIL : "init"); + return 3; /* return fail, error message, and where */ + } +} + + + +/* +** {====================================================== +** 'require' function +** ======================================================= +*/ + + +static int readable (const char *filename) { + FILE *f = fopen(filename, "r"); /* try to open file */ + if (f == NULL) return 0; /* open failed */ + fclose(f); + return 1; +} + + +/* +** Get the next name in '*path' = 'name1;name2;name3;...', changing +** the ending ';' to '\0' to create a zero-terminated string. Return +** NULL when list ends. +*/ +static const char *getnextfilename (char **path, char *end) { + char *sep; + char *name = *path; + if (name == end) + return NULL; /* no more names */ + else if (*name == '\0') { /* from previous iteration? */ + *name = *LUA_PATH_SEP; /* restore separator */ + name++; /* skip it */ + } + sep = strchr(name, *LUA_PATH_SEP); /* find next separator */ + if (sep == NULL) /* separator not found? */ + sep = end; /* name goes until the end */ + *sep = '\0'; /* finish file name */ + *path = sep; /* will start next search from here */ + return name; +} + + +/* +** Given a path such as ";blabla.so;blublu.so", pushes the string +** +** no file 'blabla.so' +** no file 'blublu.so' +*/ +static void pusherrornotfound (lua_State *L, const char *path) { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addstring(&b, "no file '"); + luaL_addgsub(&b, path, LUA_PATH_SEP, "'\n\tno file '"); + luaL_addstring(&b, "'"); + luaL_pushresult(&b); +} + + +static const char *searchpath (lua_State *L, const char *name, + const char *path, + const char *sep, + const char *dirsep) { + luaL_Buffer buff; + char *pathname; /* path with name inserted */ + char *endpathname; /* its end */ + const char *filename; + /* separator is non-empty and appears in 'name'? */ + if (*sep != '\0' && strchr(name, *sep) != NULL) + name = luaL_gsub(L, name, sep, dirsep); /* replace it by 'dirsep' */ + luaL_buffinit(L, &buff); + /* add path to the buffer, replacing marks ('?') with the file name */ + luaL_addgsub(&buff, path, LUA_PATH_MARK, name); + luaL_addchar(&buff, '\0'); + pathname = luaL_buffaddr(&buff); /* writable list of file names */ + endpathname = pathname + luaL_bufflen(&buff) - 1; + while ((filename = getnextfilename(&pathname, endpathname)) != NULL) { + if (readable(filename)) /* does file exist and is readable? */ + return lua_pushstring(L, filename); /* save and return name */ + } + luaL_pushresult(&buff); /* push path to create error message */ + pusherrornotfound(L, lua_tostring(L, -1)); /* create error message */ + return NULL; /* not found */ +} + + +static int ll_searchpath (lua_State *L) { + const char *f = searchpath(L, luaL_checkstring(L, 1), + luaL_checkstring(L, 2), + luaL_optstring(L, 3, "."), + luaL_optstring(L, 4, LUA_DIRSEP)); + if (f != NULL) return 1; + else { /* error message is on top of the stack */ + luaL_pushfail(L); + lua_insert(L, -2); + return 2; /* return fail + error message */ + } +} + + +static const char *findfile (lua_State *L, const char *name, + const char *pname, + const char *dirsep) { + const char *path; + lua_getfield(L, lua_upvalueindex(1), pname); + path = lua_tostring(L, -1); + if (path == NULL) + luaL_error(L, "'package.%s' must be a string", pname); + return searchpath(L, name, path, ".", dirsep); +} + + +static int checkload (lua_State *L, int stat, const char *filename) { + if (stat) { /* module loaded successfully? */ + lua_pushstring(L, filename); /* will be 2nd argument to module */ + return 2; /* return open function and file name */ + } + else + return luaL_error(L, "error loading module '%s' from file '%s':\n\t%s", + lua_tostring(L, 1), filename, lua_tostring(L, -1)); +} + + +static int searcher_Lua (lua_State *L) { + const char *filename; + const char *name = luaL_checkstring(L, 1); + filename = findfile(L, name, "path", LUA_LSUBSEP); + if (filename == NULL) return 1; /* module not found in this path */ + return checkload(L, (luaL_loadfile(L, filename) == LUA_OK), filename); +} + + +/* +** Try to find a load function for module 'modname' at file 'filename'. +** First, change '.' to '_' in 'modname'; then, if 'modname' has +** the form X-Y (that is, it has an "ignore mark"), build a function +** name "luaopen_X" and look for it. (For compatibility, if that +** fails, it also tries "luaopen_Y".) If there is no ignore mark, +** look for a function named "luaopen_modname". +*/ +static int loadfunc (lua_State *L, const char *filename, const char *modname) { + const char *openfunc; + const char *mark; + modname = luaL_gsub(L, modname, ".", LUA_OFSEP); + mark = strchr(modname, *LUA_IGMARK); + if (mark) { + int stat; + openfunc = lua_pushlstring(L, modname, mark - modname); + openfunc = lua_pushfstring(L, LUA_POF"%s", openfunc); + stat = lookforfunc(L, filename, openfunc); + if (stat != ERRFUNC) return stat; + modname = mark + 1; /* else go ahead and try old-style name */ + } + openfunc = lua_pushfstring(L, LUA_POF"%s", modname); + return lookforfunc(L, filename, openfunc); +} + + +static int searcher_C (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + const char *filename = findfile(L, name, "cpath", LUA_CSUBSEP); + if (filename == NULL) return 1; /* module not found in this path */ + return checkload(L, (loadfunc(L, filename, name) == 0), filename); +} + + +static int searcher_Croot (lua_State *L) { + const char *filename; + const char *name = luaL_checkstring(L, 1); + const char *p = strchr(name, '.'); + int stat; + if (p == NULL) return 0; /* is root */ + lua_pushlstring(L, name, p - name); + filename = findfile(L, lua_tostring(L, -1), "cpath", LUA_CSUBSEP); + if (filename == NULL) return 1; /* root not found */ + if ((stat = loadfunc(L, filename, name)) != 0) { + if (stat != ERRFUNC) + return checkload(L, 0, filename); /* real error */ + else { /* open function not found */ + lua_pushfstring(L, "no module '%s' in file '%s'", name, filename); + return 1; + } + } + lua_pushstring(L, filename); /* will be 2nd argument to module */ + return 2; +} + + +static int searcher_preload (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); + if (lua_getfield(L, -1, name) == LUA_TNIL) { /* not found? */ + lua_pushfstring(L, "no field package.preload['%s']", name); + return 1; + } + else { + lua_pushliteral(L, ":preload:"); + return 2; + } +} + + +static void findloader (lua_State *L, const char *name) { + int i; + luaL_Buffer msg; /* to build error message */ + /* push 'package.searchers' to index 3 in the stack */ + if (lua_getfield(L, lua_upvalueindex(1), "searchers") != LUA_TTABLE) + luaL_error(L, "'package.searchers' must be a table"); + luaL_buffinit(L, &msg); + /* iterate over available searchers to find a loader */ + for (i = 1; ; i++) { + luaL_addstring(&msg, "\n\t"); /* error-message prefix */ + if (lua_rawgeti(L, 3, i) == LUA_TNIL) { /* no more searchers? */ + lua_pop(L, 1); /* remove nil */ + luaL_buffsub(&msg, 2); /* remove prefix */ + luaL_pushresult(&msg); /* create error message */ + luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1)); + } + lua_pushstring(L, name); + lua_call(L, 1, 2); /* call it */ + if (lua_isfunction(L, -2)) /* did it find a loader? */ + return; /* module loader found */ + else if (lua_isstring(L, -2)) { /* searcher returned error message? */ + lua_pop(L, 1); /* remove extra return */ + luaL_addvalue(&msg); /* concatenate error message */ + } + else { /* no error message */ + lua_pop(L, 2); /* remove both returns */ + luaL_buffsub(&msg, 2); /* remove prefix */ + } + } +} + + +static int ll_require (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + lua_settop(L, 1); /* LOADED table will be at index 2 */ + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_getfield(L, 2, name); /* LOADED[name] */ + if (lua_toboolean(L, -1)) /* is it there? */ + return 1; /* package is already loaded */ + /* else must load package */ + lua_pop(L, 1); /* remove 'getfield' result */ + findloader(L, name); + lua_rotate(L, -2, 1); /* function <-> loader data */ + lua_pushvalue(L, 1); /* name is 1st argument to module loader */ + lua_pushvalue(L, -3); /* loader data is 2nd argument */ + /* stack: ...; loader data; loader function; mod. name; loader data */ + lua_call(L, 2, 1); /* run loader to load module */ + /* stack: ...; loader data; result from loader */ + if (!lua_isnil(L, -1)) /* non-nil return? */ + lua_setfield(L, 2, name); /* LOADED[name] = returned value */ + else + lua_pop(L, 1); /* pop nil */ + if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */ + lua_pushboolean(L, 1); /* use true as result */ + lua_copy(L, -1, -2); /* replace loader result */ + lua_setfield(L, 2, name); /* LOADED[name] = true */ + } + lua_rotate(L, -2, 1); /* loader data <-> module result */ + return 2; /* return module result and loader data */ +} + +/* }====================================================== */ + + + + +static const luaL_Reg pk_funcs[] = { + {"loadlib", ll_loadlib}, + {"searchpath", ll_searchpath}, + /* placeholders */ + {"preload", NULL}, + {"cpath", NULL}, + {"path", NULL}, + {"searchers", NULL}, + {"loaded", NULL}, + {NULL, NULL} +}; + + +static const luaL_Reg ll_funcs[] = { + {"require", ll_require}, + {NULL, NULL} +}; + + +static void createsearcherstable (lua_State *L) { + static const lua_CFunction searchers[] = + {searcher_preload, searcher_Lua, searcher_C, searcher_Croot, NULL}; + int i; + /* create 'searchers' table */ + lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0); + /* fill it with predefined searchers */ + for (i=0; searchers[i] != NULL; i++) { + lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */ + lua_pushcclosure(L, searchers[i], 1); + lua_rawseti(L, -2, i+1); + } + lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */ +} + + +/* +** create table CLIBS to keep track of loaded C libraries, +** setting a finalizer to close all libraries when closing state. +*/ +static void createclibstable (lua_State *L) { + luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); /* create CLIBS table */ + lua_createtable(L, 0, 1); /* create metatable for CLIBS */ + lua_pushcfunction(L, gctm); + lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */ + lua_setmetatable(L, -2); +} + + +LUAMOD_API int luaopen_package (lua_State *L) { + createclibstable(L); + luaL_newlib(L, pk_funcs); /* create 'package' table */ + createsearcherstable(L); + /* set paths */ + setpath(L, "path", LUA_PATH_VAR, LUA_PATH_DEFAULT); + setpath(L, "cpath", LUA_CPATH_VAR, LUA_CPATH_DEFAULT); + /* store config information */ + lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n" + LUA_EXEC_DIR "\n" LUA_IGMARK "\n"); + lua_setfield(L, -2, "config"); + /* set field 'loaded' */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_setfield(L, -2, "loaded"); + /* set field 'preload' */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); + lua_setfield(L, -2, "preload"); + lua_pushglobaltable(L); + lua_pushvalue(L, -2); /* set 'package' as upvalue for next lib */ + luaL_setfuncs(L, ll_funcs, 1); /* open lib into global table */ + lua_pop(L, 1); /* pop global table */ + return 1; /* return 'package' table */ +} + diff --git a/Lua/lobject.c b/Lua/lobject.c new file mode 100644 index 00000000..0e504be0 --- /dev/null +++ b/Lua/lobject.c @@ -0,0 +1,592 @@ +/* +** $Id: lobject.c $ +** Some generic functions over Lua objects +** See Copyright Notice in lua.h +*/ + +#define lobject_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lctype.h" +#include "ldebug.h" +#include "ldo.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "lvm.h" + + +/* +** Computes ceil(log2(x)) +*/ +int luaO_ceillog2 (unsigned int x) { + static const lu_byte log_2[256] = { /* log_2[i] = ceil(log2(i - 1)) */ + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + }; + int l = 0; + x--; + while (x >= 256) { l += 8; x >>= 8; } + return l + log_2[x]; +} + + +static lua_Integer intarith (lua_State *L, int op, lua_Integer v1, + lua_Integer v2) { + switch (op) { + case LUA_OPADD: return intop(+, v1, v2); + case LUA_OPSUB:return intop(-, v1, v2); + case LUA_OPMUL:return intop(*, v1, v2); + case LUA_OPMOD: return luaV_mod(L, v1, v2); + case LUA_OPIDIV: return luaV_idiv(L, v1, v2); + case LUA_OPBAND: return intop(&, v1, v2); + case LUA_OPBOR: return intop(|, v1, v2); + case LUA_OPBXOR: return intop(^, v1, v2); + case LUA_OPSHL: return luaV_shiftl(v1, v2); + case LUA_OPSHR: return luaV_shiftl(v1, -v2); + case LUA_OPUNM: return intop(-, 0, v1); + case LUA_OPBNOT: return intop(^, ~l_castS2U(0), v1); + default: lua_assert(0); return 0; + } +} + + +static lua_Number numarith (lua_State *L, int op, lua_Number v1, + lua_Number v2) { + switch (op) { + case LUA_OPADD: return luai_numadd(L, v1, v2); + case LUA_OPSUB: return luai_numsub(L, v1, v2); + case LUA_OPMUL: return luai_nummul(L, v1, v2); + case LUA_OPDIV: return luai_numdiv(L, v1, v2); + case LUA_OPPOW: return luai_numpow(L, v1, v2); + case LUA_OPIDIV: return luai_numidiv(L, v1, v2); + case LUA_OPUNM: return luai_numunm(L, v1); + case LUA_OPMOD: return luaV_modf(L, v1, v2); + default: lua_assert(0); return 0; + } +} + + +int luaO_rawarith (lua_State *L, int op, const TValue *p1, const TValue *p2, + TValue *res) { + switch (op) { + case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: + case LUA_OPSHL: case LUA_OPSHR: + case LUA_OPBNOT: { /* operate only on integers */ + lua_Integer i1; lua_Integer i2; + if (tointegerns(p1, &i1) && tointegerns(p2, &i2)) { + setivalue(res, intarith(L, op, i1, i2)); + return 1; + } + else return 0; /* fail */ + } + case LUA_OPDIV: case LUA_OPPOW: { /* operate only on floats */ + lua_Number n1; lua_Number n2; + if (tonumberns(p1, n1) && tonumberns(p2, n2)) { + setfltvalue(res, numarith(L, op, n1, n2)); + return 1; + } + else return 0; /* fail */ + } + default: { /* other operations */ + lua_Number n1; lua_Number n2; + if (ttisinteger(p1) && ttisinteger(p2)) { + setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2))); + return 1; + } + else if (tonumberns(p1, n1) && tonumberns(p2, n2)) { + setfltvalue(res, numarith(L, op, n1, n2)); + return 1; + } + else return 0; /* fail */ + } + } +} + + +void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2, + StkId res) { + if (!luaO_rawarith(L, op, p1, p2, s2v(res))) { + /* could not perform raw operation; try metamethod */ + luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD)); + } +} + + +int luaO_hexavalue (int c) { + if (lisdigit(c)) return c - '0'; + else return (ltolower(c) - 'a') + 10; +} + + +static int isneg (const char **s) { + if (**s == '-') { (*s)++; return 1; } + else if (**s == '+') (*s)++; + return 0; +} + + + +/* +** {================================================================== +** Lua's implementation for 'lua_strx2number' +** =================================================================== +*/ + +#if !defined(lua_strx2number) + +/* maximum number of significant digits to read (to avoid overflows + even with single floats) */ +#define MAXSIGDIG 30 + +/* +** convert a hexadecimal numeric string to a number, following +** C99 specification for 'strtod' +*/ +static lua_Number lua_strx2number (const char *s, char **endptr) { + int dot = lua_getlocaledecpoint(); + lua_Number r = 0.0; /* result (accumulator) */ + int sigdig = 0; /* number of significant digits */ + int nosigdig = 0; /* number of non-significant digits */ + int e = 0; /* exponent correction */ + int neg; /* 1 if number is negative */ + int hasdot = 0; /* true after seen a dot */ + *endptr = cast_charp(s); /* nothing is valid yet */ + while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ + neg = isneg(&s); /* check sign */ + if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */ + return 0.0; /* invalid format (no '0x') */ + for (s += 2; ; s++) { /* skip '0x' and read numeral */ + if (*s == dot) { + if (hasdot) break; /* second dot? stop loop */ + else hasdot = 1; + } + else if (lisxdigit(cast_uchar(*s))) { + if (sigdig == 0 && *s == '0') /* non-significant digit (zero)? */ + nosigdig++; + else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */ + r = (r * cast_num(16.0)) + luaO_hexavalue(*s); + else e++; /* too many digits; ignore, but still count for exponent */ + if (hasdot) e--; /* decimal digit? correct exponent */ + } + else break; /* neither a dot nor a digit */ + } + if (nosigdig + sigdig == 0) /* no digits? */ + return 0.0; /* invalid format */ + *endptr = cast_charp(s); /* valid up to here */ + e *= 4; /* each digit multiplies/divides value by 2^4 */ + if (*s == 'p' || *s == 'P') { /* exponent part? */ + int exp1 = 0; /* exponent value */ + int neg1; /* exponent sign */ + s++; /* skip 'p' */ + neg1 = isneg(&s); /* sign */ + if (!lisdigit(cast_uchar(*s))) + return 0.0; /* invalid; must have at least one digit */ + while (lisdigit(cast_uchar(*s))) /* read exponent */ + exp1 = exp1 * 10 + *(s++) - '0'; + if (neg1) exp1 = -exp1; + e += exp1; + *endptr = cast_charp(s); /* valid up to here */ + } + if (neg) r = -r; + return l_mathop(ldexp)(r, e); +} + +#endif +/* }====================================================== */ + + +/* maximum length of a numeral to be converted to a number */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + +/* +** Convert string 's' to a Lua number (put in 'result'). Return NULL on +** fail or the address of the ending '\0' on success. ('mode' == 'x') +** means a hexadecimal numeral. +*/ +static const char *l_str2dloc (const char *s, lua_Number *result, int mode) { + char *endptr; + *result = (mode == 'x') ? lua_strx2number(s, &endptr) /* try to convert */ + : lua_str2number(s, &endptr); + if (endptr == s) return NULL; /* nothing recognized? */ + while (lisspace(cast_uchar(*endptr))) endptr++; /* skip trailing spaces */ + return (*endptr == '\0') ? endptr : NULL; /* OK iff no trailing chars */ +} + + +/* +** Convert string 's' to a Lua number (put in 'result') handling the +** current locale. +** This function accepts both the current locale or a dot as the radix +** mark. If the conversion fails, it may mean number has a dot but +** locale accepts something else. In that case, the code copies 's' +** to a buffer (because 's' is read-only), changes the dot to the +** current locale radix mark, and tries to convert again. +** The variable 'mode' checks for special characters in the string: +** - 'n' means 'inf' or 'nan' (which should be rejected) +** - 'x' means a hexadecimal numeral +** - '.' just optimizes the search for the common case (no special chars) +*/ +static const char *l_str2d (const char *s, lua_Number *result) { + const char *endptr; + const char *pmode = strpbrk(s, ".xXnN"); /* look for special chars */ + int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0; + if (mode == 'n') /* reject 'inf' and 'nan' */ + return NULL; + endptr = l_str2dloc(s, result, mode); /* try to convert */ + if (endptr == NULL) { /* failed? may be a different locale */ + char buff[L_MAXLENNUM + 1]; + const char *pdot = strchr(s, '.'); + if (pdot == NULL || strlen(s) > L_MAXLENNUM) + return NULL; /* string too long or no dot; fail */ + strcpy(buff, s); /* copy string to buffer */ + buff[pdot - s] = lua_getlocaledecpoint(); /* correct decimal point */ + endptr = l_str2dloc(buff, result, mode); /* try again */ + if (endptr != NULL) + endptr = s + (endptr - buff); /* make relative to 's' */ + } + return endptr; +} + + +#define MAXBY10 cast(lua_Unsigned, LUA_MAXINTEGER / 10) +#define MAXLASTD cast_int(LUA_MAXINTEGER % 10) + +static const char *l_str2int (const char *s, lua_Integer *result) { + lua_Unsigned a = 0; + int empty = 1; + int neg; + while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ + neg = isneg(&s); + if (s[0] == '0' && + (s[1] == 'x' || s[1] == 'X')) { /* hex? */ + s += 2; /* skip '0x' */ + for (; lisxdigit(cast_uchar(*s)); s++) { + a = a * 16 + luaO_hexavalue(*s); + empty = 0; + } + } + else { /* decimal */ + for (; lisdigit(cast_uchar(*s)); s++) { + int d = *s - '0'; + if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ + return NULL; /* do not accept it (as integer) */ + a = a * 10 + d; + empty = 0; + } + } + while (lisspace(cast_uchar(*s))) s++; /* skip trailing spaces */ + if (empty || *s != '\0') return NULL; /* something wrong in the numeral */ + else { + *result = l_castU2S((neg) ? 0u - a : a); + return s; + } +} + + +size_t luaO_str2num (const char *s, TValue *o) { + lua_Integer i; lua_Number n; + const char *e; + if ((e = l_str2int(s, &i)) != NULL) { /* try as an integer */ + setivalue(o, i); + } + else if ((e = l_str2d(s, &n)) != NULL) { /* else try as a float */ + setfltvalue(o, n); + } + else + return 0; /* conversion failed */ + return (e - s) + 1; /* success; return string size */ +} + + +int luaO_utf8esc (char *buff, unsigned long x) { + int n = 1; /* number of bytes put in buffer (backwards) */ + lua_assert(x <= 0x7FFFFFFFu); + if (x < 0x80) /* ascii? */ + buff[UTF8BUFFSZ - 1] = cast_char(x); + else { /* need continuation bytes */ + unsigned int mfb = 0x3f; /* maximum that fits in first byte */ + do { /* add continuation bytes */ + buff[UTF8BUFFSZ - (n++)] = cast_char(0x80 | (x & 0x3f)); + x >>= 6; /* remove added bits */ + mfb >>= 1; /* now there is one less bit available in first byte */ + } while (x > mfb); /* still needs continuation byte? */ + buff[UTF8BUFFSZ - n] = cast_char((~mfb << 1) | x); /* add first byte */ + } + return n; +} + + +/* +** Maximum length of the conversion of a number to a string. Must be +** enough to accommodate both LUA_INTEGER_FMT and LUA_NUMBER_FMT. +** (For a long long int, this is 19 digits plus a sign and a final '\0', +** adding to 21. For a long double, it can go to a sign, 33 digits, +** the dot, an exponent letter, an exponent sign, 5 exponent digits, +** and a final '\0', adding to 43.) +*/ +#define MAXNUMBER2STR 44 + + +/* +** Convert a number object to a string, adding it to a buffer +*/ +static int tostringbuff (TValue *obj, char *buff) { + int len; + lua_assert(ttisnumber(obj)); + if (ttisinteger(obj)) + len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj)); + else { + len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj)); + if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */ + buff[len++] = lua_getlocaledecpoint(); + buff[len++] = '0'; /* adds '.0' to result */ + } + } + return len; +} + + +/* +** Convert a number object to a Lua string, replacing the value at 'obj' +*/ +void luaO_tostring (lua_State *L, TValue *obj) { + char buff[MAXNUMBER2STR]; + int len = tostringbuff(obj, buff); + setsvalue(L, obj, luaS_newlstr(L, buff, len)); +} + + + + +/* +** {================================================================== +** 'luaO_pushvfstring' +** =================================================================== +*/ + +/* size for buffer space used by 'luaO_pushvfstring' */ +#define BUFVFS 200 + +/* buffer used by 'luaO_pushvfstring' */ +typedef struct BuffFS { + lua_State *L; + int pushed; /* number of string pieces already on the stack */ + int blen; /* length of partial string in 'space' */ + char space[BUFVFS]; /* holds last part of the result */ +} BuffFS; + + +/* +** Push given string to the stack, as part of the buffer, and +** join the partial strings in the stack into one. +*/ +static void pushstr (BuffFS *buff, const char *str, size_t l) { + lua_State *L = buff->L; + setsvalue2s(L, L->top, luaS_newlstr(L, str, l)); + L->top++; /* may use one extra slot */ + buff->pushed++; + luaV_concat(L, buff->pushed); /* join partial results into one */ + buff->pushed = 1; +} + + +/* +** empty the buffer space into the stack +*/ +static void clearbuff (BuffFS *buff) { + pushstr(buff, buff->space, buff->blen); /* push buffer contents */ + buff->blen = 0; /* space now is empty */ +} + + +/* +** Get a space of size 'sz' in the buffer. If buffer has not enough +** space, empty it. 'sz' must fit in an empty buffer. +*/ +static char *getbuff (BuffFS *buff, int sz) { + lua_assert(buff->blen <= BUFVFS); lua_assert(sz <= BUFVFS); + if (sz > BUFVFS - buff->blen) /* not enough space? */ + clearbuff(buff); + return buff->space + buff->blen; +} + + +#define addsize(b,sz) ((b)->blen += (sz)) + + +/* +** Add 'str' to the buffer. If string is larger than the buffer space, +** push the string directly to the stack. +*/ +static void addstr2buff (BuffFS *buff, const char *str, size_t slen) { + if (slen <= BUFVFS) { /* does string fit into buffer? */ + char *bf = getbuff(buff, cast_int(slen)); + memcpy(bf, str, slen); /* add string to buffer */ + addsize(buff, cast_int(slen)); + } + else { /* string larger than buffer */ + clearbuff(buff); /* string comes after buffer's content */ + pushstr(buff, str, slen); /* push string */ + } +} + + +/* +** Add a number to the buffer. +*/ +static void addnum2buff (BuffFS *buff, TValue *num) { + char *numbuff = getbuff(buff, MAXNUMBER2STR); + int len = tostringbuff(num, numbuff); /* format number into 'numbuff' */ + addsize(buff, len); +} + + +/* +** this function handles only '%d', '%c', '%f', '%p', '%s', and '%%' + conventional formats, plus Lua-specific '%I' and '%U' +*/ +const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { + BuffFS buff; /* holds last part of the result */ + const char *e; /* points to next '%' */ + buff.pushed = buff.blen = 0; + buff.L = L; + while ((e = strchr(fmt, '%')) != NULL) { + addstr2buff(&buff, fmt, e - fmt); /* add 'fmt' up to '%' */ + switch (*(e + 1)) { /* conversion specifier */ + case 's': { /* zero-terminated string */ + const char *s = va_arg(argp, char *); + if (s == NULL) s = "(null)"; + addstr2buff(&buff, s, strlen(s)); + break; + } + case 'c': { /* an 'int' as a character */ + char c = cast_uchar(va_arg(argp, int)); + addstr2buff(&buff, &c, sizeof(char)); + break; + } + case 'd': { /* an 'int' */ + TValue num; + setivalue(&num, va_arg(argp, int)); + addnum2buff(&buff, &num); + break; + } + case 'I': { /* a 'lua_Integer' */ + TValue num; + setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt))); + addnum2buff(&buff, &num); + break; + } + case 'f': { /* a 'lua_Number' */ + TValue num; + setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber))); + addnum2buff(&buff, &num); + break; + } + case 'p': { /* a pointer */ + const int sz = 3 * sizeof(void*) + 8; /* enough space for '%p' */ + char *bf = getbuff(&buff, sz); + void *p = va_arg(argp, void *); + int len = lua_pointer2str(bf, sz, p); + addsize(&buff, len); + break; + } + case 'U': { /* a 'long' as a UTF-8 sequence */ + char bf[UTF8BUFFSZ]; + int len = luaO_utf8esc(bf, va_arg(argp, long)); + addstr2buff(&buff, bf + UTF8BUFFSZ - len, len); + break; + } + case '%': { + addstr2buff(&buff, "%", 1); + break; + } + default: { + luaG_runerror(L, "invalid option '%%%c' to 'lua_pushfstring'", + *(e + 1)); + } + } + fmt = e + 2; /* skip '%' and the specifier */ + } + addstr2buff(&buff, fmt, strlen(fmt)); /* rest of 'fmt' */ + clearbuff(&buff); /* empty buffer into the stack */ + lua_assert(buff.pushed == 1); + return svalue(s2v(L->top - 1)); +} + + +const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) { + const char *msg; + va_list argp; + va_start(argp, fmt); + msg = luaO_pushvfstring(L, fmt, argp); + va_end(argp); + return msg; +} + +/* }================================================================== */ + + +#define RETS "..." +#define PRE "[string \"" +#define POS "\"]" + +#define addstr(a,b,l) ( memcpy(a,b,(l) * sizeof(char)), a += (l) ) + +void luaO_chunkid (char *out, const char *source, size_t srclen) { + size_t bufflen = LUA_IDSIZE; /* free space in buffer */ + if (*source == '=') { /* 'literal' source */ + if (srclen <= bufflen) /* small enough? */ + memcpy(out, source + 1, srclen * sizeof(char)); + else { /* truncate it */ + addstr(out, source + 1, bufflen - 1); + *out = '\0'; + } + } + else if (*source == '@') { /* file name */ + if (srclen <= bufflen) /* small enough? */ + memcpy(out, source + 1, srclen * sizeof(char)); + else { /* add '...' before rest of name */ + addstr(out, RETS, LL(RETS)); + bufflen -= LL(RETS); + memcpy(out, source + 1 + srclen - bufflen, bufflen * sizeof(char)); + } + } + else { /* string; format as [string "source"] */ + const char *nl = strchr(source, '\n'); /* find first new line (if any) */ + addstr(out, PRE, LL(PRE)); /* add prefix */ + bufflen -= LL(PRE RETS POS) + 1; /* save space for prefix+suffix+'\0' */ + if (srclen < bufflen && nl == NULL) { /* small one-line source? */ + addstr(out, source, srclen); /* keep it */ + } + else { + if (nl != NULL) srclen = nl - source; /* stop at first newline */ + if (srclen > bufflen) srclen = bufflen; + addstr(out, source, srclen); + addstr(out, RETS, LL(RETS)); + } + memcpy(out, POS, (LL(POS) + 1) * sizeof(char)); + } +} + diff --git a/Lua/lobject.h b/Lua/lobject.h new file mode 100644 index 00000000..1cc8e757 --- /dev/null +++ b/Lua/lobject.h @@ -0,0 +1,790 @@ +/* +** $Id: lobject.h $ +** Type definitions for Lua objects +** See Copyright Notice in lua.h +*/ + + +#ifndef lobject_h +#define lobject_h + + +#include + + +#include "llimits.h" +#include "lua.h" + + +/* +** Extra types for collectable non-values +*/ +#define LUA_TUPVAL LUA_NUMTYPES /* upvalues */ +#define LUA_TPROTO (LUA_NUMTYPES+1) /* function prototypes */ +#define LUA_TDEADKEY (LUA_NUMTYPES+2) /* removed keys in tables */ + + + +/* +** number of all possible types (including LUA_TNONE but excluding DEADKEY) +*/ +#define LUA_TOTALTYPES (LUA_TPROTO + 2) + + +/* +** tags for Tagged Values have the following use of bits: +** bits 0-3: actual tag (a LUA_T* constant) +** bits 4-5: variant bits +** bit 6: whether value is collectable +*/ + +/* add variant bits to a type */ +#define makevariant(t,v) ((t) | ((v) << 4)) + + + +/* +** Union of all Lua values +*/ +typedef union Value { + struct GCObject *gc; /* collectable objects */ + void *p; /* light userdata */ + lua_CFunction f; /* light C functions */ + lua_Integer i; /* integer numbers */ + lua_Number n; /* float numbers */ +} Value; + + +/* +** Tagged Values. This is the basic representation of values in Lua: +** an actual value plus a tag with its type. +*/ + +#define TValuefields Value value_; lu_byte tt_ + +typedef struct TValue { + TValuefields; +} TValue; + + +#define val_(o) ((o)->value_) +#define valraw(o) (&val_(o)) + + +/* raw type tag of a TValue */ +#define rawtt(o) ((o)->tt_) + +/* tag with no variants (bits 0-3) */ +#define novariant(t) ((t) & 0x0F) + +/* type tag of a TValue (bits 0-3 for tags + variant bits 4-5) */ +#define withvariant(t) ((t) & 0x3F) +#define ttypetag(o) withvariant(rawtt(o)) + +/* type of a TValue */ +#define ttype(o) (novariant(rawtt(o))) + + +/* Macros to test type */ +#define checktag(o,t) (rawtt(o) == (t)) +#define checktype(o,t) (ttype(o) == (t)) + + +/* Macros for internal tests */ + +/* collectable object has the same tag as the original value */ +#define righttt(obj) (ttypetag(obj) == gcvalue(obj)->tt) + +/* +** Any value being manipulated by the program either is non +** collectable, or the collectable object has the right tag +** and it is not dead. The option 'L == NULL' allows other +** macros using this one to be used where L is not available. +*/ +#define checkliveness(L,obj) \ + ((void)L, lua_longassert(!iscollectable(obj) || \ + (righttt(obj) && (L == NULL || !isdead(G(L),gcvalue(obj)))))) + + +/* Macros to set values */ + +/* set a value's tag */ +#define settt_(o,t) ((o)->tt_=(t)) + + +/* main macro to copy values (from 'obj1' to 'obj2') */ +#define setobj(L,obj1,obj2) \ + { TValue *io1=(obj1); const TValue *io2=(obj2); \ + io1->value_ = io2->value_; settt_(io1, io2->tt_); \ + checkliveness(L,io1); lua_assert(!isnonstrictnil(io1)); } + +/* +** Different types of assignments, according to source and destination. +** (They are mostly equal now, but may be different in the future.) +*/ + +/* from stack to stack */ +#define setobjs2s(L,o1,o2) setobj(L,s2v(o1),s2v(o2)) +/* to stack (not from same stack) */ +#define setobj2s(L,o1,o2) setobj(L,s2v(o1),o2) +/* from table to same table */ +#define setobjt2t setobj +/* to new object */ +#define setobj2n setobj +/* to table */ +#define setobj2t setobj + + +/* +** Entries in the Lua stack +*/ +typedef union StackValue { + TValue val; +} StackValue; + + +/* index to stack elements */ +typedef StackValue *StkId; + +/* convert a 'StackValue' to a 'TValue' */ +#define s2v(o) (&(o)->val) + + + +/* +** {================================================================== +** Nil +** =================================================================== +*/ + +/* Standard nil */ +#define LUA_VNIL makevariant(LUA_TNIL, 0) + +/* Empty slot (which might be different from a slot containing nil) */ +#define LUA_VEMPTY makevariant(LUA_TNIL, 1) + +/* Value returned for a key not found in a table (absent key) */ +#define LUA_VABSTKEY makevariant(LUA_TNIL, 2) + + +/* macro to test for (any kind of) nil */ +#define ttisnil(v) checktype((v), LUA_TNIL) + + +/* macro to test for a standard nil */ +#define ttisstrictnil(o) checktag((o), LUA_VNIL) + + +#define setnilvalue(obj) settt_(obj, LUA_VNIL) + + +#define isabstkey(v) checktag((v), LUA_VABSTKEY) + + +/* +** macro to detect non-standard nils (used only in assertions) +*/ +#define isnonstrictnil(v) (ttisnil(v) && !ttisstrictnil(v)) + + +/* +** By default, entries with any kind of nil are considered empty. +** (In any definition, values associated with absent keys must also +** be accepted as empty.) +*/ +#define isempty(v) ttisnil(v) + + +/* macro defining a value corresponding to an absent key */ +#define ABSTKEYCONSTANT {NULL}, LUA_VABSTKEY + + +/* mark an entry as empty */ +#define setempty(v) settt_(v, LUA_VEMPTY) + + + +/* }================================================================== */ + + +/* +** {================================================================== +** Booleans +** =================================================================== +*/ + + +#define LUA_VFALSE makevariant(LUA_TBOOLEAN, 0) +#define LUA_VTRUE makevariant(LUA_TBOOLEAN, 1) + +#define ttisboolean(o) checktype((o), LUA_TBOOLEAN) +#define ttisfalse(o) checktag((o), LUA_VFALSE) +#define ttistrue(o) checktag((o), LUA_VTRUE) + + +#define l_isfalse(o) (ttisfalse(o) || ttisnil(o)) + + +#define setbfvalue(obj) settt_(obj, LUA_VFALSE) +#define setbtvalue(obj) settt_(obj, LUA_VTRUE) + +/* }================================================================== */ + + +/* +** {================================================================== +** Threads +** =================================================================== +*/ + +#define LUA_VTHREAD makevariant(LUA_TTHREAD, 0) + +#define ttisthread(o) checktag((o), ctb(LUA_VTHREAD)) + +#define thvalue(o) check_exp(ttisthread(o), gco2th(val_(o).gc)) + +#define setthvalue(L,obj,x) \ + { TValue *io = (obj); lua_State *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTHREAD)); \ + checkliveness(L,io); } + +#define setthvalue2s(L,o,t) setthvalue(L,s2v(o),t) + +/* }================================================================== */ + + +/* +** {================================================================== +** Collectable Objects +** =================================================================== +*/ + +/* +** Common Header for all collectable objects (in macro form, to be +** included in other objects) +*/ +#define CommonHeader struct GCObject *next; lu_byte tt; lu_byte marked + + +/* Common type for all collectable objects */ +typedef struct GCObject { + CommonHeader; +} GCObject; + + +/* Bit mark for collectable types */ +#define BIT_ISCOLLECTABLE (1 << 6) + +#define iscollectable(o) (rawtt(o) & BIT_ISCOLLECTABLE) + +/* mark a tag as collectable */ +#define ctb(t) ((t) | BIT_ISCOLLECTABLE) + +#define gcvalue(o) check_exp(iscollectable(o), val_(o).gc) + +#define gcvalueraw(v) ((v).gc) + +#define setgcovalue(L,obj,x) \ + { TValue *io = (obj); GCObject *i_g=(x); \ + val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); } + +/* }================================================================== */ + + +/* +** {================================================================== +** Numbers +** =================================================================== +*/ + +/* Variant tags for numbers */ +#define LUA_VNUMINT makevariant(LUA_TNUMBER, 0) /* integer numbers */ +#define LUA_VNUMFLT makevariant(LUA_TNUMBER, 1) /* float numbers */ + +#define ttisnumber(o) checktype((o), LUA_TNUMBER) +#define ttisfloat(o) checktag((o), LUA_VNUMFLT) +#define ttisinteger(o) checktag((o), LUA_VNUMINT) + +#define nvalue(o) check_exp(ttisnumber(o), \ + (ttisinteger(o) ? cast_num(ivalue(o)) : fltvalue(o))) +#define fltvalue(o) check_exp(ttisfloat(o), val_(o).n) +#define ivalue(o) check_exp(ttisinteger(o), val_(o).i) + +#define fltvalueraw(v) ((v).n) +#define ivalueraw(v) ((v).i) + +#define setfltvalue(obj,x) \ + { TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_VNUMFLT); } + +#define chgfltvalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); } + +#define setivalue(obj,x) \ + { TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_VNUMINT); } + +#define chgivalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); } + +/* }================================================================== */ + + +/* +** {================================================================== +** Strings +** =================================================================== +*/ + +/* Variant tags for strings */ +#define LUA_VSHRSTR makevariant(LUA_TSTRING, 0) /* short strings */ +#define LUA_VLNGSTR makevariant(LUA_TSTRING, 1) /* long strings */ + +#define ttisstring(o) checktype((o), LUA_TSTRING) +#define ttisshrstring(o) checktag((o), ctb(LUA_VSHRSTR)) +#define ttislngstring(o) checktag((o), ctb(LUA_VLNGSTR)) + +#define tsvalueraw(v) (gco2ts((v).gc)) + +#define tsvalue(o) check_exp(ttisstring(o), gco2ts(val_(o).gc)) + +#define setsvalue(L,obj,x) \ + { TValue *io = (obj); TString *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \ + checkliveness(L,io); } + +/* set a string to the stack */ +#define setsvalue2s(L,o,s) setsvalue(L,s2v(o),s) + +/* set a string to a new object */ +#define setsvalue2n setsvalue + + +/* +** Header for a string value. +*/ +typedef struct TString { + CommonHeader; + lu_byte extra; /* reserved words for short strings; "has hash" for longs */ + lu_byte shrlen; /* length for short strings */ + unsigned int hash; + union { + size_t lnglen; /* length for long strings */ + struct TString *hnext; /* linked list for hash table */ + } u; + char contents[1]; +} TString; + + + +/* +** Get the actual string (array of bytes) from a 'TString'. +*/ +#define getstr(ts) ((ts)->contents) + + +/* get the actual string (array of bytes) from a Lua value */ +#define svalue(o) getstr(tsvalue(o)) + +/* get string length from 'TString *s' */ +#define tsslen(s) ((s)->tt == LUA_VSHRSTR ? (s)->shrlen : (s)->u.lnglen) + +/* get string length from 'TValue *o' */ +#define vslen(o) tsslen(tsvalue(o)) + +/* }================================================================== */ + + +/* +** {================================================================== +** Userdata +** =================================================================== +*/ + + +/* +** Light userdata should be a variant of userdata, but for compatibility +** reasons they are also different types. +*/ +#define LUA_VLIGHTUSERDATA makevariant(LUA_TLIGHTUSERDATA, 0) + +#define LUA_VUSERDATA makevariant(LUA_TUSERDATA, 0) + +#define ttislightuserdata(o) checktag((o), LUA_VLIGHTUSERDATA) +#define ttisfulluserdata(o) checktag((o), ctb(LUA_VUSERDATA)) + +#define pvalue(o) check_exp(ttislightuserdata(o), val_(o).p) +#define uvalue(o) check_exp(ttisfulluserdata(o), gco2u(val_(o).gc)) + +#define pvalueraw(v) ((v).p) + +#define setpvalue(obj,x) \ + { TValue *io=(obj); val_(io).p=(x); settt_(io, LUA_VLIGHTUSERDATA); } + +#define setuvalue(L,obj,x) \ + { TValue *io = (obj); Udata *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VUSERDATA)); \ + checkliveness(L,io); } + + +/* Ensures that addresses after this type are always fully aligned. */ +typedef union UValue { + TValue uv; + LUAI_MAXALIGN; /* ensures maximum alignment for udata bytes */ +} UValue; + + +/* +** Header for userdata with user values; +** memory area follows the end of this structure. +*/ +typedef struct Udata { + CommonHeader; + unsigned short nuvalue; /* number of user values */ + size_t len; /* number of bytes */ + struct Table *metatable; + GCObject *gclist; + UValue uv[1]; /* user values */ +} Udata; + + +/* +** Header for userdata with no user values. These userdata do not need +** to be gray during GC, and therefore do not need a 'gclist' field. +** To simplify, the code always use 'Udata' for both kinds of userdata, +** making sure it never accesses 'gclist' on userdata with no user values. +** This structure here is used only to compute the correct size for +** this representation. (The 'bindata' field in its end ensures correct +** alignment for binary data following this header.) +*/ +typedef struct Udata0 { + CommonHeader; + unsigned short nuvalue; /* number of user values */ + size_t len; /* number of bytes */ + struct Table *metatable; + union {LUAI_MAXALIGN;} bindata; +} Udata0; + + +/* compute the offset of the memory area of a userdata */ +#define udatamemoffset(nuv) \ + ((nuv) == 0 ? offsetof(Udata0, bindata) \ + : offsetof(Udata, uv) + (sizeof(UValue) * (nuv))) + +/* get the address of the memory block inside 'Udata' */ +#define getudatamem(u) (cast_charp(u) + udatamemoffset((u)->nuvalue)) + +/* compute the size of a userdata */ +#define sizeudata(nuv,nb) (udatamemoffset(nuv) + (nb)) + +/* }================================================================== */ + + +/* +** {================================================================== +** Prototypes +** =================================================================== +*/ + +#define LUA_VPROTO makevariant(LUA_TPROTO, 0) + + +/* +** Description of an upvalue for function prototypes +*/ +typedef struct Upvaldesc { + TString *name; /* upvalue name (for debug information) */ + lu_byte instack; /* whether it is in stack (register) */ + lu_byte idx; /* index of upvalue (in stack or in outer function's list) */ + lu_byte kind; /* kind of corresponding variable */ +} Upvaldesc; + + +/* +** Description of a local variable for function prototypes +** (used for debug information) +*/ +typedef struct LocVar { + TString *varname; + int startpc; /* first point where variable is active */ + int endpc; /* first point where variable is dead */ +} LocVar; + + +/* +** Associates the absolute line source for a given instruction ('pc'). +** The array 'lineinfo' gives, for each instruction, the difference in +** lines from the previous instruction. When that difference does not +** fit into a byte, Lua saves the absolute line for that instruction. +** (Lua also saves the absolute line periodically, to speed up the +** computation of a line number: we can use binary search in the +** absolute-line array, but we must traverse the 'lineinfo' array +** linearly to compute a line.) +*/ +typedef struct AbsLineInfo { + int pc; + int line; +} AbsLineInfo; + +/* +** Function Prototypes +*/ +typedef struct Proto { + CommonHeader; + lu_byte numparams; /* number of fixed (named) parameters */ + lu_byte is_vararg; + lu_byte maxstacksize; /* number of registers needed by this function */ + int sizeupvalues; /* size of 'upvalues' */ + int sizek; /* size of 'k' */ + int sizecode; + int sizelineinfo; + int sizep; /* size of 'p' */ + int sizelocvars; + int sizeabslineinfo; /* size of 'abslineinfo' */ + int linedefined; /* debug information */ + int lastlinedefined; /* debug information */ + TValue *k; /* constants used by the function */ + Instruction *code; /* opcodes */ + struct Proto **p; /* functions defined inside the function */ + Upvaldesc *upvalues; /* upvalue information */ + ls_byte *lineinfo; /* information about source lines (debug information) */ + AbsLineInfo *abslineinfo; /* idem */ + LocVar *locvars; /* information about local variables (debug information) */ + TString *source; /* used for debug information */ + GCObject *gclist; +} Proto; + +/* }================================================================== */ + + +/* +** {================================================================== +** Functions +** =================================================================== +*/ + +#define LUA_VUPVAL makevariant(LUA_TUPVAL, 0) + + +/* Variant tags for functions */ +#define LUA_VLCL makevariant(LUA_TFUNCTION, 0) /* Lua closure */ +#define LUA_VLCF makevariant(LUA_TFUNCTION, 1) /* light C function */ +#define LUA_VCCL makevariant(LUA_TFUNCTION, 2) /* C closure */ + +#define ttisfunction(o) checktype(o, LUA_TFUNCTION) +#define ttisclosure(o) ((rawtt(o) & 0x1F) == LUA_VLCL) +#define ttisLclosure(o) checktag((o), ctb(LUA_VLCL)) +#define ttislcf(o) checktag((o), LUA_VLCF) +#define ttisCclosure(o) checktag((o), ctb(LUA_VCCL)) + +#define isLfunction(o) ttisLclosure(o) + +#define clvalue(o) check_exp(ttisclosure(o), gco2cl(val_(o).gc)) +#define clLvalue(o) check_exp(ttisLclosure(o), gco2lcl(val_(o).gc)) +#define fvalue(o) check_exp(ttislcf(o), val_(o).f) +#define clCvalue(o) check_exp(ttisCclosure(o), gco2ccl(val_(o).gc)) + +#define fvalueraw(v) ((v).f) + +#define setclLvalue(L,obj,x) \ + { TValue *io = (obj); LClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VLCL)); \ + checkliveness(L,io); } + +#define setclLvalue2s(L,o,cl) setclLvalue(L,s2v(o),cl) + +#define setfvalue(obj,x) \ + { TValue *io=(obj); val_(io).f=(x); settt_(io, LUA_VLCF); } + +#define setclCvalue(L,obj,x) \ + { TValue *io = (obj); CClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VCCL)); \ + checkliveness(L,io); } + + +/* +** Upvalues for Lua closures +*/ +typedef struct UpVal { + CommonHeader; + lu_byte tbc; /* true if it represents a to-be-closed variable */ + TValue *v; /* points to stack or to its own value */ + union { + struct { /* (when open) */ + struct UpVal *next; /* linked list */ + struct UpVal **previous; + } open; + TValue value; /* the value (when closed) */ + } u; +} UpVal; + + + +#define ClosureHeader \ + CommonHeader; lu_byte nupvalues; GCObject *gclist + +typedef struct CClosure { + ClosureHeader; + lua_CFunction f; + TValue upvalue[1]; /* list of upvalues */ +} CClosure; + + +typedef struct LClosure { + ClosureHeader; + struct Proto *p; + UpVal *upvals[1]; /* list of upvalues */ +} LClosure; + + +typedef union Closure { + CClosure c; + LClosure l; +} Closure; + + +#define getproto(o) (clLvalue(o)->p) + +/* }================================================================== */ + + +/* +** {================================================================== +** Tables +** =================================================================== +*/ + +#define LUA_VTABLE makevariant(LUA_TTABLE, 0) + +#define ttistable(o) checktag((o), ctb(LUA_VTABLE)) + +#define hvalue(o) check_exp(ttistable(o), gco2t(val_(o).gc)) + +#define sethvalue(L,obj,x) \ + { TValue *io = (obj); Table *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTABLE)); \ + checkliveness(L,io); } + +#define sethvalue2s(L,o,h) sethvalue(L,s2v(o),h) + + +/* +** Nodes for Hash tables: A pack of two TValue's (key-value pairs) +** plus a 'next' field to link colliding entries. The distribution +** of the key's fields ('key_tt' and 'key_val') not forming a proper +** 'TValue' allows for a smaller size for 'Node' both in 4-byte +** and 8-byte alignments. +*/ +typedef union Node { + struct NodeKey { + TValuefields; /* fields for value */ + lu_byte key_tt; /* key type */ + int next; /* for chaining */ + Value key_val; /* key value */ + } u; + TValue i_val; /* direct access to node's value as a proper 'TValue' */ +} Node; + + +/* copy a value into a key */ +#define setnodekey(L,node,obj) \ + { Node *n_=(node); const TValue *io_=(obj); \ + n_->u.key_val = io_->value_; n_->u.key_tt = io_->tt_; \ + checkliveness(L,io_); } + + +/* copy a value from a key */ +#define getnodekey(L,obj,node) \ + { TValue *io_=(obj); const Node *n_=(node); \ + io_->value_ = n_->u.key_val; io_->tt_ = n_->u.key_tt; \ + checkliveness(L,io_); } + + +/* +** About 'alimit': if 'isrealasize(t)' is true, then 'alimit' is the +** real size of 'array'. Otherwise, the real size of 'array' is the +** smallest power of two not smaller than 'alimit' (or zero iff 'alimit' +** is zero); 'alimit' is then used as a hint for #t. +*/ + +#define BITRAS (1 << 7) +#define isrealasize(t) (!((t)->flags & BITRAS)) +#define setrealasize(t) ((t)->flags &= cast_byte(~BITRAS)) +#define setnorealasize(t) ((t)->flags |= BITRAS) + + +typedef struct Table { + CommonHeader; + lu_byte flags; /* 1<

u.key_tt) +#define keyval(node) ((node)->u.key_val) + +#define keyisnil(node) (keytt(node) == LUA_TNIL) +#define keyisinteger(node) (keytt(node) == LUA_VNUMINT) +#define keyival(node) (keyval(node).i) +#define keyisshrstr(node) (keytt(node) == ctb(LUA_VSHRSTR)) +#define keystrval(node) (gco2ts(keyval(node).gc)) + +#define setnilkey(node) (keytt(node) = LUA_TNIL) + +#define keyiscollectable(n) (keytt(n) & BIT_ISCOLLECTABLE) + +#define gckey(n) (keyval(n).gc) +#define gckeyN(n) (keyiscollectable(n) ? gckey(n) : NULL) + + +/* +** Dead keys in tables have the tag DEADKEY but keep their original +** gcvalue. This distinguishes them from regular keys but allows them to +** be found when searched in a special way. ('next' needs that to find +** keys removed from a table during a traversal.) +*/ +#define setdeadkey(node) (keytt(node) = LUA_TDEADKEY) +#define keyisdead(node) (keytt(node) == LUA_TDEADKEY) + +/* }================================================================== */ + + + +/* +** 'module' operation for hashing (size is always a power of 2) +*/ +#define lmod(s,size) \ + (check_exp((size&(size-1))==0, (cast_int((s) & ((size)-1))))) + + +#define twoto(x) (1<<(x)) +#define sizenode(t) (twoto((t)->lsizenode)) + + +/* size of buffer for 'luaO_utf8esc' function */ +#define UTF8BUFFSZ 8 + +LUAI_FUNC int luaO_utf8esc (char *buff, unsigned long x); +LUAI_FUNC int luaO_ceillog2 (unsigned int x); +LUAI_FUNC int luaO_rawarith (lua_State *L, int op, const TValue *p1, + const TValue *p2, TValue *res); +LUAI_FUNC void luaO_arith (lua_State *L, int op, const TValue *p1, + const TValue *p2, StkId res); +LUAI_FUNC size_t luaO_str2num (const char *s, TValue *o); +LUAI_FUNC int luaO_hexavalue (int c); +LUAI_FUNC void luaO_tostring (lua_State *L, TValue *obj); +LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt, + va_list argp); +LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...); +LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t srclen); + + +#endif + diff --git a/Lua/lopcodes.c b/Lua/lopcodes.c new file mode 100644 index 00000000..c67aa227 --- /dev/null +++ b/Lua/lopcodes.c @@ -0,0 +1,104 @@ +/* +** $Id: lopcodes.c $ +** Opcodes for Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#define lopcodes_c +#define LUA_CORE + +#include "lprefix.h" + + +#include "lopcodes.h" + + +/* ORDER OP */ + +LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { +/* MM OT IT T A mode opcode */ + opmode(0, 0, 0, 0, 1, iABC) /* OP_MOVE */ + ,opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADI */ + ,opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADF */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_LOADK */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_LOADKX */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADFALSE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LFALSESKIP */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADTRUE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADNIL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETUPVAL */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETUPVAL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETTABUP */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETTABLE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETFIELD */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETTABUP */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETTABLE */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETI */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETFIELD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_NEWTABLE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SELF */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADDI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADDK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUBK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MULK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MODK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_POWK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_DIVK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_IDIVK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BANDK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BORK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXORK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHRI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHLI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUB */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MUL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MOD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_POW */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_DIV */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_IDIV */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BAND */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BOR */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXOR */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHR */ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBIN */ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBINI*/ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBINK*/ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_UNM */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BNOT */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_NOT */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LEN */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_CONCAT */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_CLOSE */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_TBC */ + ,opmode(0, 0, 0, 0, 0, isJ) /* OP_JMP */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQ */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LT */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LE */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQK */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LTI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LEI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_GTI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_GEI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_TEST */ + ,opmode(0, 0, 0, 1, 1, iABC) /* OP_TESTSET */ + ,opmode(0, 1, 1, 0, 1, iABC) /* OP_CALL */ + ,opmode(0, 1, 1, 0, 1, iABC) /* OP_TAILCALL */ + ,opmode(0, 0, 1, 0, 0, iABC) /* OP_RETURN */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_RETURN0 */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_RETURN1 */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_FORLOOP */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_FORPREP */ + ,opmode(0, 0, 0, 0, 0, iABx) /* OP_TFORPREP */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_TFORCALL */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_TFORLOOP */ + ,opmode(0, 0, 1, 0, 0, iABC) /* OP_SETLIST */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */ + ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ + ,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */ + ,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */ +}; + diff --git a/Lua/lopcodes.h b/Lua/lopcodes.h new file mode 100644 index 00000000..120cdd94 --- /dev/null +++ b/Lua/lopcodes.h @@ -0,0 +1,392 @@ +/* +** $Id: lopcodes.h $ +** Opcodes for Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lopcodes_h +#define lopcodes_h + +#include "llimits.h" + + +/*=========================================================================== + We assume that instructions are unsigned 32-bit integers. + All instructions have an opcode in the first 7 bits. + Instructions can have the following formats: + + 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 + 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +iABC C(8) | B(8) |k| A(8) | Op(7) | +iABx Bx(17) | A(8) | Op(7) | +iAsBx sBx (signed)(17) | A(8) | Op(7) | +iAx Ax(25) | Op(7) | +isJ sJ(25) | Op(7) | + + A signed argument is represented in excess K: the represented value is + the written unsigned value minus K, where K is half the maximum for the + corresponding unsigned argument. +===========================================================================*/ + + +enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */ + + +/* +** size and position of opcode arguments. +*/ +#define SIZE_C 8 +#define SIZE_B 8 +#define SIZE_Bx (SIZE_C + SIZE_B + 1) +#define SIZE_A 8 +#define SIZE_Ax (SIZE_Bx + SIZE_A) +#define SIZE_sJ (SIZE_Bx + SIZE_A) + +#define SIZE_OP 7 + +#define POS_OP 0 + +#define POS_A (POS_OP + SIZE_OP) +#define POS_k (POS_A + SIZE_A) +#define POS_B (POS_k + 1) +#define POS_C (POS_B + SIZE_B) + +#define POS_Bx POS_k + +#define POS_Ax POS_A + +#define POS_sJ POS_A + + +/* +** limits for opcode arguments. +** we use (signed) 'int' to manipulate most arguments, +** so they must fit in ints. +*/ + +/* Check whether type 'int' has at least 'b' bits ('b' < 32) */ +#define L_INTHASBITS(b) ((UINT_MAX >> ((b) - 1)) >= 1) + + +#if L_INTHASBITS(SIZE_Bx) +#define MAXARG_Bx ((1<>1) /* 'sBx' is signed */ + + +#if L_INTHASBITS(SIZE_Ax) +#define MAXARG_Ax ((1<> 1) + + +#define MAXARG_A ((1<> 1) + +#define int2sC(i) ((i) + OFFSET_sC) +#define sC2int(i) ((i) - OFFSET_sC) + + +/* creates a mask with 'n' 1 bits at position 'p' */ +#define MASK1(n,p) ((~((~(Instruction)0)<<(n)))<<(p)) + +/* creates a mask with 'n' 0 bits at position 'p' */ +#define MASK0(n,p) (~MASK1(n,p)) + +/* +** the following macros help to manipulate instructions +*/ + +#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))) +#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ + ((cast(Instruction, o)<>(pos)) & MASK1(size,0))) +#define setarg(i,v,pos,size) ((i) = (((i)&MASK0(size,pos)) | \ + ((cast(Instruction, v)<> sC */ +OP_SHLI,/* A B sC R[A] := sC << R[B] */ + +OP_ADD,/* A B C R[A] := R[B] + R[C] */ +OP_SUB,/* A B C R[A] := R[B] - R[C] */ +OP_MUL,/* A B C R[A] := R[B] * R[C] */ +OP_MOD,/* A B C R[A] := R[B] % R[C] */ +OP_POW,/* A B C R[A] := R[B] ^ R[C] */ +OP_DIV,/* A B C R[A] := R[B] / R[C] */ +OP_IDIV,/* A B C R[A] := R[B] // R[C] */ + +OP_BAND,/* A B C R[A] := R[B] & R[C] */ +OP_BOR,/* A B C R[A] := R[B] | R[C] */ +OP_BXOR,/* A B C R[A] := R[B] ~ R[C] */ +OP_SHL,/* A B C R[A] := R[B] << R[C] */ +OP_SHR,/* A B C R[A] := R[B] >> R[C] */ + +OP_MMBIN,/* A B C call C metamethod over R[A] and R[B] */ +OP_MMBINI,/* A sB C k call C metamethod over R[A] and sB */ +OP_MMBINK,/* A B C k call C metamethod over R[A] and K[B] */ + +OP_UNM,/* A B R[A] := -R[B] */ +OP_BNOT,/* A B R[A] := ~R[B] */ +OP_NOT,/* A B R[A] := not R[B] */ +OP_LEN,/* A B R[A] := #R[B] (length operator) */ + +OP_CONCAT,/* A B R[A] := R[A].. ... ..R[A + B - 1] */ + +OP_CLOSE,/* A close all upvalues >= R[A] */ +OP_TBC,/* A mark variable A "to be closed" */ +OP_JMP,/* sJ pc += sJ */ +OP_EQ,/* A B k if ((R[A] == R[B]) ~= k) then pc++ */ +OP_LT,/* A B k if ((R[A] < R[B]) ~= k) then pc++ */ +OP_LE,/* A B k if ((R[A] <= R[B]) ~= k) then pc++ */ + +OP_EQK,/* A B k if ((R[A] == K[B]) ~= k) then pc++ */ +OP_EQI,/* A sB k if ((R[A] == sB) ~= k) then pc++ */ +OP_LTI,/* A sB k if ((R[A] < sB) ~= k) then pc++ */ +OP_LEI,/* A sB k if ((R[A] <= sB) ~= k) then pc++ */ +OP_GTI,/* A sB k if ((R[A] > sB) ~= k) then pc++ */ +OP_GEI,/* A sB k if ((R[A] >= sB) ~= k) then pc++ */ + +OP_TEST,/* A k if (not R[A] == k) then pc++ */ +OP_TESTSET,/* A B k if (not R[B] == k) then pc++ else R[A] := R[B] */ + +OP_CALL,/* A B C R[A], ... ,R[A+C-2] := R[A](R[A+1], ... ,R[A+B-1]) */ +OP_TAILCALL,/* A B C k return R[A](R[A+1], ... ,R[A+B-1]) */ + +OP_RETURN,/* A B C k return R[A], ... ,R[A+B-2] (see note) */ +OP_RETURN0,/* return */ +OP_RETURN1,/* A return R[A] */ + +OP_FORLOOP,/* A Bx update counters; if loop continues then pc-=Bx; */ +OP_FORPREP,/* A Bx ; + if not to run then pc+=Bx+1; */ + +OP_TFORPREP,/* A Bx create upvalue for R[A + 3]; pc+=Bx */ +OP_TFORCALL,/* A C R[A+4], ... ,R[A+3+C] := R[A](R[A+1], R[A+2]); */ +OP_TFORLOOP,/* A Bx if R[A+2] ~= nil then { R[A]=R[A+2]; pc -= Bx } */ + +OP_SETLIST,/* A B C k R[A][C+i] := R[A+i], 1 <= i <= B */ + +OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */ + +OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */ + +OP_VARARGPREP,/*A (adjust vararg parameters) */ + +OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ +} OpCode; + + +#define NUM_OPCODES ((int)(OP_EXTRAARG) + 1) + + + +/*=========================================================================== + Notes: + (*) In OP_CALL, if (B == 0) then B = top - A. If (C == 0), then + 'top' is set to last_result+1, so next open instruction (OP_CALL, + OP_RETURN*, OP_SETLIST) may use 'top'. + + (*) In OP_VARARG, if (C == 0) then use actual number of varargs and + set top (like in OP_CALL with C == 0). + + (*) In OP_RETURN, if (B == 0) then return up to 'top'. + + (*) In OP_LOADKX and OP_NEWTABLE, the next instruction is always + OP_EXTRAARG. + + (*) In OP_SETLIST, if (B == 0) then real B = 'top'; if k, then + real C = EXTRAARG _ C (the bits of EXTRAARG concatenated with the + bits of C). + + (*) In OP_NEWTABLE, B is log2 of the hash size (which is always a + power of 2) plus 1, or zero for size zero. If not k, the array size + is C. Otherwise, the array size is EXTRAARG _ C. + + (*) For comparisons, k specifies what condition the test should accept + (true or false). + + (*) In OP_MMBINI/OP_MMBINK, k means the arguments were flipped + (the constant is the first operand). + + (*) All 'skips' (pc++) assume that next instruction is a jump. + + (*) In instructions OP_RETURN/OP_TAILCALL, 'k' specifies that the + function builds upvalues, which may need to be closed. C > 0 means + the function is vararg, so that its 'func' must be corrected before + returning; in this case, (C - 1) is its number of fixed parameters. + + (*) In comparisons with an immediate operand, C signals whether the + original operand was a float. (It must be corrected in case of + metamethods.) + +===========================================================================*/ + + +/* +** masks for instruction properties. The format is: +** bits 0-2: op mode +** bit 3: instruction set register A +** bit 4: operator is a test (next instruction must be a jump) +** bit 5: instruction uses 'L->top' set by previous instruction (when B == 0) +** bit 6: instruction sets 'L->top' for next instruction (when C == 0) +** bit 7: instruction is an MM instruction (call a metamethod) +*/ + +LUAI_DDEC(const lu_byte luaP_opmodes[NUM_OPCODES];) + +#define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 7)) +#define testAMode(m) (luaP_opmodes[m] & (1 << 3)) +#define testTMode(m) (luaP_opmodes[m] & (1 << 4)) +#define testITMode(m) (luaP_opmodes[m] & (1 << 5)) +#define testOTMode(m) (luaP_opmodes[m] & (1 << 6)) +#define testMMMode(m) (luaP_opmodes[m] & (1 << 7)) + +/* "out top" (set top for next instruction) */ +#define isOT(i) \ + ((testOTMode(GET_OPCODE(i)) && GETARG_C(i) == 0) || \ + GET_OPCODE(i) == OP_TAILCALL) + +/* "in top" (uses top from previous instruction) */ +#define isIT(i) (testITMode(GET_OPCODE(i)) && GETARG_B(i) == 0) + +#define opmode(mm,ot,it,t,a,m) \ + (((mm) << 7) | ((ot) << 6) | ((it) << 5) | ((t) << 4) | ((a) << 3) | (m)) + + +/* number of list items to accumulate before a SETLIST instruction */ +#define LFIELDS_PER_FLUSH 50 + +#endif diff --git a/Lua/lopnames.h b/Lua/lopnames.h new file mode 100644 index 00000000..965cec9b --- /dev/null +++ b/Lua/lopnames.h @@ -0,0 +1,103 @@ +/* +** $Id: lopnames.h $ +** Opcode names +** See Copyright Notice in lua.h +*/ + +#if !defined(lopnames_h) +#define lopnames_h + +#include + + +/* ORDER OP */ + +static const char *const opnames[] = { + "MOVE", + "LOADI", + "LOADF", + "LOADK", + "LOADKX", + "LOADFALSE", + "LFALSESKIP", + "LOADTRUE", + "LOADNIL", + "GETUPVAL", + "SETUPVAL", + "GETTABUP", + "GETTABLE", + "GETI", + "GETFIELD", + "SETTABUP", + "SETTABLE", + "SETI", + "SETFIELD", + "NEWTABLE", + "SELF", + "ADDI", + "ADDK", + "SUBK", + "MULK", + "MODK", + "POWK", + "DIVK", + "IDIVK", + "BANDK", + "BORK", + "BXORK", + "SHRI", + "SHLI", + "ADD", + "SUB", + "MUL", + "MOD", + "POW", + "DIV", + "IDIV", + "BAND", + "BOR", + "BXOR", + "SHL", + "SHR", + "MMBIN", + "MMBINI", + "MMBINK", + "UNM", + "BNOT", + "NOT", + "LEN", + "CONCAT", + "CLOSE", + "TBC", + "JMP", + "EQ", + "LT", + "LE", + "EQK", + "EQI", + "LTI", + "LEI", + "GTI", + "GEI", + "TEST", + "TESTSET", + "CALL", + "TAILCALL", + "RETURN", + "RETURN0", + "RETURN1", + "FORLOOP", + "FORPREP", + "TFORPREP", + "TFORCALL", + "TFORLOOP", + "SETLIST", + "CLOSURE", + "VARARG", + "VARARGPREP", + "EXTRAARG", + NULL +}; + +#endif + diff --git a/Lua/loslib.c b/Lua/loslib.c new file mode 100644 index 00000000..e65e188b --- /dev/null +++ b/Lua/loslib.c @@ -0,0 +1,430 @@ +/* +** $Id: loslib.c $ +** Standard Operating System library +** See Copyright Notice in lua.h +*/ + +#define loslib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** {================================================================== +** List of valid conversion specifiers for the 'strftime' function; +** options are grouped by length; group of length 2 start with '||'. +** =================================================================== +*/ +#if !defined(LUA_STRFTIMEOPTIONS) /* { */ + +/* options for ANSI C 89 (only 1-char options) */ +#define L_STRFTIMEC89 "aAbBcdHIjmMpSUwWxXyYZ%" + +/* options for ISO C 99 and POSIX */ +#define L_STRFTIMEC99 "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%" \ + "||" "EcECExEXEyEY" "OdOeOHOIOmOMOSOuOUOVOwOWOy" /* two-char options */ + +/* options for Windows */ +#define L_STRFTIMEWIN "aAbBcdHIjmMpSUwWxXyYzZ%" \ + "||" "#c#x#d#H#I#j#m#M#S#U#w#W#y#Y" /* two-char options */ + +#if defined(LUA_USE_WINDOWS) +#define LUA_STRFTIMEOPTIONS L_STRFTIMEWIN +#elif defined(LUA_USE_C89) +#define LUA_STRFTIMEOPTIONS L_STRFTIMEC89 +#else /* C99 specification */ +#define LUA_STRFTIMEOPTIONS L_STRFTIMEC99 +#endif + +#endif /* } */ +/* }================================================================== */ + + +/* +** {================================================================== +** Configuration for time-related stuff +** =================================================================== +*/ + +/* +** type to represent time_t in Lua +*/ +#if !defined(LUA_NUMTIME) /* { */ + +#define l_timet lua_Integer +#define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t)) +#define l_gettime(L,arg) luaL_checkinteger(L, arg) + +#else /* }{ */ + +#define l_timet lua_Number +#define l_pushtime(L,t) lua_pushnumber(L,(lua_Number)(t)) +#define l_gettime(L,arg) luaL_checknumber(L, arg) + +#endif /* } */ + + +#if !defined(l_gmtime) /* { */ +/* +** By default, Lua uses gmtime/localtime, except when POSIX is available, +** where it uses gmtime_r/localtime_r +*/ + +#if defined(LUA_USE_POSIX) /* { */ + +#define l_gmtime(t,r) gmtime_r(t,r) +#define l_localtime(t,r) localtime_r(t,r) + +#else /* }{ */ + +/* ISO C definitions */ +#define l_gmtime(t,r) ((void)(r)->tm_sec, gmtime(t)) +#define l_localtime(t,r) ((void)(r)->tm_sec, localtime(t)) + +#endif /* } */ + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Configuration for 'tmpnam': +** By default, Lua uses tmpnam except when POSIX is available, where +** it uses mkstemp. +** =================================================================== +*/ +#if !defined(lua_tmpnam) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#include + +#define LUA_TMPNAMBUFSIZE 32 + +#if !defined(LUA_TMPNAMTEMPLATE) +#define LUA_TMPNAMTEMPLATE "/tmp/lua_XXXXXX" +#endif + +#define lua_tmpnam(b,e) { \ + strcpy(b, LUA_TMPNAMTEMPLATE); \ + e = mkstemp(b); \ + if (e != -1) close(e); \ + e = (e == -1); } + +#else /* }{ */ + +/* ISO C definitions */ +#define LUA_TMPNAMBUFSIZE L_tmpnam +#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } + +#endif /* } */ + +#endif /* } */ +/* }================================================================== */ + + + +static int os_execute (lua_State *L) { + const char *cmd = luaL_optstring(L, 1, NULL); + int stat; + errno = 0; + stat = system(cmd); + if (cmd != NULL) + return luaL_execresult(L, stat); + else { + lua_pushboolean(L, stat); /* true if there is a shell */ + return 1; + } +} + + +static int os_remove (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + return luaL_fileresult(L, remove(filename) == 0, filename); +} + + +static int os_rename (lua_State *L) { + const char *fromname = luaL_checkstring(L, 1); + const char *toname = luaL_checkstring(L, 2); + return luaL_fileresult(L, rename(fromname, toname) == 0, NULL); +} + + +static int os_tmpname (lua_State *L) { + char buff[LUA_TMPNAMBUFSIZE]; + int err; + lua_tmpnam(buff, err); + if (err) + return luaL_error(L, "unable to generate a unique filename"); + lua_pushstring(L, buff); + return 1; +} + + +static int os_getenv (lua_State *L) { + lua_pushstring(L, getenv(luaL_checkstring(L, 1))); /* if NULL push nil */ + return 1; +} + + +static int os_clock (lua_State *L) { + lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC); + return 1; +} + + +/* +** {====================================================== +** Time/Date operations +** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S, +** wday=%w+1, yday=%j, isdst=? } +** ======================================================= +*/ + +/* +** About the overflow check: an overflow cannot occur when time +** is represented by a lua_Integer, because either lua_Integer is +** large enough to represent all int fields or it is not large enough +** to represent a time that cause a field to overflow. However, if +** times are represented as doubles and lua_Integer is int, then the +** time 0x1.e1853b0d184f6p+55 would cause an overflow when adding 1900 +** to compute the year. +*/ +static void setfield (lua_State *L, const char *key, int value, int delta) { + #if (defined(LUA_NUMTIME) && LUA_MAXINTEGER <= INT_MAX) + if (value > LUA_MAXINTEGER - delta) + luaL_error(L, "field '%s' is out-of-bound", key); + #endif + lua_pushinteger(L, (lua_Integer)value + delta); + lua_setfield(L, -2, key); +} + + +static void setboolfield (lua_State *L, const char *key, int value) { + if (value < 0) /* undefined? */ + return; /* does not set field */ + lua_pushboolean(L, value); + lua_setfield(L, -2, key); +} + + +/* +** Set all fields from structure 'tm' in the table on top of the stack +*/ +static void setallfields (lua_State *L, struct tm *stm) { + setfield(L, "year", stm->tm_year, 1900); + setfield(L, "month", stm->tm_mon, 1); + setfield(L, "day", stm->tm_mday, 0); + setfield(L, "hour", stm->tm_hour, 0); + setfield(L, "min", stm->tm_min, 0); + setfield(L, "sec", stm->tm_sec, 0); + setfield(L, "yday", stm->tm_yday, 1); + setfield(L, "wday", stm->tm_wday, 1); + setboolfield(L, "isdst", stm->tm_isdst); +} + + +static int getboolfield (lua_State *L, const char *key) { + int res; + res = (lua_getfield(L, -1, key) == LUA_TNIL) ? -1 : lua_toboolean(L, -1); + lua_pop(L, 1); + return res; +} + + +static int getfield (lua_State *L, const char *key, int d, int delta) { + int isnum; + int t = lua_getfield(L, -1, key); /* get field and its type */ + lua_Integer res = lua_tointegerx(L, -1, &isnum); + if (!isnum) { /* field is not an integer? */ + if (t != LUA_TNIL) /* some other value? */ + return luaL_error(L, "field '%s' is not an integer", key); + else if (d < 0) /* absent field; no default? */ + return luaL_error(L, "field '%s' missing in date table", key); + res = d; + } + else { + /* unsigned avoids overflow when lua_Integer has 32 bits */ + if (!(res >= 0 ? (lua_Unsigned)res <= (lua_Unsigned)INT_MAX + delta + : (lua_Integer)INT_MIN + delta <= res)) + return luaL_error(L, "field '%s' is out-of-bound", key); + res -= delta; + } + lua_pop(L, 1); + return (int)res; +} + + +static const char *checkoption (lua_State *L, const char *conv, + ptrdiff_t convlen, char *buff) { + const char *option = LUA_STRFTIMEOPTIONS; + int oplen = 1; /* length of options being checked */ + for (; *option != '\0' && oplen <= convlen; option += oplen) { + if (*option == '|') /* next block? */ + oplen++; /* will check options with next length (+1) */ + else if (memcmp(conv, option, oplen) == 0) { /* match? */ + memcpy(buff, conv, oplen); /* copy valid option to buffer */ + buff[oplen] = '\0'; + return conv + oplen; /* return next item */ + } + } + luaL_argerror(L, 1, + lua_pushfstring(L, "invalid conversion specifier '%%%s'", conv)); + return conv; /* to avoid warnings */ +} + + +static time_t l_checktime (lua_State *L, int arg) { + l_timet t = l_gettime(L, arg); + luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds"); + return (time_t)t; +} + + +/* maximum size for an individual 'strftime' item */ +#define SIZETIMEFMT 250 + + +static int os_date (lua_State *L) { + size_t slen; + const char *s = luaL_optlstring(L, 1, "%c", &slen); + time_t t = luaL_opt(L, l_checktime, 2, time(NULL)); + const char *se = s + slen; /* 's' end */ + struct tm tmr, *stm; + if (*s == '!') { /* UTC? */ + stm = l_gmtime(&t, &tmr); + s++; /* skip '!' */ + } + else + stm = l_localtime(&t, &tmr); + if (stm == NULL) /* invalid date? */ + return luaL_error(L, + "date result cannot be represented in this installation"); + if (strcmp(s, "*t") == 0) { + lua_createtable(L, 0, 9); /* 9 = number of fields */ + setallfields(L, stm); + } + else { + char cc[4]; /* buffer for individual conversion specifiers */ + luaL_Buffer b; + cc[0] = '%'; + luaL_buffinit(L, &b); + while (s < se) { + if (*s != '%') /* not a conversion specifier? */ + luaL_addchar(&b, *s++); + else { + size_t reslen; + char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT); + s++; /* skip '%' */ + s = checkoption(L, s, se - s, cc + 1); /* copy specifier to 'cc' */ + reslen = strftime(buff, SIZETIMEFMT, cc, stm); + luaL_addsize(&b, reslen); + } + } + luaL_pushresult(&b); + } + return 1; +} + + +static int os_time (lua_State *L) { + time_t t; + if (lua_isnoneornil(L, 1)) /* called without args? */ + t = time(NULL); /* get current time */ + else { + struct tm ts; + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 1); /* make sure table is at the top */ + ts.tm_year = getfield(L, "year", -1, 1900); + ts.tm_mon = getfield(L, "month", -1, 1); + ts.tm_mday = getfield(L, "day", -1, 0); + ts.tm_hour = getfield(L, "hour", 12, 0); + ts.tm_min = getfield(L, "min", 0, 0); + ts.tm_sec = getfield(L, "sec", 0, 0); + ts.tm_isdst = getboolfield(L, "isdst"); + t = mktime(&ts); + setallfields(L, &ts); /* update fields with normalized values */ + } + if (t != (time_t)(l_timet)t || t == (time_t)(-1)) + return luaL_error(L, + "time result cannot be represented in this installation"); + l_pushtime(L, t); + return 1; +} + + +static int os_difftime (lua_State *L) { + time_t t1 = l_checktime(L, 1); + time_t t2 = l_checktime(L, 2); + lua_pushnumber(L, (lua_Number)difftime(t1, t2)); + return 1; +} + +/* }====================================================== */ + + +static int os_setlocale (lua_State *L) { + static const int cat[] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, + LC_NUMERIC, LC_TIME}; + static const char *const catnames[] = {"all", "collate", "ctype", "monetary", + "numeric", "time", NULL}; + const char *l = luaL_optstring(L, 1, NULL); + int op = luaL_checkoption(L, 2, "all", catnames); + lua_pushstring(L, setlocale(cat[op], l)); + return 1; +} + + +static int os_exit (lua_State *L) { + int status; + if (lua_isboolean(L, 1)) + status = (lua_toboolean(L, 1) ? EXIT_SUCCESS : EXIT_FAILURE); + else + status = (int)luaL_optinteger(L, 1, EXIT_SUCCESS); + if (lua_toboolean(L, 2)) + lua_close(L); + if (L) exit(status); /* 'if' to avoid warnings for unreachable 'return' */ + return 0; +} + + +static const luaL_Reg syslib[] = { + {"clock", os_clock}, + {"date", os_date}, + {"difftime", os_difftime}, + {"execute", os_execute}, + {"exit", os_exit}, + {"getenv", os_getenv}, + {"remove", os_remove}, + {"rename", os_rename}, + {"setlocale", os_setlocale}, + {"time", os_time}, + {"tmpname", os_tmpname}, + {NULL, NULL} +}; + +/* }====================================================== */ + + + +LUAMOD_API int luaopen_os (lua_State *L) { + luaL_newlib(L, syslib); + return 1; +} + diff --git a/Lua/lparser.c b/Lua/lparser.c new file mode 100644 index 00000000..77813a74 --- /dev/null +++ b/Lua/lparser.c @@ -0,0 +1,1956 @@ +/* +** $Id: lparser.c $ +** Lua Parser +** See Copyright Notice in lua.h +*/ + +#define lparser_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "llex.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" + + + +/* maximum number of local variables per function (must be smaller + than 250, due to the bytecode format) */ +#define MAXVARS 200 + + +#define hasmultret(k) ((k) == VCALL || (k) == VVARARG) + + +/* because all strings are unified by the scanner, the parser + can use pointer equality for string equality */ +#define eqstr(a,b) ((a) == (b)) + + +/* +** nodes for block list (list of active blocks) +*/ +typedef struct BlockCnt { + struct BlockCnt *previous; /* chain */ + int firstlabel; /* index of first label in this block */ + int firstgoto; /* index of first pending goto in this block */ + lu_byte nactvar; /* # active locals outside the block */ + lu_byte upval; /* true if some variable in the block is an upvalue */ + lu_byte isloop; /* true if 'block' is a loop */ + lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ +} BlockCnt; + + + +/* +** prototypes for recursive non-terminal functions +*/ +static void statement (LexState *ls); +static void expr (LexState *ls, expdesc *v); + + +static l_noret error_expected (LexState *ls, int token) { + luaX_syntaxerror(ls, + luaO_pushfstring(ls->L, "%s expected", luaX_token2str(ls, token))); +} + + +static l_noret errorlimit (FuncState *fs, int limit, const char *what) { + lua_State *L = fs->ls->L; + const char *msg; + int line = fs->f->linedefined; + const char *where = (line == 0) + ? "main function" + : luaO_pushfstring(L, "function at line %d", line); + msg = luaO_pushfstring(L, "too many %s (limit is %d) in %s", + what, limit, where); + luaX_syntaxerror(fs->ls, msg); +} + + +static void checklimit (FuncState *fs, int v, int l, const char *what) { + if (v > l) errorlimit(fs, l, what); +} + + +/* +** Test whether next token is 'c'; if so, skip it. +*/ +static int testnext (LexState *ls, int c) { + if (ls->t.token == c) { + luaX_next(ls); + return 1; + } + else return 0; +} + + +/* +** Check that next token is 'c'. +*/ +static void check (LexState *ls, int c) { + if (ls->t.token != c) + error_expected(ls, c); +} + + +/* +** Check that next token is 'c' and skip it. +*/ +static void checknext (LexState *ls, int c) { + check(ls, c); + luaX_next(ls); +} + + +#define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); } + + +/* +** Check that next token is 'what' and skip it. In case of error, +** raise an error that the expected 'what' should match a 'who' +** in line 'where' (if that is not the current line). +*/ +static void check_match (LexState *ls, int what, int who, int where) { + if (unlikely(!testnext(ls, what))) { + if (where == ls->linenumber) /* all in the same line? */ + error_expected(ls, what); /* do not need a complex message */ + else { + luaX_syntaxerror(ls, luaO_pushfstring(ls->L, + "%s expected (to close %s at line %d)", + luaX_token2str(ls, what), luaX_token2str(ls, who), where)); + } + } +} + + +static TString *str_checkname (LexState *ls) { + TString *ts; + check(ls, TK_NAME); + ts = ls->t.seminfo.ts; + luaX_next(ls); + return ts; +} + + +static void init_exp (expdesc *e, expkind k, int i) { + e->f = e->t = NO_JUMP; + e->k = k; + e->u.info = i; +} + + +static void codestring (expdesc *e, TString *s) { + e->f = e->t = NO_JUMP; + e->k = VKSTR; + e->u.strval = s; +} + + +static void codename (LexState *ls, expdesc *e) { + codestring(e, str_checkname(ls)); +} + + +/* +** Register a new local variable in the active 'Proto' (for debug +** information). +*/ +static int registerlocalvar (LexState *ls, FuncState *fs, TString *varname) { + Proto *f = fs->f; + int oldsize = f->sizelocvars; + luaM_growvector(ls->L, f->locvars, fs->ndebugvars, f->sizelocvars, + LocVar, SHRT_MAX, "local variables"); + while (oldsize < f->sizelocvars) + f->locvars[oldsize++].varname = NULL; + f->locvars[fs->ndebugvars].varname = varname; + f->locvars[fs->ndebugvars].startpc = fs->pc; + luaC_objbarrier(ls->L, f, varname); + return fs->ndebugvars++; +} + + +/* +** Create a new local variable with the given 'name'. Return its index +** in the function. +*/ +static int new_localvar (LexState *ls, TString *name) { + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Dyndata *dyd = ls->dyd; + Vardesc *var; + checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, + MAXVARS, "local variables"); + luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1, + dyd->actvar.size, Vardesc, USHRT_MAX, "local variables"); + var = &dyd->actvar.arr[dyd->actvar.n++]; + var->vd.kind = VDKREG; /* default */ + var->vd.name = name; + return dyd->actvar.n - 1 - fs->firstlocal; +} + +#define new_localvarliteral(ls,v) \ + new_localvar(ls, \ + luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1)); + + + +/* +** Return the "variable description" (Vardesc) of a given variable. +** (Unless noted otherwise, all variables are referred to by their +** compiler indices.) +*/ +static Vardesc *getlocalvardesc (FuncState *fs, int vidx) { + return &fs->ls->dyd->actvar.arr[fs->firstlocal + vidx]; +} + + +/* +** Convert 'nvar', a compiler index level, to it corresponding +** stack index level. For that, search for the highest variable +** below that level that is in the stack and uses its stack +** index ('sidx'). +*/ +static int stacklevel (FuncState *fs, int nvar) { + while (nvar-- > 0) { + Vardesc *vd = getlocalvardesc(fs, nvar); /* get variable */ + if (vd->vd.kind != RDKCTC) /* is in the stack? */ + return vd->vd.sidx + 1; + } + return 0; /* no variables in the stack */ +} + + +/* +** Return the number of variables in the stack for function 'fs' +*/ +int luaY_nvarstack (FuncState *fs) { + return stacklevel(fs, fs->nactvar); +} + + +/* +** Get the debug-information entry for current variable 'vidx'. +*/ +static LocVar *localdebuginfo (FuncState *fs, int vidx) { + Vardesc *vd = getlocalvardesc(fs, vidx); + if (vd->vd.kind == RDKCTC) + return NULL; /* no debug info. for constants */ + else { + int idx = vd->vd.pidx; + lua_assert(idx < fs->ndebugvars); + return &fs->f->locvars[idx]; + } +} + + +/* +** Create an expression representing variable 'vidx' +*/ +static void init_var (FuncState *fs, expdesc *e, int vidx) { + e->f = e->t = NO_JUMP; + e->k = VLOCAL; + e->u.var.vidx = vidx; + e->u.var.sidx = getlocalvardesc(fs, vidx)->vd.sidx; +} + + +/* +** Raises an error if variable described by 'e' is read only +*/ +static void check_readonly (LexState *ls, expdesc *e) { + FuncState *fs = ls->fs; + TString *varname = NULL; /* to be set if variable is const */ + switch (e->k) { + case VCONST: { + varname = ls->dyd->actvar.arr[e->u.info].vd.name; + break; + } + case VLOCAL: { + Vardesc *vardesc = getlocalvardesc(fs, e->u.var.vidx); + if (vardesc->vd.kind != VDKREG) /* not a regular variable? */ + varname = vardesc->vd.name; + break; + } + case VUPVAL: { + Upvaldesc *up = &fs->f->upvalues[e->u.info]; + if (up->kind != VDKREG) + varname = up->name; + break; + } + default: + return; /* other cases cannot be read-only */ + } + if (varname) { + const char *msg = luaO_pushfstring(ls->L, + "attempt to assign to const variable '%s'", getstr(varname)); + luaK_semerror(ls, msg); /* error */ + } +} + + +/* +** Start the scope for the last 'nvars' created variables. +*/ +static void adjustlocalvars (LexState *ls, int nvars) { + FuncState *fs = ls->fs; + int stklevel = luaY_nvarstack(fs); + int i; + for (i = 0; i < nvars; i++) { + int vidx = fs->nactvar++; + Vardesc *var = getlocalvardesc(fs, vidx); + var->vd.sidx = stklevel++; + var->vd.pidx = registerlocalvar(ls, fs, var->vd.name); + } +} + + +/* +** Close the scope for all variables up to level 'tolevel'. +** (debug info.) +*/ +static void removevars (FuncState *fs, int tolevel) { + fs->ls->dyd->actvar.n -= (fs->nactvar - tolevel); + while (fs->nactvar > tolevel) { + LocVar *var = localdebuginfo(fs, --fs->nactvar); + if (var) /* does it have debug information? */ + var->endpc = fs->pc; + } +} + + +/* +** Search the upvalues of the function 'fs' for one +** with the given 'name'. +*/ +static int searchupvalue (FuncState *fs, TString *name) { + int i; + Upvaldesc *up = fs->f->upvalues; + for (i = 0; i < fs->nups; i++) { + if (eqstr(up[i].name, name)) return i; + } + return -1; /* not found */ +} + + +static Upvaldesc *allocupvalue (FuncState *fs) { + Proto *f = fs->f; + int oldsize = f->sizeupvalues; + checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); + luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues, + Upvaldesc, MAXUPVAL, "upvalues"); + while (oldsize < f->sizeupvalues) + f->upvalues[oldsize++].name = NULL; + return &f->upvalues[fs->nups++]; +} + + +static int newupvalue (FuncState *fs, TString *name, expdesc *v) { + Upvaldesc *up = allocupvalue(fs); + FuncState *prev = fs->prev; + if (v->k == VLOCAL) { + up->instack = 1; + up->idx = v->u.var.sidx; + up->kind = getlocalvardesc(prev, v->u.var.vidx)->vd.kind; + lua_assert(eqstr(name, getlocalvardesc(prev, v->u.var.vidx)->vd.name)); + } + else { + up->instack = 0; + up->idx = cast_byte(v->u.info); + up->kind = prev->f->upvalues[v->u.info].kind; + lua_assert(eqstr(name, prev->f->upvalues[v->u.info].name)); + } + up->name = name; + luaC_objbarrier(fs->ls->L, fs->f, name); + return fs->nups - 1; +} + + +/* +** Look for an active local variable with the name 'n' in the +** function 'fs'. If found, initialize 'var' with it and return +** its expression kind; otherwise return -1. +*/ +static int searchvar (FuncState *fs, TString *n, expdesc *var) { + int i; + for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { + Vardesc *vd = getlocalvardesc(fs, i); + if (eqstr(n, vd->vd.name)) { /* found? */ + if (vd->vd.kind == RDKCTC) /* compile-time constant? */ + init_exp(var, VCONST, fs->firstlocal + i); + else /* real variable */ + init_var(fs, var, i); + return var->k; + } + } + return -1; /* not found */ +} + + +/* +** Mark block where variable at given level was defined +** (to emit close instructions later). +*/ +static void markupval (FuncState *fs, int level) { + BlockCnt *bl = fs->bl; + while (bl->nactvar > level) + bl = bl->previous; + bl->upval = 1; + fs->needclose = 1; +} + + +/* +** Find a variable with the given name 'n'. If it is an upvalue, add +** this upvalue into all intermediate functions. If it is a global, set +** 'var' as 'void' as a flag. +*/ +static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { + if (fs == NULL) /* no more levels? */ + init_exp(var, VVOID, 0); /* default is global */ + else { + int v = searchvar(fs, n, var); /* look up locals at current level */ + if (v >= 0) { /* found? */ + if (v == VLOCAL && !base) + markupval(fs, var->u.var.vidx); /* local will be used as an upval */ + } + else { /* not found as local at current level; try upvalues */ + int idx = searchupvalue(fs, n); /* try existing upvalues */ + if (idx < 0) { /* not found? */ + singlevaraux(fs->prev, n, var, 0); /* try upper levels */ + if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */ + idx = newupvalue(fs, n, var); /* will be a new upvalue */ + else /* it is a global or a constant */ + return; /* don't need to do anything at this level */ + } + init_exp(var, VUPVAL, idx); /* new or old upvalue */ + } + } +} + + +/* +** Find a variable with the given name 'n', handling global variables +** too. +*/ +static void singlevar (LexState *ls, expdesc *var) { + TString *varname = str_checkname(ls); + FuncState *fs = ls->fs; + singlevaraux(fs, varname, var, 1); + if (var->k == VVOID) { /* global name? */ + expdesc key; + singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ + lua_assert(var->k != VVOID); /* this one must exist */ + codestring(&key, varname); /* key is variable name */ + luaK_indexed(fs, var, &key); /* env[varname] */ + } +} + + +/* +** Adjust the number of results from an expression list 'e' with 'nexps' +** expressions to 'nvars' values. +*/ +static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { + FuncState *fs = ls->fs; + int needed = nvars - nexps; /* extra values needed */ + if (hasmultret(e->k)) { /* last expression has multiple returns? */ + int extra = needed + 1; /* discount last expression itself */ + if (extra < 0) + extra = 0; + luaK_setreturns(fs, e, extra); /* last exp. provides the difference */ + } + else { + if (e->k != VVOID) /* at least one expression? */ + luaK_exp2nextreg(fs, e); /* close last expression */ + if (needed > 0) /* missing values? */ + luaK_nil(fs, fs->freereg, needed); /* complete with nils */ + } + if (needed > 0) + luaK_reserveregs(fs, needed); /* registers for extra values */ + else /* adding 'needed' is actually a subtraction */ + fs->freereg += needed; /* remove extra values */ +} + + +#define enterlevel(ls) luaE_incCstack(ls->L) + + +#define leavelevel(ls) ((ls)->L->nCcalls--) + + +/* +** Generates an error that a goto jumps into the scope of some +** local variable. +*/ +static l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) { + const char *varname = getstr(getlocalvardesc(ls->fs, gt->nactvar)->vd.name); + const char *msg = " at line %d jumps into the scope of local '%s'"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line, varname); + luaK_semerror(ls, msg); /* raise the error */ +} + + +/* +** Solves the goto at index 'g' to given 'label' and removes it +** from the list of pending goto's. +** If it jumps into the scope of some variable, raises an error. +*/ +static void solvegoto (LexState *ls, int g, Labeldesc *label) { + int i; + Labellist *gl = &ls->dyd->gt; /* list of goto's */ + Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */ + lua_assert(eqstr(gt->name, label->name)); + if (unlikely(gt->nactvar < label->nactvar)) /* enter some scope? */ + jumpscopeerror(ls, gt); + luaK_patchlist(ls->fs, gt->pc, label->pc); + for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */ + gl->arr[i] = gl->arr[i + 1]; + gl->n--; +} + + +/* +** Search for an active label with the given name. +*/ +static Labeldesc *findlabel (LexState *ls, TString *name) { + int i; + Dyndata *dyd = ls->dyd; + /* check labels in current function for a match */ + for (i = ls->fs->firstlabel; i < dyd->label.n; i++) { + Labeldesc *lb = &dyd->label.arr[i]; + if (eqstr(lb->name, name)) /* correct label? */ + return lb; + } + return NULL; /* label not found */ +} + + +/* +** Adds a new label/goto in the corresponding list. +*/ +static int newlabelentry (LexState *ls, Labellist *l, TString *name, + int line, int pc) { + int n = l->n; + luaM_growvector(ls->L, l->arr, n, l->size, + Labeldesc, SHRT_MAX, "labels/gotos"); + l->arr[n].name = name; + l->arr[n].line = line; + l->arr[n].nactvar = ls->fs->nactvar; + l->arr[n].close = 0; + l->arr[n].pc = pc; + l->n = n + 1; + return n; +} + + +static int newgotoentry (LexState *ls, TString *name, int line, int pc) { + return newlabelentry(ls, &ls->dyd->gt, name, line, pc); +} + + +/* +** Solves forward jumps. Check whether new label 'lb' matches any +** pending gotos in current block and solves them. Return true +** if any of the goto's need to close upvalues. +*/ +static int solvegotos (LexState *ls, Labeldesc *lb) { + Labellist *gl = &ls->dyd->gt; + int i = ls->fs->bl->firstgoto; + int needsclose = 0; + while (i < gl->n) { + if (eqstr(gl->arr[i].name, lb->name)) { + needsclose |= gl->arr[i].close; + solvegoto(ls, i, lb); /* will remove 'i' from the list */ + } + else + i++; + } + return needsclose; +} + + +/* +** Create a new label with the given 'name' at the given 'line'. +** 'last' tells whether label is the last non-op statement in its +** block. Solves all pending goto's to this new label and adds +** a close instruction if necessary. +** Returns true iff it added a close instruction. +*/ +static int createlabel (LexState *ls, TString *name, int line, + int last) { + FuncState *fs = ls->fs; + Labellist *ll = &ls->dyd->label; + int l = newlabelentry(ls, ll, name, line, luaK_getlabel(fs)); + if (last) { /* label is last no-op statement in the block? */ + /* assume that locals are already out of scope */ + ll->arr[l].nactvar = fs->bl->nactvar; + } + if (solvegotos(ls, &ll->arr[l])) { /* need close? */ + luaK_codeABC(fs, OP_CLOSE, luaY_nvarstack(fs), 0, 0); + return 1; + } + return 0; +} + + +/* +** Adjust pending gotos to outer level of a block. +*/ +static void movegotosout (FuncState *fs, BlockCnt *bl) { + int i; + Labellist *gl = &fs->ls->dyd->gt; + /* correct pending gotos to current block */ + for (i = bl->firstgoto; i < gl->n; i++) { /* for each pending goto */ + Labeldesc *gt = &gl->arr[i]; + /* leaving a variable scope? */ + if (stacklevel(fs, gt->nactvar) > stacklevel(fs, bl->nactvar)) + gt->close |= bl->upval; /* jump may need a close */ + gt->nactvar = bl->nactvar; /* update goto level */ + } +} + + +static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { + bl->isloop = isloop; + bl->nactvar = fs->nactvar; + bl->firstlabel = fs->ls->dyd->label.n; + bl->firstgoto = fs->ls->dyd->gt.n; + bl->upval = 0; + bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc); + bl->previous = fs->bl; + fs->bl = bl; + lua_assert(fs->freereg == luaY_nvarstack(fs)); +} + + +/* +** generates an error for an undefined 'goto'. +*/ +static l_noret undefgoto (LexState *ls, Labeldesc *gt) { + const char *msg; + if (eqstr(gt->name, luaS_newliteral(ls->L, "break"))) { + msg = "break outside loop at line %d"; + msg = luaO_pushfstring(ls->L, msg, gt->line); + } + else { + msg = "no visible label '%s' for at line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); + } + luaK_semerror(ls, msg); +} + + +static void leaveblock (FuncState *fs) { + BlockCnt *bl = fs->bl; + LexState *ls = fs->ls; + int hasclose = 0; + int stklevel = stacklevel(fs, bl->nactvar); /* level outside the block */ + if (bl->isloop) /* fix pending breaks? */ + hasclose = createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0); + if (!hasclose && bl->previous && bl->upval) + luaK_codeABC(fs, OP_CLOSE, stklevel, 0, 0); + fs->bl = bl->previous; + removevars(fs, bl->nactvar); + lua_assert(bl->nactvar == fs->nactvar); + fs->freereg = stklevel; /* free registers */ + ls->dyd->label.n = bl->firstlabel; /* remove local labels */ + if (bl->previous) /* inner block? */ + movegotosout(fs, bl); /* update pending gotos to outer block */ + else { + if (bl->firstgoto < ls->dyd->gt.n) /* pending gotos in outer block? */ + undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */ + } +} + + +/* +** adds a new prototype into list of prototypes +*/ +static Proto *addprototype (LexState *ls) { + Proto *clp; + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Proto *f = fs->f; /* prototype of current function */ + if (fs->np >= f->sizep) { + int oldsize = f->sizep; + luaM_growvector(L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "functions"); + while (oldsize < f->sizep) + f->p[oldsize++] = NULL; + } + f->p[fs->np++] = clp = luaF_newproto(L); + luaC_objbarrier(L, f, clp); + return clp; +} + + +/* +** codes instruction to create new closure in parent function. +** The OP_CLOSURE instruction uses the last available register, +** so that, if it invokes the GC, the GC knows which registers +** are in use at that time. + +*/ +static void codeclosure (LexState *ls, expdesc *v) { + FuncState *fs = ls->fs->prev; + init_exp(v, VRELOC, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np - 1)); + luaK_exp2nextreg(fs, v); /* fix it at the last register */ +} + + +static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { + Proto *f = fs->f; + fs->prev = ls->fs; /* linked list of funcstates */ + fs->ls = ls; + ls->fs = fs; + fs->pc = 0; + fs->previousline = f->linedefined; + fs->iwthabs = 0; + fs->lasttarget = 0; + fs->freereg = 0; + fs->nk = 0; + fs->nabslineinfo = 0; + fs->np = 0; + fs->nups = 0; + fs->ndebugvars = 0; + fs->nactvar = 0; + fs->needclose = 0; + fs->firstlocal = ls->dyd->actvar.n; + fs->firstlabel = ls->dyd->label.n; + fs->bl = NULL; + f->source = ls->source; + luaC_objbarrier(ls->L, f, f->source); + f->maxstacksize = 2; /* registers 0/1 are always valid */ + enterblock(fs, bl, 0); +} + + +static void close_func (LexState *ls) { + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Proto *f = fs->f; + luaK_ret(fs, luaY_nvarstack(fs), 0); /* final return */ + leaveblock(fs); + lua_assert(fs->bl == NULL); + luaK_finish(fs); + luaM_shrinkvector(L, f->code, f->sizecode, fs->pc, Instruction); + luaM_shrinkvector(L, f->lineinfo, f->sizelineinfo, fs->pc, ls_byte); + luaM_shrinkvector(L, f->abslineinfo, f->sizeabslineinfo, + fs->nabslineinfo, AbsLineInfo); + luaM_shrinkvector(L, f->k, f->sizek, fs->nk, TValue); + luaM_shrinkvector(L, f->p, f->sizep, fs->np, Proto *); + luaM_shrinkvector(L, f->locvars, f->sizelocvars, fs->ndebugvars, LocVar); + luaM_shrinkvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc); + ls->fs = fs->prev; + luaC_checkGC(L); +} + + + +/*============================================================*/ +/* GRAMMAR RULES */ +/*============================================================*/ + + +/* +** check whether current token is in the follow set of a block. +** 'until' closes syntactical blocks, but do not close scope, +** so it is handled in separate. +*/ +static int block_follow (LexState *ls, int withuntil) { + switch (ls->t.token) { + case TK_ELSE: case TK_ELSEIF: + case TK_END: case TK_EOS: + return 1; + case TK_UNTIL: return withuntil; + default: return 0; + } +} + + +static void statlist (LexState *ls) { + /* statlist -> { stat [';'] } */ + while (!block_follow(ls, 1)) { + if (ls->t.token == TK_RETURN) { + statement(ls); + return; /* 'return' must be last statement */ + } + statement(ls); + } +} + + +static void fieldsel (LexState *ls, expdesc *v) { + /* fieldsel -> ['.' | ':'] NAME */ + FuncState *fs = ls->fs; + expdesc key; + luaK_exp2anyregup(fs, v); + luaX_next(ls); /* skip the dot or colon */ + codename(ls, &key); + luaK_indexed(fs, v, &key); +} + + +static void yindex (LexState *ls, expdesc *v) { + /* index -> '[' expr ']' */ + luaX_next(ls); /* skip the '[' */ + expr(ls, v); + luaK_exp2val(ls->fs, v); + checknext(ls, ']'); +} + + +/* +** {====================================================================== +** Rules for Constructors +** ======================================================================= +*/ + + +typedef struct ConsControl { + expdesc v; /* last list item read */ + expdesc *t; /* table descriptor */ + int nh; /* total number of 'record' elements */ + int na; /* number of array elements already stored */ + int tostore; /* number of array elements pending to be stored */ +} ConsControl; + + +static void recfield (LexState *ls, ConsControl *cc) { + /* recfield -> (NAME | '['exp']') = exp */ + FuncState *fs = ls->fs; + int reg = ls->fs->freereg; + expdesc tab, key, val; + if (ls->t.token == TK_NAME) { + checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); + codename(ls, &key); + } + else /* ls->t.token == '[' */ + yindex(ls, &key); + cc->nh++; + checknext(ls, '='); + tab = *cc->t; + luaK_indexed(fs, &tab, &key); + expr(ls, &val); + luaK_storevar(fs, &tab, &val); + fs->freereg = reg; /* free registers */ +} + + +static void closelistfield (FuncState *fs, ConsControl *cc) { + if (cc->v.k == VVOID) return; /* there is no list item */ + luaK_exp2nextreg(fs, &cc->v); + cc->v.k = VVOID; + if (cc->tostore == LFIELDS_PER_FLUSH) { + luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); /* flush */ + cc->na += cc->tostore; + cc->tostore = 0; /* no more items pending */ + } +} + + +static void lastlistfield (FuncState *fs, ConsControl *cc) { + if (cc->tostore == 0) return; + if (hasmultret(cc->v.k)) { + luaK_setmultret(fs, &cc->v); + luaK_setlist(fs, cc->t->u.info, cc->na, LUA_MULTRET); + cc->na--; /* do not count last expression (unknown number of elements) */ + } + else { + if (cc->v.k != VVOID) + luaK_exp2nextreg(fs, &cc->v); + luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); + } + cc->na += cc->tostore; +} + + +static void listfield (LexState *ls, ConsControl *cc) { + /* listfield -> exp */ + expr(ls, &cc->v); + cc->tostore++; +} + + +static void field (LexState *ls, ConsControl *cc) { + /* field -> listfield | recfield */ + switch(ls->t.token) { + case TK_NAME: { /* may be 'listfield' or 'recfield' */ + if (luaX_lookahead(ls) != '=') /* expression? */ + listfield(ls, cc); + else + recfield(ls, cc); + break; + } + case '[': { + recfield(ls, cc); + break; + } + default: { + listfield(ls, cc); + break; + } + } +} + + +static void constructor (LexState *ls, expdesc *t) { + /* constructor -> '{' [ field { sep field } [sep] ] '}' + sep -> ',' | ';' */ + FuncState *fs = ls->fs; + int line = ls->linenumber; + int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); + ConsControl cc; + luaK_code(fs, 0); /* space for extra arg. */ + cc.na = cc.nh = cc.tostore = 0; + cc.t = t; + init_exp(t, VNONRELOC, fs->freereg); /* table will be at stack top */ + luaK_reserveregs(fs, 1); + init_exp(&cc.v, VVOID, 0); /* no value (yet) */ + checknext(ls, '{'); + do { + lua_assert(cc.v.k == VVOID || cc.tostore > 0); + if (ls->t.token == '}') break; + closelistfield(fs, &cc); + field(ls, &cc); + } while (testnext(ls, ',') || testnext(ls, ';')); + check_match(ls, '}', '{', line); + lastlistfield(fs, &cc); + luaK_settablesize(fs, pc, t->u.info, cc.na, cc.nh); +} + +/* }====================================================================== */ + + +static void setvararg (FuncState *fs, int nparams) { + fs->f->is_vararg = 1; + luaK_codeABC(fs, OP_VARARGPREP, nparams, 0, 0); +} + + +static void parlist (LexState *ls) { + /* parlist -> [ {NAME ','} (NAME | '...') ] */ + FuncState *fs = ls->fs; + Proto *f = fs->f; + int nparams = 0; + int isvararg = 0; + if (ls->t.token != ')') { /* is 'parlist' not empty? */ + do { + switch (ls->t.token) { + case TK_NAME: { + new_localvar(ls, str_checkname(ls)); + nparams++; + break; + } + case TK_DOTS: { + luaX_next(ls); + isvararg = 1; + break; + } + default: luaX_syntaxerror(ls, " or '...' expected"); + } + } while (!isvararg && testnext(ls, ',')); + } + adjustlocalvars(ls, nparams); + f->numparams = cast_byte(fs->nactvar); + if (isvararg) + setvararg(fs, f->numparams); /* declared vararg */ + luaK_reserveregs(fs, fs->nactvar); /* reserve registers for parameters */ +} + + +static void body (LexState *ls, expdesc *e, int ismethod, int line) { + /* body -> '(' parlist ')' block END */ + FuncState new_fs; + BlockCnt bl; + new_fs.f = addprototype(ls); + new_fs.f->linedefined = line; + open_func(ls, &new_fs, &bl); + checknext(ls, '('); + if (ismethod) { + new_localvarliteral(ls, "self"); /* create 'self' parameter */ + adjustlocalvars(ls, 1); + } + parlist(ls); + checknext(ls, ')'); + statlist(ls); + new_fs.f->lastlinedefined = ls->linenumber; + check_match(ls, TK_END, TK_FUNCTION, line); + codeclosure(ls, e); + close_func(ls); +} + + +static int explist (LexState *ls, expdesc *v) { + /* explist -> expr { ',' expr } */ + int n = 1; /* at least one expression */ + expr(ls, v); + while (testnext(ls, ',')) { + luaK_exp2nextreg(ls->fs, v); + expr(ls, v); + n++; + } + return n; +} + + +static void funcargs (LexState *ls, expdesc *f, int line) { + FuncState *fs = ls->fs; + expdesc args; + int base, nparams; + switch (ls->t.token) { + case '(': { /* funcargs -> '(' [ explist ] ')' */ + luaX_next(ls); + if (ls->t.token == ')') /* arg list is empty? */ + args.k = VVOID; + else { + explist(ls, &args); + if (hasmultret(args.k)) + luaK_setmultret(fs, &args); + } + check_match(ls, ')', '(', line); + break; + } + case '{': { /* funcargs -> constructor */ + constructor(ls, &args); + break; + } + case TK_STRING: { /* funcargs -> STRING */ + codestring(&args, ls->t.seminfo.ts); + luaX_next(ls); /* must use 'seminfo' before 'next' */ + break; + } + default: { + luaX_syntaxerror(ls, "function arguments expected"); + } + } + lua_assert(f->k == VNONRELOC); + base = f->u.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = LUA_MULTRET; /* open call */ + else { + if (args.k != VVOID) + luaK_exp2nextreg(fs, &args); /* close last argument */ + nparams = fs->freereg - (base+1); + } + init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2)); + luaK_fixline(fs, line); + fs->freereg = base+1; /* call remove function and arguments and leaves + (unless changed) one result */ +} + + + + +/* +** {====================================================================== +** Expression parsing +** ======================================================================= +*/ + + +static void primaryexp (LexState *ls, expdesc *v) { + /* primaryexp -> NAME | '(' expr ')' */ + switch (ls->t.token) { + case '(': { + int line = ls->linenumber; + luaX_next(ls); + expr(ls, v); + check_match(ls, ')', '(', line); + luaK_dischargevars(ls->fs, v); + return; + } + case TK_NAME: { + singlevar(ls, v); + return; + } + default: { + luaX_syntaxerror(ls, "unexpected symbol"); + } + } +} + + +static void suffixedexp (LexState *ls, expdesc *v) { + /* suffixedexp -> + primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ + FuncState *fs = ls->fs; + int line = ls->linenumber; + primaryexp(ls, v); + for (;;) { + switch (ls->t.token) { + case '.': { /* fieldsel */ + fieldsel(ls, v); + break; + } + case '[': { /* '[' exp ']' */ + expdesc key; + luaK_exp2anyregup(fs, v); + yindex(ls, &key); + luaK_indexed(fs, v, &key); + break; + } + case ':': { /* ':' NAME funcargs */ + expdesc key; + luaX_next(ls); + codename(ls, &key); + luaK_self(fs, v, &key); + funcargs(ls, v, line); + break; + } + case '(': case TK_STRING: case '{': { /* funcargs */ + luaK_exp2nextreg(fs, v); + funcargs(ls, v, line); + break; + } + default: return; + } + } +} + + +static void simpleexp (LexState *ls, expdesc *v) { + /* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... | + constructor | FUNCTION body | suffixedexp */ + switch (ls->t.token) { + case TK_FLT: { + init_exp(v, VKFLT, 0); + v->u.nval = ls->t.seminfo.r; + break; + } + case TK_INT: { + init_exp(v, VKINT, 0); + v->u.ival = ls->t.seminfo.i; + break; + } + case TK_STRING: { + codestring(v, ls->t.seminfo.ts); + break; + } + case TK_NIL: { + init_exp(v, VNIL, 0); + break; + } + case TK_TRUE: { + init_exp(v, VTRUE, 0); + break; + } + case TK_FALSE: { + init_exp(v, VFALSE, 0); + break; + } + case TK_DOTS: { /* vararg */ + FuncState *fs = ls->fs; + check_condition(ls, fs->f->is_vararg, + "cannot use '...' outside a vararg function"); + init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 0, 1)); + break; + } + case '{': { /* constructor */ + constructor(ls, v); + return; + } + case TK_FUNCTION: { + luaX_next(ls); + body(ls, v, 0, ls->linenumber); + return; + } + default: { + suffixedexp(ls, v); + return; + } + } + luaX_next(ls); +} + + +static UnOpr getunopr (int op) { + switch (op) { + case TK_NOT: return OPR_NOT; + case '-': return OPR_MINUS; + case '~': return OPR_BNOT; + case '#': return OPR_LEN; + default: return OPR_NOUNOPR; + } +} + + +static BinOpr getbinopr (int op) { + switch (op) { + case '+': return OPR_ADD; + case '-': return OPR_SUB; + case '*': return OPR_MUL; + case '%': return OPR_MOD; + case '^': return OPR_POW; + case '/': return OPR_DIV; + case TK_IDIV: return OPR_IDIV; + case '&': return OPR_BAND; + case '|': return OPR_BOR; + case '~': return OPR_BXOR; + case TK_SHL: return OPR_SHL; + case TK_SHR: return OPR_SHR; + case TK_CONCAT: return OPR_CONCAT; + case TK_NE: return OPR_NE; + case TK_EQ: return OPR_EQ; + case '<': return OPR_LT; + case TK_LE: return OPR_LE; + case '>': return OPR_GT; + case TK_GE: return OPR_GE; + case TK_AND: return OPR_AND; + case TK_OR: return OPR_OR; + default: return OPR_NOBINOPR; + } +} + + +/* +** Priority table for binary operators. +*/ +static const struct { + lu_byte left; /* left priority for each binary operator */ + lu_byte right; /* right priority */ +} priority[] = { /* ORDER OPR */ + {10, 10}, {10, 10}, /* '+' '-' */ + {11, 11}, {11, 11}, /* '*' '%' */ + {14, 13}, /* '^' (right associative) */ + {11, 11}, {11, 11}, /* '/' '//' */ + {6, 6}, {4, 4}, {5, 5}, /* '&' '|' '~' */ + {7, 7}, {7, 7}, /* '<<' '>>' */ + {9, 8}, /* '..' (right associative) */ + {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ + {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ + {2, 2}, {1, 1} /* and, or */ +}; + +#define UNARY_PRIORITY 12 /* priority for unary operators */ + + +/* +** subexpr -> (simpleexp | unop subexpr) { binop subexpr } +** where 'binop' is any binary operator with a priority higher than 'limit' +*/ +static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { + BinOpr op; + UnOpr uop; + enterlevel(ls); + uop = getunopr(ls->t.token); + if (uop != OPR_NOUNOPR) { /* prefix (unary) operator? */ + int line = ls->linenumber; + luaX_next(ls); /* skip operator */ + subexpr(ls, v, UNARY_PRIORITY); + luaK_prefix(ls->fs, uop, v, line); + } + else simpleexp(ls, v); + /* expand while operators have priorities higher than 'limit' */ + op = getbinopr(ls->t.token); + while (op != OPR_NOBINOPR && priority[op].left > limit) { + expdesc v2; + BinOpr nextop; + int line = ls->linenumber; + luaX_next(ls); /* skip operator */ + luaK_infix(ls->fs, op, v); + /* read sub-expression with higher priority */ + nextop = subexpr(ls, &v2, priority[op].right); + luaK_posfix(ls->fs, op, v, &v2, line); + op = nextop; + } + leavelevel(ls); + return op; /* return first untreated operator */ +} + + +static void expr (LexState *ls, expdesc *v) { + subexpr(ls, v, 0); +} + +/* }==================================================================== */ + + + +/* +** {====================================================================== +** Rules for Statements +** ======================================================================= +*/ + + +static void block (LexState *ls) { + /* block -> statlist */ + FuncState *fs = ls->fs; + BlockCnt bl; + enterblock(fs, &bl, 0); + statlist(ls); + leaveblock(fs); +} + + +/* +** structure to chain all variables in the left-hand side of an +** assignment +*/ +struct LHS_assign { + struct LHS_assign *prev; + expdesc v; /* variable (global, local, upvalue, or indexed) */ +}; + + +/* +** check whether, in an assignment to an upvalue/local variable, the +** upvalue/local variable is begin used in a previous assignment to a +** table. If so, save original upvalue/local value in a safe place and +** use this safe copy in the previous assignment. +*/ +static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { + FuncState *fs = ls->fs; + int extra = fs->freereg; /* eventual position to save local variable */ + int conflict = 0; + for (; lh; lh = lh->prev) { /* check all previous assignments */ + if (vkisindexed(lh->v.k)) { /* assignment to table field? */ + if (lh->v.k == VINDEXUP) { /* is table an upvalue? */ + if (v->k == VUPVAL && lh->v.u.ind.t == v->u.info) { + conflict = 1; /* table is the upvalue being assigned now */ + lh->v.k = VINDEXSTR; + lh->v.u.ind.t = extra; /* assignment will use safe copy */ + } + } + else { /* table is a register */ + if (v->k == VLOCAL && lh->v.u.ind.t == v->u.var.sidx) { + conflict = 1; /* table is the local being assigned now */ + lh->v.u.ind.t = extra; /* assignment will use safe copy */ + } + /* is index the local being assigned? */ + if (lh->v.k == VINDEXED && v->k == VLOCAL && + lh->v.u.ind.idx == v->u.var.sidx) { + conflict = 1; + lh->v.u.ind.idx = extra; /* previous assignment will use safe copy */ + } + } + } + } + if (conflict) { + /* copy upvalue/local value to a temporary (in position 'extra') */ + if (v->k == VLOCAL) + luaK_codeABC(fs, OP_MOVE, extra, v->u.var.sidx, 0); + else + luaK_codeABC(fs, OP_GETUPVAL, extra, v->u.info, 0); + luaK_reserveregs(fs, 1); + } +} + +/* +** Parse and compile a multiple assignment. The first "variable" +** (a 'suffixedexp') was already read by the caller. +** +** assignment -> suffixedexp restassign +** restassign -> ',' suffixedexp restassign | '=' explist +*/ +static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) { + expdesc e; + check_condition(ls, vkisvar(lh->v.k), "syntax error"); + check_readonly(ls, &lh->v); + if (testnext(ls, ',')) { /* restassign -> ',' suffixedexp restassign */ + struct LHS_assign nv; + nv.prev = lh; + suffixedexp(ls, &nv.v); + if (!vkisindexed(nv.v.k)) + check_conflict(ls, lh, &nv.v); + enterlevel(ls); /* control recursion depth */ + restassign(ls, &nv, nvars+1); + leavelevel(ls); + } + else { /* restassign -> '=' explist */ + int nexps; + checknext(ls, '='); + nexps = explist(ls, &e); + if (nexps != nvars) + adjust_assign(ls, nvars, nexps, &e); + else { + luaK_setoneret(ls->fs, &e); /* close last expression */ + luaK_storevar(ls->fs, &lh->v, &e); + return; /* avoid default */ + } + } + init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ + luaK_storevar(ls->fs, &lh->v, &e); +} + + +static int cond (LexState *ls) { + /* cond -> exp */ + expdesc v; + expr(ls, &v); /* read condition */ + if (v.k == VNIL) v.k = VFALSE; /* 'falses' are all equal here */ + luaK_goiftrue(ls->fs, &v); + return v.f; +} + + +static void gotostat (LexState *ls) { + FuncState *fs = ls->fs; + int line = ls->linenumber; + TString *name = str_checkname(ls); /* label's name */ + Labeldesc *lb = findlabel(ls, name); + if (lb == NULL) /* no label? */ + /* forward jump; will be resolved when the label is declared */ + newgotoentry(ls, name, line, luaK_jump(fs)); + else { /* found a label */ + /* backward jump; will be resolved here */ + int lblevel = stacklevel(fs, lb->nactvar); /* label level */ + if (luaY_nvarstack(fs) > lblevel) /* leaving the scope of a variable? */ + luaK_codeABC(fs, OP_CLOSE, lblevel, 0, 0); + /* create jump and link it to the label */ + luaK_patchlist(fs, luaK_jump(fs), lb->pc); + } +} + + +/* +** Break statement. Semantically equivalent to "goto break". +*/ +static void breakstat (LexState *ls) { + int line = ls->linenumber; + luaX_next(ls); /* skip break */ + newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, luaK_jump(ls->fs)); +} + + +/* +** Check whether there is already a label with the given 'name'. +*/ +static void checkrepeated (LexState *ls, TString *name) { + Labeldesc *lb = findlabel(ls, name); + if (unlikely(lb != NULL)) { /* already defined? */ + const char *msg = "label '%s' already defined on line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line); + luaK_semerror(ls, msg); /* error */ + } +} + + +static void labelstat (LexState *ls, TString *name, int line) { + /* label -> '::' NAME '::' */ + checknext(ls, TK_DBCOLON); /* skip double colon */ + while (ls->t.token == ';' || ls->t.token == TK_DBCOLON) + statement(ls); /* skip other no-op statements */ + checkrepeated(ls, name); /* check for repeated labels */ + createlabel(ls, name, line, block_follow(ls, 0)); +} + + +static void whilestat (LexState *ls, int line) { + /* whilestat -> WHILE cond DO block END */ + FuncState *fs = ls->fs; + int whileinit; + int condexit; + BlockCnt bl; + luaX_next(ls); /* skip WHILE */ + whileinit = luaK_getlabel(fs); + condexit = cond(ls); + enterblock(fs, &bl, 1); + checknext(ls, TK_DO); + block(ls); + luaK_jumpto(fs, whileinit); + check_match(ls, TK_END, TK_WHILE, line); + leaveblock(fs); + luaK_patchtohere(fs, condexit); /* false conditions finish the loop */ +} + + +static void repeatstat (LexState *ls, int line) { + /* repeatstat -> REPEAT block UNTIL cond */ + int condexit; + FuncState *fs = ls->fs; + int repeat_init = luaK_getlabel(fs); + BlockCnt bl1, bl2; + enterblock(fs, &bl1, 1); /* loop block */ + enterblock(fs, &bl2, 0); /* scope block */ + luaX_next(ls); /* skip REPEAT */ + statlist(ls); + check_match(ls, TK_UNTIL, TK_REPEAT, line); + condexit = cond(ls); /* read condition (inside scope block) */ + leaveblock(fs); /* finish scope */ + if (bl2.upval) { /* upvalues? */ + int exit = luaK_jump(fs); /* normal exit must jump over fix */ + luaK_patchtohere(fs, condexit); /* repetition must close upvalues */ + luaK_codeABC(fs, OP_CLOSE, stacklevel(fs, bl2.nactvar), 0, 0); + condexit = luaK_jump(fs); /* repeat after closing upvalues */ + luaK_patchtohere(fs, exit); /* normal exit comes to here */ + } + luaK_patchlist(fs, condexit, repeat_init); /* close the loop */ + leaveblock(fs); /* finish loop */ +} + + +/* +** Read an expression and generate code to put its results in next +** stack slot. +** +*/ +static void exp1 (LexState *ls) { + expdesc e; + expr(ls, &e); + luaK_exp2nextreg(ls->fs, &e); + lua_assert(e.k == VNONRELOC); +} + + +/* +** Fix for instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua). 'back' true means +** a back jump. +*/ +static void fixforjump (FuncState *fs, int pc, int dest, int back) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest - (pc + 1); + if (back) + offset = -offset; + if (unlikely(offset > MAXARG_Bx)) + luaX_syntaxerror(fs->ls, "control structure too long"); + SETARG_Bx(*jmp, offset); +} + + +/* +** Generate code for a 'for' loop. +*/ +static void forbody (LexState *ls, int base, int line, int nvars, int isgen) { + /* forbody -> DO block */ + static const OpCode forprep[2] = {OP_FORPREP, OP_TFORPREP}; + static const OpCode forloop[2] = {OP_FORLOOP, OP_TFORLOOP}; + BlockCnt bl; + FuncState *fs = ls->fs; + int prep, endfor; + checknext(ls, TK_DO); + prep = luaK_codeABx(fs, forprep[isgen], base, 0); + enterblock(fs, &bl, 0); /* scope for declared variables */ + adjustlocalvars(ls, nvars); + luaK_reserveregs(fs, nvars); + block(ls); + leaveblock(fs); /* end of scope for declared variables */ + fixforjump(fs, prep, luaK_getlabel(fs), 0); + if (isgen) { /* generic for? */ + luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars); + luaK_fixline(fs, line); + } + endfor = luaK_codeABx(fs, forloop[isgen], base, 0); + fixforjump(fs, endfor, prep + 1, 1); + luaK_fixline(fs, line); +} + + +static void fornum (LexState *ls, TString *varname, int line) { + /* fornum -> NAME = exp,exp[,exp] forbody */ + FuncState *fs = ls->fs; + int base = fs->freereg; + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvar(ls, varname); + checknext(ls, '='); + exp1(ls); /* initial value */ + checknext(ls, ','); + exp1(ls); /* limit */ + if (testnext(ls, ',')) + exp1(ls); /* optional step */ + else { /* default step = 1 */ + luaK_int(fs, fs->freereg, 1); + luaK_reserveregs(fs, 1); + } + adjustlocalvars(ls, 3); /* control variables */ + forbody(ls, base, line, 1, 0); +} + + +static void forlist (LexState *ls, TString *indexname) { + /* forlist -> NAME {,NAME} IN explist forbody */ + FuncState *fs = ls->fs; + expdesc e; + int nvars = 5; /* gen, state, control, toclose, 'indexname' */ + int line; + int base = fs->freereg; + /* create control variables */ + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + /* create declared variables */ + new_localvar(ls, indexname); + while (testnext(ls, ',')) { + new_localvar(ls, str_checkname(ls)); + nvars++; + } + checknext(ls, TK_IN); + line = ls->linenumber; + adjust_assign(ls, 4, explist(ls, &e), &e); + adjustlocalvars(ls, 4); /* control variables */ + markupval(fs, fs->nactvar); /* last control var. must be closed */ + luaK_checkstack(fs, 3); /* extra space to call generator */ + forbody(ls, base, line, nvars - 4, 1); +} + + +static void forstat (LexState *ls, int line) { + /* forstat -> FOR (fornum | forlist) END */ + FuncState *fs = ls->fs; + TString *varname; + BlockCnt bl; + enterblock(fs, &bl, 1); /* scope for loop and control variables */ + luaX_next(ls); /* skip 'for' */ + varname = str_checkname(ls); /* first variable name */ + switch (ls->t.token) { + case '=': fornum(ls, varname, line); break; + case ',': case TK_IN: forlist(ls, varname); break; + default: luaX_syntaxerror(ls, "'=' or 'in' expected"); + } + check_match(ls, TK_END, TK_FOR, line); + leaveblock(fs); /* loop scope ('break' jumps to this point) */ +} + + +static void test_then_block (LexState *ls, int *escapelist) { + /* test_then_block -> [IF | ELSEIF] cond THEN block */ + BlockCnt bl; + FuncState *fs = ls->fs; + expdesc v; + int jf; /* instruction to skip 'then' code (if condition is false) */ + luaX_next(ls); /* skip IF or ELSEIF */ + expr(ls, &v); /* read condition */ + checknext(ls, TK_THEN); + if (ls->t.token == TK_BREAK) { /* 'if x then break' ? */ + int line = ls->linenumber; + luaK_goiffalse(ls->fs, &v); /* will jump if condition is true */ + luaX_next(ls); /* skip 'break' */ + enterblock(fs, &bl, 0); /* must enter block before 'goto' */ + newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, v.t); + while (testnext(ls, ';')) {} /* skip semicolons */ + if (block_follow(ls, 0)) { /* jump is the entire block? */ + leaveblock(fs); + return; /* and that is it */ + } + else /* must skip over 'then' part if condition is false */ + jf = luaK_jump(fs); + } + else { /* regular case (not a break) */ + luaK_goiftrue(ls->fs, &v); /* skip over block if condition is false */ + enterblock(fs, &bl, 0); + jf = v.f; + } + statlist(ls); /* 'then' part */ + leaveblock(fs); + if (ls->t.token == TK_ELSE || + ls->t.token == TK_ELSEIF) /* followed by 'else'/'elseif'? */ + luaK_concat(fs, escapelist, luaK_jump(fs)); /* must jump over it */ + luaK_patchtohere(fs, jf); +} + + +static void ifstat (LexState *ls, int line) { + /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ + FuncState *fs = ls->fs; + int escapelist = NO_JUMP; /* exit list for finished parts */ + test_then_block(ls, &escapelist); /* IF cond THEN block */ + while (ls->t.token == TK_ELSEIF) + test_then_block(ls, &escapelist); /* ELSEIF cond THEN block */ + if (testnext(ls, TK_ELSE)) + block(ls); /* 'else' part */ + check_match(ls, TK_END, TK_IF, line); + luaK_patchtohere(fs, escapelist); /* patch escape list to 'if' end */ +} + + +static void localfunc (LexState *ls) { + expdesc b; + FuncState *fs = ls->fs; + int fvar = fs->nactvar; /* function's variable index */ + new_localvar(ls, str_checkname(ls)); /* new local variable */ + adjustlocalvars(ls, 1); /* enter its scope */ + body(ls, &b, 0, ls->linenumber); /* function created in next register */ + /* debug information will only see the variable after this point! */ + localdebuginfo(fs, fvar)->startpc = fs->pc; +} + + +static int getlocalattribute (LexState *ls) { + /* ATTRIB -> ['<' Name '>'] */ + if (testnext(ls, '<')) { + const char *attr = getstr(str_checkname(ls)); + checknext(ls, '>'); + if (strcmp(attr, "const") == 0) + return RDKCONST; /* read-only variable */ + else if (strcmp(attr, "close") == 0) + return RDKTOCLOSE; /* to-be-closed variable */ + else + luaK_semerror(ls, + luaO_pushfstring(ls->L, "unknown attribute '%s'", attr)); + } + return VDKREG; /* regular variable */ +} + + +static void checktoclose (LexState *ls, int level) { + if (level != -1) { /* is there a to-be-closed variable? */ + FuncState *fs = ls->fs; + markupval(fs, level + 1); + fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ + luaK_codeABC(fs, OP_TBC, stacklevel(fs, level), 0, 0); + } +} + + +static void localstat (LexState *ls) { + /* stat -> LOCAL NAME ATTRIB { ',' NAME ATTRIB } ['=' explist] */ + FuncState *fs = ls->fs; + int toclose = -1; /* index of to-be-closed variable (if any) */ + Vardesc *var; /* last variable */ + int vidx, kind; /* index and kind of last variable */ + int nvars = 0; + int nexps; + expdesc e; + do { + vidx = new_localvar(ls, str_checkname(ls)); + kind = getlocalattribute(ls); + getlocalvardesc(fs, vidx)->vd.kind = kind; + if (kind == RDKTOCLOSE) { /* to-be-closed? */ + if (toclose != -1) /* one already present? */ + luaK_semerror(ls, "multiple to-be-closed variables in local list"); + toclose = fs->nactvar + nvars; + } + nvars++; + } while (testnext(ls, ',')); + if (testnext(ls, '=')) + nexps = explist(ls, &e); + else { + e.k = VVOID; + nexps = 0; + } + var = getlocalvardesc(fs, vidx); /* get last variable */ + if (nvars == nexps && /* no adjustments? */ + var->vd.kind == RDKCONST && /* last variable is const? */ + luaK_exp2const(fs, &e, &var->k)) { /* compile-time constant? */ + var->vd.kind = RDKCTC; /* variable is a compile-time constant */ + adjustlocalvars(ls, nvars - 1); /* exclude last variable */ + fs->nactvar++; /* but count it */ + } + else { + adjust_assign(ls, nvars, nexps, &e); + adjustlocalvars(ls, nvars); + } + checktoclose(ls, toclose); +} + + +static int funcname (LexState *ls, expdesc *v) { + /* funcname -> NAME {fieldsel} [':' NAME] */ + int ismethod = 0; + singlevar(ls, v); + while (ls->t.token == '.') + fieldsel(ls, v); + if (ls->t.token == ':') { + ismethod = 1; + fieldsel(ls, v); + } + return ismethod; +} + + +static void funcstat (LexState *ls, int line) { + /* funcstat -> FUNCTION funcname body */ + int ismethod; + expdesc v, b; + luaX_next(ls); /* skip FUNCTION */ + ismethod = funcname(ls, &v); + body(ls, &b, ismethod, line); + luaK_storevar(ls->fs, &v, &b); + luaK_fixline(ls->fs, line); /* definition "happens" in the first line */ +} + + +static void exprstat (LexState *ls) { + /* stat -> func | assignment */ + FuncState *fs = ls->fs; + struct LHS_assign v; + suffixedexp(ls, &v.v); + if (ls->t.token == '=' || ls->t.token == ',') { /* stat -> assignment ? */ + v.prev = NULL; + restassign(ls, &v, 1); + } + else { /* stat -> func */ + Instruction *inst; + check_condition(ls, v.v.k == VCALL, "syntax error"); + inst = &getinstruction(fs, &v.v); + SETARG_C(*inst, 1); /* call statement uses no results */ + } +} + + +static void retstat (LexState *ls) { + /* stat -> RETURN [explist] [';'] */ + FuncState *fs = ls->fs; + expdesc e; + int nret; /* number of values being returned */ + int first = luaY_nvarstack(fs); /* first slot to be returned */ + if (block_follow(ls, 1) || ls->t.token == ';') + nret = 0; /* return no values */ + else { + nret = explist(ls, &e); /* optional return values */ + if (hasmultret(e.k)) { + luaK_setmultret(fs, &e); + if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */ + SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); + lua_assert(GETARG_A(getinstruction(fs,&e)) == luaY_nvarstack(fs)); + } + nret = LUA_MULTRET; /* return all values */ + } + else { + if (nret == 1) /* only one single value? */ + first = luaK_exp2anyreg(fs, &e); /* can use original slot */ + else { /* values must go to the top of the stack */ + luaK_exp2nextreg(fs, &e); + lua_assert(nret == fs->freereg - first); + } + } + } + luaK_ret(fs, first, nret); + testnext(ls, ';'); /* skip optional semicolon */ +} + + +static void statement (LexState *ls) { + int line = ls->linenumber; /* may be needed for error messages */ + enterlevel(ls); + switch (ls->t.token) { + case ';': { /* stat -> ';' (empty statement) */ + luaX_next(ls); /* skip ';' */ + break; + } + case TK_IF: { /* stat -> ifstat */ + ifstat(ls, line); + break; + } + case TK_WHILE: { /* stat -> whilestat */ + whilestat(ls, line); + break; + } + case TK_DO: { /* stat -> DO block END */ + luaX_next(ls); /* skip DO */ + block(ls); + check_match(ls, TK_END, TK_DO, line); + break; + } + case TK_FOR: { /* stat -> forstat */ + forstat(ls, line); + break; + } + case TK_REPEAT: { /* stat -> repeatstat */ + repeatstat(ls, line); + break; + } + case TK_FUNCTION: { /* stat -> funcstat */ + funcstat(ls, line); + break; + } + case TK_LOCAL: { /* stat -> localstat */ + luaX_next(ls); /* skip LOCAL */ + if (testnext(ls, TK_FUNCTION)) /* local function? */ + localfunc(ls); + else + localstat(ls); + break; + } + case TK_DBCOLON: { /* stat -> label */ + luaX_next(ls); /* skip double colon */ + labelstat(ls, str_checkname(ls), line); + break; + } + case TK_RETURN: { /* stat -> retstat */ + luaX_next(ls); /* skip RETURN */ + retstat(ls); + break; + } + case TK_BREAK: { /* stat -> breakstat */ + breakstat(ls); + break; + } + case TK_GOTO: { /* stat -> 'goto' NAME */ + luaX_next(ls); /* skip 'goto' */ + gotostat(ls); + break; + } + default: { /* stat -> func | assignment */ + exprstat(ls); + break; + } + } + lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && + ls->fs->freereg >= luaY_nvarstack(ls->fs)); + ls->fs->freereg = luaY_nvarstack(ls->fs); /* free registers */ + leavelevel(ls); +} + +/* }====================================================================== */ + + +/* +** compiles the main function, which is a regular vararg function with an +** upvalue named LUA_ENV +*/ +static void mainfunc (LexState *ls, FuncState *fs) { + BlockCnt bl; + Upvaldesc *env; + open_func(ls, fs, &bl); + setvararg(fs, 0); /* main function is always declared vararg */ + env = allocupvalue(fs); /* ...set environment upvalue */ + env->instack = 1; + env->idx = 0; + env->kind = VDKREG; + env->name = ls->envn; + luaC_objbarrier(ls->L, fs->f, env->name); + luaX_next(ls); /* read first token */ + statlist(ls); /* parse main body */ + check(ls, TK_EOS); + close_func(ls); +} + + +LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar) { + LexState lexstate; + FuncState funcstate; + LClosure *cl = luaF_newLclosure(L, 1); /* create main closure */ + setclLvalue2s(L, L->top, cl); /* anchor it (to avoid being collected) */ + luaD_inctop(L); + lexstate.h = luaH_new(L); /* create table for scanner */ + sethvalue2s(L, L->top, lexstate.h); /* anchor it */ + luaD_inctop(L); + funcstate.f = cl->p = luaF_newproto(L); + luaC_objbarrier(L, cl, cl->p); + funcstate.f->source = luaS_new(L, name); /* create and anchor TString */ + luaC_objbarrier(L, funcstate.f, funcstate.f->source); + lexstate.buff = buff; + lexstate.dyd = dyd; + dyd->actvar.n = dyd->gt.n = dyd->label.n = 0; + luaX_setinput(L, &lexstate, z, funcstate.f->source, firstchar); + mainfunc(&lexstate, &funcstate); + lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs); + /* all scopes should be correctly finished */ + lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0); + L->top--; /* remove scanner's table */ + return cl; /* closure is on the stack, too */ +} + diff --git a/Lua/lparser.h b/Lua/lparser.h new file mode 100644 index 00000000..2e6dae72 --- /dev/null +++ b/Lua/lparser.h @@ -0,0 +1,171 @@ +/* +** $Id: lparser.h $ +** Lua Parser +** See Copyright Notice in lua.h +*/ + +#ifndef lparser_h +#define lparser_h + +#include "llimits.h" +#include "lobject.h" +#include "lzio.h" + + +/* +** Expression and variable descriptor. +** Code generation for variables and expressions can be delayed to allow +** optimizations; An 'expdesc' structure describes a potentially-delayed +** variable/expression. It has a description of its "main" value plus a +** list of conditional jumps that can also produce its value (generated +** by short-circuit operators 'and'/'or'). +*/ + +/* kinds of variables/expressions */ +typedef enum { + VVOID, /* when 'expdesc' describes the last expression of a list, + this kind means an empty list (so, no expression) */ + VNIL, /* constant nil */ + VTRUE, /* constant true */ + VFALSE, /* constant false */ + VK, /* constant in 'k'; info = index of constant in 'k' */ + VKFLT, /* floating constant; nval = numerical float value */ + VKINT, /* integer constant; ival = numerical integer value */ + VKSTR, /* string constant; strval = TString address; + (string is fixed by the lexer) */ + VNONRELOC, /* expression has its value in a fixed register; + info = result register */ + VLOCAL, /* local variable; var.sidx = stack index (local register); + var.vidx = relative index in 'actvar.arr' */ + VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */ + VCONST, /* compile-time variable; + info = absolute index in 'actvar.arr' */ + VINDEXED, /* indexed variable; + ind.t = table register; + ind.idx = key's R index */ + VINDEXUP, /* indexed upvalue; + ind.t = table upvalue; + ind.idx = key's K index */ + VINDEXI, /* indexed variable with constant integer; + ind.t = table register; + ind.idx = key's value */ + VINDEXSTR, /* indexed variable with literal string; + ind.t = table register; + ind.idx = key's K index */ + VJMP, /* expression is a test/comparison; + info = pc of corresponding jump instruction */ + VRELOC, /* expression can put result in any register; + info = instruction pc */ + VCALL, /* expression is a function call; info = instruction pc */ + VVARARG /* vararg expression; info = instruction pc */ +} expkind; + + +#define vkisvar(k) (VLOCAL <= (k) && (k) <= VINDEXSTR) +#define vkisindexed(k) (VINDEXED <= (k) && (k) <= VINDEXSTR) + + +typedef struct expdesc { + expkind k; + union { + lua_Integer ival; /* for VKINT */ + lua_Number nval; /* for VKFLT */ + TString *strval; /* for VKSTR */ + int info; /* for generic use */ + struct { /* for indexed variables */ + short idx; /* index (R or "long" K) */ + lu_byte t; /* table (register or upvalue) */ + } ind; + struct { /* for local variables */ + lu_byte sidx; /* index in the stack */ + unsigned short vidx; /* compiler index (in 'actvar.arr') */ + } var; + } u; + int t; /* patch list of 'exit when true' */ + int f; /* patch list of 'exit when false' */ +} expdesc; + + +/* kinds of variables */ +#define VDKREG 0 /* regular */ +#define RDKCONST 1 /* constant */ +#define RDKTOCLOSE 2 /* to-be-closed */ +#define RDKCTC 3 /* compile-time constant */ + +/* description of an active local variable */ +typedef union Vardesc { + struct { + TValuefields; /* constant value (if it is a compile-time constant) */ + lu_byte kind; + lu_byte sidx; /* index of the variable in the stack */ + short pidx; /* index of the variable in the Proto's 'locvars' array */ + TString *name; /* variable name */ + } vd; + TValue k; /* constant value (if any) */ +} Vardesc; + + + +/* description of pending goto statements and label statements */ +typedef struct Labeldesc { + TString *name; /* label identifier */ + int pc; /* position in code */ + int line; /* line where it appeared */ + lu_byte nactvar; /* number of active variables in that position */ + lu_byte close; /* goto that escapes upvalues */ +} Labeldesc; + + +/* list of labels or gotos */ +typedef struct Labellist { + Labeldesc *arr; /* array */ + int n; /* number of entries in use */ + int size; /* array size */ +} Labellist; + + +/* dynamic structures used by the parser */ +typedef struct Dyndata { + struct { /* list of all active local variables */ + Vardesc *arr; + int n; + int size; + } actvar; + Labellist gt; /* list of pending gotos */ + Labellist label; /* list of active labels */ +} Dyndata; + + +/* control of blocks */ +struct BlockCnt; /* defined in lparser.c */ + + +/* state needed to generate code for a given function */ +typedef struct FuncState { + Proto *f; /* current function header */ + struct FuncState *prev; /* enclosing function */ + struct LexState *ls; /* lexical state */ + struct BlockCnt *bl; /* chain of current blocks */ + int pc; /* next position to code (equivalent to 'ncode') */ + int lasttarget; /* 'label' of last 'jump label' */ + int previousline; /* last line that was saved in 'lineinfo' */ + int nk; /* number of elements in 'k' */ + int np; /* number of elements in 'p' */ + int nabslineinfo; /* number of elements in 'abslineinfo' */ + int firstlocal; /* index of first local var (in Dyndata array) */ + int firstlabel; /* index of first label (in 'dyd->label->arr') */ + short ndebugvars; /* number of elements in 'f->locvars' */ + lu_byte nactvar; /* number of active local variables */ + lu_byte nups; /* number of upvalues */ + lu_byte freereg; /* first free register */ + lu_byte iwthabs; /* instructions issued since last absolute line info */ + lu_byte needclose; /* function needs to close upvalues when returning */ +} FuncState; + + +LUAI_FUNC int luaY_nvarstack (FuncState *fs); +LUAI_FUNC LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar); + + +#endif diff --git a/Lua/lprefix.h b/Lua/lprefix.h new file mode 100644 index 00000000..484f2ad6 --- /dev/null +++ b/Lua/lprefix.h @@ -0,0 +1,45 @@ +/* +** $Id: lprefix.h $ +** Definitions for Lua code that must come before any other header file +** See Copyright Notice in lua.h +*/ + +#ifndef lprefix_h +#define lprefix_h + + +/* +** Allows POSIX/XSI stuff +*/ +#if !defined(LUA_USE_C89) /* { */ + +#if !defined(_XOPEN_SOURCE) +#define _XOPEN_SOURCE 600 +#elif _XOPEN_SOURCE == 0 +#undef _XOPEN_SOURCE /* use -D_XOPEN_SOURCE=0 to undefine it */ +#endif + +/* +** Allows manipulation of large files in gcc and some other compilers +*/ +#if !defined(LUA_32BITS) && !defined(_FILE_OFFSET_BITS) +#define _LARGEFILE_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#endif + +#endif /* } */ + + +/* +** Windows stuff +*/ +#if defined(_WIN32) /* { */ + +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS /* avoid warnings about ISO C functions */ +#endif + +#endif /* } */ + +#endif + diff --git a/Lua/lstate.c b/Lua/lstate.c new file mode 100644 index 00000000..1c7b8791 --- /dev/null +++ b/Lua/lstate.c @@ -0,0 +1,434 @@ +/* +** $Id: lstate.c $ +** Global State +** See Copyright Notice in lua.h +*/ + +#define lstate_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "llex.h" +#include "lmem.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + + +/* +** thread state + extra space +*/ +typedef struct LX { + lu_byte extra_[LUA_EXTRASPACE]; + lua_State l; +} LX; + + +/* +** Main thread combines a thread state and the global state +*/ +typedef struct LG { + LX l; + global_State g; +} LG; + + + +#define fromstate(L) (cast(LX *, cast(lu_byte *, (L)) - offsetof(LX, l))) + + +/* +** A macro to create a "random" seed when a state is created; +** the seed is used to randomize string hashes. +*/ +#if !defined(luai_makeseed) + +#include + +/* +** Compute an initial seed with some level of randomness. +** Rely on Address Space Layout Randomization (if present) and +** current time. +*/ +#define addbuff(b,p,e) \ + { size_t t = cast_sizet(e); \ + memcpy(b + p, &t, sizeof(t)); p += sizeof(t); } + +static unsigned int luai_makeseed (lua_State *L) { + char buff[3 * sizeof(size_t)]; + unsigned int h = cast_uint(time(NULL)); + int p = 0; + addbuff(buff, p, L); /* heap variable */ + addbuff(buff, p, &h); /* local variable */ + addbuff(buff, p, &lua_newstate); /* public function */ + lua_assert(p == sizeof(buff)); + return luaS_hash(buff, p, h); +} + +#endif + + +/* +** set GCdebt to a new value keeping the value (totalbytes + GCdebt) +** invariant (and avoiding underflows in 'totalbytes') +*/ +void luaE_setdebt (global_State *g, l_mem debt) { + l_mem tb = gettotalbytes(g); + lua_assert(tb > 0); + if (debt < tb - MAX_LMEM) + debt = tb - MAX_LMEM; /* will make 'totalbytes == MAX_LMEM' */ + g->totalbytes = tb - debt; + g->GCdebt = debt; +} + + +LUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) { + UNUSED(L); UNUSED(limit); + return LUAI_MAXCCALLS; /* warning?? */ +} + + +CallInfo *luaE_extendCI (lua_State *L) { + CallInfo *ci; + lua_assert(L->ci->next == NULL); + ci = luaM_new(L, CallInfo); + lua_assert(L->ci->next == NULL); + L->ci->next = ci; + ci->previous = L->ci; + ci->next = NULL; + ci->u.l.trap = 0; + L->nci++; + return ci; +} + + +/* +** free all CallInfo structures not in use by a thread +*/ +void luaE_freeCI (lua_State *L) { + CallInfo *ci = L->ci; + CallInfo *next = ci->next; + ci->next = NULL; + while ((ci = next) != NULL) { + next = ci->next; + luaM_free(L, ci); + L->nci--; + } +} + + +/* +** free half of the CallInfo structures not in use by a thread, +** keeping the first one. +*/ +void luaE_shrinkCI (lua_State *L) { + CallInfo *ci = L->ci->next; /* first free CallInfo */ + CallInfo *next; + if (ci == NULL) + return; /* no extra elements */ + while ((next = ci->next) != NULL) { /* two extra elements? */ + CallInfo *next2 = next->next; /* next's next */ + ci->next = next2; /* remove next from the list */ + L->nci--; + luaM_free(L, next); /* free next */ + if (next2 == NULL) + break; /* no more elements */ + else { + next2->previous = ci; + ci = next2; /* continue */ + } + } +} + + +/* +** Called when 'getCcalls(L)' larger or equal to LUAI_MAXCCALLS. +** If equal, raises an overflow error. If value is larger than +** LUAI_MAXCCALLS (which means it is handling an overflow) but +** not much larger, does not report an error (to allow overflow +** handling to work). +*/ +void luaE_checkcstack (lua_State *L) { + if (getCcalls(L) == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (getCcalls(L) >= (LUAI_MAXCCALLS / 10 * 11)) + luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ +} + + +LUAI_FUNC void luaE_incCstack (lua_State *L) { + L->nCcalls++; + if (unlikely(getCcalls(L) >= LUAI_MAXCCALLS)) + luaE_checkcstack(L); +} + + +static void stack_init (lua_State *L1, lua_State *L) { + int i; CallInfo *ci; + /* initialize stack array */ + L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, StackValue); + for (i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) + setnilvalue(s2v(L1->stack + i)); /* erase new stack */ + L1->top = L1->stack; + L1->stack_last = L1->stack + BASIC_STACK_SIZE; + /* initialize first ci */ + ci = &L1->base_ci; + ci->next = ci->previous = NULL; + ci->callstatus = CIST_C; + ci->func = L1->top; + ci->u.c.k = NULL; + ci->nresults = 0; + setnilvalue(s2v(L1->top)); /* 'function' entry for this 'ci' */ + L1->top++; + ci->top = L1->top + LUA_MINSTACK; + L1->ci = ci; +} + + +static void freestack (lua_State *L) { + if (L->stack == NULL) + return; /* stack not completely built yet */ + L->ci = &L->base_ci; /* free the entire 'ci' list */ + luaE_freeCI(L); + lua_assert(L->nci == 0); + luaM_freearray(L, L->stack, stacksize(L) + EXTRA_STACK); /* free stack */ +} + + +/* +** Create registry table and its predefined values +*/ +static void init_registry (lua_State *L, global_State *g) { + TValue temp; + /* create registry */ + Table *registry = luaH_new(L); + sethvalue(L, &g->l_registry, registry); + luaH_resize(L, registry, LUA_RIDX_LAST, 0); + /* registry[LUA_RIDX_MAINTHREAD] = L */ + setthvalue(L, &temp, L); /* temp = L */ + luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &temp); + /* registry[LUA_RIDX_GLOBALS] = table of globals */ + sethvalue(L, &temp, luaH_new(L)); /* temp = new table (global table) */ + luaH_setint(L, registry, LUA_RIDX_GLOBALS, &temp); +} + + +/* +** open parts of the state that may cause memory-allocation errors. +** ('g->nilvalue' being a nil value flags that the state was completely +** build.) +*/ +static void f_luaopen (lua_State *L, void *ud) { + global_State *g = G(L); + UNUSED(ud); + stack_init(L, L); /* init stack */ + init_registry(L, g); + luaS_init(L); + luaT_init(L); + luaX_init(L); + g->gcrunning = 1; /* allow gc */ + setnilvalue(&g->nilvalue); + luai_userstateopen(L); +} + + +/* +** preinitialize a thread with consistent values without allocating +** any memory (to avoid errors) +*/ +static void preinit_thread (lua_State *L, global_State *g) { + G(L) = g; + L->stack = NULL; + L->ci = NULL; + L->nci = 0; + L->twups = L; /* thread has no upvalues */ + L->errorJmp = NULL; + L->hook = NULL; + L->hookmask = 0; + L->basehookcount = 0; + L->allowhook = 1; + resethookcount(L); + L->openupval = NULL; + L->status = LUA_OK; + L->errfunc = 0; + L->oldpc = 0; +} + + +static void close_state (lua_State *L) { + global_State *g = G(L); + luaF_close(L, L->stack, CLOSEPROTECT); /* close all upvalues */ + luaC_freeallobjects(L); /* collect all objects */ + if (ttisnil(&g->nilvalue)) /* closing a fully built state? */ + luai_userstateclose(L); + luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); + freestack(L); + lua_assert(gettotalbytes(g) == sizeof(LG)); + (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */ +} + + +LUA_API lua_State *lua_newthread (lua_State *L) { + global_State *g; + lua_State *L1; + lua_lock(L); + g = G(L); + luaC_checkGC(L); + /* create new thread */ + L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l; + L1->marked = luaC_white(g); + L1->tt = LUA_VTHREAD; + /* link it on list 'allgc' */ + L1->next = g->allgc; + g->allgc = obj2gco(L1); + /* anchor it on L stack */ + setthvalue2s(L, L->top, L1); + api_incr_top(L); + preinit_thread(L1, g); + L1->nCcalls = 0; + L1->hookmask = L->hookmask; + L1->basehookcount = L->basehookcount; + L1->hook = L->hook; + resethookcount(L1); + /* initialize L1 extra space */ + memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread), + LUA_EXTRASPACE); + luai_userstatethread(L, L1); + stack_init(L1, L); /* init stack */ + lua_unlock(L); + return L1; +} + + +void luaE_freethread (lua_State *L, lua_State *L1) { + LX *l = fromstate(L1); + luaF_close(L1, L1->stack, NOCLOSINGMETH); /* close all upvalues */ + lua_assert(L1->openupval == NULL); + luai_userstatefree(L, L1); + freestack(L1); + luaM_free(L, l); +} + + +int lua_resetthread (lua_State *L) { + CallInfo *ci; + int status; + lua_lock(L); + L->ci = ci = &L->base_ci; /* unwind CallInfo list */ + setnilvalue(s2v(L->stack)); /* 'function' entry for basic 'ci' */ + ci->func = L->stack; + ci->callstatus = CIST_C; + status = luaF_close(L, L->stack, CLOSEPROTECT); + if (status != CLOSEPROTECT) /* real errors? */ + luaD_seterrorobj(L, status, L->stack + 1); + else { + status = LUA_OK; + L->top = L->stack + 1; + } + ci->top = L->top + LUA_MINSTACK; + L->status = status; + lua_unlock(L); + return status; +} + + +LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { + int i; + lua_State *L; + global_State *g; + LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); + if (l == NULL) return NULL; + L = &l->l.l; + g = &l->g; + L->tt = LUA_VTHREAD; + g->currentwhite = bitmask(WHITE0BIT); + L->marked = luaC_white(g); + preinit_thread(L, g); + g->allgc = obj2gco(L); /* by now, only object is the main thread */ + L->next = NULL; + L->nCcalls = 0; + incnny(L); /* main thread is always non yieldable */ + g->frealloc = f; + g->ud = ud; + g->warnf = NULL; + g->ud_warn = NULL; + g->mainthread = L; + g->seed = luai_makeseed(L); + g->gcrunning = 0; /* no GC while building state */ + g->strt.size = g->strt.nuse = 0; + g->strt.hash = NULL; + setnilvalue(&g->l_registry); + g->panic = NULL; + g->gcstate = GCSpause; + g->gckind = KGC_INC; + g->gcemergency = 0; + g->finobj = g->tobefnz = g->fixedgc = NULL; + g->firstold1 = g->survival = g->old1 = g->reallyold = NULL; + g->finobjsur = g->finobjold1 = g->finobjrold = NULL; + g->sweepgc = NULL; + g->gray = g->grayagain = NULL; + g->weak = g->ephemeron = g->allweak = NULL; + g->twups = NULL; + g->totalbytes = sizeof(LG); + g->GCdebt = 0; + g->lastatomic = 0; + setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */ + setgcparam(g->gcpause, LUAI_GCPAUSE); + setgcparam(g->gcstepmul, LUAI_GCMUL); + g->gcstepsize = LUAI_GCSTEPSIZE; + setgcparam(g->genmajormul, LUAI_GENMAJORMUL); + g->genminormul = LUAI_GENMINORMUL; + for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL; + if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) { + /* memory allocation error: free partial state */ + close_state(L); + L = NULL; + } + return L; +} + + +LUA_API void lua_close (lua_State *L) { + lua_lock(L); + L = G(L)->mainthread; /* only the main thread can be closed */ + close_state(L); +} + + +void luaE_warning (lua_State *L, const char *msg, int tocont) { + lua_WarnFunction wf = G(L)->warnf; + if (wf != NULL) + wf(G(L)->ud_warn, msg, tocont); +} + + +/* +** Generate a warning from an error message +*/ +void luaE_warnerror (lua_State *L, const char *where) { + TValue *errobj = s2v(L->top - 1); /* error object */ + const char *msg = (ttisstring(errobj)) + ? svalue(errobj) + : "error object is not a string"; + /* produce warning "error in %s (%s)" (where, msg) */ + luaE_warning(L, "error in ", 1); + luaE_warning(L, where, 1); + luaE_warning(L, " (", 1); + luaE_warning(L, msg, 1); + luaE_warning(L, ")", 0); +} + diff --git a/Lua/lstate.h b/Lua/lstate.h new file mode 100644 index 00000000..cbcf07e2 --- /dev/null +++ b/Lua/lstate.h @@ -0,0 +1,365 @@ +/* +** $Id: lstate.h $ +** Global State +** See Copyright Notice in lua.h +*/ + +#ifndef lstate_h +#define lstate_h + +#include "lua.h" + +#include "lobject.h" +#include "ltm.h" +#include "lzio.h" + + +/* +** Some notes about garbage-collected objects: All objects in Lua must +** be kept somehow accessible until being freed, so all objects always +** belong to one (and only one) of these lists, using field 'next' of +** the 'CommonHeader' for the link: +** +** 'allgc': all objects not marked for finalization; +** 'finobj': all objects marked for finalization; +** 'tobefnz': all objects ready to be finalized; +** 'fixedgc': all objects that are not to be collected (currently +** only small strings, such as reserved words). +** +** For the generational collector, some of these lists have marks for +** generations. Each mark points to the first element in the list for +** that particular generation; that generation goes until the next mark. +** +** 'allgc' -> 'survival': new objects; +** 'survival' -> 'old': objects that survived one collection; +** 'old1' -> 'reallyold': objects that became old in last collection; +** 'reallyold' -> NULL: objects old for more than one cycle. +** +** 'finobj' -> 'finobjsur': new objects marked for finalization; +** 'finobjsur' -> 'finobjold1': survived """"; +** 'finobjold1' -> 'finobjrold': just old """"; +** 'finobjrold' -> NULL: really old """". +** +** All lists can contain elements older than their main ages, due +** to 'luaC_checkfinalizer' and 'udata2finalize', which move +** objects between the normal lists and the "marked for finalization" +** lists. Moreover, barriers can age young objects in young lists as +** OLD0, which then become OLD1. However, a list never contains +** elements younger than their main ages. +** +** The generational collector also uses a pointer 'firstold1', which +** points to the first OLD1 object in the list. It is used to optimize +** 'markold'. (Potentially OLD1 objects can be anywhere between 'allgc' +** and 'reallyold', but often the list has no OLD1 objects or they are +** after 'old1'.) Note the difference between it and 'old1': +** 'firstold1': no OLD1 objects before this point; there can be all +** ages after it. +** 'old1': no objects younger than OLD1 after this point. +*/ + +/* +** Moreover, there is another set of lists that control gray objects. +** These lists are linked by fields 'gclist'. (All objects that +** can become gray have such a field. The field is not the same +** in all objects, but it always has this name.) Any gray object +** must belong to one of these lists, and all objects in these lists +** must be gray (with two exceptions explained below): +** +** 'gray': regular gray objects, still waiting to be visited. +** 'grayagain': objects that must be revisited at the atomic phase. +** That includes +** - black objects got in a write barrier; +** - all kinds of weak tables during propagation phase; +** - all threads. +** 'weak': tables with weak values to be cleared; +** 'ephemeron': ephemeron tables with white->white entries; +** 'allweak': tables with weak keys and/or weak values to be cleared. +** +** The exceptions to that "gray rule" are: +** - TOUCHED2 objects in generational mode stay in a gray list (because +** they must be visited again at the end of the cycle), but they are +** marked black because assignments to them must activate barriers (to +** move them back to TOUCHED1). +** - Open upvales are kept gray to avoid barriers, but they stay out +** of gray lists. (They don't even have a 'gclist' field.) +*/ + + + +/* +** About 'nCcalls': This count has two parts: the lower 16 bits counts +** the number of recursive invocations in the C stack; the higher +** 16 bits counts the number of non-yieldable calls in the stack. +** (They are together so that we can change and save both with one +** instruction.) +*/ + + +/* true if this thread does not have non-yieldable calls in the stack */ +#define yieldable(L) (((L)->nCcalls & 0xffff0000) == 0) + +/* real number of C calls */ +#define getCcalls(L) ((L)->nCcalls & 0xffff) + + +/* Increment the number of non-yieldable calls */ +#define incnny(L) ((L)->nCcalls += 0x10000) + +/* Decrement the number of non-yieldable calls */ +#define decnny(L) ((L)->nCcalls -= 0x10000) + +/* Non-yieldable call increment */ +#define nyci (0x10000 | 1) + + + + +struct lua_longjmp; /* defined in ldo.c */ + + +/* +** Atomic type (relative to signals) to better ensure that 'lua_sethook' +** is thread safe +*/ +#if !defined(l_signalT) +#include +#define l_signalT sig_atomic_t +#endif + + +/* +** Extra stack space to handle TM calls and some other extras. This +** space is not included in 'stack_last'. It is used only to avoid stack +** checks, either because the element will be promptly popped or because +** there will be a stack check soon after the push. Function frames +** never use this extra space, so it does not need to be kept clean. +*/ +#define EXTRA_STACK 5 + + +#define BASIC_STACK_SIZE (2*LUA_MINSTACK) + +#define stacksize(th) cast_int((th)->stack_last - (th)->stack) + + +/* kinds of Garbage Collection */ +#define KGC_INC 0 /* incremental gc */ +#define KGC_GEN 1 /* generational gc */ + + +typedef struct stringtable { + TString **hash; + int nuse; /* number of elements */ + int size; +} stringtable; + + +/* +** Information about a call. +*/ +typedef struct CallInfo { + StkId func; /* function index in the stack */ + StkId top; /* top for this function */ + struct CallInfo *previous, *next; /* dynamic call link */ + union { + struct { /* only for Lua functions */ + const Instruction *savedpc; + volatile l_signalT trap; + int nextraargs; /* # of extra arguments in vararg functions */ + } l; + struct { /* only for C functions */ + lua_KFunction k; /* continuation in case of yields */ + ptrdiff_t old_errfunc; + lua_KContext ctx; /* context info. in case of yields */ + } c; + } u; + union { + int funcidx; /* called-function index */ + int nyield; /* number of values yielded */ + struct { /* info about transferred values (for call/return hooks) */ + unsigned short ftransfer; /* offset of first value transferred */ + unsigned short ntransfer; /* number of values transferred */ + } transferinfo; + } u2; + short nresults; /* expected number of results from this function */ + unsigned short callstatus; +} CallInfo; + + +/* +** Bits in CallInfo status +*/ +#define CIST_OAH (1<<0) /* original value of 'allowhook' */ +#define CIST_C (1<<1) /* call is running a C function */ +#define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */ +#define CIST_HOOKED (1<<3) /* call is running a debug hook */ +#define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ +#define CIST_TAIL (1<<5) /* call was tail called */ +#define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ +#define CIST_FIN (1<<7) /* call is running a finalizer */ +#define CIST_TRAN (1<<8) /* 'ci' has transfer information */ +#if defined(LUA_COMPAT_LT_LE) +#define CIST_LEQ (1<<9) /* using __lt for __le */ +#endif + +/* active function is a Lua function */ +#define isLua(ci) (!((ci)->callstatus & CIST_C)) + +/* call is running Lua code (not a hook) */ +#define isLuacode(ci) (!((ci)->callstatus & (CIST_C | CIST_HOOKED))) + +/* assume that CIST_OAH has offset 0 and that 'v' is strictly 0/1 */ +#define setoah(st,v) ((st) = ((st) & ~CIST_OAH) | (v)) +#define getoah(st) ((st) & CIST_OAH) + + +/* +** 'global state', shared by all threads of this state +*/ +typedef struct global_State { + lua_Alloc frealloc; /* function to reallocate memory */ + void *ud; /* auxiliary data to 'frealloc' */ + l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ + l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ + lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ + lu_mem lastatomic; /* see function 'genstep' in file 'lgc.c' */ + stringtable strt; /* hash table for strings */ + TValue l_registry; + TValue nilvalue; /* a nil value */ + unsigned int seed; /* randomized seed for hashes */ + lu_byte currentwhite; + lu_byte gcstate; /* state of garbage collector */ + lu_byte gckind; /* kind of GC running */ + lu_byte genminormul; /* control for minor generational collections */ + lu_byte genmajormul; /* control for major generational collections */ + lu_byte gcrunning; /* true if GC is running */ + lu_byte gcemergency; /* true if this is an emergency collection */ + lu_byte gcpause; /* size of pause between successive GCs */ + lu_byte gcstepmul; /* GC "speed" */ + lu_byte gcstepsize; /* (log2 of) GC granularity */ + GCObject *allgc; /* list of all collectable objects */ + GCObject **sweepgc; /* current position of sweep in list */ + GCObject *finobj; /* list of collectable objects with finalizers */ + GCObject *gray; /* list of gray objects */ + GCObject *grayagain; /* list of objects to be traversed atomically */ + GCObject *weak; /* list of tables with weak values */ + GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ + GCObject *allweak; /* list of all-weak tables */ + GCObject *tobefnz; /* list of userdata to be GC */ + GCObject *fixedgc; /* list of objects not to be collected */ + /* fields for generational collector */ + GCObject *survival; /* start of objects that survived one GC cycle */ + GCObject *old1; /* start of old1 objects */ + GCObject *reallyold; /* objects more than one cycle old ("really old") */ + GCObject *firstold1; /* first OLD1 object in the list (if any) */ + GCObject *finobjsur; /* list of survival objects with finalizers */ + GCObject *finobjold1; /* list of old1 objects with finalizers */ + GCObject *finobjrold; /* list of really old objects with finalizers */ + struct lua_State *twups; /* list of threads with open upvalues */ + lua_CFunction panic; /* to be called in unprotected errors */ + struct lua_State *mainthread; + TString *memerrmsg; /* message for memory-allocation errors */ + TString *tmname[TM_N]; /* array with tag-method names */ + struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */ + TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ + lua_WarnFunction warnf; /* warning function */ + void *ud_warn; /* auxiliary data to 'warnf' */ +} global_State; + + +/* +** 'per thread' state +*/ +struct lua_State { + CommonHeader; + lu_byte status; + lu_byte allowhook; + unsigned short nci; /* number of items in 'ci' list */ + StkId top; /* first free slot in the stack */ + global_State *l_G; + CallInfo *ci; /* call info for current function */ + StkId stack_last; /* end of stack (last element + 1) */ + StkId stack; /* stack base */ + UpVal *openupval; /* list of open upvalues in this stack */ + GCObject *gclist; + struct lua_State *twups; /* list of threads with open upvalues */ + struct lua_longjmp *errorJmp; /* current error recover point */ + CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ + volatile lua_Hook hook; + ptrdiff_t errfunc; /* current error handling function (stack index) */ + l_uint32 nCcalls; /* number of nested (non-yieldable | C) calls */ + int oldpc; /* last pc traced */ + int basehookcount; + int hookcount; + volatile l_signalT hookmask; +}; + + +#define G(L) (L->l_G) + + +/* +** Union of all collectable objects (only for conversions) +** ISO C99, 6.5.2.3 p.5: +** "if a union contains several structures that share a common initial +** sequence [...], and if the union object currently contains one +** of these structures, it is permitted to inspect the common initial +** part of any of them anywhere that a declaration of the complete type +** of the union is visible." +*/ +union GCUnion { + GCObject gc; /* common header */ + struct TString ts; + struct Udata u; + union Closure cl; + struct Table h; + struct Proto p; + struct lua_State th; /* thread */ + struct UpVal upv; +}; + + +/* +** ISO C99, 6.7.2.1 p.14: +** "A pointer to a union object, suitably converted, points to each of +** its members [...], and vice versa." +*/ +#define cast_u(o) cast(union GCUnion *, (o)) + +/* macros to convert a GCObject into a specific value */ +#define gco2ts(o) \ + check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts)) +#define gco2u(o) check_exp((o)->tt == LUA_VUSERDATA, &((cast_u(o))->u)) +#define gco2lcl(o) check_exp((o)->tt == LUA_VLCL, &((cast_u(o))->cl.l)) +#define gco2ccl(o) check_exp((o)->tt == LUA_VCCL, &((cast_u(o))->cl.c)) +#define gco2cl(o) \ + check_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl)) +#define gco2t(o) check_exp((o)->tt == LUA_VTABLE, &((cast_u(o))->h)) +#define gco2p(o) check_exp((o)->tt == LUA_VPROTO, &((cast_u(o))->p)) +#define gco2th(o) check_exp((o)->tt == LUA_VTHREAD, &((cast_u(o))->th)) +#define gco2upv(o) check_exp((o)->tt == LUA_VUPVAL, &((cast_u(o))->upv)) + + +/* +** macro to convert a Lua object into a GCObject +** (The access to 'tt' tries to ensure that 'v' is actually a Lua object.) +*/ +#define obj2gco(v) check_exp((v)->tt >= LUA_TSTRING, &(cast_u(v)->gc)) + + +/* actual number of total bytes allocated */ +#define gettotalbytes(g) cast(lu_mem, (g)->totalbytes + (g)->GCdebt) + +LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); +LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); +LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); +LUAI_FUNC void luaE_freeCI (lua_State *L); +LUAI_FUNC void luaE_shrinkCI (lua_State *L); +LUAI_FUNC void luaE_checkcstack (lua_State *L); +LUAI_FUNC void luaE_incCstack (lua_State *L); +LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont); +LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where); + + +#endif + diff --git a/Lua/lstring.c b/Lua/lstring.c new file mode 100644 index 00000000..138871c7 --- /dev/null +++ b/Lua/lstring.c @@ -0,0 +1,273 @@ +/* +** $Id: lstring.c $ +** String table (keeps all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + +#define lstring_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" + + +/* +** Maximum size for string table. +*/ +#define MAXSTRTB cast_int(luaM_limitN(MAX_INT, TString*)) + + +/* +** equality for long strings +*/ +int luaS_eqlngstr (TString *a, TString *b) { + size_t len = a->u.lnglen; + lua_assert(a->tt == LUA_VLNGSTR && b->tt == LUA_VLNGSTR); + return (a == b) || /* same instance or... */ + ((len == b->u.lnglen) && /* equal length and ... */ + (memcmp(getstr(a), getstr(b), len) == 0)); /* equal contents */ +} + + +unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) { + unsigned int h = seed ^ cast_uint(l); + for (; l > 0; l--) + h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1])); + return h; +} + + +unsigned int luaS_hashlongstr (TString *ts) { + lua_assert(ts->tt == LUA_VLNGSTR); + if (ts->extra == 0) { /* no hash? */ + size_t len = ts->u.lnglen; + ts->hash = luaS_hash(getstr(ts), len, ts->hash); + ts->extra = 1; /* now it has its hash */ + } + return ts->hash; +} + + +static void tablerehash (TString **vect, int osize, int nsize) { + int i; + for (i = osize; i < nsize; i++) /* clear new elements */ + vect[i] = NULL; + for (i = 0; i < osize; i++) { /* rehash old part of the array */ + TString *p = vect[i]; + vect[i] = NULL; + while (p) { /* for each string in the list */ + TString *hnext = p->u.hnext; /* save next */ + unsigned int h = lmod(p->hash, nsize); /* new position */ + p->u.hnext = vect[h]; /* chain it into array */ + vect[h] = p; + p = hnext; + } + } +} + + +/* +** Resize the string table. If allocation fails, keep the current size. +** (This can degrade performance, but any non-zero size should work +** correctly.) +*/ +void luaS_resize (lua_State *L, int nsize) { + stringtable *tb = &G(L)->strt; + int osize = tb->size; + TString **newvect; + if (nsize < osize) /* shrinking table? */ + tablerehash(tb->hash, osize, nsize); /* depopulate shrinking part */ + newvect = luaM_reallocvector(L, tb->hash, osize, nsize, TString*); + if (unlikely(newvect == NULL)) { /* reallocation failed? */ + if (nsize < osize) /* was it shrinking table? */ + tablerehash(tb->hash, nsize, osize); /* restore to original size */ + /* leave table as it was */ + } + else { /* allocation succeeded */ + tb->hash = newvect; + tb->size = nsize; + if (nsize > osize) + tablerehash(newvect, osize, nsize); /* rehash for new size */ + } +} + + +/* +** Clear API string cache. (Entries cannot be empty, so fill them with +** a non-collectable string.) +*/ +void luaS_clearcache (global_State *g) { + int i, j; + for (i = 0; i < STRCACHE_N; i++) + for (j = 0; j < STRCACHE_M; j++) { + if (iswhite(g->strcache[i][j])) /* will entry be collected? */ + g->strcache[i][j] = g->memerrmsg; /* replace it with something fixed */ + } +} + + +/* +** Initialize the string table and the string cache +*/ +void luaS_init (lua_State *L) { + global_State *g = G(L); + int i, j; + stringtable *tb = &G(L)->strt; + tb->hash = luaM_newvector(L, MINSTRTABSIZE, TString*); + tablerehash(tb->hash, 0, MINSTRTABSIZE); /* clear array */ + tb->size = MINSTRTABSIZE; + /* pre-create memory-error message */ + g->memerrmsg = luaS_newliteral(L, MEMERRMSG); + luaC_fix(L, obj2gco(g->memerrmsg)); /* it should never be collected */ + for (i = 0; i < STRCACHE_N; i++) /* fill cache with valid strings */ + for (j = 0; j < STRCACHE_M; j++) + g->strcache[i][j] = g->memerrmsg; +} + + + +/* +** creates a new string object +*/ +static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) { + TString *ts; + GCObject *o; + size_t totalsize; /* total size of TString object */ + totalsize = sizelstring(l); + o = luaC_newobj(L, tag, totalsize); + ts = gco2ts(o); + ts->hash = h; + ts->extra = 0; + getstr(ts)[l] = '\0'; /* ending 0 */ + return ts; +} + + +TString *luaS_createlngstrobj (lua_State *L, size_t l) { + TString *ts = createstrobj(L, l, LUA_VLNGSTR, G(L)->seed); + ts->u.lnglen = l; + return ts; +} + + +void luaS_remove (lua_State *L, TString *ts) { + stringtable *tb = &G(L)->strt; + TString **p = &tb->hash[lmod(ts->hash, tb->size)]; + while (*p != ts) /* find previous element */ + p = &(*p)->u.hnext; + *p = (*p)->u.hnext; /* remove element from its list */ + tb->nuse--; +} + + +static void growstrtab (lua_State *L, stringtable *tb) { + if (unlikely(tb->nuse == MAX_INT)) { /* too many strings? */ + luaC_fullgc(L, 1); /* try to free some... */ + if (tb->nuse == MAX_INT) /* still too many? */ + luaM_error(L); /* cannot even create a message... */ + } + if (tb->size <= MAXSTRTB / 2) /* can grow string table? */ + luaS_resize(L, tb->size * 2); +} + + +/* +** Checks whether short string exists and reuses it or creates a new one. +*/ +static TString *internshrstr (lua_State *L, const char *str, size_t l) { + TString *ts; + global_State *g = G(L); + stringtable *tb = &g->strt; + unsigned int h = luaS_hash(str, l, g->seed); + TString **list = &tb->hash[lmod(h, tb->size)]; + lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */ + for (ts = *list; ts != NULL; ts = ts->u.hnext) { + if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { + /* found! */ + if (isdead(g, ts)) /* dead (but not collected yet)? */ + changewhite(ts); /* resurrect it */ + return ts; + } + } + /* else must create a new string */ + if (tb->nuse >= tb->size) { /* need to grow string table? */ + growstrtab(L, tb); + list = &tb->hash[lmod(h, tb->size)]; /* rehash with new size */ + } + ts = createstrobj(L, l, LUA_VSHRSTR, h); + memcpy(getstr(ts), str, l * sizeof(char)); + ts->shrlen = cast_byte(l); + ts->u.hnext = *list; + *list = ts; + tb->nuse++; + return ts; +} + + +/* +** new string (with explicit length) +*/ +TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { + if (l <= LUAI_MAXSHORTLEN) /* short string? */ + return internshrstr(L, str, l); + else { + TString *ts; + if (unlikely(l >= (MAX_SIZE - sizeof(TString))/sizeof(char))) + luaM_toobig(L); + ts = luaS_createlngstrobj(L, l); + memcpy(getstr(ts), str, l * sizeof(char)); + return ts; + } +} + + +/* +** Create or reuse a zero-terminated string, first checking in the +** cache (using the string address as a key). The cache can contain +** only zero-terminated strings, so it is safe to use 'strcmp' to +** check hits. +*/ +TString *luaS_new (lua_State *L, const char *str) { + unsigned int i = point2uint(str) % STRCACHE_N; /* hash */ + int j; + TString **p = G(L)->strcache[i]; + for (j = 0; j < STRCACHE_M; j++) { + if (strcmp(str, getstr(p[j])) == 0) /* hit? */ + return p[j]; /* that is it */ + } + /* normal route */ + for (j = STRCACHE_M - 1; j > 0; j--) + p[j] = p[j - 1]; /* move out last element */ + /* new element is first in the list */ + p[0] = luaS_newlstr(L, str, strlen(str)); + return p[0]; +} + + +Udata *luaS_newudata (lua_State *L, size_t s, int nuvalue) { + Udata *u; + int i; + GCObject *o; + if (unlikely(s > MAX_SIZE - udatamemoffset(nuvalue))) + luaM_toobig(L); + o = luaC_newobj(L, LUA_VUSERDATA, sizeudata(nuvalue, s)); + u = gco2u(o); + u->len = s; + u->nuvalue = nuvalue; + u->metatable = NULL; + for (i = 0; i < nuvalue; i++) + setnilvalue(&u->uv[i].uv); + return u; +} + diff --git a/Lua/lstring.h b/Lua/lstring.h new file mode 100644 index 00000000..450c2390 --- /dev/null +++ b/Lua/lstring.h @@ -0,0 +1,57 @@ +/* +** $Id: lstring.h $ +** String table (keep all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + +#ifndef lstring_h +#define lstring_h + +#include "lgc.h" +#include "lobject.h" +#include "lstate.h" + + +/* +** Memory-allocation error message must be preallocated (it cannot +** be created after memory is exhausted) +*/ +#define MEMERRMSG "not enough memory" + + +/* +** Size of a TString: Size of the header plus space for the string +** itself (including final '\0'). +*/ +#define sizelstring(l) (offsetof(TString, contents) + ((l) + 1) * sizeof(char)) + +#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ + (sizeof(s)/sizeof(char))-1)) + + +/* +** test whether a string is a reserved word +*/ +#define isreserved(s) ((s)->tt == LUA_VSHRSTR && (s)->extra > 0) + + +/* +** equality for short strings, which are always internalized +*/ +#define eqshrstr(a,b) check_exp((a)->tt == LUA_VSHRSTR, (a) == (b)) + + +LUAI_FUNC unsigned int luaS_hash (const char *str, size_t l, unsigned int seed); +LUAI_FUNC unsigned int luaS_hashlongstr (TString *ts); +LUAI_FUNC int luaS_eqlngstr (TString *a, TString *b); +LUAI_FUNC void luaS_resize (lua_State *L, int newsize); +LUAI_FUNC void luaS_clearcache (global_State *g); +LUAI_FUNC void luaS_init (lua_State *L); +LUAI_FUNC void luaS_remove (lua_State *L, TString *ts); +LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, int nuvalue); +LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); +LUAI_FUNC TString *luaS_new (lua_State *L, const char *str); +LUAI_FUNC TString *luaS_createlngstrobj (lua_State *L, size_t l); + + +#endif diff --git a/Lua/lstrlib.c b/Lua/lstrlib.c new file mode 100644 index 00000000..940a14ca --- /dev/null +++ b/Lua/lstrlib.c @@ -0,0 +1,1802 @@ +/* +** $Id: lstrlib.c $ +** Standard library for string operations and pattern-matching +** See Copyright Notice in lua.h +*/ + +#define lstrlib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** maximum number of captures that a pattern can do during +** pattern-matching. This limit is arbitrary, but must fit in +** an unsigned char. +*/ +#if !defined(LUA_MAXCAPTURES) +#define LUA_MAXCAPTURES 32 +#endif + + +/* macro to 'unsign' a character */ +#define uchar(c) ((unsigned char)(c)) + + +/* +** Some sizes are better limited to fit in 'int', but must also fit in +** 'size_t'. (We assume that 'lua_Integer' cannot be smaller than 'int'.) +*/ +#define MAX_SIZET ((size_t)(~(size_t)0)) + +#define MAXSIZE \ + (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) + + + + +static int str_len (lua_State *L) { + size_t l; + luaL_checklstring(L, 1, &l); + lua_pushinteger(L, (lua_Integer)l); + return 1; +} + + +/* +** translate a relative initial string position +** (negative means back from end): clip result to [1, inf). +** The length of any string in Lua must fit in a lua_Integer, +** so there are no overflows in the casts. +** The inverted comparison avoids a possible overflow +** computing '-pos'. +*/ +static size_t posrelatI (lua_Integer pos, size_t len) { + if (pos > 0) + return (size_t)pos; + else if (pos == 0) + return 1; + else if (pos < -(lua_Integer)len) /* inverted comparison */ + return 1; /* clip to 1 */ + else return len + (size_t)pos + 1; +} + + +/* +** Gets an optional ending string position from argument 'arg', +** with default value 'def'. +** Negative means back from end: clip result to [0, len] +*/ +static size_t getendpos (lua_State *L, int arg, lua_Integer def, + size_t len) { + lua_Integer pos = luaL_optinteger(L, arg, def); + if (pos > (lua_Integer)len) + return len; + else if (pos >= 0) + return (size_t)pos; + else if (pos < -(lua_Integer)len) + return 0; + else return len + (size_t)pos + 1; +} + + +static int str_sub (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + size_t start = posrelatI(luaL_checkinteger(L, 2), l); + size_t end = getendpos(L, 3, -1, l); + if (start <= end) + lua_pushlstring(L, s + start - 1, (end - start) + 1); + else lua_pushliteral(L, ""); + return 1; +} + + +static int str_reverse (lua_State *L) { + size_t l, i; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + char *p = luaL_buffinitsize(L, &b, l); + for (i = 0; i < l; i++) + p[i] = s[l - i - 1]; + luaL_pushresultsize(&b, l); + return 1; +} + + +static int str_lower (lua_State *L) { + size_t l; + size_t i; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + char *p = luaL_buffinitsize(L, &b, l); + for (i=0; i MAXSIZE / n) /* may overflow? */ + return luaL_error(L, "resulting string too large"); + else { + size_t totallen = (size_t)n * l + (size_t)(n - 1) * lsep; + luaL_Buffer b; + char *p = luaL_buffinitsize(L, &b, totallen); + while (n-- > 1) { /* first n-1 copies (followed by separator) */ + memcpy(p, s, l * sizeof(char)); p += l; + if (lsep > 0) { /* empty 'memcpy' is not that cheap */ + memcpy(p, sep, lsep * sizeof(char)); + p += lsep; + } + } + memcpy(p, s, l * sizeof(char)); /* last copy (not followed by separator) */ + luaL_pushresultsize(&b, totallen); + } + return 1; +} + + +static int str_byte (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + lua_Integer pi = luaL_optinteger(L, 2, 1); + size_t posi = posrelatI(pi, l); + size_t pose = getendpos(L, 3, pi, l); + int n, i; + if (posi > pose) return 0; /* empty interval; return no values */ + if (pose - posi >= (size_t)INT_MAX) /* arithmetic overflow? */ + return luaL_error(L, "string slice too long"); + n = (int)(pose - posi) + 1; + luaL_checkstack(L, n, "string slice too long"); + for (i=0; iinit) { + state->init = 1; + luaL_buffinit(L, &state->B); + } + luaL_addlstring(&state->B, (const char *)b, size); + return 0; +} + + +static int str_dump (lua_State *L) { + struct str_Writer state; + int strip = lua_toboolean(L, 2); + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, 1); /* ensure function is on the top of the stack */ + state.init = 0; + if (lua_dump(L, writer, &state, strip) != 0) + return luaL_error(L, "unable to dump given function"); + luaL_pushresult(&state.B); + return 1; +} + + + +/* +** {====================================================== +** METAMETHODS +** ======================================================= +*/ + +#if defined(LUA_NOCVTS2N) /* { */ + +/* no coercion from strings to numbers */ + +static const luaL_Reg stringmetamethods[] = { + {"__index", NULL}, /* placeholder */ + {NULL, NULL} +}; + +#else /* }{ */ + +static int tonum (lua_State *L, int arg) { + if (lua_type(L, arg) == LUA_TNUMBER) { /* already a number? */ + lua_pushvalue(L, arg); + return 1; + } + else { /* check whether it is a numerical string */ + size_t len; + const char *s = lua_tolstring(L, arg, &len); + return (s != NULL && lua_stringtonumber(L, s) == len + 1); + } +} + + +static void trymt (lua_State *L, const char *mtname) { + lua_settop(L, 2); /* back to the original arguments */ + if (lua_type(L, 2) == LUA_TSTRING || !luaL_getmetafield(L, 2, mtname)) + luaL_error(L, "attempt to %s a '%s' with a '%s'", mtname + 2, + luaL_typename(L, -2), luaL_typename(L, -1)); + lua_insert(L, -3); /* put metamethod before arguments */ + lua_call(L, 2, 1); /* call metamethod */ +} + + +static int arith (lua_State *L, int op, const char *mtname) { + if (tonum(L, 1) && tonum(L, 2)) + lua_arith(L, op); /* result will be on the top */ + else + trymt(L, mtname); + return 1; +} + + +static int arith_add (lua_State *L) { + return arith(L, LUA_OPADD, "__add"); +} + +static int arith_sub (lua_State *L) { + return arith(L, LUA_OPSUB, "__sub"); +} + +static int arith_mul (lua_State *L) { + return arith(L, LUA_OPMUL, "__mul"); +} + +static int arith_mod (lua_State *L) { + return arith(L, LUA_OPMOD, "__mod"); +} + +static int arith_pow (lua_State *L) { + return arith(L, LUA_OPPOW, "__pow"); +} + +static int arith_div (lua_State *L) { + return arith(L, LUA_OPDIV, "__div"); +} + +static int arith_idiv (lua_State *L) { + return arith(L, LUA_OPIDIV, "__idiv"); +} + +static int arith_unm (lua_State *L) { + return arith(L, LUA_OPUNM, "__unm"); +} + + +static const luaL_Reg stringmetamethods[] = { + {"__add", arith_add}, + {"__sub", arith_sub}, + {"__mul", arith_mul}, + {"__mod", arith_mod}, + {"__pow", arith_pow}, + {"__div", arith_div}, + {"__idiv", arith_idiv}, + {"__unm", arith_unm}, + {"__index", NULL}, /* placeholder */ + {NULL, NULL} +}; + +#endif /* } */ + +/* }====================================================== */ + +/* +** {====================================================== +** PATTERN MATCHING +** ======================================================= +*/ + + +#define CAP_UNFINISHED (-1) +#define CAP_POSITION (-2) + + +typedef struct MatchState { + const char *src_init; /* init of source string */ + const char *src_end; /* end ('\0') of source string */ + const char *p_end; /* end ('\0') of pattern */ + lua_State *L; + int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ + unsigned char level; /* total number of captures (finished or unfinished) */ + struct { + const char *init; + ptrdiff_t len; + } capture[LUA_MAXCAPTURES]; +} MatchState; + + +/* recursive function */ +static const char *match (MatchState *ms, const char *s, const char *p); + + +/* maximum recursion depth for 'match' */ +#if !defined(MAXCCALLS) +#define MAXCCALLS 200 +#endif + + +#define L_ESC '%' +#define SPECIALS "^$*+?.([%-" + + +static int check_capture (MatchState *ms, int l) { + l -= '1'; + if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED) + return luaL_error(ms->L, "invalid capture index %%%d", l + 1); + return l; +} + + +static int capture_to_close (MatchState *ms) { + int level = ms->level; + for (level--; level>=0; level--) + if (ms->capture[level].len == CAP_UNFINISHED) return level; + return luaL_error(ms->L, "invalid pattern capture"); +} + + +static const char *classend (MatchState *ms, const char *p) { + switch (*p++) { + case L_ESC: { + if (p == ms->p_end) + luaL_error(ms->L, "malformed pattern (ends with '%%')"); + return p+1; + } + case '[': { + if (*p == '^') p++; + do { /* look for a ']' */ + if (p == ms->p_end) + luaL_error(ms->L, "malformed pattern (missing ']')"); + if (*(p++) == L_ESC && p < ms->p_end) + p++; /* skip escapes (e.g. '%]') */ + } while (*p != ']'); + return p+1; + } + default: { + return p; + } + } +} + + +static int match_class (int c, int cl) { + int res; + switch (tolower(cl)) { + case 'a' : res = isalpha(c); break; + case 'c' : res = iscntrl(c); break; + case 'd' : res = isdigit(c); break; + case 'g' : res = isgraph(c); break; + case 'l' : res = islower(c); break; + case 'p' : res = ispunct(c); break; + case 's' : res = isspace(c); break; + case 'u' : res = isupper(c); break; + case 'w' : res = isalnum(c); break; + case 'x' : res = isxdigit(c); break; + case 'z' : res = (c == 0); break; /* deprecated option */ + default: return (cl == c); + } + return (islower(cl) ? res : !res); +} + + +static int matchbracketclass (int c, const char *p, const char *ec) { + int sig = 1; + if (*(p+1) == '^') { + sig = 0; + p++; /* skip the '^' */ + } + while (++p < ec) { + if (*p == L_ESC) { + p++; + if (match_class(c, uchar(*p))) + return sig; + } + else if ((*(p+1) == '-') && (p+2 < ec)) { + p+=2; + if (uchar(*(p-2)) <= c && c <= uchar(*p)) + return sig; + } + else if (uchar(*p) == c) return sig; + } + return !sig; +} + + +static int singlematch (MatchState *ms, const char *s, const char *p, + const char *ep) { + if (s >= ms->src_end) + return 0; + else { + int c = uchar(*s); + switch (*p) { + case '.': return 1; /* matches any char */ + case L_ESC: return match_class(c, uchar(*(p+1))); + case '[': return matchbracketclass(c, p, ep-1); + default: return (uchar(*p) == c); + } + } +} + + +static const char *matchbalance (MatchState *ms, const char *s, + const char *p) { + if (p >= ms->p_end - 1) + luaL_error(ms->L, "malformed pattern (missing arguments to '%%b')"); + if (*s != *p) return NULL; + else { + int b = *p; + int e = *(p+1); + int cont = 1; + while (++s < ms->src_end) { + if (*s == e) { + if (--cont == 0) return s+1; + } + else if (*s == b) cont++; + } + } + return NULL; /* string ends out of balance */ +} + + +static const char *max_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + ptrdiff_t i = 0; /* counts maximum expand for item */ + while (singlematch(ms, s + i, p, ep)) + i++; + /* keeps trying to match with the maximum repetitions */ + while (i>=0) { + const char *res = match(ms, (s+i), ep+1); + if (res) return res; + i--; /* else didn't match; reduce 1 repetition to try again */ + } + return NULL; +} + + +static const char *min_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + for (;;) { + const char *res = match(ms, s, ep+1); + if (res != NULL) + return res; + else if (singlematch(ms, s, p, ep)) + s++; /* try with one more repetition */ + else return NULL; + } +} + + +static const char *start_capture (MatchState *ms, const char *s, + const char *p, int what) { + const char *res; + int level = ms->level; + if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures"); + ms->capture[level].init = s; + ms->capture[level].len = what; + ms->level = level+1; + if ((res=match(ms, s, p)) == NULL) /* match failed? */ + ms->level--; /* undo capture */ + return res; +} + + +static const char *end_capture (MatchState *ms, const char *s, + const char *p) { + int l = capture_to_close(ms); + const char *res; + ms->capture[l].len = s - ms->capture[l].init; /* close capture */ + if ((res = match(ms, s, p)) == NULL) /* match failed? */ + ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ + return res; +} + + +static const char *match_capture (MatchState *ms, const char *s, int l) { + size_t len; + l = check_capture(ms, l); + len = ms->capture[l].len; + if ((size_t)(ms->src_end-s) >= len && + memcmp(ms->capture[l].init, s, len) == 0) + return s+len; + else return NULL; +} + + +static const char *match (MatchState *ms, const char *s, const char *p) { + if (ms->matchdepth-- == 0) + luaL_error(ms->L, "pattern too complex"); + init: /* using goto's to optimize tail recursion */ + if (p != ms->p_end) { /* end of pattern? */ + switch (*p) { + case '(': { /* start capture */ + if (*(p + 1) == ')') /* position capture? */ + s = start_capture(ms, s, p + 2, CAP_POSITION); + else + s = start_capture(ms, s, p + 1, CAP_UNFINISHED); + break; + } + case ')': { /* end capture */ + s = end_capture(ms, s, p + 1); + break; + } + case '$': { + if ((p + 1) != ms->p_end) /* is the '$' the last char in pattern? */ + goto dflt; /* no; go to default */ + s = (s == ms->src_end) ? s : NULL; /* check end of string */ + break; + } + case L_ESC: { /* escaped sequences not in the format class[*+?-]? */ + switch (*(p + 1)) { + case 'b': { /* balanced string? */ + s = matchbalance(ms, s, p + 2); + if (s != NULL) { + p += 4; goto init; /* return match(ms, s, p + 4); */ + } /* else fail (s == NULL) */ + break; + } + case 'f': { /* frontier? */ + const char *ep; char previous; + p += 2; + if (*p != '[') + luaL_error(ms->L, "missing '[' after '%%f' in pattern"); + ep = classend(ms, p); /* points to what is next */ + previous = (s == ms->src_init) ? '\0' : *(s - 1); + if (!matchbracketclass(uchar(previous), p, ep - 1) && + matchbracketclass(uchar(*s), p, ep - 1)) { + p = ep; goto init; /* return match(ms, s, ep); */ + } + s = NULL; /* match failed */ + break; + } + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': { /* capture results (%0-%9)? */ + s = match_capture(ms, s, uchar(*(p + 1))); + if (s != NULL) { + p += 2; goto init; /* return match(ms, s, p + 2) */ + } + break; + } + default: goto dflt; + } + break; + } + default: dflt: { /* pattern class plus optional suffix */ + const char *ep = classend(ms, p); /* points to optional suffix */ + /* does not match at least once? */ + if (!singlematch(ms, s, p, ep)) { + if (*ep == '*' || *ep == '?' || *ep == '-') { /* accept empty? */ + p = ep + 1; goto init; /* return match(ms, s, ep + 1); */ + } + else /* '+' or no suffix */ + s = NULL; /* fail */ + } + else { /* matched once */ + switch (*ep) { /* handle optional suffix */ + case '?': { /* optional */ + const char *res; + if ((res = match(ms, s + 1, ep + 1)) != NULL) + s = res; + else { + p = ep + 1; goto init; /* else return match(ms, s, ep + 1); */ + } + break; + } + case '+': /* 1 or more repetitions */ + s++; /* 1 match already done */ + /* FALLTHROUGH */ + case '*': /* 0 or more repetitions */ + s = max_expand(ms, s, p, ep); + break; + case '-': /* 0 or more repetitions (minimum) */ + s = min_expand(ms, s, p, ep); + break; + default: /* no suffix */ + s++; p = ep; goto init; /* return match(ms, s + 1, ep); */ + } + } + break; + } + } + } + ms->matchdepth++; + return s; +} + + + +static const char *lmemfind (const char *s1, size_t l1, + const char *s2, size_t l2) { + if (l2 == 0) return s1; /* empty strings are everywhere */ + else if (l2 > l1) return NULL; /* avoids a negative 'l1' */ + else { + const char *init; /* to search for a '*s2' inside 's1' */ + l2--; /* 1st char will be checked by 'memchr' */ + l1 = l1-l2; /* 's2' cannot be found after that */ + while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { + init++; /* 1st char is already checked */ + if (memcmp(init, s2+1, l2) == 0) + return init-1; + else { /* correct 'l1' and 's1' to try again */ + l1 -= init-s1; + s1 = init; + } + } + return NULL; /* not found */ + } +} + + +/* +** get information about the i-th capture. If there are no captures +** and 'i==0', return information about the whole match, which +** is the range 's'..'e'. If the capture is a string, return +** its length and put its address in '*cap'. If it is an integer +** (a position), push it on the stack and return CAP_POSITION. +*/ +static size_t get_onecapture (MatchState *ms, int i, const char *s, + const char *e, const char **cap) { + if (i >= ms->level) { + if (i != 0) + luaL_error(ms->L, "invalid capture index %%%d", i + 1); + *cap = s; + return e - s; + } + else { + ptrdiff_t capl = ms->capture[i].len; + *cap = ms->capture[i].init; + if (capl == CAP_UNFINISHED) + luaL_error(ms->L, "unfinished capture"); + else if (capl == CAP_POSITION) + lua_pushinteger(ms->L, (ms->capture[i].init - ms->src_init) + 1); + return capl; + } +} + + +/* +** Push the i-th capture on the stack. +*/ +static void push_onecapture (MatchState *ms, int i, const char *s, + const char *e) { + const char *cap; + ptrdiff_t l = get_onecapture(ms, i, s, e, &cap); + if (l != CAP_POSITION) + lua_pushlstring(ms->L, cap, l); + /* else position was already pushed */ +} + + +static int push_captures (MatchState *ms, const char *s, const char *e) { + int i; + int nlevels = (ms->level == 0 && s) ? 1 : ms->level; + luaL_checkstack(ms->L, nlevels, "too many captures"); + for (i = 0; i < nlevels; i++) + push_onecapture(ms, i, s, e); + return nlevels; /* number of strings pushed */ +} + + +/* check whether pattern has no special characters */ +static int nospecials (const char *p, size_t l) { + size_t upto = 0; + do { + if (strpbrk(p + upto, SPECIALS)) + return 0; /* pattern has a special character */ + upto += strlen(p + upto) + 1; /* may have more after \0 */ + } while (upto <= l); + return 1; /* no special chars found */ +} + + +static void prepstate (MatchState *ms, lua_State *L, + const char *s, size_t ls, const char *p, size_t lp) { + ms->L = L; + ms->matchdepth = MAXCCALLS; + ms->src_init = s; + ms->src_end = s + ls; + ms->p_end = p + lp; +} + + +static void reprepstate (MatchState *ms) { + ms->level = 0; + lua_assert(ms->matchdepth == MAXCCALLS); +} + + +static int str_find_aux (lua_State *L, int find) { + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1; + if (init > ls) { /* start after string's end? */ + luaL_pushfail(L); /* cannot find anything */ + return 1; + } + /* explicit request or no special characters? */ + if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) { + /* do a plain search */ + const char *s2 = lmemfind(s + init, ls - init, p, lp); + if (s2) { + lua_pushinteger(L, (s2 - s) + 1); + lua_pushinteger(L, (s2 - s) + lp); + return 2; + } + } + else { + MatchState ms; + const char *s1 = s + init; + int anchor = (*p == '^'); + if (anchor) { + p++; lp--; /* skip anchor character */ + } + prepstate(&ms, L, s, ls, p, lp); + do { + const char *res; + reprepstate(&ms); + if ((res=match(&ms, s1, p)) != NULL) { + if (find) { + lua_pushinteger(L, (s1 - s) + 1); /* start */ + lua_pushinteger(L, res - s); /* end */ + return push_captures(&ms, NULL, 0) + 2; + } + else + return push_captures(&ms, s1, res); + } + } while (s1++ < ms.src_end && !anchor); + } + luaL_pushfail(L); /* not found */ + return 1; +} + + +static int str_find (lua_State *L) { + return str_find_aux(L, 1); +} + + +static int str_match (lua_State *L) { + return str_find_aux(L, 0); +} + + +/* state for 'gmatch' */ +typedef struct GMatchState { + const char *src; /* current position */ + const char *p; /* pattern */ + const char *lastmatch; /* end of last match */ + MatchState ms; /* match state */ +} GMatchState; + + +static int gmatch_aux (lua_State *L) { + GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3)); + const char *src; + gm->ms.L = L; + for (src = gm->src; src <= gm->ms.src_end; src++) { + const char *e; + reprepstate(&gm->ms); + if ((e = match(&gm->ms, src, gm->p)) != NULL && e != gm->lastmatch) { + gm->src = gm->lastmatch = e; + return push_captures(&gm->ms, src, e); + } + } + return 0; /* not found */ +} + + +static int gmatch (lua_State *L) { + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1; + GMatchState *gm; + lua_settop(L, 2); /* keep strings on closure to avoid being collected */ + gm = (GMatchState *)lua_newuserdatauv(L, sizeof(GMatchState), 0); + if (init > ls) /* start after string's end? */ + init = ls + 1; /* avoid overflows in 's + init' */ + prepstate(&gm->ms, L, s, ls, p, lp); + gm->src = s + init; gm->p = p; gm->lastmatch = NULL; + lua_pushcclosure(L, gmatch_aux, 3); + return 1; +} + + +static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e) { + size_t l; + lua_State *L = ms->L; + const char *news = lua_tolstring(L, 3, &l); + const char *p; + while ((p = (char *)memchr(news, L_ESC, l)) != NULL) { + luaL_addlstring(b, news, p - news); + p++; /* skip ESC */ + if (*p == L_ESC) /* '%%' */ + luaL_addchar(b, *p); + else if (*p == '0') /* '%0' */ + luaL_addlstring(b, s, e - s); + else if (isdigit(uchar(*p))) { /* '%n' */ + const char *cap; + ptrdiff_t resl = get_onecapture(ms, *p - '1', s, e, &cap); + if (resl == CAP_POSITION) + luaL_addvalue(b); /* add position to accumulated result */ + else + luaL_addlstring(b, cap, resl); + } + else + luaL_error(L, "invalid use of '%c' in replacement string", L_ESC); + l -= p + 1 - news; + news = p + 1; + } + luaL_addlstring(b, news, l); +} + + +/* +** Add the replacement value to the string buffer 'b'. +** Return true if the original string was changed. (Function calls and +** table indexing resulting in nil or false do not change the subject.) +*/ +static int add_value (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e, int tr) { + lua_State *L = ms->L; + switch (tr) { + case LUA_TFUNCTION: { /* call the function */ + int n; + lua_pushvalue(L, 3); /* push the function */ + n = push_captures(ms, s, e); /* all captures as arguments */ + lua_call(L, n, 1); /* call it */ + break; + } + case LUA_TTABLE: { /* index the table */ + push_onecapture(ms, 0, s, e); /* first capture is the index */ + lua_gettable(L, 3); + break; + } + default: { /* LUA_TNUMBER or LUA_TSTRING */ + add_s(ms, b, s, e); /* add value to the buffer */ + return 1; /* something changed */ + } + } + if (!lua_toboolean(L, -1)) { /* nil or false? */ + lua_pop(L, 1); /* remove value */ + luaL_addlstring(b, s, e - s); /* keep original text */ + return 0; /* no changes */ + } + else if (!lua_isstring(L, -1)) + return luaL_error(L, "invalid replacement value (a %s)", + luaL_typename(L, -1)); + else { + luaL_addvalue(b); /* add result to accumulator */ + return 1; /* something changed */ + } +} + + +static int str_gsub (lua_State *L) { + size_t srcl, lp; + const char *src = luaL_checklstring(L, 1, &srcl); /* subject */ + const char *p = luaL_checklstring(L, 2, &lp); /* pattern */ + const char *lastmatch = NULL; /* end of last match */ + int tr = lua_type(L, 3); /* replacement type */ + lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); /* max replacements */ + int anchor = (*p == '^'); + lua_Integer n = 0; /* replacement count */ + int changed = 0; /* change flag */ + MatchState ms; + luaL_Buffer b; + luaL_argexpected(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || + tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, + "string/function/table"); + luaL_buffinit(L, &b); + if (anchor) { + p++; lp--; /* skip anchor character */ + } + prepstate(&ms, L, src, srcl, p, lp); + while (n < max_s) { + const char *e; + reprepstate(&ms); /* (re)prepare state for new match */ + if ((e = match(&ms, src, p)) != NULL && e != lastmatch) { /* match? */ + n++; + changed = add_value(&ms, &b, src, e, tr) | changed; + src = lastmatch = e; + } + else if (src < ms.src_end) /* otherwise, skip one character */ + luaL_addchar(&b, *src++); + else break; /* end of subject */ + if (anchor) break; + } + if (!changed) /* no changes? */ + lua_pushvalue(L, 1); /* return original string */ + else { /* something changed */ + luaL_addlstring(&b, src, ms.src_end-src); + luaL_pushresult(&b); /* create and return new string */ + } + lua_pushinteger(L, n); /* number of substitutions */ + return 2; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** STRING FORMAT +** ======================================================= +*/ + +#if !defined(lua_number2strx) /* { */ + +/* +** Hexadecimal floating-point formatter +*/ + +#define SIZELENMOD (sizeof(LUA_NUMBER_FRMLEN)/sizeof(char)) + + +/* +** Number of bits that goes into the first digit. It can be any value +** between 1 and 4; the following definition tries to align the number +** to nibble boundaries by making what is left after that first digit a +** multiple of 4. +*/ +#define L_NBFD ((l_floatatt(MANT_DIG) - 1)%4 + 1) + + +/* +** Add integer part of 'x' to buffer and return new 'x' +*/ +static lua_Number adddigit (char *buff, int n, lua_Number x) { + lua_Number dd = l_mathop(floor)(x); /* get integer part from 'x' */ + int d = (int)dd; + buff[n] = (d < 10 ? d + '0' : d - 10 + 'a'); /* add to buffer */ + return x - dd; /* return what is left */ +} + + +static int num2straux (char *buff, int sz, lua_Number x) { + /* if 'inf' or 'NaN', format it like '%g' */ + if (x != x || x == (lua_Number)HUGE_VAL || x == -(lua_Number)HUGE_VAL) + return l_sprintf(buff, sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)x); + else if (x == 0) { /* can be -0... */ + /* create "0" or "-0" followed by exponent */ + return l_sprintf(buff, sz, LUA_NUMBER_FMT "x0p+0", (LUAI_UACNUMBER)x); + } + else { + int e; + lua_Number m = l_mathop(frexp)(x, &e); /* 'x' fraction and exponent */ + int n = 0; /* character count */ + if (m < 0) { /* is number negative? */ + buff[n++] = '-'; /* add sign */ + m = -m; /* make it positive */ + } + buff[n++] = '0'; buff[n++] = 'x'; /* add "0x" */ + m = adddigit(buff, n++, m * (1 << L_NBFD)); /* add first digit */ + e -= L_NBFD; /* this digit goes before the radix point */ + if (m > 0) { /* more digits? */ + buff[n++] = lua_getlocaledecpoint(); /* add radix point */ + do { /* add as many digits as needed */ + m = adddigit(buff, n++, m * 16); + } while (m > 0); + } + n += l_sprintf(buff + n, sz - n, "p%+d", e); /* add exponent */ + lua_assert(n < sz); + return n; + } +} + + +static int lua_number2strx (lua_State *L, char *buff, int sz, + const char *fmt, lua_Number x) { + int n = num2straux(buff, sz, x); + if (fmt[SIZELENMOD] == 'A') { + int i; + for (i = 0; i < n; i++) + buff[i] = toupper(uchar(buff[i])); + } + else if (fmt[SIZELENMOD] != 'a') + return luaL_error(L, "modifiers for format '%%a'/'%%A' not implemented"); + return n; +} + +#endif /* } */ + + +/* +** Maximum size for items formatted with '%f'. This size is produced +** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.', +** and '\0') + number of decimal digits to represent maxfloat (which +** is maximum exponent + 1). (99+3+1, adding some extra, 110) +*/ +#define MAX_ITEMF (110 + l_floatatt(MAX_10_EXP)) + + +/* +** All formats except '%f' do not need that large limit. The other +** float formats use exponents, so that they fit in the 99 limit for +** significant digits; 's' for large strings and 'q' add items directly +** to the buffer; all integer formats also fit in the 99 limit. The +** worst case are floats: they may need 99 significant digits, plus +** '0x', '-', '.', 'e+XXXX', and '\0'. Adding some extra, 120. +*/ +#define MAX_ITEM 120 + + +/* valid flags in a format specification */ +#if !defined(L_FMTFLAGS) +#define L_FMTFLAGS "-+ #0" +#endif + + +/* +** maximum size of each format specification (such as "%-099.99d") +*/ +#define MAX_FORMAT 32 + + +static void addquoted (luaL_Buffer *b, const char *s, size_t len) { + luaL_addchar(b, '"'); + while (len--) { + if (*s == '"' || *s == '\\' || *s == '\n') { + luaL_addchar(b, '\\'); + luaL_addchar(b, *s); + } + else if (iscntrl(uchar(*s))) { + char buff[10]; + if (!isdigit(uchar(*(s+1)))) + l_sprintf(buff, sizeof(buff), "\\%d", (int)uchar(*s)); + else + l_sprintf(buff, sizeof(buff), "\\%03d", (int)uchar(*s)); + luaL_addstring(b, buff); + } + else + luaL_addchar(b, *s); + s++; + } + luaL_addchar(b, '"'); +} + + +/* +** Serialize a floating-point number in such a way that it can be +** scanned back by Lua. Use hexadecimal format for "common" numbers +** (to preserve precision); inf, -inf, and NaN are handled separately. +** (NaN cannot be expressed as a numeral, so we write '(0/0)' for it.) +*/ +static int quotefloat (lua_State *L, char *buff, lua_Number n) { + const char *s; /* for the fixed representations */ + if (n == (lua_Number)HUGE_VAL) /* inf? */ + s = "1e9999"; + else if (n == -(lua_Number)HUGE_VAL) /* -inf? */ + s = "-1e9999"; + else if (n != n) /* NaN? */ + s = "(0/0)"; + else { /* format number as hexadecimal */ + int nb = lua_number2strx(L, buff, MAX_ITEM, + "%" LUA_NUMBER_FRMLEN "a", n); + /* ensures that 'buff' string uses a dot as the radix character */ + if (memchr(buff, '.', nb) == NULL) { /* no dot? */ + char point = lua_getlocaledecpoint(); /* try locale point */ + char *ppoint = (char *)memchr(buff, point, nb); + if (ppoint) *ppoint = '.'; /* change it to a dot */ + } + return nb; + } + /* for the fixed representations */ + return l_sprintf(buff, MAX_ITEM, "%s", s); +} + + +static void addliteral (lua_State *L, luaL_Buffer *b, int arg) { + switch (lua_type(L, arg)) { + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(L, arg, &len); + addquoted(b, s, len); + break; + } + case LUA_TNUMBER: { + char *buff = luaL_prepbuffsize(b, MAX_ITEM); + int nb; + if (!lua_isinteger(L, arg)) /* float? */ + nb = quotefloat(L, buff, lua_tonumber(L, arg)); + else { /* integers */ + lua_Integer n = lua_tointeger(L, arg); + const char *format = (n == LUA_MININTEGER) /* corner case? */ + ? "0x%" LUA_INTEGER_FRMLEN "x" /* use hex */ + : LUA_INTEGER_FMT; /* else use default format */ + nb = l_sprintf(buff, MAX_ITEM, format, (LUAI_UACINT)n); + } + luaL_addsize(b, nb); + break; + } + case LUA_TNIL: case LUA_TBOOLEAN: { + luaL_tolstring(L, arg, NULL); + luaL_addvalue(b); + break; + } + default: { + luaL_argerror(L, arg, "value has no literal form"); + } + } +} + + +static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { + const char *p = strfrmt; + while (*p != '\0' && strchr(L_FMTFLAGS, *p) != NULL) p++; /* skip flags */ + if ((size_t)(p - strfrmt) >= sizeof(L_FMTFLAGS)/sizeof(char)) + luaL_error(L, "invalid format (repeated flags)"); + if (isdigit(uchar(*p))) p++; /* skip width */ + if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ + if (*p == '.') { + p++; + if (isdigit(uchar(*p))) p++; /* skip precision */ + if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ + } + if (isdigit(uchar(*p))) + luaL_error(L, "invalid format (width or precision too long)"); + *(form++) = '%'; + memcpy(form, strfrmt, ((p - strfrmt) + 1) * sizeof(char)); + form += (p - strfrmt) + 1; + *form = '\0'; + return p; +} + + +/* +** add length modifier into formats +*/ +static void addlenmod (char *form, const char *lenmod) { + size_t l = strlen(form); + size_t lm = strlen(lenmod); + char spec = form[l - 1]; + strcpy(form + l - 1, lenmod); + form[l + lm - 1] = spec; + form[l + lm] = '\0'; +} + + +static int str_format (lua_State *L) { + int top = lua_gettop(L); + int arg = 1; + size_t sfl; + const char *strfrmt = luaL_checklstring(L, arg, &sfl); + const char *strfrmt_end = strfrmt+sfl; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (strfrmt < strfrmt_end) { + if (*strfrmt != L_ESC) + luaL_addchar(&b, *strfrmt++); + else if (*++strfrmt == L_ESC) + luaL_addchar(&b, *strfrmt++); /* %% */ + else { /* format item */ + char form[MAX_FORMAT]; /* to store the format ('%...') */ + int maxitem = MAX_ITEM; + char *buff = luaL_prepbuffsize(&b, maxitem); /* to put formatted item */ + int nb = 0; /* number of bytes in added item */ + if (++arg > top) + return luaL_argerror(L, arg, "no value"); + strfrmt = scanformat(L, strfrmt, form); + switch (*strfrmt++) { + case 'c': { + nb = l_sprintf(buff, maxitem, form, (int)luaL_checkinteger(L, arg)); + break; + } + case 'd': case 'i': + case 'o': case 'u': case 'x': case 'X': { + lua_Integer n = luaL_checkinteger(L, arg); + addlenmod(form, LUA_INTEGER_FRMLEN); + nb = l_sprintf(buff, maxitem, form, (LUAI_UACINT)n); + break; + } + case 'a': case 'A': + addlenmod(form, LUA_NUMBER_FRMLEN); + nb = lua_number2strx(L, buff, maxitem, form, + luaL_checknumber(L, arg)); + break; + case 'f': + maxitem = MAX_ITEMF; /* extra space for '%f' */ + buff = luaL_prepbuffsize(&b, maxitem); + /* FALLTHROUGH */ + case 'e': case 'E': case 'g': case 'G': { + lua_Number n = luaL_checknumber(L, arg); + addlenmod(form, LUA_NUMBER_FRMLEN); + nb = l_sprintf(buff, maxitem, form, (LUAI_UACNUMBER)n); + break; + } + case 'p': { + const void *p = lua_topointer(L, arg); + if (p == NULL) { /* avoid calling 'printf' with argument NULL */ + p = "(null)"; /* result */ + form[strlen(form) - 1] = 's'; /* format it as a string */ + } + nb = l_sprintf(buff, maxitem, form, p); + break; + } + case 'q': { + if (form[2] != '\0') /* modifiers? */ + return luaL_error(L, "specifier '%%q' cannot have modifiers"); + addliteral(L, &b, arg); + break; + } + case 's': { + size_t l; + const char *s = luaL_tolstring(L, arg, &l); + if (form[2] == '\0') /* no modifiers? */ + luaL_addvalue(&b); /* keep entire string */ + else { + luaL_argcheck(L, l == strlen(s), arg, "string contains zeros"); + if (!strchr(form, '.') && l >= 100) { + /* no precision and string is too long to be formatted */ + luaL_addvalue(&b); /* keep entire string */ + } + else { /* format the string into 'buff' */ + nb = l_sprintf(buff, maxitem, form, s); + lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + } + } + break; + } + default: { /* also treat cases 'pnLlh' */ + return luaL_error(L, "invalid conversion '%s' to 'format'", form); + } + } + lua_assert(nb < maxitem); + luaL_addsize(&b, nb); + } + } + luaL_pushresult(&b); + return 1; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** PACK/UNPACK +** ======================================================= +*/ + + +/* value used for padding */ +#if !defined(LUAL_PACKPADBYTE) +#define LUAL_PACKPADBYTE 0x00 +#endif + +/* maximum size for the binary representation of an integer */ +#define MAXINTSIZE 16 + +/* number of bits in a character */ +#define NB CHAR_BIT + +/* mask for one character (NB 1's) */ +#define MC ((1 << NB) - 1) + +/* size of a lua_Integer */ +#define SZINT ((int)sizeof(lua_Integer)) + + +/* dummy union to get native endianness */ +static const union { + int dummy; + char little; /* true iff machine is little endian */ +} nativeendian = {1}; + + +/* dummy structure to get native alignment requirements */ +struct cD { + char c; + union { double d; void *p; lua_Integer i; lua_Number n; } u; +}; + +#define MAXALIGN (offsetof(struct cD, u)) + + +/* +** Union for serializing floats +*/ +typedef union Ftypes { + float f; + double d; + lua_Number n; +} Ftypes; + + +/* +** information to pack/unpack stuff +*/ +typedef struct Header { + lua_State *L; + int islittle; + int maxalign; +} Header; + + +/* +** options for pack/unpack +*/ +typedef enum KOption { + Kint, /* signed integers */ + Kuint, /* unsigned integers */ + Kfloat, /* floating-point numbers */ + Kchar, /* fixed-length strings */ + Kstring, /* strings with prefixed length */ + Kzstr, /* zero-terminated strings */ + Kpadding, /* padding */ + Kpaddalign, /* padding for alignment */ + Knop /* no-op (configuration or spaces) */ +} KOption; + + +/* +** Read an integer numeral from string 'fmt' or return 'df' if +** there is no numeral +*/ +static int digit (int c) { return '0' <= c && c <= '9'; } + +static int getnum (const char **fmt, int df) { + if (!digit(**fmt)) /* no number? */ + return df; /* return default value */ + else { + int a = 0; + do { + a = a*10 + (*((*fmt)++) - '0'); + } while (digit(**fmt) && a <= ((int)MAXSIZE - 9)/10); + return a; + } +} + + +/* +** Read an integer numeral and raises an error if it is larger +** than the maximum size for integers. +*/ +static int getnumlimit (Header *h, const char **fmt, int df) { + int sz = getnum(fmt, df); + if (sz > MAXINTSIZE || sz <= 0) + return luaL_error(h->L, "integral size (%d) out of limits [1,%d]", + sz, MAXINTSIZE); + return sz; +} + + +/* +** Initialize Header +*/ +static void initheader (lua_State *L, Header *h) { + h->L = L; + h->islittle = nativeendian.little; + h->maxalign = 1; +} + + +/* +** Read and classify next option. 'size' is filled with option's size. +*/ +static KOption getoption (Header *h, const char **fmt, int *size) { + int opt = *((*fmt)++); + *size = 0; /* default */ + switch (opt) { + case 'b': *size = sizeof(char); return Kint; + case 'B': *size = sizeof(char); return Kuint; + case 'h': *size = sizeof(short); return Kint; + case 'H': *size = sizeof(short); return Kuint; + case 'l': *size = sizeof(long); return Kint; + case 'L': *size = sizeof(long); return Kuint; + case 'j': *size = sizeof(lua_Integer); return Kint; + case 'J': *size = sizeof(lua_Integer); return Kuint; + case 'T': *size = sizeof(size_t); return Kuint; + case 'f': *size = sizeof(float); return Kfloat; + case 'd': *size = sizeof(double); return Kfloat; + case 'n': *size = sizeof(lua_Number); return Kfloat; + case 'i': *size = getnumlimit(h, fmt, sizeof(int)); return Kint; + case 'I': *size = getnumlimit(h, fmt, sizeof(int)); return Kuint; + case 's': *size = getnumlimit(h, fmt, sizeof(size_t)); return Kstring; + case 'c': + *size = getnum(fmt, -1); + if (*size == -1) + luaL_error(h->L, "missing size for format option 'c'"); + return Kchar; + case 'z': return Kzstr; + case 'x': *size = 1; return Kpadding; + case 'X': return Kpaddalign; + case ' ': break; + case '<': h->islittle = 1; break; + case '>': h->islittle = 0; break; + case '=': h->islittle = nativeendian.little; break; + case '!': h->maxalign = getnumlimit(h, fmt, MAXALIGN); break; + default: luaL_error(h->L, "invalid format option '%c'", opt); + } + return Knop; +} + + +/* +** Read, classify, and fill other details about the next option. +** 'psize' is filled with option's size, 'notoalign' with its +** alignment requirements. +** Local variable 'size' gets the size to be aligned. (Kpadal option +** always gets its full alignment, other options are limited by +** the maximum alignment ('maxalign'). Kchar option needs no alignment +** despite its size. +*/ +static KOption getdetails (Header *h, size_t totalsize, + const char **fmt, int *psize, int *ntoalign) { + KOption opt = getoption(h, fmt, psize); + int align = *psize; /* usually, alignment follows size */ + if (opt == Kpaddalign) { /* 'X' gets alignment from following option */ + if (**fmt == '\0' || getoption(h, fmt, &align) == Kchar || align == 0) + luaL_argerror(h->L, 1, "invalid next option for option 'X'"); + } + if (align <= 1 || opt == Kchar) /* need no alignment? */ + *ntoalign = 0; + else { + if (align > h->maxalign) /* enforce maximum alignment */ + align = h->maxalign; + if ((align & (align - 1)) != 0) /* is 'align' not a power of 2? */ + luaL_argerror(h->L, 1, "format asks for alignment not power of 2"); + *ntoalign = (align - (int)(totalsize & (align - 1))) & (align - 1); + } + return opt; +} + + +/* +** Pack integer 'n' with 'size' bytes and 'islittle' endianness. +** The final 'if' handles the case when 'size' is larger than +** the size of a Lua integer, correcting the extra sign-extension +** bytes if necessary (by default they would be zeros). +*/ +static void packint (luaL_Buffer *b, lua_Unsigned n, + int islittle, int size, int neg) { + char *buff = luaL_prepbuffsize(b, size); + int i; + buff[islittle ? 0 : size - 1] = (char)(n & MC); /* first byte */ + for (i = 1; i < size; i++) { + n >>= NB; + buff[islittle ? i : size - 1 - i] = (char)(n & MC); + } + if (neg && size > SZINT) { /* negative number need sign extension? */ + for (i = SZINT; i < size; i++) /* correct extra bytes */ + buff[islittle ? i : size - 1 - i] = (char)MC; + } + luaL_addsize(b, size); /* add result to buffer */ +} + + +/* +** Copy 'size' bytes from 'src' to 'dest', correcting endianness if +** given 'islittle' is different from native endianness. +*/ +static void copywithendian (char *dest, const char *src, + int size, int islittle) { + if (islittle == nativeendian.little) + memcpy(dest, src, size); + else { + dest += size - 1; + while (size-- != 0) + *(dest--) = *(src++); + } +} + + +static int str_pack (lua_State *L) { + luaL_Buffer b; + Header h; + const char *fmt = luaL_checkstring(L, 1); /* format string */ + int arg = 1; /* current argument to pack */ + size_t totalsize = 0; /* accumulate total size of result */ + initheader(L, &h); + lua_pushnil(L); /* mark to separate arguments from string buffer */ + luaL_buffinit(L, &b); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + totalsize += ntoalign + size; + while (ntoalign-- > 0) + luaL_addchar(&b, LUAL_PACKPADBYTE); /* fill alignment */ + arg++; + switch (opt) { + case Kint: { /* signed integers */ + lua_Integer n = luaL_checkinteger(L, arg); + if (size < SZINT) { /* need overflow check? */ + lua_Integer lim = (lua_Integer)1 << ((size * NB) - 1); + luaL_argcheck(L, -lim <= n && n < lim, arg, "integer overflow"); + } + packint(&b, (lua_Unsigned)n, h.islittle, size, (n < 0)); + break; + } + case Kuint: { /* unsigned integers */ + lua_Integer n = luaL_checkinteger(L, arg); + if (size < SZINT) /* need overflow check? */ + luaL_argcheck(L, (lua_Unsigned)n < ((lua_Unsigned)1 << (size * NB)), + arg, "unsigned overflow"); + packint(&b, (lua_Unsigned)n, h.islittle, size, 0); + break; + } + case Kfloat: { /* floating-point options */ + Ftypes u; + char *buff = luaL_prepbuffsize(&b, size); + lua_Number n = luaL_checknumber(L, arg); /* get argument */ + if (size == sizeof(u.f)) u.f = (float)n; /* copy it into 'u' */ + else if (size == sizeof(u.d)) u.d = (double)n; + else u.n = n; + /* move 'u' to final result, correcting endianness if needed */ + copywithendian(buff, (char *)&u, size, h.islittle); + luaL_addsize(&b, size); + break; + } + case Kchar: { /* fixed-size string */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, len <= (size_t)size, arg, + "string longer than given size"); + luaL_addlstring(&b, s, len); /* add string */ + while (len++ < (size_t)size) /* pad extra space */ + luaL_addchar(&b, LUAL_PACKPADBYTE); + break; + } + case Kstring: { /* strings with length count */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, size >= (int)sizeof(size_t) || + len < ((size_t)1 << (size * NB)), + arg, "string length does not fit in given size"); + packint(&b, (lua_Unsigned)len, h.islittle, size, 0); /* pack length */ + luaL_addlstring(&b, s, len); + totalsize += len; + break; + } + case Kzstr: { /* zero-terminated string */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, strlen(s) == len, arg, "string contains zeros"); + luaL_addlstring(&b, s, len); + luaL_addchar(&b, '\0'); /* add zero at the end */ + totalsize += len + 1; + break; + } + case Kpadding: luaL_addchar(&b, LUAL_PACKPADBYTE); /* FALLTHROUGH */ + case Kpaddalign: case Knop: + arg--; /* undo increment */ + break; + } + } + luaL_pushresult(&b); + return 1; +} + + +static int str_packsize (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); /* format string */ + size_t totalsize = 0; /* accumulate total size of result */ + initheader(L, &h); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + luaL_argcheck(L, opt != Kstring && opt != Kzstr, 1, + "variable-length format"); + size += ntoalign; /* total space used by option */ + luaL_argcheck(L, totalsize <= MAXSIZE - size, 1, + "format result too large"); + totalsize += size; + } + lua_pushinteger(L, (lua_Integer)totalsize); + return 1; +} + + +/* +** Unpack an integer with 'size' bytes and 'islittle' endianness. +** If size is smaller than the size of a Lua integer and integer +** is signed, must do sign extension (propagating the sign to the +** higher bits); if size is larger than the size of a Lua integer, +** it must check the unread bytes to see whether they do not cause an +** overflow. +*/ +static lua_Integer unpackint (lua_State *L, const char *str, + int islittle, int size, int issigned) { + lua_Unsigned res = 0; + int i; + int limit = (size <= SZINT) ? size : SZINT; + for (i = limit - 1; i >= 0; i--) { + res <<= NB; + res |= (lua_Unsigned)(unsigned char)str[islittle ? i : size - 1 - i]; + } + if (size < SZINT) { /* real size smaller than lua_Integer? */ + if (issigned) { /* needs sign extension? */ + lua_Unsigned mask = (lua_Unsigned)1 << (size*NB - 1); + res = ((res ^ mask) - mask); /* do sign extension */ + } + } + else if (size > SZINT) { /* must check unread bytes */ + int mask = (!issigned || (lua_Integer)res >= 0) ? 0 : MC; + for (i = limit; i < size; i++) { + if ((unsigned char)str[islittle ? i : size - 1 - i] != mask) + luaL_error(L, "%d-byte integer does not fit into Lua Integer", size); + } + } + return (lua_Integer)res; +} + + +static int str_unpack (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); + size_t ld; + const char *data = luaL_checklstring(L, 2, &ld); + size_t pos = posrelatI(luaL_optinteger(L, 3, 1), ld) - 1; + int n = 0; /* number of results */ + luaL_argcheck(L, pos <= ld, 3, "initial position out of string"); + initheader(L, &h); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, pos, &fmt, &size, &ntoalign); + luaL_argcheck(L, (size_t)ntoalign + size <= ld - pos, 2, + "data string too short"); + pos += ntoalign; /* skip alignment */ + /* stack space for item + next position */ + luaL_checkstack(L, 2, "too many results"); + n++; + switch (opt) { + case Kint: + case Kuint: { + lua_Integer res = unpackint(L, data + pos, h.islittle, size, + (opt == Kint)); + lua_pushinteger(L, res); + break; + } + case Kfloat: { + Ftypes u; + lua_Number num; + copywithendian((char *)&u, data + pos, size, h.islittle); + if (size == sizeof(u.f)) num = (lua_Number)u.f; + else if (size == sizeof(u.d)) num = (lua_Number)u.d; + else num = u.n; + lua_pushnumber(L, num); + break; + } + case Kchar: { + lua_pushlstring(L, data + pos, size); + break; + } + case Kstring: { + size_t len = (size_t)unpackint(L, data + pos, h.islittle, size, 0); + luaL_argcheck(L, len <= ld - pos - size, 2, "data string too short"); + lua_pushlstring(L, data + pos + size, len); + pos += len; /* skip string */ + break; + } + case Kzstr: { + size_t len = strlen(data + pos); + luaL_argcheck(L, pos + len < ld, 2, + "unfinished string for format 'z'"); + lua_pushlstring(L, data + pos, len); + pos += len + 1; /* skip string plus final '\0' */ + break; + } + case Kpaddalign: case Kpadding: case Knop: + n--; /* undo increment */ + break; + } + pos += size; + } + lua_pushinteger(L, pos + 1); /* next position */ + return n + 1; +} + +/* }====================================================== */ + + +static const luaL_Reg strlib[] = { + {"byte", str_byte}, + {"char", str_char}, + {"dump", str_dump}, + {"find", str_find}, + {"format", str_format}, + {"gmatch", gmatch}, + {"gsub", str_gsub}, + {"len", str_len}, + {"lower", str_lower}, + {"match", str_match}, + {"rep", str_rep}, + {"reverse", str_reverse}, + {"sub", str_sub}, + {"upper", str_upper}, + {"pack", str_pack}, + {"packsize", str_packsize}, + {"unpack", str_unpack}, + {NULL, NULL} +}; + + +static void createmetatable (lua_State *L) { + /* table to be metatable for strings */ + luaL_newlibtable(L, stringmetamethods); + luaL_setfuncs(L, stringmetamethods, 0); + lua_pushliteral(L, ""); /* dummy string */ + lua_pushvalue(L, -2); /* copy table */ + lua_setmetatable(L, -2); /* set table as metatable for strings */ + lua_pop(L, 1); /* pop dummy string */ + lua_pushvalue(L, -2); /* get string library */ + lua_setfield(L, -2, "__index"); /* metatable.__index = string */ + lua_pop(L, 1); /* pop metatable */ +} + + +/* +** Open string library +*/ +LUAMOD_API int luaopen_string (lua_State *L) { + luaL_newlib(L, strlib); + createmetatable(L); + return 1; +} + diff --git a/Lua/ltable.c b/Lua/ltable.c new file mode 100644 index 00000000..7e7cbed9 --- /dev/null +++ b/Lua/ltable.c @@ -0,0 +1,938 @@ +/* +** $Id: ltable.c $ +** Lua tables (hash) +** See Copyright Notice in lua.h +*/ + +#define ltable_c +#define LUA_CORE + +#include "lprefix.h" + + +/* +** Implementation of tables (aka arrays, objects, or hash tables). +** Tables keep its elements in two parts: an array part and a hash part. +** Non-negative integer keys are all candidates to be kept in the array +** part. The actual size of the array is the largest 'n' such that +** more than half the slots between 1 and n are in use. +** Hash uses a mix of chained scatter table with Brent's variation. +** A main invariant of these tables is that, if an element is not +** in its main position (i.e. the 'original' position that its hash gives +** to it), then the colliding element is in its own main position. +** Hence even when the load factor reaches 100%, performance remains good. +*/ + +#include +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lvm.h" + + +/* +** MAXABITS is the largest integer such that MAXASIZE fits in an +** unsigned int. +*/ +#define MAXABITS cast_int(sizeof(int) * CHAR_BIT - 1) + + +/* +** MAXASIZE is the maximum size of the array part. It is the minimum +** between 2^MAXABITS and the maximum size that, measured in bytes, +** fits in a 'size_t'. +*/ +#define MAXASIZE luaM_limitN(1u << MAXABITS, TValue) + +/* +** MAXHBITS is the largest integer such that 2^MAXHBITS fits in a +** signed int. +*/ +#define MAXHBITS (MAXABITS - 1) + + +/* +** MAXHSIZE is the maximum size of the hash part. It is the minimum +** between 2^MAXHBITS and the maximum size such that, measured in bytes, +** it fits in a 'size_t'. +*/ +#define MAXHSIZE luaM_limitN(1u << MAXHBITS, Node) + + +#define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) + +#define hashstr(t,str) hashpow2(t, (str)->hash) +#define hashboolean(t,p) hashpow2(t, p) +#define hashint(t,i) hashpow2(t, i) + + +/* +** for some types, it is better to avoid modulus by power of 2, as +** they tend to have many 2 factors. +*/ +#define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) + + +#define hashpointer(t,p) hashmod(t, point2uint(p)) + + +#define dummynode (&dummynode_) + +static const Node dummynode_ = { + {{NULL}, LUA_VEMPTY, /* value's value and type */ + LUA_VNIL, 0, {NULL}} /* key type, next, and key value */ +}; + + +static const TValue absentkey = {ABSTKEYCONSTANT}; + + + +/* +** Hash for floating-point numbers. +** The main computation should be just +** n = frexp(n, &i); return (n * INT_MAX) + i +** but there are some numerical subtleties. +** In a two-complement representation, INT_MAX does not has an exact +** representation as a float, but INT_MIN does; because the absolute +** value of 'frexp' is smaller than 1 (unless 'n' is inf/NaN), the +** absolute value of the product 'frexp * -INT_MIN' is smaller or equal +** to INT_MAX. Next, the use of 'unsigned int' avoids overflows when +** adding 'i'; the use of '~u' (instead of '-u') avoids problems with +** INT_MIN. +*/ +#if !defined(l_hashfloat) +static int l_hashfloat (lua_Number n) { + int i; + lua_Integer ni; + n = l_mathop(frexp)(n, &i) * -cast_num(INT_MIN); + if (!lua_numbertointeger(n, &ni)) { /* is 'n' inf/-inf/NaN? */ + lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == cast_num(HUGE_VAL)); + return 0; + } + else { /* normal case */ + unsigned int u = cast_uint(i) + cast_uint(ni); + return cast_int(u <= cast_uint(INT_MAX) ? u : ~u); + } +} +#endif + + +/* +** returns the 'main' position of an element in a table (that is, +** the index of its hash value). The key comes broken (tag in 'ktt' +** and value in 'vkl') so that we can call it on keys inserted into +** nodes. +*/ +static Node *mainposition (const Table *t, int ktt, const Value *kvl) { + switch (withvariant(ktt)) { + case LUA_VNUMINT: + return hashint(t, ivalueraw(*kvl)); + case LUA_VNUMFLT: + return hashmod(t, l_hashfloat(fltvalueraw(*kvl))); + case LUA_VSHRSTR: + return hashstr(t, tsvalueraw(*kvl)); + case LUA_VLNGSTR: + return hashpow2(t, luaS_hashlongstr(tsvalueraw(*kvl))); + case LUA_VFALSE: + return hashboolean(t, 0); + case LUA_VTRUE: + return hashboolean(t, 1); + case LUA_VLIGHTUSERDATA: + return hashpointer(t, pvalueraw(*kvl)); + case LUA_VLCF: + return hashpointer(t, fvalueraw(*kvl)); + default: + return hashpointer(t, gcvalueraw(*kvl)); + } +} + + +/* +** Returns the main position of an element given as a 'TValue' +*/ +static Node *mainpositionTV (const Table *t, const TValue *key) { + return mainposition(t, rawtt(key), valraw(key)); +} + + +/* +** Check whether key 'k1' is equal to the key in node 'n2'. This +** equality is raw, so there are no metamethods. Floats with integer +** values have been normalized, so integers cannot be equal to +** floats. It is assumed that 'eqshrstr' is simply pointer equality, so +** that short strings are handled in the default case. +** A true 'deadok' means to accept dead keys as equal to their original +** values. All dead keys are compared in the default case, by pointer +** identity. (Only collectable objects can produce dead keys.) Note that +** dead long strings are also compared by identity. +** Once a key is dead, its corresponding value may be collected, and +** then another value can be created with the same address. If this +** other value is given to 'next', 'equalkey' will signal a false +** positive. In a regular traversal, this situation should never happen, +** as all keys given to 'next' came from the table itself, and therefore +** could not have been collected. Outside a regular traversal, we +** have garbage in, garbage out. What is relevant is that this false +** positive does not break anything. (In particular, 'next' will return +** some other valid item on the table or nil.) +*/ +static int equalkey (const TValue *k1, const Node *n2, int deadok) { + if ((rawtt(k1) != keytt(n2)) && /* not the same variants? */ + !(deadok && keyisdead(n2) && iscollectable(k1))) + return 0; /* cannot be same key */ + switch (keytt(n2)) { + case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: + return 1; + case LUA_VNUMINT: + return (ivalue(k1) == keyival(n2)); + case LUA_VNUMFLT: + return luai_numeq(fltvalue(k1), fltvalueraw(keyval(n2))); + case LUA_VLIGHTUSERDATA: + return pvalue(k1) == pvalueraw(keyval(n2)); + case LUA_VLCF: + return fvalue(k1) == fvalueraw(keyval(n2)); + case ctb(LUA_VLNGSTR): + return luaS_eqlngstr(tsvalue(k1), keystrval(n2)); + default: + return gcvalue(k1) == gcvalueraw(keyval(n2)); + } +} + + +/* +** True if value of 'alimit' is equal to the real size of the array +** part of table 't'. (Otherwise, the array part must be larger than +** 'alimit'.) +*/ +#define limitequalsasize(t) (isrealasize(t) || ispow2((t)->alimit)) + + +/* +** Returns the real size of the 'array' array +*/ +LUAI_FUNC unsigned int luaH_realasize (const Table *t) { + if (limitequalsasize(t)) + return t->alimit; /* this is the size */ + else { + unsigned int size = t->alimit; + /* compute the smallest power of 2 not smaller than 'n' */ + size |= (size >> 1); + size |= (size >> 2); + size |= (size >> 4); + size |= (size >> 8); + size |= (size >> 16); +#if (UINT_MAX >> 30) > 3 + size |= (size >> 32); /* unsigned int has more than 32 bits */ +#endif + size++; + lua_assert(ispow2(size) && size/2 < t->alimit && t->alimit < size); + return size; + } +} + + +/* +** Check whether real size of the array is a power of 2. +** (If it is not, 'alimit' cannot be changed to any other value +** without changing the real size.) +*/ +static int ispow2realasize (const Table *t) { + return (!isrealasize(t) || ispow2(t->alimit)); +} + + +static unsigned int setlimittosize (Table *t) { + t->alimit = luaH_realasize(t); + setrealasize(t); + return t->alimit; +} + + +#define limitasasize(t) check_exp(isrealasize(t), t->alimit) + + + +/* +** "Generic" get version. (Not that generic: not valid for integers, +** which may be in array part, nor for floats with integral values.) +** See explanation about 'deadok' in function 'equalkey'. +*/ +static const TValue *getgeneric (Table *t, const TValue *key, int deadok) { + Node *n = mainpositionTV(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (equalkey(key, n, deadok)) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) + return &absentkey; /* not found */ + n += nx; + } + } +} + + +/* +** returns the index for 'k' if 'k' is an appropriate key to live in +** the array part of a table, 0 otherwise. +*/ +static unsigned int arrayindex (lua_Integer k) { + if (l_castS2U(k) - 1u < MAXASIZE) /* 'k' in [1, MAXASIZE]? */ + return cast_uint(k); /* 'key' is an appropriate array index */ + else + return 0; +} + + +/* +** returns the index of a 'key' for table traversals. First goes all +** elements in the array part, then elements in the hash part. The +** beginning of a traversal is signaled by 0. +*/ +static unsigned int findindex (lua_State *L, Table *t, TValue *key, + unsigned int asize) { + unsigned int i; + if (ttisnil(key)) return 0; /* first iteration */ + i = ttisinteger(key) ? arrayindex(ivalue(key)) : 0; + if (i - 1u < asize) /* is 'key' inside array part? */ + return i; /* yes; that's the index */ + else { + const TValue *n = getgeneric(t, key, 1); + if (unlikely(isabstkey(n))) + luaG_runerror(L, "invalid key to 'next'"); /* key not found */ + i = cast_int(nodefromval(n) - gnode(t, 0)); /* key index in hash table */ + /* hash elements are numbered after array ones */ + return (i + 1) + asize; + } +} + + +int luaH_next (lua_State *L, Table *t, StkId key) { + unsigned int asize = luaH_realasize(t); + unsigned int i = findindex(L, t, s2v(key), asize); /* find original key */ + for (; i < asize; i++) { /* try first array part */ + if (!isempty(&t->array[i])) { /* a non-empty entry? */ + setivalue(s2v(key), i + 1); + setobj2s(L, key + 1, &t->array[i]); + return 1; + } + } + for (i -= asize; cast_int(i) < sizenode(t); i++) { /* hash part */ + if (!isempty(gval(gnode(t, i)))) { /* a non-empty entry? */ + Node *n = gnode(t, i); + getnodekey(L, s2v(key), n); + setobj2s(L, key + 1, gval(n)); + return 1; + } + } + return 0; /* no more elements */ +} + + +static void freehash (lua_State *L, Table *t) { + if (!isdummy(t)) + luaM_freearray(L, t->node, cast_sizet(sizenode(t))); +} + + +/* +** {============================================================= +** Rehash +** ============================================================== +*/ + +/* +** Compute the optimal size for the array part of table 't'. 'nums' is a +** "count array" where 'nums[i]' is the number of integers in the table +** between 2^(i - 1) + 1 and 2^i. 'pna' enters with the total number of +** integer keys in the table and leaves with the number of keys that +** will go to the array part; return the optimal size. (The condition +** 'twotoi > 0' in the for loop stops the loop if 'twotoi' overflows.) +*/ +static unsigned int computesizes (unsigned int nums[], unsigned int *pna) { + int i; + unsigned int twotoi; /* 2^i (candidate for optimal size) */ + unsigned int a = 0; /* number of elements smaller than 2^i */ + unsigned int na = 0; /* number of elements to go to array part */ + unsigned int optimal = 0; /* optimal size for array part */ + /* loop while keys can fill more than half of total size */ + for (i = 0, twotoi = 1; + twotoi > 0 && *pna > twotoi / 2; + i++, twotoi *= 2) { + a += nums[i]; + if (a > twotoi/2) { /* more than half elements present? */ + optimal = twotoi; /* optimal size (till now) */ + na = a; /* all elements up to 'optimal' will go to array part */ + } + } + lua_assert((optimal == 0 || optimal / 2 < na) && na <= optimal); + *pna = na; + return optimal; +} + + +static int countint (lua_Integer key, unsigned int *nums) { + unsigned int k = arrayindex(key); + if (k != 0) { /* is 'key' an appropriate array index? */ + nums[luaO_ceillog2(k)]++; /* count as such */ + return 1; + } + else + return 0; +} + + +/* +** Count keys in array part of table 't': Fill 'nums[i]' with +** number of keys that will go into corresponding slice and return +** total number of non-nil keys. +*/ +static unsigned int numusearray (const Table *t, unsigned int *nums) { + int lg; + unsigned int ttlg; /* 2^lg */ + unsigned int ause = 0; /* summation of 'nums' */ + unsigned int i = 1; /* count to traverse all array keys */ + unsigned int asize = limitasasize(t); /* real array size */ + /* traverse each slice */ + for (lg = 0, ttlg = 1; lg <= MAXABITS; lg++, ttlg *= 2) { + unsigned int lc = 0; /* counter */ + unsigned int lim = ttlg; + if (lim > asize) { + lim = asize; /* adjust upper limit */ + if (i > lim) + break; /* no more elements to count */ + } + /* count elements in range (2^(lg - 1), 2^lg] */ + for (; i <= lim; i++) { + if (!isempty(&t->array[i-1])) + lc++; + } + nums[lg] += lc; + ause += lc; + } + return ause; +} + + +static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) { + int totaluse = 0; /* total number of elements */ + int ause = 0; /* elements added to 'nums' (can go to array part) */ + int i = sizenode(t); + while (i--) { + Node *n = &t->node[i]; + if (!isempty(gval(n))) { + if (keyisinteger(n)) + ause += countint(keyival(n), nums); + totaluse++; + } + } + *pna += ause; + return totaluse; +} + + +/* +** Creates an array for the hash part of a table with the given +** size, or reuses the dummy node if size is zero. +** The computation for size overflow is in two steps: the first +** comparison ensures that the shift in the second one does not +** overflow. +*/ +static void setnodevector (lua_State *L, Table *t, unsigned int size) { + if (size == 0) { /* no elements to hash part? */ + t->node = cast(Node *, dummynode); /* use common 'dummynode' */ + t->lsizenode = 0; + t->lastfree = NULL; /* signal that it is using dummy node */ + } + else { + int i; + int lsize = luaO_ceillog2(size); + if (lsize > MAXHBITS || (1u << lsize) > MAXHSIZE) + luaG_runerror(L, "table overflow"); + size = twoto(lsize); + t->node = luaM_newvector(L, size, Node); + for (i = 0; i < (int)size; i++) { + Node *n = gnode(t, i); + gnext(n) = 0; + setnilkey(n); + setempty(gval(n)); + } + t->lsizenode = cast_byte(lsize); + t->lastfree = gnode(t, size); /* all positions are free */ + } +} + + +/* +** (Re)insert all elements from the hash part of 'ot' into table 't'. +*/ +static void reinsert (lua_State *L, Table *ot, Table *t) { + int j; + int size = sizenode(ot); + for (j = 0; j < size; j++) { + Node *old = gnode(ot, j); + if (!isempty(gval(old))) { + /* doesn't need barrier/invalidate cache, as entry was + already present in the table */ + TValue k; + getnodekey(L, &k, old); + setobjt2t(L, luaH_set(L, t, &k), gval(old)); + } + } +} + + +/* +** Exchange the hash part of 't1' and 't2'. +*/ +static void exchangehashpart (Table *t1, Table *t2) { + lu_byte lsizenode = t1->lsizenode; + Node *node = t1->node; + Node *lastfree = t1->lastfree; + t1->lsizenode = t2->lsizenode; + t1->node = t2->node; + t1->lastfree = t2->lastfree; + t2->lsizenode = lsizenode; + t2->node = node; + t2->lastfree = lastfree; +} + + +/* +** Resize table 't' for the new given sizes. Both allocations (for +** the hash part and for the array part) can fail, which creates some +** subtleties. If the first allocation, for the hash part, fails, an +** error is raised and that is it. Otherwise, it copies the elements from +** the shrinking part of the array (if it is shrinking) into the new +** hash. Then it reallocates the array part. If that fails, the table +** is in its original state; the function frees the new hash part and then +** raises the allocation error. Otherwise, it sets the new hash part +** into the table, initializes the new part of the array (if any) with +** nils and reinserts the elements of the old hash back into the new +** parts of the table. +*/ +void luaH_resize (lua_State *L, Table *t, unsigned int newasize, + unsigned int nhsize) { + unsigned int i; + Table newt; /* to keep the new hash part */ + unsigned int oldasize = setlimittosize(t); + TValue *newarray; + /* create new hash part with appropriate size into 'newt' */ + setnodevector(L, &newt, nhsize); + if (newasize < oldasize) { /* will array shrink? */ + t->alimit = newasize; /* pretend array has new size... */ + exchangehashpart(t, &newt); /* and new hash */ + /* re-insert into the new hash the elements from vanishing slice */ + for (i = newasize; i < oldasize; i++) { + if (!isempty(&t->array[i])) + luaH_setint(L, t, i + 1, &t->array[i]); + } + t->alimit = oldasize; /* restore current size... */ + exchangehashpart(t, &newt); /* and hash (in case of errors) */ + } + /* allocate new array */ + newarray = luaM_reallocvector(L, t->array, oldasize, newasize, TValue); + if (unlikely(newarray == NULL && newasize > 0)) { /* allocation failed? */ + freehash(L, &newt); /* release new hash part */ + luaM_error(L); /* raise error (with array unchanged) */ + } + /* allocation ok; initialize new part of the array */ + exchangehashpart(t, &newt); /* 't' has the new hash ('newt' has the old) */ + t->array = newarray; /* set new array part */ + t->alimit = newasize; + for (i = oldasize; i < newasize; i++) /* clear new slice of the array */ + setempty(&t->array[i]); + /* re-insert elements from old hash part into new parts */ + reinsert(L, &newt, t); /* 'newt' now has the old hash */ + freehash(L, &newt); /* free old hash part */ +} + + +void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize) { + int nsize = allocsizenode(t); + luaH_resize(L, t, nasize, nsize); +} + +/* +** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i +*/ +static void rehash (lua_State *L, Table *t, const TValue *ek) { + unsigned int asize; /* optimal size for array part */ + unsigned int na; /* number of keys in the array part */ + unsigned int nums[MAXABITS + 1]; + int i; + int totaluse; + for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */ + setlimittosize(t); + na = numusearray(t, nums); /* count keys in array part */ + totaluse = na; /* all those keys are integer keys */ + totaluse += numusehash(t, nums, &na); /* count keys in hash part */ + /* count extra key */ + if (ttisinteger(ek)) + na += countint(ivalue(ek), nums); + totaluse++; + /* compute new size for array part */ + asize = computesizes(nums, &na); + /* resize the table to new computed sizes */ + luaH_resize(L, t, asize, totaluse - na); +} + + + +/* +** }============================================================= +*/ + + +Table *luaH_new (lua_State *L) { + GCObject *o = luaC_newobj(L, LUA_VTABLE, sizeof(Table)); + Table *t = gco2t(o); + t->metatable = NULL; + t->flags = cast_byte(maskflags); /* table has no metamethod fields */ + t->array = NULL; + t->alimit = 0; + setnodevector(L, t, 0); + return t; +} + + +void luaH_free (lua_State *L, Table *t) { + freehash(L, t); + luaM_freearray(L, t->array, luaH_realasize(t)); + luaM_free(L, t); +} + + +static Node *getfreepos (Table *t) { + if (!isdummy(t)) { + while (t->lastfree > t->node) { + t->lastfree--; + if (keyisnil(t->lastfree)) + return t->lastfree; + } + } + return NULL; /* could not find a free place */ +} + + + +/* +** inserts a new key into a hash table; first, check whether key's main +** position is free. If not, check whether colliding node is in its main +** position or not: if it is not, move colliding node to an empty place and +** put new key in its main position; otherwise (colliding node is in its main +** position), new key goes to an empty position. +*/ +TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { + Node *mp; + TValue aux; + if (unlikely(ttisnil(key))) + luaG_runerror(L, "table index is nil"); + else if (ttisfloat(key)) { + lua_Number f = fltvalue(key); + lua_Integer k; + if (luaV_flttointeger(f, &k, F2Ieq)) { /* does key fit in an integer? */ + setivalue(&aux, k); + key = &aux; /* insert it as an integer */ + } + else if (unlikely(luai_numisnan(f))) + luaG_runerror(L, "table index is NaN"); + } + mp = mainpositionTV(t, key); + if (!isempty(gval(mp)) || isdummy(t)) { /* main position is taken? */ + Node *othern; + Node *f = getfreepos(t); /* get a free place */ + if (f == NULL) { /* cannot find a free place? */ + rehash(L, t, key); /* grow table */ + /* whatever called 'newkey' takes care of TM cache */ + return luaH_set(L, t, key); /* insert key into grown table */ + } + lua_assert(!isdummy(t)); + othern = mainposition(t, keytt(mp), &keyval(mp)); + if (othern != mp) { /* is colliding node out of its main position? */ + /* yes; move colliding node into free position */ + while (othern + gnext(othern) != mp) /* find previous */ + othern += gnext(othern); + gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */ + *f = *mp; /* copy colliding node into free pos. (mp->next also goes) */ + if (gnext(mp) != 0) { + gnext(f) += cast_int(mp - f); /* correct 'next' */ + gnext(mp) = 0; /* now 'mp' is free */ + } + setempty(gval(mp)); + } + else { /* colliding node is in its own main position */ + /* new node will go into free position */ + if (gnext(mp) != 0) + gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */ + else lua_assert(gnext(f) == 0); + gnext(mp) = cast_int(f - mp); + mp = f; + } + } + setnodekey(L, mp, key); + luaC_barrierback(L, obj2gco(t), key); + lua_assert(isempty(gval(mp))); + return gval(mp); +} + + +/* +** Search function for integers. If integer is inside 'alimit', get it +** directly from the array part. Otherwise, if 'alimit' is not equal to +** the real size of the array, key still can be in the array part. In +** this case, try to avoid a call to 'luaH_realasize' when key is just +** one more than the limit (so that it can be incremented without +** changing the real size of the array). +*/ +const TValue *luaH_getint (Table *t, lua_Integer key) { + if (l_castS2U(key) - 1u < t->alimit) /* 'key' in [1, t->alimit]? */ + return &t->array[key - 1]; + else if (!limitequalsasize(t) && /* key still may be in the array part? */ + (l_castS2U(key) == t->alimit + 1 || + l_castS2U(key) - 1u < luaH_realasize(t))) { + t->alimit = cast_uint(key); /* probably '#t' is here now */ + return &t->array[key - 1]; + } + else { + Node *n = hashint(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (keyisinteger(n) && keyival(n) == key) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) break; + n += nx; + } + } + return &absentkey; + } +} + + +/* +** search function for short strings +*/ +const TValue *luaH_getshortstr (Table *t, TString *key) { + Node *n = hashstr(t, key); + lua_assert(key->tt == LUA_VSHRSTR); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (keyisshrstr(n) && eqshrstr(keystrval(n), key)) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) + return &absentkey; /* not found */ + n += nx; + } + } +} + + +const TValue *luaH_getstr (Table *t, TString *key) { + if (key->tt == LUA_VSHRSTR) + return luaH_getshortstr(t, key); + else { /* for long strings, use generic case */ + TValue ko; + setsvalue(cast(lua_State *, NULL), &ko, key); + return getgeneric(t, &ko, 0); + } +} + + +/* +** main search function +*/ +const TValue *luaH_get (Table *t, const TValue *key) { + switch (ttypetag(key)) { + case LUA_VSHRSTR: return luaH_getshortstr(t, tsvalue(key)); + case LUA_VNUMINT: return luaH_getint(t, ivalue(key)); + case LUA_VNIL: return &absentkey; + case LUA_VNUMFLT: { + lua_Integer k; + if (luaV_flttointeger(fltvalue(key), &k, F2Ieq)) /* integral index? */ + return luaH_getint(t, k); /* use specialized version */ + /* else... */ + } /* FALLTHROUGH */ + default: + return getgeneric(t, key, 0); + } +} + + +/* +** beware: when using this function you probably need to check a GC +** barrier and invalidate the TM cache. +*/ +TValue *luaH_set (lua_State *L, Table *t, const TValue *key) { + const TValue *p = luaH_get(t, key); + if (!isabstkey(p)) + return cast(TValue *, p); + else return luaH_newkey(L, t, key); +} + + +void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) { + const TValue *p = luaH_getint(t, key); + TValue *cell; + if (!isabstkey(p)) + cell = cast(TValue *, p); + else { + TValue k; + setivalue(&k, key); + cell = luaH_newkey(L, t, &k); + } + setobj2t(L, cell, value); +} + + +/* +** Try to find a boundary in the hash part of table 't'. From the +** caller, we know that 'j' is zero or present and that 'j + 1' is +** present. We want to find a larger key that is absent from the +** table, so that we can do a binary search between the two keys to +** find a boundary. We keep doubling 'j' until we get an absent index. +** If the doubling would overflow, we try LUA_MAXINTEGER. If it is +** absent, we are ready for the binary search. ('j', being max integer, +** is larger or equal to 'i', but it cannot be equal because it is +** absent while 'i' is present; so 'j > i'.) Otherwise, 'j' is a +** boundary. ('j + 1' cannot be a present integer key because it is +** not a valid integer in Lua.) +*/ +static lua_Unsigned hash_search (Table *t, lua_Unsigned j) { + lua_Unsigned i; + if (j == 0) j++; /* the caller ensures 'j + 1' is present */ + do { + i = j; /* 'i' is a present index */ + if (j <= l_castS2U(LUA_MAXINTEGER) / 2) + j *= 2; + else { + j = LUA_MAXINTEGER; + if (isempty(luaH_getint(t, j))) /* t[j] not present? */ + break; /* 'j' now is an absent index */ + else /* weird case */ + return j; /* well, max integer is a boundary... */ + } + } while (!isempty(luaH_getint(t, j))); /* repeat until an absent t[j] */ + /* i < j && t[i] present && t[j] absent */ + while (j - i > 1u) { /* do a binary search between them */ + lua_Unsigned m = (i + j) / 2; + if (isempty(luaH_getint(t, m))) j = m; + else i = m; + } + return i; +} + + +static unsigned int binsearch (const TValue *array, unsigned int i, + unsigned int j) { + while (j - i > 1u) { /* binary search */ + unsigned int m = (i + j) / 2; + if (isempty(&array[m - 1])) j = m; + else i = m; + } + return i; +} + + +/* +** Try to find a boundary in table 't'. (A 'boundary' is an integer index +** such that t[i] is present and t[i+1] is absent, or 0 if t[1] is absent +** and 'maxinteger' if t[maxinteger] is present.) +** (In the next explanation, we use Lua indices, that is, with base 1. +** The code itself uses base 0 when indexing the array part of the table.) +** The code starts with 'limit = t->alimit', a position in the array +** part that may be a boundary. +** +** (1) If 't[limit]' is empty, there must be a boundary before it. +** As a common case (e.g., after 't[#t]=nil'), check whether 'limit-1' +** is present. If so, it is a boundary. Otherwise, do a binary search +** between 0 and limit to find a boundary. In both cases, try to +** use this boundary as the new 'alimit', as a hint for the next call. +** +** (2) If 't[limit]' is not empty and the array has more elements +** after 'limit', try to find a boundary there. Again, try first +** the special case (which should be quite frequent) where 'limit+1' +** is empty, so that 'limit' is a boundary. Otherwise, check the +** last element of the array part. If it is empty, there must be a +** boundary between the old limit (present) and the last element +** (absent), which is found with a binary search. (This boundary always +** can be a new limit.) +** +** (3) The last case is when there are no elements in the array part +** (limit == 0) or its last element (the new limit) is present. +** In this case, must check the hash part. If there is no hash part +** or 'limit+1' is absent, 'limit' is a boundary. Otherwise, call +** 'hash_search' to find a boundary in the hash part of the table. +** (In those cases, the boundary is not inside the array part, and +** therefore cannot be used as a new limit.) +*/ +lua_Unsigned luaH_getn (Table *t) { + unsigned int limit = t->alimit; + if (limit > 0 && isempty(&t->array[limit - 1])) { /* (1)? */ + /* there must be a boundary before 'limit' */ + if (limit >= 2 && !isempty(&t->array[limit - 2])) { + /* 'limit - 1' is a boundary; can it be a new limit? */ + if (ispow2realasize(t) && !ispow2(limit - 1)) { + t->alimit = limit - 1; + setnorealasize(t); /* now 'alimit' is not the real size */ + } + return limit - 1; + } + else { /* must search for a boundary in [0, limit] */ + unsigned int boundary = binsearch(t->array, 0, limit); + /* can this boundary represent the real size of the array? */ + if (ispow2realasize(t) && boundary > luaH_realasize(t) / 2) { + t->alimit = boundary; /* use it as the new limit */ + setnorealasize(t); + } + return boundary; + } + } + /* 'limit' is zero or present in table */ + if (!limitequalsasize(t)) { /* (2)? */ + /* 'limit' > 0 and array has more elements after 'limit' */ + if (isempty(&t->array[limit])) /* 'limit + 1' is empty? */ + return limit; /* this is the boundary */ + /* else, try last element in the array */ + limit = luaH_realasize(t); + if (isempty(&t->array[limit - 1])) { /* empty? */ + /* there must be a boundary in the array after old limit, + and it must be a valid new limit */ + unsigned int boundary = binsearch(t->array, t->alimit, limit); + t->alimit = boundary; + return boundary; + } + /* else, new limit is present in the table; check the hash part */ + } + /* (3) 'limit' is the last element and either is zero or present in table */ + lua_assert(limit == luaH_realasize(t) && + (limit == 0 || !isempty(&t->array[limit - 1]))); + if (isdummy(t) || isempty(luaH_getint(t, cast(lua_Integer, limit + 1)))) + return limit; /* 'limit + 1' is absent */ + else /* 'limit + 1' is also present */ + return hash_search(t, limit); +} + + + +#if defined(LUA_DEBUG) + +/* export these functions for the test library */ + +Node *luaH_mainposition (const Table *t, const TValue *key) { + return mainpositionTV(t, key); +} + +int luaH_isdummy (const Table *t) { return isdummy(t); } + +#endif diff --git a/Lua/ltable.h b/Lua/ltable.h new file mode 100644 index 00000000..c0060f4b --- /dev/null +++ b/Lua/ltable.h @@ -0,0 +1,62 @@ +/* +** $Id: ltable.h $ +** Lua tables (hash) +** See Copyright Notice in lua.h +*/ + +#ifndef ltable_h +#define ltable_h + +#include "lobject.h" + + +#define gnode(t,i) (&(t)->node[i]) +#define gval(n) (&(n)->i_val) +#define gnext(n) ((n)->u.next) + + +/* +** Clear all bits of fast-access metamethods, which means that the table +** may have any of these metamethods. (First access that fails after the +** clearing will set the bit again.) +*/ +#define invalidateTMcache(t) ((t)->flags &= ~maskflags) + + +/* true when 't' is using 'dummynode' as its hash part */ +#define isdummy(t) ((t)->lastfree == NULL) + + +/* allocated size for hash nodes */ +#define allocsizenode(t) (isdummy(t) ? 0 : sizenode(t)) + + +/* returns the Node, given the value of a table entry */ +#define nodefromval(v) cast(Node *, (v)) + + +LUAI_FUNC const TValue *luaH_getint (Table *t, lua_Integer key); +LUAI_FUNC void luaH_setint (lua_State *L, Table *t, lua_Integer key, + TValue *value); +LUAI_FUNC const TValue *luaH_getshortstr (Table *t, TString *key); +LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); +LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); +LUAI_FUNC TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key); +LUAI_FUNC TValue *luaH_set (lua_State *L, Table *t, const TValue *key); +LUAI_FUNC Table *luaH_new (lua_State *L); +LUAI_FUNC void luaH_resize (lua_State *L, Table *t, unsigned int nasize, + unsigned int nhsize); +LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize); +LUAI_FUNC void luaH_free (lua_State *L, Table *t); +LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); +LUAI_FUNC lua_Unsigned luaH_getn (Table *t); +LUAI_FUNC unsigned int luaH_realasize (const Table *t); + + +#if defined(LUA_DEBUG) +LUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key); +LUAI_FUNC int luaH_isdummy (const Table *t); +#endif + + +#endif diff --git a/Lua/ltablib.c b/Lua/ltablib.c new file mode 100644 index 00000000..d344a47e --- /dev/null +++ b/Lua/ltablib.c @@ -0,0 +1,428 @@ +/* +** $Id: ltablib.c $ +** Library for Table Manipulation +** See Copyright Notice in lua.h +*/ + +#define ltablib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** Operations that an object must define to mimic a table +** (some functions only need some of them) +*/ +#define TAB_R 1 /* read */ +#define TAB_W 2 /* write */ +#define TAB_L 4 /* length */ +#define TAB_RW (TAB_R | TAB_W) /* read/write */ + + +#define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n)) + + +static int checkfield (lua_State *L, const char *key, int n) { + lua_pushstring(L, key); + return (lua_rawget(L, -n) != LUA_TNIL); +} + + +/* +** Check that 'arg' either is a table or can behave like one (that is, +** has a metatable with the required metamethods) +*/ +static void checktab (lua_State *L, int arg, int what) { + if (lua_type(L, arg) != LUA_TTABLE) { /* is it not a table? */ + int n = 1; /* number of elements to pop */ + if (lua_getmetatable(L, arg) && /* must have metatable */ + (!(what & TAB_R) || checkfield(L, "__index", ++n)) && + (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && + (!(what & TAB_L) || checkfield(L, "__len", ++n))) { + lua_pop(L, n); /* pop metatable and tested metamethods */ + } + else + luaL_checktype(L, arg, LUA_TTABLE); /* force an error */ + } +} + + +static int tinsert (lua_State *L) { + lua_Integer e = aux_getn(L, 1, TAB_RW) + 1; /* first empty element */ + lua_Integer pos; /* where to insert new element */ + switch (lua_gettop(L)) { + case 2: { /* called with only 2 arguments */ + pos = e; /* insert new element at the end */ + break; + } + case 3: { + lua_Integer i; + pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */ + /* check whether 'pos' is in [1, e] */ + luaL_argcheck(L, (lua_Unsigned)pos - 1u < (lua_Unsigned)e, 2, + "position out of bounds"); + for (i = e; i > pos; i--) { /* move up elements */ + lua_geti(L, 1, i - 1); + lua_seti(L, 1, i); /* t[i] = t[i - 1] */ + } + break; + } + default: { + return luaL_error(L, "wrong number of arguments to 'insert'"); + } + } + lua_seti(L, 1, pos); /* t[pos] = v */ + return 0; +} + + +static int tremove (lua_State *L) { + lua_Integer size = aux_getn(L, 1, TAB_RW); + lua_Integer pos = luaL_optinteger(L, 2, size); + if (pos != size) /* validate 'pos' if given */ + /* check whether 'pos' is in [1, size + 1] */ + luaL_argcheck(L, (lua_Unsigned)pos - 1u <= (lua_Unsigned)size, 1, + "position out of bounds"); + lua_geti(L, 1, pos); /* result = t[pos] */ + for ( ; pos < size; pos++) { + lua_geti(L, 1, pos + 1); + lua_seti(L, 1, pos); /* t[pos] = t[pos + 1] */ + } + lua_pushnil(L); + lua_seti(L, 1, pos); /* remove entry t[pos] */ + return 1; +} + + +/* +** Copy elements (1[f], ..., 1[e]) into (tt[t], tt[t+1], ...). Whenever +** possible, copy in increasing order, which is better for rehashing. +** "possible" means destination after original range, or smaller +** than origin, or copying to another table. +*/ +static int tmove (lua_State *L) { + lua_Integer f = luaL_checkinteger(L, 2); + lua_Integer e = luaL_checkinteger(L, 3); + lua_Integer t = luaL_checkinteger(L, 4); + int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */ + checktab(L, 1, TAB_R); + checktab(L, tt, TAB_W); + if (e >= f) { /* otherwise, nothing to move */ + lua_Integer n, i; + luaL_argcheck(L, f > 0 || e < LUA_MAXINTEGER + f, 3, + "too many elements to move"); + n = e - f + 1; /* number of elements to move */ + luaL_argcheck(L, t <= LUA_MAXINTEGER - n + 1, 4, + "destination wrap around"); + if (t > e || t <= f || (tt != 1 && !lua_compare(L, 1, tt, LUA_OPEQ))) { + for (i = 0; i < n; i++) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); + } + } + else { + for (i = n - 1; i >= 0; i--) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); + } + } + } + lua_pushvalue(L, tt); /* return destination table */ + return 1; +} + + +static void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) { + lua_geti(L, 1, i); + if (!lua_isstring(L, -1)) + luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", + luaL_typename(L, -1), i); + luaL_addvalue(b); +} + + +static int tconcat (lua_State *L) { + luaL_Buffer b; + lua_Integer last = aux_getn(L, 1, TAB_R); + size_t lsep; + const char *sep = luaL_optlstring(L, 2, "", &lsep); + lua_Integer i = luaL_optinteger(L, 3, 1); + last = luaL_optinteger(L, 4, last); + luaL_buffinit(L, &b); + for (; i < last; i++) { + addfield(L, &b, i); + luaL_addlstring(&b, sep, lsep); + } + if (i == last) /* add last value (if interval was not empty) */ + addfield(L, &b, i); + luaL_pushresult(&b); + return 1; +} + + +/* +** {====================================================== +** Pack/unpack +** ======================================================= +*/ + +static int tpack (lua_State *L) { + int i; + int n = lua_gettop(L); /* number of elements to pack */ + lua_createtable(L, n, 1); /* create result table */ + lua_insert(L, 1); /* put it at index 1 */ + for (i = n; i >= 1; i--) /* assign elements */ + lua_seti(L, 1, i); + lua_pushinteger(L, n); + lua_setfield(L, 1, "n"); /* t.n = number of elements */ + return 1; /* return table */ +} + + +static int tunpack (lua_State *L) { + lua_Unsigned n; + lua_Integer i = luaL_optinteger(L, 2, 1); + lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); + if (i > e) return 0; /* empty range */ + n = (lua_Unsigned)e - i; /* number of elements minus 1 (avoid overflows) */ + if (n >= (unsigned int)INT_MAX || !lua_checkstack(L, (int)(++n))) + return luaL_error(L, "too many results to unpack"); + for (; i < e; i++) { /* push arg[i..e - 1] (to avoid overflows) */ + lua_geti(L, 1, i); + } + lua_geti(L, 1, e); /* push last element */ + return (int)n; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Quicksort +** (based on 'Algorithms in MODULA-3', Robert Sedgewick; +** Addison-Wesley, 1993.) +** ======================================================= +*/ + + +/* type for array indices */ +typedef unsigned int IdxT; + + +/* +** Produce a "random" 'unsigned int' to randomize pivot choice. This +** macro is used only when 'sort' detects a big imbalance in the result +** of a partition. (If you don't want/need this "randomness", ~0 is a +** good choice.) +*/ +#if !defined(l_randomizePivot) /* { */ + +#include + +/* size of 'e' measured in number of 'unsigned int's */ +#define sof(e) (sizeof(e) / sizeof(unsigned int)) + +/* +** Use 'time' and 'clock' as sources of "randomness". Because we don't +** know the types 'clock_t' and 'time_t', we cannot cast them to +** anything without risking overflows. A safe way to use their values +** is to copy them to an array of a known type and use the array values. +*/ +static unsigned int l_randomizePivot (void) { + clock_t c = clock(); + time_t t = time(NULL); + unsigned int buff[sof(c) + sof(t)]; + unsigned int i, rnd = 0; + memcpy(buff, &c, sof(c) * sizeof(unsigned int)); + memcpy(buff + sof(c), &t, sof(t) * sizeof(unsigned int)); + for (i = 0; i < sof(buff); i++) + rnd += buff[i]; + return rnd; +} + +#endif /* } */ + + +/* arrays larger than 'RANLIMIT' may use randomized pivots */ +#define RANLIMIT 100u + + +static void set2 (lua_State *L, IdxT i, IdxT j) { + lua_seti(L, 1, i); + lua_seti(L, 1, j); +} + + +/* +** Return true iff value at stack index 'a' is less than the value at +** index 'b' (according to the order of the sort). +*/ +static int sort_comp (lua_State *L, int a, int b) { + if (lua_isnil(L, 2)) /* no function? */ + return lua_compare(L, a, b, LUA_OPLT); /* a < b */ + else { /* function */ + int res; + lua_pushvalue(L, 2); /* push function */ + lua_pushvalue(L, a-1); /* -1 to compensate function */ + lua_pushvalue(L, b-2); /* -2 to compensate function and 'a' */ + lua_call(L, 2, 1); /* call function */ + res = lua_toboolean(L, -1); /* get result */ + lua_pop(L, 1); /* pop result */ + return res; + } +} + + +/* +** Does the partition: Pivot P is at the top of the stack. +** precondition: a[lo] <= P == a[up-1] <= a[up], +** so it only needs to do the partition from lo + 1 to up - 2. +** Pos-condition: a[lo .. i - 1] <= a[i] == P <= a[i + 1 .. up] +** returns 'i'. +*/ +static IdxT partition (lua_State *L, IdxT lo, IdxT up) { + IdxT i = lo; /* will be incremented before first use */ + IdxT j = up - 1; /* will be decremented before first use */ + /* loop invariant: a[lo .. i] <= P <= a[j .. up] */ + for (;;) { + /* next loop: repeat ++i while a[i] < P */ + while ((void)lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) { + if (i == up - 1) /* a[i] < P but a[up - 1] == P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* after the loop, a[i] >= P and a[lo .. i - 1] < P */ + /* next loop: repeat --j while P < a[j] */ + while ((void)lua_geti(L, 1, --j), sort_comp(L, -3, -1)) { + if (j < i) /* j < i but a[j] > P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + /* after the loop, a[j] <= P and a[j + 1 .. up] >= P */ + if (j < i) { /* no elements out of place? */ + /* a[lo .. i - 1] <= P <= a[j + 1 .. i .. up] */ + lua_pop(L, 1); /* pop a[j] */ + /* swap pivot (a[up - 1]) with a[i] to satisfy pos-condition */ + set2(L, up - 1, i); + return i; + } + /* otherwise, swap a[i] - a[j] to restore invariant and repeat */ + set2(L, i, j); + } +} + + +/* +** Choose an element in the middle (2nd-3th quarters) of [lo,up] +** "randomized" by 'rnd' +*/ +static IdxT choosePivot (IdxT lo, IdxT up, unsigned int rnd) { + IdxT r4 = (up - lo) / 4; /* range/4 */ + IdxT p = rnd % (r4 * 2) + (lo + r4); + lua_assert(lo + r4 <= p && p <= up - r4); + return p; +} + + +/* +** Quicksort algorithm (recursive function) +*/ +static void auxsort (lua_State *L, IdxT lo, IdxT up, + unsigned int rnd) { + while (lo < up) { /* loop for tail recursion */ + IdxT p; /* Pivot index */ + IdxT n; /* to be used later */ + /* sort elements 'lo', 'p', and 'up' */ + lua_geti(L, 1, lo); + lua_geti(L, 1, up); + if (sort_comp(L, -1, -2)) /* a[up] < a[lo]? */ + set2(L, lo, up); /* swap a[lo] - a[up] */ + else + lua_pop(L, 2); /* remove both values */ + if (up - lo == 1) /* only 2 elements? */ + return; /* already sorted */ + if (up - lo < RANLIMIT || rnd == 0) /* small interval or no randomize? */ + p = (lo + up)/2; /* middle element is a good pivot */ + else /* for larger intervals, it is worth a random pivot */ + p = choosePivot(lo, up, rnd); + lua_geti(L, 1, p); + lua_geti(L, 1, lo); + if (sort_comp(L, -2, -1)) /* a[p] < a[lo]? */ + set2(L, p, lo); /* swap a[p] - a[lo] */ + else { + lua_pop(L, 1); /* remove a[lo] */ + lua_geti(L, 1, up); + if (sort_comp(L, -1, -2)) /* a[up] < a[p]? */ + set2(L, p, up); /* swap a[up] - a[p] */ + else + lua_pop(L, 2); + } + if (up - lo == 2) /* only 3 elements? */ + return; /* already sorted */ + lua_geti(L, 1, p); /* get middle element (Pivot) */ + lua_pushvalue(L, -1); /* push Pivot */ + lua_geti(L, 1, up - 1); /* push a[up - 1] */ + set2(L, p, up - 1); /* swap Pivot (a[p]) with a[up - 1] */ + p = partition(L, lo, up); + /* a[lo .. p - 1] <= a[p] == P <= a[p + 1 .. up] */ + if (p - lo < up - p) { /* lower interval is smaller? */ + auxsort(L, lo, p - 1, rnd); /* call recursively for lower interval */ + n = p - lo; /* size of smaller interval */ + lo = p + 1; /* tail call for [p + 1 .. up] (upper interval) */ + } + else { + auxsort(L, p + 1, up, rnd); /* call recursively for upper interval */ + n = up - p; /* size of smaller interval */ + up = p - 1; /* tail call for [lo .. p - 1] (lower interval) */ + } + if ((up - lo) / 128 > n) /* partition too imbalanced? */ + rnd = l_randomizePivot(); /* try a new randomization */ + } /* tail call auxsort(L, lo, up, rnd) */ +} + + +static int sort (lua_State *L) { + lua_Integer n = aux_getn(L, 1, TAB_RW); + if (n > 1) { /* non-trivial interval? */ + luaL_argcheck(L, n < INT_MAX, 1, "array too big"); + if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ + luaL_checktype(L, 2, LUA_TFUNCTION); /* must be a function */ + lua_settop(L, 2); /* make sure there are two arguments */ + auxsort(L, 1, (IdxT)n, 0); + } + return 0; +} + +/* }====================================================== */ + + +static const luaL_Reg tab_funcs[] = { + {"concat", tconcat}, + {"insert", tinsert}, + {"pack", tpack}, + {"unpack", tunpack}, + {"remove", tremove}, + {"move", tmove}, + {"sort", sort}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_table (lua_State *L) { + luaL_newlib(L, tab_funcs); + return 1; +} + diff --git a/Lua/ltm.c b/Lua/ltm.c new file mode 100644 index 00000000..4770f96b --- /dev/null +++ b/Lua/ltm.c @@ -0,0 +1,270 @@ +/* +** $Id: ltm.c $ +** Tag methods +** See Copyright Notice in lua.h +*/ + +#define ltm_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + +static const char udatatypename[] = "userdata"; + +LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTYPES] = { + "no value", + "nil", "boolean", udatatypename, "number", + "string", "table", "function", udatatypename, "thread", + "upvalue", "proto" /* these last cases are used for tests only */ +}; + + +void luaT_init (lua_State *L) { + static const char *const luaT_eventname[] = { /* ORDER TM */ + "__index", "__newindex", + "__gc", "__mode", "__len", "__eq", + "__add", "__sub", "__mul", "__mod", "__pow", + "__div", "__idiv", + "__band", "__bor", "__bxor", "__shl", "__shr", + "__unm", "__bnot", "__lt", "__le", + "__concat", "__call", "__close" + }; + int i; + for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]); + luaC_fix(L, obj2gco(G(L)->tmname[i])); /* never collect these names */ + } +} + + +/* +** function to be used with macro "fasttm": optimized for absence of +** tag methods +*/ +const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { + const TValue *tm = luaH_getshortstr(events, ename); + lua_assert(event <= TM_EQ); + if (notm(tm)) { /* no tag method? */ + events->flags |= cast_byte(1u<metatable; + break; + case LUA_TUSERDATA: + mt = uvalue(o)->metatable; + break; + default: + mt = G(L)->mt[ttype(o)]; + } + return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : &G(L)->nilvalue); +} + + +/* +** Return the name of the type of an object. For tables and userdata +** with metatable, use their '__name' metafield, if present. +*/ +const char *luaT_objtypename (lua_State *L, const TValue *o) { + Table *mt; + if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) || + (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL)) { + const TValue *name = luaH_getshortstr(mt, luaS_new(L, "__name")); + if (ttisstring(name)) /* is '__name' a string? */ + return getstr(tsvalue(name)); /* use it as type name */ + } + return ttypename(ttype(o)); /* else use standard type name */ +} + + +void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, const TValue *p3) { + StkId func = L->top; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + setobj2s(L, func + 3, p3); /* 3rd argument */ + L->top = func + 4; + /* metamethod may yield only when called from Lua code */ + if (isLuacode(L->ci)) + luaD_call(L, func, 0); + else + luaD_callnoyield(L, func, 0); +} + + +void luaT_callTMres (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, StkId res) { + ptrdiff_t result = savestack(L, res); + StkId func = L->top; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + L->top += 3; + /* metamethod may yield only when called from Lua code */ + if (isLuacode(L->ci)) + luaD_call(L, func, 1); + else + luaD_callnoyield(L, func, 1); + res = restorestack(L, result); + setobjs2s(L, res, --L->top); /* move result to its place */ +} + + +static int callbinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ + if (notm(tm)) + tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ + if (notm(tm)) return 0; + luaT_callTMres(L, tm, p1, p2, res); + return 1; +} + + +void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + if (!callbinTM(L, p1, p2, res, event)) { + switch (event) { + case TM_BAND: case TM_BOR: case TM_BXOR: + case TM_SHL: case TM_SHR: case TM_BNOT: { + if (ttisnumber(p1) && ttisnumber(p2)) + luaG_tointerror(L, p1, p2); + else + luaG_opinterror(L, p1, p2, "perform bitwise operation on"); + } + /* calls never return, but to avoid warnings: *//* FALLTHROUGH */ + default: + luaG_opinterror(L, p1, p2, "perform arithmetic on"); + } + } +} + + +void luaT_tryconcatTM (lua_State *L) { + StkId top = L->top; + if (!callbinTM(L, s2v(top - 2), s2v(top - 1), top - 2, TM_CONCAT)) + luaG_concaterror(L, s2v(top - 2), s2v(top - 1)); +} + + +void luaT_trybinassocTM (lua_State *L, const TValue *p1, const TValue *p2, + int flip, StkId res, TMS event) { + if (flip) + luaT_trybinTM(L, p2, p1, res, event); + else + luaT_trybinTM(L, p1, p2, res, event); +} + + +void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2, + int flip, StkId res, TMS event) { + TValue aux; + setivalue(&aux, i2); + luaT_trybinassocTM(L, p1, &aux, flip, res, event); +} + + +/* +** Calls an order tag method. +** For lessequal, LUA_COMPAT_LT_LE keeps compatibility with old +** behavior: if there is no '__le', try '__lt', based on l <= r iff +** !(r < l) (assuming a total order). If the metamethod yields during +** this substitution, the continuation has to know about it (to negate +** the result of rtop, event)) /* try original event */ + return !l_isfalse(s2v(L->top)); +#if defined(LUA_COMPAT_LT_LE) + else if (event == TM_LE) { + /* try '!(p2 < p1)' for '(p1 <= p2)' */ + L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ + if (callbinTM(L, p2, p1, L->top, TM_LT)) { + L->ci->callstatus ^= CIST_LEQ; /* clear mark */ + return l_isfalse(s2v(L->top)); + } + /* else error will remove this 'ci'; no need to clear mark */ + } +#endif + luaG_ordererror(L, p1, p2); /* no metamethod found */ + return 0; /* to avoid warnings */ +} + + +int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, + int flip, int isfloat, TMS event) { + TValue aux; const TValue *p2; + if (isfloat) { + setfltvalue(&aux, cast_num(v2)); + } + else + setivalue(&aux, v2); + if (flip) { /* arguments were exchanged? */ + p2 = p1; p1 = &aux; /* correct them */ + } + else + p2 = &aux; + return luaT_callorderTM(L, p1, p2, event); +} + + +void luaT_adjustvarargs (lua_State *L, int nfixparams, CallInfo *ci, + const Proto *p) { + int i; + int actual = cast_int(L->top - ci->func) - 1; /* number of arguments */ + int nextra = actual - nfixparams; /* number of extra arguments */ + ci->u.l.nextraargs = nextra; + luaD_checkstack(L, p->maxstacksize + 1); + /* copy function to the top of the stack */ + setobjs2s(L, L->top++, ci->func); + /* move fixed parameters to the top of the stack */ + for (i = 1; i <= nfixparams; i++) { + setobjs2s(L, L->top++, ci->func + i); + setnilvalue(s2v(ci->func + i)); /* erase original parameter (for GC) */ + } + ci->func += actual + 1; + ci->top += actual + 1; + lua_assert(L->top <= ci->top && ci->top <= L->stack_last); +} + + +void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) { + int i; + int nextra = ci->u.l.nextraargs; + if (wanted < 0) { + wanted = nextra; /* get all extra arguments available */ + checkstackGCp(L, nextra, where); /* ensure stack space */ + L->top = where + nextra; /* next instruction will need top */ + } + for (i = 0; i < wanted && i < nextra; i++) + setobjs2s(L, where + i, ci->func - nextra + i); + for (; i < wanted; i++) /* complete required results with nil */ + setnilvalue(s2v(where + i)); +} + diff --git a/Lua/ltm.h b/Lua/ltm.h new file mode 100644 index 00000000..73b833c6 --- /dev/null +++ b/Lua/ltm.h @@ -0,0 +1,103 @@ +/* +** $Id: ltm.h $ +** Tag methods +** See Copyright Notice in lua.h +*/ + +#ifndef ltm_h +#define ltm_h + + +#include "lobject.h" + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER TM" and "ORDER OP" +*/ +typedef enum { + TM_INDEX, + TM_NEWINDEX, + TM_GC, + TM_MODE, + TM_LEN, + TM_EQ, /* last tag method with fast access */ + TM_ADD, + TM_SUB, + TM_MUL, + TM_MOD, + TM_POW, + TM_DIV, + TM_IDIV, + TM_BAND, + TM_BOR, + TM_BXOR, + TM_SHL, + TM_SHR, + TM_UNM, + TM_BNOT, + TM_LT, + TM_LE, + TM_CONCAT, + TM_CALL, + TM_CLOSE, + TM_N /* number of elements in the enum */ +} TMS; + + +/* +** Mask with 1 in all fast-access methods. A 1 in any of these bits +** in the flag of a (meta)table means the metatable does not have the +** corresponding metamethod field. (Bit 7 of the flag is used for +** 'isrealasize'.) +*/ +#define maskflags (~(~0u << (TM_EQ + 1))) + + +/* +** Test whether there is no tagmethod. +** (Because tagmethods use raw accesses, the result may be an "empty" nil.) +*/ +#define notm(tm) ttisnil(tm) + + +#define gfasttm(g,et,e) ((et) == NULL ? NULL : \ + ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) + +#define fasttm(l,et,e) gfasttm(G(l), et, e) + +#define ttypename(x) luaT_typenames_[(x) + 1] + +LUAI_DDEC(const char *const luaT_typenames_[LUA_TOTALTYPES];) + + +LUAI_FUNC const char *luaT_objtypename (lua_State *L, const TValue *o); + +LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); +LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, + TMS event); +LUAI_FUNC void luaT_init (lua_State *L); + +LUAI_FUNC void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, const TValue *p3); +LUAI_FUNC void luaT_callTMres (lua_State *L, const TValue *f, + const TValue *p1, const TValue *p2, StkId p3); +LUAI_FUNC void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event); +LUAI_FUNC void luaT_tryconcatTM (lua_State *L); +LUAI_FUNC void luaT_trybinassocTM (lua_State *L, const TValue *p1, + const TValue *p2, int inv, StkId res, TMS event); +LUAI_FUNC void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2, + int inv, StkId res, TMS event); +LUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1, + const TValue *p2, TMS event); +LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, + int inv, int isfloat, TMS event); + +LUAI_FUNC void luaT_adjustvarargs (lua_State *L, int nfixparams, + struct CallInfo *ci, const Proto *p); +LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci, + StkId where, int wanted); + + +#endif diff --git a/Lua/lua.h b/Lua/lua.h new file mode 100644 index 00000000..c9d64d7f --- /dev/null +++ b/Lua/lua.h @@ -0,0 +1,517 @@ +/* +** $Id: lua.h $ +** Lua - A Scripting Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + + +#ifndef lua_h +#define lua_h + +#include +#include + + +#include "luaconf.h" + + +#define LUA_VERSION_MAJOR "5" +#define LUA_VERSION_MINOR "4" +#define LUA_VERSION_RELEASE "2" + +#define LUA_VERSION_NUM 504 +#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 0) + +#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2020 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" + + +/* mark for precompiled code ('Lua') */ +#define LUA_SIGNATURE "\x1bLua" + +/* option for multiple returns in 'lua_pcall' and 'lua_call' */ +#define LUA_MULTRET (-1) + + +/* +** Pseudo-indices +** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty +** space after that to help overflow detection) +*/ +#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) +#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) + + +/* thread status */ +#define LUA_OK 0 +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRERR 5 + + +typedef struct lua_State lua_State; + + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + +#define LUA_NUMTYPES 9 + + + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + + +/* predefined values in the registry */ +#define LUA_RIDX_MAINTHREAD 1 +#define LUA_RIDX_GLOBALS 2 +#define LUA_RIDX_LAST LUA_RIDX_GLOBALS + + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + +/* unsigned integer type */ +typedef LUA_UNSIGNED lua_Unsigned; + +/* type for continuation-function contexts */ +typedef LUA_KCONTEXT lua_KContext; + + +/* +** Type for C functions registered with Lua +*/ +typedef int (*lua_CFunction) (lua_State *L); + +/* +** Type for continuation functions +*/ +typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx); + + +/* +** Type for functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud); + + +/* +** Type for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + + +/* +** Type for warning functions +*/ +typedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont); + + + + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + + +/* +** RCS ident string +*/ +extern const char lua_ident[]; + + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); +LUA_API void (lua_close) (lua_State *L); +LUA_API lua_State *(lua_newthread) (lua_State *L); +LUA_API int (lua_resetthread) (lua_State *L); + +LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); + + +LUA_API lua_Number (lua_version) (lua_State *L); + + +/* +** basic stack manipulation +*/ +LUA_API int (lua_absindex) (lua_State *L, int idx); +LUA_API int (lua_gettop) (lua_State *L); +LUA_API void (lua_settop) (lua_State *L, int idx); +LUA_API void (lua_pushvalue) (lua_State *L, int idx); +LUA_API void (lua_rotate) (lua_State *L, int idx, int n); +LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); +LUA_API int (lua_checkstack) (lua_State *L, int n); + +LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); + + +/* +** access functions (stack -> C) +*/ + +LUA_API int (lua_isnumber) (lua_State *L, int idx); +LUA_API int (lua_isstring) (lua_State *L, int idx); +LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isinteger) (lua_State *L, int idx); +LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_type) (lua_State *L, int idx); +LUA_API const char *(lua_typename) (lua_State *L, int tp); + +LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); +LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); +LUA_API int (lua_toboolean) (lua_State *L, int idx); +LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); +LUA_API lua_Unsigned (lua_rawlen) (lua_State *L, int idx); +LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); +LUA_API void *(lua_touserdata) (lua_State *L, int idx); +LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); +LUA_API const void *(lua_topointer) (lua_State *L, int idx); + + +/* +** Comparison and arithmetic functions +*/ + +#define LUA_OPADD 0 /* ORDER TM, ORDER OP */ +#define LUA_OPSUB 1 +#define LUA_OPMUL 2 +#define LUA_OPMOD 3 +#define LUA_OPPOW 4 +#define LUA_OPDIV 5 +#define LUA_OPIDIV 6 +#define LUA_OPBAND 7 +#define LUA_OPBOR 8 +#define LUA_OPBXOR 9 +#define LUA_OPSHL 10 +#define LUA_OPSHR 11 +#define LUA_OPUNM 12 +#define LUA_OPBNOT 13 + +LUA_API void (lua_arith) (lua_State *L, int op); + +#define LUA_OPEQ 0 +#define LUA_OPLT 1 +#define LUA_OPLE 2 + +LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op); + + +/* +** push functions (C -> stack) +*/ +LUA_API void (lua_pushnil) (lua_State *L); +LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); +LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); +LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); +LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); +LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); +LUA_API void (lua_pushboolean) (lua_State *L, int b); +LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); +LUA_API int (lua_pushthread) (lua_State *L); + + +/* +** get functions (Lua -> stack) +*/ +LUA_API int (lua_getglobal) (lua_State *L, const char *name); +LUA_API int (lua_gettable) (lua_State *L, int idx); +LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawget) (lua_State *L, int idx); +LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); + +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue); +LUA_API int (lua_getmetatable) (lua_State *L, int objindex); +LUA_API int (lua_getiuservalue) (lua_State *L, int idx, int n); + + +/* +** set functions (stack -> Lua) +*/ +LUA_API void (lua_setglobal) (lua_State *L, const char *name); +LUA_API void (lua_settable) (lua_State *L, int idx); +LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawset) (lua_State *L, int idx); +LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); +LUA_API int (lua_setmetatable) (lua_State *L, int objindex); +LUA_API int (lua_setiuservalue) (lua_State *L, int idx, int n); + + +/* +** 'load' and 'call' functions (load and run Lua code) +*/ +LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k); +#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) + +LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, + lua_KContext ctx, lua_KFunction k); +#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL) + +LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, + const char *chunkname, const char *mode); + +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip); + + +/* +** coroutine functions +*/ +LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k); +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg, + int *nres); +LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); + +#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) + + +/* +** Warning-related functions +*/ +LUA_API void (lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud); +LUA_API void (lua_warning) (lua_State *L, const char *msg, int tocont); + + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 +#define LUA_GCISRUNNING 9 +#define LUA_GCGEN 10 +#define LUA_GCINC 11 + +LUA_API int (lua_gc) (lua_State *L, int what, ...); + + +/* +** miscellaneous functions +*/ + +LUA_API int (lua_error) (lua_State *L); + +LUA_API int (lua_next) (lua_State *L, int idx); + +LUA_API void (lua_concat) (lua_State *L, int n); +LUA_API void (lua_len) (lua_State *L, int idx); + +LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); + +LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); +LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); + +LUA_API void (lua_toclose) (lua_State *L, int idx); + + +/* +** {============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +#define lua_tonumber(L,i) lua_tonumberx(L,(i),NULL) +#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL) + +#define lua_pop(L,n) lua_settop(L, -(n)-1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) + +#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) lua_pushstring(L, "" s) + +#define lua_pushglobaltable(L) \ + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) + +#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) + + +#define lua_insert(L,idx) lua_rotate(L, (idx), 1) + +#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) + +#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) + +/* }============================================================== */ + + +/* +** {============================================================== +** compatibility macros +** =============================================================== +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define lua_pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define lua_tounsignedx(L,i,is) ((lua_Unsigned)lua_tointegerx(L,i,is)) +#define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) + +#endif + +#define lua_newuserdata(L,s) lua_newuserdatauv(L,s,1) +#define lua_getuservalue(L,idx) lua_getiuservalue(L,idx,1) +#define lua_setuservalue(L,idx) lua_setiuservalue(L,idx,1) + +#define LUA_NUMTAGS LUA_NUMTYPES + +/* }============================================================== */ + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILCALL 4 + + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + +typedef struct lua_Debug lua_Debug; /* activation record */ + + +/* Functions to be called by the debugger in specific events */ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + + +LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar); +LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *(lua_getlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_setlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_getupvalue) (lua_State *L, int funcindex, int n); +LUA_API const char *(lua_setupvalue) (lua_State *L, int funcindex, int n); + +LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n); +LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, + int fidx2, int n2); + +LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook (lua_gethook) (lua_State *L); +LUA_API int (lua_gethookmask) (lua_State *L); +LUA_API int (lua_gethookcount) (lua_State *L); + +LUA_API int (lua_setcstacklimit) (lua_State *L, unsigned int limit); + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ + const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ + const char *source; /* (S) */ + size_t srclen; /* (S) */ + int currentline; /* (l) */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + unsigned char nups; /* (u) number of upvalues */ + unsigned char nparams;/* (u) number of parameters */ + char isvararg; /* (u) */ + char istailcall; /* (t) */ + unsigned short ftransfer; /* (r) index of first value transferred */ + unsigned short ntransfer; /* (r) number of transferred values */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + struct CallInfo *i_ci; /* active function */ +}; + +/* }====================================================================== */ + + +/****************************************************************************** +* Copyright (C) 1994-2020 Lua.org, PUC-Rio. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ + + +#endif diff --git a/Lua/lua.hpp b/Lua/lua.hpp new file mode 100644 index 00000000..ec417f59 --- /dev/null +++ b/Lua/lua.hpp @@ -0,0 +1,9 @@ +// lua.hpp +// Lua header files for C++ +// <> not supplied automatically because Lua also compiles as C++ + +extern "C" { +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +} diff --git a/Lua/luac.c b/Lua/luac.c new file mode 100644 index 00000000..56ddc414 --- /dev/null +++ b/Lua/luac.c @@ -0,0 +1,724 @@ +/* +** $Id: luac.c $ +** Lua compiler (saves bytecodes to files; also lists bytecodes) +** See Copyright Notice in lua.h +*/ + +#define luac_c +#define LUA_CORE + +#include "lprefix.h" + +#include +#include +#include +#include +#include + +#include "lua.h" +#include "lauxlib.h" + +#include "ldebug.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lopnames.h" +#include "lstate.h" +#include "lundump.h" + +static void PrintFunction(const Proto* f, int full); +#define luaU_print PrintFunction + +#define PROGNAME "luac" /* default program name */ +#define OUTPUT PROGNAME ".out" /* default output file */ + +static int listing=0; /* list bytecodes? */ +static int dumping=1; /* dump bytecodes? */ +static int stripping=0; /* strip debug information? */ +static char Output[]={ OUTPUT }; /* default output file name */ +static const char* output=Output; /* actual output file name */ +static const char* progname=PROGNAME; /* actual program name */ +static TString **tmname; + +static void fatal(const char* message) +{ + fprintf(stderr,"%s: %s\n",progname,message); + exit(EXIT_FAILURE); +} + +static void cannot(const char* what) +{ + fprintf(stderr,"%s: cannot %s %s: %s\n",progname,what,output,strerror(errno)); + exit(EXIT_FAILURE); +} + +static void usage(const char* message) +{ + if (*message=='-') + fprintf(stderr,"%s: unrecognized option '%s'\n",progname,message); + else + fprintf(stderr,"%s: %s\n",progname,message); + fprintf(stderr, + "usage: %s [options] [filenames]\n" + "Available options are:\n" + " -l list (use -l -l for full listing)\n" + " -o name output to file 'name' (default is \"%s\")\n" + " -p parse only\n" + " -s strip debug information\n" + " -v show version information\n" + " -- stop handling options\n" + " - stop handling options and process stdin\n" + ,progname,Output); + exit(EXIT_FAILURE); +} + +#define IS(s) (strcmp(argv[i],s)==0) + +static int doargs(int argc, char* argv[]) +{ + int i; + int version=0; + if (argv[0]!=NULL && *argv[0]!=0) progname=argv[0]; + for (i=1; itop+(i))) + +static const Proto* combine(lua_State* L, int n) +{ + if (n==1) + return toproto(L,-1); + else + { + Proto* f; + int i=n; + if (lua_load(L,reader,&i,"=(" PROGNAME ")",NULL)!=LUA_OK) fatal(lua_tostring(L,-1)); + f=toproto(L,-1); + for (i=0; ip[i]=toproto(L,i-n-1); + if (f->p[i]->sizeupvalues>0) f->p[i]->upvalues[0].instack=0; + } + f->sizelineinfo=0; + return f; + } +} + +static int writer(lua_State* L, const void* p, size_t size, void* u) +{ + UNUSED(L); + return (fwrite(p,size,1,(FILE*)u)!=1) && (size!=0); +} + +static int pmain(lua_State* L) +{ + int argc=(int)lua_tointeger(L,1); + char** argv=(char**)lua_touserdata(L,2); + const Proto* f; + int i; + tmname=G(L)->tmname; + if (!lua_checkstack(L,argc)) fatal("too many input files"); + for (i=0; i1); + if (dumping) + { + FILE* D= (output==NULL) ? stdout : fopen(output,"wb"); + if (D==NULL) cannot("open"); + lua_lock(L); + luaU_dump(L,f,writer,D,stripping); + lua_unlock(L); + if (ferror(D)) cannot("write"); + if (fclose(D)) cannot("close"); + } + return 0; +} + +int main(int argc, char* argv[]) +{ + lua_State* L; + int i=doargs(argc,argv); + argc-=i; argv+=i; + if (argc<=0) usage("no input files given"); + L=luaL_newstate(); + if (L==NULL) fatal("cannot create state: not enough memory"); + lua_pushcfunction(L,&pmain); + lua_pushinteger(L,argc); + lua_pushlightuserdata(L,argv); + if (lua_pcall(L,2,0,0)!=LUA_OK) fatal(lua_tostring(L,-1)); + lua_close(L); + return EXIT_SUCCESS; +} + +/* +** print bytecodes +*/ + +#define UPVALNAME(x) ((f->upvalues[x].name) ? getstr(f->upvalues[x].name) : "-") +#define VOID(p) ((const void*)(p)) +#define eventname(i) (getstr(tmname[i])) + +static void PrintString(const TString* ts) +{ + const char* s=getstr(ts); + size_t i,n=tsslen(ts); + printf("\""); + for (i=0; ik[i]; + switch (ttypetag(o)) + { + case LUA_VNIL: + printf("N"); + break; + case LUA_VFALSE: + case LUA_VTRUE: + printf("B"); + break; + case LUA_VNUMFLT: + printf("F"); + break; + case LUA_VNUMINT: + printf("I"); + break; + case LUA_VSHRSTR: + case LUA_VLNGSTR: + printf("S"); + break; + default: /* cannot happen */ + printf("?%d",ttypetag(o)); + break; + } + printf("\t"); +} + +static void PrintConstant(const Proto* f, int i) +{ + const TValue* o=&f->k[i]; + switch (ttypetag(o)) + { + case LUA_VNIL: + printf("nil"); + break; + case LUA_VFALSE: + printf("false"); + break; + case LUA_VTRUE: + printf("true"); + break; + case LUA_VNUMFLT: + { + char buff[100]; + sprintf(buff,LUA_NUMBER_FMT,fltvalue(o)); + printf("%s",buff); + if (buff[strspn(buff,"-0123456789")]=='\0') printf(".0"); + break; + } + case LUA_VNUMINT: + printf(LUA_INTEGER_FMT,ivalue(o)); + break; + case LUA_VSHRSTR: + case LUA_VLNGSTR: + PrintString(tsvalue(o)); + break; + default: /* cannot happen */ + printf("?%d",ttypetag(o)); + break; + } +} + +#define COMMENT "\t; " +#define EXTRAARG GETARG_Ax(code[pc+1]) +#define EXTRAARGC (EXTRAARG*(MAXARG_C+1)) +#define ISK (isk ? "k" : "") + +static void PrintCode(const Proto* f) +{ + const Instruction* code=f->code; + int pc,n=f->sizecode; + for (pc=0; pc0) printf("[%d]\t",line); else printf("[-]\t"); + printf("%-9s\t",opnames[o]); + switch (o) + { + case OP_MOVE: + printf("%d %d",a,b); + break; + case OP_LOADI: + printf("%d %d",a,sbx); + break; + case OP_LOADF: + printf("%d %d",a,sbx); + break; + case OP_LOADK: + printf("%d %d",a,bx); + printf(COMMENT); PrintConstant(f,bx); + break; + case OP_LOADKX: + printf("%d",a); + printf(COMMENT); PrintConstant(f,EXTRAARG); + break; + case OP_LOADFALSE: + printf("%d",a); + break; + case OP_LFALSESKIP: + printf("%d",a); + break; + case OP_LOADTRUE: + printf("%d",a); + break; + case OP_LOADNIL: + printf("%d %d",a,b); + printf(COMMENT "%d out",b+1); + break; + case OP_GETUPVAL: + printf("%d %d",a,b); + printf(COMMENT "%s",UPVALNAME(b)); + break; + case OP_SETUPVAL: + printf("%d %d",a,b); + printf(COMMENT "%s",UPVALNAME(b)); + break; + case OP_GETTABUP: + printf("%d %d %d",a,b,c); + printf(COMMENT "%s",UPVALNAME(b)); + printf(" "); PrintConstant(f,c); + break; + case OP_GETTABLE: + printf("%d %d %d",a,b,c); + break; + case OP_GETI: + printf("%d %d %d",a,b,c); + break; + case OP_GETFIELD: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_SETTABUP: + printf("%d %d %d%s",a,b,c,ISK); + printf(COMMENT "%s",UPVALNAME(a)); + printf(" "); PrintConstant(f,b); + if (isk) { printf(" "); PrintConstant(f,c); } + break; + case OP_SETTABLE: + printf("%d %d %d%s",a,b,c,ISK); + if (isk) { printf(COMMENT); PrintConstant(f,c); } + break; + case OP_SETI: + printf("%d %d %d%s",a,b,c,ISK); + if (isk) { printf(COMMENT); PrintConstant(f,c); } + break; + case OP_SETFIELD: + printf("%d %d %d%s",a,b,c,ISK); + printf(COMMENT); PrintConstant(f,b); + if (isk) { printf(" "); PrintConstant(f,c); } + break; + case OP_NEWTABLE: + printf("%d %d %d",a,b,c); + printf(COMMENT "%d",c+EXTRAARGC); + break; + case OP_SELF: + printf("%d %d %d%s",a,b,c,ISK); + if (isk) { printf(COMMENT); PrintConstant(f,c); } + break; + case OP_ADDI: + printf("%d %d %d",a,b,sc); + break; + case OP_ADDK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_SUBK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_MULK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_MODK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_POWK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_DIVK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_IDIVK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_BANDK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_BORK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_BXORK: + printf("%d %d %d",a,b,c); + printf(COMMENT); PrintConstant(f,c); + break; + case OP_SHRI: + printf("%d %d %d",a,b,sc); + break; + case OP_SHLI: + printf("%d %d %d",a,b,sc); + break; + case OP_ADD: + printf("%d %d %d",a,b,c); + break; + case OP_SUB: + printf("%d %d %d",a,b,c); + break; + case OP_MUL: + printf("%d %d %d",a,b,c); + break; + case OP_MOD: + printf("%d %d %d",a,b,c); + break; + case OP_POW: + printf("%d %d %d",a,b,c); + break; + case OP_DIV: + printf("%d %d %d",a,b,c); + break; + case OP_IDIV: + printf("%d %d %d",a,b,c); + break; + case OP_BAND: + printf("%d %d %d",a,b,c); + break; + case OP_BOR: + printf("%d %d %d",a,b,c); + break; + case OP_BXOR: + printf("%d %d %d",a,b,c); + break; + case OP_SHL: + printf("%d %d %d",a,b,c); + break; + case OP_SHR: + printf("%d %d %d",a,b,c); + break; + case OP_MMBIN: + printf("%d %d %d",a,b,c); + printf(COMMENT "%s",eventname(c)); + break; + case OP_MMBINI: + printf("%d %d %d %d",a,sb,c,isk); + printf(COMMENT "%s",eventname(c)); + if (isk) printf(" flip"); + break; + case OP_MMBINK: + printf("%d %d %d %d",a,b,c,isk); + printf(COMMENT "%s ",eventname(c)); PrintConstant(f,b); + if (isk) printf(" flip"); + break; + case OP_UNM: + printf("%d %d",a,b); + break; + case OP_BNOT: + printf("%d %d",a,b); + break; + case OP_NOT: + printf("%d %d",a,b); + break; + case OP_LEN: + printf("%d %d",a,b); + break; + case OP_CONCAT: + printf("%d %d",a,b); + break; + case OP_CLOSE: + printf("%d",a); + break; + case OP_TBC: + printf("%d",a); + break; + case OP_JMP: + printf("%d",GETARG_sJ(i)); + printf(COMMENT "to %d",GETARG_sJ(i)+pc+2); + break; + case OP_EQ: + printf("%d %d %d",a,b,isk); + break; + case OP_LT: + printf("%d %d %d",a,b,isk); + break; + case OP_LE: + printf("%d %d %d",a,b,isk); + break; + case OP_EQK: + printf("%d %d %d",a,b,isk); + printf(COMMENT); PrintConstant(f,b); + break; + case OP_EQI: + printf("%d %d %d",a,sb,isk); + break; + case OP_LTI: + printf("%d %d %d",a,sb,isk); + break; + case OP_LEI: + printf("%d %d %d",a,sb,isk); + break; + case OP_GTI: + printf("%d %d %d",a,sb,isk); + break; + case OP_GEI: + printf("%d %d %d",a,sb,isk); + break; + case OP_TEST: + printf("%d %d",a,isk); + break; + case OP_TESTSET: + printf("%d %d %d",a,b,isk); + break; + case OP_CALL: + printf("%d %d %d",a,b,c); + printf(COMMENT); + if (b==0) printf("all in "); else printf("%d in ",b-1); + if (c==0) printf("all out"); else printf("%d out",c-1); + break; + case OP_TAILCALL: + printf("%d %d %d",a,b,c); + printf(COMMENT "%d in",b-1); + break; + case OP_RETURN: + printf("%d %d %d",a,b,c); + printf(COMMENT); + if (b==0) printf("all out"); else printf("%d out",b-1); + break; + case OP_RETURN0: + break; + case OP_RETURN1: + printf("%d",a); + break; + case OP_FORLOOP: + printf("%d %d",a,bx); + printf(COMMENT "to %d",pc-bx+2); + break; + case OP_FORPREP: + printf("%d %d",a,bx); + printf(COMMENT "to %d",pc+bx+2); + break; + case OP_TFORPREP: + printf("%d %d",a,bx); + printf(COMMENT "to %d",pc+bx+2); + break; + case OP_TFORCALL: + printf("%d %d",a,c); + break; + case OP_TFORLOOP: + printf("%d %d",a,bx); + printf(COMMENT "to %d",pc-bx+2); + break; + case OP_SETLIST: + printf("%d %d %d",a,b,c); + if (isk) printf(COMMENT "%d",c+EXTRAARGC); + break; + case OP_CLOSURE: + printf("%d %d",a,bx); + printf(COMMENT "%p",VOID(f->p[bx])); + break; + case OP_VARARG: + printf("%d %d",a,c); + printf(COMMENT); + if (c==0) printf("all out"); else printf("%d out",c-1); + break; + case OP_VARARGPREP: + printf("%d",a); + break; + case OP_EXTRAARG: + printf("%d",ax); + break; +#if 0 + default: + printf("%d %d %d",a,b,c); + printf(COMMENT "not handled"); + break; +#endif + } + printf("\n"); + } +} + + +#define SS(x) ((x==1)?"":"s") +#define S(x) (int)(x),SS(x) + +static void PrintHeader(const Proto* f) +{ + const char* s=f->source ? getstr(f->source) : "=?"; + if (*s=='@' || *s=='=') + s++; + else if (*s==LUA_SIGNATURE[0]) + s="(bstring)"; + else + s="(string)"; + printf("\n%s <%s:%d,%d> (%d instruction%s at %p)\n", + (f->linedefined==0)?"main":"function",s, + f->linedefined,f->lastlinedefined, + S(f->sizecode),VOID(f)); + printf("%d%s param%s, %d slot%s, %d upvalue%s, ", + (int)(f->numparams),f->is_vararg?"+":"",SS(f->numparams), + S(f->maxstacksize),S(f->sizeupvalues)); + printf("%d local%s, %d constant%s, %d function%s\n", + S(f->sizelocvars),S(f->sizek),S(f->sizep)); +} + +static void PrintDebug(const Proto* f) +{ + int i,n; + n=f->sizek; + printf("constants (%d) for %p:\n",n,VOID(f)); + for (i=0; isizelocvars; + printf("locals (%d) for %p:\n",n,VOID(f)); + for (i=0; ilocvars[i].varname),f->locvars[i].startpc+1,f->locvars[i].endpc+1); + } + n=f->sizeupvalues; + printf("upvalues (%d) for %p:\n",n,VOID(f)); + for (i=0; iupvalues[i].instack,f->upvalues[i].idx); + } +} + +static void PrintFunction(const Proto* f, int full) +{ + int i,n=f->sizep; + PrintHeader(f); + PrintCode(f); + if (full) PrintDebug(f); + for (i=0; ip[i],full); +} diff --git a/Lua/luaconf.h b/Lua/luaconf.h new file mode 100644 index 00000000..d9cf18ca --- /dev/null +++ b/Lua/luaconf.h @@ -0,0 +1,761 @@ +/* +** $Id: luaconf.h $ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + + +#ifndef luaconf_h +#define luaconf_h + +#include +#include + + +/* +** =================================================================== +** General Configuration File for Lua +** +** Some definitions here can be changed externally, through the +** compiler (e.g., with '-D' options). Those are protected by +** '#if !defined' guards. However, several other definitions should +** be changed directly here, either because they affect the Lua +** ABI (by making the changes here, you ensure that all software +** connected to Lua, such as C libraries, will be compiled with the +** same configuration); or because they are seldom changed. +** +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + + +/* +** {==================================================================== +** System Configuration: macros to adapt (if needed) Lua to some +** particular platform, for instance restricting it to C89. +** ===================================================================== +*/ + +/* +@@ LUA_USE_C89 controls the use of non-ISO-C89 features. +** Define it if you want Lua to avoid the use of a few C99 features +** or Windows-specific features on Windows. +*/ +/* #define LUA_USE_C89 */ + + +/* +** By default, Lua on Windows use (some) specific Windows features +*/ +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_USE_WINDOWS /* enable goodies for regular Windows */ +#endif + + +#if defined(LUA_USE_WINDOWS) +#define LUA_DL_DLL /* enable support for DLL */ +#define LUA_USE_C89 /* broadly, Windows is C89 */ +#endif + + +#if defined(LUA_USE_LINUX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#endif + + +#if defined(LUA_USE_MACOSX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ +#endif + + +/* +@@ LUAI_IS32INT is true iff 'int' has (at least) 32 bits. +*/ +#define LUAI_IS32INT ((UINT_MAX >> 30) >= 3) + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Number types. +** =================================================================== +*/ + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. +*/ +/* #define LUA_32BITS */ + + +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS +#endif + + +/* +@@ LUA_INT_TYPE defines the type for Lua integers. +@@ LUA_FLOAT_TYPE defines the type for Lua floats. +** Lua should work fine with any mix of these options supported +** by your C compiler. The usual configurations are 64-bit integers +** and 'double' (the default), 32-bit integers and 'float' (for +** restricted platforms), and 'long'/'double' (for C compilers not +** compliant with C99, which may not have support for 'long long'). +*/ + +/* predefined options for LUA_INT_TYPE */ +#define LUA_INT_INT 1 +#define LUA_INT_LONG 2 +#define LUA_INT_LONGLONG 3 + +/* predefined options for LUA_FLOAT_TYPE */ +#define LUA_FLOAT_FLOAT 1 +#define LUA_FLOAT_DOUBLE 2 +#define LUA_FLOAT_LONGDOUBLE 3 + +#if defined(LUA_32BITS) /* { */ +/* +** 32-bit integers and 'float' +*/ +#if LUAI_IS32INT /* use 'int' if big enough */ +#define LUA_INT_TYPE LUA_INT_INT +#else /* otherwise use 'long' */ +#define LUA_INT_TYPE LUA_INT_LONG +#endif +#define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT + +#elif defined(LUA_C89_NUMBERS) /* }{ */ +/* +** largest types available for C89 ('long' and 'double') +*/ +#define LUA_INT_TYPE LUA_INT_LONG +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE + +#endif /* } */ + + +/* +** default configuration for 64-bit Lua ('long long' and 'double') +*/ +#if !defined(LUA_INT_TYPE) +#define LUA_INT_TYPE LUA_INT_LONGLONG +#endif + +#if !defined(LUA_FLOAT_TYPE) +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE +#endif + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ + +/* +** LUA_PATH_SEP is the character that separates templates in a path. +** LUA_PATH_MARK is the string that marks the substitution points in a +** template. +** LUA_EXEC_DIR in a Windows path is replaced by the executable's +** directory. +*/ +#define LUA_PATH_SEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXEC_DIR "!" + + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +** Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +** C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ + +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#if defined(_WIN32) /* { */ +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" + +#if !defined(LUA_PATH_DEFAULT) +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ + LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ + ".\\?.lua;" ".\\?\\init.lua" +#endif + +#if !defined(LUA_CPATH_DEFAULT) +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.dll;" \ + LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ + LUA_CDIR"loadall.dll;" ".\\?.dll" +#endif + +#else /* }{ */ + +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" + +#if !defined(LUA_PATH_DEFAULT) +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ + "./?.lua;" "./?/init.lua" +#endif + +#if !defined(LUA_CPATH_DEFAULT) +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" +#endif + +#endif /* } */ + + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if !defined(LUA_DIRSEP) + +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== +*/ + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all auxiliary library functions. +@@ LUAMOD_API is a mark for all standard library opening functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) /* { */ + +#if defined(LUA_CORE) || defined(LUA_LIB) /* { */ +#define LUA_API __declspec(dllexport) +#else /* }{ */ +#define LUA_API __declspec(dllimport) +#endif /* } */ + +#else /* }{ */ + +#define LUA_API extern + +#endif /* } */ + + +/* +** More often than not the libs go together with the core. +*/ +#define LUALIB_API LUA_API +#define LUAMOD_API LUA_API + + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +** exported to outside modules. +@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables, +** none of which to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. Not all elf targets support +** this attribute. Unfortunately, gcc does not offer a way to check +** whether the target offers that support, and those without support +** give a warning about it. To avoid these warnings, change to the +** default definition. +*/ +#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("internal"))) extern +#else /* }{ */ +#define LUAI_FUNC extern +#endif /* } */ + +#define LUAI_DDEC(dec) LUAI_FUNC dec +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Compatibility with previous versions +** =================================================================== +*/ + +/* +@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3. +** You can define it to get all options, or change specific options +** to fit your specific needs. +*/ +#if defined(LUA_COMPAT_5_3) /* { */ + +/* +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. +** (These functions were already officially removed in 5.3; +** nevertheless they are still available here.) +*/ +#define LUA_COMPAT_MATHLIB + +/* +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) +** (These macros were also officially removed in 5.3, but they are still +** available here.) +*/ +#define LUA_COMPAT_APIINTCASTS + + +/* +@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod +** using '__lt'. +*/ +#define LUA_COMPAT_LT_LE + + +/* +@@ The following macros supply trivial compatibility for some +** changes in the API. The macros themselves document how to +** change your code to avoid using them. +** (Once more, these macros were officially removed in 5.3, but they are +** still available here.) +*/ +#define lua_strlen(L,i) lua_rawlen(L, (i)) + +#define lua_objlen(L,i) lua_rawlen(L, (i)) + +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT) + +#endif /* } */ + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Numbers. +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== +*/ + +/* +@@ LUA_NUMBER is the floating-point type used by Lua. +@@ LUAI_UACNUMBER is the result of a 'default argument promotion' +@@ over a floating number. +@@ l_floatatt(x) corrects float attribute 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeral to a number. +*/ + + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) \ + l_sprintf((s), sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)(n)) + +/* +@@ lua_numbertointeger converts a float number with an integral value +** to an integer, or returns 0 if float is not within the range of +** a lua_Integer. (The range comparisons are tricky because of +** rounding. The tests here assume a two-complement representation, +** where MININTEGER always has an exact representation as a float; +** MAXINTEGER may not have one, and therefore its conversion to float +** may have an ill-defined value.) +*/ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + +#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ + +#define LUA_NUMBER float + +#define l_floatatt(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7g" + +#define l_mathop(op) op##f + +#define lua_str2number(s,p) strtof((s), (p)) + + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE /* }{ long double */ + +#define LUA_NUMBER long double + +#define l_floatatt(n) (LDBL_##n) + +#define LUAI_UACNUMBER long double + +#define LUA_NUMBER_FRMLEN "L" +#define LUA_NUMBER_FMT "%.19Lg" + +#define l_mathop(op) op##l + +#define lua_str2number(s,p) strtold((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE /* }{ double */ + +#define LUA_NUMBER double + +#define l_floatatt(n) (DBL_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.14g" + +#define l_mathop(op) op + +#define lua_str2number(s,p) strtod((s), (p)) + +#else /* }{ */ + +#error "numeric float type not defined" + +#endif /* } */ + + + +/* +@@ LUA_INTEGER is the integer type used by Lua. +** +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +** +@@ LUAI_UACINT is the result of a 'default argument promotion' +@@ over a LUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ LUA_MAXUNSIGNED is the maximum value for a LUA_UNSIGNED. +@@ LUA_UNSIGNEDBITS is the number of bits in a LUA_UNSIGNED. +@@ lua_integer2str converts an integer to a string. +*/ + + +/* The following definitions are good for most cases here */ + +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" + +#define LUAI_UACINT LUA_INTEGER + +#define lua_integer2str(s,sz,n) \ + l_sprintf((s), sz, LUA_INTEGER_FMT, (LUAI_UACINT)(n)) + +/* +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) +*/ +#define LUA_UNSIGNED unsigned LUAI_UACINT + + +#define LUA_UNSIGNEDBITS (sizeof(LUA_UNSIGNED) * CHAR_BIT) + + +/* now the variable definitions */ + +#if LUA_INT_TYPE == LUA_INT_INT /* { int */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#define LUA_MAXUNSIGNED UINT_MAX + +#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ + +#define LUA_INTEGER long +#define LUA_INTEGER_FRMLEN "l" + +#define LUA_MAXINTEGER LONG_MAX +#define LUA_MININTEGER LONG_MIN + +#define LUA_MAXUNSIGNED ULONG_MAX + +#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ + +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ +#if defined(LLONG_MAX) /* { */ +/* use ISO C99 stuff */ + +#define LUA_INTEGER long long +#define LUA_INTEGER_FRMLEN "ll" + +#define LUA_MAXINTEGER LLONG_MAX +#define LUA_MININTEGER LLONG_MIN + +#define LUA_MAXUNSIGNED ULLONG_MAX + +#elif defined(LUA_USE_WINDOWS) /* }{ */ +/* in Windows, can use specific Windows types */ + +#define LUA_INTEGER __int64 +#define LUA_INTEGER_FRMLEN "I64" + +#define LUA_MAXINTEGER _I64_MAX +#define LUA_MININTEGER _I64_MIN + +#define LUA_MAXUNSIGNED _UI64_MAX + +#else /* }{ */ + +#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ + or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)" + +#endif /* } */ + +#else /* }{ */ + +#error "numeric integer type not defined" + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Dependencies with C99 and other C details +** =================================================================== +*/ + +/* +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,f,i)) +#endif + + +/* +@@ lua_strx2number converts a hexadecimal numeral to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s,p) lua_str2number(s,p) +#endif + + +/* +@@ lua_pointer2str converts a pointer to a readable string in a +** non-specified way. +*/ +#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p) + + +/* +@@ lua_number2strx converts a float to a hexadecimal numeral. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_number2strx(L,b,sz,f,n) \ + ((void)L, l_sprintf(b,sz,f,(LUAI_UACNUMBER)(n))) +#endif + + +/* +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) +*/ +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number)op /* no variant */ +#define lua_str2number(s,p) ((lua_Number)strtod((s), (p))) +#endif + + +/* +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) +*/ +#define LUA_KCONTEXT ptrdiff_t + +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t +#endif +#endif + + +/* +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include the header 'locale.h'.) +*/ +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Language Variations +** ===================================================================== +*/ + +/* +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. +*/ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ + + +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l,e) assert(e) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). +** ===================================================================== +*/ + +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +** (It must fit into max(size_t)/32.) +*/ +#if LUAI_IS32INT +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif + + +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) + + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@@ of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + + +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +*/ +#define LUAL_BUFFERSIZE ((int)(16 * sizeof(void*) * sizeof(lua_Number))) + + +/* +@@ LUAI_MAXALIGN defines fields that, when used in a union, ensure +** maximum alignment for the other items in that union. +*/ +#define LUAI_MAXALIGN lua_Number n; double u; void *s; lua_Integer i; long l + +/* }================================================================== */ + + + + + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + + + + + +#endif + diff --git a/Lua/lualib.h b/Lua/lualib.h new file mode 100644 index 00000000..eb08b530 --- /dev/null +++ b/Lua/lualib.h @@ -0,0 +1,58 @@ +/* +** $Id: lualib.h $ +** Lua standard libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lualib_h +#define lualib_h + +#include "lua.h" + + +/* version suffix for environment variable names */ +#define LUA_VERSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR + + +LUAMOD_API int (luaopen_base) (lua_State *L); + +#define LUA_COLIBNAME "coroutine" +LUAMOD_API int (luaopen_coroutine) (lua_State *L); + +#define LUA_TABLIBNAME "table" +LUAMOD_API int (luaopen_table) (lua_State *L); + +#define LUA_IOLIBNAME "io" +LUAMOD_API int (luaopen_io) (lua_State *L); + +#define LUA_OSLIBNAME "os" +LUAMOD_API int (luaopen_os) (lua_State *L); + +#define LUA_STRLIBNAME "string" +LUAMOD_API int (luaopen_string) (lua_State *L); + +#define LUA_UTF8LIBNAME "utf8" +LUAMOD_API int (luaopen_utf8) (lua_State *L); + +#define LUA_MATHLIBNAME "math" +LUAMOD_API int (luaopen_math) (lua_State *L); + +#define LUA_DBLIBNAME "debug" +LUAMOD_API int (luaopen_debug) (lua_State *L); + +#define LUA_LOADLIBNAME "package" +LUAMOD_API int (luaopen_package) (lua_State *L); + + +/* open all previous libraries */ +LUALIB_API void (luaL_openlibs) (lua_State *L); + + + +#if !defined(lua_assert) +#define lua_assert(x) ((void)0) +#endif + + +#endif diff --git a/Lua/lundump.c b/Lua/lundump.c new file mode 100644 index 00000000..5aa55c44 --- /dev/null +++ b/Lua/lundump.c @@ -0,0 +1,333 @@ +/* +** $Id: lundump.c $ +** load precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#define lundump_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstring.h" +#include "lundump.h" +#include "lzio.h" + + +#if !defined(luai_verifycode) +#define luai_verifycode(L,f) /* empty */ +#endif + + +typedef struct { + lua_State *L; + ZIO *Z; + const char *name; +} LoadState; + + +static l_noret error (LoadState *S, const char *why) { + luaO_pushfstring(S->L, "%s: bad binary format (%s)", S->name, why); + luaD_throw(S->L, LUA_ERRSYNTAX); +} + + +/* +** All high-level loads go through loadVector; you can change it to +** adapt to the endianness of the input +*/ +#define loadVector(S,b,n) loadBlock(S,b,(n)*sizeof((b)[0])) + +static void loadBlock (LoadState *S, void *b, size_t size) { + if (luaZ_read(S->Z, b, size) != 0) + error(S, "truncated chunk"); +} + + +#define loadVar(S,x) loadVector(S,&x,1) + + +static lu_byte loadByte (LoadState *S) { + int b = zgetc(S->Z); + if (b == EOZ) + error(S, "truncated chunk"); + return cast_byte(b); +} + + +static size_t loadUnsigned (LoadState *S, size_t limit) { + size_t x = 0; + int b; + limit >>= 7; + do { + b = loadByte(S); + if (x >= limit) + error(S, "integer overflow"); + x = (x << 7) | (b & 0x7f); + } while ((b & 0x80) == 0); + return x; +} + + +static size_t loadSize (LoadState *S) { + return loadUnsigned(S, ~(size_t)0); +} + + +static int loadInt (LoadState *S) { + return cast_int(loadUnsigned(S, INT_MAX)); +} + + +static lua_Number loadNumber (LoadState *S) { + lua_Number x; + loadVar(S, x); + return x; +} + + +static lua_Integer loadInteger (LoadState *S) { + lua_Integer x; + loadVar(S, x); + return x; +} + + +/* +** Load a nullable string into prototype 'p'. +*/ +static TString *loadStringN (LoadState *S, Proto *p) { + lua_State *L = S->L; + TString *ts; + size_t size = loadSize(S); + if (size == 0) /* no string? */ + return NULL; + else if (--size <= LUAI_MAXSHORTLEN) { /* short string? */ + char buff[LUAI_MAXSHORTLEN]; + loadVector(S, buff, size); /* load string into buffer */ + ts = luaS_newlstr(L, buff, size); /* create string */ + } + else { /* long string */ + ts = luaS_createlngstrobj(L, size); /* create string */ + setsvalue2s(L, L->top, ts); /* anchor it ('loadVector' can GC) */ + luaD_inctop(L); + loadVector(S, getstr(ts), size); /* load directly in final place */ + L->top--; /* pop string */ + } + luaC_objbarrier(L, p, ts); + return ts; +} + + +/* +** Load a non-nullable string into prototype 'p'. +*/ +static TString *loadString (LoadState *S, Proto *p) { + TString *st = loadStringN(S, p); + if (st == NULL) + error(S, "bad format for constant string"); + return st; +} + + +static void loadCode (LoadState *S, Proto *f) { + int n = loadInt(S); + f->code = luaM_newvectorchecked(S->L, n, Instruction); + f->sizecode = n; + loadVector(S, f->code, n); +} + + +static void loadFunction(LoadState *S, Proto *f, TString *psource); + + +static void loadConstants (LoadState *S, Proto *f) { + int i; + int n = loadInt(S); + f->k = luaM_newvectorchecked(S->L, n, TValue); + f->sizek = n; + for (i = 0; i < n; i++) + setnilvalue(&f->k[i]); + for (i = 0; i < n; i++) { + TValue *o = &f->k[i]; + int t = loadByte(S); + switch (t) { + case LUA_VNIL: + setnilvalue(o); + break; + case LUA_VFALSE: + setbfvalue(o); + break; + case LUA_VTRUE: + setbtvalue(o); + break; + case LUA_VNUMFLT: + setfltvalue(o, loadNumber(S)); + break; + case LUA_VNUMINT: + setivalue(o, loadInteger(S)); + break; + case LUA_VSHRSTR: + case LUA_VLNGSTR: + setsvalue2n(S->L, o, loadString(S, f)); + break; + default: lua_assert(0); + } + } +} + + +static void loadProtos (LoadState *S, Proto *f) { + int i; + int n = loadInt(S); + f->p = luaM_newvectorchecked(S->L, n, Proto *); + f->sizep = n; + for (i = 0; i < n; i++) + f->p[i] = NULL; + for (i = 0; i < n; i++) { + f->p[i] = luaF_newproto(S->L); + luaC_objbarrier(S->L, f, f->p[i]); + loadFunction(S, f->p[i], f->source); + } +} + + +/* +** Load the upvalues for a function. The names must be filled first, +** because the filling of the other fields can raise read errors and +** the creation of the error message can call an emergency collection; +** in that case all prototypes must be consistent for the GC. +*/ +static void loadUpvalues (LoadState *S, Proto *f) { + int i, n; + n = loadInt(S); + f->upvalues = luaM_newvectorchecked(S->L, n, Upvaldesc); + f->sizeupvalues = n; + for (i = 0; i < n; i++) /* make array valid for GC */ + f->upvalues[i].name = NULL; + for (i = 0; i < n; i++) { /* following calls can raise errors */ + f->upvalues[i].instack = loadByte(S); + f->upvalues[i].idx = loadByte(S); + f->upvalues[i].kind = loadByte(S); + } +} + + +static void loadDebug (LoadState *S, Proto *f) { + int i, n; + n = loadInt(S); + f->lineinfo = luaM_newvectorchecked(S->L, n, ls_byte); + f->sizelineinfo = n; + loadVector(S, f->lineinfo, n); + n = loadInt(S); + f->abslineinfo = luaM_newvectorchecked(S->L, n, AbsLineInfo); + f->sizeabslineinfo = n; + for (i = 0; i < n; i++) { + f->abslineinfo[i].pc = loadInt(S); + f->abslineinfo[i].line = loadInt(S); + } + n = loadInt(S); + f->locvars = luaM_newvectorchecked(S->L, n, LocVar); + f->sizelocvars = n; + for (i = 0; i < n; i++) + f->locvars[i].varname = NULL; + for (i = 0; i < n; i++) { + f->locvars[i].varname = loadStringN(S, f); + f->locvars[i].startpc = loadInt(S); + f->locvars[i].endpc = loadInt(S); + } + n = loadInt(S); + for (i = 0; i < n; i++) + f->upvalues[i].name = loadStringN(S, f); +} + + +static void loadFunction (LoadState *S, Proto *f, TString *psource) { + f->source = loadStringN(S, f); + if (f->source == NULL) /* no source in dump? */ + f->source = psource; /* reuse parent's source */ + f->linedefined = loadInt(S); + f->lastlinedefined = loadInt(S); + f->numparams = loadByte(S); + f->is_vararg = loadByte(S); + f->maxstacksize = loadByte(S); + loadCode(S, f); + loadConstants(S, f); + loadUpvalues(S, f); + loadProtos(S, f); + loadDebug(S, f); +} + + +static void checkliteral (LoadState *S, const char *s, const char *msg) { + char buff[sizeof(LUA_SIGNATURE) + sizeof(LUAC_DATA)]; /* larger than both */ + size_t len = strlen(s); + loadVector(S, buff, len); + if (memcmp(s, buff, len) != 0) + error(S, msg); +} + + +static void fchecksize (LoadState *S, size_t size, const char *tname) { + if (loadByte(S) != size) + error(S, luaO_pushfstring(S->L, "%s size mismatch", tname)); +} + + +#define checksize(S,t) fchecksize(S,sizeof(t),#t) + +static void checkHeader (LoadState *S) { + /* skip 1st char (already read and checked) */ + checkliteral(S, &LUA_SIGNATURE[1], "not a binary chunk"); + if (loadByte(S) != LUAC_VERSION) + error(S, "version mismatch"); + if (loadByte(S) != LUAC_FORMAT) + error(S, "format mismatch"); + checkliteral(S, LUAC_DATA, "corrupted chunk"); + checksize(S, Instruction); + checksize(S, lua_Integer); + checksize(S, lua_Number); + if (loadInteger(S) != LUAC_INT) + error(S, "integer format mismatch"); + if (loadNumber(S) != LUAC_NUM) + error(S, "float format mismatch"); +} + + +/* +** Load precompiled chunk. +*/ +LClosure *luaU_undump(lua_State *L, ZIO *Z, const char *name) { + LoadState S; + LClosure *cl; + if (*name == '@' || *name == '=') + S.name = name + 1; + else if (*name == LUA_SIGNATURE[0]) + S.name = "binary string"; + else + S.name = name; + S.L = L; + S.Z = Z; + checkHeader(&S); + cl = luaF_newLclosure(L, loadByte(&S)); + setclLvalue2s(L, L->top, cl); + luaD_inctop(L); + cl->p = luaF_newproto(L); + luaC_objbarrier(L, cl, cl->p); + loadFunction(&S, cl->p, NULL); + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luai_verifycode(L, cl->p); + return cl; +} + diff --git a/Lua/lundump.h b/Lua/lundump.h new file mode 100644 index 00000000..f3748a99 --- /dev/null +++ b/Lua/lundump.h @@ -0,0 +1,36 @@ +/* +** $Id: lundump.h $ +** load precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#ifndef lundump_h +#define lundump_h + +#include "llimits.h" +#include "lobject.h" +#include "lzio.h" + + +/* data to catch conversion errors */ +#define LUAC_DATA "\x19\x93\r\n\x1a\n" + +#define LUAC_INT 0x5678 +#define LUAC_NUM cast_num(370.5) + +/* +** Encode major-minor version in one byte, one nibble for each +*/ +#define MYINT(s) (s[0]-'0') /* assume one-digit numerals */ +#define LUAC_VERSION (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR)) + +#define LUAC_FORMAT 0 /* this is the official format */ + +/* load one chunk; from lundump.c */ +LUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, const char* name); + +/* dump one chunk; from ldump.c */ +LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, + void* data, int strip); + +#endif diff --git a/Lua/lutf8lib.c b/Lua/lutf8lib.c new file mode 100644 index 00000000..901d985f --- /dev/null +++ b/Lua/lutf8lib.c @@ -0,0 +1,289 @@ +/* +** $Id: lutf8lib.c $ +** Standard library for UTF-8 manipulation +** See Copyright Notice in lua.h +*/ + +#define lutf8lib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#define MAXUNICODE 0x10FFFFu + +#define MAXUTF 0x7FFFFFFFu + +/* +** Integer type for decoded UTF-8 values; MAXUTF needs 31 bits. +*/ +#if (UINT_MAX >> 30) >= 1 +typedef unsigned int utfint; +#else +typedef unsigned long utfint; +#endif + + +#define iscont(p) ((*(p) & 0xC0) == 0x80) + + +/* from strlib */ +/* translate a relative string position: negative means back from end */ +static lua_Integer u_posrelat (lua_Integer pos, size_t len) { + if (pos >= 0) return pos; + else if (0u - (size_t)pos > len) return 0; + else return (lua_Integer)len + pos + 1; +} + + +/* +** Decode one UTF-8 sequence, returning NULL if byte sequence is +** invalid. The array 'limits' stores the minimum value for each +** sequence length, to check for overlong representations. Its first +** entry forces an error for non-ascii bytes with no continuation +** bytes (count == 0). +*/ +static const char *utf8_decode (const char *s, utfint *val, int strict) { + static const utfint limits[] = + {~(utfint)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u}; + unsigned int c = (unsigned char)s[0]; + utfint res = 0; /* final result */ + if (c < 0x80) /* ascii? */ + res = c; + else { + int count = 0; /* to count number of continuation bytes */ + for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */ + unsigned int cc = (unsigned char)s[++count]; /* read next byte */ + if ((cc & 0xC0) != 0x80) /* not a continuation byte? */ + return NULL; /* invalid byte sequence */ + res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ + } + res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */ + if (count > 5 || res > MAXUTF || res < limits[count]) + return NULL; /* invalid byte sequence */ + s += count; /* skip continuation bytes read */ + } + if (strict) { + /* check for invalid code points; too large or surrogates */ + if (res > MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu)) + return NULL; + } + if (val) *val = res; + return s + 1; /* +1 to include first byte */ +} + + +/* +** utf8len(s [, i [, j [, lax]]]) --> number of characters that +** start in the range [i,j], or nil + current position if 's' is not +** well formed in that interval +*/ +static int utflen (lua_State *L) { + lua_Integer n = 0; /* counter for the number of characters */ + size_t len; /* string length in bytes */ + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len); + lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len); + int lax = lua_toboolean(L, 4); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2, + "initial position out of bounds"); + luaL_argcheck(L, --posj < (lua_Integer)len, 3, + "final position out of bounds"); + while (posi <= posj) { + const char *s1 = utf8_decode(s + posi, NULL, !lax); + if (s1 == NULL) { /* conversion error? */ + luaL_pushfail(L); /* return fail ... */ + lua_pushinteger(L, posi + 1); /* ... and current position */ + return 2; + } + posi = s1 - s; + n++; + } + lua_pushinteger(L, n); + return 1; +} + + +/* +** codepoint(s, [i, [j [, lax]]]) -> returns codepoints for all +** characters that start in the range [i,j] +*/ +static int codepoint (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len); + lua_Integer pose = u_posrelat(luaL_optinteger(L, 3, posi), len); + int lax = lua_toboolean(L, 4); + int n; + const char *se; + luaL_argcheck(L, posi >= 1, 2, "out of bounds"); + luaL_argcheck(L, pose <= (lua_Integer)len, 3, "out of bounds"); + if (posi > pose) return 0; /* empty interval; return no values */ + if (pose - posi >= INT_MAX) /* (lua_Integer -> int) overflow? */ + return luaL_error(L, "string slice too long"); + n = (int)(pose - posi) + 1; /* upper bound for number of returns */ + luaL_checkstack(L, n, "string slice too long"); + n = 0; /* count the number of returns */ + se = s + pose; /* string end */ + for (s += posi - 1; s < se;) { + utfint code; + s = utf8_decode(s, &code, !lax); + if (s == NULL) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, code); + n++; + } + return n; +} + + +static void pushutfchar (lua_State *L, int arg) { + lua_Unsigned code = (lua_Unsigned)luaL_checkinteger(L, arg); + luaL_argcheck(L, code <= MAXUTF, arg, "value out of range"); + lua_pushfstring(L, "%U", (long)code); +} + + +/* +** utfchar(n1, n2, ...) -> char(n1)..char(n2)... +*/ +static int utfchar (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + if (n == 1) /* optimize common case of single char */ + pushutfchar(L, 1); + else { + int i; + luaL_Buffer b; + luaL_buffinit(L, &b); + for (i = 1; i <= n; i++) { + pushutfchar(L, i); + luaL_addvalue(&b); + } + luaL_pushresult(&b); + } + return 1; +} + + +/* +** offset(s, n, [i]) -> index where n-th character counting from +** position 'i' starts; 0 means character at 'i'. +*/ +static int byteoffset (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = luaL_checkinteger(L, 2); + lua_Integer posi = (n >= 0) ? 1 : len + 1; + posi = u_posrelat(luaL_optinteger(L, 3, posi), len); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 3, + "position out of bounds"); + if (n == 0) { + /* find beginning of current byte sequence */ + while (posi > 0 && iscont(s + posi)) posi--; + } + else { + if (iscont(s + posi)) + return luaL_error(L, "initial position is a continuation byte"); + if (n < 0) { + while (n < 0 && posi > 0) { /* move back */ + do { /* find beginning of previous character */ + posi--; + } while (posi > 0 && iscont(s + posi)); + n++; + } + } + else { + n--; /* do not move for 1st character */ + while (n > 0 && posi < (lua_Integer)len) { + do { /* find beginning of next character */ + posi++; + } while (iscont(s + posi)); /* (cannot pass final '\0') */ + n--; + } + } + } + if (n == 0) /* did it find given character? */ + lua_pushinteger(L, posi + 1); + else /* no such character */ + luaL_pushfail(L); + return 1; +} + + +static int iter_aux (lua_State *L, int strict) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = lua_tointeger(L, 2) - 1; + if (n < 0) /* first iteration? */ + n = 0; /* start from here */ + else if (n < (lua_Integer)len) { + n++; /* skip current byte */ + while (iscont(s + n)) n++; /* and its continuations */ + } + if (n >= (lua_Integer)len) + return 0; /* no more codepoints */ + else { + utfint code; + const char *next = utf8_decode(s + n, &code, strict); + if (next == NULL) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, n + 1); + lua_pushinteger(L, code); + return 2; + } +} + + +static int iter_auxstrict (lua_State *L) { + return iter_aux(L, 1); +} + +static int iter_auxlax (lua_State *L) { + return iter_aux(L, 0); +} + + +static int iter_codes (lua_State *L) { + int lax = lua_toboolean(L, 2); + luaL_checkstring(L, 1); + lua_pushcfunction(L, lax ? iter_auxlax : iter_auxstrict); + lua_pushvalue(L, 1); + lua_pushinteger(L, 0); + return 3; +} + + +/* pattern to match a single UTF-8 character */ +#define UTF8PATT "[\0-\x7F\xC2-\xFD][\x80-\xBF]*" + + +static const luaL_Reg funcs[] = { + {"offset", byteoffset}, + {"codepoint", codepoint}, + {"char", utfchar}, + {"len", utflen}, + {"codes", iter_codes}, + /* placeholders */ + {"charpattern", NULL}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_utf8 (lua_State *L) { + luaL_newlib(L, funcs); + lua_pushlstring(L, UTF8PATT, sizeof(UTF8PATT)/sizeof(char) - 1); + lua_setfield(L, -2, "charpattern"); + return 1; +} + diff --git a/Lua/lvm.c b/Lua/lvm.c new file mode 100644 index 00000000..aa3b22bf --- /dev/null +++ b/Lua/lvm.c @@ -0,0 +1,1829 @@ +/* +** $Id: lvm.c $ +** Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#define lvm_c +#define LUA_CORE + +#include "lprefix.h" + +#include +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + +/* +** By default, use jump tables in the main interpreter loop on gcc +** and compatible compilers. +*/ +#if !defined(LUA_USE_JUMPTABLE) +#if defined(__GNUC__) +#define LUA_USE_JUMPTABLE 1 +#else +#define LUA_USE_JUMPTABLE 0 +#endif +#endif + + + +/* limit for table tag-method chains (to avoid infinite loops) */ +#define MAXTAGLOOP 2000 + + +/* +** 'l_intfitsf' checks whether a given integer is in the range that +** can be converted to a float without rounding. Used in comparisons. +*/ + +/* number of bits in the mantissa of a float */ +#define NBM (l_floatatt(MANT_DIG)) + +/* +** Check whether some integers may not fit in a float, testing whether +** (maxinteger >> NBM) > 0. (That implies (1 << NBM) <= maxinteger.) +** (The shifts are done in parts, to avoid shifting by more than the size +** of an integer. In a worst case, NBM == 113 for long double and +** sizeof(long) == 32.) +*/ +#if ((((LUA_MAXINTEGER >> (NBM / 4)) >> (NBM / 4)) >> (NBM / 4)) \ + >> (NBM - (3 * (NBM / 4)))) > 0 + +/* limit for integers that fit in a float */ +#define MAXINTFITSF ((lua_Unsigned)1 << NBM) + +/* check whether 'i' is in the interval [-MAXINTFITSF, MAXINTFITSF] */ +#define l_intfitsf(i) ((MAXINTFITSF + l_castS2U(i)) <= (2 * MAXINTFITSF)) + +#else /* all integers fit in a float precisely */ + +#define l_intfitsf(i) 1 + +#endif + + +/* +** Try to convert a value from string to a number value. +** If the value is not a string or is a string not representing +** a valid numeral (or if coercions from strings to numbers +** are disabled via macro 'cvt2num'), do not modify 'result' +** and return 0. +*/ +static int l_strton (const TValue *obj, TValue *result) { + lua_assert(obj != result); + if (!cvt2num(obj)) /* is object not a string? */ + return 0; + else + return (luaO_str2num(svalue(obj), result) == vslen(obj) + 1); +} + + +/* +** Try to convert a value to a float. The float case is already handled +** by the macro 'tonumber'. +*/ +int luaV_tonumber_ (const TValue *obj, lua_Number *n) { + TValue v; + if (ttisinteger(obj)) { + *n = cast_num(ivalue(obj)); + return 1; + } + else if (l_strton(obj, &v)) { /* string coercible to number? */ + *n = nvalue(&v); /* convert result of 'luaO_str2num' to a float */ + return 1; + } + else + return 0; /* conversion failed */ +} + + +/* +** try to convert a float to an integer, rounding according to 'mode'. +*/ +int luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode) { + lua_Number f = l_floor(n); + if (n != f) { /* not an integral value? */ + if (mode == F2Ieq) return 0; /* fails if mode demands integral value */ + else if (mode == F2Iceil) /* needs ceil? */ + f += 1; /* convert floor to ceil (remember: n != f) */ + } + return lua_numbertointeger(f, p); +} + + +/* +** try to convert a value to an integer, rounding according to 'mode', +** without string coercion. +** ("Fast track" handled by macro 'tointegerns'.) +*/ +int luaV_tointegerns (const TValue *obj, lua_Integer *p, F2Imod mode) { + if (ttisfloat(obj)) + return luaV_flttointeger(fltvalue(obj), p, mode); + else if (ttisinteger(obj)) { + *p = ivalue(obj); + return 1; + } + else + return 0; +} + + +/* +** try to convert a value to an integer. +*/ +int luaV_tointeger (const TValue *obj, lua_Integer *p, F2Imod mode) { + TValue v; + if (l_strton(obj, &v)) /* does 'obj' point to a numerical string? */ + obj = &v; /* change it to point to its corresponding number */ + return luaV_tointegerns(obj, p, mode); +} + + +/* +** Try to convert a 'for' limit to an integer, preserving the semantics +** of the loop. Return true if the loop must not run; otherwise, '*p' +** gets the integer limit. +** (The following explanation assumes a positive step; it is valid for +** negative steps mutatis mutandis.) +** If the limit is an integer or can be converted to an integer, +** rounding down, that is the limit. +** Otherwise, check whether the limit can be converted to a float. If +** the float is too large, clip it to LUA_MAXINTEGER. If the float +** is too negative, the loop should not run, because any initial +** integer value is greater than such limit; so, the function returns +** true to signal that. (For this latter case, no integer limit would be +** correct; even a limit of LUA_MININTEGER would run the loop once for +** an initial value equal to LUA_MININTEGER.) +*/ +static int forlimit (lua_State *L, lua_Integer init, const TValue *lim, + lua_Integer *p, lua_Integer step) { + if (!luaV_tointeger(lim, p, (step < 0 ? F2Iceil : F2Ifloor))) { + /* not coercible to in integer */ + lua_Number flim; /* try to convert to float */ + if (!tonumber(lim, &flim)) /* cannot convert to float? */ + luaG_forerror(L, lim, "limit"); + /* else 'flim' is a float out of integer bounds */ + if (luai_numlt(0, flim)) { /* if it is positive, it is too large */ + if (step < 0) return 1; /* initial value must be less than it */ + *p = LUA_MAXINTEGER; /* truncate */ + } + else { /* it is less than min integer */ + if (step > 0) return 1; /* initial value must be greater than it */ + *p = LUA_MININTEGER; /* truncate */ + } + } + return (step > 0 ? init > *p : init < *p); /* not to run? */ +} + + +/* +** Prepare a numerical for loop (opcode OP_FORPREP). +** Return true to skip the loop. Otherwise, +** after preparation, stack will be as follows: +** ra : internal index (safe copy of the control variable) +** ra + 1 : loop counter (integer loops) or limit (float loops) +** ra + 2 : step +** ra + 3 : control variable +*/ +static int forprep (lua_State *L, StkId ra) { + TValue *pinit = s2v(ra); + TValue *plimit = s2v(ra + 1); + TValue *pstep = s2v(ra + 2); + if (ttisinteger(pinit) && ttisinteger(pstep)) { /* integer loop? */ + lua_Integer init = ivalue(pinit); + lua_Integer step = ivalue(pstep); + lua_Integer limit; + if (step == 0) + luaG_runerror(L, "'for' step is zero"); + setivalue(s2v(ra + 3), init); /* control variable */ + if (forlimit(L, init, plimit, &limit, step)) + return 1; /* skip the loop */ + else { /* prepare loop counter */ + lua_Unsigned count; + if (step > 0) { /* ascending loop? */ + count = l_castS2U(limit) - l_castS2U(init); + if (step != 1) /* avoid division in the too common case */ + count /= l_castS2U(step); + } + else { /* step < 0; descending loop */ + count = l_castS2U(init) - l_castS2U(limit); + /* 'step+1' avoids negating 'mininteger' */ + count /= l_castS2U(-(step + 1)) + 1u; + } + /* store the counter in place of the limit (which won't be + needed anymore) */ + setivalue(plimit, l_castU2S(count)); + } + } + else { /* try making all values floats */ + lua_Number init; lua_Number limit; lua_Number step; + if (unlikely(!tonumber(plimit, &limit))) + luaG_forerror(L, plimit, "limit"); + if (unlikely(!tonumber(pstep, &step))) + luaG_forerror(L, pstep, "step"); + if (unlikely(!tonumber(pinit, &init))) + luaG_forerror(L, pinit, "initial value"); + if (step == 0) + luaG_runerror(L, "'for' step is zero"); + if (luai_numlt(0, step) ? luai_numlt(limit, init) + : luai_numlt(init, limit)) + return 1; /* skip the loop */ + else { + /* make sure internal values are all floats */ + setfltvalue(plimit, limit); + setfltvalue(pstep, step); + setfltvalue(s2v(ra), init); /* internal index */ + setfltvalue(s2v(ra + 3), init); /* control variable */ + } + } + return 0; +} + + +/* +** Execute a step of a float numerical for loop, returning +** true iff the loop must continue. (The integer case is +** written online with opcode OP_FORLOOP, for performance.) +*/ +static int floatforloop (StkId ra) { + lua_Number step = fltvalue(s2v(ra + 2)); + lua_Number limit = fltvalue(s2v(ra + 1)); + lua_Number idx = fltvalue(s2v(ra)); /* internal index */ + idx = luai_numadd(L, idx, step); /* increment index */ + if (luai_numlt(0, step) ? luai_numle(idx, limit) + : luai_numle(limit, idx)) { + chgfltvalue(s2v(ra), idx); /* update internal index */ + setfltvalue(s2v(ra + 3), idx); /* and control variable */ + return 1; /* jump back */ + } + else + return 0; /* finish the loop */ +} + + +/* +** Finish the table access 'val = t[key]'. +** if 'slot' is NULL, 't' is not a table; otherwise, 'slot' points to +** t[k] entry (which must be empty). +*/ +void luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val, + const TValue *slot) { + int loop; /* counter to avoid infinite loops */ + const TValue *tm; /* metamethod */ + for (loop = 0; loop < MAXTAGLOOP; loop++) { + if (slot == NULL) { /* 't' is not a table? */ + lua_assert(!ttistable(t)); + tm = luaT_gettmbyobj(L, t, TM_INDEX); + if (unlikely(notm(tm))) + luaG_typeerror(L, t, "index"); /* no metamethod */ + /* else will try the metamethod */ + } + else { /* 't' is a table */ + lua_assert(isempty(slot)); + tm = fasttm(L, hvalue(t)->metatable, TM_INDEX); /* table's metamethod */ + if (tm == NULL) { /* no metamethod? */ + setnilvalue(s2v(val)); /* result is nil */ + return; + } + /* else will try the metamethod */ + } + if (ttisfunction(tm)) { /* is metamethod a function? */ + luaT_callTMres(L, tm, t, key, val); /* call it */ + return; + } + t = tm; /* else try to access 'tm[key]' */ + if (luaV_fastget(L, t, key, slot, luaH_get)) { /* fast track? */ + setobj2s(L, val, slot); /* done */ + return; + } + /* else repeat (tail call 'luaV_finishget') */ + } + luaG_runerror(L, "'__index' chain too long; possible loop"); +} + + +/* +** Finish a table assignment 't[key] = val'. +** If 'slot' is NULL, 't' is not a table. Otherwise, 'slot' points +** to the entry 't[key]', or to a value with an absent key if there +** is no such entry. (The value at 'slot' must be empty, otherwise +** 'luaV_fastget' would have done the job.) +*/ +void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + TValue *val, const TValue *slot) { + int loop; /* counter to avoid infinite loops */ + for (loop = 0; loop < MAXTAGLOOP; loop++) { + const TValue *tm; /* '__newindex' metamethod */ + if (slot != NULL) { /* is 't' a table? */ + Table *h = hvalue(t); /* save 't' table */ + lua_assert(isempty(slot)); /* slot must be empty */ + tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get metamethod */ + if (tm == NULL) { /* no metamethod? */ + if (isabstkey(slot)) /* no previous entry? */ + slot = luaH_newkey(L, h, key); /* create one */ + /* no metamethod and (now) there is an entry with given key */ + setobj2t(L, cast(TValue *, slot), val); /* set its new value */ + invalidateTMcache(h); + luaC_barrierback(L, obj2gco(h), val); + return; + } + /* else will try the metamethod */ + } + else { /* not a table; check metamethod */ + tm = luaT_gettmbyobj(L, t, TM_NEWINDEX); + if (unlikely(notm(tm))) + luaG_typeerror(L, t, "index"); + } + /* try the metamethod */ + if (ttisfunction(tm)) { + luaT_callTM(L, tm, t, key, val); + return; + } + t = tm; /* else repeat assignment over 'tm' */ + if (luaV_fastget(L, t, key, slot, luaH_get)) { + luaV_finishfastset(L, t, slot, val); + return; /* done */ + } + /* else 'return luaV_finishset(L, t, key, val, slot)' (loop) */ + } + luaG_runerror(L, "'__newindex' chain too long; possible loop"); +} + + +/* +** Compare two strings 'ls' x 'rs', returning an integer less-equal- +** -greater than zero if 'ls' is less-equal-greater than 'rs'. +** The code is a little tricky because it allows '\0' in the strings +** and it uses 'strcoll' (to respect locales) for each segments +** of the strings. +*/ +static int l_strcmp (const TString *ls, const TString *rs) { + const char *l = getstr(ls); + size_t ll = tsslen(ls); + const char *r = getstr(rs); + size_t lr = tsslen(rs); + for (;;) { /* for each segment */ + int temp = strcoll(l, r); + if (temp != 0) /* not equal? */ + return temp; /* done */ + else { /* strings are equal up to a '\0' */ + size_t len = strlen(l); /* index of first '\0' in both strings */ + if (len == lr) /* 'rs' is finished? */ + return (len == ll) ? 0 : 1; /* check 'ls' */ + else if (len == ll) /* 'ls' is finished? */ + return -1; /* 'ls' is less than 'rs' ('rs' is not finished) */ + /* both strings longer than 'len'; go on comparing after the '\0' */ + len++; + l += len; ll -= len; r += len; lr -= len; + } + } +} + + +/* +** Check whether integer 'i' is less than float 'f'. If 'i' has an +** exact representation as a float ('l_intfitsf'), compare numbers as +** floats. Otherwise, use the equivalence 'i < f <=> i < ceil(f)'. +** If 'ceil(f)' is out of integer range, either 'f' is greater than +** all integers or less than all integers. +** (The test with 'l_intfitsf' is only for performance; the else +** case is correct for all values, but it is slow due to the conversion +** from float to int.) +** When 'f' is NaN, comparisons must result in false. +*/ +static int LTintfloat (lua_Integer i, lua_Number f) { + if (l_intfitsf(i)) + return luai_numlt(cast_num(i), f); /* compare them as floats */ + else { /* i < f <=> i < ceil(f) */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Iceil)) /* fi = ceil(f) */ + return i < fi; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f > 0; /* greater? */ + } +} + + +/* +** Check whether integer 'i' is less than or equal to float 'f'. +** See comments on previous function. +*/ +static int LEintfloat (lua_Integer i, lua_Number f) { + if (l_intfitsf(i)) + return luai_numle(cast_num(i), f); /* compare them as floats */ + else { /* i <= f <=> i <= floor(f) */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ifloor)) /* fi = floor(f) */ + return i <= fi; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f > 0; /* greater? */ + } +} + + +/* +** Check whether float 'f' is less than integer 'i'. +** See comments on previous function. +*/ +static int LTfloatint (lua_Number f, lua_Integer i) { + if (l_intfitsf(i)) + return luai_numlt(f, cast_num(i)); /* compare them as floats */ + else { /* f < i <=> floor(f) < i */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ifloor)) /* fi = floor(f) */ + return fi < i; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f < 0; /* less? */ + } +} + + +/* +** Check whether float 'f' is less than or equal to integer 'i'. +** See comments on previous function. +*/ +static int LEfloatint (lua_Number f, lua_Integer i) { + if (l_intfitsf(i)) + return luai_numle(f, cast_num(i)); /* compare them as floats */ + else { /* f <= i <=> ceil(f) <= i */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Iceil)) /* fi = ceil(f) */ + return fi <= i; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f < 0; /* less? */ + } +} + + +/* +** Return 'l < r', for numbers. +*/ +static int LTnum (const TValue *l, const TValue *r) { + lua_assert(ttisnumber(l) && ttisnumber(r)); + if (ttisinteger(l)) { + lua_Integer li = ivalue(l); + if (ttisinteger(r)) + return li < ivalue(r); /* both are integers */ + else /* 'l' is int and 'r' is float */ + return LTintfloat(li, fltvalue(r)); /* l < r ? */ + } + else { + lua_Number lf = fltvalue(l); /* 'l' must be float */ + if (ttisfloat(r)) + return luai_numlt(lf, fltvalue(r)); /* both are float */ + else /* 'l' is float and 'r' is int */ + return LTfloatint(lf, ivalue(r)); + } +} + + +/* +** Return 'l <= r', for numbers. +*/ +static int LEnum (const TValue *l, const TValue *r) { + lua_assert(ttisnumber(l) && ttisnumber(r)); + if (ttisinteger(l)) { + lua_Integer li = ivalue(l); + if (ttisinteger(r)) + return li <= ivalue(r); /* both are integers */ + else /* 'l' is int and 'r' is float */ + return LEintfloat(li, fltvalue(r)); /* l <= r ? */ + } + else { + lua_Number lf = fltvalue(l); /* 'l' must be float */ + if (ttisfloat(r)) + return luai_numle(lf, fltvalue(r)); /* both are float */ + else /* 'l' is float and 'r' is int */ + return LEfloatint(lf, ivalue(r)); + } +} + + +/* +** return 'l < r' for non-numbers. +*/ +static int lessthanothers (lua_State *L, const TValue *l, const TValue *r) { + lua_assert(!ttisnumber(l) || !ttisnumber(r)); + if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) < 0; + else + return luaT_callorderTM(L, l, r, TM_LT); +} + + +/* +** Main operation less than; return 'l < r'. +*/ +int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { + if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ + return LTnum(l, r); + else return lessthanothers(L, l, r); +} + + +/* +** return 'l <= r' for non-numbers. +*/ +static int lessequalothers (lua_State *L, const TValue *l, const TValue *r) { + lua_assert(!ttisnumber(l) || !ttisnumber(r)); + if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; + else + return luaT_callorderTM(L, l, r, TM_LE); +} + + +/* +** Main operation less than or equal to; return 'l <= r'. +*/ +int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) { + if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ + return LEnum(l, r); + else return lessequalothers(L, l, r); +} + + +/* +** Main operation for equality of Lua values; return 't1 == t2'. +** L == NULL means raw equality (no metamethods) +*/ +int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { + const TValue *tm; + if (ttypetag(t1) != ttypetag(t2)) { /* not the same variant? */ + if (ttype(t1) != ttype(t2) || ttype(t1) != LUA_TNUMBER) + return 0; /* only numbers can be equal with different variants */ + else { /* two numbers with different variants */ + lua_Integer i1, i2; /* compare them as integers */ + return (tointegerns(t1, &i1) && tointegerns(t2, &i2) && i1 == i2); + } + } + /* values have same type and same variant */ + switch (ttypetag(t1)) { + case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: return 1; + case LUA_VNUMINT: return (ivalue(t1) == ivalue(t2)); + case LUA_VNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); + case LUA_VLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); + case LUA_VLCF: return fvalue(t1) == fvalue(t2); + case LUA_VSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2)); + case LUA_VLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2)); + case LUA_VUSERDATA: { + if (uvalue(t1) == uvalue(t2)) return 1; + else if (L == NULL) return 0; + tm = fasttm(L, uvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, uvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + case LUA_VTABLE: { + if (hvalue(t1) == hvalue(t2)) return 1; + else if (L == NULL) return 0; + tm = fasttm(L, hvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, hvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + default: + return gcvalue(t1) == gcvalue(t2); + } + if (tm == NULL) /* no TM? */ + return 0; /* objects are different */ + else { + luaT_callTMres(L, tm, t1, t2, L->top); /* call TM */ + return !l_isfalse(s2v(L->top)); + } +} + + +/* macro used by 'luaV_concat' to ensure that element at 'o' is a string */ +#define tostring(L,o) \ + (ttisstring(o) || (cvt2str(o) && (luaO_tostring(L, o), 1))) + +#define isemptystr(o) (ttisshrstring(o) && tsvalue(o)->shrlen == 0) + +/* copy strings in stack from top - n up to top - 1 to buffer */ +static void copy2buff (StkId top, int n, char *buff) { + size_t tl = 0; /* size already copied */ + do { + size_t l = vslen(s2v(top - n)); /* length of string being copied */ + memcpy(buff + tl, svalue(s2v(top - n)), l * sizeof(char)); + tl += l; + } while (--n > 0); +} + + +/* +** Main operation for concatenation: concat 'total' values in the stack, +** from 'L->top - total' up to 'L->top - 1'. +*/ +void luaV_concat (lua_State *L, int total) { + if (total == 1) + return; /* "all" values already concatenated */ + do { + StkId top = L->top; + int n = 2; /* number of elements handled in this pass (at least 2) */ + if (!(ttisstring(s2v(top - 2)) || cvt2str(s2v(top - 2))) || + !tostring(L, s2v(top - 1))) + luaT_tryconcatTM(L); + else if (isemptystr(s2v(top - 1))) /* second operand is empty? */ + cast_void(tostring(L, s2v(top - 2))); /* result is first operand */ + else if (isemptystr(s2v(top - 2))) { /* first operand is empty string? */ + setobjs2s(L, top - 2, top - 1); /* result is second op. */ + } + else { + /* at least two non-empty string values; get as many as possible */ + size_t tl = vslen(s2v(top - 1)); + TString *ts; + /* collect total length and number of strings */ + for (n = 1; n < total && tostring(L, s2v(top - n - 1)); n++) { + size_t l = vslen(s2v(top - n - 1)); + if (unlikely(l >= (MAX_SIZE/sizeof(char)) - tl)) + luaG_runerror(L, "string length overflow"); + tl += l; + } + if (tl <= LUAI_MAXSHORTLEN) { /* is result a short string? */ + char buff[LUAI_MAXSHORTLEN]; + copy2buff(top, n, buff); /* copy strings to buffer */ + ts = luaS_newlstr(L, buff, tl); + } + else { /* long string; copy strings directly to final result */ + ts = luaS_createlngstrobj(L, tl); + copy2buff(top, n, getstr(ts)); + } + setsvalue2s(L, top - n, ts); /* create result */ + } + total -= n-1; /* got 'n' strings to create 1 new */ + L->top -= n-1; /* popped 'n' strings and pushed one */ + } while (total > 1); /* repeat until only 1 result left */ +} + + +/* +** Main operation 'ra = #rb'. +*/ +void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { + const TValue *tm; + switch (ttypetag(rb)) { + case LUA_VTABLE: { + Table *h = hvalue(rb); + tm = fasttm(L, h->metatable, TM_LEN); + if (tm) break; /* metamethod? break switch to call it */ + setivalue(s2v(ra), luaH_getn(h)); /* else primitive len */ + return; + } + case LUA_VSHRSTR: { + setivalue(s2v(ra), tsvalue(rb)->shrlen); + return; + } + case LUA_VLNGSTR: { + setivalue(s2v(ra), tsvalue(rb)->u.lnglen); + return; + } + default: { /* try metamethod */ + tm = luaT_gettmbyobj(L, rb, TM_LEN); + if (unlikely(notm(tm))) /* no metamethod? */ + luaG_typeerror(L, rb, "get length of"); + break; + } + } + luaT_callTMres(L, tm, rb, rb, ra); +} + + +/* +** Integer division; return 'm // n', that is, floor(m/n). +** C division truncates its result (rounds towards zero). +** 'floor(q) == trunc(q)' when 'q >= 0' or when 'q' is integer, +** otherwise 'floor(q) == trunc(q) - 1'. +*/ +lua_Integer luaV_idiv (lua_State *L, lua_Integer m, lua_Integer n) { + if (unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */ + if (n == 0) + luaG_runerror(L, "attempt to divide by zero"); + return intop(-, 0, m); /* n==-1; avoid overflow with 0x80000...//-1 */ + } + else { + lua_Integer q = m / n; /* perform C division */ + if ((m ^ n) < 0 && m % n != 0) /* 'm/n' would be negative non-integer? */ + q -= 1; /* correct result for different rounding */ + return q; + } +} + + +/* +** Integer modulus; return 'm % n'. (Assume that C '%' with +** negative operands follows C99 behavior. See previous comment +** about luaV_idiv.) +*/ +lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) { + if (unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */ + if (n == 0) + luaG_runerror(L, "attempt to perform 'n%%0'"); + return 0; /* m % -1 == 0; avoid overflow with 0x80000...%-1 */ + } + else { + lua_Integer r = m % n; + if (r != 0 && (r ^ n) < 0) /* 'm/n' would be non-integer negative? */ + r += n; /* correct result for different rounding */ + return r; + } +} + + +/* +** Float modulus +*/ +lua_Number luaV_modf (lua_State *L, lua_Number m, lua_Number n) { + lua_Number r; + luai_nummod(L, m, n, r); + return r; +} + + +/* number of bits in an integer */ +#define NBITS cast_int(sizeof(lua_Integer) * CHAR_BIT) + +/* +** Shift left operation. (Shift right just negates 'y'.) +*/ +#define luaV_shiftr(x,y) luaV_shiftl(x,-(y)) + +lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) { + if (y < 0) { /* shift right? */ + if (y <= -NBITS) return 0; + else return intop(>>, x, -y); + } + else { /* shift left */ + if (y >= NBITS) return 0; + else return intop(<<, x, y); + } +} + + +/* +** create a new Lua closure, push it in the stack, and initialize +** its upvalues. +*/ +static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, + StkId ra) { + int nup = p->sizeupvalues; + Upvaldesc *uv = p->upvalues; + int i; + LClosure *ncl = luaF_newLclosure(L, nup); + ncl->p = p; + setclLvalue2s(L, ra, ncl); /* anchor new closure in stack */ + for (i = 0; i < nup; i++) { /* fill in its upvalues */ + if (uv[i].instack) /* upvalue refers to local variable? */ + ncl->upvals[i] = luaF_findupval(L, base + uv[i].idx); + else /* get upvalue from enclosing function */ + ncl->upvals[i] = encup[uv[i].idx]; + luaC_objbarrier(L, ncl, ncl->upvals[i]); + } +} + + +/* +** finish execution of an opcode interrupted by a yield +*/ +void luaV_finishOp (lua_State *L) { + CallInfo *ci = L->ci; + StkId base = ci->func + 1; + Instruction inst = *(ci->u.l.savedpc - 1); /* interrupted instruction */ + OpCode op = GET_OPCODE(inst); + switch (op) { /* finish its execution */ + case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: { + setobjs2s(L, base + GETARG_A(*(ci->u.l.savedpc - 2)), --L->top); + break; + } + case OP_UNM: case OP_BNOT: case OP_LEN: + case OP_GETTABUP: case OP_GETTABLE: case OP_GETI: + case OP_GETFIELD: case OP_SELF: { + setobjs2s(L, base + GETARG_A(inst), --L->top); + break; + } + case OP_LT: case OP_LE: + case OP_LTI: case OP_LEI: + case OP_GTI: case OP_GEI: + case OP_EQ: { /* note that 'OP_EQI'/'OP_EQK' cannot yield */ + int res = !l_isfalse(s2v(L->top - 1)); + L->top--; +#if defined(LUA_COMPAT_LT_LE) + if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ + ci->callstatus ^= CIST_LEQ; /* clear mark */ + res = !res; /* negate result */ + } +#endif + lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); + if (res != GETARG_k(inst)) /* condition failed? */ + ci->u.l.savedpc++; /* skip jump instruction */ + break; + } + case OP_CONCAT: { + StkId top = L->top - 1; /* top when 'luaT_tryconcatTM' was called */ + int a = GETARG_A(inst); /* first element to concatenate */ + int total = cast_int(top - 1 - (base + a)); /* yet to concatenate */ + setobjs2s(L, top - 2, top); /* put TM result in proper position */ + L->top = top - 1; /* top is one after last element (at top-2) */ + luaV_concat(L, total); /* concat them (may yield again) */ + break; + } + default: { + /* only these other opcodes can yield */ + lua_assert(op == OP_TFORCALL || op == OP_CALL || + op == OP_TAILCALL || op == OP_SETTABUP || op == OP_SETTABLE || + op == OP_SETI || op == OP_SETFIELD); + break; + } + } +} + + + + +/* +** {================================================================== +** Macros for arithmetic/bitwise/comparison opcodes in 'luaV_execute' +** =================================================================== +*/ + +#define l_addi(L,a,b) intop(+, a, b) +#define l_subi(L,a,b) intop(-, a, b) +#define l_muli(L,a,b) intop(*, a, b) +#define l_band(a,b) intop(&, a, b) +#define l_bor(a,b) intop(|, a, b) +#define l_bxor(a,b) intop(^, a, b) + +#define l_lti(a,b) (a < b) +#define l_lei(a,b) (a <= b) +#define l_gti(a,b) (a > b) +#define l_gei(a,b) (a >= b) + + +/* +** Arithmetic operations with immediate operands. 'iop' is the integer +** operation, 'fop' is the float operation. +*/ +#define op_arithI(L,iop,fop) { \ + TValue *v1 = vRB(i); \ + int imm = GETARG_sC(i); \ + if (ttisinteger(v1)) { \ + lua_Integer iv1 = ivalue(v1); \ + pc++; setivalue(s2v(ra), iop(L, iv1, imm)); \ + } \ + else if (ttisfloat(v1)) { \ + lua_Number nb = fltvalue(v1); \ + lua_Number fimm = cast_num(imm); \ + pc++; setfltvalue(s2v(ra), fop(L, nb, fimm)); \ + }} + + +/* +** Auxiliary function for arithmetic operations over floats and others +** with two register operands. +*/ +#define op_arithf_aux(L,v1,v2,fop) { \ + lua_Number n1; lua_Number n2; \ + if (tonumberns(v1, n1) && tonumberns(v2, n2)) { \ + pc++; setfltvalue(s2v(ra), fop(L, n1, n2)); \ + }} + + +/* +** Arithmetic operations over floats and others with register operands. +*/ +#define op_arithf(L,fop) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations with K operands for floats. +*/ +#define op_arithfK(L,fop) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); \ + op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations over integers and floats. +*/ +#define op_arith_aux(L,v1,v2,iop,fop) { \ + if (ttisinteger(v1) && ttisinteger(v2)) { \ + lua_Integer i1 = ivalue(v1); lua_Integer i2 = ivalue(v2); \ + pc++; setivalue(s2v(ra), iop(L, i1, i2)); \ + } \ + else op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations with register operands. +*/ +#define op_arith(L,iop,fop) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + op_arith_aux(L, v1, v2, iop, fop); } + + +/* +** Arithmetic operations with K operands. +*/ +#define op_arithK(L,iop,fop) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); \ + op_arith_aux(L, v1, v2, iop, fop); } + + +/* +** Bitwise operations with constant operand. +*/ +#define op_bitwiseK(L,op) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); \ + lua_Integer i1; \ + lua_Integer i2 = ivalue(v2); \ + if (tointegerns(v1, &i1)) { \ + pc++; setivalue(s2v(ra), op(i1, i2)); \ + }} + + +/* +** Bitwise operations with register operands. +*/ +#define op_bitwise(L,op) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + lua_Integer i1; lua_Integer i2; \ + if (tointegerns(v1, &i1) && tointegerns(v2, &i2)) { \ + pc++; setivalue(s2v(ra), op(i1, i2)); \ + }} + + +/* +** Order operations with register operands. 'opn' actually works +** for all numbers, but the fast track improves performance for +** integers. +*/ +#define op_order(L,opi,opn,other) { \ + int cond; \ + TValue *rb = vRB(i); \ + if (ttisinteger(s2v(ra)) && ttisinteger(rb)) { \ + lua_Integer ia = ivalue(s2v(ra)); \ + lua_Integer ib = ivalue(rb); \ + cond = opi(ia, ib); \ + } \ + else if (ttisnumber(s2v(ra)) && ttisnumber(rb)) \ + cond = opn(s2v(ra), rb); \ + else \ + Protect(cond = other(L, s2v(ra), rb)); \ + docondjump(); } + + +/* +** Order operations with immediate operand. (Immediate operand is +** always small enough to have an exact representation as a float.) +*/ +#define op_orderI(L,opi,opf,inv,tm) { \ + int cond; \ + int im = GETARG_sB(i); \ + if (ttisinteger(s2v(ra))) \ + cond = opi(ivalue(s2v(ra)), im); \ + else if (ttisfloat(s2v(ra))) { \ + lua_Number fa = fltvalue(s2v(ra)); \ + lua_Number fim = cast_num(im); \ + cond = opf(fa, fim); \ + } \ + else { \ + int isf = GETARG_C(i); \ + Protect(cond = luaT_callorderiTM(L, s2v(ra), im, inv, isf, tm)); \ + } \ + docondjump(); } + +/* }================================================================== */ + + +/* +** {================================================================== +** Function 'luaV_execute': main interpreter loop +** =================================================================== +*/ + +/* +** some macros for common tasks in 'luaV_execute' +*/ + + +#define RA(i) (base+GETARG_A(i)) +#define RB(i) (base+GETARG_B(i)) +#define vRB(i) s2v(RB(i)) +#define KB(i) (k+GETARG_B(i)) +#define RC(i) (base+GETARG_C(i)) +#define vRC(i) s2v(RC(i)) +#define KC(i) (k+GETARG_C(i)) +#define RKC(i) ((TESTARG_k(i)) ? k + GETARG_C(i) : s2v(base + GETARG_C(i))) + + + +#define updatetrap(ci) (trap = ci->u.l.trap) + +#define updatebase(ci) (base = ci->func + 1) + + +#define updatestack(ci) { if (trap) { updatebase(ci); ra = RA(i); } } + + +/* +** Execute a jump instruction. The 'updatetrap' allows signals to stop +** tight loops. (Without it, the local copy of 'trap' could never change.) +*/ +#define dojump(ci,i,e) { pc += GETARG_sJ(i) + e; updatetrap(ci); } + + +/* for test instructions, execute the jump instruction that follows it */ +#define donextjump(ci) { Instruction ni = *pc; dojump(ci, ni, 1); } + +/* +** do a conditional jump: skip next instruction if 'cond' is not what +** was expected (parameter 'k'), else do next instruction, which must +** be a jump. +*/ +#define docondjump() if (cond != GETARG_k(i)) pc++; else donextjump(ci); + + +/* +** Correct global 'pc'. +*/ +#define savepc(L) (ci->u.l.savedpc = pc) + + +/* +** Whenever code can raise errors, the global 'pc' and the global +** 'top' must be correct to report occasional errors. +*/ +#define savestate(L,ci) (savepc(L), L->top = ci->top) + + +/* +** Protect code that, in general, can raise errors, reallocate the +** stack, and change the hooks. +*/ +#define Protect(exp) (savestate(L,ci), (exp), updatetrap(ci)) + +/* special version that does not change the top */ +#define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci)) + +/* +** Protect code that can only raise errors. (That is, it cannnot change +** the stack or hooks.) +*/ +#define halfProtect(exp) (savestate(L,ci), (exp)) + +/* 'c' is the limit of live values in the stack */ +#define checkGC(L,c) \ + { luaC_condGC(L, (savepc(L), L->top = (c)), \ + updatetrap(ci)); \ + luai_threadyield(L); } + + +/* fetch an instruction and prepare its execution */ +#define vmfetch() { \ + if (trap) { /* stack reallocation or hooks? */ \ + trap = luaG_traceexec(L, pc); /* handle hooks */ \ + updatebase(ci); /* correct stack */ \ + } \ + i = *(pc++); \ + ra = RA(i); /* WARNING: any stack reallocation invalidates 'ra' */ \ +} + +#define vmdispatch(o) switch(o) +#define vmcase(l) case l: +#define vmbreak break + + +void luaV_execute (lua_State *L, CallInfo *ci) { + LClosure *cl; + TValue *k; + StkId base; + const Instruction *pc; + int trap; +#if LUA_USE_JUMPTABLE +#include "ljumptab.h" +#endif + startfunc: + trap = L->hookmask; + returning: /* trap already set */ + cl = clLvalue(s2v(ci->func)); + k = cl->p->k; + pc = ci->u.l.savedpc; + if (trap) { + if (pc == cl->p->code) { /* first instruction (not resuming)? */ + if (cl->p->is_vararg) + trap = 0; /* hooks will start after VARARGPREP instruction */ + else /* check 'call' hook */ + luaD_hookcall(L, ci); + } + ci->u.l.trap = 1; /* assume trap is on, for now */ + } + base = ci->func + 1; + /* main loop of interpreter */ + for (;;) { + Instruction i; /* instruction being executed */ + StkId ra; /* instruction's A register */ + vmfetch(); + lua_assert(base == ci->func + 1); + lua_assert(base <= L->top && L->top < L->stack_last); + /* invalidate top for instructions not expecting it */ + lua_assert(isIT(i) || (cast_void(L->top = base), 1)); + vmdispatch (GET_OPCODE(i)) { + vmcase(OP_MOVE) { + setobjs2s(L, ra, RB(i)); + vmbreak; + } + vmcase(OP_LOADI) { + lua_Integer b = GETARG_sBx(i); + setivalue(s2v(ra), b); + vmbreak; + } + vmcase(OP_LOADF) { + int b = GETARG_sBx(i); + setfltvalue(s2v(ra), cast_num(b)); + vmbreak; + } + vmcase(OP_LOADK) { + TValue *rb = k + GETARG_Bx(i); + setobj2s(L, ra, rb); + vmbreak; + } + vmcase(OP_LOADKX) { + TValue *rb; + rb = k + GETARG_Ax(*pc); pc++; + setobj2s(L, ra, rb); + vmbreak; + } + vmcase(OP_LOADFALSE) { + setbfvalue(s2v(ra)); + vmbreak; + } + vmcase(OP_LFALSESKIP) { + setbfvalue(s2v(ra)); + pc++; /* skip next instruction */ + vmbreak; + } + vmcase(OP_LOADTRUE) { + setbtvalue(s2v(ra)); + vmbreak; + } + vmcase(OP_LOADNIL) { + int b = GETARG_B(i); + do { + setnilvalue(s2v(ra++)); + } while (b--); + vmbreak; + } + vmcase(OP_GETUPVAL) { + int b = GETARG_B(i); + setobj2s(L, ra, cl->upvals[b]->v); + vmbreak; + } + vmcase(OP_SETUPVAL) { + UpVal *uv = cl->upvals[GETARG_B(i)]; + setobj(L, uv->v, s2v(ra)); + luaC_barrier(L, uv, s2v(ra)); + vmbreak; + } + vmcase(OP_GETTABUP) { + const TValue *slot; + TValue *upval = cl->upvals[GETARG_B(i)]->v; + TValue *rc = KC(i); + TString *key = tsvalue(rc); /* key must be a string */ + if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, upval, rc, ra, slot)); + vmbreak; + } + vmcase(OP_GETTABLE) { + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = vRC(i); + lua_Unsigned n; + if (ttisinteger(rc) /* fast track for integers? */ + ? (cast_void(n = ivalue(rc)), luaV_fastgeti(L, rb, n, slot)) + : luaV_fastget(L, rb, rc, slot, luaH_get)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); + vmbreak; + } + vmcase(OP_GETI) { + const TValue *slot; + TValue *rb = vRB(i); + int c = GETARG_C(i); + if (luaV_fastgeti(L, rb, c, slot)) { + setobj2s(L, ra, slot); + } + else { + TValue key; + setivalue(&key, c); + Protect(luaV_finishget(L, rb, &key, ra, slot)); + } + vmbreak; + } + vmcase(OP_GETFIELD) { + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = KC(i); + TString *key = tsvalue(rc); /* key must be a string */ + if (luaV_fastget(L, rb, key, slot, luaH_getshortstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); + vmbreak; + } + vmcase(OP_SETTABUP) { + const TValue *slot; + TValue *upval = cl->upvals[GETARG_A(i)]->v; + TValue *rb = KB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rb); /* key must be a string */ + if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) { + luaV_finishfastset(L, upval, slot, rc); + } + else + Protect(luaV_finishset(L, upval, rb, rc, slot)); + vmbreak; + } + vmcase(OP_SETTABLE) { + const TValue *slot; + TValue *rb = vRB(i); /* key (table is in 'ra') */ + TValue *rc = RKC(i); /* value */ + lua_Unsigned n; + if (ttisinteger(rb) /* fast track for integers? */ + ? (cast_void(n = ivalue(rb)), luaV_fastgeti(L, s2v(ra), n, slot)) + : luaV_fastget(L, s2v(ra), rb, slot, luaH_get)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else + Protect(luaV_finishset(L, s2v(ra), rb, rc, slot)); + vmbreak; + } + vmcase(OP_SETI) { + const TValue *slot; + int c = GETARG_B(i); + TValue *rc = RKC(i); + if (luaV_fastgeti(L, s2v(ra), c, slot)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else { + TValue key; + setivalue(&key, c); + Protect(luaV_finishset(L, s2v(ra), &key, rc, slot)); + } + vmbreak; + } + vmcase(OP_SETFIELD) { + const TValue *slot; + TValue *rb = KB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rb); /* key must be a string */ + if (luaV_fastget(L, s2v(ra), key, slot, luaH_getshortstr)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else + Protect(luaV_finishset(L, s2v(ra), rb, rc, slot)); + vmbreak; + } + vmcase(OP_NEWTABLE) { + int b = GETARG_B(i); /* log2(hash size) + 1 */ + int c = GETARG_C(i); /* array size */ + Table *t; + if (b > 0) + b = 1 << (b - 1); /* size is 2^(b - 1) */ + lua_assert((!TESTARG_k(i)) == (GETARG_Ax(*pc) == 0)); + if (TESTARG_k(i)) /* non-zero extra argument? */ + c += GETARG_Ax(*pc) * (MAXARG_C + 1); /* add it to size */ + pc++; /* skip extra argument */ + L->top = ra + 1; /* correct top in case of emergency GC */ + t = luaH_new(L); /* memory allocation */ + sethvalue2s(L, ra, t); + if (b != 0 || c != 0) + luaH_resize(L, t, c, b); /* idem */ + checkGC(L, ra + 1); + vmbreak; + } + vmcase(OP_SELF) { + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rc); /* key must be a string */ + setobj2s(L, ra + 1, rb); + if (luaV_fastget(L, rb, key, slot, luaH_getstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); + vmbreak; + } + vmcase(OP_ADDI) { + op_arithI(L, l_addi, luai_numadd); + vmbreak; + } + vmcase(OP_ADDK) { + op_arithK(L, l_addi, luai_numadd); + vmbreak; + } + vmcase(OP_SUBK) { + op_arithK(L, l_subi, luai_numsub); + vmbreak; + } + vmcase(OP_MULK) { + op_arithK(L, l_muli, luai_nummul); + vmbreak; + } + vmcase(OP_MODK) { + op_arithK(L, luaV_mod, luaV_modf); + vmbreak; + } + vmcase(OP_POWK) { + op_arithfK(L, luai_numpow); + vmbreak; + } + vmcase(OP_DIVK) { + op_arithfK(L, luai_numdiv); + vmbreak; + } + vmcase(OP_IDIVK) { + op_arithK(L, luaV_idiv, luai_numidiv); + vmbreak; + } + vmcase(OP_BANDK) { + op_bitwiseK(L, l_band); + vmbreak; + } + vmcase(OP_BORK) { + op_bitwiseK(L, l_bor); + vmbreak; + } + vmcase(OP_BXORK) { + op_bitwiseK(L, l_bxor); + vmbreak; + } + vmcase(OP_SHRI) { + TValue *rb = vRB(i); + int ic = GETARG_sC(i); + lua_Integer ib; + if (tointegerns(rb, &ib)) { + pc++; setivalue(s2v(ra), luaV_shiftl(ib, -ic)); + } + vmbreak; + } + vmcase(OP_SHLI) { + TValue *rb = vRB(i); + int ic = GETARG_sC(i); + lua_Integer ib; + if (tointegerns(rb, &ib)) { + pc++; setivalue(s2v(ra), luaV_shiftl(ic, ib)); + } + vmbreak; + } + vmcase(OP_ADD) { + op_arith(L, l_addi, luai_numadd); + vmbreak; + } + vmcase(OP_SUB) { + op_arith(L, l_subi, luai_numsub); + vmbreak; + } + vmcase(OP_MUL) { + op_arith(L, l_muli, luai_nummul); + vmbreak; + } + vmcase(OP_MOD) { + op_arith(L, luaV_mod, luaV_modf); + vmbreak; + } + vmcase(OP_POW) { + op_arithf(L, luai_numpow); + vmbreak; + } + vmcase(OP_DIV) { /* float division (always with floats) */ + op_arithf(L, luai_numdiv); + vmbreak; + } + vmcase(OP_IDIV) { /* floor division */ + op_arith(L, luaV_idiv, luai_numidiv); + vmbreak; + } + vmcase(OP_BAND) { + op_bitwise(L, l_band); + vmbreak; + } + vmcase(OP_BOR) { + op_bitwise(L, l_bor); + vmbreak; + } + vmcase(OP_BXOR) { + op_bitwise(L, l_bxor); + vmbreak; + } + vmcase(OP_SHR) { + op_bitwise(L, luaV_shiftr); + vmbreak; + } + vmcase(OP_SHL) { + op_bitwise(L, luaV_shiftl); + vmbreak; + } + vmcase(OP_MMBIN) { + Instruction pi = *(pc - 2); /* original arith. expression */ + TValue *rb = vRB(i); + TMS tm = (TMS)GETARG_C(i); + StkId result = RA(pi); + lua_assert(OP_ADD <= GET_OPCODE(pi) && GET_OPCODE(pi) <= OP_SHR); + Protect(luaT_trybinTM(L, s2v(ra), rb, result, tm)); + vmbreak; + } + vmcase(OP_MMBINI) { + Instruction pi = *(pc - 2); /* original arith. expression */ + int imm = GETARG_sB(i); + TMS tm = (TMS)GETARG_C(i); + int flip = GETARG_k(i); + StkId result = RA(pi); + Protect(luaT_trybiniTM(L, s2v(ra), imm, flip, result, tm)); + vmbreak; + } + vmcase(OP_MMBINK) { + Instruction pi = *(pc - 2); /* original arith. expression */ + TValue *imm = KB(i); + TMS tm = (TMS)GETARG_C(i); + int flip = GETARG_k(i); + StkId result = RA(pi); + Protect(luaT_trybinassocTM(L, s2v(ra), imm, flip, result, tm)); + vmbreak; + } + vmcase(OP_UNM) { + TValue *rb = vRB(i); + lua_Number nb; + if (ttisinteger(rb)) { + lua_Integer ib = ivalue(rb); + setivalue(s2v(ra), intop(-, 0, ib)); + } + else if (tonumberns(rb, nb)) { + setfltvalue(s2v(ra), luai_numunm(L, nb)); + } + else + Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM)); + vmbreak; + } + vmcase(OP_BNOT) { + TValue *rb = vRB(i); + lua_Integer ib; + if (tointegerns(rb, &ib)) { + setivalue(s2v(ra), intop(^, ~l_castS2U(0), ib)); + } + else + Protect(luaT_trybinTM(L, rb, rb, ra, TM_BNOT)); + vmbreak; + } + vmcase(OP_NOT) { + TValue *rb = vRB(i); + if (l_isfalse(rb)) + setbtvalue(s2v(ra)); + else + setbfvalue(s2v(ra)); + vmbreak; + } + vmcase(OP_LEN) { + Protect(luaV_objlen(L, ra, vRB(i))); + vmbreak; + } + vmcase(OP_CONCAT) { + int n = GETARG_B(i); /* number of elements to concatenate */ + L->top = ra + n; /* mark the end of concat operands */ + ProtectNT(luaV_concat(L, n)); + checkGC(L, L->top); /* 'luaV_concat' ensures correct top */ + vmbreak; + } + vmcase(OP_CLOSE) { + Protect(luaF_close(L, ra, LUA_OK)); + vmbreak; + } + vmcase(OP_TBC) { + /* create new to-be-closed upvalue */ + halfProtect(luaF_newtbcupval(L, ra)); + vmbreak; + } + vmcase(OP_JMP) { + dojump(ci, i, 0); + vmbreak; + } + vmcase(OP_EQ) { + int cond; + TValue *rb = vRB(i); + Protect(cond = luaV_equalobj(L, s2v(ra), rb)); + docondjump(); + vmbreak; + } + vmcase(OP_LT) { + op_order(L, l_lti, LTnum, lessthanothers); + vmbreak; + } + vmcase(OP_LE) { + op_order(L, l_lei, LEnum, lessequalothers); + vmbreak; + } + vmcase(OP_EQK) { + TValue *rb = KB(i); + /* basic types do not use '__eq'; we can use raw equality */ + int cond = luaV_rawequalobj(s2v(ra), rb); + docondjump(); + vmbreak; + } + vmcase(OP_EQI) { + int cond; + int im = GETARG_sB(i); + if (ttisinteger(s2v(ra))) + cond = (ivalue(s2v(ra)) == im); + else if (ttisfloat(s2v(ra))) + cond = luai_numeq(fltvalue(s2v(ra)), cast_num(im)); + else + cond = 0; /* other types cannot be equal to a number */ + docondjump(); + vmbreak; + } + vmcase(OP_LTI) { + op_orderI(L, l_lti, luai_numlt, 0, TM_LT); + vmbreak; + } + vmcase(OP_LEI) { + op_orderI(L, l_lei, luai_numle, 0, TM_LE); + vmbreak; + } + vmcase(OP_GTI) { + op_orderI(L, l_gti, luai_numgt, 1, TM_LT); + vmbreak; + } + vmcase(OP_GEI) { + op_orderI(L, l_gei, luai_numge, 1, TM_LE); + vmbreak; + } + vmcase(OP_TEST) { + int cond = !l_isfalse(s2v(ra)); + docondjump(); + vmbreak; + } + vmcase(OP_TESTSET) { + TValue *rb = vRB(i); + if (l_isfalse(rb) == GETARG_k(i)) + pc++; + else { + setobj2s(L, ra, rb); + donextjump(ci); + } + vmbreak; + } + vmcase(OP_CALL) { + CallInfo *newci; + int b = GETARG_B(i); + int nresults = GETARG_C(i) - 1; + if (b != 0) /* fixed number of arguments? */ + L->top = ra + b; /* top signals number of arguments */ + /* else previous instruction set top */ + savepc(L); /* in case of errors */ + if ((newci = luaD_precall(L, ra, nresults)) == NULL) + updatetrap(ci); /* C call; nothing else to be done */ + else { /* Lua call: run function in this same C frame */ + ci = newci; + ci->callstatus = 0; /* call re-uses 'luaV_execute' */ + goto startfunc; + } + vmbreak; + } + vmcase(OP_TAILCALL) { + int b = GETARG_B(i); /* number of arguments + 1 (function) */ + int nparams1 = GETARG_C(i); + /* delta is virtual 'func' - real 'func' (vararg functions) */ + int delta = (nparams1) ? ci->u.l.nextraargs + nparams1 : 0; + if (b != 0) + L->top = ra + b; + else /* previous instruction set top */ + b = cast_int(L->top - ra); + savepc(ci); /* several calls here can raise errors */ + if (TESTARG_k(i)) { + /* close upvalues from current call; the compiler ensures + that there are no to-be-closed variables here, so this + call cannot change the stack */ + luaF_close(L, base, NOCLOSINGMETH); + lua_assert(base == ci->func + 1); + } + while (!ttisfunction(s2v(ra))) { /* not a function? */ + luaD_tryfuncTM(L, ra); /* try '__call' metamethod */ + b++; /* there is now one extra argument */ + checkstackGCp(L, 1, ra); + } + if (!ttisLclosure(s2v(ra))) { /* C function? */ + luaD_precall(L, ra, LUA_MULTRET); /* call it */ + updatetrap(ci); + updatestack(ci); /* stack may have been relocated */ + ci->func -= delta; /* restore 'func' (if vararg) */ + luaD_poscall(L, ci, cast_int(L->top - ra)); /* finish caller */ + updatetrap(ci); /* 'luaD_poscall' can change hooks */ + goto ret; /* caller returns after the tail call */ + } + ci->func -= delta; /* restore 'func' (if vararg) */ + luaD_pretailcall(L, ci, ra, b); /* prepare call frame */ + goto startfunc; /* execute the callee */ + } + vmcase(OP_RETURN) { + int n = GETARG_B(i) - 1; /* number of results */ + int nparams1 = GETARG_C(i); + if (n < 0) /* not fixed? */ + n = cast_int(L->top - ra); /* get what is available */ + savepc(ci); + if (TESTARG_k(i)) { /* may there be open upvalues? */ + if (L->top < ci->top) + L->top = ci->top; + luaF_close(L, base, LUA_OK); + updatetrap(ci); + updatestack(ci); + } + if (nparams1) /* vararg function? */ + ci->func -= ci->u.l.nextraargs + nparams1; + L->top = ra + n; /* set call for 'luaD_poscall' */ + luaD_poscall(L, ci, n); + updatetrap(ci); /* 'luaD_poscall' can change hooks */ + goto ret; + } + vmcase(OP_RETURN0) { + if (L->hookmask) { + L->top = ra; + savepc(ci); + luaD_poscall(L, ci, 0); /* no hurry... */ + trap = 1; + } + else { /* do the 'poscall' here */ + int nres = ci->nresults; + L->ci = ci->previous; /* back to caller */ + L->top = base - 1; + while (nres-- > 0) + setnilvalue(s2v(L->top++)); /* all results are nil */ + } + goto ret; + } + vmcase(OP_RETURN1) { + if (L->hookmask) { + L->top = ra + 1; + savepc(ci); + luaD_poscall(L, ci, 1); /* no hurry... */ + trap = 1; + } + else { /* do the 'poscall' here */ + int nres = ci->nresults; + L->ci = ci->previous; /* back to caller */ + if (nres == 0) + L->top = base - 1; /* asked for no results */ + else { + setobjs2s(L, base - 1, ra); /* at least this result */ + L->top = base; + while (--nres > 0) /* complete missing results */ + setnilvalue(s2v(L->top++)); + } + } + ret: /* return from a Lua function */ + if (ci->callstatus & CIST_FRESH) + return; /* end this frame */ + else { + ci = ci->previous; + goto returning; /* continue running caller in this frame */ + } + } + vmcase(OP_FORLOOP) { + if (ttisinteger(s2v(ra + 2))) { /* integer loop? */ + lua_Unsigned count = l_castS2U(ivalue(s2v(ra + 1))); + if (count > 0) { /* still more iterations? */ + lua_Integer step = ivalue(s2v(ra + 2)); + lua_Integer idx = ivalue(s2v(ra)); /* internal index */ + chgivalue(s2v(ra + 1), count - 1); /* update counter */ + idx = intop(+, idx, step); /* add step to index */ + chgivalue(s2v(ra), idx); /* update internal index */ + setivalue(s2v(ra + 3), idx); /* and control variable */ + pc -= GETARG_Bx(i); /* jump back */ + } + } + else if (floatforloop(ra)) /* float loop */ + pc -= GETARG_Bx(i); /* jump back */ + updatetrap(ci); /* allows a signal to break the loop */ + vmbreak; + } + vmcase(OP_FORPREP) { + savestate(L, ci); /* in case of errors */ + if (forprep(L, ra)) + pc += GETARG_Bx(i) + 1; /* skip the loop */ + vmbreak; + } + vmcase(OP_TFORPREP) { + /* create to-be-closed upvalue (if needed) */ + halfProtect(luaF_newtbcupval(L, ra + 3)); + pc += GETARG_Bx(i); + i = *(pc++); /* go to next instruction */ + lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i)); + goto l_tforcall; + } + vmcase(OP_TFORCALL) { + l_tforcall: + /* 'ra' has the iterator function, 'ra + 1' has the state, + 'ra + 2' has the control variable, and 'ra + 3' has the + to-be-closed variable. The call will use the stack after + these values (starting at 'ra + 4') + */ + /* push function, state, and control variable */ + memcpy(ra + 4, ra, 3 * sizeof(*ra)); + L->top = ra + 4 + 3; + ProtectNT(luaD_call(L, ra + 4, GETARG_C(i))); /* do the call */ + updatestack(ci); /* stack may have changed */ + i = *(pc++); /* go to next instruction */ + lua_assert(GET_OPCODE(i) == OP_TFORLOOP && ra == RA(i)); + goto l_tforloop; + } + vmcase(OP_TFORLOOP) { + l_tforloop: + if (!ttisnil(s2v(ra + 4))) { /* continue loop? */ + setobjs2s(L, ra + 2, ra + 4); /* save control variable */ + pc -= GETARG_Bx(i); /* jump back */ + } + vmbreak; + } + vmcase(OP_SETLIST) { + int n = GETARG_B(i); + unsigned int last = GETARG_C(i); + Table *h = hvalue(s2v(ra)); + if (n == 0) + n = cast_int(L->top - ra) - 1; /* get up to the top */ + else + L->top = ci->top; /* correct top in case of emergency GC */ + last += n; + if (TESTARG_k(i)) { + last += GETARG_Ax(*pc) * (MAXARG_C + 1); + pc++; + } + if (last > luaH_realasize(h)) /* needs more space? */ + luaH_resizearray(L, h, last); /* preallocate it at once */ + for (; n > 0; n--) { + TValue *val = s2v(ra + n); + setobj2t(L, &h->array[last - 1], val); + last--; + luaC_barrierback(L, obj2gco(h), val); + } + vmbreak; + } + vmcase(OP_CLOSURE) { + Proto *p = cl->p->p[GETARG_Bx(i)]; + halfProtect(pushclosure(L, p, cl->upvals, base, ra)); + checkGC(L, ra + 1); + vmbreak; + } + vmcase(OP_VARARG) { + int n = GETARG_C(i) - 1; /* required results */ + Protect(luaT_getvarargs(L, ci, ra, n)); + vmbreak; + } + vmcase(OP_VARARGPREP) { + ProtectNT(luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p)); + if (trap) { + luaD_hookcall(L, ci); + L->oldpc = 1; /* next opcode will be seen as a "new" line */ + } + updatebase(ci); /* function has new base after adjustment */ + vmbreak; + } + vmcase(OP_EXTRAARG) { + lua_assert(0); + vmbreak; + } + } + } +} + +/* }================================================================== */ diff --git a/Lua/lvm.h b/Lua/lvm.h new file mode 100644 index 00000000..2d4ac160 --- /dev/null +++ b/Lua/lvm.h @@ -0,0 +1,134 @@ +/* +** $Id: lvm.h $ +** Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lvm_h +#define lvm_h + + +#include "ldo.h" +#include "lobject.h" +#include "ltm.h" + + +#if !defined(LUA_NOCVTN2S) +#define cvt2str(o) ttisnumber(o) +#else +#define cvt2str(o) 0 /* no conversion from numbers to strings */ +#endif + + +#if !defined(LUA_NOCVTS2N) +#define cvt2num(o) ttisstring(o) +#else +#define cvt2num(o) 0 /* no conversion from strings to numbers */ +#endif + + +/* +** You can define LUA_FLOORN2I if you want to convert floats to integers +** by flooring them (instead of raising an error if they are not +** integral values) +*/ +#if !defined(LUA_FLOORN2I) +#define LUA_FLOORN2I F2Ieq +#endif + + +/* +** Rounding modes for float->integer coercion + */ +typedef enum { + F2Ieq, /* no rounding; accepts only integral values */ + F2Ifloor, /* takes the floor of the number */ + F2Iceil /* takes the ceil of the number */ +} F2Imod; + + +/* convert an object to a float (including string coercion) */ +#define tonumber(o,n) \ + (ttisfloat(o) ? (*(n) = fltvalue(o), 1) : luaV_tonumber_(o,n)) + + +/* convert an object to a float (without string coercion) */ +#define tonumberns(o,n) \ + (ttisfloat(o) ? ((n) = fltvalue(o), 1) : \ + (ttisinteger(o) ? ((n) = cast_num(ivalue(o)), 1) : 0)) + + +/* convert an object to an integer (including string coercion) */ +#define tointeger(o,i) \ + (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,LUA_FLOORN2I)) + + +/* convert an object to an integer (without string coercion) */ +#define tointegerns(o,i) \ + (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointegerns(o,i,LUA_FLOORN2I)) + + +#define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2)) + +#define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2) + + +/* +** fast track for 'gettable': if 't' is a table and 't[k]' is present, +** return 1 with 'slot' pointing to 't[k]' (position of final result). +** Otherwise, return 0 (meaning it will have to check metamethod) +** with 'slot' pointing to an empty 't[k]' (if 't' is a table) or NULL +** (otherwise). 'f' is the raw get function to use. +*/ +#define luaV_fastget(L,t,k,slot,f) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = f(hvalue(t), k), /* else, do raw access */ \ + !isempty(slot))) /* result not empty? */ + + +/* +** Special case of 'luaV_fastget' for integers, inlining the fast case +** of 'luaH_getint'. +*/ +#define luaV_fastgeti(L,t,k,slot) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = (l_castS2U(k) - 1u < hvalue(t)->alimit) \ + ? &hvalue(t)->array[k - 1] : luaH_getint(hvalue(t), k), \ + !isempty(slot))) /* result not empty? */ + + +/* +** Finish a fast set operation (when fast get succeeds). In that case, +** 'slot' points to the place to put the value. +*/ +#define luaV_finishfastset(L,t,slot,v) \ + { setobj2t(L, cast(TValue *,slot), v); \ + luaC_barrierback(L, gcvalue(t), v); } + + + + +LUAI_FUNC int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2); +LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); +LUAI_FUNC int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r); +LUAI_FUNC int luaV_tonumber_ (const TValue *obj, lua_Number *n); +LUAI_FUNC int luaV_tointeger (const TValue *obj, lua_Integer *p, F2Imod mode); +LUAI_FUNC int luaV_tointegerns (const TValue *obj, lua_Integer *p, + F2Imod mode); +LUAI_FUNC int luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode); +LUAI_FUNC void luaV_finishget (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); +LUAI_FUNC void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + TValue *val, const TValue *slot); +LUAI_FUNC void luaV_finishOp (lua_State *L); +LUAI_FUNC void luaV_execute (lua_State *L, CallInfo *ci); +LUAI_FUNC void luaV_concat (lua_State *L, int total); +LUAI_FUNC lua_Integer luaV_idiv (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Number luaV_modf (lua_State *L, lua_Number x, lua_Number y); +LUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y); +LUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb); + +#endif diff --git a/Lua/lzio.c b/Lua/lzio.c new file mode 100644 index 00000000..cd0a02d5 --- /dev/null +++ b/Lua/lzio.c @@ -0,0 +1,68 @@ +/* +** $Id: lzio.c $ +** Buffered streams +** See Copyright Notice in lua.h +*/ + +#define lzio_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "llimits.h" +#include "lmem.h" +#include "lstate.h" +#include "lzio.h" + + +int luaZ_fill (ZIO *z) { + size_t size; + lua_State *L = z->L; + const char *buff; + lua_unlock(L); + buff = z->reader(L, z->data, &size); + lua_lock(L); + if (buff == NULL || size == 0) + return EOZ; + z->n = size - 1; /* discount char being returned */ + z->p = buff; + return cast_uchar(*(z->p++)); +} + + +void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) { + z->L = L; + z->reader = reader; + z->data = data; + z->n = 0; + z->p = NULL; +} + + +/* --------------------------------------------------------------- read --- */ +size_t luaZ_read (ZIO *z, void *b, size_t n) { + while (n) { + size_t m; + if (z->n == 0) { /* no bytes in buffer? */ + if (luaZ_fill(z) == EOZ) /* try to read more */ + return n; /* no more input; return number of missing bytes */ + else { + z->n++; /* luaZ_fill consumed first byte; put it back */ + z->p--; + } + } + m = (n <= z->n) ? n : z->n; /* min. between n and z->n */ + memcpy(b, z->p, m); + z->n -= m; + z->p += m; + b = (char *)b + m; + n -= m; + } + return 0; +} + diff --git a/Lua/lzio.h b/Lua/lzio.h new file mode 100644 index 00000000..38f397fd --- /dev/null +++ b/Lua/lzio.h @@ -0,0 +1,66 @@ +/* +** $Id: lzio.h $ +** Buffered streams +** See Copyright Notice in lua.h +*/ + + +#ifndef lzio_h +#define lzio_h + +#include "lua.h" + +#include "lmem.h" + + +#define EOZ (-1) /* end of stream */ + +typedef struct Zio ZIO; + +#define zgetc(z) (((z)->n--)>0 ? cast_uchar(*(z)->p++) : luaZ_fill(z)) + + +typedef struct Mbuffer { + char *buffer; + size_t n; + size_t buffsize; +} Mbuffer; + +#define luaZ_initbuffer(L, buff) ((buff)->buffer = NULL, (buff)->buffsize = 0) + +#define luaZ_buffer(buff) ((buff)->buffer) +#define luaZ_sizebuffer(buff) ((buff)->buffsize) +#define luaZ_bufflen(buff) ((buff)->n) + +#define luaZ_buffremove(buff,i) ((buff)->n -= (i)) +#define luaZ_resetbuffer(buff) ((buff)->n = 0) + + +#define luaZ_resizebuffer(L, buff, size) \ + ((buff)->buffer = luaM_reallocvchar(L, (buff)->buffer, \ + (buff)->buffsize, size), \ + (buff)->buffsize = size) + +#define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) + + +LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, + void *data); +LUAI_FUNC size_t luaZ_read (ZIO* z, void *b, size_t n); /* read next n bytes */ + + + +/* --------- Private Part ------------------ */ + +struct Zio { + size_t n; /* bytes still unread */ + const char *p; /* current position in buffer */ + lua_Reader reader; /* reader function */ + void *data; /* additional data */ + lua_State *L; /* Lua state (for reader) */ +}; + + +LUAI_FUNC int luaZ_fill (ZIO *z); + +#endif diff --git a/QQAPI/DDAPI.cpp b/QQAPI/DDAPI.cpp new file mode 100644 index 00000000..a2856a2c --- /dev/null +++ b/QQAPI/DDAPI.cpp @@ -0,0 +1,174 @@ +#include "DDAPI.h" + +using std::string; +using std::unordered_map; +using namespace DD; + +#define DDAPI(Name, ReturnType, ...) using Name##_TYPE = ReturnType (*)(__VA_ARGS__) + +DDAPI(_DriverVer, const char*); +DDAPI(GetRootDir, const std::string&); +DDAPI(Reload, bool); +DDAPI(Remake, bool); +DDAPI(Killme, void); +DDAPI(DebugLog, void, const std::string&); +DDAPI(IsDiceMaid, bool, long long); +DDAPI(GetDiceSisters, const std::set&); +DDAPI(DiceHeartbeat, void, long long, const std::string&); +DDAPI(DiceUploadBlack, int, long long, long long, long long, const std::string&, std::string&); +DDAPI(DiceUpdate, bool, const std::string&, std::string&); +DDAPI(GetNick, const std::string&, long long, long long); +DDAPI(SendPrivateMsg, void, long long, long long, const std::string&); +DDAPI(SendGroupMsg, void, long long, long long, const std::string&); +DDAPI(SendDiscussMsg, void, long long, long long, const std::string&); +DDAPI(GetFriendQQList, const std::set&, long long); +DDAPI(GetGroupIDList, const std::set&, long long); +DDAPI(GetGroupMemberList, const std::set&, long long, long long); +DDAPI(GetGroupAdminList, const std::set&, long long, long long); +DDAPI(GetGroupAuth, int, long long, long long, long long); +DDAPI(IsGroupAdmin, bool, long long, long long, long long, bool); +DDAPI(IsGroupOwner, bool, long long, long long, long long, bool); +DDAPI(IsGroupMember, bool, long long, long long, long long, bool); +DDAPI(AnswerFriendRequest, void, long long, long long, int, const std::string&); +DDAPI(AnswerGroupInvited, void, long long, long long, int); +DDAPI(GetGroupSize, const Size&, long long, long long); +DDAPI(GetGroupName, const std::string&, long long, long long); +DDAPI(GetGroupNick, const std::string&, long long, long long, long long); +DDAPI(GetGroupLastMsg, long long, long long, long long, long long); +DDAPI(PrintGroupInfo, const std::string&, long long, long long); +DDAPI(SetGroupKick, void, long long, long long, long long); +DDAPI(SetGroupBan, void, long long, long long, long long, int); +DDAPI(SetGroupAdmin, void, long long, long long, long long, bool); +DDAPI(SetGroupCard, void, long long, long long, long long, const string&); +DDAPI(SetGroupTitle, void, long long, long long, long long, const string&); +DDAPI(SetGroupWholeBan, void, long long, long long, int); +DDAPI(SetGroupLeave, void, long long, long long); +DDAPI(SetDiscussLeave, void, long long, long long); + +#define CALLVOID(Name, ...) if(ApiList.count(#Name))reinterpret_cast(ApiList[#Name])(__VA_ARGS__) +#define CALLGET(Name, ...) ApiList.count(#Name) ? reinterpret_cast(ApiList[#Name])(__VA_ARGS__) + +namespace DD { + const std::string& getDriVer() { + static string ver{ CALLGET(_DriverVer) :"" }; + return ver; + } + const string& getRootDir() { + static string dir{ CALLGET(GetRootDir) :"" }; + return dir; + } + bool reload() { + return CALLGET(Reload) :false; + } + bool remake() { + return CALLGET(Remake) :false; + } + void killme() { + CALLVOID(Killme); + } + void debugLog(const std::string& log) { + CALLVOID(DebugLog, log); + } + bool isDiceMaid(long long aimQQ){ + return CALLGET(IsDiceMaid, aimQQ) :false; + } + std::set getDiceSisters() { + return CALLGET(GetDiceSisters) :std::set(); + } + void heartbeat(const string& info) { + CALLVOID(DiceHeartbeat, loginQQ, info); + } + int uploadBlack(long long DiceMaid, long long fromQQ, long long fromGroup, + const std::string& type, std::string& info) { + return CALLGET(DiceUploadBlack, DiceMaid, fromQQ, fromGroup, type, info) :-2; + } + bool updateDice(const std::string& ver, std::string& ret) { + ret = "更新接口不存在"; + return CALLGET(DiceUpdate, ver, ret) :false; + } + std::string getQQNick(long long aimQQ) { + return CALLGET(GetNick, loginQQ, aimQQ) :""; + } + void sendPrivateMsg(long long rcvQQ, const std::string& msg) { + if (msg.empty())return; + CALLVOID(SendPrivateMsg, loginQQ, rcvQQ, msg); + } + void sendGroupMsg(long long rcvChat, const std::string& msg) { + if (msg.empty())return; + CALLVOID(SendGroupMsg, loginQQ, rcvChat, msg); + } + void sendDiscussMsg(long long rcvChat, const std::string& msg) { + if (msg.empty())return; + CALLVOID(SendDiscussMsg, loginQQ, rcvChat, msg); + } + std::set getFriendQQList() { + return CALLGET(GetFriendQQList, loginQQ) :std::set(); + } + std::set getGroupIDList() { + return CALLGET(GetGroupIDList, loginQQ) :std::set(); + } + std::set getGroupMemberList(long long aimGroup) { + return CALLGET(GetGroupMemberList, loginQQ, aimGroup) :std::set(); + } + std::set getGroupAdminList(long long aimGroup) { + return CALLGET(GetGroupAdminList, loginQQ, aimGroup) :std::set(); + } + int getGroupAuth(long long llgroup, long long llQQ, int iDefault) { + int auth{ CALLGET(GetGroupAuth, loginQQ, llgroup, llQQ) : iDefault }; + return auth < 0 ? iDefault : auth; + } + bool isGroupAdmin(long long llgroup, long long llQQ, bool bDefault) { + return CALLGET(IsGroupAdmin, loginQQ, llgroup, llQQ, bDefault) :bDefault; + } + bool isGroupOwner(long long llgroup, long long llQQ, bool bDefault) { + return CALLGET(IsGroupOwner, loginQQ, llgroup, llQQ, bDefault) :bDefault; + } + bool isGroupMember(long long llgroup, long long llQQ, bool bDefault) { + return CALLGET(IsGroupMember, loginQQ, llgroup, llQQ, bDefault) :bDefault; + } + void answerFriendRequest(long long fromQQ, int respon, const std::string& msg) { + CALLVOID(AnswerFriendRequest, loginQQ, fromQQ, respon, msg); + } + void answerGroupInvited(long long fromGroup, int respon) { + CALLVOID(AnswerGroupInvited, loginQQ, fromGroup, respon); + } + Size getGroupSize(long long aimGroup) { + return CALLGET(GetGroupSize, loginQQ, aimGroup) :Size(); + } + std::string getGroupName(long long aimGroup) { + return CALLGET(GetGroupName, loginQQ, aimGroup) :""; + } + std::string getGroupNick(long long aimGroup, long long aimQQ) { + return CALLGET(GetGroupNick, loginQQ, aimGroup, aimQQ) :""; + } + long long getGroupLastMsg(long long aimGroup, long long aimQQ) { + return CALLGET(GetGroupLastMsg, loginQQ, aimGroup, aimQQ) :-1; + } + std::string printGroupInfo(long long aimGroup) { + return CALLGET(PrintGroupInfo, loginQQ, aimGroup) :"[" + getGroupName(aimGroup) + "](" + std::to_string(aimGroup) + ")[" + getGroupSize(aimGroup).tostring() + "]"; + } + void setGroupKick(long long llGroup, long long llQQ) { + CALLVOID(SetGroupKick, loginQQ, llGroup, llQQ); + } + void setGroupBan(long long llGroup, long long llQQ, int intTime) { + CALLVOID(SetGroupBan, loginQQ, llGroup, llQQ, intTime); + } + void setGroupAdmin(long long llGroup, long long llQQ, bool bSet) { + CALLVOID(SetGroupAdmin, loginQQ, llGroup, llQQ, bSet); + } + void setGroupCard(long long llGroup, long long llQQ, const string& card) { + CALLVOID(SetGroupCard, loginQQ, llGroup, llQQ, card); + } + void setGroupTitle(long long llGroup, long long llQQ, const string& card){ + CALLVOID(SetGroupTitle, loginQQ, llGroup, llQQ, card); + } + void setGroupWholeBan(long long llGroup, int intTime){ + CALLVOID(SetGroupWholeBan, loginQQ, llGroup, intTime); + } + void setGroupLeave(long long llGroup){ + CALLVOID(SetGroupLeave, loginQQ, llGroup); + } + void setDiscussLeave(long long llGroup){ + CALLVOID(SetDiscussLeave, loginQQ, llGroup); + } +} diff --git a/QQAPI/DDAPI.h b/QQAPI/DDAPI.h new file mode 100644 index 00000000..2f133a96 --- /dev/null +++ b/QQAPI/DDAPI.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include +#include + +inline long long loginQQ{ 0 }; + +struct Size { + unsigned int siz{ 0 }; + unsigned int cap{ 0 }; + std::string tostring() { + return std::to_string(siz) + "/" + std::to_string(cap); + } +}; + +using api_list = std::unordered_map; +//DiceDriver接口 +namespace DD { + const std::string& getDriVer(); + bool reload(); + bool remake(); + void killme(); + bool updateDice(const std::string&, std::string&); + inline api_list ApiList; + const std::string& getRootDir(); + inline long long getLoginQQ() { return loginQQ; } + std::string getQQNick(long long); + inline std::string getLoginNick() { return getQQNick(loginQQ); } + void debugLog(const std::string&); + bool isDiceMaid(long long); + std::set getDiceSisters(); + void heartbeat(const std::string&); + int uploadBlack(long long DiceMaid, long long fromQQ, long long fromGroup, const std::string& type, std::string& info); + void sendPrivateMsg(long long, const std::string& msg); + void sendGroupMsg(long long, const std::string& msg); + void sendDiscussMsg(long long, const std::string& msg); + std::set getFriendQQList(); + std::set getGroupIDList(); + std::set getGroupMemberList(long long); + std::set getGroupAdminList(long long); + int getGroupAuth(long long llgroup, long long llQQ, int iDefault); + bool isGroupAdmin(long long, long long, bool); + bool isGroupOwner(long long, long long, bool); + bool isGroupMember(long long, long long, bool); + void answerFriendRequest(long long fromQQ, int respon, const std::string& msg = {}); + void answerGroupInvited(long long fromGroup, int respon); + Size getGroupSize(long long); + std::string getGroupName(long long); + std::string getGroupNick(long long, long long); + long long getGroupLastMsg(long long, long long); + std::string printGroupInfo(long long); + void setGroupKick(long long, long long); + void setGroupBan(long long, long long, int); + void setGroupAdmin(long long, long long, bool = true); + void setGroupCard(long long, long long, const std::string&); + void setGroupTitle(long long, long long, const std::string&); + void setGroupWholeBan(long long, int); + void setGroupLeave(long long); + void setDiscussLeave(long long); +} +//enum class msgtype : int { Private = 0, Group = 1, Discuss = 2 }; \ No newline at end of file diff --git a/QQAPI/QQEvent.cpp b/QQAPI/QQEvent.cpp new file mode 100644 index 00000000..7ca894c3 --- /dev/null +++ b/QQAPI/QQEvent.cpp @@ -0,0 +1,9 @@ +#include "QQEvent.h" +#include "DDAPI.h" +using namespace QQ; + +EVE_Startup(eventStartUp) { + loginQQ = botQQ; + DD::ApiList = reinterpret_cast(initApi)(); + DD::debugLog("Dice" + std::to_string(botQQ) + ".init"); +} \ No newline at end of file diff --git a/QQAPI/QQEvent.h b/QQAPI/QQEvent.h new file mode 100644 index 00000000..2a0327d1 --- /dev/null +++ b/QQAPI/QQEvent.h @@ -0,0 +1,48 @@ +#pragma once +namespace QQ { +#if (defined _WIN32 && !defined _WIN64) +#ifdef _MSC_VER +#define QQEVENT(ReturnType, Name, Size) __pragma(comment(linker, "/EXPORT:" #Name "=_" #Name "@" #Size))\ + extern "C" __declspec(dllexport) ReturnType __stdcall Name +#else +#define QQEVENT(ReturnType, Name, Size)\ + extern "C" __attribute__((dllexport)) ReturnType __attribute__((__stdcall__)) Name +#endif /*_MSC_VER*/ +#elif defined _WIN64 +#ifdef _MSC_VER +#define QQEVENT(ReturnType, Name, Size)\ + extern "C" __declspec(dllexport) ReturnType __stdcall Name +#else +#define QQEVENT(ReturnType, Name, Size)\ + extern "C" __attribute__((dllexport)) Name +#endif /*_MSC_VER*/ +#else +#define QQEVENT(ReturnType, Name, Size)\ + extern "C" __attribute__((visibility ("default"))) ReturnType Name +#endif + +/* +初始化事件,加载api并获取QQ +*/ +#define EVE_Startup(Name) QQEVENT(void, Name, 12)(void* initApi, long long botQQ) +/* +启用Dice +*/ +#define EVE_Enable(Name) QQEVENT(void, Name, 0)() +#define EVE_Disable(Name) QQEVENT(void, Name, 0)() +#define EVE_Exit(Name) QQEVENT(void, Name, 0)() +#define EVE_PrivateMsg(Name) QQEVENT(int, Name, 16)(int msgId, long long fromQQ, const char* message) +#define EVE_GroupMsg(Name) QQEVENT(int, Name, 24)(int msgId, long long fromGroup, long long fromQQ, const char* message) +#define EVE_DiscussMsg(Name) QQEVENT(int, Name, 24)(int msgId, long long fromDiscuss, long long fromQQ, const char* message) +#define EVE_GroupMemberKicked(Name) QQEVENT(int, Name, 24)(long long fromGroup, long long beingOperateQQ, long long fromQQ) +#define EVE_GroupMemberIncrease(Name) QQEVENT(int, Name, 24)(long long fromGroup, long long fromQQ, long long operatorQQ) +#define EVE_GroupBan(Name) QQEVENT(int, Name, 28)(long long fromGroup, long long beingOperateQQ, long long operatorQQ, const char* duration) +#define EVE_GroupInvited(Name) QQEVENT(int, Name, 16)(long long fromGroup, long long fromQQ) +#define EVE_FriendRequest(Name) QQEVENT(int, Name, 12)(long long fromQQ, const char* message) +#define EVE_FriendAdded(Name) QQEVENT(int, Name, 8)(long long fromQQ) + +//菜单,待设置 +#define EVE_Menu(Name) QQEVENT(int, Name, 0)() +//悬浮窗,暂时无用 +//#define EVE_Status_EX(Name) QQEVENT(const char*, Name, 0)() +} \ No newline at end of file diff --git a/README.md b/README.md index f85b59df..8bcc3683 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,20 @@ 锘# Dice! -QQ Dice Robot For TRPG Based on CoolQ/Mirai +QQ Dice Robot For TRPG Based on CoolQ/Mirai/XQ -[![License](https://img.shields.io/github/license/w4123/Dice.svg)](http://www.gnu.org/licenses) -[![Build status](https://ci.appveyor.com/api/projects/status/6qm1l31k07dst0rk?svg=true)](https://ci.appveyor.com/project/w4123/dice) -[![Downloads](https://img.shields.io/github/downloads/w4123/dice/total.svg)](https://github.com/w4123/Dice/releases) -[![GitHub contributors](https://img.shields.io/github/contributors/w4123/dice.svg)](https://github.com/w4123/Dice/graphs/contributors) -[![GitHub last commit](https://img.shields.io/github/last-commit/w4123/dice.svg)](https://github.com/w4123/Dice/commits) +[![License](https://img.shields.io/github/license/Dice-Developer-Team/Dice.svg)](http://www.gnu.org/licenses) +[![Downloads](https://img.shields.io/github/downloads/Dice-Developer-Team/dice/total.svg)](https://github.com/Dice-Developer-Team/Dice/releases) +[![GitHub contributors](https://img.shields.io/github/contributors/Dice-Developer-Team/dice.svg)](https://github.com/Dice-Developer-Team/Dice/graphs/contributors) +[![GitHub last commit](https://img.shields.io/github/last-commit/Dice-Developer-Team/dice.svg)](https://github.com/Dice-Developer-Team/Dice/commits) ## 绠浠 -Dice!鏄竴娆惧熀浜庨叿Q鐨凲Q璺戝洟鎺烽鏈哄櫒浜 浜ゆ祦QQ缇:882747577鎴941980833鎴624807593(宸叉弧) +Dice!鏄竴娆惧熀浜庨叿Q/XQ/Mirai/XLZ鐨凲Q璺戝洟鎺烽鏈哄櫒浜 -涓婚〉: +璁哄潧: -Latest Stable Release: [![GitHub release](https://img.shields.io/github/release/w4123/dice.svg)](https://github.com/w4123/Dice/releases) [![GitHub Release Date](https://img.shields.io/github/release-date/w4123/dice.svg)](https://github.com/w4123/Dice/releases) +Latest Stable Release: [![GitHub release](https://img.shields.io/github/release/Dice-Developer-Team/dice.svg)](https://github.com/w4123/Dice-Developer-Team/releases) [![GitHub Release Date](https://img.shields.io/github/release-date/Dice-Developer-Team/dice.svg)](https://github.com/Dice-Developer-Team/Dice/releases) -Latest Release: [![GitHub release](https://img.shields.io/github/release-pre/w4123/dice.svg)](https://github.com/w4123/Dice/releases) [![GitHub Release Date](https://img.shields.io/github/release-date-pre/w4123/dice.svg)](https://github.com/w4123/Dice/releases) +Latest Release: [![GitHub release](https://img.shields.io/github/release-pre/Dice-Developer-Team/dice.svg)](https://github.com/Dice-Developer-Team/Dice/releases) [![GitHub Release Date](https://img.shields.io/github/release-date-pre/Dice-Developer-Team/dice.svg)](https://github.com/Dice-Developer-Team/Dice/releases) ## 寮鍙戣 @@ -25,17 +24,13 @@ Latest Release: [![GitHub release](https://img.shields.io/github/release-pre/w41 ## 缂栬瘧椤荤煡 -浠嶨itHub鍏嬮殕婧愮爜鏃惰涓嶈鐩存帴浠巑aster鍒嗘敮鍏嬮殕, 鍥犱负鎵鏈夌殑鏇存敼閮戒細鎻愪氦鍒版鍒嗘敮, 寰堟湁鍙兘鍖呭惈鏈鏂扮殑娴嬭瘯鎬ф洿鏀, 鏈粡杩囨祴璇曟棤娉曚繚璇佺ǔ瀹 璇烽夋嫨Tag涓渶鏂扮殑Release杩涜涓嬭浇 +1. 瀹夎CMake(v3.15+), git鍜屼竴涓悎閫傜殑鏀寔C++17鐨勭紪璇戝櫒 +2. clone姝epo锛岃娉ㄦ剰姝epo鍚湁submodule锛宍``git clone https://github.com/Dice-Developer-Team/Dice --recursive``` +3. 濡傛灉浣犲湪Cross-compile锛岃鍏堟墽琛宍``./vcpkg/bootstrap-vcpkg.sh```锛屼互闃叉Cross-compiler鍐茬獊銆傚鏋滀綘娌″湪Cross-compile锛屼綘鍙互鐩存帴杩愯涓嬩竴姝ワ紝vcpkg浼氳嚜鍔ㄨ瀹夎銆 +4. 鎵ц```cmake .```锛屽鏋滀綘鍦ㄤ氦鍙夌紪璇戯紝浣犲彲鑳介渶瑕佽缃甡``CC```, ```CXX```, ```VCPKG_TARGET_TRIPLET```绛夌幆澧冨彉閲忓埌瀵瑰簲鐨勫弬鏁版墠鑳芥甯哥紪璇戙 +5. 鎵ц```cmake --build .```瀹屾垚缂栬瘧 -璇蜂娇鐢ㄦ渶鏂扮増Visual Studio 2019鎴栦互涓婄増鏈 (鎴栧叾鐙珛缂栬瘧鍣)杩涜缂栬瘧, 椤圭洰涓绘枃浠朵负Dice.sln, 缂栬瘧鏃跺姟蹇呬娇鐢╓in32妯″紡鍚﹀垯鏃犳硶缂栬瘧鎴愬姛 - -鏂板: 鐜板湪鍙互鐢℅CC/Clang缂栬瘧, 鍙祴璇曚簡鍑犱釜鐗堟湰, 缂栬瘧鍑虹幇闂璇峰弽棣, 涓嬮潰鍒楀嚭缂栬瘧閫夐」, 姝e湪鍐檆make - -- GCC(9.0.0+): ` g++ -shared -static -std=c++17 -O2 -o com.w4123.dice.dll -Wl,--kill-at -I CQSDK\ -I Dice\ CQSDKCPP\*.cpp Dice\*.cpp Dice\CQP.lib -pthread -lWinInet -luser32 ` -- Clang(4+)+MSVC(VS2019+): ` clang-cl --target=i686-pc-windows-msvc /MT /O2 /EHsc /std:c++17 /D "UNICODE" /LD /link "user32.lib" /o com.w4123.dice.dll /I CQSDK\ /I Dice\ CQSDKCPP\*.cpp Dice\*.cpp Dice\CQP.lib -Wno-invalid-source-encoding ` -- Clang(4+)+GCC(9.0.0+): ` clang++ --target=i686-pc-windows-gnu -m32 -shared -static -o com.w4123.dice.dll -Xclang -flto-visibility-public-std -Wl,--kill-at -std=c++17 -O2 -I CQSDK\ -I Dice\ CQSDKCPP\*.cpp Dice\*.cpp Dice\CQP.lib -lWinInet -luser32 -pthread -Wno-invalid-source-encoding ` - -缂栬瘧鍚庝細寰楀埌com.w4123.dice.dll鏂囦欢, 璇峰嬁鏇存敼姝ゆ枃浠剁殑鍚嶇О! 璇蜂粠Releases涓笅杞藉搴旂殑json鏂囦欢(鎴栬嚜宸辩紪鍐), 鏀捐嚦閰稱 app鏂囦欢澶逛笅, 骞跺紑鍚紑鍙戞ā寮, 鍦ㄥ簲鐢ㄧ鐞嗕腑鍚堟垚cpk鏂囦欢鍗冲彲姝e父浣跨敤 +鍦ㄥぇ澶氭暟鎯呭喌涓嬶紝浣犱笉闇瑕佽嚜琛岀紪璇戙備綘鍙互鍦℅ithub Releases 浠ュ強 Github Actions涓壘鍒扮紪璇戝ソ鐨勪簩杩涘埗鐗堟湰銆 ## Issue鎻愪氦 @@ -45,7 +40,7 @@ Latest Release: [![GitHub release](https://img.shields.io/github/release-pre/w41 Dice! QQ Dice Robot for TRPG -Copyright (C) 2018-2020 w4123婧磩 Shiki +Copyright (C) 2018-2021 w4123婧磩 Shiki This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, diff --git a/appveyor.yml b/appveyor.yml index 9988a5b3..6404a3cc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,8 +2,12 @@ version: 2.{build} skip_tags: true image: - Visual Studio 2019 - -configuration: Release +before_build: +- cmd: nuget restore +configuration: +- Debug +- Release platform: x86 build: + parallel: true verbosity: minimal diff --git a/libiconv b/libiconv new file mode 160000 index 00000000..dd88e27c --- /dev/null +++ b/libiconv @@ -0,0 +1 @@ +Subproject commit dd88e27c772f3d608303e57f4dc41837e3534e5c diff --git a/triplets/arm-android-static.cmake b/triplets/arm-android-static.cmake new file mode 100644 index 00000000..ed219a7d --- /dev/null +++ b/triplets/arm-android-static.cmake @@ -0,0 +1,6 @@ + +set(VCPKG_TARGET_ARCHITECTURE arm) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_CMAKE_SYSTEM_NAME Android) + diff --git a/triplets/arm-android.cmake b/triplets/arm-android.cmake new file mode 100644 index 00000000..07115d9e --- /dev/null +++ b/triplets/arm-android.cmake @@ -0,0 +1,6 @@ + +set(VCPKG_TARGET_ARCHITECTURE arm) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) +set(VCPKG_CMAKE_SYSTEM_NAME Android) + diff --git a/triplets/arm64-android-static.cmake b/triplets/arm64-android-static.cmake new file mode 100644 index 00000000..c2bfff58 --- /dev/null +++ b/triplets/arm64-android-static.cmake @@ -0,0 +1,6 @@ + +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_CMAKE_SYSTEM_NAME Android) + diff --git a/triplets/arm64-android.cmake b/triplets/arm64-android.cmake new file mode 100644 index 00000000..eaee73ff --- /dev/null +++ b/triplets/arm64-android.cmake @@ -0,0 +1,6 @@ + +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) +set(VCPKG_CMAKE_SYSTEM_NAME Android) + diff --git a/triplets/x64-android-static.cmake b/triplets/x64-android-static.cmake new file mode 100644 index 00000000..2b7b693d --- /dev/null +++ b/triplets/x64-android-static.cmake @@ -0,0 +1,6 @@ + +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_CMAKE_SYSTEM_NAME Android) + diff --git a/triplets/x64-android.cmake b/triplets/x64-android.cmake new file mode 100644 index 00000000..d5b2dcc6 --- /dev/null +++ b/triplets/x64-android.cmake @@ -0,0 +1,6 @@ + +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) +set(VCPKG_CMAKE_SYSTEM_NAME Android) + diff --git a/triplets/x86-android-static.cmake b/triplets/x86-android-static.cmake new file mode 100644 index 00000000..efb1ecee --- /dev/null +++ b/triplets/x86-android-static.cmake @@ -0,0 +1,6 @@ + +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_CMAKE_SYSTEM_NAME Android) + diff --git a/triplets/x86-android.cmake b/triplets/x86-android.cmake new file mode 100644 index 00000000..6ecc4f31 --- /dev/null +++ b/triplets/x86-android.cmake @@ -0,0 +1,6 @@ + +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) +set(VCPKG_CMAKE_SYSTEM_NAME Android) + diff --git a/triplets/x86-linux.cmake b/triplets/x86-linux.cmake new file mode 100644 index 00000000..527417a6 --- /dev/null +++ b/triplets/x86-linux.cmake @@ -0,0 +1,10 @@ + +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CMAKE_SYSTEM_NAME Linux) + +if(NOT CMAKE_HOST_SYSTEM_PROCESSOR) + execute_process(COMMAND "uname" "-m" OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_PROCESSOR OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() diff --git a/vcpkg b/vcpkg new file mode 160000 index 00000000..371e41d8 --- /dev/null +++ b/vcpkg @@ -0,0 +1 @@ +Subproject commit 371e41d82165f5e74aa4ffc83c0e54a7e5d10209 diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..a34edf25 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,26 @@ +{ + "name": "dice", + "version-string": "2.5.0", + "dependencies": [ + "openssl", + { + "name": "aws-sdk-cpp", + "default-features": false, + "features": [ "s3" ] + }, + { + "name": "openssl", + "platform": "!windows" + }, + { + "name": "curl", + "default-features": false, + "features": [ "openssl" ], + "platform" : "!windows" + }, + { + "name": "libiconv", + "platform" : "!windows & !android" + } + ] +}