Repository: microsoft/napajs Branch: master Commit: b38a2385c076 Files: 458 Total size: 2.3 MB Directory structure: gitextract_sogveh4o/ ├── .gitignore ├── .npmignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── appveyor.yml ├── benchmark/ │ ├── README.md │ ├── bench-utils.ts │ ├── bench.ts │ ├── execute-overhead.ts │ ├── execute-scalability.ts │ ├── node-napa-perf-comparison.ts │ ├── store-overhead.ts │ ├── transport-overhead.ts │ └── tsconfig.json ├── build.bat ├── build.js ├── docs/ │ ├── api/ │ │ ├── index.md │ │ ├── log.md │ │ ├── memory.md │ │ ├── metric.md │ │ ├── module.md │ │ ├── napa-globals.md │ │ ├── node-api.md │ │ ├── store.md │ │ ├── sync.md │ │ ├── transport.md │ │ └── zone.md │ └── design/ │ └── transport-js-builtins.md ├── examples/ │ ├── modules/ │ │ ├── README.md │ │ ├── async-number/ │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── README.md │ │ │ ├── binding.gyp │ │ │ ├── lib/ │ │ │ │ ├── async-number.ts │ │ │ │ └── tsconfig.json │ │ │ ├── napa/ │ │ │ │ └── addon.cpp │ │ │ ├── package.json │ │ │ └── test/ │ │ │ ├── test.ts │ │ │ └── tsconfig.json │ │ ├── hello-world/ │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── README.md │ │ │ ├── binding.gyp │ │ │ ├── lib/ │ │ │ │ ├── hello-world.ts │ │ │ │ └── tsconfig.json │ │ │ ├── napa/ │ │ │ │ └── addon.cpp │ │ │ ├── package.json │ │ │ └── test/ │ │ │ ├── test.ts │ │ │ └── tsconfig.json │ │ └── plus-number/ │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── inc/ │ │ │ └── plus-number.h │ │ ├── lib/ │ │ │ ├── plus-number.ts │ │ │ └── tsconfig.json │ │ ├── napa/ │ │ │ ├── CMakeLists.txt │ │ │ ├── addon.cpp │ │ │ ├── plus-number-wrap.cpp │ │ │ └── plus-number-wrap.h │ │ ├── package.json │ │ ├── src/ │ │ │ ├── CMakeLists.txt │ │ │ └── plus-number.cpp │ │ ├── test/ │ │ │ ├── test.ts │ │ │ └── tsconfig.json │ │ └── unittest/ │ │ ├── CMakeLists.txt │ │ ├── catch/ │ │ │ ├── LICENSE.txt │ │ │ └── catch.hpp │ │ ├── main.cpp │ │ └── run.js │ └── tutorial/ │ ├── estimate-pi-in-parallel/ │ │ ├── README.md │ │ ├── estimate-pi-in-parallel.js │ │ └── package.json │ ├── max-square-sub-matrix/ │ │ ├── README.md │ │ ├── max-square-sub-matrix.js │ │ └── package.json │ ├── napa-runner/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── estimate-PI.js │ │ ├── main.cpp │ │ └── package.json │ ├── parallel-quick-sort/ │ │ ├── README.md │ │ ├── package.json │ │ └── parallel-quick-sort.js │ ├── recursive-fibonacci/ │ │ ├── README.md │ │ ├── package.json │ │ └── recursive-fibonacci.js │ └── synchronized-loading/ │ ├── README.md │ ├── package.json │ ├── phone-book-data.json │ ├── phone-book.js │ └── synchronized-loading.js ├── inc/ │ ├── napa/ │ │ ├── assert.h │ │ ├── async.h │ │ ├── capi.h │ │ ├── exports.h │ │ ├── log.h │ │ ├── memory/ │ │ │ ├── allocator-debugger.h │ │ │ ├── allocator.h │ │ │ └── common.h │ │ ├── memory.h │ │ ├── module/ │ │ │ ├── binding/ │ │ │ │ ├── basic-wraps.h │ │ │ │ └── wraps.h │ │ │ ├── binding.h │ │ │ ├── common.h │ │ │ ├── module-internal.h │ │ │ ├── module-node-compat.h │ │ │ ├── object-wrap.h │ │ │ ├── shareable-wrap.h │ │ │ └── transport-context-wrap.h │ │ ├── module.h │ │ ├── providers/ │ │ │ ├── logging.h │ │ │ └── metric.h │ │ ├── result-codes.inc │ │ ├── stl/ │ │ │ ├── allocator.h │ │ │ ├── deque.h │ │ │ ├── list.h │ │ │ ├── map.h │ │ │ ├── queue.h │ │ │ ├── set.h │ │ │ ├── stack.h │ │ │ ├── string.h │ │ │ ├── unordered_map.h │ │ │ ├── unordered_set.h │ │ │ └── vector.h │ │ ├── transport/ │ │ │ ├── transport-context.h │ │ │ ├── transport.h │ │ │ └── transportable.h │ │ ├── transport.h │ │ ├── types.h │ │ ├── utils.h │ │ ├── v8-helpers/ │ │ │ ├── array.h │ │ │ ├── console.h │ │ │ ├── conversion.h │ │ │ ├── flow.h │ │ │ ├── function.h │ │ │ ├── json.h │ │ │ ├── maybe.h │ │ │ ├── object.h │ │ │ ├── ptr.h │ │ │ ├── string.h │ │ │ └── time.h │ │ ├── v8-helpers.h │ │ ├── version.h │ │ ├── zone/ │ │ │ ├── napa-async-runner.h │ │ │ └── node-async-runner.h │ │ └── zone.h │ └── napa.h ├── lib/ │ ├── binding.js │ ├── core/ │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── README.md │ │ ├── core-modules.json │ │ ├── timers/ │ │ │ ├── timer-api.ts │ │ │ └── timer.ts │ │ └── timers.ts │ ├── index.ts │ ├── log.ts │ ├── memory/ │ │ ├── allocator.ts │ │ ├── handle.ts │ │ └── shareable.ts │ ├── memory.ts │ ├── metric.ts │ ├── runtime/ │ │ └── platform.ts │ ├── runtime.ts │ ├── store/ │ │ ├── store-api.ts │ │ └── store.ts │ ├── store.ts │ ├── sync/ │ │ └── lock.ts │ ├── sync.ts │ ├── transport/ │ │ ├── builtin-object-transporter.ts │ │ ├── function-transporter.ts │ │ ├── transport.ts │ │ └── transportable.ts │ ├── transport.ts │ ├── tsconfig.json │ ├── v8/ │ │ └── stack-trace.ts │ ├── v8.ts │ ├── zone/ │ │ ├── function-call.ts │ │ ├── zone-impl.ts │ │ └── zone.ts │ └── zone.ts ├── node/ │ ├── CMakeLists.txt │ ├── addon.cpp │ ├── node-zone-delegates.cpp │ └── node-zone-delegates.h ├── package.json ├── scripts/ │ ├── clean.js │ ├── embedded.js │ ├── install.js │ └── paths.js ├── src/ │ ├── CMakeLists.txt │ ├── api/ │ │ └── capi.cpp │ ├── memory/ │ │ └── built-in-allocators.cpp │ ├── module/ │ │ ├── core-modules/ │ │ │ ├── core-modules.h │ │ │ ├── napa/ │ │ │ │ ├── allocator-debugger-wrap.cpp │ │ │ │ ├── allocator-debugger-wrap.h │ │ │ │ ├── allocator-wrap.cpp │ │ │ │ ├── allocator-wrap.h │ │ │ │ ├── call-context-wrap.cpp │ │ │ │ ├── call-context-wrap.h │ │ │ │ ├── lock-wrap.cpp │ │ │ │ ├── lock-wrap.h │ │ │ │ ├── metric-wrap.cpp │ │ │ │ ├── metric-wrap.h │ │ │ │ ├── napa-binding.cpp │ │ │ │ ├── napa-binding.h │ │ │ │ ├── shared-ptr-wrap.cpp │ │ │ │ ├── shared-ptr-wrap.h │ │ │ │ ├── store-wrap.cpp │ │ │ │ ├── store-wrap.h │ │ │ │ ├── timer-wrap.cpp │ │ │ │ ├── timer-wrap.h │ │ │ │ ├── transport-context-wrap-impl.cpp │ │ │ │ ├── transport-context-wrap-impl.h │ │ │ │ ├── zone-wrap.cpp │ │ │ │ └── zone-wrap.h │ │ │ └── node/ │ │ │ ├── console.cpp │ │ │ ├── console.h │ │ │ ├── file-system-helpers.cpp │ │ │ ├── file-system-helpers.h │ │ │ ├── file-system.cpp │ │ │ ├── file-system.h │ │ │ ├── os.cpp │ │ │ ├── os.h │ │ │ ├── path.cpp │ │ │ ├── path.h │ │ │ ├── process.cpp │ │ │ ├── process.h │ │ │ ├── tty-wrap.cpp │ │ │ └── tty-wrap.h │ │ ├── loader/ │ │ │ ├── binary-module-loader.cpp │ │ │ ├── binary-module-loader.h │ │ │ ├── core-module-loader.cpp │ │ │ ├── core-module-loader.h │ │ │ ├── javascript-module-loader.cpp │ │ │ ├── javascript-module-loader.h │ │ │ ├── json-module-loader.cpp │ │ │ ├── json-module-loader.h │ │ │ ├── module-cache.cpp │ │ │ ├── module-cache.h │ │ │ ├── module-file-loader.h │ │ │ ├── module-loader-helpers.cpp │ │ │ ├── module-loader-helpers.h │ │ │ ├── module-loader.cpp │ │ │ ├── module-loader.h │ │ │ ├── module-resolver-cache.cpp │ │ │ ├── module-resolver-cache.h │ │ │ ├── module-resolver.cpp │ │ │ └── module-resolver.h │ │ └── module.cpp │ ├── platform/ │ │ ├── dll.cpp │ │ ├── dll.h │ │ ├── filesystem.cpp │ │ ├── filesystem.h │ │ ├── os.cpp │ │ ├── os.h │ │ ├── platform.h │ │ ├── process.cpp │ │ ├── process.h │ │ └── thread-local.h │ ├── providers/ │ │ ├── console-logging-provider.h │ │ ├── nop-logging-provider.h │ │ ├── nop-metric-provider.h │ │ ├── providers.cpp │ │ └── providers.h │ ├── settings/ │ │ ├── settings-parser.cpp │ │ ├── settings-parser.h │ │ └── settings.h │ ├── store/ │ │ ├── store.cpp │ │ └── store.h │ ├── utils/ │ │ └── string.h │ ├── v8-extensions/ │ │ ├── CMakeLists.txt │ │ ├── array-buffer-allocator.cpp │ │ ├── array-buffer-allocator.h │ │ ├── deserializer.cpp │ │ ├── deserializer.h │ │ ├── externalized-contents.cpp │ │ ├── externalized-contents.h │ │ ├── serialized-data.cpp │ │ ├── serialized-data.h │ │ ├── serializer.cpp │ │ ├── serializer.h │ │ ├── v8-common.cpp │ │ ├── v8-common.h │ │ ├── v8-extensions-macros.h │ │ ├── v8-extensions.cpp │ │ └── v8-extensions.h │ └── zone/ │ ├── async-complete-task.cpp │ ├── async-complete-task.h │ ├── async-context.h │ ├── async-runner.cpp │ ├── call-context.cpp │ ├── call-context.h │ ├── call-task.cpp │ ├── call-task.h │ ├── eval-task.cpp │ ├── eval-task.h │ ├── napa-zone.cpp │ ├── napa-zone.h │ ├── node-zone.cpp │ ├── node-zone.h │ ├── schedule-phase.h │ ├── scheduler.cpp │ ├── scheduler.h │ ├── simple-thread-pool.cpp │ ├── simple-thread-pool.h │ ├── task-decorators.h │ ├── task.h │ ├── terminable-task.cpp │ ├── terminable-task.h │ ├── timer.cpp │ ├── timer.h │ ├── worker-context.cpp │ ├── worker-context.h │ ├── worker.cpp │ ├── worker.h │ └── zone.h ├── test/ │ ├── memory-test.ts │ ├── module/ │ │ ├── addon/ │ │ │ ├── CMakeLists.txt │ │ │ ├── addon.cpp │ │ │ ├── simple-object-wrap.cpp │ │ │ └── simple-object-wrap.h │ │ ├── cycle-a.js │ │ ├── cycle-b.js │ │ ├── jsmodule.js │ │ ├── node_modules/ │ │ │ └── file.js │ │ ├── resolution-tests.js │ │ ├── sub-folder/ │ │ │ ├── addon/ │ │ │ │ └── mock-addon.napa │ │ │ └── file.js │ │ └── test.json │ ├── module-test.ts │ ├── napa-zone/ │ │ ├── function-as-module.ts │ │ ├── test-main.ts │ │ └── test.ts │ ├── store-test.ts │ ├── sync-test.ts │ ├── timer-test.ts │ ├── transport-test.ts │ ├── tsconfig.json │ └── zone-test.ts ├── third-party/ │ ├── args/ │ │ ├── LICENSE │ │ └── args.hxx │ ├── catch/ │ │ ├── LICENSE.txt │ │ └── catch.hpp │ └── rapidjson/ │ ├── LICENSE.txt │ ├── allocators.h │ ├── document.h │ ├── encodedstream.h │ ├── encodings.h │ ├── error/ │ │ ├── en.h │ │ └── error.h │ ├── filereadstream.h │ ├── filewritestream.h │ ├── fwd.h │ ├── internal/ │ │ ├── biginteger.h │ │ ├── diyfp.h │ │ ├── dtoa.h │ │ ├── ieee754.h │ │ ├── itoa.h │ │ ├── meta.h │ │ ├── pow10.h │ │ ├── regex.h │ │ ├── stack.h │ │ ├── strfunc.h │ │ ├── strtod.h │ │ └── swap.h │ ├── istreamwrapper.h │ ├── memorybuffer.h │ ├── memorystream.h │ ├── msinttypes/ │ │ ├── inttypes.h │ │ └── stdint.h │ ├── ostreamwrapper.h │ ├── pointer.h │ ├── prettywriter.h │ ├── rapidjson.h │ ├── reader.h │ ├── schema.h │ ├── stream.h │ ├── stringbuffer.h │ └── writer.h └── unittest/ ├── CMakeLists.txt ├── main.cpp ├── module/ │ ├── file-system-helpers-tests.cpp │ ├── module-resolver-cache-tests.cpp │ ├── module-resolver-tests.cpp │ └── test-files/ │ ├── node_modules/ │ │ ├── resolve-nm-file │ │ ├── resolve-nm-file-js.js │ │ ├── resolve-nm-file-js.json │ │ ├── resolve-nm-file-js.napa │ │ ├── resolve-nm-file-json.json │ │ ├── resolve-nm-file-json.napa │ │ ├── resolve-nm-file-napa.napa │ │ ├── resolve-nm-file.js │ │ ├── resolve-nm-file.json │ │ ├── resolve-nm-file.napa │ │ ├── resolver-nm/ │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ ├── index.napa │ │ │ ├── package.json │ │ │ └── resolve-file │ │ ├── resolver-nm-js/ │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ └── index.napa │ │ ├── resolver-nm-json/ │ │ │ ├── index.json │ │ │ └── index.napa │ │ └── resolver-nm-napa/ │ │ └── index.napa │ ├── resolve-directory/ │ │ ├── resolver/ │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ ├── index.napa │ │ │ ├── package.json │ │ │ └── resolve-file │ │ ├── resolver-js/ │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ └── index.napa │ │ ├── resolver-json/ │ │ │ ├── index.json │ │ │ └── index.napa │ │ └── resolver-napa/ │ │ └── index.napa │ ├── resolve-env/ │ │ ├── resolve-env-file │ │ ├── resolve-env-file-js.js │ │ ├── resolve-env-file-js.json │ │ ├── resolve-env-file-js.napa │ │ ├── resolve-env-file-json.json │ │ ├── resolve-env-file-json.napa │ │ ├── resolve-env-file-napa.napa │ │ ├── resolve-env-file.js │ │ ├── resolve-env-file.json │ │ ├── resolve-env-file.napa │ │ ├── resolver-env/ │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ ├── index.napa │ │ │ ├── package.json │ │ │ └── resolve-file │ │ ├── resolver-env-js/ │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ └── index.napa │ │ ├── resolver-env-json/ │ │ │ ├── index.json │ │ │ └── index.napa │ │ └── resolver-env-napa/ │ │ └── index.napa │ ├── resolve-file │ ├── resolve-file-js.js │ ├── resolve-file-js.json │ ├── resolve-file-js.napa │ ├── resolve-file-json.json │ ├── resolve-file-json.napa │ ├── resolve-file-napa.napa │ ├── resolve-file-no.js │ ├── resolve-file.js │ ├── resolve-file.json │ └── resolve-file.napa ├── platform/ │ └── filesystem-tests.cpp ├── run.js ├── settings/ │ └── parser-tests.cpp ├── utils/ │ └── string-tests.cpp └── zone/ ├── scheduler-tests.cpp └── timer-tests.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ bin build node_modules types # ignore all js files under lib except explicit ones specified here lib/**/*.js !lib/core/*.js !lib/zone/zone-main.js !lib/binding.js # ignore all js files under test test/**/*.js test/module/test-dir test/module/test-file !test/module/node_modules !test/module/**/*.js !unittest/module/test-files/node_modules # ignore all js files under benchmark benchmark/**/*.js package-lock.json !tsconfig.json # ignore all VSCode files .vscode # ignore npm related files npm-debug.log ================================================ FILE: .npmignore ================================================ bin build examples # Exclude TypeScript source files. benchmark/**/*.ts benchmark/tsconfig.json lib/**/*.ts lib/tsconfig.json test/**/*.ts test/module/test-dir test/module/test-file test/tsconfig.json .vscode .npmrc .npmignore .taskkey .travis.yml appveyor.yml npm-debug.log ================================================ FILE: .travis.yml ================================================ language: node_js compiler: default matrix: exclude: # Disable the default build and use customized matrix only. - compiler: default include: # Node 6.x Linux (Precise) G++5.4.1 - os: linux dist: precise node_js: '6' compiler: g++-5 addons: apt: # The apt source 'ubuntu-toolchain-r-test' is for GCC 5+ # The apt source 'george-edison55-precise-backports' is for CMake 3.2+ sources: - ubuntu-toolchain-r-test - george-edison55-precise-backports packages: - g++-5 - cmake-data - cmake env: - COMPILER_OVERRIDE="CXX=g++-5 CC=gcc-5" # Node 6.x OS X (Yosemite) AppleClang 6.1 - os: osx node_js: '6' osx_image: xcode6.4 # Node LTS (8.x) Linux (Trusty) G++6.4.0 - os: linux dist: trusty node_js: '8' compiler: g++-6 addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-6 env: - COMPILER_OVERRIDE="CXX=g++-6 CC=gcc-6" # Node LTS (8.x) OS X (El Capitan) AppleClang 7.3 - os: osx node_js: '8' osx_image: xcode7.3 # Node (9.x) Linux (Trusty) G++6.4.0 - os: linux dist: trusty node_js: '9' compiler: g++-6 addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-6 env: - COMPILER_OVERRIDE="CXX=g++-6 CC=gcc-6" # Node (9.x) macOS (Sierra) AppleClang 8.1 - os: osx node_js: '9' osx_image: xcode8.3 # Node Current (10.x) Linux (Trusty) G++7.3.0 - os: linux dist: trusty node_js: '10' compiler: g++-7 addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-7 env: - COMPILER_OVERRIDE="CXX=g++-7 CC=gcc-7" # Node Current (10.x) macOS (High Sierra) AppleClang 9.1 - os: osx node_js: '10' osx_image: xcode9.3 before_install: - | if [ $TRAVIS_OS_NAME == linux ]; then export ${COMPILER_OVERRIDE} fi install: - npm install cmake-js -g - npm install --no-fetch script: - npm test - npm run unittest ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2 FATAL_ERROR) project("napa") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) # Require Cxx14 features set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) if (NOT WIN32 AND NOT APPLE) # Set symbol visibility to hidden by default. # Napa shared library shares a few classes with napa-binding.node with different compile definition, # exposing the same symbols from both shared libraries may cause improper behaviors under gcc. set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN) # Prevent symbol relocations internal to our wrapper library to be overwritten by the application. set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-Bsymbolic -Wl,-Bsymbolic-functions") set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-Bsymbolic -Wl,-Bsymbolic-functions") # Mark object non-lazy runtime binding. set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,now") set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,now") endif () # Build napa shared library. add_subdirectory(src) if (CMAKE_JS_VERSION) # Build napa addon for node. add_subdirectory(node) add_subdirectory(test/module/addon) endif() ================================================ FILE: LICENSE.txt ================================================ Napa.JS Copyright (c) Microsoft Corporation. All rights reserved. MIT License 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. ================================================ FILE: README.md ================================================ [![Build Status for Linux/MacOS](https://travis-ci.org/Microsoft/napajs.svg?branch=master)](https://travis-ci.org/Microsoft/napajs) [![Build Status for Windows](https://ci.appveyor.com/api/projects/status/github/Microsoft/napajs?branch=master&svg=true)](https://ci.appveyor.com/project/napajs/napajs) [![npm version](https://badge.fury.io/js/napajs.svg)](https://www.npmjs.com/package/napajs) [![Downloads](https://img.shields.io/npm/dm/napajs.svg)](https://www.npmjs.com/package/napajs) # Napa.js Napa.js is a multi-threaded JavaScript runtime built on [V8](https://github.com/v8/v8), which was originally designed to develop highly iterative services with non-compromised performance in Bing. As it evolves, we find it useful to complement [Node.js](https://nodejs.org) in CPU-bound tasks, with the capability of executing JavaScript in multiple V8 isolates and communicating between them. Napa.js is exposed as a Node.js module, while it can also be embedded in a host process without Node.js dependency. ## Installation Install the latest stable version: ``` npm install napajs ``` Other options can be found in [Build Napa.js](https://github.com/Microsoft/napajs/wiki/build-napa.js). ## Quick Start ```js const napa = require('napajs'); const zone1 = napa.zone.create('zone1', { workers: 4 }); // Broadcast code to all 4 workers in 'zone1'. zone1.broadcast('console.log("hello world");'); // Execute an anonymous function in any worker thread in 'zone1'. zone1.execute( (text) => text, ['hello napa']) .then((result) => { console.log(result.value); }); ``` More examples: * [Estimate PI in parallel](./examples/tutorial/estimate-pi-in-parallel) * [Max sub-matrix of 1s with layered parallelism](./examples/tutorial/max-square-sub-matrix) * [Parallel Quick Sort](./examples/tutorial/parallel-quick-sort) * [Recursive Fibonacci with multiple JavaScript threads](./examples/tutorial/recursive-fibonacci) * [Synchronized loading](./examples/tutorial/synchronized-loading) ## Features - Multi-threaded JavaScript runtime. - Node.js compatible module architecture with NPM support. - API for object transportation, object sharing and synchronization across JavaScript threads. - API for pluggable logging, metric and memory allocator. - Distributed as a Node.js module, as well as supporting embed scenarios. ## Documentation - [Napa.js Home](https://github.com/Microsoft/napajs/wiki) - [API Reference](./docs/api/index.md) - [FAQ](https://github.com/Microsoft/napajs/wiki/FAQ) # Contribute You can contribute to Napa.js in following ways: * [Report issues](https://github.com/Microsoft/napajs/issues) and help us verify fixes as they are checked in. * Review the [source code changes](https://github.com/Microsoft/napajs/pulls). * Contribute to core module compatibility with Node. * Contribute bug fixes. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact opencode@microsoft.com with any additional questions or comments. # License Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the [MIT](https://github.com/Microsoft/napajs/blob/master/LICENSE.txt) License. ================================================ FILE: appveyor.yml ================================================ environment: matrix: # Windows Server 2012 R2 Visual C++ Build Tools 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 nodejs_version: 6 # Windows Server 2012 R2 Visual C++ Build Tools 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 nodejs_version: 8 # Windows Server 2016 Visual C++ Build Tools 2017 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 nodejs_version: 9 # Windows Server 2016 Visual C++ Build Tools 2017 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 nodejs_version: 10 platform: - x64 install: - ps: Install-Product node $env:nodejs_version $env:platform - npm install cmake-js -g - npm install --no-fetch # Skip MSBuild stage build: off test_script: - npm test - npm run unittest ================================================ FILE: benchmark/README.md ================================================ # Benchmark ## Summary - JavaScript execution in napajs is on par with node, using the same version of V8, which is expected. - `zone.execute` scales linearly on number of workers, which is expected. - The overhead of calling `zone.execute` from node is around 0.1ms after warm-up. The cost of using anonymous function is neglectable. - `transport.marshall` cost on small plain JavaScript values is about 3x of JSON.stringify. - The overhead of `store.set` and `store.get` is around 0.06ms plus transport overhead on the objects. We got this report on environment below: | Name | Value | |-------------------|---------------------------------------------------------------------------------------| |**Processor** |Intel(R) Xeon(R) CPU L5640 @ 2.27GHz, 8 virtual processors | |**System Type** |x64-based PC | |**Physical Memory**|16.0 GB | |**OS version** |Microsoft Windows Server 2012 R2 | ## Napa vs. Node on JavaScript execution Please refer to [node-napa-perf-comparison.ts](node-napa-perf-comparison.ts). | node time | napa time | | --------- | --------- | | 3026.76 | 3025.81 | ## Linear scalability `zone.execute` scales linearly on number of workers. We performed 1M CRC32 calls on a 1024-length string on each worker, here are the numbers. We still need to understand why the time of more workers running parallel would beat less workers. | | node | napa - 1 worker | napa - 2 workers | napa - 4 workers | napa - 8 workers | | ------- | ----------- | --------------- | ---------------- | ---------------- | ---------------- | | time | 8,649521600 | 6146.98 | 4912.57 | 4563.48 | 6168.41 | | cpu% | ~15% | ~15% | ~27% | ~55% | ~99% | Please refer to [execute-scalability.ts](./execute-scalability.ts) for test details. ## Execute overhead The overhead of `zone.execute` includes 1. Marshalling cost of arguments in caller thread. 2. Queuing time before a worker can execute. 3. Unmarshalling cost of arguments in target worker. 4. Marshalling cost of return value from target worker. 5. Queuing time before caller callback is notified. 6. Unmarshalling cost of return value in caller thread. In this section we will examine #2 and #5. So we use empty function with no arguments and no return value. Transport overhead (#1, #3, #4, #6) varies by size and complexity of payload, will be benchmarked separately in [Transport Overhead](#transport-overhead) section. Please refer to [execute-overhead.ts](./execute-overhead.ts) for test details. ### Overhead after warm-up Average overhead is around 0.06ms to 0.12ms for `zone.execute`. | repeat | zone.execute (ms) | |----------|-------------------| | 200 | 24.932 | | 5000 | 456.893 | | 10000 | 810.687 | | 50000 | 3387.361 | *10000 times of zone.execute on anonymous function is 807.241ms. The gap is within range of bench noise. ### Overhead during warm-up: | Sequence of call | Time (ms) | |------------------|-----------| | 1 | 6.040 | | 2 | 4.065 | | 3 | 5.250 | | 4 | 4.652 | | 5 | 1.572 | | 6 | 1.366 | | 7 | 1.403 | | 8 | 1.213 | | 9 | 0.450 | | 10 | 0.324 | | 11 | 0.193 | | 12 | 0.238 | | 13 | 0.191 | | 14 | 0.230 | | 15 | 0.203 | | 16 | 0.188 | | 17 | 0.188 | | 18 | 0.181 | | 19 | 0.185 | | 20 | 0.182 | ## Transport overhead The overhead of `transport.marshall` includes 1. overhead of needing replacer callback during JSON.stringify. (even an empty callback will slow down JSON.stringify significantly) 2. traverse every value during JSON.stringify, to check value type and get `cid` to put into payload. - a. If value doesn't need special care. - b. If value is a transportable object that needs special care. 2.b is related to individual transportable classes, which may vary per individual class. Thus we examine #1 and #2.a in this test. The overhead of `transport.unmarshall` includes 1. overhead of needing reviver callback during JSON.parse. 2. traverse every value during JSON.parse, to check if object has `_cid` property. - a. If value doesn't have property `_cid`. - b. Otherwise, find constructor and call the [`Transportable.marshall`](../docs/api/transport.md#transportable-marshall). We also evaluate only #1, #2.a in this test. Please refer to [transport-overhead.ts](./transport-overhead.ts) for test details. \*All operations are repeated for 1000 times. | payload type | size | JSON.stringify (ms) | transport.marshall (ms) | JSON.parse (ms) | transport.unmarshall (ms) | | ---------------------------------- | ----- | ------------------- | ----------------------- | --------------- | ------------------------- | | 1 level - 10 integers | 91 | 4.90 | 18.05 (3.68x) | 3.50 | 17.98 (5.14x) | | 1 level - 100 integers | 1081 | 65.45 | 92.78 (1.42x) | 20.45 | 122.25 (5.98x) | | 10 level - 2 integers | 18415 | 654.40 | 2453.37 (3.75x) | 995.02 | 2675.72 (2.69x) | | 2 level - 10 integers | 991 | 19.74 | 66.82 (3.39x) | 27.85 | 138.45 (4.97x) | | 3 level - 5 integers | 1396 | 33.66 | 146.33 (4.35x) | 51.54 | 189.07 (3.67x) | | 1 level - 10 strings - length 10 | 201 | 3.81 | 10.17 (2.67x) | 9.46 | 20.81 (2.20x) | | 1 level - 100 strings - length 10 | 2191 | 76.53 | 115.74 (1.51x) | 77.71 | 181.24 (2.33x) | | 2 level - 10 strings - length 10 | 2091 | 30.15 | 97.65 (3.24x) | 95.51 | 213.20 (2.23x) | | 3 level - 5 strings - length 10 | 2646 | 41.95 | 155.42 (3.71x) | 123.82 | 227.90 (1.84x) | | 1 level - 10 strings - length 100 | 1101 | 7.74 | 12.19 (1.57x) | 17.34 | 29.83 (1.72x) | | 1 level - 100 strings - length 100 | 11191 | 66.17 | 112.83 (1.71x) | 197.67 | 282.63 (1.43x) | | 2 level - 10 strings - length 100 | 11091 | 68.46 | 149.99 (2.19x) | 202.85 | 298.19 (1.47x) | | 3 level - 5 integers | 13896 | 89.46 | 208.21 (2.33x) | 265.25 | 418.42 (1.58x) | | 1 level - 10 booleans | 126 | 2.84 | 8.14 (2.87x) | 3.06 | 14.20 (4.65x) | | 1 level - 100 booleans | 1341 | 20.28 | 59.36 (2.93x) | 21.59 | 121.15 (5.61x) | | 2 level - 10 booleans | 1341 | 23.92 | 89.62 (3.75x) | 31.84 | 137.92 (4.33x) | | 3 level - 5 booleans | 1821 | 36.15 | 138.24 (3.82x) | 55.71 | 195.50 (3.51x) | ## Store access overhead The overhead of `store.set` includes 1. Overhead of calling `transport.marshall` on value. 2. Overhead of put marshalled data and transport context into C++ map (with exclusive_lock). The overhead of `store.get` includes 1. Overhead of getting marshalled data and transport context from C++ map (with shared_lock). 2. Overhead of calling `transport.unmarshall` on marshalled data. For `store.set`, numbers below indicates the cost beyond marshall is around 0.07~0.4ms varies per payload size. (10B to 18KB). `store.get` takes a bit more: 0.06~0.9ms with the same payload size variance. If the value in store is not updated frequently, it's always good to cache it in JavaScript world. Please refer to [store-overhead.ts](./store-overhead.ts) for test details. \*All operations are repeated for 1000 times. | payload type | size | transport.marshall (ms) | store.save (ms) | transport.unmarshall (ms) | store.get (ms) | | ---------------------------------- | ----- | ----------------------- | --------------- | ------------------------- | -------------- | | 1 level - 1 integers | 10 | 2.54 | 73.85 | 3.98 | 65.57 | | 1 level - 10 integers | 91 | 8.27 | 98.55 | 17.23 | 90.89 | | 1 level - 100 integers | 1081 | 97.10 | 185.31 | 144.75 | 274.39 | | 10 level - 2 integers | 18415 | 2525.18 | 2973.17 | 3093.06 | 3927.80 | | 2 level - 10 integers | 991 | 71.22 | 174.01 | 154.76 | 276.04 | | 3 level - 5 integers | 1396 | 127.06 | 219.73 | 182.27 | 337.59 | | 1 level - 10 strings - length 10 | 201 | 14.43 | 79.68 | 31.28 | 84.71 | | 1 level - 100 strings - length 10 | 2191 | 104.40 | 212.44 | 173.32 | 239.09 | | 2 level - 10 strings - length 10 | 2091 | 79.54 | 188.72 | 189.29 | 252.83 | | 3 level - 5 strings - length 10 | 2646 | 155.14 | 257.78 | 276.22 | 342.95 | | 1 level - 10 strings - length 100 | 1101 | 15.22 | 89.84 | 30.87 | 88.18 | | 1 level - 100 strings - length 100 | 11191 | 119.89 | 284.05 | 287.17 | 403.77 | | 2 level - 10 strings - length 100 | 11091 | 137.10 | 299.32 | 244.13 | 297.12 | | 3 level - 5 integers | 13896 | 183.84 | 310.89 | 285.80 | 363.50 | | 1 level - 10 booleans | 126 | 5.74 | 49.89 | 22.69 | 97.27 | | 1 level - 100 booleans | 1341 | 57.41 | 157.80 | 106.30 | 218.05 | | 2 level - 10 booleans | 1341 | 76.93 | 150.25 | 104.02 | 185.82 | | 3 level - 5 booleans | 1821 | 102.47 | 171.44 | 150.42 | 207.27 | ================================================ FILE: benchmark/bench-utils.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. /// Utility function to generate object for testing. export function generateString(length: number): string { return Array(length).join('x'); } export function generateObject(keys: number, depth: number, valueType: string = "string", valueLength = 7) { let object: any = {}; for (let i = 0; i < keys; ++i) { let key = `key${i}`; let value: any = null; if (depth > 1) { value = generateObject(keys, depth - 1, valueType, valueLength); } else if (valueType === 'string') { // We try to make each string different. value = generateString(valueLength - 1) + (depth * keys + i); } else if (valueType === 'number') { value = i; } else if (valueType === 'boolean') { value = i % 2 == 0; } object[key] = value; } return object; } export function timeDiffInMs(diff: [number, number]): number { return (diff[0] * 1e9 + diff[1]) / 1e6; } export function formatTimeDiff(diff: number | [number, number], printUnit: boolean = false): string { if (Array.isArray(diff)) { diff = timeDiffInMs(diff); } let message = diff.toFixed(2); if (printUnit) { message += "ms" } return message; } export function formatRatio(dividend: number, divider: number): string { return "(" + (dividend / divider).toFixed(2) + "x)"; } ================================================ FILE: benchmark/bench.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from '../lib/index'; import * as nodeNapaPerfComp from './node-napa-perf-comparison'; import * as executeOverhead from './execute-overhead'; import * as executeScalability from './execute-scalability'; import * as transportOverhead from './transport-overhead'; import * as storeOverhead from './store-overhead'; export function bench(): Promise { // Non-zone related benchmarks. transportOverhead.bench(); storeOverhead.bench(); // Create zones for execute related benchmark. let singleWorkerZone = napa.zone.create('single-worker-zone', { workers: 1}); let multiWorkerZone = napa.zone.create('multi-worker-zone', { workers: 8 }); return nodeNapaPerfComp.bench(singleWorkerZone) .then(() => { return executeOverhead.bench(singleWorkerZone); }) .then(() => { return executeScalability.bench(multiWorkerZone);}); } bench(); ================================================ FILE: benchmark/execute-overhead.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from '../lib/index'; import * as mdTable from 'markdown-table'; import { formatTimeDiff } from './bench-utils'; function batchExecuteOnNamedFunction( zone: napa.zone.Zone, repeat: number, args: any[]): Promise { return new Promise((resolve, reject) => { let finished = 0; let start = process.hrtime(); for (let i = 0; i < repeat; ++i) { zone.execute("", "test", args).then(() => { ++finished; if (finished === repeat) { resolve(); } }); } }); } function batchExecuteOnAnonymousFunction( zone: napa.zone.Zone, repeat: number, args: any[]): Promise { return new Promise((resolve, reject) => { let finished = 0; let start = process.hrtime(); for (let i = 0; i < repeat; ++i) { zone.execute(() => {}, args).then(() => { ++finished; if (finished === repeat) { resolve(); } }); } }); } export async function bench(zone: napa.zone.Zone): Promise { console.log("Benchmarking execute overhead..."); // Prepare a empty function. await zone.broadcast("function test() {}"); const ARGS = [1, "hello", {a: 1, b: true}]; // Warm-up. const WARMUP_REPEAT: number = 20; console.log("## Execute overhead during warmup\n") let warmupTable = []; warmupTable.push(["call #", "time (ms)"]); for (let i = 0; i < WARMUP_REPEAT; ++i) { let start = process.hrtime(); await zone.execute("", "test", ARGS); warmupTable.push([i.toString(), formatTimeDiff(process.hrtime(start))]); } console.log(mdTable(warmupTable)); // execute after warm-up const REPEAT: number = 10000; console.log("## `zone.execute` overhead (use function name)\n"); let start = process.hrtime(); await batchExecuteOnNamedFunction(zone, REPEAT, ARGS); console.log(`Elapse of running empty function by name for ${REPEAT} times: ${formatTimeDiff(process.hrtime(start), true)}\n`); console.log("## `zone.execute` overhead (use anonymous function)\n"); start = process.hrtime(); await batchExecuteOnAnonymousFunction(zone, REPEAT, ARGS); console.log(`Elapse of running empty anonymous function for ${REPEAT} times: ${formatTimeDiff(process.hrtime(start), true)}\n`); return; } ================================================ FILE: benchmark/execute-scalability.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from '../lib/index'; import * as assert from 'assert'; import * as mdTable from 'markdown-table'; import { formatTimeDiff } from './bench-utils'; function makeCRCTable(){ var c; var crcTable = []; for(var n =0; n < 256; n++){ c = n; for(var k =0; k < 8; k++){ c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } crcTable[n] = c; } return crcTable; } let crcTable = makeCRCTable(); function crc32(str: string) { let crc = 0 ^ (-1); for (var i = 0; i < str.length; i++ ) { crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF]; } return (crc ^ (-1)) >>> 0; }; function testCrc() { const REPEAT: number = 1000000; let result = 0; let key = Array(1024).join('x'); for (let i = 0; i < REPEAT; ++i) { let hash = crc32(key); result = result ^ hash; } return result; } export async function bench(zone: napa.zone.Zone): Promise { console.log("Benchmarking execute scalability..."); // Prepare a empty function. await zone.broadcast(makeCRCTable.toString()); await zone.broadcast("var crcTable = makeCRCTable();"); await zone.broadcast(crc32.toString()); await zone.broadcast(testCrc.toString()); // Warm-up. let crcResult = testCrc(); await zone.broadcast("testCrc()"); // Execute in Node with 1 thread. let start = process.hrtime(); assert(testCrc() === crcResult); let nodeTime = formatTimeDiff(process.hrtime(start)); let executeTime = {}; let scalabilityTest = function(workers: number): Promise { let finished = 0; let start = process.hrtime(); return new Promise((resolve, reject) => { for (let i = 0; i < workers; ++i) { zone.execute("", "testCrc", []).then((result: napa.zone.Result) => { assert(crcResult === result.value); ++finished; if (finished === workers) { executeTime[workers] = formatTimeDiff(process.hrtime(start)) resolve(); } }); } }) }; // Execute from 1 worker to 8 workers. await scalabilityTest(1); await scalabilityTest(2); await scalabilityTest(4); await scalabilityTest(8); console.log("## Execute scalability\n") console.log(mdTable([ ["node", "napa - 1 worker", "napa - 2 workers", "napa - 4 workers", "napa - 8 workers"], [nodeTime, executeTime[1], executeTime[2], executeTime[4], executeTime[8]] ])); console.log(''); } ================================================ FILE: benchmark/node-napa-perf-comparison.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from '../lib/index'; import * as mdTable from 'markdown-table'; import { formatTimeDiff } from './bench-utils'; export function timeIt(func: () => void): [number, number] { let start = process.hrtime(); func(); return process.hrtime(start); } export function test1(): [number, number] { return timeIt(() => { const REPEAT = 1000000000; let sum = 0; for (let i = 0; i < REPEAT; ++i) { sum += i; } }); } export async function bench(zone: napa.zone.Zone): Promise { zone.broadcast(timeIt.toString()); zone.broadcast(test1.toString()); // Warm up. test1(); await zone.execute('', 'test1', []); // Actual test. let table = []; table.push(["node time", "napa time"]); let nodeTime = formatTimeDiff(test1()); let napaTime = formatTimeDiff((await zone.execute('', 'test1', [])).value); table.push([nodeTime, napaTime]); console.log("## Node vs Napa JavaScript execution performance\n"); console.log(mdTable(table)); console.log(''); } ================================================ FILE: benchmark/store-overhead.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from '../lib/index'; import * as assert from 'assert'; import * as mdTable from 'markdown-table'; import { generateObject, formatTimeDiff } from './bench-utils'; type BenchmarkSettings = [ string, // label number, // # of keys per level number, // # of levels string, // value type number // value length for string. ]; export function bench() { console.log("Benchmarking store overhead..."); let settings: BenchmarkSettings[] = [ // Number ["1 level - 1 integers", 1, 1, "number", 0], ["1 level - 10 integers", 10, 1, "number", 0], ["1 level - 100 integers", 100, 1, "number", 0], ["10 level - 2 integers", 2, 10, "number", 0], ["2 level - 10 integers", 10, 2, "number", 0], ["3 level - 5 integers", 5, 3, "number", 0], // String ["1 level - 10 strings - length 10", 10, 1, "string", 10], ["1 level - 100 strings - length 10", 100, 1, "string", 10], ["2 level - 10 strings - length 10", 10, 2, "string", 10], ["3 level - 5 strings - length 10", 5, 3, "string", 10], ["1 level - 10 strings - length 100", 10, 1, "string", 100], ["1 level - 100 strings - length 100", 100, 1, "string", 100], ["2 level - 10 strings - length 100", 10, 2, "string", 100], ["3 level - 5 integers", 5, 3, "string", 100], // Boolean ["1 level - 10 booleans", 10, 1, "boolean", 0], ["1 level - 100 booleans", 100, 1, "boolean", 0], ["2 level - 10 booleans", 10, 2, "boolean", 0], ["3 level - 5 booleans", 5, 3, "boolean", 0], ]; let store = napa.store.create('store'); let table = []; table.push(["payload type", "size", "transport.marshall (ms)", "store.save (ms)", "transport.unmarshall (ms)", "store.get (ms)"]); for (let s of settings) { const REPEAT = 1000; let object = generateObject(s[1], s[2], s[3], s[4]) let payload = JSON.stringify(object); let size = payload.length; // transport.marshall let start = process.hrtime(); for (let i = 0; i < REPEAT; ++i) { napa.transport.marshall(object, null); } let marshallTime = formatTimeDiff(process.hrtime(start)); // store.set start = process.hrtime(); for (let i = 0; i < REPEAT; ++i) { store.set('key', object); } let storeSetTime = formatTimeDiff(process.hrtime(start)); assert.deepEqual(object, store.get('key')); // transport.unmarshall start = process.hrtime(); for (let i = 0; i < REPEAT; ++i) { napa.transport.unmarshall(payload, null); } let unmarshallTime = formatTimeDiff(process.hrtime(start)); // store.get start = process.hrtime(); for (let i = 0; i < REPEAT; ++i) { store.get('key'); } let storeGetTime = formatTimeDiff(process.hrtime(start)); table.push([s[0], size, marshallTime, storeSetTime, unmarshallTime, storeGetTime]); } console.log("## Store access overhead\n"); console.log(mdTable(table)); console.log(''); } ================================================ FILE: benchmark/transport-overhead.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from '../lib/index'; import * as assert from 'assert'; import * as mdTable from 'markdown-table'; import { generateObject, timeDiffInMs, formatTimeDiff, formatRatio } from './bench-utils'; type BenchmarkSettings = [ string, // label number, // # of keys per level number, // # of levels string, // value type number // value length for string. ]; export function bench() { console.log("Benchmarking transport overhead..."); let settings: BenchmarkSettings[] = [ // Number ["1 level - 10 integers", 10, 1, "number", 0], ["1 level - 100 integers", 100, 1, "number", 0], ["10 level - 2 integers", 2, 10, "number", 0], ["2 level - 10 integers", 10, 2, "number", 0], ["3 level - 5 integers", 5, 3, "number", 0], // String ["1 level - 10 strings - length 10", 10, 1, "string", 10], ["1 level - 100 strings - length 10", 100, 1, "string", 10], ["2 level - 10 strings - length 10", 10, 2, "string", 10], ["3 level - 5 strings - length 10", 5, 3, "string", 10], ["1 level - 10 strings - length 100", 10, 1, "string", 100], ["1 level - 100 strings - length 100", 100, 1, "string", 100], ["2 level - 10 strings - length 100", 10, 2, "string", 100], ["3 level - 5 integers", 5, 3, "string", 100], // Boolean ["1 level - 10 booleans", 10, 1, "boolean", 0], ["1 level - 100 booleans", 100, 1, "boolean", 0], ["2 level - 10 booleans", 10, 2, "boolean", 0], ["3 level - 5 booleans", 5, 3, "boolean", 0], ]; let table = []; table.push(["payload type", "size", "JSON.stringify (ms)", "transport.marshall (ms)", "JSON.parse (ms)", "transport.unmarshall (ms)"]); for (let s of settings) { const REPEAT = 1000; let object = generateObject(s[1], s[2], s[3], s[4]) let payload = JSON.stringify(object); let size = payload.length; // JSON.stringify let start = process.hrtime(); for (let i = 0; i < REPEAT; ++i) { JSON.stringify(object); } let stringifyTime = timeDiffInMs(process.hrtime(start)); let stringifyTimeText = formatTimeDiff(stringifyTime); // transport.marshall start = process.hrtime(); for (let i = 0; i < REPEAT; ++i) { napa.transport.marshall(object, null); } let marshallTime = timeDiffInMs(process.hrtime(start)); let marshallTimeText = formatTimeDiff(marshallTime) + " " + formatRatio(marshallTime, stringifyTime); let marshalled = napa.transport.marshall(object, null); assert.deepEqual(payload, marshalled); // JSON.parse start = process.hrtime(); for (let i = 0; i < REPEAT; ++i) { JSON.parse(payload); } let parseTime = timeDiffInMs(process.hrtime(start)); let parseTimeText = formatTimeDiff(parseTime); start = process.hrtime(); for (let i = 0; i < REPEAT; ++i) { napa.transport.unmarshall(payload, null); } let unmarshallTime = timeDiffInMs(process.hrtime(start)); let unmarshallTimeText = formatTimeDiff(unmarshallTime) + " " + formatRatio(unmarshallTime, parseTime); table.push([s[0], size, stringifyTimeText, marshallTimeText, parseTimeText, unmarshallTimeText]); } console.log("## Transport overhead\n"); console.log(mdTable(table)); console.log(''); } ================================================ FILE: benchmark/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es5", "experimentalDecorators": true, "noImplicitAny": false, "declaration": false, "sourceMap": false, "lib": ["es2015"] } } ================================================ FILE: build.bat ================================================ :: Copyright (c) Microsoft Corporation. All rights reserved. :: Licensed under the MIT license. @echo off set NODE_EXE=%~dp0\node.exe if not exist "%NODE_EXE%" ( set NODE_EXE=node ) %NODE_EXE% %~dp0\build.js %* ================================================ FILE: build.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. // Distinguish between running this file as a script or loading it as a module. if (module.parent) { // Loaded as a module - 'require('./build.js') exports.paths = require('./scripts/paths'); } else { // Loaded as a script - 'node build.js' // Remove first two parameters which are node executable and this javascript file. // Transform all parameters to lowercase for easier handling. let params = process.argv.slice(2).map((item) => item.toLowerCase()); // Specify wheater cleanup is needed. if (params.indexOf('clean') != -1) { require('./scripts/clean').clean(); } // Only build if embed was specified as a parameter. if (params.indexOf('embed') != -1) { let embedded = require('./scripts/embedded'); if (params.indexOf('debug') != -1) { embedded.build('debug'); } else { embedded.build('release'); } } } ================================================ FILE: docs/api/index.md ================================================ # API References ## Core modules - Napa.js specific [`global`](./napa-globals.md) variables - Namespace [`zone`](./zone.md): Multi-thread JavaScript runtime - Namespace [`transport`](./transport.md): Passing JavaScript values across threads - Namespace [`store`](./store.md): Sharing JavaScript values by global storage - Namespace [`sync`](./sync.md): Handling synchronization between threads - Namespace [`memory`](./memory.md): Handling native objects and memory - Namespace [`metric`](./metric.md): Pluggable metrics. - Function [`log`](./log.md): Pluggable logging. ## Node compatibility - [List of supported Node APIs](./node-api.md) ## API for creating modules - [Writing Napa.js modules](./module.md) ================================================ FILE: docs/api/log.md ================================================ # Function `log` ## Table of Contents - [Introduction](#intro) - [C++ API](#cpp-api) - [JavaScript API](#js-api) - [`log(message: string): void`](#log) - [`log(section: string, message: string): void`](#log-with-section) - [`log(section: string, traceId: string, message: string): void`](#log-with-traceid) - [`log.err(...)`](#log-err) - [`log.warn(...)`](#log-warn) - [`log.info(...)`](#log-info) - [`log.debug(...)`](#log-debug) - [Using custom logging providers](#use-custom-providers) - [Developing custom logging providers](#develop-custom-providers) ## Introduction Logging is a basic requirement for building services. `napajs` logging API enables developers to integrate their own logging capabilities in both JavaScript and C++ (addon) world. A log row may contain the following information: - (Optional) Section: Useful field to filter log rows. Treatment is defined by logging providers. - (Optional) Trace ID: Useful field to join logs in the same transaction or request. - (Required) Message: Log message. - (Required) Logging level: - Error: for application error. - Warn: for warning information. - Info: for notification. - Debug: for debugging purpose. ## C++ API Include header: `` Macros: - LOG_ERROR(section, format, ...) - LOG_ERROR_WITH_TRACEID(section, traceId, format, ...) - LOG_WARNING(section, format, ...) - LOG_WARNING_WITH_TRACEID(section, traceId, format, ...) - LOG_INFO(section, format, ...) - LOG_INFO_WITH_TRACEID(section, traceId, format, ...) - LOG_DEBUG(section, format, ...) - LOG_DEBUG_WITH_TRACEID(section, traceId, format, ...) ```cpp #include void MyFunction() { // ... LOG_ERROR("init", "error: %s", errorMessage.c_str()); } ``` ## JavaScript API ### log(message: string): void It logs a message. Using info level. * A `log` is a shortcut for `log.info`.* Example: ```js var napa = require('napajs'); napa.log('program started'); ``` ### log(section: string, message: string): void It logs a message with a section. Using info level. Example: ```js napa.log('init', 'program started'); ``` ### log(section: string, traceId: string, message: string): void It logs a message with a section, associating it with a traceId. Using info level. Example: ```js napa.log('request', 'A1B2C3D4', 'request received'); ``` ### log.err(...) It logs an error message. Three variations of arguments are the same with the `log`. ### log.warn(...) It logs a warning message. Three variations of arguments are the same with the `log`. ### log.info(...) It logs an info message. Three variations of arguments are the same with the `log`. ### log.debug(...) It logs a debug message. Three combinations of arguments are the same with the `log`. ## Using custom logging providers Developers can hook up custom logging provider by calling the following before creation of any zones: ```js napa.runtime.setPlatformSettings({ "loggingProvider": "" } ``` ## Developing custom logging providers TBD ================================================ FILE: docs/api/memory.md ================================================ # Namespace `memory` ## Table of Contents - [API](#api) - Type [`Handle`](#handle) - Interface [`Shareable`](#shareable) - Interface [`Allocator`](#allocator) - [`allocator.allocate(size: number): Handle`](#allocator-allocate) - [`allocator.deallocate(handle: Handle, sizeHint: number): void`](#allocator-deallocate) - [`allocator.type: string`](#allocator-type) - Interface [`AllocatorDebugger`](#allocatordebugger) - [`allocatorDebugger.getDebugInfo(): string`](#allocatordebugger-getdebuginfo) - Function [`debugAllocator(allocator: Allocator): AllocatorDebugger`](#debugallocator) - Object [`crtAllocator`](#crtallocator) - Object [`defaultAllocator`](#defaultallocator) - [Memory allocation in C++ addon](#memory-allocation-in-cpp-addon) ## API ## Type `Handle` Handle is defined in TypeScript as below: ```ts type Handle = [number, number] ``` It is a standard way to represent a 64-bit pointer in Napa. ## Interface `Shareable` Interface for native object wrap that can be shared across multiple JavaScript threads. ## Interface `Allocator` Interface for memory allocator that allocates memory for native objects. ### allocator.allocate(size: number): Handle It allocates memory of requested size. ```js var handle = allocator.allocate(10); ``` ### allocator.deallocate(handle: Handle, sizeHint: number): void It deallocates memory from a input handle, with a size hint which is helpful for some C++ allocator implementations for deallocating memory. ```js allocator.deallocate(handle, 10); ``` ### allocator.type: string It gets a string type identifier for the allocator, which will be useful during debugging purpose. ## Interface `AllocatorDebugger` `AllocatorDebugger` extends interface `Allocator`, with a member function `getDebugInfo` to expose debug information. Basically an allocator debugger will use a pass-in allocator for memory allocation, meanwhile intercepting it to keep track of allocation count and size. ### allocatorDebugger.getDebugInfo(): string It gets the debug information for allocation. Implementations of interface `AllocatorDebugger` can have different schema on debug info. ## debugAllocator(allocator: Allocator): AllocatorDebugger It returns a simple allocator debugger, which returns debug information like below: ```json { "allocate": 10, "allocateSize": 1024, "deallocate": 8, "deallocateSize": 912 } ``` ## Object `crtAllocator` It returns a C-runtime allocator from Napa.js shared library. Its corresponding C++ part is `napa::memory::GetCrtAllocator()`. ## Object `defaultAllocator` It returns the default allocator from Napa.js shared library. Its corresponding C++ part is `napa::memory::GetDefaultAllocator()`. Users can set default allocation/deallocation callback in `napa_allocator_set` API. ## Memory allocation in C++ addon Memory allocation in C++ addon is tricky. A common pitfall is to allocate memory in one dll, but deallocate in another. This can cause issue if C-runtime in these 2 dlls are not compiled the same way. There are also advanced scenarios that user want to customize memory allocation. Napa.js provides APIs for customizing memory allocator as well. ### Recommended way of allocate memory TBD ### Customize memory allocation TBD ================================================ FILE: docs/api/metric.md ================================================ # Namespace `metric` ## Table of Contents - [Introduction](#intro) - [C++ API](#cpp-api) - Interface [`Metric`](#cpp-metric) - Interface [`MetricProvider`](#cpp-metricprovider) - Function [`MetricProvider& GetMetricProvider()`](#cpp-getmetricprovider) - [JavaScript API](#js-api) - Enum [`MetricType`](#metrictype) - Class [`Metric`](#metric) - [`set(value: number, dimensions?: string[]): void`](#metric-set) - [`increment(dimensions?: string[]): void`](#metric-increment); - [`decrement(dimensions?: string[]): void`](#metric-decrement); - Function [`get(section: string, name: string, type: MetricType, dimensionNames: string[])`](#get) - [Using custom metric providers](#use-custom-providers) - [Developing custom metric providers](#develop-custom-providers) ## Introduction Similar as logging, metric is a basic requirement for creating monitorable services. Metric API enables developers to use their own metric system in both JavaScript and C++ (addon) world. A metric has its identity containing following information: - **Section**: The group or category of the metric. - **Name**: Name of the metric. Section/Name combination should be unique in the system. - **Type**: Type of the metric, which can be - *Number*: An absolute number, e.g: PrivateBytes. - *Rate*: A flowing volume in number, e.g: QueryPerSecond. - *Percentile*: An absolute number that needs to be sampled by percentiles, e.g: SuccessLatency. - **Dimensions**: A metric can have multiple dimensions, each dimension can bind with a string value at runtime. e.g: IncomingRequestRate can have 2 dimensions: ['client-id', 'request-type']. Metrics are process-wise objects, which can be used across [zones](./zone.md#intro). ## C++ API ### Interface Metric ```cpp /// Enumeration of metric type. enum class MetricType { Number = 0, Rate, Percentile, }; /// Interface to represent a multi-dimensional metric with a maximum dimensionality of 64. class Metric { public: /// Sets a metric value with variadic dimension arguments. /// Int64 value. /// Number of dimensions being set. /// Array of dimension value names. /// Success/Fail. /// /// The number of dimension values must exactly match the number of dimensions provided when /// creating this metric. /// virtual bool Set(int64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0; /// /// Increments a metric value with variadic dimension arguments. /// Use mainly to simplify rate counters. /// /// UInt64 value to increment. /// Number of dimensions being set. /// Array of dimension value names. /// Success/Fail. /// /// The number of dimension values must exactly match the number of dimensions /// provided when creating this metric. /// virtual bool Increment(uint64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0; /// /// Decrements metric value with variadic dimension arguments. /// Use mainly to simplify rate counters. /// /// UInt64 value to decrement. /// Number of dimensions being set. /// Array of dimension value names. /// Success/Fail. /// /// The number of dimension values must exactly match the number of dimensions /// provided when creating this metric. /// virtual bool Decrement(uint64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0; /// Explicitly destroys the Metric. /// /// Consumers are not required to call this. /// The MetricProvider owns this class and will automatically perform cleanup on shutdown. /// virtual void Destroy() = 0; protected: /// Prevent calling delete on the interface. Must use Destroy! virtual ~Metric() = default; }; ``` ### Interface MetricProvider ```cpp /// Interface for a generic metric provider. /// /// Ownership of this metric provider belongs to the shared library which created it. Hence the explicit /// Destroy method in this class. To simplify memory management across multiple shared libraries, this class /// can only be created via a factory method provided by the shared library. When it is no longer needed, /// the caller may call Destroy() which will tell the shared library which created it to dispose of the object. /// class MetricProvider { public: /// /// Gets or creates a N-dimensional metric. Metric objects are owned and cached by this class. /// Up to 64 dimensions may be used. /// Section of the metric. /// Name of the metric. /// Type of the metric. /// /// Number of dimensions requested for this metric. /// Represents the size of the array passed in for p_dimensionNames. /// /// Array of dimension names being requested for this metric. /// /// The IMetric class returned is owned and cached by this class. /// Callers are not required to call destroy() on the Metric. /// virtual Metric* GetMetric( const char* section, const char* name, MetricType type, size_t dimensions, const char* dimensionNames[]) = 0; /// Explicitly destroys the metric provider. virtual void Destroy() = 0; protected: /// Prevent calling delete on the interface. Must use Destroy! virtual ~MetricProvider() = default; }; ``` ### function `MetricProvider& GetMetricProvider()` ```cpp /// Exports a getter function for retrieves the configured metric provider. NAPA_API MetricProvider& GetMetricProvider(); ``` ## JavaScript API ### enum `MetricType` ```ts export enum MetricType { Number = 0, Rate, Percentile, } ``` ### Class `Metric` Class to manipulate metrics in JavaScript. ### `set(value: number, dimensions?: string[]): void` Set absolute value on an instance of the metric constrained by dimension values. Example: ```js // Create a percentile metric to measure end-to-end latency, with 1 dimension of client-id. latency = napa.metric.get( 'app1', 'end-to-end-latency', napa.metric.MetricType.Percentile, ['client-id']); // Set end-to-end latency of current request to 100, with client-id 'client1'. latency.set(100, ['client1']); ``` #### `increment(dimensions?: string[]): void` Increment the value of an instance of the metric constrained by dimension values. Example: ```js // Create a percentile metric to measure end-to-end latency, with 1 dimension of client-id. latency = napa.metric.get( 'app1', 'qps', napa.metric.MetricType.Rate, ['client-id']); // Increment QPS of client-id 'client1'. latency.increment(['client1']); ``` #### `decrement(dimensions?: string[]): void` Decrement the value of an instance of the metric constrained by dimension values. ### function `get(section: string, name: string, type: MetricType, dimensions: string[] = []): Metric` Create a metric with an identity consisting of section, name, type and dimensions. If a metric already exists with given parameters, returns existing one. Example: ```ts import * as napa from 'napajs'; let metric = napa.metric.get( 'app1', 'counter1', napa.metric.MetricType.Number, []); metric.increment([]); ``` ## Using custom metric providers Developers can hook up custom metric provider by calling the following before creation of any zones: ```ts napa.runtime.setPlatformSettings({ "metricProvider": "" } ``` ## Developing custom metric providers TBD ================================================ FILE: docs/api/module.md ================================================ # Napa.js Module ## Table of Contents - [Introduction](#intro) - [Developing modules](#develop-modules) - [Module: JavaScript vs C++](#js-vs-cpp) - [Quick reference](#quick-ref) - [JavaScript module](#ref-js-module) - [C++ module](#ref-cpp-module) - [API](#api) - [JavaScript API](#js-api) - [C++ API](#cpp-api) - [Exporting JavaScript class from C++ modules](#export-class) - [V8 helpers](#v8helpers) - [Using STL with custom allocators](#stl-with-allocator) - [Special topics](#topics) - [Topic #1: Make objects shareable across multiple JavaScript threads](#topic-shareable-objects) - [Topic #2: Asynchronous functions](#topic-async-functions) - [Topic #3: Memory management in C++ modules](#topic-memory-management) ## Introduction Napa.js follows [Node.js' convention](https://nodejs.org/api/modules.html) to support modules, that means: 1) Both JavaScript modules and C++ modules are supported. 2) Module resolution follows the [same algorithm](https://nodejs.org/api/modules.html#modules_all_together), except instead of searching file extension `.node` for addons, Napa.JS searches `.napa`. 3) Supports NPM, with the [same way](https://docs.npmjs.com/getting-started/creating-node-modules) to create and publish modules. 4) API of creating C++ modules (addons) are similar. Napa.JS introduced macros that the same source code can be compiled to produce both Napa.js addon and Node.js addon. But there are also differences: 1) C++ module that is designed/implemented for Napa.js can run on Node.JS (need different compile flags to produce '.napa' and '.node'). But not vice versa. 2) Napa.js doesn't support all Node.js API. Node API are supported [incrementally](./node-api.md) on the motivation of adding Node.js built-ins and core modules that are needed for computation heavy tasks. You can access full capabilities of Node exposed via [Node zone](./zone.md#node-zone). 3) Napa.js doesn't provide `uv` functionalities, thus built-ins and core modules have its own implementation. To write async function in addon, methods `DoAsyncWork`/`PostAsyncWork` are introduced to work for both Napa.js and Node.js. 4) Napa.js supports embed mode. C++ modules need separate compilation between Node mode and embed mode. ## Developing modules ### Module: JavaScript vs. C++ A quick glance at NPM will reveal that most modules are pure JavaScript. These are only a few reasons that you may want to create a C++ module. - You want to expose JavaScript API for existing C/C++ functionalities. - Code includes considerably amount of computation that is performance critical. - Objects need to be shared across multiple JavaScript threads, marshalling/unmarshalling cost on these objects is not trivial (big payload size, complex structure, etc.), but it's reasonable cheap to expose JavaScript APIs from underlying native objects. - In embed mode, you want to communicate with host process with native objects. [This post](https://docs.npmjs.com/getting-started/creating-node-modules) gives a good introduction on creating a JavaScript module. For creating a Napa.JS C++ module, please refer to the [API](#api) section or checkout examples in the [quick reference](#quick-reference) section. ## Quick reference ### JavaScript module | Description | Transportable | Example code | | ------------------------------------------------------------ | ------------- | ------------ | | Standard JavaScript module | | [Blog post](https://www.hacksparrow.com/how-to-write-node-js-modules.html) | | Share JavaScript object across isolates | X | | ### C++ module | Description | ObjectWrap | Transportable | Async function | Example code | | ------------------------------------------------------------ | ---------- | ------------- | -------------- | ------------ | | Export JavaScript function only | | | | hello-world [[.md](../../examples/modules/hello-world/README.md) [.cpp](../../examples/modules/hello-world/node/addon.cpp) [test](../../examples/modules/hello-world/test/test.ts)] | | Export JavaScript object (ObjectWrap) | X | | | plus-number [[.md](../../examples/modules/plus-number/README.md) [.cpp](../../examples/modules/plus-number/node/addon.cpp) [test](../../examples/modules/plus-number/test/module-test/test.ts)] | | Share C++ object across isolates | X | X | | allocator-wrap [[.h](../../src/module/core-modules/napa/allocator-wrap.h) [.cpp](../../src/module/core-modules/napa/allocator-wrap.cpp)] | | Export asynchronous JavaScript function | X | | X | async-number [[.md](../../examples/modules/async-number/README.md) [.cpp](../../examples/modules/async-number/node/addon.cpp) [test](../../examples/modules/async-number/test/test.ts)] | ## API ### JavaScript See [API reference](./index.md). ### C++ #### Exporting JavaScript classes from C++ modules TBD #### V8 helpers TBD #### Using STL with custom allocators TBD ## Special topics ### Topic #1: Make objects shareable across multiple JavaScript threads TBD ### Topic #2: Asynchronous functions TBD ### Topic #3: Memory management in C++ modules TBD ================================================ FILE: docs/api/napa-globals.md ================================================ # Napa.js specific global variables This file describes Napa.js specific globals, please refer to [this documentation](./node-api.md#globals) for Node.js globals. ## `global.napa` Shortcut to access `napajs` module in all Napa enabled isolates. This is helpful to avoid extra `require` when using `napajs` module in anonymous function during `broadcast` or `execute`. Example: ```js var napa = require('napajs'); var zone = napa.zone.create('zone1'); function test() { global.napa.log('hi'); } zone.execute(test); ``` ================================================ FILE: docs/api/node-api.md ================================================ # Node.js Compatibility Napa.js doesn't support full compatibility with node.js and necessary core modules will be added incrementally. Here are the list of what Napa.js currently supports. Please refer to https://nodejs.org/api/all.html for details. ## Assert Since Napa doesn't support *Buffer* yet, assert is not working on *Buffer*. * assert(value[, message]) * assert.deepEqual(actual, expected[, message]) * assert.doesNotThrow(block[, error][, message]) * assert.equal(actual, expected[, message]) * assert.fail(actual, expected, message, operator) * assert.ifError(value) * assert.notDeepEqual(actual, expected[, message]) * assert.notEqual(actual, expected[, message]) * assert.notStrictEqual(actual, expected[, message]) * assert.ok(value[, message]) * assert.strictEqual(actual, expected[, message]) * assert.throws(block[, error][, message]) ## Console * console.log([data][, ...args]) ## Events * Event: 'newListener' * Event: 'removeListener' * EventEmitter.listenerCount(emitter, eventName) * EventEmitter.defaultMaxListeners * emitter.addListener(eventName, listener) * emitter.emit(eventName[, ...args]) * emitter.eventNames() * emitter.getMaxListeners() * emitter.listenerCount(eventName) * emitter.listeners(eventName) * emitter.on(eventName, listener) * emitter.once(eventName, listener) * emitter.prependListener(eventName, listener) * emitter.prependOnceListener(eventName, listener) * emitter.removeAllListeners([eventName]) * emitter.removeListener(eventName, listener) * emitter.setMaxListeners(n) ## File system * fs.readFileSync(path) * fs.writeFileSync(file, data) * fs.mkdirSync(path) * fs.existsSync(path) * fs.readdirSync(path) ## Globals * __dirname * __filename * console * exports * global * module * module.exports * module.id * process * require ## OS * os.type ## Path * path.basename(path[, ext]) * path.dirname(path) * path.extname(path) * path.format(pathObject) * path.isAbsolute(path) * path.join([...paths]) * path.normalize(path) * path.relative(from, to) * path.resolve([...paths]) * path.sep ## Process * process.argv * process.cwd() * process.chdir(directory) * process.env * process.execPath * process.exit(code) * process.hrtime([time]) * process.pid * process.platform * process.umask([mask]) ## TTY * tty.isatty(fd) ## Util * util.debuglog(section) * util.format(format[, ...args]) * util.inherits(constructor, superConstructor) * util.inspect(object[, options]) * util._extend(target, source) ================================================ FILE: docs/api/store.md ================================================ # Namespace `store` ## Table of Contents - [Introduction](#intro) - [API](#api) - [`create(id: string): Store`](#create) - [`get(id: string): Store`](#get) - [`getOrCreate(id: string): Store`](#getorcreate) - [`count: number`](#count) - Interface [`Store`](#store) - [`store.id: string`](#store-id) - [`store.set(key: string, value: any): void`](#store-set) - [`store.get(key: string): any`](#store-get) - [`store.has(key: string): boolean`](#store-has) - [`store.size: number`](#store-size) ## Introduction Store API is a necessary complement of sharing [transportable](transport.md#transportable-types) objects across JavaScript threads, on top of passing objects via arguments. During [`store.set`](#store-set), values marshalled into JSON and stored in process heap, so all threads can access it, and unmarshalled while users retrieve them via [`store.get`](#store-get). Though very convenient, it's not recommended to use store to pass values within a transaction or request, since its overhead is more than passing objects by arguments (there are extra locking, etc.). Besides, developers have the obligation to delete the key after usage, while it's automatically managed by reference counting in passing arguments. ## API Following APIs are exposed to create, get and operate upon stores. ### create(id: string): Store It creates a store by a string identifier that can be used to get the store later. When all references to the store from all JavaScript VMs are cleared, the store will be destroyed. Thus always keep a reference at global or module scope is usually a good practice using `Store`. Error will be thrown if the id already exists. Example: ```js var store = napa.store.create('store1'); ``` ### get(id: string): Store It gets a reference of store by a string identifier. `undefined` will be returned if the id doesn't exist. Example: ```js var store = napa.store.get('store1'); ``` ### getOrCreate(id: string): Store It gets a reference of store by a string identifier, or creates it if the id doesn't exist. This API is handy when you want to create a store in code that is executed by every worker of a zone, since it doesn't break symmetry. Example: ```js var store = napa.store.getOrCreate('store1'); ``` ### count: number It returns count of living stores. ### Interface `Store` Interface that let user to put and get objects across multiple JavaScript VMs. ### store.id: string It gets the string identifier for the store. ### store.set(key: string, value: any): void It puts a [transportable](transport.md#transportable-types) value into store with a string key. If key already exists, new value will override existing value. Example: ```js store.set('status', 1); ``` ### store.get(key: string): any It gets a [transportable](transportable.md#transportable-types) value from the store by a string key. If key doesn't exist, `undefined` will be returned. Example: ```js var value = store.get('status'); assert(value === 1); ``` ### store.has(key: string): boolean It tells if a key exists in current store. Example: ```js assert(store.has('status')) ``` ### store.size: number It tells how many keys are stored in current store. ================================================ FILE: docs/api/sync.md ================================================ # Namespace `sync` ## Table of Contents - class [`Lock`](#interface-lock) - [`lock.guardSync(func: (...params: any[]) => any, params?: any[]): any`](#lock-guard-sync-func-any-any) ## APIs Namespace `sync` deal with synchronization between threads in Napa. `Lock` is provided for exclusive and shared mutex scenarios. ## Interface `Lock` Exclusive Lock, which is [transportable](transport.md#transportable) across JavaScript threads. Use `napa.sync.createLock()` to create a lock. ```ts var lock = napa.sync.createLock(); ``` ### lock.guardSync(func: (...params: any[]) => any, params?: any[]): any Run input function synchronously and obtain the lock during its execution, returns what the function returns, or throws error if input function throws. Lock will be released once execution finishes. ```ts try { var value = lock.guardSync(() => { // DoSomething may throw. return DoSomething(); }); console.log(value); } catch(error) { console.log(error); } ``` An example [Synchronized Loading](./../../examples/tutorial/synchronized-loading) demonstrated how to implement a shared, lazy-loading phone book. ================================================ FILE: docs/api/transport.md ================================================ # Namespace `transport` ## Table of Contents - [Introduction](#intro) - [Transportable types](#transportable-types) - [Constructor ID](#constructor-id) - [Transport context](#transport-context) - [Transporting functions](#transporting-functions) - [Transporting JavaScript built-in objects](#transporting-built-in) - API - [`isTransportable(jsValue: any): boolean`](#istransportable) - [`register(transportableClass: new(...args: any[]) => any): void`](#register) - [`marshall(jsValue: any, context: TransportContext): string`](#marshall) - [`unmarshall(json: string, context: TransporteContext): any`](#unmarshall) - class [`TransportContext`](#transportcontext) - [`context.saveShared(object: memory.Shareable): void`](transportcontext-saveshared) - [`context.loadShared(handle: memory.Handle): memory.Shareable`](transportcontext-loadshared) - [`context.sharedCount: number`](transportcontext-sharedcount) - interface [`Transportable`](#transportable) - [`transportable.cid: string`](#transportable-cid) - [`transportable.marshall(context: TransportContext): object`](#transportable-marshall) - [`transportable.unmarshall(payload: object, context: TransportContext): void`](#transportable-unmarshall) - abstract class [`TransportableObject`](#transportableobject) - [`transportableObject.cid: string`](#transportableobject-cid) - [`transportableObject.marshall(context: TransportContext): object`](#transportableobject-marshall) - [`transportableObject.unmarshall(payload: object, context: TransportContext): void`](#transportableobject-unmarshall) - abstract [`transportableObject.save(payload: object, context: TransportContext): void`](#transportableobject-save) - abstract [`transportableObject.load(payload: object, context: TransportContext): void`](#transportableobject-load) - decorator [`cid`](#decorator-cid) ## Introduction Existing JavaScript engines are not designed for running JavaScript across multiple VMs, which means every VM manages their own heap. Passing values from one VM to another has to be marshalled/unmarshalled. The size of payload and complexity of object will greatly impact communication efficiency. In Napa, we try to work out a design pattern for efficient object sharing, based on the fact that all JavaScript VMs (exposed as workers) reside in the same process, and native objects can be wrapped and exposed as JavaScripts objects. Following concepts are introduced to implement this pattern: ### Transportable types Transportable types are JavaScript types that can be passed or shared transparently across Napa workers. They are used as value types for passing arguments in [`zone.broadcast`](zone.md#broadcast-function) / [`zone.execute`](zone.md#execute-anonymous-function), and sharing objects in key/value pairs via [`store.set`](store.md#store-set) / [`store.get`](store.md#store-get). Transportable types are: - JavaScript primitive types: undefined, null, boolean, number, string - Object (TypeScript class) that implement [`Transportable`](#transportable) interface - Function without referencing closures. - JavaScript standard built-in objects in this whitelist. * ArrayBuffer * Float32Array * Float64Array * Int16Array * Int32Array * Int8Array * SharedArrayBuffer * Uint16Array * Uint32Array * Uint8Array - Array or plain JavaScript object that is composite pattern of above. ### Constructor ID (cid) For user classes that implement the [`Transportable`](#transportable) interface, Napa uses Constructor ID (`cid`) to lookup constructors for creating a right object from a string payload. `cid` is marshalled as a part of the payload. During unmarshalling, the transport layer will extract the `cid`, create an object instance using the constructor associated with it, and then call unmarshall on the object. It's the class developer's responsibility to choose the right `cid` for your class. To avoid conflict, we suggest to use the combination of module.id and class name as `cid`. Developer can use class decorator [`cid`](#decorator-cid) to register a user Transportable class automatically, when using TypeScript with decorator feature enabled. Or call [`transport.register`](#register) manually during module initialization. ### Transport context There are states that cannot be saved or loaded in serialized form (like std::shared_ptr), or it's very inefficient to serialize (like JavaScript function). Transport context is introduced to help in these scenarios. TransportContext objects can be passed from one JavaScript VM to another, or stored in the native world, so lifecycle of shared native objects extended by using TransportContext. An example of the `Transportable` implementation using TransportContext is [`ShareableWrap`](./../../inc/napa/module/shareable-wrap.h). ### Transporting functions JavaScript function is a special transportable type, through marshalling its definition into a [store](./store.md#intro), and generate a new function from its definition on target thread. Highlights on transporting functions are: - For the same function, marshall/unmarshall is an one-time cost on each JavaScript thread. Once a function is transported for the first time, later transportation of the same function to previous JavaScript thread can be regarded as free. - Closure cannot be transported, but you won't get an error when transporting a function. Instead, you will get runtime error complaining a variable (from closure) is undefined when you can the function later. - `__dirname` / `__filename` can be accessed in transported function, which is determined by `origin` property of the function. By default, `origin` property is set to the current working directory. ### Transporting JavaScript built-in objects JavaScript standard built-in objects in [the whitelist](#built-in-whitelist) can be transported among napa workers transparently. JavaScript Objects with properties in these types are also able to be transported. Please refer to [unit tests](./../../test/transport-test.ts) for detail. An example [Parallel Quick Sort](./../../examples/tutorial/parallel-quick-sort) demonstrated transporting TypedArray (created from SharedArrayBuffer) among multiple Napa workers for efficient data sharing. ## API ### isTransportable(jsValue: any): boolean It tells whether a JavaScript value is transportable or not. ```ts // JS primitives assert(transport.isTransportable(undefined)); assert(transport.isTransportable(null)); assert(transport.isTransportable(1)); assert(transport.isTransportable('string')); assert(transport.isTransportable(true)); // Transportable addon assert(transport.isTransportable(napa.memory.crtAllocator)); // Composite of transportable types. assert(transport.isTransportable([ 1, "string", { a: napa.memory.crtAllocator } ])); class B { field1: number; field2: string; } // Not transportable JS class. (not registered with @cid). assert(!transport.isTransportable(new B())); ``` ### register(transportableClass: new(...args: any[]) => any): void Register a `Transportable` class before the transport layer can marshall/unmarshall its instances. User can also use class decorator [`@cid`](#cid-decorator) for class registration. Example: ```ts class A extends transport.AutoTransportable { field1: string, method1(): string { return this.field1; } } // Explicitly register class A in transport. transport.register(A); ``` ### marshall(jsValue: any, context: TransportContext): string Marshall a [transportable](#transportable-types) JavaScript value into a JSON payload with a [`TransportContext`](#transport-context).An Error will be thrown if the value is not transportable. Example: ```js var context = transport.createTransportContext(); var jsonPayload = transport.marshall( [1, 'string', napa.memory.crtAllocator], context); console.log(jsonPayload); ``` ### unmarshall(json: string, context: TransportContext): any Unmarshall a [transportable](#transportable-types) JavaScript value from a JSON payload with a [`TransportContext`](#transport-context).An Error will be thrown if `cid` property is found and not registered with the transport layer. Example: ```js var value = transport.unmarshall(jsonPayload, context); ``` ## Class `TransportContext` Class for [Transport Context](#transport-context), that stores shared pointers and functions during marshall/unmarshall. ### context.saveShared(object: memory.Shareable): void Save a shareable object in context. ### context.loadShared(handle: memory.Handle): memory.Shareable Load a shareable object from handle. ### context.sharedCount: number Count of shareable objects saved in the current context. ## Interface `Transportable` Interface for the Transportable object. ### transportable.cid: string Get accessor for [Constructor ID](#constructor-id). It is used to lookup constructor for the payload of the current class. ### transportable.marshall(context: TransportContext): object Marshall transforms this object into a plain JavaScript object with the help of [TransportContext](#transport-context). ### transportable.unmarshall(payload: object, context: TransportContext): void Unmarshall transforms marshalled payload into current object. ## Abstract class `TransportableObject` TBD ### Decorator `cid` TBD ================================================ FILE: docs/api/zone.md ================================================ # Namespace `zone` ## Table of Contents - [Introduction](#intro) - [Multiple workers vs Multiple zones](#worker-vs-zone) - [Zone types](#zone-types) - [Zone operations](#zone-operations) - [API](#api) - [`create(id: string, settings: ZoneSettings = DEFAULT_SETTINGS): Zone`](#create) - [`get(id: string): Zone`](#get) - [`current: Zone`](#current) - [`node: Zone`](#node-zone) - Interface [`ZoneSettings`](#zone-settings) - [`settings.workers: number`](#zone-settings-workers) - Object [`DEFAULT_SETTINGS: ZoneSettings`](#default-settings) - Interface [`Zone`](#zone) - [`zone.id: string`](#zone-id) - [`zone.broadcast(code: string): Promise`](#broadcast-code) - [`zone.broadcast(function: (...args: any[]) => void, args?: any[]): Promise`](#broadcast-function) - [`zone.execute(moduleName: string, functionName: string, args?: any[], options?: CallOptions): Promise`](#execute-by-name) - [`zone.execute(function: (...args[]) => any, args?: any[], options?: CallOptions): Promise`](#execute-anonymous-function) - Interface [`CallOptions`](#call-options) - [`options.timeout: number`](#call-options-timeout) - Interface [`Result`](#result) - [`result.value: any`](#result-value) - [`result.payload: string`](#result-payload) - [`result.transportContext: transport.TransportContext`](#result-transportcontext) ## Introduction Zone is a key concept of napajs that exposes multi-thread capabilities in JavaScript world, which is a logical group of symmetric workers for specific tasks. Please note that it's not the same `zone` concept of a context object for async calls in [Dart](https://www.dartlang.org/articles/libraries/zones), or [Angular](https://github.com/angular/zone.js), or a proposal in [TC39](https://github.com/domenic/zones). ### Multiple workers vs. Multiple zones Zone consists of one or multiple JavaScript threads, we name each thread `worker`. Workers within a zone are symmetric, which means code executed on any worker from the zone should return the same result, and the internal state of every worker should be the same from a long-running point of view. Multiple zones can co-exist in the same process, with each loading different code, bearing different states or applying different policies, like heap size, etc. The purpose of having multiple zone is to allow multiple roles for complex work, each role loads the minimum resources for its own usage. ### Zone types There are two types of zone: - **Napa zone** - zone consists of Napa.js managed JavaScript workers (V8 isolates). Can be multiple, each may contain multiple workers. Workers in Napa zone support partial Node.JS APIs. - **Node zone** - a 'virtual' zone which exposes Node.js eventloop, has access to full Node.js capabilities. ### Zone operations There are two operations, designed to reinforce the symmetry of workers within a zone: 1) **Broadcast** - run code that changes worker state on all workers, returning a promise for the pending operation. Through the promise, we can only know if the operation succeeded or failed. Usually we use `broadcast` to bootstrap the application, pre-cache objects, or change application settings. Function `broadcastSync` is also offered as a synchronized version of broadcast operations. 2) **Execute** - run code that doesn't change worker state on an arbitrary worker, returning a promise of getting the result. Execute is designed for doing the real work. Zone operations are on a basis of first-come-first-serve, while `broadcast` takes higher priority over `execute`. ## API ### create(id: string, settings: ZoneSettings): Zone It creates a Napa zone with a string id. If a zone with the id is already created, an error will be thrown. [`ZoneSettings`](#zone-settings) can be specified for creating zones. Example 1: Create a zone with id 'zone1', using default ZoneSettings. ```js var napa = require('napajs'); var zone1 = napa.zone.create('zone1'); ``` Example 2: Create a zone with id 'zone2', with 1 worker. ```js var zone2 = napa.zone.create('zone2', { workers: 1 }); ``` ### get(id: string): Zone It gets a reference of zone by an id. Error will be thrown if the zone doesn't exist. Example: ```js var zone = napa.zone.get('zone1'); ``` ### current: Zone It returns a reference of the zone of the currently running isolate. If it's under node, it returns the [node zone](#node-zone). Example: Get current zone. ```js var zone = napa.zone.current; ``` ### node: Zone It returns a reference to the node zone. It is equivalent to `napa.zone.get('node')`; Example: ```js var zone = napa.zone.node; ``` ## Interface `ZoneSettings` Settings for zones, which will be specified during the creation of zones. If not specified, [DEFAULT_SETTINGS](#default-settings) will be used. ### settings.workers: number Number of workers in the zone. ## Object `DEFAULT_SETTINGS` Default settings for creating zones. ```js { workers: 2 } ``` ## Interface `Zone` Zone is the basic concept to execute JavaScript and apply policies in Napa. You can find its definition in [Introduction](#intro). Through the Zone API, developers can broadcast JavaScript code on all workers, or execute a function on one of them. When you program against a zone, it is the best practice to ensure all workers within a zone are symmetrical to each other, that is, you should not assume a worker may maintain its own states. The two major sets of APIs are [`broadcast`](#broadcast-code) and [`execute`](#execute-by-name), which are asynchronous operations with a few variations on their inputs. ### zone.id: string It gets the id of the zone. ### zone.broadcast(code: string): Promise\ It asynchronously broadcasts a snippet of JavaScript code in a string to all workers, which returns a Promise of void. If any of the workers failed to execute the code, the promise will be rejected with an error message. Example: ```js var napa = require('napajs'); var zone = napa.zone.get('zone1'); zone.broadcast('var state = 0;') .then(() => { console.log('broadcast succeeded.'); }) .catch((error) => { console.log('broadcast failed.') }); ``` ### zone.broadcastSync(code: string): void It synchronously broadcasts a snippet of JavaScript code in a string to all workers. If any of the workers failed to execute the code, an exception will be thrown with an error message. Remarks: - It's not allowed to call `broadcastSync` on current zone. It will cause a deadlock Example: ```js var napa = require('napajs'); var zone = napa.zone.get('zone1'); try { zone.broadcastSync('var state = 0;'); console.log('broadcast succeeded.'); } catch (error) { console.log('broadcast failed.') } ``` ### zone.broadcast(function: (...args: any[]) => void | Promise\, args?: any[]): Promise\ It asynchronously broadcasts an anonymous function with its arguments to all workers, which returns a Promise of void. If any of the workers failed to execute the code, the promise will be rejected with an error message. Remarks: - If the function returns a Promise object, its state will be adopted to `broadcast`'s return value - The function object cannot access variables from closure - Unless the function object has an `origin` property, it will use the current file as `origin`, which will be used to set `__filename` and `__dirname`. (See [transporting functions](./transport.md#transporting-functions)) - Transport context is not available in broadcast. All types that depend on [TransportContext](./transport.md#transport-context) (eg. [ShareableWrap](https://github.com/Microsoft/napajs/blob/master/inc/napa/module/shareable-wrap.h), [Transportable](./transport.md#-interface-transportable)) cannot be passed in arguments list. Example: ```js zone.broadcast((state) => { require('some-module').setModuleState(state) }, [{field1: 1}]) .then(() => { console.log('broadcast succeeded.'); }) .catch((error) => { console.log('broadcast failed:', error) }); ``` ### zone.broadcastSync(function: (...args: any[]) => void | Promise\, args?: any[]): void It synchronously broadcasts an anonymous function with its arguments to all workers. If any of the workers failed to execute the code, an exception will be thrown with an error message. Remarks: - It's not allowed to call `broadcastSync` on current zone. It will cause a deadlock - If the function returns a Promise object, its state will be adopted. Function `broadcastSync` will not return until that Promise resolved or rejected. - The function object cannot access variables from closure - Unless the function object has an `origin` property, it will use the current file as `origin`, which will be used to set `__filename` and `__dirname`. (See [transporting functions](./transport.md#transporting-functions)) - Transport context is not available in broadcast. All types that depend on [TransportContext](./transport.md#transport-context) (eg. [ShareableWrap](https://github.com/Microsoft/napajs/blob/master/inc/napa/module/shareable-wrap.h), [Transportable](./transport.md#-interface-transportable)) cannot be passed in arguments list. Example: ```js try { zone.broadcastSync((state) => { require('some-module').setModuleState(state) }, [{field1: 1}]); console.log('broadcast succeeded.'); } catch (error) { console.log('broadcast failed:', error) } ``` ### zone.execute(moduleName: string, functionName: string, args?: any[], options?: CallOptions): Promise\ Execute a function asynchronously on an arbitrary worker via module name and function name. Arguments can be of any JavaScript type that is [transportable](transport.md#transportable-types). It returns a Promise of [`Result`](#result). If an error happens, either bad code, user exception, or timeout is reached, the promise will be rejected. Example: Execute function `bar` in module `foo`, with arguments [1, 'hello', { field1: 1 }]. 300ms timeout is applied. ```js zone.execute( 'foo', 'bar', [1, "hello", {field1: 1}], { timeout: 300 }) .then((result) => { console.log('execute succeeded:', result.value); }) .catch((error) => { console.log('execute failed:', error); }); ``` ### zone.execute(function: (...args: any[]) => any, args?: any[], options?: CallOptions): Promise\ Execute a function object asynchronously on an arbitrary worker. Arguments can be of any JavaScript type that is [transportable](transport.md#transportable-types). It returns a Promise of [`Result`](#result). If an error happens, either bad code, user exception, or timeout is reached, promise will be rejected. Remarks: - If the function returns a Promise object, it will be adopted - The function object cannot access variables from closure - Unless the function object has an `origin` property, it will use the current file as `origin`, which will be used to set `__filename` and `__dirname`. (See [transporting functions](./transport.md#transporting-functions)) Example: ```js zone.execute((a: number, b: string, c: object) => { return a + b + JSON.stringify(c); }, [1, "hello", {field1: 1}]) .then((result) => { console.log('execute succeeded:', result.value); }) .catch((error) => { console.log('execute failed:', error); }); ``` Output: ``` execute succeeded: 1hello{"field1":1} ``` Another example demonstrates accessing `__filename` when executing an anonymous function: ```js // File: /usr/file1.js zone.execute(() => { console.log(__filename);}); ``` Output: ``` /usr/file1.js ``` ## Interface `CallOptions` Interface for options to call functions in `zone.execute`. ### options.timeout: number Timeout in milliseconds. Default value 0 indicates no timeout. ## Interface `Result` Interface to access the return value of [`execute`](#execute-by-name). ### result.value: any JavaScript value returned from the function which is invoked from zone.execute/executeSync. Napa marshalls/unmarshalls [transportable values](transport.md#transportable-types) between different workers (V8 isolates). Unmarshalling will happen when the first `result.value` is queried. Example: ```js var value = result.value; ``` ### result.payload: string Marshalled payload (in JSON) from the returned value. This field is for users that want to pass results through to its caller, where the unmarshalled value is not required. Example: ```js var payload = result.payload; ``` ### result.transportContext: transport.TransportContext [TransportContext](transport.md#transport-context) that is required to unmarshall [`result.payload`](#result-payload) into [`result.value`](#result-value). Example: ```js var napa = require('napajs'); var zone = napa.zone.create('zone1'); zone.execute(() => { return 0; }, []) .then((result) => { // Manually marshall. var transportContext = result.transportContext; var value = napa.transport.unmarshall(result.payload, result.transportContext); // result.value and manual unmarshall from payload are the same. assert.equal(value, result.value); }); ``` ================================================ FILE: docs/design/transport-js-builtins.md ================================================ # Transport JavaScript standard built-in objects ## Incentives The abstraction of 'Transportable' lies in the center of Napa.js to efficiently share objects between JavaScript VMs (Napa workers). Except JavaScript primitive types, an object needs to implement 'Transportable' interface to make it transportable. It means [JavaScript standard built-in objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects) are not transportable unless wrappers or equivalent implementations for them are implemented by extending 'Transportable' interface. The development cost for these objects is not trivial, and new abstraction layer (wrappers or equivalent implementations) will create barriers for users to learn and adopt these new things. Moreover, developers also need to deal with the interaction between JavaScript standards objects and those wrappers or equivalent implementations. The incentive of this design is to provide a solution to make JavaScript standard built-in objects transportable with requirements listed in the Goals section. At the first stage, we will focus on an efficient solution to share data between Napa workers. Basically, it is about making *SharedArrayBuffer / TypedArray / DataView* transportable. ## Goals Make Javascript standard built-in objects transportable with - An efficient way to share structured data, like *SharedArrayBuffer*, among Napa workers - Consistent APIs with ECMA standards - No new abstraction layers for the simplest usage - The least new concepts for advanced usage - A scalable solution to make all JavaScript standard built-in objects transportable, avoiding to make them transportable one by one. ## Example The below example shows how *SharedArrayBuffer* object is transported across multiple Napa workers. It will print the TypedArray 'ta' created from a *SharedArrayBuffer*, with all its elements set to 100 from different Napa workers. ```js var napa = require("napajs"); var zone = napa.zone.create('zone', { workers: 4 }); function foo(sab, i) { var ta = new Uint8Array(sab); ta[i] = 100; return i; } function run() { var promises = []; var sab = new SharedArrayBuffer(4); for (var i = 0; i < 4; i++) { promises[i] = zone.execute(foo, [sab, i]); } return Promise.all(promises).then(values => { var ta = new Uint8Array(sab); console.log(ta); }); } run(); ``` ## Solution Here we just give a high-level description of the solution. Its API will go to [docs/api/transport](https://github.com/Microsoft/napajs/blob/master/docs/api/transport.md). - V8 provides its value-serialization mechanism by ValueSerializer and ValueDeserializer, which is compatible with the HTML structured clone algorithm. It is a horizontal solution to serialize / deserialize JavaScript objects. ValueSerializer::Delegate and ValueDeserializer::Delegate are their inner class. They work as base classes from which developers can deprive to customize some special handling of external / shared resources, like memory used by a SharedArrayBuffer object. - **napa::v8_extensions::ExternalizedContents** - It holds the externalized contents (memory) of a SharedArrayBuffer instance once it is serialized via *napa::v8_extensions::Utils::SerializeValue()*. - Only 1 instance of ExternalizedContents will be generated for each SharedArrayBuffer. If a SharedArrayBuffer had been externalized, it will reuse the ExternalizedContents instance created before in *napa::v8_extensions::Utils::SerializeValue()*. - **napa::v8_extensions::SerializedData** - It is generated by *napa::v8_extensions::Utils::SerializeValue()*. It holds the serialized data of a JavaScript object, which is required during its deserialization. - **BuiltInObjectTransporter** - **napa::v8_extensions::Serializer, derived from v8::ValueSerializer::Delegate** - **napa::v8_extensions::Deserializer, derived from v8::ValueDeserializer::Delegate** - **static std::shared_ptr\ v8_extensions::Utils::SerializeValue(Isolate\* isolate, Local\ value);** - Generate the *SerializedData* instance given an input value. - If any *SharedArrayBuffer* instances exist in the input value, their *ExternalizedContents* instances will be generated and attached to the *ShareArrayBuffer* instances respectively. - **static MaybeLocal\ v8_extensions::Utils::DeserializeValue(Isolate\* isolate, std::shared_ptr\ data);** - Restore a JavaScript value from its SerializedData instance generated by *v8_extensions::Utils::SerializeValue()* before. - Currently, Napa relies on Transportable API and a registered constructor to make an object transportable. In [marshallTransform](https://github.com/Microsoft/napajs/blob/master/lib/transport/transport.ts), when a JavaScript object is detected to have a registered constructor, it will go with Napa way to marshall this object with the help of a **TransportContext** object, otherwise a non-transportable error is thrown. - Instead of throwing an error when no registered constructor is detected, the **BuiltInObjectTransporter** can help handle this object. We can use a whitelist of object types to restrict this solution to those verified types at first. ```js export function marshallTransform(jsValue: any, context: transportable.TransportContext): any { if (jsValue != null && typeof jsValue === 'object' && !Array.isArray(jsValue)) { let constructorName = Object.getPrototypeOf(jsValue).constructor.name; if (constructorName !== 'Object') { if (typeof jsValue['cid'] === 'function') { return (jsValue).marshall(context); } else if (_builtInTypeWhitelist.has(constructorName)) { let serializedData = builtinObjectTransporter.serializeValue(jsValue); if (serializedData) { return { _serialized : serializedData }; } else { throw new Error(`Failed to serialize object with type of \"${constructorName}\".`); } } else { throw new Error(`Object type \"${constructorName}\" is not transportable.`); } } } return jsValue; } ``` - The reverse process will be invoked in [unmarshallTransform](https://github.com/Microsoft/napajs/edit/master/lib/transport/transport.ts) if the payload is detected to have '_serialized' property. ```js function unmarshallTransform(payload: any, context: transportable.TransportContext): any { if (payload != null && payload._cid !== undefined) { let cid = payload._cid; if (cid === 'function') { return functionTransporter.load(payload.hash); } let subClass = _registry.get(cid); if (subClass == null) { throw new Error(`Unrecognized Constructor ID (cid) "${cid}". Please ensure @cid is applied on the class or transport.register is called on the class.`); } let object = new subClass(); object.unmarshall(payload, context); return object; } else if (payload.hasOwnProperty('_serialized')) { return builtinObjectTransporter.deserializeValue(payload['_serialized']); } return payload; } ``` #### Lifecycle of SharedArrayBuffer (SAB) - When a SAB participates transportation among Napa workers, its life cycle will be extended till the last reference this SAB. The reference of a SAB could be: - SAB object in its original isolate. - Received SAB transported from another Napa workers, including node zone of Napa. - TypedArray or DataView created from the original SAB or a received SAB. - The life cycle extension during transportation is achieved through the *ExternalizedContents* *SharedPtrWrap* of the SAB. - When a SAB is transported for the first time, it will be externalized and its ExternalizedContents will be stored in its *SerializedData*. At the same time, the *SharedPtrWrap* of the *ExternalizedContents* will be set to the '_externalized' property of the original SAB. - When a SAB is transported for the second time or later, it will skip externalization and find its *ExternalizedContents* from its '_externalized' property, and store it to its *SerializedData*. - When a Napa worker tries to restore a transported SAB, it will find the pre-stored *ExternalizedContents*, and create a *SharedPtrWrap* for it, then set it to the to-be-restored SAB. - The life cycle of the *SharedArrayBuffer* is extended by the *SharedPtrWrap* of its *ExternalizedContents*. ## Constraints The above solution is based on the serialization / deserialization mechanism of V8. It may have the following constraints. - Not all [JavaScripts standard built-in objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects) are supported by Node (as a dependency of Napa in node mode) or V8 of a given version. We only provide transporting solution for those mature object types. - Presently, Node does not explicitly support multiple V8 isolates. There may be inconsistencies in transporting objects between Node zones and Napa zones. Extra effort might be required to make it consistent. ================================================ FILE: examples/modules/README.md ================================================ ## Napa module source tree The source codes can be organized as the structure below. ``` +-- napa-module-example +-- inc | +-- example.h +-- lib | +-- example.ts | +-- tsconfig.json +-- napa | +-- addon.cpp | +-- CMakeLists.txt (cmake specific) | +-- example-wrap.cpp | +-- example-wrap.h +-- src | +-- CMakeLists.txt (cmake specific) | +-- example.cpp +-- test | +-- test.ts | +-- tsconfig.json +-- binding.gyp (gyp specific) +-- CMakeLists.txt (cmake specific) +-- package.json +-- README.md ``` ## How to build and test ? For the module examples, node-gyp or cmake-js build solution is provided for your reference. Please make sure [node-gyp](https://github.com/nodejs/node-gyp#installation) or [cmake-js](https://github.com/cmake-js/cmake-js#installation) has been set up correctly at your client, then you could follow the steps below to build and test the module examples. ``` 1. go to the directory of a module example 2. run "npm install" to build the module 3. run "npm test" to build and run the module test, as well as its unittest if any ``` ================================================ FILE: examples/modules/async-number/.gitignore ================================================ bin build node_modules types # ignore all js files under lib lib/*.js # ignore all js files under test test/*.js package-lock.json ================================================ FILE: examples/modules/async-number/.npmignore ================================================ bin build # Exclude TypeScript source files. lib/*.ts lib/tsconfig.json test/*.ts test/tsconfig.json ================================================ FILE: examples/modules/async-number/README.md ================================================ # Asynchronous numbering ## APIs Napa provides two functions to support asynchronous call and they work for both node.js and Napa. ```cpp void napa::module::PostAsyncWork(v8::Local jsCallback, std::function asyncWork, std::function, void*)> asyncCompleteCallback); ``` It fires asynchronous work onto separate thread. * *jsCallback*: Javascript callback given at Javascript land. * *asyncWork*: C++ function to run at separate thread asynchronously. * *asyncCompleteCallback*: C++ callback function, which has isolate instance and *jsCallback* as arguments. It's called at the same isolate with caller's one after *asyncWork* completes. ```cpp void napa::module::DoAsyncWork(v8::Local jsCallback, const std::function)>& asyncWork, std::function, void*)> asyncCompleteCallback); ``` If you have a function already supporting async, use this API. * *jsCallback*: Javascript callback given at Javascript land. * *asyncWork*: Function to wrap C++ function supporting async, which callback must call Napa completion callback given as argument. * *asyncCompleteCallback*: C++ callback function, which has isolate instance and *jsCallback* as arguments. It's called at the same isolate with caller's one after *asyncWork* completes. ### Diagram This diagram shows how asynchronous call is working corresponding to the below example, ```diagram |-------------------------| |------------------------| |------------------------------------| |-------------------| (V8 thread)-----| Call *increase()* at JS |-----| Run the next statement |-------------------| Run *asyncCompleteCallback* at C++ |-----| Call *jsCallback* | |-------------------------| |------------------------| |------------------------------------| |-------------------| | + | | |----------| | | | + | |------------------| |--------------------------------------------| | (thread)-----| Run *asyncWork* |-----| Post a task to run *asyncCompleteCallback* |---------| |------------------| |--------------------------------------------| ``` ## C++ module This example shows how to create async module. It keeps one number and three APIs operating on it as follows, * *Increase*: It increases a number by a given parameter in separate thread and post a completion to run Javascript callback at the next execution loop. * *IncreaseSync*: It increases a number by a given parameter in the same thread and post a completion to run Javascript callback at the next execution loop. * *Now*: It returns the current value of a number. ```cpp #include #include #include namespace napa { namespace demo { using namespace v8; namespace { std::atomic _now(0); } /// It increases a number by a given parameter asynchronously and runs a callback at the next execution loop. void Increase(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); CHECK_ARG(isolate, args.Length() == 2 && args[0]->IsUint32() && args[1]->IsFunction(), "It requires unsigned integer and callback as arguments"); auto value = args[0]->Uint32Value(); napa::module::PostAsyncWork(Local::Cast(args[1]), [value]() { // This runs in a separate thread. _now += value; return reinterpret_cast(static_cast(_now.load())); }, [](auto jsCallback, void* result) { // This runs in the same thread as the one Increase() is called in. auto isolate = Isolate::GetCurrent(); int32_t argc = 1; Local argv[] = { Integer::NewFromUnsigned(isolate, static_cast(reinterpret_cast(result))) }; jsCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv); } ); } /// It increases a number by a given parameter synchronously and runs a callback at the next execution loop. void IncreaseSync(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); CHECK_ARG(isolate, args.Length() == 2 && args[0]->IsUint32() && args[1]->IsFunction(), "It requires unsigned integer and callback as arguments"); auto value = args[0]->Uint32Value(); napa::module::DoAsyncWork(Local::Cast(args[1]), [value](auto complete) { // This runs in the same thread. _now += value; complete(reinterpret_cast(static_cast(_now.load()))); }, [](auto jsCallback, void* result) { // This runs in the same thread as the one IncreaseSync() is called in. auto isolate = Isolate::GetCurrent(); int32_t argc = 1; Local argv[] = { Integer::NewFromUnsigned(isolate, static_cast(reinterpret_cast(result))) }; jsCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv); } ); } /// It returns the current value of a number. void Now(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); HandleScope scope(isolate); args.GetReturnValue().Set(Integer::NewFromUnsigned(isolate, _now)); } void Init(Local exports) { NAPA_SET_METHOD(exports, "increase", Increase); NAPA_SET_METHOD(exports, "increaseSync", IncreaseSync); NAPA_SET_METHOD(exports, "now", Now); } NAPA_MODULE(addon, Init) } // namespace demo } // namespace napa ``` ## Typescript ### async-number.ts ```ts var addon = require('../bin/addon'); export function increase(value: number, callback: (now: number) => void) { return addon.increase(value, callback); } export function increaseSync(value: number, callback: (now: number) => void) { return addon.increaseSync(value, callback); } export function now(): string { return addon.now(); } ``` ### async-number.d.ts ```d.ts export declare function increase(extra: number, callback: (now: number) => void): any; export declare function increaseSync(extra: number, callback: (now: number) => void): any; export declare function now(): string; ``` ## Test ```ts var assert = require('assert'); var asyncNumber = require('async-number'); describe('Test suite for async-number', function() { it('change number asynchronously on separate thread', function(done) { let now = asyncNumber.now(); assert.equal(now, 0); asyncNumber.increase(3, (value: number) => { // This must be called after the last statement of *it* block is executed. assert(value == 3 || value == 6); now = asyncNumber.now(); assert.equal(now, 6); done(); }); asyncNumber.increaseSync(3, (value) => {} ); }); it('change number synchronously on current thread', function(done) { let now = asyncNumber.now(); assert.equal(now, 0); asyncNumber.increaseSync(3, (value: number) => { // This must be called after the last statement of *it* block is executed. assert.equal(value, 3); now = asyncNumber.now(); assert.equal(now, 6); done(); }); now = asyncNumber.now(); // 'now' should be 3. assert.equal(now, 3); asyncNumber.increaseSync(3, (value) => {} ); }); }) ``` ================================================ FILE: examples/modules/async-number/binding.gyp ================================================ { "variables": { "napajs_lib": " void) { return addon.increase(value, callback); } export function increaseSync(value: number, callback: (now: number) => void) { return addon.increaseSync(value, callback); } export function now(): string { return addon.now(); } ================================================ FILE: examples/modules/async-number/lib/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es5", "experimentalDecorators": true, "noImplicitAny": true, "declaration": true, "sourceMap": false, "lib": ["es2015"], "declarationDir": "../types" } } ================================================ FILE: examples/modules/async-number/napa/addon.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include #include namespace napa { namespace demo { using namespace v8; namespace { std::atomic _now(0); } /// It increases a number by a given parameter asynchronously and runs a callback at the next execution loop. void Increase(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); CHECK_ARG(isolate, args.Length() == 2 && args[0]->IsUint32() && args[1]->IsFunction(), "It requires unsigned integer and callback as arguments"); auto value = args[0]->Uint32Value(); napa::zone::PostAsyncWork(Local::Cast(args[1]), [value]() { // This runs in a separate thread. _now += value; return reinterpret_cast(static_cast(_now.load())); }, [](auto jsCallback, void* result) { // This runs in the same thread as the one Increase() is called in. auto isolate = Isolate::GetCurrent(); int32_t argc = 1; Local argv[] = { Integer::NewFromUnsigned(isolate, static_cast(reinterpret_cast(result))) }; jsCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv); } ); } /// It increases a number by a given parameter synchronously and runs a callback at the next execution loop. void IncreaseSync(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); CHECK_ARG(isolate, args.Length() == 2 && args[0]->IsUint32() && args[1]->IsFunction(), "It requires unsigned integer and callback as arguments"); auto value = args[0]->Uint32Value(); napa::zone::DoAsyncWork(Local::Cast(args[1]), [value](auto complete) { // This runs in the same thread. _now += value; complete(reinterpret_cast(static_cast(_now.load()))); }, [](auto jsCallback, void* result) { // This runs in the same thread as the one IncreaseSync() is called in. auto isolate = Isolate::GetCurrent(); int32_t argc = 1; Local argv[] = { Integer::NewFromUnsigned(isolate, static_cast(reinterpret_cast(result))) }; jsCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv); } ); } /// It returns the current value of a number. void Now(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); HandleScope scope(isolate); args.GetReturnValue().Set(Integer::NewFromUnsigned(isolate, _now)); } void Init(Local exports) { NAPA_SET_METHOD(exports, "increase", Increase); NAPA_SET_METHOD(exports, "increaseSync", IncreaseSync); NAPA_SET_METHOD(exports, "now", Now); } NAPA_MODULE(addon, Init) } // namespace demo } // namespace napa ================================================ FILE: examples/modules/async-number/package.json ================================================ { "name": "async-number", "version": "0.1.0", "author": "napajs", "description": "Example of an async napa module.", "main": "./lib/async-number.js", "types": "./types/async-number.d.ts", "gypfile": true, "license": "MIT", "devDependencies": { "mocha": ">= 3.4.2", "typescript": ">= 2.2.1", "@types/node": ">= 7.0.8", "@types/mocha": ">= 2.2.0", "markdown-table": "1.1.0" }, "dependencies": { "napajs": ">= 0.1.2" }, "scripts": { "install": "node-gyp configure && node-gyp build", "prepare": "tsc -p lib", "pretest": "tsc -p test", "test": "mocha test --recursive" } } ================================================ FILE: examples/modules/async-number/test/test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let assert = require('assert'); let asyncNumber = require('..'); let napa = require('napajs'); let zone = napa.zone.create('zone'); describe('Test suite for async-number', function() { this.timeout(0); it('change number asynchronously on separate thread', () => { let now = asyncNumber.now(); assert.equal(now, 0); asyncNumber.increase(3, (value: number) => { // This must be called after the last statement of *it* block is executed. assert(value == 3 || value == 6); now = asyncNumber.now(); assert.equal(now, 6); }); asyncNumber.increaseSync(3, (value: number) => {} ); }); it('change number synchronously on current thread', () => { let now = asyncNumber.now(); assert.equal(now, 6); asyncNumber.increaseSync(3, (value: number) => { // This must be called after the last statement of *it* block is executed. assert.equal(value, 9); now = asyncNumber.now(); assert.equal(now, 12); }); now = asyncNumber.now(); // 'now' should be 9. assert.equal(now, 9); asyncNumber.increaseSync(3, (value: number) => {} ); }); it('change number asynchronously on separate thread in napa zone', () => { return zone.execute(() => { let assert = require('assert'); let asyncNumber = require('..'); let now = asyncNumber.now(); assert.equal(now, 0); asyncNumber.increase(3, (value: number) => { // This must be called after the last statement of *it* block is executed. assert(value == 3 || value == 6); now = asyncNumber.now(); assert.equal(now, 6); }); asyncNumber.increaseSync(3, (value: number) => {} ); }); }); it('change number synchronously on current thread in napa zone', () => { return zone.execute(() => { let assert = require('assert'); let asyncNumber = require('..'); let now = asyncNumber.now(); assert.equal(now, 6); asyncNumber.increaseSync(3, (value: number) => { // This must be called after the last statement of *it* block is executed. assert.equal(value, 9); now = asyncNumber.now(); assert.equal(now, 12); }); now = asyncNumber.now(); // 'now' should be 9. assert.equal(now, 9); asyncNumber.increaseSync(3, (value: number) => {} ); return 1; }); }); }) ================================================ FILE: examples/modules/async-number/test/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es5", "experimentalDecorators": true, "noImplicitAny": true, "declaration": false, "sourceMap": false, "lib": ["es2015"] } } ================================================ FILE: examples/modules/hello-world/.gitignore ================================================ bin build node_modules types # ignore all js files under lib lib/*.js # ignore all js files under test test/*.js package-lock.json ================================================ FILE: examples/modules/hello-world/.npmignore ================================================ bin build # Exclude TypeScript source files. lib/*.ts lib/tsconfig.json test/*.ts test/tsconfig.json ================================================ FILE: examples/modules/hello-world/README.md ================================================ # Hello World This example shows the simple napa module, which shows the basic difference between node.js module and napa module. ```cpp #include namespace napa { namespace demo { using namespace v8; void Method(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world")); } void Init(Local exports) { NAPA_SET_METHOD(exports, "hello", Method); } NAPA_MODULE(addon, Init) } // namespace demo } // namespace napa ``` ## Transition from node.js module * *napa/module.h* is used instead of *node.h*. Depending on preprocessor definition, *NAPA_MODULE_EXTENSION* or *BUILDING_NODE_EXTENSION* preprocessor definition, *napa/module.h* includes necessary napa or node header files accordingly and build system creates either node.js module or napa module. * *NAPA_SET_METHOD* is equivalent to *NODE_SET_METHOD*. This module will have *hello()* function. * *NAPA_MODULE* is equivalent to *NODE_MODULE*, which exports an initialization function. ## Typescript It's recommended that typescript or typescript definition is provided to let the user know the APIs without the source codes and develop Typescript project easily. ### hello-world.ts ```ts var addon = require('../bin/addon'); export function hello(): string { return addon.hello(); } ``` ### hello-world.d.ts ```d.ts export declare function hello(): string; ``` ## Mocha test ```js var assert = require('assert'); var helloWorld = require('hello-world'); describe('Test suite for hello-word', function() { it('prints the string "world"', function() { var result = helloWorld.hello(); assert.equal(result, 'world'); }); }) ``` ================================================ FILE: examples/modules/hello-world/binding.gyp ================================================ { "variables": { "napajs_lib": " namespace napa { namespace demo { using namespace v8; void Method(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world")); } void Init(Local exports) { NAPA_SET_METHOD(exports, "hello", Method); } NAPA_MODULE(addon, Init) } // namespace demo } // namespace napa ================================================ FILE: examples/modules/hello-world/package.json ================================================ { "name": "hello-world", "version": "0.1.0", "author": "napajs", "description": "Example of a simple napa module.", "main": "./lib/hello-world.js", "types": "./types/hello-world.d.ts", "gypfile": true, "license": "MIT", "devDependencies": { "mocha": ">= 3.4.2", "typescript": ">= 2.2.1", "@types/node": ">= 7.0.8", "@types/mocha": ">= 2.2.0", "markdown-table": "1.1.0" }, "dependencies": { "napajs": ">= 0.1.2" }, "scripts": { "install": "node-gyp configure && node-gyp build", "prepare": "tsc -p lib", "pretest": "tsc -p test", "test": "mocha test --recursive" } } ================================================ FILE: examples/modules/hello-world/test/test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let assert = require('assert'); let helloWorld = require('..'); let napa = require('napajs'); let zone = napa.zone.create('zone'); describe('Test suite for hello-word', function() { this.timeout(0); it('prints the string "world"', function() { let result: string = helloWorld.hello(); assert.equal(result, 'world'); }); it('prints the string "world" in napa zone', function() { return zone.execute(() => { let helloWorld = require('..'); let result: string = helloWorld.hello(); return result; }).then((result : any) => { assert.equal(result.value, 'world'); }); }); }) ================================================ FILE: examples/modules/hello-world/test/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es5", "experimentalDecorators": true, "noImplicitAny": true, "declaration": false, "sourceMap": false, "lib": ["es2015"] } } ================================================ FILE: examples/modules/plus-number/.gitignore ================================================ bin build node_modules types # ignore all js files under lib lib/*.js # ignore all js files under test test/*.js package-lock.json ================================================ FILE: examples/modules/plus-number/.npmignore ================================================ bin build unittest # Exclude TypeScript source files. lib/*.ts lib/tsconfig.json test/*.ts test/tsconfig.json ================================================ FILE: examples/modules/plus-number/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2 FATAL_ERROR) project("addon") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) # Require Cxx14 features set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Build plus-number library add_subdirectory(src) # Build napa addon add_subdirectory(napa) ================================================ FILE: examples/modules/plus-number/README.md ================================================ # Plus Number This example shows the napa module, which wraps C++ objects/classes. Instead of using Javascript *new* operator, it uses a factory pattern. i.e. ``` var obj = addon.createPlusNumber(); // instead of // var obj = new addon.PlusNumber(); ``` ## Wrapped class *plus-number.h* declares the class with one constructor and one method, *Add()*. ```h namespace napa { namespace demo { /// Example class to show how to create Napa module using a wrapped C++ class. class PlusNumber { public: /// Constructor with initial value. explicit PlusNumber(double value = 0.0); /// Add the given value and return the result. double Add(double value); private: double _value; }; } // namespace demo } // namespace napa ``` ## Wrapper class *plus-number-wrap.h* declares the wrapper class inherited from *NAPA_OBJECTWRAP* as follows, ```h #include #include namespace napa { namespace demo { /// Napa example module wrapping PlusNumber class. class PlusNumberWrap : public NAPA_OBJECTWRAP { public: /// Register this class into V8. static void Init(); /// Enable to create an instance by createPlusNumber() Javascript API. /// Addend as PlusNumber constructor parameter. static void NewInstance(const v8::FunctionCallbackInfo& args); private: /// Exported class name. static const char* _exportName; /// Constructor with initial value. explicit PlusNumberWrap(double value = 0.0); /// Create PlusNumber instance at V8. /// Addend as PlusNumber constructor parameter. static void NewCallback(const v8::FunctionCallbackInfo& args); /// Add value. /// Addend. static void Add(const v8::FunctionCallbackInfo& args); /// Declare persistent constructor to create PlusNumber instance. /// Napa creates persistent constructor at each isolate while node.js creates the static instance. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); PlusNumber _plusNumber; }; } // namespace demo } // namespace napa ``` *plus-number-wrap.cpp* implements each functions as follows, ```cpp #include "plus-number-wrap.h" using namespace napa::demo; using namespace v8; const char* PlusNumberWrap::_exportName = "PlusNumberWrap"; // Define persistent constructor. NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(PlusNumberWrap); PlusNumberWrap::PlusNumberWrap(double value) : _plusNumber(value) { } void PlusNumberWrap::Init() { auto isolate = Isolate::GetCurrent(); // Prepare constructor template. auto functionTemplate = FunctionTemplate::New(isolate, NewCallback); functionTemplate->SetClassName(String::NewFromUtf8(isolate, _exportName)); functionTemplate->InstanceTemplate()->SetInternalFieldCount(1); // Set prototype method. NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "add", Add); // Set persistent constructor into V8. NAPA_SET_PERSISTENT_CONSTRUCTOR(_exportName, functionTemplate->GetFunction()); } void PlusNumberWrap::NewInstance(const v8::FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); HandleScope scope(isolate); const int argc = 1; Local argv[argc] = { args[0] }; auto constructor = NAPA_GET_PERSISTENT_CONSTRUCTOR(_exportName, PlusNumberWrap); auto context = isolate->GetCurrentContext(); auto instance = constructor->NewInstance(context, argc, argv).ToLocalChecked(); args.GetReturnValue().Set(instance); } void PlusNumberWrap::NewCallback(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); HandleScope scope(isolate); CHECK_ARG(isolate, args.IsConstructCall(), "PlusNumberWrap instance must be created by the factory."); CHECK_ARG(isolate, args.Length() == 0 || args.Length() == 1, "Only one or no argument is allowed."); if (args.Length() == 1) { CHECK_ARG(isolate, args[0]->IsNumber(), "The first argument must be a number."); } double value = args[0]->IsUndefined() ? 0.0 : args[0]->NumberValue(); auto wrap = new PlusNumberWrap(value); wrap->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } void PlusNumberWrap::Add(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsNumber(), "Number must be given as argument."); auto wrap = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto value = wrap->_plusNumber.Add(args[0]->NumberValue()); args.GetReturnValue().Set(Number::New(isolate, value)); } ``` *addon.cpp* implements the addon as below, ```cpp #include "plus-number-wrap.h" using namespace napa::demo; using namespace v8; void CreatePlusNumber(const FunctionCallbackInfo& args) { PlusNumberWrap::NewInstance(args); } void InitAll(Local exports) { PlusNumberWrap::Init(); NAPA_SET_METHOD(exports, "createPlusNumber", CreatePlusNumber); } NAPA_MODULE(addon, InitAll); ``` ## Transition from node.js module * *NAPA_OBJECTWRAP* is equivalent to *node::ObjectWrap*, so object's lifetime works as Javascript object. * One big difference is how to handle the persistent constructor. While Node.js has only one thread for Javascript and static constructor instance is fine, Napa should support one constructor instance per V8 isolate. These macros help constructor instance creation. * *NAPA_DECLARE_PERSISTENT_CONSTRUCTOR* declares constructor instance. * *NAPA_DEFINE_PERSISTENT_CONSTRUCTOR* defines constructor instance. * *NAPA_SET_PERSISTENT_CONSTRUCTOR* makes a local constructor as persistent. Napa stores it into thread local storage to allow one instance per V8 isolate. * *NAPA_GET_PERSISTENT_CONSTRUCTOR* return stored constructor instance. * *NAPA_SET_PROTOTYPE_METHOD* is equivalent to *NODE_SET_PROTOTYPE_METHOD*, which add a prototype method to Javascript object. ## Typescript *plus-number.ts* doesn't need to fully implement *PlusNumber.add()* since the signature of *PlusNumber* instance returned by *addon.createPlusNumber()* is the same. ### plus-number.ts ```ts var addon = require('../bin/addon'); export declare class PlusNumber { public add(value: number): number; } export function createPlusNumber(value: number = 0): PlusNumber { return addon.createPlusNumber(value); } ``` ### plus-number.d.ts ```d.ts export declare class PlusNumber { add(value: number): number; } export declare function createPlusNumber(value?: number): PlusNumber; ``` ## NPM Package NPM package contains the additional binary *plus-number.dll*, which is the shared library for *PlusNumber* class. It's placed at *bin* directory, so either Node.js or Napa can resolve the shared object path in the same way as it does for a module. ## Mocha test ```js var assert = require('assert'); var plusNumber = require('plus-number'); describe('Test suite for plus-number', function () { it('adds a given value', function () { var po = plusNumber.createPlusNumber(3); var result = po.add(4); assert.equal(result, 7); }); it('fails with constructor call', function () { var failed = false; try { var po = new plusNumber.PlusNumber(); } catch (error) { failed = true; } assert.equal(failed, true); }); }); ``` ================================================ FILE: examples/modules/plus-number/inc/plus-number.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #if defined(_WIN32) || defined(__WIN32__) #ifdef NAPA_EXAMPLE_API #define NAPA_EXAMPLE_EXPORT __declspec(dllexport) #else #define NAPA_EXAMPLE_EXPORT __declspec(dllimport) #endif #else #define NAPA_EXAMPLE_EXPORT #endif namespace napa { namespace demo { /// Example class to show how to create Napa module using a wrapped C++ class. class NAPA_EXAMPLE_EXPORT PlusNumber { public: /// Constructor with initial value. explicit PlusNumber(double value = 0.0); /// Add the given value and return the result. double Add(double value); private: double _value; }; } // napespace demo } // namespace napa ================================================ FILE: examples/modules/plus-number/lib/plus-number.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let addon = require('../bin/addon'); export declare class PlusNumber { public add(value: number): number; } export function createPlusNumber(value: number = 0.0): PlusNumber { return addon.createPlusNumber(value); } ================================================ FILE: examples/modules/plus-number/lib/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es5", "experimentalDecorators": true, "noImplicitAny": true, "declaration": true, "sourceMap": false, "lib": ["es2015"], "declarationDir": "../types" } } ================================================ FILE: examples/modules/plus-number/napa/CMakeLists.txt ================================================ execute_process(COMMAND node -e "require('napajs/build').paths.inc" RESULT_VARIABLE ERR OUTPUT_VARIABLE NAPAJS_INC) if(ERR) message(FATAL_ERROR "Failed to resolve napa include directory") endif(ERR) execute_process(COMMAND node -e "require('napajs/build').paths.lib" RESULT_VARIABLE ERR OUTPUT_VARIABLE NAPAJS_LIB) if(ERR) message(FATAL_ERROR "Failed to resolve napa library path") endif(ERR) ####################################################################################### # Build napa addon. set(NAPA_ADDON_TARGET_NAME "${PROJECT_NAME}.napa") # The generated library add_library(${NAPA_ADDON_TARGET_NAME} SHARED "addon.cpp" "plus-number-wrap.cpp") set_target_properties(${NAPA_ADDON_TARGET_NAME} PROPERTIES PREFIX "" SUFFIX "") # Include directories target_include_directories(${NAPA_ADDON_TARGET_NAME} PRIVATE ../inc ${CMAKE_JS_INC} ${NAPAJS_INC}) # Compiler definitions target_compile_definitions(${NAPA_ADDON_TARGET_NAME} PRIVATE BUILDING_NAPA_EXTENSION) # Link libraries target_link_libraries(${NAPA_ADDON_TARGET_NAME} PRIVATE plus-number ${CMAKE_JS_LIB} ${NAPAJS_LIB}) ####################################################################################### # Build napa addon for node. set(NODE_ADDON_TARGET_NAME "${PROJECT_NAME}.node") # The generated library add_library(${NODE_ADDON_TARGET_NAME} SHARED "addon.cpp" "plus-number-wrap.cpp") set_target_properties(${NODE_ADDON_TARGET_NAME} PROPERTIES PREFIX "" SUFFIX "") # Include directories target_include_directories(${NODE_ADDON_TARGET_NAME} PRIVATE ../inc ${CMAKE_JS_INC} ${NAPAJS_INC}) # Compiler definitions target_compile_definitions(${NODE_ADDON_TARGET_NAME} PRIVATE BUILDING_NODE_EXTENSION) # Link libraries target_link_libraries(${NODE_ADDON_TARGET_NAME} PRIVATE plus-number ${CMAKE_JS_LIB}) ================================================ FILE: examples/modules/plus-number/napa/addon.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "plus-number-wrap.h" using namespace napa::demo; using namespace v8; void CreatePlusNumber(const FunctionCallbackInfo& args) { PlusNumberWrap::NewInstance(args); } void InitAll(Local exports) { PlusNumberWrap::Init(); NAPA_SET_METHOD(exports, "createPlusNumber", CreatePlusNumber); } NAPA_MODULE(addon, InitAll); ================================================ FILE: examples/modules/plus-number/napa/plus-number-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "plus-number-wrap.h" using namespace napa::demo; using namespace v8; const char* PlusNumberWrap::_exportName = "PlusNumberWrap"; // Define persistent constructor. NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(PlusNumberWrap); PlusNumberWrap::PlusNumberWrap(double value) : _plusNumber(value) { } void PlusNumberWrap::Init() { auto isolate = Isolate::GetCurrent(); // Prepare constructor template. auto functionTemplate = FunctionTemplate::New(isolate, NewCallback); functionTemplate->SetClassName(String::NewFromUtf8(isolate, _exportName)); functionTemplate->InstanceTemplate()->SetInternalFieldCount(1); // Set prototype method. NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "add", Add); // Set persistent constructor into V8. NAPA_SET_PERSISTENT_CONSTRUCTOR(_exportName, functionTemplate->GetFunction()); } void PlusNumberWrap::NewInstance(const v8::FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); HandleScope scope(isolate); const int argc = 1; Local argv[argc] = { args[0] }; auto constructor = NAPA_GET_PERSISTENT_CONSTRUCTOR(_exportName, PlusNumberWrap); auto context = isolate->GetCurrentContext(); auto instance = constructor->NewInstance(context, argc, argv).ToLocalChecked(); args.GetReturnValue().Set(instance); } void PlusNumberWrap::NewCallback(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); HandleScope scope(isolate); CHECK_ARG(isolate, args.IsConstructCall(), "PlusNumberWrap instance must be created by the factory."); CHECK_ARG(isolate, args.Length() == 0 || args.Length() == 1, "Only one or no argument is allowed."); if (args.Length() == 1) { CHECK_ARG(isolate, args[0]->IsNumber(), "The first argument must be a number."); } double value = args[0]->IsUndefined() ? 0.0 : args[0]->NumberValue(); auto wrap = new PlusNumberWrap(value); wrap->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } void PlusNumberWrap::Add(const FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsNumber(), "Number must be given as argument."); auto wrap = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto value = wrap->_plusNumber.Add(args[0]->NumberValue()); args.GetReturnValue().Set(Number::New(isolate, value)); } ================================================ FILE: examples/modules/plus-number/napa/plus-number-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace demo { /// Napa example module wrapping PlusNumber class. class PlusNumberWrap : public NAPA_OBJECTWRAP { public: /// Register this class into V8. static void Init(); /// Enable to create an instance by createPlusNumber() Javascript API. /// Addend as PlusNumber constructor parameter. static void NewInstance(const v8::FunctionCallbackInfo& args); private: /// Exported class name. static const char* _exportName; /// Constructor with initial value. explicit PlusNumberWrap(double value = 0.0); /// Create PlusNumber instance at V8. /// Addend as PlusNumber constructor parameter. static void NewCallback(const v8::FunctionCallbackInfo& args); /// Add value. /// Addend. static void Add(const v8::FunctionCallbackInfo& args); /// Declare persistent constructor to create PlusNumber instance. /// Napa creates persistent constructor at each isolate while node.js creates the static instance. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); PlusNumber _plusNumber; }; } // namespace demo } // namespace napa ================================================ FILE: examples/modules/plus-number/package.json ================================================ { "name": "plus-number", "version": "0.1.0", "author": "napajs", "description": "Example of a napa module wrapping a class.", "main": "./lib/plus-number.js", "types": "./types/plus-number.d.ts", "license": "MIT", "devDependencies": { "mocha": ">= 3.4.2", "typescript": ">= 2.2.1", "@types/node": ">= 7.0.8", "@types/mocha": ">= 2.2.0", "markdown-table": "1.1.0" }, "dependencies": { "napajs": ">= 0.1.2" }, "scripts": { "install": "cmake-js compile", "pretest": "tsc -p lib && cmake-js compile -d unittest && tsc -p test", "test": "node unittest/run.js && mocha test --recursive" } } ================================================ FILE: examples/modules/plus-number/src/CMakeLists.txt ================================================ # Build plus-number library. set(TARGET_NAME "plus-number") # The generated library add_library(${TARGET_NAME} SHARED "plus-number.cpp") # Include directories target_include_directories(${TARGET_NAME} PRIVATE ../inc) # Compiler definitions target_compile_definitions(${TARGET_NAME} PRIVATE NAPA_EXAMPLE_API) ================================================ FILE: examples/modules/plus-number/src/plus-number.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "plus-number.h" using namespace napa::demo; PlusNumber::PlusNumber(double value) : _value(value) { } double PlusNumber::Add(double value) { return _value + value; } ================================================ FILE: examples/modules/plus-number/test/test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let assert = require('assert'); let plusNumber = require('..'); let napa = require('napajs'); let zone = napa.zone.create('zone'); describe('Test suite for plus-number', function() { this.timeout(0); it('adds a given value', function() { let po = plusNumber.createPlusNumber(3); let result: number = po.add(4); assert.equal(result, 7); }); it('fails with constructor call', function() { let failed: boolean = false; try { let po = new plusNumber.PlusNumber(); } catch (error) { failed = true; } assert.equal(failed, true); }); it('adds a given value in napa zone', function() { return zone.execute(() => { let plusNumber = require('..'); let po = plusNumber.createPlusNumber(3); let result: number = po.add(4); return result; }) .then((result: any) => { assert.equal(result.value, 7); }); }); it('adds a given value in node zone', function() { return napa.zone.node.execute(() => { let plusNumber = require('..'); let po = plusNumber.createPlusNumber(3); let result: number = po.add(4); return result; }) .then((result: any) => { assert.equal(result.value, 7); }); }); }) ================================================ FILE: examples/modules/plus-number/test/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es5", "experimentalDecorators": true, "noImplicitAny": true, "declaration": false, "sourceMap": false, "lib": ["es2015"] } } ================================================ FILE: examples/modules/plus-number/unittest/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2 FATAL_ERROR) project("library-test") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) # Require Cxx14 features set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Set symbol visibility to hidden by default. # Napa shared library shares a few classes with napa-binding.node with different compile definition, # exposing the same symbols from both shared libraries may cause improper behaviors under gcc. set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN) file(GLOB LIBRARY-FILES "../bin/*plus-number.*") file(COPY ${LIBRARY-FILES} DESTINATION ${PROJECT_SOURCE_DIR}/bin/) # Files to compile file(GLOB SOURCE_FILES "main.cpp") # The executable name set(TARGET_NAME "library-test") # The generated library add_executable(${TARGET_NAME} ${SOURCE_FILES}) # Include directories target_include_directories(${TARGET_NAME} PRIVATE ../inc) find_library(PLUS_NUMBER_LIBRARY NAMES plus-number PATHS ${PROJECT_SOURCE_DIR}/bin) # Link libraries target_link_libraries(${TARGET_NAME} PRIVATE ${PLUS_NUMBER_LIBRARY}) ================================================ FILE: examples/modules/plus-number/unittest/catch/LICENSE.txt ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: examples/modules/plus-number/unittest/catch/catch.hpp ================================================ /* * Catch v1.6.1 * Generated: 2017-01-20 12:33:53.497767 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_CATCH_HPP_INCLUDED #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ # pragma GCC system_header #endif // #included from: internal/catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic ignored "-Wglobal-constructors" # pragma clang diagnostic ignored "-Wvariadic-macros" # pragma clang diagnostic ignored "-Wc99-extensions" # pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wc++98-compat" # pragma clang diagnostic ignored "-Wc++98-compat-pedantic" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL #endif #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // #included from: internal/catch_notimplemented_exception.h #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED // #included from: catch_common.h #define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED // #included from: catch_compiler_capabilities.h #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED // Detect a number of compiler features - mostly C++11/14 conformance - by compiler // The following features are defined: // // CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? // CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? // CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? // CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported // CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? // CATCH_CONFIG_CPP11_OVERRIDE : is override supported? // CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. // All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 #ifdef __cplusplus # if __cplusplus >= 201103L # define CATCH_CPP11_OR_GREATER # endif # if __cplusplus >= 201402L # define CATCH_CPP14_OR_GREATER # endif #endif #ifdef __clang__ # if __has_feature(cxx_nullptr) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if __has_feature(cxx_noexcept) # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif # if defined(CATCH_CPP11_OR_GREATER) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) # endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Borland #ifdef __BORLANDC__ #endif // __BORLANDC__ //////////////////////////////////////////////////////////////////////////////// // EDG #ifdef __EDG_VERSION__ #endif // __EDG_VERSION__ //////////////////////////////////////////////////////////////////////////////// // Digital Mars #ifdef __DMC__ #endif // __DMC__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ # if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) # endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #if (_MSC_VER >= 1600) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) #define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE #endif #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Use variadic macros if the compiler supports them #if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ ( defined __GNUC__ && __GNUC__ >= 3 ) || \ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) #define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS #endif // Use __COUNTER__ if the compiler supports it #if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ ( defined __GNUC__ && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \ ( defined __clang__ && __clang_major__ >= 3 ) #define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 #if defined(CATCH_CPP11_OR_GREATER) # if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS # define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM # define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE # define CATCH_INTERNAL_CONFIG_CPP11_TUPLE # endif # ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) # define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) # define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) # define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) # define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE # endif #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured #if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_NULLPTR #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_NOEXCEPT #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_GENERATED_METHODS #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_IS_ENUM #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_TUPLE #endif #if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) # define CATCH_CONFIG_VARIADIC_MACROS #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_LONG_LONG #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_OVERRIDE #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_UNIQUE_PTR #endif // Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for // analytics) because, at time of writing, __COUNTER__ is not properly handled by it. // This does not affect compilation #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__) # define CATCH_CONFIG_COUNTER #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_SHUFFLE #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif // noexcept support: #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) # define CATCH_NOEXCEPT noexcept # define CATCH_NOEXCEPT_IS(x) noexcept(x) #else # define CATCH_NOEXCEPT throw() # define CATCH_NOEXCEPT_IS(x) #endif // nullptr support #ifdef CATCH_CONFIG_CPP11_NULLPTR # define CATCH_NULL nullptr #else # define CATCH_NULL NULL #endif // override support #ifdef CATCH_CONFIG_CPP11_OVERRIDE # define CATCH_OVERRIDE override #else # define CATCH_OVERRIDE #endif // unique_ptr support #ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR # define CATCH_AUTO_PTR( T ) std::unique_ptr #else # define CATCH_AUTO_PTR( T ) std::auto_ptr #endif #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif #define INTERNAL_CATCH_STRINGIFY2( expr ) #expr #define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) #include #include #include namespace Catch { struct IConfig; struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { #ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; #else NonCopyable( NonCopyable const& info ); NonCopyable& operator = ( NonCopyable const& ); #endif protected: NonCopyable() {} virtual ~NonCopyable(); }; class SafeBool { public: typedef void (SafeBool::*type)() const; static type makeSafe( bool value ) { return value ? &SafeBool::trueValue : 0; } private: void trueValue() const {} }; template inline void deleteAll( ContainerT& container ) { typename ContainerT::const_iterator it = container.begin(); typename ContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete *it; } template inline void deleteAllValues( AssociativeContainerT& container ) { typename AssociativeContainerT::const_iterator it = container.begin(); typename AssociativeContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete it->second; } bool startsWith( std::string const& s, std::string const& prefix ); bool endsWith( std::string const& s, std::string const& suffix ); bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); std::string trim( std::string const& str ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); struct pluralise { pluralise( std::size_t count, std::string const& label ); friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); std::size_t m_count; std::string m_label; }; struct SourceLineInfo { SourceLineInfo(); SourceLineInfo( char const* _file, std::size_t _line ); SourceLineInfo( SourceLineInfo const& other ); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SourceLineInfo( SourceLineInfo && ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo& operator = ( SourceLineInfo && ) = default; # endif bool empty() const; bool operator == ( SourceLineInfo const& other ) const; bool operator < ( SourceLineInfo const& other ) const; std::string file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // This is just here to avoid compiler warnings with macro constants and boolean literals inline bool alwaysTrue( std::size_t = 0 ) { return true; } inline bool alwaysFalse( std::size_t = 0 ) { return false; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); void seedRng( IConfig const& config ); unsigned int rngSeed(); // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() { return std::string(); } }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) #define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); #include namespace Catch { class NotImplementedException : public std::exception { public: NotImplementedException( SourceLineInfo const& lineInfo ); NotImplementedException( NotImplementedException const& ) {} virtual ~NotImplementedException() CATCH_NOEXCEPT {} virtual const char* what() const CATCH_NOEXCEPT; private: std::string m_what; SourceLineInfo m_lineInfo; }; } // end namespace Catch /////////////////////////////////////////////////////////////////////////////// #define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) // #included from: internal/catch_context.h #define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED // #included from: catch_interfaces_generators.h #define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED #include namespace Catch { struct IGeneratorInfo { virtual ~IGeneratorInfo(); virtual bool moveNext() = 0; virtual std::size_t getCurrentIndex() const = 0; }; struct IGeneratorsForTest { virtual ~IGeneratorsForTest(); virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; virtual bool moveNext() = 0; }; IGeneratorsForTest* createGeneratorsForTest(); } // end namespace Catch // #included from: catch_ptr.hpp #define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { // An intrusive reference counting smart pointer. // T must implement addRef() and release() methods // typically implementing the IShared interface template class Ptr { public: Ptr() : m_p( CATCH_NULL ){} Ptr( T* p ) : m_p( p ){ if( m_p ) m_p->addRef(); } Ptr( Ptr const& other ) : m_p( other.m_p ){ if( m_p ) m_p->addRef(); } ~Ptr(){ if( m_p ) m_p->release(); } void reset() { if( m_p ) m_p->release(); m_p = CATCH_NULL; } Ptr& operator = ( T* p ){ Ptr temp( p ); swap( temp ); return *this; } Ptr& operator = ( Ptr const& other ){ Ptr temp( other ); swap( temp ); return *this; } void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } T* get() const{ return m_p; } T& operator*() const { return *m_p; } T* operator->() const { return m_p; } bool operator !() const { return m_p == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } private: T* m_p; }; struct IShared : NonCopyable { virtual ~IShared(); virtual void addRef() const = 0; virtual void release() const = 0; }; template struct SharedImpl : T { SharedImpl() : m_rc( 0 ){} virtual void addRef() const { ++m_rc; } virtual void release() const { if( --m_rc == 0 ) delete this; } mutable unsigned int m_rc; }; } // end namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif #include #include #include namespace Catch { class TestCase; class Stream; struct IResultCapture; struct IRunner; struct IGeneratorsForTest; struct IConfig; struct IContext { virtual ~IContext(); virtual IResultCapture* getResultCapture() = 0; virtual IRunner* getRunner() = 0; virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; virtual bool advanceGeneratorsForCurrentTest() = 0; virtual Ptr getConfig() const = 0; }; struct IMutableContext : IContext { virtual ~IMutableContext(); virtual void setResultCapture( IResultCapture* resultCapture ) = 0; virtual void setRunner( IRunner* runner ) = 0; virtual void setConfig( Ptr const& config ) = 0; }; IContext& getCurrentContext(); IMutableContext& getCurrentMutableContext(); void cleanUpContext(); Stream createStream( std::string const& streamName ); } // #included from: internal/catch_test_registry.hpp #define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED // #included from: catch_interfaces_testcase.h #define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED #include namespace Catch { class TestSpec; struct ITestCase : IShared { virtual void invoke () const = 0; protected: virtual ~ITestCase(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); } namespace Catch { template class MethodTestCase : public SharedImpl { public: MethodTestCase( void (C::*method)() ) : m_method( method ) {} virtual void invoke() const { C obj; (obj.*m_method)(); } private: virtual ~MethodTestCase() {} void (C::*m_method)(); }; typedef void(*TestFunction)(); struct NameAndDesc { NameAndDesc( const char* _name = "", const char* _description= "" ) : name( _name ), description( _description ) {} const char* name; const char* description; }; void registerTestCase ( ITestCase* testCase, char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ); struct AutoReg { AutoReg ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); template AutoReg ( void (C::*method)(), char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { registerTestCase ( new MethodTestCase( method ), className, nameAndDesc, lineInfo ); } ~AutoReg(); private: AutoReg( AutoReg const& ); void operator= ( AutoReg const& ); }; void registerTestCaseFunction ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ static void TestName(); \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ namespace{ \ struct TestName : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ } \ void TestName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); #else /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ static void TestName(); \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ static void TestName() #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ namespace{ \ struct TestCaseName : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ } \ void TestCaseName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); #endif // #included from: internal/catch_capture.hpp #define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED // #included from: catch_result_builder.h #define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED // #included from: catch_result_type.h #define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED namespace Catch { // ResultWas::OfType enum struct ResultWas { enum OfType { Unknown = -1, Ok = 0, Info = 1, Warning = 2, FailureBit = 0x10, ExpressionFailed = FailureBit | 1, ExplicitFailure = FailureBit | 2, Exception = 0x100 | FailureBit, ThrewException = Exception | 1, DidntThrowException = Exception | 2, FatalErrorCondition = 0x200 | FailureBit }; }; inline bool isOk( ResultWas::OfType resultType ) { return ( resultType & ResultWas::FailureBit ) == 0; } inline bool isJustInfo( int flags ) { return flags == ResultWas::Info; } // ResultDisposition::Flags enum struct ResultDisposition { enum Flags { Normal = 0x01, ContinueOnFailure = 0x02, // Failures fail test, but execution continues FalseTest = 0x04, // Prefix expression with ! SuppressFail = 0x08 // Failures are reported but do not fail the test }; }; inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { return static_cast( static_cast( lhs ) | static_cast( rhs ) ); } inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } } // end namespace Catch // #included from: catch_assertionresult.h #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED #include namespace Catch { struct AssertionInfo { AssertionInfo() {} AssertionInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ); std::string macroName; SourceLineInfo lineInfo; std::string capturedExpression; ResultDisposition::Flags resultDisposition; }; struct AssertionResultData { AssertionResultData() : resultType( ResultWas::Unknown ) {} std::string reconstructedExpression; std::string message; ResultWas::OfType resultType; }; class AssertionResult { public: AssertionResult(); AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); ~AssertionResult(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionResult( AssertionResult const& ) = default; AssertionResult( AssertionResult && ) = default; AssertionResult& operator = ( AssertionResult const& ) = default; AssertionResult& operator = ( AssertionResult && ) = default; # endif bool isOk() const; bool succeeded() const; ResultWas::OfType getResultType() const; bool hasExpression() const; bool hasMessage() const; std::string getExpression() const; std::string getExpressionInMacro() const; bool hasExpandedExpression() const; std::string getExpandedExpression() const; std::string getMessage() const; SourceLineInfo getSourceInfo() const; std::string getTestMacroName() const; protected: AssertionInfo m_info; AssertionResultData m_resultData; }; } // end namespace Catch // #included from: catch_matchers.hpp #define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED namespace Catch { namespace Matchers { namespace Impl { namespace Generic { template class AllOf; template class AnyOf; template class Not; } template struct Matcher : SharedImpl { typedef ExpressionT ExpressionType; virtual ~Matcher() {} virtual Ptr clone() const = 0; virtual bool match( ExpressionT const& expr ) const = 0; virtual std::string toString() const = 0; Generic::AllOf operator && ( Matcher const& other ) const; Generic::AnyOf operator || ( Matcher const& other ) const; Generic::Not operator ! () const; }; template struct MatcherImpl : Matcher { virtual Ptr > clone() const { return Ptr >( new DerivedT( static_cast( *this ) ) ); } }; namespace Generic { template class Not : public MatcherImpl, ExpressionT> { public: explicit Not( Matcher const& matcher ) : m_matcher(matcher.clone()) {} Not( Not const& other ) : m_matcher( other.m_matcher ) {} virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE { return !m_matcher->match( expr ); } virtual std::string toString() const CATCH_OVERRIDE { return "not " + m_matcher->toString(); } private: Ptr< Matcher > m_matcher; }; template class AllOf : public MatcherImpl, ExpressionT> { public: AllOf() {} AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} AllOf& add( Matcher const& matcher ) { m_matchers.push_back( matcher.clone() ); return *this; } virtual bool match( ExpressionT const& expr ) const { for( std::size_t i = 0; i < m_matchers.size(); ++i ) if( !m_matchers[i]->match( expr ) ) return false; return true; } virtual std::string toString() const { std::ostringstream oss; oss << "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) oss << " and "; oss << m_matchers[i]->toString(); } oss << " )"; return oss.str(); } AllOf operator && ( Matcher const& other ) const { AllOf allOfExpr( *this ); allOfExpr.add( other ); return allOfExpr; } private: std::vector > > m_matchers; }; template class AnyOf : public MatcherImpl, ExpressionT> { public: AnyOf() {} AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} AnyOf& add( Matcher const& matcher ) { m_matchers.push_back( matcher.clone() ); return *this; } virtual bool match( ExpressionT const& expr ) const { for( std::size_t i = 0; i < m_matchers.size(); ++i ) if( m_matchers[i]->match( expr ) ) return true; return false; } virtual std::string toString() const { std::ostringstream oss; oss << "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) oss << " or "; oss << m_matchers[i]->toString(); } oss << " )"; return oss.str(); } AnyOf operator || ( Matcher const& other ) const { AnyOf anyOfExpr( *this ); anyOfExpr.add( other ); return anyOfExpr; } private: std::vector > > m_matchers; }; } // namespace Generic template Generic::AllOf Matcher::operator && ( Matcher const& other ) const { Generic::AllOf allOfExpr; allOfExpr.add( *this ); allOfExpr.add( other ); return allOfExpr; } template Generic::AnyOf Matcher::operator || ( Matcher const& other ) const { Generic::AnyOf anyOfExpr; anyOfExpr.add( *this ); anyOfExpr.add( other ); return anyOfExpr; } template Generic::Not Matcher::operator ! () const { return Generic::Not( *this ); } namespace StdString { inline std::string makeString( std::string const& str ) { return str; } inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } struct CasedString { CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), m_str( adjustString( str ) ) {} std::string adjustString( std::string const& str ) const { return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; } std::string toStringSuffix() const { return m_caseSensitivity == CaseSensitive::No ? " (case insensitive)" : ""; } CaseSensitive::Choice m_caseSensitivity; std::string m_str; }; struct Equals : MatcherImpl { Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( str, caseSensitivity ) {} Equals( Equals const& other ) : m_data( other.m_data ){} virtual ~Equals(); virtual bool match( std::string const& expr ) const { return m_data.m_str == m_data.adjustString( expr );; } virtual std::string toString() const { return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct Contains : MatcherImpl { Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} Contains( Contains const& other ) : m_data( other.m_data ){} virtual ~Contains(); virtual bool match( std::string const& expr ) const { return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos; } virtual std::string toString() const { return "contains: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct StartsWith : MatcherImpl { StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} StartsWith( StartsWith const& other ) : m_data( other.m_data ){} virtual ~StartsWith(); virtual bool match( std::string const& expr ) const { return startsWith( m_data.adjustString( expr ), m_data.m_str ); } virtual std::string toString() const { return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct EndsWith : MatcherImpl { EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} EndsWith( EndsWith const& other ) : m_data( other.m_data ){} virtual ~EndsWith(); virtual bool match( std::string const& expr ) const { return endsWith( m_data.adjustString( expr ), m_data.m_str ); } virtual std::string toString() const { return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; } // namespace StdString } // namespace Impl // The following functions create the actual matcher objects. // This allows the types to be inferred template inline Impl::Generic::Not Not( Impl::Matcher const& m ) { return Impl::Generic::Not( m ); } template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); } inline Impl::StdString::Equals Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Equals( str, caseSensitivity ); } inline Impl::StdString::Equals Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity ); } inline Impl::StdString::Contains Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Contains( substr, caseSensitivity ); } inline Impl::StdString::Contains Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity ); } inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { return Impl::StdString::StartsWith( substr ); } inline Impl::StdString::StartsWith StartsWith( const char* substr ) { return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); } inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { return Impl::StdString::EndsWith( substr ); } inline Impl::StdString::EndsWith EndsWith( const char* substr ) { return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); } } // namespace Matchers using namespace Matchers; } // namespace Catch namespace Catch { struct TestFailureException{}; template class ExpressionLhs; struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; struct CopyableStream { CopyableStream() {} CopyableStream( CopyableStream const& other ) { oss << other.oss.str(); } CopyableStream& operator=( CopyableStream const& other ) { oss.str(""); oss << other.oss.str(); return *this; } std::ostringstream oss; }; class ResultBuilder { public: ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition, char const* secondArg = "" ); template ExpressionLhs operator <= ( T const& operand ); ExpressionLhs operator <= ( bool value ); template ResultBuilder& operator << ( T const& value ) { m_stream.oss << value; return *this; } template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); ResultBuilder& setResultType( ResultWas::OfType result ); ResultBuilder& setResultType( bool result ); ResultBuilder& setLhs( std::string const& lhs ); ResultBuilder& setRhs( std::string const& rhs ); ResultBuilder& setOp( std::string const& op ); void endExpression(); std::string reconstructExpression() const; AssertionResult build() const; void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); void captureResult( ResultWas::OfType resultType ); void captureExpression(); void captureExpectedException( std::string const& expectedMessage ); void captureExpectedException( Matchers::Impl::Matcher const& matcher ); void handleResult( AssertionResult const& result ); void react(); bool shouldDebugBreak() const; bool allowThrows() const; private: AssertionInfo m_assertionInfo; AssertionResultData m_data; struct ExprComponents { ExprComponents() : testFalse( false ) {} bool testFalse; std::string lhs, rhs, op; } m_exprComponents; CopyableStream m_stream; bool m_shouldDebugBreak; bool m_shouldThrow; }; } // namespace Catch // Include after due to circular dependency: // #included from: catch_expression_lhs.hpp #define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED // #included from: catch_evaluate.hpp #define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4389) // '==' : signed/unsigned mismatch #endif #include namespace Catch { namespace Internal { enum Operator { IsEqualTo, IsNotEqualTo, IsLessThan, IsGreaterThan, IsLessThanOrEqualTo, IsGreaterThanOrEqualTo }; template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; template inline T& opCast(T const& t) { return const_cast(t); } // nullptr_t support based on pull request #154 from Konstantin Baumann #ifdef CATCH_CONFIG_CPP11_NULLPTR inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } #endif // CATCH_CONFIG_CPP11_NULLPTR // So the compare overloads can be operator agnostic we convey the operator as a template // enum, which is used to specialise an Evaluator for doing the comparison. template class Evaluator{}; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { return bool( opCast( lhs ) == opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) != opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) < opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) > opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) >= opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) <= opCast( rhs ) ); } }; template bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // This level of indirection allows us to specialise for integer types // to avoid signed/ unsigned warnings // "base" overload template bool compare( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // unsigned X to int template bool compare( unsigned int lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // unsigned X to long template bool compare( unsigned int lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // int to unsigned X template bool compare( int lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // long to unsigned X template bool compare( long lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // pointer to long (when comparing against NULL) template bool compare( long lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, long rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } // pointer to int (when comparing against NULL) template bool compare( int lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, int rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } #ifdef CATCH_CONFIG_CPP11_LONG_LONG // long long to unsigned X template bool compare( long long lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned long long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // unsigned long long to X template bool compare( unsigned long long lhs, int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, long long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // pointer to long long (when comparing against NULL) template bool compare( long long lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, long long rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } #endif // CATCH_CONFIG_CPP11_LONG_LONG #ifdef CATCH_CONFIG_CPP11_NULLPTR // pointer to nullptr_t (when comparing against nullptr) template bool compare( std::nullptr_t, T* rhs ) { return Evaluator::evaluate( nullptr, rhs ); } template bool compare( T* lhs, std::nullptr_t ) { return Evaluator::evaluate( lhs, nullptr ); } #endif // CATCH_CONFIG_CPP11_NULLPTR } // end of namespace Internal } // end of namespace Catch #ifdef _MSC_VER #pragma warning(pop) #endif // #included from: catch_tostring.h #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED #include #include #include #include #include #ifdef __OBJC__ // #included from: catch_objc_arc.hpp #define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED #import #ifdef __has_feature #define CATCH_ARC_ENABLED __has_feature(objc_arc) #else #define CATCH_ARC_ENABLED 0 #endif void arcSafeRelease( NSObject* obj ); id performOptionalSelector( id obj, SEL sel ); #if !CATCH_ARC_ENABLED inline void arcSafeRelease( NSObject* obj ) { [obj release]; } inline id performOptionalSelector( id obj, SEL sel ) { if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; return nil; } #define CATCH_UNSAFE_UNRETAINED #define CATCH_ARC_STRONG #else inline void arcSafeRelease( NSObject* ){} inline id performOptionalSelector( id obj, SEL sel ) { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" #endif if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; #ifdef __clang__ #pragma clang diagnostic pop #endif return nil; } #define CATCH_UNSAFE_UNRETAINED __unsafe_unretained #define CATCH_ARC_STRONG __strong #endif #endif #ifdef CATCH_CONFIG_CPP11_TUPLE #include #endif #ifdef CATCH_CONFIG_CPP11_IS_ENUM #include #endif namespace Catch { // Why we're here. template std::string toString( T const& value ); // Built in overloads std::string toString( std::string const& value ); std::string toString( std::wstring const& value ); std::string toString( const char* const value ); std::string toString( char* const value ); std::string toString( const wchar_t* const value ); std::string toString( wchar_t* const value ); std::string toString( int value ); std::string toString( unsigned long value ); std::string toString( unsigned int value ); std::string toString( const double value ); std::string toString( const float value ); std::string toString( bool value ); std::string toString( char value ); std::string toString( signed char value ); std::string toString( unsigned char value ); #ifdef CATCH_CONFIG_CPP11_LONG_LONG std::string toString( long long value ); std::string toString( unsigned long long value ); #endif #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ); #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ); std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); std::string toString( NSObject* const& nsObject ); #endif namespace Detail { extern const std::string unprintableString; struct BorgType { template BorgType( T const& ); }; struct TrueType { char sizer[1]; }; struct FalseType { char sizer[2]; }; TrueType& testStreamable( std::ostream& ); FalseType testStreamable( FalseType ); FalseType operator<<( std::ostream const&, BorgType const& ); template struct IsStreamInsertable { static std::ostream &s; static T const&t; enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; }; #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template::value > struct EnumStringMaker { static std::string convert( T const& ) { return unprintableString; } }; template struct EnumStringMaker { static std::string convert( T const& v ) { return ::Catch::toString( static_cast::type>(v) ); } }; #endif template struct StringMakerBase { #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template static std::string convert( T const& v ) { return EnumStringMaker::convert( v ); } #else template static std::string convert( T const& ) { return unprintableString; } #endif }; template<> struct StringMakerBase { template static std::string convert( T const& _value ) { std::ostringstream oss; oss << _value; return oss.str(); } }; std::string rawMemoryToString( const void *object, std::size_t size ); template inline std::string rawMemoryToString( const T& object ) { return rawMemoryToString( &object, sizeof(object) ); } } // end namespace Detail template struct StringMaker : Detail::StringMakerBase::value> {}; template struct StringMaker { template static std::string convert( U* p ) { if( !p ) return "NULL"; else return Detail::rawMemoryToString( p ); } }; template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) return "NULL"; else return Detail::rawMemoryToString( p ); } }; namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ); } //template //struct StringMaker > { // static std::string convert( std::vector const& v ) { // return Detail::rangeToString( v.begin(), v.end() ); // } //}; template std::string toString( std::vector const& v ) { return Detail::rangeToString( v.begin(), v.end() ); } #ifdef CATCH_CONFIG_CPP11_TUPLE // toString for tuples namespace TupleDetail { template< typename Tuple, std::size_t N = 0, bool = (N < std::tuple_size::value) > struct ElementPrinter { static void print( const Tuple& tuple, std::ostream& os ) { os << ( N ? ", " : " " ) << Catch::toString(std::get(tuple)); ElementPrinter::print(tuple,os); } }; template< typename Tuple, std::size_t N > struct ElementPrinter { static void print( const Tuple&, std::ostream& ) {} }; } template struct StringMaker> { static std::string convert( const std::tuple& tuple ) { std::ostringstream os; os << '{'; TupleDetail::ElementPrinter>::print( tuple, os ); os << " }"; return os.str(); } }; #endif // CATCH_CONFIG_CPP11_TUPLE namespace Detail { template std::string makeString( T const& value ) { return StringMaker::convert( value ); } } // end namespace Detail /// \brief converts any type to a string /// /// The default template forwards on to ostringstream - except when an /// ostringstream overload does not exist - in which case it attempts to detect /// that and writes {?}. /// Overload (not specialise) this template for custom typs that you don't want /// to provide an ostream overload for. template std::string toString( T const& value ) { return StringMaker::convert( value ); } namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ) { std::ostringstream oss; oss << "{ "; if( first != last ) { oss << Catch::toString( *first ); for( ++first ; first != last ; ++first ) oss << ", " << Catch::toString( *first ); } oss << " }"; return oss.str(); } } } // end namespace Catch namespace Catch { // Wraps the LHS of an expression and captures the operator and RHS (if any) - // wrapping them all in a ResultBuilder object template class ExpressionLhs { ExpressionLhs& operator = ( ExpressionLhs const& ); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS ExpressionLhs& operator = ( ExpressionLhs && ) = delete; # endif public: ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS ExpressionLhs( ExpressionLhs const& ) = default; ExpressionLhs( ExpressionLhs && ) = default; # endif template ResultBuilder& operator == ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator != ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator < ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator > ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator <= ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator >= ( RhsT const& rhs ) { return captureExpression( rhs ); } ResultBuilder& operator == ( bool rhs ) { return captureExpression( rhs ); } ResultBuilder& operator != ( bool rhs ) { return captureExpression( rhs ); } void endExpression() { bool value = m_lhs ? true : false; m_rb .setLhs( Catch::toString( value ) ) .setResultType( value ) .endExpression(); } // Only simple binary expressions are allowed on the LHS. // If more complex compositions are required then place the sub expression in parentheses template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); private: template ResultBuilder& captureExpression( RhsT const& rhs ) { return m_rb .setResultType( Internal::compare( m_lhs, rhs ) ) .setLhs( Catch::toString( m_lhs ) ) .setRhs( Catch::toString( rhs ) ) .setOp( Internal::OperatorTraits::getName() ); } private: ResultBuilder& m_rb; T m_lhs; }; } // end namespace Catch namespace Catch { template inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { return ExpressionLhs( *this, operand ); } inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { return ExpressionLhs( *this, value ); } } // namespace Catch // #included from: catch_message.h #define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED #include namespace Catch { struct MessageInfo { MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ); std::string macroName; SourceLineInfo lineInfo; ResultWas::OfType type; std::string message; unsigned int sequence; bool operator == ( MessageInfo const& other ) const { return sequence == other.sequence; } bool operator < ( MessageInfo const& other ) const { return sequence < other.sequence; } private: static unsigned int globalCount; }; struct MessageBuilder { MessageBuilder( std::string const& macroName, SourceLineInfo const& lineInfo, ResultWas::OfType type ) : m_info( macroName, lineInfo, type ) {} template MessageBuilder& operator << ( T const& value ) { m_stream << value; return *this; } MessageInfo m_info; std::ostringstream m_stream; }; class ScopedMessage { public: ScopedMessage( MessageBuilder const& builder ); ScopedMessage( ScopedMessage const& other ); ~ScopedMessage(); MessageInfo m_info; }; } // end namespace Catch // #included from: catch_interfaces_capture.h #define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED #include namespace Catch { class TestCase; class AssertionResult; struct AssertionInfo; struct SectionInfo; struct SectionEndInfo; struct MessageInfo; class ScopedMessageBuilder; struct Counts; struct IResultCapture { virtual ~IResultCapture(); virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; virtual void handleFatalErrorCondition( std::string const& message ) = 0; }; IResultCapture& getResultCapture(); } // #included from: catch_debugger.h #define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED // #included from: catch_platform.h #define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) # define CATCH_PLATFORM_MAC #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) # define CATCH_PLATFORM_IPHONE #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) # define CATCH_PLATFORM_WINDOWS # if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) # define CATCH_DEFINES_NOMINMAX # endif # if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) # define CATCH_DEFINES_WIN32_LEAN_AND_MEAN # endif #endif #include namespace Catch{ bool isDebuggerActive(); void writeToDebugConsole( std::string const& text ); } #ifdef CATCH_PLATFORM_MAC // The following code snippet based on: // http://cocoawithlove.com/2008/03/break-into-debugger.html #ifdef DEBUG #if defined(__ppc64__) || defined(__ppc__) #define CATCH_TRAP() \ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ : : : "memory","r0","r3","r4" ) #else #define CATCH_TRAP() _asm__("int $3\n" : : ) #endif #endif #elif defined(CATCH_PLATFORM_LINUX) // If we can use inline assembler, do it because this allows us to break // directly at the location of the failing check instead of breaking inside // raise() called from it, i.e. one stack frame below. #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) #define CATCH_TRAP() asm volatile ("int $3") #else // Fall back to the generic way. #include #define CATCH_TRAP() raise(SIGTRAP) #endif #elif defined(_MSC_VER) #define CATCH_TRAP() __debugbreak() #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); #define CATCH_TRAP() DebugBreak() #endif #ifdef CATCH_TRAP #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } #else #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); #endif // #included from: catch_interfaces_runner.h #define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED namespace Catch { class TestCase; struct IRunner { virtual ~IRunner(); virtual bool aborting() const = 0; }; } /////////////////////////////////////////////////////////////////////////////// // In the event of a failure works out if the debugger needs to be invoked // and/or an exception thrown and takes appropriate action. // This needs to be done as a macro so the debugger will stop in the user // source code rather than in Catch library code #define INTERNAL_CATCH_REACT( resultBuilder ) \ if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ resultBuilder.react(); /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ ( __catchResult <= expr ).endExpression(); \ } \ catch( ... ) { \ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse( sizeof(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ if( Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ if( !Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ __catchResult.captureExpectedException( matcher ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( exceptionType ) { \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #else #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << log + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #endif /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_INFO( log, macroName ) \ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ try { \ std::string matcherAsString = (matcher).toString(); \ __catchResult \ .setLhs( Catch::toString( arg ) ) \ .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ .setOp( "matches" ) \ .setResultType( (matcher).match( arg ) ); \ __catchResult.captureExpression(); \ } catch( ... ) { \ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) // #included from: internal/catch_section.h #define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED // #included from: catch_section_info.h #define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED // #included from: catch_totals.hpp #define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED #include namespace Catch { struct Counts { Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} Counts operator - ( Counts const& other ) const { Counts diff; diff.passed = passed - other.passed; diff.failed = failed - other.failed; diff.failedButOk = failedButOk - other.failedButOk; return diff; } Counts& operator += ( Counts const& other ) { passed += other.passed; failed += other.failed; failedButOk += other.failedButOk; return *this; } std::size_t total() const { return passed + failed + failedButOk; } bool allPassed() const { return failed == 0 && failedButOk == 0; } bool allOk() const { return failed == 0; } std::size_t passed; std::size_t failed; std::size_t failedButOk; }; struct Totals { Totals operator - ( Totals const& other ) const { Totals diff; diff.assertions = assertions - other.assertions; diff.testCases = testCases - other.testCases; return diff; } Totals delta( Totals const& prevTotals ) const { Totals diff = *this - prevTotals; if( diff.assertions.failed > 0 ) ++diff.testCases.failed; else if( diff.assertions.failedButOk > 0 ) ++diff.testCases.failedButOk; else ++diff.testCases.passed; return diff; } Totals& operator += ( Totals const& other ) { assertions += other.assertions; testCases += other.testCases; return *this; } Counts assertions; Counts testCases; }; } namespace Catch { struct SectionInfo { SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description = std::string() ); std::string name; std::string description; SourceLineInfo lineInfo; }; struct SectionEndInfo { SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) {} SectionInfo sectionInfo; Counts prevAssertions; double durationInSeconds; }; } // end namespace Catch // #included from: catch_timer.h #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED #ifdef CATCH_PLATFORM_WINDOWS typedef unsigned long long uint64_t; #else #include #endif namespace Catch { class Timer { public: Timer() : m_ticks( 0 ) {} void start(); unsigned int getElapsedMicroseconds() const; unsigned int getElapsedMilliseconds() const; double getElapsedSeconds() const; private: uint64_t m_ticks; }; } // namespace Catch #include namespace Catch { class Section : NonCopyable { public: Section( SectionInfo const& info ); ~Section(); // This indicates whether the section should be executed or not operator bool() const; private: SectionInfo m_info; std::string m_name; Counts m_assertions; bool m_sectionIncluded; Timer m_timer; }; } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_SECTION( ... ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) #else #define INTERNAL_CATCH_SECTION( name, desc ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) #endif // #included from: internal/catch_generators.hpp #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED #include #include #include #include namespace Catch { template struct IGenerator { virtual ~IGenerator() {} virtual T getValue( std::size_t index ) const = 0; virtual std::size_t size () const = 0; }; template class BetweenGenerator : public IGenerator { public: BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} virtual T getValue( std::size_t index ) const { return m_from+static_cast( index ); } virtual std::size_t size() const { return static_cast( 1+m_to-m_from ); } private: T m_from; T m_to; }; template class ValuesGenerator : public IGenerator { public: ValuesGenerator(){} void add( T value ) { m_values.push_back( value ); } virtual T getValue( std::size_t index ) const { return m_values[index]; } virtual std::size_t size() const { return m_values.size(); } private: std::vector m_values; }; template class CompositeGenerator { public: CompositeGenerator() : m_totalSize( 0 ) {} // *** Move semantics, similar to auto_ptr *** CompositeGenerator( CompositeGenerator& other ) : m_fileInfo( other.m_fileInfo ), m_totalSize( 0 ) { move( other ); } CompositeGenerator& setFileInfo( const char* fileInfo ) { m_fileInfo = fileInfo; return *this; } ~CompositeGenerator() { deleteAll( m_composed ); } operator T () const { size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); typename std::vector*>::const_iterator it = m_composed.begin(); typename std::vector*>::const_iterator itEnd = m_composed.end(); for( size_t index = 0; it != itEnd; ++it ) { const IGenerator* generator = *it; if( overallIndex >= index && overallIndex < index + generator->size() ) { return generator->getValue( overallIndex-index ); } index += generator->size(); } CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so } void add( const IGenerator* generator ) { m_totalSize += generator->size(); m_composed.push_back( generator ); } CompositeGenerator& then( CompositeGenerator& other ) { move( other ); return *this; } CompositeGenerator& then( T value ) { ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( value ); add( valuesGen ); return *this; } private: void move( CompositeGenerator& other ) { std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); m_totalSize += other.m_totalSize; other.m_composed.clear(); } std::vector*> m_composed; std::string m_fileInfo; size_t m_totalSize; }; namespace Generators { template CompositeGenerator between( T from, T to ) { CompositeGenerator generators; generators.add( new BetweenGenerator( from, to ) ); return generators; } template CompositeGenerator values( T val1, T val2 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3 ){ CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3, T val4 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); valuesGen->add( val4 ); generators.add( valuesGen ); return generators; } } // end namespace Generators using namespace Generators; } // end namespace Catch #define INTERNAL_CATCH_LINESTR2( line ) #line #define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) #define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) // #included from: internal/catch_interfaces_exception.h #define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED #include #include // #included from: catch_interfaces_registry_hub.h #define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED #include namespace Catch { class TestCase; struct ITestCaseRegistry; struct IExceptionTranslatorRegistry; struct IExceptionTranslator; struct IReporterRegistry; struct IReporterFactory; struct IRegistryHub { virtual ~IRegistryHub(); virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; }; struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; virtual void registerListener( Ptr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; }; IRegistryHub& getRegistryHub(); IMutableRegistryHub& getMutableRegistryHub(); void cleanUp(); std::string translateActiveException(); } namespace Catch { typedef std::string(*exceptionTranslateFunction)(); struct IExceptionTranslator; typedef std::vector ExceptionTranslators; struct IExceptionTranslator { virtual ~IExceptionTranslator(); virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; }; struct IExceptionTranslatorRegistry { virtual ~IExceptionTranslatorRegistry(); virtual std::string translateActiveException() const = 0; }; class ExceptionTranslatorRegistrar { template class ExceptionTranslator : public IExceptionTranslator { public: ExceptionTranslator( std::string(*translateFunction)( T& ) ) : m_translateFunction( translateFunction ) {} virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { try { if( it == itEnd ) throw; else return (*it)->translate( it+1, itEnd ); } catch( T& ex ) { return m_translateFunction( ex ); } } protected: std::string(*m_translateFunction)( T& ); }; public: template ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { getMutableRegistryHub().registerTranslator ( new ExceptionTranslator( translateFunction ) ); } }; } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ static std::string translatorName( signature ); \ namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ static std::string translatorName( signature ) #define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // #included from: internal/catch_approx.hpp #define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED #include #include namespace Catch { namespace Detail { class Approx { public: explicit Approx ( double value ) : m_epsilon( std::numeric_limits::epsilon()*100 ), m_scale( 1.0 ), m_value( value ) {} Approx( Approx const& other ) : m_epsilon( other.m_epsilon ), m_scale( other.m_scale ), m_value( other.m_value ) {} static Approx custom() { return Approx( 0 ); } Approx operator()( double value ) { Approx approx( value ); approx.epsilon( m_epsilon ); approx.scale( m_scale ); return approx; } friend bool operator == ( double lhs, Approx const& rhs ) { // Thanks to Richard Harris for his help refining this formula return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); } friend bool operator == ( Approx const& lhs, double rhs ) { return operator==( rhs, lhs ); } friend bool operator != ( double lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } friend bool operator != ( Approx const& lhs, double rhs ) { return !operator==( rhs, lhs ); } friend bool operator <= ( double lhs, Approx const& rhs ) { return lhs < rhs.m_value || lhs == rhs; } friend bool operator <= ( Approx const& lhs, double rhs ) { return lhs.m_value < rhs || lhs == rhs; } friend bool operator >= ( double lhs, Approx const& rhs ) { return lhs > rhs.m_value || lhs == rhs; } friend bool operator >= ( Approx const& lhs, double rhs ) { return lhs.m_value > rhs || lhs == rhs; } Approx& epsilon( double newEpsilon ) { m_epsilon = newEpsilon; return *this; } Approx& scale( double newScale ) { m_scale = newScale; return *this; } std::string toString() const { std::ostringstream oss; oss << "Approx( " << Catch::toString( m_value ) << " )"; return oss.str(); } private: double m_epsilon; double m_scale; double m_value; }; } template<> inline std::string toString( Detail::Approx const& value ) { return value.toString(); } } // end namespace Catch // #included from: internal/catch_interfaces_tag_alias_registry.h #define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED // #included from: catch_tag_alias.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED #include namespace Catch { struct TagAlias { TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} std::string tag; SourceLineInfo lineInfo; }; struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } // #included from: catch_option.hpp #define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED namespace Catch { // An optional type template class Option { public: Option() : nullableValue( CATCH_NULL ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) {} ~Option() { reset(); } Option& operator= ( Option const& _other ) { if( &_other != this ) { reset(); if( _other ) nullableValue = new( storage ) T( *_other ); } return *this; } Option& operator = ( T const& _value ) { reset(); nullableValue = new( storage ) T( _value ); return *this; } void reset() { if( nullableValue ) nullableValue->~T(); nullableValue = CATCH_NULL; } T& operator*() { return *nullableValue; } T const& operator*() const { return *nullableValue; } T* operator->() { return nullableValue; } const T* operator->() const { return nullableValue; } T valueOr( T const& defaultValue ) const { return nullableValue ? *nullableValue : defaultValue; } bool some() const { return nullableValue != CATCH_NULL; } bool none() const { return nullableValue == CATCH_NULL; } bool operator !() const { return nullableValue == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( some() ); } private: T* nullableValue; char storage[sizeof(T)]; }; } // end namespace Catch namespace Catch { struct ITagAliasRegistry { virtual ~ITagAliasRegistry(); virtual Option find( std::string const& alias ) const = 0; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; static ITagAliasRegistry const& get(); }; } // end namespace Catch // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections // #included from: internal/catch_test_case_info.h #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED #include #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { struct ITestCase; struct TestCaseInfo { enum SpecialProperties{ None = 0, IsHidden = 1 << 1, ShouldFail = 1 << 2, MayFail = 1 << 3, Throws = 1 << 4 }; TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ); TestCaseInfo( TestCaseInfo const& other ); friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); bool isHidden() const; bool throws() const; bool okToFail() const; bool expectedToFail() const; std::string name; std::string className; std::string description; std::set tags; std::set lcaseTags; std::string tagsAsString; SourceLineInfo lineInfo; SpecialProperties properties; }; class TestCase : public TestCaseInfo { public: TestCase( ITestCase* testCase, TestCaseInfo const& info ); TestCase( TestCase const& other ); TestCase withName( std::string const& _newName ) const; void invoke() const; TestCaseInfo const& getTestCaseInfo() const; void swap( TestCase& other ); bool operator == ( TestCase const& other ) const; bool operator < ( TestCase const& other ) const; TestCase& operator = ( TestCase const& other ); private: Ptr test; }; TestCase makeTestCase( ITestCase* testCase, std::string const& className, std::string const& name, std::string const& description, SourceLineInfo const& lineInfo ); } #ifdef __clang__ #pragma clang diagnostic pop #endif #ifdef __OBJC__ // #included from: internal/catch_objc.hpp #define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED #import #include // NB. Any general catch headers included here must be included // in catch.hpp first to make sure they are included by the single // header for non obj-usage /////////////////////////////////////////////////////////////////////////////// // This protocol is really only here for (self) documenting purposes, since // all its methods are optional. @protocol OcFixture @optional -(void) setUp; -(void) tearDown; @end namespace Catch { class OcMethod : public SharedImpl { public: OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} virtual void invoke() const { id obj = [[m_cls alloc] init]; performOptionalSelector( obj, @selector(setUp) ); performOptionalSelector( obj, m_sel ); performOptionalSelector( obj, @selector(tearDown) ); arcSafeRelease( obj ); } private: virtual ~OcMethod() {} Class m_cls; SEL m_sel; }; namespace Detail{ inline std::string getAnnotation( Class cls, std::string const& annotationName, std::string const& testCaseName ) { NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; SEL sel = NSSelectorFromString( selStr ); arcSafeRelease( selStr ); id value = performOptionalSelector( cls, sel ); if( value ) return [(NSString*)value UTF8String]; return ""; } } inline size_t registerTestMethods() { size_t noTestMethods = 0; int noClasses = objc_getClassList( CATCH_NULL, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); for( int c = 0; c < noClasses; c++ ) { Class cls = classes[c]; { u_int count; Method* methods = class_copyMethodList( cls, &count ); for( u_int m = 0; m < count ; m++ ) { SEL selector = method_getName(methods[m]); std::string methodName = sel_getName(selector); if( startsWith( methodName, "Catch_TestCase_" ) ) { std::string testCaseName = methodName.substr( 15 ); std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); const char* className = class_getName( cls ); getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); noTestMethods++; } } free(methods); } } return noTestMethods; } namespace Matchers { namespace Impl { namespace NSStringMatchers { template struct StringHolder : MatcherImpl{ StringHolder( NSString* substr ) : m_substr( [substr copy] ){} StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} StringHolder() { arcSafeRelease( m_substr ); } NSString* m_substr; }; struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } virtual std::string toString() const { return "equals string: " + Catch::toString( m_substr ); } }; struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } virtual std::string toString() const { return "contains string: " + Catch::toString( m_substr ); } }; struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } virtual std::string toString() const { return "starts with: " + Catch::toString( m_substr ); } }; struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } virtual std::string toString() const { return "ends with: " + Catch::toString( m_substr ); } }; } // namespace NSStringMatchers } // namespace Impl inline Impl::NSStringMatchers::Equals Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } inline Impl::NSStringMatchers::Contains Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } inline Impl::NSStringMatchers::StartsWith StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } inline Impl::NSStringMatchers::EndsWith EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } } // namespace Matchers using namespace Matchers; } // namespace Catch /////////////////////////////////////////////////////////////////////////////// #define OC_TEST_CASE( name, desc )\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ {\ return @ name; \ }\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ { \ return @ desc; \ } \ -(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) #endif #ifdef CATCH_IMPL // #included from: internal/catch_impl.hpp #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED // Collect all the implementation files together here // These are the equivalent of what would usually be cpp files #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wweak-vtables" #endif // #included from: ../catch_session.hpp #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED // #included from: internal/catch_commandline.hpp #define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED // #included from: catch_config.hpp #define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED // #included from: catch_test_spec_parser.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_test_spec.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_wildcard_pattern.hpp #define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED namespace Catch { class WildcardPattern { enum WildcardPosition { NoWildcard = 0, WildcardAtStart = 1, WildcardAtEnd = 2, WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd }; public: WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), m_wildcard( NoWildcard ), m_pattern( adjustCase( pattern ) ) { if( startsWith( m_pattern, "*" ) ) { m_pattern = m_pattern.substr( 1 ); m_wildcard = WildcardAtStart; } if( endsWith( m_pattern, "*" ) ) { m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); } } virtual ~WildcardPattern(); virtual bool matches( std::string const& str ) const { switch( m_wildcard ) { case NoWildcard: return m_pattern == adjustCase( str ); case WildcardAtStart: return endsWith( adjustCase( str ), m_pattern ); case WildcardAtEnd: return startsWith( adjustCase( str ), m_pattern ); case WildcardAtBothEnds: return contains( adjustCase( str ), m_pattern ); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" #endif throw std::logic_error( "Unknown enum" ); #ifdef __clang__ #pragma clang diagnostic pop #endif } private: std::string adjustCase( std::string const& str ) const { return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; } CaseSensitive::Choice m_caseSensitivity; WildcardPosition m_wildcard; std::string m_pattern; }; } #include #include namespace Catch { class TestSpec { struct Pattern : SharedImpl<> { virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; class NamePattern : public Pattern { public: NamePattern( std::string const& name ) : m_wildcardPattern( toLower( name ), CaseSensitive::No ) {} virtual ~NamePattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return m_wildcardPattern.matches( toLower( testCase.name ) ); } private: WildcardPattern m_wildcardPattern; }; class TagPattern : public Pattern { public: TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} virtual ~TagPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); } private: std::string m_tag; }; class ExcludedPattern : public Pattern { public: ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} virtual ~ExcludedPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } private: Ptr m_underlyingPattern; }; struct Filter { std::vector > m_patterns; bool matches( TestCaseInfo const& testCase ) const { // All patterns in a filter must match for the filter to be a match for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { if( !(*it)->matches( testCase ) ) return false; } return true; } }; public: bool hasFilters() const { return !m_filters.empty(); } bool matches( TestCaseInfo const& testCase ) const { // A TestSpec matches if any filter matches for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) if( it->matches( testCase ) ) return true; return false; } private: std::vector m_filters; friend class TestSpecParser; }; } #ifdef __clang__ #pragma clang diagnostic pop #endif namespace Catch { class TestSpecParser { enum Mode{ None, Name, QuotedName, Tag, EscapedName }; Mode m_mode; bool m_exclusion; std::size_t m_start, m_pos; std::string m_arg; std::vector m_escapeChars; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; ITagAliasRegistry const* m_tagAliases; public: TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} TestSpecParser& parse( std::string const& arg ) { m_mode = None; m_exclusion = false; m_start = std::string::npos; m_arg = m_tagAliases->expandAliases( arg ); m_escapeChars.clear(); for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) visitChar( m_arg[m_pos] ); if( m_mode == Name ) addPattern(); return *this; } TestSpec testSpec() { addFilter(); return m_testSpec; } private: void visitChar( char c ) { if( m_mode == None ) { switch( c ) { case ' ': return; case '~': m_exclusion = true; return; case '[': return startNewMode( Tag, ++m_pos ); case '"': return startNewMode( QuotedName, ++m_pos ); case '\\': return escape(); default: startNewMode( Name, m_pos ); break; } } if( m_mode == Name ) { if( c == ',' ) { addPattern(); addFilter(); } else if( c == '[' ) { if( subString() == "exclude:" ) m_exclusion = true; else addPattern(); startNewMode( Tag, ++m_pos ); } else if( c == '\\' ) escape(); } else if( m_mode == EscapedName ) m_mode = Name; else if( m_mode == QuotedName && c == '"' ) addPattern(); else if( m_mode == Tag && c == ']' ) addPattern(); } void startNewMode( Mode mode, std::size_t start ) { m_mode = mode; m_start = start; } void escape() { m_mode = EscapedName; m_escapeChars.push_back( m_pos ); } std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } template void addPattern() { std::string token = subString(); for( size_t i = 0; i < m_escapeChars.size(); ++i ) token = token.substr( 0, m_escapeChars[i] ) + token.substr( m_escapeChars[i]+1 ); m_escapeChars.clear(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); } if( !token.empty() ) { Ptr pattern = new T( token ); if( m_exclusion ) pattern = new TestSpec::ExcludedPattern( pattern ); m_currentFilter.m_patterns.push_back( pattern ); } m_exclusion = false; m_mode = None; } void addFilter() { if( !m_currentFilter.m_patterns.empty() ) { m_testSpec.m_filters.push_back( m_currentFilter ); m_currentFilter = TestSpec::Filter(); } } }; inline TestSpec parseTestSpec( std::string const& arg ) { return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_interfaces_config.h #define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED #include #include #include namespace Catch { struct Verbosity { enum Level { NoOutput = 0, Quiet, Normal }; }; struct WarnAbout { enum What { Nothing = 0x00, NoAssertions = 0x01 }; }; struct ShowDurations { enum OrNot { DefaultForReporter, Always, Never }; }; struct RunTests { enum InWhatOrder { InDeclarationOrder, InLexicographicalOrder, InRandomOrder }; }; struct UseColour { enum YesOrNo { Auto, Yes, No }; }; class TestSpec; struct IConfig : IShared { virtual ~IConfig(); virtual bool allowThrows() const = 0; virtual std::ostream& stream() const = 0; virtual std::string name() const = 0; virtual bool includeSuccessfulResults() const = 0; virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; virtual ShowDurations::OrNot showDurations() const = 0; virtual TestSpec const& testSpec() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; virtual UseColour::YesOrNo useColour() const = 0; }; } // #included from: catch_stream.h #define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED // #included from: catch_streambuf.h #define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED #include namespace Catch { class StreamBufBase : public std::streambuf { public: virtual ~StreamBufBase() CATCH_NOEXCEPT; }; } #include #include #include #include namespace Catch { std::ostream& cout(); std::ostream& cerr(); struct IStream { virtual ~IStream() CATCH_NOEXCEPT; virtual std::ostream& stream() const = 0; }; class FileStream : public IStream { mutable std::ofstream m_ofs; public: FileStream( std::string const& filename ); virtual ~FileStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; class CoutStream : public IStream { mutable std::ostream m_os; public: CoutStream(); virtual ~CoutStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; class DebugOutStream : public IStream { CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; mutable std::ostream m_os; public: DebugOutStream(); virtual ~DebugOutStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; } #include #include #include #include #include #ifndef CATCH_CONFIG_CONSOLE_WIDTH #define CATCH_CONFIG_CONSOLE_WIDTH 80 #endif namespace Catch { struct ConfigData { ConfigData() : listTests( false ), listTags( false ), listReporters( false ), listTestNamesOnly( false ), showSuccessfulTests( false ), shouldDebugBreak( false ), noThrow( false ), showHelp( false ), showInvisibles( false ), filenamesAsTags( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), warnings( WarnAbout::Nothing ), showDurations( ShowDurations::DefaultForReporter ), runOrder( RunTests::InDeclarationOrder ), useColour( UseColour::Auto ) {} bool listTests; bool listTags; bool listReporters; bool listTestNamesOnly; bool showSuccessfulTests; bool shouldDebugBreak; bool noThrow; bool showHelp; bool showInvisibles; bool filenamesAsTags; int abortAfter; unsigned int rngSeed; Verbosity::Level verbosity; WarnAbout::What warnings; ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; UseColour::YesOrNo useColour; std::string outputFilename; std::string name; std::string processName; std::vector reporterNames; std::vector testsOrTags; }; class Config : public SharedImpl { private: Config( Config const& other ); Config& operator = ( Config const& other ); virtual void dummy(); public: Config() {} Config( ConfigData const& data ) : m_data( data ), m_stream( openStream() ) { if( !data.testsOrTags.empty() ) { TestSpecParser parser( ITagAliasRegistry::get() ); for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) parser.parse( data.testsOrTags[i] ); m_testSpec = parser.testSpec(); } } virtual ~Config() { } std::string const& getFilename() const { return m_data.outputFilename ; } bool listTests() const { return m_data.listTests; } bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } bool listTags() const { return m_data.listTags; } bool listReporters() const { return m_data.listReporters; } std::string getProcessName() const { return m_data.processName; } bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } std::vector getReporterNames() const { return m_data.reporterNames; } int abortAfter() const { return m_data.abortAfter; } TestSpec const& testSpec() const { return m_testSpec; } bool showHelp() const { return m_data.showHelp; } bool showInvisibles() const { return m_data.showInvisibles; } // IConfig interface virtual bool allowThrows() const { return !m_data.noThrow; } virtual std::ostream& stream() const { return m_stream->stream(); } virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } virtual unsigned int rngSeed() const { return m_data.rngSeed; } virtual UseColour::YesOrNo useColour() const { return m_data.useColour; } private: IStream const* openStream() { if( m_data.outputFilename.empty() ) return new CoutStream(); else if( m_data.outputFilename[0] == '%' ) { if( m_data.outputFilename == "%debug" ) return new DebugOutStream(); else throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); } else return new FileStream( m_data.outputFilename ); } ConfigData m_data; CATCH_AUTO_PTR( IStream const ) m_stream; TestSpec m_testSpec; }; } // end namespace Catch // #included from: catch_clara.h #define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED // Use Catch's value for console width (store Clara's off to the side, if present) #ifdef CLARA_CONFIG_CONSOLE_WIDTH #define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH #undef CLARA_CONFIG_CONSOLE_WIDTH #endif #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH // Declare Clara inside the Catch namespace #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { // #included from: ../external/clara.h // Version 0.0.2.4 // Only use header guard if we are not using an outer namespace #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) #ifndef STITCH_CLARA_OPEN_NAMESPACE #define TWOBLUECUBES_CLARA_H_INCLUDED #define STITCH_CLARA_OPEN_NAMESPACE #define STITCH_CLARA_CLOSE_NAMESPACE #else #define STITCH_CLARA_CLOSE_NAMESPACE } #endif #define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE // ----------- #included from tbc_text_format.h ----------- // Only use header guard if we are not using an outer namespace #if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) #ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE #define TBC_TEXT_FORMAT_H_INCLUDED #endif #include #include #include #include // Use optional outer namespace #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TBC_TEXT_FORMAT_H_INCLUDED // ----------- end of #include from tbc_text_format.h ----------- // ........... back in clara.h #undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE // ----------- #included from clara_compilers.h ----------- #ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED #define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED // Detect a number of compiler features - mostly C++11/14 conformance - by compiler // The following features are defined: // // CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? // CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? // CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CLARA_CONFIG_CPP11_OVERRIDE : is override supported? // CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? // In general each macro has a _NO_ form // (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. // All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 #ifdef __clang__ #if __has_feature(cxx_nullptr) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif #if __has_feature(cxx_noexcept) #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ #if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #if (_MSC_VER >= 1600) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 #if defined(__cplusplus) && __cplusplus >= 201103L #define CLARA_CPP11_OR_GREATER #if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif #ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #endif #ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) #define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE #endif #if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) #define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured #if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_NULLPTR #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_NOEXCEPT #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_GENERATED_METHODS #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_OVERRIDE #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_UNIQUE_PTR #endif // noexcept support: #if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) #define CLARA_NOEXCEPT noexcept # define CLARA_NOEXCEPT_IS(x) noexcept(x) #else #define CLARA_NOEXCEPT throw() # define CLARA_NOEXCEPT_IS(x) #endif // nullptr support #ifdef CLARA_CONFIG_CPP11_NULLPTR #define CLARA_NULL nullptr #else #define CLARA_NULL NULL #endif // override support #ifdef CLARA_CONFIG_CPP11_OVERRIDE #define CLARA_OVERRIDE override #else #define CLARA_OVERRIDE #endif // unique_ptr support #ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR # define CLARA_AUTO_PTR( T ) std::unique_ptr #else # define CLARA_AUTO_PTR( T ) std::auto_ptr #endif #endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED // ----------- end of #include from clara_compilers.h ----------- // ........... back in clara.h #include #include #include #if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) #define CLARA_PLATFORM_WINDOWS #endif // Use optional outer namespace #ifdef STITCH_CLARA_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE #endif namespace Clara { struct UnpositionalTag {}; extern UnpositionalTag _; #ifdef CLARA_CONFIG_MAIN UnpositionalTag _; #endif namespace Detail { #ifdef CLARA_CONSOLE_WIDTH const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif using namespace Tbc; inline bool startsWith( std::string const& str, std::string const& prefix ) { return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; } template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct IsBool { static const bool value = false; }; template<> struct IsBool { static const bool value = true; }; template void convertInto( std::string const& _source, T& _dest ) { std::stringstream ss; ss << _source; ss >> _dest; if( ss.fail() ) throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); } inline void convertInto( std::string const& _source, std::string& _dest ) { _dest = _source; } char toLowerCh(char c) { return static_cast( ::tolower( c ) ); } inline void convertInto( std::string const& _source, bool& _dest ) { std::string sourceLC = _source; std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) _dest = true; else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) _dest = false; else throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); } template struct IArgFunction { virtual ~IArgFunction() {} #ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS IArgFunction() = default; IArgFunction( IArgFunction const& ) = default; #endif virtual void set( ConfigT& config, std::string const& value ) const = 0; virtual bool takesArg() const = 0; virtual IArgFunction* clone() const = 0; }; template class BoundArgFunction { public: BoundArgFunction() : functionObj( CLARA_NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; delete functionObj; functionObj = newFunctionObj; return *this; } ~BoundArgFunction() { delete functionObj; } void set( ConfigT& config, std::string const& value ) const { functionObj->set( config, value ); } bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { return functionObj != CLARA_NULL; } private: IArgFunction* functionObj; }; template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; template struct BoundDataMember : IArgFunction{ BoundDataMember( M C::* _member ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { convertInto( stringValue, p.*member ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; }; template struct BoundUnaryMethod : IArgFunction{ BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); (p.*member)( value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); }; template struct BoundNullaryMethod : IArgFunction{ BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) (p.*member)(); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); }; template struct BoundUnaryFunction : IArgFunction{ BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) function( obj ); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); }; template struct BoundBinaryFunction : IArgFunction{ BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); function( obj, value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } void (*function)( C&, T ); }; } // namespace Detail inline std::vector argsToVector( int argc, char const* const* const argv ) { std::vector args( static_cast( argc ) ); for( std::size_t i = 0; i < static_cast( argc ); ++i ) args[i] = argv[i]; return args; } class Parser { enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; Mode mode; std::size_t from; bool inQuotes; public: struct Token { enum Type { Positional, ShortOpt, LongOpt }; Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} Type type; std::string data; }; Parser() : mode( None ), from( 0 ), inQuotes( false ){} void parseIntoTokens( std::vector const& args, std::vector& tokens ) { const std::string doubleDash = "--"; for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) parseIntoTokens( args[i], tokens); } void parseIntoTokens( std::string const& arg, std::vector& tokens ) { for( std::size_t i = 0; i <= arg.size(); ++i ) { char c = arg[i]; if( c == '"' ) inQuotes = !inQuotes; mode = handleMode( i, c, arg, tokens ); } } Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { switch( mode ) { case None: return handleNone( i, c ); case MaybeShortOpt: return handleMaybeShortOpt( i, c ); case ShortOpt: case LongOpt: case SlashOpt: return handleOpt( i, c, arg, tokens ); case Positional: return handlePositional( i, c, arg, tokens ); default: throw std::logic_error( "Unknown mode" ); } } Mode handleNone( std::size_t i, char c ) { if( inQuotes ) { from = i; return Positional; } switch( c ) { case '-': return MaybeShortOpt; #ifdef CLARA_PLATFORM_WINDOWS case '/': from = i+1; return SlashOpt; #endif default: from = i; return Positional; } } Mode handleMaybeShortOpt( std::size_t i, char c ) { switch( c ) { case '-': from = i+1; return LongOpt; default: from = i; return ShortOpt; } } Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) return mode; std::string optName = arg.substr( from, i-from ); if( mode == ShortOpt ) for( std::size_t j = 0; j < optName.size(); ++j ) tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); else if( mode == SlashOpt && optName.size() == 1 ) tokens.push_back( Token( Token::ShortOpt, optName ) ); else tokens.push_back( Token( Token::LongOpt, optName ) ); return None; } Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) return mode; std::string data = arg.substr( from, i-from ); tokens.push_back( Token( Token::Positional, data ) ); return None; } }; template struct CommonArgProperties { CommonArgProperties() {} CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} Detail::BoundArgFunction boundField; std::string description; std::string detail; std::string placeholder; // Only value if boundField takes an arg bool takesArg() const { return !placeholder.empty(); } void validate() const { if( !boundField.isSet() ) throw std::logic_error( "option not bound" ); } }; struct OptionArgProperties { std::vector shortNames; std::string longName; bool hasShortName( std::string const& shortName ) const { return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); } bool hasLongName( std::string const& _longName ) const { return _longName == longName; } }; struct PositionalArgProperties { PositionalArgProperties() : position( -1 ) {} int position; // -1 means non-positional (floating) bool isFixedPositional() const { return position != -1; } }; template class CommandLine { struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { Arg() {} Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} using CommonArgProperties::placeholder; // !TBD std::string dbgName() const { if( !longName.empty() ) return "--" + longName; if( !shortNames.empty() ) return "-" + shortNames[0]; return "positional args"; } std::string commands() const { std::ostringstream oss; bool first = true; std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); for(; it != itEnd; ++it ) { if( first ) first = false; else oss << ", "; oss << "-" << *it; } if( !longName.empty() ) { if( !first ) oss << ", "; oss << "--" << longName; } if( !placeholder.empty() ) oss << " <" << placeholder << ">"; return oss.str(); } }; typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; friend void addOptName( Arg& arg, std::string const& optName ) { if( optName.empty() ) return; if( Detail::startsWith( optName, "--" ) ) { if( !arg.longName.empty() ) throw std::logic_error( "Only one long opt may be specified. '" + arg.longName + "' already specified, now attempting to add '" + optName + "'" ); arg.longName = optName.substr( 2 ); } else if( Detail::startsWith( optName, "-" ) ) arg.shortNames.push_back( optName.substr( 1 ) ); else throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); } friend void setPositionalArg( Arg& arg, int position ) { arg.position = position; } class ArgBuilder { public: ArgBuilder( Arg* arg ) : m_arg( arg ) {} // Bind a non-boolean data member (requires placeholder string) template void bind( M C::* field, std::string const& placeholder ) { m_arg->boundField = new Detail::BoundDataMember( field ); m_arg->placeholder = placeholder; } // Bind a boolean data member (no placeholder required) template void bind( bool C::* field ) { m_arg->boundField = new Detail::BoundDataMember( field ); } // Bind a method taking a single, non-boolean argument (requires a placeholder string) template void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); m_arg->placeholder = placeholder; } // Bind a method taking a single, boolean argument (no placeholder string required) template void bind( void (C::* unaryMethod)( bool ) ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); } // Bind a method that takes no arguments (will be called if opt is present) template void bind( void (C::* nullaryMethod)() ) { m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); } // Bind a free function taking a single argument - the object to operate on (no placeholder string required) template void bind( void (* unaryFunction)( C& ) ) { m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); } // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) template void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); m_arg->placeholder = placeholder; } ArgBuilder& describe( std::string const& description ) { m_arg->description = description; return *this; } ArgBuilder& detail( std::string const& detail ) { m_arg->detail = detail; return *this; } protected: Arg* m_arg; }; class OptBuilder : public ArgBuilder { public: OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} OptBuilder& operator[]( std::string const& optName ) { addOptName( *ArgBuilder::m_arg, optName ); return *this; } }; public: CommandLine() : m_boundProcessName( new Detail::NullBinder() ), m_highestSpecifiedArgPosition( 0 ), m_throwOnUnrecognisedTokens( false ) {} CommandLine( CommandLine const& other ) : m_boundProcessName( other.m_boundProcessName ), m_options ( other.m_options ), m_positionalArgs( other.m_positionalArgs ), m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) { if( other.m_floatingArg.get() ) m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); } CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { m_throwOnUnrecognisedTokens = shouldThrow; return *this; } OptBuilder operator[]( std::string const& optName ) { m_options.push_back( Arg() ); addOptName( m_options.back(), optName ); OptBuilder builder( &m_options.back() ); return builder; } ArgBuilder operator[]( int position ) { m_positionalArgs.insert( std::make_pair( position, Arg() ) ); if( position > m_highestSpecifiedArgPosition ) m_highestSpecifiedArgPosition = position; setPositionalArg( m_positionalArgs[position], position ); ArgBuilder builder( &m_positionalArgs[position] ); return builder; } // Invoke this with the _ instance ArgBuilder operator[]( UnpositionalTag ) { if( m_floatingArg.get() ) throw std::logic_error( "Only one unpositional argument can be added" ); m_floatingArg.reset( new Arg() ); ArgBuilder builder( m_floatingArg.get() ); return builder; } template void bindProcessName( M C::* field ) { m_boundProcessName = new Detail::BoundDataMember( field ); } template void bindProcessName( void (C::*_unaryMethod)( M ) ) { m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); } void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; std::size_t maxWidth = 0; for( it = itBegin; it != itEnd; ++it ) maxWidth = (std::max)( maxWidth, it->commands().size() ); for( it = itBegin; it != itEnd; ++it ) { Detail::Text usage( it->commands(), Detail::TextAttributes() .setWidth( maxWidth+indent ) .setIndent( indent ) ); Detail::Text desc( it->description, Detail::TextAttributes() .setWidth( width - maxWidth - 3 ) ); for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { std::string usageCol = i < usage.size() ? usage[i] : ""; os << usageCol; if( i < desc.size() && !desc[i].empty() ) os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) << desc[i]; os << "\n"; } } } std::string optUsage() const { std::ostringstream oss; optUsage( oss ); return oss.str(); } void argSynopsis( std::ostream& os ) const { for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { if( i > 1 ) os << " "; typename std::map::const_iterator it = m_positionalArgs.find( i ); if( it != m_positionalArgs.end() ) os << "<" << it->second.placeholder << ">"; else if( m_floatingArg.get() ) os << "<" << m_floatingArg->placeholder << ">"; else throw std::logic_error( "non consecutive positional arguments with no floating args" ); } // !TBD No indication of mandatory args if( m_floatingArg.get() ) { if( m_highestSpecifiedArgPosition > 1 ) os << " "; os << "[<" << m_floatingArg->placeholder << "> ...]"; } } std::string argSynopsis() const { std::ostringstream oss; argSynopsis( oss ); return oss.str(); } void usage( std::ostream& os, std::string const& procName ) const { validate(); os << "usage:\n " << procName << " "; argSynopsis( os ); if( !m_options.empty() ) { os << " [options]\n\nwhere options are: \n"; optUsage( os, 2 ); } os << "\n"; } std::string usage( std::string const& procName ) const { std::ostringstream oss; usage( oss, procName ); return oss.str(); } ConfigT parse( std::vector const& args ) const { ConfigT config; parseInto( args, config ); return config; } std::vector parseInto( std::vector const& args, ConfigT& config ) const { std::string processName = args[0]; std::size_t lastSlash = processName.find_last_of( "/\\" ); if( lastSlash != std::string::npos ) processName = processName.substr( lastSlash+1 ); m_boundProcessName.set( config, processName ); std::vector tokens; Parser parser; parser.parseIntoTokens( args, tokens ); return populate( tokens, config ); } std::vector populate( std::vector const& tokens, ConfigT& config ) const { validate(); std::vector unusedTokens = populateOptions( tokens, config ); unusedTokens = populateFixedArgs( unusedTokens, config ); unusedTokens = populateFloatingArgs( unusedTokens, config ); return unusedTokens; } std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; std::vector errors; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); for(; it != itEnd; ++it ) { Arg const& arg = *it; try { if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { if( arg.takesArg() ) { if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) errors.push_back( "Expected argument to option: " + token.data ); else arg.boundField.set( config, tokens[++i].data ); } else { arg.boundField.set( config, "true" ); } break; } } catch( std::exception& ex ) { errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); } } if( it == itEnd ) { if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) unusedTokens.push_back( token ); else if( errors.empty() && m_throwOnUnrecognisedTokens ) errors.push_back( "unrecognised option: " + token.data ); } } if( !errors.empty() ) { std::ostringstream oss; for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); it != itEnd; ++it ) { if( it != errors.begin() ) oss << "\n"; oss << *it; } throw std::runtime_error( oss.str() ); } return unusedTokens; } std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; int position = 1; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::map::const_iterator it = m_positionalArgs.find( position ); if( it != m_positionalArgs.end() ) it->second.boundField.set( config, token.data ); else unusedTokens.push_back( token ); if( token.type == Parser::Token::Positional ) position++; } return unusedTokens; } std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { if( !m_floatingArg.get() ) return tokens; std::vector unusedTokens; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; if( token.type == Parser::Token::Positional ) m_floatingArg->boundField.set( config, token.data ); else unusedTokens.push_back( token ); } return unusedTokens; } void validate() const { if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) throw std::logic_error( "No options or arguments specified" ); for( typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); it != itEnd; ++it ) it->validate(); } private: Detail::BoundArgFunction m_boundProcessName; std::vector m_options; std::map m_positionalArgs; ArgAutoPtr m_floatingArg; int m_highestSpecifiedArgPosition; bool m_throwOnUnrecognisedTokens; }; } // end namespace Clara STITCH_CLARA_CLOSE_NAMESPACE #undef STITCH_CLARA_OPEN_NAMESPACE #undef STITCH_CLARA_CLOSE_NAMESPACE #endif // TWOBLUECUBES_CLARA_H_INCLUDED #undef STITCH_CLARA_OPEN_NAMESPACE // Restore Clara's value for console width, if present #ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #endif #include namespace Catch { inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } inline void abortAfterX( ConfigData& config, int x ) { if( x < 1 ) throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); config.abortAfter = x; } inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); else throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); } inline void setOrder( ConfigData& config, std::string const& order ) { if( startsWith( "declared", order ) ) config.runOrder = RunTests::InDeclarationOrder; else if( startsWith( "lexical", order ) ) config.runOrder = RunTests::InLexicographicalOrder; else if( startsWith( "random", order ) ) config.runOrder = RunTests::InRandomOrder; else throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); } inline void setRngSeed( ConfigData& config, std::string const& seed ) { if( seed == "time" ) { config.rngSeed = static_cast( std::time(0) ); } else { std::stringstream ss; ss << seed; ss >> config.rngSeed; if( ss.fail() ) throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); } } inline void setVerbosity( ConfigData& config, int level ) { // !TBD: accept strings? config.verbosity = static_cast( level ); } inline void setShowDurations( ConfigData& config, bool _showDurations ) { config.showDurations = _showDurations ? ShowDurations::Always : ShowDurations::Never; } inline void setUseColour( ConfigData& config, std::string const& value ) { std::string mode = toLower( value ); if( mode == "yes" ) config.useColour = UseColour::Yes; else if( mode == "no" ) config.useColour = UseColour::No; else if( mode == "auto" ) config.useColour = UseColour::Auto; else throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); } inline void forceColour( ConfigData& config ) { config.useColour = UseColour::Yes; } inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { std::ifstream f( _filename.c_str() ); if( !f.is_open() ) throw std::domain_error( "Unable to load input file: " + _filename ); std::string line; while( std::getline( f, line ) ) { line = trim(line); if( !line.empty() && !startsWith( line, "#" ) ) { if( !startsWith( line, "\"" ) ) line = "\"" + line + "\""; addTestOrTags( config, line + "," ); } } } inline Clara::CommandLine makeCommandLineParser() { using namespace Clara; CommandLine cli; cli.bindProcessName( &ConfigData::processName ); cli["-?"]["-h"]["--help"] .describe( "display usage information" ) .bind( &ConfigData::showHelp ); cli["-l"]["--list-tests"] .describe( "list all/matching test cases" ) .bind( &ConfigData::listTests ); cli["-t"]["--list-tags"] .describe( "list all/matching tags" ) .bind( &ConfigData::listTags ); cli["-s"]["--success"] .describe( "include successful tests in output" ) .bind( &ConfigData::showSuccessfulTests ); cli["-b"]["--break"] .describe( "break into debugger on failure" ) .bind( &ConfigData::shouldDebugBreak ); cli["-e"]["--nothrow"] .describe( "skip exception tests" ) .bind( &ConfigData::noThrow ); cli["-i"]["--invisibles"] .describe( "show invisibles (tabs, newlines)" ) .bind( &ConfigData::showInvisibles ); cli["-o"]["--out"] .describe( "output filename" ) .bind( &ConfigData::outputFilename, "filename" ); cli["-r"]["--reporter"] // .placeholder( "name[:filename]" ) .describe( "reporter to use (defaults to console)" ) .bind( &addReporterName, "name" ); cli["-n"]["--name"] .describe( "suite name" ) .bind( &ConfigData::name, "name" ); cli["-a"]["--abort"] .describe( "abort at first failure" ) .bind( &abortAfterFirst ); cli["-x"]["--abortx"] .describe( "abort after x failures" ) .bind( &abortAfterX, "no. failures" ); cli["-w"]["--warn"] .describe( "enable warnings" ) .bind( &addWarning, "warning name" ); // - needs updating if reinstated // cli.into( &setVerbosity ) // .describe( "level of verbosity (0=no output)" ) // .shortOpt( "v") // .longOpt( "verbosity" ) // .placeholder( "level" ); cli[_] .describe( "which test or tests to use" ) .bind( &addTestOrTags, "test name, pattern or tags" ); cli["-d"]["--durations"] .describe( "show test durations" ) .bind( &setShowDurations, "yes|no" ); cli["-f"]["--input-file"] .describe( "load test names to run from a file" ) .bind( &loadTestNamesFromFile, "filename" ); cli["-#"]["--filenames-as-tags"] .describe( "adds a tag for the filename" ) .bind( &ConfigData::filenamesAsTags ); // Less common commands which don't have a short form cli["--list-test-names-only"] .describe( "list all/matching test cases names only" ) .bind( &ConfigData::listTestNamesOnly ); cli["--list-reporters"] .describe( "list all reporters" ) .bind( &ConfigData::listReporters ); cli["--order"] .describe( "test case order (defaults to decl)" ) .bind( &setOrder, "decl|lex|rand" ); cli["--rng-seed"] .describe( "set a specific seed for random numbers" ) .bind( &setRngSeed, "'time'|number" ); cli["--force-colour"] .describe( "force colourised output (deprecated)" ) .bind( &forceColour ); cli["--use-colour"] .describe( "should output be colourised" ) .bind( &setUseColour, "yes|no" ); return cli; } } // end namespace Catch // #included from: internal/catch_list.hpp #define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED // #included from: catch_text.h #define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED #define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH #define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch // #included from: ../external/tbc_text_format.h // Only use header guard if we are not using an outer namespace #ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE # ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # endif # else # define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # endif #endif #ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #include #include #include // Use optional outer namespace #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace Catch { using Tbc::Text; using Tbc::TextAttributes; } // #included from: catch_console_colour.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED namespace Catch { struct Colour { enum Code { None = 0, White, Red, Green, Blue, Cyan, Yellow, Grey, Bright = 0x10, BrightRed = Bright | Red, BrightGreen = Bright | Green, LightGrey = Bright | Grey, BrightWhite = Bright | White, // By intention FileName = LightGrey, Warning = Yellow, ResultError = BrightRed, ResultSuccess = BrightGreen, ResultExpectedFailure = Warning, Error = BrightRed, Success = Green, OriginalExpression = Cyan, ReconstructedExpression = Yellow, SecondaryText = LightGrey, Headers = White }; // Use constructed object for RAII guard Colour( Code _colourCode ); Colour( Colour const& other ); ~Colour(); // Use static method for one-shot changes static void use( Code _colourCode ); private: bool m_moved; }; inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } } // end namespace Catch // #included from: catch_interfaces_reporter.h #define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED #include #include #include #include namespace Catch { struct ReporterConfig { explicit ReporterConfig( Ptr const& _fullConfig ) : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} std::ostream& stream() const { return *m_stream; } Ptr fullConfig() const { return m_fullConfig; } private: std::ostream* m_stream; Ptr m_fullConfig; }; struct ReporterPreferences { ReporterPreferences() : shouldRedirectStdOut( false ) {} bool shouldRedirectStdOut; }; template struct LazyStat : Option { LazyStat() : used( false ) {} LazyStat& operator=( T const& _value ) { Option::operator=( _value ); used = false; return *this; } void reset() { Option::reset(); used = false; } bool used; }; struct TestRunInfo { TestRunInfo( std::string const& _name ) : name( _name ) {} std::string name; }; struct GroupInfo { GroupInfo( std::string const& _name, std::size_t _groupIndex, std::size_t _groupsCount ) : name( _name ), groupIndex( _groupIndex ), groupsCounts( _groupsCount ) {} std::string name; std::size_t groupIndex; std::size_t groupsCounts; }; struct AssertionStats { AssertionStats( AssertionResult const& _assertionResult, std::vector const& _infoMessages, Totals const& _totals ) : assertionResult( _assertionResult ), infoMessages( _infoMessages ), totals( _totals ) { if( assertionResult.hasMessage() ) { // Copy message into messages list. // !TBD This should have been done earlier, somewhere MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); builder << assertionResult.getMessage(); builder.m_info.message = builder.m_stream.str(); infoMessages.push_back( builder.m_info ); } } virtual ~AssertionStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionStats( AssertionStats const& ) = default; AssertionStats( AssertionStats && ) = default; AssertionStats& operator = ( AssertionStats const& ) = default; AssertionStats& operator = ( AssertionStats && ) = default; # endif AssertionResult assertionResult; std::vector infoMessages; Totals totals; }; struct SectionStats { SectionStats( SectionInfo const& _sectionInfo, Counts const& _assertions, double _durationInSeconds, bool _missingAssertions ) : sectionInfo( _sectionInfo ), assertions( _assertions ), durationInSeconds( _durationInSeconds ), missingAssertions( _missingAssertions ) {} virtual ~SectionStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SectionStats( SectionStats const& ) = default; SectionStats( SectionStats && ) = default; SectionStats& operator = ( SectionStats const& ) = default; SectionStats& operator = ( SectionStats && ) = default; # endif SectionInfo sectionInfo; Counts assertions; double durationInSeconds; bool missingAssertions; }; struct TestCaseStats { TestCaseStats( TestCaseInfo const& _testInfo, Totals const& _totals, std::string const& _stdOut, std::string const& _stdErr, bool _aborting ) : testInfo( _testInfo ), totals( _totals ), stdOut( _stdOut ), stdErr( _stdErr ), aborting( _aborting ) {} virtual ~TestCaseStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestCaseStats( TestCaseStats const& ) = default; TestCaseStats( TestCaseStats && ) = default; TestCaseStats& operator = ( TestCaseStats const& ) = default; TestCaseStats& operator = ( TestCaseStats && ) = default; # endif TestCaseInfo testInfo; Totals totals; std::string stdOut; std::string stdErr; bool aborting; }; struct TestGroupStats { TestGroupStats( GroupInfo const& _groupInfo, Totals const& _totals, bool _aborting ) : groupInfo( _groupInfo ), totals( _totals ), aborting( _aborting ) {} TestGroupStats( GroupInfo const& _groupInfo ) : groupInfo( _groupInfo ), aborting( false ) {} virtual ~TestGroupStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestGroupStats( TestGroupStats const& ) = default; TestGroupStats( TestGroupStats && ) = default; TestGroupStats& operator = ( TestGroupStats const& ) = default; TestGroupStats& operator = ( TestGroupStats && ) = default; # endif GroupInfo groupInfo; Totals totals; bool aborting; }; struct TestRunStats { TestRunStats( TestRunInfo const& _runInfo, Totals const& _totals, bool _aborting ) : runInfo( _runInfo ), totals( _totals ), aborting( _aborting ) {} virtual ~TestRunStats(); # ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS TestRunStats( TestRunStats const& _other ) : runInfo( _other.runInfo ), totals( _other.totals ), aborting( _other.aborting ) {} # else TestRunStats( TestRunStats const& ) = default; TestRunStats( TestRunStats && ) = default; TestRunStats& operator = ( TestRunStats const& ) = default; TestRunStats& operator = ( TestRunStats && ) = default; # endif TestRunInfo runInfo; Totals totals; bool aborting; }; class MultipleReporters; struct IStreamingReporter : IShared { virtual ~IStreamingReporter(); // Implementing class must also provide the following static method: // static std::string getDescription(); virtual ReporterPreferences getPreferences() const = 0; virtual void noMatchingTestCases( std::string const& spec ) = 0; virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; virtual void skipTest( TestCaseInfo const& testInfo ) = 0; virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } }; struct IReporterFactory : IShared { virtual ~IReporterFactory(); virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; virtual std::string getDescription() const = 0; }; struct IReporterRegistry { typedef std::map > FactoryMap; typedef std::vector > Listeners; virtual ~IReporterRegistry(); virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; virtual Listeners const& getListeners() const = 0; }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); } #include #include namespace Catch { inline std::size_t listTests( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Matching test cases:\n"; else { Catch::cout() << "All available test cases:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::size_t matchedTests = 0; TextAttributes nameAttr, tagsAttr; nameAttr.setInitialIndent( 2 ).setIndent( 4 ); tagsAttr.setIndent( 6 ); std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); Colour::Code colour = testCaseInfo.isHidden() ? Colour::SecondaryText : Colour::None; Colour colourGuard( colour ); Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; if( !testCaseInfo.tags.empty() ) Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; } if( !config.testSpec().hasFilters() ) Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; else Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; return matchedTests; } inline std::size_t listTestsNamesOnly( Config const& config ) { TestSpec testSpec = config.testSpec(); if( !config.testSpec().hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); if( startsWith( testCaseInfo.name, "#" ) ) Catch::cout() << "\"" << testCaseInfo.name << "\"" << std::endl; else Catch::cout() << testCaseInfo.name << std::endl; } return matchedTests; } struct TagInfo { TagInfo() : count ( 0 ) {} void add( std::string const& spelling ) { ++count; spellings.insert( spelling ); } std::string all() const { std::string out; for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); it != itEnd; ++it ) out += "[" + *it + "]"; return out; } std::set spellings; std::size_t count; }; inline std::size_t listTags( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Tags for matching test cases:\n"; else { Catch::cout() << "All available tags:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::map tagCounts; std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), tagItEnd = it->getTestCaseInfo().tags.end(); tagIt != tagItEnd; ++tagIt ) { std::string tagName = *tagIt; std::string lcaseTagName = toLower( tagName ); std::map::iterator countIt = tagCounts.find( lcaseTagName ); if( countIt == tagCounts.end() ) countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; countIt->second.add( tagName ); } } for( std::map::const_iterator countIt = tagCounts.begin(), countItEnd = tagCounts.end(); countIt != countItEnd; ++countIt ) { std::ostringstream oss; oss << " " << std::setw(2) << countIt->second.count << " "; Text wrapper( countIt->second.all(), TextAttributes() .setInitialIndent( 0 ) .setIndent( oss.str().size() ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); Catch::cout() << oss.str() << wrapper << "\n"; } Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; return tagCounts.size(); } inline std::size_t listReporters( Config const& /*config*/ ) { Catch::cout() << "Available reporters:\n"; IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; std::size_t maxNameLen = 0; for(it = itBegin; it != itEnd; ++it ) maxNameLen = (std::max)( maxNameLen, it->first.size() ); for(it = itBegin; it != itEnd; ++it ) { Text wrapper( it->second->getDescription(), TextAttributes() .setInitialIndent( 0 ) .setIndent( 7+maxNameLen ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); Catch::cout() << " " << it->first << ":" << std::string( maxNameLen - it->first.size() + 2, ' ' ) << wrapper << "\n"; } Catch::cout() << std::endl; return factories.size(); } inline Option list( Config const& config ) { Option listedCount; if( config.listTests() ) listedCount = listedCount.valueOr(0) + listTests( config ); if( config.listTestNamesOnly() ) listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); if( config.listTags() ) listedCount = listedCount.valueOr(0) + listTags( config ); if( config.listReporters() ) listedCount = listedCount.valueOr(0) + listReporters( config ); return listedCount; } } // end namespace Catch // #included from: internal/catch_run_context.hpp #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED // #included from: catch_test_case_tracker.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED #include #include #include #include namespace Catch { namespace TestCaseTracking { struct ITracker : SharedImpl<> { virtual ~ITracker(); // static queries virtual std::string name() const = 0; // dynamic queries virtual bool isComplete() const = 0; // Successfully completed or failed virtual bool isSuccessfullyCompleted() const = 0; virtual bool isOpen() const = 0; // Started but not complete virtual bool hasChildren() const = 0; virtual ITracker& parent() = 0; // actions virtual void close() = 0; // Successfully complete virtual void fail() = 0; virtual void markAsNeedingAnotherRun() = 0; virtual void addChild( Ptr const& child ) = 0; virtual ITracker* findChild( std::string const& name ) = 0; virtual void openChild() = 0; // Debug/ checking virtual bool isSectionTracker() const = 0; virtual bool isIndexTracker() const = 0; }; class TrackerContext { enum RunState { NotStarted, Executing, CompletedCycle }; Ptr m_rootTracker; ITracker* m_currentTracker; RunState m_runState; public: static TrackerContext& instance() { static TrackerContext s_instance; return s_instance; } TrackerContext() : m_currentTracker( CATCH_NULL ), m_runState( NotStarted ) {} ITracker& startRun(); void endRun() { m_rootTracker.reset(); m_currentTracker = CATCH_NULL; m_runState = NotStarted; } void startCycle() { m_currentTracker = m_rootTracker.get(); m_runState = Executing; } void completeCycle() { m_runState = CompletedCycle; } bool completedCycle() const { return m_runState == CompletedCycle; } ITracker& currentTracker() { return *m_currentTracker; } void setCurrentTracker( ITracker* tracker ) { m_currentTracker = tracker; } }; class TrackerBase : public ITracker { protected: enum CycleState { NotStarted, Executing, ExecutingChildren, NeedsAnotherRun, CompletedSuccessfully, Failed }; class TrackerHasName { std::string m_name; public: TrackerHasName( std::string const& name ) : m_name( name ) {} bool operator ()( Ptr const& tracker ) { return tracker->name() == m_name; } }; typedef std::vector > Children; std::string m_name; TrackerContext& m_ctx; ITracker* m_parent; Children m_children; CycleState m_runState; public: TrackerBase( std::string const& name, TrackerContext& ctx, ITracker* parent ) : m_name( name ), m_ctx( ctx ), m_parent( parent ), m_runState( NotStarted ) {} virtual ~TrackerBase(); virtual std::string name() const CATCH_OVERRIDE { return m_name; } virtual bool isComplete() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully || m_runState == Failed; } virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully; } virtual bool isOpen() const CATCH_OVERRIDE { return m_runState != NotStarted && !isComplete(); } virtual bool hasChildren() const CATCH_OVERRIDE { return !m_children.empty(); } virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { m_children.push_back( child ); } virtual ITracker* findChild( std::string const& name ) CATCH_OVERRIDE { Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) ); return( it != m_children.end() ) ? it->get() : CATCH_NULL; } virtual ITracker& parent() CATCH_OVERRIDE { assert( m_parent ); // Should always be non-null except for root return *m_parent; } virtual void openChild() CATCH_OVERRIDE { if( m_runState != ExecutingChildren ) { m_runState = ExecutingChildren; if( m_parent ) m_parent->openChild(); } } virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } void open() { m_runState = Executing; moveToThis(); if( m_parent ) m_parent->openChild(); } virtual void close() CATCH_OVERRIDE { // Close any still open children (e.g. generators) while( &m_ctx.currentTracker() != this ) m_ctx.currentTracker().close(); switch( m_runState ) { case NotStarted: case CompletedSuccessfully: case Failed: throw std::logic_error( "Illogical state" ); case NeedsAnotherRun: break;; case Executing: m_runState = CompletedSuccessfully; break; case ExecutingChildren: if( m_children.empty() || m_children.back()->isComplete() ) m_runState = CompletedSuccessfully; break; default: throw std::logic_error( "Unexpected state" ); } moveToParent(); m_ctx.completeCycle(); } virtual void fail() CATCH_OVERRIDE { m_runState = Failed; if( m_parent ) m_parent->markAsNeedingAnotherRun(); moveToParent(); m_ctx.completeCycle(); } virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { m_runState = NeedsAnotherRun; } private: void moveToParent() { assert( m_parent ); m_ctx.setCurrentTracker( m_parent ); } void moveToThis() { m_ctx.setCurrentTracker( this ); } }; class SectionTracker : public TrackerBase { public: SectionTracker( std::string const& name, TrackerContext& ctx, ITracker* parent ) : TrackerBase( name, ctx, parent ) {} virtual ~SectionTracker(); virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) { SectionTracker* section = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { assert( childTracker ); assert( childTracker->isSectionTracker() ); section = static_cast( childTracker ); } else { section = new SectionTracker( name, ctx, ¤tTracker ); currentTracker.addChild( section ); } if( !ctx.completedCycle() && !section->isComplete() ) { section->open(); } return *section; } }; class IndexTracker : public TrackerBase { int m_size; int m_index; public: IndexTracker( std::string const& name, TrackerContext& ctx, ITracker* parent, int size ) : TrackerBase( name, ctx, parent ), m_size( size ), m_index( -1 ) {} virtual ~IndexTracker(); virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) { IndexTracker* tracker = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { assert( childTracker ); assert( childTracker->isIndexTracker() ); tracker = static_cast( childTracker ); } else { tracker = new IndexTracker( name, ctx, ¤tTracker, size ); currentTracker.addChild( tracker ); } if( !ctx.completedCycle() && !tracker->isComplete() ) { if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) tracker->moveNext(); tracker->open(); } return *tracker; } int index() const { return m_index; } void moveNext() { m_index++; m_children.clear(); } virtual void close() CATCH_OVERRIDE { TrackerBase::close(); if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) m_runState = Executing; } }; inline ITracker& TrackerContext::startRun() { m_rootTracker = new SectionTracker( "{root}", *this, CATCH_NULL ); m_currentTracker = CATCH_NULL; m_runState = Executing; return *m_rootTracker; } } // namespace TestCaseTracking using TestCaseTracking::ITracker; using TestCaseTracking::TrackerContext; using TestCaseTracking::SectionTracker; using TestCaseTracking::IndexTracker; } // namespace Catch // #included from: catch_fatal_condition.hpp #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED namespace Catch { // Report the error condition then exit the process inline void fatal( std::string const& message, int exitCode ) { IContext& context = Catch::getCurrentContext(); IResultCapture* resultCapture = context.getResultCapture(); resultCapture->handleFatalErrorCondition( message ); if( Catch::alwaysTrue() ) // avoids "no return" warnings exit( exitCode ); } } // namespace Catch #if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// namespace Catch { struct FatalConditionHandler { void reset() {} }; } // namespace Catch #else // Not Windows - assumed to be POSIX compatible ////////////////////////// #include namespace Catch { struct SignalDefs { int id; const char* name; }; extern SignalDefs signalDefs[]; SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, { SIGTERM, "SIGTERM - Termination request signal" }, { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; struct FatalConditionHandler { static void handleSignal( int sig ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) if( sig == signalDefs[i].id ) fatal( signalDefs[i].name, -sig ); fatal( "", -sig ); } FatalConditionHandler() : m_isSet( true ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) signal( signalDefs[i].id, handleSignal ); } ~FatalConditionHandler() { reset(); } void reset() { if( m_isSet ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) signal( signalDefs[i].id, SIG_DFL ); m_isSet = false; } } bool m_isSet; }; } // namespace Catch #endif // not Windows #include #include namespace Catch { class StreamRedirect { public: StreamRedirect( std::ostream& stream, std::string& targetString ) : m_stream( stream ), m_prevBuf( stream.rdbuf() ), m_targetString( targetString ) { stream.rdbuf( m_oss.rdbuf() ); } ~StreamRedirect() { m_targetString += m_oss.str(); m_stream.rdbuf( m_prevBuf ); } private: std::ostream& m_stream; std::streambuf* m_prevBuf; std::ostringstream m_oss; std::string& m_targetString; }; /////////////////////////////////////////////////////////////////////////// class RunContext : public IResultCapture, public IRunner { RunContext( RunContext const& ); void operator =( RunContext const& ); public: explicit RunContext( Ptr const& _config, Ptr const& reporter ) : m_runInfo( _config->name() ), m_context( getCurrentMutableContext() ), m_activeTestCase( CATCH_NULL ), m_config( _config ), m_reporter( reporter ) { m_context.setRunner( this ); m_context.setConfig( m_config ); m_context.setResultCapture( this ); m_reporter->testRunStarting( m_runInfo ); } virtual ~RunContext() { m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); } void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); } void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); } Totals runTest( TestCase const& testCase ) { Totals prevTotals = m_totals; std::string redirectedCout; std::string redirectedCerr; TestCaseInfo testInfo = testCase.getTestCaseInfo(); m_reporter->testCaseStarting( testInfo ); m_activeTestCase = &testCase; do { m_trackerContext.startRun(); do { m_trackerContext.startCycle(); m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, testInfo.name ); runCurrentTest( redirectedCout, redirectedCerr ); } while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); } // !TBD: deprecated - this will be replaced by indexed trackers while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { deltaTotals.assertions.failed++; deltaTotals.testCases.passed--; deltaTotals.testCases.failed++; } m_totals.testCases += deltaTotals.testCases; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, redirectedCout, redirectedCerr, aborting() ) ); m_activeTestCase = CATCH_NULL; m_testCaseTracker = CATCH_NULL; return deltaTotals; } Ptr config() const { return m_config; } private: // IResultCapture virtual void assertionEnded( AssertionResult const& result ) { if( result.getResultType() == ResultWas::Ok ) { m_totals.assertions.passed++; } else if( !result.isOk() ) { m_totals.assertions.failed++; } if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) m_messages.clear(); // Reset working state m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); m_lastResult = result; } virtual bool sectionStarted ( SectionInfo const& sectionInfo, Counts& assertions ) { std::ostringstream oss; oss << sectionInfo.name << "@" << sectionInfo.lineInfo; ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, oss.str() ); if( !sectionTracker.isOpen() ) return false; m_activeSections.push_back( §ionTracker ); m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; m_reporter->sectionStarting( sectionInfo ); assertions = m_totals.assertions; return true; } bool testForMissingAssertions( Counts& assertions ) { if( assertions.total() != 0 ) return false; if( !m_config->warnAboutMissingAssertions() ) return false; if( m_trackerContext.currentTracker().hasChildren() ) return false; m_totals.assertions.failed++; assertions.failed++; return true; } virtual void sectionEnded( SectionEndInfo const& endInfo ) { Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); if( !m_activeSections.empty() ) { m_activeSections.back()->close(); m_activeSections.pop_back(); } m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); m_messages.clear(); } virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { if( m_unfinishedSections.empty() ) m_activeSections.back()->fail(); else m_activeSections.back()->close(); m_activeSections.pop_back(); m_unfinishedSections.push_back( endInfo ); } virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } virtual void popScopedMessage( MessageInfo const& message ) { m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); } virtual std::string getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name : ""; } virtual const AssertionResult* getLastResult() const { return &m_lastResult; } virtual void handleFatalErrorCondition( std::string const& message ) { ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); resultBuilder.setResultType( ResultWas::FatalErrorCondition ); resultBuilder << message; resultBuilder.captureExpression(); handleUnfinishedSections(); // Recreate section for test case (as we will lose the one that was in scope) TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); Counts assertions; assertions.failed = 1; SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); m_reporter->sectionEnded( testCaseSectionStats ); TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); Totals deltaTotals; deltaTotals.testCases.failed = 1; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, "", "", false ) ); m_totals.testCases.failed++; testGroupEnded( "", m_totals, 1, 1 ); m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); } public: // !TBD We need to do this another way! bool aborting() const { return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); } private: void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); m_reporter->sectionStarting( testCaseSection ); Counts prevAssertions = m_totals.assertions; double duration = 0; try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); seedRng( *m_config ); Timer timer; timer.start(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { StreamRedirect coutRedir( Catch::cout(), redirectedCout ); StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); invokeActiveTestCase(); } else { invokeActiveTestCase(); } duration = timer.getElapsedSeconds(); } catch( TestFailureException& ) { // This just means the test was aborted due to failure } catch(...) { makeUnexpectedResultBuilder().useActiveException(); } m_testCaseTracker->close(); handleUnfinishedSections(); m_messages.clear(); Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); if( testCaseInfo.okToFail() ) { std::swap( assertions.failedButOk, assertions.failed ); m_totals.assertions.failed -= assertions.failedButOk; m_totals.assertions.failedButOk += assertions.failedButOk; } SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); m_reporter->sectionEnded( testCaseSectionStats ); } void invokeActiveTestCase() { FatalConditionHandler fatalConditionHandler; // Handle signals m_activeTestCase->invoke(); fatalConditionHandler.reset(); } private: ResultBuilder makeUnexpectedResultBuilder() const { return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), m_lastAssertionInfo.lineInfo, m_lastAssertionInfo.capturedExpression.c_str(), m_lastAssertionInfo.resultDisposition ); } void handleUnfinishedSections() { // If sections ended prematurely due to an exception we stored their // infos here so we can tear them down outside the unwind process. for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd; ++it ) sectionEnded( *it ); m_unfinishedSections.clear(); } TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; ITracker* m_testCaseTracker; ITracker* m_currentSectionTracker; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; }; IResultCapture& getResultCapture() { if( IResultCapture* capture = getCurrentContext().getResultCapture() ) return *capture; else throw std::logic_error( "No result capture instance" ); } } // end namespace Catch // #included from: internal/catch_version.h #define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED namespace Catch { // Versioning information struct Version { Version( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, std::string const& _branchName, unsigned int _buildNumber ); unsigned int const majorVersion; unsigned int const minorVersion; unsigned int const patchNumber; // buildNumber is only used if branchName is not null std::string const branchName; unsigned int const buildNumber; friend std::ostream& operator << ( std::ostream& os, Version const& version ); private: void operator=( Version const& ); }; extern Version libraryVersion; } #include #include #include namespace Catch { Ptr createReporter( std::string const& reporterName, Ptr const& config ) { Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); if( !reporter ) { std::ostringstream oss; oss << "No reporter registered with name: '" << reporterName << "'"; throw std::domain_error( oss.str() ); } return reporter; } Ptr makeReporter( Ptr const& config ) { std::vector reporters = config->getReporterNames(); if( reporters.empty() ) reporters.push_back( "console" ); Ptr reporter; for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); it != itEnd; ++it ) reporter = addReporter( reporter, createReporter( *it, config ) ); return reporter; } Ptr addListeners( Ptr const& config, Ptr reporters ) { IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); it != itEnd; ++it ) reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); return reporters; } Totals runTests( Ptr const& config ) { Ptr iconfig = config.get(); Ptr reporter = makeReporter( config ); reporter = addListeners( iconfig, reporter ); RunContext context( iconfig, reporter ); Totals totals; context.testGroupStarting( config->name(), 1, 1 ); TestSpec testSpec = config->testSpec(); if( !testSpec.hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); it != itEnd; ++it ) { if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) totals += context.runTest( *it ); else reporter->skipTest( *it ); } context.testGroupEnded( iconfig->name(), totals, 1, 1 ); return totals; } void applyFilenamesAsTags( IConfig const& config ) { std::vector const& tests = getAllTestCasesSorted( config ); for(std::size_t i = 0; i < tests.size(); ++i ) { TestCase& test = const_cast( tests[i] ); std::set tags = test.tags; std::string filename = test.lineInfo.file; std::string::size_type lastSlash = filename.find_last_of( "\\/" ); if( lastSlash != std::string::npos ) filename = filename.substr( lastSlash+1 ); std::string::size_type lastDot = filename.find_last_of( "." ); if( lastDot != std::string::npos ) filename = filename.substr( 0, lastDot ); tags.insert( "#" + filename ); setTags( test, tags ); } } class Session : NonCopyable { static bool alreadyInstantiated; public: struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; Session() : m_cli( makeCommandLineParser() ) { if( alreadyInstantiated ) { std::string msg = "Only one instance of Catch::Session can ever be used"; Catch::cerr() << msg << std::endl; throw std::logic_error( msg ); } alreadyInstantiated = true; } ~Session() { Catch::cleanUp(); } void showHelp( std::string const& processName ) { Catch::cout() << "\nCatch v" << libraryVersion << "\n"; m_cli.usage( Catch::cout(), processName ); Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); if( m_configData.showHelp ) showHelp( m_configData.processName ); m_config.reset(); } catch( std::exception& ex ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "\nError(s) in input:\n" << Text( ex.what(), TextAttributes().setIndent(2) ) << "\n\n"; } m_cli.usage( Catch::cout(), m_configData.processName ); return (std::numeric_limits::max)(); } return 0; } void useConfigData( ConfigData const& _configData ) { m_configData = _configData; m_config.reset(); } int run( int argc, char const* const* const argv ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) returnCode = run(); return returnCode; } int run() { if( m_configData.showHelp ) return 0; try { config(); // Force config to be constructed seedRng( *m_config ); if( m_configData.filenamesAsTags ) applyFilenamesAsTags( *m_config ); // Handle list request if( Option listed = list( config() ) ) return static_cast( *listed ); return static_cast( runTests( m_config ).assertions.failed ); } catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; return (std::numeric_limits::max)(); } } Clara::CommandLine const& cli() const { return m_cli; } std::vector const& unusedTokens() const { return m_unusedTokens; } ConfigData& configData() { return m_configData; } Config& config() { if( !m_config ) m_config = new Config( m_configData ); return *m_config; } private: Clara::CommandLine m_cli; std::vector m_unusedTokens; ConfigData m_configData; Ptr m_config; }; bool Session::alreadyInstantiated = false; } // end namespace Catch // #included from: catch_registry_hub.hpp #define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED // #included from: catch_test_case_registry_impl.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED #include #include #include #include #include namespace Catch { struct RandomNumberGenerator { typedef std::ptrdiff_t result_type; result_type operator()( result_type n ) const { return std::rand() % n; } #ifdef CATCH_CONFIG_CPP11_SHUFFLE static constexpr result_type min() { return 0; } static constexpr result_type max() { return 1000000; } result_type operator()() const { return std::rand() % max(); } #endif template static void shuffle( V& vector ) { RandomNumberGenerator rng; #ifdef CATCH_CONFIG_CPP11_SHUFFLE std::shuffle( vector.begin(), vector.end(), rng ); #else std::random_shuffle( vector.begin(), vector.end(), rng ); #endif } }; inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { std::vector sorted = unsortedTestCases; switch( config.runOrder() ) { case RunTests::InLexicographicalOrder: std::sort( sorted.begin(), sorted.end() ); break; case RunTests::InRandomOrder: { seedRng( config ); RandomNumberGenerator::shuffle( sorted ); } break; case RunTests::InDeclarationOrder: // already in declaration order break; } return sorted; } bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); } void enforceNoDuplicateTestCases( std::vector const& functions ) { std::set seenFunctions; for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); it != itEnd; ++it ) { std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); if( !prev.second ) { std::ostringstream ss; ss << Colour( Colour::Red ) << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; throw std::runtime_error(ss.str()); } } } std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { std::vector filtered; filtered.reserve( testCases.size() ); for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); it != itEnd; ++it ) if( matchTest( *it, testSpec, config ) ) filtered.push_back( *it ); return filtered; } std::vector const& getAllTestCasesSorted( IConfig const& config ) { return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); } class TestRegistry : public ITestCaseRegistry { public: TestRegistry() : m_currentSortOrder( RunTests::InDeclarationOrder ), m_unnamedCount( 0 ) {} virtual ~TestRegistry(); virtual void registerTest( TestCase const& testCase ) { std::string name = testCase.getTestCaseInfo().name; if( name == "" ) { std::ostringstream oss; oss << "Anonymous test case " << ++m_unnamedCount; return registerTest( testCase.withName( oss.str() ) ); } m_functions.push_back( testCase ); } virtual std::vector const& getAllTests() const { return m_functions; } virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { if( m_sortedFunctions.empty() ) enforceNoDuplicateTestCases( m_functions ); if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { m_sortedFunctions = sortTests( config, m_functions ); m_currentSortOrder = config.runOrder(); } return m_sortedFunctions; } private: std::vector m_functions; mutable RunTests::InWhatOrder m_currentSortOrder; mutable std::vector m_sortedFunctions; size_t m_unnamedCount; std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised }; /////////////////////////////////////////////////////////////////////////// class FreeFunctionTestCase : public SharedImpl { public: FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} virtual void invoke() const { m_fun(); } private: virtual ~FreeFunctionTestCase(); TestFunction m_fun; }; inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { std::string className = classOrQualifiedMethodName; if( startsWith( className, "&" ) ) { std::size_t lastColons = className.rfind( "::" ); std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); if( penultimateColons == std::string::npos ) penultimateColons = 1; className = className.substr( penultimateColons, lastColons-penultimateColons ); } return className; } void registerTestCase ( ITestCase* testCase, char const* classOrQualifiedMethodName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { getMutableRegistryHub().registerTest ( makeTestCase ( testCase, extractClassName( classOrQualifiedMethodName ), nameAndDesc.name, nameAndDesc.description, lineInfo ) ); } void registerTestCaseFunction ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); } /////////////////////////////////////////////////////////////////////////// AutoReg::AutoReg ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCaseFunction( function, lineInfo, nameAndDesc ); } AutoReg::~AutoReg() {} } // end namespace Catch // #included from: catch_reporter_registry.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED #include namespace Catch { class ReporterRegistry : public IReporterRegistry { public: virtual ~ReporterRegistry() CATCH_OVERRIDE {} virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { FactoryMap::const_iterator it = m_factories.find( name ); if( it == m_factories.end() ) return CATCH_NULL; return it->second->create( ReporterConfig( config ) ); } void registerReporter( std::string const& name, Ptr const& factory ) { m_factories.insert( std::make_pair( name, factory ) ); } void registerListener( Ptr const& factory ) { m_listeners.push_back( factory ); } virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { return m_factories; } virtual Listeners const& getListeners() const CATCH_OVERRIDE { return m_listeners; } private: FactoryMap m_factories; Listeners m_listeners; }; } // #included from: catch_exception_translator_registry.hpp #define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED #ifdef __OBJC__ #import "Foundation/Foundation.h" #endif namespace Catch { class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { public: ~ExceptionTranslatorRegistry() { deleteAll( m_translators ); } virtual void registerTranslator( const IExceptionTranslator* translator ) { m_translators.push_back( translator ); } virtual std::string translateActiveException() const { try { #ifdef __OBJC__ // In Objective-C try objective-c exceptions first @try { return tryTranslators(); } @catch (NSException *exception) { return Catch::toString( [exception description] ); } #else return tryTranslators(); #endif } catch( TestFailureException& ) { throw; } catch( std::exception& ex ) { return ex.what(); } catch( std::string& msg ) { return msg; } catch( const char* msg ) { return msg; } catch(...) { return "Unknown exception"; } } std::string tryTranslators() const { if( m_translators.empty() ) throw; else return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); } private: std::vector m_translators; }; } namespace Catch { namespace { class RegistryHub : public IRegistryHub, public IMutableRegistryHub { RegistryHub( RegistryHub const& ); void operator=( RegistryHub const& ); public: // IRegistryHub RegistryHub() { } virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { return m_reporterRegistry; } virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { return m_testCaseRegistry; } virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { return m_exceptionTranslatorRegistry; } public: // IMutableRegistryHub virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerReporter( name, factory ); } virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerListener( factory ); } virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { m_testCaseRegistry.registerTest( testInfo ); } virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { m_exceptionTranslatorRegistry.registerTranslator( translator ); } private: TestRegistry m_testCaseRegistry; ReporterRegistry m_reporterRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; }; // Single, global, instance inline RegistryHub*& getTheRegistryHub() { static RegistryHub* theRegistryHub = CATCH_NULL; if( !theRegistryHub ) theRegistryHub = new RegistryHub(); return theRegistryHub; } } IRegistryHub& getRegistryHub() { return *getTheRegistryHub(); } IMutableRegistryHub& getMutableRegistryHub() { return *getTheRegistryHub(); } void cleanUp() { delete getTheRegistryHub(); getTheRegistryHub() = CATCH_NULL; cleanUpContext(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); } } // end namespace Catch // #included from: catch_notimplemented_exception.hpp #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED #include namespace Catch { NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) : m_lineInfo( lineInfo ) { std::ostringstream oss; oss << lineInfo << ": function "; oss << "not implemented"; m_what = oss.str(); } const char* NotImplementedException::what() const CATCH_NOEXCEPT { return m_what.c_str(); } } // end namespace Catch // #included from: catch_context_impl.hpp #define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED // #included from: catch_stream.hpp #define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED #include #include #include namespace Catch { template class StreamBufImpl : public StreamBufBase { char data[bufferSize]; WriterF m_writer; public: StreamBufImpl() { setp( data, data + sizeof(data) ); } ~StreamBufImpl() CATCH_NOEXCEPT { sync(); } private: int overflow( int c ) { sync(); if( c != EOF ) { if( pbase() == epptr() ) m_writer( std::string( 1, static_cast( c ) ) ); else sputc( static_cast( c ) ); } return 0; } int sync() { if( pbase() != pptr() ) { m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); setp( pbase(), epptr() ); } return 0; } }; /////////////////////////////////////////////////////////////////////////// FileStream::FileStream( std::string const& filename ) { m_ofs.open( filename.c_str() ); if( m_ofs.fail() ) { std::ostringstream oss; oss << "Unable to open file: '" << filename << "'"; throw std::domain_error( oss.str() ); } } std::ostream& FileStream::stream() const { return m_ofs; } struct OutputDebugWriter { void operator()( std::string const&str ) { writeToDebugConsole( str ); } }; DebugOutStream::DebugOutStream() : m_streamBuf( new StreamBufImpl() ), m_os( m_streamBuf.get() ) {} std::ostream& DebugOutStream::stream() const { return m_os; } // Store the streambuf from cout up-front because // cout may get redirected when running tests CoutStream::CoutStream() : m_os( Catch::cout().rdbuf() ) {} std::ostream& CoutStream::stream() const { return m_os; } #ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions std::ostream& cout() { return std::cout; } std::ostream& cerr() { return std::cerr; } #endif } namespace Catch { class Context : public IMutableContext { Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} Context( Context const& ); void operator=( Context const& ); public: virtual ~Context() { deleteAllValues( m_generatorsByTestName ); } public: // IContext virtual IResultCapture* getResultCapture() { return m_resultCapture; } virtual IRunner* getRunner() { return m_runner; } virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { return getGeneratorsForCurrentTest() .getGeneratorInfo( fileInfo, totalSize ) .getCurrentIndex(); } virtual bool advanceGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); return generators && generators->moveNext(); } virtual Ptr getConfig() const { return m_config; } public: // IMutableContext virtual void setResultCapture( IResultCapture* resultCapture ) { m_resultCapture = resultCapture; } virtual void setRunner( IRunner* runner ) { m_runner = runner; } virtual void setConfig( Ptr const& config ) { m_config = config; } friend IMutableContext& getCurrentMutableContext(); private: IGeneratorsForTest* findGeneratorsForCurrentTest() { std::string testName = getResultCapture()->getCurrentTestName(); std::map::const_iterator it = m_generatorsByTestName.find( testName ); return it != m_generatorsByTestName.end() ? it->second : CATCH_NULL; } IGeneratorsForTest& getGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); if( !generators ) { std::string testName = getResultCapture()->getCurrentTestName(); generators = createGeneratorsForTest(); m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); } return *generators; } private: Ptr m_config; IRunner* m_runner; IResultCapture* m_resultCapture; std::map m_generatorsByTestName; }; namespace { Context* currentContext = CATCH_NULL; } IMutableContext& getCurrentMutableContext() { if( !currentContext ) currentContext = new Context(); return *currentContext; } IContext& getCurrentContext() { return getCurrentMutableContext(); } void cleanUpContext() { delete currentContext; currentContext = CATCH_NULL; } } // #included from: catch_console_colour_impl.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED namespace Catch { namespace { struct IColourImpl { virtual ~IColourImpl() {} virtual void use( Colour::Code _colourCode ) = 0; }; struct NoColourImpl : IColourImpl { void use( Colour::Code ) {} static IColourImpl* instance() { static NoColourImpl s_instance; return &s_instance; } }; } // anon namespace } // namespace Catch #if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) # ifdef CATCH_PLATFORM_WINDOWS # define CATCH_CONFIG_COLOUR_WINDOWS # else # define CATCH_CONFIG_COLOUR_ANSI # endif #endif #if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// // #included from: catch_windows_h_proxy.h #define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED #ifdef CATCH_DEFINES_NOMINMAX # define NOMINMAX #endif #ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN #endif #ifdef __AFXDLL #include #else #include #endif #ifdef CATCH_DEFINES_NOMINMAX # undef NOMINMAX #endif #ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN # undef WIN32_LEAN_AND_MEAN #endif namespace Catch { namespace { class Win32ColourImpl : public IColourImpl { public: Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Red: return setTextAttribute( FOREGROUND_RED ); case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); case Colour::Grey: return setTextAttribute( 0 ); case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } private: void setTextAttribute( WORD _textAttribute ) { SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); } HANDLE stdoutHandle; WORD originalForegroundAttributes; WORD originalBackgroundAttributes; }; IColourImpl* platformColourInstance() { static Win32ColourImpl s_instance; Ptr config = getCurrentContext().getConfig(); UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; if( colourMode == UseColour::Auto ) colourMode = !isDebuggerActive() ? UseColour::Yes : UseColour::No; return colourMode == UseColour::Yes ? &s_instance : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// #include namespace Catch { namespace { // use POSIX/ ANSI console terminal codes // Thanks to Adam Strzelecki for original contribution // (http://github.com/nanoant) // https://github.com/philsquared/Catch/pull/131 class PosixColourImpl : public IColourImpl { public: virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: case Colour::White: return setColour( "[0m" ); case Colour::Red: return setColour( "[0;31m" ); case Colour::Green: return setColour( "[0;32m" ); case Colour::Blue: return setColour( "[0;34m" ); case Colour::Cyan: return setColour( "[0;36m" ); case Colour::Yellow: return setColour( "[0;33m" ); case Colour::Grey: return setColour( "[1;30m" ); case Colour::LightGrey: return setColour( "[0;37m" ); case Colour::BrightRed: return setColour( "[1;31m" ); case Colour::BrightGreen: return setColour( "[1;32m" ); case Colour::BrightWhite: return setColour( "[1;37m" ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } static IColourImpl* instance() { static PosixColourImpl s_instance; return &s_instance; } private: void setColour( const char* _escapeCode ) { Catch::cout() << '\033' << _escapeCode; } }; IColourImpl* platformColourInstance() { Ptr config = getCurrentContext().getConfig(); UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; if( colourMode == UseColour::Auto ) colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) ? UseColour::Yes : UseColour::No; return colourMode == UseColour::Yes ? PosixColourImpl::instance() : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #else // not Windows or ANSI /////////////////////////////////////////////// namespace Catch { static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } } // end namespace Catch #endif // Windows/ ANSI/ None namespace Catch { Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } Colour::~Colour(){ if( !m_moved ) use( None ); } void Colour::use( Code _colourCode ) { static IColourImpl* impl = platformColourInstance(); impl->use( _colourCode ); } } // end namespace Catch // #included from: catch_generators_impl.hpp #define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED #include #include #include namespace Catch { struct GeneratorInfo : IGeneratorInfo { GeneratorInfo( std::size_t size ) : m_size( size ), m_currentIndex( 0 ) {} bool moveNext() { if( ++m_currentIndex == m_size ) { m_currentIndex = 0; return false; } return true; } std::size_t getCurrentIndex() const { return m_currentIndex; } std::size_t m_size; std::size_t m_currentIndex; }; /////////////////////////////////////////////////////////////////////////// class GeneratorsForTest : public IGeneratorsForTest { public: ~GeneratorsForTest() { deleteAll( m_generatorsInOrder ); } IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { std::map::const_iterator it = m_generatorsByName.find( fileInfo ); if( it == m_generatorsByName.end() ) { IGeneratorInfo* info = new GeneratorInfo( size ); m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); m_generatorsInOrder.push_back( info ); return *info; } return *it->second; } bool moveNext() { std::vector::const_iterator it = m_generatorsInOrder.begin(); std::vector::const_iterator itEnd = m_generatorsInOrder.end(); for(; it != itEnd; ++it ) { if( (*it)->moveNext() ) return true; } return false; } private: std::map m_generatorsByName; std::vector m_generatorsInOrder; }; IGeneratorsForTest* createGeneratorsForTest() { return new GeneratorsForTest(); } } // end namespace Catch // #included from: catch_assertionresult.hpp #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED namespace Catch { AssertionInfo::AssertionInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ) : macroName( _macroName ), lineInfo( _lineInfo ), capturedExpression( _capturedExpression ), resultDisposition( _resultDisposition ) {} AssertionResult::AssertionResult() {} AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) : m_info( info ), m_resultData( data ) {} AssertionResult::~AssertionResult() {} // Result was a success bool AssertionResult::succeeded() const { return Catch::isOk( m_resultData.resultType ); } // Result was a success, or failure is suppressed bool AssertionResult::isOk() const { return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); } ResultWas::OfType AssertionResult::getResultType() const { return m_resultData.resultType; } bool AssertionResult::hasExpression() const { return !m_info.capturedExpression.empty(); } bool AssertionResult::hasMessage() const { return !m_resultData.message.empty(); } std::string AssertionResult::getExpression() const { if( isFalseTest( m_info.resultDisposition ) ) return "!" + m_info.capturedExpression; else return m_info.capturedExpression; } std::string AssertionResult::getExpressionInMacro() const { if( m_info.macroName.empty() ) return m_info.capturedExpression; else return m_info.macroName + "( " + m_info.capturedExpression + " )"; } bool AssertionResult::hasExpandedExpression() const { return hasExpression() && getExpandedExpression() != getExpression(); } std::string AssertionResult::getExpandedExpression() const { return m_resultData.reconstructedExpression; } std::string AssertionResult::getMessage() const { return m_resultData.message; } SourceLineInfo AssertionResult::getSourceInfo() const { return m_info.lineInfo; } std::string AssertionResult::getTestMacroName() const { return m_info.macroName; } } // end namespace Catch // #included from: catch_test_case_info.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED namespace Catch { inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { if( startsWith( tag, "." ) || tag == "hide" || tag == "!hide" ) return TestCaseInfo::IsHidden; else if( tag == "!throws" ) return TestCaseInfo::Throws; else if( tag == "!shouldfail" ) return TestCaseInfo::ShouldFail; else if( tag == "!mayfail" ) return TestCaseInfo::MayFail; else return TestCaseInfo::None; } inline bool isReservedTag( std::string const& tag ) { return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); } inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { if( isReservedTag( tag ) ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "Tag name [" << tag << "] not allowed.\n" << "Tag names starting with non alpha-numeric characters are reserved\n"; } { Colour colourGuard( Colour::FileName ); Catch::cerr() << _lineInfo << std::endl; } exit(1); } } TestCase makeTestCase( ITestCase* _testCase, std::string const& _className, std::string const& _name, std::string const& _descOrTags, SourceLineInfo const& _lineInfo ) { bool isHidden( startsWith( _name, "./" ) ); // Legacy support // Parse out tags std::set tags; std::string desc, tag; bool inTag = false; for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { char c = _descOrTags[i]; if( !inTag ) { if( c == '[' ) inTag = true; else desc += c; } else { if( c == ']' ) { TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); if( prop == TestCaseInfo::IsHidden ) isHidden = true; else if( prop == TestCaseInfo::None ) enforceNotReservedTag( tag, _lineInfo ); tags.insert( tag ); tag.clear(); inTag = false; } else tag += c; } } if( isHidden ) { tags.insert( "hide" ); tags.insert( "." ); } TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); return TestCase( _testCase, info ); } void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) { testCaseInfo.tags = tags; testCaseInfo.lcaseTags.clear(); std::ostringstream oss; for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { oss << "[" << *it << "]"; std::string lcaseTag = toLower( *it ); testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); testCaseInfo.lcaseTags.insert( lcaseTag ); } testCaseInfo.tagsAsString = oss.str(); } TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ) : name( _name ), className( _className ), description( _description ), lineInfo( _lineInfo ), properties( None ) { setTags( *this, _tags ); } TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) : name( other.name ), className( other.className ), description( other.description ), tags( other.tags ), lcaseTags( other.lcaseTags ), tagsAsString( other.tagsAsString ), lineInfo( other.lineInfo ), properties( other.properties ) {} bool TestCaseInfo::isHidden() const { return ( properties & IsHidden ) != 0; } bool TestCaseInfo::throws() const { return ( properties & Throws ) != 0; } bool TestCaseInfo::okToFail() const { return ( properties & (ShouldFail | MayFail ) ) != 0; } bool TestCaseInfo::expectedToFail() const { return ( properties & (ShouldFail ) ) != 0; } TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} TestCase::TestCase( TestCase const& other ) : TestCaseInfo( other ), test( other.test ) {} TestCase TestCase::withName( std::string const& _newName ) const { TestCase other( *this ); other.name = _newName; return other; } void TestCase::swap( TestCase& other ) { test.swap( other.test ); name.swap( other.name ); className.swap( other.className ); description.swap( other.description ); tags.swap( other.tags ); lcaseTags.swap( other.lcaseTags ); tagsAsString.swap( other.tagsAsString ); std::swap( TestCaseInfo::properties, static_cast( other ).properties ); std::swap( lineInfo, other.lineInfo ); } void TestCase::invoke() const { test->invoke(); } bool TestCase::operator == ( TestCase const& other ) const { return test.get() == other.test.get() && name == other.name && className == other.className; } bool TestCase::operator < ( TestCase const& other ) const { return name < other.name; } TestCase& TestCase::operator = ( TestCase const& other ) { TestCase temp( other ); swap( temp ); return *this; } TestCaseInfo const& TestCase::getTestCaseInfo() const { return *this; } } // end namespace Catch // #included from: catch_version.hpp #define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED namespace Catch { Version::Version ( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, std::string const& _branchName, unsigned int _buildNumber ) : majorVersion( _majorVersion ), minorVersion( _minorVersion ), patchNumber( _patchNumber ), branchName( _branchName ), buildNumber( _buildNumber ) {} std::ostream& operator << ( std::ostream& os, Version const& version ) { os << version.majorVersion << "." << version.minorVersion << "." << version.patchNumber; if( !version.branchName.empty() ) { os << "-" << version.branchName << "." << version.buildNumber; } return os; } Version libraryVersion( 1, 6, 1, "", 0 ); } // #included from: catch_message.hpp #define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED namespace Catch { MessageInfo::MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ) : macroName( _macroName ), lineInfo( _lineInfo ), type( _type ), sequence( ++globalCount ) {} // This may need protecting if threading support is added unsigned int MessageInfo::globalCount = 0; //////////////////////////////////////////////////////////////////////////// ScopedMessage::ScopedMessage( MessageBuilder const& builder ) : m_info( builder.m_info ) { m_info.message = builder.m_stream.str(); getResultCapture().pushScopedMessage( m_info ); } ScopedMessage::ScopedMessage( ScopedMessage const& other ) : m_info( other.m_info ) {} ScopedMessage::~ScopedMessage() { getResultCapture().popScopedMessage( m_info ); } } // end namespace Catch // #included from: catch_legacy_reporter_adapter.hpp #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED // #included from: catch_legacy_reporter_adapter.h #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED namespace Catch { // Deprecated struct IReporter : IShared { virtual ~IReporter(); virtual bool shouldRedirectStdout() const = 0; virtual void StartTesting() = 0; virtual void EndTesting( Totals const& totals ) = 0; virtual void StartGroup( std::string const& groupName ) = 0; virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; virtual void Aborted() = 0; virtual void Result( AssertionResult const& result ) = 0; }; class LegacyReporterAdapter : public SharedImpl { public: LegacyReporterAdapter( Ptr const& legacyReporter ); virtual ~LegacyReporterAdapter(); virtual ReporterPreferences getPreferences() const; virtual void noMatchingTestCases( std::string const& ); virtual void testRunStarting( TestRunInfo const& ); virtual void testGroupStarting( GroupInfo const& groupInfo ); virtual void testCaseStarting( TestCaseInfo const& testInfo ); virtual void sectionStarting( SectionInfo const& sectionInfo ); virtual void assertionStarting( AssertionInfo const& ); virtual bool assertionEnded( AssertionStats const& assertionStats ); virtual void sectionEnded( SectionStats const& sectionStats ); virtual void testCaseEnded( TestCaseStats const& testCaseStats ); virtual void testGroupEnded( TestGroupStats const& testGroupStats ); virtual void testRunEnded( TestRunStats const& testRunStats ); virtual void skipTest( TestCaseInfo const& ); private: Ptr m_legacyReporter; }; } namespace Catch { LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) : m_legacyReporter( legacyReporter ) {} LegacyReporterAdapter::~LegacyReporterAdapter() {} ReporterPreferences LegacyReporterAdapter::getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); return prefs; } void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { m_legacyReporter->StartTesting(); } void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { m_legacyReporter->StartGroup( groupInfo.name ); } void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { m_legacyReporter->StartTestCase( testInfo ); } void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); } void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { // Not on legacy interface } bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); rb << it->message; rb.setResultType( ResultWas::Info ); AssertionResult result = rb.build(); m_legacyReporter->Result( result ); } } } m_legacyReporter->Result( assertionStats.assertionResult ); return true; } void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { if( sectionStats.missingAssertions ) m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); } void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { m_legacyReporter->EndTestCase ( testCaseStats.testInfo, testCaseStats.totals, testCaseStats.stdOut, testCaseStats.stdErr ); } void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { if( testGroupStats.aborting ) m_legacyReporter->Aborted(); m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); } void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { m_legacyReporter->EndTesting( testRunStats.totals ); } void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { } } // #included from: catch_timer.hpp #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++11-long-long" #endif #ifdef CATCH_PLATFORM_WINDOWS #else #include #endif namespace Catch { namespace { #ifdef CATCH_PLATFORM_WINDOWS uint64_t getCurrentTicks() { static uint64_t hz=0, hzo=0; if (!hz) { QueryPerformanceFrequency( reinterpret_cast( &hz ) ); QueryPerformanceCounter( reinterpret_cast( &hzo ) ); } uint64_t t; QueryPerformanceCounter( reinterpret_cast( &t ) ); return ((t-hzo)*1000000)/hz; } #else uint64_t getCurrentTicks() { timeval t; gettimeofday(&t,CATCH_NULL); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif } void Timer::start() { m_ticks = getCurrentTicks(); } unsigned int Timer::getElapsedMicroseconds() const { return static_cast(getCurrentTicks() - m_ticks); } unsigned int Timer::getElapsedMilliseconds() const { return static_cast(getElapsedMicroseconds()/1000); } double Timer::getElapsedSeconds() const { return getElapsedMicroseconds()/1000000.0; } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_common.hpp #define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED namespace Catch { bool startsWith( std::string const& s, std::string const& prefix ) { return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; } bool endsWith( std::string const& s, std::string const& suffix ) { return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; } bool contains( std::string const& s, std::string const& infix ) { return s.find( infix ) != std::string::npos; } char toLowerCh(char c) { return static_cast( ::tolower( c ) ); } void toLowerInPlace( std::string& s ) { std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); } std::string toLower( std::string const& s ) { std::string lc = s; toLowerInPlace( lc ); return lc; } std::string trim( std::string const& str ) { static char const* whitespaceChars = "\n\r\t "; std::string::size_type start = str.find_first_not_of( whitespaceChars ); std::string::size_type end = str.find_last_not_of( whitespaceChars ); return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; } bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { bool replaced = false; std::size_t i = str.find( replaceThis ); while( i != std::string::npos ) { replaced = true; str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); if( i < str.size()-withThis.size() ) i = str.find( replaceThis, i+withThis.size() ); else i = std::string::npos; } return replaced; } pluralise::pluralise( std::size_t count, std::string const& label ) : m_count( count ), m_label( label ) {} std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { os << pluraliser.m_count << " " << pluraliser.m_label; if( pluraliser.m_count != 1 ) os << "s"; return os; } SourceLineInfo::SourceLineInfo() : line( 0 ){} SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) : file( _file ), line( _line ) {} SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) : file( other.file ), line( other.line ) {} bool SourceLineInfo::empty() const { return file.empty(); } bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { return line == other.line && file == other.file; } bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { return line < other.line || ( line == other.line && file < other.file ); } void seedRng( IConfig const& config ) { if( config.rngSeed() != 0 ) std::srand( config.rngSeed() ); } unsigned int rngSeed() { return getCurrentContext().getConfig()->rngSeed(); } std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { #ifndef __GNUG__ os << info.file << "(" << info.line << ")"; #else os << info.file << ":" << info.line; #endif return os; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { std::ostringstream oss; oss << locationInfo << ": Internal Catch error: '" << message << "'"; if( alwaysTrue() ) throw std::logic_error( oss.str() ); } } // #included from: catch_section.hpp #define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED namespace Catch { SectionInfo::SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description ) : name( _name ), description( _description ), lineInfo( _lineInfo ) {} Section::Section( SectionInfo const& info ) : m_info( info ), m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) { m_timer.start(); } Section::~Section() { if( m_sectionIncluded ) { SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); if( std::uncaught_exception() ) getResultCapture().sectionEndedEarly( endInfo ); else getResultCapture().sectionEnded( endInfo ); } } // This indicates whether the section should be executed or not Section::operator bool() const { return m_sectionIncluded; } } // end namespace Catch // #included from: catch_debugger.hpp #define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED #include #ifdef CATCH_PLATFORM_MAC #include #include #include #include #include namespace Catch{ // The following function is taken directly from the following technical note: // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). bool isDebuggerActive(){ int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; return false; } // We're being debugged if the P_TRACED flag is set. return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } } // namespace Catch #elif defined(CATCH_PLATFORM_LINUX) #include #include namespace Catch{ // The standard POSIX way of detecting a debugger is to attempt to // ptrace() the process, but this needs to be done from a child and not // this process itself to still allow attaching to this process later // if wanted, so is rather heavy. Under Linux we have the PID of the // "debugger" (which doesn't need to be gdb, of course, it could also // be strace, for example) in /proc/$PID/status, so just get it from // there instead. bool isDebuggerActive(){ std::ifstream in("/proc/self/status"); for( std::string line; std::getline(in, line); ) { static const int PREFIX_LEN = 11; if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { // We're traced if the PID is not 0 and no other PID starts // with 0 digit, so it's enough to check for just a single // character. return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; } } return false; } } // namespace Catch #elif defined(_MSC_VER) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #elif defined(__MINGW32__) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #else namespace Catch { inline bool isDebuggerActive() { return false; } } #endif // Platform #ifdef CATCH_PLATFORM_WINDOWS extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); namespace Catch { void writeToDebugConsole( std::string const& text ) { ::OutputDebugStringA( text.c_str() ); } } #else namespace Catch { void writeToDebugConsole( std::string const& text ) { // !TBD: Need a version for Mac/ XCode and other IDEs Catch::cout() << text; } } #endif // Platform // #included from: catch_tostring.hpp #define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED namespace Catch { namespace Detail { const std::string unprintableString = "{?}"; namespace { const int hexThreshold = 255; struct Endianness { enum Arch { Big, Little }; static Arch which() { union _{ int asInt; char asChar[sizeof (int)]; } u; u.asInt = 1; return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; } }; } std::string rawMemoryToString( const void *object, std::size_t size ) { // Reverse order for little endian architectures int i = 0, end = static_cast( size ), inc = 1; if( Endianness::which() == Endianness::Little ) { i = end-1; end = inc = -1; } unsigned char const *bytes = static_cast(object); std::ostringstream os; os << "0x" << std::setfill('0') << std::hex; for( ; i != end; i += inc ) os << std::setw(2) << static_cast(bytes[i]); return os.str(); } } std::string toString( std::string const& value ) { std::string s = value; if( getCurrentContext().getConfig()->showInvisibles() ) { for(size_t i = 0; i < s.size(); ++i ) { std::string subs; switch( s[i] ) { case '\n': subs = "\\n"; break; case '\t': subs = "\\t"; break; default: break; } if( !subs.empty() ) { s = s.substr( 0, i ) + subs + s.substr( i+1 ); ++i; } } } return "\"" + s + "\""; } std::string toString( std::wstring const& value ) { std::string s; s.reserve( value.size() ); for(size_t i = 0; i < value.size(); ++i ) s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; return Catch::toString( s ); } std::string toString( const char* const value ) { return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); } std::string toString( char* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( const wchar_t* const value ) { return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); } std::string toString( wchar_t* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( int value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned int value ) { return Catch::toString( static_cast( value ) ); } template std::string fpToString( T value, int precision ) { std::ostringstream oss; oss << std::setprecision( precision ) << std::fixed << value; std::string d = oss.str(); std::size_t i = d.find_last_not_of( '0' ); if( i != std::string::npos && i != d.size()-1 ) { if( d[i] == '.' ) i++; d = d.substr( 0, i+1 ); } return d; } std::string toString( const double value ) { return fpToString( value, 10 ); } std::string toString( const float value ) { return fpToString( value, 5 ) + "f"; } std::string toString( bool value ) { return value ? "true" : "false"; } std::string toString( char value ) { return value < ' ' ? toString( static_cast( value ) ) : Detail::makeString( value ); } std::string toString( signed char value ) { return toString( static_cast( value ) ); } std::string toString( unsigned char value ) { return toString( static_cast( value ) ); } #ifdef CATCH_CONFIG_CPP11_LONG_LONG std::string toString( long long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned long long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } #endif #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ) { return "nullptr"; } #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSObject* const& nsObject ) { return toString( [nsObject description] ); } #endif } // end namespace Catch // #included from: catch_result_builder.hpp #define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED namespace Catch { std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) { return secondArg.empty() || secondArg == "\"\"" ? capturedExpression : capturedExpression + ", " + secondArg; } ResultBuilder::ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition, char const* secondArg ) : m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ), m_shouldDebugBreak( false ), m_shouldThrow( false ) {} ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { m_data.resultType = result; return *this; } ResultBuilder& ResultBuilder::setResultType( bool result ) { m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; return *this; } ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { m_exprComponents.lhs = lhs; return *this; } ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { m_exprComponents.rhs = rhs; return *this; } ResultBuilder& ResultBuilder::setOp( std::string const& op ) { m_exprComponents.op = op; return *this; } void ResultBuilder::endExpression() { m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); captureExpression(); } void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { m_assertionInfo.resultDisposition = resultDisposition; m_stream.oss << Catch::translateActiveException(); captureResult( ResultWas::ThrewException ); } void ResultBuilder::captureResult( ResultWas::OfType resultType ) { setResultType( resultType ); captureExpression(); } void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { if( expectedMessage.empty() ) captureExpectedException( Matchers::Impl::Generic::AllOf() ); else captureExpectedException( Matchers::Equals( expectedMessage ) ); } void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher const& matcher ) { assert( m_exprComponents.testFalse == false ); AssertionResultData data = m_data; data.resultType = ResultWas::Ok; data.reconstructedExpression = m_assertionInfo.capturedExpression; std::string actualMessage = Catch::translateActiveException(); if( !matcher.match( actualMessage ) ) { data.resultType = ResultWas::ExpressionFailed; data.reconstructedExpression = actualMessage; } AssertionResult result( m_assertionInfo, data ); handleResult( result ); } void ResultBuilder::captureExpression() { AssertionResult result = build(); handleResult( result ); } void ResultBuilder::handleResult( AssertionResult const& result ) { getResultCapture().assertionEnded( result ); if( !result.isOk() ) { if( getCurrentContext().getConfig()->shouldDebugBreak() ) m_shouldDebugBreak = true; if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) m_shouldThrow = true; } } void ResultBuilder::react() { if( m_shouldThrow ) throw Catch::TestFailureException(); } bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } AssertionResult ResultBuilder::build() const { assert( m_data.resultType != ResultWas::Unknown ); AssertionResultData data = m_data; // Flip bool results if testFalse is set if( m_exprComponents.testFalse ) { if( data.resultType == ResultWas::Ok ) data.resultType = ResultWas::ExpressionFailed; else if( data.resultType == ResultWas::ExpressionFailed ) data.resultType = ResultWas::Ok; } data.message = m_stream.oss.str(); data.reconstructedExpression = reconstructExpression(); if( m_exprComponents.testFalse ) { if( m_exprComponents.op == "" ) data.reconstructedExpression = "!" + data.reconstructedExpression; else data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; } return AssertionResult( m_assertionInfo, data ); } std::string ResultBuilder::reconstructExpression() const { if( m_exprComponents.op == "" ) return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.lhs; else if( m_exprComponents.op == "matches" ) return m_exprComponents.lhs + " " + m_exprComponents.rhs; else if( m_exprComponents.op != "!" ) { if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && m_exprComponents.lhs.find("\n") == std::string::npos && m_exprComponents.rhs.find("\n") == std::string::npos ) return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; else return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; } else return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; } } // end namespace Catch // #included from: catch_tag_alias_registry.hpp #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED // #included from: catch_tag_alias_registry.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED #include namespace Catch { class TagAliasRegistry : public ITagAliasRegistry { public: virtual ~TagAliasRegistry(); virtual Option find( std::string const& alias ) const; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); static TagAliasRegistry& get(); private: std::map m_registry; }; } // end namespace Catch #include #include namespace Catch { TagAliasRegistry::~TagAliasRegistry() {} Option TagAliasRegistry::find( std::string const& alias ) const { std::map::const_iterator it = m_registry.find( alias ); if( it != m_registry.end() ) return it->second; else return Option(); } std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { std::string expandedTestSpec = unexpandedTestSpec; for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); it != itEnd; ++it ) { std::size_t pos = expandedTestSpec.find( it->first ); if( pos != std::string::npos ) { expandedTestSpec = expandedTestSpec.substr( 0, pos ) + it->second.tag + expandedTestSpec.substr( pos + it->first.size() ); } } return expandedTestSpec; } void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { std::ostringstream oss; oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; throw std::domain_error( oss.str().c_str() ); } if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { std::ostringstream oss; oss << "error: tag alias, \"" << alias << "\" already registered.\n" << "\tFirst seen at " << find(alias)->lineInfo << "\n" << "\tRedefined at " << lineInfo; throw std::domain_error( oss.str().c_str() ); } } TagAliasRegistry& TagAliasRegistry::get() { static TagAliasRegistry instance; return instance; } ITagAliasRegistry::~ITagAliasRegistry() {} ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { try { TagAliasRegistry::get().add( alias, tag, lineInfo ); } catch( std::exception& ex ) { Colour colourGuard( Colour::Red ); Catch::cerr() << ex.what() << std::endl; exit(1); } } } // end namespace Catch // #included from: ../reporters/catch_reporter_multi.hpp #define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED namespace Catch { class MultipleReporters : public SharedImpl { typedef std::vector > Reporters; Reporters m_reporters; public: void add( Ptr const& reporter ) { m_reporters.push_back( reporter ); } public: // IStreamingReporter virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporters[0]->getPreferences(); } virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->noMatchingTestCases( spec ); } virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testRunStarting( testRunInfo ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testGroupStarting( groupInfo ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testCaseStarting( testInfo ); } virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->sectionStarting( sectionInfo ); } virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->assertionStarting( assertionInfo ); } // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { bool clearBuffer = false; for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) clearBuffer |= (*it)->assertionEnded( assertionStats ); return clearBuffer; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->sectionEnded( sectionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testGroupEnded( testGroupStats ); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testRunEnded( testRunStats ); } virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->skipTest( testInfo ); } virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { return this; } }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { Ptr resultingReporter; if( existingReporter ) { MultipleReporters* multi = existingReporter->tryAsMulti(); if( !multi ) { multi = new MultipleReporters; resultingReporter = Ptr( multi ); if( existingReporter ) multi->add( existingReporter ); } else resultingReporter = existingReporter; multi->add( additionalReporter ); } else resultingReporter = additionalReporter; return resultingReporter; } } // end namespace Catch // #included from: ../reporters/catch_reporter_xml.hpp #define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED // #included from: catch_reporter_bases.hpp #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED #include namespace Catch { struct StreamingReporterBase : SharedImpl { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; } virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporterPrefs; } virtual ~StreamingReporterBase() CATCH_OVERRIDE; virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { currentTestRunInfo = _testRunInfo; } virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { currentGroupInfo = _groupInfo; } virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { currentTestCaseInfo = _testInfo; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_sectionStack.push_back( _sectionInfo ); } virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); } virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { currentGroupInfo.reset(); } virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { // Don't do anything with this by default. // It can optionally be overridden in the derived class. } Ptr m_config; std::ostream& stream; LazyStat currentTestRunInfo; LazyStat currentGroupInfo; LazyStat currentTestCaseInfo; std::vector m_sectionStack; ReporterPreferences m_reporterPrefs; }; struct CumulativeReporterBase : SharedImpl { template struct Node : SharedImpl<> { explicit Node( T const& _value ) : value( _value ) {} virtual ~Node() {} typedef std::vector > ChildNodes; T value; ChildNodes children; }; struct SectionNode : SharedImpl<> { explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} virtual ~SectionNode(); bool operator == ( SectionNode const& other ) const { return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; } bool operator == ( Ptr const& other ) const { return operator==( *other ); } SectionStats stats; typedef std::vector > ChildSections; typedef std::vector Assertions; ChildSections childSections; Assertions assertions; std::string stdOut; std::string stdErr; }; struct BySectionInfo { BySectionInfo( SectionInfo const& other ) : m_other( other ) {} BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} bool operator() ( Ptr const& node ) const { return node->stats.sectionInfo.lineInfo == m_other.lineInfo; } private: void operator=( BySectionInfo const& ); SectionInfo const& m_other; }; typedef Node TestCaseNode; typedef Node TestGroupNode; typedef Node TestRunNode; CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; } ~CumulativeReporterBase(); virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporterPrefs; } virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); Ptr node; if( m_sectionStack.empty() ) { if( !m_rootSection ) m_rootSection = new SectionNode( incompleteStats ); node = m_rootSection; } else { SectionNode& parentNode = *m_sectionStack.back(); SectionNode::ChildSections::const_iterator it = std::find_if( parentNode.childSections.begin(), parentNode.childSections.end(), BySectionInfo( sectionInfo ) ); if( it == parentNode.childSections.end() ) { node = new SectionNode( incompleteStats ); parentNode.childSections.push_back( node ); } else node = *it; } m_sectionStack.push_back( node ); m_deepestSection = node; } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { Ptr node = new TestCaseNode( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); m_testCases.push_back( node ); m_rootSection.reset(); assert( m_deepestSection ); m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { Ptr node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { Ptr node = new TestRunNode( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); testRunEndedCumulative(); } virtual void testRunEndedCumulative() = 0; virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} Ptr m_config; std::ostream& stream; std::vector m_assertions; std::vector > > m_sections; std::vector > m_testCases; std::vector > m_testGroups; std::vector > m_testRuns; Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; ReporterPreferences m_reporterPrefs; }; template char const* getLineOfChars() { static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; if( !*line ) { memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; } return line; } struct TestEventListenerBase : StreamingReporterBase { TestEventListenerBase( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { return false; } }; } // end namespace Catch // #included from: ../internal/catch_reporter_registrars.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED namespace Catch { template class LegacyReporterRegistrar { class ReporterFactory : public IReporterFactory { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new LegacyReporterAdapter( new T( config ) ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: LegacyReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ReporterRegistrar { class ReporterFactory : public SharedImpl { // *** Please Note ***: // - If you end up here looking at a compiler error because it's trying to register // your custom reporter class be aware that the native reporter interface has changed // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. // However please consider updating to the new interface as the old one is now // deprecated and will probably be removed quite soon! // Please contact me via github if you have any questions at all about this. // In fact, ideally, please contact me anyway to let me know you've hit this - as I have // no idea who is actually using custom reporters at all (possibly no-one!). // The new interface is designed to minimise exposure to interface changes in the future. virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: ReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ListenerRegistrar { class ListenerFactory : public SharedImpl { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return ""; } }; public: ListenerRegistrar() { getMutableRegistryHub().registerListener( new ListenerFactory() ); } }; } #define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include #include #include #include namespace Catch { class XmlEncode { public: enum ForWhat { ForTextNodes, ForAttributes }; XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) : m_str( str ), m_forWhat( forWhat ) {} void encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: http://www.w3.org/TR/xml/#syntax) for( std::size_t i = 0; i < m_str.size(); ++ i ) { char c = m_str[i]; switch( c ) { case '<': os << "<"; break; case '&': os << "&"; break; case '>': // See: http://www.w3.org/TR/xml/#syntax if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) os << ">"; else os << c; break; case '\"': if( m_forWhat == ForAttributes ) os << """; else os << c; break; default: // Escape control chars - based on contribution by @espenalb in PR #465 and // by @mrpi PR #588 if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) os << "&#x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast( c ) << ';'; else os << c; } } } friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { xmlEncode.encodeTo( os ); return os; } private: std::string m_str; ForWhat m_forWhat; }; class XmlWriter { public: class ScopedElement { public: ScopedElement( XmlWriter* writer ) : m_writer( writer ) {} ScopedElement( ScopedElement const& other ) : m_writer( other.m_writer ){ other.m_writer = CATCH_NULL; } ~ScopedElement() { if( m_writer ) m_writer->endElement(); } ScopedElement& writeText( std::string const& text, bool indent = true ) { m_writer->writeText( text, indent ); return *this; } template ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { m_writer->writeAttribute( name, attribute ); return *this; } private: mutable XmlWriter* m_writer; }; XmlWriter() : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &Catch::cout() ) { // We encode control characters, which requires // XML 1.1 // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 *m_os << "\n"; } XmlWriter( std::ostream& os ) : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &os ) { *m_os << "\n"; } ~XmlWriter() { while( !m_tags.empty() ) endElement(); } XmlWriter& startElement( std::string const& name ) { ensureTagClosed(); newlineIfNecessary(); stream() << m_indent << "<" << name; m_tags.push_back( name ); m_indent += " "; m_tagIsOpen = true; return *this; } ScopedElement scopedElement( std::string const& name ) { ScopedElement scoped( this ); startElement( name ); return scoped; } XmlWriter& endElement() { newlineIfNecessary(); m_indent = m_indent.substr( 0, m_indent.size()-2 ); if( m_tagIsOpen ) { stream() << "/>\n"; m_tagIsOpen = false; } else { stream() << m_indent << "\n"; } m_tags.pop_back(); return *this; } XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { if( !name.empty() && !attribute.empty() ) stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\""; return *this; } XmlWriter& writeAttribute( std::string const& name, bool attribute ) { stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; return *this; } template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { std::ostringstream oss; oss << attribute; return writeAttribute( name, oss.str() ); } XmlWriter& writeText( std::string const& text, bool indent = true ) { if( !text.empty() ){ bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); if( tagWasOpen && indent ) stream() << m_indent; stream() << XmlEncode( text ); m_needsNewline = true; } return *this; } XmlWriter& writeComment( std::string const& text ) { ensureTagClosed(); stream() << m_indent << ""; m_needsNewline = true; return *this; } XmlWriter& writeBlankLine() { ensureTagClosed(); stream() << "\n"; return *this; } void setStream( std::ostream& os ) { m_os = &os; } private: XmlWriter( XmlWriter const& ); void operator=( XmlWriter const& ); std::ostream& stream() { return *m_os; } void ensureTagClosed() { if( m_tagIsOpen ) { stream() << ">\n"; m_tagIsOpen = false; } } void newlineIfNecessary() { if( m_needsNewline ) { stream() << "\n"; m_needsNewline = false; } } bool m_tagIsOpen; bool m_needsNewline; std::vector m_tags; std::string m_indent; std::ostream* m_os; }; } // #included from: catch_reenable_warnings.h #define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(pop) # else # pragma clang diagnostic pop # endif #elif defined __GNUC__ # pragma GCC diagnostic pop #endif namespace Catch { class XmlReporter : public StreamingReporterBase { public: XmlReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_xml(_config.stream()), m_sectionDepth( 0 ) { m_reporterPrefs.shouldRedirectStdOut = true; } virtual ~XmlReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as an XML document"; } public: // StreamingReporterBase virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { StreamingReporterBase::noMatchingTestCases( s ); } virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testRunStarting( testInfo ); m_xml.startElement( "Catch" ); if( !m_config->name().empty() ) m_xml.writeAttribute( "name", m_config->name() ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { StreamingReporterBase::testGroupStarting( groupInfo ); m_xml.startElement( "Group" ) .writeAttribute( "name", groupInfo.name ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testCaseStarting(testInfo); m_xml.startElement( "TestCase" ).writeAttribute( "name", testInfo.name ); if ( m_config->showDurations() == ShowDurations::Always ) m_testCaseTimer.start(); } virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) .writeAttribute( "name", trim( sectionInfo.name ) ) .writeAttribute( "description", sectionInfo.description ); } } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { const AssertionResult& assertionResult = assertionStats.assertionResult; // Print any info messages in tags. if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { m_xml.scopedElement( "Info" ) .writeText( it->message ); } else if ( it->type == ResultWas::Warning ) { m_xml.scopedElement( "Warning" ) .writeText( it->message ); } } } // Drop out if result was successful but we're not printing them. if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) return true; // Print the expression if there is one. if( assertionResult.hasExpression() ) { m_xml.startElement( "Expression" ) .writeAttribute( "success", assertionResult.succeeded() ) .writeAttribute( "type", assertionResult.getTestMacroName() ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ); m_xml.scopedElement( "Original" ) .writeText( assertionResult.getExpression() ); m_xml.scopedElement( "Expanded" ) .writeText( assertionResult.getExpandedExpression() ); } // And... Print a result applicable to each result type. switch( assertionResult.getResultType() ) { case ResultWas::ThrewException: m_xml.scopedElement( "Exception" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); break; case ResultWas::FatalErrorCondition: m_xml.scopedElement( "FatalErrorCondition" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); break; case ResultWas::Info: m_xml.scopedElement( "Info" ) .writeText( assertionResult.getMessage() ); break; case ResultWas::Warning: // Warning will already have been written break; case ResultWas::ExplicitFailure: m_xml.scopedElement( "Failure" ) .writeText( assertionResult.getMessage() ); break; default: break; } if( assertionResult.hasExpression() ) m_xml.endElement(); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { StreamingReporterBase::sectionEnded( sectionStats ); if( --m_sectionDepth > 0 ) { XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); e.writeAttribute( "successes", sectionStats.assertions.passed ); e.writeAttribute( "failures", sectionStats.assertions.failed ); e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); m_xml.endElement(); } } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( testCaseStats ); XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); m_xml.endElement(); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { StreamingReporterBase::testGroupEnded( testGroupStats ); // TODO: Check testGroupStats.aborting and act accordingly. m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); m_xml.endElement(); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { StreamingReporterBase::testRunEnded( testRunStats ); m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testRunStats.totals.assertions.passed ) .writeAttribute( "failures", testRunStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); m_xml.endElement(); } private: Timer m_testCaseTimer; XmlWriter m_xml; int m_sectionDepth; }; INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_junit.hpp #define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED #include namespace Catch { namespace { std::string getCurrentTimestamp() { // Beware, this is not reentrant because of backward compatibility issues // Also, UTC only, again because of backward compatibility (%z is C++11) time_t rawtime; std::time(&rawtime); const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z"); #ifdef CATCH_PLATFORM_WINDOWS std::tm timeInfo = {}; gmtime_s(&timeInfo, &rawtime); #else std::tm* timeInfo; timeInfo = std::gmtime(&rawtime); #endif char timeStamp[timeStampSize]; const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; #ifdef CATCH_PLATFORM_WINDOWS std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); #else std::strftime(timeStamp, timeStampSize, fmt, timeInfo); #endif return std::string(timeStamp); } } class JunitReporter : public CumulativeReporterBase { public: JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), xml( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = true; } virtual ~JunitReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { suiteTimer.start(); stdOutForSuite.str(""); stdErrForSuite.str(""); unexpectedExceptions = 0; CumulativeReporterBase::testGroupStarting( groupInfo ); } virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) unexpectedExceptions++; return CumulativeReporterBase::assertionEnded( assertionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { stdOutForSuite << testCaseStats.stdOut; stdErrForSuite << testCaseStats.stdErr; CumulativeReporterBase::testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { double suiteTime = suiteTimer.getElapsedSeconds(); CumulativeReporterBase::testGroupEnded( testGroupStats ); writeGroup( *m_testGroups.back(), suiteTime ); } virtual void testRunEndedCumulative() CATCH_OVERRIDE { xml.endElement(); } void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); TestGroupStats const& stats = groupNode.value; xml.writeAttribute( "name", stats.groupInfo.name ); xml.writeAttribute( "errors", unexpectedExceptions ); xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); xml.writeAttribute( "tests", stats.totals.assertions.total() ); xml.writeAttribute( "hostname", "tbd" ); // !TBD if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else xml.writeAttribute( "time", suiteTime ); xml.writeAttribute( "timestamp", getCurrentTimestamp() ); // Write test cases for( TestGroupNode::ChildNodes::const_iterator it = groupNode.children.begin(), itEnd = groupNode.children.end(); it != itEnd; ++it ) writeTestCase( **it ); xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); } void writeTestCase( TestCaseNode const& testCaseNode ) { TestCaseStats const& stats = testCaseNode.value; // All test cases have exactly one section - which represents the // test case itself. That section may have 0-n nested sections assert( testCaseNode.children.size() == 1 ); SectionNode const& rootSection = *testCaseNode.children.front(); std::string className = stats.testInfo.className; if( className.empty() ) { if( rootSection.childSections.empty() ) className = "global"; } writeSection( className, "", rootSection ); } void writeSection( std::string const& className, std::string const& rootName, SectionNode const& sectionNode ) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + "/" + name; if( !sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty() ) { XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); if( className.empty() ) { xml.writeAttribute( "classname", name ); xml.writeAttribute( "name", "root" ); } else { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); writeAssertions( sectionNode ); if( !sectionNode.stdOut.empty() ) xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); if( !sectionNode.stdErr.empty() ) xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); } for( SectionNode::ChildSections::const_iterator it = sectionNode.childSections.begin(), itEnd = sectionNode.childSections.end(); it != itEnd; ++it ) if( className.empty() ) writeSection( name, "", **it ); else writeSection( className, name, **it ); } void writeAssertions( SectionNode const& sectionNode ) { for( SectionNode::Assertions::const_iterator it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); it != itEnd; ++it ) writeAssertion( *it ); } void writeAssertion( AssertionStats const& stats ) { AssertionResult const& result = stats.assertionResult; if( !result.isOk() ) { std::string elementName; switch( result.getResultType() ) { case ResultWas::ThrewException: case ResultWas::FatalErrorCondition: elementName = "error"; break; case ResultWas::ExplicitFailure: elementName = "failure"; break; case ResultWas::ExpressionFailed: elementName = "failure"; break; case ResultWas::DidntThrowException: elementName = "failure"; break; // We should never see these here: case ResultWas::Info: case ResultWas::Warning: case ResultWas::Ok: case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: elementName = "internalError"; break; } XmlWriter::ScopedElement e = xml.scopedElement( elementName ); xml.writeAttribute( "message", result.getExpandedExpression() ); xml.writeAttribute( "type", result.getTestMacroName() ); std::ostringstream oss; if( !result.getMessage().empty() ) oss << result.getMessage() << "\n"; for( std::vector::const_iterator it = stats.infoMessages.begin(), itEnd = stats.infoMessages.end(); it != itEnd; ++it ) if( it->type == ResultWas::Info ) oss << it->message << "\n"; oss << "at " << result.getSourceInfo(); xml.writeText( oss.str(), false ); } } XmlWriter xml; Timer suiteTimer; std::ostringstream stdOutForSuite; std::ostringstream stdErrForSuite; unsigned int unexpectedExceptions; }; INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_console.hpp #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED namespace Catch { struct ConsoleReporter : StreamingReporterBase { ConsoleReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_headerPrinted( false ) {} virtual ~ConsoleReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as plain lines of text"; } virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } lazyPrint(); AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); if( m_sectionStack.size() > 1 ) stream << "\nNo assertions in section"; else stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } if( m_headerPrinted ) { if( m_config->showDurations() == ShowDurations::Always ) stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; m_headerPrinted = false; } else { if( m_config->showDurations() == ShowDurations::Always ) stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; } StreamingReporterBase::sectionEnded( _sectionStats ); } virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); stream << "\n" << std::endl; } StreamingReporterBase::testGroupEnded( _testGroupStats ); } virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ), stats( _stats ), result( _stats.assertionResult ), colour( Colour::None ), message( result.getMessage() ), messages( _stats.infoMessages ), printInfoMessages( _printInfoMessages ) { switch( result.getResultType() ) { case ResultWas::Ok: colour = Colour::Success; passOrFail = "PASSED"; //if( result.hasMessage() ) if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ExpressionFailed: if( result.isOk() ) { colour = Colour::Success; passOrFail = "FAILED - but was ok"; } else { colour = Colour::Error; passOrFail = "FAILED"; } if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ThrewException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to unexpected exception with message"; break; case ResultWas::FatalErrorCondition: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to a fatal error condition"; break; case ResultWas::DidntThrowException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "because no exception was thrown where one was expected"; break; case ResultWas::Info: messageLabel = "info"; break; case ResultWas::Warning: messageLabel = "warning"; break; case ResultWas::ExplicitFailure: passOrFail = "FAILED"; colour = Colour::Error; if( _stats.infoMessages.size() == 1 ) messageLabel = "explicitly with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "explicitly with messages"; break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: passOrFail = "** internal error **"; colour = Colour::Error; break; } } void print() const { printSourceInfo(); if( stats.totals.assertions.total() > 0 ) { if( result.isOk() ) stream << "\n"; printResultType(); printOriginalExpression(); printReconstructedExpression(); } else { stream << "\n"; } printMessage(); } private: void printResultType() const { if( !passOrFail.empty() ) { Colour colourGuard( colour ); stream << passOrFail << ":\n"; } } void printOriginalExpression() const { if( result.hasExpression() ) { Colour colourGuard( Colour::OriginalExpression ); stream << " "; stream << result.getExpressionInMacro(); stream << "\n"; } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { stream << "with expansion:\n"; Colour colourGuard( Colour::ReconstructedExpression ); stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; } } void printMessage() const { if( !messageLabel.empty() ) stream << messageLabel << ":" << "\n"; for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); it != itEnd; ++it ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || it->type != ResultWas::Info ) stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; } } void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ": "; } std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; Colour::Code colour; std::string passOrFail; std::string messageLabel; std::string message; std::vector messages; bool printInfoMessages; }; void lazyPrint() { if( !currentTestRunInfo.used ) lazyPrintRunInfo(); if( !currentGroupInfo.used ) lazyPrintGroupInfo(); if( !m_headerPrinted ) { printTestCaseAndSectionHeader(); m_headerPrinted = true; } } void lazyPrintRunInfo() { stream << "\n" << getLineOfChars<'~'>() << "\n"; Colour colour( Colour::SecondaryText ); stream << currentTestRunInfo->name << " is a Catch v" << libraryVersion << " host application.\n" << "Run with -? for options\n\n"; if( m_config->rngSeed() != 0 ) stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; currentTestRunInfo.used = true; } void lazyPrintGroupInfo() { if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { printClosedHeader( "Group: " + currentGroupInfo->name ); currentGroupInfo.used = true; } } void printTestCaseAndSectionHeader() { assert( !m_sectionStack.empty() ); printOpenHeader( currentTestCaseInfo->name ); if( m_sectionStack.size() > 1 ) { Colour colourGuard( Colour::Headers ); std::vector::const_iterator it = m_sectionStack.begin()+1, // Skip first section (test case) itEnd = m_sectionStack.end(); for( ; it != itEnd; ++it ) printHeaderString( it->name, 2 ); } SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; if( !lineInfo.empty() ){ stream << getLineOfChars<'-'>() << "\n"; Colour colourGuard( Colour::FileName ); stream << lineInfo << "\n"; } stream << getLineOfChars<'.'>() << "\n" << std::endl; } void printClosedHeader( std::string const& _name ) { printOpenHeader( _name ); stream << getLineOfChars<'.'>() << "\n"; } void printOpenHeader( std::string const& _name ) { stream << getLineOfChars<'-'>() << "\n"; { Colour colourGuard( Colour::Headers ); printHeaderString( _name ); } } // if string has a : in first line will set indent to follow it on // subsequent lines void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { std::size_t i = _string.find( ": " ); if( i != std::string::npos ) i+=2; else i = 0; stream << Text( _string, TextAttributes() .setIndent( indent+i) .setInitialIndent( indent ) ) << "\n"; } struct SummaryColumn { SummaryColumn( std::string const& _label, Colour::Code _colour ) : label( _label ), colour( _colour ) {} SummaryColumn addRow( std::size_t count ) { std::ostringstream oss; oss << count; std::string row = oss.str(); for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { while( it->size() < row.size() ) *it = " " + *it; while( it->size() > row.size() ) row = " " + row; } rows.push_back( row ); return *this; } std::string label; Colour::Code colour; std::vector rows; }; void printTotals( Totals const& totals ) { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " << pluralise( totals.testCases.passed, "test case" ) << ")" << "\n"; } else { std::vector columns; columns.push_back( SummaryColumn( "", Colour::None ) .addRow( totals.testCases.total() ) .addRow( totals.assertions.total() ) ); columns.push_back( SummaryColumn( "passed", Colour::Success ) .addRow( totals.testCases.passed ) .addRow( totals.assertions.passed ) ); columns.push_back( SummaryColumn( "failed", Colour::ResultError ) .addRow( totals.testCases.failed ) .addRow( totals.assertions.failed ) ); columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) .addRow( totals.testCases.failedButOk ) .addRow( totals.assertions.failedButOk ) ); printSummaryRow( "test cases", columns, 0 ); printSummaryRow( "assertions", columns, 1 ); } } void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { std::string value = it->rows[row]; if( it->label.empty() ) { stream << label << ": "; if( value != "0" ) stream << value; else stream << Colour( Colour::Warning ) << "- none -"; } else if( value != "0" ) { stream << Colour( Colour::LightGrey ) << " | "; stream << Colour( it->colour ) << value << " " << it->label; } } stream << "\n"; } static std::size_t makeRatio( std::size_t number, std::size_t total ) { std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; return ( ratio == 0 && number > 0 ) ? 1 : ratio; } static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { if( i > j && i > k ) return i; else if( j > k ) return j; else return k; } void printTotalsDivider( Totals const& totals ) { if( totals.testCases.total() > 0 ) { std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )++; while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )--; stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); if( totals.testCases.allPassed() ) stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); else stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); } else { stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); } stream << "\n"; } void printSummaryDivider() { stream << getLineOfChars<'-'>() << "\n"; } private: bool m_headerPrinted; }; INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_compact.hpp #define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED namespace Catch { struct CompactReporter : StreamingReporterBase { CompactReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual ~CompactReporter(); static std::string getDescription() { return "Reports test results on a single line, suitable for IDEs"; } virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; } virtual void noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void testRunEnded( TestRunStats const& _testRunStats ) { printTotals( _testRunStats.totals ); stream << "\n" << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ) , stats( _stats ) , result( _stats.assertionResult ) , messages( _stats.infoMessages ) , itMessage( _stats.infoMessages.begin() ) , printInfoMessages( _printInfoMessages ) {} void print() { printSourceInfo(); itMessage = messages.begin(); switch( result.getResultType() ) { case ResultWas::Ok: printResultType( Colour::ResultSuccess, passedString() ); printOriginalExpression(); printReconstructedExpression(); if ( ! result.hasExpression() ) printRemainingMessages( Colour::None ); else printRemainingMessages(); break; case ResultWas::ExpressionFailed: if( result.isOk() ) printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); else printResultType( Colour::Error, failedString() ); printOriginalExpression(); printReconstructedExpression(); printRemainingMessages(); break; case ResultWas::ThrewException: printResultType( Colour::Error, failedString() ); printIssue( "unexpected exception with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::FatalErrorCondition: printResultType( Colour::Error, failedString() ); printIssue( "fatal error condition with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::DidntThrowException: printResultType( Colour::Error, failedString() ); printIssue( "expected exception, got none" ); printExpressionWas(); printRemainingMessages(); break; case ResultWas::Info: printResultType( Colour::None, "info" ); printMessage(); printRemainingMessages(); break; case ResultWas::Warning: printResultType( Colour::None, "warning" ); printMessage(); printRemainingMessages(); break; case ResultWas::ExplicitFailure: printResultType( Colour::Error, failedString() ); printIssue( "explicitly" ); printRemainingMessages( Colour::None ); break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: printResultType( Colour::Error, "** internal error **" ); break; } } private: // Colour::LightGrey static Colour::Code dimColour() { return Colour::FileName; } #ifdef CATCH_PLATFORM_MAC static const char* failedString() { return "FAILED"; } static const char* passedString() { return "PASSED"; } #else static const char* failedString() { return "failed"; } static const char* passedString() { return "passed"; } #endif void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ":"; } void printResultType( Colour::Code colour, std::string passOrFail ) const { if( !passOrFail.empty() ) { { Colour colourGuard( colour ); stream << " " << passOrFail; } stream << ":"; } } void printIssue( std::string issue ) const { stream << " " << issue; } void printExpressionWas() { if( result.hasExpression() ) { stream << ";"; { Colour colour( dimColour() ); stream << " expression was:"; } printOriginalExpression(); } } void printOriginalExpression() const { if( result.hasExpression() ) { stream << " " << result.getExpression(); } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { { Colour colour( dimColour() ); stream << " for: "; } stream << result.getExpandedExpression(); } } void printMessage() { if ( itMessage != messages.end() ) { stream << " '" << itMessage->message << "'"; ++itMessage; } } void printRemainingMessages( Colour::Code colour = dimColour() ) { if ( itMessage == messages.end() ) return; // using messages.end() directly yields compilation error: std::vector::const_iterator itEnd = messages.end(); const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); { Colour colourGuard( colour ); stream << " with " << pluralise( N, "message" ) << ":"; } for(; itMessage != itEnd; ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || itMessage->type != ResultWas::Info ) { stream << " '" << itMessage->message << "'"; if ( ++itMessage != itEnd ) { Colour colourGuard( dimColour() ); stream << " and"; } } } } private: std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; std::vector messages; std::vector::const_iterator itMessage; bool printInfoMessages; }; // Colour, message variants: // - white: No tests ran. // - red: Failed [both/all] N test cases, failed [both/all] M assertions. // - white: Passed [both/all] N test cases (no assertions). // - red: Failed N tests cases, failed M assertions. // - green: Passed [both/all] N tests cases with M assertions. std::string bothOrAll( std::size_t count ) const { return count == 1 ? "" : count == 2 ? "both " : "all " ; } void printTotals( const Totals& totals ) const { if( totals.testCases.total() == 0 ) { stream << "No tests ran."; } else if( totals.testCases.failed == totals.testCases.total() ) { Colour colour( Colour::ResultError ); const std::string qualify_assertions_failed = totals.assertions.failed == totals.assertions.total() ? bothOrAll( totals.assertions.failed ) : ""; stream << "Failed " << bothOrAll( totals.testCases.failed ) << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << qualify_assertions_failed << pluralise( totals.assertions.failed, "assertion" ) << "."; } else if( totals.assertions.total() == 0 ) { stream << "Passed " << bothOrAll( totals.testCases.total() ) << pluralise( totals.testCases.total(), "test case" ) << " (no assertions)."; } else if( totals.assertions.failed ) { Colour colour( Colour::ResultError ); stream << "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; } else { Colour colour( Colour::ResultSuccess ); stream << "Passed " << bothOrAll( totals.testCases.passed ) << pluralise( totals.testCases.passed, "test case" ) << " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; } } }; INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) } // end namespace Catch namespace Catch { // These are all here to avoid warnings about not having any out of line // virtual methods NonCopyable::~NonCopyable() {} IShared::~IShared() {} IStream::~IStream() CATCH_NOEXCEPT {} FileStream::~FileStream() CATCH_NOEXCEPT {} CoutStream::~CoutStream() CATCH_NOEXCEPT {} DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} IContext::~IContext() {} IResultCapture::~IResultCapture() {} ITestCase::~ITestCase() {} ITestCaseRegistry::~ITestCaseRegistry() {} IRegistryHub::~IRegistryHub() {} IMutableRegistryHub::~IMutableRegistryHub() {} IExceptionTranslator::~IExceptionTranslator() {} IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} IReporter::~IReporter() {} IReporterFactory::~IReporterFactory() {} IReporterRegistry::~IReporterRegistry() {} IStreamingReporter::~IStreamingReporter() {} AssertionStats::~AssertionStats() {} SectionStats::~SectionStats() {} TestCaseStats::~TestCaseStats() {} TestGroupStats::~TestGroupStats() {} TestRunStats::~TestRunStats() {} CumulativeReporterBase::SectionNode::~SectionNode() {} CumulativeReporterBase::~CumulativeReporterBase() {} StreamingReporterBase::~StreamingReporterBase() {} ConsoleReporter::~ConsoleReporter() {} CompactReporter::~CompactReporter() {} IRunner::~IRunner() {} IMutableContext::~IMutableContext() {} IConfig::~IConfig() {} XmlReporter::~XmlReporter() {} JunitReporter::~JunitReporter() {} TestRegistry::~TestRegistry() {} FreeFunctionTestCase::~FreeFunctionTestCase() {} IGeneratorInfo::~IGeneratorInfo() {} IGeneratorsForTest::~IGeneratorsForTest() {} WildcardPattern::~WildcardPattern() {} TestSpec::Pattern::~Pattern() {} TestSpec::NamePattern::~NamePattern() {} TestSpec::TagPattern::~TagPattern() {} TestSpec::ExcludedPattern::~ExcludedPattern() {} Matchers::Impl::StdString::Equals::~Equals() {} Matchers::Impl::StdString::Contains::~Contains() {} Matchers::Impl::StdString::StartsWith::~StartsWith() {} Matchers::Impl::StdString::EndsWith::~EndsWith() {} void Config::dummy() {} namespace TestCaseTracking { ITracker::~ITracker() {} TrackerBase::~TrackerBase() {} SectionTracker::~SectionTracker() {} IndexTracker::~IndexTracker() {} } } #ifdef __clang__ #pragma clang diagnostic pop #endif #endif #ifdef CATCH_CONFIG_MAIN // #included from: internal/catch_default_main.hpp #define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED #ifndef __OBJC__ // Standard C/C++ main entry point int main (int argc, char * argv[]) { return Catch::Session().run( argc, argv ); } #else // __OBJC__ // Objective-C entry point int main (int argc, char * const argv[]) { #if !CATCH_ARC_ENABLED NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; #endif Catch::registerTestMethods(); int result = Catch::Session().run( argc, (char* const*)argv ); #if !CATCH_ARC_ENABLED [pool drain]; #endif return result; } #endif // __OBJC__ #endif #ifdef CLARA_CONFIG_MAIN_NOT_DEFINED # undef CLARA_CONFIG_MAIN #endif ////// // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL #define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) #define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) #define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" ) #define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) #define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) #define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) #define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) #define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) #define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) #define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CATCH_CHECK_THROWS" ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" ) #define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) #define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) #define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) #else #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) #endif #define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) #define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) #define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) #define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) #define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) #define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else #define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) #define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) #define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" ) #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) #define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" ) #define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) #define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) #define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) #define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) #define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) #define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) #define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" ) #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) #define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" ) #define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) #define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) #define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) #else #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) #endif #define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) #endif #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) #define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) #define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) #define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) #define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) using Catch::Detail::Approx; #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED ================================================ FILE: examples/modules/plus-number/unittest/main.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "plus-number.h" #define CATCH_CONFIG_MAIN #include "catch/catch.hpp" using namespace napa::demo; TEST_CASE("PlusNumber: add the given value", "[add]") { PlusNumber plusNumber(3); REQUIRE(plusNumber.Add(2) == 5); REQUIRE(plusNumber.Add(3) != 7); } ================================================ FILE: examples/modules/plus-number/unittest/run.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. var path = require('path'); var childProcess = require('child_process'); try { childProcess.execFileSync( path.join(__dirname, 'bin', process.platform === 'win32'? 'library-test.exe': 'library-test'), [], { cwd: path.join(__dirname, 'bin'), stdio: 'inherit' } ); } catch(err) { process.exit(1); // Error } ================================================ FILE: examples/tutorial/estimate-pi-in-parallel/README.md ================================================ # Estimate PI in parallel This example implements an algorithm to [estimate PI using Monte Carlo method](http://mathfaculty.fullerton.edu/mathews/n2003/montecarlopimod.html). It demonstrates how to fan out sub-tasks into multiple JavaScript threads, execute them in parallel and aggregate output into a final result. In the implementation, multiple batches of points are evaluated simultaneously in a [napa zone](https://github.com/Microsoft/napajs/wiki/introduction#zone) of 4 workers. Results are aggregated to calculate the final PI after all batches finishes. ## How to run 1. Go to directory of `examples/tutorial/estimate-pi-in-parallel` 2. Run `npm install` to install napajs 3. Run `node estimate-pi-in-parallel.js` ## Program output The table below shows results of PI calculated under different settings, each setting emulates 4,000,000 points evaluated by a napa zone of 4 workers. These 4,000,000 points are divided into multiple batches, each setting differs only in number of batches. For settings (1-batch, 2-batch, and 4-batch) whose batch number is less than the worker number, total latency is proportional to the number of batches, that means we have enough workers to pick up coming batches. On the contrary, the 8-batch setting cannot scale linearly due to insufficient free worker, which is expected. ``` # of points # of batches # of workers latency in MS estimated π deviation --------------------------------------------------------------------------------------- 4000000 1 4 218 3.141958 0.0003653464 4000000 2 4 110 3.141953 0.0003603464 4000000 4 4 78 3.139600 0.001992654 4000000 8 4 62 3.142732 0.001139346 ``` We got results under environment: | Name | Value | |-------------------|---------------------------------------------------------------------------------------| |**Processor** |Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz, 2000 Mhz, 6 Core(s), 12 Logical Processor(s) | |**System Type** |x64-based PC | |**Physical Memory**|32.0 GB | |**OS version** |Microsoft Windows Server 2016 Datacenter | ================================================ FILE: examples/tutorial/estimate-pi-in-parallel/estimate-pi-in-parallel.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. var napa = require("napajs"); // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. var zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); /* Estimate the value of π by using a Monte Carlo method. Take `points` samples of random x and y values on a [0,1][0,1] plane. Calculating the length of the diagonal tells us whether the point lies inside, or outside a quarter circle running from 0,1 to 1,0. The ratio of the number of points inside to outside gives us an approximation of π/4. See https://en.wikipedia.org/wiki/File:Pi_30K.gif for a visualization of how this works. */ function estimatePI(points) { var i = points; var inside = 0; while (i-- > 0) { var x = Math.random(); var y = Math.random(); if ((x * x) + (y * y) <= 1) { inside++; } } return inside / points * 4; } function run(points, batches) { var start = Date.now(); var promises = []; for (var i = 0; i < batches; i++) { promises[i] = zone.execute(estimatePI, [points / batches]); } return Promise.all(promises).then(values => { var aggregate = 0; values.forEach(result => aggregate += result.value); printResult(points, batches, aggregate / batches, Date.now() - start); }); } function printResult(points, batches, pi, ms) { console.log('\t' + points + '\t\t' + batches + '\t\t' + NUMBER_OF_WORKERS + '\t\t' + ms + '\t\t' + pi.toPrecision(7) + '\t' + Math.abs(pi - Math.PI).toPrecision(7)); } console.log(); console.log('\t# of points\t# of batches\t# of workers\tlatency in MS\testimated π\tdeviation'); console.log('\t---------------------------------------------------------------------------------------'); // Run with different # of points and batches in sequence. run(4000000, 1) .then(result => run(4000000, 2)) .then(result => run(4000000, 4)) .then(result => run(4000000, 8)) ================================================ FILE: examples/tutorial/estimate-pi-in-parallel/package.json ================================================ { "name": "napajs-tutorial", "version": "0.1.0", "author": "napajs", "main": "./estimate-pi-in-parallel.js", "dependencies": { "napajs": ">= 0.1.0" } } ================================================ FILE: examples/tutorial/max-square-sub-matrix/README.md ================================================ # Max square sub matrix This example implements an algorithm to solve [Max square sub-matrix of all 1s](http://www.geeksforgeeks.org/maximum-size-sub-matrix-with-all-1s-in-a-binary-matrix/) using dynamic programming. It demonstrates parallelism among multiple JavaScript threads via [zone](https://github.com/Microsoft/napajs/wiki/introduction#zone) and cross-thread data sharing via [store](https://github.com/Microsoft/napajs/wiki/introduction#cross-worker-storage). In this implementation, all units to be evaluated were divided into 2 * size of the square matrix + 1 layers. Units in next layer were evaluated based on results of units from previous layers. Different layers were evaluated in sequence, while units within the same layer were evaluated in parallel, for there is no dependency between them. Results from previous layers were communicated by a store. Please note that this example is to demonstrate the programming paradigm, while itself is NOT performance efficient, since each worker does too little CPU operation and major overhead is on communication. ## How to run 1. Go to directory of `examples/tutorial/max-square-sub-matrix` 2. Run `npm install` to install napajs 3. Run `node max-square-sub-matrix.js` **Note**: This example uses 'async / await', so Node version that supports ES6 is required. (newer than v7.6.0). ## Program output The output below shows the result of the implementation. By a napa zone with 4 workers, it took 15 ms to find out the max square sub matrix with all 1s for the given 6 * 6 square binary matrix. ``` [ [ 0, 1, 1, 0, 1, 1 ], [ 1, 1, 0, 1, 0, 1 ], [ 0, 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0, 0 ], [ 1, 1, 1, 1, 1, 1 ], [ 0, 0, 0, 0, 0, 0 ] ] Max square sub matrix with all 1s ------------------------------------- I ends at : 4 J ends at : 3 matrix size : 3 # of workers : 4 Latency in MS : 15 ------------------------------------- ``` We got results under environment: | Name | Value | |-------------------|---------------------------------------------------------------------------------------| |**Processor** |Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz, 2000 Mhz, 6 Core(s), 12 Logical Processor(s) | |**System Type** |x64-based PC | |**Physical Memory**|32.0 GB | |**OS version** |Microsoft Windows Server 2016 Datacenter | ================================================ FILE: examples/tutorial/max-square-sub-matrix/max-square-sub-matrix.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. var napa = require("napajs"); // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. var zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); // Create a napa store with 'sub-matrix-size-store' as its key. // It is used to communicate max sub binary matrix size across isolates. var subMatrixSizeStore = napa.store.create('sub-matrix-size-store'); function run() { var squareMatrix = [ [0, 1, 1, 0, 1, 1], [1, 1, 0, 1, 0, 1], [0, 1, 1, 1, 0, 1], [1, 1, 1, 1, 0, 0], [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0] ]; // Setup all workers with functions to be executed. zone.broadcast(' \ var napa = require("napajs"); \ var zone = napa.zone.get("zone"); \ var store = napa.store.get("sub-matrix-size-store"); \ '); zone.broadcast(get_key_of_store.toString()); zone.broadcast(max_square_sub_matrix_with_all_1s_ended_at.toString()); zone.broadcast(max_square_sub_matrix_with_all_1s_at_layer.toString()); zone.broadcast(max_square_sub_matrix_with_all_1s.toString()); var start = Date.now(); // Start to execute. return zone.execute('', 'max_square_sub_matrix_with_all_1s', [squareMatrix]) .then(result => { print_result(squareMatrix, Date.now() - start); }); } /* Given a binary square matrix, find out the maximum square sub matrix with all 1s. For example, consider the below binary matrix [example_matrix], 0 1 1 0 1 1 1 1 0 1 0 1 0 1 1 1 0 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 0 0 0 the max square sub matrix with all 1s is - - - - - - - - - - - - - 1 1 1 - - - 1 1 1 - - - 1 1 1 - - - - - - - - with size = 3, and the lowest-rightest element = example_matrix[4, 3]. Notation: SubMatrixSize[i, j]: represents the size of the max square sub matrix with all 1s, whose lowest-rightest element is example_matrix[i, j]. Layer of element: Layer_0: [0,0] Layer_1: [0,1][1,0] Layer_2: [0,2][1,1][2,0] Layer_3: [0,3][1,2][2,1][3,0] Layer_4: [0,4][1,3][2,2][3,1][4,0] Layer_5: [0,5][1,4][2,3][3,2][4,1][5,0] Layer_6: [5,1][4,2][3,3][2,4][1,5] Layer_7: [5,2][4,3][3,4][2,5] Layer_8: [5,3][4,4][3,5] Layer_9: [5,4][4,5] Layer_10:[5,5] Algorithm: 1. SubMatrixSize[i, j] = example_matrix[i, j] when i === 0 or j === 0; 2. SubMatrixSize[i, j] = example_matrix[i, j] === 0 ? 0 : Min(SubMatrixSize[i - 1, j], SubMatrixSize[i - 1, j - 1], SubMatrixSize[i, j - 1]) + 1; By the above algorithm, the lower-righter layer (with larger layer #) can be evaluated from the results of the upper-lefter layer (with smaller layer #). There are no dependencies between evaluation of the elements in the same layer, so evaluation of the elements in the same layer could be parallel. */ async function max_square_sub_matrix_with_all_1s(squareMatrix) { var n = squareMatrix.length; var layer = 0; // All the layers are evaluated in sequence from upp-left to low-right. while (layer < 2 * n - 1) { await zone.execute('', 'max_square_sub_matrix_with_all_1s_at_layer', [squareMatrix, layer]); layer++; } } function max_square_sub_matrix_with_all_1s_at_layer(squareMatrix, layer) { var n = squareMatrix.length; var promises = []; var promiseIndex = 0; // Evaluate the elements in the current layer in parallel. if (layer < n) { for (var i = 0; i <= layer; i++) { promises[promiseIndex++] = zone.execute('', 'max_square_sub_matrix_with_all_1s_ended_at', [squareMatrix, i, layer - i] ); } } else { for (var j = layer - n + 1; j < n; j++) { promises[promiseIndex++] = zone.execute('', 'max_square_sub_matrix_with_all_1s_ended_at', [squareMatrix, layer - j, j] ); } } return Promise.all(promises).then(result => { }); } // Evaluate the size of max square sub matrix with all 1s // whose lowest-rightest element is the squareMatrix[i, j]. function max_square_sub_matrix_with_all_1s_ended_at(squareMatrix, i, j) { if (i === 0 || j === 0) { store.set(get_key_of_store(i, j), squareMatrix[i][j]); } else { store.set(get_key_of_store(i, j), squareMatrix[i][j] === 0 ? 0 : Math.min( store.get(get_key_of_store(i - 1, j)), store.get(get_key_of_store(i, j - 1)), store.get(get_key_of_store(i - 1, j - 1)) ) + 1 ); } } function get_key_of_store(i, j) { return i + '-' + j; } function print_result(squareMatrix, ms) { var n = squareMatrix.length; var maxSize = 0, maxI = 0, maxJ = 0; for (var i = 0; i < n; i++) { for (var j = 0; j < n; j++) { var maxIJ = subMatrixSizeStore.get(get_key_of_store(i, j)); if (maxIJ > maxSize) { maxI = i; maxJ = j; maxSize = maxIJ; } } } console.log(); console.log(squareMatrix); console.log(); console.log('Max square sub matrix with all 1s'); console.log('-------------------------------------'); console.log(' I ends at\t\t:', maxI); console.log(' J ends at\t\t:', maxJ); console.log(' matrix size\t\t:', maxSize); console.log(' # of workers\t:', NUMBER_OF_WORKERS); console.log(' Latency in MS\t:', ms); console.log('-------------------------------------'); } // Run program. run(); ================================================ FILE: examples/tutorial/max-square-sub-matrix/package.json ================================================ { "name": "napajs-tutorial", "version": "0.1.0", "author": "napajs", "main": "./max-square-sub-matrix.js", "dependencies": { "napajs": ">= 0.1.0" } } ================================================ FILE: examples/tutorial/napa-runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2 FATAL_ERROR) project("napa-runer") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin) # Require Cxx14 features set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(NAPAJS_ROOT ${PROJECT_SOURCE_DIR}/../../..) set(NAPAJS_INC ${NAPAJS_ROOT}/inc) find_library(NAPAJS_LIB NAMES napa PATHS ${NAPAJS_ROOT}/bin) # A stable node version that can build as a library. set(NODE_ROOT ${NAPAJS_ROOT}/build/node-v6.10.3) set(BUILD_TYPE "Release") if ((DEFINED CMAKE_BUILD_TYPE) AND (CMAKE_BUILD_TYPE STREQUAL "debug")) set(BUILD_TYPE "Debug") endif() set(TARGET_NAME "${PROJECT_NAME}") add_executable(${TARGET_NAME} "main.cpp") target_compile_definitions(${TARGET_NAME} PRIVATE BUILDING_NAPA_EXTENSION) if("${CMAKE_SYSTEM}" MATCHES "Linux") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") # Include directories target_include_directories(${TARGET_NAME} PRIVATE ${NODE_ROOT}/deps/v8/include ${NAPAJS_INC}) # TODO #124: Remove dependencies of v8_libbase and icustubdata to embed Napa.js into a c++ program, because they are already dependencies to build napa shared library. find_library(V8_LIBBASE NAMES v8_libbase PATHS ${NODE_ROOT}/out/${BUILD_TYPE}/obj.target/deps/v8/tools/gyp) find_library(ICUSTUBDATA_LIB NAMES icustubdata PATHS ${NODE_ROOT}/out/${BUILD_TYPE}/obj.target/tools/icu) # Link libraries target_link_libraries(${TARGET_NAME} PRIVATE ${NAPAJS_LIB} ${CMAKE_DL_LIBS} ${V8_LIBBASE} ${ICUSTUBDATA_LIB} ) else() message(FATAL_ERROR "Build solution of this example is not provided for ${CMAKE_SYSTEM}") endif() ================================================ FILE: examples/tutorial/napa-runner/README.md ================================================ # Napa runner Napa runner is an example to embed Napa.JS into a C++ program. It simply runs JavaScript with all Napa capability without Node dependency from command line. ## How to build 1. Go to napajs root directory and run `node build.js embed` to build napa library for embeded mode. **NOTE**: This step may take about 30 mins, because it will build V8 library from Node. We are using node v6.10.3, a stable version can build as a library. It is specified in [embedded.js](https://github.com/Microsoft/napajs/blob/master/scripts/embedded.js) and [napa-runner CMakeLists.txt](https://github.com/Microsoft/napajs/blob/master/examples/tutorial/napa-runner/CMakeLists.txt). Please update both of them if you want to use a different version of Node/V8. 2. Go to directory of `examples/tutorial/napa-runner`, and run `cmake-js build` to build napa runner **NOTE**: Build solution of napa-runner is provided only for linux system so far. Windows / Mac OS support will come in near future. ## How to use 1. Run `npm install` to intall required npm modules 2. Run `./bin/napa-runner emstimate-PI.js` ================================================ FILE: examples/tutorial/napa-runner/estimate-PI.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. function estimatePI(points) { var i = points; var inside = 0; while (i-- > 0) { if (i%10000 == 0) console.log(i); var x = Math.random(); var y = Math.random(); if ((x * x) + (y * y) <= 1) { inside++; } } return inside / points * 4; } function run() { console.log('start run'); var napa = require('napajs'); console.log('napajs loaded'); const NUMBER_OF_WORKERS = 4; var zone = napa.zone.create('zone1', { workers: NUMBER_OF_WORKERS }); console.log('zone created with 4 workers'); var promises = []; for (var i = 0; i < 4; i++) { promises[i] = zone.execute(estimatePI, [40000]); } console.log('4 times napa.zone.execution issued.'); Promise.all(promises).then(values => { var aggregate = 0; values.forEach(result => aggregate += result.value); console.log('PI: ', aggregate / 4); }); return 'returned-value-to-cpp-world'; } run(); ================================================ FILE: examples/tutorial/napa-runner/main.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include #include void ReadJSFromFile(std::stringstream& jsStream, const std::string& jsFileName); int main(int argc, char* argv[]) { if (argc != 2) { std::cout << "usage: napa-runner " << std::endl; exit(0); } // Read js from user specifed js file. std::stringstream jsStream; ReadJSFromFile(jsStream, argv[1]); // Initializes napa with glabal scope settings. napa::Initialize(); // Create a napa zone with 1 worker. auto mainZone = std::make_unique("main", "--workers 1"); // Broadcast js workload. napa::ResultCode resultCode = mainZone->BroadcastSync(jsStream.str()); if (resultCode != NAPA_RESULT_SUCCESS) { std::cout << napa_result_code_to_string(resultCode) << std::endl; // Shut down napa. napa::Shutdown(); exit(1); } // Put a dead loop here to allow all tasks to complete in napa zones before shutting down napa. // TODO #123: Support shut down napa gracefully to save this. while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } // Shut down napa. napa::Shutdown(); return 0; } void ReadJSFromFile(std::stringstream& jsStream, const std::string& jsFileName) { std::ifstream jsFile; jsFile.open(jsFileName); if (!jsFile) { std::cout << "Failed to open js file: " << jsFileName << std::endl; exit(0); } jsStream << jsFile.rdbuf(); jsFile.close(); } ================================================ FILE: examples/tutorial/napa-runner/package.json ================================================ { "name": "napajs-tutorial", "version": "0.1.0", "author": "napajs", "dependencies": { "napajs": ">= 0.1.4" } } ================================================ FILE: examples/tutorial/parallel-quick-sort/README.md ================================================ # Parallel Quick Sort This example implements a parallel version of quicksort by napa.js, and compares its performance to that of a serial version. It demonstrates 1. How does napa.js transport a TypedArray (JavaScript built-in objects) among napa worker transparently. The TypedArray is created from a SharedArrayBuffer. 2. How does napa.js accelerate a computation heavy task by parallelization. ## How to run 1. Go to directory of `examples/tutorial/parallel-quick-sort`. 2. Run `npm install` to install napajs. 3. Run `node parallel-quick-sort.js`. **Note**: This example involves TypedArray and SharedArrayBuffer. It requires Node v9.0.0 or newer. ## Program output The table below shows results of the serial quicksort and the parallel one. ``` quickSort: It took ( 1006 ) MS to sort an array with 4194304 elements. parallelQuickSort: It took ( 388 ) MS to sort an array with 4194304 elements. ``` Both of them were executed on a 4 millions length Float64Array. The parallel implementation by napa.js brought 60+% performance gain under my test environment below. | Name | Value | |-------------------|---------------------------------------------------------------------------------------| |**Processor** |Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz, 2000 Mhz, 6 Core(s), 12 Logical Processor(s) | |**System Type** |x64-based PC | |**Physical Memory**|32.0 GB | |**OS version** |Microsoft Windows Server 2016 Datacenter | ================================================ FILE: examples/tutorial/parallel-quick-sort/package.json ================================================ { "name": "napajs-tutorial", "version": "0.1.0", "author": "napajs", "main": "./parallel-quick-sort.js", "dependencies": { "napajs": ">= 0.1.8" } } ================================================ FILE: examples/tutorial/parallel-quick-sort/parallel-quick-sort.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. const napa = require("napajs"); const assert = require("assert"); const MILLION = 1024 * 1024; // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. let zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); // Swap elements with index 'left' and 'right' in a TypedArray. function swap(typedArray, left, right) { let temp = typedArray[left]; typedArray[left] = typedArray[right]; typedArray[right] = temp; } function partition(typedArray, left, right) { let pi = left - 1; let pivot = typedArray[right]; for (let index = left; index <= right; index++) { if (typedArray[index] <= pivot) { swap(typedArray, ++pi, index); } } return pi; } function quickSort(typedArray, left, right) { if (right <= left) return; let pi = partition(typedArray, left, right); quickSort(typedArray, left, pi - 1); quickSort(typedArray, pi + 1, right); } // A parallel version of quicksort by napa.js. // After partition, if any part (left or right) is longer than parallelLength, // it will recursively execute parallelQuickSort, otherwise, it will execute quickSort. function parallelQuickSort(typedArray, left, right, parallelLength) { if (right <= left) return; let pi = global.partition(typedArray, left, right); let promises = []; if (pi - left >= parallelLength) { promises.push(global.napa.zone.get('zone').execute('', 'parallelQuickSort', [typedArray, left, pi - 1, parallelLength])); } else { promises.push(global.napa.zone.get('zone').execute('', 'quickSort', [typedArray, left, pi - 1])); } if (right - pi >= parallelLength) { promises.push(global.napa.zone.get('zone').execute('', 'parallelQuickSort', [typedArray, pi + 1, right, parallelLength])); } else { promises.push(global.napa.zone.get('zone').execute('', 'quickSort', [typedArray, pi + 1, right])); } return Promise.all(promises).then(values => {return true;}); } function run(length) { let sab1 = new SharedArrayBuffer(length * 8); let ta1 = new Float64Array(sab1); let sab2 = new SharedArrayBuffer(length * 8); let ta2 = new Float64Array(sab2); // Initialize the 2 TypedArrays with the same random numbers. for (let i = 0; i < length; i++) { let temp = Math.random(); ta1[i] = temp; ta2[i] = temp; } // Execute quickSort. let start1 = Date.now(); quickSort(ta1, 0, length - 1); console.log('quickSort:\t\tIt took (', Date.now() - start1, ') MS to sort an array with ', length, ' elements.'); // Assert the TypedArray has been sorted in ascending order. for (let i = 0; i < length - 1; i++) { assert(ta1[i] <= ta1[i + 1], 'the array is not in ascending order after quicksort.') } // Execute parallelQuickSort. let parallelLength = length / NUMBER_OF_WORKERS; let start2 = Date.now(); return zone.execute(parallelQuickSort, [ta2, 0, length - 1, parallelLength]).then(result => { console.log('parallelQuickSort:\tIt took (', Date.now() - start2, ') MS to sort an array with ', length, ' elements.'); // Assert the TypedArray has been sorted in ascending order. for (let i = 0; i < length - 1; i++) { assert(ta2[i] <= ta2[i + 1], 'the array is not in ascending order after quicksort.') } }); } // Bootstrap zone workers. zone.broadcast('napa = require("napajs");'); zone.broadcast(swap.toString()); zone.broadcast(partition.toString()); zone.broadcast(quickSort.toString()); zone.broadcast(parallelQuickSort.toString()); console.log(); // Run with length of 4 millions. run(4 * MILLION); ================================================ FILE: examples/tutorial/recursive-fibonacci/README.md ================================================ # Recursive Fibonacci This example implements an algorithm to calculate [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) in a distributed recursive way. Recursion is supported on a single thread by JavaScript language, how if we want to involve multiple JavaScript threads to collaborate on a task? Since recursion will block caller until callee returns, dispatching recursive tasks to other threads will soon block all threads, which leads to deadlock. This example demonstrates recursive dispatching using [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), that is, current thread will continue to serve other tasks while its sub-tasks are pending, and resume work once sub-tasks complete. Please note that this example is to demonstrate the programming paradigm, while itself is *NOT* performance efficient, since each worker does too little CPU operation (simply '+') and major overhead is on communication. ## How to run 1. Go to directory of `examples/tutorial/recursive-fibonacci` 2. Run `npm install`to install napajs 3. Run `node recursive-fibonacci.js` ## Program output Table below shows statistics of calculating Nth Fibonacci number by a napa zone with 4 workers. ``` Nth Fibonacci # of workers latency in MS ----------------------------------------------------------- 10 55 4 46 11 89 4 47 12 144 4 47 13 233 4 47 14 377 4 94 15 610 4 140 16 987 4 266 ``` We got results under environment: | Name | Value | |-------------------|---------------------------------------------------------------------------------------| |**Processor** |Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz, 2000 Mhz, 6 Core(s), 12 Logical Processor(s) | |**System Type** |x64-based PC | |**Physical Memory**|32.0 GB | |**OS version** |Microsoft Windows Server 2016 Datacenter | ================================================ FILE: examples/tutorial/recursive-fibonacci/package.json ================================================ { "name": "napajs-tutorial", "version": "0.1.0", "author": "napajs", "main": "./recursive-fibonacci.js", "dependencies": { "napajs": ">= 0.1.0" } } ================================================ FILE: examples/tutorial/recursive-fibonacci/recursive-fibonacci.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. var napa = require("napajs"); // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. var zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); /* Fibonacci sequence starts with 0 and 1, which we'll think of as the zeroth and first Fibonacci numbers, and each succeeding number is the sum of the two preceding Fibonacci numbers. Thus, the second number is 0 + 1 = 1. And to get the third Fibonacci number, we'd sum the first (1) and the second (1) to get 2. And the fourth is the sum of the second (1) and the third (2), which is 3. And so on. n: | 0 1 2 3 4 5 6 7 8 9 10 11 ... ------------------------------------------------------------------------- NTH Fibonacci: | 0 1 1 2 3 5 8 13 21 34 55 89 ... */ function fibonacci(n) { if (n <= 1) { return n; } var p1 = zone.execute("", "fibonacci", [n - 1]); var p2 = zone.execute("", "fibonacci", [n - 2]); // Returning promise to avoid blocking each worker. return Promise.all([p1, p2]).then(([result1, result2]) => { return result1.value + result2.value; }); } function run(n) { var start = Date.now(); return zone.execute('', "fibonacci", [n]) .then(result => { printResult(n, result.value, Date.now() - start); return result.value; }); } function printResult(nth, fibonacci, ms) { console.log('\t' + nth + '\t' + fibonacci + '\t\t' + NUMBER_OF_WORKERS + '\t\t' + ms); } console.log(); console.log('\tNth\tFibonacci\t# of workers\tlatency in MS'); console.log('\t-----------------------------------------------------------'); // Broadcast declaration of 'napa' and 'zone' to napa workers. zone.broadcast(' \ var napa = require("napajs"); \ var zone = napa.zone.get("zone"); \ '); // Broadcast function declaration of 'fibonacci' to napa workers. zone.broadcast(fibonacci.toString()); // Run fibonacci evaluation in sequence. run(10) .then(result => run(11)) .then(result => run(12)) .then(result => run(13)) .then(result => run(14)) .then(result => run(15)) .then(result => run(16)) ================================================ FILE: examples/tutorial/synchronized-loading/README.md ================================================ # Synchronized loading This example implements a shared phone book component. The component will not load data until the first lookup request happens. When it starts to load data, it ensures the loading will run only once. The component is implemented using lazy-loading pattern with the use of [`napa.sync.Lock`](./../../../docs/api/sync.md#interface-lock). ## How to run 1. Go to directory of `examples/tutorial/synchronized-loading` 2. Run `npm install` to install napajs 3. Run `node synchronized-loading.js` ## Program output The output below shows one possible result. The sequence of the output may be different but the data loading will always run once only. ``` [lookupPhoneNumber] Start to lookup phone number of david. [lookupPhoneNumber] Start to lookup phone number of wade. [load_data] loading... [lookupPhoneNumber] Start to lookup phone number of lisa. [lookupPhoneNumber] wade : . [lookupPhoneNumber] lisa : 567-888-9999. [lookupPhoneNumber] david : 123-444-5555. [run] All operations are completed. ``` ================================================ FILE: examples/tutorial/synchronized-loading/package.json ================================================ { "name": "napajs-tutorial", "version": "0.1.0", "author": "napajs", "main": "./synchronized-loading.js", "dependencies": { "napajs": ">= 0.1.8" } } ================================================ FILE: examples/tutorial/synchronized-loading/phone-book-data.json ================================================ { "ashley": "123-456-7890", "david": "123-444-5555", "lisa": "567-888-9999", "tony": "456-789-0000" } ================================================ FILE: examples/tutorial/synchronized-loading/phone-book.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. const napa = require("napajs"); const store = napa.store.getOrCreate('my-phone-book'); const initialize = function () { store.set('_loadLock', napa.sync.createLock()); } const load_data = function () { // load data. This function should run only once. console.log('[load_data] loading...'); const fs = require('fs'); let phoneBookData = JSON.parse(fs.readFileSync('./phone-book-data.json').toString()); for (let name in phoneBookData) { store.set(name, phoneBookData[name]); } store.set('_loaded', true); } let loaded = false; const lookup = function (name) { if (!loaded) { const lock = store.get('_loadLock'); lock.guardSync(function() { if (!store.get('_loaded')) { load_data(); } }); loaded = true; } return store.get(name); }; module.exports.initialize = initialize; module.exports.lookup = lookup; ================================================ FILE: examples/tutorial/synchronized-loading/synchronized-loading.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. const napa = require("napajs"); // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. const zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); function run() { // Initialize the phone book component const phoneBook = require('./phone-book'); phoneBook.initialize(); // Setup all workers with functions to be executed. zone.broadcast(' \ var napa = require("napajs"); \ var zone = napa.zone.get("zone"); \ var phoneBook = require("./phone-book"); \ '); zone.broadcast(lookupPhoneNumber.toString()); var tasks = []; tasks.push(zone.execute('', 'lookupPhoneNumber', ['david'])); tasks.push(zone.execute('', 'lookupPhoneNumber', ['lisa'])); tasks.push(zone.execute('', 'lookupPhoneNumber', ['wade'])); Promise.all(tasks).then(function () { console.log('[run] All operations are completed.'); }); } function lookupPhoneNumber(name) { console.log(`[lookupPhoneNumber] Start to lookup phone number of ${name}.`); var n = phoneBook.lookup(name); console.log(`[lookupPhoneNumber] ${name} : ${n ? n : ''}.`); } // Run program. run(); ================================================ FILE: inc/napa/assert.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include /// The maximum message length of a single assert call. Anything over will be truncated. static constexpr size_t MAX_ASSERT_MESSAGE_SIZE = 512; inline void OutputAssertMessage(const char* condition, const char* file, int line, const char* format, ...) { char message[MAX_ASSERT_MESSAGE_SIZE]; va_list args; va_start(args, format); int size = vsnprintf(message, MAX_ASSERT_MESSAGE_SIZE, format, args); va_end(args); if (size >= 0) { std::cerr << "Assertion failed: `" << condition << "`, file " << file << ", line " << line << " : " << message << "." << std::endl; } } #define NAPA_ASSERT(condition, format, ...) do { \ if (!(condition)) { \ OutputAssertMessage(#condition, __FILE__, __LINE__, format, ##__VA_ARGS__); \ std::terminate(); \ } \ } while (false) #define NAPA_FAIL(message) NAPA_ASSERT(false, message) ================================================ FILE: inc/napa/async.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #ifdef BUILDING_NAPA_EXTENSION #include "napa/zone/napa-async-runner.h" #else #include "napa/zone/node-async-runner.h" #endif ================================================ FILE: inc/napa/capi.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "napa/exports.h" #include "napa/types.h" /// Creates a napa zone. /// A unique id for the zone. /// /// This function returns a handle that must be release when it's no longer needed. /// Napa keeps track of all zone handles and destroys the zone when all handles have been released. /// EXTERN_C NAPA_API napa_zone_handle napa_zone_create(napa_string_ref id); /// Retrieves a zone by id. /// A unique id for the zone. /// The zone handle if exists, null otherwise. /// /// This function returns a handle that must be release when it's no longer needed. /// Napa keeps track of all zone handles and destroys the zone when all handles have been released. /// EXTERN_C NAPA_API napa_zone_handle napa_zone_get(napa_string_ref id); /// Retrieves the current zone. /// The zone handle if this thread is associated with one, null otherwise. /// /// This function returns a handle that must be release when it's no longer needed. /// Napa keeps track of all zone handles and destroys the zone when all handles have been released. /// EXTERN_C NAPA_API napa_zone_handle napa_zone_get_current(); /// Releases the zone handle. When all handles for a zone are released the zone is destroyed. /// The zone handle. EXTERN_C NAPA_API napa_result_code napa_zone_release(napa_zone_handle handle); /// /// Initializes the napa zone, providing specific settings. /// The provided settings override any settings that were previously set. /// TODO: specify public settings here /// /// The zone handle. /// The settings string. EXTERN_C NAPA_API napa_result_code napa_zone_init( napa_zone_handle handle, napa_string_ref settings); /// Retrieves the zone id. /// The zone handle. EXTERN_C NAPA_API napa_string_ref napa_zone_get_id(napa_zone_handle handle); /// Executes a pre-loaded function asynchronously on all zone workers. /// The zone handle. /// The function spec to call. /// A callback that is triggered when broadcast is done. /// An opaque pointer that is passed back in the callback. EXTERN_C NAPA_API void napa_zone_broadcast( napa_zone_handle handle, napa_zone_function_spec spec, napa_zone_broadcast_callback callback, void* context); /// Executes a pre-loaded function asynchronously in a single zone worker. /// The zone handle. /// The function spec to call. /// A callback that is triggered when execution is done. /// An opaque pointer that is passed back in the callback. EXTERN_C NAPA_API void napa_zone_execute( napa_zone_handle handle, napa_zone_function_spec spec, napa_zone_execute_callback callback, void* context); /// /// Global napa initialization. Invokes initialization steps that are cross zones. /// The settings passed represent the defaults for all the zones /// but can be overriden in the zone initialization API. /// TODO: specify public settings here. /// /// The settings string. EXTERN_C NAPA_API napa_result_code napa_initialize(napa_string_ref settings); /// /// Same as napa_initialize only accepts arguments as provided by console /// /// Number of arguments. /// The arguments. EXTERN_C NAPA_API napa_result_code napa_initialize_from_console( int argc, const char* argv[]); /// Invokes napa shutdown steps. All non released zones will be destroyed. EXTERN_C NAPA_API napa_result_code napa_shutdown(); /// Convert the napa result code to its string representation. /// The result code. EXTERN_C NAPA_API const char* napa_result_code_to_string(napa_result_code code); /// Set customized allocator, which will be used for napa_allocate and napa_deallocate. /// If user doesn't call napa_allocator_set, C runtime malloc/free from napa.dll will be used. /// Function pointer for allocating memory, which should be valid during the entire process. /// Function pointer for deallocating memory, which should be valid during the entire process. EXTERN_C NAPA_API void napa_allocator_set( napa_allocate_callback allocate_callback, napa_deallocate_callback deallocate_callback); /// Allocate memory using napa allocator from napa_allocator_set, which is using C runtime ::malloc if not called. /// Size of memory requested in byte. /// Allocated memory. EXTERN_C NAPA_API void* napa_allocate(size_t size); /// Free memory using napa allocator from napa_allocator_set, which is using C runtime ::free if not called. /// Pointer to memory to be freed. /// Hint of size to deallocate. EXTERN_C NAPA_API void napa_deallocate(void* pointer, size_t size_hint); /// Allocate memory using C runtime ::malloc from napa.dll. /// Size of memory requested in byte. /// Allocated memory. EXTERN_C NAPA_API void* napa_malloc(size_t size); /// Free memory using C runtime ::free from napa.dll. /// Pointer to memory to be freed. /// Hint of size to deallocate. EXTERN_C NAPA_API void napa_free(void* pointer, size_t size_hint); ================================================ FILE: inc/napa/exports.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once // Microsoft Windows #if defined(_WIN32) || defined(__WIN32__) #define DLL_EXPORT __declspec(dllexport) #define DLL_IMPORT __declspec(dllimport) // Linux #elif defined(__GNUC__) #define DLL_EXPORT __attribute__((visibility("default"))) #define DLL_IMPORT #else static_assert(false, "Unknown dynamic link import/export semantics"); #endif // API exported from napa.dll #ifdef NAPA_EXPORTS #define NAPA_API DLL_EXPORT #else #define NAPA_API DLL_IMPORT #endif // NAPA_EXPORTS // API exported from napa-binding. (both napa.dll and napa-binding.node) #ifdef NAPA_BINDING_EXPORTS #define NAPA_BINDING_API DLL_EXPORT #else #define NAPA_BINDING_API DLL_IMPORT #endif // NAPA_BINDING_EXPORTS #ifdef __cplusplus #define EXTERN_C extern "C" #else #define EXTERN_C #endif // __cplusplus ================================================ FILE: inc/napa/log.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include /// The maximum string length of a single log call. Anything over will be truncated. const size_t LOG_MAX_SIZE = 512; inline void LogFormattedMessage( napa::providers::LoggingProvider& logger, const char* section, napa::providers::LoggingProvider::Verboseness level, const char* traceId, const char* file, int line, const char* format, ...) { char message[LOG_MAX_SIZE]; va_list args; va_start(args, format); int size = vsnprintf(message, LOG_MAX_SIZE, format, args); va_end(args); NAPA_ASSERT(size >= 0, "Log formatting error, probably wrong format encoding"); logger.LogMessage(section, level, traceId, file, line, message); } #ifndef NAPA_LOG_DISABLED #define LOG(section, level, traceId, format, ...) do { \ auto& logger = napa::providers::GetLoggingProvider(); \ if (logger.IsLogEnabled(section, level)) { \ LogFormattedMessage(logger, section, level, traceId, __FILE__, __LINE__, format, ##__VA_ARGS__); \ } \ } while (false) #else // Do nothing without generating any "unreferenced variable" warnings. template inline void LOG(Types&&...) {} #endif #define LOG_ERROR(section, format, ...) \ LOG(section, napa::providers::LoggingProvider::Verboseness::Error, "", format, ##__VA_ARGS__) #define LOG_ERROR_WITH_TRACEID(section, traceId, format, ...) \ LOG(section, napa::providers::LoggingProvider::Verboseness::Error, traceId, format, ##__VA_ARGS__) #define LOG_WARNING(section, format, ...) \ LOG(section, napa::providers::LoggingProvider::Verboseness::Warning, "", format, ##__VA_ARGS__) #define LOG_WARNING_WITH_TRACEID(section, traceId, format, ...) \ LOG(section, napa::providers::LoggingProvider::Verboseness::Warning, traceId, format, ##__VA_ARGS__) #define LOG_INFO(section, format, ...) \ LOG(section, napa::providers::LoggingProvider::Verboseness::Info, "", format, ##__VA_ARGS__) #define LOG_INFO_WITH_TRACEID(section, traceId, format, ...) \ LOG(section, napa::providers::LoggingProvider::Verboseness::Info, traceId, format, ##__VA_ARGS__) #define LOG_DEBUG(section, format, ...) \ LOG(section, napa::providers::LoggingProvider::Verboseness::Debug, "", format, ##__VA_ARGS__) #define LOG_DEBUG_WITH_TRACEID(section, traceId, format, ...) \ LOG(section, napa::providers::LoggingProvider::Verboseness::Debug, traceId, format, ##__VA_ARGS__) // Macro NAPA_DEBUG is used to help debugging Napa source code. #if defined(NAPA_DEBUG_ENABLED) #define NAPA_DEBUG(section, format, ...) LOG_DEBUG(section, format, ##__VA_ARGS__) #else // Do nothing without generating any "unreferenced variable" warnings. #define NAPA_DEBUG(section, format, ...) ((void)0) #endif ================================================ FILE: inc/napa/memory/allocator-debugger.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include namespace napa { namespace memory { /// Interface for allocator debugger. class AllocatorDebugger: public Allocator { public: /// Get allocator debug information in JSON. /// It's up to implementation to decide the schema of JSON. /// virtual std::string GetDebugInfo() const = 0; protected: virtual ~AllocatorDebugger() = default; }; /// Simple allocator debugger that reports total allocate/deallocate count and total size allocated. class SimpleAllocatorDebugger: public AllocatorDebugger { public: /// Constructor /// Actual allocator to allocate/deallocate memory. SimpleAllocatorDebugger(std::shared_ptr allocator) : _allocator(std::move(allocator)), _allocateCount(0), _deallocateCount(0), _allocatedSize(0), _deallocatedSize(0) { std::stringstream stream; stream << "SimpleAllocatorDebugger<" << _allocator->GetType() << ">"; _typeName = stream.str(); } /// Copy constructor SimpleAllocatorDebugger(const SimpleAllocatorDebugger& other) : _allocator(other._allocator), _typeName(other._typeName), _allocateCount(other._allocateCount.load()), _deallocateCount(other._deallocateCount.load()), _allocatedSize(other._allocatedSize.load()), _deallocatedSize(other._deallocatedSize.load()) { } /// Allocate memory of given size. /// Requested size. /// Allocated memory. May throw if error happens. void* Allocate(size_t size) override { _allocateCount++; _allocatedSize += size; return _allocator->Allocate(size); } /// Deallocate memory allocated from this allocator. /// Pointer to the memory. /// Hint of size to delete. 0 if not available from caller. /// None. May throw if error happens. void Deallocate(void* memory, size_t sizeHint) override { _deallocateCount++; _deallocatedSize += sizeHint; _allocator->Deallocate(memory, sizeHint); } /// Get allocator type for better debuggability. const char* GetType() const override { return _typeName.c_str(); } /// Get allocator debug information in JSON. /// Allocator debugger should own returned buffer. /// std::string GetDebugInfo() const override { std::stringstream stream; stream << "{ " << "\"allocate\": " << _allocateCount << ", " << "\"deallocate\": " << _deallocateCount << ", " << "\"allocatedSize\": " << _allocatedSize << ", " << "\"deallocatedSize\": " << _deallocatedSize << " }"; return stream.str(); } /// Tell if another allocator equals to this allocator. bool operator==(const Allocator& other) const override { return strcmp(other.GetType(), GetType()) == 0 && (_allocator == dynamic_cast(&other)->_allocator); } private: std::shared_ptr _allocator; std::string _typeName; std::atomic _allocateCount; std::atomic _deallocateCount; std::atomic _allocatedSize; std::atomic _deallocatedSize; }; } } ================================================ FILE: inc/napa/memory/allocator.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace memory { /// Interface for allocator. class Allocator { public: /// Allocate memory of given size. /// Requested size. /// Allocated memory. May throw if error happens. virtual void* Allocate(size_t size) = 0; /// Deallocate memory allocated from this allocator. /// Pointer to the memory. /// Hint of size to delete. 0 if not available from caller. /// None. May throw if error happens. virtual void Deallocate(void* memory, size_t sizeHint) = 0; /// Get allocator type for better debuggability. virtual const char* GetType() const = 0; /// Tell if another allocator equals to this allocator. virtual bool operator==(const Allocator& other) const = 0; /// Can only be destructed by child class. virtual ~Allocator() = default; }; /// Get a long living CRT allocator for convenience. User can create their own as well. NAPA_API Allocator& GetCrtAllocator(); /// Get a long living default allocator for convenience. User can create their own as well. NAPA_API Allocator& GetDefaultAllocator(); } } ================================================ FILE: inc/napa/memory/common.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include namespace napa { namespace memory { /// Deleter using Napa's default allocator. template void DefaultDeleter(T* object) { static_assert(std::is_destructible::value, "Not destructible."); object->~T(); ::napa_deallocate(object, sizeof(T)); } /// std::unique_ptr using Napa default allocator. template using UniquePtr = std::unique_ptr; /// std::make_unique using Napa default allocator. template UniquePtr MakeUnique(Args&&... args) { void* memory = ::napa_allocate(sizeof(T)); T* t = new (memory) T(std::forward(args)...); return UniquePtr(t, DefaultDeleter); } /// std::allocate_shared using napa::memory::Allocator. template std::shared_ptr AllocateShared(Alloc& allocator, Args&&... args) { return std::allocate_shared( napa::stl::Allocator(allocator), std::forward(args)...); } /// std::make_shared using Napa default allocator. template std::shared_ptr MakeShared(Args&&... args) { return AllocateShared( GetDefaultAllocator(), std::forward(args)...); } } } ================================================ FILE: inc/napa/memory.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #define NAPA_MALLOC(size) ::napa_malloc(size) #define NAPA_FREE(pointer, sizeHint) ::napa_free(pointer, sizeHint) #define NAPA_SET_DEFAULT_ALLOCATOR(malloc, free) ::napa_allocator_set(malloc, free) #define NAPA_RESET_DEFAULT_ALLOCATOR() ::napa_allocator_set(napa_malloc, napa_free) #define NAPA_ALLOCATE(size) ::napa_allocate(size) #define NAPA_DEALLOCATE(pointer, sizeHint) ::napa_deallocate(pointer, sizeHint) #define NAPA_DEFAULT_ALLOCATOR napa::memory::GetDefaultAllocator() #define NAPA_CRT_ALLOCATOR napa::memory::GetCrtAllocator() #define NAPA_MAKE_UNIQUE napa::memory::MakeUnique #define NAPA_MAKE_SHARED napa::memory::MakeShared #define NAPA_ALLOCATE_SHARED napa::memory::AllocateShared ================================================ FILE: inc/napa/module/binding/basic-wraps.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace module { namespace binding { /// It creates a new instance of wrapType with a shared_ptr. /// shared_ptr of object. /// wrap type from napa-binding, which extends napa::module::Sharable. /// V8 object of wrapType. template inline v8::Local CreateShareableWrap(std::shared_ptr object, const char* wrapType = "SharedPtrWrap") { auto instance = NewInstance(wrapType, 0, nullptr).ToLocalChecked(); ShareableWrap::Set(instance, std::move(object)); return instance; } } } } ================================================ FILE: inc/napa/module/binding/wraps.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include namespace napa { namespace module { namespace binding { /// It creates a new instance of AllocatorWrap. /// shared_ptr of allocator. /// V8 object of AllocatorWrap. inline v8::Local CreateAllocatorWrap(std::shared_ptr allocator) { return CreateShareableWrap(allocator, "AllocatorWrap"); } /// It creates a new instance of AllocatorDebuggerWrap. /// shared_ptr of allocatorDebugger. /// V8 object of AllocatorDebuggerWrap. inline v8::Local CreateAllocatorDebuggerWrap(std::shared_ptr allocatorDebugger) { return CreateShareableWrap(allocatorDebugger, "AllocatorDebuggerWrap"); } } } } ================================================ FILE: inc/napa/module/binding.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include #include namespace napa { namespace module { namespace binding { /// Get 'module' object of napa binding, which is napa-binding.node in Node.JS isolate or napa-binding from core-modules in Napa isolate. /// 'module' object for napa binding (napajs/bin/napa-binding.node or napa.dll) NAPA_BINDING_API v8::Local GetModule(); /// Get 'module.exports' from napa binding. /// 'module.exports' object for napa binding (napajs/bin/napa-binding.node or napa.dll) inline v8::Local GetBinding() { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto bindingModule = GetModule(); auto binding = bindingModule->Get(napa::v8_helpers::MakeV8String(isolate, "exports")); NAPA_ASSERT(!binding.IsEmpty() && binding->IsObject(), "\"exports\" is not available or not object type."); return scope.Escape(v8::Local::Cast(binding)); } /// It calls 'module.require' from context of napa binding in C++. /// Module name in node 'require' convention. /// Object if success, or an empty handle with exception thrown. /// /// 1) 'napajs' must be required from JS first before Require can be used. /// 2) Base directory calling 'require' is from 'napajs/bin'. /// inline v8::MaybeLocal Require(const char* moduleName) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto bindingModule = GetModule(); auto require = bindingModule->Get(napa::v8_helpers::MakeV8String(isolate, "require")); NAPA_ASSERT(!require.IsEmpty() && require->IsFunction(), "Function \"require\" is not available from module object"); v8::Local argv[] = { napa::v8_helpers::MakeV8String(isolate, moduleName)}; return scope.Escape( napa::v8_helpers::ToLocal( v8::Local::Cast(require)->Call(isolate->GetCurrentContext(), bindingModule, 1, argv))); } /// Create a new instance of a wrap type exported from napa binding. inline v8::MaybeLocal NewInstance(const char* wrapType, int argc, v8::Local argv[]) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto binding = GetBinding(); auto constructor = binding->Get(napa::v8_helpers::MakeV8String(isolate, wrapType)); JS_ENSURE_WITH_RETURN( isolate, !constructor.IsEmpty() && constructor->IsFunction(), v8::MaybeLocal(), "Wrap type \"%s\" is not found in napa binding.", wrapType); return scope.Escape( v8::Local::Cast(constructor)->NewInstance(isolate->GetCurrentContext(), argc, argv) .FromMaybe(v8::Local())); } /// Call a function from a module under current context /// Module name. /// Function from module. /// Number of arguments. /// Actual arguments. /// Return value of function, or an empty handle if exception is thrown. /// moduleName should take 'napajs/bin' as base directory. inline v8::MaybeLocal NewInstance( const char* moduleName, const char* className, int argc = 0, v8::Local argv[] = nullptr) { auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); v8::EscapableHandleScope scope(isolate); auto moduleHandle = Require(moduleName); RETURN_VALUE_ON_PENDING_EXCEPTION(moduleHandle, v8::MaybeLocal()); auto module = moduleHandle.ToLocalChecked(); auto constructor = v8::Local::Cast( module->Get(v8_helpers::MakeV8String(isolate, className))); JS_ENSURE_WITH_RETURN( isolate, !constructor.IsEmpty(), v8::MaybeLocal(), "Class \"%s\" is not found in \"%s\".", className, moduleName); return scope.Escape( constructor->NewInstance(context, argc, argv) .FromMaybe(v8::Local())); } /// Call a function from a module under current context /// Module name. /// Function from module. /// Number of arguments. /// Actual arguments. /// Return value of function, or an empty handle if exception is thrown. /// moduleName should take 'napajs/bin' as base directory. inline v8::MaybeLocal Call( const char* moduleName, const char* functionName, int argc = 0, v8::Local argv[] = nullptr) { auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); v8::EscapableHandleScope scope(isolate); auto moduleHandle = Require(moduleName); RETURN_VALUE_ON_PENDING_EXCEPTION(moduleHandle, v8::MaybeLocal()); auto module = moduleHandle.ToLocalChecked(); auto function = module->Get(v8_helpers::MakeV8String(isolate, functionName)); JS_ENSURE_WITH_RETURN( isolate, !function.IsEmpty() && function->IsFunction(), v8::MaybeLocal(), "Function \"%s\" is not found in module \"%s\".", functionName, moduleName); return scope.Escape( v8::Local::Cast(function)->Call(context, module, argc, argv) .FromMaybe(v8::Local())); } } } } ================================================ FILE: inc/napa/module/common.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace module { /// It defines a default constructor for a NAPA_OBJECTWRAP sub-class from Javascript world. /// /// 1. const char* WrapType::exportName must be present as a member of WrapType. /// 2. DefaultConstructorCallback must be declared as a friend function in WrapType. /// template inline void DefaultConstructorCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 0, "class \"%s\" doesn't accept any arguments in constructor.'", WrapType::exportName); JS_ENSURE(isolate, args.IsConstructCall(), "class \"%s\" allows constructor call only.", WrapType::exportName); // It's deleted when its Javascript object is garbage collected by V8's GC. auto wrap = new WrapType(); wrap->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } /// Create an instance of WrapType with arguments. /// There are 2 requirements on WrapType: /// 1) static const char* WrapType::exportName must be present as a public member, or add NewInstance as a friend function. /// 2) WrapType must put NAPA_DECLARE_PERSISTENT_CONSTRUCTOR in public, or add NewInstance as a friend function. /// template inline v8::MaybeLocal NewInstance(int argc = 0, v8::Local argv[] = nullptr) { auto constructor = NAPA_GET_PERSISTENT_CONSTRUCTOR(WrapType::exportName, WrapType); return constructor->NewInstance(v8::Isolate::GetCurrent()->GetCurrentContext(), argc, argv); } } } ================================================ FILE: inc/napa/module/module-internal.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include #include #include namespace napa { namespace module { /// Napa module version. static const int32_t MODULE_VERSION = 2; /// Variable name to export for module registration. static const char* NAPA_MODULE_EXPORT = "_napa_module"; /// Function pointer to initialize a module. It's called after a module is loaded. typedef void(*ModuleInitializer)(v8::Local exports, v8::Local module); /// Module information. struct NapaModule { /// Current module version. int32_t version; /// Module name. const char* moduleName; /// Function pointer to initialize a module. ModuleInitializer initializer; }; /// It binds the method name with V8 function. /// V8 object to bind with the given callback function. /// Method name. /// Binding V8 function object. template void SetMethod(const T& exports, const char* name, v8::FunctionCallback callback) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope handleScope(isolate); auto functionTemplate = v8::FunctionTemplate::New(isolate, callback); auto function = functionTemplate->GetFunction(); auto functionName = v8::String::NewFromUtf8(isolate, name); function->SetName(functionName); exports->Set(functionName, function); } /// It binds the method name with V8 prototype function object. /// V8 function template object to bind with the given callback function. /// Method name. /// Binding V8 function object. inline void SetPrototypeMethod(v8::Local functionTemplate, const char* name, v8::FunctionCallback callback) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope handleScope(isolate); auto signature = v8::Signature::New(isolate, functionTemplate); functionTemplate->PrototypeTemplate()->Set( v8::String::NewFromUtf8(isolate, name, v8::NewStringType::kNormal).ToLocalChecked(), v8::FunctionTemplate::New(isolate, callback, v8::Local(), signature)); } #define NAPA_REGISTER_MODULE(name, function) \ extern "C" { \ DLL_EXPORT napa::module::NapaModule _napa_module = { \ napa::module::MODULE_VERSION, \ #name, \ reinterpret_cast(function) \ }; \ } /// It sets the persistent constructor at the current V8 isolate. /// Unique constructor name. It's recommended to use the same name as module. /// V8 persistent function to constructor V8 object. NAPA_API void SetPersistentConstructor(const char* name, v8::Local constructor); /// It gets the given persistent constructor from the current V8 isolate. /// Unique constructor name given at SetPersistentConstructor() call. /// V8 local function object. NAPA_API v8::Local GetPersistentConstructor(const char* name); } // End of namespace module. } // End of namespace napa. ================================================ FILE: inc/napa/module/module-node-compat.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once /// It binds the method name with V8 function object. #define NODE_SET_METHOD napa::module::SetMethod /// It binds the method name with V8 prototype function object. #define NODE_SET_PROTOTYPE_METHOD napa::module::SetPrototypeMethod /// It registers the module with the name and the initializer. #define NODE_MODULE NAPA_REGISTER_MODULE ================================================ FILE: inc/napa/module/object-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { // Base class C++ class wrapper to manage lifetime. // It comes from node_object_wrap.h. class ObjectWrap { public: ObjectWrap() { _refs = 0; } virtual ~ObjectWrap() { if (persistent().IsEmpty()) { return; } assert(persistent().IsNearDeath()); persistent().ClearWeak(); persistent().Reset(); } template static inline T* Unwrap(v8::Local handle) { assert(!handle.IsEmpty()); assert(handle->InternalFieldCount() > 0); // Cast to ObjectWrap before casting to T. A direct cast from void // to T won't work right when T has more than one base class. void* ptr = handle->GetAlignedPointerFromInternalField(0); ObjectWrap* wrap = static_cast(ptr); return static_cast(wrap); } v8::Local handle() { return handle(v8::Isolate::GetCurrent()); } v8::Local handle(v8::Isolate* isolate) { return v8::Local::New(isolate, persistent()); } v8::Persistent& persistent() { return _handle; } protected: void Wrap(v8::Local handle) { assert(persistent().IsEmpty()); assert(handle->InternalFieldCount() > 0); handle->SetAlignedPointerInInternalField(0, this); persistent().Reset(v8::Isolate::GetCurrent(), handle); MakeWeak(); } void MakeWeak(void) { persistent().SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); persistent().MarkIndependent(); } /* Ref() marks the object as being attached to an event loop. * Refed objects will not be garbage collected, even if * all references are lost. */ virtual void Ref() { assert(!persistent().IsEmpty()); persistent().ClearWeak(); _refs++; } /* Unref() marks an object as detached from the event loop. This is its * default state. When an object with a "weak" reference changes from * attached to detached state it will be freed. Be careful not to access * the object after making this call as it might be gone! * (A "weak reference" means an object that only has a * persistant handle.) * * DO NOT CALL THIS FROM DESTRUCTOR */ virtual void Unref() { assert(!persistent().IsEmpty()); assert(!persistent().IsWeak()); assert(_refs > 0); if (--_refs == 0) { MakeWeak(); } } // Reference counter. int _refs; private: static void WeakCallback(const v8::WeakCallbackInfo& data) { ObjectWrap* wrap = data.GetParameter(); assert(wrap->_refs == 0); wrap->_handle.Reset(); delete wrap; } v8::Persistent _handle; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: inc/napa/module/shareable-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include namespace napa { namespace module { /// Abstract class for wraps that contains a C++ std::shared_ptr and allow it be shared across isolates. /// see napajs/lib/memory/shareable.ts class ShareableWrap : public NAPA_OBJECTWRAP { public: /// It initialize constructor template of a sub-class of ShareableWrap. /// Constructor template of wrap class. /// Should call this method in sub-class Init. template static void InitConstructorTemplate(v8::Local constructorTemplate) { // Blessed with methods from napajs.transport.Transportable. napa::transport::TransportableObject::InitConstructorTemplate(constructorTemplate); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "load", WrapType::LoadCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "save", WrapType::SaveCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "isNull", WrapType::IsNullCallback); NAPA_SET_ACCESSOR(constructorTemplate, "refCount", WrapType::RefCountCallback, nullptr); NAPA_SET_ACCESSOR(constructorTemplate, "handle", WrapType::GetHandleCallback, nullptr); } /// It initialize constructor of a sub-class of ShareableWrap. /// Should call this method in sub-class Init. /// Cid used for transporting the wrap. /// Constructor of wrap class. static void InitConstructor(const char* cid, v8::Local constructor) { napa::transport::TransportableObject::InitConstructor(cid, constructor); } /// Set an instance of ShareableWrap child-class with shared_ptr of T. template static void Set(v8::Local wrap, std::shared_ptr object) { auto shareable = NAPA_OBJECTWRAP::Unwrap(wrap); shareable->_object = std::static_pointer_cast(std::move(object)); } /// Get shared_ptr of T, which is the type of contained native object. template typename std::enable_if_t::value, std::shared_ptr> Get() { return std::static_pointer_cast(_object); } /// Get shared_ptr of void to a native object. template typename std::enable_if::value, std::shared_ptr>::type Get() { return _object; } /// Get reference of T, which is the type of contained native object. template typename std::enable_if_t::value, T&> GetRef() { return *std::static_pointer_cast(_object); } /// It creates a new instance of WrapType of shared_ptr, WrapType is a sub-class of ShareableWrap. /// shared_ptr of object. /// V8 object of type ShareableWrap. template static v8::Local NewInstance(std::shared_ptr object) { auto instance = napa::module::NewInstance().ToLocalChecked(); Set(instance, std::move(object)); return instance; } protected: /// Friend default constructor callback to access protected method NAPA_OBJECTWRAP::Unwrap. template friend void napa::module::DefaultConstructorCallback(const v8::FunctionCallbackInfo&); /// Default constructor. ShareableWrap() = default; /// Constructor. explicit ShareableWrap(std::shared_ptr object) : _object(std::move(object)) {} /// Allow inheritance. virtual ~ShareableWrap() = default; /// It implements readonly Shareable.handle : Handle static void GetHandleCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args){ auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); args.GetReturnValue().Set(v8_helpers::PtrToV8Uint32Array(isolate, thisObject->_object.get())); } /// It implements Shareable.refCount(): boolean static void RefCountCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args){ auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); args.GetReturnValue().Set(static_cast(thisObject->_object.use_count())); } /// It implements Shareable.isNull(): boolean static void IsNullCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); args.GetReturnValue().Set(thisObject->_object.get() == nullptr); } /// It implements TransportableObject.load(payload: object, transportContext: TransportContext): void static void LoadCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); CHECK_ARG(isolate, args.Length() == 2, "2 arguments are required for \"load\"."); CHECK_ARG(isolate, args[0]->IsObject(), "Argument \"payload\" shall be 'Object' type."); CHECK_ARG(isolate, args[1]->IsObject(), "Argument \"transportContext\" shall be 'TransportContextWrap' type."); auto payload = v8::Local::Cast(args[0]); auto numberArray = payload->Get(v8_helpers::MakeV8String(isolate, "handle")); auto result = v8_helpers::V8ValueToUintptr(isolate, numberArray); JS_ENSURE(isolate, result.second, "Unable to cast \"handle\" to pointer. Please check if it's in valid handle format."); auto transportContextWrap = NAPA_OBJECTWRAP::Unwrap(v8::Local::Cast(args[1])); JS_ENSURE(isolate, transportContextWrap != nullptr, "Argument \"transportContext\" should be 'TransportContextWrap' type."); // Load object from transport context. auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); thisObject->_object = transportContextWrap->Get()->LoadShared(result.first); } /// It implements TransportableObject.save(payload: object, transportContext: TransportContext): void static void SaveCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); CHECK_ARG(isolate, args.Length() == 2, "2 arguments are required for \"save\"."); CHECK_ARG(isolate, args[0]->IsObject(), "Argument \"payload\" should be 'Object' type."); CHECK_ARG(isolate, args[1]->IsObject(), "Argument \"transportContext\" should be 'TransportContextWrap' type."); auto payload = v8::Local::Cast(args[0]); auto transportContextWrap = NAPA_OBJECTWRAP::Unwrap(v8::Local::Cast(args[1])); JS_ENSURE(isolate, transportContextWrap != nullptr, "Argument \"transportContext\" should be 'TransportContextWrap' type."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); payload->CreateDataProperty( context, v8_helpers::MakeV8String(isolate, "handle"), v8_helpers::PtrToV8Uint32Array(isolate, thisObject->_object.get())); // Save object to transport context. transportContextWrap->Get()->SaveShared(thisObject->_object); } /// Shared object. std::shared_ptr _object; }; } } ================================================ FILE: inc/napa/module/transport-context-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// Interface for TransportContextWrap. class TransportContextWrap: public NAPA_OBJECTWRAP { public: /// Get transport context. virtual napa::transport::TransportContext* Get() = 0; virtual ~TransportContextWrap() = default; }; } } ================================================ FILE: inc/napa/module.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once // Suppress 4100 warnings. #pragma warning(push) #pragma warning(disable: 4100) #ifdef BUILDING_NAPA_EXTENSION #include "napa/module/module-internal.h" #include "napa/module/module-node-compat.h" #include "napa/module/object-wrap.h" #else #include #include #endif #include "napa/v8-helpers.h" #pragma warning(pop) /// It binds the method name with V8 function object. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_SET_METHOD napa::module::SetMethod #else #define NAPA_SET_METHOD NODE_SET_METHOD #endif /// It binds the method name with V8 prototype function object. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_SET_PROTOTYPE_METHOD napa::module::SetPrototypeMethod #else #define NAPA_SET_PROTOTYPE_METHOD NODE_SET_PROTOTYPE_METHOD #endif /// It binds a name with a property with V8 prototype function object. #define NAPA_SET_PROTOTYPE_PROPERTY(functionTemplate, name, value) \ functionTemplate->PrototypeTemplate()->Set(v8::Isolate::GetCurrent(), \ name, \ value) /// It registers the module with the name and the initializer. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_MODULE NAPA_REGISTER_MODULE #else #define NAPA_MODULE NODE_MODULE #endif /// It wraps V8 object, so its lifetime can be managed by napa/node. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_OBJECTWRAP napa::module::ObjectWrap #else #define NAPA_OBJECTWRAP node::ObjectWrap #endif /// It sets the accessors for the given V8 function template object. #define NAPA_SET_ACCESSOR(functionTemplate, name, getter, setter) \ functionTemplate->InstanceTemplate()->SetAccessor(v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), name), \ getter, \ setter) /// It sets the property for the given V8 function template object. #define NAPA_SET_PROPERTY(functionTemplate, name, value) \ functionTemplate->InstanceTemplate()->Set(v8::Isolate::GetCurrent(), \ name, \ value) /// It declares the persistent constructor. /// Napa registers constructor at local thread storage. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_DECLARE_PERSISTENT_CONSTRUCTOR() #else #define NAPA_DECLARE_PERSISTENT_CONSTRUCTOR() \ static v8::Persistent _constructor #endif /// It defines the persistent constructor. /// Napa does nothing since it registers a constructor while setting it. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(className) #else #define NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(className) \ v8::Persistent className::_constructor #endif /// It defines the template class's persistent constructor. /// Napa does nothing since it registers a constructor while setting it. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_DEFINE_TEMPLATE_PERSISTENT_CONSTRUCTOR(className) #else #define NAPA_DEFINE_TEMPLATE_PERSISTENT_CONSTRUCTOR(className) \ template \ v8::Persistent className::_constructor #endif /// It sets the persistent constructor at the current V8 isolate. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_SET_PERSISTENT_CONSTRUCTOR(name, function) \ napa::module::SetPersistentConstructor(name, function) #else #define NAPA_SET_PERSISTENT_CONSTRUCTOR(name, function) \ _constructor.Reset(v8::Isolate::GetCurrent(), function) #endif /// It gets the given persistent constructor from the current V8 isolate. /// V8 local function object. #ifdef BUILDING_NAPA_EXTENSION #define NAPA_GET_PERSISTENT_CONSTRUCTOR(exportName, className) \ napa::module::GetPersistentConstructor(exportName) #else #define NAPA_GET_PERSISTENT_CONSTRUCTOR(exportName, className) \ v8::Local::New(v8::Isolate::GetCurrent(), className::_constructor) #endif /// It exports a NAPA_OBJECTWRAP subclass to addon exports object. #define NAPA_EXPORT_OBJECTWRAP(exports, exportName, className) \ exports->Set(v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), exportName), \ NAPA_GET_PERSISTENT_CONSTRUCTOR(exportName, className)) // Depends on NAPA_GET_PERSISTENT_CONSTRUCTOR. #include "napa/module/common.h" ================================================ FILE: inc/napa/providers/logging.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace providers { /// Interface for a generic logging provider. /// /// Ownership of this logging provider belongs to the shared library which created it. Hence the explicit /// Destroy method in this class. To simplify memory management across multiple shared libraries, this class /// can only be created via a factory method provided by the shared library. When it is no longer needed, /// the caller may call Destroy() which will tell the shared library which created it to dispose of the object. /// class LoggingProvider { public: /// Represents verboseness for logging. enum class Verboseness { Error = 0, Warning, Info, Debug }; /// Logs a message. /// Logging section. /// Logging verboseness level. /// Trace ID. /// The source file this log message originated from. /// The source line this log message originated from. /// The message. virtual void LogMessage( const char* section, Verboseness level, const char* traceId, const char* file, int line, const char* message) = 0; /// Returns if logging is enabled for section/level/title. /// Logging section. /// Logging verboseness level. /// True if logging will occur for section/level, false otherwise. virtual bool IsLogEnabled(const char* section, Verboseness level) = 0; /// Explicitly destroys the logging provider. virtual void Destroy() = 0; protected: /// Prevent calling delete on the interface. Must use Destroy! virtual ~LoggingProvider() = default; }; /// Exports a getter function for retrieves the configured logging provider. NAPA_API LoggingProvider& GetLoggingProvider(); /// Singnature of the logging provider factory method. typedef LoggingProvider* (*CreateLoggingProvider)(); } } ================================================ FILE: inc/napa/providers/metric.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace providers { /// Enumeration of metric type. enum class MetricType { Number = 0, Rate, Percentile, }; /// Interface to represents a multi-dimensional metric with a maximum dimensionality of 64. class Metric { public: /// Sets a metric value with variadic dimension arguments. /// Int64 value. /// Number of dimensions being set. /// Array of dimension value names. /// Success/Fail. /// /// The number of dimension values must exactly match the number of dimensions provided when /// creating this metric. /// virtual bool Set(int64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0; /// /// Increments a metric value with variadic dimension arguments. /// Use mainly to simplify rate counters. /// /// UInt64 value to increment. /// Number of dimensions being set. /// Array of dimension value names. /// Success/Fail. /// /// The number of dimension values must exactly match the number of dimensions /// provided when creating this metric. /// virtual bool Increment(uint64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0; /// /// Decrements metric value with variadic dimension arguments. /// Use mainly to simplify rate counters. /// /// UInt64 value to decrement. /// Number of dimensions being set. /// Array of dimension value names. /// Success/Fail. /// /// The number of dimension values must exactly match the number of dimensions /// provided when creating this metric. /// virtual bool Decrement(uint64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0; /// Explicitly destroys the Metric. /// /// Consumers are not required to call this. /// The MetricProvider owns this class and will automatically perform cleanup on shutdown. /// virtual void Destroy() = 0; protected: /// Prevent calling delete on the interface. Must use Destroy! virtual ~Metric() = default; }; /// Interface for a generic metric provider. /// /// Ownership of this metric provider belongs to the shared library which created it. Hence the explicit /// Destroy method in this class. To simplify memory management across multiple shared libraries, this class /// can only be created via a factory method provided by the shared library. When it is no longer needed, /// the caller may call Destroy() which will tell the shared library which created it to dispose of the object. /// class MetricProvider { public: /// /// Gets or creates a N-dimensional metric. Metric objects are owned and cached by this class. /// Up to 64 dimensions may be used. /// Section of the metric. /// Name of the metric. /// Type of the metric. /// /// Number of dimensions requested for this metric. /// Represents the size of the array passed in for p_dimensionNames. /// /// Array of dimension names being requested for this metric. /// /// The IMetric class returned is owned and cached by this class. /// Callers are not required to call destroy() on the Metric. /// virtual Metric* GetMetric( const char* section, const char* name, MetricType type, size_t dimensions, const char* dimensionNames[]) = 0; /// Explicitly destroys the metric provider. virtual void Destroy() = 0; protected: /// Prevent calling delete on the interface. Must use Destroy! virtual ~MetricProvider() = default; }; /// Exports a getter function for retrieves the configured metric provider. NAPA_API MetricProvider& GetMetricProvider(); typedef MetricProvider* (*CreateMetricProvider)(); } } ================================================ FILE: inc/napa/result-codes.inc ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. /// /// Napa result codes definition file! /// /// Guidelines: /// 1. Use NAPA_RESULT_CODE_DEF to define new codes. /// 2. Add new codes at the end of the list. /// 3. Make sure to add a comma at the end of the previous result code defintion /// /// |----------------------- symbol name ---------- string representation --| /// NAPA_RESULT_CODE_DEF( EXAMPLE_NAME1, "Example string message1"), /// NAPA_RESULT_CODE_DEF( EXAMPLE_NAME2, "Example string message2") /// Always add news codes /// #ifndef NAPA_RESULT_CODE_DEF #error NAPA_RESULT_CODE_DEF must be defined before including response_code.inc #endif NAPA_RESULT_CODE_DEF( SUCCESS, "Success"), NAPA_RESULT_CODE_DEF( UNDEFINED, "Undefined"), NAPA_RESULT_CODE_DEF( INTERNAL_ERROR, "Napa internal error"), NAPA_RESULT_CODE_DEF( TIMEOUT, "The request timed out"), NAPA_RESULT_CODE_DEF( ZONE_INIT_ERROR, "Failed to initialize zone"), NAPA_RESULT_CODE_DEF( BROADCAST_SCRIPT_ERROR, "Failed to broadcast JavaScript code in zone"), NAPA_RESULT_CODE_DEF( EXECUTE_FUNC_ERROR, "Failed to execute the JavaScript function"), NAPA_RESULT_CODE_DEF( SETTINGS_PARSER_ERROR, "Failed to parse settings"), NAPA_RESULT_CODE_DEF( PROVIDERS_INIT_ERROR, "Failed to initialize providers"), NAPA_RESULT_CODE_DEF( V8_INIT_ERROR, "Failed to initialize V8"), NAPA_RESULT_CODE_DEF( GLOBAL_VALUE_ERROR, "Failed to set global value") ================================================ FILE: inc/napa/stl/allocator.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { /// Allocator is a is a wrapper class for napa::memory::Allocator. It is used to /// create an allocator that is compatible with the STL container classes /// like map and vector. /// /// This template declaration is a subset of the declaration in the C++98 /// spec (ISO/IEC 14882:1998), section 20.4.1. /// See specification draft: http://www.open-std.org/jtc1/sc22/open/n2356/. /// Also see http://www.codeguru.com/Cpp/Cpp/cpp_mfc/stl/article.php/c4079/. /// template class Allocator { public: typedef size_t size_type; typedef ptrdiff_t difference_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef T value_type; template struct rebind { typedef Allocator other; }; /// Constructor that uses NAPA_DEFAULT_ALLOCATOR. Allocator(); /// Constructor that accepts a custom allocator explicit Allocator(napa::memory::Allocator& allocator); /// Copy constructor will be used in assignment of std::shared_ptr in GCC Allocator(const Allocator& other) = default; Allocator& operator=(const Allocator&) = default; template Allocator(const Allocator&) throw(); /// The following method is in the C++98 specification but was /// not implemented. Keeping the declarations here to document /// differences from the specification and to help with debugging. /// ~Allocator() throw(); pointer address(reference x) const; const_pointer address(const_reference x) const; pointer allocate(size_type count, const void* hint = 0); void deallocate(pointer p, size_type n); size_type max_size() const throw(); void construct(pointer p, const_reference val); void destroy(pointer p); bool operator==(const Allocator&) const; bool operator!=(const Allocator&) const; private: template friend class Allocator; napa::memory::Allocator* _allocator; }; template Allocator::Allocator() : _allocator(&napa::memory::GetDefaultAllocator()) { } template Allocator::Allocator(napa::memory::Allocator& allocator) : _allocator(&allocator) { } template template Allocator::Allocator(const Allocator& rhs) throw() : _allocator(rhs._allocator) { } template typename Allocator::pointer Allocator::address(typename Allocator::reference x) const { return &x; } template typename Allocator::const_pointer Allocator::address(typename Allocator::const_reference x) const { return &x; } template typename Allocator::pointer Allocator::allocate(typename Allocator::size_type count, const void* /*hint*/) { return static_cast::pointer>(_allocator->Allocate(sizeof(T) * count)); } template void Allocator::deallocate(typename Allocator::pointer p, typename Allocator::size_type count) { _allocator->Deallocate(p, sizeof(T) * count); } template typename Allocator::size_type Allocator::max_size() const throw() { return std::numeric_limits::size_type>::max(); } template void Allocator::construct(typename Allocator::pointer ptr, typename Allocator::const_reference val) { new (ptr) T(val); } #pragma warning(push) #pragma warning(disable:4100) // Warning C4100 says that 'ptr' is unreferenced as a formal parameter. // This happens when T is a simple type like int. In this case it seems that // the compiler optimizes away the call to ptr->~T(), causing the ptr to // seem unreferenced. This seems like a compiler bug. template void Allocator::destroy(typename Allocator::pointer ptr) { ptr->~T(); } #pragma warning(pop) template bool Allocator::operator==(const Allocator& other) const { return *_allocator == *(other._allocator); } template bool Allocator::operator!=(const Allocator& other) const { return !(*_allocator == *(other._allocator)); } } } ================================================ FILE: inc/napa/stl/deque.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template using Deque = std::deque>; } } ================================================ FILE: inc/napa/stl/list.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template using List = std::list>; } } ================================================ FILE: inc/napa/stl/map.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template > using Map = std::map>>; template > using MultiMap = std::multimap>>; } } ================================================ FILE: inc/napa/stl/queue.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template using Queue = std::queue>>; template using PriorityQueue = std::priority_queue>>; } } ================================================ FILE: inc/napa/stl/set.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template > using Set = std::set>; template > using MultiSet = std::multiset>; } } ================================================ FILE: inc/napa/stl/stack.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template using Stack = std::priority_queue>>; } } ================================================ FILE: inc/napa/stl/string.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template > using BasicString = std::basic_string>; typedef BasicString String; typedef BasicString U16String; } } #if defined(__GNUC__) && !defined(__clang__) namespace std { // std::hash specialization for napa::stl::String. template<> struct hash : public __hash_base { size_t operator()(const napa::stl::String& s) const noexcept { return std::_Hash_impl::hash(s.data(), s.length() * sizeof(char)); } }; template<> struct __is_fast_hash> : std::false_type { }; // std::hash specialization for napa::stl::U16String. template<> struct hash : public __hash_base { size_t operator()(const napa::stl::U16String& s) const noexcept { return std::_Hash_impl::hash(s.data(), s.length() * sizeof(char16_t)); } }; template<> struct __is_fast_hash> : std::false_type { }; } #endif ================================================ FILE: inc/napa/stl/unordered_map.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template < typename Key, typename T, typename Hash = std::hash, typename KeyEqual = std::equal_to > using UnorderedMap = std::unordered_map< Key, T, Hash, KeyEqual, napa::stl::Allocator>>; template < typename Key, typename T, typename Hash = std::hash, typename KeyEqual = std::equal_to > using UnorderedMultiMap = std::unordered_multimap< Key, T, Hash, KeyEqual, napa::stl::Allocator>>; } } ================================================ FILE: inc/napa/stl/unordered_set.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template < typename Key, typename Hash = std::hash, typename KeyEqual = std::equal_to > using UnorderedSet = std::unordered_set>; template < typename Key, typename Hash = std::hash, typename KeyEqual = std::equal_to > using UnorderedMultiSet = std::unordered_multiset>; } } ================================================ FILE: inc/napa/stl/vector.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace stl { template using Vector = std::vector>; } } ================================================ FILE: inc/napa/transport/transport-context.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace transport { /// It facilitates transportation of C++ objects /// which need to transfer/extend their ownership across isolates by assignment operator. /// /// Known cases are: /// 1) Transfer/extend ownership via passing shared objects in arguments to Zone::execute. /// 2) Extend ownership by cross-isolate sharing via memory.global.set/get or Zone.global.set/get. /// /// At this time, only transport std::shared_ptr is supported of transfering ownership. /// class TransportContext { public: /// Default constructor. TransportContext() = default; /// Move constructor. TransportContext(TransportContext&& other) : _sharedDepot(std::move(other._sharedDepot)) { } /// Move assignment. TransportContext& operator=(TransportContext&& other) { _sharedDepot = std::move(other._sharedDepot); return *this; } /// Non-copyable. TransportContext(const TransportContext&) = delete; TransportContext& operator=(const TransportContext&) = delete; /// It saves a shared pointer that can be loaded later. /// Shared pointer to transfer ownership to another isolate. template void SaveShared(std::shared_ptr pointer) { _sharedDepot[reinterpret_cast(pointer.get())] = std::move(pointer); } /// It loads a previously saved shared pointer. /// uintptr_t value. /// shared_ptr for requested handle, or empty shared_ptr if not found. template std::shared_ptr LoadShared(uintptr_t handle) { auto it = _sharedDepot.find(handle); if (it != _sharedDepot.end()) { return std::static_pointer_cast(it->second); } return std::shared_ptr(); } /// Get count of saved shared_ptr. uint32_t GetSharedCount() { return static_cast(_sharedDepot.size()); } private: /// shared_ptr depot. napa::stl::UnorderedMap> _sharedDepot; }; } } ================================================ FILE: inc/napa/transport/transport.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace transport { /// Register a Transportable object wrap with transport. /// Constructor of wrap type. /// 'napajs/lib/transport/transport' is required instead of 'napajs/lib/transport' to avoid circular dependency on addon. inline void Register(v8::Local constructor) { v8::Local argv[] = { constructor }; (void)napa::module::binding::Call("../lib/transport/transport", "register", sizeof(argv) / sizeof(v8::Local), argv); } /// Marshall an object with transport context. C++ modules can use this helper function to marshall its members. /// Object to marshall, it can be built-in JavaScript types or object implements napajs.transport.Transportable. /// TransportContextWrap to save shareable states if any. /// Payload in V8 string of marshalled object. /// 'napajs/lib/transport/transport' is required instead of 'napajs/lib/transport' to avoid circular dependency on addon. inline v8::MaybeLocal Marshall(v8::Local object, v8::Local transportContextWrap) { v8::Local argv[] = { object, transportContextWrap }; return v8_helpers::MaybeCast(napa::module::binding::Call( "../lib/transport/transport", "marshall", sizeof(argv) / sizeof(v8::Local), argv)); } /// Marshall an object with transport context. C++ modules can use this helper function to marshall its members. /// Object to marshall, it can be built-in JavaScript types or object implements napajs.transport.Transportable. /// TransportContext to save shareable states if any. /// Payload in V8 string of marshalled object. /// 'napajs/lib/transport/transport' is required instead of 'napajs/lib/transport' to avoid circular dependency on addon. inline v8::MaybeLocal Marshall(v8::Local object, napa::transport::TransportContext* transportContext) { auto isolate = v8::Isolate::GetCurrent(); v8::Local argv[] = { v8::Boolean::New(isolate, false), // Not owning since wrap is temporary. v8_helpers::PtrToV8Uint32Array(isolate, transportContext) }; auto transportContextWrap = napa::module::binding::NewInstance( "TransportContextWrap", sizeof(argv) / sizeof(v8::Local), argv).ToLocalChecked(); return Marshall(object, transportContextWrap); } /// Unmarshall a payload with transport context. C++ modules can use this helper function to unmarshall its members. /// Payload to unmarshall, it is plain JS object generated by transport.marshallSingle. /// TransportContextWrap to load shareable states if any. /// Unmarshalled V8 value from payload. /// 'napajs/lib/transport/transport' is required instead of 'napajs/lib/transport' to avoid circular dependency on addon. inline v8::MaybeLocal Unmarshall(v8::Local payload, v8::Local transportContextWrap) { v8::Local argv[] = { payload, transportContextWrap }; return napa::module::binding::Call("../lib/transport/transport", "unmarshall", sizeof(argv) / sizeof(v8::Local), argv); } /// Unmarshall a payload with transport context. C++ modules can use this helper function to unmarshall its members. /// Payload to unmarshall, it is plain JS object generated by transport.marshallSingle. /// TransportContext to load shareable states if any. /// Unmarshalled V8 value from payload. /// 'napajs/lib/transport/transport' is required instead of 'napajs/lib/transport' to avoid circular dependency on addon. inline v8::MaybeLocal Unmarshall(v8::Local payload, const napa::transport::TransportContext* transportContext) { auto isolate = v8::Isolate::GetCurrent(); v8::Local argv[] = { v8::Boolean::New(isolate, false), // Not owning since wrap is temporary. v8_helpers::PtrToV8Uint32Array(isolate, transportContext) }; auto transportContextWrap = napa::module::binding::NewInstance( "TransportContextWrap", sizeof(argv) / sizeof(v8::Local), argv).ToLocalChecked(); return Unmarshall(payload, transportContextWrap); } } } ================================================ FILE: inc/napa/transport/transportable.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace transport { /// Helper for extending TransportableObject /// Reference: napajs/lib/transport/transportable.ts#TransportableObject struct TransportableObject { /// It initialize a wrap object constructor template with TransportableObject methods. /// Constructor function template of the wrap. static void InitConstructorTemplate(v8::Local constructorTemplate) { NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "marshall", TransportableObject::MarshallCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "unmarshall", TransportableObject::UnmarshallCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "cid", TransportableObject::GetCidCallback); } /// It initialize a wrap constructor with static property _cid. /// Cid used for transporting the wrap. /// Constructor function of the wrap. static void InitConstructor(const char* cid, v8::Local constructor) { auto isolate = v8::Isolate::GetCurrent(); constructor->Set(v8_helpers::MakeV8String(isolate, "_cid"), v8_helpers::MakeV8String(isolate, cid)); napa::transport::Register(constructor); } /// It implements Transportable.cid() : string static void GetCidCallback(const v8::FunctionCallbackInfo& args){ auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto prototype = v8::Local::Cast(args.Holder()->GetPrototype()); auto constructor = v8::Local::Cast(prototype->Get( v8_helpers::MakeV8String(isolate, "constructor"))); args.GetReturnValue().Set(constructor->Get(v8_helpers::MakeV8String(isolate, "_cid"))); } /// It implements Transportable.marshall(context: TransportContext): object static void MarshallCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); CHECK_ARG(isolate, args.Length() == 1, "1 argument is required for calling 'marshall'."); CHECK_ARG(isolate, args[0]->IsObject(), "The 1st argument of 'marshall' shall be object of TransportContext."); // Get cid from property '_cid' of constructor and class name. auto holder = args.Holder(); auto proto = v8::Local::Cast(holder->GetPrototype()); JS_ENSURE(isolate, !proto.IsEmpty(), "Prototype is not Object type"); auto constructor = v8::Local::Cast( proto->Get(v8_helpers::MakeV8String(isolate, "constructor"))); auto cid = constructor->Get(v8_helpers::MakeV8String(isolate, "_cid")); // Save property "_cid". auto payload = v8::Object::New(isolate); payload->CreateDataProperty( context, v8_helpers::MakeV8String(isolate, "_cid"), cid); // Delegate to sub-class to save its members. auto saveMethod = v8::Local::Cast( holder->Get(v8_helpers::MakeV8String(isolate, "save"))); JS_ENSURE(isolate, !saveMethod.IsEmpty(), "\"save\" method doesn't exist."); constexpr int argc = 2; v8::Local argv[argc] = {payload, args[0]}; saveMethod->Call( context, holder, argc, argv); args.GetReturnValue().Set(payload); } /// It implements Transportable.unmarshall(payload: object, context: TransportContext): void static void UnmarshallCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); CHECK_ARG(isolate, args.Length() == 2, "Two arguments are required for calling 'unmarshall'. "); auto holder = args.Holder(); // Delegate to sub-class to load its members. auto loadMethod = v8::Local::Cast(holder->Get(v8_helpers::MakeV8String(isolate, "load"))); JS_ENSURE(isolate, !loadMethod.IsEmpty(), "\"load\" method doesn't exist."); constexpr int argc = 2; v8::Local argv[argc] = {args[0], args[1]}; loadMethod->Call( context, holder, argc, argv); } }; } } ================================================ FILE: inc/napa/transport.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include ================================================ FILE: inc/napa/types.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "stddef.h" #include "stdint.h" /// Simple non owning string. Should only be used for binding. typedef struct { const char* data; size_t size; } napa_string_ref; #define NAPA_STRING_REF_WITH_SIZE(data, size) (napa_string_ref { (data), (size) }) #define NAPA_STRING_REF(data) NAPA_STRING_REF_WITH_SIZE(data, strlen(data)) const napa_string_ref EMPTY_NAPA_STRING_REF = NAPA_STRING_REF_WITH_SIZE(0, 0); #ifdef __cplusplus namespace napa { typedef napa_string_ref StringRef; } #define STD_STRING_TO_NAPA_STRING_REF(str) (napa_string_ref { (str).data(), (str).size() }) #define NAPA_STRING_REF_TO_STD_STRING(str) (std::string((str).data, (str).size)) #endif // __cplusplus /// Represents result code in napa zone apis. #define NAPA_RESULT_CODE_DEF(symbol, ...) NAPA_RESULT_##symbol typedef enum { #include "napa/result-codes.inc" } napa_result_code; #undef NAPA_RESULT_CODE_DEF #ifdef __cplusplus namespace napa { typedef napa_result_code ResultCode; } #endif // __cplusplus /// Represents option for transporting objects in zone.execute. typedef enum { /// /// transport.marshall/unmarshall will be done by `napajs` automatically. /// This is the most common way, but may not be performance optimal with objects /// that will be shared in multiple zone.execute. /// AUTO, /// transport.marshall/unmarshall will be done by user manually. MANUAL, } napa_transport_option; #ifdef __cplusplus namespace napa { typedef napa_transport_option TransportOption; } #endif // __cplusplus /// Represents options for calling a function. typedef struct { /// Timeout in milliseconds - Use 0 for inifinite. uint32_t timeout; /// Arguments transport option. Default is AUTO. napa_transport_option transport; } napa_zone_call_options; #ifdef __cplusplus namespace napa { typedef napa_zone_call_options CallOptions; } #endif // __cplusplus /// Represents a function to run within a zone, with binded arguments . typedef struct { /// The module that exports the function to execute. napa_string_ref module; /// The function to execute. napa_string_ref function; /// The function arguments. const napa_string_ref* arguments; /// The number of arguments. size_t arguments_count; /// Options. napa_zone_call_options options; /// A context used for transporting handles across zones/workers. void* transport_context; } napa_zone_function_spec; #ifdef __cplusplus #include #include #include #include namespace napa { /// Represents a function to call with its arguments. struct FunctionSpec { /// The module that exports the function to execute. StringRef module = EMPTY_NAPA_STRING_REF; /// The function to execute. StringRef function = EMPTY_NAPA_STRING_REF; /// The function arguments. std::vector arguments; /// Execute options. CallOptions options = { 0, AUTO }; /// Used for transporting shared_ptr and unique_ptr across zones/workers. mutable std::unique_ptr transportContext; }; } #endif // __cplusplus /// Represents a result from executing in a zone. typedef struct { /// A result code. napa_result_code code; /// The error message in case of an error. napa_string_ref error_message; /// The return value in case of success. napa_string_ref return_value; /// A context used for transporting handles across zones/workers. void* transport_context; } napa_zone_result; #ifdef __cplusplus namespace napa { /// Represents a function call result. struct Result { /// A result code. ResultCode code; /// The error message in case of an error. std::string errorMessage; /// The return value in case of success. std::string returnValue; /// Used for transporting shared_ptr and unique_ptr across zones/workers. mutable std::unique_ptr transportContext; }; } #endif // __cplusplus /// Callback signatures. typedef void(*napa_zone_callback)(napa_zone_result result, void* context); typedef napa_zone_callback napa_zone_broadcast_callback; typedef napa_zone_callback napa_zone_execute_callback; #ifdef __cplusplus #include namespace napa { typedef std::function ZoneCallback; typedef ZoneCallback BroadcastCallback; typedef ZoneCallback ExecuteCallback; } #endif // __cplusplus /// Zone handle type. typedef struct napa_zone *napa_zone_handle; /// Callback for customized memory allocator. typedef void* (*napa_allocate_callback)(size_t); typedef void (*napa_deallocate_callback)(void*, size_t); ================================================ FILE: inc/napa/utils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace utils { /// Format message with truncation. inline void FormatMessageWithTruncation(char* buffer, size_t bufferSize, const char* format, ...) { va_list args; va_start(args, format); int size = vsnprintf(buffer, bufferSize, format, args); va_end(args); NAPA_ASSERT(size >= 0, "Format message error, probably wrong format encoding"); } } } ================================================ FILE: inc/napa/v8-helpers/array.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "conversion.h" #include #include namespace napa { namespace v8_helpers { /// Convert V8::Array to std::vector template inline std::vector V8ArrayToVector(v8::Isolate* isolate, const v8::Local& array) { auto context = isolate->GetCurrentContext(); std::vector res; res.reserve(array->Length()); for (uint32_t i = 0; i < array->Length(); i++) { res.emplace_back(V8ValueTo(array->Get(context, i).ToLocalChecked())); } return res; } } } ================================================ FILE: inc/napa/v8-helpers/console.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace v8_helpers { struct Console { /// Call console.log from C++ /// Number of arguments. /// Actual arguments. static void Log(int argc, v8::Local argv[]) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto console = v8::Local::Cast(context->Global()->Get( v8_helpers::MakeV8String(isolate, "console"))); v8_helpers::Call(console, "log", argc, argv); } }; } } ================================================ FILE: inc/napa/v8-helpers/conversion.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace v8_helpers { /// Unified method signature for convert V8 value to C++ types. template inline T V8ValueTo(const v8::Local& value) { static_assert(sizeof(T) == -1, "No specialization exists for this type"); } /// Convert a v8 value to std::string. template <> inline std::string V8ValueTo(const v8::Local& value) { v8::String::Utf8Value utf8Value(value); return std::string(*utf8Value); } /// Convert a v8 value to std::u16string. template <> inline std::u16string V8ValueTo(const v8::Local& value) { v8::String::Value utf16Value(value); return std::u16string(reinterpret_cast(*utf16Value)); } /// Convert a v8 value to napa::stl::String. template <> inline napa::stl::String V8ValueTo(const v8::Local& value) { v8::String::Utf8Value utf8Value(value); return napa::stl::String(*utf8Value); } /// Convert a v8 value to napa::stl::U16String. template <> inline napa::stl::U16String V8ValueTo(const v8::Local& value) { v8::String::Value utf16Value(value); return napa::stl::U16String(reinterpret_cast(*utf16Value)); } /// Convert a v8 value to a Utf8String. template <> inline Utf8String V8ValueTo(const v8::Local& value) { return Utf8String(value); } } } ================================================ FILE: inc/napa/v8-helpers/flow.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #define THROW_IF_FAIL(isolate, expression, result, function, line, format, ...) \ if (!(expression)) { \ constexpr int MAX_ERROR_MESSAGE_SIZE = 512; \ char message[MAX_ERROR_MESSAGE_SIZE]; \ napa::utils::FormatMessageWithTruncation(message, MAX_ERROR_MESSAGE_SIZE, format, ##__VA_ARGS__); \ std::stringstream temp; \ temp << function << ":" << line << " -- " << message; \ isolate->ThrowException(v8::Exception::TypeError(napa::v8_helpers::MakeV8String(isolate, temp.str()))); \ return result; \ } #define CHECK_ARG(isolate, expression, format, ...) \ THROW_IF_FAIL(isolate, expression, /* empty */, __FUNCTION__, __LINE__, format, ##__VA_ARGS__) #define CHECK_ARG_WITH_RETURN(isolate, expression, result, format, ...) \ THROW_IF_FAIL(isolate, expression, result, __FUNCTION__, __LINE__, format, ##__VA_ARGS__) #define JS_ASSERT(isolate, expression, format, ...) CHECK_ARG(isolate, expression, format, ##__VA_ARGS__) #define JS_ENSURE(isolate, expression, format, ...) CHECK_ARG(isolate, expression, format, ##__VA_ARGS__) #define JS_FAIL(isolate, format, ...) CHECK_ARG(isolate, false, format, ##__VA_ARGS__) #define JS_ENSURE_WITH_RETURN(isolate, expression, result, format, ...) \ CHECK_ARG_WITH_RETURN(isolate, expression, result, format, ##__VA_ARGS__) #define SHORT_CIRCUIT_ON_PENDING_EXCEPTION(maybe, result) \ if (maybe.IsEmpty()) { \ return result; \ } #define RETURN_ON_PENDING_EXCEPTION(maybe) \ SHORT_CIRCUIT_ON_PENDING_EXCEPTION(maybe, /* empty */) #define RETURN_VALUE_ON_PENDING_EXCEPTION(maybe, result) \ SHORT_CIRCUIT_ON_PENDING_EXCEPTION(maybe, result) ================================================ FILE: inc/napa/v8-helpers/function.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include namespace napa { namespace v8_helpers { /// Call a function under current context /// Function under current context. /// Number of arguments. /// Actual arguments. /// Return value of function, or an empty handle if exception is thrown. inline v8::MaybeLocal Call( const char* functionName, int argc = 0, v8::Local argv[] = nullptr) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto function = context->Global()->Get(v8_helpers::MakeV8String(isolate, functionName)); JS_ENSURE_WITH_RETURN( isolate, !function.IsEmpty() && function->IsFunction(), v8::MaybeLocal(), "Function \"%s\" is not found in current context.", functionName); return scope.Escape( v8::Local::Cast(function)->Call(context, context->Global(), argc, argv) .FromMaybe(v8::Local())); } /// Call a member function of an object /// JavaScript object. /// Member function name. /// Number of arguments. /// Actual arguments. /// Return value of function, or empty handle if exception is thrown. inline v8::MaybeLocal Call( v8::Local object, const char* functionName, int argc = 0, v8::Local argv[] = nullptr) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto function = object->Get(v8_helpers::MakeV8String(isolate, functionName)); JS_ENSURE_WITH_RETURN( isolate, !function.IsEmpty() && function->IsFunction(), v8::MaybeLocal(), "Function \"%s\" is not found in object.", functionName); return scope.Escape( v8::Local::Cast(function)->Call(context, object, argc, argv) .FromMaybe(v8::Local())); } } } ================================================ FILE: inc/napa/v8-helpers/json.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace v8_helpers { namespace JSON { /// JSON.stringify /// TODO @asib: Use v8::JSON::Stringify when available inline v8::MaybeLocal Stringify(v8::Isolate* isolate, const v8::Local& value) { v8::EscapableHandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto json = context->Global() ->Get(context, v8::String::NewFromUtf8(isolate, "JSON")) .ToLocalChecked()->ToObject(context) .ToLocalChecked(); constexpr int argc = 1; v8::Local argv[] = { value }; return scope.Escape(ToLocal(Call(json, "stringify", argc, argv))); } /// JSON.parse #if (V8_MINOR_VERSION >= 6) inline v8::MaybeLocal Parse(v8::Local context, const v8::Local& jsonString) { return v8::JSON::Parse(context, jsonString); } #else inline v8::MaybeLocal Parse(const v8::Local& jsonString) { return v8::JSON::Parse(jsonString); } #endif } } } ================================================ FILE: inc/napa/v8-helpers/maybe.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace v8_helpers { /// Cast MaybeLocal from source type to target type. template v8::MaybeLocal MaybeCast(v8::MaybeLocal handle) { if (handle.IsEmpty()) { return v8::MaybeLocal(); } return v8::Local::Cast(handle.ToLocalChecked()); } /// Cast MaybeLocal to Local from source type to target type with default value. template v8::Local ToLocal(v8::MaybeLocal handle, v8::Local defaultValue = v8::Local()) { if (handle.IsEmpty()) { return defaultValue; } return v8::Local::Cast(handle.ToLocalChecked()); } } } ================================================ FILE: inc/napa/v8-helpers/object.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace v8_helpers { /// Convert a V8 object to an map. template inline std::unordered_map V8ObjectToMap( v8::Isolate* isolate, const v8::Local& obj) { auto context = isolate->GetCurrentContext(); std::unordered_map res; auto maybeProps = obj->GetOwnPropertyNames(context); if (!maybeProps.IsEmpty()) { auto props = maybeProps.ToLocalChecked(); res.reserve(props->Length()); for (uint32_t i = 0; i < props->Length(); i++) { auto key = props->Get(context, i).ToLocalChecked(); auto value = obj->Get(context, key).ToLocalChecked(); v8::String::Utf8Value keyString(key->ToString(context).ToLocalChecked()); res.emplace(*keyString, V8ValueTo(value)); } } return res; } } } ================================================ FILE: inc/napa/v8-helpers/ptr.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace v8_helpers { /// Uint32ptr size of multiple 32-bits. static const uint32_t UINTPTR_SIZE_IN_UINT32 = static_cast(sizeof(uintptr_t) / sizeof(uint32_t)); /// Convert a pointer value to V8 Uint32 array. /// V8 Isolate instance. /// Pointer value. /// V8 uint32 array inline v8::Local UintptrToV8Uint32Array(v8::Isolate* isolate, uintptr_t source) { v8::EscapableHandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto target = v8::Array::New(isolate, UINTPTR_SIZE_IN_UINT32); for (uint32_t i = 0; i < UINTPTR_SIZE_IN_UINT32; ++i) { auto value = static_cast(source); target->CreateDataProperty(context, i, v8::Integer::NewFromUnsigned(isolate, value)); source >>= 32; } return scope.Escape(target); } /// Convert a void pointer to V8 Uint32 array. /// V8 Isolate instance. /// Void pointer. /// V8 uint32 array inline v8::Local PtrToV8Uint32Array(v8::Isolate* isolate, const void* pointer) { return UintptrToV8Uint32Array(isolate, reinterpret_cast(pointer)); } /// Convert a V8 value (should be uint32 array) to uintptr. /// V8 Isolate instance. /// V8 uint32 array holding pointer value. /// The pair of converted pointer value and success/failure. inline std::pair V8ValueToUintptr(v8::Isolate* isolate, const v8::Local& source) { if (!source->IsArray()) { return std::make_pair(0, false); } auto numberArray = v8::Local::Cast(source); if (numberArray->Length() != UINTPTR_SIZE_IN_UINT32) { return std::make_pair(0, false); } auto context = isolate->GetCurrentContext(); uintptr_t result = 0; for (uint32_t i = 0; i < numberArray->Length(); ++i) { auto value = numberArray->Get(context, i).ToLocalChecked(); if (!value->IsUint32()) { return std::make_pair(0, false); } result |= static_cast(value->Uint32Value()) << 32 * i; } return std::make_pair(result, true); } /// Convert a V8 value (should be uint32 array) to void pointer. /// V8 Isolate instance. /// V8 uint32 array holding pointer value. /// The pair of success and converted void pointer. template inline std::pair V8ValueToPtr(v8::Isolate* isolate, const v8::Local& source) { auto result = V8ValueToUintptr(isolate, source); return std::make_pair(static_cast(reinterpret_cast(result.first)), result.second); } } } ================================================ FILE: inc/napa/v8-helpers/string.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace v8_helpers { /// Make a V8 string by making a copy of const char*. inline v8::Local MakeV8String(v8::Isolate *isolate, const char* str, int length = -1) { return v8::String::NewFromUtf8(isolate, str, v8::NewStringType::kNormal, length).ToLocalChecked(); } /// Make a V8 string from std::string. inline v8::Local MakeV8String(v8::Isolate *isolate, const std::string& str) { return MakeV8String(isolate, str.c_str(), static_cast(str.length())); } /// Make a V8 string from napa::stl::String. inline v8::Local MakeV8String(v8::Isolate *isolate, const napa::stl::String& str) { return MakeV8String(isolate, str.c_str(), static_cast(str.length())); } /// Make a V8 string by making a copy of const uint16_t*. inline v8::Local MakeV8String(v8::Isolate *isolate, const uint16_t* str, int length = -1) { return v8::String::NewFromTwoByte(isolate, str, v8::NewStringType::kNormal, length).ToLocalChecked(); } /// Make a V8 string by making a copy of const char16_t*. inline v8::Local MakeV8String(v8::Isolate *isolate, const char16_t* str, int length = -1) { return MakeV8String(isolate, reinterpret_cast(str), length); } /// Make a V8 string from std::u16string. inline v8::Local MakeV8String(v8::Isolate *isolate, const std::u16string& str) { return MakeV8String(isolate, str.c_str(), static_cast(str.length())); } /// Make a V8 string from napa::stl::U16String. inline v8::Local MakeV8String(v8::Isolate *isolate, const napa::stl::U16String& str) { return MakeV8String(isolate, str.c_str(), static_cast(str.length())); } using ExternalOneByteStringView = v8::ExternalOneByteStringResourceImpl; class ExternalTwoByteStringView : public v8::String::ExternalStringResource { public: ExternalTwoByteStringView() : _data(nullptr), _length(0) {} ExternalTwoByteStringView(const uint16_t* data, size_t length) : _data(data), _length(length) {} const uint16_t* data() const { return _data; } size_t length() const { return _length; } private: const uint16_t* _data; size_t _length; }; /// Make a V8 string from external const char*. /// The input data should only contains Latin-1 chars. inline v8::Local MakeExternalV8String(v8::Isolate *isolate, const char* data, size_t length) { // V8 garbage collection frees ExternalOneByteStringView. auto externalResource = new ExternalOneByteStringView(data, length); return v8::String::NewExternalOneByte(isolate, externalResource).ToLocalChecked(); } /// Make a V8 string from external const char*. /// The input data should only contains Latin-1 chars. inline v8::Local MakeExternalV8String(v8::Isolate *isolate, const char* data) { return MakeExternalV8String(isolate, data, strlen(data)); } /// Make a V8 string from external std::string. /// The input data should only contains Latin-1 chars. inline v8::Local MakeExternalV8String(v8::Isolate *isolate, const std::string& str) { return MakeExternalV8String(isolate, str.data(), str.length()); } /// Make a V8 string from external napa::stl::String. /// The input data should only contains Latin-1 chars. inline v8::Local MakeExternalV8String(v8::Isolate *isolate, const napa::stl::String& str) { return MakeExternalV8String(isolate, str.data(), str.length()); } /// Make a V8 string from external const uint16_t*. inline v8::Local MakeExternalV8String(v8::Isolate *isolate, const uint16_t* data, size_t length) { // V8 garbage collection frees ExternalTwoByteStringView. auto externalResource = new ExternalTwoByteStringView(data, length); return v8::String::NewExternalTwoByte(isolate, externalResource).ToLocalChecked(); } /// Make a V8 string from external const char16_t*. inline v8::Local MakeExternalV8String(v8::Isolate *isolate, const char16_t* data, size_t length) { return MakeExternalV8String(isolate, reinterpret_cast(data), length); } /// Make a V8 string from external std::u16string. inline v8::Local MakeExternalV8String(v8::Isolate *isolate, const std::u16string& str) { return MakeExternalV8String(isolate, str.data(), str.length()); } /// Make a V8 string from external napa::stl::U16String. inline v8::Local MakeExternalV8String(v8::Isolate *isolate, const napa::stl::U16String& str) { return MakeExternalV8String(isolate, str.data(), str.length()); } /// Converts a V8 string object to a movable Utf8String which supports an allocator. template class Utf8StringWithAllocator { public: Utf8StringWithAllocator() : _data(nullptr), _length(0) { } Utf8StringWithAllocator( const v8::Local& val, const Alloc& alloc = Alloc()) : _alloc(alloc), _data(nullptr), _length(0) { if (val.IsEmpty()) { return; } auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); v8::Local str; if (!val->ToString(context).ToLocal(&str)) { return; } _length = str->Utf8Length(); _data = _alloc.allocate(_length + 1); str->WriteUtf8(_data); } ~Utf8StringWithAllocator() { if (_data != nullptr) { _alloc.deallocate(_data, _length); } } const char* Data() const { return _data; } size_t Length() const { return _length; } /// Non copyable. Utf8StringWithAllocator(const Utf8StringWithAllocator&) = delete; Utf8StringWithAllocator& operator=(const Utf8StringWithAllocator&) = delete; /// Move constructor. Utf8StringWithAllocator(Utf8StringWithAllocator&& rhs) : _data(rhs._data), _length(rhs._length), _alloc(std::move(rhs._alloc)) { rhs._data = nullptr; rhs._length = 0; } /// Move assignment. Utf8StringWithAllocator& operator=(Utf8StringWithAllocator&& rhs) { _data = rhs._data; _length = rhs._length; _alloc = std::move(rhs._alloc); rhs._data = nullptr; rhs._length = 0; return *this; } private: char* _data; size_t _length; Alloc _alloc; }; /// Utf8String in C++. typedef Utf8StringWithAllocator> Utf8String; } } ================================================ FILE: inc/napa/v8-helpers/time.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace v8_helpers { static const uint32_t NANOS_PER_SECOND = 1000000000; /// Make a v8 array from high-resolution time. inline v8::Local HrtimeToV8Uint32Array(v8::Isolate* isolate, uint64_t time) { v8::EscapableHandleScope scope(isolate); auto context = isolate->GetCurrentContext(); v8::Local res = v8::Array::New(isolate, 2); (void)res->CreateDataProperty( context, 0, v8::Integer::NewFromUnsigned(isolate, static_cast(time / NANOS_PER_SECOND))); (void)res->CreateDataProperty( context, 1, v8::Integer::NewFromUnsigned(isolate, static_cast(time % NANOS_PER_SECOND))); return scope.Escape(res); } /// Convert a 2-element v8 array to high-resolution time in nano-seconds. inline std::pair V8Uint32ArrayToHrtime(v8::Isolate* isolate, v8::Local value) { v8::EscapableHandleScope scope(isolate); auto context = isolate->GetCurrentContext(); if (value.IsEmpty() || !value->IsArray()) { return std::make_pair(0, false); } auto array = v8::Local::Cast(value); if (array->Length() != 2) { return std::make_pair(0, false); } return std::make_pair(static_cast(array->Get(0)->Uint32Value()) * NANOS_PER_SECOND + array->Get(1)->Uint32Value(), true); } } } ================================================ FILE: inc/napa/v8-helpers.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include #include #include #include #include #include ================================================ FILE: inc/napa/version.h ================================================ #pragma once #ifndef NAPA_VERSION #define NAPA_VERSION_MAJOR 0 #define NAPA_VERSION_MINOR 1 #define NAPA_VERSION_PATCH 0 #endif ================================================ FILE: inc/napa/zone/napa-async-runner.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace zone { /// Function to run asynchronously in separate thread. /// Return value will be the input to 'AsyncCompleteCallback'. using AsyncWork = std::function; /// Function to run async-supporting function in the current thread. /// /// Completion function given as argument must be called with return values to notify asynchronous work completion. /// using CompletionWork = std::function)>; /// Callback running in V8 isolate after asynchronous callback completes. /// /// It's called inside v8::HandleScopoe. /// Function has two arguments, Javascript callback and return value from asynchronous work. /// using AsyncCompleteCallback = std::function, void*)>; /// It runs a synchronous function in a separate thread and posts a completion into the current V8 execution loop. /// Javascript callback. /// Function to run asynchronously in separate thread. /// Callback running in V8 isolate after asynchronous callback completes. NAPA_API void PostAsyncWork(v8::Local jsCallback, AsyncWork asyncWork, AsyncCompleteCallback asyncCompleteCallback); /// It runs an asynchronous function and post a completion into the current V8 execution loop. /// Javascript callback. /// Function to wrap async-supporting function. /// Callback running in V8 isolate after asynchronous function completes. NAPA_API void DoAsyncWork(v8::Local jsCallback, const CompletionWork& asyncWork, AsyncCompleteCallback asyncCompleteCallback); } // End of namespace module. } // End of namespace napa. ================================================ FILE: inc/napa/zone/node-async-runner.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include namespace napa { namespace zone { /// Function to run asynchronously in separate thread. /// Return value will be the input to 'AsyncCompleteCallback'. using AsyncWork = std::function; /// Function to run async-supporting function in the current thread. /// /// Completion function given as argument must be called with return values to notify asynchronous work completion. /// using CompletionWork = std::function)>; /// Callback running in V8 isolate after asynchronous callback completes. /// /// It's called inside v8::HandleScopoe. /// Function has two arguments, Javascript callback and return value from asynchronous work. /// using AsyncCompleteCallback = std::function, void*)>; /// Class holding asynchronous callbacks and libuv request. struct AsyncContext { /// libuv request. uv_work_t work; /// Javascript callback. v8::Persistent jsCallback; /// Function to run asynchronously in separate thread. AsyncWork asyncWork; /// Result from asynchronous work. void* result = nullptr; /// Callback running in V8 isolate after asynchronous callback completes. AsyncCompleteCallback asyncCompleteCallback; }; /// Class holding completion callback and libuv request. struct CompletionContext { /// libuv request. uv_async_t work; /// Javascript callback. v8::Persistent jsCallback; /// Result from asynchronous work. void* result = nullptr; /// Callback running in V8 isolate after asynchronous callback completes. AsyncCompleteCallback asyncCompleteCallback; }; /// Callback run asynchronously in separate thread. /// libuv request holding asynchronous callbacks. inline void RunAsyncWork(uv_work_t* work) { auto context = static_cast(work->data); context->result = context->asyncWork(); } /// Callback run in V8 isolate after asynchronous callback completes. /// libuv request holding asynchronous callbacks. /// O if asynchronous work is successful. inline void RunAsyncCompleteCallback(uv_work_t* work, int status) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = static_cast(work->data); std::unique_ptr> deferred(context, [](auto context) { context->jsCallback.Reset(); delete context; }); if (status != 0) { return; } auto jsCallback = v8::Local::New(isolate, context->jsCallback); context->asyncCompleteCallback(jsCallback, context->result); } /// Callback run in node event loop. /// libuv request holding asynchronous callbacks. inline void RunCompletionCallback(uv_async_t* work) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = static_cast(work->data); auto jsCallback = v8::Local::New(isolate, context->jsCallback); context->asyncCompleteCallback(jsCallback, context->result); uv_close(reinterpret_cast(work), [](auto work) { auto context = static_cast(work->data); context->jsCallback.Reset(); delete context; }); } /// It runs a synchronous function in a separate thread and posts a completion into the current V8 execution loop. /// Javascript callback. /// Function to run asynchronously in separate thread. /// Callback running in V8 isolate after asynchronous callback completes. /// Return value from 'asyncWork' will be the input to 'asyncCompleteCallback'. inline void PostAsyncWork(v8::Local jsCallback, AsyncWork asyncWork, AsyncCompleteCallback asyncCompleteCallback) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = new AsyncContext(); context->work.data = context; context->jsCallback.Reset(isolate, jsCallback); context->asyncWork = std::move(asyncWork); context->asyncCompleteCallback = std::move(asyncCompleteCallback); uv_queue_work(uv_default_loop(), &context->work, RunAsyncWork, RunAsyncCompleteCallback); } /// It runs an asynchronous function and post a completion into the current V8 execution loop. /// Javascript callback. /// Function to wrap async-supporting function. /// Callback running in V8 isolate after asynchronous function completes. /// Argument at 'asyncWork' completion callback will be the input to 'asyncCompleteCallback'. inline void DoAsyncWork(v8::Local jsCallback, const CompletionWork& asyncWork, AsyncCompleteCallback asyncCompleteCallback) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = new CompletionContext(); context->work.data = context; context->jsCallback.Reset(isolate, jsCallback); context->asyncCompleteCallback = std::move(asyncCompleteCallback); uv_async_init(uv_default_loop(), &context->work, RunCompletionCallback); asyncWork([context](void* result) { context->result = result; uv_async_send(&context->work); }); } } // End of namespace module. } // End of namespace napa. ================================================ FILE: inc/napa/zone.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "napa/capi.h" #include #include namespace napa { /// Initializes napa with global scope settings. inline ResultCode Initialize(const std::string& settings = "") { return napa_initialize(STD_STRING_TO_NAPA_STRING_REF(settings)); } /// Initialize napa using console provided arguments. inline ResultCode InitializeFromConsole(int argc, const char* argv[]) { return napa_initialize_from_console(argc, argv); } /// Shut down napa. inline ResultCode Shutdown() { return napa_shutdown(); } /// C++ proxy around napa Zone C APIs. class Zone { public: /// Creates a new zone and wraps it with a zone proxy instance. /// A unique id for the zone. /// A settings string to set zone specific settings. explicit Zone(const std::string& id, const std::string& settings = "") : _zoneId(id) { _handle = napa_zone_create(STD_STRING_TO_NAPA_STRING_REF(id)); auto res = napa_zone_init(_handle, STD_STRING_TO_NAPA_STRING_REF(settings)); if (res != NAPA_RESULT_SUCCESS) { napa_zone_release(_handle); throw std::runtime_error(napa_result_code_to_string(res)); } } /// Releases the underlying zone handle. ~Zone() { napa_zone_release(_handle); } /// Compiles and run the provided source code on all zone workers asynchronously. /// The source code. /// A callback that is triggered when broadcasting is done. const std::string& GetId() const { return _zoneId; } /// void Broadcast(const FunctionSpec& spec, BroadcastCallback callback) { // Will be deleted on when the callback scope ends. auto context = new BroadcastCallback(std::move(callback)); napa_zone_function_spec req; req.module = spec.module; req.function = spec.function; req.arguments = spec.arguments.data(); req.arguments_count = spec.arguments.size(); req.options = spec.options; // Release ownership of transport context req.transport_context = reinterpret_cast(spec.transportContext.release()); napa_zone_broadcast(_handle, req, [](napa_zone_result result, void* context) { // Ensures the context is deleted when this scope ends. std::unique_ptr callback(reinterpret_cast(context)); Result res; res.code = result.code; res.errorMessage = NAPA_STRING_REF_TO_STD_STRING(result.error_message); res.returnValue = NAPA_STRING_REF_TO_STD_STRING(result.return_value); // Assume ownership of transport context res.transportContext.reset( reinterpret_cast(result.transport_context)); (*callback)(std::move(res)); }, context); } /// Executes a pre-loaded JS function synchronously. /// A function spec to call. Result BroadcastSync(const FunctionSpec& spec) { if (GetCurrent()->GetId() == GetId()) { return {NAPA_RESULT_BROADCAST_SCRIPT_ERROR, "Cannot call `broadcastSync` on current zone.", "", nullptr}; } std::promise prom; auto fut = prom.get_future(); Broadcast(spec, [&prom](Result result) { prom.set_value(std::move(result)); }); return fut.get(); } /// Executes a pre-loaded JS function asynchronously. /// A function spec to call. /// A callback that is triggered when execution is done. void Execute(const FunctionSpec& spec, ExecuteCallback callback) { // Will be deleted on when the callback scope ends. auto context = new ExecuteCallback(std::move(callback)); napa_zone_function_spec req; req.module = spec.module; req.function = spec.function; req.arguments = spec.arguments.data(); req.arguments_count = spec.arguments.size(); req.options = spec.options; // Release ownership of transport context req.transport_context = reinterpret_cast(spec.transportContext.release()); napa_zone_execute(_handle, req, [](napa_zone_result result, void* context) { // Ensures the context is deleted when this scope ends. std::unique_ptr callback(reinterpret_cast(context)); Result res; res.code = result.code; res.errorMessage = NAPA_STRING_REF_TO_STD_STRING(result.error_message); res.returnValue = NAPA_STRING_REF_TO_STD_STRING(result.return_value); // Assume ownership of transport context res.transportContext.reset( reinterpret_cast(result.transport_context)); (*callback)(std::move(res)); }, context); } /// Executes a pre-loaded JS function synchronously. /// The function spec to call. Result ExecuteSync(const FunctionSpec& spec) { std::promise prom; auto fut = prom.get_future(); Execute(spec, [&prom](Result result) { prom.set_value(std::move(result)); }); return fut.get(); } /// Retrieves a new zone proxy for the zone id, throws if zone is not found. static std::unique_ptr Get(const std::string& id) { auto handle = napa_zone_get(STD_STRING_TO_NAPA_STRING_REF(id)); if (!handle) { throw std::runtime_error("No zone exists for id '" + id + "'"); } return std::unique_ptr(new Zone(id, handle)); } /// Creates a proxy to the current zone, throws if non is associated with this thread. static std::unique_ptr GetCurrent() { auto handle = napa_zone_get_current(); if (!handle) { throw std::runtime_error("The calling thread is not associated with a zone"); } auto zoneId = NAPA_STRING_REF_TO_STD_STRING(napa_zone_get_id(handle)); return std::unique_ptr(new Zone(std::move(zoneId), handle)); } private: /// Private constructor to create a C++ zone proxy from a C handle. explicit Zone(const std::string& id, napa_zone_handle handle) : _zoneId(id), _handle(handle) {} /// The zone id. std::string _zoneId; /// Underlying zone handle. napa_zone_handle _handle; }; } ================================================ FILE: inc/napa.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #ifdef BUILDING_NAPA_EXTENSION #define USING_V8_SHARED 1 #endif #include #include #include #include #include #include #include #include #include #include #include ================================================ FILE: lib/binding.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. function checkNodeVersion() { var semver = require('semver'); var currentNodeVersion = semver.coerce(process.version); if (semver.gt(currentNodeVersion, 'v8.4.0')) { var log = require('npmlog'); log.warn('napajs binding', 'Thanks for using Napa.js.'); log.warn('napajs binding', 'There is a compatibility issue on Node v8.5.0 and above.'); log.warn('napajs binding', 'The flag "--noincremental-marking" is set to disable V8 incremental marking as a workaround.'); log.warn('napajs binding', 'We are working with Node.js team on a fix in newer Node versions.'); require('v8').setFlagsFromString('--noincremental-marking'); } else if (semver.lt(currentNodeVersion, 'v4.5.0')) { var errorMessage = 'Napa.js is not supported on Node version lower than v4.5'; require('npmlog').error('napajs binding', errorMessage); throw new Error(errorMessage); } } if (typeof __in_napa === 'undefined') { checkNodeVersion(); module.exports = require('../bin/napa-binding'); } else { module.exports = process.binding('napa-binding'); } ================================================ FILE: lib/core/.gitignore ================================================ # Ignore all .js files by default **/*.js # Specify all .js files to preserve explicitly !/assert.js !/events.js !/process.js !/tty.js !/util.js ================================================ FILE: lib/core/.npmignore ================================================ # Keep this file so that npm can pick up correct files. ================================================ FILE: lib/core/README.md ================================================ # Napa.js core modules development guideline ## Summary A Napa.js **core module** is a module that provided by Napa.js and initialized ready for use in a Napa zone. There are 2 types of core modules: - **Napa API core modules**: A set of Napa.js specific modules that described in [Napa.js modules](https://github.com/Microsoft/napajs/blob/master/docs/api/index.md#core-modules). - **Node API core modules**: A set of Napa.js modules that implement a subset of [Node.js modules](https://github.com/Microsoft/napajs/blob/master/docs/api/node-api.md). All core modules can be imported by using global function `require()` in napa zone. If a module is a built-in module, it is already loaded and can be accessed by its name as a global object. ## Core modules index Header file [`/src/module/core-modules/core-modules.h`](https://github.com/Microsoft/napajs/blob/master/src/module/core-modules/core-modules.h) lists all native core modules to export. JSON file [`/lib/core/core-modules.json`](https://github.com/Microsoft/napajs/blob/master/lib/core/core-modules.json) lists all core modules available in Napa zone. ## Napa API core modules Napa API core modules are implemented in C++ and Typescript. C++ implementation goes to folder `/src/module/core-modules/napa`. File [`/src/module/core-modules/napa/napa-binding.cpp`](https://github.com/Microsoft/napajs/blob/master/src/module/core-modules/napa/napa-binding.cpp) is the entry of native boundary. Typescript implementation goes to folder `/lib`. It uses `process.binding()` to access binary core modules and exports whatever defined in API document. File [`/lib/index.ts`](https://github.com/Microsoft/napajs/blob/master/lib/index.ts) is the entry of javascript boundary. ## Node API core modules Napa API core modules are implemented in C++ and Typescript. C++ implementation goes to folder `/src/module/core-modules/node`. Typescript implementation goes to folder `/lib/core`. It uses `process.binding()` to access binary core modules and exports whatever defined in API document. ## Developing guideline - To add a pure native module, follow the following steps: - Create a module class in the correct folder (see above) with a static `Init()` method. - Add the class to the list in `/src/module/core-modules/core-modules.h`. - Add the module name in `/lib/core/core-modules.json`. - To add a pure typescript/javascript Node API module, follow the following steps: - Add files under folder `/lib/core`. - Update file `/lib/core/.gitignore` if any javascript file added under folder `/lib/core`. - Add the module name in `/lib/core/core-modules.json`. - To add a native/js mixed Node API module, follow the steps of both as described above. Use `process.binding()` to access binary core modules in typescript/javascript. - To add a Napa API module usually means a big change to existing structure. May need a design review. - Try to always use typescript rather than javascript. Use javascript only when leveraging an existing javascript implementation. - Node API modules should be tested in Napa zone, and Napa API modules should be tested in both Node and Napa zone. - Pure native test goes to `/unittest` (Using [Catch](https://github.com/catchorg/Catch2)) and js test goes to `/test` (Using [mocha](https://github.com/mochajs/mocha)). ================================================ FILE: lib/core/core-modules.json ================================================ [ { "name": "assert", "type": "core" }, { "name": "events", "type": "core" }, { "name": "process", "type": "builtin" }, { "name": "tty", "type": "core" }, { "name": "util", "type": "core" }, { "name": "timers", "type": "builtin" } ] ================================================ FILE: lib/core/timers/timer-api.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import { Timeout, Immediate } from './timer'; let binding = require('../../binding'); export function setImmediate(func: (...args: any[]) => void, ...args: any[]): Immediate { let timeout = new Timeout(func, 0, 0, args); binding.setImmediate(timeout); return timeout; } export function clearImmediate(immediate: Immediate): void { immediate._active = false; } export function setTimeout(func: (...args: any[]) => void, after: number, ...args: any[]): Timeout { let timeout = new Timeout(func, after, 0, args); binding.setTimers(timeout); return timeout; } export function clearTimeout(timeout: Timeout): void { timeout._active = false; } export function setInterval(func: (...args: any[]) => void, after: number, ...args: any[]): Timeout { let timeout = new Timeout(func, after, after, args); binding.setTimers(timeout); return timeout; } export function clearInterval(timeout: Timeout): void { timeout._active = false; } ================================================ FILE: lib/core/timers/timer.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. /// Timer is a facility to run timed callbacks in caller's isolates. export interface Timer { } const TIMEOUT_MAX = 2 ** 31 -1; export class Timeout { _callback: (...args: any[]) => void; private _after: number; private _repeat: number; private _args: any[]; _active: boolean; private _timer: Timer; constructor(callback: (...args: any[]) => void, after: number, repeat: number, args: any[]) { if (after < 1) after = 0; //0 used for immediate if (after > TIMEOUT_MAX) after = 1; if (repeat < 1 || after == 0) repeat = 0; // do not repeat if (repeat > TIMEOUT_MAX) repeat = 1; this._callback = callback; this._after = after; this._repeat = repeat; this._args = args; this._timer = null; this._active = true; } } export type Immediate = Timeout; ================================================ FILE: lib/core/timers.ts ================================================ export * from './timers/timer'; export * from './timers/timer-api'; ================================================ FILE: lib/index.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import { log } from './log'; import * as memory from './memory'; import * as metric from './metric'; import * as runtime from './runtime'; import * as store from './store'; import * as sync from './sync'; import * as transport from './transport'; import * as v8 from './v8'; import * as zone from './zone'; export { log, memory, metric, runtime, store, sync, transport, v8, zone }; // Add execute proxy to global context. import { call } from './zone/function-call'; ((global))["__napa_zone_call__"] = call; // Export 'napa' in global for all isolates that require napajs. ((global))["napa"] = exports; ================================================ FILE: lib/log.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let binding = require('./binding'); export interface LogFunction { (message: string): void; (section: string, message: string): void; (section: string, traceId: string, message: string): void; } export interface Log extends LogFunction { err(message: string): void; err(section: string, message: string): void; err(section: string, traceId: string, message: string): void; warn(message: string): void; warn(section: string, message: string): void; warn(section: string, traceId: string, message: string): void; info(message: string): void; info(section: string, message: string): void; info(section: string, traceId: string, message: string): void; debug(message: string): void; debug(section: string, message: string): void; debug(section: string, traceId: string, message: string): void; } export let log: Log = createLogObject(); enum LogLevel { Error = 0, Warning = 1, Info = 2, Debug = 3, } function dispatchLog(level: LogLevel, arg1: string, arg2?: string, arg3?: string) { if (arg3 != undefined) { binding.log(level, arg1, arg2, arg3); } else if (arg2 != undefined) { binding.log(level, arg1, undefined, arg2); } else { binding.log(level, undefined, undefined, arg1); } } function createLogObject() : Log { // napa.log() let logObj: any = function(arg1: string, arg2?: string, arg3?: string) { dispatchLog(LogLevel.Info, arg1, arg2, arg3); } // napa.log.err() logObj.err = function(arg1: string, arg2?: string, arg3?: string) { dispatchLog(LogLevel.Error, arg1, arg2, arg3); } // napa.log.warn() logObj.warn = function(arg1: string, arg2?: string, arg3?: string) { dispatchLog(LogLevel.Warning, arg1, arg2, arg3); } // napa.log.info() logObj.info = function(arg1: string, arg2?: string, arg3?: string) { dispatchLog(LogLevel.Info, arg1, arg2, arg3); } // napa.log.debug() logObj.debug = function(arg1: string, arg2?: string, arg3?: string) { dispatchLog(LogLevel.Debug, arg1, arg2, arg3); } return logObj; } ================================================ FILE: lib/memory/allocator.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import { Handle } from './handle'; import { Shareable } from './shareable'; /// Javascript interface for allocator. /// An allocator must be a native object shared by all isolates, which allocate memory. /// export interface Allocator extends Shareable { /// Allocate memory of requested size in bytes. /// Size in bytes to allocate. allocate(size: number): Handle; /// Deallocate memory of requested handle. /// Handle of memory to deallocate. /// Hint for the size of memory to deallocate. Pass 0 if unknown. deallocate(handle: Handle, sizeHint: number): void; /// Type of allocator for better debuggability. readonly type: string; } /// Javascript interface for allocator debugger. export interface AllocatorDebugger extends Allocator { /// Get debug info. getDebugInfo(): string; } let binding = require('../binding'); /// Export Crt allocator from napa.dll. export let crtAllocator: Allocator = binding.getCrtAllocator(); /// Export default allocator from napa.dll. export let defaultAllocator: Allocator = binding.getDefaultAllocator(); /// Create a debug allocator around allocator. /// User allocator. export function debugAllocator(allocator: Allocator): AllocatorDebugger { return new binding.AllocatorDebuggerWrap(allocator); } ================================================ FILE: lib/memory/handle.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. export type Handle = [number, number] | [number, number, number]; /// nullptr. export const EMPTY_HANDLE: Handle = [0, 0]; /// Tell if pointer is nullptr. export function isEmpty(handle: Handle): boolean { return handle == null || (handle[0] === 0 && handle[1] === 0); } /// Tell if a pointer has type information. export function getTypeHash(handle: Handle): number { return handle.length === 3 ? handle[2] : 0; } ================================================ FILE: lib/memory/shareable.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import { Handle} from './handle'; import { Transportable, isTransportable } from '../transport/transportable'; /// Interface of native object (wrapped shared_ptr) that can be shared across isolates. /// The counterpart of Shareable is napa::module::ShareableWrap, always extend napa::module::ShareableWrap for sharable native object. export interface Shareable extends Transportable { /// Return handle of this object. readonly handle: Handle; /// Return true if handle is empty. isNull(): boolean; /// Return reference count for shared object. readonly refCount: number; } /// Tells if a JavaScript is value or not. export function isShareable(jsValue: any): boolean { if (isTransportable(jsValue)) { let object = jsValue; return object.hasOwnProperty('handle') && object.hasOwnProperty('refCount'); } return false; } ================================================ FILE: lib/memory.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. export * from './memory/allocator'; export * from './memory/handle'; export * from './memory/shareable'; ================================================ FILE: lib/metric.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let binding = require('./binding'); export enum MetricType { Number = 0, Rate, Percentile, } export interface Metric { readonly section: string; readonly name: string; set(value: number, dimensions?: string[]): void; increment(dimensions?: string[]): void; decrement(dimensions?: string[]): void; } /// A cache for metric wraps. var _metricsCache: { [key: string]: Metric } = {}; export function get(section: string, name: string, type: MetricType, dimensions: string[] = []) : Metric { let key: string = (section ? section : "") + "\\" + (name ? name : ""); // Check cache first let metric: Metric = _metricsCache[key]; if (metric) { return metric; } // Add to cache let metricWrap: any = new binding.MetricWrap(section, name, type, dimensions); metricWrap.section = section; metricWrap.name = name; _metricsCache[key] = metricWrap; return metricWrap; } ================================================ FILE: lib/runtime/platform.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let binding = require('../binding'); import { log } from '../log'; // This variable is either defined by napa runtime, or not defined (hence node runtime) declare var __in_napa: boolean; /// /// Describes the available platform settings. /// export interface PlatformSettings { /// The logging provider to use when outputting logs. loggingProvider?: string; /// The metric provider to use when creating/setting metric values. metricProvider?: string; } /// Initialization of napa is only needed if we run in node. let _initializationNeeded: boolean = (typeof __in_napa === 'undefined'); /// Empty platform settings. let _platformSettings: PlatformSettings = {}; /// Sets the platform settings. Must be called from node before the first container is created. export function setPlatformSettings(settings: PlatformSettings) { if (!_initializationNeeded) { // If we don't need to initialize we can't set platform settings. log.err("Cannot set platform settings after napa was already initialized"); return; } _platformSettings = settings; initialize(); } export function initialize() { if (_initializationNeeded) { // Guard initialization, should only be called once. binding.initialize(_platformSettings); _initializationNeeded = false; } } ================================================ FILE: lib/runtime.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. export { setPlatformSettings } from './runtime/platform'; ================================================ FILE: lib/store/store-api.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import { Store } from './store'; let binding = require('../binding'); /// Create a store with an id. /// String identifier which can be used to get the store from all isolates. /// A store object or throws Error if store with this id already exists. /// Store object will be destroyed when reference from all isolates are unreferenced. /// It's usually a best practice to keep a long-living reference in user modules or global scope. export function create(id: string): Store { return binding.createStore(id); } /// Get a store with an id. /// String identifier which is passed to create/getOrCreate earlier. /// A store object if exists, otherwise undefined. export function get(id: string): Store { return binding.getStore(id); } /// Get a store with an id, or create it if not exist. /// String identifier which can be used to get the store from all isolates. /// A store object associated with the id. /// Store object will be destroyed when reference from all isolates are unreferenced. /// It's usually a best practice to keep a long-living reference in user modules or global scope. export function getOrCreate(id: string): Store { return binding.getOrCreateStore(id); } /// Returns number of stores that is alive. export function count(): number { return binding.getStoreCount(); } ================================================ FILE: lib/store/store.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. /// Store is a facility to share (built-in JavaScript types or Transportable subclasses) objects across isolates. export interface Store { /// Id of this store. readonly id: string; /// Number of keys in this store. readonly size: number; /// Check if this store has a key. /// Case-sensitive string key. /// True if this store has the key. has(key: string): boolean; /// Get JavaScript value by key. /// Case-sensitive string key. /// Value for key, undefined if not found. get(key: string): any; /// Insert or update a JavaScript value by key. /// Case-sensitive string key. /// Value. Any value of built-in JavaScript types or Transportable subclasses can be accepted. set(key: string, value: any): void; /// Remove a key with its value from this store. /// Case-sensitive string key. delete(key: string): void; } ================================================ FILE: lib/store.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. export * from './store/store'; export * from './store/store-api'; ================================================ FILE: lib/sync/lock.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let binding = require('../binding'); import { cid, TransportContext, TransportableObject } from '../transport'; export interface Lock { /// /// Obtain the lock and input function synchronously. /// or throws error if input function throws. /// Lock will be released once execution finishes or an exception is thrown. /// /// The input function to run. /// Optional. A list of parameters that passed to func. /// The value that the input function returns. /// This function will obtain the lock before running the input function. It will wait until the /// lock is available. If the input function throws exception, the exception will be thrown out. guardSync(func: (...params: any[]) => any, params?: any[]): any; } export function createLock(): Lock { return binding.createLock(); } ================================================ FILE: lib/sync.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. export * from './sync/lock'; ================================================ FILE: lib/transport/builtin-object-transporter.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import { Shareable } from '../memory/shareable'; /// /// ShareableWrap of SerializedData. /// export interface SerializedData extends Shareable { } export function serializeValue(jsValue: any): SerializedData { return require('../binding').serializeValue(jsValue); } export function deserializeValue(serializedData: SerializedData): any { return require('../binding').deserializeValue(serializedData); } ================================================ FILE: lib/transport/function-transporter.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. //////////////////////////////////////////////////////////////////////// // Module to support function transport. import { Store } from '../store/store'; import * as assert from 'assert'; import * as path from 'path'; /// Function hash to function cache. let _hashToFunctionCache: {[hash: string]: (...args: any[]) => any} = {}; /// Function to hash cache. /// Function cannot be used as a key in TypeScript. let _functionToHashCache: any = {}; /// Marshalled function body cache. let _store: Store; /// Interface for function definition that will be saved in store. interface FunctionDef { /// From which file name the function is defined. origin: string; /// Function body. body: string; } /// Get underlying store to save marshall function body across isolates. function getStore(): Store { if (_store == null) { // Lazy creation function store // 1) avoid circular runtime dependency between store and transport. // 2) avoid unnecessary cost when function transport is not used. _store = require('../store/store-api') .getOrCreate('__napajs_marshalled_functions'); } return _store; } /// Save function and get a hash string to use it later. export function save(func: (...args: any[]) => any): string { let hash = _functionToHashCache[((func))]; if (hash == null) { // Should happen only on first marshall of input function in current isolate. let origin = (func).origin || ''; let body = func.toString(); let fullContent = origin + ":" + body; hash = getFunctionHash(fullContent); let def: FunctionDef = { origin: origin, body: body }; getStore().set(hash, def); cacheFunction(hash, func); } return hash; } /// Load a function with a hash retrieved from `save`. export function load(hash: string): (...args: any[]) => any { let func = _hashToFunctionCache[hash]; if (func == null) { // Should happen only on first unmarshall of given hash in current isolate.. let def: FunctionDef = getStore().get(hash); if (def == null) { throw new Error(`Function hash cannot be found: ${hash}`); } func = loadFunction(def); cacheFunction(hash, func); } return func; } /// Cache function with its hash in current isolate. function cacheFunction(hash: string, func: (...args: any[]) => any) { _functionToHashCache[(func)] = hash; _hashToFunctionCache[hash] = func; } /// Generate hash for function definition using DJB2 algorithm. /// See: https://en.wikipedia.org/wiki/DJB2 /// function getFunctionHash(signature: string): string { let hash = 5381; for (let i = 0; i < signature.length; ++i) { hash = (hash * 33) ^ signature.charCodeAt(i); } /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed * integers. Since we want the results to be always positive, convert the * signed int to an unsigned by doing an unsigned bitshift. */ return (hash >>> 0).toString(16); } declare var __in_napa: boolean; /// Load function from definition. function loadFunction(def: FunctionDef): (...args: any[]) => any { let moduleId = def.origin; let script = "module.exports = " + def.body + ";"; let func: any = null; if (typeof __in_napa === 'undefined') { // In node, we create a sandbox using Module let Module: any = null; if (Module == null) { Module = require('module'); } let module = new Module(moduleId); module.filename = moduleId; module.paths = Module._nodeModulePaths(path.dirname(def.origin)); module._compile(script, moduleId); func = module.exports; } else { // In napa, we create a sandbox using require(path, script); func = (require)(moduleId, script); } func.origin = def.origin; return func; } ================================================ FILE: lib/transport/transport.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as transportable from './transportable'; import * as functionTransporter from './function-transporter'; import * as builtinObjectTransporter from './builtin-object-transporter'; import * as path from 'path'; /// Per-isolate cid => constructor registry. let _registry: Map transportable.Transportable> = new Map transportable.Transportable>(); let _builtInTypeWhitelist = new Set(); [ 'ArrayBuffer', 'Float32Array', 'Float64Array', 'Int16Array', 'Int32Array', 'Int8Array', 'SharedArrayBuffer', 'Uint16Array', 'Uint32Array', 'Uint8Array' ].forEach((type) => { _builtInTypeWhitelist.add(type); }); /// Register a TransportableObject sub-class with a Constructor ID (cid). export function register(subClass: new(...args: any[]) => any) { // Check cid from constructor first, which is for TransportableObject. // Thus we don't need to construct the object to get cid according to Transportable interface. let cid: string = (subClass)['_cid']; if (cid == null) { cid = new subClass().cid(); } if (cid == null) { throw new Error(`Class "${subClass.name}" doesn't implement cid(), did you forget put @cid decorator before class declaration?`); } if (_registry.has(cid)) { throw new Error(`Constructor ID (cid) "${cid}" is already registered.`); } _registry.set(cid, subClass); } /// Marshall transform a JS value to a plain JS value that will be stringified. export function marshallTransform(jsValue: any, context: transportable.TransportContext): any { if (jsValue != null && typeof jsValue === 'object' && !Array.isArray(jsValue)) { let constructorName = Object.getPrototypeOf(jsValue).constructor.name; if (constructorName !== 'Object') { if (typeof jsValue['cid'] === 'function') { if (context == null) { throw new Error(`Cannot transport type \"${constructorName}\" without a transport context.`); } return (jsValue).marshall(context); } else if (_builtInTypeWhitelist.has(constructorName)) { let serializedData = builtinObjectTransporter.serializeValue(jsValue); if (serializedData) { return { _serialized : serializedData }; } else { throw new Error(`Failed to serialize object with type of \"${constructorName}\".`); } } else { throw new Error(`Object type \"${constructorName}\" is not transportable.`); } } } return jsValue; } /// Unmarshall transform a plain JS value to a transportable class instance. /// Plain Javascript value. /// Transport context. /// Transported value. function unmarshallTransform(payload: any, context: transportable.TransportContext): any { if (payload == null) return payload; if (payload._cid !== undefined) { let cid = payload._cid; if (cid === 'function') { return functionTransporter.load(payload.hash); } if (context == null) { throw new Error(`Cannot transport type with cid \"${cid}\" without a transport context.`); } let subClass = _registry.get(cid); if (subClass == null) { throw new Error(`Unrecognized Constructor ID (cid) "${cid}". Please ensure @cid is applied on the class or transport.register is called on the class.`); } let object = new subClass(); object.unmarshall(payload, context); return object; } else if (payload.hasOwnProperty('_serialized')) { return builtinObjectTransporter.deserializeValue(payload['_serialized']); } return payload; } /// Unmarshall from JSON string to a JavaScript value, which could contain complex/native objects. /// JSON string. /// Transport context to save shared pointers. /// Parsed JavaScript value, which could be built-in JavaScript types or deserialized Transportable objects. export function unmarshall( json: string, context: transportable.TransportContext): any { if (json === "undefined") { return undefined; } return JSON.parse(json, (key: any, value: any): any => { return unmarshallTransform(value, context); }); } /// Marshall a JavaScript value to JSON. /// JavaScript value to stringify, which maybe built-in JavaScript types or transportable objects. /// Transport context to save shared pointers. /// JSON string. export function marshall( jsValue: any, context: transportable.TransportContext): string { // Function is transportable only as root object. // This is to avoid unexpected marshalling on member functions. if (typeof jsValue === 'function') { return `{"_cid": "function", "hash": "${functionTransporter.save(jsValue)}"}`; } return JSON.stringify(jsValue, (key: string, value: any) => { return marshallTransform(value, context); }); } ================================================ FILE: lib/transport/transportable.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. /// In Napa.JS, transporting objects across isolates is required for multi-thread collaborations. /// /// A JavaScript value is transportable, if /// 1) it is a built-in JavaScript types, which includes primitive types, plain objects (whose constructor name is 'Object') and arrays. /// 2) or a class implements Transportable. /// 3) or it is a composite of #1 and #2. /// /// import * as transport from './transport' import * as v8 from '../v8'; import { Shareable } from '../memory/shareable'; import { Handle } from '../memory/handle'; import * as path from 'path'; /// Transport context carries additional information needed to unmarshall /// objects, besides the payload itself. Currently, only std::shared_ptr is transported via TransportContext, /// since their lifecycle are beyond an V8 isolate thus cannot be managed only via payload. /// TransportContext is a C++ add-on and only accessed from C++ thus no method is exposed to JavaScript world. /// /// Reference: napa::binding::TransportContextWrapImpl. export interface TransportContext { /// Save a shared_ptr for later load in another isolate. saveShared(object: Shareable): void; /// Load a shared_ptr from previous save in another isolate. loadShared(handle: Handle): Shareable; /// Number of shared object saved in this TransportContext. readonly sharedCount: number; } /// Interface for transportable non-built-in JavaScript types. export interface Transportable { /// Get Constructor ID (cid) for transportable object. cid(): string; /// Marshall object into plain JavaScript object. /// Transport context for saving shared pointers, only usable for C++ addons that extends napa::module::ShareableWrap. /// Plain JavaScript value. marshall(context: TransportContext): object; /// Unmarshall object from payload, which is a plain JavaScript value, and a transport context. /// Payload to read from, which already have inner objects transported. /// Transport context for loading shared pointers, only needed for C++ addons that extends napa::module::ShareableWrap. unmarshall(payload: object, context: TransportContext): void } /// Abstract class for transportable objects. /// Subclass' obligations: /// 1) Constructor should accept zero parameters . /// 2) Implement save()/load() to marshall/unmarshall internal state. /// 3) Register with transport with a Constructor ID (cid) via one of following methods: /// - declare class decorator: @cid() use '.' as cid. /// - declare class decorator: @cid('') use the specified GUID as cid. /// export abstract class TransportableObject implements Transportable{ /// Get Constructor ID (cid) for this object. cid(): string { return Object.getPrototypeOf(this).constructor._cid; } /// Subclass to save state to payload. /// Payload to write to. /// The sub-class should always serialize states into the payload /// if they are required to load the sub-class instance. /// Transport context for saving shared pointers, only usable for C++ addons that extends napa::module::ShareableWrap. abstract save(payload: object, context: TransportContext): void; /// Subclass to load state from payload. /// Payload to read from, which already have inner objects transported. /// Transport context for loading shared pointers, only usable for C++ addons that extends napa::module::ShareableWrap. abstract load(payload: object, context: TransportContext): void; /// Marshall object into plain JavaScript object. /// Plain JavaScript value. marshall(context: TransportContext): object { let payload = { _cid: this.cid() }; this.save(payload, context); return payload; } /// Unmarshall object from payload, which is a plain JavaScript value, and a transport context. /// Payload to read from, which already have inner objects transported. /// Transport context for looking up shared pointers. unmarshall(payload: object, context: TransportContext): void { this.load(payload, context); } } /// Base class for JavaScript class that is auto transportable. /// A JavaScript class can be auto transportable when /// 1) it has a default constructor. /// 2) members are transportable types. /// 3) register via class decorator @cid or transport.register. /// export class AutoTransportable extends TransportableObject { /// Automatically save own properties to payload. /// Plain JS object to write to. /// Transport context for saving shared pointers, only usable for C++ addons that extends napa::module::ShareableWrap. save(payload: object, context: TransportContext) { for (let property of Object.getOwnPropertyNames(this)) { ((payload))[property] = transport.marshallTransform(((this))[property], context); } } /// Automatically load own properties from payload. /// Payload to read from, which already have inner objects transported. /// Transport context for loading shared pointers, only usable for C++ addons that extends napa::module::ShareableWrap. load(payload: object, context: TransportContext) { // Members have already been unmarshalled. for (let property of Object.getOwnPropertyNames(payload)) { ((this))[property] = ((payload))[property]; } } } /// Tell if a jsValue is transportable. export function isTransportable(jsValue: any): boolean { if (Array.isArray(jsValue)) { // Traverse array. for (let element of jsValue) { if (!isTransportable(element)) { return false; } } } else if (typeof jsValue === 'object') { let constructor = Object.getPrototypeOf(jsValue).constructor; if (constructor.name === 'Object') { // Traverse object. for (let property in jsValue) { if (!isTransportable(jsValue[property])) { return false; } } } else if (typeof jsValue['cid'] !== 'function') { return false; } } return true; } /// Decorator 'cid' to register a transportable class with a 'cid'. /// If specified, use this GUID as cid. export function cid(guid?: string) { let moduleName: string = null; if (!guid) { moduleName = extractModuleName(v8.currentStack(2)[1].getFileName()); } return (constructor: new(...args: any[]) => any ) => { let cid = moduleName ? `${moduleName}.${constructor.name}` : guid; (constructor)['_cid'] = cid; transport.register(constructor); } } const NODE_MODULE_PREFIX: string = 'node_modules/'; /// Extract module name from module.id. function extractModuleName(moduleId: string): string { moduleId = moduleId.replace('\\\\', '/'); if (moduleId.endsWith('.js')) { moduleId = moduleId.substr(0, moduleId.length - 3); } let moduleRootStart = moduleId.lastIndexOf(NODE_MODULE_PREFIX); if (moduleRootStart >= 0) { // module is under node_modules. return moduleId.substr(moduleRootStart + NODE_MODULE_PREFIX.length); } else { // module is located using absolute or relative path. return path.relative(process.cwd(), moduleId).replace('\\\\', '/'); } } ================================================ FILE: lib/transport.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. export { Transportable, TransportableObject, TransportContext, AutoTransportable, isTransportable, cid } from './transport/transportable'; export * from './transport/transport'; import { Handle } from './memory/handle'; import { TransportContext } from './transport/transportable'; import * as functionTransporter from './transport/function-transporter'; let binding = require('./binding'); /// Create a transport context. export function createTransportContext(owning: boolean = true, handle : Handle = undefined): TransportContext { return new binding.TransportContextWrap(owning, handle); } export let saveFunction = functionTransporter.save; export let loadFunction = functionTransporter.load; ================================================ FILE: lib/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es5", "experimentalDecorators": true, "noImplicitAny": true, "declaration": true, "sourceMap": false, "lib": ["es2015"], "declarationDir": "../types" } } ================================================ FILE: lib/v8/stack-trace.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. /// Reference: https://github.com/v8/v8/wiki/Stack-Trace-API /// Represents a stack frame. export interface CallSite { /// Returns the value of this. getThis(): any; /// /// Returns the type of this as a string. /// This is the name of the function stored in the constructor field of this, if available, /// otherwise the object's [[Class]] internal property. /// getTypeName(): string; /// Returns the current function. getFunction(): any; /// /// Returns the name of the current function, typically its name property. /// If a name property is not available an attempt will be made to try to infer a name from the function's context. /// getFunctionName(): string; /// Returns the name of the property of this or one of its prototypes that holds the current function. getMethodName(): string; /// If this function was defined in a script returns the name of the script. getFileName(): string; /// If this function was defined in a script returns the current line number. getLineNumber(): number; /// If this function was defined in a script returns the current column number. getColumnNumber(): number; /// If this function was created using a call to eval returns a CallSite object representing the location where eval was called. getEvalOrigin(): CallSite; /// Is this a toplevel invocation, that is, is this the global object. isToplevel(): boolean; /// Is this call in native V8 code. isNative(): boolean; /// Is this a constructor call. isConstructor(): boolean; /// Does this call take place in code defined by a call to eval. isEval(): boolean; } /// Get current stack. /// Max stack depth to trace. /// Array of CallSite. export function currentStack(stackTraceLimit: number = 0): CallSite[] { let e: any = Error; const originPrepare = e.prepareStackTrace; const originLimit = e.stackTraceLimit; if (stackTraceLimit > 0) { e.stackTraceLimit = stackTraceLimit + 1; } e.prepareStackTrace = (prepare: any, stack: any) => stack; // We remove stack at top since it's always this function. let stack = new e().stack.slice(1); e.prepareStackTrace = originPrepare; if (stackTraceLimit > 0) { e.stackTraceLimit = originLimit; } return stack; } /// Format stack trace. export function formatStackTrace(trace: CallSite[]): string { let s = ''; for (let site of trace) { s += 'at ' + site.getFunctionName() + '(' + site.getFileName() + ':' + site.getLineNumber() + ':' + site.getColumnNumber() + ")\n"; } return s; } ================================================ FILE: lib/v8.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. export * from './v8/stack-trace'; ================================================ FILE: lib/zone/function-call.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as transport from '../transport'; import { CallOptions } from './zone'; /// Rejection type /// TODO: we need a better mapping between error code and result code. export enum RejectionType { TIMEOUT = 3, APP_ERROR = 6 } /// Interface for Call context. export interface CallContext { /// Resolve task with marshalled result. resolve(result: string): void; /// Reject task with reason. reject(reason: any): void; /// Reject task with a rejection type and reason. reject(type: RejectionType, reason: any): void; /// Returns whether task has finished (either completed or cancelled). readonly finished: boolean; /// Elapse in nano-seconds since task started. readonly elapse: [number, number]; /// Module name to select function. readonly module: string; /// Function name to execute. readonly function: string; /// Marshalled arguments. readonly args: string[]; /// Transport context. readonly transportContext: transport.TransportContext; /// Execute options. readonly options: CallOptions; } /// /// Proxy function for __napa_zone_call__. /// 1) calling a global function: /// module name: undefined or empty string /// function name: global function name /// 2) calling an anonymous function at client side: /// module name: literal '__function' /// function name: hash returned from transport.saveFunction(). /// 3) calling a function from a module: /// module name: target module path. /// function name: target function name from the module. /// /// function name can have multiple levels like 'foo.bar'. /// export function call(context: CallContext): void { // Cache the context since every call to context.transportContext will create a new wrap upon inner TransportContext pointer. let transportContext = context.transportContext; let result: any = undefined; try { result = callFunction( context.module, context.function, context.args, transportContext, context.options); } catch(error) { context.reject(error); return; } if (result != null && typeof result === 'object' && typeof result['then'] === 'function') { // Delay completion if return value is a promise. result.then((value: any) => { finishCall(context, transportContext, value); }) .catch((error: any) => { context.reject(error); }); return; } finishCall(context, transportContext, result); } /// Call a function. function callFunction( moduleName: string, functionName: string, marshalledArgs: string[], transportContext: transport.TransportContext, options: CallOptions): any { let module: any = null; let useAnonymousFunction: boolean = false; if (moduleName == null || moduleName.length === 0 || moduleName === 'global') { module = global; } else if (moduleName === '__function') { useAnonymousFunction = true; } else { module = require(moduleName); } let func = null; if (useAnonymousFunction) { func = transport.loadFunction(functionName); } else { if (module == null) { throw new Error(`Cannot load module \"${moduleName}\".`); } func = module; if (functionName != null && functionName.length != 0) { var path = functionName.split('.'); for (let item of path) { func = func[item]; if (func === undefined) { throw new Error("Cannot find function '" + functionName + "' in module '" + moduleName + "'"); } } } if (typeof func !== 'function') { throw new Error("'" + functionName + "' in module '" + moduleName + "' is not a function"); } } let args = marshalledArgs.map((arg) => { return transport.unmarshall(arg, transportContext); }); return func.apply(this, args); } /// Finish call with result. function finishCall( context: CallContext, transportContext: transport.TransportContext, result: any) { let payload: string = undefined; try { payload = transport.marshall(result, transportContext); } catch (error) { context.reject(error); return; } context.resolve(payload); } ================================================ FILE: lib/zone/zone-impl.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as path from 'path'; import * as zone from './zone'; import * as transport from '../transport'; import * as v8 from '../v8'; interface FunctionSpec { module: string; function: string; arguments: any[]; options: zone.CallOptions; transportContext: transport.TransportContext; } class Result implements zone.Result{ constructor(payload: string, transportContext: transport.TransportContext) { this._payload = payload; this._transportContext = transportContext; } get value(): any { if (this._value == null) { this._value = transport.unmarshall(this._payload, this._transportContext); } return this._value; } get payload(): string { return this._payload; } get transportContext(): transport.TransportContext { return this._transportContext; } private _transportContext: transport.TransportContext; private _payload: string; private _value: any; }; declare var __in_napa: boolean; /// Helper function to workaround possible delay in Promise resolve/reject when working with Node event loop. /// See https://github.com/audreyt/node-webworker-threads/issues/123#issuecomment-254019552 /// function runImmediately(func : () => void) { if (typeof __in_napa === 'undefined') { // In node. setImmediate(func); } else { // In napa workers. func(); } } /// Zone consists of Napa isolates. export class ZoneImpl implements zone.Zone { private _nativeZone: any; constructor(nativeZone: any) { this._nativeZone = nativeZone; } public get id(): string { return this._nativeZone.getId(); } public toJSON(): any { return { id: this.id, type: this.id === 'node'? 'node': 'napa' }; } public broadcast(arg1: any, arg2?: any) : Promise { let spec: FunctionSpec = this.createBroadcastRequest(arg1, arg2); return new Promise((resolve, reject) => { this._nativeZone.broadcast(spec, (result: any) => { runImmediately(() => { if (result.code === 0) { resolve(); } else { reject(result.errorMessage); } }); }); }); } public broadcastSync(arg1: any, arg2?: any) : void { let spec: FunctionSpec = this.createBroadcastRequest(arg1, arg2); let result = this._nativeZone.broadcastSync(spec); if (result.code !== 0) { throw new Error(result.errorMessage); } } public execute(arg1: any, arg2?: any, arg3?: any, arg4?: any) : Promise { let spec : FunctionSpec = this.createExecuteRequest(arg1, arg2, arg3, arg4); return new Promise((resolve, reject) => { this._nativeZone.execute(spec, (result: any) => { runImmediately(() => { if (result.code === 0) { resolve(new Result( result.returnValue, transport.createTransportContext(true, result.contextHandle))); } else { reject(result.errorMessage); } }) }); }); } private createBroadcastRequest(arg1: any, arg2?: any) : FunctionSpec { if (typeof arg1 === "function") { // broadcast with function if (arg1.origin == null) { // We get caller stack at index 2. // -> broadcast -> createBroadcastRequest // 2 1 0 arg1.origin = v8.currentStack(3)[2].getFileName(); } return { module: "__function", function: transport.saveFunction(arg1), arguments: (arg2 == null ? [] : (>arg2).map(arg => transport.marshall(arg, null))), options: zone.DEFAULT_CALL_OPTIONS, transportContext: null }; } else { // broadcast with source return { module: "", function: "eval", arguments: [JSON.stringify(arg1)], options: zone.DEFAULT_CALL_OPTIONS, transportContext: null }; } } private createExecuteRequest(arg1: any, arg2: any, arg3?: any, arg4?: any) : FunctionSpec { let moduleName: string = null; let functionName: string = null; let args: any[] = null; let options: zone.CallOptions = undefined; if (typeof arg1 === 'function') { moduleName = "__function"; if (arg1.origin == null) { // We get caller stack at index 2. // -> execute -> createExecuteRequest // 2 1 0 arg1.origin = v8.currentStack(3)[2].getFileName(); } functionName = transport.saveFunction(arg1); args = arg2; options = arg3; } else { moduleName = arg1; // If module name is relative path, try to deduce from call site. if (moduleName != null && moduleName.length != 0 && !path.isAbsolute(moduleName)) { moduleName = path.resolve( path.dirname(v8.currentStack(3)[2].getFileName()), moduleName); } functionName = arg2; args = arg3; options = arg4; } if (args == null) { args = []; } // Create a non-owning transport context which will be passed to execute call. let transportContext: transport.TransportContext = transport.createTransportContext(false); return { module: moduleName, function: functionName, arguments: (>args).map(arg => transport.marshall(arg, transportContext)), options: options != null? options: zone.DEFAULT_CALL_OPTIONS, transportContext: transportContext }; } } ================================================ FILE: lib/zone/zone.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as transport from "../transport"; /// Describes the available settings for customizing a zone. export interface ZoneSettings { /// The number of workers that will serve zone requests. workers?: number; } /// Default ZoneSettings export let DEFAULT_SETTINGS: ZoneSettings = { /// Set default workers to 2. workers: 2 }; /// Represent option to transport arguments in zone.execute. export enum TransportOption { /// transport.marshall/unmarshall will be done by `napajs` automatically. /// This is the most common way, but may not be performance optimal with objects /// that will be shared in multiple zone.execute. /// AUTO, /// transport.marshall/unmarshall will be done by user manually. MANUAL, } /// Represent the options of calling a function. export interface CallOptions { /// Timeout in milliseconds. By default set to 0 if timeout is not needed. timeout?: number, /// Transport option on passing arguments. By default set to TransportOption.AUTO transport?: TransportOption } /// Default execution options. export let DEFAULT_CALL_OPTIONS: CallOptions = { /// No timeout. timeout: 0, /// Set argument transport option to automatic. transport: TransportOption.AUTO } /// Represent the result of an execute call. export interface Result { /// The unmarshalled result value. readonly value : any; /// A marshalled result. readonly payload : string; /// Transport context carries additional information needed to unmarshall. readonly transportContext : transport.TransportContext; } /// /// Interface for Zone (for both Napa zone and Node zone) /// A `zone` consists of one or multiple JavaScript threads, we name each thread `worker`. /// Workers within a zone are symmetric, which means execute on any worker from the zone should return the same result, /// and the internal state of every worker should be the same from long lasting point of view. /// /// There are 2 operations, designed to reinforce the symmetry of workers: /// 1) Broadcast - run code that changes worker state on all workers, returning a promise for pending operation. /// Through the promise, we can only know if operation succeed or failed. Usually we use `broadcast` to bootstrap /// application, pre-cache objects, or change application settings. /// 2) Execute - run code that doesn't change worker state on an arbitrary worker, returning a promise of getting the result. /// Execute is designed for doing the real work. /// /// Zone operations are on a basis of first-come-first-serve, while `broadcast` takes higher priority over `execute`. /// export interface Zone { /// The zone id. readonly id: string; /// Compiles and run the provided source code on all zone workers. /// A valid javascript source code. /// A promise which is resolved when broadcast completes, and rejected when failed. /// /// Broadcast is designed for the purpose of bootstrapping/changing internal state on all workers. /// Function broadcast returns a promise of void, telling whether the operation succeeded or failed. /// Promise will be rejected on failure from any worker, though most likely all workers will fail if one fails. /// broadcast(source: string) : Promise; /// Run the function on all workers with the given arguments. /// The JS function. /// The arguments that will pass to the function. /// A promise which is resolved when broadcast completes and rejected when failed. /// /// Broadcast is designed for the purpose of bootstrapping/changing internal state on all workers. /// Function broadcast returns a promise of void, telling whether the operation succeeded or failed. /// Promise will be rejected on failure from any worker, though most likely all workers will fail if one fails. /// broadcast(func: (...args: any[]) => void | Promise, args?: any[]) : Promise; /// Compiles and run the provided source code on all zone workers synchronously. /// A valid javascript source code. /// /// Broadcast is designed for the purpose of bootstrapping/changing internal state on all workers. /// Function broadcastSync wait for all workers to complete. /// An exception will be thrown when any worker fails. /// broadcastSync(source: string) : void; /// Run the function on all workers with the given arguments. /// The JS function. /// The arguments that will pass to the function. /// A promise which is resolved when broadcast completes and rejected when failed. /// /// Broadcast is designed for the purpose of bootstrapping/changing internal state on all workers. /// Function broadcast returns a promise of void, telling whether the operation succeeded or failed. /// Promise will be rejected on failure from any worker, though most likely all workers will fail if one fails. /// broadcastSync(func: (...args: any[]) => void | Promise, args?: any[]) : void; /// Executes the function on one of the zone workers. /// The module name that contains the function to execute. /// The function name to execute. /// The arguments that will pass to the function. /// Call options, defaults to DEFAULT_CALL_OPTIONS. /// A promise of result which is resolved when execute completes, and rejected when failed. execute(module: string, func: string, args?: any[], options?: CallOptions) : Promise; /// Executes the function on one of the zone workers. /// The JS function to execute. /// The arguments that will pass to the function. /// Call options, defaults to DEFAULT_CALL_OPTIONS. /// A promise of result which is resolved when execute completes, and rejected when failed. execute(func: (...args: any[]) => any, args?: any[], options?: CallOptions) : Promise; } ================================================ FILE: lib/zone.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as zone from './zone/zone'; import * as impl from './zone/zone-impl'; import * as platform from './runtime/platform'; let binding = require('./binding'); // This variable is either defined by napa runtime, or not defined (hence node runtime) declare var __in_napa: boolean; /// Creates a new zone. /// A unique id to identify the zone. /// The settings of the new zone. export function create(id: string, settings: zone.ZoneSettings = zone.DEFAULT_SETTINGS) : zone.Zone { platform.initialize(); return new impl.ZoneImpl(binding.createZone(id, settings)); } /// Returns the zone associated with the provided id. export function get(id: string) : zone.Zone { platform.initialize(); return new impl.ZoneImpl(binding.getZone(id)); } /// TODO: add function getOrCreate(id: string, settings: zone.ZoneSettings): Zone. /// Define a getter property 'current' to retrieve the current zone. export declare let current: zone.Zone; Object.defineProperty(exports, "current", { get: function () : zone.Zone { platform.initialize(); return new impl.ZoneImpl(binding.getCurrentZone()); } }); /// Define a getter property 'node' to retrieve node zone. export declare let node: zone.Zone; Object.defineProperty(exports, "node", { get: function () : zone.Zone { platform.initialize(); return new impl.ZoneImpl(binding.getZone('node')); } }); export * from './zone/zone'; ================================================ FILE: node/CMakeLists.txt ================================================ # Files to compile # Note: Do not add napa core-modules cpp files that not needed in node isolation, # like timer-wrap.cpp. file(GLOB SOURCE_FILES "addon.cpp" "node-zone-delegates.cpp" "${PROJECT_SOURCE_DIR}/src/platform/filesystem.cpp" "${PROJECT_SOURCE_DIR}/src/platform/os.cpp" "${PROJECT_SOURCE_DIR}/src/zone/call-context.cpp" "${PROJECT_SOURCE_DIR}/src/zone/call-task.cpp" "${PROJECT_SOURCE_DIR}/src/zone/eval-task.cpp" "${PROJECT_SOURCE_DIR}/src/zone/terminable-task.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/allocator-debugger-wrap.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/allocator-wrap.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/call-context-wrap.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/lock-wrap.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/metric-wrap.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/napa-binding.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/shared-ptr-wrap.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/store-wrap.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/transport-context-wrap-impl.cpp" "${PROJECT_SOURCE_DIR}/src/module/core-modules/napa/zone-wrap.cpp" ) # The addon name set(TARGET_NAME "${PROJECT_NAME}-binding") # The generated library add_library(${TARGET_NAME} SHARED ${SOURCE_FILES}) set_target_properties(${TARGET_NAME} PROPERTIES PREFIX "" SUFFIX ".node") # Rpath definitions if (APPLE) set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@loader_path") else () set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/") endif() set_target_properties(${TARGET_NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH FALSE) # Include directories target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_JS_INC} ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/module/core-modules/napa) # Compiler definitions target_compile_definitions(${TARGET_NAME} PRIVATE BUILDING_NODE_EXTENSION NAPA_BINDING_EXPORTS) # Link libraries target_link_libraries(${TARGET_NAME} PRIVATE ${PROJECT_NAME} ${CMAKE_JS_LIB}) ================================================ FILE: node/addon.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "napa-binding.h" #include "node-zone-delegates.h" #include #include #include void Initialize(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); if (args.Length() <= 0 || args[0]->IsUndefined()) { // No settings provided. napa::InitializeFromConsole(0, nullptr); } else { CHECK_ARG(isolate, args[0]->IsObject(), "first argument to initialize must be an object"); auto settingsObj = args[0]->ToObject(context).ToLocalChecked(); auto settingsMap = napa::v8_helpers::V8ObjectToMap(isolate, settingsObj); std::stringstream ss; for (const auto& kv : settingsMap) { ss << " --" << kv.first << " " << kv.second; } napa::Initialize(ss.str()); } } void Shutdown(const v8::FunctionCallbackInfo&) { napa::Shutdown(); } void InitAll(v8::Local exports, v8::Local module) { // Init node zone before initialize modules. napa::zone::NodeZone::Init(napa::node_zone::Broadcast, napa::node_zone::Execute); // Init core napa modules. napa::module::binding::Init(exports, module); // Only node addon can initialize/shutdown napa. NAPA_SET_METHOD(exports, "initialize", Initialize); NAPA_SET_METHOD(exports, "shutdown", Shutdown); } NAPA_MODULE(addon, InitAll) ================================================ FILE: node/node-zone-delegates.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "node-zone-delegates.h" #include #include #include struct AsyncContext { /// libuv request. uv_async_t work; /// Callback that will be running in Node event loop. std::function callback; }; /// Run an async work item in Node. void Run(uv_async_t* work) { auto context = static_cast(work->data); context->callback(); uv_close(reinterpret_cast(work), [](auto work) { auto context = static_cast(work->data); delete context; }); } /// Schedule a function in Node event loop. void ScheduleInNode(std::function callback) { auto context = new AsyncContext(); context->work.data = context; context->callback = std::move(callback); uv_async_init(uv_default_loop(), &context->work, Run); uv_async_send(&context->work); } void napa::node_zone::Broadcast(const napa::FunctionSpec& spec, napa::BroadcastCallback callback) { auto requestContext = std::make_shared(spec, callback); ScheduleInNode([requestContext = std::move(requestContext)]() { napa::zone::CallTask task(std::move(requestContext)); task.Execute(); }); } void napa::node_zone::Execute(const napa::FunctionSpec& spec, napa::ExecuteCallback callback) { auto requestContext = std::make_shared(spec, callback); ScheduleInNode([requestContext = std::move(requestContext)]() { napa::zone::CallTask task(std::move(requestContext)); task.Execute(); }); } ================================================ FILE: node/node-zone-delegates.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace node_zone { /// Broadcast to Node zone. void Broadcast(const napa::FunctionSpec& spec, napa::BroadcastCallback callback); /// Execute in Node zone. void Execute(const napa::FunctionSpec& spec, napa::ExecuteCallback callback); } } ================================================ FILE: package.json ================================================ { "name": "napajs", "description": "Napa.js is a multi-threaded JavaScript runtime built on V8", "version": "0.2.3", "author": "napajs", "main": "./lib/index.js", "types": "./types/index.d.ts", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/Microsoft/napajs.git" }, "binary": { "module_name": "napa-binding", "module_path": "./bin/", "host": "https://github.com/Microsoft/napajs/releases/download/", "remote_path": "{version}" }, "dependencies": { "node-pre-gyp": "^0.6.36", "npmlog": "^4.1.2", "semver": "^5.5.0" }, "devDependencies": { "cmake-js": "^3.7.3", "node-pre-gyp-github": "^1.3.1", "mocha": "^3.5.0", "typescript": "^2.4.2", "@types/node": "^8.0.22", "@types/mocha": "^2.2.41", "markdown-table": "^1.1.1" }, "scripts": { "benchmark": "node benchmark/bench.js", "install": "node scripts/install.js", "prepare": "tsc -p lib && tsc -p test && tsc -p benchmark", "test": "mocha test -g \"^((?!napajs/timers).)*$\" --recursive && mocha test -g \"^napajs/timers\"", "rebuild": "cmake-js rebuild && tsc -p lib", "rebuildd": "cmake-js rebuild --debug && tsc -p lib", "retest": "cmake-js rebuild -d test/module/addon && tsc -p test && mocha test --recursive", "reunittest": "cmake-js rebuild -d unittest && node unittest/run.js", "unittest": "cmake-js compile -d unittest && node unittest/run.js" } } ================================================ FILE: scripts/clean.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. "use strict"; var fs = require('fs'); var path = require("path"); exports.clean = function() { console.log('Cleaning up build files.'); // Remove build folder var buildDir = path.join(__dirname, "../build"); console.log(`Removing ${buildDir}.`); removeDirectory(buildDir); // Remove bin folder var binDir = path.join(__dirname, "../bin"); console.log(`Removing ${binDir}.`); removeDirectory(binDir); } // Helper function for removing all files from a directory. function removeDirectory(dirPath) { if (fs.existsSync(dirPath)) { fs.readdirSync(dirPath).forEach((file, index) => { var currentPath = dirPath + "/" + file; if (fs.lstatSync(currentPath).isDirectory()) { // Recursive call removeDirectory(currentPath); } else { // delete a file fs.unlinkSync(currentPath); } }); // Remove the empty directory. fs.rmdirSync(dirPath); } } ================================================ FILE: scripts/embedded.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. "use strict"; var childProcess = require('child_process'); var os = require("os"); var path = require("path"); exports.build = function (buildType) { var napaRoot = path.join(__dirname, ".."); var nodeVersion = "v6.10.3"; // Stable node version that can build as a library. var nodeCloneRoot = path.join(napaRoot, "build/node-" + nodeVersion); var archOption = ""; console.log("\x1b[1m\x1b[32m", `Building napa in embed mode (${buildType}) based on Node.JS ${nodeVersion}`,'\x1b[0m'); // Refer to https://github.com/nodejs/node/blob/master/BUILDING.md#windows-1 for prerequisites. childProcess.execSync(`git clone --branch ${nodeVersion} https://github.com/nodejs/node.git ${nodeCloneRoot}`, { stdio: 'inherit' }); if (os.platform() === "win32") { console.log("\x1b[1m\x1b[32m", "Building Node.JS as dll.",'\x1b[0m'); // Build node shared library to extract intermediate v8 static libraries to build napa.dll. childProcess.execSync(`vcbuild.bat x64 ${buildType} nosign dll`, { cwd: nodeCloneRoot, stdio: 'inherit' }); archOption = " -A x64 "; } else if (os.platform() === 'linux') { console.log("\x1b[1m\x1b[32m", "Building Node.JS.",'\x1b[0m'); // Modify configure to add '-fPIC' cflags. childProcess.execSync("sed -i 's/'\\'cflags\\':\\ \\\\[]'/'\\'cflags\\':\\ [\\'\\ -fPIC\\ \\']'/g' configure", { cwd: nodeCloneRoot, stdio: 'inherit' }); // Build node to extract intermediate v8 static libraries to build napa shared library. childProcess.execSync(`./configure && make -j4`, { cwd: nodeCloneRoot, stdio: 'inherit' }); } else { // TODO (asib): support other platforms console.log("\x1b[1m\x1b[32m", "Napa build solution for embedded mode is not provided for ", os.platform(),'\x1b[0m'); return; } // Create platform specific build files using cmake. console.log("\x1b[1m\x1b[32m", "Running cmake to generate build files.",'\x1b[0m'); childProcess.execSync(`cmake ${archOption} -DNODE_ROOT=${nodeCloneRoot} -DCMAKE_BUILD_TYPE=${buildType} ..`, { cwd: path.join(napaRoot, "build"), stdio: 'inherit' }); // Build. console.log("\x1b[1m\x1b[32m", "Building napa.",'\x1b[0m'); childProcess.execSync(`cmake --build . --config ${buildType}`, { cwd: path.join(napaRoot, "build"), stdio: 'inherit' }); // Install napa dependencies and compile typescript files.. console.log("\x1b[1m\x1b[32m", "Running npm install to compile typescript files.",'\x1b[0m'); childProcess.execSync("npm install --no-fetch --no-build", { cwd: napaRoot, stdio: 'inherit' }); } ================================================ FILE: scripts/install.js ================================================ #!/usr/bin/env node var log = require('npmlog'); var fileExistsSync = require('fs').existsSync; var path = require('path'); var execSync = require('child_process').execSync; // Default command var fetchCommand = 'node-pre-gyp install --fallback-to-build=false'; var buildCommand = 'cmake-js compile'; // ==== Determine install options ==== // Skip the fetch stage. '--no-fetch', or '--fetch=false' var skipFetch = process.env.hasOwnProperty('npm_config_fetch') && !process.env['npm_config_fetch']; // Skip the build stage. '--no-build', or '--build=false' var skipBuild = process.env.hasOwnProperty('npm_config_build') && !process.env['npm_config_build']; // Skip the prepare stage. '--no-prepare', or '--prepare=false' var skipPrepare = process.env.hasOwnProperty('npm_config_prepare') && !process.env['npm_config_prepare']; // Use debug build if (process.env.hasOwnProperty('npm_config_debug') && process.env['npm_config_debug']) { buildCommand += " --debug"; } log.info('NAPA_INSTALL', 'installing napajs...'); var errorCode = 0; // ==== Try to fetch the pre-build binaries ==== if (!skipFetch) { try { log.info('NAPA_INSTALL', 'downloading the pre-build binaries...'); execSync(fetchCommand, { 'stdio': 'inherit' }); log.info('NAPA_INSTALL', 'completed successfully by download.'); skipBuild = true; } catch (e) { errorCode = e.status; log.warn('NAPA_INSTALL', 'failed to download the pre-build binaries.'); if (!skipBuild) { log.warn('NAPA_INSTALL', 'will fallback to build stage.'); } } } // ==== Try to build from sources ==== if (!skipBuild) { errorCode = 0; try { log.info('NAPA_INSTALL', 'building from sources...'); execSync(buildCommand, { 'stdio': 'inherit' }); log.info('NAPA_INSTALL', 'completed successfully by build.'); } catch (e) { errorCode = e.status; log.warn('NAPA_INSTALL', 'failed to build from sources.'); } } // ==== Running "npm run prepare" explicitly ==== // // NOTE: Napa.js has the "prepare" script, which is supposed to run by NPM // after a "npm install" command without parameters. // However, NPM below 4.x does not recognize script "prepare". // We have to run it explicitly in this case. if (!skipPrepare && errorCode == 0) { var npmVersion = execSync('npm --version').toString().trim(); log.info('NAPA_INSTALL', `current NPM version=${npmVersion}.`); var npmMajorVersion = npmVersion.split('.')[0]; var npmMajorVersionNumber = parseInt(npmMajorVersion); if (npmMajorVersionNumber < 4) { log.info('NAPA_INSTALL', 'NPM below 4.x does not recognize script "prepare". We need to run it explicitly.'); // Skip this step if we already have the TypeScripts compiled. var mainFilePath = path.join(__dirname, '..', process.env['npm_package_main']); if (fileExistsSync(mainFilePath) ) { log.info('NAPA_INSTALL', 'already have compiled typescripts. skip running "npm run prepare".'); } else { log.info('NAPA_INSTALL', 'running "npm run prepare"...'); try { execSync('npm run prepare', { 'stdio': 'inherit' }); } catch (e) { errorCode = e.status; } } } } if (errorCode != 0) { log.error('NAPA_INSTALL', 'failed to install napajs.'); } process.exit(errorCode); ================================================ FILE: scripts/paths.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. let path = require('path'); Object.defineProperty(exports, 'root', { get: function() { let rootPath = path.resolve(__dirname, '..'); // Output the path to stdout for cmake/gyp commands. process.stdout.write(rootPath); return rootPath; } }); Object.defineProperty(exports, 'lib', { get: function() { let libPath = path.resolve(__dirname, '../bin/' + getLibraryName('napa')); // Output the path to stdout for cmake/gyp commands. process.stdout.write(libPath); return libPath; } }); Object.defineProperty(exports, 'inc', { get: function() { let incPath = path.resolve(__dirname, '../inc'); // Output the path to stdout for cmake/gyp commands. process.stdout.write(incPath); return incPath; } }); // Resolve library name according to platform type. function getLibraryName(originalName) { if (process.platform === "win32") { return originalName + '.lib'; } else if (process.platform === 'darwin') { return 'lib' + originalName + '.dylib'; } else if (process.platform === 'linux') { return 'lib' + originalName + '.so'; } else { throw new Error( 'Failed to resolve the library name of "' + originalName + '" because your platform type "' + process.platform + '"is not supported by require(\'napajs/build\').paths'); } } ================================================ FILE: src/CMakeLists.txt ================================================ set(CMAKE_CXX_FLAGS_ORG ${CMAKE_CXX_FLAGS}) # Use -fno-rtti and -fPIC to build v8-extensions as a static library for compatibility with node and v8. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fPIC") add_subdirectory(v8-extensions) # Restore CMAKE_CXX_FLAGS from CMAKE_CXX_FLAGS_ORG. set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS_ORG}) # Files to compile file(GLOB SOURCE_FILES_0 ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) file(GLOB_RECURSE SOURCE_FILES_1 ${CMAKE_CURRENT_SOURCE_DIR}/api/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/memory/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/module/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platform/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/providers/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/settings/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/store/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/zone/*.cpp ) set(SOURCE_FILES ${SOURCE_FILES_0} ${SOURCE_FILES_1}) # The target name set(TARGET_NAME ${PROJECT_NAME}) # The generated library add_library(${TARGET_NAME} SHARED ${SOURCE_FILES}) # Include directories target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/third-party PUBLIC ${PROJECT_SOURCE_DIR}/inc) # Compiler definitions target_compile_definitions(${TARGET_NAME} PRIVATE NAPA_EXPORTS NAPA_BINDING_EXPORTS BUILDING_NAPA_EXTENSION) # Link libraries target_link_libraries(${TARGET_NAME} PRIVATE "v8-extensions") if(CMAKE_JS_VERSION) # Building Napa as an npm package for node usage (using exported v8 from node.exe) target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_JS_INC}) target_link_libraries(${TARGET_NAME} PRIVATE ${CMAKE_JS_LIB}) # Using the V8 functions exported from node.exe target_compile_definitions(${TARGET_NAME} PRIVATE USING_V8_SHARED) else() # Building Napa for embed scenarios (static linking with v8) if (NOT DEFINED NODE_ROOT) message(FATAL_ERROR "NODE_ROOT must be set to the node sources root directory") endif() set(NODE_BUILD_TYPE "Release") if ((DEFINED CMAKE_BUILD_TYPE) AND (CMAKE_BUILD_TYPE STREQUAL "debug")) set(NODE_BUILD_TYPE "Debug") endif() # V8 header files target_include_directories(${TARGET_NAME} PRIVATE ${NODE_ROOT}/deps/v8/include) if(WIN32) find_library(V8_BASE_0_LIBRARY NAMES v8_base_0 PATHS ${NODE_ROOT}/build/${NODE_BUILD_TYPE}/lib) find_library(V8_BASE_1_LIBRARY NAMES v8_base_1 PATHS ${NODE_ROOT}/build/${NODE_BUILD_TYPE}/lib) find_library(V8_BASE_2_LIBRARY NAMES v8_base_2 PATHS ${NODE_ROOT}/build/${NODE_BUILD_TYPE}/lib) find_library(V8_BASE_3_LIBRARY NAMES v8_base_3 PATHS ${NODE_ROOT}/build/${NODE_BUILD_TYPE}/lib) find_library(V8_LIBBASE_LIBRARY NAMES v8_libbase PATHS ${NODE_ROOT}/build/${NODE_BUILD_TYPE}/lib) find_library(V8_LIBPLATFORM_LIBRARY NAMES v8_libplatform PATHS ${NODE_ROOT}/build/${NODE_BUILD_TYPE}/lib) find_library(V8_NOSNAPSHOT_LIBRARY NAMES v8_nosnapshot PATHS ${NODE_ROOT}/build/${NODE_BUILD_TYPE}/lib) find_library(ICUI18N_LIBRARY NAMES icui18n PATHS ${NODE_ROOT}/${NODE_BUILD_TYPE}/lib) find_library(ICUSTUBDATA_LIBRARY NAMES icustubdata PATHS ${NODE_ROOT}/${NODE_BUILD_TYPE}/lib) find_library(ICUUCX_LIBRARY NAMES icuucx PATHS ${NODE_ROOT}/${NODE_BUILD_TYPE}/lib) set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/LTCG") # V8 static libraries target_link_libraries(${TARGET_NAME} PRIVATE ${V8_BASE_0_LIBRARY} ${V8_BASE_1_LIBRARY} ${V8_BASE_2_LIBRARY} ${V8_BASE_3_LIBRARY} ${V8_LIBBASE_LIBRARY} ${V8_LIBPLATFORM_LIBRARY} ${V8_NOSNAPSHOT_LIBRARY} ${ICUI18N_LIBRARY} ${ICUSTUBDATA_LIBRARY} ${ICUUCX_LIBRARY}) elseif("${CMAKE_SYSTEM}" MATCHES "Linux") find_library(V8_BASE_LIBRARY NAMES v8_base PATHS ${NODE_ROOT}/out/${NODE_BUILD_TYPE}/obj.target/deps/v8/tools/gyp) find_library(V8_LIBBASE_LIBRARY NAMES v8_libbase PATHS ${NODE_ROOT}/out/${NODE_BUILD_TYPE}/obj.target/deps/v8/tools/gyp) find_library(V8_LIBPLATFORM_LIBRARY NAMES v8_libplatform PATHS ${NODE_ROOT}/out/${NODE_BUILD_TYPE}/obj.target/deps/v8/tools/gyp) find_library(V8_NOSNAPSHOT_LIBRARY NAMES v8_nosnapshot PATHS ${NODE_ROOT}/out/${NODE_BUILD_TYPE}/obj.target/deps/v8/tools/gyp) find_library(ICUI18N_LIBRARY NAMES icui18n PATHS ${NODE_ROOT}/out/${NODE_BUILD_TYPE}/obj.target/tools/icu) find_library(ICUSTUBDATA_LIBRARY NAMES icustubdata PATHS ${NODE_ROOT}/out/${NODE_BUILD_TYPE}/obj.target/tools/icu) find_library(ICUUCX_LIBRARY NAMES icuucx PATHS ${NODE_ROOT}/out/${NODE_BUILD_TYPE}/obj.target/tools/icu) # V8 static libraries target_link_libraries(${TARGET_NAME} PRIVATE ${V8_BASE_LIBRARY} ${V8_LIBBASE_LIBRARY} ${V8_LIBPLATFORM_LIBRARY} ${V8_NOSNAPSHOT_LIBRARY} ${ICUI18N_LIBRARY} ${ICUSTUBDATA_LIBRARY} ${ICUUCX_LIBRARY}) else() message(FATAL_ERROR "Node build solution for embed mode is not provided for ${CMAKE_SYSTEM}") endif() endif() if(WIN32) target_link_libraries(${TARGET_NAME} PRIVATE winmm.lib) endif() ================================================ FILE: src/api/capi.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace napa; static std::atomic _initialized(false); static settings::PlatformSettings _platformSettings; /// A simple wrapper around Zone for managing lifetime using shared_ptr. struct napa_zone { std::string id; std::shared_ptr zone; }; napa_zone_handle napa_zone_create(napa_string_ref id) { NAPA_ASSERT(_initialized, "Napa wasn't initialized"); // The actual zone is created upon initialization. return new napa_zone { NAPA_STRING_REF_TO_STD_STRING(id), nullptr }; } napa_zone_handle napa_zone_get(napa_string_ref id) { NAPA_ASSERT(_initialized, "Napa wasn't initialized"); auto zoneId = NAPA_STRING_REF_TO_STD_STRING(id); std::shared_ptr zone; if (zoneId == "node") { zone = zone::NodeZone::Get(); } else { zone = zone::NapaZone::Get(zoneId); } if (!zone) { return nullptr; } return new napa_zone { std::move(zoneId), std::move(zone) }; } napa_zone_handle napa_zone_get_current() { NAPA_ASSERT(_initialized, "Napa platform wasn't initialized"); auto zone = reinterpret_cast(zone::WorkerContext::Get(zone::WorkerContextItem::ZONE)); if (zone == nullptr) { LOG_WARNING("Api", "Trying to get current zone from a thread that is not associated with a zone"); return nullptr; } return napa_zone_get(STD_STRING_TO_NAPA_STRING_REF(zone->GetId())); } napa_result_code napa_zone_init(napa_zone_handle handle, napa_string_ref settings) { NAPA_ASSERT(_initialized, "Napa platform wasn't initialized"); NAPA_ASSERT(handle, "Zone handle is null"); settings::ZoneSettings zoneSettings; if (!napa::settings::ParseFromString(NAPA_STRING_REF_TO_STD_STRING(settings), zoneSettings)) { NAPA_DEBUG("Api", "Failed to parse zone settings: %s", settings.data); return NAPA_RESULT_SETTINGS_PARSER_ERROR; } zoneSettings.id = handle->id; // Create the actual zone. handle->zone = zone::NapaZone::Create(zoneSettings); if (handle->zone == nullptr) { NAPA_DEBUG("Api", "Failed to create Napa zone '%s' with settings: %s", handle->id.c_str(), settings.data); return NAPA_RESULT_ZONE_INIT_ERROR; } NAPA_DEBUG("Api", "Napa zone '%s' created with settings: %s", handle->id.c_str(), settings.data); return NAPA_RESULT_SUCCESS; } napa_result_code napa_zone_release(napa_zone_handle handle) { NAPA_ASSERT(_initialized, "Napa platform wasn't initialized"); NAPA_ASSERT(handle, "Zone handle is null"); handle->zone = nullptr; delete handle; return NAPA_RESULT_SUCCESS; } napa_string_ref napa_zone_get_id(napa_zone_handle handle) { NAPA_ASSERT(_initialized, "Napa platform wasn't initialized"); NAPA_ASSERT(handle, "Zone handle is null"); return STD_STRING_TO_NAPA_STRING_REF(handle->id); } void napa_zone_broadcast(napa_zone_handle handle, napa_zone_function_spec spec, napa_zone_broadcast_callback callback, void* context) { NAPA_ASSERT(_initialized, "Napa platform wasn't initialized"); NAPA_ASSERT(handle, "Zone handle is null"); NAPA_ASSERT(handle->zone, "Zone handle wasn't initialized"); FunctionSpec req; req.module = spec.module; req.function = spec.function; req.arguments.reserve(spec.arguments_count); for (size_t i = 0; i < spec.arguments_count; i++) { req.arguments.emplace_back(spec.arguments[i]); } req.options = spec.options; // Assume ownership of transport context req.transportContext.reset(reinterpret_cast(spec.transport_context)); handle->zone->Broadcast(req, [callback, context](Result result) { napa_zone_result res; res.code = result.code; res.error_message = STD_STRING_TO_NAPA_STRING_REF(result.errorMessage); res.return_value = STD_STRING_TO_NAPA_STRING_REF(result.returnValue); // Release ownership of transport context res.transport_context = reinterpret_cast(result.transportContext.release()); callback(res, context); }); } void napa_zone_execute(napa_zone_handle handle, napa_zone_function_spec spec, napa_zone_execute_callback callback, void* context) { NAPA_ASSERT(_initialized, "Napa platform wasn't initialized"); NAPA_ASSERT(handle, "Zone handle is null"); NAPA_ASSERT(handle->zone, "Zone handle wasn't initialized"); FunctionSpec req; req.module = spec.module; req.function = spec.function; req.arguments.reserve(spec.arguments_count); for (size_t i = 0; i < spec.arguments_count; i++) { req.arguments.emplace_back(spec.arguments[i]); } req.options = spec.options; // Assume ownership of transport context req.transportContext.reset(reinterpret_cast(spec.transport_context)); handle->zone->Execute(req, [callback, context](Result result) { napa_zone_result res; res.code = result.code; res.error_message = STD_STRING_TO_NAPA_STRING_REF(result.errorMessage); res.return_value = STD_STRING_TO_NAPA_STRING_REF(result.returnValue); // Release ownership of transport context res.transport_context = reinterpret_cast(result.transportContext.release()); callback(res, context); }); } static napa_result_code napa_initialize_common() { if (!napa::providers::Initialize(_platformSettings)) { return NAPA_RESULT_PROVIDERS_INIT_ERROR; } if (!napa::v8_common::Initialize()) { return NAPA_RESULT_V8_INIT_ERROR; } _initialized = true; NAPA_DEBUG("Api", "Napa platform initialized successfully"); return NAPA_RESULT_SUCCESS; } napa_result_code napa_initialize(napa_string_ref settings) { NAPA_ASSERT(!_initialized, "Napa platform was already initialized"); if (!napa::settings::ParseFromString(NAPA_STRING_REF_TO_STD_STRING(settings), _platformSettings)) { return NAPA_RESULT_SETTINGS_PARSER_ERROR; } return napa_initialize_common(); } napa_result_code napa_initialize_from_console(int argc, const char* argv[]) { NAPA_ASSERT(!_initialized, "Napa platform was already initialized"); if (!napa::settings::ParseFromConsole(argc, argv, _platformSettings)) { return NAPA_RESULT_SETTINGS_PARSER_ERROR; } return napa_initialize_common(); } napa_result_code napa_shutdown() { NAPA_ASSERT(_initialized, "Napa platform wasn't initialized"); napa::providers::Shutdown(); napa::v8_common::Shutdown(); LOG_INFO("Api", "Napa platform shutdown successfully"); return NAPA_RESULT_SUCCESS; } #define NAPA_RESULT_CODE_DEF(symbol, string_rep) string_rep static const char* NAPA_REPONSE_CODE_STRINGS[] = { #include "napa/result-codes.inc" }; #undef NAPA_RESULT_CODE_DEF template constexpr size_t size(T(&)[N]) { return N; } const char* napa_result_code_to_string(napa_result_code code) { NAPA_ASSERT(code >= 0, "result code out of range (%d)", code); NAPA_ASSERT(static_cast(code) < size(NAPA_REPONSE_CODE_STRINGS), "result code out of range (%d)", code); return NAPA_REPONSE_CODE_STRINGS[code]; } /////////////////////////////////////////////////////////////// /// Implementation of napa.memory C API void* napa_malloc(size_t size) { return ::malloc(size); } void napa_free(void* pointer, size_t /*size_hint*/) { ::free(pointer); } namespace { napa_allocate_callback _global_allocate = napa_malloc; napa_deallocate_callback _global_deallocate = napa_free; } // namespace void napa_allocator_set( napa_allocate_callback allocate_callback, napa_deallocate_callback deallocate_callback) { NAPA_ASSERT(allocate_callback != nullptr, "'allocate_callback' should be a valid function."); NAPA_ASSERT(deallocate_callback != nullptr, "'deallocate_callback' should be a valid function."); _global_allocate = allocate_callback; _global_deallocate = deallocate_callback; } void* napa_allocate(size_t size) { return _global_allocate(size); } void napa_deallocate(void* pointer, size_t size_hint) { _global_deallocate(pointer, size_hint); } ================================================ FILE: src/memory/built-in-allocators.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include /// C runtime allocator from napa.dll. class CrtAllocator: public napa::memory::Allocator { public: /// Allocate memory of given size. /// Requested size. /// Allocated memory. May throw if error happens. void* Allocate(size_t size) override { return ::napa_malloc(size); } /// Deallocate memory allocated from this allocator. /// Pointer to the memory. /// Hint of size to delete. 0 if not available from caller. /// None. May throw if error happens. void Deallocate(void* memory, size_t sizeHint) override { ::napa_free(memory, sizeHint); } /// Get allocator type for better debuggability. const char* GetType() const override { return "CrtAllocator"; } /// Tell if another allocator equals to this allocator. bool operator==(const Allocator& other) const override { return std::strcmp(other.GetType(), GetType()) == 0; } }; /// Allocator that uses napa_allocate and napa_deallocate. class DefaultAllocator: public napa::memory::Allocator { public: /// Allocate memory of given size. /// Requested size. /// Allocated memory. May throw if error happens. void* Allocate(size_t size) override { return ::napa_allocate(size); } /// Deallocate memory allocated from this allocator. /// Pointer to the memory. /// Hint of size to delete. 0 if not available from caller. /// None. May throw if error happens. void Deallocate(void* memory, size_t sizeHint) override { return ::napa_deallocate(memory, sizeHint); } /// Get allocator type for better debuggability. const char* GetType() const override { return "DefaultAllocator"; } /// Tell if another allocator equals to this allocator. bool operator==(const Allocator& other) const override { return std::strcmp(other.GetType(), GetType()) == 0; } }; namespace napa { namespace memory { namespace { // Never destory to ensure they live longer than all consumers. auto _crtAllocator = new CrtAllocator(); auto _defaultAllocator = new DefaultAllocator(); } Allocator& GetCrtAllocator() { return *_crtAllocator; } Allocator& GetDefaultAllocator() { return *_defaultAllocator; } } // namespace memory } // namespace napa ================================================ FILE: src/module/core-modules/core-modules.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "napa/napa-binding.h" #include "node/console.h" #include "node/file-system.h" #include "node/os.h" #include "node/path.h" #include "node/process.h" #include "node/tty-wrap.h" #define INITIALIZE_CORE_MODULE(registerer, name, built_in, init) \ do { \ napa::module::ModuleInitializer initializer = [](auto exports, auto module) { \ return reinterpret_cast(init)(exports, module); \ }; \ registerer(name, built_in, initializer); \ } while (false) // napa-binding needs to be put at bottom since it may access other core modules. // macro arguments: registerer, core module name, is built-in, core module initialization function. #define INITIALIZE_CORE_MODULES(registerer) \ INITIALIZE_CORE_MODULE(registerer, "console", true, console::Init); \ INITIALIZE_CORE_MODULE(registerer, "fs", false, file_system::Init); \ INITIALIZE_CORE_MODULE(registerer, "os", false, os::Init); \ INITIALIZE_CORE_MODULE(registerer, "path", false, path::Init); \ INITIALIZE_CORE_MODULE(registerer, "process", true, process::Init); \ INITIALIZE_CORE_MODULE(registerer, "tty_wrap", false, tty_wrap::Init); \ INITIALIZE_CORE_MODULE(registerer, "napa-binding", false, binding::Init); ================================================ FILE: src/module/core-modules/napa/allocator-debugger-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "allocator-debugger-wrap.h" #include using namespace napa::module; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(AllocatorDebuggerWrap); AllocatorDebuggerWrap::AllocatorDebuggerWrap(std::shared_ptr allocatorDebugger) { this->_object = std::move(allocatorDebugger); } void AllocatorDebuggerWrap::Init(){ auto isolate = v8::Isolate::GetCurrent(); auto constructorTemplate = v8::FunctionTemplate::New(isolate, ConstructorCallback); constructorTemplate->SetClassName(v8_helpers::MakeV8String(isolate, exportName)); constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); InitConstructorTemplate(constructorTemplate); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "allocate", AllocatorWrap::AllocateCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "deallocate", AllocatorWrap::DeallocateCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "getDebugInfo", GetDebugInfoCallback); NAPA_SET_ACCESSOR(constructorTemplate, "type", AllocatorWrap::GetTypeCallback, nullptr); auto constructor = constructorTemplate->GetFunction(); InitConstructor("", constructor); NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, constructor); } void AllocatorDebuggerWrap::ConstructorCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); JS_ENSURE(isolate, args.IsConstructCall(), "Class \"AllocatorDebuggerWrap\" allows constructor call only."); CHECK_ARG(isolate, args.Length() <= 1, "Class \"AllocatorDebuggerWrap\" requires 1 argument of \"allocator\" in constructor.'"); std::shared_ptr allocator; if (args.Length() == 0) { allocator = std::shared_ptr( &napa::memory::GetDefaultAllocator(), [](napa::memory::Allocator*){}); } else { CHECK_ARG(isolate, args[0]->IsObject(), "Argument \"allocator\" should be \"AllocatorWrap\" type.'"); auto allocatorWrap = NAPA_OBJECTWRAP::Unwrap(v8::Local::Cast(args[0])); JS_ENSURE(isolate, allocatorWrap != nullptr, "argument \"allocator\" must be of type \"AllocatorWrap\"."); allocator = allocatorWrap->Get(); } // It's deleted when its Javascript object is garbage collected by V8's GC. auto wrap = new AllocatorDebuggerWrap(NAPA_MAKE_SHARED(allocator)); wrap->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } void AllocatorDebuggerWrap::GetDebugInfoCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, thisObject->Get()->GetDebugInfo())); } ================================================ FILE: src/module/core-modules/napa/allocator-debugger-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "allocator-wrap.h" #include #include namespace napa { namespace module { /// It wraps napa::memory::AllocatorDebugger. /// Reference: napajs/lib/memory/allocator.ts#AllocatorDebugger class AllocatorDebuggerWrap: public AllocatorWrap { public: /// Init this wrap. static void Init(); /// Declare constructor in public, so we can export class constructor to JavaScript world. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); /// Exported class name. static constexpr const char* exportName = "AllocatorDebuggerWrap"; private: /// Constructor. explicit AllocatorDebuggerWrap(std::shared_ptr allocatorDebugger); /// No copy allowed. AllocatorDebuggerWrap(const AllocatorDebuggerWrap&) = delete; AllocatorDebuggerWrap& operator=(const AllocatorDebuggerWrap&) = delete; /// AllocatorDebuggerWrap.constructor static void ConstructorCallback(const v8::FunctionCallbackInfo& args); /// It implements AllocatorDebugger.debugInfo static void GetDebugInfoCallback(const v8::FunctionCallbackInfo& args); }; } } ================================================ FILE: src/module/core-modules/napa/allocator-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "allocator-wrap.h" using namespace napa::module; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(AllocatorWrap); void AllocatorWrap::Init() { auto isolate = v8::Isolate::GetCurrent(); auto constructorTemplate = v8::FunctionTemplate::New(isolate, DefaultConstructorCallback); constructorTemplate->SetClassName(v8_helpers::MakeV8String(isolate, exportName)); constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); InitConstructorTemplate(constructorTemplate); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "allocate", AllocateCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "deallocate", DeallocateCallback); NAPA_SET_ACCESSOR(constructorTemplate, "type", GetTypeCallback, nullptr); auto constructor = constructorTemplate->GetFunction(); InitConstructor("", constructor); NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, constructor); } std::shared_ptr AllocatorWrap::Get() { return ShareableWrap::Get(); } void AllocatorWrap::AllocateCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument of 'size' is required for \"allocate\"."); CHECK_ARG(isolate, args[0]->IsUint32(), "Argument \"size\" must be a unsigned integer."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto allocator = thisObject->Get(); JS_ENSURE(isolate, allocator != nullptr, "AllocatorWrap is not attached with any C++ allocator."); auto handle = v8_helpers::PtrToV8Uint32Array(isolate, allocator->Allocate(args[0]->Uint32Value())); args.GetReturnValue().Set(handle); } void AllocatorWrap::DeallocateCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 2, "2 arguments is required for \"deallocate\"."); auto result = v8_helpers::V8ValueToUintptr(isolate, args[0]); JS_ENSURE(isolate, result.second, "Unable to cast \"handle\" to pointer. Please check if it's in valid handle format."); CHECK_ARG(isolate, args[1]->IsUint32(), "Argument \"sizeHint\" must be a 32-bit unsigned integer."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto allocator = thisObject->Get(); JS_ENSURE(isolate, allocator != nullptr, "AllocatorWrap is not attached with any C++ allocator."); allocator->Deallocate(reinterpret_cast(result.first), args[1]->Uint32Value()); } void AllocatorWrap::GetTypeCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args){ auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto allocator = thisObject->Get(); args.GetReturnValue().Set( v8_helpers::MakeV8String( isolate, allocator != nullptr ? allocator->GetType() : "")); } ================================================ FILE: src/module/core-modules/napa/allocator-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace module { /// Interface for AllocatorWrap. class AllocatorWrap: public ShareableWrap { public: /// It creates a persistent constructor for AllocatorWrap instance. static void Init(); /// Get transport context. std::shared_ptr Get(); /// Exported class name. static constexpr const char* exportName = "AllocatorWrap"; /// Declare constructor in public, so we can export class constructor in JavaScript world. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); protected: /// It implements Allocator.allocate(size: number): napajs.memory.Handle static void AllocateCallback(const v8::FunctionCallbackInfo& args); /// It implements Allocator.allocate(size: number): napajs.memory.Handle static void DeallocateCallback(const v8::FunctionCallbackInfo& args); /// It implements readonly Allocator.type: string static void GetTypeCallback(v8::Local propertyName, const v8::PropertyCallbackInfo& args); }; } } ================================================ FILE: src/module/core-modules/napa/call-context-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "call-context-wrap.h" #include "transport-context-wrap-impl.h" #include using namespace napa; using namespace napa::module; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(CallContextWrap); void CallContextWrap::Init() { auto isolate = v8::Isolate::GetCurrent(); auto constructorTemplate = v8::FunctionTemplate::New(isolate, DefaultConstructorCallback); constructorTemplate->SetClassName(v8_helpers::MakeV8String(isolate, exportName)); constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); InitConstructorTemplate(constructorTemplate); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "resolve", ResolveCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "reject", RejectCallback); NAPA_SET_ACCESSOR(constructorTemplate, "finished", IsFinishedCallback, nullptr); NAPA_SET_ACCESSOR(constructorTemplate, "elapse", GetElapseCallback, nullptr); NAPA_SET_ACCESSOR(constructorTemplate, "module", GetModuleCallback, nullptr); NAPA_SET_ACCESSOR(constructorTemplate, "function", GetFunctionCallback, nullptr); NAPA_SET_ACCESSOR(constructorTemplate, "args", GetArgumentsCallback, nullptr); NAPA_SET_ACCESSOR(constructorTemplate, "transportContext", GetTransportContextCallback, nullptr); NAPA_SET_ACCESSOR(constructorTemplate, "options", GetOptionsCallback, nullptr); auto constructor = constructorTemplate->GetFunction(); InitConstructor("", constructor); NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, constructor); } v8::Local CallContextWrap::NewInstance(std::shared_ptr call) { return ShareableWrap::NewInstance(call); } zone::CallContext& CallContextWrap::GetRef() { return ShareableWrap::GetRef(); } void CallContextWrap::ResolveCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument of 'result' is required for \"resolve\"."); v8::String::Utf8Value result(args[0]); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto success = thisObject->GetRef().Resolve(std::string(*result, result.length())); JS_ENSURE(isolate, success, "Resolve call failed: Already finished."); } void CallContextWrap::RejectCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 || args.Length() == 2, "at least 1 argument of 'reason' is required for \"reject\"."); napa::ResultCode code = NAPA_RESULT_EXECUTE_FUNC_ERROR; v8::Local reason; if (args.Length() == 1) { reason = args[0]; } else { CHECK_ARG(isolate, args[0]->IsUint32(), "arg 'resultCode' should be a number type."); code = static_cast(args[0]->Uint32Value()); reason = args[1]; } v8::String::Utf8Value reasonStr(reason); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto success = thisObject->GetRef().Reject(code, std::string(*reasonStr, reasonStr.length())); JS_ENSURE(isolate, success, "Reject call failed: Already finished."); } void CallContextWrap::IsFinishedCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args){ auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); args.GetReturnValue().Set(thisObject->GetRef().IsFinished()); } void CallContextWrap::GetElapseCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args){ auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); args.GetReturnValue().Set(v8_helpers::HrtimeToV8Uint32Array(isolate, thisObject->GetRef().GetElapse().count())); } void CallContextWrap::GetModuleCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, thisObject->GetRef().GetModule())); } void CallContextWrap::GetFunctionCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, thisObject->GetRef().GetFunction())); } void CallContextWrap::GetArgumentsCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto& cppArgs = thisObject->GetRef().GetArguments(); auto jsArgs = v8::Array::New(isolate, static_cast(cppArgs.size())); for (size_t i = 0; i < cppArgs.size(); ++i) { // TODO: Switch to 2-bytes external string. (void)jsArgs->CreateDataProperty(context, static_cast(i), v8_helpers::MakeV8String(isolate, cppArgs[i])); } args.GetReturnValue().Set(jsArgs); } void CallContextWrap::GetTransportContextCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto& transportContext = thisObject->GetRef().GetTransportContext(); // Create a non-owning transport context wrap, since transport context is always owned by call context. auto wrap = TransportContextWrapImpl::NewInstance(false, &transportContext); args.GetReturnValue().Set(wrap); } void CallContextWrap::GetOptionsCallback(v8::Local /*propertyName*/, const v8::PropertyCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); // Prepare execute options. // NOTE: export necessary fields from CallContext.GetOptions to jsOptions object here. Now it's empty. auto jsOptions = v8::Object::New(isolate); args.GetReturnValue().Set(jsOptions); } ================================================ FILE: src/module/core-modules/napa/call-context-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace module { /// /// Class that wraps zone::CallContext, which enables JavaScript world to /// resolve or reject a function call. /// class CallContextWrap: public ShareableWrap { public: /// It creates a persistent constructor for CallContextWrap instance. static void Init(); /// Create a new instance of wrap associating with specific call context. static v8::Local NewInstance(std::shared_ptr call); /// Get call context. zone::CallContext& GetRef(); /// Exported class name. static constexpr const char* exportName = "CallContextWrap"; /// Declare constructor in public, so we can export class constructor in JavaScript world. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); protected: /// It implements CallContext.resolve(result: any): void static void ResolveCallback(const v8::FunctionCallbackInfo& args); /// It implements CallContext.reject(reason: string): void static void RejectCallback(const v8::FunctionCallbackInfo& args); /// It implements CallContext.finished: boolean static void IsFinishedCallback(v8::Local propertyName, const v8::PropertyCallbackInfo& args); /// It implements CallContext.module: string static void GetModuleCallback(v8::Local propertyName, const v8::PropertyCallbackInfo& args); /// It implements CallContext.function: string static void GetFunctionCallback(v8::Local propertyName, const v8::PropertyCallbackInfo& args); /// It implements CallContext.args: string[] static void GetArgumentsCallback(v8::Local propertyName, const v8::PropertyCallbackInfo& args); /// It implements CallContext.transportContext: TransportContext static void GetTransportContextCallback(v8::Local propertyName, const v8::PropertyCallbackInfo& args); /// It implements CallContext.options: CallOptions static void GetOptionsCallback(v8::Local propertyName, const v8::PropertyCallbackInfo& args); /// It implements CallContext.elapse: [number, number] (precision in nano-second) static void GetElapseCallback(v8::Local propertyName, const v8::PropertyCallbackInfo& args); }; } } ================================================ FILE: src/module/core-modules/napa/lock-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "lock-wrap.h" #include #include #include #include using namespace napa::module; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(napa::module::LockWrap); void LockWrap::Init() { auto isolate = v8::Isolate::GetCurrent(); auto constructorTemplate = v8::FunctionTemplate::New(isolate, DefaultConstructorCallback); constructorTemplate->SetClassName(v8_helpers::MakeV8String(isolate, exportName)); constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); InitConstructorTemplate(constructorTemplate); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "guardSync", GuardSyncCallback); auto constructor = constructorTemplate->GetFunction(); InitConstructor("", constructor); NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, constructor); } v8::Local LockWrap::NewInstance() { return binding::CreateShareableWrap(std::make_shared(), exportName); } void LockWrap::GuardSyncCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() >= 1, "1 argument is required for calling 'guardSync'."); CHECK_ARG(isolate, args[0]->IsFunction(), "Argument \"func\" shall be 'Function' type."); CHECK_ARG(isolate, args.Length() < 2 || args[1]->IsArray(), "Argument \"params\" shall be a valid array."); auto context = isolate->GetCurrentContext(); auto holder = args.Holder(); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); v8::TryCatch tryCatch(isolate); try { auto mutex = thisObject->Get(); std::lock_guard guard(*mutex); std::vector> params; if (args.Length() >= 2) { auto paramsArray = v8::Local::Cast(args[1]); int paramsLength = paramsArray->Length(); params.reserve(paramsLength); for (int i = 0; i < paramsLength; i++) { auto item = paramsArray->Get(context, i).ToLocalChecked(); params.emplace_back(item); } } auto result = v8::Local::Cast(args[0])->Call( context, holder, static_cast(params.size()), params.empty() ? nullptr : params.data()); if (result.IsEmpty() || tryCatch.HasCaught()) { tryCatch.ReThrow(); } else { args.GetReturnValue().Set(result.ToLocalChecked()); } } catch (const std::system_error& ex) { isolate->ThrowException(v8::Exception::Error(v8_helpers::MakeV8String(isolate, ex.what()))); } } ================================================ FILE: src/module/core-modules/napa/lock-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// An object wrap to expose lock APIs. class LockWrap : public ShareableWrap { public: /// Initializes the wrap. static void Init(); /// Creates a new instance of LockWrap. static v8::Local NewInstance(); /// Exported class name. static constexpr const char* exportName = "LockWrap"; /// Declare persistent constructor to create Lock Javascript wrapper instance. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); private: // LockWrap methods static void GuardSyncCallback(const v8::FunctionCallbackInfo& args); }; } } ================================================ FILE: src/module/core-modules/napa/metric-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "metric-wrap.h" using namespace napa::module; using namespace napa::v8_helpers; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(MetricWrap); void MetricWrap::Init() { auto isolate = v8::Isolate::GetCurrent(); // Prepare constructor template. auto functionTemplate = v8::FunctionTemplate::New(isolate, ConstructorCallback); functionTemplate->SetClassName(MakeV8String(isolate, _exportName)); functionTemplate->InstanceTemplate()->SetInternalFieldCount(1); // Prototypes. NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "set", Set); NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "increment", Increment); NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "decrement", Decrement); // Set persistent constructor into V8. NAPA_SET_PERSISTENT_CONSTRUCTOR(_exportName, functionTemplate->GetFunction()); } MetricWrap::MetricWrap(napa::providers::Metric* metric, uint32_t dimensions) : _metric(metric), _dimensions(dimensions) { } void MetricWrap::ConstructorCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); JS_ENSURE(isolate, args.IsConstructCall(), "class \"MetricWrap\" allows constructor call only."); CHECK_ARG(isolate, args.Length() == 4, "class \"MetricWrap\" accepts exactly 4 arguments (section, name, type, dimensions)"); CHECK_ARG(isolate, args[0]->IsString(), "'section' must be a valid string"); CHECK_ARG(isolate, args[1]->IsString(), "'name' must be a valid string"); CHECK_ARG(isolate, args[2]->IsUint32(), "'type' must be a uint32 type that represents the native enum"); CHECK_ARG(isolate, args[3]->IsArray(), "'dimensions' must be a valid array"); auto section = V8ValueTo(args[0]); auto name = V8ValueTo(args[1]); auto type = static_cast(args[2]->Uint32Value()); // Holds te dimensions strings on the stack for the GetMetric call. auto dimensionsStringsHolder = V8ArrayToVector(isolate, v8::Local::Cast(args[3])); std::vector dimensions; dimensions.reserve(dimensionsStringsHolder.size()); for (const auto& dimension : dimensionsStringsHolder) { dimensions.emplace_back(dimension.Data()); } auto& metricProvider = napa::providers::GetMetricProvider(); auto metric = metricProvider.GetMetric(section.Data(), name.Data(), type, dimensions.size(), dimensions.data()); auto wrap = new MetricWrap(metric, static_cast(dimensions.size())); wrap->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } void MetricWrap::Set(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args[0]->IsUint32(), "'value' argument must be a valid Uint32"); auto value = args[0]->Uint32Value(); InvokeWithDimensions(args, 1, [value](napa::providers::Metric* metric, std::vector& dimensions) { metric->Set(value, dimensions.size(), dimensions.data()); }); } void MetricWrap::Increment(const v8::FunctionCallbackInfo& args) { InvokeWithDimensions(args, 0, [](napa::providers::Metric* metric, std::vector& dimensions) { metric->Increment(1, dimensions.size(), dimensions.data()); }); } void MetricWrap::Decrement(const v8::FunctionCallbackInfo& args) { InvokeWithDimensions(args, 0, [](napa::providers::Metric* metric, std::vector& dimensions) { metric->Decrement(1, dimensions.size(), dimensions.data()); }); } template void MetricWrap::InvokeWithDimensions(const v8::FunctionCallbackInfo& args, uint32_t index, Func&& func) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto dimensionsArg = args[index]; CHECK_ARG(isolate, dimensionsArg->IsArray() || dimensionsArg->IsUndefined(), "'dimensions' must be an array or undefined"); auto wrap = NAPA_OBJECTWRAP::Unwrap(args.Holder()); // Holds the dimensions strings on the stack for so it exists during the call to func. std::vector dimensionsStringsHolder; std::vector dimensions; if (dimensionsArg->IsArray() && wrap->_dimensions > 0) { auto arr = v8::Local::Cast(dimensionsArg); JS_ENSURE( isolate, wrap->_dimensions == arr->Length(), "the dimensions count does not match. expected: %d, received: %d", wrap->_dimensions, arr->Length()); dimensionsStringsHolder = napa::v8_helpers::V8ArrayToVector(isolate, arr); dimensions.reserve(dimensionsStringsHolder.size()); for (const auto& dimension : dimensionsStringsHolder) { dimensions.emplace_back(dimension.Data()); } } else { JS_ENSURE(isolate, wrap->_dimensions == 0, "expected %s dimensions but received 0", wrap->_dimensions); } func(wrap->_metric, dimensions); } ================================================ FILE: src/module/core-modules/napa/metric-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace module { /// An object wrap to expose metric APIs. class MetricWrap : public NAPA_OBJECTWRAP { public: /// Initializes the wrap. static void Init(); /// Create a new MetricWrap instance that wraps the provided metric. static v8::Local NewInstance(napa::providers::Metric* metric, uint32_t dimensions); /// Declare persistent constructor to create Metric Javascript wrapper instance. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); private: /// The underlying metric. napa::providers::Metric* _metric; /// Exported class name. static constexpr const char* _exportName = "MetricWrap"; /// Number of dimensions this metric expects. uint32_t _dimensions; /// Constructor. MetricWrap(napa::providers::Metric* metric, uint32_t dimensions); /// MetricWrap.constructor static void ConstructorCallback(const v8::FunctionCallbackInfo& args); // MetricWrap getters static void NameGetter(v8::Local, const v8::PropertyCallbackInfo& args); static void SectionGetter(v8::Local, const v8::PropertyCallbackInfo& args); // MetricWrap methods static void Set(const v8::FunctionCallbackInfo& args); static void Increment(const v8::FunctionCallbackInfo& args); static void Decrement(const v8::FunctionCallbackInfo& args); /// /// Helper method that extracts the dimensions and metric from args and calls the func /// with these arguments. /// template static void InvokeWithDimensions(const v8::FunctionCallbackInfo& args, uint32_t index, Func&& func); }; } } ================================================ FILE: src/module/core-modules/napa/napa-binding.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "napa-binding.h" #include "allocator-debugger-wrap.h" #include "allocator-wrap.h" #include "call-context-wrap.h" #include "lock-wrap.h" #include "metric-wrap.h" #include "shared-ptr-wrap.h" #include "store-wrap.h" #include "timer-wrap.h" #include "transport-context-wrap-impl.h" #include "zone-wrap.h" #include #include #include #include #include #include #include #include #if V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER #include #endif using namespace napa; using namespace napa::module; static void RegisterBinding(v8::Local module) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto persistentModule = new v8::Persistent(isolate, module); zone::WorkerContext::Set(zone::WorkerContextItem::NAPA_BINDING, persistentModule); } v8::Local napa::module::binding::GetModule() { auto persistentModule = reinterpret_cast*>( zone::WorkerContext::Get(zone::WorkerContextItem::NAPA_BINDING)); NAPA_ASSERT(persistentModule != nullptr, "\"napajs\" must be required before napa::module::binding::GetModule() can be called from C++."); return v8::Local::New(v8::Isolate::GetCurrent(), *persistentModule); } static void CreateZone(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); CHECK_ARG(isolate, args[0]->IsString(), "first argument to createZone must be a string"); v8::String::Utf8Value zoneId(args[0]->ToString()); std::stringstream ss; if ((args.Length() > 1) && (!args[1]->IsUndefined())) { CHECK_ARG(isolate, args[1]->IsObject(), "second argument to createZone must be an object"); auto settingsObj = args[1]->ToObject(context).ToLocalChecked(); auto settingsMap = napa::v8_helpers::V8ObjectToMap(isolate, settingsObj); for (const auto& kv : settingsMap) { ss << " --" << kv.first << " " << kv.second; } } try { auto zoneProxy = std::make_unique(*zoneId, ss.str()); args.GetReturnValue().Set(ZoneWrap::NewInstance(std::move(zoneProxy))); } catch (const std::runtime_error& ex) { JS_FAIL(isolate, ex.what()); } catch (...) { JS_FAIL(isolate, "Failed to initialize zone with id ", *zoneId); } } static void GetZone(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args[0]->IsString(), "first argument to getZone must be a string"); v8::String::Utf8Value zoneId(args[0]->ToString()); try { auto zoneProxy = napa::Zone::Get(*zoneId); args.GetReturnValue().Set(ZoneWrap::NewInstance(std::move(zoneProxy))); } catch (const std::runtime_error &ex) { JS_FAIL(isolate, ex.what()); } catch (...) { JS_FAIL(isolate, "No zone exists with id ", *zoneId); } } static void GetCurrentZone(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); args.GetReturnValue().Set(ZoneWrap::NewInstance(napa::Zone::GetCurrent())); } ///////////////////////////////////////////////////////////////////// /// Store APIs static void CreateStore(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument of 'id' is required."); CHECK_ARG(isolate, args[0]->IsString(), "Argument 'id' must be string."); auto id = napa::v8_helpers::V8ValueTo(args[0]); auto store = napa::store::CreateStore(id.c_str()); JS_ENSURE(isolate, store != nullptr, "Store with id \"%s\" already exists.", id.c_str()); args.GetReturnValue().Set(StoreWrap::NewInstance(store)); } static void GetOrCreateStore(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument of 'id' is required."); CHECK_ARG(isolate, args[0]->IsString(), "Argument 'id' must be string."); auto id = napa::v8_helpers::V8ValueTo(args[0]); auto store = napa::store::GetOrCreateStore(id.c_str()); args.GetReturnValue().Set(StoreWrap::NewInstance(store)); } static void GetStore(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument of 'id' is required."); CHECK_ARG(isolate, args[0]->IsString(), "Argument 'id' must be string."); auto id = napa::v8_helpers::V8ValueTo(args[0]); auto store = napa::store::GetStore(id.c_str()); if (store != nullptr) { args.GetReturnValue().Set(StoreWrap::NewInstance(store)); } } static void GetStoreCount(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(static_cast(napa::store::GetStoreCount())); } ///////////////////////////////////////////////////////////////////// /// Sync APIs static void CreateLock(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); args.GetReturnValue().Set(LockWrap::NewInstance()); } static void GetCrtAllocator(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(binding::CreateAllocatorWrap( std::shared_ptr( &napa::memory::GetCrtAllocator(), [](napa::memory::Allocator*){}))); } static void GetDefaultAllocator(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(binding::CreateAllocatorWrap( std::shared_ptr( &napa::memory::GetDefaultAllocator(), [](napa::memory::Allocator*){}))); } static void Log(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 4, "log accepts exactly 4 arguments (level, section, traceId, message)"); CHECK_ARG(isolate, args[0]->IsUint32(), "'level' must be a uint32 type that represents the native enum"); CHECK_ARG(isolate, args[1]->IsString() || args[1]->IsUndefined(), "'section' must be a valid string or undefined"); auto level = static_cast(args[0]->Uint32Value()); napa::v8_helpers::Utf8String sectionValue; const char* section = ""; if (!args[1]->IsUndefined()) { sectionValue = napa::v8_helpers::V8ValueTo(args[1]); section = sectionValue.Length() > 0 ? sectionValue.Data() : ""; } auto& logger = napa::providers::GetLoggingProvider(); // If log is not enabled we can return early. if (!logger.IsLogEnabled(section, level)) { return; } CHECK_ARG(isolate, args[2]->IsString() || args[2]->IsUndefined(), "'traceId' must be a valid string or undefined"); CHECK_ARG(isolate, args[3]->IsString(), "'message' must be a valid string"); napa::v8_helpers::Utf8String traceIdValue; const char* traceId = ""; if (!args[2]->IsUndefined()) { traceIdValue = napa::v8_helpers::V8ValueTo(args[2]); traceId = traceIdValue.Length() > 0 ? traceIdValue.Data() : ""; } v8::String::Utf8Value message(args[3]->ToString()); // Get the first frame in user code. // The first 2 frames are part of the log.js file. auto stackFrame = v8::StackTrace::CurrentStackTrace(isolate, 3)->GetFrame(2); v8::String::Utf8Value file(stackFrame->GetScriptName()); int line = stackFrame->GetLineNumber(); logger.LogMessage(section, level, traceId, *file, line, *message); } void SerializeValue(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); #if V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER CHECK_ARG(isolate, args.Length() == 1, "1 argument is required for \"serializeValue\"."); auto serializedData = v8_extensions::Utils::SerializeValue(isolate, args[0]); if (serializedData) { args.GetReturnValue().Set(binding::CreateShareableWrap(serializedData)); } #else isolate->ThrowException(v8::Exception::TypeError(napa::v8_helpers::MakeV8String( isolate, "It requires v8 newer than 6.2.x to transport builtin types. \ If run in node mode, please make sure the node version is v9.0.0 or above."))); #endif } void DeserializeValue(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); #if V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER CHECK_ARG(isolate, args.Length() == 1, "1 argument is required for \"deserializeValue\"."); CHECK_ARG(isolate, args[0]->IsObject(), "Argument \"object\" shall be 'SharedPtrWrap' type."); auto shareableWrap = NAPA_OBJECTWRAP::Unwrap(v8::Local::Cast(args[0])); auto serializedData = shareableWrap->Get(); v8::Local value; if (v8_extensions::Utils::DeserializeValue(isolate, serializedData).ToLocal(&value)) { args.GetReturnValue().Set(value); } #else isolate->ThrowException(v8::Exception::TypeError(napa::v8_helpers::MakeV8String( isolate, "It requires v8 newer than 6.2.x to transport builtin types. \ If run in node mode, please make sure the node version is v9.0.0 or above."))); #endif } ///////////////////////////////////////////////////////////////////// /// Timers APIs, these APIs only valid in non-node isolation, i.e., /// they are not needed when building the napa_binding.node #ifdef BUILDING_NAPA_EXTENSION static void SetImmediate(const v8::FunctionCallbackInfo& args) { TimerWrap::SetImmediateCallback(args); } // Set Timeout or Set Interval static void SetTimers(const v8::FunctionCallbackInfo& args) { TimerWrap::SetTimersCallback(args); } #endif static void InitNapaOnlyBindings(v8::Local exports) { #ifdef BUILDING_NAPA_EXTENSION TimerWrap::Init(); NAPA_SET_METHOD(exports, "setImmediate", SetImmediate); NAPA_SET_METHOD(exports, "setTimers", SetTimers); #endif } void binding::Init(v8::Local exports, v8::Local module) { // Register napa binding in worker context. RegisterBinding(module); AllocatorDebuggerWrap::Init(); AllocatorWrap::Init(); CallContextWrap::Init(); LockWrap::Init(); MetricWrap::Init(); SharedPtrWrap::Init(); StoreWrap::Init(); TransportContextWrapImpl::Init(); ZoneWrap::Init(); NAPA_EXPORT_OBJECTWRAP(exports, "AllocatorDebuggerWrap", AllocatorDebuggerWrap); NAPA_EXPORT_OBJECTWRAP(exports, "AllocatorWrap", AllocatorWrap); NAPA_EXPORT_OBJECTWRAP(exports, "CallContextWrap", CallContextWrap); NAPA_EXPORT_OBJECTWRAP(exports, "LockWrap", LockWrap); NAPA_EXPORT_OBJECTWRAP(exports, "MetricWrap", MetricWrap); NAPA_EXPORT_OBJECTWRAP(exports, "SharedPtrWrap", SharedPtrWrap); NAPA_EXPORT_OBJECTWRAP(exports, "TransportContextWrap", TransportContextWrapImpl); NAPA_SET_METHOD(exports, "createZone", CreateZone); NAPA_SET_METHOD(exports, "getZone", GetZone); NAPA_SET_METHOD(exports, "getCurrentZone", GetCurrentZone); NAPA_SET_METHOD(exports, "createStore", CreateStore); NAPA_SET_METHOD(exports, "getOrCreateStore", GetOrCreateStore); NAPA_SET_METHOD(exports, "getStore", GetStore); NAPA_SET_METHOD(exports, "getStoreCount", GetStoreCount); NAPA_SET_METHOD(exports, "createLock", CreateLock); NAPA_SET_METHOD(exports, "getCrtAllocator", GetCrtAllocator); NAPA_SET_METHOD(exports, "getDefaultAllocator", GetDefaultAllocator); NAPA_SET_METHOD(exports, "log", Log); NAPA_SET_METHOD(exports, "serializeValue", SerializeValue); NAPA_SET_METHOD(exports, "deserializeValue", DeserializeValue); InitNapaOnlyBindings(exports); } ================================================ FILE: src/module/core-modules/napa/napa-binding.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace module { namespace binding { /// Initialize and export napa related functions and object wraps. void Init(v8::Local exports, v8::Local module); } } } ================================================ FILE: src/module/core-modules/napa/shared-ptr-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "shared-ptr-wrap.h" using namespace napa::module; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(napa::module::SharedPtrWrap); void SharedPtrWrap::Init() { auto isolate = v8::Isolate::GetCurrent(); auto constructorTemplate = v8::FunctionTemplate::New(isolate, DefaultConstructorCallback); constructorTemplate->SetClassName(v8_helpers::MakeV8String(isolate, SharedPtrWrap::exportName)); constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); InitConstructorTemplate(constructorTemplate); auto constructor = constructorTemplate->GetFunction(); InitConstructor("", constructor); NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, constructor); } ================================================ FILE: src/module/core-modules/napa/shared-ptr-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// It wraps C++ std::shared_ptr and allow it be shared across isolates. /// see napajs/lib/memory/shared-ptr-wrap.d.ts#SharedPtrWrap class SharedPtrWrap : public ShareableWrap { public: /// Init SharedPtrWrap. static void Init(); /// Declare constructor in public, so we can export class constructor in JavaScript world. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); /// Exported class name. static constexpr const char* exportName = "SharedPtrWrap"; }; } } ================================================ FILE: src/module/core-modules/napa/store-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "store-wrap.h" #include using namespace napa::module; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(StoreWrap); void StoreWrap::Init() { auto isolate = v8::Isolate::GetCurrent(); auto constructorTemplate = v8::FunctionTemplate::New(isolate, DefaultConstructorCallback); constructorTemplate->SetClassName(v8_helpers::MakeV8String(isolate, exportName)); constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "set", SetCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "get", GetCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "has", HasCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "delete", DeleteCallback); NAPA_SET_ACCESSOR(constructorTemplate, "id", GetIdCallback, nullptr); NAPA_SET_ACCESSOR(constructorTemplate, "size", GetSizeCallback, nullptr); NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, constructorTemplate->GetFunction()); } v8::Local StoreWrap::NewInstance(std::shared_ptr store) { auto object = napa::module::NewInstance().ToLocalChecked(); auto wrap = NAPA_OBJECTWRAP::Unwrap(object); wrap->_store = std::move(store); return object; } napa::store::Store& StoreWrap::Get() { return *_store; } void StoreWrap::SetCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 2, "2 arguments are required for \"set\"."); CHECK_ARG(isolate, args[0]->IsString(), "Argument \"key\" must be string."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto& store = thisObject->Get(); // Marshall value object into payload. napa::transport::TransportContext transportContext; auto payload = napa::transport::Marshall(args[1], &transportContext); RETURN_ON_PENDING_EXCEPTION(payload); store.Set( v8_helpers::V8ValueTo(args[0]).c_str(), std::make_shared(napa::store::Store::ValueType { v8_helpers::V8ValueTo(payload.ToLocalChecked()), std::move(transportContext) })); } void StoreWrap::GetCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument are required for \"get\"."); CHECK_ARG(isolate, args[0]->IsString(), "Argument 'key' must be string."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto& store = thisObject->Get(); // Marshall value object into payload. auto key = v8_helpers::V8ValueTo(args[0]); auto storeValue = store.Get(key.c_str()); if (storeValue != nullptr) { auto value = napa::transport::Unmarshall( v8_helpers::MakeExternalV8String(isolate, storeValue->payload), &(storeValue->transportContext)); RETURN_ON_PENDING_EXCEPTION(value); args.GetReturnValue().Set(value.ToLocalChecked()); } } void StoreWrap::HasCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument are required for \"has\"."); CHECK_ARG(isolate, args[0]->IsString(), "Argument 'key' must be string."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto& store = thisObject->Get(); args.GetReturnValue().Set(store.Has(v8_helpers::V8ValueTo(args[0]).c_str())); } void StoreWrap::DeleteCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument are required for \"delete\"."); CHECK_ARG(isolate, args[0]->IsString(), "Argument 'key' must be string."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto& store = thisObject->Get(); store.Delete(v8_helpers::V8ValueTo(args[0]).c_str()); } void StoreWrap::GetIdCallback(v8::Local, const v8::PropertyCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto& store = thisObject->Get(); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, store.GetId())); } void StoreWrap::GetSizeCallback(v8::Local, const v8::PropertyCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto& store = thisObject->Get(); args.GetReturnValue().Set(static_cast(store.Size())); } ================================================ FILE: src/module/core-modules/napa/store-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// It wraps napa::store::Store. /// Reference: napajs/lib/store.ts#Store class StoreWrap: public NAPA_OBJECTWRAP { public: /// Init this wrap. static void Init(); /// It creates an instance of StoreWrap with a napa::store::Store pointer. static v8::Local NewInstance(std::shared_ptr store); /// Get napa::store::Store from wrap. napa::store::Store& Get(); private: /// Default constructor. StoreWrap() = default; /// No copy allowed. StoreWrap(const StoreWrap&) = delete; StoreWrap& operator=(const StoreWrap&) = delete; /// It implements Store.set(key: string, value: any): void static void SetCallback(const v8::FunctionCallbackInfo& args); /// It implements Store.get(key: string): any static void GetCallback(const v8::FunctionCallbackInfo& args); /// It implements Store.has(key: string): boolean static void HasCallback(const v8::FunctionCallbackInfo& args); /// It implements Store.delete(key: string): void static void DeleteCallback(const v8::FunctionCallbackInfo& args); /// It implements Store.id static void GetIdCallback(v8::Local, const v8::PropertyCallbackInfo& args); /// It implements Store.size static void GetSizeCallback(v8::Local, const v8::PropertyCallbackInfo& args); /// Friend default constructor callback. template friend void napa::module::DefaultConstructorCallback(const v8::FunctionCallbackInfo&); template friend v8::MaybeLocal napa::module::NewInstance(int argc, v8::Local argv[]); /// Exported class name. static constexpr const char* exportName = "StoreWrap"; /// Hid constructor from public access. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); /// Store. std::shared_ptr _store; }; } } ================================================ FILE: src/module/core-modules/napa/timer-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. // See: https://groups.google.com/forum/#!topic/nodejs/onA0S01INtw #ifdef BUILDING_NODE_EXTENSION #include #endif #include #include #include #include "timer-wrap.h" #include #include #include #include #include #include using napa::zone::WorkerId; using napa::zone::WorkerContext; using napa::zone::WorkerContextItem; using napa::zone::NapaZone; using napa::zone::SchedulePhase; using v8::Array; using v8::Boolean; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Global; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::Number; using v8::Object; using v8::Persistent; using v8::String; using v8::Value; namespace napa { namespace zone { /// A task to run a C++ callback task without cross isolation. class CallbackTask : public Task { public: typedef std::function Callback; /// Constructor. /// Structure containing asynchronous work's context. CallbackTask(Callback callback) : _callback(std::move(callback)) { } /// Overrides Task.Execute to define running execution logic. virtual void Execute() { _callback(); } private: Callback _callback; }; } } using namespace napa::module; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(TimerWrap); void TimerWrap::Init() { auto isolate = Isolate::GetCurrent(); auto constructorTemplate = FunctionTemplate::New(isolate, DefaultConstructorCallback); constructorTemplate->SetClassName(v8_helpers::MakeV8String(isolate, exportName)); constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, constructorTemplate->GetFunction()); } Local TimerWrap::NewInstance(std::shared_ptr timer) { auto object = napa::module::NewInstance().ToLocalChecked(); auto wrap = NAPA_OBJECTWRAP::Unwrap(object); wrap->_timer = std::move(timer); return object; } void TimerWrap::Reset() { _timer.reset(); } napa::zone::Timer& TimerWrap::Get() { return *_timer; } // This is created as SetWeak(void) is not exists in v8 used in NodeJS 6. static void EmptyWeakCallback(const v8::WeakCallbackInfo& data) { } std::shared_ptr buildTimeoutTask( std::shared_ptr> sharedTimeout, std::shared_ptr> sharedContext) { return std::make_shared( [sharedTimeout, sharedContext]() { auto isolate = Isolate::GetCurrent(); HandleScope handleScope(isolate); auto context = Local::New(isolate, *sharedContext); Context::Scope contextScope(context); auto timeout = Local::New(isolate, *sharedTimeout); bool needDestroy = true; Local active = Local::Cast(timeout->Get(String::NewFromUtf8(isolate, "_active"))); if (active->Value()) { Local cb = Local::Cast(timeout->Get(String::NewFromUtf8(isolate, "_callback"))); Local args = Local::Cast(timeout->Get(String::NewFromUtf8(isolate, "_args"))); std::vector> parameters; parameters.reserve(args->Length()); for (int i = 0; i < static_cast(args->Length()); ++i) { Local v = args->Get(context, i).ToLocalChecked(); parameters.emplace_back(v); } cb->Call(context, context->Global(), static_cast(parameters.size()), parameters.data()); Local interval = Local::Cast(timeout->Get(String::NewFromUtf8(isolate, "_repeat"))); bool isInterval = (interval->Value() >= 1); if (isInterval) { auto jsTimer = NAPA_OBJECTWRAP::Unwrap( Local::Cast(timeout->Get(String::NewFromUtf8(isolate, "_timer")))); //Re-arm the interval timer in napa's timer schedule thread. jsTimer->Get().Start(); // If not interval timer, global v8 handle for Timeout and Context will be SetWeak. // Otherwise keep holding the hanle as they will be used some time later. needDestroy = false; } } if (needDestroy) { if (!sharedTimeout->IsEmpty()) { sharedTimeout->SetWeak((int*)nullptr, EmptyWeakCallback, v8::WeakCallbackType::kParameter); sharedTimeout->Reset(); } if (!sharedContext->IsEmpty()) { sharedContext->SetWeak((int*)nullptr, EmptyWeakCallback, v8::WeakCallbackType::kParameter); sharedContext->Reset(); } } } ); } void TimerWrap::SetImmediateCallback(const FunctionCallbackInfo& args) { auto isolate = Isolate::GetCurrent(); HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument is required for calling 'SetImmediateCallback'."); CHECK_ARG(isolate, args[0]->IsObject(), "Argument \"timeout\" shall be 'Timeout' type."); auto zone = reinterpret_cast(WorkerContext::Get(WorkerContextItem::ZONE)); if (zone == nullptr) { throw new std::runtime_error("Null zone encountered!"); } auto scheduler = zone->GetScheduler().get(); if (scheduler == nullptr) { throw new std::runtime_error("Null scheduler encountered!"); } Local timeout = Local::Cast(args[0]); auto sharedTimeout = std::make_shared>(isolate, timeout); auto context = isolate->GetCurrentContext(); auto sharedContext = std::make_shared>(isolate, context); auto immediateCallbackTask = buildTimeoutTask(sharedTimeout, sharedContext); auto workerId = static_cast( reinterpret_cast(WorkerContext::Get(WorkerContextItem::WORKER_ID))); scheduler->ScheduleOnWorker(workerId, immediateCallbackTask, SchedulePhase::ImmediatePhase); } void TimerWrap::SetTimersCallback(const FunctionCallbackInfo& args) { auto isolate = Isolate::GetCurrent(); HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 argument is required for calling 'SetTimersCallback'."); CHECK_ARG(isolate, args[0]->IsObject(), "Argument \"timeout\" shall be 'Timeout' type."); auto zone = reinterpret_cast(WorkerContext::Get(WorkerContextItem::ZONE)); if (zone == nullptr) { throw new std::runtime_error("Null zone encountered!"); } auto scheduler = zone->GetScheduler().get(); if (scheduler == nullptr) { throw new std::runtime_error("Null scheduler encountered!"); } auto workerId = static_cast( reinterpret_cast(WorkerContext::Get(WorkerContextItem::WORKER_ID))); Local timeout = Local::Cast(args[0]); auto sharedTimeout = std::make_shared>(isolate, timeout); auto context = isolate->GetCurrentContext(); auto sharedContext = std::make_shared>(isolate, context); Local after = Local::Cast(timeout->Get(String::NewFromUtf8(isolate, "_after"))); std::chrono::milliseconds msAfter{static_cast(after->Value())}; auto sharedTimer = std::make_shared( [sharedTimeout, sharedContext, scheduler, workerId]() { auto timerCallbackTask = buildTimeoutTask(sharedTimeout, sharedContext); scheduler->ScheduleOnWorker(workerId, timerCallbackTask, SchedulePhase::DefaultPhase); }, msAfter); auto jsTimer = TimerWrap::NewInstance(sharedTimer); timeout->Set(String::NewFromUtf8(isolate, "_timer"), jsTimer); sharedTimer->Start(); } ================================================ FILE: src/module/core-modules/napa/timer-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// It wraps napa::zone::Timer. /// Reference: napajs/lib/timer.ts#Timer class TimerWrap: public NAPA_OBJECTWRAP { public: /// Init this wrap. static void Init(); /// It creates an instance of TimerWrap with a napa::zone::Timer pointer. static v8::Local NewInstance(std::shared_ptr timer); napa::zone::Timer& Get(); void Reset(); static void SetImmediateCallback(const v8::FunctionCallbackInfo& args); static void SetTimersCallback(const v8::FunctionCallbackInfo& args); private: /// Default constructor. TimerWrap() = default; /// No copy allowed. TimerWrap(const TimerWrap&) = delete; TimerWrap& operator=(const TimerWrap&) = delete; /// Friend default constructor callback. template friend void napa::module::DefaultConstructorCallback(const v8::FunctionCallbackInfo&); template friend v8::MaybeLocal napa::module::NewInstance(int argc, v8::Local argv[]); /// Exported class name. static constexpr const char* exportName = "TimerWrap"; /// Hid constructor from public access. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); std::shared_ptr _timer; }; } } ================================================ FILE: src/module/core-modules/napa/transport-context-wrap-impl.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "transport-context-wrap-impl.h" #include #include using namespace napa; using namespace napa::transport; using namespace napa::module; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(TransportContextWrapImpl); TransportContextWrapImpl::TransportContextWrapImpl(TransportContext* context, bool owning) : _context(context), _owning(owning) { } v8::Local TransportContextWrapImpl::NewInstance(bool owning, napa::transport::TransportContext* context) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); v8::Local argv[] = { v8::Boolean::New(isolate, owning), v8_helpers::PtrToV8Uint32Array(isolate, context) }; auto object = napa::module::NewInstance(sizeof(argv) / sizeof(v8::Local), argv); RETURN_VALUE_ON_PENDING_EXCEPTION(object, v8::Local()); return scope.Escape(object.ToLocalChecked()); } TransportContextWrapImpl::~TransportContextWrapImpl() { if (_owning) { delete _context; } } TransportContext* TransportContextWrapImpl::Get() { return _context; } void TransportContextWrapImpl::Init() { auto isolate = v8::Isolate::GetCurrent(); auto constructorTemplate = v8::FunctionTemplate::New(isolate, TransportContextWrapImpl::ConstructorCallback); constructorTemplate->SetClassName(v8_helpers::MakeV8String(isolate, exportName)); constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "saveShared", SaveSharedCallback); NAPA_SET_PROTOTYPE_METHOD(constructorTemplate, "loadShared", LoadSharedCallback); NAPA_SET_ACCESSOR(constructorTemplate, "sharedCount", GetSharedCountCallback, nullptr); NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, constructorTemplate->GetFunction()); } void TransportContextWrapImpl::GetSharedCountCallback(v8::Local, const v8::PropertyCallbackInfo& args){ auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); JS_ENSURE(isolate, thisObject != nullptr, "Invalid object to get property \"sharedCount\"."); args.GetReturnValue().Set(thisObject->_context->GetSharedCount()); } void TransportContextWrapImpl::ConstructorCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); JS_ENSURE(isolate, args.IsConstructCall(), "class \"TransportContextWrap\" allows constructor call only."); CHECK_ARG(isolate, args.Length() == 1 || args.Length() == 2, "class \"TransportContextWrap\" accept a required boolean argument 'owning' and an optional argument 'handle' of Handle type."); CHECK_ARG(isolate, args[0]->IsBoolean(), "Argument \"owning\" must be boolean."); bool owning = args[0]->BooleanValue(); // It's deleted when its Javascript object is garbage collected by V8's GC. TransportContext* context = nullptr; if (args.Length() == 1 || args[1]->IsUndefined()) { context = new TransportContext(); } else { auto result = v8_helpers::V8ValueToPtr(isolate, args[1]); JS_ENSURE(isolate, result.second, "argument 'handle' must be of type [number, number]."); context = result.first; } auto wrap = new TransportContextWrapImpl(context, owning); wrap->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } void TransportContextWrapImpl::SaveSharedCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 arguments are required for \"saveShared\"."); CHECK_ARG(isolate, args[0]->IsObject(), "Argument \"object\" shall be 'ShareableWrap' type."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto sharedWrap = NAPA_OBJECTWRAP::Unwrap(v8::Local::Cast(args[0])); thisObject->Get()->SaveShared(sharedWrap->Get()); } void TransportContextWrapImpl::LoadSharedCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "1 arguments are required for \"saveShared\"."); auto result = v8_helpers::V8ValueToUintptr(isolate, args[0]); JS_ENSURE(isolate, result.second, "Unable to cast \"handle\" to pointer. Please check if it's in valid format."); auto thisObject = NAPA_OBJECTWRAP::Unwrap(args.Holder()); auto object = thisObject->Get()->LoadShared(result.first); args.GetReturnValue().Set(binding::CreateShareableWrap(object)); } ================================================ FILE: src/module/core-modules/napa/transport-context-wrap-impl.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// It implements napa::module::TransportContextWrap. /// Reference: napajs/lib/transport/transportable.ts#TransportContext class TransportContextWrapImpl: public napa::module::TransportContextWrap { public: /// Init this wrap. static void Init(); /// Create a non-owning transport context wrap. static v8::Local NewInstance(bool owning = true, napa::transport::TransportContext* context = nullptr); /// Destructor. ~TransportContextWrapImpl(); /// Get transport context. napa::transport::TransportContext* Get() override; /// Declare constructor in public, so we can export class constructor to JavaScript world. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); /// Exported class name. static constexpr const char* exportName = "TransportContextWrap"; private: /// Constructor. TransportContextWrapImpl(napa::transport::TransportContext* context, bool owning); /// No copy allowed. TransportContextWrapImpl(const TransportContextWrapImpl&) = delete; TransportContextWrapImpl& operator=(const TransportContextWrapImpl&) = delete; /// TransportContextWrap.constructor static void ConstructorCallback(const v8::FunctionCallbackInfo& args); /// It implements TransportContext.sharedCount static void GetSharedCountCallback(v8::Local, const v8::PropertyCallbackInfo& args); /// It implements TransportContext.saveShared(handle: Handle, object: napajs.memory.ShareableWrap) static void SaveSharedCallback(const v8::FunctionCallbackInfo& args); /// It implements TransportContext.loadShared(handle: Handle): napajs.memory.ShareableWrap) static void LoadSharedCallback(const v8::FunctionCallbackInfo& args); /// Transport context. napa::transport::TransportContext* _context; /// Own context or not. bool _owning; }; } } ================================================ FILE: src/module/core-modules/napa/zone-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "zone-wrap.h" #include "transport-context-wrap-impl.h" #include #include #include #include #include #include using namespace napa::module; using namespace napa::v8_helpers; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(ZoneWrap); // Forward declaration. static v8::Local CreateResponseObject(const napa::Result& result); template static void CreateRequestAndExecute(v8::Local obj, Func&& func); void ZoneWrap::Init() { auto isolate = v8::Isolate::GetCurrent(); // Prepare constructor template. auto functionTemplate = v8::FunctionTemplate::New(isolate, DefaultConstructorCallback); functionTemplate->SetClassName(MakeV8String(isolate, exportName)); functionTemplate->InstanceTemplate()->SetInternalFieldCount(1); // Prototypes. NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "getId", GetId); NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "broadcast", Broadcast); NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "broadcastSync", BroadcastSync); NODE_SET_PROTOTYPE_METHOD(functionTemplate, "execute", Execute); NODE_SET_PROTOTYPE_METHOD(functionTemplate, "executeSync", ExecuteSync); // Set persistent constructor into V8. NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, functionTemplate->GetFunction()); } v8::Local ZoneWrap::NewInstance(std::unique_ptr zoneProxy) { auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); auto constructor = NAPA_GET_PERSISTENT_CONSTRUCTOR(exportName, ZoneWrap); auto object = constructor->NewInstance(context).ToLocalChecked(); auto wrap = NAPA_OBJECTWRAP::Unwrap(object); wrap->_zoneProxy = std::move(zoneProxy); return object; } void ZoneWrap::GetId(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); auto wrap = ObjectWrap::Unwrap(args.Holder()); args.GetReturnValue().Set(MakeV8String(isolate, wrap->_zoneProxy->GetId())); } void ZoneWrap::Broadcast(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); CHECK_ARG(isolate, args[0]->IsObject(), "first argument to zone.broadcast must be the function spec object"); CHECK_ARG(isolate, args[1]->IsFunction(), "second argument to zone.broadcast must be the callback"); v8::String::Utf8Value source(args[0]->ToString()); napa::zone::DoAsyncWork(v8::Local::Cast(args[1]), [&args](std::function complete) { CreateRequestAndExecute(args[0]->ToObject(), [&args, &complete](const napa::FunctionSpec& spec) { auto wrap = ObjectWrap::Unwrap(args.Holder()); wrap->_zoneProxy->Broadcast(spec, [complete = std::move(complete)](napa::Result result) { complete(new napa::Result(std::move(result))); }); }); }, [](auto jsCallback, void* res) { auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); auto result = static_cast(res); v8::HandleScope scope(isolate); std::vector> argv; argv.emplace_back(CreateResponseObject(*result)); (void)jsCallback->Call(context, context->Global(), static_cast(argv.size()), argv.data()); delete result; } ); } void ZoneWrap::BroadcastSync(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); CHECK_ARG(isolate, args[0]->IsObject(), "first argument to zone.broadcastSync must be the function spec object"); CreateRequestAndExecute(args[0]->ToObject(), [&args](const napa::FunctionSpec& spec) { auto wrap = ObjectWrap::Unwrap(args.Holder()); napa::Result result = wrap->_zoneProxy->BroadcastSync(spec); args.GetReturnValue().Set(CreateResponseObject(result)); }); } void ZoneWrap::Execute(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); CHECK_ARG(isolate, args[0]->IsObject(), "first argument to zone.execute must be the function spec object"); CHECK_ARG(isolate, args[1]->IsFunction(), "second argument to zone.execute must be the callback"); napa::zone::DoAsyncWork(v8::Local::Cast(args[1]), [&args](std::function complete) { CreateRequestAndExecute(args[0]->ToObject(), [&args, &complete](const napa::FunctionSpec& spec) { auto wrap = ObjectWrap::Unwrap(args.Holder()); wrap->_zoneProxy->Execute(spec, [complete = std::move(complete)](napa::Result result) { complete(new napa::Result(std::move(result))); }); }); }, [](auto jsCallback, void* res) { auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); auto result = static_cast(res); v8::HandleScope scope(isolate); std::vector> argv; argv.emplace_back(CreateResponseObject(*result)); (void)jsCallback->Call(context, context->Global(), static_cast(argv.size()), argv.data()); delete result; } ); } void ZoneWrap::ExecuteSync(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); CHECK_ARG(isolate, args[0]->IsObject(), "first argument to zone.execute must be the function spec object"); CreateRequestAndExecute(args[0]->ToObject(), [&args](const napa::FunctionSpec& spec) { auto wrap = ObjectWrap::Unwrap(args.Holder()); napa::Result result = wrap->_zoneProxy->ExecuteSync(spec); args.GetReturnValue().Set(CreateResponseObject(result)); }); } static v8::Local CreateResponseObject(const napa::Result& result) { auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); auto responseObject = v8::Object::New(isolate); (void)responseObject->CreateDataProperty( context, MakeV8String(isolate, "code"), v8::Uint32::NewFromUnsigned(isolate, result.code)); (void)responseObject->CreateDataProperty( context, MakeV8String(isolate, "errorMessage"), MakeV8String(isolate, result.errorMessage)); (void)responseObject->CreateDataProperty( context, MakeV8String(isolate, "returnValue"), MakeV8String(isolate, result.returnValue)); // Transport context handle v8::Local contextHandleValue; auto transportContext = result.transportContext.release(); if (transportContext == nullptr) { contextHandleValue = v8::Null(isolate); } else { contextHandleValue = PtrToV8Uint32Array(isolate, transportContext); } (void)responseObject->CreateDataProperty( context, MakeV8String(isolate, "contextHandle"), contextHandleValue); return responseObject; } template static void CreateRequestAndExecute(v8::Local obj, Func&& func) { auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); napa::FunctionSpec spec; // module property is optional in a spec Utf8String module; auto maybe = obj->Get(context, MakeV8String(isolate, "module")); if (!maybe.IsEmpty()) { module = Utf8String(maybe.ToLocalChecked()); spec.module = NAPA_STRING_REF_WITH_SIZE(module.Data(), module.Length()); } // function property is mandatory in a spec maybe = obj->Get(context, MakeV8String(isolate, "function")); CHECK_ARG(isolate, !maybe.IsEmpty(), "function property is missing in function spec object"); auto functionValue = maybe.ToLocalChecked(); CHECK_ARG(isolate, functionValue->IsString(), "function property in function spec object must be a string"); v8::String::Utf8Value function(functionValue->ToString()); spec.function = NAPA_STRING_REF_WITH_SIZE(*function, static_cast(function.length())); // arguments are optional in a spec maybe = obj->Get(context, MakeV8String(isolate, "arguments")); std::vector arguments; if (!maybe.IsEmpty()) { arguments = V8ArrayToVector(isolate, v8::Local::Cast(maybe.ToLocalChecked())); spec.arguments.reserve(arguments.size()); for (const auto& arg : arguments) { spec.arguments.emplace_back(NAPA_STRING_REF_WITH_SIZE(arg.Data(), arg.Length())); } } // options argument is optional. maybe = obj->Get(context, MakeV8String(isolate, "options")); if (!maybe.IsEmpty()) { auto optionsValue = maybe.ToLocalChecked(); JS_ENSURE(isolate, optionsValue->IsObject(), "argument 'options' must be an object."); auto options = v8::Local::Cast(optionsValue); // timeout is optional. maybe = options->Get(context, MakeV8String(isolate, "timeout")); if (!maybe.IsEmpty()) { spec.options.timeout = maybe.ToLocalChecked()->Uint32Value(context).FromJust(); } // transport option is optional. maybe = options->Get(context, MakeV8String(isolate, "transport")); if (!maybe.IsEmpty()) { spec.options.transport = static_cast(maybe.ToLocalChecked()->Uint32Value(context).FromJust()); } } // transportContext property is mandatory in a spec maybe = obj->Get(context, MakeV8String(isolate, "transportContext")); CHECK_ARG(isolate, !maybe.IsEmpty(), "transportContext property is missing in function spec object"); // for broadcast(), we are not creating transportContext. The value of the property is set to null // otherwise it should be an object. auto transportContextValue = maybe.ToLocalChecked(); if (!transportContextValue->IsNull()) { CHECK_ARG(isolate, transportContextValue->IsObject(), "transportContext must be null or an object."); auto transportContextWrap = NAPA_OBJECTWRAP::Unwrap(transportContextValue->ToObject()); spec.transportContext.reset(transportContextWrap->Get()); } // Execute func(spec); } ================================================ FILE: src/module/core-modules/napa/zone-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include // Forward declare zone. namespace napa { class Zone; } namespace napa { namespace module { /// An object wrap to expose zone APIs. class ZoneWrap : public NAPA_OBJECTWRAP { public: /// Exported class name. static constexpr const char* exportName = "ZoneWrap"; /// Initializes the wrap. static void Init(); /// Create a new ZoneWrap instance that wraps the provided proxy. static v8::Local NewInstance(std::unique_ptr zoneProxy); private: /// Declare persistent constructor to create Zone Javascript wrapper instance. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); std::unique_ptr _zoneProxy; // ZoneWrap methods static void GetId(const v8::FunctionCallbackInfo& args); static void Broadcast(const v8::FunctionCallbackInfo& args); static void BroadcastSync(const v8::FunctionCallbackInfo& args); static void Execute(const v8::FunctionCallbackInfo& args); static void ExecuteSync(const v8::FunctionCallbackInfo& args); /// Friend default constructor callback. template friend void napa::module::DefaultConstructorCallback(const v8::FunctionCallbackInfo&); }; } } ================================================ FILE: src/module/core-modules/node/console.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "console.h" #include #include #include using namespace napa; using namespace napa::module; namespace { /// Log a message to console. /// All arguments are converted to string and printed out to console. void LogCallback(const v8::FunctionCallbackInfo& args); } // End of anonymous namespace. void console::Init(v8::Local exports) { NAPA_SET_METHOD(exports, "log", LogCallback); } namespace { void LogCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); std::ostringstream oss; for (int i = 0; i < args.Length(); ++i) { v8::String::Utf8Value argument(args[i]->ToString()); oss << *argument << " "; } std::string message = oss.str(); if (!message.empty()) { message.pop_back(); } std::cout << message << std::endl; args.GetReturnValue().Set(args.Holder()); } } // End of anonymous namespace. ================================================ FILE: src/module/core-modules/node/console.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace module { /// Napa built-in addon to print a message to console. namespace console { /// Set console object as global variable of given context. /// Object to set module. void Init(v8::Local exports); } // End of namespace console } // End of namespace module } // End of namespace napa ================================================ FILE: src/module/core-modules/node/file-system-helpers.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS #endif #include "file-system-helpers.h" #include #include #include #include #include using namespace napa; using namespace napa::module; namespace { std::string GetFileFullPath(const std::string& file) { return filesystem::Path(file).Absolute().Normalize().String(); } } // End of anonymous namespace. std::string file_system_helpers::ReadFileSync(const std::string& filename) { std::string fileFullPath = GetFileFullPath(filename); FILE* source = fopen(fileFullPath.c_str(), "rb"); if (source == nullptr) { std::ostringstream oss; oss << "Can't open for read " << fileFullPath; throw std::runtime_error(oss.str()); } std::unique_ptr> deferred(source, [](auto file) { fclose(file); }); fseek(source, 0, SEEK_END); auto size = static_cast(ftell(source)); rewind(source); std::string content; content.resize(size); for (size_t i = 0; i < size; ) { i += fread(&content[i], 1, size - i, source); if (ferror(source) != 0) { std::ostringstream oss; oss << "Can't read " << fileFullPath; throw std::runtime_error(oss.str()); } } return content; } void file_system_helpers::WriteFileSync(const std::string& filename, const char* data, size_t length) { auto fileFullPath = GetFileFullPath(filename); FILE* target = fopen(fileFullPath.c_str(), "wb"); if (target == nullptr) { std::ostringstream oss; oss << "Can't open for write " << fileFullPath; throw std::runtime_error(oss.str()); } std::unique_ptr> deferred(target, [](auto file) { fclose(file); }); for (size_t written = 0; written < length; ) { written += fwrite(data + written, 1, length - written, target); if (ferror(target) != 0) { std::ostringstream oss; oss << "Can't write " << fileFullPath; throw std::runtime_error(oss.str()); } } } void file_system_helpers::MkdirSync(const std::string& directory) { filesystem::Path path(GetFileFullPath(directory)); if (!filesystem::IsDirectory(path) && !filesystem::MakeDirectory(path)) { std::ostringstream oss; oss << "The directory: " << directory << " doesn't exist, and can't be created."; throw std::runtime_error(oss.str()); } } bool file_system_helpers::ExistsSync(const std::string& path) { return filesystem::Exists(GetFileFullPath(path)); } std::vector file_system_helpers::ReadDirectorySync(const std::string& directory) { std::vector names; filesystem::PathIterator iterator(GetFileFullPath(directory)); while (iterator.Next()) { names.emplace_back(iterator->Filename().String()); } return names; } ================================================ FILE: src/module/core-modules/node/file-system-helpers.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// Helper APIs for file system operations. namespace file_system_helpers { /// Read file synchronously. /// Filename to read. std::string ReadFileSync(const std::string& filename); /// Write file synchronously. /// Filename to write. /// Buffer of data to write. /// Length of data to write. void WriteFileSync(const std::string& filename, const char* data, size_t length); /// Make directory synchronously. /// Directory to make. void MkdirSync(const std::string& directory); /// Check if a path exits synchronously. /// Path to check. /// True if path exists. bool ExistsSync(const std::string& path); /// Read a directory synchronously. /// Directory to read. /// File and directory names except '.' and '..'. std::vector ReadDirectorySync(const std::string& directory); } // End of namespace file_system_helpers } // End of namespace module } // End of namespace napa ================================================ FILE: src/module/core-modules/node/file-system.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "file-system.h" #include "file-system-helpers.h" #include using namespace napa; using namespace napa::module; namespace { /// Read file synchronously. /// It holds filename. void ReadFileSyncCallback(const v8::FunctionCallbackInfo& args); /// Write file synchronously. /// It holds filename and string to write. void WriteFileSyncCallback(const v8::FunctionCallbackInfo& args); /// Make directory synchronously. /// It holds directory to make. void MkdirSyncCallback(const v8::FunctionCallbackInfo& args); /// Check if a path exits synchronously. /// A string argument of path. void ExistsSyncCallback(const v8::FunctionCallbackInfo& args); /// Read a directory synchronously. /// A string argument of path. void ReaddirSyncCallback(const v8::FunctionCallbackInfo& args); } // End of anonymous namespace. void file_system::Init(v8::Local exports) { NAPA_SET_METHOD(exports, "readFileSync", ReadFileSyncCallback); NAPA_SET_METHOD(exports, "writeFileSync", WriteFileSyncCallback); NAPA_SET_METHOD(exports, "mkdirSync", MkdirSyncCallback); NAPA_SET_METHOD(exports, "existsSync", ExistsSyncCallback); NAPA_SET_METHOD(exports, "readdirSync", ReaddirSyncCallback); } namespace { void ReadFileSyncCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() > 0, "fs.readFileSync requires at least 1 argument."); CHECK_ARG(isolate, args[0]->IsString(), "fs.readFileSync requires a string of file path as the 1st argument."); v8::String::Utf8Value filename(args[0]); try { auto content = file_system_helpers::ReadFileSync(*filename); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, content)); } catch (const std::exception& ex) { isolate->ThrowException(v8::Exception::Error(v8_helpers::MakeV8String(isolate, ex.what()))); args.GetReturnValue().SetUndefined(); } } void WriteFileSyncCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() >= 2, "fs.writeFileSync requires 2 parameters."); CHECK_ARG(isolate, args[0]->IsString(), "fs.writeFileSync requires a string as the 1st parameter for file name."); CHECK_ARG(isolate, args[1]->IsString(), "fs.writeFileSync require a string as the 2nd parameter for data to write."); v8::String::Utf8Value fileName(args[0]); v8::String::Utf8Value content(args[1]); try { file_system_helpers::WriteFileSync(std::string(*fileName), *content, static_cast(content.length())); } catch (const std::exception& ex) { isolate->ThrowException(v8::Exception::Error(v8_helpers::MakeV8String(isolate, ex.what()))); } } void MkdirSyncCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() >= 1, "fs.mkdirFileSync requires 1 parameters."); CHECK_ARG(isolate, args[0]->IsString(), "fs.mkdirFileSync requires a string as the 1st parameter for the directory."); v8::String::Utf8Value directory(args[0]); try { file_system_helpers::MkdirSync(std::string(*directory)); } catch (const std::exception& ex) { isolate->ThrowException(v8::Exception::Error(v8_helpers::MakeV8String(isolate, ex.what()))); } } void ExistsSyncCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() >= 1, "fs.existsSync requires 1 parameters."); CHECK_ARG(isolate, args[0]->IsString(), "fs.existsSync requires a string as the 1st parameter for the path to check."); v8::String::Utf8Value path(args[0]); auto exists = file_system_helpers::ExistsSync(std::string(*path)); args.GetReturnValue().Set(exists); } void ReaddirSyncCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() >= 1, "fs.readdirSync requires 1 parameters."); CHECK_ARG(isolate, args[0]->IsString(), "fs.readdirSync requires a string as the 1st parameter for the path to check."); v8::String::Utf8Value directory(args[0]); auto names = file_system_helpers::ReadDirectorySync(std::string(*directory)); auto context = isolate->GetCurrentContext(); auto count = static_cast(names.size()); auto result = v8::Array::New(isolate, count); for (uint32_t i = 0; i < count; ++i) { (void)result->CreateDataProperty(context, i, v8_helpers::MakeV8String(isolate, names[i])); } args.GetReturnValue().Set(result); } } // End of anonymous namespace. ================================================ FILE: src/module/core-modules/node/file-system.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// Napa built-in addon for file system operations. namespace file_system { /// Set file system object. /// Object to set module. void Init(v8::Local exports); } // End of namespace file_system } // End of namespace module } // End of namespace napa ================================================ FILE: src/module/core-modules/node/os.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "os.h" #include #include using namespace napa; using namespace napa::module; void TypeCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, platform::GetOSType())); } void os::Init(v8::Local exports) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); NAPA_SET_METHOD(exports, "type", TypeCallback); } ================================================ FILE: src/module/core-modules/node/os.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace module { /// Napa built-in addon for operating system. namespace os { /// Set os object. /// Object to set module. void Init(v8::Local exports); } // End of namespace os } // End of namespace module } // End of namespace napa ================================================ FILE: src/module/core-modules/node/path.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "path.h" #include #include #include #include using namespace napa; using namespace napa::module; namespace { /// /// Normalize a path by removing '.' and '..' and use preferred separator. (windows in \\ and posix in /). /// Example: /// path.normalize('c:/foo\\bar/.././baz/.') /// // returns 'c:\\foo\\baz'. /// /// A sequence of paths to resolve. void NormalizeCallback(const v8::FunctionCallbackInfo& args); /// /// Resolve a sequence of paths to one absolute path. /// Example: /// path.resolve('c:/foo\\bar', '.\\baz') /// // returns 'c:\\foo\\bar\\baz'. /// /// path.resolve('c:\\foo/bar', 'd:/tmp/file/') /// // returns 'd:\\tmp\\file'. /// /// path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif') /// // if the current working directory is c:\\home\\myself\\node, /// // this returns 'c:\\home\\myself\\node\\wwwroot\\static_files\\gif\\image.gif'. /// /// A sequence of paths to resolve. void ResolveCallback(const v8::FunctionCallbackInfo& args); /// /// Join a sequence of paths to one. /// Example: /// path.join('/foo', 'bar', 'baz/asdf', 'quux', '..') /// // returns '/foo/bar/baz/asdf' /// /// A sequence of paths to join. void JoinCallback(const v8::FunctionCallbackInfo& args); /// /// Parent directory name of a file name. /// Example: /// path.dirname('c:/foo\\bar\\baz/asdf\\quux') /// // returns 'c:\\foo\\bar\\baz\\asdf'. /// // even quux is a directory. The behavior is the same with Node.JS. /// /// 1 Argument of file path. void DirnameCallback(const v8::FunctionCallbackInfo& args); /// /// Base name of a file path. /// Example: /// path.basename('c:\\foo/bar/baz/asdf\\quux.html') /// // returns 'quux.html'. /// path.basename('c:/foo\\bar/baz/asdf/quux.html', '.html') /// // returns 'quux'. /// /// 1 required argument of file path and another optional argument of extension. void BasenameCallback(const v8::FunctionCallbackInfo& args); /// /// Extension of a file path. /// Example: /// path.extname('index.html') /// // returns '.html'. /// /// path.extname('index.coffee.md') /// // returns '.md'. /// /// path.extname('index.') /// // returns '.'. /// /// path.extname('index') /// // returns ''. /// /// 1 argument of file path. void ExtnameCallback(const v8::FunctionCallbackInfo& args); /// /// Check if a path is an absolute path or not. /// Example: /// path.isAbsolute('c:/foo/bar') // returns true. /// path.isAbsolute('c:\\baz/..') // returns true. /// path.isAbsolute('qux/') // returns false. /// path.isAbsolute('.') // returns false. /// /// 1 argument of file path. void IsAbsoluteCallback(const v8::FunctionCallbackInfo& args); /// /// Get relative path from the first path to the second. /// Example: /// path.relative('c:/foo\\bar', 'c:/foo/abc.txt') /// // returns '..\\abc.txt'. /// /// path.relative('c:/foo\\../bar', 'c:/bar') /// // returns '.'. /// /// path.relative('c:\\foo', 'c:/') /// // returns '..'. /// /// path.relative('c:\\foo', 'd:\\bar') /// // returns 'd:\\bar'. /// /// 2 strings represent from-path and to-path. void RelativeCallback(const v8::FunctionCallbackInfo& args); } // End of anonymous namespace. void path::Init(v8::Local exports) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); NAPA_SET_METHOD(exports, "normalize", NormalizeCallback); NAPA_SET_METHOD(exports, "resolve", ResolveCallback); NAPA_SET_METHOD(exports, "join", JoinCallback); NAPA_SET_METHOD(exports, "dirname", DirnameCallback); NAPA_SET_METHOD(exports, "basename", BasenameCallback); NAPA_SET_METHOD(exports, "extname", ExtnameCallback); NAPA_SET_METHOD(exports, "isAbsolute", IsAbsoluteCallback); NAPA_SET_METHOD(exports, "relative", RelativeCallback); (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "sep"), v8_helpers::MakeV8String(isolate, platform::DIR_SEPARATOR)); } namespace { void NormalizeCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsString() , "path.normalize requires 1 string parameter of file path."); v8::String::Utf8Value utf8Path(args[0]); auto path = filesystem::Path(*utf8Path).Normalize(); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, path.String())); } void ResolveCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() > 0, "path.resolve requires at least one string parameters."); auto path = filesystem::CurrentDirectory(); for (int i = 0; i < args.Length(); ++i) { CHECK_ARG(isolate, args[i]->IsString(), "path.resolve doesn't accept non-string argument."); v8::String::Utf8Value nextPath(args[i]); path /= *nextPath; } args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, path.Absolute().Normalize().String())); } void JoinCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() > 0 && args[0]->IsString(), "path.join requires at least one string parameters."); v8::String::Utf8Value basePath(args[0]); auto path = filesystem::Path(*basePath); for (int i = 1; i < args.Length(); ++i) { CHECK_ARG(isolate, args[i]->IsString(), "path.join doesn't accept non-string argument."); v8::String::Utf8Value nextPath(args[i]); path /= *nextPath; } args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, path.Normalize().String())); } void DirnameCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsString(), "path.dirname requires 1 string parameter of file path."); v8::String::Utf8Value utf8Path(args[0]); auto path = filesystem::Path(*utf8Path); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, path.Dirname().String())); } void BasenameCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 || args.Length() == 2, "path.basename takes 1 required argument of file path and 1 optional argument of extension"); CHECK_ARG(isolate, args[0]->IsString() , "path.basename requires a string parameter of file path."); v8::String::Utf8Value utf8Path(args[0]); auto fileName = filesystem::Path(*utf8Path).Filename().String(); if (args.Length() == 2) { CHECK_ARG(isolate, args[1]->IsString() , "path.basename requires a string as 2nd parameter of extension."); v8::String::Utf8Value extension(args[1]); auto pos = fileName.find(*extension); if (pos != std::string::npos) { fileName = fileName.substr(0, pos); } } args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, fileName)); } void ExtnameCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsString(), "path.extname requires 1 string parameter of file path."); v8::String::Utf8Value utf8Path(args[0]); auto path = filesystem::Path(*utf8Path); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, path.Extension().String())); } void IsAbsoluteCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsString() , "path.isAbsolute requires 1 string parameter of file path."); v8::String::Utf8Value utf8Path(args[0]); auto path = filesystem::Path(*utf8Path); args.GetReturnValue().Set(path.IsAbsolute()); } void RelativeCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 2 && args[0]->IsString() && args[1]->IsString(), "path.relative requires 2 arguments of string type."); v8::String::Utf8Value from(args[0]); v8::String::Utf8Value to(args[1]); auto relativePath = filesystem::Path(*to).Relative(*from); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, relativePath.String())); } } // End of anonymous namespace. ================================================ FILE: src/module/core-modules/node/path.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace module { /// Napa built-in addon for path operations. namespace path { /// Set path object. /// Object to set module. void Init(v8::Local exports); } // End of namespace path } // End of namespace module } // End of namespace napa ================================================ FILE: src/module/core-modules/node/process.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "process.h" #include #include #include #include #include #include #include using namespace napa; using namespace napa::module; namespace { /// Callback to cwd(). void CwdCallback(const v8::FunctionCallbackInfo&); /// Callback to chdir(). void ChdirCallback(const v8::FunctionCallbackInfo&); /// Callback to exit process. void ExitCallback(const v8::FunctionCallbackInfo&); /// Callback to hrtime. void HrtimeCallback(const v8::FunctionCallbackInfo&); /// Callback to umask. void UmaskCallback(const v8::FunctionCallbackInfo&); /// Environment variable getter. void EnvGetterCallback(v8::Local, const v8::PropertyCallbackInfo&); /// Environment variable setter. void EnvSetterCallback(v8::Local, v8::Local, const v8::PropertyCallbackInfo&); } // End of anonymous namespace. void process::Init(v8::Local exports) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); NAPA_SET_METHOD(exports, "cwd", CwdCallback); NAPA_SET_METHOD(exports, "chdir", ChdirCallback); NAPA_SET_METHOD(exports, "exit", ExitCallback); NAPA_SET_METHOD(exports, "hrtime", HrtimeCallback); NAPA_SET_METHOD(exports, "umask", UmaskCallback); auto argc = platform::GetArgc(); auto argv = platform::GetArgv(); auto arguments = v8::Array::New(isolate, argc); for (int i = 0; i < argc; ++i) { (void)arguments->CreateDataProperty(context, i, v8_helpers::MakeV8String(isolate, argv[i])); } (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "argv"), arguments); auto envObjectTemplate = v8::ObjectTemplate::New(isolate); envObjectTemplate->SetNamedPropertyHandler(EnvGetterCallback, EnvSetterCallback); (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "env"), envObjectTemplate->NewInstance()); (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "platform"), v8_helpers::MakeV8String(isolate, platform::PLATFORM)); (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "version"), v8_helpers::MakeV8String(isolate, std::to_string(MODULE_VERSION))); (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "execPath"), v8_helpers::MakeV8String(isolate, filesystem::ProgramPath().String())); (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "pid"), v8::Integer::New(isolate, platform::Getpid())); } namespace { void CwdCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, filesystem::CurrentDirectory().String())); } void ChdirCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsString(), "process.chdir requires a string parameter."); v8::String::Utf8Value dirname(args[0]); filesystem::Path dir(*dirname); JS_ENSURE(isolate, filesystem::IsDirectory(dir), "Directory \"%s\" doesn't exist", dir.c_str()); filesystem::SetCurrentDirectory(dir); args.GetReturnValue().SetUndefined(); } void ExitCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1, "process.exit requires 1 int32 parameter as exit code."); CHECK_ARG(isolate, args[0]->IsInt32(), "Exit code must be integer."); std::exit(args[0]->Int32Value()); } void HrtimeCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); // Returns the current time in nanoseconds uint64_t time = std::chrono::high_resolution_clock::now().time_since_epoch().count(); if (args.Length() == 1) { auto result = v8_helpers::V8Uint32ArrayToHrtime(isolate, args[0]); JS_ENSURE(isolate, result.second, "The 1st argument of hrtime must be a two-element uint32 array."); // Calculate the delta time -= result.first; } args.GetReturnValue().Set(v8_helpers::HrtimeToV8Uint32Array(isolate, time)); } void UmaskCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); if (args.Length() == 0 || args[0]->IsUndefined()) { auto old = platform::Umask(0); platform::Umask(old); args.GetReturnValue().Set(old); return; } CHECK_ARG(isolate, args[0]->IsInt32() || args[0]->IsString(), "Argument must be an integer or octal string"); int32_t value = 0; if (args[0]->IsInt32()) { // Integer. value = args[0]->Int32Value(); } else { // Octal string. v8::String::Utf8Value arg(args[0]); value = std::strtol(*arg, nullptr, 8); } auto old = platform::Umask(value); args.GetReturnValue().Set(old); } void EnvGetterCallback(v8::Local propertyKey, const v8::PropertyCallbackInfo& info) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); v8::String::Utf8Value key(propertyKey); auto value = platform::GetEnv(*key); if (value.empty()) { info.GetReturnValue().SetUndefined(); } else { info.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, value)); } } void EnvSetterCallback(v8::Local propertyKey, v8::Local propertyValue, const v8::PropertyCallbackInfo& info) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); v8::String::Utf8Value key(propertyKey); v8::String::Utf8Value value(propertyValue); platform::SetEnv(*key, *value); info.GetReturnValue().Set(propertyValue); } } // End of anonymous namespace. ================================================ FILE: src/module/core-modules/node/process.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace module { /// Napa built-in addon to provide helper APIs for process. namespace process { /// Set process object as global variable of given context. /// Object to set module. void Init(v8::Local exports); } // End of namespace process } // End of namespace module } // End of namespace napa ================================================ FILE: src/module/core-modules/node/tty-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "tty-wrap.h" #include #include using namespace napa; using namespace napa::module; namespace { /// Callback to isTTY(). void IsTTYCallback(const v8::FunctionCallbackInfo&); /// Callback to guessHandleType(). void GuessHandleTypeCallback(const v8::FunctionCallbackInfo&); } // End of anonymous namespace. void tty_wrap::Init(v8::Local exports) { NAPA_SET_METHOD(exports, "isTTY", IsTTYCallback); NAPA_SET_METHOD(exports, "guessHandleType", GuessHandleTypeCallback); } namespace { void IsTTYCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsInt32(), "tty_wrap.isTTY requires a file descriptor"); int32_t fd = args[0]->Int32Value(); CHECK_ARG(isolate, fd >= 0, "Invalid file descriptor"); args.GetReturnValue().Set(platform::Isatty(fd) > 0); } void GuessHandleTypeCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsInt32(), "tty_wrap.getHandleType requires a integer parameter"); int32_t fd = args[0]->Int32Value(); CHECK_ARG(isolate, fd >= 0, "Invalid file descriptor"); // Napa doesn't have any information about handle type, so return as unknown handle. args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, "UNKNOWN")); } } // End of anonymous namespace. ================================================ FILE: src/module/core-modules/node/tty-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace module { /// Napa built-in addon to provide helper APIs for text terminal. namespace tty_wrap { /// Set tty object as global variable of given context. /// Object to set module. void Init(v8::Local exports); } // End of namespace tty_wrap } // End of namespace module } // End of namespace napa ================================================ FILE: src/module/loader/binary-module-loader.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "binary-module-loader.h" #include "module-loader-helpers.h" #include #include using namespace napa; using namespace napa::module; BinaryModuleLoader::BinaryModuleLoader(BuiltInModulesSetter builtInModulesSetter) : _builtInModulesSetter(std::move(builtInModulesSetter)) {} bool BinaryModuleLoader::TryGet(const std::string& path, v8::Local /*arg*/, v8::Local& module) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto library = std::make_shared(path); auto napaModule = library->Import(napa::module::NAPA_MODULE_EXPORT); JS_ENSURE_WITH_RETURN(isolate, napaModule != nullptr, false, "Can't import napa module: \"%s\"", path.c_str()); JS_ENSURE_WITH_RETURN(isolate, napaModule->version == MODULE_VERSION, false, "Module version is not compatible: \"%s\"", path.c_str()); _modules.push_back(library); auto context = isolate->GetCurrentContext(); auto moduleContext = v8::Context::New(isolate); JS_ENSURE_WITH_RETURN(isolate, !moduleContext.IsEmpty(), false, "Can't create module context for \"%s\"", path.c_str()); // We set an empty security token so callee can access caller's context. moduleContext->SetSecurityToken(v8::Undefined(isolate)); v8::Context::Scope contextScope(moduleContext); module_loader_helpers::SetupModuleContext(context, moduleContext, path); _builtInModulesSetter(moduleContext); module = scope.Escape(module_loader_helpers::ExportModule(moduleContext->Global(), napaModule->initializer)); return true; } ================================================ FILE: src/module/loader/binary-module-loader.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "module-file-loader.h" #include "platform/dll.h" #include #include #include namespace napa { namespace module { /// It loads a module from binary file. class BinaryModuleLoader : public ModuleFileLoader { public: /// Constructor. /// Built-in modules registerer. BinaryModuleLoader(BuiltInModulesSetter builtInModulesSetter); /// It loads a module from binary file. /// Module path called by require(). /// Argument for loading the file. Passed through as arg1 from require. /// Loaded binary module if successful. /// True if binary module is loaded, false otherwise. bool TryGet(const std::string& path, v8::Local arg, v8::Local& module) override; private: /// Built-in modules registerer. BuiltInModulesSetter _builtInModulesSetter; /// It keeps binary modules loaded. std::vector> _modules; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: src/module/loader/core-module-loader.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "core-module-loader.h" #include "javascript-module-loader.h" #include "module-cache.h" #include "module-loader-helpers.h" #include #include using namespace napa; using namespace napa::module; namespace { const std::string CORE_MODULE_EXTENSION = ".js"; const std::string CORE_MODULE_DIRECTORY = "lib\\core"; } // End of anonymous namespace. CoreModuleLoader::CoreModuleLoader(BuiltInModulesSetter builtInModulesSetter, ModuleCache& moduleCache, ModuleCache& bindingCache) : JavascriptModuleLoader(std::move(builtInModulesSetter), moduleCache), _bindingCache(bindingCache) {} bool CoreModuleLoader::TryGet(const std::string& name, v8::Local /*arg*/, v8::Local& module) { filesystem::Path basePath(module_loader_helpers::GetNapaRuntimeDirectory()); auto fileName = name + CORE_MODULE_EXTENSION; // Check ./lib or ../lib directory only auto fullPath = basePath / CORE_MODULE_DIRECTORY / fileName; if (filesystem::IsRegularFile(fullPath)) { // Load javascript core module from a file at ./lib directory. return JavascriptModuleLoader::TryGet(fullPath.String(), v8::Local(), module); } fullPath = (basePath.Parent() / CORE_MODULE_DIRECTORY / fileName).Normalize(); if (filesystem::IsRegularFile(fullPath)) { // Load javascript core module from a file at ../lib directory. return JavascriptModuleLoader::TryGet(fullPath.String(), v8::Local(), module); } // Return binary core module if exists. return _bindingCache.TryGet(name, module); } ================================================ FILE: src/module/loader/core-module-loader.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "javascript-module-loader.h" #include namespace napa { namespace module { /// It loads a core module. class CoreModuleLoader : public JavascriptModuleLoader { public: /// Constructor. /// Built-in modules registerer. /// Cache for all modules. /// Cache for binding core binary modules. CoreModuleLoader(BuiltInModulesSetter builtInModulesSetter, ModuleCache& moduleCache, ModuleCache& bindingCache); /// It loads a core module. /// Core module name. /// Argument for loading the file. Passed through as arg1 from require. /// Loaded core module if successful. /// True if core module is loaded, false otherwise. bool TryGet(const std::string& name, v8::Local arg, v8::Local& module) override; private: /// Cache instance for binding binary core modules. ModuleCache& _bindingCache; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: src/module/loader/javascript-module-loader.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "javascript-module-loader.h" #include "module-cache.h" #include "module-loader-helpers.h" #include using namespace napa; using namespace napa::module; JavascriptModuleLoader::JavascriptModuleLoader(BuiltInModulesSetter builtInModulesSetter, ModuleCache& moduleCache) : _builtInModulesSetter(std::move(builtInModulesSetter)) , _moduleCache(moduleCache) {} bool JavascriptModuleLoader::TryGet(const std::string& path, v8::Local arg, v8::Local& module) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); bool fromContent = !arg.IsEmpty(); v8::Local source; if (!fromContent) { source = module_loader_helpers::ReadModuleFile(path); JS_ENSURE_WITH_RETURN(isolate, !source.IsEmpty(), false, "Can't read Javascript module: \"%s\"", path.c_str()); } else { JS_ENSURE_WITH_RETURN(isolate, arg->IsString(), false, "The 2nd argument of 'require' must be content of string type."); source = v8::Local::Cast(arg); } auto context = isolate->GetCurrentContext(); auto moduleContext = v8::Context::New(isolate); JS_ENSURE_WITH_RETURN(isolate, !moduleContext.IsEmpty(), false, "Can't create module context for: \"%s\"", path.c_str()); // We set an empty security token so callee can access caller's context. moduleContext->SetSecurityToken(v8::Undefined(isolate)); v8::Context::Scope contextScope(moduleContext); module_loader_helpers::SetupModuleContext(context, moduleContext, path); _builtInModulesSetter(moduleContext); // To prevent cycle, cache unloaded module first. if (!fromContent) { _moduleCache.Upsert(path, module_loader_helpers::ExportModule(moduleContext->Global(), nullptr)); } v8::TryCatch tryCatch(isolate); { auto origin = v8::ScriptOrigin(v8_helpers::MakeV8String(isolate, path)); // The wrapper set 'this' reference to module.exports in module's top level code v8::Local wrappedSource = v8::String::Concat( v8_helpers::MakeV8String(isolate, "(function(){"), v8::String::Concat( source, v8_helpers::MakeV8String(isolate, "}).apply(module.exports);") ) ); auto script = v8::Script::Compile(wrappedSource, &origin); if (script.IsEmpty() || tryCatch.HasCaught()) { tryCatch.ReThrow(); return false; } auto run = script->Run(); if (run.IsEmpty() || tryCatch.HasCaught()) { tryCatch.ReThrow(); return false; } } // Export a loaded module. module = scope.Escape(module_loader_helpers::ExportModule(moduleContext->Global(), nullptr)); return true; } ================================================ FILE: src/module/loader/javascript-module-loader.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "module-file-loader.h" #include namespace napa { namespace module { // forward declaration. class ModuleCache; /// It loads a module from javascript file or content from arg. class JavascriptModuleLoader : public ModuleFileLoader { public: /// Constructor. /// Built-in modules registerer. /// Cache for all modules. JavascriptModuleLoader(BuiltInModulesSetter builtInModulesSetter, ModuleCache& moduleCache); /// It loads a module from javascript file. /// Module path called by require(). /// Argument for loading the file. Passed through as arg1 from require. /// Loaded javascript module if successful. /// True if the javascript module is loaded, false otherwise. bool TryGet(const std::string& path, v8::Local arg, v8::Local& module) override; private: /// Built-in modules registerer. BuiltInModulesSetter _builtInModulesSetter; /// Module cache instance. ModuleCache& _moduleCache; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: src/module/loader/json-module-loader.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "json-module-loader.h" #include "module-loader-helpers.h" #include using namespace napa; using namespace napa::module; bool JsonModuleLoader::TryGet(const std::string& path, v8::Local arg, v8::Local& module) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto source = module_loader_helpers::ReadModuleFile(path); JS_ENSURE_WITH_RETURN(isolate, !source.IsEmpty(), false, "Can't read JSON module: \"%s\"", path.c_str()); auto json = v8::JSON::Parse(isolate, source).ToLocalChecked(); JS_ENSURE_WITH_RETURN(isolate, !json.IsEmpty(), false, "Can't parse JSON from \"%s\"", path.c_str()); module = scope.Escape(json->ToObject(isolate->GetCurrentContext()).ToLocalChecked()); return true; } ================================================ FILE: src/module/loader/json-module-loader.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "module-file-loader.h" #include namespace napa { namespace module { /// It loads an object from json file. class JsonModuleLoader : public ModuleFileLoader { public: /// It loads an object from json file. /// Module path called by require(). /// Argument for loading the file. Passed through as arg1 from require. /// Loaded object if successful. /// True if the object is loaded, false otherwise. bool TryGet(const std::string& path, v8::Local arg, v8::Local& module) override; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: src/module/loader/module-cache.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "module-cache.h" #include using namespace napa; using namespace napa::module; using PersistentModule = v8::Persistent>; using PersistentModuleCache = std::unordered_map; struct ModuleCache::ModuleCacheImpl { /// Module cache to avoid module loading overhead. PersistentModuleCache moduleCache; }; ModuleCache::ModuleCache() : _impl(std::make_unique()) {} ModuleCache::~ModuleCache() = default; std::string NormalizeCacheKey(const std::string& path) { #ifdef _WIN32 // Remove UNC prefix if present. if (path.substr(0, 4) == "\\\\?\\") { return path.substr(4); } #endif return path; } void ModuleCache::Upsert(const std::string& path, v8::Local module) { if (module.IsEmpty()) { return; } auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto key = NormalizeCacheKey(path); auto iter = _impl->moduleCache.find(key); if (iter != _impl->moduleCache.end()) { // If exists, reset it and override with new module. iter->second.Reset(); iter->second = PersistentModule(isolate, module); } else { _impl->moduleCache.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(isolate, module)); } } bool ModuleCache::TryGet(const std::string& path, v8::Local& module) const { auto isolate = v8::Isolate::GetCurrent(); auto key = NormalizeCacheKey(path); auto iter = _impl->moduleCache.find(key); if (iter == _impl->moduleCache.end()) { return false; } module = v8::Local::New(isolate, iter->second); return true; } ================================================ FILE: src/module/loader/module-cache.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { /// Module cache to avoid module loading overhead. class ModuleCache { public: /// Constructor. ModuleCache(); /// Default destructor. ~ModuleCache(); /// Non-copyable. ModuleCache(const ModuleCache&) = delete; ModuleCache& operator=(const ModuleCache&) = delete; /// Movable. ModuleCache(ModuleCache&&) = default; ModuleCache& operator=(ModuleCache&&) = default; /// It inserts or updates a module into cache using path as a key. /// The module name path. /// V8 representative javascript object to be cached. void Upsert(const std::string& path, v8::Local module); /// A helper to load a module from cache. /// Full path of javascript or napa module. /// Cached module if successful. /// True if it finds successfully. False, otherwise. bool TryGet(const std::string& path, v8::Local& module) const; private: /// Implementation of module cache. struct ModuleCacheImpl; std::unique_ptr _impl; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: src/module/loader/module-file-loader.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace module { using BuiltInModulesSetter = std::function context)>; /// Interface to load a module from file. class ModuleFileLoader { public: virtual ~ModuleFileLoader() = default; /// It loads a module from file. /// Module path called by require(). /// Optional argument for loading module file. Passed through as arg1 from require. /// Loaded module if successful. /// True if the module was loaded, false otherwise. virtual bool TryGet(const std::string& path, v8::Local arg, v8::Local& module) = 0; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: src/module/loader/module-loader-helpers.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "module-loader-helpers.h" #include #include #include #include #include #include #include #include #include using namespace napa; using namespace napa::module; namespace { /// Set up __dirname and __filename at V8 context. /// Object to set module paths. /// Module directory name. /// Module file name. void SetupModulePath(v8::Local exports, const std::string& dirname, const std::string& filename); /// Set up module objects at V8 context. /// Parent context. /// Object to set module objects. /// Module id. void SetupModuleObjects(v8::Local parentContext, v8::Local exports, const std::string& id); } // End of anonymous namespace. v8::Local module_loader_helpers::ExportModule(v8::Local object, const napa::module::ModuleInitializer& initializer) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto module = object->Get(v8_helpers::MakeV8String(isolate, "module"))->ToObject(); auto exports = module->Get(v8_helpers::MakeV8String(isolate, "exports"))->ToObject(); if (initializer != nullptr) { initializer(exports, module); } return scope.Escape(exports); } std::string module_loader_helpers::GetCurrentContextDirectory() { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto contextObject = context->Global(); auto dirPropertyName = v8_helpers::MakeV8String(isolate, "__dirname"); if (contextObject.IsEmpty() || contextObject->IsNull() || !contextObject->Has(dirPropertyName)) { return GetCurrentWorkingDirectory(); } v8::String::Utf8Value callingPath(contextObject->Get(dirPropertyName)); return std::string(*callingPath); } std::string module_loader_helpers::GetModuleDirectory(v8::Local module) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto filename = module->Get(v8_helpers::MakeV8String(isolate, "filename")); if (filename.IsEmpty() || !filename->IsString()) { return ""; } filesystem::Path moduleFile(v8_helpers::V8ValueTo(filename)); return moduleFile.Parent().Normalize().String(); } const std::string& module_loader_helpers::GetNapaDllPath() { static std::string dllPath = dll::ThisLineLocation(); return dllPath; } const std::string& module_loader_helpers::GetNapaRuntimeDirectory() { static std::string runtimeDirectory = filesystem::Path(GetNapaDllPath()).Parent().Normalize().String(); return runtimeDirectory; } const std::string& module_loader_helpers::GetCurrentWorkingDirectory() { static std::string currentWorkingDirectory = filesystem::CurrentDirectory().String(); return currentWorkingDirectory; } const std::string& module_loader_helpers::GetLibDirectory() { static std::string libDirectory = (filesystem::Path(GetNapaDllPath()).Parent().Parent() / "lib").Normalize().String(); return libDirectory; } void module_loader_helpers::SetupTopLevelContext() { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto exports = isolate->GetCurrentContext()->Global(); SetupModulePath(exports, GetCurrentWorkingDirectory(), std::string()); SetupModuleObjects(v8::Local(), exports, "."); // Set 'global' variable shared by top-level context and module contexts. (void)exports->Set(v8_helpers::MakeV8String(isolate, "global"), exports); } void module_loader_helpers::SetupModuleContext(v8::Local parentContext, v8::Local moduleContext, const std::string& path) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto exports = moduleContext->Global(); SetupModuleObjects(parentContext, exports, path); if (path.empty()) { SetupModulePath(exports, GetCurrentWorkingDirectory(), std::string()); } else { filesystem::Path current(path); SetupModulePath(exports, current.Parent().Normalize().String(), path); } auto global = parentContext->Global()->Get(parentContext, v8_helpers::MakeV8String(isolate, "global")).ToLocalChecked()->ToObject(); (void)exports->Set(v8_helpers::MakeV8String(isolate, "global"), global); } std::vector module_loader_helpers::ReadCoreModulesJson() { static const std::string CORE_MODULES_JSON_PATH = (filesystem::Path(GetLibDirectory()) / "core" / "core-modules.json").String(); NAPA_ASSERT(filesystem::IsRegularFile(CORE_MODULES_JSON_PATH), "Core module JSON file \"%s\" does not exist.", CORE_MODULES_JSON_PATH.c_str()); std::vector coreModuleInfos; // Reserve capacity to help avoiding too frequent allocation. coreModuleInfos.reserve(16); rapidjson::Document document; try { std::ifstream ifs(CORE_MODULES_JSON_PATH); rapidjson::IStreamWrapper isw(ifs); if (document.ParseStream(isw).HasParseError()) { throw std::runtime_error(rapidjson::GetParseError_En(document.GetParseError())); } for (const auto& obj : document.GetArray()) { auto moduleName = obj["name"].GetString(); auto isBuiltIn = (obj["type"] == "builtin"); coreModuleInfos.emplace_back(moduleName, isBuiltIn); } } catch (const std::exception& ex) { NAPA_ASSERT(false, "Reading core-modules.json failed: %s", ex.what()); } return coreModuleInfos; } v8::Local module_loader_helpers::ReadModuleFile(const std::string& path) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); std::string content; try { content = file_system_helpers::ReadFileSync(path); } catch (const std::exception& ex) { isolate->ThrowException(v8::Exception::Error(v8_helpers::MakeV8String(isolate, ex.what()))); return scope.Escape(v8::Local()); } JS_ENSURE_WITH_RETURN(isolate, !content.empty(), scope.Escape(v8::Local()), "\"%s\" is empty", path.c_str()); return scope.Escape(v8_helpers::MakeV8String(isolate, content)); } namespace { void SetupModulePath(v8::Local exports, const std::string& dirname, const std::string& filename) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); (void)exports->Set(v8_helpers::MakeV8String(isolate, "__dirname"), v8_helpers::MakeV8String(isolate, dirname)); if (filename.empty()) { (void)exports->Set(v8_helpers::MakeV8String(isolate, "__filename"), v8::Null(isolate)); } else { (void)exports->Set(v8_helpers::MakeV8String(isolate, "__filename"), v8_helpers::MakeV8String(isolate, filename)); } } void SetupModuleObjects(v8::Local parentContext, v8::Local exports, const std::string& id) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto module = v8::Object::New(isolate); (void)module->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "exports"), v8::Object::New(isolate)); (void)module->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "paths"), v8::Array::New(isolate)); (void)module->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "id"), v8_helpers::MakeV8String(isolate, id)); (void)module->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "filename"), v8_helpers::MakeV8String(isolate, id)); // Setup 'module.require'. if (!parentContext.IsEmpty()) { auto requireKey = v8_helpers::MakeV8String(isolate, "require"); (void)module->CreateDataProperty(context, requireKey, parentContext->Global()->Get(requireKey)); } (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "module"), module); (void)exports->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "exports"), module->Get(v8_helpers::MakeV8String(isolate, "exports"))->ToObject()); // Set '__in_napa' variable in all modules context to distinguish node and napa runtime. (void)exports->Set(v8_helpers::MakeV8String(isolate, "__in_napa"), v8::Boolean::New(isolate, true)); #ifndef USING_V8_SHARED // Set '__in_embed' variable in all modules context to distinguish between napa running in node and embedded mode. (void)exports->Set(v8_helpers::MakeV8String(isolate, "__in_embed"), v8::Boolean::New(isolate, true)); #endif } } // End of anonymous namespace. ================================================ FILE: src/module/loader/module-loader-helpers.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace module { namespace module_loader_helpers { /// Core module information from 'core-modules.json'. struct CoreModuleInfo { /// Core module name. std::string name; /// True if it's a built-in module. False, otherwise. bool isBuiltIn; /// Constructor. CoreModuleInfo(std::string name, bool isBuiltIn) : name(std::move(name)), isBuiltIn(isBuiltIn) {} }; /// It exports loaded module. /// Loaded javascript object. /// Callback function to initialize a module. /// Exported javascript object. v8::Local ExportModule(v8::Local object, const napa::module::ModuleInitializer& initializer); /// It returns directory of a JavaScript context. /// Directory of context. If called from external string, module root directory will be returned. std::string GetCurrentContextDirectory(); /// It returns parent directory of module.filename. /// Parent directory of module.filename. std::string GetModuleDirectory(v8::Local module); /// It returns the directory of napa runtime. /// Directory of napa runtime. const std::string& GetNapaRuntimeDirectory(); /// It returns napa.dll path. /// Path of napa.dll. const std::string& GetNapaDllPath(); /// It returns the process current working directory. const std::string& GetCurrentWorkingDirectory(); /// It returns the root directory of napa lib files. const std::string& GetLibDirectory(); /// It sets globals and the root module path of global context as the script path. void SetupTopLevelContext(); /// It sets up a module context. /// Parent context. /// Module context. /// Module path called by require(). /// V8 context object. void SetupModuleContext(v8::Local parentContext, v8::Local moduleContext, const std::string& path); /// It reads javascript core module information. std::vector ReadCoreModulesJson(); /// It reads a module file to javascript string. /// File path to read. /// V8 string containing file content. v8::Local ReadModuleFile(const std::string& path); } // End of namespace module_loader_helpers. } // End of namespace module } // End of namespace napa ================================================ FILE: src/module/loader/module-loader.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "module-loader.h" #include "binary-module-loader.h" #include "core-module-loader.h" #include "javascript-module-loader.h" #include "json-module-loader.h" #include "module-cache.h" #include "module-loader-helpers.h" #include "module-resolver.h" #include #include // TODO: decouple dependencies between module-loader and zone. #include #include #include #include using namespace napa; using namespace napa::module; /// Implementation of module loader. /// /// It has three kinds of core modules. /// Built-in module: /// It can be accessed with/without require() such as console, process, but can't be with process.binding(). /// It's cached at both binding and module cache. /// If javascript core file exists, it overrides binary core module. /// Binary core module: /// It can be accessed with only process.binding(), not with require(). /// It's cached at only binding cache. /// Javascript core module: /// It exists as Javascript file at './lib' or '../lib' directory. /// It can be accessed with only require() and cached at only module cache. /// If javascript core file exists, it overrides binary core module. /// class ModuleLoader::ModuleLoaderImpl { public: /// Constructor. ModuleLoaderImpl(); /// Bootstrap core modules into module loader. /// /// Bootstraping must be done after module loader is created and registered into isolate data /// because Javascript core modules may call 'require'. /// void Bootstrap(); private: /// Global callback function for require(). /// V8 argument to return module object. static void RequireCallback(const v8::FunctionCallbackInfo& args); /// Callback to resolve a given module path. /// Module name. static void ResolveCallback(const v8::FunctionCallbackInfo& args); /// Callback to bind core binary module. /// Module name. static void BindingCallback(const v8::FunctionCallbackInfo& args); /// It loads a module. /// Module path called by require(). /// V8 argument to return module object. void RequireModule(const char* path, const v8::FunctionCallbackInfo& args); /// It registers a binary core module. /// Module name. /// True if it's a built-in module, which doesn't need require() to call. /// Module initialization function. void LoadBinaryCoreModule(const char* name, bool isBuiltInModule, const napa::module::ModuleInitializer& initializer); /// It sets up built-in modules at each module's' context. /// V8 context. void SetupBuiltInModules(v8::Local context); /// It sets up require function. /// V8 context. void SetupRequire(v8::Local context); /// It adds the extra functions, which access module loader's function, into built-in modules. /// V8 context. /// It assumes that calling built-in modules are already loaded into a context. void DecorateBuiltInModules(v8::Local context); /// Module cache to avoid module loading overhead. ModuleCache _moduleCache; /// Cache for core binary modules, which can be accessed by process.binding(). ModuleCache _bindingCache; /// Module resolver to resolve module path. ModuleResolver _resolver; /// Built-in module list. std::unordered_set _builtInNames; /// Module loaders. std::array, static_cast(ModuleType::END_OF_MODULE_TYPE)> _loaders; }; void ModuleLoader::CreateModuleLoader() { auto moduleLoader = reinterpret_cast(zone::WorkerContext::Get(zone::WorkerContextItem::MODULE_LOADER)); if (moduleLoader == nullptr) { moduleLoader = new ModuleLoader(); zone::WorkerContext::Set(zone::WorkerContextItem::MODULE_LOADER, moduleLoader); // Now, Javascript core module's 'require' can find module loader instance correctly. moduleLoader->_impl->Bootstrap(); } NAPA_DEBUG("ModuleLoader", "Module loader is created successfully."); } ModuleLoader::ModuleLoader() : _impl(std::make_unique()) {} ModuleLoader::~ModuleLoader() = default; ModuleLoader::ModuleLoaderImpl::ModuleLoaderImpl() { auto builtInModulesSetter = [this](v8::Local context) { SetupRequire(context); SetupBuiltInModules(context); }; // Set up module loaders for each module type. _loaders = {{ nullptr, std::make_unique(builtInModulesSetter, _moduleCache, _bindingCache), std::make_unique(builtInModulesSetter, _moduleCache), std::make_unique(), std::make_unique(builtInModulesSetter) }}; } void ModuleLoader::ModuleLoaderImpl::Bootstrap() { // Set up top-level context. module_loader_helpers::SetupTopLevelContext(); auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); // 'require' needs to be available in top-level context before core-module is loaded. SetupRequire(context); // Initialize core modules listed in core-modules.h. INITIALIZE_CORE_MODULES(LoadBinaryCoreModule) NAPA_DEBUG("ModuleLoader", "Binary core modules are loaded."); // Set up built-in modules from binaries. SetupBuiltInModules(context); // Load core module information from 'core-modules.json'. auto coreModuleInfos = module_loader_helpers::ReadCoreModulesJson(); for (auto& info : coreModuleInfos) { _resolver.SetAsCoreModule(info.name.c_str()); if (info.isBuiltIn) { _builtInNames.emplace(std::move(info.name)); } } auto& coreModuleLoader = _loaders[static_cast(ModuleType::CORE)]; // Override built-in modules with javascript file if exists. for (const auto& name : _builtInNames) { v8::Local module; if (coreModuleLoader->TryGet(name, v8::Local(), module)) { // If javascript core module exists, replace the existing one. _moduleCache.Upsert(name, module); (void)context->Global()->Set(context, v8_helpers::MakeV8String(isolate, name), module); } } NAPA_DEBUG("ModuleLoader", "JavaScript core modules are loaded."); } void ModuleLoader::ModuleLoaderImpl::RequireCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 || args.Length() == 2 || args[0]->IsString(), "Invalid arguments"); auto moduleLoader = reinterpret_cast(zone::WorkerContext::Get(zone::WorkerContextItem::MODULE_LOADER)); JS_ENSURE(isolate, moduleLoader != nullptr, "Module loader is not initialized"); v8::String::Utf8Value path(args[0]); moduleLoader->_impl->RequireModule(*path, args); } void ModuleLoader::ModuleLoaderImpl::ResolveCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsString(), "Invalid arguments"); auto moduleLoader = reinterpret_cast(zone::WorkerContext::Get(zone::WorkerContextItem::MODULE_LOADER)); JS_ENSURE(isolate, moduleLoader != nullptr, "Module loader is not initialized"); v8::String::Utf8Value path(args[0]); auto contextDir = module_loader_helpers::GetCurrentContextDirectory(); auto moduleInfo = moduleLoader->_impl->_resolver.Resolve(*path, contextDir.c_str()); JS_ENSURE(isolate, moduleInfo.type != ModuleType::NONE, "Cannot find module \"%s\"", *path); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, moduleInfo.fullPath)); } void ModuleLoader::ModuleLoaderImpl::BindingCallback(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsString(), "Invalid arguments"); auto moduleLoader = reinterpret_cast(zone::WorkerContext::Get(zone::WorkerContextItem::MODULE_LOADER)); JS_ENSURE(isolate, moduleLoader != nullptr, "Module loader is not initialized"); v8::String::Utf8Value name(args[0]); v8::Local module; if (moduleLoader->_impl->_bindingCache.TryGet(*name, module)) { args.GetReturnValue().Set(module); return; } LOG_WARNING("ModuleLoader", "process.binding for \"%s\" does not exist.", *name); args.GetReturnValue().SetUndefined(); } void ModuleLoader::ModuleLoaderImpl::RequireModule(const char* path, const v8::FunctionCallbackInfo& args) { if (path == nullptr) { args.GetReturnValue().SetUndefined(); return; } auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); // Set optional argument for module file loader. auto arg = args.Length() == 1 ? v8::Local() : args[1]; bool fromContent = !arg.IsEmpty() && arg->IsString(); // If require is called with a module receiver, use module.filename to deduce context directory. std::string contextDir; if (!args.Holder().IsEmpty()) { contextDir = module_loader_helpers::GetModuleDirectory(args.Holder()); } if (contextDir.empty()) { contextDir = module_loader_helpers::GetCurrentContextDirectory(); } ModuleInfo moduleInfo; if (fromContent) { moduleInfo = ModuleInfo { ModuleType::JAVASCRIPT, (filesystem::Path(contextDir) / path).Normalize().String(), // Module id "" // No package.json }; } else { moduleInfo = _resolver.Resolve(path, contextDir.c_str()); JS_ENSURE(isolate, moduleInfo.type != ModuleType::NONE, "Cannot resolve module path \"%s\"", path); } v8::Local module; // Module from content script is not cached. if (!fromContent) { if (_moduleCache.TryGet(moduleInfo.fullPath, module)) { NAPA_DEBUG("ModuleLoader", "Retrieved module from cache: \"%s\".", path); args.GetReturnValue().Set(module); return; } } auto& loader = _loaders[static_cast(moduleInfo.type)]; JS_ENSURE(isolate, loader != nullptr, "No proper module loader is defined"); auto succeeded = loader->TryGet(moduleInfo.fullPath, arg, module); if (!succeeded) { NAPA_DEBUG("ModuleLoader", "Cannot load module \"%s\".", path); // Exception has already been thrown upon loader failure. args.GetReturnValue().SetUndefined(); return; } NAPA_DEBUG("ModuleLoader", "Loaded module from file (first time): \"%s\".", path); if (!fromContent) { _moduleCache.Upsert(moduleInfo.fullPath, module); } args.GetReturnValue().Set(module); } void ModuleLoader::ModuleLoaderImpl::LoadBinaryCoreModule( const char* name, bool isBuiltInModule, const napa::module::ModuleInitializer& initializer) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); auto moduleContext = v8::Context::New(isolate); NAPA_ASSERT(!moduleContext.IsEmpty(), "Can't set up context for core modules"); // We set an empty security token so callee can access caller's context. moduleContext->SetSecurityToken(v8::Undefined(isolate)); v8::Context::Scope contextScope(moduleContext); module_loader_helpers::SetupModuleContext(context, moduleContext, module_loader_helpers::GetNapaDllPath()); // Put it into module resolver to prevent from resolving as user module. _resolver.SetAsCoreModule(name); auto module = module_loader_helpers::ExportModule(moduleContext->Global(), initializer); if (isBuiltInModule) { _builtInNames.emplace(name); // Put core module into cache. // This makes the same behavior with node.js, i.e. it must be loaded by 'require'. _moduleCache.Upsert(name, module); } else { // Put into binding cache. // It must be accessed by process.binding(), not by require(). _bindingCache.Upsert(name, module); } } void ModuleLoader::ModuleLoaderImpl::SetupBuiltInModules(v8::Local context) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); // Assume that built-in modules are already cached. for (const auto& name : _builtInNames) { v8::Local module; if (_moduleCache.TryGet(name, module)) { (void)context->Global()->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, name), module); } } // Can decorate a context after loading up built-in modules. DecorateBuiltInModules(context); } void ModuleLoader::ModuleLoaderImpl::SetupRequire(v8::Local context) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); // Set up require(). auto requireFunctionTemplate = v8::FunctionTemplate::New(isolate, RequireCallback); (void)context->Global()->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "require"), requireFunctionTemplate->GetFunction()); // Set up require.resolve(). auto require = context->Global()->Get(context, v8_helpers::MakeV8String(isolate, "require")).ToLocalChecked()->ToObject(); auto resolveFunctionTemplate = v8::FunctionTemplate::New(isolate, ResolveCallback); (void)require->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "resolve"), resolveFunctionTemplate->GetFunction()); } // If we have more decorations, move them out from this class. void ModuleLoader::ModuleLoaderImpl::DecorateBuiltInModules(v8::Local context) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); // Add process.binding() auto process = context->Global()->Get(context, v8_helpers::MakeV8String(isolate, "process")).ToLocalChecked()->ToObject(); JS_ENSURE(isolate, !process.IsEmpty(), "Process built-in module doesn't exist"); auto bindingFunctionTemplate = v8::FunctionTemplate::New(isolate, BindingCallback); (void)process->CreateDataProperty(context, v8_helpers::MakeV8String(isolate, "binding"), bindingFunctionTemplate->GetFunction()); } ================================================ FILE: src/module/loader/module-loader.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace module { /// /// It follows node.js's module resolution algorithm, https://nodejs.org/api/modules.html#modules_all_together except, /// - Napa module is created in thread-safe way. /// - Napa native module uses '.napa' extension instead of '.node'. /// class ModuleLoader { public: /// It creates a module loader. One thread can have only one module loader. static void CreateModuleLoader(); /// /// A helper macro to create a module loader instance at current thread. /// This must be called before calling 'require'. /// #define CREATE_MODULE_LOADER napa::module::ModuleLoader::CreateModuleLoader /// Non-copyable and Non-movable. ModuleLoader(const ModuleLoader&) = delete; ModuleLoader& operator=(const ModuleLoader&) = delete; ModuleLoader(ModuleLoader&&) = delete; ModuleLoader& operator=(ModuleLoader&&) = delete; private: /// Constructor. ModuleLoader(); /// Default destructor. ~ModuleLoader(); class ModuleLoaderImpl; std::unique_ptr _impl; }; } } ================================================ FILE: src/module/loader/module-resolver-cache.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "module-resolver-cache.h" #include #include using namespace napa; using namespace napa::module; struct ModuleResolverCacheKey { std::string _name; std::string _path; bool operator==(const ModuleResolverCacheKey& other) const { return _name == other._name && _path == other._path; } }; namespace std { template<> struct hash { std::size_t operator()(ModuleResolverCacheKey key) const { return std::hash{}(key._name) ^ std::hash{}(key._path); } }; } class ModuleResolverCache::ModuleResolverCacheImpl { public: ModuleInfo Lookup(const char* name, const char* path); void Insert(const char* name, const char* path, const ModuleInfo& moduleInfo); private: std::mutex _lock; std::unordered_map _resolvedModules; }; ModuleResolverCache::ModuleResolverCache() : _impl(std::make_unique()) {} ModuleResolverCache::~ModuleResolverCache() = default; ModuleInfo ModuleResolverCache::Lookup(const char* name, const char* path) { return _impl->Lookup(name, path); } void ModuleResolverCache::Insert(const char* name, const char* path, const ModuleInfo& moduleInfo) { _impl->Insert(name, path, moduleInfo); } ModuleInfo ModuleResolverCache::ModuleResolverCacheImpl::Lookup(const char* name, const char* path) { ModuleResolverCacheKey key{name, path}; std::lock_guard lock(_lock); auto result = _resolvedModules.find(key); if (result != _resolvedModules.end()) { return result->second; } return ModuleInfo{ModuleType::NONE, name, path}; } void ModuleResolverCache::ModuleResolverCacheImpl::Insert(const char* name, const char* path, const ModuleInfo& moduleInfo) { ModuleResolverCacheKey key{name, path}; std::lock_guard lock(_lock); _resolvedModules.emplace(std::make_pair(key, moduleInfo)); } ================================================ FILE: src/module/loader/module-resolver-cache.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "module-resolver.h" #include namespace napa { namespace module { /// /// The Module resolver cache helps to reduce the overhead when ModuleResolver /// is trying to traversal the file system. /// class ModuleResolverCache { public: /// Constructor. ModuleResolverCache(); /// Default destructor. ~ModuleResolverCache(); /// Non-copyable. ModuleResolverCache(const ModuleResolverCache&) = delete; ModuleResolverCache& operator=(const ModuleResolverCache&) = delete; /// Movable. ModuleResolverCache(ModuleResolverCache&&) = default; ModuleResolverCache& operator=(ModuleResolverCache&&) = default; /// Lookup the module info. /// Module name or path. /// Current context path. If nullptr, it'll be current path. /// Module resolution information. ModuleInfo Lookup(const char* name, const char* path); /// Insert the specific module info into the cache. /// Module name or path. /// Current context path. If nullptr, it'll be current path. void Insert(const char* name, const char* path, const ModuleInfo& moduleInfo); private: class ModuleResolverCacheImpl; std::unique_ptr _impl; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: src/module/loader/module-resolver.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "module-resolver.h" #include "module-resolver-cache.h" #include #include #include #include #include #include #include #include #include #include using namespace napa; using namespace napa::module; namespace { const std::string NAPA_MODULE_EXTENSION = ".napa"; const std::string JAVASCRIPT_MODULE_EXTENSION = ".js"; const std::string JSON_OBJECT_EXTENSION = ".json"; } // End of anonymous namespace. class ModuleResolver::ModuleResolverImpl { public: /// Constructor. ModuleResolverImpl(); /// It resolves a full module path from a given argument of require(). /// Module name or path. /// Current context path. /// Module resolution details. /// It also searches NODE_PATH directories. ModuleInfo Resolve(const char* name, const char* path); /// It registers core modules, so they are resolved first. /// Module name. /// /// True if it successfully adds a module. /// If it fails or there is a duplication, return false. /// bool SetAsCoreModule(const char* name); private: /// It resolves a full module path from a given argument of require(). /// Module name or path. /// Base path. /// Module resolution details. ModuleInfo ResolveFromPath(const filesystem::Path& name, const filesystem::Path& path); /// It resolves a full module path from 'NODE_PATH'. /// Module name or path. /// Base path. /// Module resolution details. ModuleInfo ResolveFromEnv(const filesystem::Path& name, const filesystem::Path& path); /// It checks whether a module exists or not. /// Module name. /// True if a module exists at registry. bool IsCoreModule(const std::string& module); /// It loads module as a file. /// Module name. /// Base path. /// Path to package.json. /// Module resolution details. ModuleInfo LoadAsFile(const filesystem::Path& name, const filesystem::Path& path); /// It loads module as a directory. /// Module path. /// Base path. /// Module resolution details. ModuleInfo LoadAsDirectory(const filesystem::Path& name, const filesystem::Path& path); /// It loads module from node_modules directories. /// Module name or path. /// Base path. /// Module resolution details. ModuleInfo LoadNodeModules(const filesystem::Path& name, const filesystem::Path& path); /// It returns all possible node module paths. /// Base path. /// List of possible node_modules paths. std::vector GetNodeModulesPaths(const filesystem::Path& path); /// It tries the extension candidates. /// Possible module path. /// Module resolution details. /// "path" argument is changed. ModuleInfo TryExtensions(const filesystem::Path& path); /// It checks whether a path is relative. /// Path. /// True if a path is relative. bool IsExplicitRelativePath(const filesystem::Path& path) const; /// Registered modules. std::unordered_set _coreModules; /// Paths in 'NODE_PATH' environment variable. std::vector _nodePaths; /// Module info cache for all loaded modules. ModuleResolverCache _cache; }; ModuleResolver::ModuleResolver() : _impl(std::make_unique()) {} ModuleResolver::~ModuleResolver() = default; ModuleInfo ModuleResolver::Resolve(const char* name, const char* path) { return _impl->Resolve(name, path); } bool ModuleResolver::SetAsCoreModule(const char* name) { return _impl->SetAsCoreModule(name); } ModuleResolver::ModuleResolverImpl::ModuleResolverImpl() { auto envPath = platform::GetEnv("NODE_PATH"); if (!envPath.empty()) { std::vector nodePaths; utils::string::Split(envPath, nodePaths, std::string(platform::ENV_DELIMITER)); for (auto& nodePath : nodePaths) { if (filesystem::IsDirectory(nodePath)) { _nodePaths.emplace_back(std::move(nodePath)); } } } } #define RETURN_IF_NOT_EMPTY(run) \ do { \ auto result = run; \ if (result.type != ModuleType::NONE) { \ return result; \ } \ } while (false); ModuleInfo ModuleResolver::ModuleResolverImpl::Resolve(const char* name, const char* path) { // If name is a core modules, return it. if (IsCoreModule(name)) { return ModuleInfo{ModuleType::CORE, std::string(name), std::string()}; } // Normalize module context path. filesystem::Path basePath = (path == nullptr) ? filesystem::CurrentDirectory() : filesystem::Path(path); // Lookup for module info cache. RETURN_IF_NOT_EMPTY(_cache.Lookup(name, basePath.c_str())); // Look up from the given path. ModuleInfo moduleInfo = ResolveFromPath(name, basePath); if (moduleInfo.type != ModuleType::NONE) { _cache.Insert(name, basePath.c_str(), moduleInfo); return moduleInfo; } // Look up NODE_PATH moduleInfo = ResolveFromEnv(name, basePath); if (moduleInfo.type != ModuleType::NONE) { _cache.Insert(name, basePath.c_str(), moduleInfo); } return moduleInfo; } bool ModuleResolver::ModuleResolverImpl::SetAsCoreModule(const char* name) { auto result = _coreModules.emplace(name); return result.second; } ModuleInfo ModuleResolver::ModuleResolverImpl::ResolveFromPath(const filesystem::Path& name, const filesystem::Path& path) { // If name begins with './' or '/' or '../', if (IsExplicitRelativePath(name) || name.IsAbsolute()) { // Load as a file (path + name). RETURN_IF_NOT_EMPTY(LoadAsFile(name, path)); // Load as a directory (path + name) RETURN_IF_NOT_EMPTY(LoadAsDirectory(name, path)); } // Load node_modules. return LoadNodeModules(name, path); } ModuleInfo ModuleResolver::ModuleResolverImpl::ResolveFromEnv(const filesystem::Path& name, const filesystem::Path& path) { for (const auto& nodePath : _nodePaths) { if (nodePath == path.String()) { continue; } RETURN_IF_NOT_EMPTY(ResolveFromPath(name, nodePath.c_str())); } return ModuleInfo{ModuleType::NONE, std::string(), std::string()}; } bool ModuleResolver::ModuleResolverImpl::IsCoreModule(const std::string& module) { return _coreModules.find(module) != _coreModules.end(); } ModuleInfo ModuleResolver::ModuleResolverImpl::LoadAsFile(const filesystem::Path& name, const filesystem::Path& path) { auto fullPath = (path / name).Normalize(); if (filesystem::IsRegularFile(fullPath)) { ModuleType type = ModuleType::JAVASCRIPT; auto extension = fullPath.Extension().String(); if (extension == JSON_OBJECT_EXTENSION) { type = ModuleType::JSON; } else if (extension == NAPA_MODULE_EXTENSION) { type = ModuleType::NAPA; } return ModuleInfo{type, fullPath.String(), std::string()}; } return TryExtensions(fullPath); } ModuleInfo ModuleResolver::ModuleResolverImpl::LoadAsDirectory(const filesystem::Path& name, const filesystem::Path& path) { auto fullPath = (path / name).Normalize(); auto packageJson = fullPath / "package.json"; if (filesystem::IsRegularFile(packageJson)) { rapidjson::Document package; try { std::ifstream ifs(packageJson.String()); rapidjson::IStreamWrapper isw(ifs); if (package.ParseStream(isw).HasParseError()) { throw std::runtime_error(rapidjson::GetParseError_En(package.GetParseError())); } if (package.HasMember("main")) { filesystem::Path mainPath(package["main"].GetString()); mainPath.Normalize(); auto moduleInfo = LoadAsFile(mainPath, fullPath); if (moduleInfo.type != ModuleType::NONE) { moduleInfo.packageJsonPath = packageJson.String(); return moduleInfo; } } } catch (...) {} // ignore exception and continue. } fullPath = fullPath / "index"; return TryExtensions(fullPath); } ModuleInfo ModuleResolver::ModuleResolverImpl::LoadNodeModules(const filesystem::Path& name, const filesystem::Path& path) { auto modulePaths = GetNodeModulesPaths(path); for (const auto& modulePath :modulePaths) { // Load as a file (path + name). RETURN_IF_NOT_EMPTY(LoadAsFile(name, modulePath)); // Load as a directory (path + name) RETURN_IF_NOT_EMPTY(LoadAsDirectory(name, modulePath)); } return ModuleInfo{ModuleType::NONE, std::string(), std::string()}; } std::vector ModuleResolver::ModuleResolverImpl::GetNodeModulesPaths(const filesystem::Path& path) { std::vector subpaths; subpaths.reserve(256); for (filesystem::Path subpath(path); !subpath.IsEmpty(); subpath = subpath.Parent().Normalize()) { if (subpath.Filename().String() == "node_modules") { continue; } auto modulePath = subpath / "node_modules"; if (filesystem::IsDirectory(modulePath)) { subpaths.emplace_back(modulePath.String()); } } return subpaths; } ModuleInfo ModuleResolver::ModuleResolverImpl::TryExtensions(const filesystem::Path& path) { std::ostringstream oss; oss << path.String() << JAVASCRIPT_MODULE_EXTENSION; auto modulePath = filesystem::Path(oss.str()); if (filesystem::IsRegularFile(modulePath)) { return ModuleInfo{ModuleType::JAVASCRIPT, modulePath.String(), std::string()}; } modulePath.ReplaceExtension(JSON_OBJECT_EXTENSION); if (filesystem::IsRegularFile(modulePath)) { return ModuleInfo{ModuleType::JSON, modulePath.String(), std::string()}; } modulePath.ReplaceExtension(NAPA_MODULE_EXTENSION); if (filesystem::IsRegularFile(modulePath)) { return ModuleInfo{ModuleType::NAPA, modulePath.String(), std::string()}; } return ModuleInfo{ModuleType::NONE, std::string(), std::string()}; } bool ModuleResolver::ModuleResolverImpl::IsExplicitRelativePath(const filesystem::Path& path) const { // Start with "." or ".." return !path.IsEmpty() && path.String()[0] == '.'; } ================================================ FILE: src/module/loader/module-resolver.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace module { enum class ModuleType : size_t { /// Module is not resolved correctly. NONE, /// Built-in or core module. CORE, /// Javascript module. JAVASCRIPT, /// Module with json object. JSON, /// Binary module. NAPA, /// End of module type. END_OF_MODULE_TYPE }; /// Module's detail information acquired at path resolution. struct ModuleInfo { /// Module type. ModuleType type; /// Full path. std::string fullPath; /// Package.json path. std::string packageJsonPath; }; /// /// It resolves a module path by the algorithm described at /// https://nodejs.org/api/modules.html#modules_all_together. /// One module loader has one module resolver, so each thread has its own instance of this class. /// class ModuleResolver { public: /// Constructor. ModuleResolver(); /// Default destructor. ~ModuleResolver(); /// Non-copyable. ModuleResolver(const ModuleResolver&) = delete; ModuleResolver& operator=(const ModuleResolver&) = delete; /// Movable. ModuleResolver(ModuleResolver&&) = default; ModuleResolver& operator=(ModuleResolver&&) = default; /// It resolves a full module path from a given argument of require(). /// Module name or path. /// Current context path. If nullptr, it'll be current path. /// Module resolution information. ModuleInfo Resolve(const char* name, const char* path = nullptr); /// It registers built-in or core modules, so they are resolved first. /// Module name. /// /// True if it successfully adds a module. /// If it fails or there is a duplication, return false. /// bool SetAsCoreModule(const char* name); private: /// Implementation of module resolver. class ModuleResolverImpl; std::unique_ptr _impl; }; } // End of namespace module. } // End of namespace napa. ================================================ FILE: src/module/module.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include #include using namespace napa; /// /// Map from module's class name to persistent constructor object. /// To suppport multiple isolates, let each isolate has its own persistent constructor at thread local storage. /// typedef v8::Persistent> PersistentConstructor; struct ConstructorInfo { std::unordered_map constructorMap; }; /// It sets the persistent constructor at the current V8 isolate. /// Unique constructor name. It's recommended to use the same name as module. /// V8 persistent function to constructor V8 object. void napa::module::SetPersistentConstructor(const char* name, v8::Local constructor) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto constructorInfo = static_cast(zone::WorkerContext::Get(zone::WorkerContextItem::CONSTRUCTOR)); if (constructorInfo == nullptr) { constructorInfo = new ConstructorInfo(); zone::WorkerContext::Set(zone::WorkerContextItem::CONSTRUCTOR, constructorInfo); } constructorInfo->constructorMap.emplace(std::piecewise_construct, std::forward_as_tuple(name), std::forward_as_tuple(isolate, constructor)); } /// It gets the given persistent constructor from the current V8 isolate. /// Unique constructor name given at SetPersistentConstructor() call. /// V8 local function object. v8::Local napa::module::GetPersistentConstructor(const char* name) { auto isolate = v8::Isolate::GetCurrent(); v8::EscapableHandleScope scope(isolate); auto constructorInfo = static_cast(zone::WorkerContext::Get(zone::WorkerContextItem::CONSTRUCTOR)); if (constructorInfo == nullptr) { return scope.Escape(v8::Local()); } auto iter = constructorInfo->constructorMap.find(name); if (iter != constructorInfo->constructorMap.end()) { auto constructor = v8::Local::New(isolate, iter->second); return scope.Escape(constructor); } else { return scope.Escape(v8::Local()); } } ================================================ FILE: src/platform/dll.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include #ifdef SUPPORT_POSIX #include #else #pragma push_macro("NOMINMAX") #define NOMINMAX #include #pragma pop_macro("NOMINMAX") #endif #include namespace napa { namespace dll { SharedLibrary::SharedLibrary(const std::string& dllFileName) { #ifdef SUPPORT_POSIX _module = dlopen(dllFileName.c_str(), RTLD_LAZY); #else _module = ::LoadLibraryA(dllFileName.c_str()); #endif if (_module == nullptr) { throw std::runtime_error(dllFileName + " cannot be loaded."); } } SharedLibrary::~SharedLibrary() { #ifdef SUPPORT_POSIX dlclose(_module); #else ::FreeLibrary(static_cast(_module)); #endif } void* SharedLibrary::ImportImpl(const std::string& symbolName) { #ifdef SUPPORT_POSIX return reinterpret_cast(dlsym(_module, symbolName.c_str())); #else return reinterpret_cast(::GetProcAddress(static_cast(_module), symbolName.c_str())); #endif } std::string ThisLineLocation() { void* symbolAddress = reinterpret_cast(&ThisLineLocation); #ifdef SUPPORT_POSIX Dl_info info; auto result = dladdr(symbolAddress, &info); if (result == 0) { return ""; } return info.dli_fname; #else MEMORY_BASIC_INFORMATION mbi; auto size = ::VirtualQuery(symbolAddress, &mbi, sizeof(mbi)); if (size == 0) { return ""; } static constexpr size_t DEFAULT_PATH_SIZE = 1024; char path[DEFAULT_PATH_SIZE]; auto len = ::GetModuleFileNameA(reinterpret_cast(mbi.AllocationBase), path, sizeof(path)); if (len == 0) { return ""; } // Currently we do not fully support UNC prefix in Napa.js. // // The following is a workaround to fix inconsistency of __dirname and __filename // between Napa.js and Node.js // // https://github.com/Microsoft/napajs/issues/131 // Should remove this code when fixing Issue #131. if (len > 4 && filesystem::Path(path).HasUncPrefix()) { return path + 4; } return path; #endif } } } ================================================ FILE: src/platform/dll.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace dll { /// Cross-platform shared library access. class SharedLibrary { public: SharedLibrary(const std::string& dllFileName); virtual ~SharedLibrary(); template T* Import(const std::string& symbolName) { return reinterpret_cast(ImportImpl(symbolName)); } private: void* ImportImpl(const std::string& symbolName); void* _module; }; /// Returns the path of the dll including this line of code. std::string ThisLineLocation(); } } ================================================ FILE: src/platform/filesystem.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include "utils/string.h" #include #include #include #include #include #include #ifdef OS_MAC #include #endif namespace napa { namespace filesystem { using CharType = Path::CharType; using StringType = Path::StringType; namespace { /// Tell if a character is path separator. bool IsSeparator(char ch) { #ifdef SUPPORT_POSIX return ch == '/'; #else return ch == '/' || ch == '\\'; #endif } /// Tell if a character is a letter. bool IsLetter(char ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); } /// Tell if it's a root of Windows path. bool IsWindowsRootSegment(const StringType& segment) { return segment.size() == 2 && IsLetter(segment[0]) && segment[1] == ':'; } /// Has UNC prefix "\\\\?" in windows. bool ParseUncPrefix(const std::string& path) { if (path.size() < 4) { return false; } return path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\'; } /// Parse driver spec. std::string ParseDriveSpec(const std::string& path, bool* hasUncPrefix = nullptr) { StringType::size_type driveSpecStart = 0; auto unc = ParseUncPrefix(path); if (unc) { driveSpecStart = 4; } if (path.size() < driveSpecStart + 2 || !IsLetter(path[driveSpecStart]) || path[driveSpecStart + 1] != ':') { return ""; } if (hasUncPrefix != nullptr) { *hasUncPrefix = unc; } return path.substr(driveSpecStart, 2); } /// Resolve path string into segments. void Parse( const StringType& path, bool* hasUncPrefix, StringType* driveSpec, bool* isAbsolute, std::vector* segments) { StringType::size_type pathStart = 0; bool unc = false; auto drive = ParseDriveSpec(path, &unc); if (unc) { pathStart += 4; } if (!drive.empty()) { pathStart += 2; } auto absolute = path.size() > pathStart && IsSeparator(path[pathStart]); if (pathStart < path.size() && segments != nullptr) { std::vector segs; std::deque resolved; utils::string::Split(path.begin() + pathStart, path.end(), segs, StringType("\\/"), true); for (auto it = segs.begin(); it < segs.end(); ++it) { auto& seg = *it; if (seg == "..") { // Backtrace '..'. if (resolved.empty() || resolved.back() == "..") { resolved.push_back(seg); } else { resolved.pop_back(); } } else if (seg != ".") { // Skip '.' resolved.push_back(seg); } } segments->reserve(resolved.size()); for (auto it = resolved.begin(); it != resolved.end(); ++it) { segments->emplace_back(std::move(*it)); } } if (hasUncPrefix != nullptr) { *hasUncPrefix = unc; } if (driveSpec != nullptr) { *driveSpec = drive; } if (isAbsolute != nullptr) { *isAbsolute = absolute; } } /// Format path name from path meta-data. std::string FormatPathString( bool hasUncPrefix, const std::string& driveSpec, bool isAbsolute, const std::vector& segments, const CharType* seperator = platform::DIR_SEPARATOR) { // Output normalized string. std::stringstream ss; if (hasUncPrefix) { ss << "\\\\?\\"; } // Drive spec could be empty. ss << driveSpec; if (isAbsolute) { ss << platform::DIR_SEPARATOR; } for (auto it = segments.begin(); it != segments.end(); ++it) { if (it != segments.begin()) { ss << seperator; } ss << *it; } auto path = ss.str(); if (path.size() == 0) { return "."; } return path; } } Path& Path::operator=(const CharType* path) { _pathname = path; return *this; } Path& Path::operator=(const StringType& path) { _pathname = path; return *this; } Path& Path::operator=(const Path& path) { _pathname = path._pathname; return *this; } Path& Path::operator=(StringType&& path) { _pathname = std::move(path); return *this; } Path& Path::operator=(Path&& path) { _pathname = std::move(path._pathname); return *this; } Path& Path::operator/=(const Path& path) { return Append(path); } Path Path::operator/(const Path& path) const { Path tmp(*this); tmp /= path; return tmp; } int Path::Compare(const Path& path) const { return _pathname.compare(path._pathname); } /// Get unnormalized path string. /// Use GenericForm() to always use '/' as delimiter. const std::string& Path::String() const { return _pathname; } /// Get unnormalized path string buffer. const CharType* Path::c_str() const { return _pathname.c_str(); } Path& Path::Append(const Path& path) { if (path.IsAbsolute()) { #ifdef _WIN32 auto drive = path.DriveSpec(); auto baseDrive = DriveSpec(); if (!path.HasUncPrefix() && drive.IsEmpty()) { _pathname = baseDrive.String() + path._pathname; return *this; } #endif _pathname = path._pathname; } else { if (!path._pathname.empty()) { if (!IsSeparator(path._pathname[0])) { _pathname += platform::DIR_SEPARATOR; } _pathname += path._pathname; } } return *this; } Path& Path::Normalize() { if (!_pathname.empty()) { std::string driveSpec; bool hasUncPrefix = false; bool isAbsolute = false; std::vector segs; Parse(_pathname, &hasUncPrefix, &driveSpec, &isAbsolute, &segs); // No parent path. if (isAbsolute && segs.size() > 0 && segs[0] == "..") { _pathname = ""; } else { _pathname = FormatPathString(hasUncPrefix, driveSpec, isAbsolute, segs, platform::DIR_SEPARATOR); } } return *this; } Path& Path::ReplaceExtension(const std::string& extension) { auto pos = _pathname.find_last_of("./\\"); if (pos != StringType::npos && pos != _pathname.size() - 1 && _pathname[pos] == '.') { _pathname.replace(pos, _pathname.size(), extension); } return *this; } Path Path::GenericForm() const { if (HasUncPrefix()) { return *this; } Path path(*this); path.Normalize(); #ifdef _WIN32 std::replace(path._pathname.begin(), path._pathname.end(), '\\', '/'); #endif return path; } Path Path::DriveSpec() const { return Path(ParseDriveSpec(_pathname)); } Path Path::Parent() const { if (IsEmpty()) { return Path(); } return *this / ".."; } Path Path::Dirname() const { if (IsEmpty()) { return Path("."); } auto pos = _pathname.find_last_of("/\\"); if (pos == StringType::npos) { // relative path, return '.' as directory name. return Path("."); } return Path(_pathname.substr(0, pos)); } Path Path::Extension() const { auto pos = _pathname.find_last_of("./\\"); if (pos == StringType::npos || pos == _pathname.size() - 1 || _pathname[pos] != '.') { return Path(); } return Path(_pathname.substr(pos)); } Path Path::Basename() const { if (IsEmpty() || IsFilenameDot() || IsFilenameDotDot()) { return Path(); } auto fileStart = _pathname.find_last_of("/\\"); if (fileStart == StringType::npos) { fileStart = 0; } auto dotStart = _pathname.find_last_of("."); if (dotStart == StringType::npos || dotStart < fileStart) { return Path(_pathname.substr(fileStart + 1)); } return Path(_pathname.substr(fileStart + 1, dotStart - fileStart - 1)); } Path Path::Filename() const { if (IsEmpty()) { return Path(); } auto pos = _pathname.find_last_of("/\\"); if (pos == StringType::npos) { // relative path, return the whole path as filename. return *this; } return Path(_pathname.substr(pos + 1)); } bool Path::HasUncPrefix() const { return ParseUncPrefix(_pathname); } bool Path::IsAbsolute() const { if (IsEmpty()) { return false; } StringType::size_type pathStart = 0; if (HasUncPrefix()) { pathStart += 4; } bool hasDriveSpec = HasDriveSpec(); if (hasDriveSpec) { pathStart += 2; } if (pathStart >= _pathname.size()) { return false; } return IsSeparator(_pathname[pathStart]); } Path Path::Relative(const Path& base) const { Path current = Absolute().Normalize(); Path other = base.Absolute().Normalize(); if (current.IsEmpty() || other.IsEmpty()) { return Path(); } std::string driveSpecCurrent; std::string driveSpecOther; std::vector segsCurrent; std::vector segsOther; Parse(current._pathname, nullptr, &driveSpecCurrent, nullptr, &segsCurrent); Parse(other._pathname, nullptr, &driveSpecOther, nullptr, &segsOther); // Return current path if base is from different drive spec. if (utils::string::ToLowerCopy(driveSpecCurrent) != utils::string::ToLowerCopy(driveSpecOther)) { return current; } StringType::size_type same = 0; for (; same < segsCurrent.size() && same < segsOther.size(); ++same) { if (utils::string::ToLowerCopy(segsCurrent[same]) != utils::string::ToLowerCopy(segsOther[same])) { break; } } std::stringstream ss; for (size_t i = 0; i < segsOther.size() - same; ++i) { ss << ".." << platform::DIR_SEPARATOR; } for (size_t i = same; i < segsCurrent.size(); ++i) { ss << segsCurrent[i]; if (i != segsCurrent.size() - 1) { ss << platform::DIR_SEPARATOR; } } return Path(ss.str()).RemoveTrailingSeparator(); } Path Path::Absolute() const { if (IsEmpty()) { return Path(); } return CurrentDirectory() / *this; } Path& Path::RemoveTrailingSeparator() { if (_pathname.size() > 0 && IsSeparator(_pathname[_pathname.size() - 1])) { _pathname.erase(_pathname.size() - 1); } return *this; } std::ostream& operator<<(std::ostream& stream, const Path& path) { stream << path.String(); return stream; } bool operator==(const Path& lhs, const Path& rhs) { return lhs.Compare(rhs) == 0; } bool operator!=(const Path& lhs, const Path& rhs) { return lhs.Compare(rhs) != 0; } bool operator<(const Path& lhs, const Path& rhs) { return lhs.Compare(rhs) < 0; } Path CurrentDirectory() { static constexpr size_t DEFAULT_PATH_SIZE = 1024; char path[DEFAULT_PATH_SIZE]; #ifdef SUPPORT_POSIX auto result = getcwd(path, sizeof(path)); if (result == nullptr) { return Path(); } #else DWORD result = ::GetCurrentDirectoryA(sizeof(path), path); if (result == 0) { return Path(); } #endif return Path(path); } bool SetCurrentDirectory(const Path& path) { #ifdef SUPPORT_POSIX return ::chdir(path.c_str()) == 0; #else return ::SetCurrentDirectoryA(path.c_str()) == TRUE; #endif } Path ProgramPath() { static constexpr size_t DEFAULT_PATH_SIZE = 1024; char path[DEFAULT_PATH_SIZE] = { '\0' }; #if defined(OS_MAC) uint32_t size = sizeof(path); if (_NSGetExecutablePath(path, &size) == 0) { return path; } #elif defined(OS_WINDOWS) if (::GetModuleFileNameA(NULL, path, sizeof(path)) > 0) { return path; } #elif defined(SUPPORT_POSIX) if (::readlink("/proc/self/exe", path, sizeof(path)) > 0 || ::readlink("/proc/curproc/file", path, sizeof(path)) > 0 || ::readlink("/proc/self/path/a.out", path, sizeof(path))) { return path; } #else static_assert(false, "Unsupported OS";) #endif return Path(); } bool Exists(const Path& path) { #ifdef SUPPORT_POSIX struct stat st; return ::stat(path.c_str(), &st) == 0; #else auto attribute = GetFileAttributesA(path.c_str()); return attribute != INVALID_FILE_ATTRIBUTES; #endif } bool IsRegularFile(const Path& path) { #ifdef SUPPORT_POSIX struct stat st; return ::stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode); #else // We don't deal with symlink for now. auto attribute = GetFileAttributesA(path.c_str()); return attribute != INVALID_FILE_ATTRIBUTES && !(attribute & FILE_ATTRIBUTE_DIRECTORY); #endif } bool IsDirectory(const Path& path) { #ifdef SUPPORT_POSIX struct stat st; return ::stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode); #else // We don't deal with symlink for now. auto attribute = GetFileAttributesA(path.c_str()); return attribute != INVALID_FILE_ATTRIBUTES && attribute & FILE_ATTRIBUTE_DIRECTORY; #endif } bool MakeDirectory(const Path& path) { #ifdef SUPPORT_POSIX return ::mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0 || errno == EEXIST; #else return ::CreateDirectoryA(path.c_str(), 0) == TRUE || ::GetLastError() == ERROR_ALREADY_EXISTS; #endif } bool MakeDirectories(const Path& path) { if (path.IsEmpty()) { return false; } if (IsDirectory(path)) { return true; } auto parent = path.Parent().Normalize(); if (!IsDirectory(parent) && !MakeDirectories(parent)) { return false; } return MakeDirectory(path); } PathIterator::PathIterator(Path path) : _base(std::move(path)) { #ifdef SUPPORT_POSIX _dir = ::opendir(_base.c_str()); #else _first = true; WIN32_FIND_DATAA findData; std::string pattern = _base.String() + "\\*"; _findHandle = ::FindFirstFileA(pattern.c_str(), &findData); if (_findHandle != INVALID_HANDLE_VALUE) { _currentPath = _base / findData.cFileName; } #endif } PathIterator::~PathIterator() { #ifdef SUPPORT_POSIX if (_dir != nullptr) { (void)closedir(_dir); } #else if (_findHandle != INVALID_HANDLE_VALUE) { (void)::FindClose(_findHandle); } #endif } const Path& PathIterator::operator*() const { return _currentPath; } const Path* PathIterator::operator->() const { return &_currentPath; } bool PathIterator::Next() { #ifdef SUPPORT_POSIX if (_dir == nullptr) { return false; } struct dirent *dp; dp = ::readdir(_dir); if (dp == nullptr) { return false; } _currentPath = _base / dp->d_name; #else if (_findHandle == INVALID_HANDLE_VALUE) { return false; } if (_first) { _first = false; } else { WIN32_FIND_DATAA findData; if (::FindNextFileA(_findHandle, &findData) == FALSE) { return false; } _currentPath = _base / findData.cFileName; } #endif if (_currentPath.IsFilenameDot() || _currentPath.IsFilenameDotDot()) { // Don't include '.' or '..' as files in a directory. return Next(); } return true; } } } ================================================ FILE: src/platform/filesystem.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #ifdef SUPPORT_POSIX #include #include #include #include #include #else #pragma push_macro("NOMINMAX") #define NOMINMAX #include #pragma pop_macro("NOMINMAX") #endif namespace napa { namespace filesystem { /// Helper class for path manipulation. class Path { public: // We only support ANSI path for now. // TODO: consider use wchar_t for Windows. typedef char CharType; typedef std::basic_string StringType; Path() {} Path(const CharType* path) : _pathname(path) {} Path(const StringType& path) : _pathname(path) {} Path(StringType&& path) : _pathname(std::move(path)) {} Path(const Path& path) : _pathname(path._pathname) {} Path(Path&& path) : _pathname(std::move(path._pathname)) {} Path& operator=(const CharType* path); Path& operator=(const StringType& path); Path& operator=(const Path& path); Path& operator=(StringType&& path); Path& operator=(Path&& path); /// Operator to append a relative path. Path& operator/=(const Path& path); /// Operator to append a relative path. Path operator/(const Path& path) const; /// Compare two paths. int Compare(const Path& path) const; /// Get unnormalized path string. /// Use GenericForm() to always use '/' as delimiter. const std::string& String() const; /// Get unnormalized path string buffer. const CharType* c_str() const; /// /// Append a path to current path /// Or return the path if it's absolute. /// Path& Append(const Path& path); /// Normalize current path. Path& Normalize(); /// Replace extension. Path& ReplaceExtension(const std::string& extension); /// Get normalized generic form of path string. using '/' as separator. Path GenericForm() const; /// Drive spec. Path DriveSpec() const; /// Get parent path. (normalized) Path Parent() const; /// Return directory name. Path Dirname() const; /// Return file extension of current path. Path Extension() const; /// Return base file name without extension. Path Basename() const; /// Return file name of current path. Path Filename() const; /// Has UNC prefix \\? (Windows). bool HasUncPrefix() const; /// Has drive specification like c: (Windows). bool HasDriveSpec() const { return !DriveSpec().IsEmpty(); } /// Tell if path has file name. bool HasFilename() const { return !Filename().IsEmpty(); } /// Tell if path has extension. bool HasExtension() const { return !Extension().IsEmpty(); } /// Tell if current path is empty. bool IsEmpty() const { return _pathname.empty(); } /// Tell if current path is an absolute path. bool IsAbsolute() const; /// Tell if current path is relative or not. bool IsRelative() const { return !IsAbsolute(); } /// Tell if current file name is '.'. bool IsFilenameDot() const { return Filename().String() == "."; } /// Tell if current file name is '..'. bool IsFilenameDotDot() const { return Filename().String() == ".."; } /// Get relative path to a base. Path Relative(const Path& base) const; /// Get normalized absolute path. Path Absolute() const; private: /// Remove one trailing separator. Path& RemoveTrailingSeparator(); /// Unnormalized path. StringType _pathname; }; /// Support outputing to ostream. std::ostream& operator<<(std::ostream& stream, const Path& path); bool operator==(const Path& lhs, const Path& rhs); bool operator!=(const Path& lhs, const Path& rhs); bool operator<(const Path& lhs, const Path& rhs); /// Get current working directory. Path CurrentDirectory(); /// Set current working directory. bool SetCurrentDirectory(const Path& path); /// Get path of current process. Path ProgramPath(); /// Tell if a path exists (either a regular file or directory). bool Exists(const Path& path); /// Tell if a path is a regular file. bool IsRegularFile(const Path& path); /// Tell if a path is a directory. bool IsDirectory(const Path& path); /// Make a directory. /// True if succeed or directory already exists, false if operation failed. bool MakeDirectory(const Path& path); /// Make directories recursively. bool MakeDirectories(const Path& path); /// Path iterator class PathIterator { public: PathIterator(Path path); ~PathIterator(); /// Operator to get the path reference. const Path& operator*() const; /// Operator to get the path pointer. const Path* operator->() const; /// Move to next file, or return false. /// Always call Next() after construction. bool Next(); private: #ifdef SUPPORT_POSIX DIR* _dir; #else HANDLE _findHandle; bool _first; #endif Path _base; Path _currentPath; }; } } ================================================ FILE: src/platform/os.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #ifdef SUPPORT_POSIX #include #endif #include namespace napa { namespace platform { const char* GetOSType() { #ifdef SUPPORT_POSIX static struct utsname info; if (uname(&info) < 0) { throw std::runtime_error("Error getting uname"); } return info.sysname; #else return "Windows_NT"; #endif } #ifdef SUPPORT_POSIX const char* ENV_DELIMITER = ":"; #else const char* ENV_DELIMITER = ";"; #endif #ifdef SUPPORT_POSIX const char* DIR_SEPARATOR = "/"; #else const char* DIR_SEPARATOR = "\\"; #endif } } ================================================ FILE: src/platform/os.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once namespace napa { namespace platform { /// Get OS type. const char* GetOSType(); /// Environment variables delimiter. extern const char* ENV_DELIMITER; /// Directory separator. extern const char* DIR_SEPARATOR; } } ================================================ FILE: src/platform/platform.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) #ifndef SUPPORT_WINDOWS #define SUPPORT_WINDOWS #endif #else #ifndef SUPPORT_POSIX #define SUPPORT_POSIX #endif #endif #if defined(__linux) || defined(__linux__) || defined(linux) #define OS_LINUX #elif defined(__APPLE__) #define OS_MAC #elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) #define OS_WINDOWS #elif defined(__FreeBSD__) || defined(__FreeBSD) #define OS_FREEBSD #else // Unknown OS #endif namespace napa { namespace platform { /// Platform. #if defined(OS_LINUX) constexpr const char* PLATFORM = "linux"; #elif defined(OS_MAC) constexpr const char* PLATFORM = "darwin"; #elif defined(OS_WINDOWS) constexpr const char* PLATFORM = "win32"; #elif defined(OS_FREEBSD) constexpr const char* PLATFORM = "freebsd"; #else constexpr const char* PLATFORM = "unknown"; #endif } // End of namespce platform. } // End of namespce napa. ================================================ FILE: src/platform/process.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #ifdef SUPPORT_POSIX #include #include #include #else #include #include #include #endif #include #include #include #include #include #include namespace napa { namespace platform { namespace { #ifdef SUPPORT_POSIX static size_t ReadBuffer(const char* filename, char* buffer, size_t length) { FILE* source = fopen(filename, "rb"); if (source == nullptr) { return 0; } std::unique_ptr> deferred(source, [](auto file) { fclose(file); }); return fread(buffer, 1, length, source); } struct CommandLineArgs { int argc; std::vector argv; CommandLineArgs() { size_t length = ReadBuffer("/proc/self/cmdline", commandLine, PATH_MAX); commandLine[length] = '\0'; ParseCommandLine(length); } private: void ParseCommandLine(size_t length) { argc = 0; argv.clear(); bool start = true; for (size_t i = 0; i < length; i++) { if (start && commandLine[i] != '\0') { start = false; argv.push_back(commandLine + i); argc++; } else if (commandLine[i] == '\0') { start = true; } } } char commandLine[PATH_MAX + 1]; }; static const CommandLineArgs& GetCommandLineArgs() { static CommandLineArgs commandLineArgs; return commandLineArgs; } #endif } int GetArgc() { #ifdef SUPPORT_POSIX return GetCommandLineArgs().argc; #else return __argc; #endif } /// Get the process arguments. char** GetArgv() { #ifdef SUPPORT_POSIX return const_cast(&GetCommandLineArgs().argv[0]); #else return __argv; #endif } bool SetEnv(const char* name, const char* value) { #ifdef SUPPORT_POSIX return 0 == setenv(name, value, 1); #else std::ostringstream oss; oss << name << "=" << value; return _putenv(oss.str().c_str()) == 0; #endif } std::string GetEnv(const char* name) { #ifdef SUPPORT_POSIX std::string value; char* buffer = getenv(name); if (buffer != nullptr) { value.assign(buffer); } return value; #else std::string value; char* buffer = nullptr; size_t size = 0; if (_dupenv_s(&buffer, &size, name) == 0 && buffer != nullptr) { value.assign(buffer, size - 1); free(buffer); } return value; #endif } int32_t Umask(int32_t mode) { #ifdef SUPPORT_POSIX return static_cast(umask(mode)); #else int32_t oldMask; if (_umask_s(mode, &oldMask)) { throw std::runtime_error("Error setting umask"); } return oldMask; #endif } int32_t Getpid() { #ifdef SUPPORT_POSIX return static_cast(getpid()); #else return _getpid(); #endif } int32_t Gettid() { #ifdef SUPPORT_POSIX return static_cast(syscall(SYS_gettid)); #else return static_cast(GetCurrentThreadId()); #endif } int32_t Isatty(int32_t fd) { #ifdef SUPPORT_POSIX return static_cast(isatty(fd)); #else return _isatty(fd); #endif } } } ================================================ FILE: src/platform/process.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace platform { /// Get the number of process arguments. int GetArgc(); /// Get the process arguments. char** GetArgv(); /// Set environment variable. bool SetEnv(const char* name, const char* value); /// Get environment variable. std::string GetEnv(const char* name); /// Set file permission mask at the current process. int32_t Umask(int32_t mode); /// Return pid. int32_t Getpid(); /// Return tid. int32_t Gettid(); /// Return nonzero value if a descriptor is associated with a character device. /// File descriptor. int32_t Isatty(int32_t fd); } } ================================================ FILE: src/platform/thread-local.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #ifdef SUPPORT_POSIX #include #else #include #endif namespace napa { namespace tls { /// Cross platform implementation for Tls. /// Use thread_local once major clang versions supports it. template class ThreadLocal { public: ThreadLocal() { #ifdef SUPPORT_POSIX auto result = pthread_key_create(&_key, nullptr); NAPA_ASSERT(result == 0, "Failed to create key for TLS"); #else _key = TlsAlloc(); NAPA_ASSERT(_key != TLS_OUT_OF_INDEXES, "Failed to create key for TLS"); #endif } ~ThreadLocal() { auto object = TlsGet(); if (object != nullptr) { delete object; } #ifdef SUPPORT_POSIX auto result = pthread_key_delete(_key); NAPA_ASSERT(result == 0, "Failed to delete key for TLS"); #else auto result = TlsFree(_key); NAPA_ASSERT(result == TRUE, "Failed to delete key "); #endif } /// Per-thread install object of T. template void Install(Args&&... args) { auto object = new T(std::forward(args)...); TlsSet(object); } T& operator*() { return *TlsGet(); } T* operator->() { return TlsGet(); } const T& operator*() const { return *TlsGet(); } const T* operator->() const { return TlsGet(); } /// Reset current Tls slot with another pointer. void Reset(T* object) { T* old = TlsGet(); if (old != nullptr) { delete old; } TlsSet(object); } private: /// Get const pointer of T in current Tls slot. T* TlsGet() const { #ifdef SUPPORT_POSIX return reinterpret_cast(pthread_getspecific(_key)); #else return reinterpret_cast(TlsGetValue(_key)); #endif } /// Set value for current Tls slot. void TlsSet(const void* object) { #ifdef SUPPORT_POSIX auto result = pthread_setspecific(_key, object); NAPA_ASSERT(result == 0, "Failed to set value in Tls"); #else auto result = TlsSetValue(_key, (LPVOID)object); NAPA_ASSERT(result == TRUE, "Failed to set value in Tls"); #endif } #ifdef SUPPORT_POSIX pthread_key_t _key; #else DWORD _key; #endif }; } } ================================================ FILE: src/providers/console-logging-provider.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace providers { /// A logging provider that logs to the standard console. class ConsoleLoggingProvider : public LoggingProvider { public: virtual void LogMessage( const char* section, Verboseness level, const char* traceId, const char* file, int line, const char* message) override { if (section == nullptr || section[0] == '\0') { printf("%s [%s:%d]\n", message, file, line); } else { printf("[%s] %s [%s:%d]\n", section, message, file, line); } } virtual bool IsLogEnabled(const char* section, Verboseness level) override { return true; } virtual void Destroy() override { // Don't actually delete. We're a lifetime process object. } }; } } ================================================ FILE: src/providers/nop-logging-provider.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace providers { /// A logging provider that does nothing. class NopLoggingProvider : public LoggingProvider { public: virtual void LogMessage( const char* section, Verboseness level, const char* traceId, const char* file, int line, const char* message) override {} virtual bool IsLogEnabled(const char* section, Verboseness level) override { return false; } virtual void Destroy() override { // Don't actually delete. We're a lifetime process object. } }; } } ================================================ FILE: src/providers/nop-metric-provider.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace providers { /// A no-operation instance of a Metric. class NopMetric : public Metric { public: bool Set(int64_t, size_t, const char*[]) override { return true; } bool Increment(uint64_t, size_t, const char*[]) override { return true; } bool Decrement(uint64_t, size_t, const char*[]) override { return true; } void Destroy() override { // Don't actually delete. We're a lifetime process object. } }; /// A no-operation instance of a MetricProvider. class NopMetricProvider : public MetricProvider { public: NopMetricProvider() : _defaultMetric(new NopMetric()) {} Metric* GetMetric(const char*, const char*, MetricType, size_t, const char**) override { return _defaultMetric; } virtual void Destroy() override { // Don't actually delete. We're a lifetime process object. } private: Metric* _defaultMetric; }; } } ================================================ FILE: src/providers/providers.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "providers.h" #include "console-logging-provider.h" #include "nop-logging-provider.h" #include "nop-metric-provider.h" #include #include #include #include #include #include #include #include #include using namespace napa; using namespace napa::providers; // Forward declarations. static LoggingProvider* LoadLoggingProvider(const std::string& providerName); static MetricProvider* LoadMetricProvider(const std::string& providerName); // Providers - Initially assigned to defaults. static LoggingProvider* _loggingProvider = LoadLoggingProvider(""); static MetricProvider* _metricProvider = LoadMetricProvider(""); bool napa::providers::Initialize(const settings::PlatformSettings& settings) { _loggingProvider = LoadLoggingProvider(settings.loggingProvider); _metricProvider = LoadMetricProvider(settings.metricProvider); return true; } void napa::providers::Shutdown() { if (_loggingProvider != nullptr) { _loggingProvider->Destroy(); } if (_metricProvider != nullptr) { _metricProvider->Destroy(); } } LoggingProvider& napa::providers::GetLoggingProvider() { return *_loggingProvider; } MetricProvider& napa::providers::GetMetricProvider() { return *_metricProvider; } template static ProviderType* LoadProvider( const std::string& providerName, const std::string& jsonPropertyPath, const std::string& functionName) { napa::module::ModuleResolver moduleResolver; // Resolve the provider module information auto moduleInfo = moduleResolver.Resolve(providerName.c_str()); NAPA_ASSERT(!moduleInfo.packageJsonPath.empty(), "missing package.json in provider '%s'", providerName.c_str()); // Full path to the root of the provider module auto modulePath = filesystem::Path(moduleInfo.packageJsonPath).Parent().Normalize(); // Extract relative path to provider dll from package.json rapidjson::Document package; std::ifstream ifs(moduleInfo.packageJsonPath); rapidjson::IStreamWrapper isw(ifs); NAPA_ASSERT(!package.ParseStream(isw).HasParseError(), rapidjson::GetParseError_En(package.GetParseError())); NAPA_ASSERT(package.HasMember(jsonPropertyPath.c_str()), "missing property '%s' in '%s'", jsonPropertyPath.c_str(), moduleInfo.packageJsonPath.c_str()); auto providerRelativePath = package[jsonPropertyPath.c_str()].GetString(); // Full path to provider dll auto providerPath = (modulePath / providerRelativePath).Normalize(); // Keep a static instance for each provider type (each template type will have its own static variable). static dll::SharedLibrary library(providerPath.String()); auto createProviderFunc = library.Import(functionName); return createProviderFunc(); } static LoggingProvider* LoadLoggingProvider(const std::string& providerName) { if (providerName.empty() || providerName == "console") { static auto consoleLoggingProvider = std::make_unique(); return consoleLoggingProvider.get(); } if (providerName == "nop") { static auto nopLoggingProvider = std::make_unique(); return nopLoggingProvider.get(); } return LoadProvider(providerName, "providers.logging", "CreateLoggingProvider"); } static MetricProvider* LoadMetricProvider(const std::string& providerName) { if (providerName.empty()) { static auto nopMetricProvider = std::make_unique(); return nopMetricProvider.get(); } return LoadProvider(providerName, "providers.metric", "CreateMetricProvider");; } ================================================ FILE: src/providers/providers.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include "settings/settings.h" namespace napa { namespace providers { /// Initializes and loads all providers based on the provided settings. bool Initialize(const settings::PlatformSettings& settings); /// Clean up and destroy all loaded providers. void Shutdown(); } } ================================================ FILE: src/settings/settings-parser.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "settings-parser.h" #include // Open source header only library for argument parsing. // https://github.com/Taywee/args #include using namespace napa; using namespace napa::settings; bool settings::Parse(const std::vector& args, PlatformSettings& settings) { args::ArgumentParser parser("platform settings parser"); args::ValueFlag loggingProvider(parser, "loggingProvider", "logging provider", { "loggingProvider" }); args::ValueFlag metricProvider(parser, "metricProvider", "metric provider", { "metricProvider" }); try { parser.ParseArgs(args); } catch (const std::exception& ex) { LOG_ERROR("Settings", "Failed to parse platform settings. Error: %s", ex.what()); return false; } if (loggingProvider) { settings.loggingProvider = loggingProvider.Get(); } if (metricProvider) { settings.metricProvider = metricProvider.Get(); } return true; } bool settings::Parse(const std::vector& args, ZoneSettings& settings) { args::ArgumentParser parser("zone settings parser"); args::ValueFlag workers(parser, "workers", "number of zone workers", { "workers" }); args::ValueFlag maxOldSpaceSize(parser, "maxOldSpaceSize", "max old space size in MB", { "maxOldSpaceSize" }); args::ValueFlag maxSemiSpaceSize(parser, "maxSemiSpaceSize", "max semi space size in MB", { "maxSemiSpaceSize" }); args::ValueFlag maxExecutableSize(parser, "maxExecutableSize", "max executable size in MB", { "maxExecutableSize" }); args::ValueFlag maxStackSize(parser, "maxStackSize", "max isolate stack size in bytes", { "maxStackSize" }); try { parser.ParseArgs(args); } catch (const std::exception& ex) { LOG_ERROR("Settings", "Failed to parse zone settings. Error: %s", ex.what()); return false; } if (workers) { NAPA_ASSERT(workers.Get() > 0, "The number of workers must be greater than 0"); settings.workers = workers.Get(); } if (maxOldSpaceSize) { settings.maxOldSpaceSize = maxOldSpaceSize.Get(); } if (maxSemiSpaceSize) { settings.maxSemiSpaceSize = maxSemiSpaceSize.Get(); } if (maxExecutableSize) { settings.maxExecutableSize = maxExecutableSize.Get(); } if (maxStackSize) { NAPA_ASSERT(maxStackSize.Get() > 0, "The maximum allowed stack size must be greater than 0"); settings.maxStackSize = maxStackSize.Get(); } return true; } ================================================ FILE: src/settings/settings-parser.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "settings.h" #include #include #include #include namespace napa { namespace settings { /// Parses napa settings from a vector of arguments. /// The arguments holding the settings. /// /// A setting out parameter that is filled with parsed settings. /// Settings that do not appear in the settings string will not be changed. /// /// True if parsing succeeded, false otherwise. bool Parse(const std::vector& args, ZoneSettings& settings); bool Parse(const std::vector& args, PlatformSettings& settings); /// Parses napa settings from string. /// The settings string that will be parsed. /// /// A setting out parameter that is filled with parsed settings. /// Settings that do not appear in the settings string will not be changed. /// /// True if parsing succeeded, false otherwise. template bool ParseFromString(const std::string& str, SettingsType& settings) { auto strCopy = utils::string::TrimCopy(str); if (strCopy.empty()) { return true; } std::vector args; try { utils::string::Split(strCopy, args, "\t ", true); } catch (std::exception& ex) { std::cerr << "Failed to split input string [" << strCopy << "] error: " << ex.what() << std::endl; return false; } return Parse(args, settings); } /// Parses napa settings console args. /// Number of arguments. /// The arguments. /// /// A setting out parameter that is filled with parsed settings. /// Settings that do not appear in the settings string will not be changed. /// /// True if parsing succeeded, false otherwise. template bool ParseFromConsole(int argc, const char* argv[], SettingsType& settings) { std::vector args; // Skip first parameter (program name) args.reserve(argc - 1); for (auto i = 1; i < argc; i++) { args.emplace_back(argv[i]); } return Parse(args, settings); } } } ================================================ FILE: src/settings/settings.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace settings { /// Platform settings - setting that affect all zones. struct PlatformSettings { /// The logging provider. std::string loggingProvider = "console"; /// The metric provider. std::string metricProvider; }; /// Zone specific settings. struct ZoneSettings { /// The zone id. std::string id; /// The number of zone workers. uint32_t workers = 2; /// Isolate memory constraint - The maximum old space size in megabytes. uint32_t maxOldSpaceSize = 0u; /// Isolate memory constraint - The maximum semi space size in megabytes. uint32_t maxSemiSpaceSize = 0u; /// Isolate memory constraint - The maximum executable size in megabytes. uint32_t maxExecutableSize = 0u; /// The maximum size that the isolate stack is allowed to grow in bytes. uint32_t maxStackSize = 500 * 1024; }; } } ================================================ FILE: src/store/store.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "store.h" #include #include #include using namespace napa::store; class StoreImpl: public Store { public: /// Constructor. explicit StoreImpl(const char* id) : _id(id) { } /// Get ID of this store. const char* GetId() const override { return _id.c_str(); } /// Set value with a key. /// Case-sensitive key to set. /// A shared pointer of ValueType, /// which is composed by a pair of payload and transport context. void Set(const char* key, std::shared_ptr value) override { std::lock_guard lock(_storeAccess); auto it = _valueMap.find(key); if (it != _valueMap.end()) { it->second = std::move(value); } else { _valueMap.emplace(std::string(key), std::move(value)); } } /// Get value by a key. /// Case-sensitive key to get. /// A ValueType shared pointer, empty if not found. std::shared_ptr Get(const char* key) const override { std::lock_guard lock(_storeAccess); auto it = _valueMap.find(key); if (it != _valueMap.end()) { return it->second; } return nullptr; } /// Check if this store has a key. /// Case-sensitive key. /// True if the key exists in store. bool Has(const char* key) const override { std::lock_guard lock(_storeAccess); return _valueMap.find(key) != _valueMap.end(); } /// Delete a key. No-op if key is not found in store. void Delete(const char* key) override { std::lock_guard lock(_storeAccess); _valueMap.erase(key); } /// Return size of the store. size_t Size() const override { std::lock_guard lock(_storeAccess); return _valueMap.size(); } private: /// ID. Case sensitive. std::string _id; /// Key to value map. std::unordered_map> _valueMap; /// Mutex to value map access. (use std::shared_mutex when it's public) mutable std::mutex _storeAccess; }; namespace napa { namespace store { namespace { std::unordered_map> _storeRegistry; std::mutex _registryAccess; } // namespace std::shared_ptr CreateStore(const char* id) { std::lock_guard lockWrite(_registryAccess); std::shared_ptr store; auto it = _storeRegistry.find(id); if (it == _storeRegistry.end()) { store = std::make_shared(id); _storeRegistry.insert(std::make_pair(std::string(id), store)); } return store; } std::shared_ptr GetOrCreateStore(const char* id) { auto store = GetStore(id); if (store == nullptr) { store = CreateStore(id); if (store == nullptr) { // Already created just now. Lookup again. store = GetStore(id); } } return store; } std::shared_ptr GetStore(const char* id) { std::lock_guard lockRead(_registryAccess); auto it = _storeRegistry.find(id); if (it != _storeRegistry.end()) { return it->second.lock(); } return std::shared_ptr(); } size_t GetStoreCount() { std::lock_guard lockWrite(_registryAccess); for (auto it = _storeRegistry.begin(); it != _storeRegistry.end(); ) { if (it->second.use_count() == 0) { _storeRegistry.erase(it++); } else { ++it; } } return _storeRegistry.size(); } } // namespace store } // namespace napa ================================================ FILE: src/store/store.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include namespace napa { namespace store { /// Class for memory store, which stores transportable JS objects across isolates. /// Store is intended to be used by StoreWrap. /// We expose Store in napa.dll instead of napa-binding for sharing memory between Napa and Node.JS. class Store { public: /// Meta-data that is necessary to marshall/unmarshall JS values. struct ValueType { /// JSON string from marshalled JS value. std::u16string payload; /// TransportContext that is needed to unmarshall the JS value. napa::transport::TransportContext transportContext; }; /// Get ID of this store. virtual const char* GetId() const = 0; /// Set value with a key. /// Case-sensitive key to set. /// A shared pointer of ValueType, /// which is composed by a pair of payload and transport context. virtual void Set(const char* key, std::shared_ptr value) = 0; /// Get value by a key. /// Case-sensitive key to get. /// A ValueType shared pointer, empty if not found. virtual std::shared_ptr Get(const char* key) const = 0; /// Check if this store has a key. /// Case-sensitive key. /// True if the key exists in store. virtual bool Has(const char* key) const = 0; /// Delete a key. No-op if key is not found in store. virtual void Delete(const char* key) = 0; /// Return size of the store. virtual size_t Size() const = 0; /// Destructor. virtual ~Store() = default; }; /// Create a store by id. /// Case-sensitive id. /// Newly created store, or nullptr if store associated with id already exists. NAPA_API std::shared_ptr CreateStore(const char* id); /// Get or create a store by id. /// Case-sensitive id. /// Existing or newly created store. Should never be nullptr. NAPA_API std::shared_ptr GetOrCreateStore(const char* id); /// Get a store by id. /// Case-sensitive id. /// Existing store or nullptr if not found. NAPA_API std::shared_ptr GetStore(const char* id); /// Get store count currently in use. NAPA_API size_t GetStoreCount(); } } ================================================ FILE: src/utils/string.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include namespace napa { namespace utils { namespace string { /// Replace all matches in a string. inline void ReplaceAll(std::string& str, const std::string& match, const std::string& replacement) { for (size_t pos = str.find(match); pos != std::string::npos; pos = str.find(match, pos)) { str.replace(pos, match.size(), replacement); pos += replacement.size(); } } /// Return a copy of string with all matches replaced. inline std::string ReplaceAllCopy(const std::string& str, const std::string& match, const std::string& replacement) { std::string copy(str); ReplaceAll(copy, match, replacement); return copy; } /// Split a string range into a list with a set of delimiters. inline void Split( std::string::const_iterator begin, std::string::const_iterator end, std::vector& outputList, const std::vector& delimiters, bool compress = false) { outputList.clear(); auto start = begin; for (auto it = begin; it != end; ++it) { for (auto j = delimiters.begin(); j < delimiters.end(); ++j) { if (*it == *j) { if (it != start || !compress) { outputList.push_back(std::string(start, it)); } start = it + 1; break; } } } if (start != end || !compress) { outputList.push_back(std::string(start, end)); } } /// Split a string range into a list with a set of delimiters passed in as a string. inline void Split( std::string::const_iterator begin, std::string::const_iterator end, std::vector& outputList, const std::string& anyCharAsDelimiter, bool compress = false) { std::vector delimiters(anyCharAsDelimiter.begin(), anyCharAsDelimiter.end()); Split(begin, end, outputList, delimiters, compress); } /// Split a string into a list with a set of delimiters. inline void Split( const std::string& str, std::vector& outputList, const std::string& delimiters, bool compress = false) { return Split(str.begin(), str.end(), outputList, delimiters, compress); } /// Trim the provided string. inline void Trim(std::string &str) { // Left trim str.erase(0, str.find_first_not_of(" \n\r\t")); // Right trim str.erase(str.find_last_not_of(" \n\r\t") + 1); } /// Return a trimmed copy of the string. inline std::string TrimCopy(const std::string &str) { std::string copy = str; Trim(copy); return copy; } /// In-place convert a string to lower case. inline void ToLower(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), (int (*)(int))std::tolower); } /// Return a copy of a string of lower case. inline std::string ToLowerCopy(const std::string& str) { std::string copy(str); ToLower(copy); return copy; } /// In-place convert a string to upper case. inline void ToUpper(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), (int (*)(int))std::toupper); } /// Return a copy of a string of upper case. inline std::string ToUpperCopy(const std::string& str) { std::string copy(str); ToUpper(copy); return copy; } /// Case insensitive compare. inline int CaseInsensitiveCompare(const std::string& left, const std::string& right) { for (size_t i = 0; i < left.size(); ++i) { if (i == right.size()) { return 1; } auto l = std::tolower(left[i]); auto r = std::tolower(right[i]); if (l != r) { return l - r; } } return 0; } /// Case insensitive equals. inline bool CaseInsensitiveEquals(const std::string& left, const std::string& right) { return CaseInsensitiveCompare(left, right) == 0; } } } } ================================================ FILE: src/v8-extensions/CMakeLists.txt ================================================ # Files to compile file(GLOB SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) # The target name set(TARGET_NAME "v8-extensions") # The generated library add_library(${TARGET_NAME} STATIC ${SOURCE_FILES}) # Include directories target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/inc) # Compiler definitions target_compile_definitions(${TARGET_NAME} PRIVATE NAPA_EXPORTS NAPA_BINDING_EXPORTS BUILDING_NAPA_EXTENSION) if(CMAKE_JS_VERSION) # Building Napa as an npm package for node usage (using exported v8 from node.exe) target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_JS_INC}) target_link_libraries(${TARGET_NAME} PRIVATE ${CMAKE_JS_LIB}) # Using the V8 functions exported from node.exe target_compile_definitions(${TARGET_NAME} PRIVATE USING_V8_SHARED) else() # Building Napa for embed scenarios (static linking with v8) if (NOT DEFINED NODE_ROOT) message(FATAL_ERROR "NODE_ROOT must be set to the node sources root directory") endif() set(NODE_BUILD_TYPE "Release") if ((DEFINED CMAKE_BUILD_TYPE) AND (CMAKE_BUILD_TYPE STREQUAL "debug")) set(NODE_BUILD_TYPE "Debug") endif() # V8 header files target_include_directories(${TARGET_NAME} PRIVATE ${NODE_ROOT}/deps/v8/include) endif() ================================================ FILE: src/v8-extensions/array-buffer-allocator.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "v8-extensions-macros.h" #if !(V8_VERSION_CHECK_FOR_ARRAY_BUFFER_ALLOCATOR) #include "array-buffer-allocator.h" #include #include #include using namespace napa::v8_extensions; void* ArrayBufferAllocator::Allocate(size_t length) { void* data = AllocateUninitialized(length); return std::memset(data, 0, length); } void* ArrayBufferAllocator::AllocateUninitialized(size_t length) { return malloc(length); } void ArrayBufferAllocator::Free(void* data, size_t length) { free(data); } #endif ================================================ FILE: src/v8-extensions/array-buffer-allocator.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace v8_extensions { /// Allocator that V8 uses to allocate |ArrayBuffer|'s memory. class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { public: /// v8::ArrayBuffer::Allocator::Allocate virtual void* Allocate(size_t length) override; /// v8::ArrayBuffer::Allocator::AllocateUninitialized virtual void* AllocateUninitialized(size_t length) override; /// v8::ArrayBuffer::Allocator::Free virtual void Free(void* data, size_t length) override; }; } } ================================================ FILE: src/v8-extensions/deserializer.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "v8-extensions-macros.h" #if V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER #include "deserializer.h" #include using namespace napa::v8_extensions; using namespace v8; Deserializer::Deserializer(Isolate* isolate, std::shared_ptr data) : _isolate(isolate), _deserializer(isolate, data->GetData(), data->GetSize(), this), _data(std::move(data)) { _deserializer.SetSupportsLegacyWireFormat(true); } MaybeLocal Deserializer::ReadValue() { bool readHeader = false; Local context = _isolate->GetCurrentContext(); if (!_deserializer.ReadHeader(context).To(&readHeader)) { return MaybeLocal(); } #if !V8_VERSION_EQUALS_TO_OR_NEWER_THAN(6, 6) uint32_t index = 0; Local key = v8_helpers::MakeV8String(_isolate, "_externalized"); for (const auto& contents : _data->GetExternalizedSharedArrayBufferContents()) { Local sharedArrayBuffers = SharedArrayBuffer::New( _isolate, contents.first.Data(), contents.first.ByteLength()); auto shareableWrap = napa::module::binding::CreateShareableWrap(contents.second); // After deserialization of a SharedArrayBuffer from its SerializedData, // set its '_externalized' property to a ShareableWrap of its ExternalizedContents. // This extends the lifecycle of the ExternalizedContents by the lifetime of the restored SharedArrayBuffer object. sharedArrayBuffers->CreateDataProperty(context, key, shareableWrap); _deserializer.TransferSharedArrayBuffer(index++, sharedArrayBuffers); } #endif return _deserializer.ReadValue(context); } #if V8_VERSION_EQUALS_TO_OR_NEWER_THAN(6, 6) MaybeLocal Deserializer::GetSharedArrayBufferFromId( Isolate* isolate, uint32_t cloneId) { if (_data && cloneId < _data->GetExternalizedSharedArrayBufferContents().size()) { auto externalizedSharedArrayBufferContents = _data->GetExternalizedSharedArrayBufferContents().at(cloneId); SharedArrayBuffer::Contents contents = externalizedSharedArrayBufferContents.first; auto sharedArrayBuffer = SharedArrayBuffer::New(isolate, contents.Data(), contents.ByteLength()); // After deserialization of a SharedArrayBuffer from its SerializedData, // set its '_externalized' property to a ShareableWrap of its ExternalizedContents. // This extends the lifecycle of the ExternalizedContents // by the lifetime of the restored SharedArrayBuffer object. Local context = _isolate->GetCurrentContext(); Local key = v8_helpers::MakeV8String(_isolate, "_externalized"); auto shareableWrap = napa::module::binding::CreateShareableWrap(externalizedSharedArrayBufferContents.second); sharedArrayBuffer->CreateDataProperty(context, key, shareableWrap); return sharedArrayBuffer; } else { return MaybeLocal(); } } #endif Deserializer* Deserializer::NewDeserializer(Isolate* isolate, std::shared_ptr data) { return new Deserializer(isolate, data); } #endif ================================================ FILE: src/v8-extensions/deserializer.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "serialized-data.h" #include namespace napa { namespace v8_extensions { /// /// Deserializer is used to deserialize a SerializedData instance to a JavaScript object. /// It is derived from v8::ValueDeserializer::Delegate, whose interface is implemented /// to handle ShardArrayBuffer specially as below. /// For each of the SharedArrayBuffer in the input SerializedData, /// 1). create a SharedArrayBuffer instance from its SharedArrayBuffer::Contents stored in SerializedData. /// 2). generate a ShareableWrap of ExternalizedContents, and attach it to the SharedArrayBuffer instance. /// class Deserializer : public v8::ValueDeserializer::Delegate { public: Deserializer(v8::Isolate* isolate, std::shared_ptr data); v8::MaybeLocal ReadValue(); #if V8_VERSION_EQUALS_TO_OR_NEWER_THAN(6, 6) v8::MaybeLocal GetSharedArrayBufferFromId( v8::Isolate* isolate, uint32_t cloneId) override; #endif static Deserializer* NewDeserializer( v8::Isolate* isolate, std::shared_ptr data); private: v8::Isolate* _isolate; v8::ValueDeserializer _deserializer; std::shared_ptr _data; Deserializer(const Deserializer&) = delete; Deserializer& operator=(const Deserializer&) = delete; }; } } ================================================ FILE: src/v8-extensions/externalized-contents.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "v8-extensions-macros.h" #if V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER #include "externalized-contents.h" #include using namespace napa::v8_extensions; using namespace v8; ExternalizedContents::ExternalizedContents(const SharedArrayBuffer::Contents& contents) : _data(contents.Data()), _size(contents.ByteLength()) {} ExternalizedContents::ExternalizedContents(ExternalizedContents&& other) : _data(other._data), _size(other._size) { other._data = nullptr; other._size = 0; } ExternalizedContents& ExternalizedContents::operator=(ExternalizedContents&& other) { if (this != &other) { _data = other._data; _size = other._size; other._data = nullptr; other._size = 0; } return *this; } ExternalizedContents::~ExternalizedContents() { // TODO #146: Get array_buffer_allocator to free ExternalizedContents. free(_data); } #endif ================================================ FILE: src/v8-extensions/externalized-contents.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace v8_extensions { /// /// 1. ExternalizedContents holds the externalized memory of a SharedArrayBuffer once it is serialized. /// 2. Only 1 instance of ExternalizedContents would be generated for each SharedArrayBuffer. /// If a SharedArrayBuffer had been externalized, it will reuse the ExternalizedContents instance /// created before in napa::v8_extensions::Utils::SerializeValue(). /// class ExternalizedContents { public: explicit ExternalizedContents(const v8::SharedArrayBuffer::Contents& contents); ExternalizedContents(ExternalizedContents&& other); ExternalizedContents& operator=(ExternalizedContents&& other); ~ExternalizedContents(); private: void* _data; size_t _size; ExternalizedContents(const ExternalizedContents&) = delete; ExternalizedContents& operator=(const ExternalizedContents&) = delete; }; } } ================================================ FILE: src/v8-extensions/serialized-data.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "v8-extensions-macros.h" #if V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER #include "serialized-data.h" #include using namespace napa::v8_extensions; using namespace v8; SerializedData::SerializedData() : _size(0) {} const uint8_t* SerializedData::GetData() const { return _data.get(); } size_t SerializedData::GetSize() const { return _size; } const std::vector& SerializedData::GetExternalizedSharedArrayBufferContents() const { return _externalizedSharedArrayBufferContents; } void SerializedData::DataDeleter::operator()(uint8_t* p) const { free(p); } #endif ================================================ FILE: src/v8-extensions/serialized-data.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "externalized-contents.h" namespace napa { namespace v8_extensions { using namespace v8; typedef std::pair> ExternalizedSharedArrayBufferContents; /// /// SerializedData holds the serialized data of a JavaScript object, and it is required during its deserialization. /// If the JavaScript object has properties or elements of SharedArrayBuffer or types based on SharedArrayBuffer, /// like DataView and TypedArray, their ExternalizedContents will be stored in _externalizedSharedArrayBufferContents. /// class SerializedData { public: SerializedData(); const uint8_t* GetData() const; size_t GetSize() const; const std::vector& GetExternalizedSharedArrayBufferContents() const; private: struct DataDeleter { void operator()(uint8_t* p) const; }; std::unique_ptr _data; size_t _size; std::vector _externalizedSharedArrayBufferContents; private: friend class Serializer; SerializedData(const SerializedData&) = delete; SerializedData& operator=(const SerializedData&) = delete; }; } } ================================================ FILE: src/v8-extensions/serializer.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "v8-extensions-macros.h" #if V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER #include "serializer.h" #include #include using namespace napa::v8_extensions; using namespace v8; Serializer::Serializer(Isolate* isolate) : _isolate(isolate), _serializer(isolate, this) {} Maybe Serializer::WriteValue(Local value) { bool ok = false; _data.reset(new SerializedData); _serializer.WriteHeader(); Local context = _isolate->GetCurrentContext(); if (!_serializer.WriteValue(context, value).To(&ok)) { _data.reset(); return Nothing(); } if (!FinalizeTransfer().To(&ok)) { return Nothing(); } std::pair pair = _serializer.Release(); _data->_data.reset(pair.first); _data->_size = pair.second; return Just(true); } std::shared_ptr Serializer::Release() { return _data; } void Serializer::ThrowDataCloneError(Local message) { _isolate->ThrowException(Exception::Error(message)); } Maybe Serializer::GetSharedArrayBufferId( Isolate* isolate, Local sharedArrayBuffer ) { for (size_t index = 0; index < _sharedArrayBuffers.size(); ++index) { if (_sharedArrayBuffers[index] == sharedArrayBuffer) { return Just(static_cast(index)); } } size_t index = _sharedArrayBuffers.size(); _sharedArrayBuffers.emplace_back(_isolate, sharedArrayBuffer); return Just(static_cast(index)); } void* Serializer::ReallocateBufferMemory(void* oldBuffer, size_t size, size_t* actualSize) { void* result = realloc(oldBuffer, size); *actualSize = result ? size : 0; return result; } void Serializer::FreeBufferMemory(void* buffer) { free(buffer); } ExternalizedSharedArrayBufferContents Serializer::MaybeExternalize(Local sharedArrayBuffer) { Local context = _isolate->GetCurrentContext(); Local key = v8_helpers::MakeV8String(_isolate, "_externalized"); bool ok = false; if (sharedArrayBuffer->IsExternal() && sharedArrayBuffer->Has(context, key).To(&ok)) { Local value; // If the SharedArrayBuffer has been externalized, just get its Contents without externalizing it again, // and get its ExternalizedContents which has been stored in the '_externalized' property of the SharedArrayBuffer. if (sharedArrayBuffer->Get(context, key).ToLocal(&value)) { auto shareableWrap = NAPA_OBJECTWRAP::Unwrap(Local::Cast(value)); auto externalizedContents = shareableWrap->Get(); return std::make_pair(sharedArrayBuffer->GetContents(), externalizedContents); } return std::make_pair(sharedArrayBuffer->GetContents(), nullptr); } else { // If the SharedArrayBuffer has not been externalized, // externalize it and get its Contents and ExternalizedContents at first, // then store its ExternalizedContents in the '_externalized' property of the original SharedArrayBuffer. auto contents = sharedArrayBuffer->Externalize(); auto externalizedContents = std::make_shared(contents); auto shareableWrap = napa::module::binding::CreateShareableWrap(externalizedContents); sharedArrayBuffer->CreateDataProperty(context, key, shareableWrap); return std::make_pair(contents, externalizedContents); } } Maybe Serializer::FinalizeTransfer() { for (const auto& globalSharedArrayBuffer : _sharedArrayBuffers) { Local sharedArrayBuffer = Local::New(_isolate, globalSharedArrayBuffer); // Externalize the SharedArrayBuffer if it hasn't been done before, // and store it's ExternalizedContents which will be used when deserializing it in deserializer. _data->_externalizedSharedArrayBufferContents.push_back(MaybeExternalize(sharedArrayBuffer)); } return Just(true); } #endif ================================================ FILE: src/v8-extensions/serializer.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "serialized-data.h" #include namespace napa { namespace v8_extensions { /// /// Serializer is used to serialize a JavaScript object to a SerializedData instance. /// It is derived from v8::ValueSerializer::Delegate, whose interface is implemented /// to handle ShardArrayBuffer specially as below. /// If a SharedArrayBuffer is being serialized for the first time, /// 1). it will be externlized and the ExternalizedContents will be attached to its SerializedData. /// 2). a ShareableWrap of the ExternalizedContents will be set to the input SharedArrayBuffer. /// If a SharedArrayBuffer has been serialized, the externalization will be skipped, and its ExternalizedContents /// will be retrieved from the input SharedArrayBuffer and attached to its SerializedData. /// class Serializer : public v8::ValueSerializer::Delegate { public: explicit Serializer(v8::Isolate* isolate); v8::Maybe WriteValue(v8::Local value); std::shared_ptr Release(); protected: void ThrowDataCloneError(v8::Local message) override; v8::Maybe GetSharedArrayBufferId( v8::Isolate* isolate, v8::Local sharedArrayBuffer ) override; void* ReallocateBufferMemory(void* oldBuffer, size_t size, size_t* actualSize) override; void FreeBufferMemory(void* buffer) override; private: ExternalizedSharedArrayBufferContents MaybeExternalize(v8::Local sharedArrayBuffer); v8::Maybe FinalizeTransfer(); v8::Isolate* _isolate; v8::ValueSerializer _serializer; std::shared_ptr _data; std::vector> _sharedArrayBuffers; Serializer(const Serializer&) = delete; Serializer& operator=(const Serializer&) = delete; }; } } ================================================ FILE: src/v8-extensions/v8-common.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "v8-common.h" #ifdef USING_V8_SHARED // Empty stubs when using shared v8 bool napa::v8_common::Initialize() { return true; } void napa::v8_common::Shutdown() {} #else #include // V8 libraries #include #include static v8::Platform* _platform = nullptr; bool napa::v8_common::Initialize() { NAPA_ASSERT(!_platform, "V8 was already initialized"); _platform = v8::platform::CreateDefaultPlatform(); v8::V8::InitializePlatform(_platform); v8::V8::Initialize(); return true; } void napa::v8_common::Shutdown() { NAPA_ASSERT(_platform, "V8 wasn't initialized"); v8::V8::Dispose(); v8::V8::ShutdownPlatform(); delete _platform; _platform = nullptr; } #endif ================================================ FILE: src/v8-extensions/v8-common.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once namespace napa { namespace v8_common { /// Performs v8 global initialization. bool Initialize(); /// Shutdown and clean v8 global resources. void Shutdown(); } } ================================================ FILE: src/v8-extensions/v8-extensions-macros.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #define V8_VERSION_EQUALS_TO_OR_NEWER_THAN(MAJOR, MINOR) \ (V8_MAJOR_VERSION == (MAJOR) && V8_MINOR_VERSION >= (MINOR) || V8_MAJOR_VERSION > (MAJOR)) #define V8_VERSION_CHECK_FOR_ARRAY_BUFFER_ALLOCATOR \ V8_VERSION_EQUALS_TO_OR_NEWER_THAN(5, 5) #define V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER \ V8_VERSION_EQUALS_TO_OR_NEWER_THAN(6, 2) ================================================ FILE: src/v8-extensions/v8-extensions.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "v8-extensions-macros.h" #if V8_VERSION_CHECK_FOR_BUILT_IN_TYPE_TRANSPORTER #include "deserializer.h" #include "serializer.h" #include "v8-extensions.h" using namespace napa; using namespace v8; std::shared_ptr v8_extensions::Utils::SerializeValue(Isolate* isolate, Local value) { bool ok = false; Serializer serializer(isolate); if (serializer.WriteValue(value).To(&ok)) { return serializer.Release(); } return nullptr; } MaybeLocal v8_extensions::Utils::DeserializeValue(Isolate* isolate, std::shared_ptr& data) { Local value; Deserializer deserializer(isolate, data); return deserializer.ReadValue(); } #endif ================================================ FILE: src/v8-extensions/v8-extensions.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace v8_extensions { class SerializedData; class NAPA_API Utils { public: static std::shared_ptr SerializeValue(v8::Isolate* isolate, v8::Local value); static v8::MaybeLocal DeserializeValue(v8::Isolate* isolate, std::shared_ptr& data); }; } } ================================================ FILE: src/zone/async-complete-task.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "async-complete-task.h" #include using namespace napa::zone; AsyncCompleteTask::AsyncCompleteTask(std::shared_ptr context) : _context(std::move(context)) {} void AsyncCompleteTask::Execute() { // If asyncWork is empty, don't need to wait for async result. It just posts a completion only. if (_context->asyncWork != nullptr) { _context->future.wait(); } auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto jsCallback = v8::Local::New(isolate, _context->jsCallback); _context->asyncCompleteCallback(jsCallback, _context->result); _context->jsCallback.Reset(); } ================================================ FILE: src/zone/async-complete-task.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "async-context.h" #include #include namespace napa { namespace zone { /// A task to run Javascript callback after asynchronous callback completes. class AsyncCompleteTask : public Task { public: /// Constructor. /// Structure containing asynchronous work's context. AsyncCompleteTask(std::shared_ptr context); /// Overrides Task.Execute to define running execution logic. virtual void Execute() override; private: std::shared_ptr _context; }; } } ================================================ FILE: src/zone/async-context.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include namespace napa { namespace zone { /// Class holding asynchronous callbacks. struct AsyncContext { /// Zone instance issueing asynchronous work. NapaZone* zone = nullptr; /// Keep scheduler instance referenced until async work completes. std::shared_ptr scheduler; /// Worker Id issueing asynchronous work. zone::WorkerId workerId; /// Future to wait async callback. std::future future; /// Javascript callback. v8::Persistent jsCallback; /// Function to run asynchronously in separate thread. AsyncWork asyncWork; /// Return value from asynchronous work. void* result = nullptr; /// Callback running in V8 isolate after asynchronous callback completes. AsyncCompleteCallback asyncCompleteCallback; }; } // End of namespace zone. } // End of namespace napa. ================================================ FILE: src/zone/async-runner.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include #include using namespace napa; using namespace napa::zone; namespace { /// Prepare asynchronous work. /// Javascript callback. /// Function to run asynchronously in separate thread. /// Callback running in V8 isolate after asynchronous callback completes. /// AsyncContext instance. std::shared_ptr PrepareAsyncWork(v8::Local jsCallback, AsyncWork asyncWork, AsyncCompleteCallback asyncCompleteCallback); } // End of anonymous namespace. /// It runs a synchronous function in the separate thread and posts a completion into the current V8 execution loop. /// Javascript callback. /// Function to run asynchronously in separate thread. /// Callback running in V8 isolate after asynchronous callback completes. void napa::zone::PostAsyncWork(v8::Local jsCallback, AsyncWork asyncWork, AsyncCompleteCallback asyncCompleteCallback) { auto context = PrepareAsyncWork(jsCallback, std::move(asyncWork), std::move(asyncCompleteCallback)); if (context == nullptr) { return; } context->future = std::async(std::launch::async, [context]() { context->result = context->asyncWork(); auto asyncCompleteTask = std::make_shared(context); context->zone->GetScheduler()->ScheduleOnWorker(context->workerId, asyncCompleteTask); }); } /// It runs an asynchronous function and post a completion into the current V8 execution loop. /// Javascript callback. /// Function to wrap async-supporting function. /// Callback running in V8 isolate after asynchronous function completes. void napa::zone::DoAsyncWork(v8::Local jsCallback, const CompletionWork& asyncWork, AsyncCompleteCallback asyncCompleteCallback) { auto context = PrepareAsyncWork(jsCallback, nullptr, std::move(asyncCompleteCallback)); if (context == nullptr) { return; } asyncWork([context](void* result) { context->result = result; auto asyncCompleteTask = std::make_shared(context); context->zone->GetScheduler()->ScheduleOnWorker(context->workerId, asyncCompleteTask); }); } namespace { std::shared_ptr PrepareAsyncWork(v8::Local jsCallback, AsyncWork asyncWork, AsyncCompleteCallback asyncCompleteCallback) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = std::make_shared(); context->zone = reinterpret_cast(WorkerContext::Get(WorkerContextItem::ZONE)); if (context->zone == nullptr) { return nullptr; } context->scheduler = context->zone->GetScheduler(); context->workerId = static_cast( reinterpret_cast(WorkerContext::Get(WorkerContextItem::WORKER_ID))); context->jsCallback.Reset(isolate, jsCallback); context->asyncWork = std::move(asyncWork); context->asyncCompleteCallback = std::move(asyncCompleteCallback); return context; } } // End of anonymous namespace. ================================================ FILE: src/zone/call-context.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. // See: https://groups.google.com/forum/#!topic/nodejs/onA0S01INtw #ifdef BUILDING_NODE_EXTENSION #include #endif #include "call-context.h" #include #include #include using namespace napa::zone; CallContext::CallContext(const napa::FunctionSpec& spec, napa::ExecuteCallback callback) : _module(NAPA_STRING_REF_TO_STD_STRING(spec.module)), _function(NAPA_STRING_REF_TO_STD_STRING(spec.function)), _callback(callback), _finished(false) { // Audit start time. _startTime = std::chrono::high_resolution_clock::now(); _arguments.reserve(spec.arguments.size()); for (auto& arg : spec.arguments) { _arguments.emplace_back(NAPA_STRING_REF_TO_STD_STRING(arg)); } _options = spec.options; // Pass ownership of the transport context. _transportContext = std::move(spec.transportContext); } bool CallContext::Resolve(std::string marshalledResult) { auto expected = false; if (!_finished.compare_exchange_strong(expected, true)) { return false; } NAPA_DEBUG("CallTask", "Call to \"%s.%s\" is resolved successfully.", _module.c_str(), _function.c_str()); _callback({ NAPA_RESULT_SUCCESS, "", std::move(marshalledResult), std::move(_transportContext) }); return true; } bool CallContext::Reject(napa::ResultCode code, std::string reason) { auto expected = false; if (!_finished.compare_exchange_strong(expected, true)) { return false; } NAPA_DEBUG("CallTask", "Call to \"%s.%s\" was rejected: %s.", _module.c_str(), _function.c_str(), reason.c_str()); _callback({ code, reason, "", std::move(_transportContext) }); return true; } bool CallContext::IsFinished() const { return _finished; } const std::string& CallContext::GetModule() const { return _module; } const std::string& CallContext::GetFunction() const { return _function; } const std::vector& CallContext::GetArguments() const { return _arguments; } napa::transport::TransportContext& CallContext::GetTransportContext() { return *_transportContext.get(); } const napa::CallOptions& CallContext::GetOptions() const { return _options; } std::chrono::nanoseconds CallContext::GetElapse() const { return std::chrono::high_resolution_clock::now() - _startTime; } ================================================ FILE: src/zone/call-context.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include #include namespace napa { namespace zone { /// Context of calling a JavaScript function. class CallContext { public: /// Construct spec from external FunctionSpec. explicit CallContext(const napa::FunctionSpec& spec, napa::ExecuteCallback callback); /// Resolve current spec. /// marshalled return value. /// True if operation is successful, otherwise if task is already finished before. //bool Resolve(v8::Local result); bool Resolve(std::string result); /// Reject current spec. /// Response code to return to user. /// Reason of cancellation. /// True if operation is successful, otherwise if task is already finished before. bool Reject(napa::ResultCode code, std::string reason); /// Returns whether current job is completed or cancelled. bool IsFinished() const; /// Get module name to load function. const std::string& GetModule() const; /// Get function name to execute. const std::string& GetFunction() const; /// Get marshalled arguments. const std::vector& GetArguments() const; /// Get transport context. napa::transport::TransportContext& GetTransportContext(); /// Get options. const napa::CallOptions& GetOptions() const; /// Get elapse since task start in nano-second. std::chrono::nanoseconds GetElapse() const; private: /// Module name. std::string _module; /// Function name. std::string _function; /// Arguments. std::vector _arguments; /// Execute options. napa::CallOptions _options; /// Transport context. std::unique_ptr _transportContext; /// Callback when task completes. napa::ExecuteCallback _callback; /// Whether this task is finished. std::atomic _finished; /// Call start time. std::chrono::high_resolution_clock::time_point _startTime; }; } } ================================================ FILE: src/zone/call-task.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. // See: https://groups.google.com/forum/#!topic/nodejs/onA0S01INtw #ifdef BUILDING_NODE_EXTENSION #include #endif #include "call-task.h" #include #include using namespace napa::zone; using namespace napa::v8_helpers; napa::zone::CallTask::CallTask(std::shared_ptr context) : _context(std::move(context)) { } void CallTask::Execute() { NAPA_DEBUG("CallTask", "Begin executing function (%s.%s).", _context->GetModule().c_str(), _context->GetFunction().c_str()); auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); // Get the module based main function from global scope. auto executeFunction = context->Global()->Get(MakeExternalV8String(isolate, "__napa_zone_call__")); JS_ENSURE(isolate, executeFunction->IsFunction(), "__napa_zone_call__ function must exist in global scope"); // Create task wrap. auto contextWrap = napa::module::CallContextWrap::NewInstance(_context); v8::Local argv[] = { contextWrap }; // Execute the function. v8::TryCatch tryCatch(isolate); auto res = v8::Local::Cast(executeFunction)->Call( isolate->GetCurrentContext(), context->Global(), 1, argv); // Terminating an isolate may occur from a different thread, i.e. from timeout service. // If the function call already finished successfully when the isolate is terminated it may lead // to one the following: // 1. Terminate was called before tryCatch.HasTerminated(), the user gets an error code. // 2. Terminate was called after tryCatch.HasTerminated(), the user gets a success code. // // In both cases the isolate is being restored since this happens before each task executes. if (tryCatch.HasTerminated()) { if (_terminationReason == TerminationReason::TIMEOUT) { (void)_context->Reject(NAPA_RESULT_TIMEOUT, "Terminated due to timeout"); } else { (void)_context->Reject(NAPA_RESULT_INTERNAL_ERROR, "Terminated with unknown reason"); } return; } NAPA_ASSERT(!tryCatch.HasCaught(), "__napa_zone_call__ should catch all user exceptions and reject task."); } ================================================ FILE: src/zone/call-task.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "call-context.h" #include "terminable-task.h" #include namespace napa { namespace zone { /// A task for executing pre-loaded javascript functions. class CallTask : public TerminableTask { public: /// Constructor. /// Call context. CallTask(std::shared_ptr context); /// Overrides Task.Execute to define execution logic. virtual void Execute() override; private: /// Call context. std::shared_ptr _context; }; } } ================================================ FILE: src/zone/eval-task.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. // See: https://groups.google.com/forum/#!topic/nodejs/onA0S01INtw #ifdef BUILDING_NODE_EXTENSION #include #endif #include "eval-task.h" #include #include #include #include using namespace napa; using namespace napa::zone; EvalTask::EvalTask(std::string source, std::string sourceOrigin, BroadcastCallback callback) : _source(std::move(source)), _sourceOrigin(std::move(sourceOrigin)), _callback(std::move(callback)) {} void EvalTask::Execute() { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); auto context = isolate->GetCurrentContext(); NAPA_DEBUG("EvalTask", "Begin executing script:\"%s\"", _source.c_str()); auto filename = v8_helpers::MakeV8String(isolate, _sourceOrigin); filesystem::Path originPath(_sourceOrigin); if (originPath.IsAbsolute()) { auto global = context->Global(); auto dirname = v8_helpers::MakeV8String(isolate, originPath.Parent().Normalize().String()); (void)global->Set(context, v8_helpers::MakeV8String(isolate, "__dirname"), dirname); (void)global->Set(context, v8_helpers::MakeV8String(isolate, "__filename"), filename); } auto source = napa::v8_helpers::MakeV8String(isolate, _source); auto sourceOrigin = v8::ScriptOrigin(filename); // Compile the source code. v8::MaybeLocal compileResult; { v8::TryCatch tryCatch(isolate); compileResult = v8::Script::Compile(context, source, &sourceOrigin); if (tryCatch.HasCaught()) { auto exception = tryCatch.Exception(); v8::String::Utf8Value exceptionStr(exception); std::stringstream ss; ss << "Compilation failed: " << *exceptionStr; std::string reason = ss.str(); NAPA_DEBUG("EvalTask", reason.c_str()); _callback({ NAPA_RESULT_BROADCAST_SCRIPT_ERROR, reason.c_str(), "", nullptr }); return; } } NAPA_DEBUG("EvalTask", "Script compiled successfully"); auto script = compileResult.ToLocalChecked(); // Run the source code. { v8::TryCatch tryCatch(isolate); (void)script->Run(context); if (tryCatch.HasCaught()) { auto exception = tryCatch.Exception(); v8::String::Utf8Value exceptionStr(exception); auto stackTrace = tryCatch.StackTrace(); v8::String::Utf8Value stackTraceStr(stackTrace); std::stringstream ss; ss << "Eval failed: " << *exceptionStr << " - " << *stackTraceStr; std::string reason = ss.str(); NAPA_DEBUG("EvalTask", reason.c_str()); _callback({ NAPA_RESULT_BROADCAST_SCRIPT_ERROR, reason.c_str(), "", nullptr }); return; } } NAPA_DEBUG("EvalTask", "Eval script completed with success"); _callback({ NAPA_RESULT_SUCCESS, "", "", nullptr }); } ================================================ FILE: src/zone/eval-task.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "task.h" #include "napa/types.h" #include #include namespace napa { namespace zone { /// A task for evaluating javascript source code. class EvalTask : public Task { public: /// Constructor. /// The JS source code to load on the isolate the runs this task. /// The origin of the source code. /// A callback that is triggered when the task execution completed. EvalTask(std::string source, std::string sourceOrigin = "", BroadcastCallback callback = [](Result) {}); /// Overrides Task.Execute to define loading execution logic. virtual void Execute() override; private: std::string _source; std::string _sourceOrigin; BroadcastCallback _callback; }; } } ================================================ FILE: src/zone/napa-zone.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "napa-zone.h" #include #include #include #include #include #include #include #include #include #include #include using namespace napa; using namespace napa::zone; // Static members initialization std::mutex NapaZone::_mutex; std::unordered_map> NapaZone::_zones; /// Load 'napajs' module during bootstrap. We use relative path to decouple from how module will be published. static const std::string NAPAJS_MODULE_PATH = filesystem::Path(dll::ThisLineLocation()).Parent().Parent().Normalize().String(); static const std::string BOOTSTRAP_SOURCE = "require('" + utils::string::ReplaceAllCopy(NAPAJS_MODULE_PATH, "\\", "\\\\") + "');"; std::shared_ptr NapaZone::Create(const settings::ZoneSettings& settings) { std::lock_guard lock(_mutex); auto iter = _zones.find(settings.id); if (iter != _zones.end() && !iter->second.expired()) { NAPA_DEBUG("Zone", "Failed to create zone '%s': a zone with this name already exists.", settings.id.c_str()); return nullptr; } // An helper class to enable make_shared of NapaZone struct MakeSharedEnabler : public NapaZone { MakeSharedEnabler(const settings::ZoneSettings& settings) : NapaZone(settings) {} }; // Fail to create Napa zone is not expected, will always trigger crash. auto zone = std::make_shared(settings); _zones[settings.id] = zone; NAPA_DEBUG("Zone", "Napa zone \"%s\" created.", settings.id.c_str()); return zone; } std::shared_ptr NapaZone::Get(const std::string& id) { std::lock_guard lock(_mutex); auto iter = _zones.find(id); if (iter == _zones.end()) { NAPA_DEBUG("Zone", "Get zone \"%s\" failed due to not found.", id.c_str()); return nullptr; } auto zone = iter->second.lock(); if (zone == nullptr) { LOG_WARNING("Zone", "Zone '%s' was already deleted.", id.c_str()); // Use this chance to clean up the map _zones.erase(id); } NAPA_DEBUG("Zone", "Get zone \"%s\" succeeded.", id.c_str()); return zone; } NapaZone::NapaZone(const settings::ZoneSettings& settings) : _settings(settings) { // Create the zone's scheduler. _scheduler = std::make_unique(_settings, [this](WorkerId id) { // Initialize the worker context TLS data INIT_WORKER_CONTEXT(); // Zone instance into TLS. WorkerContext::Set(WorkerContextItem::ZONE, reinterpret_cast(this)); // Worker Id into TLS. WorkerContext::Set(WorkerContextItem::WORKER_ID, reinterpret_cast(static_cast(id))); // Load module loader and built-in modules of require, console and etc. CREATE_MODULE_LOADER(); }); // Bootstrap after zone is created. std::promise promise; auto future = promise.get_future(); // Makes sure the callback is only called once, after all workers finished running the broadcast task. auto counter = std::make_shared>(_settings.workers); auto callOnce = [&promise, counter](Result result) { if (--(*counter) == 0) { promise.set_value(result.code); } }; auto bootstrapTask = std::make_shared(BOOTSTRAP_SOURCE, "", std::move(callOnce)); _scheduler->ScheduleOnAllWorkers(std::move(bootstrapTask)); NAPA_DEBUG("Zone", "Scheduling bootstrap script \"%s\" to zone \"%s\"", BOOTSTRAP_SOURCE.c_str(), _settings.id.c_str()); NAPA_ASSERT(future.get() == NAPA_RESULT_SUCCESS, "Bootstrap Napa zone failed."); } const std::string& NapaZone::GetId() const { return _settings.id; } void NapaZone::Broadcast(const FunctionSpec& spec, BroadcastCallback callback) { // Makes sure the callback is only called once, after all workers finished running the broadcast task. auto counter = std::make_shared>(_settings.workers); auto callOnce = [this, callback = std::move(callback), counter](Result result) { if (--(*counter) == 0) { callback(std::move(result)); } }; for (WorkerId id = 0; id < _settings.workers; id++) { std::shared_ptr task; if (spec.options.timeout > 0) { task = std::make_shared>( std::chrono::milliseconds(spec.options.timeout), std::make_shared(spec, callOnce)); } else { task = std::make_shared(std::make_shared(spec, callOnce)); } _scheduler->ScheduleOnWorker(id, std::move(task)); } NAPA_DEBUG("Zone", "Broadcast function \"%s.%s\" on zone \"%s\"", spec.module.data, spec.function.data, _settings.id.c_str()); } void NapaZone::Execute(const FunctionSpec& spec, ExecuteCallback callback) { std::shared_ptr task; if (spec.options.timeout > 0) { task = std::make_shared>( std::chrono::milliseconds(spec.options.timeout), std::make_shared(spec, std::move(callback))); } else { task = std::make_shared(std::make_shared(spec, std::move(callback))); } NAPA_DEBUG("Zone", "Execute function \"%s.%s\" on zone \"%s\"", spec.module.data, spec.function.data, _settings.id.c_str()); _scheduler->Schedule(std::move(task)); } const settings::ZoneSettings& NapaZone::GetSettings() const { return _settings; } std::shared_ptr NapaZone::GetScheduler() { return _scheduler; } ================================================ FILE: src/zone/napa-zone.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "zone.h" #include "zone/scheduler.h" #include "settings/settings.h" #include #include #include namespace napa { namespace zone { /// Concrete implementation of a Napa zone. class NapaZone : public Zone { public: /// Creates a new zone with the provided id and settings. static std::shared_ptr Create(const settings::ZoneSettings& settings); /// Retrieves an existing zone by id. static std::shared_ptr Get(const std::string& id); /// virtual const std::string& GetId() const override; /// virtual void Broadcast(const FunctionSpec& spec, BroadcastCallback callback) override; /// virtual void Execute(const FunctionSpec& spec, ExecuteCallback callback) override; /// Retrieves the zone settings. const settings::ZoneSettings& GetSettings() const; /// Retrieves the zone scheduler. /// Asynchronous works keep the reference on scheduler, so they can finish up safely. std::shared_ptr GetScheduler(); private: explicit NapaZone(const settings::ZoneSettings& settings); settings::ZoneSettings _settings; std::shared_ptr _scheduler; static std::mutex _mutex; static std::unordered_map> _zones; }; } } ================================================ FILE: src/zone/node-zone.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "node-zone.h" #include "worker-context.h" #include using namespace napa; using namespace napa::zone; std::shared_ptr NodeZone::_instance; void NodeZone::Init(BroadcastDelegate broadcast, ExecuteDelegate execute) { _instance.reset(new NodeZone(broadcast, execute)); } NodeZone::NodeZone(BroadcastDelegate broadcast, ExecuteDelegate execute): _broadcast(std::move(broadcast)), _execute(std::move(execute)), _id("node") { NAPA_ASSERT(_broadcast, "Broadcast delegate must be a valid function."); NAPA_ASSERT(_execute, "Execute delegate must be a valid function."); // Init worker context for Node event loop. INIT_WORKER_CONTEXT(); // Zone instance into TLS. WorkerContext::Set(WorkerContextItem::ZONE, reinterpret_cast(this)); // Worker Id into TLS. WorkerContext::Set(WorkerContextItem::WORKER_ID, reinterpret_cast(static_cast(0))); } std::shared_ptr NodeZone::Get() { return _instance; } const std::string& NodeZone::GetId() const { return _id; } void NodeZone::Broadcast(const FunctionSpec& source, BroadcastCallback callback) { _broadcast(source, callback); } void NodeZone::Execute(const FunctionSpec& spec, ExecuteCallback callback) { _execute(spec, callback); } ================================================ FILE: src/zone/node-zone.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include "zone.h" #include namespace napa { namespace zone { /// Delegate for Broadcast on Node zone. using BroadcastDelegate = std::function; /// Delegate for Execute on Node zone. using ExecuteDelegate = std::function; /// Concrete implementation of a Node zone. class NodeZone : public Zone { public: /// Set delegate function for Broadcast and Execute on node zone. This is intended to be called from napa-binding.node. static NAPA_API void Init(BroadcastDelegate broadcast, ExecuteDelegate execute); /// /// Retrieves an existing zone. /// If Node is not applicable (like in embed mode), a nullptr will be returned. /// static std::shared_ptr Get(); /// virtual const std::string& GetId() const override; /// virtual void Broadcast(const FunctionSpec& spec, BroadcastCallback callback) override; /// virtual void Execute(const FunctionSpec& spec, ExecuteCallback callback) override; private: /// Constructor. NodeZone(BroadcastDelegate broadcast, ExecuteDelegate execute); /// Broadcast delegate for node zone. BroadcastDelegate _broadcast; /// Execute delegate for node zone. ExecuteDelegate _execute; /// Node zone id. std::string _id; /// Node zone instance. static std::shared_ptr _instance; }; } } ================================================ FILE: src/zone/schedule-phase.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace zone { // Define the phase (like priority related type) of tasks. // To be used mainly by schduler and worker to schedule its tasks. enum class SchedulePhase : uint32_t { DefaultPhase = 0, ImmediatePhase = 1 }; }; } ================================================ FILE: src/zone/scheduler.cpp ================================================ #include "scheduler.h" template class napa::zone::SchedulerImpl; ================================================ FILE: src/zone/scheduler.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "schedule-phase.h" #include "simple-thread-pool.h" #include "task.h" #include "worker.h" #include #include #include #include #include #include #include #include namespace napa { namespace zone { /// The scheduler is responsible for assigning tasks to workers. template class SchedulerImpl { public: /// Constructor. /// A settings object. /// Callback to setup the isolate after worker created its isolate. SchedulerImpl(const settings::ZoneSettings& settings, std::function workerSetupCallback); /// Destructor. Waits for all tasks to finish. ~SchedulerImpl(); /// Schedules the task on a single worker. /// Task to schedule. void Schedule(std::shared_ptr task); /// Schedules the task on a specific worker. /// The id of the worker. /// Task to schedule. /// Which phase of the task, like Immediate or Normal. /// /// By design, it enqueues a task immediately, /// so the task will have higher priority than ones called by Schedule(). /// void ScheduleOnWorker(WorkerId workerId, std::shared_ptr task, SchedulePhase phase = SchedulePhase::DefaultPhase); /// Schedules the task on all workers. /// Task to schedule. /// /// By design, it enqueues a task immediately, /// so the task will have higher priority than ones called by Schedule(). /// void ScheduleOnAllWorkers(std::shared_ptr task); private: /// The logic invoked when a worker is idle. void IdleWorkerNotificationCallback(WorkerId workerId); /// The workers that are used for running the tasks. std::vector _workers; /// New tasks that weren't assigned to a specific worker. std::queue> _nonScheduledTasks; /// List of idle workers, used when assigning non scheduled tasks. std::list _idleWorkers; /// Flags to indicate that a worker is in the idle list. std::vector::iterator> _idleWorkersFlags; /// Uses a single thread to synchronize task queuing and posting. std::unique_ptr _synchronizer; /// A flag to signal that scheduler is stopping. std::atomic _shouldStop; /// Tasks being scheduled but not yet dispatched to worker or put into non-scheduled queue. std::atomic _beingScheduled; }; typedef SchedulerImpl Scheduler; template SchedulerImpl::SchedulerImpl(const settings::ZoneSettings& settings, std::function workerSetupCallback) : _idleWorkersFlags(settings.workers, _idleWorkers.end()), _synchronizer(std::make_unique(1)), _shouldStop(false), _beingScheduled(0) { _workers.reserve(settings.workers); for (WorkerId i = 0; i < settings.workers; i++) { _workers.emplace_back(i, settings, workerSetupCallback, [this](WorkerId workerId) { IdleWorkerNotificationCallback(workerId); }); _workers[i].Start(); } } template SchedulerImpl::~SchedulerImpl() { NAPA_DEBUG("Scheduler", "Shutting down: Start draining unscheduled tasks..."); // Wait for all tasks to be scheduled. while (_beingScheduled > 0 || !_nonScheduledTasks.empty()) { std::this_thread::yield(); } // Signal scheduler callbacks to not process anymore tasks. _shouldStop = true; // Wait for synchronizer to finish his book-keeping. _synchronizer = nullptr; // Wait for all workers to finish processing remaining tasks. _workers.clear(); NAPA_DEBUG("Scheduler", "Shutdown completed"); } template void SchedulerImpl::Schedule(std::shared_ptr task) { NAPA_ASSERT(task, "task is null"); _beingScheduled++; _synchronizer->Execute([this, task]() { if (_idleWorkers.empty()) { NAPA_DEBUG("Scheduler", "All workers are busy, putting task to non-scheduled queue."); // If there is no idle worker, put the task into the non-scheduled queue. _nonScheduledTasks.emplace(std::move(task)); } else { // Pop the worker id from the idle workers list. auto workerId = _idleWorkers.front(); _idleWorkers.pop_front(); _idleWorkersFlags[workerId] = _idleWorkers.end(); // Schedule task on worker _workers[workerId].Schedule(std::move(task)); NAPA_DEBUG("Scheduler", "Scheduled task on worker %u.", workerId); } _beingScheduled--; }); } template void SchedulerImpl::ScheduleOnWorker( WorkerId workerId, std::shared_ptr task, SchedulePhase phase) { NAPA_ASSERT(workerId < _workers.size(), "worker id out of range"); _synchronizer->Execute([workerId, this, task, phase]() { // If the worker is idle, change it's status. if (_idleWorkersFlags[workerId] != _idleWorkers.end()) { _idleWorkers.erase(_idleWorkersFlags[workerId]); _idleWorkersFlags[workerId] = _idleWorkers.end(); } // Schedule task on worker _workers[workerId].Schedule(std::move(task), phase); NAPA_DEBUG("Scheduler", "Explicitly scheduled task on worker %u.", workerId); }); } template void SchedulerImpl::ScheduleOnAllWorkers(std::shared_ptr task) { NAPA_ASSERT(task, "task is null"); _synchronizer->Execute([this, task]() { // Clear all idle workers. _idleWorkers.clear(); for (auto& flag : _idleWorkersFlags) { flag = _idleWorkers.end(); } // Schedule the task on all workers. for (auto& worker : _workers) { worker.Schedule(task); } NAPA_DEBUG("Scheduler", "Scheduled task on all workers"); }); } template void SchedulerImpl::IdleWorkerNotificationCallback(WorkerId workerId) { NAPA_ASSERT(workerId < _workers.size(), "worker id out of range"); if (_shouldStop) { return; } _synchronizer->Execute([this, workerId]() { if (!_nonScheduledTasks.empty()) { // If there is a non scheduled task, schedule it on the idle worker. auto task = _nonScheduledTasks.front(); _nonScheduledTasks.pop(); _workers[workerId].Schedule(std::move(task)); NAPA_DEBUG("Scheduler", "Worker %u fetched a task from non-scheduled queue", workerId); } else { // Put worker in idle list. if (_idleWorkersFlags[workerId] == _idleWorkers.end()) { auto iter = _idleWorkers.emplace(_idleWorkers.end(), workerId); _idleWorkersFlags[workerId] = iter; NAPA_DEBUG("Scheduler", "Worker %u becomes idle", workerId); } } }); } } } ================================================ FILE: src/zone/simple-thread-pool.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "simple-thread-pool.h" using namespace napa::zone; SimpleThreadPool::Worker::Worker(SimpleThreadPool& pool) : _pool(pool) {} void SimpleThreadPool::Worker::operator()() { std::function task; while (true) { { std::unique_lock lock(_pool._queueLock); if (!_pool._isStopped) { _pool._queueCondition.wait(lock, [this]() { return _pool._isStopped || !_pool._taskQueue.empty(); }); } // Drain all existing tasks before actually stopping. if (_pool._isStopped && _pool._taskQueue.empty()) { break; } task = std::move(_pool._taskQueue.front()); _pool._taskQueue.pop(); } task(); } } SimpleThreadPool::SimpleThreadPool(uint32_t numberOfWorkers) : _isStopped(false) { _workers.reserve(numberOfWorkers); for (uint32_t i = 0; i < numberOfWorkers; ++i) { _workers.emplace_back(Worker(*this)); } } SimpleThreadPool::~SimpleThreadPool() { { std::unique_lock lock(_queueLock); _isStopped = true; } _queueCondition.notify_all(); for (auto& thread : _workers) { if (thread.joinable()) { thread.join(); } } } ================================================ FILE: src/zone/simple-thread-pool.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include #include #include #include #include #include namespace napa { namespace zone { /// Simple thread pool. class SimpleThreadPool { public: /// Constructor. /// Number of workers. explicit SimpleThreadPool(uint32_t numberOfWorkers); /// Destructor. virtual ~SimpleThreadPool(); /// Execute the given function in one of the workers. /// Function to run. /// Arguments for function. template void Execute(T&& function, Args&&... args); private: /// Class for worker main loop. class Worker { public: /// Constructor. Worker(SimpleThreadPool& pool); /// Main thread function. void operator()(); private: /// Thread pool instance. SimpleThreadPool& _pool; }; std::vector _workers; std::queue> _taskQueue; /// Critical section and event for task queue. std::mutex _queueLock; std::condition_variable _queueCondition; /// Flag to stop threads. bool _isStopped; }; template void SimpleThreadPool::Execute(T&& function, Args&&... args) { auto task = std::bind(std::forward(function), std::forward(args)...); { std::unique_lock lock(_queueLock); _taskQueue.emplace(task); } _queueCondition.notify_one(); } } } ================================================ FILE: src/zone/task-decorators.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "task.h" #include "terminable-task.h" #include "timer.h" #include #include #include #include namespace napa { namespace zone { template class TaskDecorator : public Task { public: template TaskDecorator(Args&&... args) : _innerTask(std::forward(args)...) {} protected: TaskType _innerTask; }; template class TimeoutTaskDecorator : public TaskDecorator { public: static_assert(std::is_base_of::value, "TaskType must inherit from TerminableTask"); template TimeoutTaskDecorator(std::chrono::milliseconds timeout, Args&&... args) : TaskDecorator(std::forward(args)...), _timeout(timeout) {} void Execute() override { auto isolate = v8::Isolate::GetCurrent(); // RAII - timer will automatically stop upon destruction. napa::zone::Timer timer([this, isolate]() { this->_innerTask.Terminate(TerminationReason::TIMEOUT, isolate); }, _timeout); timer.Start(); this->_innerTask.Execute(); } private: std::chrono::milliseconds _timeout; }; } } ================================================ FILE: src/zone/task.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once namespace napa { namespace zone { /// Represents an execution logic that can be scheduled using the Napa scheduler. class Task { public: /// Executes the task. virtual void Execute() = 0; /// Virtual destructor. virtual ~Task() = default; }; } } ================================================ FILE: src/zone/terminable-task.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. // See: https://groups.google.com/forum/#!topic/nodejs/onA0S01INtw #ifdef BUILDING_NODE_EXTENSION #include #endif #include "terminable-task.h" #include using namespace napa::zone; void TerminableTask::Terminate(TerminationReason reason, v8::Isolate* isolate) { _terminationReason = reason; isolate->TerminateExecution(); } ================================================ FILE: src/zone/terminable-task.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "task.h" namespace v8 { class Isolate; } namespace napa { namespace zone { /// Specifies the possible reasons for termination. enum class TerminationReason { UNKNOWN, TIMEOUT }; /// Base class for tasks that can be terminated. class TerminableTask : public Task { public: /// Use this to terminate a currently running task. /// The reason for the termination. /// The isolate this task currently runs on. void Terminate(TerminationReason reason, v8::Isolate* isolate); protected: TerminationReason _terminationReason = TerminationReason::UNKNOWN; }; } } ================================================ FILE: src/zone/timer.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "timer.h" #include #include #include #include #include #include #include #include #include using namespace napa::zone; struct TimerInfo { bool active; std::chrono::milliseconds timeout; Timer::Callback callback; }; struct ActiveTimerEntry { Timer::Index index; std::chrono::high_resolution_clock::time_point expirationTime; }; static bool operator<(const ActiveTimerEntry& first, const ActiveTimerEntry& second) { // We want the entry that is closest to expire to be at the top of the queue. return first.expirationTime > second.expirationTime; } struct TimersScheduler { ~TimersScheduler(); bool StartMainLoop(); std::priority_queue activeTimers; std::stack freeSlots; std::vector timers; std::condition_variable cv; std::mutex mutex; std::atomic running; std::thread thread; }; TimersScheduler::~TimersScheduler() { running = false; cv.notify_one(); if (thread.joinable()) { thread.join(); } } bool TimersScheduler::StartMainLoop() { running = true; thread = std::thread([this]() { std::unique_lock lock(mutex); // Timers main loop. while (running) { cv.wait(lock, [this]() { return !activeTimers.empty() || !running; }); if (!running) { return; } auto nextExpirationTime = activeTimers.top().expirationTime; if (nextExpirationTime <= std::chrono::high_resolution_clock::now()) { // Pop before callback(), so that it could be re-armed for interval task logic. auto expiredTimer = activeTimers.top(); activeTimers.pop(); if (timers[expiredTimer.index].active) { timers[expiredTimer.index].active = false; try { // Fire the callback. // The callback is assumed to be very fast as it is meant to dispatch to appropriate // callback queues. timers[expiredTimer.index].callback(); } catch (const std::exception &ex) { LOG_ERROR("Timers", "Timer callback threw an exception. %s", ex.what()); } } } else { // Wait for timer expiration. Stop waiting if new urgent active timer is arm-ed. cv.wait_until(lock, nextExpirationTime, [this, nextExpirationTime]() { return activeTimers.top().expirationTime < nextExpirationTime; }); } } }); return true; } static TimersScheduler _timersScheduler; Timer::Timer(Callback callback, std::chrono::milliseconds timeout) { // Start the timers scheduler if this is the first timer created. static bool init = _timersScheduler.StartMainLoop(); std::lock_guard lock(_timersScheduler.mutex); TimerInfo timerInfo{ false, timeout, callback }; if (!_timersScheduler.freeSlots.empty()) { _index = _timersScheduler.freeSlots.top(); _timersScheduler.freeSlots.pop(); _timersScheduler.timers[_index] = std::move(timerInfo); } else { _index = static_cast(_timersScheduler.timers.size()); _timersScheduler.timers.emplace_back(timerInfo); } } Timer::~Timer() { std::lock_guard lock(_timersScheduler.mutex); _timersScheduler.timers[_index].active = false; // Free the timer slot. _timersScheduler.freeSlots.emplace(_index); } void Timer::Start() { { std::lock_guard lock(_timersScheduler.mutex); auto& timerInfo = _timersScheduler.timers[_index]; timerInfo.active = true; ActiveTimerEntry entry = { _index, std::chrono::high_resolution_clock::now() + timerInfo.timeout }; _timersScheduler.activeTimers.emplace(std::move(entry)); } _timersScheduler.cv.notify_one(); } void Timer::Stop() { std::lock_guard lock(_timersScheduler.mutex); _timersScheduler.timers[_index].active = false; } ================================================ FILE: src/zone/timer.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include #include namespace napa { namespace zone { /// A timer class that will trigger a callback after elapsed time. class Timer { public: typedef uint16_t Index; typedef std::function Callback; /// Creates a new timer which is not active initially. /// The callback. /// The timeout in millisecond after which the callback will be triggered. Timer(Callback callback, std::chrono::milliseconds timeout); /// Destructor. Stops the timer. ~Timer(); /// Activates the timer to trigger the callback after specified milliseconds. void Start(); /// Disables the timer, preventing the calback from triggering. void Stop(); private: Index _index; }; } } ================================================ FILE: src/zone/worker-context.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "worker-context.h" #include #include #include #include using namespace napa; using namespace napa::zone; namespace { tls::ThreadLocal(WorkerContextItem::END_OF_WORKER_CONTEXT_ITEM)>> items; } void WorkerContext::Init() { items.Install(); items->fill(nullptr); } void* WorkerContext::Get(WorkerContextItem item) { NAPA_ASSERT(item < WorkerContextItem::END_OF_WORKER_CONTEXT_ITEM, "Invalid WorkerContextItem"); return (*items)[static_cast(item)]; } void WorkerContext::Set(WorkerContextItem item, void* data) { NAPA_ASSERT(item < WorkerContextItem::END_OF_WORKER_CONTEXT_ITEM, "Invalid WorkerContextItem"); (*items)[static_cast(item)] = data; } ================================================ FILE: src/zone/worker-context.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace zone { /// Worker context item to store Napa specific data for a module to be able to access. enum class WorkerContextItem : uint32_t { /// Isolate instance. ISOLATE = 0, /// Module's persistent constructor object. CONSTRUCTOR, /// Module loader instance. MODULE_LOADER, /// Module object for Napa binding. It will be filled under both Napa and Node isolate. NAPA_BINDING, /// Zone instance. ZONE, /// Worker Id. WORKER_ID, /// End of index. END_OF_WORKER_CONTEXT_ITEM }; /// Napa specific data stored at TLS. class NAPA_API WorkerContext { public: /// Initialize isolate data. static void Init(); /// Get stored TLS data. /// Pre-defined data id for Napa specific data. /// Stored TLS data. static void* Get(WorkerContextItem item); /// Set TLS data into the given slot. /// Pre-defined data id for Napa specific data. /// Pointer to stored data. static void Set(WorkerContextItem item, void* data); }; #define INIT_WORKER_CONTEXT napa::zone::WorkerContext::Init } // End of namespace zone. } // End of namespace napa. ================================================ FILE: src/zone/worker.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "worker.h" #include #include #include #include #include #include #include #include #if !(V8_VERSION_CHECK_FOR_ARRAY_BUFFER_ALLOCATOR) #include #endif using namespace napa; using namespace napa::zone; // Forward declaration static v8::Isolate* CreateIsolate(const settings::ZoneSettings& settings); static void ConfigureIsolate(v8::Isolate* isolate, const settings::ZoneSettings& settings); struct Worker::Impl { /// The worker id. WorkerId id; /// The thread that executes the tasks. std::thread workerThread; /// Queue for tasks scheduled on this worker. std::queue> tasks; /// Queue for tasks scheduled on this worker. std::queue> immediateTasks; /// Condition variable to indicate if there are more tasks to consume. std::condition_variable hasTaskEvent; /// Lock for task queue and immediate task queue. std::mutex queueLock; /// V8 isolate associated with this worker. v8::Isolate* isolate; /// A callback function to setup the isolate after worker created its isolate. std::function setupCallback; /// A callback function that is called when worker becomes idle. std::function idleNotificationCallback; /// The zone settings for the current worker. settings::ZoneSettings settings; }; Worker::Worker(WorkerId id, const settings::ZoneSettings& settings, std::function setupCallback, std::function idleNotificationCallback) : _impl(std::make_unique()) { _impl->id = id; _impl->setupCallback = std::move(setupCallback); _impl->idleNotificationCallback = std::move(idleNotificationCallback); _impl->settings = settings; } Worker::~Worker() { // Signal the thread loop that it should stop processing tasks. Enqueue(nullptr, SchedulePhase::DefaultPhase); NAPA_DEBUG("Worker", "(id=%u) Shutting down: Start draining task queue.", _impl->id); _impl->workerThread.join(); if (_impl->isolate != nullptr) { _impl->isolate->Dispose(); } NAPA_DEBUG("Worker", "(id=%u) Shutdown complete.", _impl->id); } Worker::Worker(Worker&&) = default; Worker& Worker::operator=(Worker&&) = default; void Worker::Start() { _impl->workerThread = std::thread(&Worker::WorkerThreadFunc, this, _impl->settings); } void Worker::Schedule(std::shared_ptr task, SchedulePhase phase) { NAPA_ASSERT(task != nullptr, "Task should not be null"); Enqueue(task, phase); NAPA_DEBUG("Worker", "(id=%u) Task queued.", _impl->id); } void Worker::Enqueue(std::shared_ptr task, SchedulePhase phase) { { std::unique_lock lock(_impl->queueLock); if (phase == SchedulePhase::ImmediatePhase && task != nullptr) { _impl->immediateTasks.emplace(std::move(task)); } else { _impl->tasks.emplace(std::move(task)); } } _impl->hasTaskEvent.notify_one(); } void Worker::WorkerThreadFunc(const settings::ZoneSettings& settings) { _impl->isolate = CreateIsolate(settings); // If any user of v8 library uses a locker on any isolate, all isolates must be locked before use. // Since we are 1-1 with threads and isolates, a top level lock that is never released is ok. v8::Locker locker(_impl->isolate); ConfigureIsolate(_impl->isolate, settings); v8::Isolate::Scope isolateScope(_impl->isolate); v8::HandleScope handleScope(_impl->isolate); v8::Local context = v8::Context::New(_impl->isolate); // We set an empty security token so callee can access caller's context. context->SetSecurityToken(v8::Undefined(_impl->isolate)); v8::Context::Scope contextScope(context); NAPA_DEBUG("Worker", "(id=%u) V8 Isolate created.", _impl->id); // Setup worker after isolate creation. _impl->setupCallback(_impl->id); NAPA_DEBUG("Worker", "(id=%u) Setup completed.", _impl->id); while (true) { std::shared_ptr task; { // Logically one merged task queue is the concatenation of immediate queue and // the normal queue. The logically merged queue is treated as empty only when both queues // are empty. And when not empty, immediate tasks will be handled prior to normal tasks. // Inside each single queue (immediate or normal), tasks are first in first out. std::unique_lock lock(_impl->queueLock); if (_impl->tasks.empty() && _impl->immediateTasks.empty()) { _impl->idleNotificationCallback(_impl->id); // Wait until new tasks come. _impl->hasTaskEvent.wait( lock, [this]() { return !(_impl->tasks.empty() && _impl->immediateTasks.empty()); }); } if (_impl->immediateTasks.empty()) { task = _impl->tasks.front(); _impl->tasks.pop(); } else { task = _impl->immediateTasks.front(); _impl->immediateTasks.pop(); } } // A null task means that the worker needs to shutdown. if (task == nullptr) { NAPA_DEBUG("Worker", "(id=%u) Finish serving tasks.", _impl->id); break; } // Resume execution capabilities if isolate was previously terminated. _impl->isolate->CancelTerminateExecution(); task->Execute(); } } static v8::Isolate* CreateIsolate(const settings::ZoneSettings& settings) { v8::Isolate::CreateParams createParams; // The allocator is a global V8 setting. #if V8_VERSION_CHECK_FOR_ARRAY_BUFFER_ALLOCATOR static std::unique_ptr defaultArrayBufferAllocator(v8::ArrayBuffer::Allocator::NewDefaultAllocator()); createParams.array_buffer_allocator = defaultArrayBufferAllocator.get(); #else static napa::v8_extensions::ArrayBufferAllocator commonAllocator; createParams.array_buffer_allocator = &commonAllocator; #endif // Set the maximum V8 heap size. createParams.constraints.set_max_old_space_size(settings.maxOldSpaceSize); createParams.constraints.set_max_semi_space_size(settings.maxSemiSpaceSize); createParams.constraints.set_max_executable_size(settings.maxExecutableSize); return v8::Isolate::New(createParams); } static void ConfigureIsolate(v8::Isolate* isolate, const settings::ZoneSettings& settings) { isolate->SetFatalErrorHandler([](const char* location, const char* message) { LOG_ERROR("V8", "V8 Fatal error at %s. Error: %s", location, message); }); // Prevent V8 from aborting on uncaught exception. isolate->SetAbortOnUncaughtExceptionCallback([](v8::Isolate*) { LOG_ERROR("V8", "V8 uncaught exception was thrown."); return false; }); // V8 takes a pointer to the minimum (x86 stack grows down) allowed stack address // so, capture the current top of the stack and calculate minimum allowed uint32_t currentStackAddress; auto limit = (reinterpret_cast(¤tStackAddress - settings.maxStackSize / sizeof(uint32_t*))); isolate->SetStackLimit(limit); } ================================================ FILE: src/zone/worker.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include "task.h" #include "schedule-phase.h" #include "settings/settings.h" #include #include namespace napa { namespace zone { // Represent the worker id type. using WorkerId = uint32_t; /// Represents an execution unit (a worker) for running tasks. class Worker { public: /// Constructor. /// The task id. /// A settings object. /// Callback to setup the isolate after worker created its isolate. /// Triggers when the worker becomes idle. Worker(WorkerId id, const settings::ZoneSettings &settings, std::function setupCallback, std::function idleNotificationCallback); /// Destructor. /// This will block until all pending tasks are completed. ~Worker(); /// Non-copyable. Worker(const Worker&) = delete; Worker& operator=(const Worker&) = delete; /// Moveable. Worker(Worker&&); Worker& operator=(Worker&&); /// Start the underlying worker thread. void Start(); /// Schedules a task on this worker. /// Task to schedule. /// Same task instance may run on multiple workers, hence the use of shared_ptr. void Schedule(std::shared_ptr task, SchedulePhase phase=SchedulePhase::DefaultPhase); private: /// The worker thread logic. void WorkerThreadFunc(const settings::ZoneSettings& settings); /// Enqueue a task. void Enqueue(std::shared_ptr task, SchedulePhase phase); struct Impl; std::unique_ptr _impl; }; } } ================================================ FILE: src/zone/zone.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include namespace napa { namespace zone { /// Interface for Zone. struct Zone { /// Get the zone id. virtual const std::string& GetId() const = 0; /// Executes a pre-loaded JS function on all zone workers asynchronously. /// The function spec. /// A callback that is triggered when broadcasting is done. virtual void Broadcast(const FunctionSpec& spec, BroadcastCallback callback) = 0; /// Executes a pre-loaded JS function asynchronously. /// The function spec. /// A callback that is triggered when execution is done. virtual void Execute(const FunctionSpec& spec, ExecuteCallback callback) = 0; /// Virtual destructor. virtual ~Zone() {} }; } } ================================================ FILE: test/memory-test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from "../lib/index"; import * as assert from 'assert'; import * as path from 'path'; describe('napajs/memory', function() { let napaZone = napa.zone.create('zone5'); describe('Handle', () => { it('#isEmpty', () => { assert(napa.memory.isEmpty([0, 0])); assert(!napa.memory.isEmpty([1, 2])); }); }); describe('Shareable', () => { it('#isShareable', () => { assert(napa.memory.isShareable(napa.memory.crtAllocator)); assert(!napa.memory.isShareable("hello world")); assert(!napa.memory.isShareable(1)); assert(!napa.memory.isShareable({ a: 1 })); }); }); describe('Allocators', () => { it('@node: crtAllocator', () => { let handle = napa.memory.crtAllocator.allocate(10); assert(!napa.memory.isEmpty(handle)); napa.memory.crtAllocator.deallocate(handle, 10); }); it('@napa: crtAllocator', () => { napaZone.execute('./napa-zone/test', "crtAllocatorTest"); }); it('@node: defaultAllocator', () => { let handle = napa.memory.defaultAllocator.allocate(10); assert(!napa.memory.isEmpty(handle)); napa.memory.defaultAllocator.deallocate(handle, 10); }); it('@napa: defaultAllocator', () => { napaZone.execute('./napa-zone/test', "defaultAllocatorTest"); }); it('@node: debugAllocator', () => { let allocator = napa.memory.debugAllocator(napa.memory.defaultAllocator); let handle = allocator.allocate(10); assert(!napa.memory.isEmpty(handle)); allocator.deallocate(handle, 10); let debugInfo = JSON.parse(allocator.getDebugInfo()); assert.deepEqual(debugInfo, { allocate: 1, allocatedSize: 10, deallocate: 1, deallocatedSize: 10 }); }); it('@napa: debugAllocator', () => { napaZone.execute('./napa-zone/test', "debugAllocatorTest"); }); }); }); ================================================ FILE: test/module/addon/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2 FATAL_ERROR) project("simple-addon") set(NAPA_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../..) # Require Cxx14 features set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # The target name set(TARGET_NAME ${PROJECT_NAME}) # The generated library add_library(${TARGET_NAME} SHARED addon.cpp simple-object-wrap.cpp) # Change output extension to 'napa' set_target_properties(${TARGET_NAME} PROPERTIES PREFIX "" SUFFIX ".napa") # Set output directory for the addon set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${NAPA_ROOT}/bin RUNTIME_OUTPUT_DIRECTORY_RELEASE ${NAPA_ROOT}/bin LIBRARY_OUTPUT_DIRECTORY_DEBUG ${NAPA_ROOT}/bin LIBRARY_OUTPUT_DIRECTORY_RELEASE ${NAPA_ROOT}/bin ) # Include directories target_include_directories(${TARGET_NAME} PRIVATE ${NAPA_ROOT}/inc ${CMAKE_JS_INC}) # Link libraries target_link_libraries(${TARGET_NAME} PRIVATE ${CMAKE_JS_LIB}) # Link with napa shared library if (WIN32) target_link_libraries(${TARGET_NAME} PRIVATE ${NAPA_ROOT}/bin/napa.lib) elseif (APPLE) target_link_libraries(${TARGET_NAME} PRIVATE ${NAPA_ROOT}/bin/libnapa.dylib) else() target_link_libraries(${TARGET_NAME} PRIVATE ${NAPA_ROOT}/bin/libnapa.so) endif() # Compiler definitions target_compile_definitions(${TARGET_NAME} PRIVATE BUILDING_NAPA_EXTENSION) ================================================ FILE: test/module/addon/addon.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include "simple-object-wrap.h" using namespace napa; using namespace napa::test; using namespace napa::module; void GetModuleName(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, "simple-napa-addon")); } void CreateSimpleObjectWrap(const v8::FunctionCallbackInfo& args) { SimpleObjectWrap::NewInstance(args); } void Init(v8::Local exports) { SimpleObjectWrap::Init(); NAPA_SET_METHOD(exports, "getModuleName", GetModuleName); NAPA_SET_METHOD(exports, "createSimpleObjectWrap", CreateSimpleObjectWrap); } NAPA_MODULE(addon, Init); ================================================ FILE: test/module/addon/simple-object-wrap.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "simple-object-wrap.h" #include #include #include #include #ifdef _WIN32 #include #else #include #endif using namespace napa::module; using namespace napa::test; using namespace napa::v8_helpers; NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(SimpleObjectWrap); void SimpleObjectWrap::Init() { auto isolate = v8::Isolate::GetCurrent(); // Prepare constructor template. auto functionTemplate = v8::FunctionTemplate::New(isolate, DefaultConstructorCallback); functionTemplate->SetClassName(MakeV8String(isolate, exportName)); functionTemplate->InstanceTemplate()->SetInternalFieldCount(1); // Prototypes. NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "getValue", GetValue); NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "setValue", SetValue); NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "doIncrementWork", DoIncrementWork); NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "postIncrementWork", PostIncrementWork); // Set persistent constructor into V8. NAPA_SET_PERSISTENT_CONSTRUCTOR(exportName, functionTemplate->GetFunction()); } void SimpleObjectWrap::NewInstance(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); auto constructor = NAPA_GET_PERSISTENT_CONSTRUCTOR(exportName, ZoneWrap); args.GetReturnValue().Set(constructor->NewInstance(context).ToLocalChecked()); } void SimpleObjectWrap::GetValue(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); auto wrap = ObjectWrap::Unwrap(args.Holder()); args.GetReturnValue().Set(wrap->value); } void SimpleObjectWrap::SetValue(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); CHECK_ARG(isolate, args[0]->IsUint32(), "first argument to setValue must be a uint32"); auto wrap = ObjectWrap::Unwrap(args.Holder()); wrap->value = args[0]->Uint32Value(); } void SimpleObjectWrap::DoIncrementWork(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsFunction(), "It requires a callback as arguments"); auto wrap = ObjectWrap::Unwrap(args.Holder()); napa::zone::DoAsyncWork( v8::Local::Cast(args[0]), [wrap](auto complete) { // This runs at the same thread. auto newValue = ++wrap->value; complete(reinterpret_cast(static_cast(newValue))); }, [](auto jsCallback, void* result) { // This runs at the same thread as one DoIncrementWork() is called. auto isolate = v8::Isolate::GetCurrent(); int32_t argc = 1; v8::Local argv[] = { v8::Integer::NewFromUnsigned(isolate, static_cast(reinterpret_cast(result))) }; jsCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv); } ); } void SimpleObjectWrap::PostIncrementWork(const v8::FunctionCallbackInfo& args) { auto isolate = v8::Isolate::GetCurrent(); CHECK_ARG(isolate, args.Length() == 1 && args[0]->IsFunction(), "It requires a callback as arguments"); auto wrap = ObjectWrap::Unwrap(args.Holder()); napa::zone::PostAsyncWork( v8::Local::Cast(args[0]), [wrap]() { #ifdef _WIN32 Sleep(10); #else // Sleep 10 ms. usleep(10 * 1000); #endif // This runs at the separate thread. auto newValue = ++wrap->value; return reinterpret_cast(static_cast(newValue)); }, [](auto jsCallback, void* result) { // This runs at the same thread as one PostIncrementWork() is called. auto isolate = v8::Isolate::GetCurrent(); int32_t argc = 1; v8::Local argv[] = { v8::Integer::NewFromUnsigned(isolate, static_cast(reinterpret_cast(result))) }; jsCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv); } ); } ================================================ FILE: test/module/addon/simple-object-wrap.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #pragma once #include #include namespace napa { namespace test { /// A simple object wrap for testing. class SimpleObjectWrap : public NAPA_OBJECTWRAP { public: /// Exported class name. static constexpr const char* exportName = "SimpleObjectWrap"; /// Initializes the wrap. static void Init(); /// Create a new wrap instance. static void NewInstance(const v8::FunctionCallbackInfo& args); std::atomic value = { 0 }; private: /// Declare persistent constructor to create Zone Javascript wrapper instance. NAPA_DECLARE_PERSISTENT_CONSTRUCTOR(); // SimpleObjectWrap methods static void GetValue(const v8::FunctionCallbackInfo& args); static void SetValue(const v8::FunctionCallbackInfo& args); static void DoIncrementWork(const v8::FunctionCallbackInfo& args); static void PostIncrementWork(const v8::FunctionCallbackInfo& args); /// Friend default constructor callback. template friend void napa::module::DefaultConstructorCallback(const v8::FunctionCallbackInfo&); }; } } ================================================ FILE: test/module/cycle-a.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. exports.done = false; var cycle_b = require('./cycle-b.js'); if (cycle_b.done == true) { exports.done = true; } ================================================ FILE: test/module/cycle-b.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. exports.done = false; var cycle_a = require('./cycle-a.js'); if (cycle_a.done == false) { exports.done = true; } ================================================ FILE: test/module/jsmodule.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. exports.wasLoaded = true; ================================================ FILE: test/module/node_modules/file.js ================================================ ================================================ FILE: test/module/resolution-tests.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. var assert = require('assert'); function run() { // Core module assert(require.resolve('fs'), 'fs'); // Full path with extension assert(require.resolve(__dirname + '/sub-folder/file.js'), __dirname + '/sub-folder/file.js'); // Full path without extension assert(require.resolve(__dirname + '/sub-folder/file'), __dirname + '/sub-folder/file.js'); // Relative path with extension assert(require.resolve('./sub-folder/file.js'), __dirname + '/sub-folder/file.js'); // Relative path without extension assert(require.resolve('./sub-folder/file'), __dirname + '/sub-folder/file.js'); // Relative path with non normalized path assert(require.resolve('./sub-folder/.././sub-folder/file.js'), __dirname + '/sub-folder/file.js'); // Relative path without extension to napa addon assert(require.resolve('./sub-folder/addon/mock-addon'), __dirname + '/sub-folder/addon/mock-addon.napa'); // From node_modules with extension assert(require.resolve('file.js'), __dirname + '/node_modules/file.js'); // From node_modules without extension assert(require.resolve('file'), __dirname + '/node_modules/file.js'); // Resolving non-existing file should throw assert.throws(() => { require.resolve('./sub-folder/non-existing-file.js'); }); } exports.run = run; ================================================ FILE: test/module/sub-folder/addon/mock-addon.napa ================================================ ================================================ FILE: test/module/sub-folder/file.js ================================================ ================================================ FILE: test/module/test.json ================================================ { "prop1": "val1", "prop2": "val2" } ================================================ FILE: test/module-test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from "../lib/index"; import * as assert from "assert"; import * as path from "path"; type Zone = napa.zone.Zone; describe('napajs/module', function () { let napaZone = napa.zone.create('module-tests-zone', { workers: 1 }); describe('load', function () { it('javascript module', () => { return napaZone.execute(() => { var assert = require("assert"); var jsmodule = require('./module/jsmodule'); assert.notEqual(jsmodule, undefined); assert.equal(jsmodule.wasLoaded, true); }); }); it('javascript module from string', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require('path'); var jsmodule = (require)( './module/jsmodule-from-string', "module.exports = function() { return __filename;}"); assert.notEqual(jsmodule, undefined); assert.equal(jsmodule(), path.resolve(__dirname, 'module/jsmodule-from-string')); }); }); it('json module', () => { return napaZone.execute(() => { var assert = require("assert"); var jsonModule = require('./module/test.json'); assert.notEqual(jsonModule, undefined); assert.equal(jsonModule.prop1, "val1"); assert.equal(jsonModule.prop2, "val2"); }); }); it('napa module', () => { return napaZone.execute(() => { var assert = require("assert"); var napaModule = require('../bin/simple-addon.napa'); assert.notEqual(napaModule, undefined); assert.equal(napaModule.getModuleName(), "simple-napa-addon"); }); }); it('object wrap module', () => { return napaZone.execute(() => { var assert = require("assert"); var napaModule = require('../bin/simple-addon.napa'); var obj = napaModule.createSimpleObjectWrap(); assert.notEqual(obj, undefined); obj.setValue(3); assert.equal(obj.getValue(), 3); }, [__dirname]); }); it('circular dependencies', () => { return napaZone.execute(() => { var assert = require("assert"); var cycle_a = require('./module/cycle-a.js'); var cycle_b = require('./module/cycle-b.js'); assert(cycle_a.done); assert(cycle_b.done); }, [__dirname]); }); it('module that does not exist', () => { return napaZone.execute(() => { try { var jsmodule = require('./module/module-does-not-exist'); assert.fail("require on module that does not exist shall throw"); } catch (e) { } }); }); }); describe('resolve', function () { // TODO: support correct __dirname in anonymous function and move tests from 'resolution-tests.js' here. it('require.resolve', () => { return napaZone.execute("./module/resolution-tests.js", "run"); }); }); describe('core-modules', function () { describe('process', function () { it.skip('argv', () => { return napaZone.execute(() => { var assert = require("assert"); assert(process.argv.length > 0); assert(process.argv[0].includes('node')); }); }); it('execPath', () => { return napaZone.execute(() => { var assert = require("assert"); assert(process.execPath.includes('node')); }); }); it('env', () => { return napaZone.execute(() => { var assert = require("assert"); process.env.test = "napa-test"; assert.equal(process.env.test, "napa-test"); }); }); it('platform', () => { return napaZone.execute(() => { var assert = require("assert"); assert(process.platform == 'win32' || process.platform == 'darwin' || process.platform == 'linux' || process.platform == 'freebsd'); }); }); it('umask', () => { return napaZone.execute(() => { var assert = require("assert"); var old = process.umask(0); assert.equal(process.umask(old), 0); }); }); it('chdir', () => { return napaZone.execute(() => { var assert = require("assert"); var cwd = process.cwd(); process.chdir('..'); assert.notEqual(cwd, process.cwd()); assert(cwd.includes(process.cwd())); process.chdir(cwd); assert.equal(cwd, process.cwd()); }); }); it('pid', () => { return napaZone.execute(() => { var assert = require("assert"); assert.notEqual(typeof process.pid, undefined); assert(!isNaN(process.pid)); }); }); }); describe('fs', function () { it('existsSync', () => { return napaZone.execute(() => { var assert = require("assert"); var fs = require('fs'); assert(fs.existsSync(__dirname + '/module/jsmodule.js')); assert(!fs.existsSync(__dirname + '/non-existing-file.txt')); }); }); it('readFileSync', () => { return napaZone.execute(() => { var assert = require("assert"); var fs = require('fs'); var content = JSON.parse(fs.readFileSync(__dirname + '/module/test.json')); assert.equal(content.prop1, 'val1'); assert.equal(content.prop2, 'val2'); }); }); it('mkdirSync', () => { return napaZone.execute(() => { var assert = require("assert"); var fs = require('fs'); fs.mkdirSync(__dirname + '/module/test-dir'); assert(fs.existsSync(__dirname + '/module/test-dir')); }).then(()=> { // Cleanup var fs = require('fs'); if (fs.existsSync('./module/test-dir')) { fs.rmdir('./module/test-dir'); } }) }); it('writeFileSync', () => { return napaZone.execute(() => { var assert = require("assert"); var fs = require('fs'); fs.writeFileSync(__dirname + '/module/test-file', 'test'); assert.equal(fs.readFileSync(__dirname + '/module/test-file'), 'test'); }, [__dirname]).then(()=> { // Cleanup var fs = require('fs'); if (fs.existsSync('./module/test-file')) { fs.unlinkSync('./module/test-file'); } }) }); it('readFileSync', () => { return napaZone.execute(() => { var assert = require("assert"); var fs = require('fs'); var testDir = __dirname + '/module/test-dir'; fs.mkdirSync(testDir); fs.writeFileSync(testDir + '/1', 'test'); fs.writeFileSync(testDir + '/2', 'test'); assert.deepEqual(fs.readdirSync(testDir).sort(), ['1', '2']); }).then(()=> { // Cleanup var fs = require('fs'); if (fs.existsSync('./module/test-dir')) { fs.unlinkSync('./module/test-dir/1'); fs.unlinkSync('./module/test-dir/2'); fs.rmdir('./module/test-dir'); } }) }); }); describe('path', function () { it('normalize', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.normalize('a\\b\\..\\c/./d/././.'), "a\\c\\d"); } else { assert.equal(path.normalize('a\\b\\..\\c/./d/././.'), "a/c/d"); } }); }); it('resolve', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.resolve('c:\\foo/bar', "a.txt"), "c:\\foo\\bar\\a.txt"); assert.equal(path.resolve("abc.txt"), process.cwd() + "\\abc.txt"); assert.equal(path.resolve("abc", "efg", "../hij", "./xyz.txt"), process.cwd() + "\\abc\\hij\\xyz.txt"); assert.equal(path.resolve("abc", "d:/a.txt"), "d:\\a.txt"); } else { assert.equal(path.resolve('/foo/bar', "a.txt"), "/foo/bar/a.txt"); assert.equal(path.resolve("abc.txt"), process.cwd() + "/abc.txt"); assert.equal(path.resolve("abc", "efg", "../hij", "./xyz.txt"), process.cwd() + "/abc/hij/xyz.txt"); assert.equal(path.resolve("abc", "/a.txt"), "/a.txt"); } }); }); it('join', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.join("/foo", "bar", "baz/asdf", "quux", ".."), "\\foo\\bar\\baz\\asdf"); } else { assert.equal(path.join("/foo", "bar", "baz/asdf", "quux", ".."), "/foo/bar/baz/asdf"); } }); }); // TODO: fix bugs // 1. Error: the string "AssertionError: '.' == 'c:'" was thrown, throw an Error :) // 2. Error: the string "AssertionError: 'c:' == 'c:\\\\'" was thrown, throw an Error :) it.skip('dirname', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.dirname("c:"), "c:"); assert.equal(path.dirname("c:\\windows"), "c:\\"); assert.equal(path.dirname("c:\\windows\\abc.txt"), "c:\\windows"); } else { assert.equal(path.dirname("/"), "/"); assert.equal(path.dirname("/etc"), "/"); assert.equal(path.dirname("/etc/passwd"), "/etc"); } }); }); it('basename', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.basename("c:\\windows\\abc.txt"), "abc.txt"); assert.equal(path.basename("c:\\windows\\a"), "a"); assert.equal(path.basename("c:\\windows\\abc.txt", ".txt"), "abc"); assert.equal(path.basename("c:\\windows\\abc.txt", ".Txt"), "abc.txt"); } else { assert.equal(path.basename("/test//abc.txt"), "abc.txt"); assert.equal(path.basename("/test//a"), "a"); assert.equal(path.basename("/test/abc.txt", ".txt"), "abc"); assert.equal(path.basename("/windows/abc.txt", ".Txt"), "abc.txt"); } }); }); // TODO: fix bugs // 1. Error: the string "AssertionError: '' == '.'" was thrown, throw an Error :) it.skip('extname', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.extname("c:\\windows\\abc.txt"), ".txt"); assert.equal(path.extname("c:\\windows\\a.json.txt"), ".txt"); assert.equal(path.extname("c:\\windows\\a."), "."); } else { assert.equal(path.extname("/test/abc.txt"), ".txt"); assert.equal(path.extname("/test/a.json.txt"), ".txt"); assert.equal(path.extname("/test/a."), "."); } }); }); it('isAbsolute', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.isAbsolute("c:\\windows\\a."), true); assert.equal(path.isAbsolute("c:/windows/.."), true); assert.equal(path.isAbsolute("../abc"), false); assert.equal(path.isAbsolute("./abc"), false); assert.equal(path.isAbsolute("abc"), false); } else { assert.equal(path.isAbsolute("/test/a."), true); assert.equal(path.isAbsolute("/test/.."), true); assert.equal(path.isAbsolute("../abc"), false); assert.equal(path.isAbsolute("./abc"), false); assert.equal(path.isAbsolute("abc"), false); } }); }); it('relative', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.relative("c:\\a\\..\\b", "c:\\b"), ""); assert.equal(path.relative("c:/a", "d:/b/../c"), "d:\\c"); assert.equal(path.relative("z:/a", "a.txt"), process.cwd() + "\\a.txt"); assert.equal(path.relative("c:/a", "c:/"), ".."); } else { assert.equal(path.relative("/test/a/../b", "/test/b"), ""); assert.equal(path.relative("/test/a", "/test1/b/../c"), "../../test1/c"); assert.equal(path.relative("/test/a", "a.txt"), "../.." + process.cwd() + "/a.txt"); assert.equal(path.relative("/test/a", "/test/"), ".."); } }); }); it('sep', () => { return napaZone.execute(() => { var assert = require("assert"); var path = require("path"); if (process.platform == 'win32') { assert.equal(path.sep, "\\"); } else { assert.equal(path.sep, "/"); } }); }); }); describe('os', function () { it('type', () => { return napaZone.execute(() => { var assert = require("assert"); var os = require("os"); assert(os.type() == "Windows_NT" || os.type() == "Darwin" || os.type() == "Linux"); }); }); }); }); describe('async', function () { it('post async work', () => { return napaZone.execute(() => { var assert = require("assert"); var napaModule = require('../bin/simple-addon.napa'); var obj = napaModule.createSimpleObjectWrap(); obj.setValue(3); var promise = new Promise((resolve) => { obj.postIncrementWork((newValue: number) => { resolve(newValue); }); }); // The value shouldn't have changed yet. assert.equal(obj.getValue(), 3); return promise; }).then((result: napa.zone.Result) => { assert.equal(result.value, 4); }); }); it('do async work', () => { return napaZone.execute(() => { var assert = require("assert"); var napaModule = require('../bin/simple-addon.napa'); var obj = napaModule.createSimpleObjectWrap(); obj.setValue(8); var promise = new Promise((resolve) => { obj.doIncrementWork((newValue: number) => { resolve(newValue); }); }); // The actual increment happened in the same thread. assert.equal(obj.getValue(), 9); return promise; }).then((result: napa.zone.Result) => { assert.equal(result.value, 9); }); }); }); }); ================================================ FILE: test/napa-zone/function-as-module.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. module.exports = function (input: any): any { return input; } ================================================ FILE: test/napa-zone/test-main.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from '../../lib/index'; export function foo() { return "hello world"; } ================================================ FILE: test/napa-zone/test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as assert from 'assert'; import * as path from 'path'; import * as util from 'util'; import * as napa from '../../lib/index'; export function bar(input: any) { return input; } export namespace ns1 { export namespace ns2 { export function foo(input: any) { return input; } } } export function getCurrentZone(): napa.zone.Zone { return napa.zone.current; } export function broadcast(id: string, code: string): Promise { let zone = napa.zone.get(id); return zone.broadcast(code); } export function broadcastSync(id: string, code: string): void { let zone = napa.zone.get(id); zone.broadcastSync(code); } export function broadcastTestFunction(id: string): Promise { return napa.zone.get(id).broadcast((input: string) => { console.log(input); }, ["hello world"]); } export function broadcastSyncTestFunction(id: string): void { napa.zone.get(id).broadcastSync((input: string) => { console.log(input); }, ["hello world"]); } export function broadcastTransportable(id: string): Promise { return napa.zone.get(id).broadcast((input: any) => { console.log(input); }, [napa.memory.crtAllocator]); } export function broadcastClosure(id: string): Promise { let zone = napa.zone.get(id); return zone.broadcast(() => { console.log(zone); }, []); } export function execute(id: string, moduleName: string, functionName: string, args?: any[]): Promise { let zone = napa.zone.get(id); return new Promise((resolve, reject) => { zone.execute(moduleName, functionName, args) .then((result: napa.zone.Result) => resolve(result.value)) .catch((error: any) => reject(error)); }); } export function executeTestFunction(id: string): Promise { let zone = napa.zone.get(id); return new Promise((resolve, reject) => { zone.execute((input: string) => { return input; }, ['hello world']) .then((result: napa.zone.Result) => resolve(result.value)) .catch((error: any) => reject(error)); }); } export function executeTestFunctionWithClosure(id: string): Promise { let zone = napa.zone.get(id); return new Promise((resolve, reject) => { zone.execute(() => { return zone; }, []) .then((result: napa.zone.Result) => resolve(result.value)) .catch((error: any) => reject(error)); }); } export function waitMS(waitTimeInMS: number): number { var start = new Date().getTime(); var wait = 0; do { wait = new Date().getTime() - start; } while (wait < waitTimeInMS); return wait - waitTimeInMS; } export function executeTestFunctionWithTimeout(id: string, waitTimeInMS: number, timeoutInMS?: number): Promise { timeoutInMS = timeoutInMS ? timeoutInMS : Number.MAX_SAFE_INTEGER; let zone = napa.zone.get(id); return new Promise((resolve, reject) => { zone.execute(waitMS, [waitTimeInMS], {timeout: timeoutInMS}) .then((result: napa.zone.Result) => resolve(result.value)) .catch((error: any) => reject(error)); }); } export function executeWithTransportableArgs(id: string): Promise { let zone = napa.zone.get(id); return new Promise((resolve, reject) => { zone.execute((allocator: napa.memory.Allocator) => { var assert = require("assert"); assert.deepEqual(allocator.handle, (global).napa.memory.crtAllocator.handle); return 1; }, [napa.memory.crtAllocator]) .then ((result: napa.zone.Result) => resolve(result.value)) .catch((error: any) => reject(error)); }); } export function executeWithTransportableReturns(id: string): Promise { let zone = napa.zone.get(id); return new Promise((resolve, reject) => { zone.execute((allocator: napa.memory.Allocator) => { return allocator; }, [napa.memory.crtAllocator]) .then ((result: napa.zone.Result) => resolve(result.value)) .catch((error: any) => reject(error)); }); } export function executeWithArgsContainingUnicodeString(id: string): Promise { let unicodeStr = "中文 español deutsch English हिन्दी العربية português বাংলা русский 日本語 ਪੰਜਾਬੀ 한국어 தமிழ் עברית"; let zone = napa.zone.get(id); return new Promise((resolve, reject) => { zone.execute((str: string) => { var assert = require("assert"); let unicodeStr = "中文 español deutsch English हिन्दी العربية português বাংলা русский 日本語 ਪੰਜਾਬੀ 한국어 தமிழ் עברית"; assert.equal(str, unicodeStr); return str; }, [unicodeStr]) .then ((result: napa.zone.Result) => resolve(result.value)) .catch((error: any) => reject(error)); }); } /// Memory test helpers. export function crtAllocatorTest(): void { let handle = napa.memory.crtAllocator.allocate(10); assert(!napa.memory.isEmpty(handle)); napa.memory.crtAllocator.deallocate(handle, 10); } export function defaultAllocatorTest(): void { let handle = napa.memory.defaultAllocator.allocate(10); assert(!napa.memory.isEmpty(handle)); napa.memory.defaultAllocator.deallocate(handle, 10); } export function debugAllocatorTest(): void { let allocator = napa.memory.debugAllocator(napa.memory.defaultAllocator); let handle = allocator.allocate(10); assert(!napa.memory.isEmpty(handle)); allocator.deallocate(handle, 10); let debugInfo = JSON.parse(allocator.getDebugInfo()); assert.deepEqual(debugInfo, { allocate: 1, allocatedSize: 10, deallocate: 1, deallocatedSize: 10 }); } let store2: napa.store.Store = null; export function getOrCreateStoreTest() { store2 = napa.store.getOrCreate('store2'); assert(store2 != null); assert.equal(store2.id, 'store2'); assert.equal(store2.size, 0); } export function getStoreTest() { let store = napa.store.get('store2'); assert.equal(store.id, 'store2'); // Store created from node zone. let store1 = napa.store.get('store1'); assert.equal(store1.id, 'store1'); } export function storeVerifyGet(storeId: string, key: string, expectedValue: any) { let store = napa.store.get(storeId); assert.deepEqual(store.get(key), expectedValue); } export function storeGetCompareHandle(storeId: string, key: string, expectedHandle: napa.memory.Handle) { let store = napa.store.get(storeId); assert.deepEqual(store.get(key).handle, expectedHandle); } export function storeSet(storeId: string, key: string, expectedValue: any) { let store = napa.store.get(storeId); store.set(key, expectedValue); } export function storeDelete(storeId: string, key: string) { let store = napa.store.get(storeId); store.delete(key); } export function storeVerifyNotExist(storeId: string, key: string) { let store = napa.store.get(storeId); assert(!store.has(key)); assert(store.get(key) === undefined); } /// Transport test helpers. export class CannotPass { field1: string; field2: number; } @napa.transport.cid() export class CanPass extends napa.transport.TransportableObject { constructor(allocator: napa.memory.Allocator) { super(); this._allocator = allocator; } save(payload: any, tc: napa.transport.TransportContext): void { payload['_allocator'] = this._allocator.marshall(tc) } load(payload: any, tc: napa.transport.TransportContext): void { // member '_allocator' is already unmarshalled. this._allocator = payload['_allocator']; } _allocator: napa.memory.Allocator; } @napa.transport.cid() export class CanAutoPass extends napa.transport.AutoTransportable { constructor(data: any) { super(); this._data = data; } _data: any; } function testMarshallUnmarshall(input: any) { let tc = napa.transport.createTransportContext(); let payload = napa.transport.marshall(input, tc); let expected = napa.transport.unmarshall(payload, tc); assert.equal(input.toString(), expected.toString()); assert.equal(JSON.stringify(input), JSON.stringify(expected)); assert.equal(util.inspect(input), util.inspect(expected)); } export function simpleTypeTransportTest() { testMarshallUnmarshall({ a: 'hello', b: { c: [0, 1] }, c: null }); } export function jsTransportTest() { testMarshallUnmarshall(new CanPass(napa.memory.crtAllocator)); } export function jsAutoTransportTest() { testMarshallUnmarshall(new CanAutoPass({ a: 'foo', b: 'bar', c: 123 })); } export function functionTransportTest() { testMarshallUnmarshall(() => { return 0; }); } export function addonTransportTest() { testMarshallUnmarshall(napa.memory.debugAllocator(napa.memory.crtAllocator)); } export function compositeTransportTest() { testMarshallUnmarshall({ a: napa.memory.debugAllocator(napa.memory.crtAllocator), b: [1, 2], c: { d: napa.memory.defaultAllocator, e: 1 } }); } export function nontransportableTest() { let tc = napa.transport.createTransportContext(); let input = new CannotPass(); let success = false; try { napa.transport.marshall(input, tc); success = true; } catch(error) { } assert(!success); } ================================================ FILE: test/store-test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from "../lib/index"; import * as assert from 'assert'; import * as path from 'path'; describe('napajs/store', function () { let napaZone = napa.zone.create('zone6'); let store1 = napa.store.create('store1'); it('@node: store.create - succeed', () => { assert(store1 != null); assert.equal(store1.id, 'store1'); assert.equal(store1.size, 0); }); it('@node: store.create - already exists', () => { let succeed = false; try { let store = napa.store.create('store1'); succeed = true; } catch (error) { } assert(!succeed); }); let store2CreationComplete: Promise; it('@napa: store.getOrCreate', () => { store2CreationComplete = napaZone.execute('./napa-zone/test', "getOrCreateStoreTest"); }); it('@node: store.get', async () => { let store = napa.store.get('store1'); assert.equal(store.id, store1.id); // Store created from napa zone. await store2CreationComplete; let store2 = napa.store.get('store2'); assert.equal(store2.id, 'store2'); }); it('@napa: store.get', async () => { await napaZone.execute('./napa-zone/test', "getStoreTest"); }); it('simple types: set in node, get in node', () => { store1.set('a', 1); assert.equal(store1.get('a'), 1); }); it('simple types: set in node, get in napa', async () => { store1.set('b', 'hi'); await napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'b', 'hi']); }); it('simple types: set in napa, get in napa', async () => { await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'c', 1]); await napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'c', 1]); }); it('simple types: set in napa, get in node', async () => { await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'd', { a: 1, b: 1}]); assert.deepEqual(store1.get('d'), { a: 1, b: 1 }); }); let unicodeStr = "中文 español deutsch English हिन्दी العربية português বাংলা русский 日本語 ਪੰਜਾਬੀ 한국어 தமிழ் עברית"; let store2 = napa.store.create('store2'); it('unicode string: set in node, get in node', () => { store2.set('a', unicodeStr); assert.equal(store2.get('a'), unicodeStr); }); it('unicode string: set in node, get in napa', async () => { store2.set('b', unicodeStr); await napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store2', 'b', unicodeStr]); }); it('unicode string: set in napa, get in napa', async () => { await napaZone.execute('./napa-zone/test', "storeSet", ['store2', 'c', unicodeStr]); await napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store2', 'c', unicodeStr]); }); it('unicode string: set in napa, get in node', async () => { await napaZone.execute('./napa-zone/test', "storeSet", ['store2', 'd', { a: 1, b: unicodeStr}]); assert.deepEqual(store2.get('d'), { a: 1, b: unicodeStr }); }); it('transportable types: set in node, get in node', () => { store1.set('a', napa.memory.crtAllocator); assert.deepEqual(store1.get('a'), napa.memory.crtAllocator); }); it('transportable types: set in node, get in napa', async () => { store1.set('b', napa.memory.defaultAllocator); await napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'b', napa.memory.defaultAllocator]); }); it('transportable types: set in napa, get in napa', async () => { // We have to compare handle in this case, since napa.memory.defaultAllocator retrieved from napa zone will have 2+ refCount. await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'e', napa.memory.defaultAllocator]); await napaZone.execute('./napa-zone/test', "storeGetCompareHandle", ['store1', 'e', napa.memory.defaultAllocator.handle]); }); it('transportable types: set in napa, get in node', async () => { let debugAllocator = napa.memory.debugAllocator(napa.memory.defaultAllocator); await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'f', debugAllocator]); assert.deepEqual(store1.get('f'), debugAllocator); }); it('function type: set in node, get in node', () => { store1.set('g', () => { return 0; }); assert.equal(store1.get('g').toString(), (() => { return 0; }).toString()); }); it('function type: set in node, get in napa', async () => { store1.set('h', () => { return 0; }); await napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'h', () => { return 0; }]); }); it('function type: set in napa, get in napa', async () => { await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'i', () => { return 0; }]); await napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'i', () => { return 0; }]); }); it('function type: set in napa, get in node', async () => { await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'j', () => { return 0; }]); assert.deepEqual(store1.get('j').toString(), (() => { return 0; }).toString()); }); it('delete in node, check in node', () => { assert(store1.has('a')); store1.delete('a'); assert(!store1.has('a')); assert(store1.get('a') === undefined); store1.delete('not-exist'); }); it('delete in node, check in napa', async () => { assert(store1.has('b')); store1.delete('b'); await napaZone.execute('./napa-zone/test', "storeVerifyNotExist", ['store1', 'b']); }); it('delete in napa, check in napa', async () => { await napaZone.execute('./napa-zone/test', "storeDelete", ['store1', 'c']); await napaZone.execute('./napa-zone/test', "storeVerifyNotExist", ['store1', 'c']); }) it('delete in napa, check in node', async () => { await napaZone.execute('./napa-zone/test', "storeDelete", ['store1', 'd']); assert(!store1.has('d')); assert(store1.get('d') === undefined); }); it('size', () => { // set 'a', 'b', 'c', 'd', 'a', 'b', 'e', 'f', 'g', 'h', 'i', 'j'. // delete 'a', 'b', 'c', 'd' assert.equal(store1.size, 6); }); }); ================================================ FILE: test/sync-test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from "../lib/index"; import * as assert from 'assert'; // spin-wait for the specific time, in milliseconds. function spinWait(time: number) { let now = Date.now(); while (Date.now() - now < time) {} } describe('napajs/sync', function () { it('@node: sync.Lock - create a lock', () => { let succeed = false; try { let lock = napa.sync.createLock(); succeed = true; } catch (error) { } assert(succeed); }); it('@node: sync.Lock - parameters passing', () => { let succeed = false; try { let lock = napa.sync.createLock(); lock.guardSync((a, b, c) => { assert.strictEqual(a, 123); assert.strictEqual(b, '456'); assert.strictEqual(c, undefined); }, [123, '456']); succeed = true; } catch (error) { } assert(succeed); }); it('@node: sync.Lock - guard single thread sync execution', () => { let succeed = false; let index = 0; try { let lock = napa.sync.createLock(); lock.guardSync(function () { spinWait(100); assert.equal(index, 0); index++; }); lock.guardSync(function () { spinWait(100); assert.equal(index, 1); index++; }); assert.equal(index, 2); succeed = true; } catch (error) { } assert(succeed); }); it('@napa: sync.Lock - multi thread sync execution (not using lock)', () => { let napaZone = napa.zone.create('zone-for-sync-test-1', { workers: 2 }); napaZone.broadcast(spinWait.toString()); // If not using lock, the store.set() in second execute() function should start before the first complete. let succeed = false; try { let lock = napa.sync.createLock(); // We use napa.store to verify the result let store = napa.store.create('store-for-sync-test-1'); store.set('before-wait', 0); store.set('after-wait', 0); let exe1 = napaZone.execute(function () { let napa = require('../lib/index'); let store = napa.store.get('store-for-sync-test-1'); store.set('before-wait', 1); (global).spinWait(500); store.set('after-wait', 1); }); let exe2 = napaZone.execute(function () { let assert = require('assert'); let napa = require('../lib/index'); let store = napa.store.get('store-for-sync-test-1'); (global).spinWait(100); assert.equal(store.get('before-wait'), 1); assert.equal(store.get('after-wait'), 0); }); return Promise.all([exe1, exe2]).then(function () { assert.equal(store.get('before-wait'), 1); assert.equal(store.get('after-wait'), 1); }); } catch (error) { } }).timeout(5000); it('@napa: sync.Lock - multi thread sync execution (using lock)', () => { let napaZone = napa.zone.create('zone-for-sync-test-2', { workers: 2 }); napaZone.broadcast(spinWait.toString()); // If guard by lock, the store.set() in second execute() function should start after the first complete. let succeed = false; try { let lock = napa.sync.createLock(); // We use napa.store to verify the result let store = napa.store.create('store-for-sync-test-2'); store.set('before-wait', 0); store.set('after-wait', 0); let exe1 = napaZone.execute(function (lock) { let napa = require('../lib/index'); let store = napa.store.get('store-for-sync-test-2'); lock.guardSync(function () { store.set('before-wait', 1); (global).spinWait(500); store.set('after-wait', 1); }); }, [lock]); let exe2 = napaZone.execute(function (lock) { let assert = require('assert'); let napa = require('../lib/index'); let store = napa.store.get('store-for-sync-test-2'); (global).spinWait(100); lock.guardSync(function () { assert.equal(store.get('before-wait'), 1); assert.equal(store.get('after-wait'), 1); }); }, [lock]); return Promise.all([exe1, exe2]).then(function () { assert.equal(store.get('before-wait'), 1); assert.equal(store.get('after-wait'), 1); }); } catch (error) { } }).timeout(5000); }); ================================================ FILE: test/timer-test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from "../lib/index"; import {setImmediate, clearImmediate, setTimeout, clearTimeout, setInterval, clearInterval } from "timers"; // To be execute in napa workers export function setImmediateTest(taskGroupId: number) : Promise { const kTaskGroupSize = 4; const kAllowedScheduleDiffInMS = 200; let correctResult = ""; let lastTaskId = 0; for (let taskId = 0; taskId < kTaskGroupSize; taskId++) { if (taskId != 1) { correctResult = `${correctResult}:${taskId}_OnTime`; lastTaskId = taskId; } } let promise = new Promise((resolve, reject) => { let execResult = ""; for (let taskId = 0; taskId < kTaskGroupSize; taskId++) { let startTime = Date.now(); let immedidate = setImmediate((lastTaskId: number) => { let delayToRun = Date.now() - startTime; execResult = `${execResult}:${taskId}_OnTime`; if (delayToRun > kAllowedScheduleDiffInMS) { execResult = `${execResult}(X)`; } if (taskId == lastTaskId) { if (execResult == correctResult) { resolve(`OK:${execResult}`) } else { reject(`FAIL:${execResult} vs ${correctResult}`) } } }, lastTaskId); if (taskId == 1) { clearImmediate(immedidate); } } }); return promise; } export function setTimeoutTest(taskGroupId: number) : Promise { const kTaskGroupSize = 4; const kAllowedScheduleDiffInMS = 200; setTimeout(() => {}, 10); // Just a warm up. let correctResult = ""; let lastTaskId = 0; for (let taskId = 0; taskId < kTaskGroupSize; taskId++) { if (taskId != 1) { correctResult = `${correctResult}:${taskId}_OnTime`; lastTaskId = taskId; } } let promise = new Promise((resolve, reject) => { let execResult = ""; for (let taskId = 0; taskId < kTaskGroupSize; taskId++) { let wait = 300 * (taskGroupId * kTaskGroupSize + taskId + 1); let startTime = Date.now(); let timeout = setTimeout((lastTaskId: number) => { let waitToRun = Date.now() - startTime; execResult = `${execResult}:${taskId}_OnTime`; if (Math.abs(waitToRun - wait) > kAllowedScheduleDiffInMS) { execResult = `${execResult}(X)`; } if (taskId == lastTaskId) { if (execResult == correctResult) { resolve(`OK:${execResult}`) } else { reject(`FAIL:${execResult} .vs. ${correctResult}`) } } }, wait, lastTaskId); if (taskId == 1) { clearTimeout(timeout); } } }); return promise; } export function setIntervalTest(taskGroupId: number, duration: number, count: number) : Promise { const kAllowedScheduleDiffInMS = 200; let correctResult = ""; for (let i = 0; i < count; ++i) { correctResult += `:${i}_OnTime` } let repeatCount = 0; let execResult = ""; let startTime = Date.now(); let interval = setInterval(() => { let wait = Date.now() - startTime; execResult += `:${repeatCount}_OnTime`; ++repeatCount; let avgScheduleDiff = Math.abs(wait - repeatCount * duration) / repeatCount; if (avgScheduleDiff > kAllowedScheduleDiffInMS) { execResult += `(X)`; } }, duration); let promise = new Promise((resolve, reject) => { setTimeout(() => { if (execResult == correctResult) { resolve(`OK:${execResult}`) } else { reject(`FAIL:${execResult} .vs. ${correctResult}`) } }, duration * (count + 2.6)); }); setTimeout(()=> { clearInterval(interval); }, Math.ceil(duration * (count + 0.8))); return promise; } declare var __in_napa: boolean; if (typeof __in_napa === 'undefined') { let assert = require('assert'); const NUMBER_OF_WORKERS = 3; const kTaskGroupCount = 3; let zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); describe("napajs/timers", () => { describe("setImmediate/clearImmediate", function() { let promises: Promise[] = []; for (let groupId = 0; groupId < kTaskGroupCount; groupId++) { let res = zone.execute('./timer-test', 'setImmediateTest', [groupId]); promises.push(res); } for (let groupId = 0; groupId < kTaskGroupCount; groupId++) { it(`Immediate test group:${groupId} should return string prefixed with OK`, async function() { let result = (await promises[groupId]).value; assert(result.startsWith('OK'), `${result}`); } ); } }); describe("setTimeout/clearTimeout", function() { let promises: Promise[] = []; for (let groupId = 0; groupId < kTaskGroupCount; groupId++) { let res = zone.execute('./timer-test', 'setTimeoutTest', [groupId]); promises.push(res); } for (let groupId = 0; groupId < kTaskGroupCount; groupId++) { it(`Timeout test group:${groupId} should return string prefixed with OK`, async function() { let result = (await promises[groupId]).value; assert(result.startsWith('OK'), `${result}`); } ).timeout(3000);; } }); describe("setInterval/clearInterval", function() { it(`Interval test should return string prefixed with OK`, async function() { let promise = zone.execute('./timer-test', 'setIntervalTest', ["0", 500, 4]); let result = (await promise).value; assert(result.startsWith('OK'), `${result}`); } ).timeout(6000); }); }); } ================================================ FILE: test/transport-test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as napa from "../lib/index"; import * as assert from 'assert'; import * as path from 'path'; import * as t from './napa-zone/test'; describe('napajs/transport', () => { let napaZone = napa.zone.create('zone10'); describe('TransportContext', () => { let tc = napa.transport.createTransportContext(); let allocator = napa.memory.debugAllocator(napa.memory.crtAllocator); it('#saveShared', () => { tc.saveShared(allocator); }); let shareable: napa.memory.Shareable = null; it('#loadShared', () => { shareable = tc.loadShared(allocator.handle); assert.deepEqual(shareable.handle, allocator.handle); }); it('#sharedCount', () => { assert.equal(shareable.refCount, 3); }); }); describe('Transportable', () => { it('#IsTransportable', () => { assert(napa.transport.isTransportable(new t.CanPass(napa.memory.crtAllocator))); assert(napa.transport.isTransportable(1)); assert(napa.transport.isTransportable('hello world')); assert(napa.transport.isTransportable([1, 2, 3])); assert(napa.transport.isTransportable([1, 2, new t.CanPass(napa.memory.crtAllocator)])); assert(napa.transport.isTransportable({ a: 1})); assert(napa.transport.isTransportable({ a: 1, b: new t.CanPass(napa.memory.crtAllocator)})); assert(napa.transport.isTransportable(() => { return 0; })); assert(!napa.transport.isTransportable(new t.CannotPass())); assert(!napa.transport.isTransportable([1, new t.CannotPass()])); assert(!napa.transport.isTransportable({ a: 1, b: new t.CannotPass()})); }); }); describe('Marshall/Unmarshall', () => { it('@node: simple types', () => { t.simpleTypeTransportTest(); }); it('@napa: simple types', () => { napaZone.execute('./napa-zone/test', "simpleTypeTransportTest"); }).timeout(3000); it('@node: JS transportable', () => { t.jsTransportTest(); }); it('@napa: JS transportable', () => { napaZone.execute('./napa-zone/test', "jsTransportTest"); }); it('@node: addon transportable', () => { t.addonTransportTest(); }); it('@napa: addon transportable', () => { napaZone.execute('./napa-zone/test', "addonTransportTest"); }); it('@node: function transportable', () => { t.functionTransportTest(); }); it('@napa: function transportable', () => { napaZone.execute('./napa-zone/test', "functionTransportTest"); }); it('@node: composite transportable', () => { t.compositeTransportTest(); }); it('@napa: composite transportable', () => { napaZone.execute('./napa-zone/test', "compositeTransportTest"); }); it('@node: non-transportable', () => { t.nontransportableTest(); }); it('@napa: non-transportable', () => { napaZone.execute('./napa-zone/test', "nontransportableTest"); }); }); function transportBuiltinObjects() { let zoneId: string = 'transport-built-in-test-zone'; let transportTestZone: napa.zone.Zone = napa.zone.create(zoneId, { workers: 4 }); /// Construct an expected result string. /// constructExpectedResult(5, 5, 255, 0) returns '0,0,0,0,0' /// constructExpectedResult(2, 5, 255, 0) returns '0,0,255,255,255' /// constructExpectedResult(0, 5, 255, 0) returns '255,255,255,255,255' function constructExpectedResult(i: number, size: number, expectedValue: number, defaultValue: number = 0): string { const assert = require('assert'); assert(i >= 0 && size >= i); let expected: string = ''; for (let t: number = 0; t < i; t++) { if (t > 0) expected += ','; expected += defaultValue.toString(); } for (var t = i; t < size; t++) { if (t > 0) expected += ','; expected += expectedValue.toString(); } return expected; } (global).constructExpectedResult = constructExpectedResult; transportTestZone.broadcast("global.constructExpectedResult = " + constructExpectedResult.toString()); it('@node: transport SharedArrayBuffer (SAB)', () => { let promises: Array> = []; let sab: SharedArrayBuffer = new SharedArrayBuffer(4); for (let i: number = 0; i < 4; i++) { promises[i] = transportTestZone.execute((sab, i) => { let ta: Uint8Array = new Uint8Array(sab); ta[i] = 100; }, [sab, i]); } return Promise.all(promises).then((values: Array) => { let ta: Uint8Array = new Uint8Array(sab); assert.deepEqual(ta.toString(), '100,100,100,100'); }); }); it('@node: transport composite object of SharedArrayBuffer', () => { let sab: SharedArrayBuffer = new SharedArrayBuffer(4); let ta1: Uint8Array = new Uint8Array(sab); let ta2: Uint8Array = new Uint8Array(sab); let obj: Object = { sab: sab, tas: { ta1: ta1, ta2: ta2 }, ta22:ta2 }; return transportTestZone.execute((obj) => { let ta: Uint8Array = new Uint8Array(obj.sab); ta[0] = 99; obj.tas.ta1[1] = 88; obj.tas.ta2[2] = 77; obj.ta22[3] = 66; }, [obj]).then((result: napa.zone.Result) => { var ta_sab: Uint8Array = new Uint8Array(sab); assert.deepEqual(ta_sab.toString(), '99,88,77,66'); }); }); function recursivelySetElementOfSharedArrayBuffer(zoneId: string, sab: SharedArrayBuffer, i: number, value: number) { if (i < 0) return; let ta: Uint8Array = new Uint8Array(sab); ta[i] = value; const assert = require('assert'); // SharedArrayBuffer shares storage when it is transported, // so elements with index > i have been set to {value} by those finished zone.executions. let expected: string = (global).constructExpectedResult(i, ta.length, value); assert.equal(ta.toString(), expected); const napa = require('../lib/index'); let zone: napa.zone.Zone = (i % 4 < 2) ? napa.zone.get(zoneId) : napa.zone.node; zone.execute( recursivelySetElementOfSharedArrayBuffer, [zoneId, sab, i - 1, value] ).then((result: napa.zone.Result) => { // SharedArrayBuffer shares storage when it is transported, // if i > 0, ta[i - 1] has been set to {value} by the previous zone.execute, // so ta.toString() should be larger than {expected} constructed before. if (i > 0) assert(ta.toString() > expected); else if (i === 0) assert.equal(ta.toString(), expected); else assert(false); }); } // @node: node -> napa -> napa -> node -> node -> napa -> napa it('@node: recursively transport received SharedArrayBuffer (SAB)', () => { let size: number = 8; let timeout: number = 50; let value: number = 255; let sab: SharedArrayBuffer = new SharedArrayBuffer(size); let ta: Uint8Array = new Uint8Array(sab); recursivelySetElementOfSharedArrayBuffer(zoneId, sab, size - 1, value); return new Promise((resolve, reject) => { setTimeout(() => { // Because SharedArrayBuffer will share storage when it is transported, // once the recursive process finished, all elements of // the original TypeArray (based on SharedArrayBuffer) should have been set to {value}. let expected = (global).constructExpectedResult(0, ta.length, value); assert.equal(ta.toString(), expected); resolve(); }, timeout); }); }); function recursivelySetElementOfTypedArray_SAB(zoneId: string, ta: Uint8Array, i: number, value: number) { if (i < 0) return; ta[i] = value; const assert = require('assert'); // SharedArrayBuffer shares storage when it is transported, // so elements with index > i have been set to {value} by those finished zone.executions. let expected: string = (global).constructExpectedResult(i, ta.length, value); assert.equal(ta.toString(), expected); const napa = require('../lib/index'); let zone: napa.zone.Zone = (i % 4 < 2) ? napa.zone.get(zoneId) : napa.zone.node; zone.execute( recursivelySetElementOfTypedArray_SAB, [zoneId, ta, i - 1, value] ).then((result: napa.zone.Result) => { // SharedArrayBuffer shares storage when it is transported, // if i > 0, ta[i - 1] has been set to {value} by the previous zone.execute, // so ta.toString() should be larger than {expected} constructed before. if (i > 0) assert(ta.toString() > expected); else if (i === 0) assert.equal(ta.toString(), expected); else assert(false); }); } // @node: node -> napa -> napa -> node -> node -> napa -> napa it('@node: recursively transport received TypedArray based on SAB', () => { let size: number = 8; let timeout: number = 50; let value: number = 255; let sab: SharedArrayBuffer = new SharedArrayBuffer(size); let ta: Uint8Array = new Uint8Array(sab); recursivelySetElementOfTypedArray_SAB(zoneId, ta, size - 1, value); return new Promise((resolve, reject) => { setTimeout(() => { // Because SharedArrayBuffer will share storage when it is transported, // once the recursive process finished, all elements of // the original TypeArray (based on SharedArrayBuffer) should have been set to {value}. let expected: string = (global).constructExpectedResult(0, ta.length, value); assert.equal(ta.toString(), expected); resolve(); }, timeout); }); }); function recursivelySetElementOfArrayBuffer(zoneId: string, ab: ArrayBuffer, i: number, value: number) { if (i < 0) { return; } let ta: Uint8Array = new Uint8Array(ab); ta[i] = value; const assert = require('assert'); // ArrayBuffer's storage will be copied when it is transported. // Elements with index > i should all be {value}. // They are copied from the previous zone.execution. let expected: string = (global).constructExpectedResult(i, ta.length, value); assert.equal(ta.toString(), expected); const napa = require('../lib/index'); let zone: napa.zone.Zone = (i % 4 < 2) ? napa.zone.get(zoneId) : napa.zone.node; zone.execute( recursivelySetElementOfArrayBuffer, [zoneId, ab, i - 1, value] ).then((result: napa.zone.Result) => { // The original TypeArray (based on ArrayBuffer) shouldn't been changed by the just-finished zone.execute. assert.equal(ta.toString(), expected); }); } // @node: node -> napa -> napa -> node -> node -> napa -> napa it('@node: recursively transport received ArrayBuffer (AB)', () => { let size: number = 8; let timeout: number = 50; let value: number = 255; let ab: ArrayBuffer = new ArrayBuffer(size); let ta: Uint8Array = new Uint8Array(ab); recursivelySetElementOfArrayBuffer(zoneId, ab, size - 1, value); return new Promise((resolve, reject) => { setTimeout(() => { // Except ta[ta-length -1] was set to {value} before the 1st transportation, // the original TypeArray (based on ArrayBuffer) shouldn't been changed by the recursive execution. let expected: string = (global).constructExpectedResult(ta.length - 1, ta.length, value); assert.equal(ta.toString(), expected); resolve(); }, timeout); }); }); function recursivelySetElementOfTypeArray_AB(zoneId: string, ta: Uint8Array, i: number, value: number) { if (i < 0) { return; } ta[i] = value; const assert = require('assert'); // ArrayBuffer's storage will be copied when it is transported. // Elements with index > i should all be {value}. // They are copied from the previous zone.execution. let expected: string = (global).constructExpectedResult(i, ta.length, value); assert.equal(ta.toString(), expected); const napa = require('../lib/index'); let zone: napa.zone.Zone = (i % 4 < 2) ? napa.zone.get(zoneId) : napa.zone.node; zone.execute( recursivelySetElementOfTypeArray_AB, [zoneId, ta, i - 1, value] ).then((result: napa.zone.Result) => { // The original TypeArray (based on ArrayBuffer) shouldn't been changed by the just-finished zone.execute. assert.equal(ta.toString(), expected); }); } // @node: node -> napa -> napa -> node -> node -> napa -> napa it('@node: recursively transport received TypedArray based on AB', () => { let size: number = 8; let timeout: number = 50; let value: number = 255; let ab: ArrayBuffer = new ArrayBuffer(size); let ta: Uint8Array = new Uint8Array(ab); recursivelySetElementOfTypeArray_AB(zoneId, ta, size - 1, value); return new Promise((resolve, reject) => { setTimeout(() => { // Except ta[ta-length -1] was set to {value} before the 1st transportation, // the original TypeArray (based on ArrayBuffer) shouldn't been changed by the recursive execution. let expected: string = (global).constructExpectedResult(ta.length - 1, ta.length, value); assert.equal(ta.toString(), expected); resolve(); }, timeout); }); }); } let builtinTestGroup = 'Transport built-in objects'; let nodeVersionMajor = parseInt(process.versions.node.split('.')[0]); if (nodeVersionMajor >= 9) { describe(builtinTestGroup, transportBuiltinObjects); } else { describe.skip(builtinTestGroup, transportBuiltinObjects); require('npmlog').warn(builtinTestGroup, 'This test group is skipped since it requires node v9.0.0 or above.'); } }); ================================================ FILE: test/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es5", "experimentalDecorators": true, "noImplicitAny": true, "declaration": false, "preserveConstEnums": true, "lib": [ "es2017" ] } } ================================================ FILE: test/zone-test.ts ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import * as assert from "assert"; import * as path from "path"; import * as napa from "../lib/index"; type Zone = napa.zone.Zone; function shouldFail(func: () => Promise) { return func().then( (value: T) => { assert(false, "Failure was expected."); }, (reason: any) => { // Swallow the rejection since we expect failure }); } describe('napajs/zone', function () { let napaZone1: Zone = napa.zone.create('napa-zone1'); let napaZone2: Zone = napa.zone.create('napa-zone2'); let napaLibPath: string = path.resolve(__dirname, '../lib'); describe('create', () => { it('@node: default settings', () => { assert(napaZone1 != null); assert.strictEqual(napaZone1.id, 'napa-zone1'); }); // This case may be slow as the first hit of napa zone execute API, so we clear timeout. it('@napa: default settings', async () => { // Zone should be destroyed when going out of scope. let result = await napaZone1.execute(`${napaLibPath}/zone`, 'create', ['new-zone']); assert.equal(result.value.id, "new-zone"); }).timeout(0); it('@node: zone id already exists', () => { assert.throws(() => { napa.zone.create('napa-zone1'); }); }); it('@napa: zone id already exists', () => { return shouldFail(() => { return napaZone1.execute(`${napaLibPath}/zone`, 'create', ['napa-zone1']); }); }); }); describe("get", () => { it('@node: get node zone', () => { let zone = napa.zone.get('node'); assert(zone != null); assert.strictEqual(zone.id, 'node'); }); it('@node: get napa zone', () => { let zone = napa.zone.get('napa-zone1'); assert(zone != null); assert.strictEqual(zone.id, 'napa-zone1'); }); it('@napa: get napa zone', async () => { let result = await napaZone1.execute(`${napaLibPath}/zone`, "get", ['napa-zone1']); assert.strictEqual(result.value.id, 'napa-zone1'); }); it('@napa: get node zone', async () => { let result = await napaZone1.execute(`${napaLibPath}/zone`, "get", ['node']); assert.strictEqual(result.value.id, 'node'); }); it('@node: get napa created zone', () => { let zone = napa.zone.get('napa-zone2'); assert(zone != null); assert.strictEqual(zone.id, 'napa-zone2'); }); it('@napa: get napa created zone', async () => { let result = await napaZone1.execute(`${napaLibPath}/zone`, 'get', ['napa-zone2']); assert.strictEqual(result.value.id, 'napa-zone2'); }); it('@node: id not existed', () => { assert.throws(() => { napa.zone.get('zonex'); }); }); it('@napa: zone not existed', () => { return shouldFail(() => { return napaZone1.execute(`${napaLibPath}/zone`, 'get', ['zonex']); }); }); }); describe("currentZone", () => { it('@node', () => { assert.strictEqual(napa.zone.current.id, 'node'); }); it('@napa', async () => { let result = await napaZone1.execute('./napa-zone/test', "getCurrentZone"); assert.strictEqual(result.value.id, 'napa-zone1'); }); }); describe('broadcast', () => { it('@node: -> node zone with JavaScript code', () => { return napa.zone.current.broadcast("var state = 0;"); }); it('@node: -> napa zone with JavaScript code', () => { return napaZone1.broadcast("var state = 0;"); }); it('@napa: -> napa zone with JavaScript code', () => { return napaZone1.execute('./napa-zone/test', "broadcast", ["napa-zone2", "var state = 0;"]); }); it('@napa: -> napa zone with JavaScript code', () => { return napaZone1.execute('./napa-zone/test', "broadcast", ["napa-zone1", "var state = 0;"]); }); it('@napa: -> node zone with JavaScript code', () => { return napaZone1.execute('./napa-zone/test', "broadcast", ["node", "var state = 0;"]); }); it('@node: bad JavaScript code', () => { return shouldFail(() => { return napaZone1.broadcast("var state() = 0;"); }); }); it('@napa: bad JavaScript code', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', "broadcast", ["napa-zone2", "var state() = 0;"]); }); }); it('@node: -> node zone throw runtime error', () => { return shouldFail(() => { return napa.zone.current.broadcast("throw new Error();"); }); }); it('@node: -> napa zone throw runtime error', () => { return shouldFail(() => { return napaZone1.broadcast("throw new Error();"); }); }); it('@napa: -> napa zone throw runtime error', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', "broadcast", ["napa-zone2", "throw new Error();"]); }); }); it('@napa: -> node zone throw runtime error', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', "broadcast", ["node", "throw new Error();"]); }); }); it('@node: -> node zone with anonymous function', () => { return napa.zone.current.broadcast((input: string) => { console.log(input); }, ['hello world']); }); it('@node: -> napa zone with anonymous function', () => { return napaZone1.broadcast((input: string) => { console.log(input); }, ['hello world']); }); it('@napa: -> napa zone with anonymous function', () => { return napaZone1.execute('./napa-zone/test', "broadcastTestFunction", ['napa-zone2']); }); it('@napa: -> node zone with anonymous function', () => { return napaZone1.execute('./napa-zone/test', "broadcastTestFunction", ['node']); }); // TODO #4: support transportable args in broadcast. it.skip('@node: -> node zone with transportable args', () => { return napa.zone.current.broadcast((allocator: any) => { console.log(allocator); }, [napa.memory.crtAllocator]); }); // TODO #4: support transportable args in broadcast. it.skip('@node: -> napa zone with transportable args', () => { return napaZone1.broadcast((allocator: any) => { console.log(allocator); }, [napa.memory.crtAllocator]); }); // Blocked by TODO #4. it.skip('@napa: -> napa zone with transportable args', () => { return napaZone1.execute('./napa-zone/test', "broadcastTransportable", ['napa-zone2']); }); // Blocked by TODO #4. it.skip('@napa: -> node zone with transportable args', () => { return napaZone1.execute('./napa-zone/test', "broadcastTransportable", ['node']); }); // This will not fail any more because current implementation uses function `eval()`. // Re-enable this case if we use `Function()` in future it.skip('@node: -> node zone with anonymous function having closure (should fail)', () => { return shouldFail(() => { return napa.zone.current.broadcast(() => { console.log(napaZone1.id); }); }); }); it('@node: -> napa zone with anonymous function having closure (should fail)', () => { return shouldFail(() => { return napaZone1.broadcast(() => { console.log(napaZone1.id); }); }); }); it('@napa: -> napa zone with anonymous function having closure (should fail)', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', "broadcastClosure", ['napa-zone2']); }); }); it('@napa: -> node zone with anonymous function having closure (should fail)', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', "broadcastClosure", ['node']); }); }); }); describe('broadcastSync', () => { it('@node: -> napa zone with JavaScript code', () => { napaZone1.broadcastSync("var state = 0;"); }); it('@napa: -> napa zone with JavaScript code in different zone', () => { return napaZone1.execute('./napa-zone/test', "broadcastSync", ["napa-zone2", "var state = 0;"]); }); it('@napa: -> napa zone with JavaScript code in current zone (should fail)', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', "broadcastSync", ["napa-zone1", "var state = 0;"]); }); }); it('@node: -> napa zone with anonymous function', () => { napaZone1.broadcastSync((input: string) => { console.log(input); }, ['hello world']); }); it('@napa: -> napa zone with anonymous function', () => { return napaZone1.execute('./napa-zone/test', "broadcastSyncTestFunction", ['napa-zone2']); }); it('@node: -> napa zone throw runtime error', () => { assert.throws(() => { napaZone1.broadcastSync("throw new Error();"); }, Error); }); it('@napa: -> napa zone throw runtime error', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', "broadcastSync", ["napa-zone2", "throw new Error();"]); }); }); }); describe('execute', () => { let fooDef = 'function foo(input) { return input; }'; let nestedFunctionDef = ` var ns1 = { ns2: { foo: function (input) { return input; } } }; `; napaZone1.broadcast(fooDef); napaZone1.broadcast(nestedFunctionDef); napaZone2.broadcast(fooDef); napa.zone.node.broadcast(fooDef); napa.zone.node.broadcast(nestedFunctionDef); it('@node: -> node zone with global function name', () => { return napa.zone.current.execute("", "foo", ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> napa zone with global function name', () => { return napaZone1.execute("", "foo", ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@napa: -> napa zone with global function name', () => { return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone2", "", "foo", ['hello world']]) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@napa: -> node zone with global function name', () => { return napaZone1.execute('./napa-zone/test', 'execute', ["node", "", "foo", ['hello world']]) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> napa zone with global function name: function with namespaces', () => { return napaZone1.execute("", "ns1.ns2.foo", ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> node zone with global function name not exists', () => { return shouldFail(() => { return napa.zone.current.execute("", "foo1", ['hello world']); }); }); it('@node: -> napa zone with global function name not exists', () => { return shouldFail(() => { return napaZone1.execute("", "foo1", ['hello world']); }); }); it('@napa: -> napa zone with global function name not exists', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone2", "", "foo1", []]); }); }); it('@napa: -> node zone with global function name not exists', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'execute', ["node", "", "foo1", []]); }); }); it('@node: -> node zone with module function name', () => { return napa.zone.current.execute('./napa-zone/test', "bar", ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> napa zone with module function name', () => { return napaZone1.execute('./napa-zone/test', "bar", ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@napa: -> napa zone with module function name', () => { return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone2", path.resolve(__dirname, './napa-zone/test'), "bar", ['hello world']]) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@napa: -> node zone with module function name', () => { return napaZone1.execute('./napa-zone/test', 'execute', ["node", path.resolve(__dirname, './napa-zone/test'), "bar", ['hello world']]) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> napa zone with module function name: function with namespaces', () => { return napaZone1.execute('./napa-zone/test', "ns1.ns2.foo", ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> napa zone with module function name: module is a function', () => { return napaZone1.execute(path.resolve(__dirname, "./napa-zone/function-as-module"), "", ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> node zone with module not exists', () => { return shouldFail(() => { return napa.zone.current.execute("abc", "foo1", ['hello world']); }); }); it('@node: -> napa zone with module not exists', () => { return shouldFail(() => { return napaZone1.execute("abc", "foo1", ['hello world']); }); }); it('@napa: -> napa zone with module not exists', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone2", "abc", ".foo", []]); }); }); it('@napa: -> node zone with module not exists', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'execute', ["node", "abc", "foo.", []]); }); }); it('@node: -> node zone with module function not exists', () => { return shouldFail(() => { return napa.zone.current.execute('./napa-zone/test', "foo1", ['hello world']); }); }); it('@node: -> napa zone with module function not exists', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', "foo1", ['hello world']) }); }); it('@napa: -> napa zone with module function not exists', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone1", './napa-zone/test', "foo1", []]); }); }); it('@napa: -> node zone with module function not exists', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'execute', ["node", './napa-zone/test', "foo1", []]); }); }); it('@node: -> node zone with anonymous function', () => { return napa.zone.current.execute((input: string) => { return input; }, ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> napa zone with anonymous function', () => { return napaZone1.execute((input: string) => { return input; }, ['hello world']) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@napa: -> napa zone with anonymous function', () => { return napaZone1.execute('./napa-zone/test', 'executeTestFunction', ["napa-zone2"]) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@napa: -> node zone with anonymous function', () => { return napaZone1.execute('./napa-zone/test', 'executeTestFunction', ["node"]) .then((result: napa.zone.Result) => { assert.equal(result.value, 'hello world'); }); }); it('@node: -> node zone with anonymous function having closure (should success)', () => { return napa.zone.current.execute(() => { return napaZone1; }); }); it('@node: -> napa zone with anonymous function having closure (should fail)', () => { return shouldFail(() => { return napaZone1.execute(() => { return napaZone1; }); }); }); it('@napa: -> napa zone with anonymous function having closure (should fail)', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'executeTestFunctionWithClosure', ["napa-zone2"]); }); }); it('@napa: -> node zone with anonymous function having closure (should fail)', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'executeTestFunctionWithClosure', ["node"]); }); }); it('@node: -> node zone with transportable args', () => { return napa.zone.current.execute((allocator: napa.memory.Allocator) => { var assert = require("assert"); assert.deepEqual(allocator.handle, (global).napa.memory.crtAllocator.handle); }, [napa.memory.crtAllocator]); }); it('@node: -> napa zone with transportable args', () => { return napaZone1.execute((allocator: napa.memory.Allocator) => { var assert = require("assert"); assert.deepEqual(allocator.handle, (global).napa.memory.crtAllocator.handle); }, [napa.memory.crtAllocator]); }); it('@napa: -> napa zone with transportable args', () => { return napaZone1.execute('./napa-zone/test', "executeWithTransportableArgs", ['napa-zone2']); }); it('@napa: -> node zone with transportable args', () => { return napaZone1.execute('./napa-zone/test', "executeWithTransportableArgs", ['node']); }); it('@node: -> node zone with transportable returns', () => { return napa.zone.current.execute((allocator: napa.memory.Allocator) => { return allocator; }, [napa.memory.crtAllocator]) .then((result: napa.zone.Result) => { assert.deepEqual(result.value.handle, napa.memory.crtAllocator.handle); }); }); it('@node: -> napa zone with transportable returns', () => { return napaZone1.execute((allocator: napa.memory.Allocator) => { return allocator; }, [napa.memory.crtAllocator]) .then((result: napa.zone.Result) => { assert.deepEqual(result.value.handle, napa.memory.crtAllocator.handle); }); }); it('@napa: -> napa zone with transportable returns', () => { return napaZone1.execute('./napa-zone/test', "executeWithTransportableReturns", ['napa-zone2']) .then((result: napa.zone.Result) => { assert.deepEqual(result.value.handle, napa.memory.crtAllocator.handle); }); }); it('@napa: -> node zone with transportable returns', () => { return napaZone1.execute('./napa-zone/test', "executeWithTransportableReturns", ['node']) .then((result: napa.zone.Result) => { assert.deepEqual(result.value.handle, napa.memory.crtAllocator.handle); }); }); let unicodeStr = "中文 español deutsch English हिन्दी العربية português বাংলা русский 日本語 ਪੰਜਾਬੀ 한국어 தமிழ் עברית"; // len = 92 it('@node: -> node zone with args containing unicode string', () => { return napa.zone.current.execute((str: string) => { var assert = require("assert"); let unicodeStr = "中文 español deutsch English हिन्दी العربية português বাংলা русский 日本語 ਪੰਜਾਬੀ 한국어 தமிழ் עברית"; assert.equal(str, unicodeStr); return str; }, [unicodeStr]).then((result: napa.zone.Result) => { assert.equal(result.value, unicodeStr); }); }); it('@node: -> napa zone with args containing unicode string', () => { return napaZone1.execute((str: string) => { var assert = require("assert"); let unicodeStr = "中文 español deutsch English हिन्दी العربية português বাংলা русский 日本語 ਪੰਜਾਬੀ 한국어 தமிழ் עברית"; assert.equal(str, unicodeStr); return unicodeStr; }, [unicodeStr]).then((result: napa.zone.Result) => { assert.equal(result.value, unicodeStr); }); }); it('@napa: -> napa zone with args containing unicode string', () => { return napaZone1.execute('./napa-zone/test', "executeWithArgsContainingUnicodeString", ['napa-zone2']) .then((result: napa.zone.Result) => { assert.equal(result.value, unicodeStr); }); }); it('@napa: -> node zone with args containing unicode string', () => { return napaZone1.execute('./napa-zone/test', "executeWithArgsContainingUnicodeString", ['node']) .then((result: napa.zone.Result) => { assert.equal(result.value, unicodeStr); }); }); it.skip('@node: -> napa zone with timeout and succeed', () => { return napaZone1.execute('./napa-zone/test', 'waitMS', [1], {timeout: 100}); }); it.skip('@napa: -> napa zone with timeout and succeed', () => { return napaZone1.execute('./napa-zone/test', 'executeTestFunctionWithTimeout', ["napa-zone2", 1], {timeout: 100}); }); it.skip('@node: -> napa zone with timed out in JavaScript', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'waitMS', [100], {timeout: 1}); }); }); it.skip('@napa: -> napa zone with timed out in JavaScript', () => { return shouldFail(() => { return napaZone1.execute('./napa-zone/test', 'executeTestFunctionWithTimeout', ["napa-zone2", 100], {timeout: 1}); }); }); it.skip('@node: -> napa zone with timed out in add-on', () => { }); it.skip('@napa: -> napa zone with timed out in add-on', () => { }); it.skip('@node: -> napa zone with timed out in multiple hops', () => { }); it.skip('@napa: -> napa zone with timed out in multiple hops', () => { }); }); }); ================================================ FILE: third-party/args/LICENSE ================================================ Copyright (c) 2016-2017 Taylor C. Richberger and Pavel Belikov 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. ================================================ FILE: third-party/args/args.hxx ================================================ /* Copyright (c) 2016-2017 Taylor C. Richberger and Pavel * Belikov * * 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. */ /** \file args.hxx * \brief this single-header lets you use all of the args functionality * * The important stuff is done inside the args namespace */ #ifndef ARGS_HXX #define ARGS_HXX #include #include #include #include #include #include #include #include #include #include #include #ifdef ARGS_TESTNAMESPACE namespace argstest { #else /** \namespace args * \brief contains all the functionality of the args library */ namespace args { #endif /** Getter to grab the value from the argument type. * * If the Get() function of the type returns a reference, so does this, and * the value will be modifiable. */ template auto get(Option &option_) -> decltype(option_.Get()) { return option_.Get(); } /** (INTERNAL) Count UTF-8 glyphs * * This is not reliable, and will fail for combinatory glyphs, but it's * good enough here for now. * * \param string The string to count glyphs from * \return The UTF-8 glyphs in the string */ inline std::string::size_type Glyphs(const std::string &string_) { std::string::size_type length = 0; for (const char c: string_) { if ((c & 0xc0) != 0x80) { ++length; } } return length; } /** (INTERNAL) Wrap a vector of words into a vector of lines * * Empty words are skipped. Word "\n" forces wrapping. * * \param begin The begin iterator * \param end The end iterator * \param width The width of the body * \param firstlinewidth the width of the first line, defaults to the width of the body * \param firstlineindent the indent of the first line, defaults to 0 * \return the vector of lines */ template inline std::vector Wrap(It begin, It end, const std::string::size_type width, std::string::size_type firstlinewidth = 0, std::string::size_type firstlineindent = 0) { std::vector output; std::string line(firstlineindent, ' '); bool empty = true; if (firstlinewidth == 0) { firstlinewidth = width; } auto currentwidth = firstlinewidth; for (auto it = begin; it != end; ++it) { if (it->empty()) { continue; } if (*it == "\n") { if (!empty) { output.push_back(line); line.clear(); empty = true; currentwidth = width; } continue; } auto itemsize = Glyphs(*it); if ((line.length() + 1 + itemsize) > currentwidth) { if (!empty) { output.push_back(line); line.clear(); empty = true; currentwidth = width; } } if (itemsize > 0) { if (!empty) { line += ' '; } line += *it; empty = false; } } if (!empty) { output.push_back(line); } return output; } namespace detail { template std::string Join(const T& array, const std::string &delimiter) { std::string res; for (auto &element : array) { if (!res.empty()) { res += delimiter; } res += element; } return res; } } /** (INTERNAL) Wrap a string into a vector of lines * * This is quick and hacky, but works well enough. You can specify a * different width for the first line * * \param width The width of the body * \param firstlinewid the width of the first line, defaults to the width of the body * \return the vector of lines */ inline std::vector Wrap(const std::string &in, const std::string::size_type width, std::string::size_type firstlinewidth = 0) { // Preserve existing line breaks const auto newlineloc = in.find('\n'); if (newlineloc != in.npos) { auto first = Wrap(std::string(in, 0, newlineloc), width); auto second = Wrap(std::string(in, newlineloc + 1), width); first.insert( std::end(first), std::make_move_iterator(std::begin(second)), std::make_move_iterator(std::end(second))); return first; } std::istringstream stream(in); std::string::size_type indent = 0; for (char c : in) { if (!isspace(c)) { break; } ++indent; } return Wrap(std::istream_iterator(stream), std::istream_iterator(), width, firstlinewidth, indent); } #ifdef ARGS_NOEXCEPT /// Error class, for when ARGS_NOEXCEPT is defined enum class Error { None, Usage, Parse, Validation, Required, Map, Extra, Help, Subparser, Completion, }; #else /** Base error class */ class Error : public std::runtime_error { public: Error(const std::string &problem) : std::runtime_error(problem) {} virtual ~Error() {} }; /** Errors that occur during usage */ class UsageError : public Error { public: UsageError(const std::string &problem) : Error(problem) {} virtual ~UsageError() {} }; /** Errors that occur during regular parsing */ class ParseError : public Error { public: ParseError(const std::string &problem) : Error(problem) {} virtual ~ParseError() {} }; /** Errors that are detected from group validation after parsing finishes */ class ValidationError : public Error { public: ValidationError(const std::string &problem) : Error(problem) {} virtual ~ValidationError() {} }; /** Errors that when a required flag is omitted */ class RequiredError : public ValidationError { public: RequiredError(const std::string &problem) : ValidationError(problem) {} virtual ~RequiredError() {} }; /** Errors in map lookups */ class MapError : public ParseError { public: MapError(const std::string &problem) : ParseError(problem) {} virtual ~MapError() {} }; /** Error that occurs when a singular flag is specified multiple times */ class ExtraError : public ParseError { public: ExtraError(const std::string &problem) : ParseError(problem) {} virtual ~ExtraError() {} }; /** An exception that indicates that the user has requested help */ class Help : public Error { public: Help(const std::string &flag) : Error(flag) {} virtual ~Help() {} }; /** (INTERNAL) An exception that emulates coroutine-like control flow for subparsers. */ class SubparserError : public Error { public: SubparserError() : Error("") {} virtual ~SubparserError() {} }; /** An exception that contains autocompletion reply */ class Completion : public Error { public: Completion(const std::string &flag) : Error(flag) {} virtual ~Completion() {} }; #endif /** A simple unified option type for unified initializer lists for the Matcher class. */ struct EitherFlag { const bool isShort; const char shortFlag; const std::string longFlag; EitherFlag(const std::string &flag) : isShort(false), shortFlag(), longFlag(flag) {} EitherFlag(const char *flag) : isShort(false), shortFlag(), longFlag(flag) {} EitherFlag(const char flag) : isShort(true), shortFlag(flag), longFlag() {} /** Get just the long flags from an initializer list of EitherFlags */ static std::unordered_set GetLong(std::initializer_list flags) { std::unordered_set longFlags; for (const EitherFlag &flag: flags) { if (!flag.isShort) { longFlags.insert(flag.longFlag); } } return longFlags; } /** Get just the short flags from an initializer list of EitherFlags */ static std::unordered_set GetShort(std::initializer_list flags) { std::unordered_set shortFlags; for (const EitherFlag &flag: flags) { if (flag.isShort) { shortFlags.insert(flag.shortFlag); } } return shortFlags; } std::string str() const { return isShort ? std::string(1, shortFlag) : longFlag; } std::string str(const std::string &shortPrefix, const std::string &longPrefix) const { return isShort ? shortPrefix + std::string(1, shortFlag) : longPrefix + longFlag; } }; /** A class of "matchers", specifying short and flags that can possibly be * matched. * * This is supposed to be constructed and then passed in, not used directly * from user code. */ class Matcher { private: const std::unordered_set shortFlags; const std::unordered_set longFlags; public: /** Specify short and long flags separately as iterators * * ex: `args::Matcher(shortFlags.begin(), shortFlags.end(), longFlags.begin(), longFlags.end())` */ template Matcher(ShortIt shortFlagsStart, ShortIt shortFlagsEnd, LongIt longFlagsStart, LongIt longFlagsEnd) : shortFlags(shortFlagsStart, shortFlagsEnd), longFlags(longFlagsStart, longFlagsEnd) { if (shortFlags.empty() && longFlags.empty()) { #ifndef ARGS_NOEXCEPT throw UsageError("empty Matcher"); #endif } } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT Error GetError() const noexcept { return shortFlags.empty() && longFlags.empty() ? Error::Usage : Error::None; } #endif /** Specify short and long flags separately as iterables * * ex: `args::Matcher(shortFlags, longFlags)` */ template Matcher(Short &&shortIn, Long &&longIn) : Matcher(std::begin(shortIn), std::end(shortIn), std::begin(longIn), std::end(longIn)) {} /** Specify a mixed single initializer-list of both short and long flags * * This is the fancy one. It takes a single initializer list of * any number of any mixed kinds of flags. Chars are * automatically interpreted as short flags, and strings are * automatically interpreted as long flags: * * args::Matcher{'a'} * args::Matcher{"foo"} * args::Matcher{'h', "help"} * args::Matcher{"foo", 'f', 'F', "FoO"} */ Matcher(std::initializer_list in) : Matcher(EitherFlag::GetShort(in), EitherFlag::GetLong(in)) {} Matcher(Matcher &&other) : shortFlags(std::move(other.shortFlags)), longFlags(std::move(other.longFlags)) {} ~Matcher() {} /** (INTERNAL) Check if there is a match of a short flag */ bool Match(const char flag) const { return shortFlags.find(flag) != shortFlags.end(); } /** (INTERNAL) Check if there is a match of a long flag */ bool Match(const std::string &flag) const { return longFlags.find(flag) != longFlags.end(); } /** (INTERNAL) Check if there is a match of a flag */ bool Match(const EitherFlag &flag) const { return flag.isShort ? Match(flag.shortFlag) : Match(flag.longFlag); } /** (INTERNAL) Get all flag strings as a vector, with the prefixes embedded */ std::vector GetFlagStrings() const { std::vector flagStrings; flagStrings.reserve(shortFlags.size() + longFlags.size()); for (const char flag: shortFlags) { flagStrings.emplace_back(flag); } for (const std::string &flag: longFlags) { flagStrings.emplace_back(flag); } return flagStrings; } /** (INTERNAL) Get long flag if it exists or any short flag */ EitherFlag GetLongOrAny() const { if (!longFlags.empty()) { return *longFlags.begin(); } if (!shortFlags.empty()) { return *shortFlags.begin(); } // should be unreachable return ' '; } /** (INTERNAL) Get short flag if it exists or any long flag */ EitherFlag GetShortOrAny() const { if (!shortFlags.empty()) { return *shortFlags.begin(); } if (!longFlags.empty()) { return *longFlags.begin(); } // should be unreachable return ' '; } }; /** Attributes for flags. */ enum class Options { /** Default options. */ None = 0x0, /** Flag can't be passed multiple times. */ Single = 0x01, /** Flag can't be omitted. */ Required = 0x02, /** Flag is excluded from usage line. */ HiddenFromUsage = 0x04, /** Flag is excluded from options help. */ HiddenFromDescription = 0x08, /** Flag is global and can be used in any subcommand. */ Global = 0x10, /** Flag stops a parser. */ KickOut = 0x20, /** Flag is excluded from auto completion. */ HiddenFromCompletion = 0x40, /** Flag is excluded from options help and usage line */ Hidden = HiddenFromUsage | HiddenFromDescription | HiddenFromCompletion, }; inline Options operator | (Options lhs, Options rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } inline Options operator & (Options lhs, Options rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } class FlagBase; class PositionalBase; class Command; class ArgumentParser; /** A simple structure of parameters for easy user-modifyable help menus */ struct HelpParams { /** The width of the help menu */ unsigned int width = 80; /** The indent of the program line */ unsigned int progindent = 2; /** The indent of the program trailing lines for long parameters */ unsigned int progtailindent = 4; /** The indent of the description and epilogs */ unsigned int descriptionindent = 4; /** The indent of the flags */ unsigned int flagindent = 6; /** The indent of the flag descriptions */ unsigned int helpindent = 40; /** The additional indent each group adds */ unsigned int eachgroupindent = 2; /** The minimum gutter between each flag and its help */ unsigned int gutter = 1; /** Show the terminator when both options and positional parameters are present */ bool showTerminator = true; /** Show the {OPTIONS} on the prog line when this is true */ bool showProglineOptions = true; /** Show the positionals on the prog line when this is true */ bool showProglinePositionals = true; /** The prefix for short flags */ std::string shortPrefix; /** The prefix for long flags */ std::string longPrefix; /** The separator for short flags */ std::string shortSeparator; /** The separator for long flags */ std::string longSeparator; /** The program name for help generation */ std::string programName; /** Show command's flags */ bool showCommandChildren = false; /** Show command's descriptions and epilog */ bool showCommandFullHelp = false; /** The postfix for progline when showProglineOptions is true and command has any flags */ std::string proglineOptions = "{OPTIONS}"; /** The prefix for progline when command has any subcommands */ std::string proglineCommand = "COMMAND"; /** The prefix for progline value */ std::string proglineValueOpen = " <"; /** The postfix for progline value */ std::string proglineValueClose = ">"; /** The prefix for progline required argument */ std::string proglineRequiredOpen = ""; /** The postfix for progline required argument */ std::string proglineRequiredClose = ""; /** The prefix for progline non-required argument */ std::string proglineNonrequiredOpen = "["; /** The postfix for progline non-required argument */ std::string proglineNonrequiredClose = "]"; /** Show flags in program line */ bool proglineShowFlags = false; /** Use short flags in program lines when possible */ bool proglinePreferShortFlags = false; /** Program line prefix */ std::string usageString; /** String shown in help before flags descriptions */ std::string optionsString = "OPTIONS:"; /** Display value name after all the long and short flags */ bool useValueNameOnce = false; /** Show value name */ bool showValueName = true; /** Add newline before flag description */ bool addNewlineBeforeDescription = false; /** The prefix for option value */ std::string valueOpen = "["; /** The postfix for option value */ std::string valueClose = "]"; /** Add choices to argument description */ bool addChoices = false; /** The prefix for choices */ std::string choiceString = "\nOne of: "; /** Add default values to argument description */ bool addDefault = false; /** The prefix for default values */ std::string defaultString = "\nDefault: "; }; /** A number of arguments which can be consumed by an option. * * Represents a closed interval [min, max]. */ struct Nargs { const size_t min; const size_t max; Nargs(size_t min_, size_t max_) : min(min_), max(max_) { #ifndef ARGS_NOEXCEPT if (max < min) { throw UsageError("Nargs: max > min"); } #endif } Nargs(size_t num_) : min(num_), max(num_) { } friend bool operator == (const Nargs &lhs, const Nargs &rhs) { return lhs.min == rhs.min && lhs.max == rhs.max; } friend bool operator != (const Nargs &lhs, const Nargs &rhs) { return !(lhs == rhs); } }; /** Base class for all match types */ class Base { private: Options options = {}; protected: bool matched = false; const std::string help; #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT mutable Error error = Error::None; mutable std::string errorMsg; #endif public: Base(const std::string &help_, Options options_ = {}) : options(options_), help(help_) {} virtual ~Base() {} Options GetOptions() const noexcept { return options; } bool IsRequired() const noexcept { return (GetOptions() & Options::Required) != Options::None; } virtual bool Matched() const noexcept { return matched; } virtual void Validate(const std::string &, const std::string &) const { } operator bool() const noexcept { return Matched(); } virtual std::vector> GetDescription(const HelpParams &, const unsigned indentLevel) const { std::tuple description; std::get<1>(description) = help; std::get<2>(description) = indentLevel; return { std::move(description) }; } virtual std::vector GetCommands() { return {}; } virtual bool IsGroup() const { return false; } virtual FlagBase *Match(const EitherFlag &) { return nullptr; } virtual PositionalBase *GetNextPositional() { return nullptr; } virtual std::vector GetAllFlags() { return {}; } virtual bool HasFlag() const { return false; } virtual bool HasPositional() const { return false; } virtual bool HasCommand() const { return false; } virtual std::vector GetProgramLine(const HelpParams &) const { return {}; } /// Sets a kick-out value for building subparsers void KickOut(bool kickout_) noexcept { if (kickout_) { options = options | Options::KickOut; } else { options = static_cast(static_cast(options) & ~static_cast(Options::KickOut)); } } /// Gets the kick-out value for building subparsers bool KickOut() const noexcept { return (options & Options::KickOut) != Options::None; } virtual void Reset() noexcept { matched = false; #ifdef ARGS_NOEXCEPT error = Error::None; errorMsg.clear(); #endif } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT virtual Error GetError() const { return error; } /// Only for ARGS_NOEXCEPT std::string GetErrorMsg() const { return errorMsg; } #endif }; /** Base class for all match types that have a name */ class NamedBase : public Base { protected: const std::string name; bool kickout = false; std::string defaultString; bool defaultStringManual = false; std::vector choicesStrings; bool choicesStringManual = false; virtual std::string GetDefaultString(const HelpParams&) const { return {}; } virtual std::vector GetChoicesStrings(const HelpParams&) const { return {}; } virtual std::string GetNameString(const HelpParams&) const { return Name(); } void AddDescriptionPostfix(std::string &dest, const bool isManual, const std::string &manual, bool isGenerated, const std::string &generated, const std::string &str) const { if (isManual && !manual.empty()) { dest += str; dest += manual; } else if (!isManual && isGenerated && !generated.empty()) { dest += str; dest += generated; } } public: NamedBase(const std::string &name_, const std::string &help_, Options options_ = {}) : Base(help_, options_), name(name_) {} virtual ~NamedBase() {} /** Sets default value string that will be added to argument description. * Use empty string to disable it for this argument. */ void HelpDefault(const std::string &str) { defaultStringManual = true; defaultString = str; } /** Gets default value string that will be added to argument description. */ std::string HelpDefault(const HelpParams ¶ms) const { return defaultStringManual ? defaultString : GetDefaultString(params); } /** Sets choices strings that will be added to argument description. * Use empty vector to disable it for this argument. */ void HelpChoices(const std::vector &array) { choicesStringManual = true; choicesStrings = array; } /** Gets choices strings that will be added to argument description. */ std::vector HelpChoices(const HelpParams ¶ms) const { return choicesStringManual ? choicesStrings : GetChoicesStrings(params); } virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned indentLevel) const override { std::tuple description; std::get<0>(description) = GetNameString(params); std::get<1>(description) = help; std::get<2>(description) = indentLevel; AddDescriptionPostfix(std::get<1>(description), choicesStringManual, detail::Join(choicesStrings, ", "), params.addChoices, detail::Join(GetChoicesStrings(params), ", "), params.choiceString); AddDescriptionPostfix(std::get<1>(description), defaultStringManual, defaultString, params.addDefault, GetDefaultString(params), params.defaultString); return { std::move(description) }; } virtual std::string Name() const { return name; } }; namespace detail { template struct IsConvertableToString : std::false_type {}; template struct IsConvertableToString() << std::declval(), int())> : std::true_type {}; template typename std::enable_if::value, std::string>::type ToString(const T &value) { std::ostringstream s; s << value; return s.str(); } template typename std::enable_if::value, std::string>::type ToString(const T &) { return {}; } template std::vector MapKeysToStrings(const T &map) { std::vector res; using K = typename std::decayfirst)>::type; if (IsConvertableToString::value) { for (const auto &p : map) { res.push_back(detail::ToString(p.first)); } std::sort(res.begin(), res.end()); } return res; } } /** Base class for all flag options */ class FlagBase : public NamedBase { protected: const Matcher matcher; virtual std::string GetNameString(const HelpParams ¶ms) const override { const std::string postfix = !params.showValueName || NumberOfArguments() == 0 ? std::string() : Name(); std::string flags; const auto flagStrings = matcher.GetFlagStrings(); const bool useValueNameOnce = flagStrings.size() == 1 ? false : params.useValueNameOnce; for (auto it = flagStrings.begin(); it != flagStrings.end(); ++it) { auto &flag = *it; if (it != flagStrings.begin()) { flags += ", "; } flags += flag.isShort ? params.shortPrefix : params.longPrefix; flags += flag.str(); if (!postfix.empty() && (!useValueNameOnce || it + 1 == flagStrings.end())) { flags += flag.isShort ? params.shortSeparator : params.longSeparator; flags += params.valueOpen + postfix + params.valueClose; } } return flags; } public: FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : NamedBase(name_, help_, extraError_ ? Options::Single : Options()), matcher(std::move(matcher_)) {} FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : NamedBase(name_, help_, options_), matcher(std::move(matcher_)) {} virtual ~FlagBase() {} virtual FlagBase *Match(const EitherFlag &flag) override { if (matcher.Match(flag)) { if ((GetOptions() & Options::Single) != Options::None && matched) { std::ostringstream problem; problem << "Flag '" << flag.str() << "' was passed multiple times, but is only allowed to be passed once"; #ifdef ARGS_NOEXCEPT error = Error::Extra; errorMsg = problem.str(); #else throw ExtraError(problem.str()); #endif } matched = true; return this; } return nullptr; } virtual std::vector GetAllFlags() override { return { this }; } const Matcher &GetMatcher() const { return matcher; } virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override { if (!Matched() && IsRequired()) { std::ostringstream problem; problem << "Flag '" << matcher.GetLongOrAny().str(shortPrefix, longPrefix) << "' is required"; #ifdef ARGS_NOEXCEPT error = Error::Required; errorMsg = problem.str(); #else throw RequiredError(problem.str()); #endif } } virtual std::vector GetProgramLine(const HelpParams ¶ms) const override { if (!params.proglineShowFlags) { return {}; } const std::string postfix = NumberOfArguments() == 0 ? std::string() : Name(); const EitherFlag flag = params.proglinePreferShortFlags ? matcher.GetShortOrAny() : matcher.GetLongOrAny(); std::string res = flag.str(params.shortPrefix, params.longPrefix); if (!postfix.empty()) { res += params.proglineValueOpen + postfix + params.proglineValueClose; } return { IsRequired() ? params.proglineRequiredOpen + res + params.proglineRequiredClose : params.proglineNonrequiredOpen + res + params.proglineNonrequiredClose }; } virtual bool HasFlag() const override { return true; } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT virtual Error GetError() const override { const auto nargs = NumberOfArguments(); if (nargs.min > nargs.max) { return Error::Usage; } const auto matcherError = matcher.GetError(); if (matcherError != Error::None) { return matcherError; } return error; } #endif /** Defines how many values can be consumed by this option. * * \return closed interval [min, max] */ virtual Nargs NumberOfArguments() const noexcept = 0; /** Parse values of this option. * * \param value Vector of values. It's size must be in NumberOfArguments() interval. */ virtual void ParseValue(const std::vector &value) = 0; }; /** Base class for value-accepting flag options */ class ValueFlagBase : public FlagBase { public: ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : FlagBase(name_, help_, std::move(matcher_), extraError_) {} ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : FlagBase(name_, help_, std::move(matcher_), options_) {} virtual ~ValueFlagBase() {} virtual Nargs NumberOfArguments() const noexcept override { return 1; } }; class CompletionFlag : public ValueFlagBase { public: std::vector reply; size_t cword = 0; std::string syntax; template CompletionFlag(GroupClass &group_, Matcher &&matcher_): ValueFlagBase("completion", "completion flag", std::move(matcher_), Options::Hidden) { group_.AddCompletion(*this); } virtual ~CompletionFlag() {} virtual Nargs NumberOfArguments() const noexcept override { return 2; } virtual void ParseValue(const std::vector &value_) override { syntax = value_.at(0); std::istringstream(value_.at(1)) >> cword; } /** Get the completion reply */ std::string Get() noexcept { return detail::Join(reply, "\n"); } virtual void Reset() noexcept override { ValueFlagBase::Reset(); cword = 0; syntax.clear(); reply.clear(); } }; /** Base class for positional options */ class PositionalBase : public NamedBase { protected: bool ready; public: PositionalBase(const std::string &name_, const std::string &help_, Options options_ = {}) : NamedBase(name_, help_, options_), ready(true) {} virtual ~PositionalBase() {} bool Ready() { return ready; } virtual void ParseValue(const std::string &value_) = 0; virtual void Reset() noexcept override { matched = false; ready = true; #ifdef ARGS_NOEXCEPT error = Error::None; errorMsg.clear(); #endif } virtual PositionalBase *GetNextPositional() override { return Ready() ? this : nullptr; } virtual bool HasPositional() const override { return true; } virtual std::vector GetProgramLine(const HelpParams ¶ms) const override { return { IsRequired() ? params.proglineRequiredOpen + Name() + params.proglineRequiredClose : params.proglineNonrequiredOpen + Name() + params.proglineNonrequiredClose }; } virtual void Validate(const std::string &, const std::string &) const override { if (IsRequired() && !Matched()) { std::ostringstream problem; problem << "Option '" << Name() << "' is required"; #ifdef ARGS_NOEXCEPT error = Error::Required; errorMsg = problem.str(); #else throw RequiredError(problem.str()); #endif } } }; /** Class for all kinds of validating groups, including ArgumentParser */ class Group : public Base { private: std::vector children; std::function validator; public: /** Default validators */ struct Validators { static bool Xor(const Group &group) { return group.MatchedChildren() == 1; } static bool AtLeastOne(const Group &group) { return group.MatchedChildren() >= 1; } static bool AtMostOne(const Group &group) { return group.MatchedChildren() <= 1; } static bool All(const Group &group) { return group.Children().size() == group.MatchedChildren(); } static bool AllOrNone(const Group &group) { return (All(group) || None(group)); } static bool AllChildGroups(const Group &group) { return std::none_of(std::begin(group.Children()), std::end(group.Children()), [](const Base* child) -> bool { return child->IsGroup() && !child->Matched(); }); } static bool DontCare(const Group &) { return true; } static bool CareTooMuch(const Group &) { return false; } static bool None(const Group &group) { return group.MatchedChildren() == 0; } }; /// If help is empty, this group will not be printed in help output Group(const std::string &help_ = std::string(), const std::function &validator_ = Validators::DontCare, Options options_ = {}) : Base(help_, options_), validator(validator_) {} /// If help is empty, this group will not be printed in help output Group(Group &group_, const std::string &help_ = std::string(), const std::function &validator_ = Validators::DontCare, Options options_ = {}) : Base(help_, options_), validator(validator_) { group_.Add(*this); } virtual ~Group() {} /** Append a child to this Group. */ void Add(Base &child) { children.emplace_back(&child); } /** Get all this group's children */ const std::vector &Children() const { return children; } /** Return the first FlagBase that matches flag, or nullptr * * \param flag The flag with prefixes stripped * \return the first matching FlagBase pointer, or nullptr if there is no match */ virtual FlagBase *Match(const EitherFlag &flag) override { for (Base *child: Children()) { if (FlagBase *match = child->Match(flag)) { return match; } } return nullptr; } virtual std::vector GetAllFlags() override { std::vector res; for (Base *child: Children()) { auto childRes = child->GetAllFlags(); res.insert(res.end(), childRes.begin(), childRes.end()); } return res; } virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override { for (Base *child: Children()) { child->Validate(shortPrefix, longPrefix); } } /** Get the next ready positional, or nullptr if there is none * * \return the first ready PositionalBase pointer, or nullptr if there is no match */ virtual PositionalBase *GetNextPositional() override { for (Base *child: Children()) { if (auto next = child->GetNextPositional()) { return next; } } return nullptr; } /** Get whether this has any FlagBase children * * \return Whether or not there are any FlagBase children */ virtual bool HasFlag() const override { return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasFlag(); }); } /** Get whether this has any PositionalBase children * * \return Whether or not there are any PositionalBase children */ virtual bool HasPositional() const override { return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasPositional(); }); } /** Get whether this has any Command children * * \return Whether or not there are any Command children */ virtual bool HasCommand() const override { return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasCommand(); }); } /** Count the number of matched children this group has */ std::vector::size_type MatchedChildren() const { return std::count_if(std::begin(Children()), std::end(Children()), [](const Base *child){return child->Matched();}); } /** Whether or not this group matches validation */ virtual bool Matched() const noexcept override { return validator(*this); } /** Get validation */ bool Get() const { return Matched(); } /** Get all the child descriptions for help generation */ virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned int indent) const override { std::vector> descriptions; // Push that group description on the back if not empty unsigned addindent = 0; if (!help.empty()) { descriptions.emplace_back(help, "", indent); addindent = 1; } for (Base *child: Children()) { if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None) { continue; } auto groupDescriptions = child->GetDescription(params, indent + addindent); descriptions.insert( std::end(descriptions), std::make_move_iterator(std::begin(groupDescriptions)), std::make_move_iterator(std::end(groupDescriptions))); } return descriptions; } /** Get the names of positional parameters */ virtual std::vector GetProgramLine(const HelpParams ¶ms) const override { std::vector names; for (Base *child: Children()) { if ((child->GetOptions() & Options::HiddenFromUsage) != Options::None) { continue; } auto groupNames = child->GetProgramLine(params); names.insert( std::end(names), std::make_move_iterator(std::begin(groupNames)), std::make_move_iterator(std::end(groupNames))); } return names; } virtual std::vector GetCommands() override { std::vector res; for (const auto &child : Children()) { auto subparsers = child->GetCommands(); res.insert(std::end(res), std::begin(subparsers), std::end(subparsers)); } return res; } virtual bool IsGroup() const override { return true; } virtual void Reset() noexcept override { Base::Reset(); for (auto &child: Children()) { child->Reset(); } #ifdef ARGS_NOEXCEPT error = Error::None; errorMsg.clear(); #endif } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT virtual Error GetError() const override { if (error != Error::None) { return error; } auto it = std::find_if(Children().begin(), Children().end(), [](const Base *child){return child->GetError() != Error::None;}); if (it == Children().end()) { return Error::None; } else { return (*it)->GetError(); } } #endif }; /** Class for using global options in ArgumentParser. */ class GlobalOptions : public Group { public: GlobalOptions(Group &base, Base &options_) : Group(base, {}, Group::Validators::DontCare, Options::Global) { Add(options_); } }; /** Utility class for building subparsers with coroutines/callbacks. * * Brief example: * \code * Command command(argumentParser, "command", "my command", [](args::Subparser &s) * { * // your command flags/positionals * s.Parse(); //required * //your command code * }); * \endcode * * For ARGS_NOEXCEPT mode don't forget to check `s.GetError()` after `s.Parse()` * and return if it isn't equals to args::Error::None. * * \sa Command */ class Subparser : public Group { private: std::vector args; std::vector kicked; ArgumentParser *parser = nullptr; const HelpParams &helpParams; const Command &command; bool isParsed = false; public: Subparser(std::vector args_, ArgumentParser &parser_, const Command &command_, const HelpParams &helpParams_) : args(std::move(args_)), parser(&parser_), helpParams(helpParams_), command(command_) { } Subparser(const Command &command_, const HelpParams &helpParams_) : helpParams(helpParams_), command(command_) { } Subparser(const Subparser&) = delete; Subparser(Subparser&&) = delete; Subparser &operator = (const Subparser&) = delete; Subparser &operator = (Subparser&&) = delete; const Command &GetCommand() { return command; } /** (INTERNAL) Determines whether Parse was called or not. */ bool IsParsed() const { return isParsed; } /** Continue parsing arguments for new command. */ void Parse(); /** Returns a vector of kicked out arguments. * * \sa Base::KickOut */ const std::vector &KickedOut() const noexcept { return kicked; } }; /** Main class for building subparsers. * * /sa Subparser */ class Command : public Group { private: friend class Subparser; std::string name; std::string help; std::string description; std::string epilog; std::string proglinePostfix; std::function parserCoroutine; bool commandIsRequired = true; Command *selectedCommand = nullptr; mutable std::vector> subparserDescription; mutable std::vector subparserProgramLine; mutable bool subparserHasFlag = false; mutable bool subparserHasPositional = false; mutable bool subparserHasCommand = false; #ifdef ARGS_NOEXCEPT mutable Error subparserError = Error::None; #endif mutable Subparser *subparser = nullptr; protected: class RaiiSubparser { public: RaiiSubparser(ArgumentParser &parser_, std::vector args_); RaiiSubparser(const Command &command_, const HelpParams ¶ms_); ~RaiiSubparser() { command.subparser = oldSubparser; } Subparser &Parser() { return parser; } private: const Command &command; Subparser parser; Subparser *oldSubparser; }; Command() = default; std::function &GetCoroutine() { return selectedCommand != nullptr ? selectedCommand->GetCoroutine() : parserCoroutine; } Command &SelectedCommand() { Command *res = this; while (res->selectedCommand != nullptr) { res = res->selectedCommand; } return *res; } const Command &SelectedCommand() const { const Command *res = this; while (res->selectedCommand != nullptr) { res = res->selectedCommand; } return *res; } void UpdateSubparserHelp(const HelpParams ¶ms) const { if (parserCoroutine) { RaiiSubparser coro(*this, params); #ifndef ARGS_NOEXCEPT try { parserCoroutine(coro.Parser()); } catch (args::SubparserError) { } #else parserCoroutine(coro.Parser()); #endif } } public: Command(Group &base_, std::string name_, std::string help_, std::function coroutine_ = {}) : name(std::move(name_)), help(std::move(help_)), parserCoroutine(std::move(coroutine_)) { base_.Add(*this); } /** The description that appears on the prog line after options */ const std::string &ProglinePostfix() const { return proglinePostfix; } /** The description that appears on the prog line after options */ void ProglinePostfix(const std::string &proglinePostfix_) { this->proglinePostfix = proglinePostfix_; } /** The description that appears above options */ const std::string &Description() const { return description; } /** The description that appears above options */ void Description(const std::string &description_) { this->description = description_; } /** The description that appears below options */ const std::string &Epilog() const { return epilog; } /** The description that appears below options */ void Epilog(const std::string &epilog_) { this->epilog = epilog_; } /** The name of command */ const std::string &Name() const { return name; } /** The description of command */ const std::string &Help() const { return help; } /** If value is true, parser will fail if no command was parsed. * * Default: true. */ void RequireCommand(bool value) { commandIsRequired = value; } virtual bool IsGroup() const override { return false; } virtual bool Matched() const noexcept override { return Base::Matched(); } operator bool() const noexcept { return Matched(); } void Match() noexcept { matched = true; } void SelectCommand(Command *c) noexcept { selectedCommand = c; if (c != nullptr) { c->Match(); } } virtual FlagBase *Match(const EitherFlag &flag) override { if (selectedCommand != nullptr) { if (auto *res = selectedCommand->Match(flag)) { return res; } for (auto *child: Children()) { if ((child->GetOptions() & Options::Global) != Options::None) { if (auto *res = child->Match(flag)) { return res; } } } return nullptr; } if (subparser != nullptr) { return subparser->Match(flag); } return Matched() ? Group::Match(flag) : nullptr; } virtual std::vector GetAllFlags() override { std::vector res; if (!Matched()) { return res; } for (auto *child: Children()) { if (selectedCommand == nullptr || (child->GetOptions() & Options::Global) != Options::None) { auto childFlags = child->GetAllFlags(); res.insert(res.end(), childFlags.begin(), childFlags.end()); } } if (selectedCommand != nullptr) { auto childFlags = selectedCommand->GetAllFlags(); res.insert(res.end(), childFlags.begin(), childFlags.end()); } if (subparser != nullptr) { auto childFlags = subparser->GetAllFlags(); res.insert(res.end(), childFlags.begin(), childFlags.end()); } return res; } virtual PositionalBase *GetNextPositional() override { if (selectedCommand != nullptr) { if (auto *res = selectedCommand->GetNextPositional()) { return res; } for (auto *child: Children()) { if ((child->GetOptions() & Options::Global) != Options::None) { if (auto *res = child->GetNextPositional()) { return res; } } } return nullptr; } if (subparser != nullptr) { return subparser->GetNextPositional(); } return Matched() ? Group::GetNextPositional() : nullptr; } virtual bool HasFlag() const override { return subparserHasFlag || Group::HasFlag(); } virtual bool HasPositional() const override { return subparserHasPositional || Group::HasPositional(); } virtual bool HasCommand() const override { return true; } std::vector GetCommandProgramLine(const HelpParams ¶ms) const { UpdateSubparserHelp(params); auto res = Group::GetProgramLine(params); res.insert(res.end(), subparserProgramLine.begin(), subparserProgramLine.end()); if (!params.proglineCommand.empty() && (Group::HasCommand() || subparserHasCommand)) { res.insert(res.begin(), commandIsRequired ? params.proglineCommand : "[" + params.proglineCommand + "]"); } if (!Name().empty()) { res.insert(res.begin(), Name()); } if ((subparserHasFlag || Group::HasFlag()) && params.showProglineOptions && !params.proglineShowFlags) { res.push_back(params.proglineOptions); } if (!ProglinePostfix().empty()) { std::string line; for (char c : ProglinePostfix()) { if (isspace(c)) { if (!line.empty()) { res.push_back(line); line.clear(); } if (c == '\n') { res.push_back("\n"); } } else { line += c; } } if (!line.empty()) { res.push_back(line); } } return res; } virtual std::vector GetProgramLine(const HelpParams ¶ms) const override { if (!Matched()) { return {}; } return GetCommandProgramLine(params); } virtual std::vector GetCommands() override { if (selectedCommand != nullptr) { return selectedCommand->GetCommands(); } if (Matched()) { return Group::GetCommands(); } return { this }; } virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned int indent) const override { std::vector> descriptions; unsigned addindent = 0; UpdateSubparserHelp(params); if (!Matched()) { if (params.showCommandFullHelp) { std::ostringstream s; bool empty = true; for (const auto &progline: GetCommandProgramLine(params)) { if (!empty) { s << ' '; } else { empty = false; } s << progline; } descriptions.emplace_back(s.str(), "", indent); } else { descriptions.emplace_back(Name(), help, indent); } if (!params.showCommandChildren && !params.showCommandFullHelp) { return descriptions; } addindent = 1; } if (params.showCommandFullHelp && !Matched()) { descriptions.emplace_back("", "", indent + addindent); descriptions.emplace_back(Description().empty() ? Help() : Description(), "", indent + addindent); descriptions.emplace_back("", "", indent + addindent); } for (Base *child: Children()) { if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None) { continue; } auto groupDescriptions = child->GetDescription(params, indent + addindent); descriptions.insert( std::end(descriptions), std::make_move_iterator(std::begin(groupDescriptions)), std::make_move_iterator(std::end(groupDescriptions))); } for (auto childDescription: subparserDescription) { std::get<2>(childDescription) += indent + addindent; descriptions.push_back(std::move(childDescription)); } if (params.showCommandFullHelp && !Matched()) { descriptions.emplace_back("", "", indent + addindent); if (!Epilog().empty()) { descriptions.emplace_back(Epilog(), "", indent + addindent); descriptions.emplace_back("", "", indent + addindent); } } return descriptions; } virtual void Validate(const std::string &shortprefix, const std::string &longprefix) const override { if (!Matched()) { return; } for (Base *child: Children()) { if (child->IsGroup() && !child->Matched()) { std::ostringstream problem; problem << "Group validation failed somewhere!"; #ifdef ARGS_NOEXCEPT error = Error::Validation; errorMsg = problem.str(); #else throw ValidationError(problem.str()); #endif } child->Validate(shortprefix, longprefix); } if (subparser != nullptr) { subparser->Validate(shortprefix, longprefix); } if (selectedCommand == nullptr && commandIsRequired && (Group::HasCommand() || subparserHasCommand)) { std::ostringstream problem; problem << "Command is required"; #ifdef ARGS_NOEXCEPT error = Error::Validation; errorMsg = problem.str(); #else throw ValidationError(problem.str()); #endif } } virtual void Reset() noexcept override { Group::Reset(); selectedCommand = nullptr; subparserProgramLine.clear(); subparserDescription.clear(); subparserHasFlag = false; subparserHasPositional = false; subparserHasCommand = false; #ifdef ARGS_NOEXCEPT subparserError = Error::None; #endif } #ifdef ARGS_NOEXCEPT /// Only for ARGS_NOEXCEPT virtual Error GetError() const override { if (!Matched()) { return Error::None; } if (error != Error::None) { return error; } if (subparserError != Error::None) { return subparserError; } return Group::GetError(); } #endif }; /** The main user facing command line argument parser class */ class ArgumentParser : public Command { friend class Subparser; private: std::string longprefix; std::string shortprefix; std::string longseparator; std::string terminator; bool allowJoinedShortValue = true; bool allowJoinedLongValue = true; bool allowSeparateShortValue = true; bool allowSeparateLongValue = true; CompletionFlag *completion = nullptr; bool readCompletion = false; protected: enum class OptionType { LongFlag, ShortFlag, Positional }; OptionType ParseOption(const std::string &s, bool allowEmpty = false) { if (s.find(longprefix) == 0 && (allowEmpty || s.length() > longprefix.length())) { return OptionType::LongFlag; } if (s.find(shortprefix) == 0 && (allowEmpty || s.length() > shortprefix.length())) { return OptionType::ShortFlag; } return OptionType::Positional; } template bool Complete(FlagBase &flag, It it, It end) { auto nextIt = it; if (!readCompletion || (++nextIt != end)) { return false; } const auto &chunk = *it; for (auto &choice : flag.HelpChoices(helpParams)) { AddCompletionReply(chunk, choice); } #ifndef ARGS_NOEXCEPT throw Completion(completion->Get()); #else return true; #endif } /** (INTERNAL) Parse flag's values * * \param arg The string to display in error message as a flag name * \param[in, out] it The iterator to first value. It will point to the last value * \param end The end iterator * \param joinedArg Joined value (e.g. bar in --foo=bar) * \param canDiscardJoined If true joined value can be parsed as flag not as a value (as in -abcd) * \param[out] values The vector to store parsed arg's values */ template std::string ParseArgsValues(FlagBase &flag, const std::string &arg, It &it, It end, const bool allowSeparate, const bool allowJoined, const bool hasJoined, const std::string &joinedArg, const bool canDiscardJoined, std::vector &values) { values.clear(); Nargs nargs = flag.NumberOfArguments(); if (hasJoined && !allowJoined && nargs.min != 0) { return "Flag '" + arg + "' was passed a joined argument, but these are disallowed"; } if (hasJoined) { if (!canDiscardJoined || nargs.max != 0) { values.push_back(joinedArg); } } else if (!allowSeparate) { if (nargs.min != 0) { return "Flag '" + arg + "' was passed a separate argument, but these are disallowed"; } } else { auto valueIt = it; ++valueIt; while (valueIt != end && values.size() < nargs.max && (nargs.min == nargs.max || ParseOption(*valueIt) == OptionType::Positional)) { if (Complete(flag, valueIt, end)) { it = end; return ""; } values.push_back(*valueIt); ++it; ++valueIt; } } if (values.size() > nargs.max) { return "Passed an argument into a non-argument flag: " + arg; } else if (values.size() < nargs.min) { if (nargs.min == 1 && nargs.max == 1) { return "Flag '" + arg + "' requires an argument but received none"; } else if (nargs.min == 1) { return "Flag '" + arg + "' requires at least one argument but received none"; } else if (nargs.min != nargs.max) { return "Flag '" + arg + "' requires at least " + std::to_string(nargs.min) + " arguments but received " + std::to_string(values.size()); } else { return "Flag '" + arg + "' requires " + std::to_string(nargs.min) + " arguments but received " + std::to_string(values.size()); } } return {}; } template bool ParseLong(It &it, It end) { const auto &chunk = *it; const auto argchunk = chunk.substr(longprefix.size()); // Try to separate it, in case of a separator: const auto separator = longseparator.empty() ? argchunk.npos : argchunk.find(longseparator); // If the separator is in the argument, separate it. const auto arg = (separator != argchunk.npos ? std::string(argchunk, 0, separator) : argchunk); const auto joined = (separator != argchunk.npos ? argchunk.substr(separator + longseparator.size()) : std::string()); if (auto flag = Match(arg)) { std::vector values; const std::string errorMessage = ParseArgsValues(*flag, arg, it, end, allowSeparateLongValue, allowJoinedLongValue, separator != argchunk.npos, joined, false, values); if (!errorMessage.empty()) { #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return false; #endif } if (!readCompletion) { flag->ParseValue(values); } if (flag->KickOut()) { ++it; return false; } } else { const std::string errorMessage("Flag could not be matched: " + arg); #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return false; #endif } return true; } template bool ParseShort(It &it, It end) { const auto &chunk = *it; const auto argchunk = chunk.substr(shortprefix.size()); for (auto argit = std::begin(argchunk); argit != std::end(argchunk); ++argit) { const auto arg = *argit; if (auto flag = Match(arg)) { const std::string value(argit + 1, std::end(argchunk)); std::vector values; const std::string errorMessage = ParseArgsValues(*flag, std::string(1, arg), it, end, allowSeparateShortValue, allowJoinedShortValue, !value.empty(), value, !value.empty(), values); if (!errorMessage.empty()) { #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return false; #endif } if (!readCompletion) { flag->ParseValue(values); } if (flag->KickOut()) { ++it; return false; } if (!values.empty()) { break; } } else { const std::string errorMessage("Flag could not be matched: '" + std::string(1, arg) + "'"); #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return false; #endif } } return true; } bool AddCompletionReply(const std::string &cur, const std::string &choice) { if (cur.empty() || choice.find(cur) == 0) { if (completion->syntax == "bash" && ParseOption(choice) == OptionType::LongFlag && choice.find(longseparator) != std::string::npos) { completion->reply.push_back(choice.substr(choice.find(longseparator) + 1)); } else { completion->reply.push_back(choice); } return true; } return false; } template bool Complete(It it, It end) { auto nextIt = it; if (!readCompletion || (++nextIt != end)) { return false; } const auto &chunk = *it; auto pos = GetNextPositional(); std::vector commands = GetCommands(); const auto optionType = ParseOption(chunk, true); if (!commands.empty() && (chunk.empty() || optionType == OptionType::Positional)) { for (auto &cmd : commands) { if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None) { AddCompletionReply(chunk, cmd->Name()); } } } else { bool hasPositionalCompletion = true; if (!commands.empty()) { for (auto &cmd : commands) { if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None) { AddCompletionReply(chunk, cmd->Name()); } } } else if (pos) { if ((pos->GetOptions() & Options::HiddenFromCompletion) == Options::None) { auto choices = pos->HelpChoices(helpParams); hasPositionalCompletion = !choices.empty() || optionType != OptionType::Positional; for (auto &choice : choices) { AddCompletionReply(chunk, choice); } } } if (hasPositionalCompletion) { auto flags = GetAllFlags(); for (auto flag : flags) { if ((flag->GetOptions() & Options::HiddenFromCompletion) != Options::None) { continue; } auto &matcher = flag->GetMatcher(); if (!AddCompletionReply(chunk, matcher.GetShortOrAny().str(shortprefix, longprefix))) { for (auto &flagName : matcher.GetFlagStrings()) { if (AddCompletionReply(chunk, flagName.str(shortprefix, longprefix))) { break; } } } } if (optionType == OptionType::LongFlag && allowJoinedLongValue) { const auto separator = longseparator.empty() ? chunk.npos : chunk.find(longseparator); if (separator != chunk.npos) { std::string arg(chunk, 0, separator); if (auto flag = this->Match(arg.substr(longprefix.size()))) { for (auto &choice : flag->HelpChoices(helpParams)) { AddCompletionReply(chunk, arg + longseparator + choice); } } } } else if (optionType == OptionType::ShortFlag && allowJoinedShortValue) { if (chunk.size() > shortprefix.size() + 1) { auto arg = chunk.at(shortprefix.size()); //TODO: support -abcVALUE where a and b take no value if (auto flag = this->Match(arg)) { for (auto &choice : flag->HelpChoices(helpParams)) { AddCompletionReply(chunk, shortprefix + arg + choice); } } } } } } #ifndef ARGS_NOEXCEPT throw Completion(completion->Get()); #else return true; #endif } template It Parse(It begin, It end) { bool terminated = false; std::vector commands = GetCommands(); // Check all arg chunks for (auto it = begin; it != end; ++it) { if (Complete(it, end)) { return end; } const auto &chunk = *it; if (!terminated && chunk == terminator) { terminated = true; } else if (!terminated && ParseOption(chunk) == OptionType::LongFlag) { if (!ParseLong(it, end)) { return it; } } else if (!terminated && ParseOption(chunk) == OptionType::ShortFlag) { if (!ParseShort(it, end)) { return it; } } else if (!terminated && !commands.empty()) { auto itCommand = std::find_if(commands.begin(), commands.end(), [&chunk](Command *c) { return c->Name() == chunk; }); if (itCommand == commands.end()) { const std::string errorMessage("Unknown command: " + chunk); #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return it; #endif } SelectCommand(*itCommand); if (const auto &coroutine = GetCoroutine()) { ++it; RaiiSubparser coro(*this, std::vector(it, end)); coroutine(coro.Parser()); #ifdef ARGS_NOEXCEPT error = GetError(); if (error != Error::None) { return end; } if (!coro.Parser().IsParsed()) { error = Error::Usage; return end; } #else if (!coro.Parser().IsParsed()) { throw UsageError("Subparser::Parse was not called"); } #endif break; } commands = GetCommands(); } else { auto pos = GetNextPositional(); if (pos) { pos->ParseValue(chunk); if (pos->KickOut()) { return ++it; } } else { const std::string errorMessage("Passed in argument, but no positional arguments were ready to receive it: " + chunk); #ifndef ARGS_NOEXCEPT throw ParseError(errorMessage); #else error = Error::Parse; errorMsg = errorMessage; return it; #endif } } if (!readCompletion && completion != nullptr && completion->Matched()) { #ifdef ARGS_NOEXCEPT error = Error::Completion; #endif readCompletion = true; ++it; size_t argsLeft = std::distance(it, end); if (completion->cword == 0 || argsLeft <= 1 || completion->cword >= argsLeft) { #ifndef ARGS_NOEXCEPT throw Completion(""); #endif } std::vector curArgs(++it, end); curArgs.resize(completion->cword); if (completion->syntax == "bash") { // bash tokenizes --flag=value as --flag=value for (size_t idx = 0; idx < curArgs.size(); ) { if (idx > 0 && curArgs[idx] == "=") { curArgs[idx - 1] += "="; if (idx + 1 < curArgs.size()) { curArgs[idx - 1] += curArgs[idx + 1]; curArgs.erase(curArgs.begin() + idx, curArgs.begin() + idx + 2); } else { curArgs.erase(curArgs.begin() + idx); } } else { ++idx; } } } #ifndef ARGS_NOEXCEPT try { Parse(curArgs.begin(), curArgs.end()); throw Completion(""); } catch (Completion &) { throw; } catch (args::Error&) { throw Completion(""); } #else return Parse(curArgs.begin(), curArgs.end()); #endif } } Validate(shortprefix, longprefix); return end; } public: HelpParams helpParams; ArgumentParser(const std::string &description_, const std::string &epilog_ = std::string()) { Description(description_); Epilog(epilog_); LongPrefix("--"); ShortPrefix("-"); LongSeparator("="); Terminator("--"); SetArgumentSeparations(true, true, true, true); matched = true; } void AddCompletion(CompletionFlag &completionFlag) { completion = &completionFlag; Add(completionFlag); } /** The program name for help generation */ const std::string &Prog() const { return helpParams.programName; } /** The program name for help generation */ void Prog(const std::string &prog_) { this->helpParams.programName = prog_; } /** The prefix for long flags */ const std::string &LongPrefix() const { return longprefix; } /** The prefix for long flags */ void LongPrefix(const std::string &longprefix_) { this->longprefix = longprefix_; this->helpParams.longPrefix = longprefix_; } /** The prefix for short flags */ const std::string &ShortPrefix() const { return shortprefix; } /** The prefix for short flags */ void ShortPrefix(const std::string &shortprefix_) { this->shortprefix = shortprefix_; this->helpParams.shortPrefix = shortprefix_; } /** The separator for long flags */ const std::string &LongSeparator() const { return longseparator; } /** The separator for long flags */ void LongSeparator(const std::string &longseparator_) { if (longseparator_.empty()) { const std::string errorMessage("longseparator can not be set to empty"); #ifdef ARGS_NOEXCEPT error = Error::Usage; errorMsg = errorMessage; #else throw UsageError(errorMessage); #endif } else { this->longseparator = longseparator_; this->helpParams.longSeparator = allowJoinedLongValue ? longseparator_ : " "; } } /** The terminator that forcibly separates flags from positionals */ const std::string &Terminator() const { return terminator; } /** The terminator that forcibly separates flags from positionals */ void Terminator(const std::string &terminator_) { this->terminator = terminator_; } /** Get the current argument separation parameters. * * See SetArgumentSeparations for details on what each one means. */ void GetArgumentSeparations( bool &allowJoinedShortValue_, bool &allowJoinedLongValue_, bool &allowSeparateShortValue_, bool &allowSeparateLongValue_) const { allowJoinedShortValue_ = this->allowJoinedShortValue; allowJoinedLongValue_ = this->allowJoinedLongValue; allowSeparateShortValue_ = this->allowSeparateShortValue; allowSeparateLongValue_ = this->allowSeparateLongValue; } /** Change allowed option separation. * * \param allowJoinedShortValue_ Allow a short flag that accepts an argument to be passed its argument immediately next to it (ie. in the same argv field) * \param allowJoinedLongValue_ Allow a long flag that accepts an argument to be passed its argument separated by the longseparator (ie. in the same argv field) * \param allowSeparateShortValue_ Allow a short flag that accepts an argument to be passed its argument separated by whitespace (ie. in the next argv field) * \param allowSeparateLongValue_ Allow a long flag that accepts an argument to be passed its argument separated by whitespace (ie. in the next argv field) */ void SetArgumentSeparations( const bool allowJoinedShortValue_, const bool allowJoinedLongValue_, const bool allowSeparateShortValue_, const bool allowSeparateLongValue_) { this->allowJoinedShortValue = allowJoinedShortValue_; this->allowJoinedLongValue = allowJoinedLongValue_; this->allowSeparateShortValue = allowSeparateShortValue_; this->allowSeparateLongValue = allowSeparateLongValue_; this->helpParams.longSeparator = allowJoinedLongValue ? longseparator : " "; this->helpParams.shortSeparator = allowJoinedShortValue ? "" : " "; } /** Pass the help menu into an ostream */ void Help(std::ostream &help_) const { auto &command = SelectedCommand(); const auto &commandDescription = command.Description().empty() ? command.Help() : command.Description(); const auto description_text = Wrap(commandDescription, helpParams.width - helpParams.descriptionindent); const auto epilog_text = Wrap(command.Epilog(), helpParams.width - helpParams.descriptionindent); const bool hasoptions = command.HasFlag(); const bool hasarguments = command.HasPositional(); std::vector prognameline; prognameline.push_back(helpParams.usageString); prognameline.push_back(Prog()); auto commandProgLine = command.GetProgramLine(helpParams); prognameline.insert(prognameline.end(), commandProgLine.begin(), commandProgLine.end()); const auto proglines = Wrap(prognameline.begin(), prognameline.end(), helpParams.width - (helpParams.progindent + helpParams.progtailindent), helpParams.width - helpParams.progindent); auto progit = std::begin(proglines); if (progit != std::end(proglines)) { help_ << std::string(helpParams.progindent, ' ') << *progit << '\n'; ++progit; } for (; progit != std::end(proglines); ++progit) { help_ << std::string(helpParams.progtailindent, ' ') << *progit << '\n'; } help_ << '\n'; if (!description_text.empty()) { for (const auto &line: description_text) { help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n"; } help_ << "\n"; } bool lastDescriptionIsNewline = false; if (!helpParams.optionsString.empty()) { help_ << std::string(helpParams.progindent, ' ') << helpParams.optionsString << "\n\n"; } for (const auto &desc: command.GetDescription(helpParams, 0)) { lastDescriptionIsNewline = std::get<0>(desc).empty() && std::get<1>(desc).empty(); const auto groupindent = std::get<2>(desc) * helpParams.eachgroupindent; const auto flags = Wrap(std::get<0>(desc), helpParams.width - (helpParams.flagindent + helpParams.helpindent + helpParams.gutter)); const auto info = Wrap(std::get<1>(desc), helpParams.width - (helpParams.helpindent + groupindent)); std::string::size_type flagssize = 0; for (auto flagsit = std::begin(flags); flagsit != std::end(flags); ++flagsit) { if (flagsit != std::begin(flags)) { help_ << '\n'; } help_ << std::string(groupindent + helpParams.flagindent, ' ') << *flagsit; flagssize = Glyphs(*flagsit); } auto infoit = std::begin(info); // groupindent is on both sides of this inequality, and therefore can be removed if ((helpParams.flagindent + flagssize + helpParams.gutter) > helpParams.helpindent || infoit == std::end(info) || helpParams.addNewlineBeforeDescription) { help_ << '\n'; } else { // groupindent is on both sides of the minus sign, and therefore doesn't actually need to be in here help_ << std::string(helpParams.helpindent - (helpParams.flagindent + flagssize), ' ') << *infoit << '\n'; ++infoit; } for (; infoit != std::end(info); ++infoit) { help_ << std::string(groupindent + helpParams.helpindent, ' ') << *infoit << '\n'; } } if (hasoptions && hasarguments && helpParams.showTerminator) { lastDescriptionIsNewline = false; for (const auto &item: Wrap(std::string("\"") + terminator + "\" can be used to terminate flag options and force all following arguments to be treated as positional options", helpParams.width - helpParams.flagindent)) { help_ << std::string(helpParams.flagindent, ' ') << item << '\n'; } } if (!lastDescriptionIsNewline) { help_ << "\n"; } for (const auto &line: epilog_text) { help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n"; } } /** Generate a help menu as a string. * * \return the help text as a single string */ std::string Help() const { std::ostringstream help_; Help(help_); return help_.str(); } virtual void Reset() noexcept override { Command::Reset(); matched = true; readCompletion = false; } /** Parse all arguments. * * \param begin an iterator to the beginning of the argument list * \param end an iterator to the past-the-end element of the argument list * \return the iterator after the last parsed value. Only useful for kick-out */ template It ParseArgs(It begin, It end) { // Reset all Matched statuses and errors Reset(); #ifdef ARGS_NOEXCEPT error = GetError(); if (error != Error::None) { return end; } #endif return Parse(begin, end); } /** Parse all arguments. * * \param args an iterable of the arguments * \return the iterator after the last parsed value. Only useful for kick-out */ template auto ParseArgs(const T &args) -> decltype(std::begin(args)) { return ParseArgs(std::begin(args), std::end(args)); } /** Convenience function to parse the CLI from argc and argv * * Just assigns the program name and vectorizes arguments for passing into ParseArgs() * * \return whether or not all arguments were parsed. This works for detecting kick-out, but is generally useless as it can't do anything with it. */ bool ParseCLI(const int argc, const char * const * argv) { if (Prog().empty()) { Prog(argv[0]); } const std::vector args(argv + 1, argv + argc); return ParseArgs(args) == std::end(args); } template bool ParseCLI(const T &args) { return ParseArgs(args) == std::end(args); } }; inline Command::RaiiSubparser::RaiiSubparser(ArgumentParser &parser_, std::vector args_) : command(parser_.SelectedCommand()), parser(std::move(args_), parser_, command, parser_.helpParams), oldSubparser(command.subparser) { command.subparser = &parser; } inline Command::RaiiSubparser::RaiiSubparser(const Command &command_, const HelpParams ¶ms_): command(command_), parser(command, params_), oldSubparser(command.subparser) { command.subparser = &parser; } inline void Subparser::Parse() { isParsed = true; Reset(); command.subparserDescription = GetDescription(helpParams, 0); command.subparserHasFlag = HasFlag(); command.subparserHasPositional = HasPositional(); command.subparserHasCommand = HasCommand(); command.subparserProgramLine = GetProgramLine(helpParams); if (parser == nullptr) { #ifndef ARGS_NOEXCEPT throw args::SubparserError(); #else error = Error::Subparser; return; #endif } auto it = parser->Parse(args.begin(), args.end()); command.Validate(parser->ShortPrefix(), parser->LongPrefix()); kicked.assign(it, args.end()); #ifdef ARGS_NOEXCEPT command.subparserError = GetError(); #endif } inline std::ostream &operator<<(std::ostream &os, const ArgumentParser &parser) { parser.Help(os); return os; } /** Boolean argument matcher */ class Flag : public FlagBase { public: Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): FlagBase(name_, help_, std::move(matcher_), options_) { group_.Add(*this); } Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false): Flag(group_, name_, help_, std::move(matcher_), extraError_ ? Options::Single : Options::None) { } virtual ~Flag() {} /** Get whether this was matched */ bool Get() const { return Matched(); } virtual Nargs NumberOfArguments() const noexcept override { return 0; } virtual void ParseValue(const std::vector&) override { } }; /** Help flag class * * Works like a regular flag, but throws an instance of Help when it is matched */ class HelpFlag : public Flag { public: HelpFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_ = {}): Flag(group_, name_, help_, std::move(matcher_), options_) {} virtual ~HelpFlag() {} virtual void ParseValue(const std::vector &) { #ifdef ARGS_NOEXCEPT error = Error::Help; errorMsg = Name(); #else throw Help(Name()); #endif } /** Get whether this was matched */ bool Get() const noexcept { return Matched(); } }; /** A flag class that simply counts the number of times it's matched */ class CounterFlag : public Flag { private: const int startcount; int count; public: CounterFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const int startcount_ = 0, Options options_ = {}): Flag(group_, name_, help_, std::move(matcher_), options_), startcount(startcount_), count(startcount_) {} virtual ~CounterFlag() {} virtual FlagBase *Match(const EitherFlag &arg) override { auto me = FlagBase::Match(arg); if (me) { ++count; } return me; } /** Get the count */ int &Get() noexcept { return count; } virtual void Reset() noexcept override { FlagBase::Reset(); count = startcount; } }; /** A flag class that calls a function when it's matched */ class ActionFlag : public FlagBase { private: std::function &)> action; Nargs nargs; public: ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, std::function &)> action_, Options options_ = {}): FlagBase(name_, help_, std::move(matcher_), options_), action(std::move(action_)), nargs(nargs_) { group_.Add(*this); } ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, std::function action_, Options options_ = {}): FlagBase(name_, help_, std::move(matcher_), options_), nargs(1) { group_.Add(*this); action = [action_](const std::vector &a) { return action_(a.at(0)); }; } ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, std::function action_, Options options_ = {}): FlagBase(name_, help_, std::move(matcher_), options_), nargs(0) { group_.Add(*this); action = [action_](const std::vector &) { return action_(); }; } virtual Nargs NumberOfArguments() const noexcept override { return nargs; } virtual void ParseValue(const std::vector &value) override { action(value); } }; /** A default Reader class for argument classes * * If destination type is assignable to std::string it uses an assignment to std::string. * Otherwise ValueReader simply uses a std::istringstream to read into the destination type, and * raises a ParseError if there are any characters left. */ struct ValueReader { template typename std::enable_if::value, bool>::type operator ()(const std::string &name, const std::string &value, T &destination) { std::istringstream ss(value); ss >> destination >> std::ws; if (ss.rdbuf()->in_avail() > 0) { #ifdef ARGS_NOEXCEPT (void)name; return false; #else std::ostringstream problem; problem << "Argument '" << name << "' received invalid value type '" << value << "'"; throw ParseError(problem.str()); #endif } return true; } template typename std::enable_if::value, bool>::type operator()(const std::string &, const std::string &value, T &destination) { destination = value; return true; } }; /** An argument-accepting flag class * * \tparam T the type to extract the argument as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, typename Reader = ValueReader> class ValueFlag : public ValueFlagBase { protected: T value; T defaultValue; virtual std::string GetDefaultString(const HelpParams&) const override { return detail::ToString(defaultValue); } private: Reader reader; public: ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), value(defaultValue_), defaultValue(defaultValue_) { group_.Add(*this); } ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), const bool extraError_ = false): ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, extraError_ ? Options::Single : Options::None) { } ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): ValueFlag(group_, name_, help_, std::move(matcher_), T(), options_) { } virtual ~ValueFlag() {} virtual void ParseValue(const std::vector &values_) override { const std::string &value_ = values_.at(0); #ifdef ARGS_NOEXCEPT if (!reader(name, value_, this->value)) { error = Error::Parse; } #else reader(name, value_, this->value); #endif } virtual void Reset() noexcept override { ValueFlagBase::Reset(); value = defaultValue; } /** Get the value */ T &Get() noexcept { return value; } /** Get the default value */ const T &GetDefault() noexcept { return defaultValue; } }; /** An optional argument-accepting flag class * * \tparam T the type to extract the argument as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, typename Reader = ValueReader> class ImplicitValueFlag : public ValueFlag { protected: T implicitValue; public: ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &implicitValue_, const T &defaultValue_ = T(), Options options_ = {}) : ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(implicitValue_) { } ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), Options options_ = {}) : ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(defaultValue_) { } ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : ValueFlag(group_, name_, help_, std::move(matcher_), {}, options_), implicitValue() { } virtual ~ImplicitValueFlag() {} virtual Nargs NumberOfArguments() const noexcept override { return {0, 1}; } virtual void ParseValue(const std::vector &value_) override { if (value_.empty()) { this->value = implicitValue; } else { ValueFlag::ParseValue(value_); } } }; /** A variadic arguments accepting flag class * * \tparam T the type to extract the argument as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, template class List = std::vector, typename Reader = ValueReader> class NargsValueFlag : public FlagBase { protected: List values; Nargs nargs; Reader reader; public: typedef List Container; typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; NargsValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, const List &defaultValues_ = {}, Options options_ = {}) : FlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_), nargs(nargs_) { group_.Add(*this); } virtual ~NargsValueFlag() {} virtual Nargs NumberOfArguments() const noexcept override { return nargs; } virtual void ParseValue(const std::vector &values_) override { values.clear(); for (const std::string &value : values_) { T v; #ifdef ARGS_NOEXCEPT if (!reader(name, value, v)) { error = Error::Parse; } #else reader(name, value, v); #endif values.insert(std::end(values), v); } } List &Get() noexcept { return values; } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; /** An argument-accepting flag class that pushes the found values into a list * * \tparam T the type to extract the argument as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, template class List = std::vector, typename Reader = ValueReader> class ValueFlagList : public ValueFlagBase { private: using Container = List; Container values; Reader reader; public: typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; ValueFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Container &defaultValues_ = Container(), Options options_ = {}): ValueFlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_) { group_.Add(*this); } virtual ~ValueFlagList() {} virtual void ParseValue(const std::vector &values_) override { const std::string &value_ = values_.at(0); T v; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, v)) { error = Error::Parse; } #else reader(name, value_, v); #endif values.insert(std::end(values), v); } /** Get the values */ Container &Get() noexcept { return values; } virtual std::string Name() const override { return name + std::string("..."); } virtual void Reset() noexcept override { ValueFlagBase::Reset(); values.clear(); } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; /** A mapping value flag class * * \tparam K the type to extract the argument as * \tparam T the type to store the result as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ template < typename K, typename T, typename Reader = ValueReader, template class Map = std::unordered_map> class MapFlag : public ValueFlagBase { private: const Map map; T value; Reader reader; protected: virtual std::vector GetChoicesStrings(const HelpParams &) const override { return detail::MapKeysToStrings(map); } public: MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), map(map_), value(defaultValue_) { group_.Add(*this); } MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const T &defaultValue_ = T(), const bool extraError_ = false): MapFlag(group_, name_, help_, std::move(matcher_), map_, defaultValue_, extraError_ ? Options::Single : Options::None) { } MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, Options options_): MapFlag(group_, name_, help_, std::move(matcher_), map_, T(), options_) { } virtual ~MapFlag() {} virtual void ParseValue(const std::vector &values_) override { const std::string &value_ = values_.at(0); K key; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, key)) { error = Error::Parse; } #else reader(name, value_, key); #endif auto it = map.find(key); if (it == std::end(map)) { std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; #ifdef ARGS_NOEXCEPT error = Error::Map; errorMsg = problem.str(); #else throw MapError(problem.str()); #endif } else { this->value = it->second; } } /** Get the value */ T &Get() noexcept { return value; } }; /** A mapping value flag list class * * \tparam K the type to extract the argument as * \tparam T the type to store the result as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ template < typename K, typename T, template class List = std::vector, typename Reader = ValueReader, template class Map = std::unordered_map> class MapFlagList : public ValueFlagBase { private: using Container = List; const Map map; Container values; Reader reader; protected: virtual std::vector GetChoicesStrings(const HelpParams &) const override { return detail::MapKeysToStrings(map); } public: typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; MapFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const Container &defaultValues_ = Container()): ValueFlagBase(name_, help_, std::move(matcher_)), map(map_), values(defaultValues_) { group_.Add(*this); } virtual ~MapFlagList() {} virtual void ParseValue(const std::vector &values_) override { const std::string &value = values_.at(0); K key; #ifdef ARGS_NOEXCEPT if (!reader(name, value, key)) { error = Error::Parse; } #else reader(name, value, key); #endif auto it = map.find(key); if (it == std::end(map)) { std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; #ifdef ARGS_NOEXCEPT error = Error::Map; errorMsg = problem.str(); #else throw MapError(problem.str()); #endif } else { this->values.emplace_back(it->second); } } /** Get the value */ Container &Get() noexcept { return values; } virtual std::string Name() const override { return name + std::string("..."); } virtual void Reset() noexcept override { ValueFlagBase::Reset(); values.clear(); } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; /** A positional argument class * * \tparam T the type to extract the argument as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, typename Reader = ValueReader> class Positional : public PositionalBase { private: T value; Reader reader; public: Positional(Group &group_, const std::string &name_, const std::string &help_, const T &defaultValue_ = T(), Options options_ = {}): PositionalBase(name_, help_, options_), value(defaultValue_) { group_.Add(*this); } Positional(Group &group_, const std::string &name_, const std::string &help_, Options options_): Positional(group_, name_, help_, T(), options_) { } virtual ~Positional() {} virtual void ParseValue(const std::string &value_) override { #ifdef ARGS_NOEXCEPT if (!reader(name, value_, this->value)) { error = Error::Parse; } #else reader(name, value_, this->value); #endif ready = false; matched = true; } /** Get the value */ T &Get() noexcept { return value; } }; /** A positional argument class that pushes the found values into a list * * \tparam T the type to extract the argument as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) */ template < typename T, template class List = std::vector, typename Reader = ValueReader> class PositionalList : public PositionalBase { private: using Container = List; Container values; Reader reader; public: typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; PositionalList(Group &group_, const std::string &name_, const std::string &help_, const Container &defaultValues_ = Container(), Options options_ = {}): PositionalBase(name_, help_, options_), values(defaultValues_) { group_.Add(*this); } PositionalList(Group &group_, const std::string &name_, const std::string &help_, Options options_): PositionalList(group_, name_, help_, {}, options_) { } virtual ~PositionalList() {} virtual void ParseValue(const std::string &value_) override { T v; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, v)) { error = Error::Parse; } #else reader(name, value_, v); #endif values.insert(std::end(values), v); matched = true; } virtual std::string Name() const override { return name + std::string("..."); } /** Get the values */ Container &Get() noexcept { return values; } virtual void Reset() noexcept override { PositionalBase::Reset(); values.clear(); } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; /** A positional argument mapping class * * \tparam K the type to extract the argument as * \tparam T the type to store the result as * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ template < typename K, typename T, typename Reader = ValueReader, template class Map = std::unordered_map> class MapPositional : public PositionalBase { private: const Map map; T value; Reader reader; protected: virtual std::vector GetChoicesStrings(const HelpParams &) const override { return detail::MapKeysToStrings(map); } public: MapPositional(Group &group_, const std::string &name_, const std::string &help_, const Map &map_, const T &defaultValue_ = T(), Options options_ = {}): PositionalBase(name_, help_, options_), map(map_), value(defaultValue_) { group_.Add(*this); } virtual ~MapPositional() {} virtual void ParseValue(const std::string &value_) override { K key; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, key)) { error = Error::Parse; } #else reader(name, value_, key); #endif auto it = map.find(key); if (it == std::end(map)) { std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; #ifdef ARGS_NOEXCEPT error = Error::Map; errorMsg = problem.str(); #else throw MapError(problem.str()); #endif } else { this->value = it->second; ready = false; matched = true; } } /** Get the value */ T &Get() noexcept { return value; } }; /** A positional argument mapping list class * * \tparam K the type to extract the argument as * \tparam T the type to store the result as * \tparam List the list type that houses the values * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ template < typename K, typename T, template class List = std::vector, typename Reader = ValueReader, template class Map = std::unordered_map> class MapPositionalList : public PositionalBase { private: using Container = List; const Map map; Container values; Reader reader; protected: virtual std::vector GetChoicesStrings(const HelpParams &) const override { return detail::MapKeysToStrings(map); } public: typedef T value_type; typedef typename Container::allocator_type allocator_type; typedef typename Container::pointer pointer; typedef typename Container::const_pointer const_pointer; typedef T& reference; typedef const T& const_reference; typedef typename Container::size_type size_type; typedef typename Container::difference_type difference_type; typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; MapPositionalList(Group &group_, const std::string &name_, const std::string &help_, const Map &map_, const Container &defaultValues_ = Container(), Options options_ = {}): PositionalBase(name_, help_, options_), map(map_), values(defaultValues_) { group_.Add(*this); } virtual ~MapPositionalList() {} virtual void ParseValue(const std::string &value_) override { K key; #ifdef ARGS_NOEXCEPT if (!reader(name, value_, key)) { error = Error::Parse; } #else reader(name, value_, key); #endif auto it = map.find(key); if (it == std::end(map)) { std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; #ifdef ARGS_NOEXCEPT error = Error::Map; errorMsg = problem.str(); #else throw MapError(problem.str()); #endif } else { this->values.emplace_back(it->second); matched = true; } } /** Get the value */ Container &Get() noexcept { return values; } virtual std::string Name() const override { return name + std::string("..."); } virtual void Reset() noexcept override { PositionalBase::Reset(); values.clear(); } iterator begin() noexcept { return values.begin(); } const_iterator begin() const noexcept { return values.begin(); } const_iterator cbegin() const noexcept { return values.cbegin(); } iterator end() noexcept { return values.end(); } const_iterator end() const noexcept { return values.end(); } const_iterator cend() const noexcept { return values.cend(); } }; } #endif ================================================ FILE: third-party/catch/LICENSE.txt ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: third-party/catch/catch.hpp ================================================ /* * Catch v1.6.1 * Generated: 2017-01-20 12:33:53.497767 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_CATCH_HPP_INCLUDED #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ # pragma GCC system_header #endif // #included from: internal/catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic ignored "-Wglobal-constructors" # pragma clang diagnostic ignored "-Wvariadic-macros" # pragma clang diagnostic ignored "-Wc99-extensions" # pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wc++98-compat" # pragma clang diagnostic ignored "-Wc++98-compat-pedantic" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL #endif #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // #included from: internal/catch_notimplemented_exception.h #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED // #included from: catch_common.h #define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED // #included from: catch_compiler_capabilities.h #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED // Detect a number of compiler features - mostly C++11/14 conformance - by compiler // The following features are defined: // // CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? // CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? // CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? // CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported // CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? // CATCH_CONFIG_CPP11_OVERRIDE : is override supported? // CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. // All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 #ifdef __cplusplus # if __cplusplus >= 201103L # define CATCH_CPP11_OR_GREATER # endif # if __cplusplus >= 201402L # define CATCH_CPP14_OR_GREATER # endif #endif #ifdef __clang__ # if __has_feature(cxx_nullptr) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if __has_feature(cxx_noexcept) # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif # if defined(CATCH_CPP11_OR_GREATER) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) # endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Borland #ifdef __BORLANDC__ #endif // __BORLANDC__ //////////////////////////////////////////////////////////////////////////////// // EDG #ifdef __EDG_VERSION__ #endif // __EDG_VERSION__ //////////////////////////////////////////////////////////////////////////////// // Digital Mars #ifdef __DMC__ #endif // __DMC__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ # if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) # endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #if (_MSC_VER >= 1600) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) #define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE #endif #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Use variadic macros if the compiler supports them #if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ ( defined __GNUC__ && __GNUC__ >= 3 ) || \ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) #define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS #endif // Use __COUNTER__ if the compiler supports it #if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ ( defined __GNUC__ && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \ ( defined __clang__ && __clang_major__ >= 3 ) #define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 #if defined(CATCH_CPP11_OR_GREATER) # if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS # define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM # define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE # define CATCH_INTERNAL_CONFIG_CPP11_TUPLE # endif # ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) # define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) # define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) # define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) # define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE # endif #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured #if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_NULLPTR #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_NOEXCEPT #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_GENERATED_METHODS #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_IS_ENUM #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_TUPLE #endif #if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) # define CATCH_CONFIG_VARIADIC_MACROS #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_LONG_LONG #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_OVERRIDE #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_UNIQUE_PTR #endif // Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for // analytics) because, at time of writing, __COUNTER__ is not properly handled by it. // This does not affect compilation #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__) # define CATCH_CONFIG_COUNTER #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_SHUFFLE #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif // noexcept support: #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) # define CATCH_NOEXCEPT noexcept # define CATCH_NOEXCEPT_IS(x) noexcept(x) #else # define CATCH_NOEXCEPT throw() # define CATCH_NOEXCEPT_IS(x) #endif // nullptr support #ifdef CATCH_CONFIG_CPP11_NULLPTR # define CATCH_NULL nullptr #else # define CATCH_NULL NULL #endif // override support #ifdef CATCH_CONFIG_CPP11_OVERRIDE # define CATCH_OVERRIDE override #else # define CATCH_OVERRIDE #endif // unique_ptr support #ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR # define CATCH_AUTO_PTR( T ) std::unique_ptr #else # define CATCH_AUTO_PTR( T ) std::auto_ptr #endif #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif #define INTERNAL_CATCH_STRINGIFY2( expr ) #expr #define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) #include #include #include namespace Catch { struct IConfig; struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { #ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; #else NonCopyable( NonCopyable const& info ); NonCopyable& operator = ( NonCopyable const& ); #endif protected: NonCopyable() {} virtual ~NonCopyable(); }; class SafeBool { public: typedef void (SafeBool::*type)() const; static type makeSafe( bool value ) { return value ? &SafeBool::trueValue : 0; } private: void trueValue() const {} }; template inline void deleteAll( ContainerT& container ) { typename ContainerT::const_iterator it = container.begin(); typename ContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete *it; } template inline void deleteAllValues( AssociativeContainerT& container ) { typename AssociativeContainerT::const_iterator it = container.begin(); typename AssociativeContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete it->second; } bool startsWith( std::string const& s, std::string const& prefix ); bool endsWith( std::string const& s, std::string const& suffix ); bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); std::string trim( std::string const& str ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); struct pluralise { pluralise( std::size_t count, std::string const& label ); friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); std::size_t m_count; std::string m_label; }; struct SourceLineInfo { SourceLineInfo(); SourceLineInfo( char const* _file, std::size_t _line ); SourceLineInfo( SourceLineInfo const& other ); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SourceLineInfo( SourceLineInfo && ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo& operator = ( SourceLineInfo && ) = default; # endif bool empty() const; bool operator == ( SourceLineInfo const& other ) const; bool operator < ( SourceLineInfo const& other ) const; std::string file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // This is just here to avoid compiler warnings with macro constants and boolean literals inline bool alwaysTrue( std::size_t = 0 ) { return true; } inline bool alwaysFalse( std::size_t = 0 ) { return false; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); void seedRng( IConfig const& config ); unsigned int rngSeed(); // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() { return std::string(); } }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) #define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); #include namespace Catch { class NotImplementedException : public std::exception { public: NotImplementedException( SourceLineInfo const& lineInfo ); NotImplementedException( NotImplementedException const& ) {} virtual ~NotImplementedException() CATCH_NOEXCEPT {} virtual const char* what() const CATCH_NOEXCEPT; private: std::string m_what; SourceLineInfo m_lineInfo; }; } // end namespace Catch /////////////////////////////////////////////////////////////////////////////// #define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) // #included from: internal/catch_context.h #define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED // #included from: catch_interfaces_generators.h #define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED #include namespace Catch { struct IGeneratorInfo { virtual ~IGeneratorInfo(); virtual bool moveNext() = 0; virtual std::size_t getCurrentIndex() const = 0; }; struct IGeneratorsForTest { virtual ~IGeneratorsForTest(); virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; virtual bool moveNext() = 0; }; IGeneratorsForTest* createGeneratorsForTest(); } // end namespace Catch // #included from: catch_ptr.hpp #define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { // An intrusive reference counting smart pointer. // T must implement addRef() and release() methods // typically implementing the IShared interface template class Ptr { public: Ptr() : m_p( CATCH_NULL ){} Ptr( T* p ) : m_p( p ){ if( m_p ) m_p->addRef(); } Ptr( Ptr const& other ) : m_p( other.m_p ){ if( m_p ) m_p->addRef(); } ~Ptr(){ if( m_p ) m_p->release(); } void reset() { if( m_p ) m_p->release(); m_p = CATCH_NULL; } Ptr& operator = ( T* p ){ Ptr temp( p ); swap( temp ); return *this; } Ptr& operator = ( Ptr const& other ){ Ptr temp( other ); swap( temp ); return *this; } void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } T* get() const{ return m_p; } T& operator*() const { return *m_p; } T* operator->() const { return m_p; } bool operator !() const { return m_p == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } private: T* m_p; }; struct IShared : NonCopyable { virtual ~IShared(); virtual void addRef() const = 0; virtual void release() const = 0; }; template struct SharedImpl : T { SharedImpl() : m_rc( 0 ){} virtual void addRef() const { ++m_rc; } virtual void release() const { if( --m_rc == 0 ) delete this; } mutable unsigned int m_rc; }; } // end namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif #include #include #include namespace Catch { class TestCase; class Stream; struct IResultCapture; struct IRunner; struct IGeneratorsForTest; struct IConfig; struct IContext { virtual ~IContext(); virtual IResultCapture* getResultCapture() = 0; virtual IRunner* getRunner() = 0; virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; virtual bool advanceGeneratorsForCurrentTest() = 0; virtual Ptr getConfig() const = 0; }; struct IMutableContext : IContext { virtual ~IMutableContext(); virtual void setResultCapture( IResultCapture* resultCapture ) = 0; virtual void setRunner( IRunner* runner ) = 0; virtual void setConfig( Ptr const& config ) = 0; }; IContext& getCurrentContext(); IMutableContext& getCurrentMutableContext(); void cleanUpContext(); Stream createStream( std::string const& streamName ); } // #included from: internal/catch_test_registry.hpp #define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED // #included from: catch_interfaces_testcase.h #define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED #include namespace Catch { class TestSpec; struct ITestCase : IShared { virtual void invoke () const = 0; protected: virtual ~ITestCase(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); } namespace Catch { template class MethodTestCase : public SharedImpl { public: MethodTestCase( void (C::*method)() ) : m_method( method ) {} virtual void invoke() const { C obj; (obj.*m_method)(); } private: virtual ~MethodTestCase() {} void (C::*m_method)(); }; typedef void(*TestFunction)(); struct NameAndDesc { NameAndDesc( const char* _name = "", const char* _description= "" ) : name( _name ), description( _description ) {} const char* name; const char* description; }; void registerTestCase ( ITestCase* testCase, char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ); struct AutoReg { AutoReg ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); template AutoReg ( void (C::*method)(), char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { registerTestCase ( new MethodTestCase( method ), className, nameAndDesc, lineInfo ); } ~AutoReg(); private: AutoReg( AutoReg const& ); void operator= ( AutoReg const& ); }; void registerTestCaseFunction ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ static void TestName(); \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ namespace{ \ struct TestName : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ } \ void TestName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); #else /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ static void TestName(); \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ static void TestName() #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ namespace{ \ struct TestCaseName : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ } \ void TestCaseName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); #endif // #included from: internal/catch_capture.hpp #define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED // #included from: catch_result_builder.h #define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED // #included from: catch_result_type.h #define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED namespace Catch { // ResultWas::OfType enum struct ResultWas { enum OfType { Unknown = -1, Ok = 0, Info = 1, Warning = 2, FailureBit = 0x10, ExpressionFailed = FailureBit | 1, ExplicitFailure = FailureBit | 2, Exception = 0x100 | FailureBit, ThrewException = Exception | 1, DidntThrowException = Exception | 2, FatalErrorCondition = 0x200 | FailureBit }; }; inline bool isOk( ResultWas::OfType resultType ) { return ( resultType & ResultWas::FailureBit ) == 0; } inline bool isJustInfo( int flags ) { return flags == ResultWas::Info; } // ResultDisposition::Flags enum struct ResultDisposition { enum Flags { Normal = 0x01, ContinueOnFailure = 0x02, // Failures fail test, but execution continues FalseTest = 0x04, // Prefix expression with ! SuppressFail = 0x08 // Failures are reported but do not fail the test }; }; inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { return static_cast( static_cast( lhs ) | static_cast( rhs ) ); } inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } } // end namespace Catch // #included from: catch_assertionresult.h #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED #include namespace Catch { struct AssertionInfo { AssertionInfo() {} AssertionInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ); std::string macroName; SourceLineInfo lineInfo; std::string capturedExpression; ResultDisposition::Flags resultDisposition; }; struct AssertionResultData { AssertionResultData() : resultType( ResultWas::Unknown ) {} std::string reconstructedExpression; std::string message; ResultWas::OfType resultType; }; class AssertionResult { public: AssertionResult(); AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); ~AssertionResult(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionResult( AssertionResult const& ) = default; AssertionResult( AssertionResult && ) = default; AssertionResult& operator = ( AssertionResult const& ) = default; AssertionResult& operator = ( AssertionResult && ) = default; # endif bool isOk() const; bool succeeded() const; ResultWas::OfType getResultType() const; bool hasExpression() const; bool hasMessage() const; std::string getExpression() const; std::string getExpressionInMacro() const; bool hasExpandedExpression() const; std::string getExpandedExpression() const; std::string getMessage() const; SourceLineInfo getSourceInfo() const; std::string getTestMacroName() const; protected: AssertionInfo m_info; AssertionResultData m_resultData; }; } // end namespace Catch // #included from: catch_matchers.hpp #define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED namespace Catch { namespace Matchers { namespace Impl { namespace Generic { template class AllOf; template class AnyOf; template class Not; } template struct Matcher : SharedImpl { typedef ExpressionT ExpressionType; virtual ~Matcher() {} virtual Ptr clone() const = 0; virtual bool match( ExpressionT const& expr ) const = 0; virtual std::string toString() const = 0; Generic::AllOf operator && ( Matcher const& other ) const; Generic::AnyOf operator || ( Matcher const& other ) const; Generic::Not operator ! () const; }; template struct MatcherImpl : Matcher { virtual Ptr > clone() const { return Ptr >( new DerivedT( static_cast( *this ) ) ); } }; namespace Generic { template class Not : public MatcherImpl, ExpressionT> { public: explicit Not( Matcher const& matcher ) : m_matcher(matcher.clone()) {} Not( Not const& other ) : m_matcher( other.m_matcher ) {} virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE { return !m_matcher->match( expr ); } virtual std::string toString() const CATCH_OVERRIDE { return "not " + m_matcher->toString(); } private: Ptr< Matcher > m_matcher; }; template class AllOf : public MatcherImpl, ExpressionT> { public: AllOf() {} AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} AllOf& add( Matcher const& matcher ) { m_matchers.push_back( matcher.clone() ); return *this; } virtual bool match( ExpressionT const& expr ) const { for( std::size_t i = 0; i < m_matchers.size(); ++i ) if( !m_matchers[i]->match( expr ) ) return false; return true; } virtual std::string toString() const { std::ostringstream oss; oss << "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) oss << " and "; oss << m_matchers[i]->toString(); } oss << " )"; return oss.str(); } AllOf operator && ( Matcher const& other ) const { AllOf allOfExpr( *this ); allOfExpr.add( other ); return allOfExpr; } private: std::vector > > m_matchers; }; template class AnyOf : public MatcherImpl, ExpressionT> { public: AnyOf() {} AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} AnyOf& add( Matcher const& matcher ) { m_matchers.push_back( matcher.clone() ); return *this; } virtual bool match( ExpressionT const& expr ) const { for( std::size_t i = 0; i < m_matchers.size(); ++i ) if( m_matchers[i]->match( expr ) ) return true; return false; } virtual std::string toString() const { std::ostringstream oss; oss << "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) oss << " or "; oss << m_matchers[i]->toString(); } oss << " )"; return oss.str(); } AnyOf operator || ( Matcher const& other ) const { AnyOf anyOfExpr( *this ); anyOfExpr.add( other ); return anyOfExpr; } private: std::vector > > m_matchers; }; } // namespace Generic template Generic::AllOf Matcher::operator && ( Matcher const& other ) const { Generic::AllOf allOfExpr; allOfExpr.add( *this ); allOfExpr.add( other ); return allOfExpr; } template Generic::AnyOf Matcher::operator || ( Matcher const& other ) const { Generic::AnyOf anyOfExpr; anyOfExpr.add( *this ); anyOfExpr.add( other ); return anyOfExpr; } template Generic::Not Matcher::operator ! () const { return Generic::Not( *this ); } namespace StdString { inline std::string makeString( std::string const& str ) { return str; } inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } struct CasedString { CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), m_str( adjustString( str ) ) {} std::string adjustString( std::string const& str ) const { return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; } std::string toStringSuffix() const { return m_caseSensitivity == CaseSensitive::No ? " (case insensitive)" : ""; } CaseSensitive::Choice m_caseSensitivity; std::string m_str; }; struct Equals : MatcherImpl { Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( str, caseSensitivity ) {} Equals( Equals const& other ) : m_data( other.m_data ){} virtual ~Equals(); virtual bool match( std::string const& expr ) const { return m_data.m_str == m_data.adjustString( expr );; } virtual std::string toString() const { return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct Contains : MatcherImpl { Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} Contains( Contains const& other ) : m_data( other.m_data ){} virtual ~Contains(); virtual bool match( std::string const& expr ) const { return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos; } virtual std::string toString() const { return "contains: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct StartsWith : MatcherImpl { StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} StartsWith( StartsWith const& other ) : m_data( other.m_data ){} virtual ~StartsWith(); virtual bool match( std::string const& expr ) const { return startsWith( m_data.adjustString( expr ), m_data.m_str ); } virtual std::string toString() const { return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct EndsWith : MatcherImpl { EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} EndsWith( EndsWith const& other ) : m_data( other.m_data ){} virtual ~EndsWith(); virtual bool match( std::string const& expr ) const { return endsWith( m_data.adjustString( expr ), m_data.m_str ); } virtual std::string toString() const { return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; } // namespace StdString } // namespace Impl // The following functions create the actual matcher objects. // This allows the types to be inferred template inline Impl::Generic::Not Not( Impl::Matcher const& m ) { return Impl::Generic::Not( m ); } template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); } inline Impl::StdString::Equals Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Equals( str, caseSensitivity ); } inline Impl::StdString::Equals Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity ); } inline Impl::StdString::Contains Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Contains( substr, caseSensitivity ); } inline Impl::StdString::Contains Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity ); } inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { return Impl::StdString::StartsWith( substr ); } inline Impl::StdString::StartsWith StartsWith( const char* substr ) { return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); } inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { return Impl::StdString::EndsWith( substr ); } inline Impl::StdString::EndsWith EndsWith( const char* substr ) { return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); } } // namespace Matchers using namespace Matchers; } // namespace Catch namespace Catch { struct TestFailureException{}; template class ExpressionLhs; struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; struct CopyableStream { CopyableStream() {} CopyableStream( CopyableStream const& other ) { oss << other.oss.str(); } CopyableStream& operator=( CopyableStream const& other ) { oss.str(""); oss << other.oss.str(); return *this; } std::ostringstream oss; }; class ResultBuilder { public: ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition, char const* secondArg = "" ); template ExpressionLhs operator <= ( T const& operand ); ExpressionLhs operator <= ( bool value ); template ResultBuilder& operator << ( T const& value ) { m_stream.oss << value; return *this; } template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); ResultBuilder& setResultType( ResultWas::OfType result ); ResultBuilder& setResultType( bool result ); ResultBuilder& setLhs( std::string const& lhs ); ResultBuilder& setRhs( std::string const& rhs ); ResultBuilder& setOp( std::string const& op ); void endExpression(); std::string reconstructExpression() const; AssertionResult build() const; void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); void captureResult( ResultWas::OfType resultType ); void captureExpression(); void captureExpectedException( std::string const& expectedMessage ); void captureExpectedException( Matchers::Impl::Matcher const& matcher ); void handleResult( AssertionResult const& result ); void react(); bool shouldDebugBreak() const; bool allowThrows() const; private: AssertionInfo m_assertionInfo; AssertionResultData m_data; struct ExprComponents { ExprComponents() : testFalse( false ) {} bool testFalse; std::string lhs, rhs, op; } m_exprComponents; CopyableStream m_stream; bool m_shouldDebugBreak; bool m_shouldThrow; }; } // namespace Catch // Include after due to circular dependency: // #included from: catch_expression_lhs.hpp #define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED // #included from: catch_evaluate.hpp #define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4389) // '==' : signed/unsigned mismatch #endif #include namespace Catch { namespace Internal { enum Operator { IsEqualTo, IsNotEqualTo, IsLessThan, IsGreaterThan, IsLessThanOrEqualTo, IsGreaterThanOrEqualTo }; template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; template inline T& opCast(T const& t) { return const_cast(t); } // nullptr_t support based on pull request #154 from Konstantin Baumann #ifdef CATCH_CONFIG_CPP11_NULLPTR inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } #endif // CATCH_CONFIG_CPP11_NULLPTR // So the compare overloads can be operator agnostic we convey the operator as a template // enum, which is used to specialise an Evaluator for doing the comparison. template class Evaluator{}; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { return bool( opCast( lhs ) == opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) != opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) < opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) > opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) >= opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) <= opCast( rhs ) ); } }; template bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // This level of indirection allows us to specialise for integer types // to avoid signed/ unsigned warnings // "base" overload template bool compare( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // unsigned X to int template bool compare( unsigned int lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // unsigned X to long template bool compare( unsigned int lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // int to unsigned X template bool compare( int lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // long to unsigned X template bool compare( long lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // pointer to long (when comparing against NULL) template bool compare( long lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, long rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } // pointer to int (when comparing against NULL) template bool compare( int lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, int rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } #ifdef CATCH_CONFIG_CPP11_LONG_LONG // long long to unsigned X template bool compare( long long lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned long long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // unsigned long long to X template bool compare( unsigned long long lhs, int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, long long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // pointer to long long (when comparing against NULL) template bool compare( long long lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, long long rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } #endif // CATCH_CONFIG_CPP11_LONG_LONG #ifdef CATCH_CONFIG_CPP11_NULLPTR // pointer to nullptr_t (when comparing against nullptr) template bool compare( std::nullptr_t, T* rhs ) { return Evaluator::evaluate( nullptr, rhs ); } template bool compare( T* lhs, std::nullptr_t ) { return Evaluator::evaluate( lhs, nullptr ); } #endif // CATCH_CONFIG_CPP11_NULLPTR } // end of namespace Internal } // end of namespace Catch #ifdef _MSC_VER #pragma warning(pop) #endif // #included from: catch_tostring.h #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED #include #include #include #include #include #ifdef __OBJC__ // #included from: catch_objc_arc.hpp #define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED #import #ifdef __has_feature #define CATCH_ARC_ENABLED __has_feature(objc_arc) #else #define CATCH_ARC_ENABLED 0 #endif void arcSafeRelease( NSObject* obj ); id performOptionalSelector( id obj, SEL sel ); #if !CATCH_ARC_ENABLED inline void arcSafeRelease( NSObject* obj ) { [obj release]; } inline id performOptionalSelector( id obj, SEL sel ) { if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; return nil; } #define CATCH_UNSAFE_UNRETAINED #define CATCH_ARC_STRONG #else inline void arcSafeRelease( NSObject* ){} inline id performOptionalSelector( id obj, SEL sel ) { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" #endif if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; #ifdef __clang__ #pragma clang diagnostic pop #endif return nil; } #define CATCH_UNSAFE_UNRETAINED __unsafe_unretained #define CATCH_ARC_STRONG __strong #endif #endif #ifdef CATCH_CONFIG_CPP11_TUPLE #include #endif #ifdef CATCH_CONFIG_CPP11_IS_ENUM #include #endif namespace Catch { // Why we're here. template std::string toString( T const& value ); // Built in overloads std::string toString( std::string const& value ); std::string toString( std::wstring const& value ); std::string toString( const char* const value ); std::string toString( char* const value ); std::string toString( const wchar_t* const value ); std::string toString( wchar_t* const value ); std::string toString( int value ); std::string toString( unsigned long value ); std::string toString( unsigned int value ); std::string toString( const double value ); std::string toString( const float value ); std::string toString( bool value ); std::string toString( char value ); std::string toString( signed char value ); std::string toString( unsigned char value ); #ifdef CATCH_CONFIG_CPP11_LONG_LONG std::string toString( long long value ); std::string toString( unsigned long long value ); #endif #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ); #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ); std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); std::string toString( NSObject* const& nsObject ); #endif namespace Detail { extern const std::string unprintableString; struct BorgType { template BorgType( T const& ); }; struct TrueType { char sizer[1]; }; struct FalseType { char sizer[2]; }; TrueType& testStreamable( std::ostream& ); FalseType testStreamable( FalseType ); FalseType operator<<( std::ostream const&, BorgType const& ); template struct IsStreamInsertable { static std::ostream &s; static T const&t; enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; }; #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template::value > struct EnumStringMaker { static std::string convert( T const& ) { return unprintableString; } }; template struct EnumStringMaker { static std::string convert( T const& v ) { return ::Catch::toString( static_cast::type>(v) ); } }; #endif template struct StringMakerBase { #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template static std::string convert( T const& v ) { return EnumStringMaker::convert( v ); } #else template static std::string convert( T const& ) { return unprintableString; } #endif }; template<> struct StringMakerBase { template static std::string convert( T const& _value ) { std::ostringstream oss; oss << _value; return oss.str(); } }; std::string rawMemoryToString( const void *object, std::size_t size ); template inline std::string rawMemoryToString( const T& object ) { return rawMemoryToString( &object, sizeof(object) ); } } // end namespace Detail template struct StringMaker : Detail::StringMakerBase::value> {}; template struct StringMaker { template static std::string convert( U* p ) { if( !p ) return "NULL"; else return Detail::rawMemoryToString( p ); } }; template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) return "NULL"; else return Detail::rawMemoryToString( p ); } }; namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ); } //template //struct StringMaker > { // static std::string convert( std::vector const& v ) { // return Detail::rangeToString( v.begin(), v.end() ); // } //}; template std::string toString( std::vector const& v ) { return Detail::rangeToString( v.begin(), v.end() ); } #ifdef CATCH_CONFIG_CPP11_TUPLE // toString for tuples namespace TupleDetail { template< typename Tuple, std::size_t N = 0, bool = (N < std::tuple_size::value) > struct ElementPrinter { static void print( const Tuple& tuple, std::ostream& os ) { os << ( N ? ", " : " " ) << Catch::toString(std::get(tuple)); ElementPrinter::print(tuple,os); } }; template< typename Tuple, std::size_t N > struct ElementPrinter { static void print( const Tuple&, std::ostream& ) {} }; } template struct StringMaker> { static std::string convert( const std::tuple& tuple ) { std::ostringstream os; os << '{'; TupleDetail::ElementPrinter>::print( tuple, os ); os << " }"; return os.str(); } }; #endif // CATCH_CONFIG_CPP11_TUPLE namespace Detail { template std::string makeString( T const& value ) { return StringMaker::convert( value ); } } // end namespace Detail /// \brief converts any type to a string /// /// The default template forwards on to ostringstream - except when an /// ostringstream overload does not exist - in which case it attempts to detect /// that and writes {?}. /// Overload (not specialise) this template for custom typs that you don't want /// to provide an ostream overload for. template std::string toString( T const& value ) { return StringMaker::convert( value ); } namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ) { std::ostringstream oss; oss << "{ "; if( first != last ) { oss << Catch::toString( *first ); for( ++first ; first != last ; ++first ) oss << ", " << Catch::toString( *first ); } oss << " }"; return oss.str(); } } } // end namespace Catch namespace Catch { // Wraps the LHS of an expression and captures the operator and RHS (if any) - // wrapping them all in a ResultBuilder object template class ExpressionLhs { ExpressionLhs& operator = ( ExpressionLhs const& ); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS ExpressionLhs& operator = ( ExpressionLhs && ) = delete; # endif public: ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS ExpressionLhs( ExpressionLhs const& ) = default; ExpressionLhs( ExpressionLhs && ) = default; # endif template ResultBuilder& operator == ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator != ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator < ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator > ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator <= ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator >= ( RhsT const& rhs ) { return captureExpression( rhs ); } ResultBuilder& operator == ( bool rhs ) { return captureExpression( rhs ); } ResultBuilder& operator != ( bool rhs ) { return captureExpression( rhs ); } void endExpression() { bool value = m_lhs ? true : false; m_rb .setLhs( Catch::toString( value ) ) .setResultType( value ) .endExpression(); } // Only simple binary expressions are allowed on the LHS. // If more complex compositions are required then place the sub expression in parentheses template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); private: template ResultBuilder& captureExpression( RhsT const& rhs ) { return m_rb .setResultType( Internal::compare( m_lhs, rhs ) ) .setLhs( Catch::toString( m_lhs ) ) .setRhs( Catch::toString( rhs ) ) .setOp( Internal::OperatorTraits::getName() ); } private: ResultBuilder& m_rb; T m_lhs; }; } // end namespace Catch namespace Catch { template inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { return ExpressionLhs( *this, operand ); } inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { return ExpressionLhs( *this, value ); } } // namespace Catch // #included from: catch_message.h #define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED #include namespace Catch { struct MessageInfo { MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ); std::string macroName; SourceLineInfo lineInfo; ResultWas::OfType type; std::string message; unsigned int sequence; bool operator == ( MessageInfo const& other ) const { return sequence == other.sequence; } bool operator < ( MessageInfo const& other ) const { return sequence < other.sequence; } private: static unsigned int globalCount; }; struct MessageBuilder { MessageBuilder( std::string const& macroName, SourceLineInfo const& lineInfo, ResultWas::OfType type ) : m_info( macroName, lineInfo, type ) {} template MessageBuilder& operator << ( T const& value ) { m_stream << value; return *this; } MessageInfo m_info; std::ostringstream m_stream; }; class ScopedMessage { public: ScopedMessage( MessageBuilder const& builder ); ScopedMessage( ScopedMessage const& other ); ~ScopedMessage(); MessageInfo m_info; }; } // end namespace Catch // #included from: catch_interfaces_capture.h #define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED #include namespace Catch { class TestCase; class AssertionResult; struct AssertionInfo; struct SectionInfo; struct SectionEndInfo; struct MessageInfo; class ScopedMessageBuilder; struct Counts; struct IResultCapture { virtual ~IResultCapture(); virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; virtual void handleFatalErrorCondition( std::string const& message ) = 0; }; IResultCapture& getResultCapture(); } // #included from: catch_debugger.h #define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED // #included from: catch_platform.h #define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) # define CATCH_PLATFORM_MAC #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) # define CATCH_PLATFORM_IPHONE #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) # define CATCH_PLATFORM_WINDOWS # if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) # define CATCH_DEFINES_NOMINMAX # endif # if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) # define CATCH_DEFINES_WIN32_LEAN_AND_MEAN # endif #endif #include namespace Catch{ bool isDebuggerActive(); void writeToDebugConsole( std::string const& text ); } #ifdef CATCH_PLATFORM_MAC // The following code snippet based on: // http://cocoawithlove.com/2008/03/break-into-debugger.html #ifdef DEBUG #if defined(__ppc64__) || defined(__ppc__) #define CATCH_TRAP() \ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ : : : "memory","r0","r3","r4" ) #else #define CATCH_TRAP() _asm__("int $3\n" : : ) #endif #endif #elif defined(CATCH_PLATFORM_LINUX) // If we can use inline assembler, do it because this allows us to break // directly at the location of the failing check instead of breaking inside // raise() called from it, i.e. one stack frame below. #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) #define CATCH_TRAP() asm volatile ("int $3") #else // Fall back to the generic way. #include #define CATCH_TRAP() raise(SIGTRAP) #endif #elif defined(_MSC_VER) #define CATCH_TRAP() __debugbreak() #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); #define CATCH_TRAP() DebugBreak() #endif #ifdef CATCH_TRAP #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } #else #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); #endif // #included from: catch_interfaces_runner.h #define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED namespace Catch { class TestCase; struct IRunner { virtual ~IRunner(); virtual bool aborting() const = 0; }; } /////////////////////////////////////////////////////////////////////////////// // In the event of a failure works out if the debugger needs to be invoked // and/or an exception thrown and takes appropriate action. // This needs to be done as a macro so the debugger will stop in the user // source code rather than in Catch library code #define INTERNAL_CATCH_REACT( resultBuilder ) \ if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ resultBuilder.react(); /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ ( __catchResult <= expr ).endExpression(); \ } \ catch( ... ) { \ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse( sizeof(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ if( Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ if( !Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ __catchResult.captureExpectedException( matcher ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( exceptionType ) { \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #else #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << log + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #endif /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_INFO( log, macroName ) \ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ try { \ std::string matcherAsString = (matcher).toString(); \ __catchResult \ .setLhs( Catch::toString( arg ) ) \ .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ .setOp( "matches" ) \ .setResultType( (matcher).match( arg ) ); \ __catchResult.captureExpression(); \ } catch( ... ) { \ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) // #included from: internal/catch_section.h #define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED // #included from: catch_section_info.h #define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED // #included from: catch_totals.hpp #define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED #include namespace Catch { struct Counts { Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} Counts operator - ( Counts const& other ) const { Counts diff; diff.passed = passed - other.passed; diff.failed = failed - other.failed; diff.failedButOk = failedButOk - other.failedButOk; return diff; } Counts& operator += ( Counts const& other ) { passed += other.passed; failed += other.failed; failedButOk += other.failedButOk; return *this; } std::size_t total() const { return passed + failed + failedButOk; } bool allPassed() const { return failed == 0 && failedButOk == 0; } bool allOk() const { return failed == 0; } std::size_t passed; std::size_t failed; std::size_t failedButOk; }; struct Totals { Totals operator - ( Totals const& other ) const { Totals diff; diff.assertions = assertions - other.assertions; diff.testCases = testCases - other.testCases; return diff; } Totals delta( Totals const& prevTotals ) const { Totals diff = *this - prevTotals; if( diff.assertions.failed > 0 ) ++diff.testCases.failed; else if( diff.assertions.failedButOk > 0 ) ++diff.testCases.failedButOk; else ++diff.testCases.passed; return diff; } Totals& operator += ( Totals const& other ) { assertions += other.assertions; testCases += other.testCases; return *this; } Counts assertions; Counts testCases; }; } namespace Catch { struct SectionInfo { SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description = std::string() ); std::string name; std::string description; SourceLineInfo lineInfo; }; struct SectionEndInfo { SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) {} SectionInfo sectionInfo; Counts prevAssertions; double durationInSeconds; }; } // end namespace Catch // #included from: catch_timer.h #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED #ifdef CATCH_PLATFORM_WINDOWS typedef unsigned long long uint64_t; #else #include #endif namespace Catch { class Timer { public: Timer() : m_ticks( 0 ) {} void start(); unsigned int getElapsedMicroseconds() const; unsigned int getElapsedMilliseconds() const; double getElapsedSeconds() const; private: uint64_t m_ticks; }; } // namespace Catch #include namespace Catch { class Section : NonCopyable { public: Section( SectionInfo const& info ); ~Section(); // This indicates whether the section should be executed or not operator bool() const; private: SectionInfo m_info; std::string m_name; Counts m_assertions; bool m_sectionIncluded; Timer m_timer; }; } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_SECTION( ... ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) #else #define INTERNAL_CATCH_SECTION( name, desc ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) #endif // #included from: internal/catch_generators.hpp #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED #include #include #include #include namespace Catch { template struct IGenerator { virtual ~IGenerator() {} virtual T getValue( std::size_t index ) const = 0; virtual std::size_t size () const = 0; }; template class BetweenGenerator : public IGenerator { public: BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} virtual T getValue( std::size_t index ) const { return m_from+static_cast( index ); } virtual std::size_t size() const { return static_cast( 1+m_to-m_from ); } private: T m_from; T m_to; }; template class ValuesGenerator : public IGenerator { public: ValuesGenerator(){} void add( T value ) { m_values.push_back( value ); } virtual T getValue( std::size_t index ) const { return m_values[index]; } virtual std::size_t size() const { return m_values.size(); } private: std::vector m_values; }; template class CompositeGenerator { public: CompositeGenerator() : m_totalSize( 0 ) {} // *** Move semantics, similar to auto_ptr *** CompositeGenerator( CompositeGenerator& other ) : m_fileInfo( other.m_fileInfo ), m_totalSize( 0 ) { move( other ); } CompositeGenerator& setFileInfo( const char* fileInfo ) { m_fileInfo = fileInfo; return *this; } ~CompositeGenerator() { deleteAll( m_composed ); } operator T () const { size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); typename std::vector*>::const_iterator it = m_composed.begin(); typename std::vector*>::const_iterator itEnd = m_composed.end(); for( size_t index = 0; it != itEnd; ++it ) { const IGenerator* generator = *it; if( overallIndex >= index && overallIndex < index + generator->size() ) { return generator->getValue( overallIndex-index ); } index += generator->size(); } CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so } void add( const IGenerator* generator ) { m_totalSize += generator->size(); m_composed.push_back( generator ); } CompositeGenerator& then( CompositeGenerator& other ) { move( other ); return *this; } CompositeGenerator& then( T value ) { ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( value ); add( valuesGen ); return *this; } private: void move( CompositeGenerator& other ) { std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); m_totalSize += other.m_totalSize; other.m_composed.clear(); } std::vector*> m_composed; std::string m_fileInfo; size_t m_totalSize; }; namespace Generators { template CompositeGenerator between( T from, T to ) { CompositeGenerator generators; generators.add( new BetweenGenerator( from, to ) ); return generators; } template CompositeGenerator values( T val1, T val2 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3 ){ CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3, T val4 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); valuesGen->add( val4 ); generators.add( valuesGen ); return generators; } } // end namespace Generators using namespace Generators; } // end namespace Catch #define INTERNAL_CATCH_LINESTR2( line ) #line #define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) #define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) // #included from: internal/catch_interfaces_exception.h #define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED #include #include // #included from: catch_interfaces_registry_hub.h #define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED #include namespace Catch { class TestCase; struct ITestCaseRegistry; struct IExceptionTranslatorRegistry; struct IExceptionTranslator; struct IReporterRegistry; struct IReporterFactory; struct IRegistryHub { virtual ~IRegistryHub(); virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; }; struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; virtual void registerListener( Ptr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; }; IRegistryHub& getRegistryHub(); IMutableRegistryHub& getMutableRegistryHub(); void cleanUp(); std::string translateActiveException(); } namespace Catch { typedef std::string(*exceptionTranslateFunction)(); struct IExceptionTranslator; typedef std::vector ExceptionTranslators; struct IExceptionTranslator { virtual ~IExceptionTranslator(); virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; }; struct IExceptionTranslatorRegistry { virtual ~IExceptionTranslatorRegistry(); virtual std::string translateActiveException() const = 0; }; class ExceptionTranslatorRegistrar { template class ExceptionTranslator : public IExceptionTranslator { public: ExceptionTranslator( std::string(*translateFunction)( T& ) ) : m_translateFunction( translateFunction ) {} virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { try { if( it == itEnd ) throw; else return (*it)->translate( it+1, itEnd ); } catch( T& ex ) { return m_translateFunction( ex ); } } protected: std::string(*m_translateFunction)( T& ); }; public: template ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { getMutableRegistryHub().registerTranslator ( new ExceptionTranslator( translateFunction ) ); } }; } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ static std::string translatorName( signature ); \ namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ static std::string translatorName( signature ) #define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // #included from: internal/catch_approx.hpp #define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED #include #include namespace Catch { namespace Detail { class Approx { public: explicit Approx ( double value ) : m_epsilon( std::numeric_limits::epsilon()*100 ), m_scale( 1.0 ), m_value( value ) {} Approx( Approx const& other ) : m_epsilon( other.m_epsilon ), m_scale( other.m_scale ), m_value( other.m_value ) {} static Approx custom() { return Approx( 0 ); } Approx operator()( double value ) { Approx approx( value ); approx.epsilon( m_epsilon ); approx.scale( m_scale ); return approx; } friend bool operator == ( double lhs, Approx const& rhs ) { // Thanks to Richard Harris for his help refining this formula return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); } friend bool operator == ( Approx const& lhs, double rhs ) { return operator==( rhs, lhs ); } friend bool operator != ( double lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } friend bool operator != ( Approx const& lhs, double rhs ) { return !operator==( rhs, lhs ); } friend bool operator <= ( double lhs, Approx const& rhs ) { return lhs < rhs.m_value || lhs == rhs; } friend bool operator <= ( Approx const& lhs, double rhs ) { return lhs.m_value < rhs || lhs == rhs; } friend bool operator >= ( double lhs, Approx const& rhs ) { return lhs > rhs.m_value || lhs == rhs; } friend bool operator >= ( Approx const& lhs, double rhs ) { return lhs.m_value > rhs || lhs == rhs; } Approx& epsilon( double newEpsilon ) { m_epsilon = newEpsilon; return *this; } Approx& scale( double newScale ) { m_scale = newScale; return *this; } std::string toString() const { std::ostringstream oss; oss << "Approx( " << Catch::toString( m_value ) << " )"; return oss.str(); } private: double m_epsilon; double m_scale; double m_value; }; } template<> inline std::string toString( Detail::Approx const& value ) { return value.toString(); } } // end namespace Catch // #included from: internal/catch_interfaces_tag_alias_registry.h #define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED // #included from: catch_tag_alias.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED #include namespace Catch { struct TagAlias { TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} std::string tag; SourceLineInfo lineInfo; }; struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } // #included from: catch_option.hpp #define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED namespace Catch { // An optional type template class Option { public: Option() : nullableValue( CATCH_NULL ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) {} ~Option() { reset(); } Option& operator= ( Option const& _other ) { if( &_other != this ) { reset(); if( _other ) nullableValue = new( storage ) T( *_other ); } return *this; } Option& operator = ( T const& _value ) { reset(); nullableValue = new( storage ) T( _value ); return *this; } void reset() { if( nullableValue ) nullableValue->~T(); nullableValue = CATCH_NULL; } T& operator*() { return *nullableValue; } T const& operator*() const { return *nullableValue; } T* operator->() { return nullableValue; } const T* operator->() const { return nullableValue; } T valueOr( T const& defaultValue ) const { return nullableValue ? *nullableValue : defaultValue; } bool some() const { return nullableValue != CATCH_NULL; } bool none() const { return nullableValue == CATCH_NULL; } bool operator !() const { return nullableValue == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( some() ); } private: T* nullableValue; char storage[sizeof(T)]; }; } // end namespace Catch namespace Catch { struct ITagAliasRegistry { virtual ~ITagAliasRegistry(); virtual Option find( std::string const& alias ) const = 0; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; static ITagAliasRegistry const& get(); }; } // end namespace Catch // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections // #included from: internal/catch_test_case_info.h #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED #include #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { struct ITestCase; struct TestCaseInfo { enum SpecialProperties{ None = 0, IsHidden = 1 << 1, ShouldFail = 1 << 2, MayFail = 1 << 3, Throws = 1 << 4 }; TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ); TestCaseInfo( TestCaseInfo const& other ); friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); bool isHidden() const; bool throws() const; bool okToFail() const; bool expectedToFail() const; std::string name; std::string className; std::string description; std::set tags; std::set lcaseTags; std::string tagsAsString; SourceLineInfo lineInfo; SpecialProperties properties; }; class TestCase : public TestCaseInfo { public: TestCase( ITestCase* testCase, TestCaseInfo const& info ); TestCase( TestCase const& other ); TestCase withName( std::string const& _newName ) const; void invoke() const; TestCaseInfo const& getTestCaseInfo() const; void swap( TestCase& other ); bool operator == ( TestCase const& other ) const; bool operator < ( TestCase const& other ) const; TestCase& operator = ( TestCase const& other ); private: Ptr test; }; TestCase makeTestCase( ITestCase* testCase, std::string const& className, std::string const& name, std::string const& description, SourceLineInfo const& lineInfo ); } #ifdef __clang__ #pragma clang diagnostic pop #endif #ifdef __OBJC__ // #included from: internal/catch_objc.hpp #define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED #import #include // NB. Any general catch headers included here must be included // in catch.hpp first to make sure they are included by the single // header for non obj-usage /////////////////////////////////////////////////////////////////////////////// // This protocol is really only here for (self) documenting purposes, since // all its methods are optional. @protocol OcFixture @optional -(void) setUp; -(void) tearDown; @end namespace Catch { class OcMethod : public SharedImpl { public: OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} virtual void invoke() const { id obj = [[m_cls alloc] init]; performOptionalSelector( obj, @selector(setUp) ); performOptionalSelector( obj, m_sel ); performOptionalSelector( obj, @selector(tearDown) ); arcSafeRelease( obj ); } private: virtual ~OcMethod() {} Class m_cls; SEL m_sel; }; namespace Detail{ inline std::string getAnnotation( Class cls, std::string const& annotationName, std::string const& testCaseName ) { NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; SEL sel = NSSelectorFromString( selStr ); arcSafeRelease( selStr ); id value = performOptionalSelector( cls, sel ); if( value ) return [(NSString*)value UTF8String]; return ""; } } inline size_t registerTestMethods() { size_t noTestMethods = 0; int noClasses = objc_getClassList( CATCH_NULL, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); for( int c = 0; c < noClasses; c++ ) { Class cls = classes[c]; { u_int count; Method* methods = class_copyMethodList( cls, &count ); for( u_int m = 0; m < count ; m++ ) { SEL selector = method_getName(methods[m]); std::string methodName = sel_getName(selector); if( startsWith( methodName, "Catch_TestCase_" ) ) { std::string testCaseName = methodName.substr( 15 ); std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); const char* className = class_getName( cls ); getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); noTestMethods++; } } free(methods); } } return noTestMethods; } namespace Matchers { namespace Impl { namespace NSStringMatchers { template struct StringHolder : MatcherImpl{ StringHolder( NSString* substr ) : m_substr( [substr copy] ){} StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} StringHolder() { arcSafeRelease( m_substr ); } NSString* m_substr; }; struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } virtual std::string toString() const { return "equals string: " + Catch::toString( m_substr ); } }; struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } virtual std::string toString() const { return "contains string: " + Catch::toString( m_substr ); } }; struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } virtual std::string toString() const { return "starts with: " + Catch::toString( m_substr ); } }; struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } virtual std::string toString() const { return "ends with: " + Catch::toString( m_substr ); } }; } // namespace NSStringMatchers } // namespace Impl inline Impl::NSStringMatchers::Equals Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } inline Impl::NSStringMatchers::Contains Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } inline Impl::NSStringMatchers::StartsWith StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } inline Impl::NSStringMatchers::EndsWith EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } } // namespace Matchers using namespace Matchers; } // namespace Catch /////////////////////////////////////////////////////////////////////////////// #define OC_TEST_CASE( name, desc )\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ {\ return @ name; \ }\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ { \ return @ desc; \ } \ -(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) #endif #ifdef CATCH_IMPL // #included from: internal/catch_impl.hpp #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED // Collect all the implementation files together here // These are the equivalent of what would usually be cpp files #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wweak-vtables" #endif // #included from: ../catch_session.hpp #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED // #included from: internal/catch_commandline.hpp #define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED // #included from: catch_config.hpp #define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED // #included from: catch_test_spec_parser.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_test_spec.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_wildcard_pattern.hpp #define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED namespace Catch { class WildcardPattern { enum WildcardPosition { NoWildcard = 0, WildcardAtStart = 1, WildcardAtEnd = 2, WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd }; public: WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), m_wildcard( NoWildcard ), m_pattern( adjustCase( pattern ) ) { if( startsWith( m_pattern, "*" ) ) { m_pattern = m_pattern.substr( 1 ); m_wildcard = WildcardAtStart; } if( endsWith( m_pattern, "*" ) ) { m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); } } virtual ~WildcardPattern(); virtual bool matches( std::string const& str ) const { switch( m_wildcard ) { case NoWildcard: return m_pattern == adjustCase( str ); case WildcardAtStart: return endsWith( adjustCase( str ), m_pattern ); case WildcardAtEnd: return startsWith( adjustCase( str ), m_pattern ); case WildcardAtBothEnds: return contains( adjustCase( str ), m_pattern ); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" #endif throw std::logic_error( "Unknown enum" ); #ifdef __clang__ #pragma clang diagnostic pop #endif } private: std::string adjustCase( std::string const& str ) const { return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; } CaseSensitive::Choice m_caseSensitivity; WildcardPosition m_wildcard; std::string m_pattern; }; } #include #include namespace Catch { class TestSpec { struct Pattern : SharedImpl<> { virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; class NamePattern : public Pattern { public: NamePattern( std::string const& name ) : m_wildcardPattern( toLower( name ), CaseSensitive::No ) {} virtual ~NamePattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return m_wildcardPattern.matches( toLower( testCase.name ) ); } private: WildcardPattern m_wildcardPattern; }; class TagPattern : public Pattern { public: TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} virtual ~TagPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); } private: std::string m_tag; }; class ExcludedPattern : public Pattern { public: ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} virtual ~ExcludedPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } private: Ptr m_underlyingPattern; }; struct Filter { std::vector > m_patterns; bool matches( TestCaseInfo const& testCase ) const { // All patterns in a filter must match for the filter to be a match for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { if( !(*it)->matches( testCase ) ) return false; } return true; } }; public: bool hasFilters() const { return !m_filters.empty(); } bool matches( TestCaseInfo const& testCase ) const { // A TestSpec matches if any filter matches for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) if( it->matches( testCase ) ) return true; return false; } private: std::vector m_filters; friend class TestSpecParser; }; } #ifdef __clang__ #pragma clang diagnostic pop #endif namespace Catch { class TestSpecParser { enum Mode{ None, Name, QuotedName, Tag, EscapedName }; Mode m_mode; bool m_exclusion; std::size_t m_start, m_pos; std::string m_arg; std::vector m_escapeChars; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; ITagAliasRegistry const* m_tagAliases; public: TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} TestSpecParser& parse( std::string const& arg ) { m_mode = None; m_exclusion = false; m_start = std::string::npos; m_arg = m_tagAliases->expandAliases( arg ); m_escapeChars.clear(); for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) visitChar( m_arg[m_pos] ); if( m_mode == Name ) addPattern(); return *this; } TestSpec testSpec() { addFilter(); return m_testSpec; } private: void visitChar( char c ) { if( m_mode == None ) { switch( c ) { case ' ': return; case '~': m_exclusion = true; return; case '[': return startNewMode( Tag, ++m_pos ); case '"': return startNewMode( QuotedName, ++m_pos ); case '\\': return escape(); default: startNewMode( Name, m_pos ); break; } } if( m_mode == Name ) { if( c == ',' ) { addPattern(); addFilter(); } else if( c == '[' ) { if( subString() == "exclude:" ) m_exclusion = true; else addPattern(); startNewMode( Tag, ++m_pos ); } else if( c == '\\' ) escape(); } else if( m_mode == EscapedName ) m_mode = Name; else if( m_mode == QuotedName && c == '"' ) addPattern(); else if( m_mode == Tag && c == ']' ) addPattern(); } void startNewMode( Mode mode, std::size_t start ) { m_mode = mode; m_start = start; } void escape() { m_mode = EscapedName; m_escapeChars.push_back( m_pos ); } std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } template void addPattern() { std::string token = subString(); for( size_t i = 0; i < m_escapeChars.size(); ++i ) token = token.substr( 0, m_escapeChars[i] ) + token.substr( m_escapeChars[i]+1 ); m_escapeChars.clear(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); } if( !token.empty() ) { Ptr pattern = new T( token ); if( m_exclusion ) pattern = new TestSpec::ExcludedPattern( pattern ); m_currentFilter.m_patterns.push_back( pattern ); } m_exclusion = false; m_mode = None; } void addFilter() { if( !m_currentFilter.m_patterns.empty() ) { m_testSpec.m_filters.push_back( m_currentFilter ); m_currentFilter = TestSpec::Filter(); } } }; inline TestSpec parseTestSpec( std::string const& arg ) { return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_interfaces_config.h #define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED #include #include #include namespace Catch { struct Verbosity { enum Level { NoOutput = 0, Quiet, Normal }; }; struct WarnAbout { enum What { Nothing = 0x00, NoAssertions = 0x01 }; }; struct ShowDurations { enum OrNot { DefaultForReporter, Always, Never }; }; struct RunTests { enum InWhatOrder { InDeclarationOrder, InLexicographicalOrder, InRandomOrder }; }; struct UseColour { enum YesOrNo { Auto, Yes, No }; }; class TestSpec; struct IConfig : IShared { virtual ~IConfig(); virtual bool allowThrows() const = 0; virtual std::ostream& stream() const = 0; virtual std::string name() const = 0; virtual bool includeSuccessfulResults() const = 0; virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; virtual ShowDurations::OrNot showDurations() const = 0; virtual TestSpec const& testSpec() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; virtual UseColour::YesOrNo useColour() const = 0; }; } // #included from: catch_stream.h #define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED // #included from: catch_streambuf.h #define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED #include namespace Catch { class StreamBufBase : public std::streambuf { public: virtual ~StreamBufBase() CATCH_NOEXCEPT; }; } #include #include #include #include namespace Catch { std::ostream& cout(); std::ostream& cerr(); struct IStream { virtual ~IStream() CATCH_NOEXCEPT; virtual std::ostream& stream() const = 0; }; class FileStream : public IStream { mutable std::ofstream m_ofs; public: FileStream( std::string const& filename ); virtual ~FileStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; class CoutStream : public IStream { mutable std::ostream m_os; public: CoutStream(); virtual ~CoutStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; class DebugOutStream : public IStream { CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; mutable std::ostream m_os; public: DebugOutStream(); virtual ~DebugOutStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; } #include #include #include #include #include #ifndef CATCH_CONFIG_CONSOLE_WIDTH #define CATCH_CONFIG_CONSOLE_WIDTH 80 #endif namespace Catch { struct ConfigData { ConfigData() : listTests( false ), listTags( false ), listReporters( false ), listTestNamesOnly( false ), showSuccessfulTests( false ), shouldDebugBreak( false ), noThrow( false ), showHelp( false ), showInvisibles( false ), filenamesAsTags( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), warnings( WarnAbout::Nothing ), showDurations( ShowDurations::DefaultForReporter ), runOrder( RunTests::InDeclarationOrder ), useColour( UseColour::Auto ) {} bool listTests; bool listTags; bool listReporters; bool listTestNamesOnly; bool showSuccessfulTests; bool shouldDebugBreak; bool noThrow; bool showHelp; bool showInvisibles; bool filenamesAsTags; int abortAfter; unsigned int rngSeed; Verbosity::Level verbosity; WarnAbout::What warnings; ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; UseColour::YesOrNo useColour; std::string outputFilename; std::string name; std::string processName; std::vector reporterNames; std::vector testsOrTags; }; class Config : public SharedImpl { private: Config( Config const& other ); Config& operator = ( Config const& other ); virtual void dummy(); public: Config() {} Config( ConfigData const& data ) : m_data( data ), m_stream( openStream() ) { if( !data.testsOrTags.empty() ) { TestSpecParser parser( ITagAliasRegistry::get() ); for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) parser.parse( data.testsOrTags[i] ); m_testSpec = parser.testSpec(); } } virtual ~Config() { } std::string const& getFilename() const { return m_data.outputFilename ; } bool listTests() const { return m_data.listTests; } bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } bool listTags() const { return m_data.listTags; } bool listReporters() const { return m_data.listReporters; } std::string getProcessName() const { return m_data.processName; } bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } std::vector getReporterNames() const { return m_data.reporterNames; } int abortAfter() const { return m_data.abortAfter; } TestSpec const& testSpec() const { return m_testSpec; } bool showHelp() const { return m_data.showHelp; } bool showInvisibles() const { return m_data.showInvisibles; } // IConfig interface virtual bool allowThrows() const { return !m_data.noThrow; } virtual std::ostream& stream() const { return m_stream->stream(); } virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } virtual unsigned int rngSeed() const { return m_data.rngSeed; } virtual UseColour::YesOrNo useColour() const { return m_data.useColour; } private: IStream const* openStream() { if( m_data.outputFilename.empty() ) return new CoutStream(); else if( m_data.outputFilename[0] == '%' ) { if( m_data.outputFilename == "%debug" ) return new DebugOutStream(); else throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); } else return new FileStream( m_data.outputFilename ); } ConfigData m_data; CATCH_AUTO_PTR( IStream const ) m_stream; TestSpec m_testSpec; }; } // end namespace Catch // #included from: catch_clara.h #define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED // Use Catch's value for console width (store Clara's off to the side, if present) #ifdef CLARA_CONFIG_CONSOLE_WIDTH #define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH #undef CLARA_CONFIG_CONSOLE_WIDTH #endif #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH // Declare Clara inside the Catch namespace #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { // #included from: ../external/clara.h // Version 0.0.2.4 // Only use header guard if we are not using an outer namespace #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) #ifndef STITCH_CLARA_OPEN_NAMESPACE #define TWOBLUECUBES_CLARA_H_INCLUDED #define STITCH_CLARA_OPEN_NAMESPACE #define STITCH_CLARA_CLOSE_NAMESPACE #else #define STITCH_CLARA_CLOSE_NAMESPACE } #endif #define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE // ----------- #included from tbc_text_format.h ----------- // Only use header guard if we are not using an outer namespace #if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) #ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE #define TBC_TEXT_FORMAT_H_INCLUDED #endif #include #include #include #include // Use optional outer namespace #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TBC_TEXT_FORMAT_H_INCLUDED // ----------- end of #include from tbc_text_format.h ----------- // ........... back in clara.h #undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE // ----------- #included from clara_compilers.h ----------- #ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED #define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED // Detect a number of compiler features - mostly C++11/14 conformance - by compiler // The following features are defined: // // CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? // CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? // CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CLARA_CONFIG_CPP11_OVERRIDE : is override supported? // CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? // In general each macro has a _NO_ form // (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. // All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 #ifdef __clang__ #if __has_feature(cxx_nullptr) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif #if __has_feature(cxx_noexcept) #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ #if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #if (_MSC_VER >= 1600) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 #if defined(__cplusplus) && __cplusplus >= 201103L #define CLARA_CPP11_OR_GREATER #if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif #ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #endif #ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) #define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE #endif #if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) #define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured #if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_NULLPTR #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_NOEXCEPT #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_GENERATED_METHODS #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_OVERRIDE #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_UNIQUE_PTR #endif // noexcept support: #if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) #define CLARA_NOEXCEPT noexcept # define CLARA_NOEXCEPT_IS(x) noexcept(x) #else #define CLARA_NOEXCEPT throw() # define CLARA_NOEXCEPT_IS(x) #endif // nullptr support #ifdef CLARA_CONFIG_CPP11_NULLPTR #define CLARA_NULL nullptr #else #define CLARA_NULL NULL #endif // override support #ifdef CLARA_CONFIG_CPP11_OVERRIDE #define CLARA_OVERRIDE override #else #define CLARA_OVERRIDE #endif // unique_ptr support #ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR # define CLARA_AUTO_PTR( T ) std::unique_ptr #else # define CLARA_AUTO_PTR( T ) std::auto_ptr #endif #endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED // ----------- end of #include from clara_compilers.h ----------- // ........... back in clara.h #include #include #include #if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) #define CLARA_PLATFORM_WINDOWS #endif // Use optional outer namespace #ifdef STITCH_CLARA_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE #endif namespace Clara { struct UnpositionalTag {}; extern UnpositionalTag _; #ifdef CLARA_CONFIG_MAIN UnpositionalTag _; #endif namespace Detail { #ifdef CLARA_CONSOLE_WIDTH const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif using namespace Tbc; inline bool startsWith( std::string const& str, std::string const& prefix ) { return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; } template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct IsBool { static const bool value = false; }; template<> struct IsBool { static const bool value = true; }; template void convertInto( std::string const& _source, T& _dest ) { std::stringstream ss; ss << _source; ss >> _dest; if( ss.fail() ) throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); } inline void convertInto( std::string const& _source, std::string& _dest ) { _dest = _source; } char toLowerCh(char c) { return static_cast( ::tolower( c ) ); } inline void convertInto( std::string const& _source, bool& _dest ) { std::string sourceLC = _source; std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) _dest = true; else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) _dest = false; else throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); } template struct IArgFunction { virtual ~IArgFunction() {} #ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS IArgFunction() = default; IArgFunction( IArgFunction const& ) = default; #endif virtual void set( ConfigT& config, std::string const& value ) const = 0; virtual bool takesArg() const = 0; virtual IArgFunction* clone() const = 0; }; template class BoundArgFunction { public: BoundArgFunction() : functionObj( CLARA_NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; delete functionObj; functionObj = newFunctionObj; return *this; } ~BoundArgFunction() { delete functionObj; } void set( ConfigT& config, std::string const& value ) const { functionObj->set( config, value ); } bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { return functionObj != CLARA_NULL; } private: IArgFunction* functionObj; }; template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; template struct BoundDataMember : IArgFunction{ BoundDataMember( M C::* _member ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { convertInto( stringValue, p.*member ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; }; template struct BoundUnaryMethod : IArgFunction{ BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); (p.*member)( value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); }; template struct BoundNullaryMethod : IArgFunction{ BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) (p.*member)(); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); }; template struct BoundUnaryFunction : IArgFunction{ BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) function( obj ); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); }; template struct BoundBinaryFunction : IArgFunction{ BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); function( obj, value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } void (*function)( C&, T ); }; } // namespace Detail inline std::vector argsToVector( int argc, char const* const* const argv ) { std::vector args( static_cast( argc ) ); for( std::size_t i = 0; i < static_cast( argc ); ++i ) args[i] = argv[i]; return args; } class Parser { enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; Mode mode; std::size_t from; bool inQuotes; public: struct Token { enum Type { Positional, ShortOpt, LongOpt }; Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} Type type; std::string data; }; Parser() : mode( None ), from( 0 ), inQuotes( false ){} void parseIntoTokens( std::vector const& args, std::vector& tokens ) { const std::string doubleDash = "--"; for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) parseIntoTokens( args[i], tokens); } void parseIntoTokens( std::string const& arg, std::vector& tokens ) { for( std::size_t i = 0; i <= arg.size(); ++i ) { char c = arg[i]; if( c == '"' ) inQuotes = !inQuotes; mode = handleMode( i, c, arg, tokens ); } } Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { switch( mode ) { case None: return handleNone( i, c ); case MaybeShortOpt: return handleMaybeShortOpt( i, c ); case ShortOpt: case LongOpt: case SlashOpt: return handleOpt( i, c, arg, tokens ); case Positional: return handlePositional( i, c, arg, tokens ); default: throw std::logic_error( "Unknown mode" ); } } Mode handleNone( std::size_t i, char c ) { if( inQuotes ) { from = i; return Positional; } switch( c ) { case '-': return MaybeShortOpt; #ifdef CLARA_PLATFORM_WINDOWS case '/': from = i+1; return SlashOpt; #endif default: from = i; return Positional; } } Mode handleMaybeShortOpt( std::size_t i, char c ) { switch( c ) { case '-': from = i+1; return LongOpt; default: from = i; return ShortOpt; } } Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) return mode; std::string optName = arg.substr( from, i-from ); if( mode == ShortOpt ) for( std::size_t j = 0; j < optName.size(); ++j ) tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); else if( mode == SlashOpt && optName.size() == 1 ) tokens.push_back( Token( Token::ShortOpt, optName ) ); else tokens.push_back( Token( Token::LongOpt, optName ) ); return None; } Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) return mode; std::string data = arg.substr( from, i-from ); tokens.push_back( Token( Token::Positional, data ) ); return None; } }; template struct CommonArgProperties { CommonArgProperties() {} CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} Detail::BoundArgFunction boundField; std::string description; std::string detail; std::string placeholder; // Only value if boundField takes an arg bool takesArg() const { return !placeholder.empty(); } void validate() const { if( !boundField.isSet() ) throw std::logic_error( "option not bound" ); } }; struct OptionArgProperties { std::vector shortNames; std::string longName; bool hasShortName( std::string const& shortName ) const { return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); } bool hasLongName( std::string const& _longName ) const { return _longName == longName; } }; struct PositionalArgProperties { PositionalArgProperties() : position( -1 ) {} int position; // -1 means non-positional (floating) bool isFixedPositional() const { return position != -1; } }; template class CommandLine { struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { Arg() {} Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} using CommonArgProperties::placeholder; // !TBD std::string dbgName() const { if( !longName.empty() ) return "--" + longName; if( !shortNames.empty() ) return "-" + shortNames[0]; return "positional args"; } std::string commands() const { std::ostringstream oss; bool first = true; std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); for(; it != itEnd; ++it ) { if( first ) first = false; else oss << ", "; oss << "-" << *it; } if( !longName.empty() ) { if( !first ) oss << ", "; oss << "--" << longName; } if( !placeholder.empty() ) oss << " <" << placeholder << ">"; return oss.str(); } }; typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; friend void addOptName( Arg& arg, std::string const& optName ) { if( optName.empty() ) return; if( Detail::startsWith( optName, "--" ) ) { if( !arg.longName.empty() ) throw std::logic_error( "Only one long opt may be specified. '" + arg.longName + "' already specified, now attempting to add '" + optName + "'" ); arg.longName = optName.substr( 2 ); } else if( Detail::startsWith( optName, "-" ) ) arg.shortNames.push_back( optName.substr( 1 ) ); else throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); } friend void setPositionalArg( Arg& arg, int position ) { arg.position = position; } class ArgBuilder { public: ArgBuilder( Arg* arg ) : m_arg( arg ) {} // Bind a non-boolean data member (requires placeholder string) template void bind( M C::* field, std::string const& placeholder ) { m_arg->boundField = new Detail::BoundDataMember( field ); m_arg->placeholder = placeholder; } // Bind a boolean data member (no placeholder required) template void bind( bool C::* field ) { m_arg->boundField = new Detail::BoundDataMember( field ); } // Bind a method taking a single, non-boolean argument (requires a placeholder string) template void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); m_arg->placeholder = placeholder; } // Bind a method taking a single, boolean argument (no placeholder string required) template void bind( void (C::* unaryMethod)( bool ) ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); } // Bind a method that takes no arguments (will be called if opt is present) template void bind( void (C::* nullaryMethod)() ) { m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); } // Bind a free function taking a single argument - the object to operate on (no placeholder string required) template void bind( void (* unaryFunction)( C& ) ) { m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); } // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) template void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); m_arg->placeholder = placeholder; } ArgBuilder& describe( std::string const& description ) { m_arg->description = description; return *this; } ArgBuilder& detail( std::string const& detail ) { m_arg->detail = detail; return *this; } protected: Arg* m_arg; }; class OptBuilder : public ArgBuilder { public: OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} OptBuilder& operator[]( std::string const& optName ) { addOptName( *ArgBuilder::m_arg, optName ); return *this; } }; public: CommandLine() : m_boundProcessName( new Detail::NullBinder() ), m_highestSpecifiedArgPosition( 0 ), m_throwOnUnrecognisedTokens( false ) {} CommandLine( CommandLine const& other ) : m_boundProcessName( other.m_boundProcessName ), m_options ( other.m_options ), m_positionalArgs( other.m_positionalArgs ), m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) { if( other.m_floatingArg.get() ) m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); } CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { m_throwOnUnrecognisedTokens = shouldThrow; return *this; } OptBuilder operator[]( std::string const& optName ) { m_options.push_back( Arg() ); addOptName( m_options.back(), optName ); OptBuilder builder( &m_options.back() ); return builder; } ArgBuilder operator[]( int position ) { m_positionalArgs.insert( std::make_pair( position, Arg() ) ); if( position > m_highestSpecifiedArgPosition ) m_highestSpecifiedArgPosition = position; setPositionalArg( m_positionalArgs[position], position ); ArgBuilder builder( &m_positionalArgs[position] ); return builder; } // Invoke this with the _ instance ArgBuilder operator[]( UnpositionalTag ) { if( m_floatingArg.get() ) throw std::logic_error( "Only one unpositional argument can be added" ); m_floatingArg.reset( new Arg() ); ArgBuilder builder( m_floatingArg.get() ); return builder; } template void bindProcessName( M C::* field ) { m_boundProcessName = new Detail::BoundDataMember( field ); } template void bindProcessName( void (C::*_unaryMethod)( M ) ) { m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); } void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; std::size_t maxWidth = 0; for( it = itBegin; it != itEnd; ++it ) maxWidth = (std::max)( maxWidth, it->commands().size() ); for( it = itBegin; it != itEnd; ++it ) { Detail::Text usage( it->commands(), Detail::TextAttributes() .setWidth( maxWidth+indent ) .setIndent( indent ) ); Detail::Text desc( it->description, Detail::TextAttributes() .setWidth( width - maxWidth - 3 ) ); for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { std::string usageCol = i < usage.size() ? usage[i] : ""; os << usageCol; if( i < desc.size() && !desc[i].empty() ) os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) << desc[i]; os << "\n"; } } } std::string optUsage() const { std::ostringstream oss; optUsage( oss ); return oss.str(); } void argSynopsis( std::ostream& os ) const { for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { if( i > 1 ) os << " "; typename std::map::const_iterator it = m_positionalArgs.find( i ); if( it != m_positionalArgs.end() ) os << "<" << it->second.placeholder << ">"; else if( m_floatingArg.get() ) os << "<" << m_floatingArg->placeholder << ">"; else throw std::logic_error( "non consecutive positional arguments with no floating args" ); } // !TBD No indication of mandatory args if( m_floatingArg.get() ) { if( m_highestSpecifiedArgPosition > 1 ) os << " "; os << "[<" << m_floatingArg->placeholder << "> ...]"; } } std::string argSynopsis() const { std::ostringstream oss; argSynopsis( oss ); return oss.str(); } void usage( std::ostream& os, std::string const& procName ) const { validate(); os << "usage:\n " << procName << " "; argSynopsis( os ); if( !m_options.empty() ) { os << " [options]\n\nwhere options are: \n"; optUsage( os, 2 ); } os << "\n"; } std::string usage( std::string const& procName ) const { std::ostringstream oss; usage( oss, procName ); return oss.str(); } ConfigT parse( std::vector const& args ) const { ConfigT config; parseInto( args, config ); return config; } std::vector parseInto( std::vector const& args, ConfigT& config ) const { std::string processName = args[0]; std::size_t lastSlash = processName.find_last_of( "/\\" ); if( lastSlash != std::string::npos ) processName = processName.substr( lastSlash+1 ); m_boundProcessName.set( config, processName ); std::vector tokens; Parser parser; parser.parseIntoTokens( args, tokens ); return populate( tokens, config ); } std::vector populate( std::vector const& tokens, ConfigT& config ) const { validate(); std::vector unusedTokens = populateOptions( tokens, config ); unusedTokens = populateFixedArgs( unusedTokens, config ); unusedTokens = populateFloatingArgs( unusedTokens, config ); return unusedTokens; } std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; std::vector errors; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); for(; it != itEnd; ++it ) { Arg const& arg = *it; try { if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { if( arg.takesArg() ) { if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) errors.push_back( "Expected argument to option: " + token.data ); else arg.boundField.set( config, tokens[++i].data ); } else { arg.boundField.set( config, "true" ); } break; } } catch( std::exception& ex ) { errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); } } if( it == itEnd ) { if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) unusedTokens.push_back( token ); else if( errors.empty() && m_throwOnUnrecognisedTokens ) errors.push_back( "unrecognised option: " + token.data ); } } if( !errors.empty() ) { std::ostringstream oss; for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); it != itEnd; ++it ) { if( it != errors.begin() ) oss << "\n"; oss << *it; } throw std::runtime_error( oss.str() ); } return unusedTokens; } std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; int position = 1; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::map::const_iterator it = m_positionalArgs.find( position ); if( it != m_positionalArgs.end() ) it->second.boundField.set( config, token.data ); else unusedTokens.push_back( token ); if( token.type == Parser::Token::Positional ) position++; } return unusedTokens; } std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { if( !m_floatingArg.get() ) return tokens; std::vector unusedTokens; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; if( token.type == Parser::Token::Positional ) m_floatingArg->boundField.set( config, token.data ); else unusedTokens.push_back( token ); } return unusedTokens; } void validate() const { if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) throw std::logic_error( "No options or arguments specified" ); for( typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); it != itEnd; ++it ) it->validate(); } private: Detail::BoundArgFunction m_boundProcessName; std::vector m_options; std::map m_positionalArgs; ArgAutoPtr m_floatingArg; int m_highestSpecifiedArgPosition; bool m_throwOnUnrecognisedTokens; }; } // end namespace Clara STITCH_CLARA_CLOSE_NAMESPACE #undef STITCH_CLARA_OPEN_NAMESPACE #undef STITCH_CLARA_CLOSE_NAMESPACE #endif // TWOBLUECUBES_CLARA_H_INCLUDED #undef STITCH_CLARA_OPEN_NAMESPACE // Restore Clara's value for console width, if present #ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #endif #include namespace Catch { inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } inline void abortAfterX( ConfigData& config, int x ) { if( x < 1 ) throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); config.abortAfter = x; } inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); else throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); } inline void setOrder( ConfigData& config, std::string const& order ) { if( startsWith( "declared", order ) ) config.runOrder = RunTests::InDeclarationOrder; else if( startsWith( "lexical", order ) ) config.runOrder = RunTests::InLexicographicalOrder; else if( startsWith( "random", order ) ) config.runOrder = RunTests::InRandomOrder; else throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); } inline void setRngSeed( ConfigData& config, std::string const& seed ) { if( seed == "time" ) { config.rngSeed = static_cast( std::time(0) ); } else { std::stringstream ss; ss << seed; ss >> config.rngSeed; if( ss.fail() ) throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); } } inline void setVerbosity( ConfigData& config, int level ) { // !TBD: accept strings? config.verbosity = static_cast( level ); } inline void setShowDurations( ConfigData& config, bool _showDurations ) { config.showDurations = _showDurations ? ShowDurations::Always : ShowDurations::Never; } inline void setUseColour( ConfigData& config, std::string const& value ) { std::string mode = toLower( value ); if( mode == "yes" ) config.useColour = UseColour::Yes; else if( mode == "no" ) config.useColour = UseColour::No; else if( mode == "auto" ) config.useColour = UseColour::Auto; else throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); } inline void forceColour( ConfigData& config ) { config.useColour = UseColour::Yes; } inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { std::ifstream f( _filename.c_str() ); if( !f.is_open() ) throw std::domain_error( "Unable to load input file: " + _filename ); std::string line; while( std::getline( f, line ) ) { line = trim(line); if( !line.empty() && !startsWith( line, "#" ) ) { if( !startsWith( line, "\"" ) ) line = "\"" + line + "\""; addTestOrTags( config, line + "," ); } } } inline Clara::CommandLine makeCommandLineParser() { using namespace Clara; CommandLine cli; cli.bindProcessName( &ConfigData::processName ); cli["-?"]["-h"]["--help"] .describe( "display usage information" ) .bind( &ConfigData::showHelp ); cli["-l"]["--list-tests"] .describe( "list all/matching test cases" ) .bind( &ConfigData::listTests ); cli["-t"]["--list-tags"] .describe( "list all/matching tags" ) .bind( &ConfigData::listTags ); cli["-s"]["--success"] .describe( "include successful tests in output" ) .bind( &ConfigData::showSuccessfulTests ); cli["-b"]["--break"] .describe( "break into debugger on failure" ) .bind( &ConfigData::shouldDebugBreak ); cli["-e"]["--nothrow"] .describe( "skip exception tests" ) .bind( &ConfigData::noThrow ); cli["-i"]["--invisibles"] .describe( "show invisibles (tabs, newlines)" ) .bind( &ConfigData::showInvisibles ); cli["-o"]["--out"] .describe( "output filename" ) .bind( &ConfigData::outputFilename, "filename" ); cli["-r"]["--reporter"] // .placeholder( "name[:filename]" ) .describe( "reporter to use (defaults to console)" ) .bind( &addReporterName, "name" ); cli["-n"]["--name"] .describe( "suite name" ) .bind( &ConfigData::name, "name" ); cli["-a"]["--abort"] .describe( "abort at first failure" ) .bind( &abortAfterFirst ); cli["-x"]["--abortx"] .describe( "abort after x failures" ) .bind( &abortAfterX, "no. failures" ); cli["-w"]["--warn"] .describe( "enable warnings" ) .bind( &addWarning, "warning name" ); // - needs updating if reinstated // cli.into( &setVerbosity ) // .describe( "level of verbosity (0=no output)" ) // .shortOpt( "v") // .longOpt( "verbosity" ) // .placeholder( "level" ); cli[_] .describe( "which test or tests to use" ) .bind( &addTestOrTags, "test name, pattern or tags" ); cli["-d"]["--durations"] .describe( "show test durations" ) .bind( &setShowDurations, "yes|no" ); cli["-f"]["--input-file"] .describe( "load test names to run from a file" ) .bind( &loadTestNamesFromFile, "filename" ); cli["-#"]["--filenames-as-tags"] .describe( "adds a tag for the filename" ) .bind( &ConfigData::filenamesAsTags ); // Less common commands which don't have a short form cli["--list-test-names-only"] .describe( "list all/matching test cases names only" ) .bind( &ConfigData::listTestNamesOnly ); cli["--list-reporters"] .describe( "list all reporters" ) .bind( &ConfigData::listReporters ); cli["--order"] .describe( "test case order (defaults to decl)" ) .bind( &setOrder, "decl|lex|rand" ); cli["--rng-seed"] .describe( "set a specific seed for random numbers" ) .bind( &setRngSeed, "'time'|number" ); cli["--force-colour"] .describe( "force colourised output (deprecated)" ) .bind( &forceColour ); cli["--use-colour"] .describe( "should output be colourised" ) .bind( &setUseColour, "yes|no" ); return cli; } } // end namespace Catch // #included from: internal/catch_list.hpp #define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED // #included from: catch_text.h #define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED #define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH #define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch // #included from: ../external/tbc_text_format.h // Only use header guard if we are not using an outer namespace #ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE # ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # endif # else # define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # endif #endif #ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #include #include #include // Use optional outer namespace #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace Catch { using Tbc::Text; using Tbc::TextAttributes; } // #included from: catch_console_colour.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED namespace Catch { struct Colour { enum Code { None = 0, White, Red, Green, Blue, Cyan, Yellow, Grey, Bright = 0x10, BrightRed = Bright | Red, BrightGreen = Bright | Green, LightGrey = Bright | Grey, BrightWhite = Bright | White, // By intention FileName = LightGrey, Warning = Yellow, ResultError = BrightRed, ResultSuccess = BrightGreen, ResultExpectedFailure = Warning, Error = BrightRed, Success = Green, OriginalExpression = Cyan, ReconstructedExpression = Yellow, SecondaryText = LightGrey, Headers = White }; // Use constructed object for RAII guard Colour( Code _colourCode ); Colour( Colour const& other ); ~Colour(); // Use static method for one-shot changes static void use( Code _colourCode ); private: bool m_moved; }; inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } } // end namespace Catch // #included from: catch_interfaces_reporter.h #define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED #include #include #include #include namespace Catch { struct ReporterConfig { explicit ReporterConfig( Ptr const& _fullConfig ) : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} std::ostream& stream() const { return *m_stream; } Ptr fullConfig() const { return m_fullConfig; } private: std::ostream* m_stream; Ptr m_fullConfig; }; struct ReporterPreferences { ReporterPreferences() : shouldRedirectStdOut( false ) {} bool shouldRedirectStdOut; }; template struct LazyStat : Option { LazyStat() : used( false ) {} LazyStat& operator=( T const& _value ) { Option::operator=( _value ); used = false; return *this; } void reset() { Option::reset(); used = false; } bool used; }; struct TestRunInfo { TestRunInfo( std::string const& _name ) : name( _name ) {} std::string name; }; struct GroupInfo { GroupInfo( std::string const& _name, std::size_t _groupIndex, std::size_t _groupsCount ) : name( _name ), groupIndex( _groupIndex ), groupsCounts( _groupsCount ) {} std::string name; std::size_t groupIndex; std::size_t groupsCounts; }; struct AssertionStats { AssertionStats( AssertionResult const& _assertionResult, std::vector const& _infoMessages, Totals const& _totals ) : assertionResult( _assertionResult ), infoMessages( _infoMessages ), totals( _totals ) { if( assertionResult.hasMessage() ) { // Copy message into messages list. // !TBD This should have been done earlier, somewhere MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); builder << assertionResult.getMessage(); builder.m_info.message = builder.m_stream.str(); infoMessages.push_back( builder.m_info ); } } virtual ~AssertionStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionStats( AssertionStats const& ) = default; AssertionStats( AssertionStats && ) = default; AssertionStats& operator = ( AssertionStats const& ) = default; AssertionStats& operator = ( AssertionStats && ) = default; # endif AssertionResult assertionResult; std::vector infoMessages; Totals totals; }; struct SectionStats { SectionStats( SectionInfo const& _sectionInfo, Counts const& _assertions, double _durationInSeconds, bool _missingAssertions ) : sectionInfo( _sectionInfo ), assertions( _assertions ), durationInSeconds( _durationInSeconds ), missingAssertions( _missingAssertions ) {} virtual ~SectionStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SectionStats( SectionStats const& ) = default; SectionStats( SectionStats && ) = default; SectionStats& operator = ( SectionStats const& ) = default; SectionStats& operator = ( SectionStats && ) = default; # endif SectionInfo sectionInfo; Counts assertions; double durationInSeconds; bool missingAssertions; }; struct TestCaseStats { TestCaseStats( TestCaseInfo const& _testInfo, Totals const& _totals, std::string const& _stdOut, std::string const& _stdErr, bool _aborting ) : testInfo( _testInfo ), totals( _totals ), stdOut( _stdOut ), stdErr( _stdErr ), aborting( _aborting ) {} virtual ~TestCaseStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestCaseStats( TestCaseStats const& ) = default; TestCaseStats( TestCaseStats && ) = default; TestCaseStats& operator = ( TestCaseStats const& ) = default; TestCaseStats& operator = ( TestCaseStats && ) = default; # endif TestCaseInfo testInfo; Totals totals; std::string stdOut; std::string stdErr; bool aborting; }; struct TestGroupStats { TestGroupStats( GroupInfo const& _groupInfo, Totals const& _totals, bool _aborting ) : groupInfo( _groupInfo ), totals( _totals ), aborting( _aborting ) {} TestGroupStats( GroupInfo const& _groupInfo ) : groupInfo( _groupInfo ), aborting( false ) {} virtual ~TestGroupStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestGroupStats( TestGroupStats const& ) = default; TestGroupStats( TestGroupStats && ) = default; TestGroupStats& operator = ( TestGroupStats const& ) = default; TestGroupStats& operator = ( TestGroupStats && ) = default; # endif GroupInfo groupInfo; Totals totals; bool aborting; }; struct TestRunStats { TestRunStats( TestRunInfo const& _runInfo, Totals const& _totals, bool _aborting ) : runInfo( _runInfo ), totals( _totals ), aborting( _aborting ) {} virtual ~TestRunStats(); # ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS TestRunStats( TestRunStats const& _other ) : runInfo( _other.runInfo ), totals( _other.totals ), aborting( _other.aborting ) {} # else TestRunStats( TestRunStats const& ) = default; TestRunStats( TestRunStats && ) = default; TestRunStats& operator = ( TestRunStats const& ) = default; TestRunStats& operator = ( TestRunStats && ) = default; # endif TestRunInfo runInfo; Totals totals; bool aborting; }; class MultipleReporters; struct IStreamingReporter : IShared { virtual ~IStreamingReporter(); // Implementing class must also provide the following static method: // static std::string getDescription(); virtual ReporterPreferences getPreferences() const = 0; virtual void noMatchingTestCases( std::string const& spec ) = 0; virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; virtual void skipTest( TestCaseInfo const& testInfo ) = 0; virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } }; struct IReporterFactory : IShared { virtual ~IReporterFactory(); virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; virtual std::string getDescription() const = 0; }; struct IReporterRegistry { typedef std::map > FactoryMap; typedef std::vector > Listeners; virtual ~IReporterRegistry(); virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; virtual Listeners const& getListeners() const = 0; }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); } #include #include namespace Catch { inline std::size_t listTests( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Matching test cases:\n"; else { Catch::cout() << "All available test cases:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::size_t matchedTests = 0; TextAttributes nameAttr, tagsAttr; nameAttr.setInitialIndent( 2 ).setIndent( 4 ); tagsAttr.setIndent( 6 ); std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); Colour::Code colour = testCaseInfo.isHidden() ? Colour::SecondaryText : Colour::None; Colour colourGuard( colour ); Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; if( !testCaseInfo.tags.empty() ) Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; } if( !config.testSpec().hasFilters() ) Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; else Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; return matchedTests; } inline std::size_t listTestsNamesOnly( Config const& config ) { TestSpec testSpec = config.testSpec(); if( !config.testSpec().hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); if( startsWith( testCaseInfo.name, "#" ) ) Catch::cout() << "\"" << testCaseInfo.name << "\"" << std::endl; else Catch::cout() << testCaseInfo.name << std::endl; } return matchedTests; } struct TagInfo { TagInfo() : count ( 0 ) {} void add( std::string const& spelling ) { ++count; spellings.insert( spelling ); } std::string all() const { std::string out; for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); it != itEnd; ++it ) out += "[" + *it + "]"; return out; } std::set spellings; std::size_t count; }; inline std::size_t listTags( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Tags for matching test cases:\n"; else { Catch::cout() << "All available tags:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::map tagCounts; std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), tagItEnd = it->getTestCaseInfo().tags.end(); tagIt != tagItEnd; ++tagIt ) { std::string tagName = *tagIt; std::string lcaseTagName = toLower( tagName ); std::map::iterator countIt = tagCounts.find( lcaseTagName ); if( countIt == tagCounts.end() ) countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; countIt->second.add( tagName ); } } for( std::map::const_iterator countIt = tagCounts.begin(), countItEnd = tagCounts.end(); countIt != countItEnd; ++countIt ) { std::ostringstream oss; oss << " " << std::setw(2) << countIt->second.count << " "; Text wrapper( countIt->second.all(), TextAttributes() .setInitialIndent( 0 ) .setIndent( oss.str().size() ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); Catch::cout() << oss.str() << wrapper << "\n"; } Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; return tagCounts.size(); } inline std::size_t listReporters( Config const& /*config*/ ) { Catch::cout() << "Available reporters:\n"; IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; std::size_t maxNameLen = 0; for(it = itBegin; it != itEnd; ++it ) maxNameLen = (std::max)( maxNameLen, it->first.size() ); for(it = itBegin; it != itEnd; ++it ) { Text wrapper( it->second->getDescription(), TextAttributes() .setInitialIndent( 0 ) .setIndent( 7+maxNameLen ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); Catch::cout() << " " << it->first << ":" << std::string( maxNameLen - it->first.size() + 2, ' ' ) << wrapper << "\n"; } Catch::cout() << std::endl; return factories.size(); } inline Option list( Config const& config ) { Option listedCount; if( config.listTests() ) listedCount = listedCount.valueOr(0) + listTests( config ); if( config.listTestNamesOnly() ) listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); if( config.listTags() ) listedCount = listedCount.valueOr(0) + listTags( config ); if( config.listReporters() ) listedCount = listedCount.valueOr(0) + listReporters( config ); return listedCount; } } // end namespace Catch // #included from: internal/catch_run_context.hpp #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED // #included from: catch_test_case_tracker.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED #include #include #include #include namespace Catch { namespace TestCaseTracking { struct ITracker : SharedImpl<> { virtual ~ITracker(); // static queries virtual std::string name() const = 0; // dynamic queries virtual bool isComplete() const = 0; // Successfully completed or failed virtual bool isSuccessfullyCompleted() const = 0; virtual bool isOpen() const = 0; // Started but not complete virtual bool hasChildren() const = 0; virtual ITracker& parent() = 0; // actions virtual void close() = 0; // Successfully complete virtual void fail() = 0; virtual void markAsNeedingAnotherRun() = 0; virtual void addChild( Ptr const& child ) = 0; virtual ITracker* findChild( std::string const& name ) = 0; virtual void openChild() = 0; // Debug/ checking virtual bool isSectionTracker() const = 0; virtual bool isIndexTracker() const = 0; }; class TrackerContext { enum RunState { NotStarted, Executing, CompletedCycle }; Ptr m_rootTracker; ITracker* m_currentTracker; RunState m_runState; public: static TrackerContext& instance() { static TrackerContext s_instance; return s_instance; } TrackerContext() : m_currentTracker( CATCH_NULL ), m_runState( NotStarted ) {} ITracker& startRun(); void endRun() { m_rootTracker.reset(); m_currentTracker = CATCH_NULL; m_runState = NotStarted; } void startCycle() { m_currentTracker = m_rootTracker.get(); m_runState = Executing; } void completeCycle() { m_runState = CompletedCycle; } bool completedCycle() const { return m_runState == CompletedCycle; } ITracker& currentTracker() { return *m_currentTracker; } void setCurrentTracker( ITracker* tracker ) { m_currentTracker = tracker; } }; class TrackerBase : public ITracker { protected: enum CycleState { NotStarted, Executing, ExecutingChildren, NeedsAnotherRun, CompletedSuccessfully, Failed }; class TrackerHasName { std::string m_name; public: TrackerHasName( std::string const& name ) : m_name( name ) {} bool operator ()( Ptr const& tracker ) { return tracker->name() == m_name; } }; typedef std::vector > Children; std::string m_name; TrackerContext& m_ctx; ITracker* m_parent; Children m_children; CycleState m_runState; public: TrackerBase( std::string const& name, TrackerContext& ctx, ITracker* parent ) : m_name( name ), m_ctx( ctx ), m_parent( parent ), m_runState( NotStarted ) {} virtual ~TrackerBase(); virtual std::string name() const CATCH_OVERRIDE { return m_name; } virtual bool isComplete() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully || m_runState == Failed; } virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully; } virtual bool isOpen() const CATCH_OVERRIDE { return m_runState != NotStarted && !isComplete(); } virtual bool hasChildren() const CATCH_OVERRIDE { return !m_children.empty(); } virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { m_children.push_back( child ); } virtual ITracker* findChild( std::string const& name ) CATCH_OVERRIDE { Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) ); return( it != m_children.end() ) ? it->get() : CATCH_NULL; } virtual ITracker& parent() CATCH_OVERRIDE { assert( m_parent ); // Should always be non-null except for root return *m_parent; } virtual void openChild() CATCH_OVERRIDE { if( m_runState != ExecutingChildren ) { m_runState = ExecutingChildren; if( m_parent ) m_parent->openChild(); } } virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } void open() { m_runState = Executing; moveToThis(); if( m_parent ) m_parent->openChild(); } virtual void close() CATCH_OVERRIDE { // Close any still open children (e.g. generators) while( &m_ctx.currentTracker() != this ) m_ctx.currentTracker().close(); switch( m_runState ) { case NotStarted: case CompletedSuccessfully: case Failed: throw std::logic_error( "Illogical state" ); case NeedsAnotherRun: break;; case Executing: m_runState = CompletedSuccessfully; break; case ExecutingChildren: if( m_children.empty() || m_children.back()->isComplete() ) m_runState = CompletedSuccessfully; break; default: throw std::logic_error( "Unexpected state" ); } moveToParent(); m_ctx.completeCycle(); } virtual void fail() CATCH_OVERRIDE { m_runState = Failed; if( m_parent ) m_parent->markAsNeedingAnotherRun(); moveToParent(); m_ctx.completeCycle(); } virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { m_runState = NeedsAnotherRun; } private: void moveToParent() { assert( m_parent ); m_ctx.setCurrentTracker( m_parent ); } void moveToThis() { m_ctx.setCurrentTracker( this ); } }; class SectionTracker : public TrackerBase { public: SectionTracker( std::string const& name, TrackerContext& ctx, ITracker* parent ) : TrackerBase( name, ctx, parent ) {} virtual ~SectionTracker(); virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) { SectionTracker* section = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { assert( childTracker ); assert( childTracker->isSectionTracker() ); section = static_cast( childTracker ); } else { section = new SectionTracker( name, ctx, ¤tTracker ); currentTracker.addChild( section ); } if( !ctx.completedCycle() && !section->isComplete() ) { section->open(); } return *section; } }; class IndexTracker : public TrackerBase { int m_size; int m_index; public: IndexTracker( std::string const& name, TrackerContext& ctx, ITracker* parent, int size ) : TrackerBase( name, ctx, parent ), m_size( size ), m_index( -1 ) {} virtual ~IndexTracker(); virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) { IndexTracker* tracker = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { assert( childTracker ); assert( childTracker->isIndexTracker() ); tracker = static_cast( childTracker ); } else { tracker = new IndexTracker( name, ctx, ¤tTracker, size ); currentTracker.addChild( tracker ); } if( !ctx.completedCycle() && !tracker->isComplete() ) { if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) tracker->moveNext(); tracker->open(); } return *tracker; } int index() const { return m_index; } void moveNext() { m_index++; m_children.clear(); } virtual void close() CATCH_OVERRIDE { TrackerBase::close(); if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) m_runState = Executing; } }; inline ITracker& TrackerContext::startRun() { m_rootTracker = new SectionTracker( "{root}", *this, CATCH_NULL ); m_currentTracker = CATCH_NULL; m_runState = Executing; return *m_rootTracker; } } // namespace TestCaseTracking using TestCaseTracking::ITracker; using TestCaseTracking::TrackerContext; using TestCaseTracking::SectionTracker; using TestCaseTracking::IndexTracker; } // namespace Catch // #included from: catch_fatal_condition.hpp #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED namespace Catch { // Report the error condition then exit the process inline void fatal( std::string const& message, int exitCode ) { IContext& context = Catch::getCurrentContext(); IResultCapture* resultCapture = context.getResultCapture(); resultCapture->handleFatalErrorCondition( message ); if( Catch::alwaysTrue() ) // avoids "no return" warnings exit( exitCode ); } } // namespace Catch #if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// namespace Catch { struct FatalConditionHandler { void reset() {} }; } // namespace Catch #else // Not Windows - assumed to be POSIX compatible ////////////////////////// #include namespace Catch { struct SignalDefs { int id; const char* name; }; extern SignalDefs signalDefs[]; SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, { SIGTERM, "SIGTERM - Termination request signal" }, { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; struct FatalConditionHandler { static void handleSignal( int sig ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) if( sig == signalDefs[i].id ) fatal( signalDefs[i].name, -sig ); fatal( "", -sig ); } FatalConditionHandler() : m_isSet( true ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) signal( signalDefs[i].id, handleSignal ); } ~FatalConditionHandler() { reset(); } void reset() { if( m_isSet ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) signal( signalDefs[i].id, SIG_DFL ); m_isSet = false; } } bool m_isSet; }; } // namespace Catch #endif // not Windows #include #include namespace Catch { class StreamRedirect { public: StreamRedirect( std::ostream& stream, std::string& targetString ) : m_stream( stream ), m_prevBuf( stream.rdbuf() ), m_targetString( targetString ) { stream.rdbuf( m_oss.rdbuf() ); } ~StreamRedirect() { m_targetString += m_oss.str(); m_stream.rdbuf( m_prevBuf ); } private: std::ostream& m_stream; std::streambuf* m_prevBuf; std::ostringstream m_oss; std::string& m_targetString; }; /////////////////////////////////////////////////////////////////////////// class RunContext : public IResultCapture, public IRunner { RunContext( RunContext const& ); void operator =( RunContext const& ); public: explicit RunContext( Ptr const& _config, Ptr const& reporter ) : m_runInfo( _config->name() ), m_context( getCurrentMutableContext() ), m_activeTestCase( CATCH_NULL ), m_config( _config ), m_reporter( reporter ) { m_context.setRunner( this ); m_context.setConfig( m_config ); m_context.setResultCapture( this ); m_reporter->testRunStarting( m_runInfo ); } virtual ~RunContext() { m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); } void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); } void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); } Totals runTest( TestCase const& testCase ) { Totals prevTotals = m_totals; std::string redirectedCout; std::string redirectedCerr; TestCaseInfo testInfo = testCase.getTestCaseInfo(); m_reporter->testCaseStarting( testInfo ); m_activeTestCase = &testCase; do { m_trackerContext.startRun(); do { m_trackerContext.startCycle(); m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, testInfo.name ); runCurrentTest( redirectedCout, redirectedCerr ); } while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); } // !TBD: deprecated - this will be replaced by indexed trackers while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { deltaTotals.assertions.failed++; deltaTotals.testCases.passed--; deltaTotals.testCases.failed++; } m_totals.testCases += deltaTotals.testCases; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, redirectedCout, redirectedCerr, aborting() ) ); m_activeTestCase = CATCH_NULL; m_testCaseTracker = CATCH_NULL; return deltaTotals; } Ptr config() const { return m_config; } private: // IResultCapture virtual void assertionEnded( AssertionResult const& result ) { if( result.getResultType() == ResultWas::Ok ) { m_totals.assertions.passed++; } else if( !result.isOk() ) { m_totals.assertions.failed++; } if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) m_messages.clear(); // Reset working state m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); m_lastResult = result; } virtual bool sectionStarted ( SectionInfo const& sectionInfo, Counts& assertions ) { std::ostringstream oss; oss << sectionInfo.name << "@" << sectionInfo.lineInfo; ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, oss.str() ); if( !sectionTracker.isOpen() ) return false; m_activeSections.push_back( §ionTracker ); m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; m_reporter->sectionStarting( sectionInfo ); assertions = m_totals.assertions; return true; } bool testForMissingAssertions( Counts& assertions ) { if( assertions.total() != 0 ) return false; if( !m_config->warnAboutMissingAssertions() ) return false; if( m_trackerContext.currentTracker().hasChildren() ) return false; m_totals.assertions.failed++; assertions.failed++; return true; } virtual void sectionEnded( SectionEndInfo const& endInfo ) { Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); if( !m_activeSections.empty() ) { m_activeSections.back()->close(); m_activeSections.pop_back(); } m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); m_messages.clear(); } virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { if( m_unfinishedSections.empty() ) m_activeSections.back()->fail(); else m_activeSections.back()->close(); m_activeSections.pop_back(); m_unfinishedSections.push_back( endInfo ); } virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } virtual void popScopedMessage( MessageInfo const& message ) { m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); } virtual std::string getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name : ""; } virtual const AssertionResult* getLastResult() const { return &m_lastResult; } virtual void handleFatalErrorCondition( std::string const& message ) { ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); resultBuilder.setResultType( ResultWas::FatalErrorCondition ); resultBuilder << message; resultBuilder.captureExpression(); handleUnfinishedSections(); // Recreate section for test case (as we will lose the one that was in scope) TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); Counts assertions; assertions.failed = 1; SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); m_reporter->sectionEnded( testCaseSectionStats ); TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); Totals deltaTotals; deltaTotals.testCases.failed = 1; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, "", "", false ) ); m_totals.testCases.failed++; testGroupEnded( "", m_totals, 1, 1 ); m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); } public: // !TBD We need to do this another way! bool aborting() const { return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); } private: void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); m_reporter->sectionStarting( testCaseSection ); Counts prevAssertions = m_totals.assertions; double duration = 0; try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); seedRng( *m_config ); Timer timer; timer.start(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { StreamRedirect coutRedir( Catch::cout(), redirectedCout ); StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); invokeActiveTestCase(); } else { invokeActiveTestCase(); } duration = timer.getElapsedSeconds(); } catch( TestFailureException& ) { // This just means the test was aborted due to failure } catch(...) { makeUnexpectedResultBuilder().useActiveException(); } m_testCaseTracker->close(); handleUnfinishedSections(); m_messages.clear(); Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); if( testCaseInfo.okToFail() ) { std::swap( assertions.failedButOk, assertions.failed ); m_totals.assertions.failed -= assertions.failedButOk; m_totals.assertions.failedButOk += assertions.failedButOk; } SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); m_reporter->sectionEnded( testCaseSectionStats ); } void invokeActiveTestCase() { FatalConditionHandler fatalConditionHandler; // Handle signals m_activeTestCase->invoke(); fatalConditionHandler.reset(); } private: ResultBuilder makeUnexpectedResultBuilder() const { return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), m_lastAssertionInfo.lineInfo, m_lastAssertionInfo.capturedExpression.c_str(), m_lastAssertionInfo.resultDisposition ); } void handleUnfinishedSections() { // If sections ended prematurely due to an exception we stored their // infos here so we can tear them down outside the unwind process. for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd; ++it ) sectionEnded( *it ); m_unfinishedSections.clear(); } TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; ITracker* m_testCaseTracker; ITracker* m_currentSectionTracker; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; }; IResultCapture& getResultCapture() { if( IResultCapture* capture = getCurrentContext().getResultCapture() ) return *capture; else throw std::logic_error( "No result capture instance" ); } } // end namespace Catch // #included from: internal/catch_version.h #define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED namespace Catch { // Versioning information struct Version { Version( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, std::string const& _branchName, unsigned int _buildNumber ); unsigned int const majorVersion; unsigned int const minorVersion; unsigned int const patchNumber; // buildNumber is only used if branchName is not null std::string const branchName; unsigned int const buildNumber; friend std::ostream& operator << ( std::ostream& os, Version const& version ); private: void operator=( Version const& ); }; extern Version libraryVersion; } #include #include #include namespace Catch { Ptr createReporter( std::string const& reporterName, Ptr const& config ) { Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); if( !reporter ) { std::ostringstream oss; oss << "No reporter registered with name: '" << reporterName << "'"; throw std::domain_error( oss.str() ); } return reporter; } Ptr makeReporter( Ptr const& config ) { std::vector reporters = config->getReporterNames(); if( reporters.empty() ) reporters.push_back( "console" ); Ptr reporter; for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); it != itEnd; ++it ) reporter = addReporter( reporter, createReporter( *it, config ) ); return reporter; } Ptr addListeners( Ptr const& config, Ptr reporters ) { IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); it != itEnd; ++it ) reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); return reporters; } Totals runTests( Ptr const& config ) { Ptr iconfig = config.get(); Ptr reporter = makeReporter( config ); reporter = addListeners( iconfig, reporter ); RunContext context( iconfig, reporter ); Totals totals; context.testGroupStarting( config->name(), 1, 1 ); TestSpec testSpec = config->testSpec(); if( !testSpec.hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); it != itEnd; ++it ) { if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) totals += context.runTest( *it ); else reporter->skipTest( *it ); } context.testGroupEnded( iconfig->name(), totals, 1, 1 ); return totals; } void applyFilenamesAsTags( IConfig const& config ) { std::vector const& tests = getAllTestCasesSorted( config ); for(std::size_t i = 0; i < tests.size(); ++i ) { TestCase& test = const_cast( tests[i] ); std::set tags = test.tags; std::string filename = test.lineInfo.file; std::string::size_type lastSlash = filename.find_last_of( "\\/" ); if( lastSlash != std::string::npos ) filename = filename.substr( lastSlash+1 ); std::string::size_type lastDot = filename.find_last_of( "." ); if( lastDot != std::string::npos ) filename = filename.substr( 0, lastDot ); tags.insert( "#" + filename ); setTags( test, tags ); } } class Session : NonCopyable { static bool alreadyInstantiated; public: struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; Session() : m_cli( makeCommandLineParser() ) { if( alreadyInstantiated ) { std::string msg = "Only one instance of Catch::Session can ever be used"; Catch::cerr() << msg << std::endl; throw std::logic_error( msg ); } alreadyInstantiated = true; } ~Session() { Catch::cleanUp(); } void showHelp( std::string const& processName ) { Catch::cout() << "\nCatch v" << libraryVersion << "\n"; m_cli.usage( Catch::cout(), processName ); Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); if( m_configData.showHelp ) showHelp( m_configData.processName ); m_config.reset(); } catch( std::exception& ex ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "\nError(s) in input:\n" << Text( ex.what(), TextAttributes().setIndent(2) ) << "\n\n"; } m_cli.usage( Catch::cout(), m_configData.processName ); return (std::numeric_limits::max)(); } return 0; } void useConfigData( ConfigData const& _configData ) { m_configData = _configData; m_config.reset(); } int run( int argc, char const* const* const argv ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) returnCode = run(); return returnCode; } int run() { if( m_configData.showHelp ) return 0; try { config(); // Force config to be constructed seedRng( *m_config ); if( m_configData.filenamesAsTags ) applyFilenamesAsTags( *m_config ); // Handle list request if( Option listed = list( config() ) ) return static_cast( *listed ); return static_cast( runTests( m_config ).assertions.failed ); } catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; return (std::numeric_limits::max)(); } } Clara::CommandLine const& cli() const { return m_cli; } std::vector const& unusedTokens() const { return m_unusedTokens; } ConfigData& configData() { return m_configData; } Config& config() { if( !m_config ) m_config = new Config( m_configData ); return *m_config; } private: Clara::CommandLine m_cli; std::vector m_unusedTokens; ConfigData m_configData; Ptr m_config; }; bool Session::alreadyInstantiated = false; } // end namespace Catch // #included from: catch_registry_hub.hpp #define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED // #included from: catch_test_case_registry_impl.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED #include #include #include #include #include namespace Catch { struct RandomNumberGenerator { typedef std::ptrdiff_t result_type; result_type operator()( result_type n ) const { return std::rand() % n; } #ifdef CATCH_CONFIG_CPP11_SHUFFLE static constexpr result_type min() { return 0; } static constexpr result_type max() { return 1000000; } result_type operator()() const { return std::rand() % max(); } #endif template static void shuffle( V& vector ) { RandomNumberGenerator rng; #ifdef CATCH_CONFIG_CPP11_SHUFFLE std::shuffle( vector.begin(), vector.end(), rng ); #else std::random_shuffle( vector.begin(), vector.end(), rng ); #endif } }; inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { std::vector sorted = unsortedTestCases; switch( config.runOrder() ) { case RunTests::InLexicographicalOrder: std::sort( sorted.begin(), sorted.end() ); break; case RunTests::InRandomOrder: { seedRng( config ); RandomNumberGenerator::shuffle( sorted ); } break; case RunTests::InDeclarationOrder: // already in declaration order break; } return sorted; } bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); } void enforceNoDuplicateTestCases( std::vector const& functions ) { std::set seenFunctions; for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); it != itEnd; ++it ) { std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); if( !prev.second ) { std::ostringstream ss; ss << Colour( Colour::Red ) << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; throw std::runtime_error(ss.str()); } } } std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { std::vector filtered; filtered.reserve( testCases.size() ); for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); it != itEnd; ++it ) if( matchTest( *it, testSpec, config ) ) filtered.push_back( *it ); return filtered; } std::vector const& getAllTestCasesSorted( IConfig const& config ) { return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); } class TestRegistry : public ITestCaseRegistry { public: TestRegistry() : m_currentSortOrder( RunTests::InDeclarationOrder ), m_unnamedCount( 0 ) {} virtual ~TestRegistry(); virtual void registerTest( TestCase const& testCase ) { std::string name = testCase.getTestCaseInfo().name; if( name == "" ) { std::ostringstream oss; oss << "Anonymous test case " << ++m_unnamedCount; return registerTest( testCase.withName( oss.str() ) ); } m_functions.push_back( testCase ); } virtual std::vector const& getAllTests() const { return m_functions; } virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { if( m_sortedFunctions.empty() ) enforceNoDuplicateTestCases( m_functions ); if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { m_sortedFunctions = sortTests( config, m_functions ); m_currentSortOrder = config.runOrder(); } return m_sortedFunctions; } private: std::vector m_functions; mutable RunTests::InWhatOrder m_currentSortOrder; mutable std::vector m_sortedFunctions; size_t m_unnamedCount; std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised }; /////////////////////////////////////////////////////////////////////////// class FreeFunctionTestCase : public SharedImpl { public: FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} virtual void invoke() const { m_fun(); } private: virtual ~FreeFunctionTestCase(); TestFunction m_fun; }; inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { std::string className = classOrQualifiedMethodName; if( startsWith( className, "&" ) ) { std::size_t lastColons = className.rfind( "::" ); std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); if( penultimateColons == std::string::npos ) penultimateColons = 1; className = className.substr( penultimateColons, lastColons-penultimateColons ); } return className; } void registerTestCase ( ITestCase* testCase, char const* classOrQualifiedMethodName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { getMutableRegistryHub().registerTest ( makeTestCase ( testCase, extractClassName( classOrQualifiedMethodName ), nameAndDesc.name, nameAndDesc.description, lineInfo ) ); } void registerTestCaseFunction ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); } /////////////////////////////////////////////////////////////////////////// AutoReg::AutoReg ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCaseFunction( function, lineInfo, nameAndDesc ); } AutoReg::~AutoReg() {} } // end namespace Catch // #included from: catch_reporter_registry.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED #include namespace Catch { class ReporterRegistry : public IReporterRegistry { public: virtual ~ReporterRegistry() CATCH_OVERRIDE {} virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { FactoryMap::const_iterator it = m_factories.find( name ); if( it == m_factories.end() ) return CATCH_NULL; return it->second->create( ReporterConfig( config ) ); } void registerReporter( std::string const& name, Ptr const& factory ) { m_factories.insert( std::make_pair( name, factory ) ); } void registerListener( Ptr const& factory ) { m_listeners.push_back( factory ); } virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { return m_factories; } virtual Listeners const& getListeners() const CATCH_OVERRIDE { return m_listeners; } private: FactoryMap m_factories; Listeners m_listeners; }; } // #included from: catch_exception_translator_registry.hpp #define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED #ifdef __OBJC__ #import "Foundation/Foundation.h" #endif namespace Catch { class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { public: ~ExceptionTranslatorRegistry() { deleteAll( m_translators ); } virtual void registerTranslator( const IExceptionTranslator* translator ) { m_translators.push_back( translator ); } virtual std::string translateActiveException() const { try { #ifdef __OBJC__ // In Objective-C try objective-c exceptions first @try { return tryTranslators(); } @catch (NSException *exception) { return Catch::toString( [exception description] ); } #else return tryTranslators(); #endif } catch( TestFailureException& ) { throw; } catch( std::exception& ex ) { return ex.what(); } catch( std::string& msg ) { return msg; } catch( const char* msg ) { return msg; } catch(...) { return "Unknown exception"; } } std::string tryTranslators() const { if( m_translators.empty() ) throw; else return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); } private: std::vector m_translators; }; } namespace Catch { namespace { class RegistryHub : public IRegistryHub, public IMutableRegistryHub { RegistryHub( RegistryHub const& ); void operator=( RegistryHub const& ); public: // IRegistryHub RegistryHub() { } virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { return m_reporterRegistry; } virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { return m_testCaseRegistry; } virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { return m_exceptionTranslatorRegistry; } public: // IMutableRegistryHub virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerReporter( name, factory ); } virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerListener( factory ); } virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { m_testCaseRegistry.registerTest( testInfo ); } virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { m_exceptionTranslatorRegistry.registerTranslator( translator ); } private: TestRegistry m_testCaseRegistry; ReporterRegistry m_reporterRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; }; // Single, global, instance inline RegistryHub*& getTheRegistryHub() { static RegistryHub* theRegistryHub = CATCH_NULL; if( !theRegistryHub ) theRegistryHub = new RegistryHub(); return theRegistryHub; } } IRegistryHub& getRegistryHub() { return *getTheRegistryHub(); } IMutableRegistryHub& getMutableRegistryHub() { return *getTheRegistryHub(); } void cleanUp() { delete getTheRegistryHub(); getTheRegistryHub() = CATCH_NULL; cleanUpContext(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); } } // end namespace Catch // #included from: catch_notimplemented_exception.hpp #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED #include namespace Catch { NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) : m_lineInfo( lineInfo ) { std::ostringstream oss; oss << lineInfo << ": function "; oss << "not implemented"; m_what = oss.str(); } const char* NotImplementedException::what() const CATCH_NOEXCEPT { return m_what.c_str(); } } // end namespace Catch // #included from: catch_context_impl.hpp #define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED // #included from: catch_stream.hpp #define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED #include #include #include namespace Catch { template class StreamBufImpl : public StreamBufBase { char data[bufferSize]; WriterF m_writer; public: StreamBufImpl() { setp( data, data + sizeof(data) ); } ~StreamBufImpl() CATCH_NOEXCEPT { sync(); } private: int overflow( int c ) { sync(); if( c != EOF ) { if( pbase() == epptr() ) m_writer( std::string( 1, static_cast( c ) ) ); else sputc( static_cast( c ) ); } return 0; } int sync() { if( pbase() != pptr() ) { m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); setp( pbase(), epptr() ); } return 0; } }; /////////////////////////////////////////////////////////////////////////// FileStream::FileStream( std::string const& filename ) { m_ofs.open( filename.c_str() ); if( m_ofs.fail() ) { std::ostringstream oss; oss << "Unable to open file: '" << filename << "'"; throw std::domain_error( oss.str() ); } } std::ostream& FileStream::stream() const { return m_ofs; } struct OutputDebugWriter { void operator()( std::string const&str ) { writeToDebugConsole( str ); } }; DebugOutStream::DebugOutStream() : m_streamBuf( new StreamBufImpl() ), m_os( m_streamBuf.get() ) {} std::ostream& DebugOutStream::stream() const { return m_os; } // Store the streambuf from cout up-front because // cout may get redirected when running tests CoutStream::CoutStream() : m_os( Catch::cout().rdbuf() ) {} std::ostream& CoutStream::stream() const { return m_os; } #ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions std::ostream& cout() { return std::cout; } std::ostream& cerr() { return std::cerr; } #endif } namespace Catch { class Context : public IMutableContext { Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} Context( Context const& ); void operator=( Context const& ); public: virtual ~Context() { deleteAllValues( m_generatorsByTestName ); } public: // IContext virtual IResultCapture* getResultCapture() { return m_resultCapture; } virtual IRunner* getRunner() { return m_runner; } virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { return getGeneratorsForCurrentTest() .getGeneratorInfo( fileInfo, totalSize ) .getCurrentIndex(); } virtual bool advanceGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); return generators && generators->moveNext(); } virtual Ptr getConfig() const { return m_config; } public: // IMutableContext virtual void setResultCapture( IResultCapture* resultCapture ) { m_resultCapture = resultCapture; } virtual void setRunner( IRunner* runner ) { m_runner = runner; } virtual void setConfig( Ptr const& config ) { m_config = config; } friend IMutableContext& getCurrentMutableContext(); private: IGeneratorsForTest* findGeneratorsForCurrentTest() { std::string testName = getResultCapture()->getCurrentTestName(); std::map::const_iterator it = m_generatorsByTestName.find( testName ); return it != m_generatorsByTestName.end() ? it->second : CATCH_NULL; } IGeneratorsForTest& getGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); if( !generators ) { std::string testName = getResultCapture()->getCurrentTestName(); generators = createGeneratorsForTest(); m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); } return *generators; } private: Ptr m_config; IRunner* m_runner; IResultCapture* m_resultCapture; std::map m_generatorsByTestName; }; namespace { Context* currentContext = CATCH_NULL; } IMutableContext& getCurrentMutableContext() { if( !currentContext ) currentContext = new Context(); return *currentContext; } IContext& getCurrentContext() { return getCurrentMutableContext(); } void cleanUpContext() { delete currentContext; currentContext = CATCH_NULL; } } // #included from: catch_console_colour_impl.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED namespace Catch { namespace { struct IColourImpl { virtual ~IColourImpl() {} virtual void use( Colour::Code _colourCode ) = 0; }; struct NoColourImpl : IColourImpl { void use( Colour::Code ) {} static IColourImpl* instance() { static NoColourImpl s_instance; return &s_instance; } }; } // anon namespace } // namespace Catch #if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) # ifdef CATCH_PLATFORM_WINDOWS # define CATCH_CONFIG_COLOUR_WINDOWS # else # define CATCH_CONFIG_COLOUR_ANSI # endif #endif #if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// // #included from: catch_windows_h_proxy.h #define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED #ifdef CATCH_DEFINES_NOMINMAX # define NOMINMAX #endif #ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN #endif #ifdef __AFXDLL #include #else #include #endif #ifdef CATCH_DEFINES_NOMINMAX # undef NOMINMAX #endif #ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN # undef WIN32_LEAN_AND_MEAN #endif namespace Catch { namespace { class Win32ColourImpl : public IColourImpl { public: Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Red: return setTextAttribute( FOREGROUND_RED ); case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); case Colour::Grey: return setTextAttribute( 0 ); case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } private: void setTextAttribute( WORD _textAttribute ) { SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); } HANDLE stdoutHandle; WORD originalForegroundAttributes; WORD originalBackgroundAttributes; }; IColourImpl* platformColourInstance() { static Win32ColourImpl s_instance; Ptr config = getCurrentContext().getConfig(); UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; if( colourMode == UseColour::Auto ) colourMode = !isDebuggerActive() ? UseColour::Yes : UseColour::No; return colourMode == UseColour::Yes ? &s_instance : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// #include namespace Catch { namespace { // use POSIX/ ANSI console terminal codes // Thanks to Adam Strzelecki for original contribution // (http://github.com/nanoant) // https://github.com/philsquared/Catch/pull/131 class PosixColourImpl : public IColourImpl { public: virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: case Colour::White: return setColour( "[0m" ); case Colour::Red: return setColour( "[0;31m" ); case Colour::Green: return setColour( "[0;32m" ); case Colour::Blue: return setColour( "[0;34m" ); case Colour::Cyan: return setColour( "[0;36m" ); case Colour::Yellow: return setColour( "[0;33m" ); case Colour::Grey: return setColour( "[1;30m" ); case Colour::LightGrey: return setColour( "[0;37m" ); case Colour::BrightRed: return setColour( "[1;31m" ); case Colour::BrightGreen: return setColour( "[1;32m" ); case Colour::BrightWhite: return setColour( "[1;37m" ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } static IColourImpl* instance() { static PosixColourImpl s_instance; return &s_instance; } private: void setColour( const char* _escapeCode ) { Catch::cout() << '\033' << _escapeCode; } }; IColourImpl* platformColourInstance() { Ptr config = getCurrentContext().getConfig(); UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; if( colourMode == UseColour::Auto ) colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) ? UseColour::Yes : UseColour::No; return colourMode == UseColour::Yes ? PosixColourImpl::instance() : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #else // not Windows or ANSI /////////////////////////////////////////////// namespace Catch { static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } } // end namespace Catch #endif // Windows/ ANSI/ None namespace Catch { Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } Colour::~Colour(){ if( !m_moved ) use( None ); } void Colour::use( Code _colourCode ) { static IColourImpl* impl = platformColourInstance(); impl->use( _colourCode ); } } // end namespace Catch // #included from: catch_generators_impl.hpp #define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED #include #include #include namespace Catch { struct GeneratorInfo : IGeneratorInfo { GeneratorInfo( std::size_t size ) : m_size( size ), m_currentIndex( 0 ) {} bool moveNext() { if( ++m_currentIndex == m_size ) { m_currentIndex = 0; return false; } return true; } std::size_t getCurrentIndex() const { return m_currentIndex; } std::size_t m_size; std::size_t m_currentIndex; }; /////////////////////////////////////////////////////////////////////////// class GeneratorsForTest : public IGeneratorsForTest { public: ~GeneratorsForTest() { deleteAll( m_generatorsInOrder ); } IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { std::map::const_iterator it = m_generatorsByName.find( fileInfo ); if( it == m_generatorsByName.end() ) { IGeneratorInfo* info = new GeneratorInfo( size ); m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); m_generatorsInOrder.push_back( info ); return *info; } return *it->second; } bool moveNext() { std::vector::const_iterator it = m_generatorsInOrder.begin(); std::vector::const_iterator itEnd = m_generatorsInOrder.end(); for(; it != itEnd; ++it ) { if( (*it)->moveNext() ) return true; } return false; } private: std::map m_generatorsByName; std::vector m_generatorsInOrder; }; IGeneratorsForTest* createGeneratorsForTest() { return new GeneratorsForTest(); } } // end namespace Catch // #included from: catch_assertionresult.hpp #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED namespace Catch { AssertionInfo::AssertionInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ) : macroName( _macroName ), lineInfo( _lineInfo ), capturedExpression( _capturedExpression ), resultDisposition( _resultDisposition ) {} AssertionResult::AssertionResult() {} AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) : m_info( info ), m_resultData( data ) {} AssertionResult::~AssertionResult() {} // Result was a success bool AssertionResult::succeeded() const { return Catch::isOk( m_resultData.resultType ); } // Result was a success, or failure is suppressed bool AssertionResult::isOk() const { return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); } ResultWas::OfType AssertionResult::getResultType() const { return m_resultData.resultType; } bool AssertionResult::hasExpression() const { return !m_info.capturedExpression.empty(); } bool AssertionResult::hasMessage() const { return !m_resultData.message.empty(); } std::string AssertionResult::getExpression() const { if( isFalseTest( m_info.resultDisposition ) ) return "!" + m_info.capturedExpression; else return m_info.capturedExpression; } std::string AssertionResult::getExpressionInMacro() const { if( m_info.macroName.empty() ) return m_info.capturedExpression; else return m_info.macroName + "( " + m_info.capturedExpression + " )"; } bool AssertionResult::hasExpandedExpression() const { return hasExpression() && getExpandedExpression() != getExpression(); } std::string AssertionResult::getExpandedExpression() const { return m_resultData.reconstructedExpression; } std::string AssertionResult::getMessage() const { return m_resultData.message; } SourceLineInfo AssertionResult::getSourceInfo() const { return m_info.lineInfo; } std::string AssertionResult::getTestMacroName() const { return m_info.macroName; } } // end namespace Catch // #included from: catch_test_case_info.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED namespace Catch { inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { if( startsWith( tag, "." ) || tag == "hide" || tag == "!hide" ) return TestCaseInfo::IsHidden; else if( tag == "!throws" ) return TestCaseInfo::Throws; else if( tag == "!shouldfail" ) return TestCaseInfo::ShouldFail; else if( tag == "!mayfail" ) return TestCaseInfo::MayFail; else return TestCaseInfo::None; } inline bool isReservedTag( std::string const& tag ) { return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); } inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { if( isReservedTag( tag ) ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "Tag name [" << tag << "] not allowed.\n" << "Tag names starting with non alpha-numeric characters are reserved\n"; } { Colour colourGuard( Colour::FileName ); Catch::cerr() << _lineInfo << std::endl; } exit(1); } } TestCase makeTestCase( ITestCase* _testCase, std::string const& _className, std::string const& _name, std::string const& _descOrTags, SourceLineInfo const& _lineInfo ) { bool isHidden( startsWith( _name, "./" ) ); // Legacy support // Parse out tags std::set tags; std::string desc, tag; bool inTag = false; for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { char c = _descOrTags[i]; if( !inTag ) { if( c == '[' ) inTag = true; else desc += c; } else { if( c == ']' ) { TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); if( prop == TestCaseInfo::IsHidden ) isHidden = true; else if( prop == TestCaseInfo::None ) enforceNotReservedTag( tag, _lineInfo ); tags.insert( tag ); tag.clear(); inTag = false; } else tag += c; } } if( isHidden ) { tags.insert( "hide" ); tags.insert( "." ); } TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); return TestCase( _testCase, info ); } void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) { testCaseInfo.tags = tags; testCaseInfo.lcaseTags.clear(); std::ostringstream oss; for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { oss << "[" << *it << "]"; std::string lcaseTag = toLower( *it ); testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); testCaseInfo.lcaseTags.insert( lcaseTag ); } testCaseInfo.tagsAsString = oss.str(); } TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ) : name( _name ), className( _className ), description( _description ), lineInfo( _lineInfo ), properties( None ) { setTags( *this, _tags ); } TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) : name( other.name ), className( other.className ), description( other.description ), tags( other.tags ), lcaseTags( other.lcaseTags ), tagsAsString( other.tagsAsString ), lineInfo( other.lineInfo ), properties( other.properties ) {} bool TestCaseInfo::isHidden() const { return ( properties & IsHidden ) != 0; } bool TestCaseInfo::throws() const { return ( properties & Throws ) != 0; } bool TestCaseInfo::okToFail() const { return ( properties & (ShouldFail | MayFail ) ) != 0; } bool TestCaseInfo::expectedToFail() const { return ( properties & (ShouldFail ) ) != 0; } TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} TestCase::TestCase( TestCase const& other ) : TestCaseInfo( other ), test( other.test ) {} TestCase TestCase::withName( std::string const& _newName ) const { TestCase other( *this ); other.name = _newName; return other; } void TestCase::swap( TestCase& other ) { test.swap( other.test ); name.swap( other.name ); className.swap( other.className ); description.swap( other.description ); tags.swap( other.tags ); lcaseTags.swap( other.lcaseTags ); tagsAsString.swap( other.tagsAsString ); std::swap( TestCaseInfo::properties, static_cast( other ).properties ); std::swap( lineInfo, other.lineInfo ); } void TestCase::invoke() const { test->invoke(); } bool TestCase::operator == ( TestCase const& other ) const { return test.get() == other.test.get() && name == other.name && className == other.className; } bool TestCase::operator < ( TestCase const& other ) const { return name < other.name; } TestCase& TestCase::operator = ( TestCase const& other ) { TestCase temp( other ); swap( temp ); return *this; } TestCaseInfo const& TestCase::getTestCaseInfo() const { return *this; } } // end namespace Catch // #included from: catch_version.hpp #define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED namespace Catch { Version::Version ( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, std::string const& _branchName, unsigned int _buildNumber ) : majorVersion( _majorVersion ), minorVersion( _minorVersion ), patchNumber( _patchNumber ), branchName( _branchName ), buildNumber( _buildNumber ) {} std::ostream& operator << ( std::ostream& os, Version const& version ) { os << version.majorVersion << "." << version.minorVersion << "." << version.patchNumber; if( !version.branchName.empty() ) { os << "-" << version.branchName << "." << version.buildNumber; } return os; } Version libraryVersion( 1, 6, 1, "", 0 ); } // #included from: catch_message.hpp #define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED namespace Catch { MessageInfo::MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ) : macroName( _macroName ), lineInfo( _lineInfo ), type( _type ), sequence( ++globalCount ) {} // This may need protecting if threading support is added unsigned int MessageInfo::globalCount = 0; //////////////////////////////////////////////////////////////////////////// ScopedMessage::ScopedMessage( MessageBuilder const& builder ) : m_info( builder.m_info ) { m_info.message = builder.m_stream.str(); getResultCapture().pushScopedMessage( m_info ); } ScopedMessage::ScopedMessage( ScopedMessage const& other ) : m_info( other.m_info ) {} ScopedMessage::~ScopedMessage() { getResultCapture().popScopedMessage( m_info ); } } // end namespace Catch // #included from: catch_legacy_reporter_adapter.hpp #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED // #included from: catch_legacy_reporter_adapter.h #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED namespace Catch { // Deprecated struct IReporter : IShared { virtual ~IReporter(); virtual bool shouldRedirectStdout() const = 0; virtual void StartTesting() = 0; virtual void EndTesting( Totals const& totals ) = 0; virtual void StartGroup( std::string const& groupName ) = 0; virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; virtual void Aborted() = 0; virtual void Result( AssertionResult const& result ) = 0; }; class LegacyReporterAdapter : public SharedImpl { public: LegacyReporterAdapter( Ptr const& legacyReporter ); virtual ~LegacyReporterAdapter(); virtual ReporterPreferences getPreferences() const; virtual void noMatchingTestCases( std::string const& ); virtual void testRunStarting( TestRunInfo const& ); virtual void testGroupStarting( GroupInfo const& groupInfo ); virtual void testCaseStarting( TestCaseInfo const& testInfo ); virtual void sectionStarting( SectionInfo const& sectionInfo ); virtual void assertionStarting( AssertionInfo const& ); virtual bool assertionEnded( AssertionStats const& assertionStats ); virtual void sectionEnded( SectionStats const& sectionStats ); virtual void testCaseEnded( TestCaseStats const& testCaseStats ); virtual void testGroupEnded( TestGroupStats const& testGroupStats ); virtual void testRunEnded( TestRunStats const& testRunStats ); virtual void skipTest( TestCaseInfo const& ); private: Ptr m_legacyReporter; }; } namespace Catch { LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) : m_legacyReporter( legacyReporter ) {} LegacyReporterAdapter::~LegacyReporterAdapter() {} ReporterPreferences LegacyReporterAdapter::getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); return prefs; } void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { m_legacyReporter->StartTesting(); } void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { m_legacyReporter->StartGroup( groupInfo.name ); } void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { m_legacyReporter->StartTestCase( testInfo ); } void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); } void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { // Not on legacy interface } bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); rb << it->message; rb.setResultType( ResultWas::Info ); AssertionResult result = rb.build(); m_legacyReporter->Result( result ); } } } m_legacyReporter->Result( assertionStats.assertionResult ); return true; } void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { if( sectionStats.missingAssertions ) m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); } void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { m_legacyReporter->EndTestCase ( testCaseStats.testInfo, testCaseStats.totals, testCaseStats.stdOut, testCaseStats.stdErr ); } void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { if( testGroupStats.aborting ) m_legacyReporter->Aborted(); m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); } void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { m_legacyReporter->EndTesting( testRunStats.totals ); } void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { } } // #included from: catch_timer.hpp #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++11-long-long" #endif #ifdef CATCH_PLATFORM_WINDOWS #else #include #endif namespace Catch { namespace { #ifdef CATCH_PLATFORM_WINDOWS uint64_t getCurrentTicks() { static uint64_t hz=0, hzo=0; if (!hz) { QueryPerformanceFrequency( reinterpret_cast( &hz ) ); QueryPerformanceCounter( reinterpret_cast( &hzo ) ); } uint64_t t; QueryPerformanceCounter( reinterpret_cast( &t ) ); return ((t-hzo)*1000000)/hz; } #else uint64_t getCurrentTicks() { timeval t; gettimeofday(&t,CATCH_NULL); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif } void Timer::start() { m_ticks = getCurrentTicks(); } unsigned int Timer::getElapsedMicroseconds() const { return static_cast(getCurrentTicks() - m_ticks); } unsigned int Timer::getElapsedMilliseconds() const { return static_cast(getElapsedMicroseconds()/1000); } double Timer::getElapsedSeconds() const { return getElapsedMicroseconds()/1000000.0; } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_common.hpp #define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED namespace Catch { bool startsWith( std::string const& s, std::string const& prefix ) { return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; } bool endsWith( std::string const& s, std::string const& suffix ) { return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; } bool contains( std::string const& s, std::string const& infix ) { return s.find( infix ) != std::string::npos; } char toLowerCh(char c) { return static_cast( ::tolower( c ) ); } void toLowerInPlace( std::string& s ) { std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); } std::string toLower( std::string const& s ) { std::string lc = s; toLowerInPlace( lc ); return lc; } std::string trim( std::string const& str ) { static char const* whitespaceChars = "\n\r\t "; std::string::size_type start = str.find_first_not_of( whitespaceChars ); std::string::size_type end = str.find_last_not_of( whitespaceChars ); return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; } bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { bool replaced = false; std::size_t i = str.find( replaceThis ); while( i != std::string::npos ) { replaced = true; str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); if( i < str.size()-withThis.size() ) i = str.find( replaceThis, i+withThis.size() ); else i = std::string::npos; } return replaced; } pluralise::pluralise( std::size_t count, std::string const& label ) : m_count( count ), m_label( label ) {} std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { os << pluraliser.m_count << " " << pluraliser.m_label; if( pluraliser.m_count != 1 ) os << "s"; return os; } SourceLineInfo::SourceLineInfo() : line( 0 ){} SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) : file( _file ), line( _line ) {} SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) : file( other.file ), line( other.line ) {} bool SourceLineInfo::empty() const { return file.empty(); } bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { return line == other.line && file == other.file; } bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { return line < other.line || ( line == other.line && file < other.file ); } void seedRng( IConfig const& config ) { if( config.rngSeed() != 0 ) std::srand( config.rngSeed() ); } unsigned int rngSeed() { return getCurrentContext().getConfig()->rngSeed(); } std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { #ifndef __GNUG__ os << info.file << "(" << info.line << ")"; #else os << info.file << ":" << info.line; #endif return os; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { std::ostringstream oss; oss << locationInfo << ": Internal Catch error: '" << message << "'"; if( alwaysTrue() ) throw std::logic_error( oss.str() ); } } // #included from: catch_section.hpp #define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED namespace Catch { SectionInfo::SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description ) : name( _name ), description( _description ), lineInfo( _lineInfo ) {} Section::Section( SectionInfo const& info ) : m_info( info ), m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) { m_timer.start(); } Section::~Section() { if( m_sectionIncluded ) { SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); if( std::uncaught_exception() ) getResultCapture().sectionEndedEarly( endInfo ); else getResultCapture().sectionEnded( endInfo ); } } // This indicates whether the section should be executed or not Section::operator bool() const { return m_sectionIncluded; } } // end namespace Catch // #included from: catch_debugger.hpp #define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED #include #ifdef CATCH_PLATFORM_MAC #include #include #include #include #include namespace Catch{ // The following function is taken directly from the following technical note: // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). bool isDebuggerActive(){ int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; return false; } // We're being debugged if the P_TRACED flag is set. return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } } // namespace Catch #elif defined(CATCH_PLATFORM_LINUX) #include #include namespace Catch{ // The standard POSIX way of detecting a debugger is to attempt to // ptrace() the process, but this needs to be done from a child and not // this process itself to still allow attaching to this process later // if wanted, so is rather heavy. Under Linux we have the PID of the // "debugger" (which doesn't need to be gdb, of course, it could also // be strace, for example) in /proc/$PID/status, so just get it from // there instead. bool isDebuggerActive(){ std::ifstream in("/proc/self/status"); for( std::string line; std::getline(in, line); ) { static const int PREFIX_LEN = 11; if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { // We're traced if the PID is not 0 and no other PID starts // with 0 digit, so it's enough to check for just a single // character. return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; } } return false; } } // namespace Catch #elif defined(_MSC_VER) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #elif defined(__MINGW32__) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #else namespace Catch { inline bool isDebuggerActive() { return false; } } #endif // Platform #ifdef CATCH_PLATFORM_WINDOWS extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); namespace Catch { void writeToDebugConsole( std::string const& text ) { ::OutputDebugStringA( text.c_str() ); } } #else namespace Catch { void writeToDebugConsole( std::string const& text ) { // !TBD: Need a version for Mac/ XCode and other IDEs Catch::cout() << text; } } #endif // Platform // #included from: catch_tostring.hpp #define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED namespace Catch { namespace Detail { const std::string unprintableString = "{?}"; namespace { const int hexThreshold = 255; struct Endianness { enum Arch { Big, Little }; static Arch which() { union _{ int asInt; char asChar[sizeof (int)]; } u; u.asInt = 1; return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; } }; } std::string rawMemoryToString( const void *object, std::size_t size ) { // Reverse order for little endian architectures int i = 0, end = static_cast( size ), inc = 1; if( Endianness::which() == Endianness::Little ) { i = end-1; end = inc = -1; } unsigned char const *bytes = static_cast(object); std::ostringstream os; os << "0x" << std::setfill('0') << std::hex; for( ; i != end; i += inc ) os << std::setw(2) << static_cast(bytes[i]); return os.str(); } } std::string toString( std::string const& value ) { std::string s = value; if( getCurrentContext().getConfig()->showInvisibles() ) { for(size_t i = 0; i < s.size(); ++i ) { std::string subs; switch( s[i] ) { case '\n': subs = "\\n"; break; case '\t': subs = "\\t"; break; default: break; } if( !subs.empty() ) { s = s.substr( 0, i ) + subs + s.substr( i+1 ); ++i; } } } return "\"" + s + "\""; } std::string toString( std::wstring const& value ) { std::string s; s.reserve( value.size() ); for(size_t i = 0; i < value.size(); ++i ) s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; return Catch::toString( s ); } std::string toString( const char* const value ) { return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); } std::string toString( char* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( const wchar_t* const value ) { return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); } std::string toString( wchar_t* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( int value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned int value ) { return Catch::toString( static_cast( value ) ); } template std::string fpToString( T value, int precision ) { std::ostringstream oss; oss << std::setprecision( precision ) << std::fixed << value; std::string d = oss.str(); std::size_t i = d.find_last_not_of( '0' ); if( i != std::string::npos && i != d.size()-1 ) { if( d[i] == '.' ) i++; d = d.substr( 0, i+1 ); } return d; } std::string toString( const double value ) { return fpToString( value, 10 ); } std::string toString( const float value ) { return fpToString( value, 5 ) + "f"; } std::string toString( bool value ) { return value ? "true" : "false"; } std::string toString( char value ) { return value < ' ' ? toString( static_cast( value ) ) : Detail::makeString( value ); } std::string toString( signed char value ) { return toString( static_cast( value ) ); } std::string toString( unsigned char value ) { return toString( static_cast( value ) ); } #ifdef CATCH_CONFIG_CPP11_LONG_LONG std::string toString( long long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned long long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } #endif #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ) { return "nullptr"; } #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSObject* const& nsObject ) { return toString( [nsObject description] ); } #endif } // end namespace Catch // #included from: catch_result_builder.hpp #define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED namespace Catch { std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) { return secondArg.empty() || secondArg == "\"\"" ? capturedExpression : capturedExpression + ", " + secondArg; } ResultBuilder::ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition, char const* secondArg ) : m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ), m_shouldDebugBreak( false ), m_shouldThrow( false ) {} ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { m_data.resultType = result; return *this; } ResultBuilder& ResultBuilder::setResultType( bool result ) { m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; return *this; } ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { m_exprComponents.lhs = lhs; return *this; } ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { m_exprComponents.rhs = rhs; return *this; } ResultBuilder& ResultBuilder::setOp( std::string const& op ) { m_exprComponents.op = op; return *this; } void ResultBuilder::endExpression() { m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); captureExpression(); } void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { m_assertionInfo.resultDisposition = resultDisposition; m_stream.oss << Catch::translateActiveException(); captureResult( ResultWas::ThrewException ); } void ResultBuilder::captureResult( ResultWas::OfType resultType ) { setResultType( resultType ); captureExpression(); } void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { if( expectedMessage.empty() ) captureExpectedException( Matchers::Impl::Generic::AllOf() ); else captureExpectedException( Matchers::Equals( expectedMessage ) ); } void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher const& matcher ) { assert( m_exprComponents.testFalse == false ); AssertionResultData data = m_data; data.resultType = ResultWas::Ok; data.reconstructedExpression = m_assertionInfo.capturedExpression; std::string actualMessage = Catch::translateActiveException(); if( !matcher.match( actualMessage ) ) { data.resultType = ResultWas::ExpressionFailed; data.reconstructedExpression = actualMessage; } AssertionResult result( m_assertionInfo, data ); handleResult( result ); } void ResultBuilder::captureExpression() { AssertionResult result = build(); handleResult( result ); } void ResultBuilder::handleResult( AssertionResult const& result ) { getResultCapture().assertionEnded( result ); if( !result.isOk() ) { if( getCurrentContext().getConfig()->shouldDebugBreak() ) m_shouldDebugBreak = true; if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) m_shouldThrow = true; } } void ResultBuilder::react() { if( m_shouldThrow ) throw Catch::TestFailureException(); } bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } AssertionResult ResultBuilder::build() const { assert( m_data.resultType != ResultWas::Unknown ); AssertionResultData data = m_data; // Flip bool results if testFalse is set if( m_exprComponents.testFalse ) { if( data.resultType == ResultWas::Ok ) data.resultType = ResultWas::ExpressionFailed; else if( data.resultType == ResultWas::ExpressionFailed ) data.resultType = ResultWas::Ok; } data.message = m_stream.oss.str(); data.reconstructedExpression = reconstructExpression(); if( m_exprComponents.testFalse ) { if( m_exprComponents.op == "" ) data.reconstructedExpression = "!" + data.reconstructedExpression; else data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; } return AssertionResult( m_assertionInfo, data ); } std::string ResultBuilder::reconstructExpression() const { if( m_exprComponents.op == "" ) return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.lhs; else if( m_exprComponents.op == "matches" ) return m_exprComponents.lhs + " " + m_exprComponents.rhs; else if( m_exprComponents.op != "!" ) { if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && m_exprComponents.lhs.find("\n") == std::string::npos && m_exprComponents.rhs.find("\n") == std::string::npos ) return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; else return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; } else return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; } } // end namespace Catch // #included from: catch_tag_alias_registry.hpp #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED // #included from: catch_tag_alias_registry.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED #include namespace Catch { class TagAliasRegistry : public ITagAliasRegistry { public: virtual ~TagAliasRegistry(); virtual Option find( std::string const& alias ) const; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); static TagAliasRegistry& get(); private: std::map m_registry; }; } // end namespace Catch #include #include namespace Catch { TagAliasRegistry::~TagAliasRegistry() {} Option TagAliasRegistry::find( std::string const& alias ) const { std::map::const_iterator it = m_registry.find( alias ); if( it != m_registry.end() ) return it->second; else return Option(); } std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { std::string expandedTestSpec = unexpandedTestSpec; for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); it != itEnd; ++it ) { std::size_t pos = expandedTestSpec.find( it->first ); if( pos != std::string::npos ) { expandedTestSpec = expandedTestSpec.substr( 0, pos ) + it->second.tag + expandedTestSpec.substr( pos + it->first.size() ); } } return expandedTestSpec; } void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { std::ostringstream oss; oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; throw std::domain_error( oss.str().c_str() ); } if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { std::ostringstream oss; oss << "error: tag alias, \"" << alias << "\" already registered.\n" << "\tFirst seen at " << find(alias)->lineInfo << "\n" << "\tRedefined at " << lineInfo; throw std::domain_error( oss.str().c_str() ); } } TagAliasRegistry& TagAliasRegistry::get() { static TagAliasRegistry instance; return instance; } ITagAliasRegistry::~ITagAliasRegistry() {} ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { try { TagAliasRegistry::get().add( alias, tag, lineInfo ); } catch( std::exception& ex ) { Colour colourGuard( Colour::Red ); Catch::cerr() << ex.what() << std::endl; exit(1); } } } // end namespace Catch // #included from: ../reporters/catch_reporter_multi.hpp #define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED namespace Catch { class MultipleReporters : public SharedImpl { typedef std::vector > Reporters; Reporters m_reporters; public: void add( Ptr const& reporter ) { m_reporters.push_back( reporter ); } public: // IStreamingReporter virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporters[0]->getPreferences(); } virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->noMatchingTestCases( spec ); } virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testRunStarting( testRunInfo ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testGroupStarting( groupInfo ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testCaseStarting( testInfo ); } virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->sectionStarting( sectionInfo ); } virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->assertionStarting( assertionInfo ); } // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { bool clearBuffer = false; for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) clearBuffer |= (*it)->assertionEnded( assertionStats ); return clearBuffer; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->sectionEnded( sectionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testGroupEnded( testGroupStats ); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testRunEnded( testRunStats ); } virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->skipTest( testInfo ); } virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { return this; } }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { Ptr resultingReporter; if( existingReporter ) { MultipleReporters* multi = existingReporter->tryAsMulti(); if( !multi ) { multi = new MultipleReporters; resultingReporter = Ptr( multi ); if( existingReporter ) multi->add( existingReporter ); } else resultingReporter = existingReporter; multi->add( additionalReporter ); } else resultingReporter = additionalReporter; return resultingReporter; } } // end namespace Catch // #included from: ../reporters/catch_reporter_xml.hpp #define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED // #included from: catch_reporter_bases.hpp #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED #include namespace Catch { struct StreamingReporterBase : SharedImpl { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; } virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporterPrefs; } virtual ~StreamingReporterBase() CATCH_OVERRIDE; virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { currentTestRunInfo = _testRunInfo; } virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { currentGroupInfo = _groupInfo; } virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { currentTestCaseInfo = _testInfo; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_sectionStack.push_back( _sectionInfo ); } virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); } virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { currentGroupInfo.reset(); } virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { // Don't do anything with this by default. // It can optionally be overridden in the derived class. } Ptr m_config; std::ostream& stream; LazyStat currentTestRunInfo; LazyStat currentGroupInfo; LazyStat currentTestCaseInfo; std::vector m_sectionStack; ReporterPreferences m_reporterPrefs; }; struct CumulativeReporterBase : SharedImpl { template struct Node : SharedImpl<> { explicit Node( T const& _value ) : value( _value ) {} virtual ~Node() {} typedef std::vector > ChildNodes; T value; ChildNodes children; }; struct SectionNode : SharedImpl<> { explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} virtual ~SectionNode(); bool operator == ( SectionNode const& other ) const { return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; } bool operator == ( Ptr const& other ) const { return operator==( *other ); } SectionStats stats; typedef std::vector > ChildSections; typedef std::vector Assertions; ChildSections childSections; Assertions assertions; std::string stdOut; std::string stdErr; }; struct BySectionInfo { BySectionInfo( SectionInfo const& other ) : m_other( other ) {} BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} bool operator() ( Ptr const& node ) const { return node->stats.sectionInfo.lineInfo == m_other.lineInfo; } private: void operator=( BySectionInfo const& ); SectionInfo const& m_other; }; typedef Node TestCaseNode; typedef Node TestGroupNode; typedef Node TestRunNode; CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; } ~CumulativeReporterBase(); virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporterPrefs; } virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); Ptr node; if( m_sectionStack.empty() ) { if( !m_rootSection ) m_rootSection = new SectionNode( incompleteStats ); node = m_rootSection; } else { SectionNode& parentNode = *m_sectionStack.back(); SectionNode::ChildSections::const_iterator it = std::find_if( parentNode.childSections.begin(), parentNode.childSections.end(), BySectionInfo( sectionInfo ) ); if( it == parentNode.childSections.end() ) { node = new SectionNode( incompleteStats ); parentNode.childSections.push_back( node ); } else node = *it; } m_sectionStack.push_back( node ); m_deepestSection = node; } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { Ptr node = new TestCaseNode( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); m_testCases.push_back( node ); m_rootSection.reset(); assert( m_deepestSection ); m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { Ptr node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { Ptr node = new TestRunNode( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); testRunEndedCumulative(); } virtual void testRunEndedCumulative() = 0; virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} Ptr m_config; std::ostream& stream; std::vector m_assertions; std::vector > > m_sections; std::vector > m_testCases; std::vector > m_testGroups; std::vector > m_testRuns; Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; ReporterPreferences m_reporterPrefs; }; template char const* getLineOfChars() { static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; if( !*line ) { memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; } return line; } struct TestEventListenerBase : StreamingReporterBase { TestEventListenerBase( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { return false; } }; } // end namespace Catch // #included from: ../internal/catch_reporter_registrars.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED namespace Catch { template class LegacyReporterRegistrar { class ReporterFactory : public IReporterFactory { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new LegacyReporterAdapter( new T( config ) ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: LegacyReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ReporterRegistrar { class ReporterFactory : public SharedImpl { // *** Please Note ***: // - If you end up here looking at a compiler error because it's trying to register // your custom reporter class be aware that the native reporter interface has changed // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. // However please consider updating to the new interface as the old one is now // deprecated and will probably be removed quite soon! // Please contact me via github if you have any questions at all about this. // In fact, ideally, please contact me anyway to let me know you've hit this - as I have // no idea who is actually using custom reporters at all (possibly no-one!). // The new interface is designed to minimise exposure to interface changes in the future. virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: ReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ListenerRegistrar { class ListenerFactory : public SharedImpl { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return ""; } }; public: ListenerRegistrar() { getMutableRegistryHub().registerListener( new ListenerFactory() ); } }; } #define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include #include #include #include namespace Catch { class XmlEncode { public: enum ForWhat { ForTextNodes, ForAttributes }; XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) : m_str( str ), m_forWhat( forWhat ) {} void encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: http://www.w3.org/TR/xml/#syntax) for( std::size_t i = 0; i < m_str.size(); ++ i ) { char c = m_str[i]; switch( c ) { case '<': os << "<"; break; case '&': os << "&"; break; case '>': // See: http://www.w3.org/TR/xml/#syntax if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) os << ">"; else os << c; break; case '\"': if( m_forWhat == ForAttributes ) os << """; else os << c; break; default: // Escape control chars - based on contribution by @espenalb in PR #465 and // by @mrpi PR #588 if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) os << "&#x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast( c ) << ';'; else os << c; } } } friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { xmlEncode.encodeTo( os ); return os; } private: std::string m_str; ForWhat m_forWhat; }; class XmlWriter { public: class ScopedElement { public: ScopedElement( XmlWriter* writer ) : m_writer( writer ) {} ScopedElement( ScopedElement const& other ) : m_writer( other.m_writer ){ other.m_writer = CATCH_NULL; } ~ScopedElement() { if( m_writer ) m_writer->endElement(); } ScopedElement& writeText( std::string const& text, bool indent = true ) { m_writer->writeText( text, indent ); return *this; } template ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { m_writer->writeAttribute( name, attribute ); return *this; } private: mutable XmlWriter* m_writer; }; XmlWriter() : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &Catch::cout() ) { // We encode control characters, which requires // XML 1.1 // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 *m_os << "\n"; } XmlWriter( std::ostream& os ) : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &os ) { *m_os << "\n"; } ~XmlWriter() { while( !m_tags.empty() ) endElement(); } XmlWriter& startElement( std::string const& name ) { ensureTagClosed(); newlineIfNecessary(); stream() << m_indent << "<" << name; m_tags.push_back( name ); m_indent += " "; m_tagIsOpen = true; return *this; } ScopedElement scopedElement( std::string const& name ) { ScopedElement scoped( this ); startElement( name ); return scoped; } XmlWriter& endElement() { newlineIfNecessary(); m_indent = m_indent.substr( 0, m_indent.size()-2 ); if( m_tagIsOpen ) { stream() << "/>\n"; m_tagIsOpen = false; } else { stream() << m_indent << "\n"; } m_tags.pop_back(); return *this; } XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { if( !name.empty() && !attribute.empty() ) stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\""; return *this; } XmlWriter& writeAttribute( std::string const& name, bool attribute ) { stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; return *this; } template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { std::ostringstream oss; oss << attribute; return writeAttribute( name, oss.str() ); } XmlWriter& writeText( std::string const& text, bool indent = true ) { if( !text.empty() ){ bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); if( tagWasOpen && indent ) stream() << m_indent; stream() << XmlEncode( text ); m_needsNewline = true; } return *this; } XmlWriter& writeComment( std::string const& text ) { ensureTagClosed(); stream() << m_indent << ""; m_needsNewline = true; return *this; } XmlWriter& writeBlankLine() { ensureTagClosed(); stream() << "\n"; return *this; } void setStream( std::ostream& os ) { m_os = &os; } private: XmlWriter( XmlWriter const& ); void operator=( XmlWriter const& ); std::ostream& stream() { return *m_os; } void ensureTagClosed() { if( m_tagIsOpen ) { stream() << ">\n"; m_tagIsOpen = false; } } void newlineIfNecessary() { if( m_needsNewline ) { stream() << "\n"; m_needsNewline = false; } } bool m_tagIsOpen; bool m_needsNewline; std::vector m_tags; std::string m_indent; std::ostream* m_os; }; } // #included from: catch_reenable_warnings.h #define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(pop) # else # pragma clang diagnostic pop # endif #elif defined __GNUC__ # pragma GCC diagnostic pop #endif namespace Catch { class XmlReporter : public StreamingReporterBase { public: XmlReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_xml(_config.stream()), m_sectionDepth( 0 ) { m_reporterPrefs.shouldRedirectStdOut = true; } virtual ~XmlReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as an XML document"; } public: // StreamingReporterBase virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { StreamingReporterBase::noMatchingTestCases( s ); } virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testRunStarting( testInfo ); m_xml.startElement( "Catch" ); if( !m_config->name().empty() ) m_xml.writeAttribute( "name", m_config->name() ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { StreamingReporterBase::testGroupStarting( groupInfo ); m_xml.startElement( "Group" ) .writeAttribute( "name", groupInfo.name ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testCaseStarting(testInfo); m_xml.startElement( "TestCase" ).writeAttribute( "name", testInfo.name ); if ( m_config->showDurations() == ShowDurations::Always ) m_testCaseTimer.start(); } virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) .writeAttribute( "name", trim( sectionInfo.name ) ) .writeAttribute( "description", sectionInfo.description ); } } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { const AssertionResult& assertionResult = assertionStats.assertionResult; // Print any info messages in tags. if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { m_xml.scopedElement( "Info" ) .writeText( it->message ); } else if ( it->type == ResultWas::Warning ) { m_xml.scopedElement( "Warning" ) .writeText( it->message ); } } } // Drop out if result was successful but we're not printing them. if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) return true; // Print the expression if there is one. if( assertionResult.hasExpression() ) { m_xml.startElement( "Expression" ) .writeAttribute( "success", assertionResult.succeeded() ) .writeAttribute( "type", assertionResult.getTestMacroName() ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ); m_xml.scopedElement( "Original" ) .writeText( assertionResult.getExpression() ); m_xml.scopedElement( "Expanded" ) .writeText( assertionResult.getExpandedExpression() ); } // And... Print a result applicable to each result type. switch( assertionResult.getResultType() ) { case ResultWas::ThrewException: m_xml.scopedElement( "Exception" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); break; case ResultWas::FatalErrorCondition: m_xml.scopedElement( "FatalErrorCondition" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); break; case ResultWas::Info: m_xml.scopedElement( "Info" ) .writeText( assertionResult.getMessage() ); break; case ResultWas::Warning: // Warning will already have been written break; case ResultWas::ExplicitFailure: m_xml.scopedElement( "Failure" ) .writeText( assertionResult.getMessage() ); break; default: break; } if( assertionResult.hasExpression() ) m_xml.endElement(); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { StreamingReporterBase::sectionEnded( sectionStats ); if( --m_sectionDepth > 0 ) { XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); e.writeAttribute( "successes", sectionStats.assertions.passed ); e.writeAttribute( "failures", sectionStats.assertions.failed ); e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); m_xml.endElement(); } } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( testCaseStats ); XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); m_xml.endElement(); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { StreamingReporterBase::testGroupEnded( testGroupStats ); // TODO: Check testGroupStats.aborting and act accordingly. m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); m_xml.endElement(); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { StreamingReporterBase::testRunEnded( testRunStats ); m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testRunStats.totals.assertions.passed ) .writeAttribute( "failures", testRunStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); m_xml.endElement(); } private: Timer m_testCaseTimer; XmlWriter m_xml; int m_sectionDepth; }; INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_junit.hpp #define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED #include namespace Catch { namespace { std::string getCurrentTimestamp() { // Beware, this is not reentrant because of backward compatibility issues // Also, UTC only, again because of backward compatibility (%z is C++11) time_t rawtime; std::time(&rawtime); const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z"); #ifdef CATCH_PLATFORM_WINDOWS std::tm timeInfo = {}; gmtime_s(&timeInfo, &rawtime); #else std::tm* timeInfo; timeInfo = std::gmtime(&rawtime); #endif char timeStamp[timeStampSize]; const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; #ifdef CATCH_PLATFORM_WINDOWS std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); #else std::strftime(timeStamp, timeStampSize, fmt, timeInfo); #endif return std::string(timeStamp); } } class JunitReporter : public CumulativeReporterBase { public: JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), xml( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = true; } virtual ~JunitReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { suiteTimer.start(); stdOutForSuite.str(""); stdErrForSuite.str(""); unexpectedExceptions = 0; CumulativeReporterBase::testGroupStarting( groupInfo ); } virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) unexpectedExceptions++; return CumulativeReporterBase::assertionEnded( assertionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { stdOutForSuite << testCaseStats.stdOut; stdErrForSuite << testCaseStats.stdErr; CumulativeReporterBase::testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { double suiteTime = suiteTimer.getElapsedSeconds(); CumulativeReporterBase::testGroupEnded( testGroupStats ); writeGroup( *m_testGroups.back(), suiteTime ); } virtual void testRunEndedCumulative() CATCH_OVERRIDE { xml.endElement(); } void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); TestGroupStats const& stats = groupNode.value; xml.writeAttribute( "name", stats.groupInfo.name ); xml.writeAttribute( "errors", unexpectedExceptions ); xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); xml.writeAttribute( "tests", stats.totals.assertions.total() ); xml.writeAttribute( "hostname", "tbd" ); // !TBD if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else xml.writeAttribute( "time", suiteTime ); xml.writeAttribute( "timestamp", getCurrentTimestamp() ); // Write test cases for( TestGroupNode::ChildNodes::const_iterator it = groupNode.children.begin(), itEnd = groupNode.children.end(); it != itEnd; ++it ) writeTestCase( **it ); xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); } void writeTestCase( TestCaseNode const& testCaseNode ) { TestCaseStats const& stats = testCaseNode.value; // All test cases have exactly one section - which represents the // test case itself. That section may have 0-n nested sections assert( testCaseNode.children.size() == 1 ); SectionNode const& rootSection = *testCaseNode.children.front(); std::string className = stats.testInfo.className; if( className.empty() ) { if( rootSection.childSections.empty() ) className = "global"; } writeSection( className, "", rootSection ); } void writeSection( std::string const& className, std::string const& rootName, SectionNode const& sectionNode ) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + "/" + name; if( !sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty() ) { XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); if( className.empty() ) { xml.writeAttribute( "classname", name ); xml.writeAttribute( "name", "root" ); } else { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); writeAssertions( sectionNode ); if( !sectionNode.stdOut.empty() ) xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); if( !sectionNode.stdErr.empty() ) xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); } for( SectionNode::ChildSections::const_iterator it = sectionNode.childSections.begin(), itEnd = sectionNode.childSections.end(); it != itEnd; ++it ) if( className.empty() ) writeSection( name, "", **it ); else writeSection( className, name, **it ); } void writeAssertions( SectionNode const& sectionNode ) { for( SectionNode::Assertions::const_iterator it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); it != itEnd; ++it ) writeAssertion( *it ); } void writeAssertion( AssertionStats const& stats ) { AssertionResult const& result = stats.assertionResult; if( !result.isOk() ) { std::string elementName; switch( result.getResultType() ) { case ResultWas::ThrewException: case ResultWas::FatalErrorCondition: elementName = "error"; break; case ResultWas::ExplicitFailure: elementName = "failure"; break; case ResultWas::ExpressionFailed: elementName = "failure"; break; case ResultWas::DidntThrowException: elementName = "failure"; break; // We should never see these here: case ResultWas::Info: case ResultWas::Warning: case ResultWas::Ok: case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: elementName = "internalError"; break; } XmlWriter::ScopedElement e = xml.scopedElement( elementName ); xml.writeAttribute( "message", result.getExpandedExpression() ); xml.writeAttribute( "type", result.getTestMacroName() ); std::ostringstream oss; if( !result.getMessage().empty() ) oss << result.getMessage() << "\n"; for( std::vector::const_iterator it = stats.infoMessages.begin(), itEnd = stats.infoMessages.end(); it != itEnd; ++it ) if( it->type == ResultWas::Info ) oss << it->message << "\n"; oss << "at " << result.getSourceInfo(); xml.writeText( oss.str(), false ); } } XmlWriter xml; Timer suiteTimer; std::ostringstream stdOutForSuite; std::ostringstream stdErrForSuite; unsigned int unexpectedExceptions; }; INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_console.hpp #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED namespace Catch { struct ConsoleReporter : StreamingReporterBase { ConsoleReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_headerPrinted( false ) {} virtual ~ConsoleReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as plain lines of text"; } virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } lazyPrint(); AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); if( m_sectionStack.size() > 1 ) stream << "\nNo assertions in section"; else stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } if( m_headerPrinted ) { if( m_config->showDurations() == ShowDurations::Always ) stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; m_headerPrinted = false; } else { if( m_config->showDurations() == ShowDurations::Always ) stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; } StreamingReporterBase::sectionEnded( _sectionStats ); } virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); stream << "\n" << std::endl; } StreamingReporterBase::testGroupEnded( _testGroupStats ); } virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ), stats( _stats ), result( _stats.assertionResult ), colour( Colour::None ), message( result.getMessage() ), messages( _stats.infoMessages ), printInfoMessages( _printInfoMessages ) { switch( result.getResultType() ) { case ResultWas::Ok: colour = Colour::Success; passOrFail = "PASSED"; //if( result.hasMessage() ) if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ExpressionFailed: if( result.isOk() ) { colour = Colour::Success; passOrFail = "FAILED - but was ok"; } else { colour = Colour::Error; passOrFail = "FAILED"; } if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ThrewException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to unexpected exception with message"; break; case ResultWas::FatalErrorCondition: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to a fatal error condition"; break; case ResultWas::DidntThrowException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "because no exception was thrown where one was expected"; break; case ResultWas::Info: messageLabel = "info"; break; case ResultWas::Warning: messageLabel = "warning"; break; case ResultWas::ExplicitFailure: passOrFail = "FAILED"; colour = Colour::Error; if( _stats.infoMessages.size() == 1 ) messageLabel = "explicitly with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "explicitly with messages"; break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: passOrFail = "** internal error **"; colour = Colour::Error; break; } } void print() const { printSourceInfo(); if( stats.totals.assertions.total() > 0 ) { if( result.isOk() ) stream << "\n"; printResultType(); printOriginalExpression(); printReconstructedExpression(); } else { stream << "\n"; } printMessage(); } private: void printResultType() const { if( !passOrFail.empty() ) { Colour colourGuard( colour ); stream << passOrFail << ":\n"; } } void printOriginalExpression() const { if( result.hasExpression() ) { Colour colourGuard( Colour::OriginalExpression ); stream << " "; stream << result.getExpressionInMacro(); stream << "\n"; } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { stream << "with expansion:\n"; Colour colourGuard( Colour::ReconstructedExpression ); stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; } } void printMessage() const { if( !messageLabel.empty() ) stream << messageLabel << ":" << "\n"; for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); it != itEnd; ++it ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || it->type != ResultWas::Info ) stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; } } void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ": "; } std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; Colour::Code colour; std::string passOrFail; std::string messageLabel; std::string message; std::vector messages; bool printInfoMessages; }; void lazyPrint() { if( !currentTestRunInfo.used ) lazyPrintRunInfo(); if( !currentGroupInfo.used ) lazyPrintGroupInfo(); if( !m_headerPrinted ) { printTestCaseAndSectionHeader(); m_headerPrinted = true; } } void lazyPrintRunInfo() { stream << "\n" << getLineOfChars<'~'>() << "\n"; Colour colour( Colour::SecondaryText ); stream << currentTestRunInfo->name << " is a Catch v" << libraryVersion << " host application.\n" << "Run with -? for options\n\n"; if( m_config->rngSeed() != 0 ) stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; currentTestRunInfo.used = true; } void lazyPrintGroupInfo() { if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { printClosedHeader( "Group: " + currentGroupInfo->name ); currentGroupInfo.used = true; } } void printTestCaseAndSectionHeader() { assert( !m_sectionStack.empty() ); printOpenHeader( currentTestCaseInfo->name ); if( m_sectionStack.size() > 1 ) { Colour colourGuard( Colour::Headers ); std::vector::const_iterator it = m_sectionStack.begin()+1, // Skip first section (test case) itEnd = m_sectionStack.end(); for( ; it != itEnd; ++it ) printHeaderString( it->name, 2 ); } SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; if( !lineInfo.empty() ){ stream << getLineOfChars<'-'>() << "\n"; Colour colourGuard( Colour::FileName ); stream << lineInfo << "\n"; } stream << getLineOfChars<'.'>() << "\n" << std::endl; } void printClosedHeader( std::string const& _name ) { printOpenHeader( _name ); stream << getLineOfChars<'.'>() << "\n"; } void printOpenHeader( std::string const& _name ) { stream << getLineOfChars<'-'>() << "\n"; { Colour colourGuard( Colour::Headers ); printHeaderString( _name ); } } // if string has a : in first line will set indent to follow it on // subsequent lines void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { std::size_t i = _string.find( ": " ); if( i != std::string::npos ) i+=2; else i = 0; stream << Text( _string, TextAttributes() .setIndent( indent+i) .setInitialIndent( indent ) ) << "\n"; } struct SummaryColumn { SummaryColumn( std::string const& _label, Colour::Code _colour ) : label( _label ), colour( _colour ) {} SummaryColumn addRow( std::size_t count ) { std::ostringstream oss; oss << count; std::string row = oss.str(); for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { while( it->size() < row.size() ) *it = " " + *it; while( it->size() > row.size() ) row = " " + row; } rows.push_back( row ); return *this; } std::string label; Colour::Code colour; std::vector rows; }; void printTotals( Totals const& totals ) { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " << pluralise( totals.testCases.passed, "test case" ) << ")" << "\n"; } else { std::vector columns; columns.push_back( SummaryColumn( "", Colour::None ) .addRow( totals.testCases.total() ) .addRow( totals.assertions.total() ) ); columns.push_back( SummaryColumn( "passed", Colour::Success ) .addRow( totals.testCases.passed ) .addRow( totals.assertions.passed ) ); columns.push_back( SummaryColumn( "failed", Colour::ResultError ) .addRow( totals.testCases.failed ) .addRow( totals.assertions.failed ) ); columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) .addRow( totals.testCases.failedButOk ) .addRow( totals.assertions.failedButOk ) ); printSummaryRow( "test cases", columns, 0 ); printSummaryRow( "assertions", columns, 1 ); } } void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { std::string value = it->rows[row]; if( it->label.empty() ) { stream << label << ": "; if( value != "0" ) stream << value; else stream << Colour( Colour::Warning ) << "- none -"; } else if( value != "0" ) { stream << Colour( Colour::LightGrey ) << " | "; stream << Colour( it->colour ) << value << " " << it->label; } } stream << "\n"; } static std::size_t makeRatio( std::size_t number, std::size_t total ) { std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; return ( ratio == 0 && number > 0 ) ? 1 : ratio; } static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { if( i > j && i > k ) return i; else if( j > k ) return j; else return k; } void printTotalsDivider( Totals const& totals ) { if( totals.testCases.total() > 0 ) { std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )++; while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )--; stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); if( totals.testCases.allPassed() ) stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); else stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); } else { stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); } stream << "\n"; } void printSummaryDivider() { stream << getLineOfChars<'-'>() << "\n"; } private: bool m_headerPrinted; }; INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_compact.hpp #define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED namespace Catch { struct CompactReporter : StreamingReporterBase { CompactReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual ~CompactReporter(); static std::string getDescription() { return "Reports test results on a single line, suitable for IDEs"; } virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; } virtual void noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void testRunEnded( TestRunStats const& _testRunStats ) { printTotals( _testRunStats.totals ); stream << "\n" << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ) , stats( _stats ) , result( _stats.assertionResult ) , messages( _stats.infoMessages ) , itMessage( _stats.infoMessages.begin() ) , printInfoMessages( _printInfoMessages ) {} void print() { printSourceInfo(); itMessage = messages.begin(); switch( result.getResultType() ) { case ResultWas::Ok: printResultType( Colour::ResultSuccess, passedString() ); printOriginalExpression(); printReconstructedExpression(); if ( ! result.hasExpression() ) printRemainingMessages( Colour::None ); else printRemainingMessages(); break; case ResultWas::ExpressionFailed: if( result.isOk() ) printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); else printResultType( Colour::Error, failedString() ); printOriginalExpression(); printReconstructedExpression(); printRemainingMessages(); break; case ResultWas::ThrewException: printResultType( Colour::Error, failedString() ); printIssue( "unexpected exception with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::FatalErrorCondition: printResultType( Colour::Error, failedString() ); printIssue( "fatal error condition with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::DidntThrowException: printResultType( Colour::Error, failedString() ); printIssue( "expected exception, got none" ); printExpressionWas(); printRemainingMessages(); break; case ResultWas::Info: printResultType( Colour::None, "info" ); printMessage(); printRemainingMessages(); break; case ResultWas::Warning: printResultType( Colour::None, "warning" ); printMessage(); printRemainingMessages(); break; case ResultWas::ExplicitFailure: printResultType( Colour::Error, failedString() ); printIssue( "explicitly" ); printRemainingMessages( Colour::None ); break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: printResultType( Colour::Error, "** internal error **" ); break; } } private: // Colour::LightGrey static Colour::Code dimColour() { return Colour::FileName; } #ifdef CATCH_PLATFORM_MAC static const char* failedString() { return "FAILED"; } static const char* passedString() { return "PASSED"; } #else static const char* failedString() { return "failed"; } static const char* passedString() { return "passed"; } #endif void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ":"; } void printResultType( Colour::Code colour, std::string passOrFail ) const { if( !passOrFail.empty() ) { { Colour colourGuard( colour ); stream << " " << passOrFail; } stream << ":"; } } void printIssue( std::string issue ) const { stream << " " << issue; } void printExpressionWas() { if( result.hasExpression() ) { stream << ";"; { Colour colour( dimColour() ); stream << " expression was:"; } printOriginalExpression(); } } void printOriginalExpression() const { if( result.hasExpression() ) { stream << " " << result.getExpression(); } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { { Colour colour( dimColour() ); stream << " for: "; } stream << result.getExpandedExpression(); } } void printMessage() { if ( itMessage != messages.end() ) { stream << " '" << itMessage->message << "'"; ++itMessage; } } void printRemainingMessages( Colour::Code colour = dimColour() ) { if ( itMessage == messages.end() ) return; // using messages.end() directly yields compilation error: std::vector::const_iterator itEnd = messages.end(); const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); { Colour colourGuard( colour ); stream << " with " << pluralise( N, "message" ) << ":"; } for(; itMessage != itEnd; ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || itMessage->type != ResultWas::Info ) { stream << " '" << itMessage->message << "'"; if ( ++itMessage != itEnd ) { Colour colourGuard( dimColour() ); stream << " and"; } } } } private: std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; std::vector messages; std::vector::const_iterator itMessage; bool printInfoMessages; }; // Colour, message variants: // - white: No tests ran. // - red: Failed [both/all] N test cases, failed [both/all] M assertions. // - white: Passed [both/all] N test cases (no assertions). // - red: Failed N tests cases, failed M assertions. // - green: Passed [both/all] N tests cases with M assertions. std::string bothOrAll( std::size_t count ) const { return count == 1 ? "" : count == 2 ? "both " : "all " ; } void printTotals( const Totals& totals ) const { if( totals.testCases.total() == 0 ) { stream << "No tests ran."; } else if( totals.testCases.failed == totals.testCases.total() ) { Colour colour( Colour::ResultError ); const std::string qualify_assertions_failed = totals.assertions.failed == totals.assertions.total() ? bothOrAll( totals.assertions.failed ) : ""; stream << "Failed " << bothOrAll( totals.testCases.failed ) << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << qualify_assertions_failed << pluralise( totals.assertions.failed, "assertion" ) << "."; } else if( totals.assertions.total() == 0 ) { stream << "Passed " << bothOrAll( totals.testCases.total() ) << pluralise( totals.testCases.total(), "test case" ) << " (no assertions)."; } else if( totals.assertions.failed ) { Colour colour( Colour::ResultError ); stream << "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; } else { Colour colour( Colour::ResultSuccess ); stream << "Passed " << bothOrAll( totals.testCases.passed ) << pluralise( totals.testCases.passed, "test case" ) << " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; } } }; INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) } // end namespace Catch namespace Catch { // These are all here to avoid warnings about not having any out of line // virtual methods NonCopyable::~NonCopyable() {} IShared::~IShared() {} IStream::~IStream() CATCH_NOEXCEPT {} FileStream::~FileStream() CATCH_NOEXCEPT {} CoutStream::~CoutStream() CATCH_NOEXCEPT {} DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} IContext::~IContext() {} IResultCapture::~IResultCapture() {} ITestCase::~ITestCase() {} ITestCaseRegistry::~ITestCaseRegistry() {} IRegistryHub::~IRegistryHub() {} IMutableRegistryHub::~IMutableRegistryHub() {} IExceptionTranslator::~IExceptionTranslator() {} IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} IReporter::~IReporter() {} IReporterFactory::~IReporterFactory() {} IReporterRegistry::~IReporterRegistry() {} IStreamingReporter::~IStreamingReporter() {} AssertionStats::~AssertionStats() {} SectionStats::~SectionStats() {} TestCaseStats::~TestCaseStats() {} TestGroupStats::~TestGroupStats() {} TestRunStats::~TestRunStats() {} CumulativeReporterBase::SectionNode::~SectionNode() {} CumulativeReporterBase::~CumulativeReporterBase() {} StreamingReporterBase::~StreamingReporterBase() {} ConsoleReporter::~ConsoleReporter() {} CompactReporter::~CompactReporter() {} IRunner::~IRunner() {} IMutableContext::~IMutableContext() {} IConfig::~IConfig() {} XmlReporter::~XmlReporter() {} JunitReporter::~JunitReporter() {} TestRegistry::~TestRegistry() {} FreeFunctionTestCase::~FreeFunctionTestCase() {} IGeneratorInfo::~IGeneratorInfo() {} IGeneratorsForTest::~IGeneratorsForTest() {} WildcardPattern::~WildcardPattern() {} TestSpec::Pattern::~Pattern() {} TestSpec::NamePattern::~NamePattern() {} TestSpec::TagPattern::~TagPattern() {} TestSpec::ExcludedPattern::~ExcludedPattern() {} Matchers::Impl::StdString::Equals::~Equals() {} Matchers::Impl::StdString::Contains::~Contains() {} Matchers::Impl::StdString::StartsWith::~StartsWith() {} Matchers::Impl::StdString::EndsWith::~EndsWith() {} void Config::dummy() {} namespace TestCaseTracking { ITracker::~ITracker() {} TrackerBase::~TrackerBase() {} SectionTracker::~SectionTracker() {} IndexTracker::~IndexTracker() {} } } #ifdef __clang__ #pragma clang diagnostic pop #endif #endif #ifdef CATCH_CONFIG_MAIN // #included from: internal/catch_default_main.hpp #define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED #ifndef __OBJC__ // Standard C/C++ main entry point int main (int argc, char * argv[]) { return Catch::Session().run( argc, argv ); } #else // __OBJC__ // Objective-C entry point int main (int argc, char * const argv[]) { #if !CATCH_ARC_ENABLED NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; #endif Catch::registerTestMethods(); int result = Catch::Session().run( argc, (char* const*)argv ); #if !CATCH_ARC_ENABLED [pool drain]; #endif return result; } #endif // __OBJC__ #endif #ifdef CLARA_CONFIG_MAIN_NOT_DEFINED # undef CLARA_CONFIG_MAIN #endif ////// // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL #define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) #define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) #define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" ) #define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) #define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) #define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) #define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) #define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) #define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) #define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CATCH_CHECK_THROWS" ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" ) #define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) #define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) #define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) #else #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) #endif #define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) #define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) #define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) #define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) #define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) #define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else #define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) #define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) #define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" ) #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) #define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" ) #define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) #define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) #define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) #define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) #define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) #define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) #define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" ) #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) #define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" ) #define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) #define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) #define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) #else #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) #endif #define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) #endif #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) #define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) #define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) #define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) #define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) using Catch::Detail::Approx; #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED ================================================ FILE: third-party/rapidjson/LICENSE.txt ================================================ Tencent is pleased to support the open source community by making RapidJSON available. Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. A copy of the MIT License is included in this file. Other dependencies and licenses: Open Source Software Licensed Under the BSD License: -------------------------------------------------------------------- The msinttypes r29 Copyright (c) 2006-2013 Alexander Chemeris All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Open Source Software Licensed Under the JSON License: -------------------------------------------------------------------- json.org Copyright (c) 2002 JSON.org All Rights Reserved. JSON_checker Copyright (c) 2002 JSON.org All Rights Reserved. Terms of the JSON License: --------------------------------------------------- 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 shall be used for Good, not Evil. 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. Terms of the MIT License: -------------------------------------------------------------------- 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. ================================================ FILE: third-party/rapidjson/allocators.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_ALLOCATORS_H_ #define RAPIDJSON_ALLOCATORS_H_ #include "rapidjson.h" RAPIDJSON_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// // Allocator /*! \class rapidjson::Allocator \brief Concept for allocating, resizing and freeing memory block. Note that Malloc() and Realloc() are non-static but Free() is static. So if an allocator need to support Free(), it needs to put its pointer in the header of memory block. \code concept Allocator { static const bool kNeedFree; //!< Whether this allocator needs to call Free(). // Allocate a memory block. // \param size of the memory block in bytes. // \returns pointer to the memory block. void* Malloc(size_t size); // Resize a memory block. // \param originalPtr The pointer to current memory block. Null pointer is permitted. // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) // \param newSize the new size in bytes. void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); // Free a memory block. // \param pointer to the memory block. Null pointer is permitted. static void Free(void *ptr); }; \endcode */ /////////////////////////////////////////////////////////////////////////////// // CrtAllocator //! C-runtime library allocator. /*! This class is just wrapper for standard C library memory routines. \note implements Allocator concept */ class CrtAllocator { public: static const bool kNeedFree = true; void* Malloc(size_t size) { if (size) // behavior of malloc(0) is implementation defined. return std::malloc(size); else return NULL; // standardize to returning NULL. } void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { (void)originalSize; if (newSize == 0) { std::free(originalPtr); return NULL; } return std::realloc(originalPtr, newSize); } static void Free(void *ptr) { std::free(ptr); } }; /////////////////////////////////////////////////////////////////////////////// // MemoryPoolAllocator //! Default memory allocator used by the parser and DOM. /*! This allocator allocate memory blocks from pre-allocated memory chunks. It does not free memory blocks. And Realloc() only allocate new memory. The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. User may also supply a buffer as the first chunk. If the user-buffer is full then additional chunks are allocated by BaseAllocator. The user-buffer is not deallocated by this allocator. \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. \note implements Allocator concept */ template class MemoryPoolAllocator { public: static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) //! Constructor with chunkSize. /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. \param baseAllocator The allocator for allocating memory chunks. */ MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) { } //! Constructor with user-supplied buffer. /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. The user buffer will not be deallocated when this allocator is destructed. \param buffer User supplied buffer. \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. \param baseAllocator The allocator for allocating memory chunks. */ MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) { RAPIDJSON_ASSERT(buffer != 0); RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); chunkHead_ = reinterpret_cast(buffer); chunkHead_->capacity = size - sizeof(ChunkHeader); chunkHead_->size = 0; chunkHead_->next = 0; } //! Destructor. /*! This deallocates all memory chunks, excluding the user-supplied buffer. */ ~MemoryPoolAllocator() { Clear(); RAPIDJSON_DELETE(ownBaseAllocator_); } //! Deallocates all memory chunks, excluding the user-supplied buffer. void Clear() { while (chunkHead_ && chunkHead_ != userBuffer_) { ChunkHeader* next = chunkHead_->next; baseAllocator_->Free(chunkHead_); chunkHead_ = next; } if (chunkHead_ && chunkHead_ == userBuffer_) chunkHead_->size = 0; // Clear user buffer } //! Computes the total capacity of allocated memory chunks. /*! \return total capacity in bytes. */ size_t Capacity() const { size_t capacity = 0; for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) capacity += c->capacity; return capacity; } //! Computes the memory blocks allocated. /*! \return total used bytes. */ size_t Size() const { size_t size = 0; for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) size += c->size; return size; } //! Allocates a memory block. (concept Allocator) void* Malloc(size_t size) { if (!size) return NULL; size = RAPIDJSON_ALIGN(size); if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size)) return NULL; void *buffer = reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; chunkHead_->size += size; return buffer; } //! Resizes a memory block (concept Allocator) void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { if (originalPtr == 0) return Malloc(newSize); if (newSize == 0) return NULL; originalSize = RAPIDJSON_ALIGN(originalSize); newSize = RAPIDJSON_ALIGN(newSize); // Do not shrink if new size is smaller than original if (originalSize >= newSize) return originalPtr; // Simply expand it if it is the last allocation and there is sufficient space if (originalPtr == reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { size_t increment = static_cast(newSize - originalSize); if (chunkHead_->size + increment <= chunkHead_->capacity) { chunkHead_->size += increment; return originalPtr; } } // Realloc process: allocate and copy memory, do not free original buffer. if (void* newBuffer = Malloc(newSize)) { if (originalSize) std::memcpy(newBuffer, originalPtr, originalSize); return newBuffer; } else return NULL; } //! Frees a memory block (concept Allocator) static void Free(void *ptr) { (void)ptr; } // Do nothing private: //! Copy constructor is not permitted. MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */; //! Copy assignment operator is not permitted. MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */; //! Creates a new chunk. /*! \param capacity Capacity of the chunk in bytes. \return true if success. */ bool AddChunk(size_t capacity) { if (!baseAllocator_) ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator)(); if (ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity))) { chunk->capacity = capacity; chunk->size = 0; chunk->next = chunkHead_; chunkHead_ = chunk; return true; } else return false; } static const int kDefaultChunkCapacity = 64 * 1024; //!< Default chunk capacity. //! Chunk header for perpending to each chunk. /*! Chunks are stored as a singly linked list. */ struct ChunkHeader { size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). size_t size; //!< Current size of allocated memory in bytes. ChunkHeader *next; //!< Next chunk in the linked list. }; ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. void *userBuffer_; //!< User supplied buffer. BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. }; RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_ENCODINGS_H_ ================================================ FILE: third-party/rapidjson/document.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_DOCUMENT_H_ #define RAPIDJSON_DOCUMENT_H_ /*! \file document.h */ #include "reader.h" #include "internal/meta.h" #include "internal/strfunc.h" #include "memorystream.h" #include "encodedstream.h" #include // placement new #include RAPIDJSON_DIAG_PUSH #ifdef _MSC_VER RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant RAPIDJSON_DIAG_OFF(4244) // conversion from kXxxFlags to 'uint16_t', possible loss of data #ifdef _MINWINDEF_ // see: http://stackoverflow.com/questions/22744262/cant-call-stdmax-because-minwindef-h-defines-max #ifndef NOMINMAX #pragma push_macro("min") #pragma push_macro("max") #undef min #undef max #endif #endif #endif #ifdef __clang__ RAPIDJSON_DIAG_OFF(padded) RAPIDJSON_DIAG_OFF(switch-enum) RAPIDJSON_DIAG_OFF(c++98-compat) #endif #ifdef __GNUC__ RAPIDJSON_DIAG_OFF(effc++) #if __GNUC__ >= 6 RAPIDJSON_DIAG_OFF(terminate) // ignore throwing RAPIDJSON_ASSERT in RAPIDJSON_NOEXCEPT functions #endif #endif // __GNUC__ #ifndef RAPIDJSON_NOMEMBERITERATORCLASS #include // std::iterator, std::random_access_iterator_tag #endif #if RAPIDJSON_HAS_CXX11_RVALUE_REFS #include // std::move #endif RAPIDJSON_NAMESPACE_BEGIN // Forward declaration. template class GenericValue; template class GenericDocument; //! Name-value pair in a JSON object value. /*! This class was internal to GenericValue. It used to be a inner struct. But a compiler (IBM XL C/C++ for AIX) have reported to have problem with that so it moved as a namespace scope struct. https://code.google.com/p/rapidjson/issues/detail?id=64 */ template struct GenericMember { GenericValue name; //!< name of member (must be a string) GenericValue value; //!< value of member. }; /////////////////////////////////////////////////////////////////////////////// // GenericMemberIterator #ifndef RAPIDJSON_NOMEMBERITERATORCLASS //! (Constant) member iterator for a JSON object value /*! \tparam Const Is this a constant iterator? \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) \tparam Allocator Allocator type for allocating memory of object, array and string. This class implements a Random Access Iterator for GenericMember elements of a GenericValue, see ISO/IEC 14882:2003(E) C++ standard, 24.1 [lib.iterator.requirements]. \note This iterator implementation is mainly intended to avoid implicit conversions from iterator values to \c NULL, e.g. from GenericValue::FindMember. \note Define \c RAPIDJSON_NOMEMBERITERATORCLASS to fall back to a pointer-based implementation, if your platform doesn't provide the C++ header. \see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator */ template class GenericMemberIterator : public std::iterator >::Type> { friend class GenericValue; template friend class GenericMemberIterator; typedef GenericMember PlainType; typedef typename internal::MaybeAddConst::Type ValueType; typedef std::iterator BaseType; public: //! Iterator type itself typedef GenericMemberIterator Iterator; //! Constant iterator type typedef GenericMemberIterator ConstIterator; //! Non-constant iterator type typedef GenericMemberIterator NonConstIterator; //! Pointer to (const) GenericMember typedef typename BaseType::pointer Pointer; //! Reference to (const) GenericMember typedef typename BaseType::reference Reference; //! Signed integer type (e.g. \c ptrdiff_t) typedef typename BaseType::difference_type DifferenceType; //! Default constructor (singular value) /*! Creates an iterator pointing to no element. \note All operations, except for comparisons, are undefined on such values. */ GenericMemberIterator() : ptr_() {} //! Iterator conversions to more const /*! \param it (Non-const) iterator to copy from Allows the creation of an iterator from another GenericMemberIterator that is "less const". Especially, creating a non-constant iterator from a constant iterator are disabled: \li const -> non-const (not ok) \li const -> const (ok) \li non-const -> const (ok) \li non-const -> non-const (ok) \note If the \c Const template parameter is already \c false, this constructor effectively defines a regular copy-constructor. Otherwise, the copy constructor is implicitly defined. */ GenericMemberIterator(const NonConstIterator & it) : ptr_(it.ptr_) {} Iterator& operator=(const NonConstIterator & it) { ptr_ = it.ptr_; return *this; } //! @name stepping //@{ Iterator& operator++(){ ++ptr_; return *this; } Iterator& operator--(){ --ptr_; return *this; } Iterator operator++(int){ Iterator old(*this); ++ptr_; return old; } Iterator operator--(int){ Iterator old(*this); --ptr_; return old; } //@} //! @name increment/decrement //@{ Iterator operator+(DifferenceType n) const { return Iterator(ptr_+n); } Iterator operator-(DifferenceType n) const { return Iterator(ptr_-n); } Iterator& operator+=(DifferenceType n) { ptr_+=n; return *this; } Iterator& operator-=(DifferenceType n) { ptr_-=n; return *this; } //@} //! @name relations //@{ bool operator==(ConstIterator that) const { return ptr_ == that.ptr_; } bool operator!=(ConstIterator that) const { return ptr_ != that.ptr_; } bool operator<=(ConstIterator that) const { return ptr_ <= that.ptr_; } bool operator>=(ConstIterator that) const { return ptr_ >= that.ptr_; } bool operator< (ConstIterator that) const { return ptr_ < that.ptr_; } bool operator> (ConstIterator that) const { return ptr_ > that.ptr_; } //@} //! @name dereference //@{ Reference operator*() const { return *ptr_; } Pointer operator->() const { return ptr_; } Reference operator[](DifferenceType n) const { return ptr_[n]; } //@} //! Distance DifferenceType operator-(ConstIterator that) const { return ptr_-that.ptr_; } private: //! Internal constructor from plain pointer explicit GenericMemberIterator(Pointer p) : ptr_(p) {} Pointer ptr_; //!< raw pointer }; #else // RAPIDJSON_NOMEMBERITERATORCLASS // class-based member iterator implementation disabled, use plain pointers template struct GenericMemberIterator; //! non-const GenericMemberIterator template struct GenericMemberIterator { //! use plain pointer as iterator type typedef GenericMember* Iterator; }; //! const GenericMemberIterator template struct GenericMemberIterator { //! use plain const pointer as iterator type typedef const GenericMember* Iterator; }; #endif // RAPIDJSON_NOMEMBERITERATORCLASS /////////////////////////////////////////////////////////////////////////////// // GenericStringRef //! Reference to a constant string (not taking a copy) /*! \tparam CharType character type of the string This helper class is used to automatically infer constant string references for string literals, especially from \c const \b (!) character arrays. The main use is for creating JSON string values without copying the source string via an \ref Allocator. This requires that the referenced string pointers have a sufficient lifetime, which exceeds the lifetime of the associated GenericValue. \b Example \code Value v("foo"); // ok, no need to copy & calculate length const char foo[] = "foo"; v.SetString(foo); // ok const char* bar = foo; // Value x(bar); // not ok, can't rely on bar's lifetime Value x(StringRef(bar)); // lifetime explicitly guaranteed by user Value y(StringRef(bar, 3)); // ok, explicitly pass length \endcode \see StringRef, GenericValue::SetString */ template struct GenericStringRef { typedef CharType Ch; //!< character type of the string //! Create string reference from \c const character array #ifndef __clang__ // -Wdocumentation /*! This constructor implicitly creates a constant string reference from a \c const character array. It has better performance than \ref StringRef(const CharType*) by inferring the string \ref length from the array length, and also supports strings containing null characters. \tparam N length of the string, automatically inferred \param str Constant character array, lifetime assumed to be longer than the use of the string in e.g. a GenericValue \post \ref s == str \note Constant complexity. \note There is a hidden, private overload to disallow references to non-const character arrays to be created via this constructor. By this, e.g. function-scope arrays used to be filled via \c snprintf are excluded from consideration. In such cases, the referenced string should be \b copied to the GenericValue instead. */ #endif template GenericStringRef(const CharType (&str)[N]) RAPIDJSON_NOEXCEPT : s(str), length(N-1) {} //! Explicitly create string reference from \c const character pointer #ifndef __clang__ // -Wdocumentation /*! This constructor can be used to \b explicitly create a reference to a constant string pointer. \see StringRef(const CharType*) \param str Constant character pointer, lifetime assumed to be longer than the use of the string in e.g. a GenericValue \post \ref s == str \note There is a hidden, private overload to disallow references to non-const character arrays to be created via this constructor. By this, e.g. function-scope arrays used to be filled via \c snprintf are excluded from consideration. In such cases, the referenced string should be \b copied to the GenericValue instead. */ #endif explicit GenericStringRef(const CharType* str) : s(str), length(internal::StrLen(str)){ RAPIDJSON_ASSERT(s != 0); } //! Create constant string reference from pointer and length #ifndef __clang__ // -Wdocumentation /*! \param str constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue \param len length of the string, excluding the trailing NULL terminator \post \ref s == str && \ref length == len \note Constant complexity. */ #endif GenericStringRef(const CharType* str, SizeType len) : s(str), length(len) { RAPIDJSON_ASSERT(s != 0); } GenericStringRef(const GenericStringRef& rhs) : s(rhs.s), length(rhs.length) {} //! implicit conversion to plain CharType pointer operator const Ch *() const { return s; } const Ch* const s; //!< plain CharType pointer const SizeType length; //!< length of the string (excluding the trailing NULL terminator) private: //! Disallow construction from non-const array template GenericStringRef(CharType (&str)[N]) /* = delete */; //! Copy assignment operator not permitted - immutable type GenericStringRef& operator=(const GenericStringRef& rhs) /* = delete */; }; //! Mark a character pointer as constant string /*! Mark a plain character pointer as a "string literal". This function can be used to avoid copying a character string to be referenced as a value in a JSON GenericValue object, if the string's lifetime is known to be valid long enough. \tparam CharType Character type of the string \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue \return GenericStringRef string reference object \relatesalso GenericStringRef \see GenericValue::GenericValue(StringRefType), GenericValue::operator=(StringRefType), GenericValue::SetString(StringRefType), GenericValue::PushBack(StringRefType, Allocator&), GenericValue::AddMember */ template inline GenericStringRef StringRef(const CharType* str) { return GenericStringRef(str, internal::StrLen(str)); } //! Mark a character pointer as constant string /*! Mark a plain character pointer as a "string literal". This function can be used to avoid copying a character string to be referenced as a value in a JSON GenericValue object, if the string's lifetime is known to be valid long enough. This version has better performance with supplied length, and also supports string containing null characters. \tparam CharType character type of the string \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue \param length The length of source string. \return GenericStringRef string reference object \relatesalso GenericStringRef */ template inline GenericStringRef StringRef(const CharType* str, size_t length) { return GenericStringRef(str, SizeType(length)); } #if RAPIDJSON_HAS_STDSTRING //! Mark a string object as constant string /*! Mark a string object (e.g. \c std::string) as a "string literal". This function can be used to avoid copying a string to be referenced as a value in a JSON GenericValue object, if the string's lifetime is known to be valid long enough. \tparam CharType character type of the string \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue \return GenericStringRef string reference object \relatesalso GenericStringRef \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. */ template inline GenericStringRef StringRef(const std::basic_string& str) { return GenericStringRef(str.data(), SizeType(str.size())); } #endif /////////////////////////////////////////////////////////////////////////////// // GenericValue type traits namespace internal { template struct IsGenericValueImpl : FalseType {}; // select candidates according to nested encoding and allocator types template struct IsGenericValueImpl::Type, typename Void::Type> : IsBaseOf, T>::Type {}; // helper to match arbitrary GenericValue instantiations, including derived classes template struct IsGenericValue : IsGenericValueImpl::Type {}; } // namespace internal /////////////////////////////////////////////////////////////////////////////// // TypeHelper namespace internal { template struct TypeHelper {}; template struct TypeHelper { static bool Is(const ValueType& v) { return v.IsBool(); } static bool Get(const ValueType& v) { return v.GetBool(); } static ValueType& Set(ValueType& v, bool data) { return v.SetBool(data); } static ValueType& Set(ValueType& v, bool data, typename ValueType::AllocatorType&) { return v.SetBool(data); } }; template struct TypeHelper { static bool Is(const ValueType& v) { return v.IsInt(); } static int Get(const ValueType& v) { return v.GetInt(); } static ValueType& Set(ValueType& v, int data) { return v.SetInt(data); } static ValueType& Set(ValueType& v, int data, typename ValueType::AllocatorType&) { return v.SetInt(data); } }; template struct TypeHelper { static bool Is(const ValueType& v) { return v.IsUint(); } static unsigned Get(const ValueType& v) { return v.GetUint(); } static ValueType& Set(ValueType& v, unsigned data) { return v.SetUint(data); } static ValueType& Set(ValueType& v, unsigned data, typename ValueType::AllocatorType&) { return v.SetUint(data); } }; template struct TypeHelper { static bool Is(const ValueType& v) { return v.IsInt64(); } static int64_t Get(const ValueType& v) { return v.GetInt64(); } static ValueType& Set(ValueType& v, int64_t data) { return v.SetInt64(data); } static ValueType& Set(ValueType& v, int64_t data, typename ValueType::AllocatorType&) { return v.SetInt64(data); } }; template struct TypeHelper { static bool Is(const ValueType& v) { return v.IsUint64(); } static uint64_t Get(const ValueType& v) { return v.GetUint64(); } static ValueType& Set(ValueType& v, uint64_t data) { return v.SetUint64(data); } static ValueType& Set(ValueType& v, uint64_t data, typename ValueType::AllocatorType&) { return v.SetUint64(data); } }; template struct TypeHelper { static bool Is(const ValueType& v) { return v.IsDouble(); } static double Get(const ValueType& v) { return v.GetDouble(); } static ValueType& Set(ValueType& v, double data) { return v.SetDouble(data); } static ValueType& Set(ValueType& v, double data, typename ValueType::AllocatorType&) { return v.SetDouble(data); } }; template struct TypeHelper { static bool Is(const ValueType& v) { return v.IsFloat(); } static float Get(const ValueType& v) { return v.GetFloat(); } static ValueType& Set(ValueType& v, float data) { return v.SetFloat(data); } static ValueType& Set(ValueType& v, float data, typename ValueType::AllocatorType&) { return v.SetFloat(data); } }; template struct TypeHelper { typedef const typename ValueType::Ch* StringType; static bool Is(const ValueType& v) { return v.IsString(); } static StringType Get(const ValueType& v) { return v.GetString(); } static ValueType& Set(ValueType& v, const StringType data) { return v.SetString(typename ValueType::StringRefType(data)); } static ValueType& Set(ValueType& v, const StringType data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } }; #if RAPIDJSON_HAS_STDSTRING template struct TypeHelper > { typedef std::basic_string StringType; static bool Is(const ValueType& v) { return v.IsString(); } static StringType Get(const ValueType& v) { return StringType(v.GetString(), v.GetStringLength()); } static ValueType& Set(ValueType& v, const StringType& data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } }; #endif template struct TypeHelper { typedef typename ValueType::Array ArrayType; static bool Is(const ValueType& v) { return v.IsArray(); } static ArrayType Get(ValueType& v) { return v.GetArray(); } static ValueType& Set(ValueType& v, ArrayType data) { return v = data; } static ValueType& Set(ValueType& v, ArrayType data, typename ValueType::AllocatorType&) { return v = data; } }; template struct TypeHelper { typedef typename ValueType::ConstArray ArrayType; static bool Is(const ValueType& v) { return v.IsArray(); } static ArrayType Get(const ValueType& v) { return v.GetArray(); } }; template struct TypeHelper { typedef typename ValueType::Object ObjectType; static bool Is(const ValueType& v) { return v.IsObject(); } static ObjectType Get(ValueType& v) { return v.GetObject(); } static ValueType& Set(ValueType& v, ObjectType data) { return v = data; } static ValueType& Set(ValueType& v, ObjectType data, typename ValueType::AllocatorType&) { return v = data; } }; template struct TypeHelper { typedef typename ValueType::ConstObject ObjectType; static bool Is(const ValueType& v) { return v.IsObject(); } static ObjectType Get(const ValueType& v) { return v.GetObject(); } }; } // namespace internal // Forward declarations template class GenericArray; template class GenericObject; /////////////////////////////////////////////////////////////////////////////// // GenericValue //! Represents a JSON value. Use Value for UTF8 encoding and default allocator. /*! A JSON value can be one of 7 types. This class is a variant type supporting these types. Use the Value if UTF8 and default allocator \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) \tparam Allocator Allocator type for allocating memory of object, array and string. */ template > class GenericValue { public: //! Name-value pair in an object. typedef GenericMember Member; typedef Encoding EncodingType; //!< Encoding type from template parameter. typedef Allocator AllocatorType; //!< Allocator type from template parameter. typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. typedef GenericStringRef StringRefType; //!< Reference to a constant string typedef typename GenericMemberIterator::Iterator MemberIterator; //!< Member iterator for iterating in object. typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. typedef GenericValue ValueType; //!< Value type of itself. typedef GenericArray Array; typedef GenericArray ConstArray; typedef GenericObject Object; typedef GenericObject ConstObject; //!@name Constructors and destructor. //@{ //! Default constructor creates a null value. GenericValue() RAPIDJSON_NOEXCEPT : data_() { data_.f.flags = kNullFlag; } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS //! Move constructor in C++11 GenericValue(GenericValue&& rhs) RAPIDJSON_NOEXCEPT : data_(rhs.data_) { rhs.data_.f.flags = kNullFlag; // give up contents } #endif private: //! Copy constructor is not permitted. GenericValue(const GenericValue& rhs); #if RAPIDJSON_HAS_CXX11_RVALUE_REFS //! Moving from a GenericDocument is not permitted. template GenericValue(GenericDocument&& rhs); //! Move assignment from a GenericDocument is not permitted. template GenericValue& operator=(GenericDocument&& rhs); #endif public: //! Constructor with JSON value type. /*! This creates a Value of specified type with default content. \param type Type of the value. \note Default content for number is zero. */ explicit GenericValue(Type type) RAPIDJSON_NOEXCEPT : data_() { static const uint16_t defaultFlags[7] = { kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kShortStringFlag, kNumberAnyFlag }; RAPIDJSON_ASSERT(type <= kNumberType); data_.f.flags = defaultFlags[type]; // Use ShortString to store empty string. if (type == kStringType) data_.ss.SetLength(0); } //! Explicit copy constructor (with allocator) /*! Creates a copy of a Value by using the given Allocator \tparam SourceAllocator allocator of \c rhs \param rhs Value to copy from (read-only) \param allocator Allocator for allocating copied elements and buffers. Commonly use GenericDocument::GetAllocator(). \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) \see CopyFrom() */ template GenericValue(const GenericValue& rhs, Allocator& allocator, bool copyConstStrings = false) { switch (rhs.GetType()) { case kObjectType: { SizeType count = rhs.data_.o.size; Member* lm = reinterpret_cast(allocator.Malloc(count * sizeof(Member))); const typename GenericValue::Member* rm = rhs.GetMembersPointer(); for (SizeType i = 0; i < count; i++) { new (&lm[i].name) GenericValue(rm[i].name, allocator, copyConstStrings); new (&lm[i].value) GenericValue(rm[i].value, allocator, copyConstStrings); } data_.f.flags = kObjectFlag; data_.o.size = data_.o.capacity = count; SetMembersPointer(lm); } break; case kArrayType: { SizeType count = rhs.data_.a.size; GenericValue* le = reinterpret_cast(allocator.Malloc(count * sizeof(GenericValue))); const GenericValue* re = rhs.GetElementsPointer(); for (SizeType i = 0; i < count; i++) new (&le[i]) GenericValue(re[i], allocator, copyConstStrings); data_.f.flags = kArrayFlag; data_.a.size = data_.a.capacity = count; SetElementsPointer(le); } break; case kStringType: if (rhs.data_.f.flags == kConstStringFlag && !copyConstStrings) { data_.f.flags = rhs.data_.f.flags; data_ = *reinterpret_cast(&rhs.data_); } else SetStringRaw(StringRef(rhs.GetString(), rhs.GetStringLength()), allocator); break; default: data_.f.flags = rhs.data_.f.flags; data_ = *reinterpret_cast(&rhs.data_); break; } } //! Constructor for boolean value. /*! \param b Boolean value \note This constructor is limited to \em real boolean values and rejects implicitly converted types like arbitrary pointers. Use an explicit cast to \c bool, if you want to construct a boolean JSON value in such cases. */ #ifndef RAPIDJSON_DOXYGEN_RUNNING // hide SFINAE from Doxygen template explicit GenericValue(T b, RAPIDJSON_ENABLEIF((internal::IsSame))) RAPIDJSON_NOEXCEPT // See #472 #else explicit GenericValue(bool b) RAPIDJSON_NOEXCEPT #endif : data_() { // safe-guard against failing SFINAE RAPIDJSON_STATIC_ASSERT((internal::IsSame::Value)); data_.f.flags = b ? kTrueFlag : kFalseFlag; } //! Constructor for int value. explicit GenericValue(int i) RAPIDJSON_NOEXCEPT : data_() { data_.n.i64 = i; data_.f.flags = (i >= 0) ? (kNumberIntFlag | kUintFlag | kUint64Flag) : kNumberIntFlag; } //! Constructor for unsigned value. explicit GenericValue(unsigned u) RAPIDJSON_NOEXCEPT : data_() { data_.n.u64 = u; data_.f.flags = (u & 0x80000000) ? kNumberUintFlag : (kNumberUintFlag | kIntFlag | kInt64Flag); } //! Constructor for int64_t value. explicit GenericValue(int64_t i64) RAPIDJSON_NOEXCEPT : data_() { data_.n.i64 = i64; data_.f.flags = kNumberInt64Flag; if (i64 >= 0) { data_.f.flags |= kNumberUint64Flag; if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) data_.f.flags |= kUintFlag; if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) data_.f.flags |= kIntFlag; } else if (i64 >= static_cast(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) data_.f.flags |= kIntFlag; } //! Constructor for uint64_t value. explicit GenericValue(uint64_t u64) RAPIDJSON_NOEXCEPT : data_() { data_.n.u64 = u64; data_.f.flags = kNumberUint64Flag; if (!(u64 & RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))) data_.f.flags |= kInt64Flag; if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) data_.f.flags |= kUintFlag; if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) data_.f.flags |= kIntFlag; } //! Constructor for double value. explicit GenericValue(double d) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = d; data_.f.flags = kNumberDoubleFlag; } //! Constructor for float value. explicit GenericValue(float f) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = static_cast(f); data_.f.flags = kNumberDoubleFlag; } //! Constructor for constant string (i.e. do not make a copy of string) GenericValue(const Ch* s, SizeType length) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(StringRef(s, length)); } //! Constructor for constant string (i.e. do not make a copy of string) explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(s); } //! Constructor for copy-string (i.e. do make a copy of string) GenericValue(const Ch* s, SizeType length, Allocator& allocator) : data_() { SetStringRaw(StringRef(s, length), allocator); } //! Constructor for copy-string (i.e. do make a copy of string) GenericValue(const Ch*s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } #if RAPIDJSON_HAS_STDSTRING //! Constructor for copy-string from a string object (i.e. do make a copy of string) /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. */ GenericValue(const std::basic_string& s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } #endif //! Constructor for Array. /*! \param a An array obtained by \c GetArray(). \note \c Array is always pass-by-value. \note the source array is moved into this value and the sourec array becomes empty. */ GenericValue(Array a) RAPIDJSON_NOEXCEPT : data_(a.value_.data_) { a.value_.data_ = Data(); a.value_.data_.f.flags = kArrayFlag; } //! Constructor for Object. /*! \param o An object obtained by \c GetObject(). \note \c Object is always pass-by-value. \note the source object is moved into this value and the sourec object becomes empty. */ GenericValue(Object o) RAPIDJSON_NOEXCEPT : data_(o.value_.data_) { o.value_.data_ = Data(); o.value_.data_.f.flags = kObjectFlag; } //! Destructor. /*! Need to destruct elements of array, members of object, or copy-string. */ ~GenericValue() { if (Allocator::kNeedFree) { // Shortcut by Allocator's trait switch(data_.f.flags) { case kArrayFlag: { GenericValue* e = GetElementsPointer(); for (GenericValue* v = e; v != e + data_.a.size; ++v) v->~GenericValue(); Allocator::Free(e); } break; case kObjectFlag: for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) m->~Member(); Allocator::Free(GetMembersPointer()); break; case kCopyStringFlag: Allocator::Free(const_cast(GetStringPointer())); break; default: break; // Do nothing for other types. } } } //@} //!@name Assignment operators //@{ //! Assignment with move semantics. /*! \param rhs Source of the assignment. It will become a null value after assignment. */ GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT { RAPIDJSON_ASSERT(this != &rhs); this->~GenericValue(); RawAssign(rhs); return *this; } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS //! Move assignment in C++11 GenericValue& operator=(GenericValue&& rhs) RAPIDJSON_NOEXCEPT { return *this = rhs.Move(); } #endif //! Assignment of constant string reference (no copy) /*! \param str Constant string reference to be assigned \note This overload is needed to avoid clashes with the generic primitive type assignment overload below. \see GenericStringRef, operator=(T) */ GenericValue& operator=(StringRefType str) RAPIDJSON_NOEXCEPT { GenericValue s(str); return *this = s; } //! Assignment with primitive types. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t \param value The value to be assigned. \note The source type \c T explicitly disallows all pointer types, especially (\c const) \ref Ch*. This helps avoiding implicitly referencing character strings with insufficient lifetime, use \ref SetString(const Ch*, Allocator&) (for copying) or \ref StringRef() (to explicitly mark the pointer as constant) instead. All other pointer types would implicitly convert to \c bool, use \ref SetBool() instead. */ template RAPIDJSON_DISABLEIF_RETURN((internal::IsPointer), (GenericValue&)) operator=(T value) { GenericValue v(value); return *this = v; } //! Deep-copy assignment from Value /*! Assigns a \b copy of the Value to the current Value object \tparam SourceAllocator Allocator type of \c rhs \param rhs Value to copy from (read-only) \param allocator Allocator to use for copying \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) */ template GenericValue& CopyFrom(const GenericValue& rhs, Allocator& allocator, bool copyConstStrings = false) { RAPIDJSON_ASSERT(static_cast(this) != static_cast(&rhs)); this->~GenericValue(); new (this) GenericValue(rhs, allocator, copyConstStrings); return *this; } //! Exchange the contents of this value with those of other. /*! \param other Another value. \note Constant complexity. */ GenericValue& Swap(GenericValue& other) RAPIDJSON_NOEXCEPT { GenericValue temp; temp.RawAssign(*this); RawAssign(other); other.RawAssign(temp); return *this; } //! free-standing swap function helper /*! Helper function to enable support for common swap implementation pattern based on \c std::swap: \code void swap(MyClass& a, MyClass& b) { using std::swap; swap(a.value, b.value); // ... } \endcode \see Swap() */ friend inline void swap(GenericValue& a, GenericValue& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } //! Prepare Value for move semantics /*! \return *this */ GenericValue& Move() RAPIDJSON_NOEXCEPT { return *this; } //@} //!@name Equal-to and not-equal-to operators //@{ //! Equal-to operator /*! \note If an object contains duplicated named member, comparing equality with any object is always \c false. \note Linear time complexity (number of all values in the subtree and total lengths of all strings). */ template bool operator==(const GenericValue& rhs) const { typedef GenericValue RhsType; if (GetType() != rhs.GetType()) return false; switch (GetType()) { case kObjectType: // Warning: O(n^2) inner-loop if (data_.o.size != rhs.data_.o.size) return false; for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) return false; } return true; case kArrayType: if (data_.a.size != rhs.data_.a.size) return false; for (SizeType i = 0; i < data_.a.size; i++) if ((*this)[i] != rhs[i]) return false; return true; case kStringType: return StringEqual(rhs); case kNumberType: if (IsDouble() || rhs.IsDouble()) { double a = GetDouble(); // May convert from integer to double. double b = rhs.GetDouble(); // Ditto return a >= b && a <= b; // Prevent -Wfloat-equal } else return data_.n.u64 == rhs.data_.n.u64; default: return true; } } //! Equal-to operator with const C-string pointer bool operator==(const Ch* rhs) const { return *this == GenericValue(StringRef(rhs)); } #if RAPIDJSON_HAS_STDSTRING //! Equal-to operator with string object /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. */ bool operator==(const std::basic_string& rhs) const { return *this == GenericValue(StringRef(rhs)); } #endif //! Equal-to operator with primitive types /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr,internal::IsGenericValue >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } //! Not-equal-to operator /*! \return !(*this == rhs) */ template bool operator!=(const GenericValue& rhs) const { return !(*this == rhs); } //! Not-equal-to operator with const C-string pointer bool operator!=(const Ch* rhs) const { return !(*this == rhs); } //! Not-equal-to operator with arbitrary types /*! \return !(*this == rhs) */ template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } //! Equal-to operator with arbitrary types (symmetric version) /*! \return (rhs == lhs) */ template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator==(const T& lhs, const GenericValue& rhs) { return rhs == lhs; } //! Not-Equal-to operator with arbitrary types (symmetric version) /*! \return !(rhs == lhs) */ template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& lhs, const GenericValue& rhs) { return !(rhs == lhs); } //@} //!@name Type //@{ Type GetType() const { return static_cast(data_.f.flags & kTypeMask); } bool IsNull() const { return data_.f.flags == kNullFlag; } bool IsFalse() const { return data_.f.flags == kFalseFlag; } bool IsTrue() const { return data_.f.flags == kTrueFlag; } bool IsBool() const { return (data_.f.flags & kBoolFlag) != 0; } bool IsObject() const { return data_.f.flags == kObjectFlag; } bool IsArray() const { return data_.f.flags == kArrayFlag; } bool IsNumber() const { return (data_.f.flags & kNumberFlag) != 0; } bool IsInt() const { return (data_.f.flags & kIntFlag) != 0; } bool IsUint() const { return (data_.f.flags & kUintFlag) != 0; } bool IsInt64() const { return (data_.f.flags & kInt64Flag) != 0; } bool IsUint64() const { return (data_.f.flags & kUint64Flag) != 0; } bool IsDouble() const { return (data_.f.flags & kDoubleFlag) != 0; } bool IsString() const { return (data_.f.flags & kStringFlag) != 0; } // Checks whether a number can be losslessly converted to a double. bool IsLosslessDouble() const { if (!IsNumber()) return false; if (IsUint64()) { uint64_t u = GetUint64(); volatile double d = static_cast(u); return (d >= 0.0) && (d < static_cast(std::numeric_limits::max())) && (u == static_cast(d)); } if (IsInt64()) { int64_t i = GetInt64(); volatile double d = static_cast(i); return (d >= static_cast(std::numeric_limits::min())) && (d < static_cast(std::numeric_limits::max())) && (i == static_cast(d)); } return true; // double, int, uint are always lossless } // Checks whether a number is a float (possible lossy). bool IsFloat() const { if ((data_.f.flags & kDoubleFlag) == 0) return false; double d = GetDouble(); return d >= -3.4028234e38 && d <= 3.4028234e38; } // Checks whether a number can be losslessly converted to a float. bool IsLosslessFloat() const { if (!IsNumber()) return false; double a = GetDouble(); if (a < static_cast(-std::numeric_limits::max()) || a > static_cast(std::numeric_limits::max())) return false; double b = static_cast(static_cast(a)); return a >= b && a <= b; // Prevent -Wfloat-equal } //@} //!@name Null //@{ GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } //@} //!@name Bool //@{ bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return data_.f.flags == kTrueFlag; } //!< Set boolean value /*! \post IsBool() == true */ GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } //@} //!@name Object //@{ //! Set this value as an empty object. /*! \post IsObject() == true */ GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } //! Get the number of members in the object. SizeType MemberCount() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size; } //! Check whether the object is empty. bool ObjectEmpty() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size == 0; } //! Get a value from an object associated with the name. /*! \pre IsObject() == true \tparam T Either \c Ch or \c const \c Ch (template used for disambiguation with \ref operator[](SizeType)) \note In version 0.1x, if the member is not found, this function returns a null value. This makes issue 7. Since 0.2, if the name is not correct, it will assert. If user is unsure whether a member exists, user should use HasMember() first. A better approach is to use FindMember(). \note Linear time complexity. */ template RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(GenericValue&)) operator[](T* name) { GenericValue n(StringRef(name)); return (*this)[n]; } template RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(const GenericValue&)) operator[](T* name) const { return const_cast(*this)[name]; } //! Get a value from an object associated with the name. /*! \pre IsObject() == true \tparam SourceAllocator Allocator of the \c name value \note Compared to \ref operator[](T*), this version is faster because it does not need a StrLen(). And it can also handle strings with embedded null characters. \note Linear time complexity. */ template GenericValue& operator[](const GenericValue& name) { MemberIterator member = FindMember(name); if (member != MemberEnd()) return member->value; else { RAPIDJSON_ASSERT(false); // see above note // This will generate -Wexit-time-destructors in clang // static GenericValue NullValue; // return NullValue; // Use static buffer and placement-new to prevent destruction static char buffer[sizeof(GenericValue)]; return *new (buffer) GenericValue(); } } template const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } #if RAPIDJSON_HAS_STDSTRING //! Get a value from an object associated with name (string object). GenericValue& operator[](const std::basic_string& name) { return (*this)[GenericValue(StringRef(name))]; } const GenericValue& operator[](const std::basic_string& name) const { return (*this)[GenericValue(StringRef(name))]; } #endif //! Const member iterator /*! \pre IsObject() == true */ ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer()); } //! Const \em past-the-end member iterator /*! \pre IsObject() == true */ ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer() + data_.o.size); } //! Member iterator /*! \pre IsObject() == true */ MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer()); } //! \em Past-the-end member iterator /*! \pre IsObject() == true */ MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer() + data_.o.size); } //! Check whether a member exists in the object. /*! \param name Member name to be searched. \pre IsObject() == true \return Whether a member with that name exists. \note It is better to use FindMember() directly if you need the obtain the value as well. \note Linear time complexity. */ bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } #if RAPIDJSON_HAS_STDSTRING //! Check whether a member exists in the object with string object. /*! \param name Member name to be searched. \pre IsObject() == true \return Whether a member with that name exists. \note It is better to use FindMember() directly if you need the obtain the value as well. \note Linear time complexity. */ bool HasMember(const std::basic_string& name) const { return FindMember(name) != MemberEnd(); } #endif //! Check whether a member exists in the object with GenericValue name. /*! This version is faster because it does not need a StrLen(). It can also handle string with null character. \param name Member name to be searched. \pre IsObject() == true \return Whether a member with that name exists. \note It is better to use FindMember() directly if you need the obtain the value as well. \note Linear time complexity. */ template bool HasMember(const GenericValue& name) const { return FindMember(name) != MemberEnd(); } //! Find member by name. /*! \param name Member name to be searched. \pre IsObject() == true \return Iterator to member, if it exists. Otherwise returns \ref MemberEnd(). \note Earlier versions of Rapidjson returned a \c NULL pointer, in case the requested member doesn't exist. For consistency with e.g. \c std::map, this has been changed to MemberEnd() now. \note Linear time complexity. */ MemberIterator FindMember(const Ch* name) { GenericValue n(StringRef(name)); return FindMember(n); } ConstMemberIterator FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } //! Find member by name. /*! This version is faster because it does not need a StrLen(). It can also handle string with null character. \param name Member name to be searched. \pre IsObject() == true \return Iterator to member, if it exists. Otherwise returns \ref MemberEnd(). \note Earlier versions of Rapidjson returned a \c NULL pointer, in case the requested member doesn't exist. For consistency with e.g. \c std::map, this has been changed to MemberEnd() now. \note Linear time complexity. */ template MemberIterator FindMember(const GenericValue& name) { RAPIDJSON_ASSERT(IsObject()); RAPIDJSON_ASSERT(name.IsString()); MemberIterator member = MemberBegin(); for ( ; member != MemberEnd(); ++member) if (name.StringEqual(member->name)) break; return member; } template ConstMemberIterator FindMember(const GenericValue& name) const { return const_cast(*this).FindMember(name); } #if RAPIDJSON_HAS_STDSTRING //! Find member by string object name. /*! \param name Member name to be searched. \pre IsObject() == true \return Iterator to member, if it exists. Otherwise returns \ref MemberEnd(). */ MemberIterator FindMember(const std::basic_string& name) { return FindMember(GenericValue(StringRef(name))); } ConstMemberIterator FindMember(const std::basic_string& name) const { return FindMember(GenericValue(StringRef(name))); } #endif //! Add a member (name-value pair) to the object. /*! \param name A string value as name of member. \param value Value of any type. \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \note The ownership of \c name and \c value will be transferred to this object on success. \pre IsObject() && name.IsString() \post name.IsNull() && value.IsNull() \note Amortized Constant time complexity. */ GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { RAPIDJSON_ASSERT(IsObject()); RAPIDJSON_ASSERT(name.IsString()); ObjectData& o = data_.o; if (o.size >= o.capacity) { if (o.capacity == 0) { o.capacity = kDefaultObjectCapacity; SetMembersPointer(reinterpret_cast(allocator.Malloc(o.capacity * sizeof(Member)))); } else { SizeType oldCapacity = o.capacity; o.capacity += (oldCapacity + 1) / 2; // grow by factor 1.5 SetMembersPointer(reinterpret_cast(allocator.Realloc(GetMembersPointer(), oldCapacity * sizeof(Member), o.capacity * sizeof(Member)))); } } Member* members = GetMembersPointer(); members[o.size].name.RawAssign(name); members[o.size].value.RawAssign(value); o.size++; return *this; } //! Add a constant string value as member (name-value pair) to the object. /*! \param name A string value as name of member. \param value constant string reference as value of member. \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \pre IsObject() \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. \note Amortized Constant time complexity. */ GenericValue& AddMember(GenericValue& name, StringRefType value, Allocator& allocator) { GenericValue v(value); return AddMember(name, v, allocator); } #if RAPIDJSON_HAS_STDSTRING //! Add a string object as member (name-value pair) to the object. /*! \param name A string value as name of member. \param value constant string reference as value of member. \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \pre IsObject() \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. \note Amortized Constant time complexity. */ GenericValue& AddMember(GenericValue& name, std::basic_string& value, Allocator& allocator) { GenericValue v(value, allocator); return AddMember(name, v, allocator); } #endif //! Add any primitive value as member (name-value pair) to the object. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t \param name A string value as name of member. \param value Value of primitive type \c T as value of member \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \pre IsObject() \note The source type \c T explicitly disallows all pointer types, especially (\c const) \ref Ch*. This helps avoiding implicitly referencing character strings with insufficient lifetime, use \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref AddMember(StringRefType, StringRefType, Allocator&). All other pointer types would implicitly convert to \c bool, use an explicit cast instead, if needed. \note Amortized Constant time complexity. */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) AddMember(GenericValue& name, T value, Allocator& allocator) { GenericValue v(value); return AddMember(name, v, allocator); } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS GenericValue& AddMember(GenericValue&& name, GenericValue&& value, Allocator& allocator) { return AddMember(name, value, allocator); } GenericValue& AddMember(GenericValue&& name, GenericValue& value, Allocator& allocator) { return AddMember(name, value, allocator); } GenericValue& AddMember(GenericValue& name, GenericValue&& value, Allocator& allocator) { return AddMember(name, value, allocator); } GenericValue& AddMember(StringRefType name, GenericValue&& value, Allocator& allocator) { GenericValue n(name); return AddMember(n, value, allocator); } #endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS //! Add a member (name-value pair) to the object. /*! \param name A constant string reference as name of member. \param value Value of any type. \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \note The ownership of \c value will be transferred to this object on success. \pre IsObject() \post value.IsNull() \note Amortized Constant time complexity. */ GenericValue& AddMember(StringRefType name, GenericValue& value, Allocator& allocator) { GenericValue n(name); return AddMember(n, value, allocator); } //! Add a constant string value as member (name-value pair) to the object. /*! \param name A constant string reference as name of member. \param value constant string reference as value of member. \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \pre IsObject() \note This overload is needed to avoid clashes with the generic primitive type AddMember(StringRefType,T,Allocator&) overload below. \note Amortized Constant time complexity. */ GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { GenericValue v(value); return AddMember(name, v, allocator); } //! Add any primitive value as member (name-value pair) to the object. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t \param name A constant string reference as name of member. \param value Value of primitive type \c T as value of member \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \pre IsObject() \note The source type \c T explicitly disallows all pointer types, especially (\c const) \ref Ch*. This helps avoiding implicitly referencing character strings with insufficient lifetime, use \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref AddMember(StringRefType, StringRefType, Allocator&). All other pointer types would implicitly convert to \c bool, use an explicit cast instead, if needed. \note Amortized Constant time complexity. */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) AddMember(StringRefType name, T value, Allocator& allocator) { GenericValue n(name); return AddMember(n, value, allocator); } //! Remove all members in the object. /*! This function do not deallocate memory in the object, i.e. the capacity is unchanged. \note Linear time complexity. */ void RemoveAllMembers() { RAPIDJSON_ASSERT(IsObject()); for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) m->~Member(); data_.o.size = 0; } //! Remove a member in object by its name. /*! \param name Name of member to be removed. \return Whether the member existed. \note This function may reorder the object members. Use \ref EraseMember(ConstMemberIterator) if you need to preserve the relative order of the remaining members. \note Linear time complexity. */ bool RemoveMember(const Ch* name) { GenericValue n(StringRef(name)); return RemoveMember(n); } #if RAPIDJSON_HAS_STDSTRING bool RemoveMember(const std::basic_string& name) { return RemoveMember(GenericValue(StringRef(name))); } #endif template bool RemoveMember(const GenericValue& name) { MemberIterator m = FindMember(name); if (m != MemberEnd()) { RemoveMember(m); return true; } else return false; } //! Remove a member in object by iterator. /*! \param m member iterator (obtained by FindMember() or MemberBegin()). \return the new iterator after removal. \note This function may reorder the object members. Use \ref EraseMember(ConstMemberIterator) if you need to preserve the relative order of the remaining members. \note Constant time complexity. */ MemberIterator RemoveMember(MemberIterator m) { RAPIDJSON_ASSERT(IsObject()); RAPIDJSON_ASSERT(data_.o.size > 0); RAPIDJSON_ASSERT(GetMembersPointer() != 0); RAPIDJSON_ASSERT(m >= MemberBegin() && m < MemberEnd()); MemberIterator last(GetMembersPointer() + (data_.o.size - 1)); if (data_.o.size > 1 && m != last) *m = *last; // Move the last one to this place else m->~Member(); // Only one left, just destroy --data_.o.size; return m; } //! Remove a member from an object by iterator. /*! \param pos iterator to the member to remove \pre IsObject() == true && \ref MemberBegin() <= \c pos < \ref MemberEnd() \return Iterator following the removed element. If the iterator \c pos refers to the last element, the \ref MemberEnd() iterator is returned. \note This function preserves the relative order of the remaining object members. If you do not need this, use the more efficient \ref RemoveMember(MemberIterator). \note Linear time complexity. */ MemberIterator EraseMember(ConstMemberIterator pos) { return EraseMember(pos, pos +1); } //! Remove members in the range [first, last) from an object. /*! \param first iterator to the first member to remove \param last iterator following the last member to remove \pre IsObject() == true && \ref MemberBegin() <= \c first <= \c last <= \ref MemberEnd() \return Iterator following the last removed element. \note This function preserves the relative order of the remaining object members. \note Linear time complexity. */ MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) { RAPIDJSON_ASSERT(IsObject()); RAPIDJSON_ASSERT(data_.o.size > 0); RAPIDJSON_ASSERT(GetMembersPointer() != 0); RAPIDJSON_ASSERT(first >= MemberBegin()); RAPIDJSON_ASSERT(first <= last); RAPIDJSON_ASSERT(last <= MemberEnd()); MemberIterator pos = MemberBegin() + (first - MemberBegin()); for (MemberIterator itr = pos; itr != last; ++itr) itr->~Member(); std::memmove(&*pos, &*last, static_cast(MemberEnd() - last) * sizeof(Member)); data_.o.size -= static_cast(last - first); return pos; } //! Erase a member in object by its name. /*! \param name Name of member to be removed. \return Whether the member existed. \note Linear time complexity. */ bool EraseMember(const Ch* name) { GenericValue n(StringRef(name)); return EraseMember(n); } #if RAPIDJSON_HAS_STDSTRING bool EraseMember(const std::basic_string& name) { return EraseMember(GenericValue(StringRef(name))); } #endif template bool EraseMember(const GenericValue& name) { MemberIterator m = FindMember(name); if (m != MemberEnd()) { EraseMember(m); return true; } else return false; } Object GetObject() { RAPIDJSON_ASSERT(IsObject()); return Object(*this); } ConstObject GetObject() const { RAPIDJSON_ASSERT(IsObject()); return ConstObject(*this); } //@} //!@name Array //@{ //! Set this value as an empty array. /*! \post IsArray == true */ GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } //! Get the number of elements in array. SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } //! Get the capacity of array. SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } //! Check whether the array is empty. bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } //! Remove all elements in the array. /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. \note Linear time complexity. */ void Clear() { RAPIDJSON_ASSERT(IsArray()); GenericValue* e = GetElementsPointer(); for (GenericValue* v = e; v != e + data_.a.size; ++v) v->~GenericValue(); data_.a.size = 0; } //! Get an element from array by index. /*! \pre IsArray() == true \param index Zero-based index of element. \see operator[](T*) */ GenericValue& operator[](SizeType index) { RAPIDJSON_ASSERT(IsArray()); RAPIDJSON_ASSERT(index < data_.a.size); return GetElementsPointer()[index]; } const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } //! Element iterator /*! \pre IsArray() == true */ ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer(); } //! \em Past-the-end element iterator /*! \pre IsArray() == true */ ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer() + data_.a.size; } //! Constant element iterator /*! \pre IsArray() == true */ ConstValueIterator Begin() const { return const_cast(*this).Begin(); } //! Constant \em past-the-end element iterator /*! \pre IsArray() == true */ ConstValueIterator End() const { return const_cast(*this).End(); } //! Request the array to have enough capacity to store elements. /*! \param newCapacity The capacity that the array at least need to have. \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \note Linear time complexity. */ GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { RAPIDJSON_ASSERT(IsArray()); if (newCapacity > data_.a.capacity) { SetElementsPointer(reinterpret_cast(allocator.Realloc(GetElementsPointer(), data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)))); data_.a.capacity = newCapacity; } return *this; } //! Append a GenericValue at the end of the array. /*! \param value Value to be appended. \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). \pre IsArray() == true \post value.IsNull() == true \return The value itself for fluent API. \note The ownership of \c value will be transferred to this array on success. \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. \note Amortized constant time complexity. */ GenericValue& PushBack(GenericValue& value, Allocator& allocator) { RAPIDJSON_ASSERT(IsArray()); if (data_.a.size >= data_.a.capacity) Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : (data_.a.capacity + (data_.a.capacity + 1) / 2), allocator); GetElementsPointer()[data_.a.size++].RawAssign(value); return *this; } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS GenericValue& PushBack(GenericValue&& value, Allocator& allocator) { return PushBack(value, allocator); } #endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS //! Append a constant string reference at the end of the array. /*! \param value Constant string reference to be appended. \param allocator Allocator for reallocating memory. It must be the same one used previously. Commonly use GenericDocument::GetAllocator(). \pre IsArray() == true \return The value itself for fluent API. \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. \note Amortized constant time complexity. \see GenericStringRef */ GenericValue& PushBack(StringRefType value, Allocator& allocator) { return (*this).template PushBack(value, allocator); } //! Append a primitive value at the end of the array. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t \param value Value of primitive type T to be appended. \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). \pre IsArray() == true \return The value itself for fluent API. \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. \note The source type \c T explicitly disallows all pointer types, especially (\c const) \ref Ch*. This helps avoiding implicitly referencing character strings with insufficient lifetime, use \ref PushBack(GenericValue&, Allocator&) or \ref PushBack(StringRefType, Allocator&). All other pointer types would implicitly convert to \c bool, use an explicit cast instead, if needed. \note Amortized constant time complexity. */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) PushBack(T value, Allocator& allocator) { GenericValue v(value); return PushBack(v, allocator); } //! Remove the last element in the array. /*! \note Constant time complexity. */ GenericValue& PopBack() { RAPIDJSON_ASSERT(IsArray()); RAPIDJSON_ASSERT(!Empty()); GetElementsPointer()[--data_.a.size].~GenericValue(); return *this; } //! Remove an element of array by iterator. /*! \param pos iterator to the element to remove \pre IsArray() == true && \ref Begin() <= \c pos < \ref End() \return Iterator following the removed element. If the iterator pos refers to the last element, the End() iterator is returned. \note Linear time complexity. */ ValueIterator Erase(ConstValueIterator pos) { return Erase(pos, pos + 1); } //! Remove elements in the range [first, last) of the array. /*! \param first iterator to the first element to remove \param last iterator following the last element to remove \pre IsArray() == true && \ref Begin() <= \c first <= \c last <= \ref End() \return Iterator following the last removed element. \note Linear time complexity. */ ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) { RAPIDJSON_ASSERT(IsArray()); RAPIDJSON_ASSERT(data_.a.size > 0); RAPIDJSON_ASSERT(GetElementsPointer() != 0); RAPIDJSON_ASSERT(first >= Begin()); RAPIDJSON_ASSERT(first <= last); RAPIDJSON_ASSERT(last <= End()); ValueIterator pos = Begin() + (first - Begin()); for (ValueIterator itr = pos; itr != last; ++itr) itr->~GenericValue(); std::memmove(pos, last, static_cast(End() - last) * sizeof(GenericValue)); data_.a.size -= static_cast(last - first); return pos; } Array GetArray() { RAPIDJSON_ASSERT(IsArray()); return Array(*this); } ConstArray GetArray() const { RAPIDJSON_ASSERT(IsArray()); return ConstArray(*this); } //@} //!@name Number //@{ int GetInt() const { RAPIDJSON_ASSERT(data_.f.flags & kIntFlag); return data_.n.i.i; } unsigned GetUint() const { RAPIDJSON_ASSERT(data_.f.flags & kUintFlag); return data_.n.u.u; } int64_t GetInt64() const { RAPIDJSON_ASSERT(data_.f.flags & kInt64Flag); return data_.n.i64; } uint64_t GetUint64() const { RAPIDJSON_ASSERT(data_.f.flags & kUint64Flag); return data_.n.u64; } //! Get the value as double type. /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessDouble() to check whether the converison is lossless. */ double GetDouble() const { RAPIDJSON_ASSERT(IsNumber()); if ((data_.f.flags & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. if ((data_.f.flags & kIntFlag) != 0) return data_.n.i.i; // int -> double if ((data_.f.flags & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double if ((data_.f.flags & kInt64Flag) != 0) return static_cast(data_.n.i64); // int64_t -> double (may lose precision) RAPIDJSON_ASSERT((data_.f.flags & kUint64Flag) != 0); return static_cast(data_.n.u64); // uint64_t -> double (may lose precision) } //! Get the value as float type. /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessFloat() to check whether the converison is lossless. */ float GetFloat() const { return static_cast(GetDouble()); } GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } GenericValue& SetFloat(float f) { this->~GenericValue(); new (this) GenericValue(static_cast(f)); return *this; } //@} //!@name String //@{ const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } //! Get the length of string. /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). */ SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return ((data_.f.flags & kInlineStrFlag) ? (data_.ss.GetLength()) : data_.s.length); } //! Set this value as a string without copying source string. /*! This version has better performance with supplied length, and also support string containing null character. \param s source string pointer. \param length The length of source string, excluding the trailing null terminator. \return The value itself for fluent API. \post IsString() == true && GetString() == s && GetStringLength() == length \see SetString(StringRefType) */ GenericValue& SetString(const Ch* s, SizeType length) { return SetString(StringRef(s, length)); } //! Set this value as a string without copying source string. /*! \param s source string reference \return The value itself for fluent API. \post IsString() == true && GetString() == s && GetStringLength() == s.length */ GenericValue& SetString(StringRefType s) { this->~GenericValue(); SetStringRaw(s); return *this; } //! Set this value as a string by copying from source string. /*! This version has better performance with supplied length, and also support string containing null character. \param s source string. \param length The length of source string, excluding the trailing null terminator. \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length */ GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { this->~GenericValue(); SetStringRaw(StringRef(s, length), allocator); return *this; } //! Set this value as a string by copying from source string. /*! \param s source string. \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length */ GenericValue& SetString(const Ch* s, Allocator& allocator) { return SetString(s, internal::StrLen(s), allocator); } #if RAPIDJSON_HAS_STDSTRING //! Set this value as a string by copying from source string. /*! \param s source string. \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). \return The value itself for fluent API. \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. */ GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(s.data(), SizeType(s.size()), allocator); } #endif //@} //!@name Array //@{ //! Templated version for checking whether this value is type T. /*! \tparam T Either \c bool, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c float, \c const \c char*, \c std::basic_string */ template bool Is() const { return internal::TypeHelper::Is(*this); } template T Get() const { return internal::TypeHelper::Get(*this); } template T Get() { return internal::TypeHelper::Get(*this); } template ValueType& Set(const T& data) { return internal::TypeHelper::Set(*this, data); } template ValueType& Set(const T& data, AllocatorType& allocator) { return internal::TypeHelper::Set(*this, data, allocator); } //@} //! Generate events of this value to a Handler. /*! This function adopts the GoF visitor pattern. Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. It can also be used to deep clone this value via GenericDocument, which is also a Handler. \tparam Handler type of handler. \param handler An object implementing concept Handler. */ template bool Accept(Handler& handler) const { switch(GetType()) { case kNullType: return handler.Null(); case kFalseType: return handler.Bool(false); case kTrueType: return handler.Bool(true); case kObjectType: if (RAPIDJSON_UNLIKELY(!handler.StartObject())) return false; for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. if (RAPIDJSON_UNLIKELY(!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.data_.f.flags & kCopyFlag) != 0))) return false; if (RAPIDJSON_UNLIKELY(!m->value.Accept(handler))) return false; } return handler.EndObject(data_.o.size); case kArrayType: if (RAPIDJSON_UNLIKELY(!handler.StartArray())) return false; for (const GenericValue* v = Begin(); v != End(); ++v) if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) return false; return handler.EndArray(data_.a.size); case kStringType: return handler.String(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0); default: RAPIDJSON_ASSERT(GetType() == kNumberType); if (IsDouble()) return handler.Double(data_.n.d); else if (IsInt()) return handler.Int(data_.n.i.i); else if (IsUint()) return handler.Uint(data_.n.u.u); else if (IsInt64()) return handler.Int64(data_.n.i64); else return handler.Uint64(data_.n.u64); } } private: template friend class GenericValue; template friend class GenericDocument; enum { kBoolFlag = 0x0008, kNumberFlag = 0x0010, kIntFlag = 0x0020, kUintFlag = 0x0040, kInt64Flag = 0x0080, kUint64Flag = 0x0100, kDoubleFlag = 0x0200, kStringFlag = 0x0400, kCopyFlag = 0x0800, kInlineStrFlag = 0x1000, // Initial flags of different types. kNullFlag = kNullType, kTrueFlag = kTrueType | kBoolFlag, kFalseFlag = kFalseType | kBoolFlag, kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, kNumberAnyFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag | kUintFlag | kUint64Flag | kDoubleFlag, kConstStringFlag = kStringType | kStringFlag, kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, kShortStringFlag = kStringType | kStringFlag | kCopyFlag | kInlineStrFlag, kObjectFlag = kObjectType, kArrayFlag = kArrayType, kTypeMask = 0x07 }; static const SizeType kDefaultArrayCapacity = 16; static const SizeType kDefaultObjectCapacity = 16; struct Flag { #if RAPIDJSON_48BITPOINTER_OPTIMIZATION char payload[sizeof(SizeType) * 2 + 6]; // 2 x SizeType + lower 48-bit pointer #elif RAPIDJSON_64BIT char payload[sizeof(SizeType) * 2 + sizeof(void*) + 6]; // 6 padding bytes #else char payload[sizeof(SizeType) * 2 + sizeof(void*) + 2]; // 2 padding bytes #endif uint16_t flags; }; struct String { SizeType length; SizeType hashcode; //!< reserved const Ch* str; }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode // implementation detail: ShortString can represent zero-terminated strings up to MaxSize chars // (excluding the terminating zero) and store a value to determine the length of the contained // string in the last character str[LenPos] by storing "MaxSize - length" there. If the string // to store has the maximal length of MaxSize then str[LenPos] will be 0 and therefore act as // the string terminator as well. For getting the string length back from that value just use // "MaxSize - str[LenPos]". // This allows to store 13-chars strings in 32-bit mode, 21-chars strings in 64-bit mode, // 13-chars strings for RAPIDJSON_48BITPOINTER_OPTIMIZATION=1 inline (for `UTF8`-encoded strings). struct ShortString { enum { MaxChars = sizeof(static_cast(0)->payload) / sizeof(Ch), MaxSize = MaxChars - 1, LenPos = MaxSize }; Ch str[MaxChars]; inline static bool Usable(SizeType len) { return (MaxSize >= len); } inline void SetLength(SizeType len) { str[LenPos] = static_cast(MaxSize - len); } inline SizeType GetLength() const { return static_cast(MaxSize - str[LenPos]); } }; // at most as many bytes as "String" above => 12 bytes in 32-bit mode, 16 bytes in 64-bit mode // By using proper binary layout, retrieval of different integer types do not need conversions. union Number { #if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN struct I { int i; char padding[4]; }i; struct U { unsigned u; char padding2[4]; }u; #else struct I { char padding[4]; int i; }i; struct U { char padding2[4]; unsigned u; }u; #endif int64_t i64; uint64_t u64; double d; }; // 8 bytes struct ObjectData { SizeType size; SizeType capacity; Member* members; }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode struct ArrayData { SizeType size; SizeType capacity; GenericValue* elements; }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode union Data { String s; ShortString ss; Number n; ObjectData o; ArrayData a; Flag f; }; // 16 bytes in 32-bit mode, 24 bytes in 64-bit mode, 16 bytes in 64-bit with RAPIDJSON_48BITPOINTER_OPTIMIZATION RAPIDJSON_FORCEINLINE const Ch* GetStringPointer() const { return RAPIDJSON_GETPOINTER(Ch, data_.s.str); } RAPIDJSON_FORCEINLINE const Ch* SetStringPointer(const Ch* str) { return RAPIDJSON_SETPOINTER(Ch, data_.s.str, str); } RAPIDJSON_FORCEINLINE GenericValue* GetElementsPointer() const { return RAPIDJSON_GETPOINTER(GenericValue, data_.a.elements); } RAPIDJSON_FORCEINLINE GenericValue* SetElementsPointer(GenericValue* elements) { return RAPIDJSON_SETPOINTER(GenericValue, data_.a.elements, elements); } RAPIDJSON_FORCEINLINE Member* GetMembersPointer() const { return RAPIDJSON_GETPOINTER(Member, data_.o.members); } RAPIDJSON_FORCEINLINE Member* SetMembersPointer(Member* members) { return RAPIDJSON_SETPOINTER(Member, data_.o.members, members); } // Initialize this value as array with initial data, without calling destructor. void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) { data_.f.flags = kArrayFlag; if (count) { GenericValue* e = static_cast(allocator.Malloc(count * sizeof(GenericValue))); SetElementsPointer(e); std::memcpy(e, values, count * sizeof(GenericValue)); } else SetElementsPointer(0); data_.a.size = data_.a.capacity = count; } //! Initialize this value as object with initial data, without calling destructor. void SetObjectRaw(Member* members, SizeType count, Allocator& allocator) { data_.f.flags = kObjectFlag; if (count) { Member* m = static_cast(allocator.Malloc(count * sizeof(Member))); SetMembersPointer(m); std::memcpy(m, members, count * sizeof(Member)); } else SetMembersPointer(0); data_.o.size = data_.o.capacity = count; } //! Initialize this value as constant string, without calling destructor. void SetStringRaw(StringRefType s) RAPIDJSON_NOEXCEPT { data_.f.flags = kConstStringFlag; SetStringPointer(s); data_.s.length = s.length; } //! Initialize this value as copy string with initial data, without calling destructor. void SetStringRaw(StringRefType s, Allocator& allocator) { Ch* str = 0; if (ShortString::Usable(s.length)) { data_.f.flags = kShortStringFlag; data_.ss.SetLength(s.length); str = data_.ss.str; } else { data_.f.flags = kCopyStringFlag; data_.s.length = s.length; str = static_cast(allocator.Malloc((s.length + 1) * sizeof(Ch))); SetStringPointer(str); } std::memcpy(str, s, s.length * sizeof(Ch)); str[s.length] = '\0'; } //! Assignment without calling destructor void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { data_ = rhs.data_; // data_.f.flags = rhs.data_.f.flags; rhs.data_.f.flags = kNullFlag; } template bool StringEqual(const GenericValue& rhs) const { RAPIDJSON_ASSERT(IsString()); RAPIDJSON_ASSERT(rhs.IsString()); const SizeType len1 = GetStringLength(); const SizeType len2 = rhs.GetStringLength(); if(len1 != len2) { return false; } const Ch* const str1 = GetString(); const Ch* const str2 = rhs.GetString(); if(str1 == str2) { return true; } // fast path for constant string return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); } Data data_; }; //! GenericValue with UTF8 encoding typedef GenericValue > Value; /////////////////////////////////////////////////////////////////////////////// // GenericDocument //! A document for parsing JSON text as DOM. /*! \note implements Handler concept \tparam Encoding Encoding for both parsing and string storage. \tparam Allocator Allocator for allocating memory for the DOM \tparam StackAllocator Allocator for allocating memory for stack during parsing. \warning Although GenericDocument inherits from GenericValue, the API does \b not provide any virtual functions, especially no virtual destructor. To avoid memory leaks, do not \c delete a GenericDocument object via a pointer to a GenericValue. */ template , typename StackAllocator = CrtAllocator> class GenericDocument : public GenericValue { public: typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. typedef GenericValue ValueType; //!< Value type of the document. typedef Allocator AllocatorType; //!< Allocator type from template parameter. //! Constructor /*! Creates an empty document of specified type. \param type Mandatory type of object to create. \param allocator Optional allocator for allocating memory. \param stackCapacity Optional initial capacity of stack in bytes. \param stackAllocator Optional allocator for allocating memory for stack. */ explicit GenericDocument(Type type, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : GenericValue(type), allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() { if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); } //! Constructor /*! Creates an empty document which type is Null. \param allocator Optional allocator for allocating memory. \param stackCapacity Optional initial capacity of stack in bytes. \param stackAllocator Optional allocator for allocating memory for stack. */ GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() { if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS //! Move constructor in C++11 GenericDocument(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT : ValueType(std::forward(rhs)), // explicit cast to avoid prohibited move from Document allocator_(rhs.allocator_), ownAllocator_(rhs.ownAllocator_), stack_(std::move(rhs.stack_)), parseResult_(rhs.parseResult_) { rhs.allocator_ = 0; rhs.ownAllocator_ = 0; rhs.parseResult_ = ParseResult(); } #endif ~GenericDocument() { Destroy(); } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS //! Move assignment in C++11 GenericDocument& operator=(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT { // The cast to ValueType is necessary here, because otherwise it would // attempt to call GenericValue's templated assignment operator. ValueType::operator=(std::forward(rhs)); // Calling the destructor here would prematurely call stack_'s destructor Destroy(); allocator_ = rhs.allocator_; ownAllocator_ = rhs.ownAllocator_; stack_ = std::move(rhs.stack_); parseResult_ = rhs.parseResult_; rhs.allocator_ = 0; rhs.ownAllocator_ = 0; rhs.parseResult_ = ParseResult(); return *this; } #endif //! Exchange the contents of this document with those of another. /*! \param rhs Another document. \note Constant complexity. \see GenericValue::Swap */ GenericDocument& Swap(GenericDocument& rhs) RAPIDJSON_NOEXCEPT { ValueType::Swap(rhs); stack_.Swap(rhs.stack_); internal::Swap(allocator_, rhs.allocator_); internal::Swap(ownAllocator_, rhs.ownAllocator_); internal::Swap(parseResult_, rhs.parseResult_); return *this; } //! free-standing swap function helper /*! Helper function to enable support for common swap implementation pattern based on \c std::swap: \code void swap(MyClass& a, MyClass& b) { using std::swap; swap(a.doc, b.doc); // ... } \endcode \see Swap() */ friend inline void swap(GenericDocument& a, GenericDocument& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } //! Populate this document by a generator which produces SAX events. /*! \tparam Generator A functor with bool f(Handler) prototype. \param g Generator functor which sends SAX events to the parameter. \return The document itself for fluent API. */ template GenericDocument& Populate(Generator& g) { ClearStackOnExit scope(*this); if (g(*this)) { RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document } return *this; } //!@name Parse from stream //!@{ //! Parse JSON text from an input stream (with Encoding conversion) /*! \tparam parseFlags Combination of \ref ParseFlag. \tparam SourceEncoding Encoding of input stream \tparam InputStream Type of input stream, implementing Stream concept \param is Input stream to be parsed. \return The document itself for fluent API. */ template GenericDocument& ParseStream(InputStream& is) { GenericReader reader( stack_.HasAllocator() ? &stack_.GetAllocator() : 0); ClearStackOnExit scope(*this); parseResult_ = reader.template Parse(is, *this); if (parseResult_) { RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document } return *this; } //! Parse JSON text from an input stream /*! \tparam parseFlags Combination of \ref ParseFlag. \tparam InputStream Type of input stream, implementing Stream concept \param is Input stream to be parsed. \return The document itself for fluent API. */ template GenericDocument& ParseStream(InputStream& is) { return ParseStream(is); } //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) /*! \tparam InputStream Type of input stream, implementing Stream concept \param is Input stream to be parsed. \return The document itself for fluent API. */ template GenericDocument& ParseStream(InputStream& is) { return ParseStream(is); } //!@} //!@name Parse in-place from mutable string //!@{ //! Parse JSON text from a mutable string /*! \tparam parseFlags Combination of \ref ParseFlag. \param str Mutable zero-terminated string to be parsed. \return The document itself for fluent API. */ template GenericDocument& ParseInsitu(Ch* str) { GenericInsituStringStream s(str); return ParseStream(s); } //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) /*! \param str Mutable zero-terminated string to be parsed. \return The document itself for fluent API. */ GenericDocument& ParseInsitu(Ch* str) { return ParseInsitu(str); } //!@} //!@name Parse from read-only string //!@{ //! Parse JSON text from a read-only string (with Encoding conversion) /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). \tparam SourceEncoding Transcoding from input Encoding \param str Read-only zero-terminated string to be parsed. */ template GenericDocument& Parse(const typename SourceEncoding::Ch* str) { RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); GenericStringStream s(str); return ParseStream(s); } //! Parse JSON text from a read-only string /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). \param str Read-only zero-terminated string to be parsed. */ template GenericDocument& Parse(const Ch* str) { return Parse(str); } //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) /*! \param str Read-only zero-terminated string to be parsed. */ GenericDocument& Parse(const Ch* str) { return Parse(str); } template GenericDocument& Parse(const typename SourceEncoding::Ch* str, size_t length) { RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); MemoryStream ms(reinterpret_cast(str), length * sizeof(typename SourceEncoding::Ch)); EncodedInputStream is(ms); ParseStream(is); return *this; } template GenericDocument& Parse(const Ch* str, size_t length) { return Parse(str, length); } GenericDocument& Parse(const Ch* str, size_t length) { return Parse(str, length); } #if RAPIDJSON_HAS_STDSTRING template GenericDocument& Parse(const std::basic_string& str) { // c_str() is constant complexity according to standard. Should be faster than Parse(const char*, size_t) return Parse(str.c_str()); } template GenericDocument& Parse(const std::basic_string& str) { return Parse(str.c_str()); } GenericDocument& Parse(const std::basic_string& str) { return Parse(str); } #endif // RAPIDJSON_HAS_STDSTRING //!@} //!@name Handling parse errors //!@{ //! Whether a parse error has occured in the last parsing. bool HasParseError() const { return parseResult_.IsError(); } //! Get the \ref ParseErrorCode of last parsing. ParseErrorCode GetParseError() const { return parseResult_.Code(); } //! Get the position of last parsing error in input, 0 otherwise. size_t GetErrorOffset() const { return parseResult_.Offset(); } //! Implicit conversion to get the last parse result #ifndef __clang // -Wdocumentation /*! \return \ref ParseResult of the last parse operation \code Document doc; ParseResult ok = doc.Parse(json); if (!ok) printf( "JSON parse error: %s (%u)\n", GetParseError_En(ok.Code()), ok.Offset()); \endcode */ #endif operator ParseResult() const { return parseResult_; } //!@} //! Get the allocator of this document. Allocator& GetAllocator() { RAPIDJSON_ASSERT(allocator_); return *allocator_; } //! Get the capacity of stack in bytes. size_t GetStackCapacity() const { return stack_.GetCapacity(); } private: // clear stack on any exit from ParseStream, e.g. due to exception struct ClearStackOnExit { explicit ClearStackOnExit(GenericDocument& d) : d_(d) {} ~ClearStackOnExit() { d_.ClearStack(); } private: ClearStackOnExit(const ClearStackOnExit&); ClearStackOnExit& operator=(const ClearStackOnExit&); GenericDocument& d_; }; // callers of the following private Handler functions // template friend class GenericReader; // for parsing template friend class GenericValue; // for deep copying public: // Implementation of Handler bool Null() { new (stack_.template Push()) ValueType(); return true; } bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } bool Int(int i) { new (stack_.template Push()) ValueType(i); return true; } bool Uint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } bool Int64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } bool Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } bool Double(double d) { new (stack_.template Push()) ValueType(d); return true; } bool RawNumber(const Ch* str, SizeType length, bool copy) { if (copy) new (stack_.template Push()) ValueType(str, length, GetAllocator()); else new (stack_.template Push()) ValueType(str, length); return true; } bool String(const Ch* str, SizeType length, bool copy) { if (copy) new (stack_.template Push()) ValueType(str, length, GetAllocator()); else new (stack_.template Push()) ValueType(str, length); return true; } bool StartObject() { new (stack_.template Push()) ValueType(kObjectType); return true; } bool Key(const Ch* str, SizeType length, bool copy) { return String(str, length, copy); } bool EndObject(SizeType memberCount) { typename ValueType::Member* members = stack_.template Pop(memberCount); stack_.template Top()->SetObjectRaw(members, memberCount, GetAllocator()); return true; } bool StartArray() { new (stack_.template Push()) ValueType(kArrayType); return true; } bool EndArray(SizeType elementCount) { ValueType* elements = stack_.template Pop(elementCount); stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); return true; } private: //! Prohibit copying GenericDocument(const GenericDocument&); //! Prohibit assignment GenericDocument& operator=(const GenericDocument&); void ClearStack() { if (Allocator::kNeedFree) while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) (stack_.template Pop(1))->~ValueType(); else stack_.Clear(); stack_.ShrinkToFit(); } void Destroy() { RAPIDJSON_DELETE(ownAllocator_); } static const size_t kDefaultStackCapacity = 1024; Allocator* allocator_; Allocator* ownAllocator_; internal::Stack stack_; ParseResult parseResult_; }; //! GenericDocument with UTF8 encoding typedef GenericDocument > Document; //! Helper class for accessing Value of array type. /*! Instance of this helper class is obtained by \c GenericValue::GetArray(). In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. */ template class GenericArray { public: typedef GenericArray ConstArray; typedef GenericArray Array; typedef ValueT PlainType; typedef typename internal::MaybeAddConst::Type ValueType; typedef ValueType* ValueIterator; // This may be const or non-const iterator typedef const ValueT* ConstValueIterator; typedef typename ValueType::AllocatorType AllocatorType; typedef typename ValueType::StringRefType StringRefType; template friend class GenericValue; GenericArray(const GenericArray& rhs) : value_(rhs.value_) {} GenericArray& operator=(const GenericArray& rhs) { value_ = rhs.value_; return *this; } ~GenericArray() {} SizeType Size() const { return value_.Size(); } SizeType Capacity() const { return value_.Capacity(); } bool Empty() const { return value_.Empty(); } void Clear() const { value_.Clear(); } ValueType& operator[](SizeType index) const { return value_[index]; } ValueIterator Begin() const { return value_.Begin(); } ValueIterator End() const { return value_.End(); } GenericArray Reserve(SizeType newCapacity, AllocatorType &allocator) const { value_.Reserve(newCapacity, allocator); return *this; } GenericArray PushBack(ValueType& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS GenericArray PushBack(ValueType&& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } #endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS GenericArray PushBack(StringRefType value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (const GenericArray&)) PushBack(T value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } GenericArray PopBack() const { value_.PopBack(); return *this; } ValueIterator Erase(ConstValueIterator pos) const { return value_.Erase(pos); } ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) const { return value_.Erase(first, last); } #if RAPIDJSON_HAS_CXX11_RANGE_FOR ValueIterator begin() const { return value_.Begin(); } ValueIterator end() const { return value_.End(); } #endif private: GenericArray(); GenericArray(ValueType& value) : value_(value) {} ValueType& value_; }; //! Helper class for accessing Value of object type. /*! Instance of this helper class is obtained by \c GenericValue::GetObject(). In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. */ template class GenericObject { public: typedef GenericObject ConstObject; typedef GenericObject Object; typedef ValueT PlainType; typedef typename internal::MaybeAddConst::Type ValueType; typedef GenericMemberIterator MemberIterator; // This may be const or non-const iterator typedef GenericMemberIterator ConstMemberIterator; typedef typename ValueType::AllocatorType AllocatorType; typedef typename ValueType::StringRefType StringRefType; typedef typename ValueType::EncodingType EncodingType; typedef typename ValueType::Ch Ch; template friend class GenericValue; GenericObject(const GenericObject& rhs) : value_(rhs.value_) {} GenericObject& operator=(const GenericObject& rhs) { value_ = rhs.value_; return *this; } ~GenericObject() {} SizeType MemberCount() const { return value_.MemberCount(); } bool ObjectEmpty() const { return value_.ObjectEmpty(); } template ValueType& operator[](T* name) const { return value_[name]; } template ValueType& operator[](const GenericValue& name) const { return value_[name]; } #if RAPIDJSON_HAS_STDSTRING ValueType& operator[](const std::basic_string& name) const { return value_[name]; } #endif MemberIterator MemberBegin() const { return value_.MemberBegin(); } MemberIterator MemberEnd() const { return value_.MemberEnd(); } bool HasMember(const Ch* name) const { return value_.HasMember(name); } #if RAPIDJSON_HAS_STDSTRING bool HasMember(const std::basic_string& name) const { return value_.HasMember(name); } #endif template bool HasMember(const GenericValue& name) const { return value_.HasMember(name); } MemberIterator FindMember(const Ch* name) const { return value_.FindMember(name); } template MemberIterator FindMember(const GenericValue& name) const { return value_.FindMember(name); } #if RAPIDJSON_HAS_STDSTRING MemberIterator FindMember(const std::basic_string& name) const { return value_.FindMember(name); } #endif GenericObject AddMember(ValueType& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } GenericObject AddMember(ValueType& name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } #if RAPIDJSON_HAS_STDSTRING GenericObject AddMember(ValueType& name, std::basic_string& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) AddMember(ValueType& name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS GenericObject AddMember(ValueType&& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } GenericObject AddMember(ValueType&& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } GenericObject AddMember(ValueType& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } GenericObject AddMember(StringRefType name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } #endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS GenericObject AddMember(StringRefType name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } GenericObject AddMember(StringRefType name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericObject)) AddMember(StringRefType name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } void RemoveAllMembers() { value_.RemoveAllMembers(); } bool RemoveMember(const Ch* name) const { return value_.RemoveMember(name); } #if RAPIDJSON_HAS_STDSTRING bool RemoveMember(const std::basic_string& name) const { return value_.RemoveMember(name); } #endif template bool RemoveMember(const GenericValue& name) const { return value_.RemoveMember(name); } MemberIterator RemoveMember(MemberIterator m) const { return value_.RemoveMember(m); } MemberIterator EraseMember(ConstMemberIterator pos) const { return value_.EraseMember(pos); } MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) const { return value_.EraseMember(first, last); } bool EraseMember(const Ch* name) const { return value_.EraseMember(name); } #if RAPIDJSON_HAS_STDSTRING bool EraseMember(const std::basic_string& name) const { return EraseMember(ValueType(StringRef(name))); } #endif template bool EraseMember(const GenericValue& name) const { return value_.EraseMember(name); } #if RAPIDJSON_HAS_CXX11_RANGE_FOR MemberIterator begin() const { return value_.MemberBegin(); } MemberIterator end() const { return value_.MemberEnd(); } #endif private: GenericObject(); GenericObject(ValueType& value) : value_(value) {} ValueType& value_; }; RAPIDJSON_NAMESPACE_END #ifdef _MINWINDEF_ // see: http://stackoverflow.com/questions/22744262/cant-call-stdmax-because-minwindef-h-defines-max #ifndef NOMINMAX #pragma pop_macro("min") #pragma pop_macro("max") #endif #endif RAPIDJSON_DIAG_POP #endif // RAPIDJSON_DOCUMENT_H_ ================================================ FILE: third-party/rapidjson/encodedstream.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_ENCODEDSTREAM_H_ #define RAPIDJSON_ENCODEDSTREAM_H_ #include "stream.h" #include "memorystream.h" #ifdef __GNUC__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(effc++) #endif #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(padded) #endif RAPIDJSON_NAMESPACE_BEGIN //! Input byte stream wrapper with a statically bound encoding. /*! \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. \tparam InputByteStream Type of input byte stream. For example, FileReadStream. */ template class EncodedInputStream { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); public: typedef typename Encoding::Ch Ch; EncodedInputStream(InputByteStream& is) : is_(is) { current_ = Encoding::TakeBOM(is_); } Ch Peek() const { return current_; } Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; } size_t Tell() const { return is_.Tell(); } // Not implemented void Put(Ch) { RAPIDJSON_ASSERT(false); } void Flush() { RAPIDJSON_ASSERT(false); } Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } private: EncodedInputStream(const EncodedInputStream&); EncodedInputStream& operator=(const EncodedInputStream&); InputByteStream& is_; Ch current_; }; //! Specialized for UTF8 MemoryStream. template <> class EncodedInputStream, MemoryStream> { public: typedef UTF8<>::Ch Ch; EncodedInputStream(MemoryStream& is) : is_(is) { if (static_cast(is_.Peek()) == 0xEFu) is_.Take(); if (static_cast(is_.Peek()) == 0xBBu) is_.Take(); if (static_cast(is_.Peek()) == 0xBFu) is_.Take(); } Ch Peek() const { return is_.Peek(); } Ch Take() { return is_.Take(); } size_t Tell() const { return is_.Tell(); } // Not implemented void Put(Ch) {} void Flush() {} Ch* PutBegin() { return 0; } size_t PutEnd(Ch*) { return 0; } MemoryStream& is_; private: EncodedInputStream(const EncodedInputStream&); EncodedInputStream& operator=(const EncodedInputStream&); }; //! Output byte stream wrapper with statically bound encoding. /*! \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. \tparam OutputByteStream Type of input byte stream. For example, FileWriteStream. */ template class EncodedOutputStream { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); public: typedef typename Encoding::Ch Ch; EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) { if (putBOM) Encoding::PutBOM(os_); } void Put(Ch c) { Encoding::Put(os_, c); } void Flush() { os_.Flush(); } // Not implemented Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} Ch Take() { RAPIDJSON_ASSERT(false); return 0;} size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } private: EncodedOutputStream(const EncodedOutputStream&); EncodedOutputStream& operator=(const EncodedOutputStream&); OutputByteStream& os_; }; #define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x //! Input stream wrapper with dynamically bound encoding and automatic encoding detection. /*! \tparam CharType Type of character for reading. \tparam InputByteStream type of input byte stream to be wrapped. */ template class AutoUTFInputStream { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); public: typedef CharType Ch; //! Constructor. /*! \param is input stream to be wrapped. \param type UTF encoding type if it is not detected from the stream. */ AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) { RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); DetectType(); static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) }; takeFunc_ = f[type_]; current_ = takeFunc_(*is_); } UTFType GetType() const { return type_; } bool HasBOM() const { return hasBOM_; } Ch Peek() const { return current_; } Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; } size_t Tell() const { return is_->Tell(); } // Not implemented void Put(Ch) { RAPIDJSON_ASSERT(false); } void Flush() { RAPIDJSON_ASSERT(false); } Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } private: AutoUTFInputStream(const AutoUTFInputStream&); AutoUTFInputStream& operator=(const AutoUTFInputStream&); // Detect encoding type with BOM or RFC 4627 void DetectType() { // BOM (Byte Order Mark): // 00 00 FE FF UTF-32BE // FF FE 00 00 UTF-32LE // FE FF UTF-16BE // FF FE UTF-16LE // EF BB BF UTF-8 const unsigned char* c = reinterpret_cast(is_->Peek4()); if (!c) return; unsigned bom = static_cast(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)); hasBOM_ = false; if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); } else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); } else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); } // RFC 4627: Section 3 // "Since the first two characters of a JSON text will always be ASCII // characters [RFC0020], it is possible to determine whether an octet // stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking // at the pattern of nulls in the first four octets." // 00 00 00 xx UTF-32BE // 00 xx 00 xx UTF-16BE // xx 00 00 00 UTF-32LE // xx 00 xx 00 UTF-16LE // xx xx xx xx UTF-8 if (!hasBOM_) { int pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0); switch (pattern) { case 0x08: type_ = kUTF32BE; break; case 0x0A: type_ = kUTF16BE; break; case 0x01: type_ = kUTF32LE; break; case 0x05: type_ = kUTF16LE; break; case 0x0F: type_ = kUTF8; break; default: break; // Use type defined by user. } } // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); } typedef Ch (*TakeFunc)(InputByteStream& is); InputByteStream* is_; UTFType type_; Ch current_; TakeFunc takeFunc_; bool hasBOM_; }; //! Output stream wrapper with dynamically bound encoding and automatic encoding detection. /*! \tparam CharType Type of character for writing. \tparam OutputByteStream type of output byte stream to be wrapped. */ template class AutoUTFOutputStream { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); public: typedef CharType Ch; //! Constructor. /*! \param os output stream to be wrapped. \param type UTF encoding type. \param putBOM Whether to write BOM at the beginning of the stream. */ AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) { RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) }; putFunc_ = f[type_]; if (putBOM) PutBOM(); } UTFType GetType() const { return type_; } void Put(Ch c) { putFunc_(*os_, c); } void Flush() { os_->Flush(); } // Not implemented Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} Ch Take() { RAPIDJSON_ASSERT(false); return 0;} size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } private: AutoUTFOutputStream(const AutoUTFOutputStream&); AutoUTFOutputStream& operator=(const AutoUTFOutputStream&); void PutBOM() { typedef void (*PutBOMFunc)(OutputByteStream&); static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) }; f[type_](*os_); } typedef void (*PutFunc)(OutputByteStream&, Ch); OutputByteStream* os_; UTFType type_; PutFunc putFunc_; }; #undef RAPIDJSON_ENCODINGS_FUNC RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #ifdef __GNUC__ RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_FILESTREAM_H_ ================================================ FILE: third-party/rapidjson/encodings.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_ENCODINGS_H_ #define RAPIDJSON_ENCODINGS_H_ #include "rapidjson.h" #ifdef _MSC_VER RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data RAPIDJSON_DIAG_OFF(4702) // unreachable code #elif defined(__GNUC__) RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(effc++) RAPIDJSON_DIAG_OFF(overflow) #endif RAPIDJSON_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// // Encoding /*! \class rapidjson::Encoding \brief Concept for encoding of Unicode characters. \code concept Encoding { typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition. enum { supportUnicode = 1 }; // or 0 if not supporting unicode //! \brief Encode a Unicode codepoint to an output stream. //! \param os Output stream. //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. template static void Encode(OutputStream& os, unsigned codepoint); //! \brief Decode a Unicode codepoint from an input stream. //! \param is Input stream. //! \param codepoint Output of the unicode codepoint. //! \return true if a valid codepoint can be decoded from the stream. template static bool Decode(InputStream& is, unsigned* codepoint); //! \brief Validate one Unicode codepoint from an encoded stream. //! \param is Input stream to obtain codepoint. //! \param os Output for copying one codepoint. //! \return true if it is valid. //! \note This function just validating and copying the codepoint without actually decode it. template static bool Validate(InputStream& is, OutputStream& os); // The following functions are deal with byte streams. //! Take a character from input byte stream, skip BOM if exist. template static CharType TakeBOM(InputByteStream& is); //! Take a character from input byte stream. template static Ch Take(InputByteStream& is); //! Put BOM to output byte stream. template static void PutBOM(OutputByteStream& os); //! Put a character to output byte stream. template static void Put(OutputByteStream& os, Ch c); }; \endcode */ /////////////////////////////////////////////////////////////////////////////// // UTF8 //! UTF-8 encoding. /*! http://en.wikipedia.org/wiki/UTF-8 http://tools.ietf.org/html/rfc3629 \tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char. \note implements Encoding concept */ template struct UTF8 { typedef CharType Ch; enum { supportUnicode = 1 }; template static void Encode(OutputStream& os, unsigned codepoint) { if (codepoint <= 0x7F) os.Put(static_cast(codepoint & 0xFF)); else if (codepoint <= 0x7FF) { os.Put(static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); os.Put(static_cast(0x80 | ((codepoint & 0x3F)))); } else if (codepoint <= 0xFFFF) { os.Put(static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); os.Put(static_cast(0x80 | (codepoint & 0x3F))); } else { RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); os.Put(static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); os.Put(static_cast(0x80 | ((codepoint >> 12) & 0x3F))); os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); os.Put(static_cast(0x80 | (codepoint & 0x3F))); } } template static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { if (codepoint <= 0x7F) PutUnsafe(os, static_cast(codepoint & 0xFF)); else if (codepoint <= 0x7FF) { PutUnsafe(os, static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); PutUnsafe(os, static_cast(0x80 | ((codepoint & 0x3F)))); } else if (codepoint <= 0xFFFF) { PutUnsafe(os, static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); } else { RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); PutUnsafe(os, static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); PutUnsafe(os, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); } } template static bool Decode(InputStream& is, unsigned* codepoint) { #define COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast(c) & 0x3Fu) #define TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) #define TAIL() COPY(); TRANS(0x70) typename InputStream::Ch c = is.Take(); if (!(c & 0x80)) { *codepoint = static_cast(c); return true; } unsigned char type = GetRange(static_cast(c)); if (type >= 32) { *codepoint = 0; } else { *codepoint = (0xFFu >> type) & static_cast(c); } bool result = true; switch (type) { case 2: TAIL(); return result; case 3: TAIL(); TAIL(); return result; case 4: COPY(); TRANS(0x50); TAIL(); return result; case 5: COPY(); TRANS(0x10); TAIL(); TAIL(); return result; case 6: TAIL(); TAIL(); TAIL(); return result; case 10: COPY(); TRANS(0x20); TAIL(); return result; case 11: COPY(); TRANS(0x60); TAIL(); TAIL(); return result; default: return false; } #undef COPY #undef TRANS #undef TAIL } template static bool Validate(InputStream& is, OutputStream& os) { #define COPY() os.Put(c = is.Take()) #define TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) #define TAIL() COPY(); TRANS(0x70) Ch c; COPY(); if (!(c & 0x80)) return true; bool result = true; switch (GetRange(static_cast(c))) { case 2: TAIL(); return result; case 3: TAIL(); TAIL(); return result; case 4: COPY(); TRANS(0x50); TAIL(); return result; case 5: COPY(); TRANS(0x10); TAIL(); TAIL(); return result; case 6: TAIL(); TAIL(); TAIL(); return result; case 10: COPY(); TRANS(0x20); TAIL(); return result; case 11: COPY(); TRANS(0x60); TAIL(); TAIL(); return result; default: return false; } #undef COPY #undef TRANS #undef TAIL } static unsigned char GetRange(unsigned char c) { // Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ // With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types. static const unsigned char type[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, }; return type[c]; } template static CharType TakeBOM(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); typename InputByteStream::Ch c = Take(is); if (static_cast(c) != 0xEFu) return c; c = is.Take(); if (static_cast(c) != 0xBBu) return c; c = is.Take(); if (static_cast(c) != 0xBFu) return c; c = is.Take(); return c; } template static Ch Take(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); return static_cast(is.Take()); } template static void PutBOM(OutputByteStream& os) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(0xEFu)); os.Put(static_cast(0xBBu)); os.Put(static_cast(0xBFu)); } template static void Put(OutputByteStream& os, Ch c) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(c)); } }; /////////////////////////////////////////////////////////////////////////////// // UTF16 //! UTF-16 encoding. /*! http://en.wikipedia.org/wiki/UTF-16 http://tools.ietf.org/html/rfc2781 \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. \note implements Encoding concept \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. For streaming, use UTF16LE and UTF16BE, which handle endianness. */ template struct UTF16 { typedef CharType Ch; RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2); enum { supportUnicode = 1 }; template static void Encode(OutputStream& os, unsigned codepoint) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); if (codepoint <= 0xFFFF) { RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair os.Put(static_cast(codepoint)); } else { RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); unsigned v = codepoint - 0x10000; os.Put(static_cast((v >> 10) | 0xD800)); os.Put(static_cast((v & 0x3FF) | 0xDC00)); } } template static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); if (codepoint <= 0xFFFF) { RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair PutUnsafe(os, static_cast(codepoint)); } else { RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); unsigned v = codepoint - 0x10000; PutUnsafe(os, static_cast((v >> 10) | 0xD800)); PutUnsafe(os, static_cast((v & 0x3FF) | 0xDC00)); } } template static bool Decode(InputStream& is, unsigned* codepoint) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); typename InputStream::Ch c = is.Take(); if (c < 0xD800 || c > 0xDFFF) { *codepoint = static_cast(c); return true; } else if (c <= 0xDBFF) { *codepoint = (static_cast(c) & 0x3FF) << 10; c = is.Take(); *codepoint |= (static_cast(c) & 0x3FF); *codepoint += 0x10000; return c >= 0xDC00 && c <= 0xDFFF; } return false; } template static bool Validate(InputStream& is, OutputStream& os) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); typename InputStream::Ch c; os.Put(static_cast(c = is.Take())); if (c < 0xD800 || c > 0xDFFF) return true; else if (c <= 0xDBFF) { os.Put(c = is.Take()); return c >= 0xDC00 && c <= 0xDFFF; } return false; } }; //! UTF-16 little endian encoding. template struct UTF16LE : UTF16 { template static CharType TakeBOM(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); CharType c = Take(is); return static_cast(c) == 0xFEFFu ? Take(is) : c; } template static CharType Take(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); unsigned c = static_cast(is.Take()); c |= static_cast(static_cast(is.Take())) << 8; return static_cast(c); } template static void PutBOM(OutputByteStream& os) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(0xFFu)); os.Put(static_cast(0xFEu)); } template static void Put(OutputByteStream& os, CharType c) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(static_cast(c) & 0xFFu)); os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); } }; //! UTF-16 big endian encoding. template struct UTF16BE : UTF16 { template static CharType TakeBOM(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); CharType c = Take(is); return static_cast(c) == 0xFEFFu ? Take(is) : c; } template static CharType Take(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); unsigned c = static_cast(static_cast(is.Take())) << 8; c |= static_cast(is.Take()); return static_cast(c); } template static void PutBOM(OutputByteStream& os) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(0xFEu)); os.Put(static_cast(0xFFu)); } template static void Put(OutputByteStream& os, CharType c) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); os.Put(static_cast(static_cast(c) & 0xFFu)); } }; /////////////////////////////////////////////////////////////////////////////// // UTF32 //! UTF-32 encoding. /*! http://en.wikipedia.org/wiki/UTF-32 \tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. \note implements Encoding concept \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. For streaming, use UTF32LE and UTF32BE, which handle endianness. */ template struct UTF32 { typedef CharType Ch; RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4); enum { supportUnicode = 1 }; template static void Encode(OutputStream& os, unsigned codepoint) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); os.Put(codepoint); } template static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); PutUnsafe(os, codepoint); } template static bool Decode(InputStream& is, unsigned* codepoint) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); Ch c = is.Take(); *codepoint = c; return c <= 0x10FFFF; } template static bool Validate(InputStream& is, OutputStream& os) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); Ch c; os.Put(c = is.Take()); return c <= 0x10FFFF; } }; //! UTF-32 little endian enocoding. template struct UTF32LE : UTF32 { template static CharType TakeBOM(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); CharType c = Take(is); return static_cast(c) == 0x0000FEFFu ? Take(is) : c; } template static CharType Take(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); unsigned c = static_cast(is.Take()); c |= static_cast(static_cast(is.Take())) << 8; c |= static_cast(static_cast(is.Take())) << 16; c |= static_cast(static_cast(is.Take())) << 24; return static_cast(c); } template static void PutBOM(OutputByteStream& os) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(0xFFu)); os.Put(static_cast(0xFEu)); os.Put(static_cast(0x00u)); os.Put(static_cast(0x00u)); } template static void Put(OutputByteStream& os, CharType c) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(c & 0xFFu)); os.Put(static_cast((c >> 8) & 0xFFu)); os.Put(static_cast((c >> 16) & 0xFFu)); os.Put(static_cast((c >> 24) & 0xFFu)); } }; //! UTF-32 big endian encoding. template struct UTF32BE : UTF32 { template static CharType TakeBOM(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); CharType c = Take(is); return static_cast(c) == 0x0000FEFFu ? Take(is) : c; } template static CharType Take(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); unsigned c = static_cast(static_cast(is.Take())) << 24; c |= static_cast(static_cast(is.Take())) << 16; c |= static_cast(static_cast(is.Take())) << 8; c |= static_cast(static_cast(is.Take())); return static_cast(c); } template static void PutBOM(OutputByteStream& os) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(0x00u)); os.Put(static_cast(0x00u)); os.Put(static_cast(0xFEu)); os.Put(static_cast(0xFFu)); } template static void Put(OutputByteStream& os, CharType c) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast((c >> 24) & 0xFFu)); os.Put(static_cast((c >> 16) & 0xFFu)); os.Put(static_cast((c >> 8) & 0xFFu)); os.Put(static_cast(c & 0xFFu)); } }; /////////////////////////////////////////////////////////////////////////////// // ASCII //! ASCII encoding. /*! http://en.wikipedia.org/wiki/ASCII \tparam CharType Code unit for storing 7-bit ASCII data. Default is char. \note implements Encoding concept */ template struct ASCII { typedef CharType Ch; enum { supportUnicode = 0 }; template static void Encode(OutputStream& os, unsigned codepoint) { RAPIDJSON_ASSERT(codepoint <= 0x7F); os.Put(static_cast(codepoint & 0xFF)); } template static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { RAPIDJSON_ASSERT(codepoint <= 0x7F); PutUnsafe(os, static_cast(codepoint & 0xFF)); } template static bool Decode(InputStream& is, unsigned* codepoint) { uint8_t c = static_cast(is.Take()); *codepoint = c; return c <= 0X7F; } template static bool Validate(InputStream& is, OutputStream& os) { uint8_t c = static_cast(is.Take()); os.Put(static_cast(c)); return c <= 0x7F; } template static CharType TakeBOM(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); uint8_t c = static_cast(Take(is)); return static_cast(c); } template static Ch Take(InputByteStream& is) { RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); return static_cast(is.Take()); } template static void PutBOM(OutputByteStream& os) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); (void)os; } template static void Put(OutputByteStream& os, Ch c) { RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); os.Put(static_cast(c)); } }; /////////////////////////////////////////////////////////////////////////////// // AutoUTF //! Runtime-specified UTF encoding type of a stream. enum UTFType { kUTF8 = 0, //!< UTF-8. kUTF16LE = 1, //!< UTF-16 little endian. kUTF16BE = 2, //!< UTF-16 big endian. kUTF32LE = 3, //!< UTF-32 little endian. kUTF32BE = 4 //!< UTF-32 big endian. }; //! Dynamically select encoding according to stream's runtime-specified UTF encoding type. /*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType(). */ template struct AutoUTF { typedef CharType Ch; enum { supportUnicode = 1 }; #define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x template static RAPIDJSON_FORCEINLINE void Encode(OutputStream& os, unsigned codepoint) { typedef void (*EncodeFunc)(OutputStream&, unsigned); static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) }; (*f[os.GetType()])(os, codepoint); } template static RAPIDJSON_FORCEINLINE void EncodeUnsafe(OutputStream& os, unsigned codepoint) { typedef void (*EncodeFunc)(OutputStream&, unsigned); static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) }; (*f[os.GetType()])(os, codepoint); } template static RAPIDJSON_FORCEINLINE bool Decode(InputStream& is, unsigned* codepoint) { typedef bool (*DecodeFunc)(InputStream&, unsigned*); static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) }; return (*f[is.GetType()])(is, codepoint); } template static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { typedef bool (*ValidateFunc)(InputStream&, OutputStream&); static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) }; return (*f[is.GetType()])(is, os); } #undef RAPIDJSON_ENCODINGS_FUNC }; /////////////////////////////////////////////////////////////////////////////// // Transcoder //! Encoding conversion. template struct Transcoder { //! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream. template static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { unsigned codepoint; if (!SourceEncoding::Decode(is, &codepoint)) return false; TargetEncoding::Encode(os, codepoint); return true; } template static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { unsigned codepoint; if (!SourceEncoding::Decode(is, &codepoint)) return false; TargetEncoding::EncodeUnsafe(os, codepoint); return true; } //! Validate one Unicode codepoint from an encoded stream. template static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { return Transcode(is, os); // Since source/target encoding is different, must transcode. } }; // Forward declaration. template inline void PutUnsafe(Stream& stream, typename Stream::Ch c); //! Specialization of Transcoder with same source and target encoding. template struct Transcoder { template static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class. return true; } template static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class. return true; } template static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { return Encoding::Validate(is, os); // source/target encoding are the same } }; RAPIDJSON_NAMESPACE_END #if defined(__GNUC__) || defined(_MSC_VER) RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_ENCODINGS_H_ ================================================ FILE: third-party/rapidjson/error/en.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_ERROR_EN_H_ #define RAPIDJSON_ERROR_EN_H_ #include "error.h" #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(switch-enum) RAPIDJSON_DIAG_OFF(covered-switch-default) #endif RAPIDJSON_NAMESPACE_BEGIN //! Maps error code of parsing into error message. /*! \ingroup RAPIDJSON_ERRORS \param parseErrorCode Error code obtained in parsing. \return the error message. \note User can make a copy of this function for localization. Using switch-case is safer for future modification of error codes. */ inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { switch (parseErrorCode) { case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); default: return RAPIDJSON_ERROR_STRING("Unknown error."); } } RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_ERROR_EN_H_ ================================================ FILE: third-party/rapidjson/error/error.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_ERROR_ERROR_H_ #define RAPIDJSON_ERROR_ERROR_H_ #include "../rapidjson.h" #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(padded) #endif /*! \file error.h */ /*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_ERROR_CHARTYPE //! Character type of error messages. /*! \ingroup RAPIDJSON_ERRORS The default character type is \c char. On Windows, user can define this macro as \c TCHAR for supporting both unicode/non-unicode settings. */ #ifndef RAPIDJSON_ERROR_CHARTYPE #define RAPIDJSON_ERROR_CHARTYPE char #endif /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_ERROR_STRING //! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. /*! \ingroup RAPIDJSON_ERRORS By default this conversion macro does nothing. On Windows, user can define this macro as \c _T(x) for supporting both unicode/non-unicode settings. */ #ifndef RAPIDJSON_ERROR_STRING #define RAPIDJSON_ERROR_STRING(x) x #endif RAPIDJSON_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// // ParseErrorCode //! Error code of parsing. /*! \ingroup RAPIDJSON_ERRORS \see GenericReader::Parse, GenericReader::GetParseErrorCode */ enum ParseErrorCode { kParseErrorNone = 0, //!< No error. kParseErrorDocumentEmpty, //!< The document is empty. kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. kParseErrorValueInvalid, //!< Invalid value. kParseErrorObjectMissName, //!< Missing a name for object member. kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. kParseErrorNumberTooBig, //!< Number too big to be stored in double. kParseErrorNumberMissFraction, //!< Miss fraction part in number. kParseErrorNumberMissExponent, //!< Miss exponent in number. kParseErrorTermination, //!< Parsing was terminated. kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. }; //! Result of parsing (wraps ParseErrorCode) /*! \ingroup RAPIDJSON_ERRORS \code Document doc; ParseResult ok = doc.Parse("[42]"); if (!ok) { fprintf(stderr, "JSON parse error: %s (%u)", GetParseError_En(ok.Code()), ok.Offset()); exit(EXIT_FAILURE); } \endcode \see GenericReader::Parse, GenericDocument::Parse */ struct ParseResult { public: //! Default constructor, no error. ParseResult() : code_(kParseErrorNone), offset_(0) {} //! Constructor to set an error. ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} //! Get the error code. ParseErrorCode Code() const { return code_; } //! Get the error offset, if \ref IsError(), 0 otherwise. size_t Offset() const { return offset_; } //! Conversion to \c bool, returns \c true, iff !\ref IsError(). operator bool() const { return !IsError(); } //! Whether the result is an error. bool IsError() const { return code_ != kParseErrorNone; } bool operator==(const ParseResult& that) const { return code_ == that.code_; } bool operator==(ParseErrorCode code) const { return code_ == code; } friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } //! Reset error code. void Clear() { Set(kParseErrorNone); } //! Update error code and offset. void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } private: ParseErrorCode code_; size_t offset_; }; //! Function pointer type of GetParseError(). /*! \ingroup RAPIDJSON_ERRORS This is the prototype for \c GetParseError_X(), where \c X is a locale. User can dynamically change locale in runtime, e.g.: \code GetParseErrorFunc GetParseError = GetParseError_En; // or whatever const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); \endcode */ typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_ERROR_ERROR_H_ ================================================ FILE: third-party/rapidjson/filereadstream.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_FILEREADSTREAM_H_ #define RAPIDJSON_FILEREADSTREAM_H_ #include "stream.h" #include #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(padded) RAPIDJSON_DIAG_OFF(unreachable-code) RAPIDJSON_DIAG_OFF(missing-noreturn) #endif RAPIDJSON_NAMESPACE_BEGIN //! File byte stream for input using fread(). /*! \note implements Stream concept */ class FileReadStream { public: typedef char Ch; //!< Character type (byte). //! Constructor. /*! \param fp File pointer opened for read. \param buffer user-supplied buffer. \param bufferSize size of buffer in bytes. Must >=4 bytes. */ FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { RAPIDJSON_ASSERT(fp_ != 0); RAPIDJSON_ASSERT(bufferSize >= 4); Read(); } Ch Peek() const { return *current_; } Ch Take() { Ch c = *current_; Read(); return c; } size_t Tell() const { return count_ + static_cast(current_ - buffer_); } // Not implemented void Put(Ch) { RAPIDJSON_ASSERT(false); } void Flush() { RAPIDJSON_ASSERT(false); } Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } // For encoding detection only. const Ch* Peek4() const { return (current_ + 4 <= bufferLast_) ? current_ : 0; } private: void Read() { if (current_ < bufferLast_) ++current_; else if (!eof_) { count_ += readCount_; readCount_ = fread(buffer_, 1, bufferSize_, fp_); bufferLast_ = buffer_ + readCount_ - 1; current_ = buffer_; if (readCount_ < bufferSize_) { buffer_[readCount_] = '\0'; ++bufferLast_; eof_ = true; } } } std::FILE* fp_; Ch *buffer_; size_t bufferSize_; Ch *bufferLast_; Ch *current_; size_t readCount_; size_t count_; //!< Number of characters read bool eof_; }; RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_FILESTREAM_H_ ================================================ FILE: third-party/rapidjson/filewritestream.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_FILEWRITESTREAM_H_ #define RAPIDJSON_FILEWRITESTREAM_H_ #include "stream.h" #include #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(unreachable-code) #endif RAPIDJSON_NAMESPACE_BEGIN //! Wrapper of C file stream for input using fread(). /*! \note implements Stream concept */ class FileWriteStream { public: typedef char Ch; //!< Character type. Only support char. FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) { RAPIDJSON_ASSERT(fp_ != 0); } void Put(char c) { if (current_ >= bufferEnd_) Flush(); *current_++ = c; } void PutN(char c, size_t n) { size_t avail = static_cast(bufferEnd_ - current_); while (n > avail) { std::memset(current_, c, avail); current_ += avail; Flush(); n -= avail; avail = static_cast(bufferEnd_ - current_); } if (n > 0) { std::memset(current_, c, n); current_ += n; } } void Flush() { if (current_ != buffer_) { size_t result = fwrite(buffer_, 1, static_cast(current_ - buffer_), fp_); if (result < static_cast(current_ - buffer_)) { // failure deliberately ignored at this time // added to avoid warn_unused_result build errors } current_ = buffer_; } } // Not implemented char Peek() const { RAPIDJSON_ASSERT(false); return 0; } char Take() { RAPIDJSON_ASSERT(false); return 0; } size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } private: // Prohibit copy constructor & assignment operator. FileWriteStream(const FileWriteStream&); FileWriteStream& operator=(const FileWriteStream&); std::FILE* fp_; char *buffer_; char *bufferEnd_; char *current_; }; //! Implement specialized version of PutN() with memset() for better performance. template<> inline void PutN(FileWriteStream& stream, char c, size_t n) { stream.PutN(c, n); } RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_FILESTREAM_H_ ================================================ FILE: third-party/rapidjson/fwd.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_FWD_H_ #define RAPIDJSON_FWD_H_ #include "rapidjson.h" RAPIDJSON_NAMESPACE_BEGIN // encodings.h template struct UTF8; template struct UTF16; template struct UTF16BE; template struct UTF16LE; template struct UTF32; template struct UTF32BE; template struct UTF32LE; template struct ASCII; template struct AutoUTF; template struct Transcoder; // allocators.h class CrtAllocator; template class MemoryPoolAllocator; // stream.h template struct GenericStringStream; typedef GenericStringStream > StringStream; template struct GenericInsituStringStream; typedef GenericInsituStringStream > InsituStringStream; // stringbuffer.h template class GenericStringBuffer; typedef GenericStringBuffer, CrtAllocator> StringBuffer; // filereadstream.h class FileReadStream; // filewritestream.h class FileWriteStream; // memorybuffer.h template struct GenericMemoryBuffer; typedef GenericMemoryBuffer MemoryBuffer; // memorystream.h struct MemoryStream; // reader.h template struct BaseReaderHandler; template class GenericReader; typedef GenericReader, UTF8, CrtAllocator> Reader; // writer.h template class Writer; // prettywriter.h template class PrettyWriter; // document.h template struct GenericMember; template class GenericMemberIterator; template struct GenericStringRef; template class GenericValue; typedef GenericValue, MemoryPoolAllocator > Value; template class GenericDocument; typedef GenericDocument, MemoryPoolAllocator, CrtAllocator> Document; // pointer.h template class GenericPointer; typedef GenericPointer Pointer; // schema.h template class IGenericRemoteSchemaDocumentProvider; template class GenericSchemaDocument; typedef GenericSchemaDocument SchemaDocument; typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; template < typename SchemaDocumentType, typename OutputHandler, typename StateAllocator> class GenericSchemaValidator; typedef GenericSchemaValidator, void>, CrtAllocator> SchemaValidator; RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_RAPIDJSONFWD_H_ ================================================ FILE: third-party/rapidjson/internal/biginteger.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_BIGINTEGER_H_ #define RAPIDJSON_BIGINTEGER_H_ #include "../rapidjson.h" #if defined(_MSC_VER) && defined(_M_AMD64) #include // for _umul128 #pragma intrinsic(_umul128) #endif RAPIDJSON_NAMESPACE_BEGIN namespace internal { class BigInteger { public: typedef uint64_t Type; BigInteger(const BigInteger& rhs) : count_(rhs.count_) { std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); } explicit BigInteger(uint64_t u) : count_(1) { digits_[0] = u; } BigInteger(const char* decimals, size_t length) : count_(1) { RAPIDJSON_ASSERT(length > 0); digits_[0] = 0; size_t i = 0; const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19 while (length >= kMaxDigitPerIteration) { AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration); length -= kMaxDigitPerIteration; i += kMaxDigitPerIteration; } if (length > 0) AppendDecimal64(decimals + i, decimals + i + length); } BigInteger& operator=(const BigInteger &rhs) { if (this != &rhs) { count_ = rhs.count_; std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); } return *this; } BigInteger& operator=(uint64_t u) { digits_[0] = u; count_ = 1; return *this; } BigInteger& operator+=(uint64_t u) { Type backup = digits_[0]; digits_[0] += u; for (size_t i = 0; i < count_ - 1; i++) { if (digits_[i] >= backup) return *this; // no carry backup = digits_[i + 1]; digits_[i + 1] += 1; } // Last carry if (digits_[count_ - 1] < backup) PushBack(1); return *this; } BigInteger& operator*=(uint64_t u) { if (u == 0) return *this = 0; if (u == 1) return *this; if (*this == 1) return *this = u; uint64_t k = 0; for (size_t i = 0; i < count_; i++) { uint64_t hi; digits_[i] = MulAdd64(digits_[i], u, k, &hi); k = hi; } if (k > 0) PushBack(k); return *this; } BigInteger& operator*=(uint32_t u) { if (u == 0) return *this = 0; if (u == 1) return *this; if (*this == 1) return *this = u; uint64_t k = 0; for (size_t i = 0; i < count_; i++) { const uint64_t c = digits_[i] >> 32; const uint64_t d = digits_[i] & 0xFFFFFFFF; const uint64_t uc = u * c; const uint64_t ud = u * d; const uint64_t p0 = ud + k; const uint64_t p1 = uc + (p0 >> 32); digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); k = p1 >> 32; } if (k > 0) PushBack(k); return *this; } BigInteger& operator<<=(size_t shift) { if (IsZero() || shift == 0) return *this; size_t offset = shift / kTypeBit; size_t interShift = shift % kTypeBit; RAPIDJSON_ASSERT(count_ + offset <= kCapacity); if (interShift == 0) { std::memmove(&digits_[count_ - 1 + offset], &digits_[count_ - 1], count_ * sizeof(Type)); count_ += offset; } else { digits_[count_] = 0; for (size_t i = count_; i > 0; i--) digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift)); digits_[offset] = digits_[0] << interShift; count_ += offset; if (digits_[count_]) count_++; } std::memset(digits_, 0, offset * sizeof(Type)); return *this; } bool operator==(const BigInteger& rhs) const { return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0; } bool operator==(const Type rhs) const { return count_ == 1 && digits_[0] == rhs; } BigInteger& MultiplyPow5(unsigned exp) { static const uint32_t kPow5[12] = { 5, 5 * 5, 5 * 5 * 5, 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 }; if (exp == 0) return *this; for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27 for (; exp >= 13; exp -= 13) *this *= static_cast(1220703125u); // 5^13 if (exp > 0) *this *= kPow5[exp - 1]; return *this; } // Compute absolute difference of this and rhs. // Assume this != rhs bool Difference(const BigInteger& rhs, BigInteger* out) const { int cmp = Compare(rhs); RAPIDJSON_ASSERT(cmp != 0); const BigInteger *a, *b; // Makes a > b bool ret; if (cmp < 0) { a = &rhs; b = this; ret = true; } else { a = this; b = &rhs; ret = false; } Type borrow = 0; for (size_t i = 0; i < a->count_; i++) { Type d = a->digits_[i] - borrow; if (i < b->count_) d -= b->digits_[i]; borrow = (d > a->digits_[i]) ? 1 : 0; out->digits_[i] = d; if (d != 0) out->count_ = i + 1; } return ret; } int Compare(const BigInteger& rhs) const { if (count_ != rhs.count_) return count_ < rhs.count_ ? -1 : 1; for (size_t i = count_; i-- > 0;) if (digits_[i] != rhs.digits_[i]) return digits_[i] < rhs.digits_[i] ? -1 : 1; return 0; } size_t GetCount() const { return count_; } Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; } bool IsZero() const { return count_ == 1 && digits_[0] == 0; } private: void AppendDecimal64(const char* begin, const char* end) { uint64_t u = ParseUint64(begin, end); if (IsZero()) *this = u; else { unsigned exp = static_cast(end - begin); (MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u } } void PushBack(Type digit) { RAPIDJSON_ASSERT(count_ < kCapacity); digits_[count_++] = digit; } static uint64_t ParseUint64(const char* begin, const char* end) { uint64_t r = 0; for (const char* p = begin; p != end; ++p) { RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); r = r * 10u + static_cast(*p - '0'); } return r; } // Assume a * b + k < 2^128 static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) { #if defined(_MSC_VER) && defined(_M_AMD64) uint64_t low = _umul128(a, b, outHigh) + k; if (low < k) (*outHigh)++; return low; #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) __extension__ typedef unsigned __int128 uint128; uint128 p = static_cast(a) * static_cast(b); p += k; *outHigh = static_cast(p >> 64); return static_cast(p); #else const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32; uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1; x1 += (x0 >> 32); // can't give carry x1 += x2; if (x1 < x2) x3 += (static_cast(1) << 32); uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF); uint64_t hi = x3 + (x1 >> 32); lo += k; if (lo < k) hi++; *outHigh = hi; return lo; #endif } static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000 static const size_t kCapacity = kBitCount / sizeof(Type); static const size_t kTypeBit = sizeof(Type) * 8; Type digits_[kCapacity]; size_t count_; }; } // namespace internal RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_BIGINTEGER_H_ ================================================ FILE: third-party/rapidjson/internal/diyfp.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. // This is a C++ header-only implementation of Grisu2 algorithm from the publication: // Loitsch, Florian. "Printing floating-point numbers quickly and accurately with // integers." ACM Sigplan Notices 45.6 (2010): 233-243. #ifndef RAPIDJSON_DIYFP_H_ #define RAPIDJSON_DIYFP_H_ #include "../rapidjson.h" #if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER) #include #pragma intrinsic(_BitScanReverse64) #pragma intrinsic(_umul128) #endif RAPIDJSON_NAMESPACE_BEGIN namespace internal { #ifdef __GNUC__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(effc++) #endif #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(padded) #endif struct DiyFp { DiyFp() : f(), e() {} DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {} explicit DiyFp(double d) { union { double d; uint64_t u64; } u = { d }; int biased_e = static_cast((u.u64 & kDpExponentMask) >> kDpSignificandSize); uint64_t significand = (u.u64 & kDpSignificandMask); if (biased_e != 0) { f = significand + kDpHiddenBit; e = biased_e - kDpExponentBias; } else { f = significand; e = kDpMinExponent + 1; } } DiyFp operator-(const DiyFp& rhs) const { return DiyFp(f - rhs.f, e); } DiyFp operator*(const DiyFp& rhs) const { #if defined(_MSC_VER) && defined(_M_AMD64) uint64_t h; uint64_t l = _umul128(f, rhs.f, &h); if (l & (uint64_t(1) << 63)) // rounding h++; return DiyFp(h, e + rhs.e + 64); #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) __extension__ typedef unsigned __int128 uint128; uint128 p = static_cast(f) * static_cast(rhs.f); uint64_t h = static_cast(p >> 64); uint64_t l = static_cast(p); if (l & (uint64_t(1) << 63)) // rounding h++; return DiyFp(h, e + rhs.e + 64); #else const uint64_t M32 = 0xFFFFFFFF; const uint64_t a = f >> 32; const uint64_t b = f & M32; const uint64_t c = rhs.f >> 32; const uint64_t d = rhs.f & M32; const uint64_t ac = a * c; const uint64_t bc = b * c; const uint64_t ad = a * d; const uint64_t bd = b * d; uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); tmp += 1U << 31; /// mult_round return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); #endif } DiyFp Normalize() const { #if defined(_MSC_VER) && defined(_M_AMD64) unsigned long index; _BitScanReverse64(&index, f); return DiyFp(f << (63 - index), e - (63 - index)); #elif defined(__GNUC__) && __GNUC__ >= 4 int s = __builtin_clzll(f); return DiyFp(f << s, e - s); #else DiyFp res = *this; while (!(res.f & (static_cast(1) << 63))) { res.f <<= 1; res.e--; } return res; #endif } DiyFp NormalizeBoundary() const { DiyFp res = *this; while (!(res.f & (kDpHiddenBit << 1))) { res.f <<= 1; res.e--; } res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); return res; } void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); mi.f <<= mi.e - pl.e; mi.e = pl.e; *plus = pl; *minus = mi; } double ToDouble() const { union { double d; uint64_t u64; }u; const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 : static_cast(e + kDpExponentBias); u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize); return u.d; } static const int kDiySignificandSize = 64; static const int kDpSignificandSize = 52; static const int kDpExponentBias = 0x3FF + kDpSignificandSize; static const int kDpMaxExponent = 0x7FF - kDpExponentBias; static const int kDpMinExponent = -kDpExponentBias; static const int kDpDenormalExponent = -kDpExponentBias + 1; static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); uint64_t f; int e; }; inline DiyFp GetCachedPowerByIndex(size_t index) { // 10^-348, 10^-340, ..., 10^340 static const uint64_t kCachedPowers_F[] = { RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76), RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea), RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df), RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f), RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c), RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5), RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d), RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637), RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7), RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5), RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b), RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996), RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6), RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8), RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053), RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd), RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94), RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b), RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac), RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3), RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb), RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c), RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000), RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984), RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70), RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245), RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8), RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a), RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea), RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85), RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2), RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3), RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25), RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece), RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5), RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a), RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c), RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a), RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129), RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429), RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d), RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841), RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9), RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b) }; static const int16_t kCachedPowers_E[] = { -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066 }; return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); } inline DiyFp GetCachedPower(int e, int* K) { //int k = static_cast(ceil((-61 - e) * 0.30102999566398114)) + 374; double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive int k = static_cast(dk); if (dk - k > 0.0) k++; unsigned index = static_cast((k >> 3) + 1); *K = -(-348 + static_cast(index << 3)); // decimal exponent no need lookup table return GetCachedPowerByIndex(index); } inline DiyFp GetCachedPower10(int exp, int *outExp) { unsigned index = (static_cast(exp) + 348u) / 8u; *outExp = -348 + static_cast(index) * 8; return GetCachedPowerByIndex(index); } #ifdef __GNUC__ RAPIDJSON_DIAG_POP #endif #ifdef __clang__ RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_OFF(padded) #endif } // namespace internal RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_DIYFP_H_ ================================================ FILE: third-party/rapidjson/internal/dtoa.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. // This is a C++ header-only implementation of Grisu2 algorithm from the publication: // Loitsch, Florian. "Printing floating-point numbers quickly and accurately with // integers." ACM Sigplan Notices 45.6 (2010): 233-243. #ifndef RAPIDJSON_DTOA_ #define RAPIDJSON_DTOA_ #include "itoa.h" // GetDigitsLut() #include "diyfp.h" #include "ieee754.h" RAPIDJSON_NAMESPACE_BEGIN namespace internal { #ifdef __GNUC__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(effc++) RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 #endif inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { while (rest < wp_w && delta - rest >= ten_kappa && (rest + ten_kappa < wp_w || /// closer wp_w - rest > rest + ten_kappa - wp_w)) { buffer[len - 1]--; rest += ten_kappa; } } inline int CountDecimalDigit32(uint32_t n) { // Simple pure C++ implementation was faster than __builtin_clz version in this situation. if (n < 10) return 1; if (n < 100) return 2; if (n < 1000) return 3; if (n < 10000) return 4; if (n < 100000) return 5; if (n < 1000000) return 6; if (n < 10000000) return 7; if (n < 100000000) return 8; // Will not reach 10 digits in DigitGen() //if (n < 1000000000) return 9; //return 10; return 9; } inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); const DiyFp wp_w = Mp - W; uint32_t p1 = static_cast(Mp.f >> -one.e); uint64_t p2 = Mp.f & (one.f - 1); int kappa = CountDecimalDigit32(p1); // kappa in [0, 9] *len = 0; while (kappa > 0) { uint32_t d = 0; switch (kappa) { case 9: d = p1 / 100000000; p1 %= 100000000; break; case 8: d = p1 / 10000000; p1 %= 10000000; break; case 7: d = p1 / 1000000; p1 %= 1000000; break; case 6: d = p1 / 100000; p1 %= 100000; break; case 5: d = p1 / 10000; p1 %= 10000; break; case 4: d = p1 / 1000; p1 %= 1000; break; case 3: d = p1 / 100; p1 %= 100; break; case 2: d = p1 / 10; p1 %= 10; break; case 1: d = p1; p1 = 0; break; default:; } if (d || *len) buffer[(*len)++] = static_cast('0' + static_cast(d)); kappa--; uint64_t tmp = (static_cast(p1) << -one.e) + p2; if (tmp <= delta) { *K += kappa; GrisuRound(buffer, *len, delta, tmp, static_cast(kPow10[kappa]) << -one.e, wp_w.f); return; } } // kappa = 0 for (;;) { p2 *= 10; delta *= 10; char d = static_cast(p2 >> -one.e); if (d || *len) buffer[(*len)++] = static_cast('0' + d); p2 &= one.f - 1; kappa--; if (p2 < delta) { *K += kappa; int index = -kappa; GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[index] : 0)); return; } } } inline void Grisu2(double value, char* buffer, int* length, int* K) { const DiyFp v(value); DiyFp w_m, w_p; v.NormalizedBoundaries(&w_m, &w_p); const DiyFp c_mk = GetCachedPower(w_p.e, K); const DiyFp W = v.Normalize() * c_mk; DiyFp Wp = w_p * c_mk; DiyFp Wm = w_m * c_mk; Wm.f++; Wp.f--; DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); } inline char* WriteExponent(int K, char* buffer) { if (K < 0) { *buffer++ = '-'; K = -K; } if (K >= 100) { *buffer++ = static_cast('0' + static_cast(K / 100)); K %= 100; const char* d = GetDigitsLut() + K * 2; *buffer++ = d[0]; *buffer++ = d[1]; } else if (K >= 10) { const char* d = GetDigitsLut() + K * 2; *buffer++ = d[0]; *buffer++ = d[1]; } else *buffer++ = static_cast('0' + static_cast(K)); return buffer; } inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) { const int kk = length + k; // 10^(kk-1) <= v < 10^kk if (0 <= k && kk <= 21) { // 1234e7 -> 12340000000 for (int i = length; i < kk; i++) buffer[i] = '0'; buffer[kk] = '.'; buffer[kk + 1] = '0'; return &buffer[kk + 2]; } else if (0 < kk && kk <= 21) { // 1234e-2 -> 12.34 std::memmove(&buffer[kk + 1], &buffer[kk], static_cast(length - kk)); buffer[kk] = '.'; if (0 > k + maxDecimalPlaces) { // When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1 // Remove extra trailing zeros (at least one) after truncation. for (int i = kk + maxDecimalPlaces; i > kk + 1; i--) if (buffer[i] != '0') return &buffer[i + 1]; return &buffer[kk + 2]; // Reserve one zero } else return &buffer[length + 1]; } else if (-6 < kk && kk <= 0) { // 1234e-6 -> 0.001234 const int offset = 2 - kk; std::memmove(&buffer[offset], &buffer[0], static_cast(length)); buffer[0] = '0'; buffer[1] = '.'; for (int i = 2; i < offset; i++) buffer[i] = '0'; if (length - kk > maxDecimalPlaces) { // When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1 // Remove extra trailing zeros (at least one) after truncation. for (int i = maxDecimalPlaces + 1; i > 2; i--) if (buffer[i] != '0') return &buffer[i + 1]; return &buffer[3]; // Reserve one zero } else return &buffer[length + offset]; } else if (kk < -maxDecimalPlaces) { // Truncate to zero buffer[0] = '0'; buffer[1] = '.'; buffer[2] = '0'; return &buffer[3]; } else if (length == 1) { // 1e30 buffer[1] = 'e'; return WriteExponent(kk - 1, &buffer[2]); } else { // 1234e30 -> 1.234e33 std::memmove(&buffer[2], &buffer[1], static_cast(length - 1)); buffer[1] = '.'; buffer[length + 1] = 'e'; return WriteExponent(kk - 1, &buffer[0 + length + 2]); } } inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) { RAPIDJSON_ASSERT(maxDecimalPlaces >= 1); Double d(value); if (d.IsZero()) { if (d.Sign()) *buffer++ = '-'; // -0.0, Issue #289 buffer[0] = '0'; buffer[1] = '.'; buffer[2] = '0'; return &buffer[3]; } else { if (value < 0) { *buffer++ = '-'; value = -value; } int length, K; Grisu2(value, buffer, &length, &K); return Prettify(buffer, length, K, maxDecimalPlaces); } } #ifdef __GNUC__ RAPIDJSON_DIAG_POP #endif } // namespace internal RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_DTOA_ ================================================ FILE: third-party/rapidjson/internal/ieee754.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_IEEE754_ #define RAPIDJSON_IEEE754_ #include "../rapidjson.h" RAPIDJSON_NAMESPACE_BEGIN namespace internal { class Double { public: Double() {} Double(double d) : d_(d) {} Double(uint64_t u) : u_(u) {} double Value() const { return d_; } uint64_t Uint64Value() const { return u_; } double NextPositiveDouble() const { RAPIDJSON_ASSERT(!Sign()); return Double(u_ + 1).Value(); } bool Sign() const { return (u_ & kSignMask) != 0; } uint64_t Significand() const { return u_ & kSignificandMask; } int Exponent() const { return static_cast(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); } bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; } bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; } bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; } bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; } bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; } uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); } int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; } uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; } static int EffectiveSignificandSize(int order) { if (order >= -1021) return 53; else if (order <= -1074) return 0; else return order + 1074; } private: static const int kSignificandSize = 52; static const int kExponentBias = 0x3FF; static const int kDenormalExponent = 1 - kExponentBias; static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); union { double d_; uint64_t u_; }; }; } // namespace internal RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_IEEE754_ ================================================ FILE: third-party/rapidjson/internal/itoa.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_ITOA_ #define RAPIDJSON_ITOA_ #include "../rapidjson.h" RAPIDJSON_NAMESPACE_BEGIN namespace internal { inline const char* GetDigitsLut() { static const char cDigitsLut[200] = { '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' }; return cDigitsLut; } inline char* u32toa(uint32_t value, char* buffer) { const char* cDigitsLut = GetDigitsLut(); if (value < 10000) { const uint32_t d1 = (value / 100) << 1; const uint32_t d2 = (value % 100) << 1; if (value >= 1000) *buffer++ = cDigitsLut[d1]; if (value >= 100) *buffer++ = cDigitsLut[d1 + 1]; if (value >= 10) *buffer++ = cDigitsLut[d2]; *buffer++ = cDigitsLut[d2 + 1]; } else if (value < 100000000) { // value = bbbbcccc const uint32_t b = value / 10000; const uint32_t c = value % 10000; const uint32_t d1 = (b / 100) << 1; const uint32_t d2 = (b % 100) << 1; const uint32_t d3 = (c / 100) << 1; const uint32_t d4 = (c % 100) << 1; if (value >= 10000000) *buffer++ = cDigitsLut[d1]; if (value >= 1000000) *buffer++ = cDigitsLut[d1 + 1]; if (value >= 100000) *buffer++ = cDigitsLut[d2]; *buffer++ = cDigitsLut[d2 + 1]; *buffer++ = cDigitsLut[d3]; *buffer++ = cDigitsLut[d3 + 1]; *buffer++ = cDigitsLut[d4]; *buffer++ = cDigitsLut[d4 + 1]; } else { // value = aabbbbcccc in decimal const uint32_t a = value / 100000000; // 1 to 42 value %= 100000000; if (a >= 10) { const unsigned i = a << 1; *buffer++ = cDigitsLut[i]; *buffer++ = cDigitsLut[i + 1]; } else *buffer++ = static_cast('0' + static_cast(a)); const uint32_t b = value / 10000; // 0 to 9999 const uint32_t c = value % 10000; // 0 to 9999 const uint32_t d1 = (b / 100) << 1; const uint32_t d2 = (b % 100) << 1; const uint32_t d3 = (c / 100) << 1; const uint32_t d4 = (c % 100) << 1; *buffer++ = cDigitsLut[d1]; *buffer++ = cDigitsLut[d1 + 1]; *buffer++ = cDigitsLut[d2]; *buffer++ = cDigitsLut[d2 + 1]; *buffer++ = cDigitsLut[d3]; *buffer++ = cDigitsLut[d3 + 1]; *buffer++ = cDigitsLut[d4]; *buffer++ = cDigitsLut[d4 + 1]; } return buffer; } inline char* i32toa(int32_t value, char* buffer) { uint32_t u = static_cast(value); if (value < 0) { *buffer++ = '-'; u = ~u + 1; } return u32toa(u, buffer); } inline char* u64toa(uint64_t value, char* buffer) { const char* cDigitsLut = GetDigitsLut(); const uint64_t kTen8 = 100000000; const uint64_t kTen9 = kTen8 * 10; const uint64_t kTen10 = kTen8 * 100; const uint64_t kTen11 = kTen8 * 1000; const uint64_t kTen12 = kTen8 * 10000; const uint64_t kTen13 = kTen8 * 100000; const uint64_t kTen14 = kTen8 * 1000000; const uint64_t kTen15 = kTen8 * 10000000; const uint64_t kTen16 = kTen8 * kTen8; if (value < kTen8) { uint32_t v = static_cast(value); if (v < 10000) { const uint32_t d1 = (v / 100) << 1; const uint32_t d2 = (v % 100) << 1; if (v >= 1000) *buffer++ = cDigitsLut[d1]; if (v >= 100) *buffer++ = cDigitsLut[d1 + 1]; if (v >= 10) *buffer++ = cDigitsLut[d2]; *buffer++ = cDigitsLut[d2 + 1]; } else { // value = bbbbcccc const uint32_t b = v / 10000; const uint32_t c = v % 10000; const uint32_t d1 = (b / 100) << 1; const uint32_t d2 = (b % 100) << 1; const uint32_t d3 = (c / 100) << 1; const uint32_t d4 = (c % 100) << 1; if (value >= 10000000) *buffer++ = cDigitsLut[d1]; if (value >= 1000000) *buffer++ = cDigitsLut[d1 + 1]; if (value >= 100000) *buffer++ = cDigitsLut[d2]; *buffer++ = cDigitsLut[d2 + 1]; *buffer++ = cDigitsLut[d3]; *buffer++ = cDigitsLut[d3 + 1]; *buffer++ = cDigitsLut[d4]; *buffer++ = cDigitsLut[d4 + 1]; } } else if (value < kTen16) { const uint32_t v0 = static_cast(value / kTen8); const uint32_t v1 = static_cast(value % kTen8); const uint32_t b0 = v0 / 10000; const uint32_t c0 = v0 % 10000; const uint32_t d1 = (b0 / 100) << 1; const uint32_t d2 = (b0 % 100) << 1; const uint32_t d3 = (c0 / 100) << 1; const uint32_t d4 = (c0 % 100) << 1; const uint32_t b1 = v1 / 10000; const uint32_t c1 = v1 % 10000; const uint32_t d5 = (b1 / 100) << 1; const uint32_t d6 = (b1 % 100) << 1; const uint32_t d7 = (c1 / 100) << 1; const uint32_t d8 = (c1 % 100) << 1; if (value >= kTen15) *buffer++ = cDigitsLut[d1]; if (value >= kTen14) *buffer++ = cDigitsLut[d1 + 1]; if (value >= kTen13) *buffer++ = cDigitsLut[d2]; if (value >= kTen12) *buffer++ = cDigitsLut[d2 + 1]; if (value >= kTen11) *buffer++ = cDigitsLut[d3]; if (value >= kTen10) *buffer++ = cDigitsLut[d3 + 1]; if (value >= kTen9) *buffer++ = cDigitsLut[d4]; if (value >= kTen8) *buffer++ = cDigitsLut[d4 + 1]; *buffer++ = cDigitsLut[d5]; *buffer++ = cDigitsLut[d5 + 1]; *buffer++ = cDigitsLut[d6]; *buffer++ = cDigitsLut[d6 + 1]; *buffer++ = cDigitsLut[d7]; *buffer++ = cDigitsLut[d7 + 1]; *buffer++ = cDigitsLut[d8]; *buffer++ = cDigitsLut[d8 + 1]; } else { const uint32_t a = static_cast(value / kTen16); // 1 to 1844 value %= kTen16; if (a < 10) *buffer++ = static_cast('0' + static_cast(a)); else if (a < 100) { const uint32_t i = a << 1; *buffer++ = cDigitsLut[i]; *buffer++ = cDigitsLut[i + 1]; } else if (a < 1000) { *buffer++ = static_cast('0' + static_cast(a / 100)); const uint32_t i = (a % 100) << 1; *buffer++ = cDigitsLut[i]; *buffer++ = cDigitsLut[i + 1]; } else { const uint32_t i = (a / 100) << 1; const uint32_t j = (a % 100) << 1; *buffer++ = cDigitsLut[i]; *buffer++ = cDigitsLut[i + 1]; *buffer++ = cDigitsLut[j]; *buffer++ = cDigitsLut[j + 1]; } const uint32_t v0 = static_cast(value / kTen8); const uint32_t v1 = static_cast(value % kTen8); const uint32_t b0 = v0 / 10000; const uint32_t c0 = v0 % 10000; const uint32_t d1 = (b0 / 100) << 1; const uint32_t d2 = (b0 % 100) << 1; const uint32_t d3 = (c0 / 100) << 1; const uint32_t d4 = (c0 % 100) << 1; const uint32_t b1 = v1 / 10000; const uint32_t c1 = v1 % 10000; const uint32_t d5 = (b1 / 100) << 1; const uint32_t d6 = (b1 % 100) << 1; const uint32_t d7 = (c1 / 100) << 1; const uint32_t d8 = (c1 % 100) << 1; *buffer++ = cDigitsLut[d1]; *buffer++ = cDigitsLut[d1 + 1]; *buffer++ = cDigitsLut[d2]; *buffer++ = cDigitsLut[d2 + 1]; *buffer++ = cDigitsLut[d3]; *buffer++ = cDigitsLut[d3 + 1]; *buffer++ = cDigitsLut[d4]; *buffer++ = cDigitsLut[d4 + 1]; *buffer++ = cDigitsLut[d5]; *buffer++ = cDigitsLut[d5 + 1]; *buffer++ = cDigitsLut[d6]; *buffer++ = cDigitsLut[d6 + 1]; *buffer++ = cDigitsLut[d7]; *buffer++ = cDigitsLut[d7 + 1]; *buffer++ = cDigitsLut[d8]; *buffer++ = cDigitsLut[d8 + 1]; } return buffer; } inline char* i64toa(int64_t value, char* buffer) { uint64_t u = static_cast(value); if (value < 0) { *buffer++ = '-'; u = ~u + 1; } return u64toa(u, buffer); } } // namespace internal RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_ITOA_ ================================================ FILE: third-party/rapidjson/internal/meta.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_INTERNAL_META_H_ #define RAPIDJSON_INTERNAL_META_H_ #include "../rapidjson.h" #ifdef __GNUC__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(effc++) #endif #if defined(_MSC_VER) RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(6334) #endif #if RAPIDJSON_HAS_CXX11_TYPETRAITS #include #endif //@cond RAPIDJSON_INTERNAL RAPIDJSON_NAMESPACE_BEGIN namespace internal { // Helper to wrap/convert arbitrary types to void, useful for arbitrary type matching template struct Void { typedef void Type; }; /////////////////////////////////////////////////////////////////////////////// // BoolType, TrueType, FalseType // template struct BoolType { static const bool Value = Cond; typedef BoolType Type; }; typedef BoolType TrueType; typedef BoolType FalseType; /////////////////////////////////////////////////////////////////////////////// // SelectIf, BoolExpr, NotExpr, AndExpr, OrExpr // template struct SelectIfImpl { template struct Apply { typedef T1 Type; }; }; template <> struct SelectIfImpl { template struct Apply { typedef T2 Type; }; }; template struct SelectIfCond : SelectIfImpl::template Apply {}; template struct SelectIf : SelectIfCond {}; template struct AndExprCond : FalseType {}; template <> struct AndExprCond : TrueType {}; template struct OrExprCond : TrueType {}; template <> struct OrExprCond : FalseType {}; template struct BoolExpr : SelectIf::Type {}; template struct NotExpr : SelectIf::Type {}; template struct AndExpr : AndExprCond::Type {}; template struct OrExpr : OrExprCond::Type {}; /////////////////////////////////////////////////////////////////////////////// // AddConst, MaybeAddConst, RemoveConst template struct AddConst { typedef const T Type; }; template struct MaybeAddConst : SelectIfCond {}; template struct RemoveConst { typedef T Type; }; template struct RemoveConst { typedef T Type; }; /////////////////////////////////////////////////////////////////////////////// // IsSame, IsConst, IsMoreConst, IsPointer // template struct IsSame : FalseType {}; template struct IsSame : TrueType {}; template struct IsConst : FalseType {}; template struct IsConst : TrueType {}; template struct IsMoreConst : AndExpr::Type, typename RemoveConst::Type>, BoolType::Value >= IsConst::Value> >::Type {}; template struct IsPointer : FalseType {}; template struct IsPointer : TrueType {}; /////////////////////////////////////////////////////////////////////////////// // IsBaseOf // #if RAPIDJSON_HAS_CXX11_TYPETRAITS template struct IsBaseOf : BoolType< ::std::is_base_of::value> {}; #else // simplified version adopted from Boost template struct IsBaseOfImpl { RAPIDJSON_STATIC_ASSERT(sizeof(B) != 0); RAPIDJSON_STATIC_ASSERT(sizeof(D) != 0); typedef char (&Yes)[1]; typedef char (&No) [2]; template static Yes Check(const D*, T); static No Check(const B*, int); struct Host { operator const B*() const; operator const D*(); }; enum { Value = (sizeof(Check(Host(), 0)) == sizeof(Yes)) }; }; template struct IsBaseOf : OrExpr, BoolExpr > >::Type {}; #endif // RAPIDJSON_HAS_CXX11_TYPETRAITS ////////////////////////////////////////////////////////////////////////// // EnableIf / DisableIf // template struct EnableIfCond { typedef T Type; }; template struct EnableIfCond { /* empty */ }; template struct DisableIfCond { typedef T Type; }; template struct DisableIfCond { /* empty */ }; template struct EnableIf : EnableIfCond {}; template struct DisableIf : DisableIfCond {}; // SFINAE helpers struct SfinaeTag {}; template struct RemoveSfinaeTag; template struct RemoveSfinaeTag { typedef T Type; }; #define RAPIDJSON_REMOVEFPTR_(type) \ typename ::RAPIDJSON_NAMESPACE::internal::RemoveSfinaeTag \ < ::RAPIDJSON_NAMESPACE::internal::SfinaeTag&(*) type>::Type #define RAPIDJSON_ENABLEIF(cond) \ typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ ::Type * = NULL #define RAPIDJSON_DISABLEIF(cond) \ typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ ::Type * = NULL #define RAPIDJSON_ENABLEIF_RETURN(cond,returntype) \ typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ ::Type #define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \ typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ ::Type } // namespace internal RAPIDJSON_NAMESPACE_END //@endcond #if defined(__GNUC__) || defined(_MSC_VER) RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_INTERNAL_META_H_ ================================================ FILE: third-party/rapidjson/internal/pow10.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_POW10_ #define RAPIDJSON_POW10_ #include "../rapidjson.h" RAPIDJSON_NAMESPACE_BEGIN namespace internal { //! Computes integer powers of 10 in double (10.0^n). /*! This function uses lookup table for fast and accurate results. \param n non-negative exponent. Must <= 308. \return 10.0^n */ inline double Pow10(int n) { static const double e[] = { // 1e-0...1e308: 309 * 8 bytes = 2472 bytes 1e+0, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 }; RAPIDJSON_ASSERT(n >= 0 && n <= 308); return e[n]; } } // namespace internal RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_POW10_ ================================================ FILE: third-party/rapidjson/internal/regex.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_INTERNAL_REGEX_H_ #define RAPIDJSON_INTERNAL_REGEX_H_ #include "../allocators.h" #include "../stream.h" #include "stack.h" #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(padded) RAPIDJSON_DIAG_OFF(switch-enum) RAPIDJSON_DIAG_OFF(implicit-fallthrough) #endif #ifdef __GNUC__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(effc++) #if __GNUC__ >= 7 RAPIDJSON_DIAG_OFF(implicit-fallthrough) #endif #endif #ifdef _MSC_VER RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated #endif #ifndef RAPIDJSON_REGEX_VERBOSE #define RAPIDJSON_REGEX_VERBOSE 0 #endif RAPIDJSON_NAMESPACE_BEGIN namespace internal { /////////////////////////////////////////////////////////////////////////////// // DecodedStream template class DecodedStream { public: DecodedStream(SourceStream& ss) : ss_(ss), codepoint_() { Decode(); } unsigned Peek() { return codepoint_; } unsigned Take() { unsigned c = codepoint_; if (c) // No further decoding when '\0' Decode(); return c; } private: void Decode() { if (!Encoding::Decode(ss_, &codepoint_)) codepoint_ = 0; } SourceStream& ss_; unsigned codepoint_; }; /////////////////////////////////////////////////////////////////////////////// // GenericRegex static const SizeType kRegexInvalidState = ~SizeType(0); //!< Represents an invalid index in GenericRegex::State::out, out1 static const SizeType kRegexInvalidRange = ~SizeType(0); template class GenericRegexSearch; //! Regular expression engine with subset of ECMAscript grammar. /*! Supported regular expression syntax: - \c ab Concatenation - \c a|b Alternation - \c a? Zero or one - \c a* Zero or more - \c a+ One or more - \c a{3} Exactly 3 times - \c a{3,} At least 3 times - \c a{3,5} 3 to 5 times - \c (ab) Grouping - \c ^a At the beginning - \c a$ At the end - \c . Any character - \c [abc] Character classes - \c [a-c] Character class range - \c [a-z0-9_] Character class combination - \c [^abc] Negated character classes - \c [^a-c] Negated character class range - \c [\b] Backspace (U+0008) - \c \\| \\\\ ... Escape characters - \c \\f Form feed (U+000C) - \c \\n Line feed (U+000A) - \c \\r Carriage return (U+000D) - \c \\t Tab (U+0009) - \c \\v Vertical tab (U+000B) \note This is a Thompson NFA engine, implemented with reference to Cox, Russ. "Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby,...).", https://swtch.com/~rsc/regexp/regexp1.html */ template class GenericRegex { public: typedef Encoding EncodingType; typedef typename Encoding::Ch Ch; template friend class GenericRegexSearch; GenericRegex(const Ch* source, Allocator* allocator = 0) : states_(allocator, 256), ranges_(allocator, 256), root_(kRegexInvalidState), stateCount_(), rangeCount_(), anchorBegin_(), anchorEnd_() { GenericStringStream ss(source); DecodedStream, Encoding> ds(ss); Parse(ds); } ~GenericRegex() {} bool IsValid() const { return root_ != kRegexInvalidState; } private: enum Operator { kZeroOrOne, kZeroOrMore, kOneOrMore, kConcatenation, kAlternation, kLeftParenthesis }; static const unsigned kAnyCharacterClass = 0xFFFFFFFF; //!< For '.' static const unsigned kRangeCharacterClass = 0xFFFFFFFE; static const unsigned kRangeNegationFlag = 0x80000000; struct Range { unsigned start; // unsigned end; SizeType next; }; struct State { SizeType out; //!< Equals to kInvalid for matching state SizeType out1; //!< Equals to non-kInvalid for split SizeType rangeStart; unsigned codepoint; }; struct Frag { Frag(SizeType s, SizeType o, SizeType m) : start(s), out(o), minIndex(m) {} SizeType start; SizeType out; //!< link-list of all output states SizeType minIndex; }; State& GetState(SizeType index) { RAPIDJSON_ASSERT(index < stateCount_); return states_.template Bottom()[index]; } const State& GetState(SizeType index) const { RAPIDJSON_ASSERT(index < stateCount_); return states_.template Bottom()[index]; } Range& GetRange(SizeType index) { RAPIDJSON_ASSERT(index < rangeCount_); return ranges_.template Bottom()[index]; } const Range& GetRange(SizeType index) const { RAPIDJSON_ASSERT(index < rangeCount_); return ranges_.template Bottom()[index]; } template void Parse(DecodedStream& ds) { Allocator allocator; Stack operandStack(&allocator, 256); // Frag Stack operatorStack(&allocator, 256); // Operator Stack atomCountStack(&allocator, 256); // unsigned (Atom per parenthesis) *atomCountStack.template Push() = 0; unsigned codepoint; while (ds.Peek() != 0) { switch (codepoint = ds.Take()) { case '^': anchorBegin_ = true; break; case '$': anchorEnd_ = true; break; case '|': while (!operatorStack.Empty() && *operatorStack.template Top() < kAlternation) if (!Eval(operandStack, *operatorStack.template Pop(1))) return; *operatorStack.template Push() = kAlternation; *atomCountStack.template Top() = 0; break; case '(': *operatorStack.template Push() = kLeftParenthesis; *atomCountStack.template Push() = 0; break; case ')': while (!operatorStack.Empty() && *operatorStack.template Top() != kLeftParenthesis) if (!Eval(operandStack, *operatorStack.template Pop(1))) return; if (operatorStack.Empty()) return; operatorStack.template Pop(1); atomCountStack.template Pop(1); ImplicitConcatenation(atomCountStack, operatorStack); break; case '?': if (!Eval(operandStack, kZeroOrOne)) return; break; case '*': if (!Eval(operandStack, kZeroOrMore)) return; break; case '+': if (!Eval(operandStack, kOneOrMore)) return; break; case '{': { unsigned n, m; if (!ParseUnsigned(ds, &n)) return; if (ds.Peek() == ',') { ds.Take(); if (ds.Peek() == '}') m = kInfinityQuantifier; else if (!ParseUnsigned(ds, &m) || m < n) return; } else m = n; if (!EvalQuantifier(operandStack, n, m) || ds.Peek() != '}') return; ds.Take(); } break; case '.': PushOperand(operandStack, kAnyCharacterClass); ImplicitConcatenation(atomCountStack, operatorStack); break; case '[': { SizeType range; if (!ParseRange(ds, &range)) return; SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, kRangeCharacterClass); GetState(s).rangeStart = range; *operandStack.template Push() = Frag(s, s, s); } ImplicitConcatenation(atomCountStack, operatorStack); break; case '\\': // Escape character if (!CharacterEscape(ds, &codepoint)) return; // Unsupported escape character // fall through to default default: // Pattern character PushOperand(operandStack, codepoint); ImplicitConcatenation(atomCountStack, operatorStack); } } while (!operatorStack.Empty()) if (!Eval(operandStack, *operatorStack.template Pop(1))) return; // Link the operand to matching state. if (operandStack.GetSize() == sizeof(Frag)) { Frag* e = operandStack.template Pop(1); Patch(e->out, NewState(kRegexInvalidState, kRegexInvalidState, 0)); root_ = e->start; #if RAPIDJSON_REGEX_VERBOSE printf("root: %d\n", root_); for (SizeType i = 0; i < stateCount_ ; i++) { State& s = GetState(i); printf("[%2d] out: %2d out1: %2d c: '%c'\n", i, s.out, s.out1, (char)s.codepoint); } printf("\n"); #endif } } SizeType NewState(SizeType out, SizeType out1, unsigned codepoint) { State* s = states_.template Push(); s->out = out; s->out1 = out1; s->codepoint = codepoint; s->rangeStart = kRegexInvalidRange; return stateCount_++; } void PushOperand(Stack& operandStack, unsigned codepoint) { SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, codepoint); *operandStack.template Push() = Frag(s, s, s); } void ImplicitConcatenation(Stack& atomCountStack, Stack& operatorStack) { if (*atomCountStack.template Top()) *operatorStack.template Push() = kConcatenation; (*atomCountStack.template Top())++; } SizeType Append(SizeType l1, SizeType l2) { SizeType old = l1; while (GetState(l1).out != kRegexInvalidState) l1 = GetState(l1).out; GetState(l1).out = l2; return old; } void Patch(SizeType l, SizeType s) { for (SizeType next; l != kRegexInvalidState; l = next) { next = GetState(l).out; GetState(l).out = s; } } bool Eval(Stack& operandStack, Operator op) { switch (op) { case kConcatenation: RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag) * 2); { Frag e2 = *operandStack.template Pop(1); Frag e1 = *operandStack.template Pop(1); Patch(e1.out, e2.start); *operandStack.template Push() = Frag(e1.start, e2.out, Min(e1.minIndex, e2.minIndex)); } return true; case kAlternation: if (operandStack.GetSize() >= sizeof(Frag) * 2) { Frag e2 = *operandStack.template Pop(1); Frag e1 = *operandStack.template Pop(1); SizeType s = NewState(e1.start, e2.start, 0); *operandStack.template Push() = Frag(s, Append(e1.out, e2.out), Min(e1.minIndex, e2.minIndex)); return true; } return false; case kZeroOrOne: if (operandStack.GetSize() >= sizeof(Frag)) { Frag e = *operandStack.template Pop(1); SizeType s = NewState(kRegexInvalidState, e.start, 0); *operandStack.template Push() = Frag(s, Append(e.out, s), e.minIndex); return true; } return false; case kZeroOrMore: if (operandStack.GetSize() >= sizeof(Frag)) { Frag e = *operandStack.template Pop(1); SizeType s = NewState(kRegexInvalidState, e.start, 0); Patch(e.out, s); *operandStack.template Push() = Frag(s, s, e.minIndex); return true; } return false; default: RAPIDJSON_ASSERT(op == kOneOrMore); if (operandStack.GetSize() >= sizeof(Frag)) { Frag e = *operandStack.template Pop(1); SizeType s = NewState(kRegexInvalidState, e.start, 0); Patch(e.out, s); *operandStack.template Push() = Frag(e.start, s, e.minIndex); return true; } return false; } } bool EvalQuantifier(Stack& operandStack, unsigned n, unsigned m) { RAPIDJSON_ASSERT(n <= m); RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag)); if (n == 0) { if (m == 0) // a{0} not support return false; else if (m == kInfinityQuantifier) Eval(operandStack, kZeroOrMore); // a{0,} -> a* else { Eval(operandStack, kZeroOrOne); // a{0,5} -> a? for (unsigned i = 0; i < m - 1; i++) CloneTopOperand(operandStack); // a{0,5} -> a? a? a? a? a? for (unsigned i = 0; i < m - 1; i++) Eval(operandStack, kConcatenation); // a{0,5} -> a?a?a?a?a? } return true; } for (unsigned i = 0; i < n - 1; i++) // a{3} -> a a a CloneTopOperand(operandStack); if (m == kInfinityQuantifier) Eval(operandStack, kOneOrMore); // a{3,} -> a a a+ else if (m > n) { CloneTopOperand(operandStack); // a{3,5} -> a a a a Eval(operandStack, kZeroOrOne); // a{3,5} -> a a a a? for (unsigned i = n; i < m - 1; i++) CloneTopOperand(operandStack); // a{3,5} -> a a a a? a? for (unsigned i = n; i < m; i++) Eval(operandStack, kConcatenation); // a{3,5} -> a a aa?a? } for (unsigned i = 0; i < n - 1; i++) Eval(operandStack, kConcatenation); // a{3} -> aaa, a{3,} -> aaa+, a{3.5} -> aaaa?a? return true; } static SizeType Min(SizeType a, SizeType b) { return a < b ? a : b; } void CloneTopOperand(Stack& operandStack) { const Frag src = *operandStack.template Top(); // Copy constructor to prevent invalidation SizeType count = stateCount_ - src.minIndex; // Assumes top operand contains states in [src->minIndex, stateCount_) State* s = states_.template Push(count); memcpy(s, &GetState(src.minIndex), count * sizeof(State)); for (SizeType j = 0; j < count; j++) { if (s[j].out != kRegexInvalidState) s[j].out += count; if (s[j].out1 != kRegexInvalidState) s[j].out1 += count; } *operandStack.template Push() = Frag(src.start + count, src.out + count, src.minIndex + count); stateCount_ += count; } template bool ParseUnsigned(DecodedStream& ds, unsigned* u) { unsigned r = 0; if (ds.Peek() < '0' || ds.Peek() > '9') return false; while (ds.Peek() >= '0' && ds.Peek() <= '9') { if (r >= 429496729 && ds.Peek() > '5') // 2^32 - 1 = 4294967295 return false; // overflow r = r * 10 + (ds.Take() - '0'); } *u = r; return true; } template bool ParseRange(DecodedStream& ds, SizeType* range) { bool isBegin = true; bool negate = false; int step = 0; SizeType start = kRegexInvalidRange; SizeType current = kRegexInvalidRange; unsigned codepoint; while ((codepoint = ds.Take()) != 0) { if (isBegin) { isBegin = false; if (codepoint == '^') { negate = true; continue; } } switch (codepoint) { case ']': if (start == kRegexInvalidRange) return false; // Error: nothing inside [] if (step == 2) { // Add trailing '-' SizeType r = NewRange('-'); RAPIDJSON_ASSERT(current != kRegexInvalidRange); GetRange(current).next = r; } if (negate) GetRange(start).start |= kRangeNegationFlag; *range = start; return true; case '\\': if (ds.Peek() == 'b') { ds.Take(); codepoint = 0x0008; // Escape backspace character } else if (!CharacterEscape(ds, &codepoint)) return false; // fall through to default default: switch (step) { case 1: if (codepoint == '-') { step++; break; } // fall through to step 0 for other characters case 0: { SizeType r = NewRange(codepoint); if (current != kRegexInvalidRange) GetRange(current).next = r; if (start == kRegexInvalidRange) start = r; current = r; } step = 1; break; default: RAPIDJSON_ASSERT(step == 2); GetRange(current).end = codepoint; step = 0; } } } return false; } SizeType NewRange(unsigned codepoint) { Range* r = ranges_.template Push(); r->start = r->end = codepoint; r->next = kRegexInvalidRange; return rangeCount_++; } template bool CharacterEscape(DecodedStream& ds, unsigned* escapedCodepoint) { unsigned codepoint; switch (codepoint = ds.Take()) { case '^': case '$': case '|': case '(': case ')': case '?': case '*': case '+': case '.': case '[': case ']': case '{': case '}': case '\\': *escapedCodepoint = codepoint; return true; case 'f': *escapedCodepoint = 0x000C; return true; case 'n': *escapedCodepoint = 0x000A; return true; case 'r': *escapedCodepoint = 0x000D; return true; case 't': *escapedCodepoint = 0x0009; return true; case 'v': *escapedCodepoint = 0x000B; return true; default: return false; // Unsupported escape character } } Stack states_; Stack ranges_; SizeType root_; SizeType stateCount_; SizeType rangeCount_; static const unsigned kInfinityQuantifier = ~0u; // For SearchWithAnchoring() bool anchorBegin_; bool anchorEnd_; }; template class GenericRegexSearch { public: typedef typename RegexType::EncodingType Encoding; typedef typename Encoding::Ch Ch; GenericRegexSearch(const RegexType& regex, Allocator* allocator = 0) : regex_(regex), allocator_(allocator), ownAllocator_(0), state0_(allocator, 0), state1_(allocator, 0), stateSet_() { RAPIDJSON_ASSERT(regex_.IsValid()); if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); stateSet_ = static_cast(allocator_->Malloc(GetStateSetSize())); state0_.template Reserve(regex_.stateCount_); state1_.template Reserve(regex_.stateCount_); } ~GenericRegexSearch() { Allocator::Free(stateSet_); RAPIDJSON_DELETE(ownAllocator_); } template bool Match(InputStream& is) { return SearchWithAnchoring(is, true, true); } bool Match(const Ch* s) { GenericStringStream is(s); return Match(is); } template bool Search(InputStream& is) { return SearchWithAnchoring(is, regex_.anchorBegin_, regex_.anchorEnd_); } bool Search(const Ch* s) { GenericStringStream is(s); return Search(is); } private: typedef typename RegexType::State State; typedef typename RegexType::Range Range; template bool SearchWithAnchoring(InputStream& is, bool anchorBegin, bool anchorEnd) { DecodedStream ds(is); state0_.Clear(); Stack *current = &state0_, *next = &state1_; const size_t stateSetSize = GetStateSetSize(); std::memset(stateSet_, 0, stateSetSize); bool matched = AddState(*current, regex_.root_); unsigned codepoint; while (!current->Empty() && (codepoint = ds.Take()) != 0) { std::memset(stateSet_, 0, stateSetSize); next->Clear(); matched = false; for (const SizeType* s = current->template Bottom(); s != current->template End(); ++s) { const State& sr = regex_.GetState(*s); if (sr.codepoint == codepoint || sr.codepoint == RegexType::kAnyCharacterClass || (sr.codepoint == RegexType::kRangeCharacterClass && MatchRange(sr.rangeStart, codepoint))) { matched = AddState(*next, sr.out) || matched; if (!anchorEnd && matched) return true; } if (!anchorBegin) AddState(*next, regex_.root_); } internal::Swap(current, next); } return matched; } size_t GetStateSetSize() const { return (regex_.stateCount_ + 31) / 32 * 4; } // Return whether the added states is a match state bool AddState(Stack& l, SizeType index) { RAPIDJSON_ASSERT(index != kRegexInvalidState); const State& s = regex_.GetState(index); if (s.out1 != kRegexInvalidState) { // Split bool matched = AddState(l, s.out); return AddState(l, s.out1) || matched; } else if (!(stateSet_[index >> 5] & (1u << (index & 31)))) { stateSet_[index >> 5] |= (1u << (index & 31)); *l.template PushUnsafe() = index; } return s.out == kRegexInvalidState; // by using PushUnsafe() above, we can ensure s is not validated due to reallocation. } bool MatchRange(SizeType rangeIndex, unsigned codepoint) const { bool yes = (regex_.GetRange(rangeIndex).start & RegexType::kRangeNegationFlag) == 0; while (rangeIndex != kRegexInvalidRange) { const Range& r = regex_.GetRange(rangeIndex); if (codepoint >= (r.start & ~RegexType::kRangeNegationFlag) && codepoint <= r.end) return yes; rangeIndex = r.next; } return !yes; } const RegexType& regex_; Allocator* allocator_; Allocator* ownAllocator_; Stack state0_; Stack state1_; uint32_t* stateSet_; }; typedef GenericRegex > Regex; typedef GenericRegexSearch RegexSearch; } // namespace internal RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #ifdef _MSC_VER RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_INTERNAL_REGEX_H_ ================================================ FILE: third-party/rapidjson/internal/stack.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_INTERNAL_STACK_H_ #define RAPIDJSON_INTERNAL_STACK_H_ #include "../allocators.h" #include "swap.h" #if defined(__clang__) RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(c++98-compat) #endif RAPIDJSON_NAMESPACE_BEGIN namespace internal { /////////////////////////////////////////////////////////////////////////////// // Stack //! A type-unsafe stack for storing different types of data. /*! \tparam Allocator Allocator for allocating stack memory. */ template class Stack { public: // Optimization note: Do not allocate memory for stack_ in constructor. // Do it lazily when first Push() -> Expand() -> Resize(). Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS Stack(Stack&& rhs) : allocator_(rhs.allocator_), ownAllocator_(rhs.ownAllocator_), stack_(rhs.stack_), stackTop_(rhs.stackTop_), stackEnd_(rhs.stackEnd_), initialCapacity_(rhs.initialCapacity_) { rhs.allocator_ = 0; rhs.ownAllocator_ = 0; rhs.stack_ = 0; rhs.stackTop_ = 0; rhs.stackEnd_ = 0; rhs.initialCapacity_ = 0; } #endif ~Stack() { Destroy(); } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS Stack& operator=(Stack&& rhs) { if (&rhs != this) { Destroy(); allocator_ = rhs.allocator_; ownAllocator_ = rhs.ownAllocator_; stack_ = rhs.stack_; stackTop_ = rhs.stackTop_; stackEnd_ = rhs.stackEnd_; initialCapacity_ = rhs.initialCapacity_; rhs.allocator_ = 0; rhs.ownAllocator_ = 0; rhs.stack_ = 0; rhs.stackTop_ = 0; rhs.stackEnd_ = 0; rhs.initialCapacity_ = 0; } return *this; } #endif void Swap(Stack& rhs) RAPIDJSON_NOEXCEPT { internal::Swap(allocator_, rhs.allocator_); internal::Swap(ownAllocator_, rhs.ownAllocator_); internal::Swap(stack_, rhs.stack_); internal::Swap(stackTop_, rhs.stackTop_); internal::Swap(stackEnd_, rhs.stackEnd_); internal::Swap(initialCapacity_, rhs.initialCapacity_); } void Clear() { stackTop_ = stack_; } void ShrinkToFit() { if (Empty()) { // If the stack is empty, completely deallocate the memory. Allocator::Free(stack_); stack_ = 0; stackTop_ = 0; stackEnd_ = 0; } else Resize(GetSize()); } // Optimization note: try to minimize the size of this function for force inline. // Expansion is run very infrequently, so it is moved to another (probably non-inline) function. template RAPIDJSON_FORCEINLINE void Reserve(size_t count = 1) { // Expand the stack if needed if (RAPIDJSON_UNLIKELY(stackTop_ + sizeof(T) * count > stackEnd_)) Expand(count); } template RAPIDJSON_FORCEINLINE T* Push(size_t count = 1) { Reserve(count); return PushUnsafe(count); } template RAPIDJSON_FORCEINLINE T* PushUnsafe(size_t count = 1) { RAPIDJSON_ASSERT(stackTop_); RAPIDJSON_ASSERT(stackTop_ + sizeof(T) * count <= stackEnd_); T* ret = reinterpret_cast(stackTop_); stackTop_ += sizeof(T) * count; return ret; } template T* Pop(size_t count) { RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); stackTop_ -= count * sizeof(T); return reinterpret_cast(stackTop_); } template T* Top() { RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); return reinterpret_cast(stackTop_ - sizeof(T)); } template const T* Top() const { RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); return reinterpret_cast(stackTop_ - sizeof(T)); } template T* End() { return reinterpret_cast(stackTop_); } template const T* End() const { return reinterpret_cast(stackTop_); } template T* Bottom() { return reinterpret_cast(stack_); } template const T* Bottom() const { return reinterpret_cast(stack_); } bool HasAllocator() const { return allocator_ != 0; } Allocator& GetAllocator() { RAPIDJSON_ASSERT(allocator_); return *allocator_; } bool Empty() const { return stackTop_ == stack_; } size_t GetSize() const { return static_cast(stackTop_ - stack_); } size_t GetCapacity() const { return static_cast(stackEnd_ - stack_); } private: template void Expand(size_t count) { // Only expand the capacity if the current stack exists. Otherwise just create a stack with initial capacity. size_t newCapacity; if (stack_ == 0) { if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); newCapacity = initialCapacity_; } else { newCapacity = GetCapacity(); newCapacity += (newCapacity + 1) / 2; } size_t newSize = GetSize() + sizeof(T) * count; if (newCapacity < newSize) newCapacity = newSize; Resize(newCapacity); } void Resize(size_t newCapacity) { const size_t size = GetSize(); // Backup the current size stack_ = static_cast(allocator_->Realloc(stack_, GetCapacity(), newCapacity)); stackTop_ = stack_ + size; stackEnd_ = stack_ + newCapacity; } void Destroy() { Allocator::Free(stack_); RAPIDJSON_DELETE(ownAllocator_); // Only delete if it is owned by the stack } // Prohibit copy constructor & assignment operator. Stack(const Stack&); Stack& operator=(const Stack&); Allocator* allocator_; Allocator* ownAllocator_; char *stack_; char *stackTop_; char *stackEnd_; size_t initialCapacity_; }; } // namespace internal RAPIDJSON_NAMESPACE_END #if defined(__clang__) RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_STACK_H_ ================================================ FILE: third-party/rapidjson/internal/strfunc.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ #define RAPIDJSON_INTERNAL_STRFUNC_H_ #include "../stream.h" #include RAPIDJSON_NAMESPACE_BEGIN namespace internal { //! Custom strlen() which works on different character types. /*! \tparam Ch Character type (e.g. char, wchar_t, short) \param s Null-terminated input string. \return Number of characters in the string. \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. */ template inline SizeType StrLen(const Ch* s) { RAPIDJSON_ASSERT(s != 0); const Ch* p = s; while (*p) ++p; return SizeType(p - s); } template <> inline SizeType StrLen(const char* s) { return SizeType(std::strlen(s)); } template <> inline SizeType StrLen(const wchar_t* s) { return SizeType(std::wcslen(s)); } //! Returns number of code points in a encoded string. template bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { RAPIDJSON_ASSERT(s != 0); RAPIDJSON_ASSERT(outCount != 0); GenericStringStream is(s); const typename Encoding::Ch* end = s + length; SizeType count = 0; while (is.src_ < end) { unsigned codepoint; if (!Encoding::Decode(is, &codepoint)) return false; count++; } *outCount = count; return true; } } // namespace internal RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_INTERNAL_STRFUNC_H_ ================================================ FILE: third-party/rapidjson/internal/strtod.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_STRTOD_ #define RAPIDJSON_STRTOD_ #include "ieee754.h" #include "biginteger.h" #include "diyfp.h" #include "pow10.h" RAPIDJSON_NAMESPACE_BEGIN namespace internal { inline double FastPath(double significand, int exp) { if (exp < -308) return 0.0; else if (exp >= 0) return significand * internal::Pow10(exp); else return significand / internal::Pow10(-exp); } inline double StrtodNormalPrecision(double d, int p) { if (p < -308) { // Prevent expSum < -308, making Pow10(p) = 0 d = FastPath(d, -308); d = FastPath(d, p + 308); } else d = FastPath(d, p); return d; } template inline T Min3(T a, T b, T c) { T m = a; if (m > b) m = b; if (m > c) m = c; return m; } inline int CheckWithinHalfULP(double b, const BigInteger& d, int dExp) { const Double db(b); const uint64_t bInt = db.IntegerSignificand(); const int bExp = db.IntegerExponent(); const int hExp = bExp - 1; int dS_Exp2 = 0, dS_Exp5 = 0, bS_Exp2 = 0, bS_Exp5 = 0, hS_Exp2 = 0, hS_Exp5 = 0; // Adjust for decimal exponent if (dExp >= 0) { dS_Exp2 += dExp; dS_Exp5 += dExp; } else { bS_Exp2 -= dExp; bS_Exp5 -= dExp; hS_Exp2 -= dExp; hS_Exp5 -= dExp; } // Adjust for binary exponent if (bExp >= 0) bS_Exp2 += bExp; else { dS_Exp2 -= bExp; hS_Exp2 -= bExp; } // Adjust for half ulp exponent if (hExp >= 0) hS_Exp2 += hExp; else { dS_Exp2 -= hExp; bS_Exp2 -= hExp; } // Remove common power of two factor from all three scaled values int common_Exp2 = Min3(dS_Exp2, bS_Exp2, hS_Exp2); dS_Exp2 -= common_Exp2; bS_Exp2 -= common_Exp2; hS_Exp2 -= common_Exp2; BigInteger dS = d; dS.MultiplyPow5(static_cast(dS_Exp5)) <<= static_cast(dS_Exp2); BigInteger bS(bInt); bS.MultiplyPow5(static_cast(bS_Exp5)) <<= static_cast(bS_Exp2); BigInteger hS(1); hS.MultiplyPow5(static_cast(hS_Exp5)) <<= static_cast(hS_Exp2); BigInteger delta(0); dS.Difference(bS, &delta); return delta.Compare(hS); } inline bool StrtodFast(double d, int p, double* result) { // Use fast path for string-to-double conversion if possible // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ if (p > 22 && p < 22 + 16) { // Fast Path Cases In Disguise d *= internal::Pow10(p - 22); p = 22; } if (p >= -22 && p <= 22 && d <= 9007199254740991.0) { // 2^53 - 1 *result = FastPath(d, p); return true; } else return false; } // Compute an approximation and see if it is within 1/2 ULP inline bool StrtodDiyFp(const char* decimals, size_t length, size_t decimalPosition, int exp, double* result) { uint64_t significand = 0; size_t i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 for (; i < length; i++) { if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) break; significand = significand * 10u + static_cast(decimals[i] - '0'); } if (i < length && decimals[i] >= '5') // Rounding significand++; size_t remaining = length - i; const int kUlpShift = 3; const int kUlp = 1 << kUlpShift; int64_t error = (remaining == 0) ? 0 : kUlp / 2; DiyFp v(significand, 0); v = v.Normalize(); error <<= -v.e; const int dExp = static_cast(decimalPosition) - static_cast(i) + exp; int actualExp; DiyFp cachedPower = GetCachedPower10(dExp, &actualExp); if (actualExp != dExp) { static const DiyFp kPow10[] = { DiyFp(RAPIDJSON_UINT64_C2(0xa0000000, 00000000), -60), // 10^1 DiyFp(RAPIDJSON_UINT64_C2(0xc8000000, 00000000), -57), // 10^2 DiyFp(RAPIDJSON_UINT64_C2(0xfa000000, 00000000), -54), // 10^3 DiyFp(RAPIDJSON_UINT64_C2(0x9c400000, 00000000), -50), // 10^4 DiyFp(RAPIDJSON_UINT64_C2(0xc3500000, 00000000), -47), // 10^5 DiyFp(RAPIDJSON_UINT64_C2(0xf4240000, 00000000), -44), // 10^6 DiyFp(RAPIDJSON_UINT64_C2(0x98968000, 00000000), -40) // 10^7 }; int adjustment = dExp - actualExp - 1; RAPIDJSON_ASSERT(adjustment >= 0 && adjustment < 7); v = v * kPow10[adjustment]; if (length + static_cast(adjustment)> 19u) // has more digits than decimal digits in 64-bit error += kUlp / 2; } v = v * cachedPower; error += kUlp + (error == 0 ? 0 : 1); const int oldExp = v.e; v = v.Normalize(); error <<= oldExp - v.e; const int effectiveSignificandSize = Double::EffectiveSignificandSize(64 + v.e); int precisionSize = 64 - effectiveSignificandSize; if (precisionSize + kUlpShift >= 64) { int scaleExp = (precisionSize + kUlpShift) - 63; v.f >>= scaleExp; v.e += scaleExp; error = (error >> scaleExp) + 1 + kUlp; precisionSize -= scaleExp; } DiyFp rounded(v.f >> precisionSize, v.e + precisionSize); const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; if (precisionBits >= halfWay + static_cast(error)) { rounded.f++; if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) rounded.f >>= 1; rounded.e++; } } *result = rounded.ToDouble(); return halfWay - static_cast(error) >= precisionBits || precisionBits >= halfWay + static_cast(error); } inline double StrtodBigInteger(double approx, const char* decimals, size_t length, size_t decimalPosition, int exp) { const BigInteger dInt(decimals, length); const int dExp = static_cast(decimalPosition) - static_cast(length) + exp; Double a(approx); int cmp = CheckWithinHalfULP(a.Value(), dInt, dExp); if (cmp < 0) return a.Value(); // within half ULP else if (cmp == 0) { // Round towards even if (a.Significand() & 1) return a.NextPositiveDouble(); else return a.Value(); } else // adjustment return a.NextPositiveDouble(); } inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { RAPIDJSON_ASSERT(d >= 0.0); RAPIDJSON_ASSERT(length >= 1); double result; if (StrtodFast(d, p, &result)) return result; // Trim leading zeros while (*decimals == '0' && length > 1) { length--; decimals++; decimalPosition--; } // Trim trailing zeros while (decimals[length - 1] == '0' && length > 1) { length--; decimalPosition--; exp++; } // Trim right-most digits const int kMaxDecimalDigit = 780; if (static_cast(length) > kMaxDecimalDigit) { int delta = (static_cast(length) - kMaxDecimalDigit); exp += delta; decimalPosition -= static_cast(delta); length = kMaxDecimalDigit; } // If too small, underflow to zero if (int(length) + exp < -324) return 0.0; if (StrtodDiyFp(decimals, length, decimalPosition, exp, &result)) return result; // Use approximation from StrtodDiyFp and make adjustment with BigInteger comparison return StrtodBigInteger(result, decimals, length, decimalPosition, exp); } } // namespace internal RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_STRTOD_ ================================================ FILE: third-party/rapidjson/internal/swap.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_INTERNAL_SWAP_H_ #define RAPIDJSON_INTERNAL_SWAP_H_ #include "../rapidjson.h" #if defined(__clang__) RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(c++98-compat) #endif RAPIDJSON_NAMESPACE_BEGIN namespace internal { //! Custom swap() to avoid dependency on C++ header /*! \tparam T Type of the arguments to swap, should be instantiated with primitive C++ types only. \note This has the same semantics as std::swap(). */ template inline void Swap(T& a, T& b) RAPIDJSON_NOEXCEPT { T tmp = a; a = b; b = tmp; } } // namespace internal RAPIDJSON_NAMESPACE_END #if defined(__clang__) RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_INTERNAL_SWAP_H_ ================================================ FILE: third-party/rapidjson/istreamwrapper.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_ISTREAMWRAPPER_H_ #define RAPIDJSON_ISTREAMWRAPPER_H_ #include "stream.h" #include #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(padded) #endif #ifdef _MSC_VER RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(4351) // new behavior: elements of array 'array' will be default initialized #endif RAPIDJSON_NAMESPACE_BEGIN //! Wrapper of \c std::basic_istream into RapidJSON's Stream concept. /*! The classes can be wrapped including but not limited to: - \c std::istringstream - \c std::stringstream - \c std::wistringstream - \c std::wstringstream - \c std::ifstream - \c std::fstream - \c std::wifstream - \c std::wfstream \tparam StreamType Class derived from \c std::basic_istream. */ template class BasicIStreamWrapper { public: typedef typename StreamType::char_type Ch; BasicIStreamWrapper(StreamType& stream) : stream_(stream), count_(), peekBuffer_() {} Ch Peek() const { typename StreamType::int_type c = stream_.peek(); return RAPIDJSON_LIKELY(c != StreamType::traits_type::eof()) ? static_cast(c) : static_cast('\0'); } Ch Take() { typename StreamType::int_type c = stream_.get(); if (RAPIDJSON_LIKELY(c != StreamType::traits_type::eof())) { count_++; return static_cast(c); } else return '\0'; } // tellg() may return -1 when failed. So we count by ourself. size_t Tell() const { return count_; } Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } void Put(Ch) { RAPIDJSON_ASSERT(false); } void Flush() { RAPIDJSON_ASSERT(false); } size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } // For encoding detection only. const Ch* Peek4() const { RAPIDJSON_ASSERT(sizeof(Ch) == 1); // Only usable for byte stream. int i; bool hasError = false; for (i = 0; i < 4; ++i) { typename StreamType::int_type c = stream_.get(); if (c == StreamType::traits_type::eof()) { hasError = true; stream_.clear(); break; } peekBuffer_[i] = static_cast(c); } for (--i; i >= 0; --i) stream_.putback(peekBuffer_[i]); return !hasError ? peekBuffer_ : 0; } private: BasicIStreamWrapper(const BasicIStreamWrapper&); BasicIStreamWrapper& operator=(const BasicIStreamWrapper&); StreamType& stream_; size_t count_; //!< Number of characters read. Note: mutable Ch peekBuffer_[4]; }; typedef BasicIStreamWrapper IStreamWrapper; typedef BasicIStreamWrapper WIStreamWrapper; #if defined(__clang__) || defined(_MSC_VER) RAPIDJSON_DIAG_POP #endif RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_ISTREAMWRAPPER_H_ ================================================ FILE: third-party/rapidjson/memorybuffer.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_MEMORYBUFFER_H_ #define RAPIDJSON_MEMORYBUFFER_H_ #include "stream.h" #include "internal/stack.h" RAPIDJSON_NAMESPACE_BEGIN //! Represents an in-memory output byte stream. /*! This class is mainly for being wrapped by EncodedOutputStream or AutoUTFOutputStream. It is similar to FileWriteBuffer but the destination is an in-memory buffer instead of a file. Differences between MemoryBuffer and StringBuffer: 1. StringBuffer has Encoding but MemoryBuffer is only a byte buffer. 2. StringBuffer::GetString() returns a null-terminated string. MemoryBuffer::GetBuffer() returns a buffer without terminator. \tparam Allocator type for allocating memory buffer. \note implements Stream concept */ template struct GenericMemoryBuffer { typedef char Ch; // byte GenericMemoryBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} void Put(Ch c) { *stack_.template Push() = c; } void Flush() {} void Clear() { stack_.Clear(); } void ShrinkToFit() { stack_.ShrinkToFit(); } Ch* Push(size_t count) { return stack_.template Push(count); } void Pop(size_t count) { stack_.template Pop(count); } const Ch* GetBuffer() const { return stack_.template Bottom(); } size_t GetSize() const { return stack_.GetSize(); } static const size_t kDefaultCapacity = 256; mutable internal::Stack stack_; }; typedef GenericMemoryBuffer<> MemoryBuffer; //! Implement specialized version of PutN() with memset() for better performance. template<> inline void PutN(MemoryBuffer& memoryBuffer, char c, size_t n) { std::memset(memoryBuffer.stack_.Push(n), c, n * sizeof(c)); } RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_MEMORYBUFFER_H_ ================================================ FILE: third-party/rapidjson/memorystream.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_MEMORYSTREAM_H_ #define RAPIDJSON_MEMORYSTREAM_H_ #include "stream.h" #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(unreachable-code) RAPIDJSON_DIAG_OFF(missing-noreturn) #endif RAPIDJSON_NAMESPACE_BEGIN //! Represents an in-memory input byte stream. /*! This class is mainly for being wrapped by EncodedInputStream or AutoUTFInputStream. It is similar to FileReadBuffer but the source is an in-memory buffer instead of a file. Differences between MemoryStream and StringStream: 1. StringStream has encoding but MemoryStream is a byte stream. 2. MemoryStream needs size of the source buffer and the buffer don't need to be null terminated. StringStream assume null-terminated string as source. 3. MemoryStream supports Peek4() for encoding detection. StringStream is specified with an encoding so it should not have Peek4(). \note implements Stream concept */ struct MemoryStream { typedef char Ch; // byte MemoryStream(const Ch *src, size_t size) : src_(src), begin_(src), end_(src + size), size_(size) {} Ch Peek() const { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_; } Ch Take() { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_++; } size_t Tell() const { return static_cast(src_ - begin_); } Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } void Put(Ch) { RAPIDJSON_ASSERT(false); } void Flush() { RAPIDJSON_ASSERT(false); } size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } // For encoding detection only. const Ch* Peek4() const { return Tell() + 4 <= size_ ? src_ : 0; } const Ch* src_; //!< Current read position. const Ch* begin_; //!< Original head of the string. const Ch* end_; //!< End of stream. size_t size_; //!< Size of the stream. }; RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_MEMORYBUFFER_H_ ================================================ FILE: third-party/rapidjson/msinttypes/inttypes.h ================================================ // ISO C9x compliant inttypes.h for Microsoft Visual Studio // Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 // // Copyright (c) 2006-2013 Alexander Chemeris // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. Neither the name of the product nor the names of its contributors may // be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /////////////////////////////////////////////////////////////////////////////// // The above software in this distribution may have been modified by // THL A29 Limited ("Tencent Modifications"). // All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. #ifndef _MSC_VER // [ #error "Use this header only with Microsoft Visual C++ compilers!" #endif // _MSC_VER ] #ifndef _MSC_INTTYPES_H_ // [ #define _MSC_INTTYPES_H_ #if _MSC_VER > 1000 #pragma once #endif #include "stdint.h" // miloyip: VC supports inttypes.h since VC2013 #if _MSC_VER >= 1800 #include #else // 7.8 Format conversion of integer types typedef struct { intmax_t quot; intmax_t rem; } imaxdiv_t; // 7.8.1 Macros for format specifiers #if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 // The fprintf macros for signed integers are: #define PRId8 "d" #define PRIi8 "i" #define PRIdLEAST8 "d" #define PRIiLEAST8 "i" #define PRIdFAST8 "d" #define PRIiFAST8 "i" #define PRId16 "hd" #define PRIi16 "hi" #define PRIdLEAST16 "hd" #define PRIiLEAST16 "hi" #define PRIdFAST16 "hd" #define PRIiFAST16 "hi" #define PRId32 "I32d" #define PRIi32 "I32i" #define PRIdLEAST32 "I32d" #define PRIiLEAST32 "I32i" #define PRIdFAST32 "I32d" #define PRIiFAST32 "I32i" #define PRId64 "I64d" #define PRIi64 "I64i" #define PRIdLEAST64 "I64d" #define PRIiLEAST64 "I64i" #define PRIdFAST64 "I64d" #define PRIiFAST64 "I64i" #define PRIdMAX "I64d" #define PRIiMAX "I64i" #define PRIdPTR "Id" #define PRIiPTR "Ii" // The fprintf macros for unsigned integers are: #define PRIo8 "o" #define PRIu8 "u" #define PRIx8 "x" #define PRIX8 "X" #define PRIoLEAST8 "o" #define PRIuLEAST8 "u" #define PRIxLEAST8 "x" #define PRIXLEAST8 "X" #define PRIoFAST8 "o" #define PRIuFAST8 "u" #define PRIxFAST8 "x" #define PRIXFAST8 "X" #define PRIo16 "ho" #define PRIu16 "hu" #define PRIx16 "hx" #define PRIX16 "hX" #define PRIoLEAST16 "ho" #define PRIuLEAST16 "hu" #define PRIxLEAST16 "hx" #define PRIXLEAST16 "hX" #define PRIoFAST16 "ho" #define PRIuFAST16 "hu" #define PRIxFAST16 "hx" #define PRIXFAST16 "hX" #define PRIo32 "I32o" #define PRIu32 "I32u" #define PRIx32 "I32x" #define PRIX32 "I32X" #define PRIoLEAST32 "I32o" #define PRIuLEAST32 "I32u" #define PRIxLEAST32 "I32x" #define PRIXLEAST32 "I32X" #define PRIoFAST32 "I32o" #define PRIuFAST32 "I32u" #define PRIxFAST32 "I32x" #define PRIXFAST32 "I32X" #define PRIo64 "I64o" #define PRIu64 "I64u" #define PRIx64 "I64x" #define PRIX64 "I64X" #define PRIoLEAST64 "I64o" #define PRIuLEAST64 "I64u" #define PRIxLEAST64 "I64x" #define PRIXLEAST64 "I64X" #define PRIoFAST64 "I64o" #define PRIuFAST64 "I64u" #define PRIxFAST64 "I64x" #define PRIXFAST64 "I64X" #define PRIoMAX "I64o" #define PRIuMAX "I64u" #define PRIxMAX "I64x" #define PRIXMAX "I64X" #define PRIoPTR "Io" #define PRIuPTR "Iu" #define PRIxPTR "Ix" #define PRIXPTR "IX" // The fscanf macros for signed integers are: #define SCNd8 "d" #define SCNi8 "i" #define SCNdLEAST8 "d" #define SCNiLEAST8 "i" #define SCNdFAST8 "d" #define SCNiFAST8 "i" #define SCNd16 "hd" #define SCNi16 "hi" #define SCNdLEAST16 "hd" #define SCNiLEAST16 "hi" #define SCNdFAST16 "hd" #define SCNiFAST16 "hi" #define SCNd32 "ld" #define SCNi32 "li" #define SCNdLEAST32 "ld" #define SCNiLEAST32 "li" #define SCNdFAST32 "ld" #define SCNiFAST32 "li" #define SCNd64 "I64d" #define SCNi64 "I64i" #define SCNdLEAST64 "I64d" #define SCNiLEAST64 "I64i" #define SCNdFAST64 "I64d" #define SCNiFAST64 "I64i" #define SCNdMAX "I64d" #define SCNiMAX "I64i" #ifdef _WIN64 // [ # define SCNdPTR "I64d" # define SCNiPTR "I64i" #else // _WIN64 ][ # define SCNdPTR "ld" # define SCNiPTR "li" #endif // _WIN64 ] // The fscanf macros for unsigned integers are: #define SCNo8 "o" #define SCNu8 "u" #define SCNx8 "x" #define SCNX8 "X" #define SCNoLEAST8 "o" #define SCNuLEAST8 "u" #define SCNxLEAST8 "x" #define SCNXLEAST8 "X" #define SCNoFAST8 "o" #define SCNuFAST8 "u" #define SCNxFAST8 "x" #define SCNXFAST8 "X" #define SCNo16 "ho" #define SCNu16 "hu" #define SCNx16 "hx" #define SCNX16 "hX" #define SCNoLEAST16 "ho" #define SCNuLEAST16 "hu" #define SCNxLEAST16 "hx" #define SCNXLEAST16 "hX" #define SCNoFAST16 "ho" #define SCNuFAST16 "hu" #define SCNxFAST16 "hx" #define SCNXFAST16 "hX" #define SCNo32 "lo" #define SCNu32 "lu" #define SCNx32 "lx" #define SCNX32 "lX" #define SCNoLEAST32 "lo" #define SCNuLEAST32 "lu" #define SCNxLEAST32 "lx" #define SCNXLEAST32 "lX" #define SCNoFAST32 "lo" #define SCNuFAST32 "lu" #define SCNxFAST32 "lx" #define SCNXFAST32 "lX" #define SCNo64 "I64o" #define SCNu64 "I64u" #define SCNx64 "I64x" #define SCNX64 "I64X" #define SCNoLEAST64 "I64o" #define SCNuLEAST64 "I64u" #define SCNxLEAST64 "I64x" #define SCNXLEAST64 "I64X" #define SCNoFAST64 "I64o" #define SCNuFAST64 "I64u" #define SCNxFAST64 "I64x" #define SCNXFAST64 "I64X" #define SCNoMAX "I64o" #define SCNuMAX "I64u" #define SCNxMAX "I64x" #define SCNXMAX "I64X" #ifdef _WIN64 // [ # define SCNoPTR "I64o" # define SCNuPTR "I64u" # define SCNxPTR "I64x" # define SCNXPTR "I64X" #else // _WIN64 ][ # define SCNoPTR "lo" # define SCNuPTR "lu" # define SCNxPTR "lx" # define SCNXPTR "lX" #endif // _WIN64 ] #endif // __STDC_FORMAT_MACROS ] // 7.8.2 Functions for greatest-width integer types // 7.8.2.1 The imaxabs function #define imaxabs _abs64 // 7.8.2.2 The imaxdiv function // This is modified version of div() function from Microsoft's div.c found // in %MSVC.NET%\crt\src\div.c #ifdef STATIC_IMAXDIV // [ static #else // STATIC_IMAXDIV ][ _inline #endif // STATIC_IMAXDIV ] imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) { imaxdiv_t result; result.quot = numer / denom; result.rem = numer % denom; if (numer < 0 && result.rem > 0) { // did division wrong; must fix up ++result.quot; result.rem -= denom; } return result; } // 7.8.2.3 The strtoimax and strtoumax functions #define strtoimax _strtoi64 #define strtoumax _strtoui64 // 7.8.2.4 The wcstoimax and wcstoumax functions #define wcstoimax _wcstoi64 #define wcstoumax _wcstoui64 #endif // _MSC_VER >= 1800 #endif // _MSC_INTTYPES_H_ ] ================================================ FILE: third-party/rapidjson/msinttypes/stdint.h ================================================ // ISO C9x compliant stdint.h for Microsoft Visual Studio // Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 // // Copyright (c) 2006-2013 Alexander Chemeris // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. Neither the name of the product nor the names of its contributors may // be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /////////////////////////////////////////////////////////////////////////////// // The above software in this distribution may have been modified by // THL A29 Limited ("Tencent Modifications"). // All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. #ifndef _MSC_VER // [ #error "Use this header only with Microsoft Visual C++ compilers!" #endif // _MSC_VER ] #ifndef _MSC_STDINT_H_ // [ #define _MSC_STDINT_H_ #if _MSC_VER > 1000 #pragma once #endif // miloyip: Originally Visual Studio 2010 uses its own stdint.h. However it generates warning with INT64_C(), so change to use this file for vs2010. #if _MSC_VER >= 1600 // [ #include #if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 #undef INT8_C #undef INT16_C #undef INT32_C #undef INT64_C #undef UINT8_C #undef UINT16_C #undef UINT32_C #undef UINT64_C // 7.18.4.1 Macros for minimum-width integer constants #define INT8_C(val) val##i8 #define INT16_C(val) val##i16 #define INT32_C(val) val##i32 #define INT64_C(val) val##i64 #define UINT8_C(val) val##ui8 #define UINT16_C(val) val##ui16 #define UINT32_C(val) val##ui32 #define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants // These #ifndef's are needed to prevent collisions with . // Check out Issue 9 for the details. #ifndef INTMAX_C // [ # define INTMAX_C INT64_C #endif // INTMAX_C ] #ifndef UINTMAX_C // [ # define UINTMAX_C UINT64_C #endif // UINTMAX_C ] #endif // __STDC_CONSTANT_MACROS ] #else // ] _MSC_VER >= 1700 [ #include // For Visual Studio 6 in C++ mode and for many Visual Studio versions when // compiling for ARM we have to wrap include with 'extern "C++" {}' // or compiler would give many errors like this: // error C2733: second C linkage of overloaded function 'wmemchr' not allowed #if defined(__cplusplus) && !defined(_M_ARM) extern "C" { #endif # include #if defined(__cplusplus) && !defined(_M_ARM) } #endif // Define _W64 macros to mark types changing their size, like intptr_t. #ifndef _W64 # if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 # define _W64 __w64 # else # define _W64 # endif #endif // 7.18.1 Integer types // 7.18.1.1 Exact-width integer types // Visual Studio 6 and Embedded Visual C++ 4 doesn't // realize that, e.g. char has the same size as __int8 // so we give up on __intX for them. #if (_MSC_VER < 1300) typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; #else typedef signed __int8 int8_t; typedef signed __int16 int16_t; typedef signed __int32 int32_t; typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; #endif typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; // 7.18.1.2 Minimum-width integer types typedef int8_t int_least8_t; typedef int16_t int_least16_t; typedef int32_t int_least32_t; typedef int64_t int_least64_t; typedef uint8_t uint_least8_t; typedef uint16_t uint_least16_t; typedef uint32_t uint_least32_t; typedef uint64_t uint_least64_t; // 7.18.1.3 Fastest minimum-width integer types typedef int8_t int_fast8_t; typedef int16_t int_fast16_t; typedef int32_t int_fast32_t; typedef int64_t int_fast64_t; typedef uint8_t uint_fast8_t; typedef uint16_t uint_fast16_t; typedef uint32_t uint_fast32_t; typedef uint64_t uint_fast64_t; // 7.18.1.4 Integer types capable of holding object pointers #ifdef _WIN64 // [ typedef signed __int64 intptr_t; typedef unsigned __int64 uintptr_t; #else // _WIN64 ][ typedef _W64 signed int intptr_t; typedef _W64 unsigned int uintptr_t; #endif // _WIN64 ] // 7.18.1.5 Greatest-width integer types typedef int64_t intmax_t; typedef uint64_t uintmax_t; // 7.18.2 Limits of specified-width integer types #if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 // 7.18.2.1 Limits of exact-width integer types #define INT8_MIN ((int8_t)_I8_MIN) #define INT8_MAX _I8_MAX #define INT16_MIN ((int16_t)_I16_MIN) #define INT16_MAX _I16_MAX #define INT32_MIN ((int32_t)_I32_MIN) #define INT32_MAX _I32_MAX #define INT64_MIN ((int64_t)_I64_MIN) #define INT64_MAX _I64_MAX #define UINT8_MAX _UI8_MAX #define UINT16_MAX _UI16_MAX #define UINT32_MAX _UI32_MAX #define UINT64_MAX _UI64_MAX // 7.18.2.2 Limits of minimum-width integer types #define INT_LEAST8_MIN INT8_MIN #define INT_LEAST8_MAX INT8_MAX #define INT_LEAST16_MIN INT16_MIN #define INT_LEAST16_MAX INT16_MAX #define INT_LEAST32_MIN INT32_MIN #define INT_LEAST32_MAX INT32_MAX #define INT_LEAST64_MIN INT64_MIN #define INT_LEAST64_MAX INT64_MAX #define UINT_LEAST8_MAX UINT8_MAX #define UINT_LEAST16_MAX UINT16_MAX #define UINT_LEAST32_MAX UINT32_MAX #define UINT_LEAST64_MAX UINT64_MAX // 7.18.2.3 Limits of fastest minimum-width integer types #define INT_FAST8_MIN INT8_MIN #define INT_FAST8_MAX INT8_MAX #define INT_FAST16_MIN INT16_MIN #define INT_FAST16_MAX INT16_MAX #define INT_FAST32_MIN INT32_MIN #define INT_FAST32_MAX INT32_MAX #define INT_FAST64_MIN INT64_MIN #define INT_FAST64_MAX INT64_MAX #define UINT_FAST8_MAX UINT8_MAX #define UINT_FAST16_MAX UINT16_MAX #define UINT_FAST32_MAX UINT32_MAX #define UINT_FAST64_MAX UINT64_MAX // 7.18.2.4 Limits of integer types capable of holding object pointers #ifdef _WIN64 // [ # define INTPTR_MIN INT64_MIN # define INTPTR_MAX INT64_MAX # define UINTPTR_MAX UINT64_MAX #else // _WIN64 ][ # define INTPTR_MIN INT32_MIN # define INTPTR_MAX INT32_MAX # define UINTPTR_MAX UINT32_MAX #endif // _WIN64 ] // 7.18.2.5 Limits of greatest-width integer types #define INTMAX_MIN INT64_MIN #define INTMAX_MAX INT64_MAX #define UINTMAX_MAX UINT64_MAX // 7.18.3 Limits of other integer types #ifdef _WIN64 // [ # define PTRDIFF_MIN _I64_MIN # define PTRDIFF_MAX _I64_MAX #else // _WIN64 ][ # define PTRDIFF_MIN _I32_MIN # define PTRDIFF_MAX _I32_MAX #endif // _WIN64 ] #define SIG_ATOMIC_MIN INT_MIN #define SIG_ATOMIC_MAX INT_MAX #ifndef SIZE_MAX // [ # ifdef _WIN64 // [ # define SIZE_MAX _UI64_MAX # else // _WIN64 ][ # define SIZE_MAX _UI32_MAX # endif // _WIN64 ] #endif // SIZE_MAX ] // WCHAR_MIN and WCHAR_MAX are also defined in #ifndef WCHAR_MIN // [ # define WCHAR_MIN 0 #endif // WCHAR_MIN ] #ifndef WCHAR_MAX // [ # define WCHAR_MAX _UI16_MAX #endif // WCHAR_MAX ] #define WINT_MIN 0 #define WINT_MAX _UI16_MAX #endif // __STDC_LIMIT_MACROS ] // 7.18.4 Limits of other integer types #if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 // 7.18.4.1 Macros for minimum-width integer constants #define INT8_C(val) val##i8 #define INT16_C(val) val##i16 #define INT32_C(val) val##i32 #define INT64_C(val) val##i64 #define UINT8_C(val) val##ui8 #define UINT16_C(val) val##ui16 #define UINT32_C(val) val##ui32 #define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants // These #ifndef's are needed to prevent collisions with . // Check out Issue 9 for the details. #ifndef INTMAX_C // [ # define INTMAX_C INT64_C #endif // INTMAX_C ] #ifndef UINTMAX_C // [ # define UINTMAX_C UINT64_C #endif // UINTMAX_C ] #endif // __STDC_CONSTANT_MACROS ] #endif // _MSC_VER >= 1600 ] #endif // _MSC_STDINT_H_ ] ================================================ FILE: third-party/rapidjson/ostreamwrapper.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_OSTREAMWRAPPER_H_ #define RAPIDJSON_OSTREAMWRAPPER_H_ #include "stream.h" #include #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(padded) #endif RAPIDJSON_NAMESPACE_BEGIN //! Wrapper of \c std::basic_ostream into RapidJSON's Stream concept. /*! The classes can be wrapped including but not limited to: - \c std::ostringstream - \c std::stringstream - \c std::wpstringstream - \c std::wstringstream - \c std::ifstream - \c std::fstream - \c std::wofstream - \c std::wfstream \tparam StreamType Class derived from \c std::basic_ostream. */ template class BasicOStreamWrapper { public: typedef typename StreamType::char_type Ch; BasicOStreamWrapper(StreamType& stream) : stream_(stream) {} void Put(Ch c) { stream_.put(c); } void Flush() { stream_.flush(); } // Not implemented char Peek() const { RAPIDJSON_ASSERT(false); return 0; } char Take() { RAPIDJSON_ASSERT(false); return 0; } size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } private: BasicOStreamWrapper(const BasicOStreamWrapper&); BasicOStreamWrapper& operator=(const BasicOStreamWrapper&); StreamType& stream_; }; typedef BasicOStreamWrapper OStreamWrapper; typedef BasicOStreamWrapper WOStreamWrapper; #ifdef __clang__ RAPIDJSON_DIAG_POP #endif RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_OSTREAMWRAPPER_H_ ================================================ FILE: third-party/rapidjson/pointer.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_POINTER_H_ #define RAPIDJSON_POINTER_H_ #include "document.h" #include "internal/itoa.h" #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(switch-enum) #endif #ifdef _MSC_VER RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated #endif RAPIDJSON_NAMESPACE_BEGIN static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token //! Error code of parsing. /*! \ingroup RAPIDJSON_ERRORS \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode */ enum PointerParseErrorCode { kPointerParseErrorNone = 0, //!< The parse is successful kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' kPointerParseErrorInvalidEscape, //!< Invalid escape kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment }; /////////////////////////////////////////////////////////////////////////////// // GenericPointer //! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. /*! This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" (https://tools.ietf.org/html/rfc6901). A JSON pointer is for identifying a specific value in a JSON document (GenericDocument). It can simplify coding of DOM tree manipulation, because it can access multiple-level depth of DOM tree with single API call. After it parses a string representation (e.g. "/foo/0" or URI fragment representation (e.g. "#/foo/0") into its internal representation (tokens), it can be used to resolve a specific value in multiple documents, or sub-tree of documents. Contrary to GenericValue, Pointer can be copy constructed and copy assigned. Apart from assignment, a Pointer cannot be modified after construction. Although Pointer is very convenient, please aware that constructing Pointer involves parsing and dynamic memory allocation. A special constructor with user- supplied tokens eliminates these. GenericPointer depends on GenericDocument and GenericValue. \tparam ValueType The value type of the DOM tree. E.g. GenericValue > \tparam Allocator The allocator type for allocating memory for internal representation. \note GenericPointer uses same encoding of ValueType. However, Allocator of GenericPointer is independent of Allocator of Value. */ template class GenericPointer { public: typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value typedef typename ValueType::Ch Ch; //!< Character type from Value //! A token is the basic units of internal representation. /*! A JSON pointer string representation "/foo/123" is parsed to two tokens: "foo" and 123. 123 will be represented in both numeric form and string form. They are resolved according to the actual value type (object or array). For token that are not numbers, or the numeric value is out of bound (greater than limits of SizeType), they are only treated as string form (i.e. the token's index will be equal to kPointerInvalidIndex). This struct is public so that user can create a Pointer without parsing and allocation, using a special constructor. */ struct Token { const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. SizeType length; //!< Length of the name. SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. }; //!@name Constructors and destructor. //@{ //! Default constructor. GenericPointer(Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} //! Constructor that parses a string or URI fragment representation. /*! \param source A null-terminated, string or URI fragment representation of JSON pointer. \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. */ explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { Parse(source, internal::StrLen(source)); } #if RAPIDJSON_HAS_STDSTRING //! Constructor that parses a string or URI fragment representation. /*! \param source A string or URI fragment representation of JSON pointer. \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. */ explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { Parse(source.c_str(), source.size()); } #endif //! Constructor that parses a string or URI fragment representation, with length of the source string. /*! \param source A string or URI fragment representation of JSON pointer. \param length Length of source. \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. \note Slightly faster than the overload without length. */ GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { Parse(source, length); } //! Constructor with user-supplied tokens. /*! This constructor let user supplies const array of tokens. This prevents the parsing process and eliminates allocation. This is preferred for memory constrained environments. \param tokens An constant array of tokens representing the JSON pointer. \param tokenCount Number of tokens. \b Example \code #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } #define INDEX(i) { #i, sizeof(#i) - 1, i } static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); // Equivalent to static const Pointer p("/foo/123"); #undef NAME #undef INDEX \endcode */ GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} //! Copy constructor. GenericPointer(const GenericPointer& rhs, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { *this = rhs; } //! Destructor. ~GenericPointer() { if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. Allocator::Free(tokens_); RAPIDJSON_DELETE(ownAllocator_); } //! Assignment operator. GenericPointer& operator=(const GenericPointer& rhs) { if (this != &rhs) { // Do not delete ownAllcator if (nameBuffer_) Allocator::Free(tokens_); tokenCount_ = rhs.tokenCount_; parseErrorOffset_ = rhs.parseErrorOffset_; parseErrorCode_ = rhs.parseErrorCode_; if (rhs.nameBuffer_) CopyFromRaw(rhs); // Normally parsed tokens. else { tokens_ = rhs.tokens_; // User supplied const tokens. nameBuffer_ = 0; } } return *this; } //@} //!@name Append token //@{ //! Append a token and return a new Pointer /*! \param token Token to be appended. \param allocator Allocator for the newly return Pointer. \return A new Pointer with appended token. */ GenericPointer Append(const Token& token, Allocator* allocator = 0) const { GenericPointer r; r.allocator_ = allocator; Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); r.tokens_[tokenCount_].name = p; r.tokens_[tokenCount_].length = token.length; r.tokens_[tokenCount_].index = token.index; return r; } //! Append a name token with length, and return a new Pointer /*! \param name Name to be appended. \param length Length of name. \param allocator Allocator for the newly return Pointer. \return A new Pointer with appended token. */ GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { Token token = { name, length, kPointerInvalidIndex }; return Append(token, allocator); } //! Append a name token without length, and return a new Pointer /*! \param name Name (const Ch*) to be appended. \param allocator Allocator for the newly return Pointer. \return A new Pointer with appended token. */ template RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >), (GenericPointer)) Append(T* name, Allocator* allocator = 0) const { return Append(name, internal::StrLen(name), allocator); } #if RAPIDJSON_HAS_STDSTRING //! Append a name token, and return a new Pointer /*! \param name Name to be appended. \param allocator Allocator for the newly return Pointer. \return A new Pointer with appended token. */ GenericPointer Append(const std::basic_string& name, Allocator* allocator = 0) const { return Append(name.c_str(), static_cast(name.size()), allocator); } #endif //! Append a index token, and return a new Pointer /*! \param index Index to be appended. \param allocator Allocator for the newly return Pointer. \return A new Pointer with appended token. */ GenericPointer Append(SizeType index, Allocator* allocator = 0) const { char buffer[21]; char* end = sizeof(SizeType) == 4 ? internal::u32toa(index, buffer) : internal::u64toa(index, buffer); SizeType length = static_cast(end - buffer); buffer[length] = '\0'; if (sizeof(Ch) == 1) { Token token = { reinterpret_cast(buffer), length, index }; return Append(token, allocator); } else { Ch name[21]; for (size_t i = 0; i <= length; i++) name[i] = static_cast(buffer[i]); Token token = { name, length, index }; return Append(token, allocator); } } //! Append a token by value, and return a new Pointer /*! \param token token to be appended. \param allocator Allocator for the newly return Pointer. \return A new Pointer with appended token. */ GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { if (token.IsString()) return Append(token.GetString(), token.GetStringLength(), allocator); else { RAPIDJSON_ASSERT(token.IsUint64()); RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); return Append(static_cast(token.GetUint64()), allocator); } } //!@name Handling Parse Error //@{ //! Check whether this is a valid pointer. bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } //! Get the parsing error offset in code unit. size_t GetParseErrorOffset() const { return parseErrorOffset_; } //! Get the parsing error code. PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } //@} //! Get the allocator of this pointer. Allocator& GetAllocator() { return *allocator_; } //!@name Tokens //@{ //! Get the token array (const version only). const Token* GetTokens() const { return tokens_; } //! Get the number of tokens. size_t GetTokenCount() const { return tokenCount_; } //@} //!@name Equality/inequality operators //@{ //! Equality operator. /*! \note When any pointers are invalid, always returns false. */ bool operator==(const GenericPointer& rhs) const { if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) return false; for (size_t i = 0; i < tokenCount_; i++) { if (tokens_[i].index != rhs.tokens_[i].index || tokens_[i].length != rhs.tokens_[i].length || (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) { return false; } } return true; } //! Inequality operator. /*! \note When any pointers are invalid, always returns true. */ bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } //@} //!@name Stringify //@{ //! Stringify the pointer into string representation. /*! \tparam OutputStream Type of output stream. \param os The output stream. */ template bool Stringify(OutputStream& os) const { return Stringify(os); } //! Stringify the pointer into URI fragment representation. /*! \tparam OutputStream Type of output stream. \param os The output stream. */ template bool StringifyUriFragment(OutputStream& os) const { return Stringify(os); } //@} //!@name Create value //@{ //! Create a value in a subtree. /*! If the value is not exist, it creates all parent values and a JSON Null value. So it always succeed and return the newly created or existing value. Remind that it may change types of parents according to tokens, so it potentially removes previously stored values. For example, if a document was an array, and "/foo" is used to create a value, then the document will be changed to an object, and all existing array elements are lost. \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. \param allocator Allocator for creating the values if the specified value or its parents are not exist. \param alreadyExist If non-null, it stores whether the resolved value is already exist. \return The resolved newly created (a JSON Null value), or already exists value. */ ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { RAPIDJSON_ASSERT(IsValid()); ValueType* v = &root; bool exist = true; for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { if (v->IsArray() && t->name[0] == '-' && t->length == 1) { v->PushBack(ValueType().Move(), allocator); v = &((*v)[v->Size() - 1]); exist = false; } else { if (t->index == kPointerInvalidIndex) { // must be object name if (!v->IsObject()) v->SetObject(); // Change to Object } else { // object name or array index if (!v->IsArray() && !v->IsObject()) v->SetArray(); // Change to Array } if (v->IsArray()) { if (t->index >= v->Size()) { v->Reserve(t->index + 1, allocator); while (t->index >= v->Size()) v->PushBack(ValueType().Move(), allocator); exist = false; } v = &((*v)[t->index]); } else { typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); if (m == v->MemberEnd()) { v->AddMember(ValueType(t->name, t->length, allocator).Move(), ValueType().Move(), allocator); v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end exist = false; } else v = &m->value; } } } if (alreadyExist) *alreadyExist = exist; return *v; } //! Creates a value in a document. /*! \param document A document to be resolved. \param alreadyExist If non-null, it stores whether the resolved value is already exist. \return The resolved newly created, or already exists value. */ template ValueType& Create(GenericDocument& document, bool* alreadyExist = 0) const { return Create(document, document.GetAllocator(), alreadyExist); } //@} //!@name Query value //@{ //! Query a value in a subtree. /*! \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token. \return Pointer to the value if it can be resolved. Otherwise null. \note There are only 3 situations when a value cannot be resolved: 1. A value in the path is not an array nor object. 2. An object value does not contain the token. 3. A token is out of range of an array value. Use unresolvedTokenIndex to retrieve the token index. */ ValueType* Get(ValueType& root, size_t* unresolvedTokenIndex = 0) const { RAPIDJSON_ASSERT(IsValid()); ValueType* v = &root; for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { switch (v->GetType()) { case kObjectType: { typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); if (m == v->MemberEnd()) break; v = &m->value; } continue; case kArrayType: if (t->index == kPointerInvalidIndex || t->index >= v->Size()) break; v = &((*v)[t->index]); continue; default: break; } // Error: unresolved token if (unresolvedTokenIndex) *unresolvedTokenIndex = static_cast(t - tokens_); return 0; } return v; } //! Query a const value in a const subtree. /*! \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. \return Pointer to the value if it can be resolved. Otherwise null. */ const ValueType* Get(const ValueType& root, size_t* unresolvedTokenIndex = 0) const { return Get(const_cast(root), unresolvedTokenIndex); } //@} //!@name Query a value with default //@{ //! Query a value in a subtree with default value. /*! Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. So that this function always succeed. \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. \param defaultValue Default value to be cloned if the value was not exists. \param allocator Allocator for creating the values if the specified value or its parents are not exist. \see Create() */ ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); } //! Query a value in a subtree with default null-terminated string. ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); return alreadyExist ? v : v.SetString(defaultValue, allocator); } #if RAPIDJSON_HAS_STDSTRING //! Query a value in a subtree with default std::basic_string. ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); return alreadyExist ? v : v.SetString(defaultValue, allocator); } #endif //! Query a value in a subtree with default primitive value. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); } //! Query a value in a document with default value. template ValueType& GetWithDefault(GenericDocument& document, const ValueType& defaultValue) const { return GetWithDefault(document, defaultValue, document.GetAllocator()); } //! Query a value in a document with default null-terminated string. template ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { return GetWithDefault(document, defaultValue, document.GetAllocator()); } #if RAPIDJSON_HAS_STDSTRING //! Query a value in a document with default std::basic_string. template ValueType& GetWithDefault(GenericDocument& document, const std::basic_string& defaultValue) const { return GetWithDefault(document, defaultValue, document.GetAllocator()); } #endif //! Query a value in a document with default primitive value. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) GetWithDefault(GenericDocument& document, T defaultValue) const { return GetWithDefault(document, defaultValue, document.GetAllocator()); } //@} //!@name Set a value //@{ //! Set a value in a subtree, with move semantics. /*! It creates all parents if they are not exist or types are different to the tokens. So this function always succeeds but potentially remove existing values. \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. \param value Value to be set. \param allocator Allocator for creating the values if the specified value or its parents are not exist. \see Create() */ ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = value; } //! Set a value in a subtree, with copy semantics. ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator).CopyFrom(value, allocator); } //! Set a null-terminated string in a subtree. ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = ValueType(value, allocator).Move(); } #if RAPIDJSON_HAS_STDSTRING //! Set a std::basic_string in a subtree. ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = ValueType(value, allocator).Move(); } #endif //! Set a primitive value in a subtree. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = ValueType(value).Move(); } //! Set a value in a document, with move semantics. template ValueType& Set(GenericDocument& document, ValueType& value) const { return Create(document) = value; } //! Set a value in a document, with copy semantics. template ValueType& Set(GenericDocument& document, const ValueType& value) const { return Create(document).CopyFrom(value, document.GetAllocator()); } //! Set a null-terminated string in a document. template ValueType& Set(GenericDocument& document, const Ch* value) const { return Create(document) = ValueType(value, document.GetAllocator()).Move(); } #if RAPIDJSON_HAS_STDSTRING //! Sets a std::basic_string in a document. template ValueType& Set(GenericDocument& document, const std::basic_string& value) const { return Create(document) = ValueType(value, document.GetAllocator()).Move(); } #endif //! Set a primitive value in a document. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) Set(GenericDocument& document, T value) const { return Create(document) = value; } //@} //!@name Swap a value //@{ //! Swap a value with a value in a subtree. /*! It creates all parents if they are not exist or types are different to the tokens. So this function always succeeds but potentially remove existing values. \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. \param value Value to be swapped. \param allocator Allocator for creating the values if the specified value or its parents are not exist. \see Create() */ ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator).Swap(value); } //! Swap a value with a value in a document. template ValueType& Swap(GenericDocument& document, ValueType& value) const { return Create(document).Swap(value); } //@} //! Erase a value in a subtree. /*! \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. \return Whether the resolved value is found and erased. \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. */ bool Erase(ValueType& root) const { RAPIDJSON_ASSERT(IsValid()); if (tokenCount_ == 0) // Cannot erase the root return false; ValueType* v = &root; const Token* last = tokens_ + (tokenCount_ - 1); for (const Token *t = tokens_; t != last; ++t) { switch (v->GetType()) { case kObjectType: { typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); if (m == v->MemberEnd()) return false; v = &m->value; } break; case kArrayType: if (t->index == kPointerInvalidIndex || t->index >= v->Size()) return false; v = &((*v)[t->index]); break; default: return false; } } switch (v->GetType()) { case kObjectType: return v->EraseMember(GenericStringRef(last->name, last->length)); case kArrayType: if (last->index == kPointerInvalidIndex || last->index >= v->Size()) return false; v->Erase(v->Begin() + last->index); return true; default: return false; } } private: //! Clone the content from rhs to this. /*! \param rhs Source pointer. \param extraToken Extra tokens to be allocated. \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. \return Start of non-occupied name buffer, for storing extra names. */ Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { if (!allocator_) // allocator is independently owned. ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) nameBufferSize += t->length; tokenCount_ = rhs.tokenCount_ + extraToken; tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); if (rhs.tokenCount_ > 0) { std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); } if (nameBufferSize > 0) { std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); } // Adjust pointers to name buffer std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) t->name += diff; return nameBuffer_ + nameBufferSize; } //! Check whether a character should be percent-encoded. /*! According to RFC 3986 2.3 Unreserved Characters. \param c The character (code unit) to be tested. */ bool NeedPercentEncode(Ch c) const { return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); } //! Parse a JSON String or its URI fragment representation into tokens. #ifndef __clang__ // -Wdocumentation /*! \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. \param length Length of the source string. \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. */ #endif void Parse(const Ch* source, size_t length) { RAPIDJSON_ASSERT(source != NULL); RAPIDJSON_ASSERT(nameBuffer_ == 0); RAPIDJSON_ASSERT(tokens_ == 0); // Create own allocator if user did not supply. if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); // Count number of '/' as tokenCount tokenCount_ = 0; for (const Ch* s = source; s != source + length; s++) if (*s == '/') tokenCount_++; Token* token = tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); Ch* name = nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); size_t i = 0; // Detect if it is a URI fragment bool uriFragment = false; if (source[i] == '#') { uriFragment = true; i++; } if (i != length && source[i] != '/') { parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; goto error; } while (i < length) { RAPIDJSON_ASSERT(source[i] == '/'); i++; // consumes '/' token->name = name; bool isNumber = true; while (i < length && source[i] != '/') { Ch c = source[i]; if (uriFragment) { // Decoding percent-encoding for URI fragment if (c == '%') { PercentDecodeStream is(&source[i], source + length); GenericInsituStringStream os(name); Ch* begin = os.PutBegin(); if (!Transcoder, EncodingType>().Validate(is, os) || !is.IsValid()) { parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; goto error; } size_t len = os.PutEnd(begin); i += is.Tell() - 1; if (len == 1) c = *name; else { name += len; isNumber = false; i++; continue; } } else if (NeedPercentEncode(c)) { parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; goto error; } } i++; // Escaping "~0" -> '~', "~1" -> '/' if (c == '~') { if (i < length) { c = source[i]; if (c == '0') c = '~'; else if (c == '1') c = '/'; else { parseErrorCode_ = kPointerParseErrorInvalidEscape; goto error; } i++; } else { parseErrorCode_ = kPointerParseErrorInvalidEscape; goto error; } } // First check for index: all of characters are digit if (c < '0' || c > '9') isNumber = false; *name++ = c; } token->length = static_cast(name - token->name); if (token->length == 0) isNumber = false; *name++ = '\0'; // Null terminator // Second check for index: more than one digit cannot have leading zero if (isNumber && token->length > 1 && token->name[0] == '0') isNumber = false; // String to SizeType conversion SizeType n = 0; if (isNumber) { for (size_t j = 0; j < token->length; j++) { SizeType m = n * 10 + static_cast(token->name[j] - '0'); if (m < n) { // overflow detection isNumber = false; break; } n = m; } } token->index = isNumber ? n : kPointerInvalidIndex; token++; } RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer parseErrorCode_ = kPointerParseErrorNone; return; error: Allocator::Free(tokens_); nameBuffer_ = 0; tokens_ = 0; tokenCount_ = 0; parseErrorOffset_ = i; return; } //! Stringify to string or URI fragment representation. /*! \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. \tparam OutputStream type of output stream. \param os The output stream. */ template bool Stringify(OutputStream& os) const { RAPIDJSON_ASSERT(IsValid()); if (uriFragment) os.Put('#'); for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { os.Put('/'); for (size_t j = 0; j < t->length; j++) { Ch c = t->name[j]; if (c == '~') { os.Put('~'); os.Put('0'); } else if (c == '/') { os.Put('~'); os.Put('1'); } else if (uriFragment && NeedPercentEncode(c)) { // Transcode to UTF8 sequence GenericStringStream source(&t->name[j]); PercentEncodeStream target(os); if (!Transcoder >().Validate(source, target)) return false; j += source.Tell() - 1; } else os.Put(c); } } return true; } //! A helper stream for decoding a percent-encoded sequence into code unit. /*! This stream decodes %XY triplet into code unit (0-255). If it encounters invalid characters, it sets output code unit as 0 and mark invalid, and to be checked by IsValid(). */ class PercentDecodeStream { public: typedef typename ValueType::Ch Ch; //! Constructor /*! \param source Start of the stream \param end Past-the-end of the stream. */ PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} Ch Take() { if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet valid_ = false; return 0; } src_++; Ch c = 0; for (int j = 0; j < 2; j++) { c = static_cast(c << 4); Ch h = *src_; if (h >= '0' && h <= '9') c = static_cast(c + h - '0'); else if (h >= 'A' && h <= 'F') c = static_cast(c + h - 'A' + 10); else if (h >= 'a' && h <= 'f') c = static_cast(c + h - 'a' + 10); else { valid_ = false; return 0; } src_++; } return c; } size_t Tell() const { return static_cast(src_ - head_); } bool IsValid() const { return valid_; } private: const Ch* src_; //!< Current read position. const Ch* head_; //!< Original head of the string. const Ch* end_; //!< Past-the-end position. bool valid_; //!< Whether the parsing is valid. }; //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. template class PercentEncodeStream { public: PercentEncodeStream(OutputStream& os) : os_(os) {} void Put(char c) { // UTF-8 must be byte unsigned char u = static_cast(c); static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; os_.Put('%'); os_.Put(static_cast(hexDigits[u >> 4])); os_.Put(static_cast(hexDigits[u & 15])); } private: OutputStream& os_; }; Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. Allocator* ownAllocator_; //!< Allocator owned by this Pointer. Ch* nameBuffer_; //!< A buffer containing all names in tokens. Token* tokens_; //!< A list of tokens. size_t tokenCount_; //!< Number of tokens in tokens_. size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. PointerParseErrorCode parseErrorCode_; //!< Parsing error code. }; //! GenericPointer for Value (UTF-8, default allocator). typedef GenericPointer Pointer; //!@name Helper functions for GenericPointer //@{ ////////////////////////////////////////////////////////////////////////////// template typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { return pointer.Create(root, a); } template typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Create(root, a); } // No allocator parameter template typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer& pointer) { return pointer.Create(document); } template typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { return GenericPointer(source, N - 1).Create(document); } ////////////////////////////////////////////////////////////////////////////// template typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { return pointer.Get(root, unresolvedTokenIndex); } template const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { return pointer.Get(root, unresolvedTokenIndex); } template typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N], size_t* unresolvedTokenIndex = 0) { return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); } template const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N], size_t* unresolvedTokenIndex = 0) { return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); } ////////////////////////////////////////////////////////////////////////////// template typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { return pointer.GetWithDefault(root, defaultValue, a); } template typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { return pointer.GetWithDefault(root, defaultValue, a); } #if RAPIDJSON_HAS_STDSTRING template typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue, typename T::AllocatorType& a) { return pointer.GetWithDefault(root, defaultValue, a); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { return pointer.GetWithDefault(root, defaultValue, a); } template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } #if RAPIDJSON_HAS_STDSTRING template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } // No allocator parameter template typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& defaultValue) { return pointer.GetWithDefault(document, defaultValue); } template typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* defaultValue) { return pointer.GetWithDefault(document, defaultValue); } #if RAPIDJSON_HAS_STDSTRING template typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const std::basic_string& defaultValue) { return pointer.GetWithDefault(document, defaultValue); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, T2 defaultValue) { return pointer.GetWithDefault(document, defaultValue); } template typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); } template typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); } #if RAPIDJSON_HAS_STDSTRING template typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string& defaultValue) { return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); } ////////////////////////////////////////////////////////////////////////////// template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); } template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); } template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); } #if RAPIDJSON_HAS_STDSTRING template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); } template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Set(root, value, a); } template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Set(root, value, a); } template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Set(root, value, a); } #if RAPIDJSON_HAS_STDSTRING template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Set(root, value, a); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Set(root, value, a); } // No allocator parameter template typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { return pointer.Set(document, value); } template typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& value) { return pointer.Set(document, value); } template typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* value) { return pointer.Set(document, value); } #if RAPIDJSON_HAS_STDSTRING template typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const std::basic_string& value) { return pointer.Set(document, value); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) SetValueByPointer(DocumentType& document, const GenericPointer& pointer, T2 value) { return pointer.Set(document, value); } template typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { return GenericPointer(source, N - 1).Set(document, value); } template typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { return GenericPointer(source, N - 1).Set(document, value); } template typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { return GenericPointer(source, N - 1).Set(document, value); } #if RAPIDJSON_HAS_STDSTRING template typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string& value) { return GenericPointer(source, N - 1).Set(document, value); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { return GenericPointer(source, N - 1).Set(document, value); } ////////////////////////////////////////////////////////////////////////////// template typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { return pointer.Swap(root, value, a); } template typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Swap(root, value, a); } template typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { return pointer.Swap(document, value); } template typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { return GenericPointer(source, N - 1).Swap(document, value); } ////////////////////////////////////////////////////////////////////////////// template bool EraseValueByPointer(T& root, const GenericPointer& pointer) { return pointer.Erase(root); } template bool EraseValueByPointer(T& root, const CharType(&source)[N]) { return GenericPointer(source, N - 1).Erase(root); } //@} RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #ifdef _MSC_VER RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_POINTER_H_ ================================================ FILE: third-party/rapidjson/prettywriter.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_PRETTYWRITER_H_ #define RAPIDJSON_PRETTYWRITER_H_ #include "writer.h" #ifdef __GNUC__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(effc++) #endif #if defined(__clang__) RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(c++98-compat) #endif RAPIDJSON_NAMESPACE_BEGIN //! Combination of PrettyWriter format flags. /*! \see PrettyWriter::SetFormatOptions */ enum PrettyFormatOptions { kFormatDefault = 0, //!< Default pretty formatting. kFormatSingleLineArray = 1 //!< Format arrays on a single line. }; //! Writer with indentation and spacing. /*! \tparam OutputStream Type of ouptut os. \tparam SourceEncoding Encoding of source string. \tparam TargetEncoding Encoding of output stream. \tparam StackAllocator Type of allocator for allocating memory of stack. */ template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> class PrettyWriter : public Writer { public: typedef Writer Base; typedef typename Base::Ch Ch; //! Constructor /*! \param os Output stream. \param allocator User supplied allocator. If it is null, it will create a private one. \param levelDepth Initial capacity of stack. */ explicit PrettyWriter(OutputStream& os, StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} explicit PrettyWriter(StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : Base(allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} #if RAPIDJSON_HAS_CXX11_RVALUE_REFS PrettyWriter(PrettyWriter&& rhs) : Base(std::forward(rhs)), indentChar_(rhs.indentChar_), indentCharCount_(rhs.indentCharCount_), formatOptions_(rhs.formatOptions_) {} #endif //! Set custom indentation. /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). \param indentCharCount Number of indent characters for each indentation level. \note The default indentation is 4 spaces. */ PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); indentChar_ = indentChar; indentCharCount_ = indentCharCount; return *this; } //! Set pretty writer formatting options. /*! \param options Formatting options. */ PrettyWriter& SetFormatOptions(PrettyFormatOptions options) { formatOptions_ = options; return *this; } /*! @name Implementation of Handler \see Handler */ //@{ bool Null() { PrettyPrefix(kNullType); return Base::WriteNull(); } bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::WriteBool(b); } bool Int(int i) { PrettyPrefix(kNumberType); return Base::WriteInt(i); } bool Uint(unsigned u) { PrettyPrefix(kNumberType); return Base::WriteUint(u); } bool Int64(int64_t i64) { PrettyPrefix(kNumberType); return Base::WriteInt64(i64); } bool Uint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::WriteUint64(u64); } bool Double(double d) { PrettyPrefix(kNumberType); return Base::WriteDouble(d); } bool RawNumber(const Ch* str, SizeType length, bool copy = false) { RAPIDJSON_ASSERT(str != 0); (void)copy; PrettyPrefix(kNumberType); return Base::WriteString(str, length); } bool String(const Ch* str, SizeType length, bool copy = false) { RAPIDJSON_ASSERT(str != 0); (void)copy; PrettyPrefix(kStringType); return Base::WriteString(str, length); } #if RAPIDJSON_HAS_STDSTRING bool String(const std::basic_string& str) { return String(str.data(), SizeType(str.size())); } #endif bool StartObject() { PrettyPrefix(kObjectType); new (Base::level_stack_.template Push()) typename Base::Level(false); return Base::WriteStartObject(); } bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } #if RAPIDJSON_HAS_STDSTRING bool Key(const std::basic_string& str) { return Key(str.data(), SizeType(str.size())); } #endif bool EndObject(SizeType memberCount = 0) { (void)memberCount; RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); // not inside an Object RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); // currently inside an Array, not Object RAPIDJSON_ASSERT(0 == Base::level_stack_.template Top()->valueCount % 2); // Object has a Key without a Value bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; if (!empty) { Base::os_->Put('\n'); WriteIndent(); } bool ret = Base::WriteEndObject(); (void)ret; RAPIDJSON_ASSERT(ret == true); if (Base::level_stack_.Empty()) // end of json text Base::Flush(); return true; } bool StartArray() { PrettyPrefix(kArrayType); new (Base::level_stack_.template Push()) typename Base::Level(true); return Base::WriteStartArray(); } bool EndArray(SizeType memberCount = 0) { (void)memberCount; RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; if (!empty && !(formatOptions_ & kFormatSingleLineArray)) { Base::os_->Put('\n'); WriteIndent(); } bool ret = Base::WriteEndArray(); (void)ret; RAPIDJSON_ASSERT(ret == true); if (Base::level_stack_.Empty()) // end of json text Base::Flush(); return true; } //@} /*! @name Convenience extensions */ //@{ //! Simpler but slower overload. bool String(const Ch* str) { return String(str, internal::StrLen(str)); } bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } //@} //! Write a raw JSON value. /*! For user to write a stringified JSON as a value. \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. \param length Length of the json. \param type Type of the root of json. \note When using PrettyWriter::RawValue(), the result json may not be indented correctly. */ bool RawValue(const Ch* json, size_t length, Type type) { RAPIDJSON_ASSERT(json != 0); PrettyPrefix(type); return Base::WriteRawValue(json, length); } protected: void PrettyPrefix(Type type) { (void)type; if (Base::level_stack_.GetSize() != 0) { // this value is not at root typename Base::Level* level = Base::level_stack_.template Top(); if (level->inArray) { if (level->valueCount > 0) { Base::os_->Put(','); // add comma if it is not the first element in array if (formatOptions_ & kFormatSingleLineArray) Base::os_->Put(' '); } if (!(formatOptions_ & kFormatSingleLineArray)) { Base::os_->Put('\n'); WriteIndent(); } } else { // in object if (level->valueCount > 0) { if (level->valueCount % 2 == 0) { Base::os_->Put(','); Base::os_->Put('\n'); } else { Base::os_->Put(':'); Base::os_->Put(' '); } } else Base::os_->Put('\n'); if (level->valueCount % 2 == 0) WriteIndent(); } if (!level->inArray && level->valueCount % 2 == 0) RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name level->valueCount++; } else { RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. Base::hasRoot_ = true; } } void WriteIndent() { size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; PutN(*Base::os_, static_cast(indentChar_), count); } Ch indentChar_; unsigned indentCharCount_; PrettyFormatOptions formatOptions_; private: // Prohibit copy constructor & assignment operator. PrettyWriter(const PrettyWriter&); PrettyWriter& operator=(const PrettyWriter&); }; RAPIDJSON_NAMESPACE_END #if defined(__clang__) RAPIDJSON_DIAG_POP #endif #ifdef __GNUC__ RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_RAPIDJSON_H_ ================================================ FILE: third-party/rapidjson/rapidjson.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_RAPIDJSON_H_ #define RAPIDJSON_RAPIDJSON_H_ /*!\file rapidjson.h \brief common definitions and configuration \see RAPIDJSON_CONFIG */ /*! \defgroup RAPIDJSON_CONFIG RapidJSON configuration \brief Configuration macros for library features Some RapidJSON features are configurable to adapt the library to a wide variety of platforms, environments and usage scenarios. Most of the features can be configured in terms of overriden or predefined preprocessor macros at compile-time. Some additional customization is available in the \ref RAPIDJSON_ERRORS APIs. \note These macros should be given on the compiler command-line (where applicable) to avoid inconsistent values when compiling different translation units of a single application. */ #include // malloc(), realloc(), free(), size_t #include // memset(), memcpy(), memmove(), memcmp() /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_VERSION_STRING // // ALWAYS synchronize the following 3 macros with corresponding variables in /CMakeLists.txt. // //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN // token stringification #define RAPIDJSON_STRINGIFY(x) RAPIDJSON_DO_STRINGIFY(x) #define RAPIDJSON_DO_STRINGIFY(x) #x // token concatenation #define RAPIDJSON_JOIN(X, Y) RAPIDJSON_DO_JOIN(X, Y) #define RAPIDJSON_DO_JOIN(X, Y) RAPIDJSON_DO_JOIN2(X, Y) #define RAPIDJSON_DO_JOIN2(X, Y) X##Y //!@endcond /*! \def RAPIDJSON_MAJOR_VERSION \ingroup RAPIDJSON_CONFIG \brief Major version of RapidJSON in integer. */ /*! \def RAPIDJSON_MINOR_VERSION \ingroup RAPIDJSON_CONFIG \brief Minor version of RapidJSON in integer. */ /*! \def RAPIDJSON_PATCH_VERSION \ingroup RAPIDJSON_CONFIG \brief Patch version of RapidJSON in integer. */ /*! \def RAPIDJSON_VERSION_STRING \ingroup RAPIDJSON_CONFIG \brief Version of RapidJSON in ".." string format. */ #define RAPIDJSON_MAJOR_VERSION 1 #define RAPIDJSON_MINOR_VERSION 1 #define RAPIDJSON_PATCH_VERSION 0 #define RAPIDJSON_VERSION_STRING \ RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_NAMESPACE_(BEGIN|END) /*! \def RAPIDJSON_NAMESPACE \ingroup RAPIDJSON_CONFIG \brief provide custom rapidjson namespace In order to avoid symbol clashes and/or "One Definition Rule" errors between multiple inclusions of (different versions of) RapidJSON in a single binary, users can customize the name of the main RapidJSON namespace. In case of a single nesting level, defining \c RAPIDJSON_NAMESPACE to a custom name (e.g. \c MyRapidJSON) is sufficient. If multiple levels are needed, both \ref RAPIDJSON_NAMESPACE_BEGIN and \ref RAPIDJSON_NAMESPACE_END need to be defined as well: \code // in some .cpp file #define RAPIDJSON_NAMESPACE my::rapidjson #define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapidjson { #define RAPIDJSON_NAMESPACE_END } } #include "rapidjson/..." \endcode \see rapidjson */ /*! \def RAPIDJSON_NAMESPACE_BEGIN \ingroup RAPIDJSON_CONFIG \brief provide custom rapidjson namespace (opening expression) \see RAPIDJSON_NAMESPACE */ /*! \def RAPIDJSON_NAMESPACE_END \ingroup RAPIDJSON_CONFIG \brief provide custom rapidjson namespace (closing expression) \see RAPIDJSON_NAMESPACE */ #ifndef RAPIDJSON_NAMESPACE #define RAPIDJSON_NAMESPACE rapidjson #endif #ifndef RAPIDJSON_NAMESPACE_BEGIN #define RAPIDJSON_NAMESPACE_BEGIN namespace RAPIDJSON_NAMESPACE { #endif #ifndef RAPIDJSON_NAMESPACE_END #define RAPIDJSON_NAMESPACE_END } #endif /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_HAS_STDSTRING #ifndef RAPIDJSON_HAS_STDSTRING #ifdef RAPIDJSON_DOXYGEN_RUNNING #define RAPIDJSON_HAS_STDSTRING 1 // force generation of documentation #else #define RAPIDJSON_HAS_STDSTRING 0 // no std::string support by default #endif /*! \def RAPIDJSON_HAS_STDSTRING \ingroup RAPIDJSON_CONFIG \brief Enable RapidJSON support for \c std::string By defining this preprocessor symbol to \c 1, several convenience functions for using \ref rapidjson::GenericValue with \c std::string are enabled, especially for construction and comparison. \hideinitializer */ #endif // !defined(RAPIDJSON_HAS_STDSTRING) #if RAPIDJSON_HAS_STDSTRING #include #endif // RAPIDJSON_HAS_STDSTRING /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_NO_INT64DEFINE /*! \def RAPIDJSON_NO_INT64DEFINE \ingroup RAPIDJSON_CONFIG \brief Use external 64-bit integer types. RapidJSON requires the 64-bit integer types \c int64_t and \c uint64_t types to be available at global scope. If users have their own definition, define RAPIDJSON_NO_INT64DEFINE to prevent RapidJSON from defining its own types. */ #ifndef RAPIDJSON_NO_INT64DEFINE //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN #if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 #include "msinttypes/stdint.h" #include "msinttypes/inttypes.h" #else // Other compilers should have this. #include #include #endif //!@endcond #ifdef RAPIDJSON_DOXYGEN_RUNNING #define RAPIDJSON_NO_INT64DEFINE #endif #endif // RAPIDJSON_NO_INT64TYPEDEF /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_FORCEINLINE #ifndef RAPIDJSON_FORCEINLINE //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN #if defined(_MSC_VER) && defined(NDEBUG) #define RAPIDJSON_FORCEINLINE __forceinline #elif defined(__GNUC__) && __GNUC__ >= 4 && defined(NDEBUG) #define RAPIDJSON_FORCEINLINE __attribute__((always_inline)) #else #define RAPIDJSON_FORCEINLINE #endif //!@endcond #endif // RAPIDJSON_FORCEINLINE /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_ENDIAN #define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine #define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine //! Endianness of the machine. /*! \def RAPIDJSON_ENDIAN \ingroup RAPIDJSON_CONFIG GCC 4.6 provided macro for detecting endianness of the target machine. But other compilers may not have this. User can define RAPIDJSON_ENDIAN to either \ref RAPIDJSON_LITTLEENDIAN or \ref RAPIDJSON_BIGENDIAN. Default detection implemented with reference to \li https://gcc.gnu.org/onlinedocs/gcc-4.6.0/cpp/Common-Predefined-Macros.html \li http://www.boost.org/doc/libs/1_42_0/boost/detail/endian.hpp */ #ifndef RAPIDJSON_ENDIAN // Detect with GCC 4.6's macro # ifdef __BYTE_ORDER__ # if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ # define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN # else # error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. # endif // __BYTE_ORDER__ // Detect with GLIBC's endian.h # elif defined(__GLIBC__) # include # if (__BYTE_ORDER == __LITTLE_ENDIAN) # define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN # elif (__BYTE_ORDER == __BIG_ENDIAN) # define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN # else # error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. # endif // __GLIBC__ // Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro # elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) # define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN # elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) # define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN // Detect with architecture macros # elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) # define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN # elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__) # define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN # elif defined(_MSC_VER) && defined(_M_ARM) # define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN # elif defined(RAPIDJSON_DOXYGEN_RUNNING) # define RAPIDJSON_ENDIAN # else # error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. # endif #endif // RAPIDJSON_ENDIAN /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_64BIT //! Whether using 64-bit architecture #ifndef RAPIDJSON_64BIT #if defined(__LP64__) || (defined(__x86_64__) && defined(__ILP32__)) || defined(_WIN64) || defined(__EMSCRIPTEN__) #define RAPIDJSON_64BIT 1 #else #define RAPIDJSON_64BIT 0 #endif #endif // RAPIDJSON_64BIT /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_ALIGN //! Data alignment of the machine. /*! \ingroup RAPIDJSON_CONFIG \param x pointer to align Some machines require strict data alignment. Currently the default uses 4 bytes alignment on 32-bit platforms and 8 bytes alignment for 64-bit platforms. User can customize by defining the RAPIDJSON_ALIGN function macro. */ #ifndef RAPIDJSON_ALIGN #if RAPIDJSON_64BIT == 1 #define RAPIDJSON_ALIGN(x) (((x) + static_cast(7u)) & ~static_cast(7u)) #else #define RAPIDJSON_ALIGN(x) (((x) + 3u) & ~3u) #endif #endif /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_UINT64_C2 //! Construct a 64-bit literal by a pair of 32-bit integer. /*! 64-bit literal with or without ULL suffix is prone to compiler warnings. UINT64_C() is C macro which cause compilation problems. Use this macro to define 64-bit constants by a pair of 32-bit integer. */ #ifndef RAPIDJSON_UINT64_C2 #define RAPIDJSON_UINT64_C2(high32, low32) ((static_cast(high32) << 32) | static_cast(low32)) #endif /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_48BITPOINTER_OPTIMIZATION //! Use only lower 48-bit address for some pointers. /*! \ingroup RAPIDJSON_CONFIG This optimization uses the fact that current X86-64 architecture only implement lower 48-bit virtual address. The higher 16-bit can be used for storing other data. \c GenericValue uses this optimization to reduce its size form 24 bytes to 16 bytes in 64-bit architecture. */ #ifndef RAPIDJSON_48BITPOINTER_OPTIMIZATION #if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) #define RAPIDJSON_48BITPOINTER_OPTIMIZATION 1 #else #define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 #endif #endif // RAPIDJSON_48BITPOINTER_OPTIMIZATION #if RAPIDJSON_48BITPOINTER_OPTIMIZATION == 1 #if RAPIDJSON_64BIT != 1 #error RAPIDJSON_48BITPOINTER_OPTIMIZATION can only be set to 1 when RAPIDJSON_64BIT=1 #endif #define RAPIDJSON_SETPOINTER(type, p, x) (p = reinterpret_cast((reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0xFFFF0000, 0x00000000))) | reinterpret_cast(reinterpret_cast(x)))) #define RAPIDJSON_GETPOINTER(type, p) (reinterpret_cast(reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0x0000FFFF, 0xFFFFFFFF)))) #else #define RAPIDJSON_SETPOINTER(type, p, x) (p = (x)) #define RAPIDJSON_GETPOINTER(type, p) (p) #endif /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_NEON/RAPIDJSON_SIMD /*! \def RAPIDJSON_SIMD \ingroup RAPIDJSON_CONFIG \brief Enable SSE2/SSE4.2/Neon optimization. RapidJSON supports optimized implementations for some parsing operations based on the SSE2, SSE4.2 or NEon SIMD extensions on modern Intel or ARM compatible processors. To enable these optimizations, three different symbols can be defined; \code // Enable SSE2 optimization. #define RAPIDJSON_SSE2 // Enable SSE4.2 optimization. #define RAPIDJSON_SSE42 \endcode // Enable ARM Neon optimization. #define RAPIDJSON_NEON \endcode \c RAPIDJSON_SSE42 takes precedence over SSE2, if both are defined. If any of these symbols is defined, RapidJSON defines the macro \c RAPIDJSON_SIMD to indicate the availability of the optimized code. */ #if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) \ || defined(RAPIDJSON_NEON) || defined(RAPIDJSON_DOXYGEN_RUNNING) #define RAPIDJSON_SIMD #endif /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_NO_SIZETYPEDEFINE #ifndef RAPIDJSON_NO_SIZETYPEDEFINE /*! \def RAPIDJSON_NO_SIZETYPEDEFINE \ingroup RAPIDJSON_CONFIG \brief User-provided \c SizeType definition. In order to avoid using 32-bit size types for indexing strings and arrays, define this preprocessor symbol and provide the type rapidjson::SizeType before including RapidJSON: \code #define RAPIDJSON_NO_SIZETYPEDEFINE namespace rapidjson { typedef ::std::size_t SizeType; } #include "rapidjson/..." \endcode \see rapidjson::SizeType */ #ifdef RAPIDJSON_DOXYGEN_RUNNING #define RAPIDJSON_NO_SIZETYPEDEFINE #endif RAPIDJSON_NAMESPACE_BEGIN //! Size type (for string lengths, array sizes, etc.) /*! RapidJSON uses 32-bit array/string indices even on 64-bit platforms, instead of using \c size_t. Users may override the SizeType by defining \ref RAPIDJSON_NO_SIZETYPEDEFINE. */ typedef unsigned SizeType; RAPIDJSON_NAMESPACE_END #endif // always import std::size_t to rapidjson namespace RAPIDJSON_NAMESPACE_BEGIN using std::size_t; RAPIDJSON_NAMESPACE_END /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_ASSERT //! Assertion. /*! \ingroup RAPIDJSON_CONFIG By default, rapidjson uses C \c assert() for internal assertions. User can override it by defining RAPIDJSON_ASSERT(x) macro. \note Parsing errors are handled and can be customized by the \ref RAPIDJSON_ERRORS APIs. */ #ifndef RAPIDJSON_ASSERT #include #define RAPIDJSON_ASSERT(x) assert(x) #endif // RAPIDJSON_ASSERT /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_STATIC_ASSERT // Prefer C++11 static_assert, if available #ifndef RAPIDJSON_STATIC_ASSERT #if __cplusplus >= 201103L || ( defined(_MSC_VER) && _MSC_VER >= 1800 ) #define RAPIDJSON_STATIC_ASSERT(x) \ static_assert(x, RAPIDJSON_STRINGIFY(x)) #endif // C++11 #endif // RAPIDJSON_STATIC_ASSERT // Adopt C++03 implementation from boost #ifndef RAPIDJSON_STATIC_ASSERT #ifndef __clang__ //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN #endif RAPIDJSON_NAMESPACE_BEGIN template struct STATIC_ASSERTION_FAILURE; template <> struct STATIC_ASSERTION_FAILURE { enum { value = 1 }; }; template struct StaticAssertTest {}; RAPIDJSON_NAMESPACE_END #if defined(__GNUC__) #define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) #else #define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE #endif #ifndef __clang__ //!@endcond #endif /*! \def RAPIDJSON_STATIC_ASSERT \brief (Internal) macro to check for conditions at compile-time \param x compile-time condition \hideinitializer */ #define RAPIDJSON_STATIC_ASSERT(x) \ typedef ::RAPIDJSON_NAMESPACE::StaticAssertTest< \ sizeof(::RAPIDJSON_NAMESPACE::STATIC_ASSERTION_FAILURE)> \ RAPIDJSON_JOIN(StaticAssertTypedef, __LINE__) RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE #endif // RAPIDJSON_STATIC_ASSERT /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_LIKELY, RAPIDJSON_UNLIKELY //! Compiler branching hint for expression with high probability to be true. /*! \ingroup RAPIDJSON_CONFIG \param x Boolean expression likely to be true. */ #ifndef RAPIDJSON_LIKELY #if defined(__GNUC__) || defined(__clang__) #define RAPIDJSON_LIKELY(x) __builtin_expect(!!(x), 1) #else #define RAPIDJSON_LIKELY(x) (x) #endif #endif //! Compiler branching hint for expression with low probability to be true. /*! \ingroup RAPIDJSON_CONFIG \param x Boolean expression unlikely to be true. */ #ifndef RAPIDJSON_UNLIKELY #if defined(__GNUC__) || defined(__clang__) #define RAPIDJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) #else #define RAPIDJSON_UNLIKELY(x) (x) #endif #endif /////////////////////////////////////////////////////////////////////////////// // Helpers //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN #define RAPIDJSON_MULTILINEMACRO_BEGIN do { #define RAPIDJSON_MULTILINEMACRO_END \ } while((void)0, 0) // adopted from Boost #define RAPIDJSON_VERSION_CODE(x,y,z) \ (((x)*100000) + ((y)*100) + (z)) /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_DIAG_PUSH/POP, RAPIDJSON_DIAG_OFF #if defined(__GNUC__) #define RAPIDJSON_GNUC \ RAPIDJSON_VERSION_CODE(__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__) #endif #if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,2,0)) #define RAPIDJSON_PRAGMA(x) _Pragma(RAPIDJSON_STRINGIFY(x)) #define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(GCC diagnostic x) #define RAPIDJSON_DIAG_OFF(x) \ RAPIDJSON_DIAG_PRAGMA(ignored RAPIDJSON_STRINGIFY(RAPIDJSON_JOIN(-W,x))) // push/pop support in Clang and GCC>=4.6 #if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) #define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) #define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) #else // GCC >= 4.2, < 4.6 #define RAPIDJSON_DIAG_PUSH /* ignored */ #define RAPIDJSON_DIAG_POP /* ignored */ #endif #elif defined(_MSC_VER) // pragma (MSVC specific) #define RAPIDJSON_PRAGMA(x) __pragma(x) #define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(warning(x)) #define RAPIDJSON_DIAG_OFF(x) RAPIDJSON_DIAG_PRAGMA(disable: x) #define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) #define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) #else #define RAPIDJSON_DIAG_OFF(x) /* ignored */ #define RAPIDJSON_DIAG_PUSH /* ignored */ #define RAPIDJSON_DIAG_POP /* ignored */ #endif // RAPIDJSON_DIAG_* /////////////////////////////////////////////////////////////////////////////// // C++11 features #ifndef RAPIDJSON_HAS_CXX11_RVALUE_REFS #if defined(__clang__) #if __has_feature(cxx_rvalue_references) && \ (defined(_LIBCPP_VERSION) || defined(__GLIBCXX__) && __GLIBCXX__ >= 20080306) #define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 #else #define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 #endif #elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ (defined(_MSC_VER) && _MSC_VER >= 1600) #define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 #else #define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 #endif #endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS #ifndef RAPIDJSON_HAS_CXX11_NOEXCEPT #if defined(__clang__) #define RAPIDJSON_HAS_CXX11_NOEXCEPT __has_feature(cxx_noexcept) #elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) // (defined(_MSC_VER) && _MSC_VER >= ????) // not yet supported #define RAPIDJSON_HAS_CXX11_NOEXCEPT 1 #else #define RAPIDJSON_HAS_CXX11_NOEXCEPT 0 #endif #endif #if RAPIDJSON_HAS_CXX11_NOEXCEPT #define RAPIDJSON_NOEXCEPT noexcept #else #define RAPIDJSON_NOEXCEPT /* noexcept */ #endif // RAPIDJSON_HAS_CXX11_NOEXCEPT // no automatic detection, yet #ifndef RAPIDJSON_HAS_CXX11_TYPETRAITS #define RAPIDJSON_HAS_CXX11_TYPETRAITS 0 #endif #ifndef RAPIDJSON_HAS_CXX11_RANGE_FOR #if defined(__clang__) #define RAPIDJSON_HAS_CXX11_RANGE_FOR __has_feature(cxx_range_for) #elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ (defined(_MSC_VER) && _MSC_VER >= 1700) #define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 #else #define RAPIDJSON_HAS_CXX11_RANGE_FOR 0 #endif #endif // RAPIDJSON_HAS_CXX11_RANGE_FOR //!@endcond /////////////////////////////////////////////////////////////////////////////// // new/delete #ifndef RAPIDJSON_NEW ///! customization point for global \c new #define RAPIDJSON_NEW(TypeName) new TypeName #endif #ifndef RAPIDJSON_DELETE ///! customization point for global \c delete #define RAPIDJSON_DELETE(x) delete x #endif /////////////////////////////////////////////////////////////////////////////// // Type /*! \namespace rapidjson \brief main RapidJSON namespace \see RAPIDJSON_NAMESPACE */ RAPIDJSON_NAMESPACE_BEGIN //! Type of JSON value enum Type { kNullType = 0, //!< null kFalseType = 1, //!< false kTrueType = 2, //!< true kObjectType = 3, //!< object kArrayType = 4, //!< array kStringType = 5, //!< string kNumberType = 6 //!< number }; RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_RAPIDJSON_H_ ================================================ FILE: third-party/rapidjson/reader.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_READER_H_ #define RAPIDJSON_READER_H_ /*! \file reader.h */ #include "allocators.h" #include "stream.h" #include "encodedstream.h" #include "internal/meta.h" #include "internal/stack.h" #include "internal/strtod.h" #include #if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) #include #pragma intrinsic(_BitScanForward) #endif #ifdef RAPIDJSON_SSE42 #include #elif defined(RAPIDJSON_SSE2) #include #elif defined(RAPIDJSON_NEON) #include #endif #ifdef _MSC_VER RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant RAPIDJSON_DIAG_OFF(4702) // unreachable code #endif #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(old-style-cast) RAPIDJSON_DIAG_OFF(padded) RAPIDJSON_DIAG_OFF(switch-enum) #endif #ifdef __GNUC__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(effc++) #endif //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN #define RAPIDJSON_NOTHING /* deliberately empty */ #ifndef RAPIDJSON_PARSE_ERROR_EARLY_RETURN #define RAPIDJSON_PARSE_ERROR_EARLY_RETURN(value) \ RAPIDJSON_MULTILINEMACRO_BEGIN \ if (RAPIDJSON_UNLIKELY(HasParseError())) { return value; } \ RAPIDJSON_MULTILINEMACRO_END #endif #define RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID \ RAPIDJSON_PARSE_ERROR_EARLY_RETURN(RAPIDJSON_NOTHING) //!@endcond /*! \def RAPIDJSON_PARSE_ERROR_NORETURN \ingroup RAPIDJSON_ERRORS \brief Macro to indicate a parse error. \param parseErrorCode \ref rapidjson::ParseErrorCode of the error \param offset position of the error in JSON input (\c size_t) This macros can be used as a customization point for the internal error handling mechanism of RapidJSON. A common usage model is to throw an exception instead of requiring the caller to explicitly check the \ref rapidjson::GenericReader::Parse's return value: \code #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode,offset) \ throw ParseException(parseErrorCode, #parseErrorCode, offset) #include // std::runtime_error #include "rapidjson/error/error.h" // rapidjson::ParseResult struct ParseException : std::runtime_error, rapidjson::ParseResult { ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) : std::runtime_error(msg), ParseResult(code, offset) {} }; #include "rapidjson/reader.h" \endcode \see RAPIDJSON_PARSE_ERROR, rapidjson::GenericReader::Parse */ #ifndef RAPIDJSON_PARSE_ERROR_NORETURN #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ RAPIDJSON_MULTILINEMACRO_BEGIN \ RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ SetParseError(parseErrorCode, offset); \ RAPIDJSON_MULTILINEMACRO_END #endif /*! \def RAPIDJSON_PARSE_ERROR \ingroup RAPIDJSON_ERRORS \brief (Internal) macro to indicate and handle a parse error. \param parseErrorCode \ref rapidjson::ParseErrorCode of the error \param offset position of the error in JSON input (\c size_t) Invokes RAPIDJSON_PARSE_ERROR_NORETURN and stops the parsing. \see RAPIDJSON_PARSE_ERROR_NORETURN \hideinitializer */ #ifndef RAPIDJSON_PARSE_ERROR #define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ RAPIDJSON_MULTILINEMACRO_BEGIN \ RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; \ RAPIDJSON_MULTILINEMACRO_END #endif #include "error/error.h" // ParseErrorCode, ParseResult RAPIDJSON_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// // ParseFlag /*! \def RAPIDJSON_PARSE_DEFAULT_FLAGS \ingroup RAPIDJSON_CONFIG \brief User-defined kParseDefaultFlags definition. User can define this as any \c ParseFlag combinations. */ #ifndef RAPIDJSON_PARSE_DEFAULT_FLAGS #define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseNoFlags #endif //! Combination of parseFlags /*! \see Reader::Parse, Document::Parse, Document::ParseInsitu, Document::ParseStream */ enum ParseFlag { kParseNoFlags = 0, //!< No flags are set. kParseInsituFlag = 1, //!< In-situ(destructive) parsing. kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings. kParseIterativeFlag = 4, //!< Iterative(constant complexity in terms of function call stack size) parsing. kParseStopWhenDoneFlag = 8, //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error. kParseFullPrecisionFlag = 16, //!< Parse number in full precision (but slower). kParseCommentsFlag = 32, //!< Allow one-line (//) and multi-line (/**/) comments. kParseNumbersAsStringsFlag = 64, //!< Parse all numbers (ints/doubles) as strings. kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays. kParseNanAndInfFlag = 256, //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles. kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS }; /////////////////////////////////////////////////////////////////////////////// // Handler /*! \class rapidjson::Handler \brief Concept for receiving events from GenericReader upon parsing. The functions return true if no error occurs. If they return false, the event publisher should terminate the process. \code concept Handler { typename Ch; bool Null(); bool Bool(bool b); bool Int(int i); bool Uint(unsigned i); bool Int64(int64_t i); bool Uint64(uint64_t i); bool Double(double d); /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) bool RawNumber(const Ch* str, SizeType length, bool copy); bool String(const Ch* str, SizeType length, bool copy); bool StartObject(); bool Key(const Ch* str, SizeType length, bool copy); bool EndObject(SizeType memberCount); bool StartArray(); bool EndArray(SizeType elementCount); }; \endcode */ /////////////////////////////////////////////////////////////////////////////// // BaseReaderHandler //! Default implementation of Handler. /*! This can be used as base class of any reader handler. \note implements Handler concept */ template, typename Derived = void> struct BaseReaderHandler { typedef typename Encoding::Ch Ch; typedef typename internal::SelectIf, BaseReaderHandler, Derived>::Type Override; bool Default() { return true; } bool Null() { return static_cast(*this).Default(); } bool Bool(bool) { return static_cast(*this).Default(); } bool Int(int) { return static_cast(*this).Default(); } bool Uint(unsigned) { return static_cast(*this).Default(); } bool Int64(int64_t) { return static_cast(*this).Default(); } bool Uint64(uint64_t) { return static_cast(*this).Default(); } bool Double(double) { return static_cast(*this).Default(); } /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) bool RawNumber(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } bool String(const Ch*, SizeType, bool) { return static_cast(*this).Default(); } bool StartObject() { return static_cast(*this).Default(); } bool Key(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } bool EndObject(SizeType) { return static_cast(*this).Default(); } bool StartArray() { return static_cast(*this).Default(); } bool EndArray(SizeType) { return static_cast(*this).Default(); } }; /////////////////////////////////////////////////////////////////////////////// // StreamLocalCopy namespace internal { template::copyOptimization> class StreamLocalCopy; //! Do copy optimization. template class StreamLocalCopy { public: StreamLocalCopy(Stream& original) : s(original), original_(original) {} ~StreamLocalCopy() { original_ = s; } Stream s; private: StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; Stream& original_; }; //! Keep reference. template class StreamLocalCopy { public: StreamLocalCopy(Stream& original) : s(original) {} Stream& s; private: StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; }; } // namespace internal /////////////////////////////////////////////////////////////////////////////// // SkipWhitespace //! Skip the JSON white spaces in a stream. /*! \param is A input stream for skipping white spaces. \note This function has SSE2/SSE4.2 specialization. */ template void SkipWhitespace(InputStream& is) { internal::StreamLocalCopy copy(is); InputStream& s(copy.s); typename InputStream::Ch c; while ((c = s.Peek()) == ' ' || c == '\n' || c == '\r' || c == '\t') s.Take(); } inline const char* SkipWhitespace(const char* p, const char* end) { while (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) ++p; return p; } #ifdef RAPIDJSON_SSE42 //! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. inline const char *SkipWhitespace_SIMD(const char* p) { // Fast return for single non-whitespace if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') ++p; else return p; // 16-byte align to the next boundary const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); while (p != nextAligned) if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') ++p; else return p; // The rest of string using SIMD static const char whitespace[16] = " \n\r\t"; const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); for (;; p += 16) { const __m128i s = _mm_load_si128(reinterpret_cast(p)); const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); if (r != 16) // some of characters is non-whitespace return p + r; } } inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { // Fast return for single non-whitespace if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) ++p; else return p; // The middle of string using SIMD static const char whitespace[16] = " \n\r\t"; const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); for (; p <= end - 16; p += 16) { const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); if (r != 16) // some of characters is non-whitespace return p + r; } return SkipWhitespace(p, end); } #elif defined(RAPIDJSON_SSE2) //! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. inline const char *SkipWhitespace_SIMD(const char* p) { // Fast return for single non-whitespace if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') ++p; else return p; // 16-byte align to the next boundary const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); while (p != nextAligned) if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') ++p; else return p; // The rest of string #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; #undef C16 const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); for (;; p += 16) { const __m128i s = _mm_load_si128(reinterpret_cast(p)); __m128i x = _mm_cmpeq_epi8(s, w0); x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); unsigned short r = static_cast(~_mm_movemask_epi8(x)); if (r != 0) { // some of characters may be non-whitespace #ifdef _MSC_VER // Find the index of first non-whitespace unsigned long offset; _BitScanForward(&offset, r); return p + offset; #else return p + __builtin_ffs(r) - 1; #endif } } } inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { // Fast return for single non-whitespace if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) ++p; else return p; // The rest of string #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; #undef C16 const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); for (; p <= end - 16; p += 16) { const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); __m128i x = _mm_cmpeq_epi8(s, w0); x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); unsigned short r = static_cast(~_mm_movemask_epi8(x)); if (r != 0) { // some of characters may be non-whitespace #ifdef _MSC_VER // Find the index of first non-whitespace unsigned long offset; _BitScanForward(&offset, r); return p + offset; #else return p + __builtin_ffs(r) - 1; #endif } } return SkipWhitespace(p, end); } #elif defined(RAPIDJSON_NEON) //! Skip whitespace with ARM Neon instructions, testing 16 8-byte characters at once. inline const char *SkipWhitespace_SIMD(const char* p) { // Fast return for single non-whitespace if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') ++p; else return p; // 16-byte align to the next boundary const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); while (p != nextAligned) if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') ++p; else return p; const uint8x16_t w0 = vmovq_n_u8(' '); const uint8x16_t w1 = vmovq_n_u8('\n'); const uint8x16_t w2 = vmovq_n_u8('\r'); const uint8x16_t w3 = vmovq_n_u8('\t'); for (;; p += 16) { const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); uint8x16_t x = vceqq_u8(s, w0); x = vorrq_u8(x, vceqq_u8(s, w1)); x = vorrq_u8(x, vceqq_u8(s, w2)); x = vorrq_u8(x, vceqq_u8(s, w3)); x = vmvnq_u8(x); // Negate x = vrev64q_u8(x); // Rev in 64 uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract if (low == 0) { if (high != 0) { int lz =__builtin_clzll(high);; return p + 8 + (lz >> 3); } } else { int lz = __builtin_clzll(low);; return p + (lz >> 3); } } } inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { // Fast return for single non-whitespace if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) ++p; else return p; const uint8x16_t w0 = vmovq_n_u8(' '); const uint8x16_t w1 = vmovq_n_u8('\n'); const uint8x16_t w2 = vmovq_n_u8('\r'); const uint8x16_t w3 = vmovq_n_u8('\t'); for (; p <= end - 16; p += 16) { const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); uint8x16_t x = vceqq_u8(s, w0); x = vorrq_u8(x, vceqq_u8(s, w1)); x = vorrq_u8(x, vceqq_u8(s, w2)); x = vorrq_u8(x, vceqq_u8(s, w3)); x = vmvnq_u8(x); // Negate x = vrev64q_u8(x); // Rev in 64 uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract if (low == 0) { if (high != 0) { int lz = __builtin_clzll(high); return p + 8 + (lz >> 3); } } else { int lz = __builtin_clzll(low); return p + (lz >> 3); } } return SkipWhitespace(p, end); } #endif // RAPIDJSON_NEON #ifdef RAPIDJSON_SIMD //! Template function specialization for InsituStringStream template<> inline void SkipWhitespace(InsituStringStream& is) { is.src_ = const_cast(SkipWhitespace_SIMD(is.src_)); } //! Template function specialization for StringStream template<> inline void SkipWhitespace(StringStream& is) { is.src_ = SkipWhitespace_SIMD(is.src_); } template<> inline void SkipWhitespace(EncodedInputStream, MemoryStream>& is) { is.is_.src_ = SkipWhitespace_SIMD(is.is_.src_, is.is_.end_); } #endif // RAPIDJSON_SIMD /////////////////////////////////////////////////////////////////////////////// // GenericReader //! SAX-style JSON parser. Use \ref Reader for UTF8 encoding and default allocator. /*! GenericReader parses JSON text from a stream, and send events synchronously to an object implementing Handler concept. It needs to allocate a stack for storing a single decoded string during non-destructive parsing. For in-situ parsing, the decoded string is directly written to the source text string, no temporary buffer is required. A GenericReader object can be reused for parsing multiple JSON text. \tparam SourceEncoding Encoding of the input stream. \tparam TargetEncoding Encoding of the parse output. \tparam StackAllocator Allocator type for stack. */ template class GenericReader { public: typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type //! Constructor. /*! \param stackAllocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) */ GenericReader(StackAllocator* stackAllocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(stackAllocator, stackCapacity), parseResult_() {} //! Parse JSON text. /*! \tparam parseFlags Combination of \ref ParseFlag. \tparam InputStream Type of input stream, implementing Stream concept. \tparam Handler Type of handler, implementing Handler concept. \param is Input stream to be parsed. \param handler The handler to receive events. \return Whether the parsing is successful. */ template ParseResult Parse(InputStream& is, Handler& handler) { if (parseFlags & kParseIterativeFlag) return IterativeParse(is, handler); parseResult_.Clear(); ClearStackOnExit scope(*this); SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) { RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); } else { ParseValue(is, handler); RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); if (!(parseFlags & kParseStopWhenDoneFlag)) { SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); if (RAPIDJSON_UNLIKELY(is.Peek() != '\0')) { RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); } } } return parseResult_; } //! Parse JSON text (with \ref kParseDefaultFlags) /*! \tparam InputStream Type of input stream, implementing Stream concept \tparam Handler Type of handler, implementing Handler concept. \param is Input stream to be parsed. \param handler The handler to receive events. \return Whether the parsing is successful. */ template ParseResult Parse(InputStream& is, Handler& handler) { return Parse(is, handler); } //! Initialize JSON text token-by-token parsing /*! */ void IterativeParseInit() { parseResult_.Clear(); state_ = IterativeParsingStartState; } //! Parse one token from JSON text /*! \tparam InputStream Type of input stream, implementing Stream concept \tparam Handler Type of handler, implementing Handler concept. \param is Input stream to be parsed. \param handler The handler to receive events. \return Whether the parsing is successful. */ template bool IterativeParseNext(InputStream& is, Handler& handler) { while (RAPIDJSON_LIKELY(is.Peek() != '\0')) { SkipWhitespaceAndComments(is); Token t = Tokenize(is.Peek()); IterativeParsingState n = Predict(state_, t); IterativeParsingState d = Transit(state_, t, n, is, handler); // If we've finished or hit an error... if (RAPIDJSON_UNLIKELY(IsIterativeParsingCompleteState(d))) { // Report errors. if (d == IterativeParsingErrorState) { HandleError(state_, is); return false; } // Transition to the finish state. RAPIDJSON_ASSERT(d == IterativeParsingFinishState); state_ = d; // If StopWhenDone is not set... if (!(parseFlags & kParseStopWhenDoneFlag)) { // ... and extra non-whitespace data is found... SkipWhitespaceAndComments(is); if (is.Peek() != '\0') { // ... this is considered an error. HandleError(state_, is); return false; } } // Success! We are done! return true; } // Transition to the new state. state_ = d; // If we parsed anything other than a delimiter, we invoked the handler, so we can return true now. if (!IsIterativeParsingDelimiterState(n)) return true; } // We reached the end of file. stack_.Clear(); if (state_ != IterativeParsingFinishState) { HandleError(state_, is); return false; } return true; } //! Check if token-by-token parsing JSON text is complete /*! \return Whether the JSON has been fully decoded. */ RAPIDJSON_FORCEINLINE bool IterativeParseComplete() { return IsIterativeParsingCompleteState(state_); } //! Whether a parse error has occured in the last parsing. bool HasParseError() const { return parseResult_.IsError(); } //! Get the \ref ParseErrorCode of last parsing. ParseErrorCode GetParseErrorCode() const { return parseResult_.Code(); } //! Get the position of last parsing error in input, 0 otherwise. size_t GetErrorOffset() const { return parseResult_.Offset(); } protected: void SetParseError(ParseErrorCode code, size_t offset) { parseResult_.Set(code, offset); } private: // Prohibit copy constructor & assignment operator. GenericReader(const GenericReader&); GenericReader& operator=(const GenericReader&); void ClearStack() { stack_.Clear(); } // clear stack on any exit from ParseStream, e.g. due to exception struct ClearStackOnExit { explicit ClearStackOnExit(GenericReader& r) : r_(r) {} ~ClearStackOnExit() { r_.ClearStack(); } private: GenericReader& r_; ClearStackOnExit(const ClearStackOnExit&); ClearStackOnExit& operator=(const ClearStackOnExit&); }; template void SkipWhitespaceAndComments(InputStream& is) { SkipWhitespace(is); if (parseFlags & kParseCommentsFlag) { while (RAPIDJSON_UNLIKELY(Consume(is, '/'))) { if (Consume(is, '*')) { while (true) { if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); else if (Consume(is, '*')) { if (Consume(is, '/')) break; } else is.Take(); } } else if (RAPIDJSON_LIKELY(Consume(is, '/'))) while (is.Peek() != '\0' && is.Take() != '\n') {} else RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); SkipWhitespace(is); } } } // Parse object: { string : value, ... } template void ParseObject(InputStream& is, Handler& handler) { RAPIDJSON_ASSERT(is.Peek() == '{'); is.Take(); // Skip '{' if (RAPIDJSON_UNLIKELY(!handler.StartObject())) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; if (Consume(is, '}')) { if (RAPIDJSON_UNLIKELY(!handler.EndObject(0))) // empty object RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); return; } for (SizeType memberCount = 0;;) { if (RAPIDJSON_UNLIKELY(is.Peek() != '"')) RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); ParseString(is, handler, true); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; if (RAPIDJSON_UNLIKELY(!Consume(is, ':'))) RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; ParseValue(is, handler); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; ++memberCount; switch (is.Peek()) { case ',': is.Take(); SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; break; case '}': is.Take(); if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); return; default: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); break; // This useless break is only for making warning and coverage happy } if (parseFlags & kParseTrailingCommasFlag) { if (is.Peek() == '}') { if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); is.Take(); return; } } } } // Parse array: [ value, ... ] template void ParseArray(InputStream& is, Handler& handler) { RAPIDJSON_ASSERT(is.Peek() == '['); is.Take(); // Skip '[' if (RAPIDJSON_UNLIKELY(!handler.StartArray())) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; if (Consume(is, ']')) { if (RAPIDJSON_UNLIKELY(!handler.EndArray(0))) // empty array RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); return; } for (SizeType elementCount = 0;;) { ParseValue(is, handler); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; ++elementCount; SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; if (Consume(is, ',')) { SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; } else if (Consume(is, ']')) { if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); return; } else RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); if (parseFlags & kParseTrailingCommasFlag) { if (is.Peek() == ']') { if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); is.Take(); return; } } } } template void ParseNull(InputStream& is, Handler& handler) { RAPIDJSON_ASSERT(is.Peek() == 'n'); is.Take(); if (RAPIDJSON_LIKELY(Consume(is, 'u') && Consume(is, 'l') && Consume(is, 'l'))) { if (RAPIDJSON_UNLIKELY(!handler.Null())) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); } else RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); } template void ParseTrue(InputStream& is, Handler& handler) { RAPIDJSON_ASSERT(is.Peek() == 't'); is.Take(); if (RAPIDJSON_LIKELY(Consume(is, 'r') && Consume(is, 'u') && Consume(is, 'e'))) { if (RAPIDJSON_UNLIKELY(!handler.Bool(true))) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); } else RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); } template void ParseFalse(InputStream& is, Handler& handler) { RAPIDJSON_ASSERT(is.Peek() == 'f'); is.Take(); if (RAPIDJSON_LIKELY(Consume(is, 'a') && Consume(is, 'l') && Consume(is, 's') && Consume(is, 'e'))) { if (RAPIDJSON_UNLIKELY(!handler.Bool(false))) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); } else RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); } template RAPIDJSON_FORCEINLINE static bool Consume(InputStream& is, typename InputStream::Ch expect) { if (RAPIDJSON_LIKELY(is.Peek() == expect)) { is.Take(); return true; } else return false; } // Helper function to parse four hexidecimal digits in \uXXXX in ParseString(). template unsigned ParseHex4(InputStream& is, size_t escapeOffset) { unsigned codepoint = 0; for (int i = 0; i < 4; i++) { Ch c = is.Peek(); codepoint <<= 4; codepoint += static_cast(c); if (c >= '0' && c <= '9') codepoint -= '0'; else if (c >= 'A' && c <= 'F') codepoint -= 'A' - 10; else if (c >= 'a' && c <= 'f') codepoint -= 'a' - 10; else { RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, escapeOffset); RAPIDJSON_PARSE_ERROR_EARLY_RETURN(0); } is.Take(); } return codepoint; } template class StackStream { public: typedef CharType Ch; StackStream(internal::Stack& stack) : stack_(stack), length_(0) {} RAPIDJSON_FORCEINLINE void Put(Ch c) { *stack_.template Push() = c; ++length_; } RAPIDJSON_FORCEINLINE void* Push(SizeType count) { length_ += count; return stack_.template Push(count); } size_t Length() const { return length_; } Ch* Pop() { return stack_.template Pop(length_); } private: StackStream(const StackStream&); StackStream& operator=(const StackStream&); internal::Stack& stack_; SizeType length_; }; // Parse string and generate String event. Different code paths for kParseInsituFlag. template void ParseString(InputStream& is, Handler& handler, bool isKey = false) { internal::StreamLocalCopy copy(is); InputStream& s(copy.s); RAPIDJSON_ASSERT(s.Peek() == '\"'); s.Take(); // Skip '\"' bool success = false; if (parseFlags & kParseInsituFlag) { typename InputStream::Ch *head = s.PutBegin(); ParseStringToStream(s, s); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; size_t length = s.PutEnd(head) - 1; RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); const typename TargetEncoding::Ch* const str = reinterpret_cast(head); success = (isKey ? handler.Key(str, SizeType(length), false) : handler.String(str, SizeType(length), false)); } else { StackStream stackStream(stack_); ParseStringToStream(s, stackStream); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; SizeType length = static_cast(stackStream.Length()) - 1; const typename TargetEncoding::Ch* const str = stackStream.Pop(); success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); } if (RAPIDJSON_UNLIKELY(!success)) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); } // Parse string to an output is // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. template RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN #define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 static const char escape[256] = { Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 }; #undef Z16 //!@endcond for (;;) { // Scan and copy string before "\\\"" or < 0x20. This is an optional optimzation. if (!(parseFlags & kParseValidateEncodingFlag)) ScanCopyUnescapedString(is, os); Ch c = is.Peek(); if (RAPIDJSON_UNLIKELY(c == '\\')) { // Escape size_t escapeOffset = is.Tell(); // For invalid escaping, report the inital '\\' as error offset is.Take(); Ch e = is.Peek(); if ((sizeof(Ch) == 1 || unsigned(e) < 256) && RAPIDJSON_LIKELY(escape[static_cast(e)])) { is.Take(); os.Put(static_cast(escape[static_cast(e)])); } else if (RAPIDJSON_LIKELY(e == 'u')) { // Unicode is.Take(); unsigned codepoint = ParseHex4(is, escapeOffset); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; if (RAPIDJSON_UNLIKELY(codepoint >= 0xD800 && codepoint <= 0xDBFF)) { // Handle UTF-16 surrogate pair if (RAPIDJSON_UNLIKELY(!Consume(is, '\\') || !Consume(is, 'u'))) RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); unsigned codepoint2 = ParseHex4(is, escapeOffset); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; if (RAPIDJSON_UNLIKELY(codepoint2 < 0xDC00 || codepoint2 > 0xDFFF)) RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; } TEncoding::Encode(os, codepoint); } else RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, escapeOffset); } else if (RAPIDJSON_UNLIKELY(c == '"')) { // Closing double quote is.Take(); os.Put('\0'); // null-terminate the string return; } else if (RAPIDJSON_UNLIKELY(static_cast(c) < 0x20)) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF if (c == '\0') RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell()); else RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, is.Tell()); } else { size_t offset = is.Tell(); if (RAPIDJSON_UNLIKELY((parseFlags & kParseValidateEncodingFlag ? !Transcoder::Validate(is, os) : !Transcoder::Transcode(is, os)))) RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, offset); } } } template static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InputStream&, OutputStream&) { // Do nothing for generic version } #if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) // StringStream -> StackStream static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { const char* p = is.src_; // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); while (p != nextAligned) if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { is.src_ = p; return; } else os.Put(*p++); // The rest of string using SIMD static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); for (;; p += 16) { const __m128i s = _mm_load_si128(reinterpret_cast(p)); const __m128i t1 = _mm_cmpeq_epi8(s, dq); const __m128i t2 = _mm_cmpeq_epi8(s, bs); const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); unsigned short r = static_cast(_mm_movemask_epi8(x)); if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped SizeType length; #ifdef _MSC_VER // Find the index of first escaped unsigned long offset; _BitScanForward(&offset, r); length = offset; #else length = static_cast(__builtin_ffs(r) - 1); #endif if (length != 0) { char* q = reinterpret_cast(os.Push(length)); for (size_t i = 0; i < length; i++) q[i] = p[i]; p += length; } break; } _mm_storeu_si128(reinterpret_cast<__m128i *>(os.Push(16)), s); } is.src_ = p; } // InsituStringStream -> InsituStringStream static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { RAPIDJSON_ASSERT(&is == &os); (void)os; if (is.src_ == is.dst_) { SkipUnescapedString(is); return; } char* p = is.src_; char *q = is.dst_; // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); while (p != nextAligned) if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { is.src_ = p; is.dst_ = q; return; } else *q++ = *p++; // The rest of string using SIMD static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); for (;; p += 16, q += 16) { const __m128i s = _mm_load_si128(reinterpret_cast(p)); const __m128i t1 = _mm_cmpeq_epi8(s, dq); const __m128i t2 = _mm_cmpeq_epi8(s, bs); const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); unsigned short r = static_cast(_mm_movemask_epi8(x)); if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped size_t length; #ifdef _MSC_VER // Find the index of first escaped unsigned long offset; _BitScanForward(&offset, r); length = offset; #else length = static_cast(__builtin_ffs(r) - 1); #endif for (const char* pend = p + length; p != pend; ) *q++ = *p++; break; } _mm_storeu_si128(reinterpret_cast<__m128i *>(q), s); } is.src_ = p; is.dst_ = q; } // When read/write pointers are the same for insitu stream, just skip unescaped characters static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { RAPIDJSON_ASSERT(is.src_ == is.dst_); char* p = is.src_; // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); for (; p != nextAligned; p++) if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { is.src_ = is.dst_ = p; return; } // The rest of string using SIMD static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); for (;; p += 16) { const __m128i s = _mm_load_si128(reinterpret_cast(p)); const __m128i t1 = _mm_cmpeq_epi8(s, dq); const __m128i t2 = _mm_cmpeq_epi8(s, bs); const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); unsigned short r = static_cast(_mm_movemask_epi8(x)); if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped size_t length; #ifdef _MSC_VER // Find the index of first escaped unsigned long offset; _BitScanForward(&offset, r); length = offset; #else length = static_cast(__builtin_ffs(r) - 1); #endif p += length; break; } } is.src_ = is.dst_ = p; } #elif defined(RAPIDJSON_NEON) // StringStream -> StackStream static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { const char* p = is.src_; // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); while (p != nextAligned) if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { is.src_ = p; return; } else os.Put(*p++); // The rest of string using SIMD const uint8x16_t s0 = vmovq_n_u8('"'); const uint8x16_t s1 = vmovq_n_u8('\\'); const uint8x16_t s2 = vmovq_n_u8('\b'); const uint8x16_t s3 = vmovq_n_u8(32); for (;; p += 16) { const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); uint8x16_t x = vceqq_u8(s, s0); x = vorrq_u8(x, vceqq_u8(s, s1)); x = vorrq_u8(x, vceqq_u8(s, s2)); x = vorrq_u8(x, vcltq_u8(s, s3)); x = vrev64q_u8(x); // Rev in 64 uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract SizeType length = 0; bool escaped = false; if (low == 0) { if (high != 0) { unsigned lz = (unsigned)__builtin_clzll(high);; length = 8 + (lz >> 3); escaped = true; } } else { unsigned lz = (unsigned)__builtin_clzll(low);; length = lz >> 3; escaped = true; } if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped if (length != 0) { char* q = reinterpret_cast(os.Push(length)); for (size_t i = 0; i < length; i++) q[i] = p[i]; p += length; } break; } vst1q_u8(reinterpret_cast(os.Push(16)), s); } is.src_ = p; } // InsituStringStream -> InsituStringStream static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { RAPIDJSON_ASSERT(&is == &os); (void)os; if (is.src_ == is.dst_) { SkipUnescapedString(is); return; } char* p = is.src_; char *q = is.dst_; // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); while (p != nextAligned) if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { is.src_ = p; is.dst_ = q; return; } else *q++ = *p++; // The rest of string using SIMD const uint8x16_t s0 = vmovq_n_u8('"'); const uint8x16_t s1 = vmovq_n_u8('\\'); const uint8x16_t s2 = vmovq_n_u8('\b'); const uint8x16_t s3 = vmovq_n_u8(32); for (;; p += 16, q += 16) { const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); uint8x16_t x = vceqq_u8(s, s0); x = vorrq_u8(x, vceqq_u8(s, s1)); x = vorrq_u8(x, vceqq_u8(s, s2)); x = vorrq_u8(x, vcltq_u8(s, s3)); x = vrev64q_u8(x); // Rev in 64 uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract SizeType length = 0; bool escaped = false; if (low == 0) { if (high != 0) { unsigned lz = (unsigned)__builtin_clzll(high); length = 8 + (lz >> 3); escaped = true; } } else { unsigned lz = (unsigned)__builtin_clzll(low); length = lz >> 3; escaped = true; } if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped for (const char* pend = p + length; p != pend; ) { *q++ = *p++; } break; } vst1q_u8(reinterpret_cast(q), s); } is.src_ = p; is.dst_ = q; } // When read/write pointers are the same for insitu stream, just skip unescaped characters static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { RAPIDJSON_ASSERT(is.src_ == is.dst_); char* p = is.src_; // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); for (; p != nextAligned; p++) if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { is.src_ = is.dst_ = p; return; } // The rest of string using SIMD const uint8x16_t s0 = vmovq_n_u8('"'); const uint8x16_t s1 = vmovq_n_u8('\\'); const uint8x16_t s2 = vmovq_n_u8('\b'); const uint8x16_t s3 = vmovq_n_u8(32); for (;; p += 16) { const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); uint8x16_t x = vceqq_u8(s, s0); x = vorrq_u8(x, vceqq_u8(s, s1)); x = vorrq_u8(x, vceqq_u8(s, s2)); x = vorrq_u8(x, vcltq_u8(s, s3)); x = vrev64q_u8(x); // Rev in 64 uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract if (low == 0) { if (high != 0) { int lz = __builtin_clzll(high); p += 8 + (lz >> 3); break; } } else { int lz = __builtin_clzll(low); p += lz >> 3; break; } } is.src_ = is.dst_ = p; } #endif // RAPIDJSON_NEON template class NumberStream; template class NumberStream { public: typedef typename InputStream::Ch Ch; NumberStream(GenericReader& reader, InputStream& s) : is(s) { (void)reader; } RAPIDJSON_FORCEINLINE Ch Peek() const { return is.Peek(); } RAPIDJSON_FORCEINLINE Ch TakePush() { return is.Take(); } RAPIDJSON_FORCEINLINE Ch Take() { return is.Take(); } RAPIDJSON_FORCEINLINE void Push(char) {} size_t Tell() { return is.Tell(); } size_t Length() { return 0; } const char* Pop() { return 0; } protected: NumberStream& operator=(const NumberStream&); InputStream& is; }; template class NumberStream : public NumberStream { typedef NumberStream Base; public: NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is), stackStream(reader.stack_) {} RAPIDJSON_FORCEINLINE Ch TakePush() { stackStream.Put(static_cast(Base::is.Peek())); return Base::is.Take(); } RAPIDJSON_FORCEINLINE void Push(char c) { stackStream.Put(c); } size_t Length() { return stackStream.Length(); } const char* Pop() { stackStream.Put('\0'); return stackStream.Pop(); } private: StackStream stackStream; }; template class NumberStream : public NumberStream { typedef NumberStream Base; public: NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is) {} RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); } }; template void ParseNumber(InputStream& is, Handler& handler) { internal::StreamLocalCopy copy(is); NumberStream s(*this, copy.s); size_t startOffset = s.Tell(); double d = 0.0; bool useNanOrInf = false; // Parse minus bool minus = Consume(s, '-'); // Parse int: zero / ( digit1-9 *DIGIT ) unsigned i = 0; uint64_t i64 = 0; bool use64bit = false; int significandDigit = 0; if (RAPIDJSON_UNLIKELY(s.Peek() == '0')) { i = 0; s.TakePush(); } else if (RAPIDJSON_LIKELY(s.Peek() >= '1' && s.Peek() <= '9')) { i = static_cast(s.TakePush() - '0'); if (minus) while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { if (RAPIDJSON_UNLIKELY(i >= 214748364)) { // 2^31 = 2147483648 if (RAPIDJSON_LIKELY(i != 214748364 || s.Peek() > '8')) { i64 = i; use64bit = true; break; } } i = i * 10 + static_cast(s.TakePush() - '0'); significandDigit++; } else while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { if (RAPIDJSON_UNLIKELY(i >= 429496729)) { // 2^32 - 1 = 4294967295 if (RAPIDJSON_LIKELY(i != 429496729 || s.Peek() > '5')) { i64 = i; use64bit = true; break; } } i = i * 10 + static_cast(s.TakePush() - '0'); significandDigit++; } } // Parse NaN or Infinity here else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) { if (Consume(s, 'N')) { if (Consume(s, 'a') && Consume(s, 'N')) { d = std::numeric_limits::quiet_NaN(); useNanOrInf = true; } } else if (RAPIDJSON_LIKELY(Consume(s, 'I'))) { if (Consume(s, 'n') && Consume(s, 'f')) { d = (minus ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()); useNanOrInf = true; if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n') && Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) { RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); } } } if (RAPIDJSON_UNLIKELY(!useNanOrInf)) { RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); } } else RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); // Parse 64bit int bool useDouble = false; if (use64bit) { if (minus) while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC))) // 2^63 = 9223372036854775808 if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8')) { d = static_cast(i64); useDouble = true; break; } i64 = i64 * 10 + static_cast(s.TakePush() - '0'); significandDigit++; } else while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999))) // 2^64 - 1 = 18446744073709551615 if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5')) { d = static_cast(i64); useDouble = true; break; } i64 = i64 * 10 + static_cast(s.TakePush() - '0'); significandDigit++; } } // Force double for big integer if (useDouble) { while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { if (RAPIDJSON_UNLIKELY(d >= 1.7976931348623157e307)) // DBL_MAX / 10.0 RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); d = d * 10 + (s.TakePush() - '0'); } } // Parse frac = decimal-point 1*DIGIT int expFrac = 0; size_t decimalPosition; if (Consume(s, '.')) { decimalPosition = s.Length(); if (RAPIDJSON_UNLIKELY(!(s.Peek() >= '0' && s.Peek() <= '9'))) RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, s.Tell()); if (!useDouble) { #if RAPIDJSON_64BIT // Use i64 to store significand in 64-bit architecture if (!use64bit) i64 = i; while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { if (i64 > RAPIDJSON_UINT64_C2(0x1FFFFF, 0xFFFFFFFF)) // 2^53 - 1 for fast path break; else { i64 = i64 * 10 + static_cast(s.TakePush() - '0'); --expFrac; if (i64 != 0) significandDigit++; } } d = static_cast(i64); #else // Use double to store significand in 32-bit architecture d = static_cast(use64bit ? i64 : i); #endif useDouble = true; } while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { if (significandDigit < 17) { d = d * 10.0 + (s.TakePush() - '0'); --expFrac; if (RAPIDJSON_LIKELY(d > 0.0)) significandDigit++; } else s.TakePush(); } } else decimalPosition = s.Length(); // decimal position at the end of integer. // Parse exp = e [ minus / plus ] 1*DIGIT int exp = 0; if (Consume(s, 'e') || Consume(s, 'E')) { if (!useDouble) { d = static_cast(use64bit ? i64 : i); useDouble = true; } bool expMinus = false; if (Consume(s, '+')) ; else if (Consume(s, '-')) expMinus = true; if (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { exp = static_cast(s.Take() - '0'); if (expMinus) { while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { exp = exp * 10 + static_cast(s.Take() - '0'); if (exp >= 214748364) { // Issue #313: prevent overflow exponent while (RAPIDJSON_UNLIKELY(s.Peek() >= '0' && s.Peek() <= '9')) // Consume the rest of exponent s.Take(); } } } else { // positive exp int maxExp = 308 - expFrac; while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { exp = exp * 10 + static_cast(s.Take() - '0'); if (RAPIDJSON_UNLIKELY(exp > maxExp)) RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); } } } else RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); if (expMinus) exp = -exp; } // Finish parsing, call event according to the type of number. bool cont = true; if (parseFlags & kParseNumbersAsStringsFlag) { if (parseFlags & kParseInsituFlag) { s.Pop(); // Pop stack no matter if it will be used or not. typename InputStream::Ch* head = is.PutBegin(); const size_t length = s.Tell() - startOffset; RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); // unable to insert the \0 character here, it will erase the comma after this number const typename TargetEncoding::Ch* const str = reinterpret_cast(head); cont = handler.RawNumber(str, SizeType(length), false); } else { SizeType numCharsToCopy = static_cast(s.Length()); StringStream srcStream(s.Pop()); StackStream dstStream(stack_); while (numCharsToCopy--) { Transcoder, TargetEncoding>::Transcode(srcStream, dstStream); } dstStream.Put('\0'); const typename TargetEncoding::Ch* str = dstStream.Pop(); const SizeType length = static_cast(dstStream.Length()) - 1; cont = handler.RawNumber(str, SizeType(length), true); } } else { size_t length = s.Length(); const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. if (useDouble) { int p = exp + expFrac; if (parseFlags & kParseFullPrecisionFlag) d = internal::StrtodFullPrecision(d, p, decimal, length, decimalPosition, exp); else d = internal::StrtodNormalPrecision(d, p); cont = handler.Double(minus ? -d : d); } else if (useNanOrInf) { cont = handler.Double(d); } else { if (use64bit) { if (minus) cont = handler.Int64(static_cast(~i64 + 1)); else cont = handler.Uint64(i64); } else { if (minus) cont = handler.Int(static_cast(~i + 1)); else cont = handler.Uint(i); } } } if (RAPIDJSON_UNLIKELY(!cont)) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, startOffset); } // Parse any JSON value template void ParseValue(InputStream& is, Handler& handler) { switch (is.Peek()) { case 'n': ParseNull (is, handler); break; case 't': ParseTrue (is, handler); break; case 'f': ParseFalse (is, handler); break; case '"': ParseString(is, handler); break; case '{': ParseObject(is, handler); break; case '[': ParseArray (is, handler); break; default : ParseNumber(is, handler); break; } } // Iterative Parsing // States enum IterativeParsingState { IterativeParsingFinishState = 0, // sink states at top IterativeParsingErrorState, // sink states at top IterativeParsingStartState, // Object states IterativeParsingObjectInitialState, IterativeParsingMemberKeyState, IterativeParsingMemberValueState, IterativeParsingObjectFinishState, // Array states IterativeParsingArrayInitialState, IterativeParsingElementState, IterativeParsingArrayFinishState, // Single value state IterativeParsingValueState, // Delimiter states (at bottom) IterativeParsingElementDelimiterState, IterativeParsingMemberDelimiterState, IterativeParsingKeyValueDelimiterState, cIterativeParsingStateCount }; // Tokens enum Token { LeftBracketToken = 0, RightBracketToken, LeftCurlyBracketToken, RightCurlyBracketToken, CommaToken, ColonToken, StringToken, FalseToken, TrueToken, NullToken, NumberToken, kTokenCount }; RAPIDJSON_FORCEINLINE Token Tokenize(Ch c) { //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN #define N NumberToken #define N16 N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N // Maps from ASCII to Token static const unsigned char tokenMap[256] = { N16, // 00~0F N16, // 10~1F N, N, StringToken, N, N, N, N, N, N, N, N, N, CommaToken, N, N, N, // 20~2F N, N, N, N, N, N, N, N, N, N, ColonToken, N, N, N, N, N, // 30~3F N16, // 40~4F N, N, N, N, N, N, N, N, N, N, N, LeftBracketToken, N, RightBracketToken, N, N, // 50~5F N, N, N, N, N, N, FalseToken, N, N, N, N, N, N, N, NullToken, N, // 60~6F N, N, N, N, TrueToken, N, N, N, N, N, N, LeftCurlyBracketToken, N, RightCurlyBracketToken, N, N, // 70~7F N16, N16, N16, N16, N16, N16, N16, N16 // 80~FF }; #undef N #undef N16 //!@endcond if (sizeof(Ch) == 1 || static_cast(c) < 256) return static_cast(tokenMap[static_cast(c)]); else return NumberToken; } RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) { // current state x one lookahead token -> new state static const char G[cIterativeParsingStateCount][kTokenCount] = { // Finish(sink state) { IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState }, // Error(sink state) { IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState }, // Start { IterativeParsingArrayInitialState, // Left bracket IterativeParsingErrorState, // Right bracket IterativeParsingObjectInitialState, // Left curly bracket IterativeParsingErrorState, // Right curly bracket IterativeParsingErrorState, // Comma IterativeParsingErrorState, // Colon IterativeParsingValueState, // String IterativeParsingValueState, // False IterativeParsingValueState, // True IterativeParsingValueState, // Null IterativeParsingValueState // Number }, // ObjectInitial { IterativeParsingErrorState, // Left bracket IterativeParsingErrorState, // Right bracket IterativeParsingErrorState, // Left curly bracket IterativeParsingObjectFinishState, // Right curly bracket IterativeParsingErrorState, // Comma IterativeParsingErrorState, // Colon IterativeParsingMemberKeyState, // String IterativeParsingErrorState, // False IterativeParsingErrorState, // True IterativeParsingErrorState, // Null IterativeParsingErrorState // Number }, // MemberKey { IterativeParsingErrorState, // Left bracket IterativeParsingErrorState, // Right bracket IterativeParsingErrorState, // Left curly bracket IterativeParsingErrorState, // Right curly bracket IterativeParsingErrorState, // Comma IterativeParsingKeyValueDelimiterState, // Colon IterativeParsingErrorState, // String IterativeParsingErrorState, // False IterativeParsingErrorState, // True IterativeParsingErrorState, // Null IterativeParsingErrorState // Number }, // MemberValue { IterativeParsingErrorState, // Left bracket IterativeParsingErrorState, // Right bracket IterativeParsingErrorState, // Left curly bracket IterativeParsingObjectFinishState, // Right curly bracket IterativeParsingMemberDelimiterState, // Comma IterativeParsingErrorState, // Colon IterativeParsingErrorState, // String IterativeParsingErrorState, // False IterativeParsingErrorState, // True IterativeParsingErrorState, // Null IterativeParsingErrorState // Number }, // ObjectFinish(sink state) { IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState }, // ArrayInitial { IterativeParsingArrayInitialState, // Left bracket(push Element state) IterativeParsingArrayFinishState, // Right bracket IterativeParsingObjectInitialState, // Left curly bracket(push Element state) IterativeParsingErrorState, // Right curly bracket IterativeParsingErrorState, // Comma IterativeParsingErrorState, // Colon IterativeParsingElementState, // String IterativeParsingElementState, // False IterativeParsingElementState, // True IterativeParsingElementState, // Null IterativeParsingElementState // Number }, // Element { IterativeParsingErrorState, // Left bracket IterativeParsingArrayFinishState, // Right bracket IterativeParsingErrorState, // Left curly bracket IterativeParsingErrorState, // Right curly bracket IterativeParsingElementDelimiterState, // Comma IterativeParsingErrorState, // Colon IterativeParsingErrorState, // String IterativeParsingErrorState, // False IterativeParsingErrorState, // True IterativeParsingErrorState, // Null IterativeParsingErrorState // Number }, // ArrayFinish(sink state) { IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState }, // Single Value (sink state) { IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState }, // ElementDelimiter { IterativeParsingArrayInitialState, // Left bracket(push Element state) IterativeParsingArrayFinishState, // Right bracket IterativeParsingObjectInitialState, // Left curly bracket(push Element state) IterativeParsingErrorState, // Right curly bracket IterativeParsingErrorState, // Comma IterativeParsingErrorState, // Colon IterativeParsingElementState, // String IterativeParsingElementState, // False IterativeParsingElementState, // True IterativeParsingElementState, // Null IterativeParsingElementState // Number }, // MemberDelimiter { IterativeParsingErrorState, // Left bracket IterativeParsingErrorState, // Right bracket IterativeParsingErrorState, // Left curly bracket IterativeParsingObjectFinishState, // Right curly bracket IterativeParsingErrorState, // Comma IterativeParsingErrorState, // Colon IterativeParsingMemberKeyState, // String IterativeParsingErrorState, // False IterativeParsingErrorState, // True IterativeParsingErrorState, // Null IterativeParsingErrorState // Number }, // KeyValueDelimiter { IterativeParsingArrayInitialState, // Left bracket(push MemberValue state) IterativeParsingErrorState, // Right bracket IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state) IterativeParsingErrorState, // Right curly bracket IterativeParsingErrorState, // Comma IterativeParsingErrorState, // Colon IterativeParsingMemberValueState, // String IterativeParsingMemberValueState, // False IterativeParsingMemberValueState, // True IterativeParsingMemberValueState, // Null IterativeParsingMemberValueState // Number }, }; // End of G return static_cast(G[state][token]); } // Make an advance in the token stream and state based on the candidate destination state which was returned by Transit(). // May return a new state on state pop. template RAPIDJSON_FORCEINLINE IterativeParsingState Transit(IterativeParsingState src, Token token, IterativeParsingState dst, InputStream& is, Handler& handler) { (void)token; switch (dst) { case IterativeParsingErrorState: return dst; case IterativeParsingObjectInitialState: case IterativeParsingArrayInitialState: { // Push the state(Element or MemeberValue) if we are nested in another array or value of member. // In this way we can get the correct state on ObjectFinish or ArrayFinish by frame pop. IterativeParsingState n = src; if (src == IterativeParsingArrayInitialState || src == IterativeParsingElementDelimiterState) n = IterativeParsingElementState; else if (src == IterativeParsingKeyValueDelimiterState) n = IterativeParsingMemberValueState; // Push current state. *stack_.template Push(1) = n; // Initialize and push the member/element count. *stack_.template Push(1) = 0; // Call handler bool hr = (dst == IterativeParsingObjectInitialState) ? handler.StartObject() : handler.StartArray(); // On handler short circuits the parsing. if (!hr) { RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); return IterativeParsingErrorState; } else { is.Take(); return dst; } } case IterativeParsingMemberKeyState: ParseString(is, handler, true); if (HasParseError()) return IterativeParsingErrorState; else return dst; case IterativeParsingKeyValueDelimiterState: RAPIDJSON_ASSERT(token == ColonToken); is.Take(); return dst; case IterativeParsingMemberValueState: // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. ParseValue(is, handler); if (HasParseError()) { return IterativeParsingErrorState; } return dst; case IterativeParsingElementState: // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. ParseValue(is, handler); if (HasParseError()) { return IterativeParsingErrorState; } return dst; case IterativeParsingMemberDelimiterState: case IterativeParsingElementDelimiterState: is.Take(); // Update member/element count. *stack_.template Top() = *stack_.template Top() + 1; return dst; case IterativeParsingObjectFinishState: { // Transit from delimiter is only allowed when trailing commas are enabled if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingMemberDelimiterState) { RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorObjectMissName, is.Tell()); return IterativeParsingErrorState; } // Get member count. SizeType c = *stack_.template Pop(1); // If the object is not empty, count the last member. if (src == IterativeParsingMemberValueState) ++c; // Restore the state. IterativeParsingState n = static_cast(*stack_.template Pop(1)); // Transit to Finish state if this is the topmost scope. if (n == IterativeParsingStartState) n = IterativeParsingFinishState; // Call handler bool hr = handler.EndObject(c); // On handler short circuits the parsing. if (!hr) { RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); return IterativeParsingErrorState; } else { is.Take(); return n; } } case IterativeParsingArrayFinishState: { // Transit from delimiter is only allowed when trailing commas are enabled if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingElementDelimiterState) { RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorValueInvalid, is.Tell()); return IterativeParsingErrorState; } // Get element count. SizeType c = *stack_.template Pop(1); // If the array is not empty, count the last element. if (src == IterativeParsingElementState) ++c; // Restore the state. IterativeParsingState n = static_cast(*stack_.template Pop(1)); // Transit to Finish state if this is the topmost scope. if (n == IterativeParsingStartState) n = IterativeParsingFinishState; // Call handler bool hr = handler.EndArray(c); // On handler short circuits the parsing. if (!hr) { RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); return IterativeParsingErrorState; } else { is.Take(); return n; } } default: // This branch is for IterativeParsingValueState actually. // Use `default:` rather than // `case IterativeParsingValueState:` is for code coverage. // The IterativeParsingStartState is not enumerated in this switch-case. // It is impossible for that case. And it can be caught by following assertion. // The IterativeParsingFinishState is not enumerated in this switch-case either. // It is a "derivative" state which cannot triggered from Predict() directly. // Therefore it cannot happen here. And it can be caught by following assertion. RAPIDJSON_ASSERT(dst == IterativeParsingValueState); // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. ParseValue(is, handler); if (HasParseError()) { return IterativeParsingErrorState; } return IterativeParsingFinishState; } } template void HandleError(IterativeParsingState src, InputStream& is) { if (HasParseError()) { // Error flag has been set. return; } switch (src) { case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; case IterativeParsingObjectInitialState: case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; case IterativeParsingKeyValueDelimiterState: case IterativeParsingArrayInitialState: case IterativeParsingElementDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); return; default: RAPIDJSON_ASSERT(src == IterativeParsingElementState); RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; } } RAPIDJSON_FORCEINLINE bool IsIterativeParsingDelimiterState(IterativeParsingState s) { return s >= IterativeParsingElementDelimiterState; } RAPIDJSON_FORCEINLINE bool IsIterativeParsingCompleteState(IterativeParsingState s) { return s <= IterativeParsingErrorState; } template ParseResult IterativeParse(InputStream& is, Handler& handler) { parseResult_.Clear(); ClearStackOnExit scope(*this); IterativeParsingState state = IterativeParsingStartState; SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); while (is.Peek() != '\0') { Token t = Tokenize(is.Peek()); IterativeParsingState n = Predict(state, t); IterativeParsingState d = Transit(state, t, n, is, handler); if (d == IterativeParsingErrorState) { HandleError(state, is); break; } state = d; // Do not further consume streams if a root JSON has been parsed. if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState) break; SkipWhitespaceAndComments(is); RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); } // Handle the end of file. if (state != IterativeParsingFinishState) HandleError(state, is); return parseResult_; } static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. ParseResult parseResult_; IterativeParsingState state_; }; // class GenericReader //! Reader with UTF8 encoding and default allocator. typedef GenericReader, UTF8<> > Reader; RAPIDJSON_NAMESPACE_END #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #ifdef __GNUC__ RAPIDJSON_DIAG_POP #endif #ifdef _MSC_VER RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_READER_H_ ================================================ FILE: third-party/rapidjson/schema.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available-> // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License-> You may obtain a copy of the License at // // http://opensource->org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied-> See the License for the // specific language governing permissions and limitations under the License-> #ifndef RAPIDJSON_SCHEMA_H_ #define RAPIDJSON_SCHEMA_H_ #include "document.h" #include "pointer.h" #include // abs, floor #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) #define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 #else #define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 #endif #if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && !defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) #define RAPIDJSON_SCHEMA_USE_STDREGEX 1 #else #define RAPIDJSON_SCHEMA_USE_STDREGEX 0 #endif #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX #include "internal/regex.h" #elif RAPIDJSON_SCHEMA_USE_STDREGEX #include #endif #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX #define RAPIDJSON_SCHEMA_HAS_REGEX 1 #else #define RAPIDJSON_SCHEMA_HAS_REGEX 0 #endif #ifndef RAPIDJSON_SCHEMA_VERBOSE #define RAPIDJSON_SCHEMA_VERBOSE 0 #endif #if RAPIDJSON_SCHEMA_VERBOSE #include "stringbuffer.h" #endif RAPIDJSON_DIAG_PUSH #if defined(__GNUC__) RAPIDJSON_DIAG_OFF(effc++) #endif #ifdef __clang__ RAPIDJSON_DIAG_OFF(weak-vtables) RAPIDJSON_DIAG_OFF(exit-time-destructors) RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) RAPIDJSON_DIAG_OFF(variadic-macros) #endif #ifdef _MSC_VER RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated #endif RAPIDJSON_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// // Verbose Utilities #if RAPIDJSON_SCHEMA_VERBOSE namespace internal { inline void PrintInvalidKeyword(const char* keyword) { printf("Fail keyword: %s\n", keyword); } inline void PrintInvalidKeyword(const wchar_t* keyword) { wprintf(L"Fail keyword: %ls\n", keyword); } inline void PrintInvalidDocument(const char* document) { printf("Fail document: %s\n\n", document); } inline void PrintInvalidDocument(const wchar_t* document) { wprintf(L"Fail document: %ls\n\n", document); } inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); } inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); } } // namespace internal #endif // RAPIDJSON_SCHEMA_VERBOSE /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_INVALID_KEYWORD_RETURN #if RAPIDJSON_SCHEMA_VERBOSE #define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) #else #define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) #endif #define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ RAPIDJSON_MULTILINEMACRO_BEGIN\ context.invalidKeyword = keyword.GetString();\ RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ return false;\ RAPIDJSON_MULTILINEMACRO_END /////////////////////////////////////////////////////////////////////////////// // Forward declarations template class GenericSchemaDocument; namespace internal { template class Schema; /////////////////////////////////////////////////////////////////////////////// // ISchemaValidator class ISchemaValidator { public: virtual ~ISchemaValidator() {} virtual bool IsValid() const = 0; }; /////////////////////////////////////////////////////////////////////////////// // ISchemaStateFactory template class ISchemaStateFactory { public: virtual ~ISchemaStateFactory() {} virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; virtual void* CreateHasher() = 0; virtual uint64_t GetHashCode(void* hasher) = 0; virtual void DestroryHasher(void* hasher) = 0; virtual void* MallocState(size_t size) = 0; virtual void FreeState(void* p) = 0; }; /////////////////////////////////////////////////////////////////////////////// // Hasher // For comparison of compound value template class Hasher { public: typedef typename Encoding::Ch Ch; Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} bool Null() { return WriteType(kNullType); } bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } bool Int(int i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } bool Double(double d) { Number n; if (d < 0) n.u.i = static_cast(d); else n.u.u = static_cast(d); n.d = d; return WriteNumber(n); } bool RawNumber(const Ch* str, SizeType len, bool) { WriteBuffer(kNumberType, str, len * sizeof(Ch)); return true; } bool String(const Ch* str, SizeType len, bool) { WriteBuffer(kStringType, str, len * sizeof(Ch)); return true; } bool StartObject() { return true; } bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } bool EndObject(SizeType memberCount) { uint64_t h = Hash(0, kObjectType); uint64_t* kv = stack_.template Pop(memberCount * 2); for (SizeType i = 0; i < memberCount; i++) h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive *stack_.template Push() = h; return true; } bool StartArray() { return true; } bool EndArray(SizeType elementCount) { uint64_t h = Hash(0, kArrayType); uint64_t* e = stack_.template Pop(elementCount); for (SizeType i = 0; i < elementCount; i++) h = Hash(h, e[i]); // Use hash to achieve element order sensitive *stack_.template Push() = h; return true; } bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } uint64_t GetHashCode() const { RAPIDJSON_ASSERT(IsValid()); return *stack_.template Top(); } private: static const size_t kDefaultSize = 256; struct Number { union U { uint64_t u; int64_t i; }u; double d; }; bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } bool WriteBuffer(Type type, const void* data, size_t len) { // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); const unsigned char* d = static_cast(data); for (size_t i = 0; i < len; i++) h = Hash(h, d[i]); *stack_.template Push() = h; return true; } static uint64_t Hash(uint64_t h, uint64_t d) { static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); h ^= d; h *= kPrime; return h; } Stack stack_; }; /////////////////////////////////////////////////////////////////////////////// // SchemaValidationContext template struct SchemaValidationContext { typedef Schema SchemaType; typedef ISchemaStateFactory SchemaValidatorFactoryType; typedef typename SchemaType::ValueType ValueType; typedef typename ValueType::Ch Ch; enum PatternValidatorType { kPatternValidatorOnly, kPatternValidatorWithProperty, kPatternValidatorWithAdditionalProperty }; SchemaValidationContext(SchemaValidatorFactoryType& f, const SchemaType* s) : factory(f), schema(s), valueSchema(), invalidKeyword(), hasher(), arrayElementHashCodes(), validators(), validatorCount(), patternPropertiesValidators(), patternPropertiesValidatorCount(), patternPropertiesSchemas(), patternPropertiesSchemaCount(), valuePatternValidatorType(kPatternValidatorOnly), propertyExist(), inArray(false), valueUniqueness(false), arrayUniqueness(false) { } ~SchemaValidationContext() { if (hasher) factory.DestroryHasher(hasher); if (validators) { for (SizeType i = 0; i < validatorCount; i++) factory.DestroySchemaValidator(validators[i]); factory.FreeState(validators); } if (patternPropertiesValidators) { for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) factory.DestroySchemaValidator(patternPropertiesValidators[i]); factory.FreeState(patternPropertiesValidators); } if (patternPropertiesSchemas) factory.FreeState(patternPropertiesSchemas); if (propertyExist) factory.FreeState(propertyExist); } SchemaValidatorFactoryType& factory; const SchemaType* schema; const SchemaType* valueSchema; const Ch* invalidKeyword; void* hasher; // Only validator access void* arrayElementHashCodes; // Only validator access this ISchemaValidator** validators; SizeType validatorCount; ISchemaValidator** patternPropertiesValidators; SizeType patternPropertiesValidatorCount; const SchemaType** patternPropertiesSchemas; SizeType patternPropertiesSchemaCount; PatternValidatorType valuePatternValidatorType; PatternValidatorType objectPatternValidatorType; SizeType arrayElementIndex; bool* propertyExist; bool inArray; bool valueUniqueness; bool arrayUniqueness; }; /////////////////////////////////////////////////////////////////////////////// // Schema template class Schema { public: typedef typename SchemaDocumentType::ValueType ValueType; typedef typename SchemaDocumentType::AllocatorType AllocatorType; typedef typename SchemaDocumentType::PointerType PointerType; typedef typename ValueType::EncodingType EncodingType; typedef typename EncodingType::Ch Ch; typedef SchemaValidationContext Context; typedef Schema SchemaType; typedef GenericValue SValue; friend class GenericSchemaDocument; Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : allocator_(allocator), typeless_(schemaDocument->GetTypeless()), enum_(), enumCount_(), not_(), type_((1 << kTotalSchemaType) - 1), // typeless validatorCount_(), properties_(), additionalPropertiesSchema_(), patternProperties_(), patternPropertyCount_(), propertyCount_(), minProperties_(), maxProperties_(SizeType(~0)), additionalProperties_(true), hasDependencies_(), hasRequired_(), hasSchemaDependencies_(), additionalItemsSchema_(), itemsList_(), itemsTuple_(), itemsTupleCount_(), minItems_(), maxItems_(SizeType(~0)), additionalItems_(true), uniqueItems_(false), pattern_(), minLength_(0), maxLength_(~SizeType(0)), exclusiveMinimum_(false), exclusiveMaximum_(false) { typedef typename SchemaDocumentType::ValueType ValueType; typedef typename ValueType::ConstValueIterator ConstValueIterator; typedef typename ValueType::ConstMemberIterator ConstMemberIterator; if (!value.IsObject()) return; if (const ValueType* v = GetMember(value, GetTypeString())) { type_ = 0; if (v->IsString()) AddType(*v); else if (v->IsArray()) for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) AddType(*itr); } if (const ValueType* v = GetMember(value, GetEnumString())) if (v->IsArray() && v->Size() > 0) { enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { typedef Hasher > EnumHasherType; char buffer[256 + 24]; MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); EnumHasherType h(&hasherAllocator, 256); itr->Accept(h); enum_[enumCount_++] = h.GetHashCode(); } } if (schemaDocument) { AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); } if (const ValueType* v = GetMember(value, GetNotString())) { schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); notValidatorIndex_ = validatorCount_; validatorCount_++; } // Object const ValueType* properties = GetMember(value, GetPropertiesString()); const ValueType* required = GetMember(value, GetRequiredString()); const ValueType* dependencies = GetMember(value, GetDependenciesString()); { // Gather properties from properties/required/dependencies SValue allProperties(kArrayType); if (properties && properties->IsObject()) for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) AddUniqueElement(allProperties, itr->name); if (required && required->IsArray()) for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) if (itr->IsString()) AddUniqueElement(allProperties, *itr); if (dependencies && dependencies->IsObject()) for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { AddUniqueElement(allProperties, itr->name); if (itr->value.IsArray()) for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) if (i->IsString()) AddUniqueElement(allProperties, *i); } if (allProperties.Size() > 0) { propertyCount_ = allProperties.Size(); properties_ = static_cast(allocator_->Malloc(sizeof(Property) * propertyCount_)); for (SizeType i = 0; i < propertyCount_; i++) { new (&properties_[i]) Property(); properties_[i].name = allProperties[i]; properties_[i].schema = typeless_; } } } if (properties && properties->IsObject()) { PointerType q = p.Append(GetPropertiesString(), allocator_); for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { SizeType index; if (FindPropertyIndex(itr->name, &index)) schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); } } if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { PointerType q = p.Append(GetPatternPropertiesString(), allocator_); patternProperties_ = static_cast(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); patternPropertyCount_ = 0; for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { new (&patternProperties_[patternPropertyCount_]) PatternProperty(); patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); patternPropertyCount_++; } } if (required && required->IsArray()) for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) if (itr->IsString()) { SizeType index; if (FindPropertyIndex(*itr, &index)) { properties_[index].required = true; hasRequired_ = true; } } if (dependencies && dependencies->IsObject()) { PointerType q = p.Append(GetDependenciesString(), allocator_); hasDependencies_ = true; for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { SizeType sourceIndex; if (FindPropertyIndex(itr->name, &sourceIndex)) { if (itr->value.IsArray()) { properties_[sourceIndex].dependencies = static_cast(allocator_->Malloc(sizeof(bool) * propertyCount_)); std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { SizeType targetIndex; if (FindPropertyIndex(*targetItr, &targetIndex)) properties_[sourceIndex].dependencies[targetIndex] = true; } } else if (itr->value.IsObject()) { hasSchemaDependencies_ = true; schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; validatorCount_++; } } } } if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { if (v->IsBool()) additionalProperties_ = v->GetBool(); else if (v->IsObject()) schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); } AssignIfExist(minProperties_, value, GetMinPropertiesString()); AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); // Array if (const ValueType* v = GetMember(value, GetItemsString())) { PointerType q = p.Append(GetItemsString(), allocator_); if (v->IsObject()) // List validation schemaDocument->CreateSchema(&itemsList_, q, *v, document); else if (v->IsArray()) { // Tuple validation itemsTuple_ = static_cast(allocator_->Malloc(sizeof(const Schema*) * v->Size())); SizeType index = 0; for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); } } AssignIfExist(minItems_, value, GetMinItemsString()); AssignIfExist(maxItems_, value, GetMaxItemsString()); if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { if (v->IsBool()) additionalItems_ = v->GetBool(); else if (v->IsObject()) schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); } AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); // String AssignIfExist(minLength_, value, GetMinLengthString()); AssignIfExist(maxLength_, value, GetMaxLengthString()); if (const ValueType* v = GetMember(value, GetPatternString())) pattern_ = CreatePattern(*v); // Number if (const ValueType* v = GetMember(value, GetMinimumString())) if (v->IsNumber()) minimum_.CopyFrom(*v, *allocator_); if (const ValueType* v = GetMember(value, GetMaximumString())) if (v->IsNumber()) maximum_.CopyFrom(*v, *allocator_); AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); if (const ValueType* v = GetMember(value, GetMultipleOfString())) if (v->IsNumber() && v->GetDouble() > 0.0) multipleOf_.CopyFrom(*v, *allocator_); } ~Schema() { AllocatorType::Free(enum_); if (properties_) { for (SizeType i = 0; i < propertyCount_; i++) properties_[i].~Property(); AllocatorType::Free(properties_); } if (patternProperties_) { for (SizeType i = 0; i < patternPropertyCount_; i++) patternProperties_[i].~PatternProperty(); AllocatorType::Free(patternProperties_); } AllocatorType::Free(itemsTuple_); #if RAPIDJSON_SCHEMA_HAS_REGEX if (pattern_) { pattern_->~RegexType(); AllocatorType::Free(pattern_); } #endif } bool BeginValue(Context& context) const { if (context.inArray) { if (uniqueItems_) context.valueUniqueness = true; if (itemsList_) context.valueSchema = itemsList_; else if (itemsTuple_) { if (context.arrayElementIndex < itemsTupleCount_) context.valueSchema = itemsTuple_[context.arrayElementIndex]; else if (additionalItemsSchema_) context.valueSchema = additionalItemsSchema_; else if (additionalItems_) context.valueSchema = typeless_; else RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); } else context.valueSchema = typeless_; context.arrayElementIndex++; } return true; } RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { if (context.patternPropertiesValidatorCount > 0) { bool otherValid = false; SizeType count = context.patternPropertiesValidatorCount; if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) otherValid = context.patternPropertiesValidators[--count]->IsValid(); bool patternValid = true; for (SizeType i = 0; i < count; i++) if (!context.patternPropertiesValidators[i]->IsValid()) { patternValid = false; break; } if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { if (!patternValid) RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); } else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { if (!patternValid || !otherValid) RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); } else if (!patternValid && !otherValid) // kPatternValidatorWithAdditionalProperty) RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); } if (enum_) { const uint64_t h = context.factory.GetHashCode(context.hasher); for (SizeType i = 0; i < enumCount_; i++) if (enum_[i] == h) goto foundEnum; RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); foundEnum:; } if (allOf_.schemas) for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) if (!context.validators[i]->IsValid()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); if (anyOf_.schemas) { for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) if (context.validators[i]->IsValid()) goto foundAny; RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); foundAny:; } if (oneOf_.schemas) { bool oneValid = false; for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) if (context.validators[i]->IsValid()) { if (oneValid) RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); else oneValid = true; } if (!oneValid) RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); } if (not_ && context.validators[notValidatorIndex_]->IsValid()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); return true; } bool Null(Context& context) const { if (!(type_ & (1 << kNullSchemaType))) RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); return CreateParallelValidator(context); } bool Bool(Context& context, bool) const { if (!(type_ & (1 << kBooleanSchemaType))) RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); return CreateParallelValidator(context); } bool Int(Context& context, int i) const { if (!CheckInt(context, i)) return false; return CreateParallelValidator(context); } bool Uint(Context& context, unsigned u) const { if (!CheckUint(context, u)) return false; return CreateParallelValidator(context); } bool Int64(Context& context, int64_t i) const { if (!CheckInt(context, i)) return false; return CreateParallelValidator(context); } bool Uint64(Context& context, uint64_t u) const { if (!CheckUint(context, u)) return false; return CreateParallelValidator(context); } bool Double(Context& context, double d) const { if (!(type_ & (1 << kNumberSchemaType))) RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) return false; if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) return false; if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) return false; return CreateParallelValidator(context); } bool String(Context& context, const Ch* str, SizeType length, bool) const { if (!(type_ & (1 << kStringSchemaType))) RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); if (minLength_ != 0 || maxLength_ != SizeType(~0)) { SizeType count; if (internal::CountStringCodePoint(str, length, &count)) { if (count < minLength_) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); if (count > maxLength_) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); } } if (pattern_ && !IsPatternMatch(pattern_, str, length)) RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); return CreateParallelValidator(context); } bool StartObject(Context& context) const { if (!(type_ & (1 << kObjectSchemaType))) RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); if (hasDependencies_ || hasRequired_) { context.propertyExist = static_cast(context.factory.MallocState(sizeof(bool) * propertyCount_)); std::memset(context.propertyExist, 0, sizeof(bool) * propertyCount_); } if (patternProperties_) { // pre-allocate schema array SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType context.patternPropertiesSchemas = static_cast(context.factory.MallocState(sizeof(const SchemaType*) * count)); context.patternPropertiesSchemaCount = 0; std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); } return CreateParallelValidator(context); } bool Key(Context& context, const Ch* str, SizeType len, bool) const { if (patternProperties_) { context.patternPropertiesSchemaCount = 0; for (SizeType i = 0; i < patternPropertyCount_; i++) if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) { context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; context.valueSchema = typeless_; } } SizeType index; if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { if (context.patternPropertiesSchemaCount > 0) { context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; context.valueSchema = typeless_; context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; } else context.valueSchema = properties_[index].schema; if (context.propertyExist) context.propertyExist[index] = true; return true; } if (additionalPropertiesSchema_) { if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; context.valueSchema = typeless_; context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; } else context.valueSchema = additionalPropertiesSchema_; return true; } else if (additionalProperties_) { context.valueSchema = typeless_; return true; } if (context.patternPropertiesSchemaCount == 0) // patternProperties are not additional properties RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); return true; } bool EndObject(Context& context, SizeType memberCount) const { if (hasRequired_) for (SizeType index = 0; index < propertyCount_; index++) if (properties_[index].required) if (!context.propertyExist[index]) RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); if (memberCount < minProperties_) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); if (memberCount > maxProperties_) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); if (hasDependencies_) { for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) if (context.propertyExist[sourceIndex]) { if (properties_[sourceIndex].dependencies) { for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) if (properties_[sourceIndex].dependencies[targetIndex] && !context.propertyExist[targetIndex]) RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); } else if (properties_[sourceIndex].dependenciesSchema) if (!context.validators[properties_[sourceIndex].dependenciesValidatorIndex]->IsValid()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); } } return true; } bool StartArray(Context& context) const { if (!(type_ & (1 << kArraySchemaType))) RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); context.arrayElementIndex = 0; context.inArray = true; return CreateParallelValidator(context); } bool EndArray(Context& context, SizeType elementCount) const { context.inArray = false; if (elementCount < minItems_) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); if (elementCount > maxItems_) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); return true; } // Generate functions for string literal according to Ch #define RAPIDJSON_STRING_(name, ...) \ static const ValueType& Get##name##String() {\ static const Ch s[] = { __VA_ARGS__, '\0' };\ static const ValueType v(s, static_cast(sizeof(s) / sizeof(Ch) - 1));\ return v;\ } RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') RAPIDJSON_STRING_(Not, 'n', 'o', 't') RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') #undef RAPIDJSON_STRING_ private: enum SchemaValueType { kNullSchemaType, kBooleanSchemaType, kObjectSchemaType, kArraySchemaType, kStringSchemaType, kNumberSchemaType, kIntegerSchemaType, kTotalSchemaType }; #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX typedef internal::GenericRegex RegexType; #elif RAPIDJSON_SCHEMA_USE_STDREGEX typedef std::basic_regex RegexType; #else typedef char RegexType; #endif struct SchemaArray { SchemaArray() : schemas(), count() {} ~SchemaArray() { AllocatorType::Free(schemas); } const SchemaType** schemas; SizeType begin; // begin index of context.validators SizeType count; }; template void AddUniqueElement(V1& a, const V2& v) { for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) if (*itr == v) return; V1 c(v, *allocator_); a.PushBack(c, *allocator_); } static const ValueType* GetMember(const ValueType& value, const ValueType& name) { typename ValueType::ConstMemberIterator itr = value.FindMember(name); return itr != value.MemberEnd() ? &(itr->value) : 0; } static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { if (const ValueType* v = GetMember(value, name)) if (v->IsBool()) out = v->GetBool(); } static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { if (const ValueType* v = GetMember(value, name)) if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) out = static_cast(v->GetUint64()); } void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { if (const ValueType* v = GetMember(value, name)) { if (v->IsArray() && v->Size() > 0) { PointerType q = p.Append(name, allocator_); out.count = v->Size(); out.schemas = static_cast(allocator_->Malloc(out.count * sizeof(const Schema*))); memset(out.schemas, 0, sizeof(Schema*)* out.count); for (SizeType i = 0; i < out.count; i++) schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); out.begin = validatorCount_; validatorCount_ += out.count; } } } #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX template RegexType* CreatePattern(const ValueType& value) { if (value.IsString()) { RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString()); if (!r->IsValid()) { r->~RegexType(); AllocatorType::Free(r); r = 0; } return r; } return 0; } static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { GenericRegexSearch rs(*pattern); return rs.Search(str); } #elif RAPIDJSON_SCHEMA_USE_STDREGEX template RegexType* CreatePattern(const ValueType& value) { if (value.IsString()) try { return new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); } catch (const std::regex_error&) { } return 0; } static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { std::match_results r; return std::regex_search(str, str + length, r, *pattern); } #else template RegexType* CreatePattern(const ValueType&) { return 0; } static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } #endif // RAPIDJSON_SCHEMA_USE_STDREGEX void AddType(const ValueType& type) { if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); } bool CreateParallelValidator(Context& context) const { if (enum_ || context.arrayUniqueness) context.hasher = context.factory.CreateHasher(); if (validatorCount_) { RAPIDJSON_ASSERT(context.validators == 0); context.validators = static_cast(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); context.validatorCount = validatorCount_; if (allOf_.schemas) CreateSchemaValidators(context, allOf_); if (anyOf_.schemas) CreateSchemaValidators(context, anyOf_); if (oneOf_.schemas) CreateSchemaValidators(context, oneOf_); if (not_) context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); if (hasSchemaDependencies_) { for (SizeType i = 0; i < propertyCount_; i++) if (properties_[i].dependenciesSchema) context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); } } return true; } void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { for (SizeType i = 0; i < schemas.count; i++) context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); } // O(n) bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { SizeType len = name.GetStringLength(); const Ch* str = name.GetString(); for (SizeType index = 0; index < propertyCount_; index++) if (properties_[index].name.GetStringLength() == len && (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) { *outIndex = index; return true; } return false; } bool CheckInt(Context& context, int64_t i) const { if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); if (!minimum_.IsNull()) { if (minimum_.IsInt64()) { if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); } else if (minimum_.IsUint64()) { RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64() } else if (!CheckDoubleMinimum(context, static_cast(i))) return false; } if (!maximum_.IsNull()) { if (maximum_.IsInt64()) { if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); } else if (maximum_.IsUint64()) { } /* do nothing */ // i <= max(int64_t) < maximum_.GetUint64() else if (!CheckDoubleMaximum(context, static_cast(i))) return false; } if (!multipleOf_.IsNull()) { if (multipleOf_.IsUint64()) { if (static_cast(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); } else if (!CheckDoubleMultipleOf(context, static_cast(i))) return false; } return true; } bool CheckUint(Context& context, uint64_t i) const { if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); if (!minimum_.IsNull()) { if (minimum_.IsUint64()) { if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); } else if (minimum_.IsInt64()) /* do nothing */; // i >= 0 > minimum.Getint64() else if (!CheckDoubleMinimum(context, static_cast(i))) return false; } if (!maximum_.IsNull()) { if (maximum_.IsUint64()) { if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); } else if (maximum_.IsInt64()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_ else if (!CheckDoubleMaximum(context, static_cast(i))) return false; } if (!multipleOf_.IsNull()) { if (multipleOf_.IsUint64()) { if (i % multipleOf_.GetUint64() != 0) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); } else if (!CheckDoubleMultipleOf(context, static_cast(i))) return false; } return true; } bool CheckDoubleMinimum(Context& context, double d) const { if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); return true; } bool CheckDoubleMaximum(Context& context, double d) const { if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); return true; } bool CheckDoubleMultipleOf(Context& context, double d) const { double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); double q = std::floor(a / b); double r = a - q * b; if (r > 0.0) RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); return true; } struct Property { Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} ~Property() { AllocatorType::Free(dependencies); } SValue name; const SchemaType* schema; const SchemaType* dependenciesSchema; SizeType dependenciesValidatorIndex; bool* dependencies; bool required; }; struct PatternProperty { PatternProperty() : schema(), pattern() {} ~PatternProperty() { if (pattern) { pattern->~RegexType(); AllocatorType::Free(pattern); } } const SchemaType* schema; RegexType* pattern; }; AllocatorType* allocator_; const SchemaType* typeless_; uint64_t* enum_; SizeType enumCount_; SchemaArray allOf_; SchemaArray anyOf_; SchemaArray oneOf_; const SchemaType* not_; unsigned type_; // bitmask of kSchemaType SizeType validatorCount_; SizeType notValidatorIndex_; Property* properties_; const SchemaType* additionalPropertiesSchema_; PatternProperty* patternProperties_; SizeType patternPropertyCount_; SizeType propertyCount_; SizeType minProperties_; SizeType maxProperties_; bool additionalProperties_; bool hasDependencies_; bool hasRequired_; bool hasSchemaDependencies_; const SchemaType* additionalItemsSchema_; const SchemaType* itemsList_; const SchemaType** itemsTuple_; SizeType itemsTupleCount_; SizeType minItems_; SizeType maxItems_; bool additionalItems_; bool uniqueItems_; RegexType* pattern_; SizeType minLength_; SizeType maxLength_; SValue minimum_; SValue maximum_; SValue multipleOf_; bool exclusiveMinimum_; bool exclusiveMaximum_; }; template struct TokenHelper { RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { *documentStack.template Push() = '/'; char buffer[21]; size_t length = static_cast((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); for (size_t i = 0; i < length; i++) *documentStack.template Push() = static_cast(buffer[i]); } }; // Partial specialized version for char to prevent buffer copying. template struct TokenHelper { RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { if (sizeof(SizeType) == 4) { char *buffer = documentStack.template Push(1 + 10); // '/' + uint *buffer++ = '/'; const char* end = internal::u32toa(index, buffer); documentStack.template Pop(static_cast(10 - (end - buffer))); } else { char *buffer = documentStack.template Push(1 + 20); // '/' + uint64 *buffer++ = '/'; const char* end = internal::u64toa(index, buffer); documentStack.template Pop(static_cast(20 - (end - buffer))); } } }; } // namespace internal /////////////////////////////////////////////////////////////////////////////// // IGenericRemoteSchemaDocumentProvider template class IGenericRemoteSchemaDocumentProvider { public: typedef typename SchemaDocumentType::Ch Ch; virtual ~IGenericRemoteSchemaDocumentProvider() {} virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; }; /////////////////////////////////////////////////////////////////////////////// // GenericSchemaDocument //! JSON schema document. /*! A JSON schema document is a compiled version of a JSON schema. It is basically a tree of internal::Schema. \note This is an immutable class (i.e. its instance cannot be modified after construction). \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. \tparam Allocator Allocator type for allocating memory of this document. */ template class GenericSchemaDocument { public: typedef ValueT ValueType; typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProviderType; typedef Allocator AllocatorType; typedef typename ValueType::EncodingType EncodingType; typedef typename EncodingType::Ch Ch; typedef internal::Schema SchemaType; typedef GenericPointer PointerType; friend class internal::Schema; template friend class GenericSchemaValidator; //! Constructor. /*! Compile a JSON document into schema document. \param document A JSON document as source. \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. \param allocator An optional allocator instance for allocating memory. Can be null. */ explicit GenericSchemaDocument(const ValueType& document, IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : remoteProvider_(remoteProvider), allocator_(allocator), ownAllocator_(), root_(), typeless_(), schemaMap_(allocator, kInitialSchemaMapSize), schemaRef_(allocator, kInitialSchemaRefSize) { if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); typeless_ = static_cast(allocator_->Malloc(sizeof(SchemaType))); new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), 0); // Generate root schema, it will call CreateSchema() to create sub-schemas, // And call AddRefSchema() if there are $ref. CreateSchemaRecursive(&root_, PointerType(), document, document); // Resolve $ref while (!schemaRef_.Empty()) { SchemaRefEntry* refEntry = schemaRef_.template Pop(1); if (const SchemaType* s = GetSchema(refEntry->target)) { if (refEntry->schema) *refEntry->schema = s; // Create entry in map if not exist if (!GetSchema(refEntry->source)) { new (schemaMap_.template Push()) SchemaEntry(refEntry->source, const_cast(s), false, allocator_); } } else if (refEntry->schema) *refEntry->schema = typeless_; refEntry->~SchemaRefEntry(); } RAPIDJSON_ASSERT(root_ != 0); schemaRef_.ShrinkToFit(); // Deallocate all memory for ref } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS //! Move constructor in C++11 GenericSchemaDocument(GenericSchemaDocument&& rhs) RAPIDJSON_NOEXCEPT : remoteProvider_(rhs.remoteProvider_), allocator_(rhs.allocator_), ownAllocator_(rhs.ownAllocator_), root_(rhs.root_), typeless_(rhs.typeless_), schemaMap_(std::move(rhs.schemaMap_)), schemaRef_(std::move(rhs.schemaRef_)) { rhs.remoteProvider_ = 0; rhs.allocator_ = 0; rhs.ownAllocator_ = 0; rhs.typeless_ = 0; } #endif //! Destructor ~GenericSchemaDocument() { while (!schemaMap_.Empty()) schemaMap_.template Pop(1)->~SchemaEntry(); if (typeless_) { typeless_->~SchemaType(); Allocator::Free(typeless_); } RAPIDJSON_DELETE(ownAllocator_); } //! Get the root schema. const SchemaType& GetRoot() const { return *root_; } private: //! Prohibit copying GenericSchemaDocument(const GenericSchemaDocument&); //! Prohibit assignment GenericSchemaDocument& operator=(const GenericSchemaDocument&); struct SchemaRefEntry { SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} PointerType source; PointerType target; const SchemaType** schema; }; struct SchemaEntry { SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} ~SchemaEntry() { if (owned) { schema->~SchemaType(); Allocator::Free(schema); } } PointerType pointer; SchemaType* schema; bool owned; }; void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { if (schema) *schema = typeless_; if (v.GetType() == kObjectType) { const SchemaType* s = GetSchema(pointer); if (!s) CreateSchema(schema, pointer, v, document); for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); } else if (v.GetType() == kArrayType) for (SizeType i = 0; i < v.Size(); i++) CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); } void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { RAPIDJSON_ASSERT(pointer.IsValid()); if (v.IsObject()) { if (!HandleRefSchema(pointer, schema, v, document)) { SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); new (schemaMap_.template Push()) SchemaEntry(pointer, s, true, allocator_); if (schema) *schema = s; } } } bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; static const ValueType kRefValue(kRefString, 4); typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); if (itr == v.MemberEnd()) return false; if (itr->value.IsString()) { SizeType len = itr->value.GetStringLength(); if (len > 0) { const Ch* s = itr->value.GetString(); SizeType i = 0; while (i < len && s[i] != '#') // Find the first # i++; if (i > 0) { // Remote reference, resolve immediately if (remoteProvider_) { if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { PointerType pointer(&s[i], len - i, allocator_); if (pointer.IsValid()) { if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { if (schema) *schema = sc; return true; } } } } } else if (s[i] == '#') { // Local reference, defer resolution PointerType pointer(&s[i], len - i, allocator_); if (pointer.IsValid()) { if (const ValueType* nv = pointer.Get(document)) if (HandleRefSchema(source, schema, *nv, document)) return true; new (schemaRef_.template Push()) SchemaRefEntry(source, pointer, schema, allocator_); return true; } } } } return false; } const SchemaType* GetSchema(const PointerType& pointer) const { for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) if (pointer == target->pointer) return target->schema; return 0; } PointerType GetPointer(const SchemaType* schema) const { for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) if (schema == target->schema) return target->pointer; return PointerType(); } const SchemaType* GetTypeless() const { return typeless_; } static const size_t kInitialSchemaMapSize = 64; static const size_t kInitialSchemaRefSize = 64; IRemoteSchemaDocumentProviderType* remoteProvider_; Allocator *allocator_; Allocator *ownAllocator_; const SchemaType* root_; //!< Root schema. SchemaType* typeless_; internal::Stack schemaMap_; // Stores created Pointer -> Schemas internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref }; //! GenericSchemaDocument using Value type. typedef GenericSchemaDocument SchemaDocument; //! IGenericRemoteSchemaDocumentProvider using SchemaDocument. typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; /////////////////////////////////////////////////////////////////////////////// // GenericSchemaValidator //! JSON Schema Validator. /*! A SAX style JSON schema validator. It uses a \c GenericSchemaDocument to validate SAX events. It delegates the incoming SAX events to an output handler. The default output handler does nothing. It can be reused multiple times by calling \c Reset(). \tparam SchemaDocumentType Type of schema document. \tparam OutputHandler Type of output handler. Default handler does nothing. \tparam StateAllocator Allocator for storing the internal validation states. */ template < typename SchemaDocumentType, typename OutputHandler = BaseReaderHandler, typename StateAllocator = CrtAllocator> class GenericSchemaValidator : public internal::ISchemaStateFactory, public internal::ISchemaValidator { public: typedef typename SchemaDocumentType::SchemaType SchemaType; typedef typename SchemaDocumentType::PointerType PointerType; typedef typename SchemaType::EncodingType EncodingType; typedef typename EncodingType::Ch Ch; //! Constructor without output handler. /*! \param schemaDocument The schema document to conform to. \param allocator Optional allocator for storing internal validation states. \param schemaStackCapacity Optional initial capacity of schema path stack. \param documentStackCapacity Optional initial capacity of document path stack. */ GenericSchemaValidator( const SchemaDocumentType& schemaDocument, StateAllocator* allocator = 0, size_t schemaStackCapacity = kDefaultSchemaStackCapacity, size_t documentStackCapacity = kDefaultDocumentStackCapacity) : schemaDocument_(&schemaDocument), root_(schemaDocument.GetRoot()), stateAllocator_(allocator), ownStateAllocator_(0), schemaStack_(allocator, schemaStackCapacity), documentStack_(allocator, documentStackCapacity), outputHandler_(CreateNullHandler()), valid_(true) #if RAPIDJSON_SCHEMA_VERBOSE , depth_(0) #endif { } //! Constructor with output handler. /*! \param schemaDocument The schema document to conform to. \param allocator Optional allocator for storing internal validation states. \param schemaStackCapacity Optional initial capacity of schema path stack. \param documentStackCapacity Optional initial capacity of document path stack. */ GenericSchemaValidator( const SchemaDocumentType& schemaDocument, OutputHandler& outputHandler, StateAllocator* allocator = 0, size_t schemaStackCapacity = kDefaultSchemaStackCapacity, size_t documentStackCapacity = kDefaultDocumentStackCapacity) : schemaDocument_(&schemaDocument), root_(schemaDocument.GetRoot()), stateAllocator_(allocator), ownStateAllocator_(0), schemaStack_(allocator, schemaStackCapacity), documentStack_(allocator, documentStackCapacity), outputHandler_(outputHandler), nullHandler_(0), valid_(true) #if RAPIDJSON_SCHEMA_VERBOSE , depth_(0) #endif { } //! Destructor. ~GenericSchemaValidator() { Reset(); if (nullHandler_) { nullHandler_->~OutputHandler(); StateAllocator::Free(nullHandler_); } RAPIDJSON_DELETE(ownStateAllocator_); } //! Reset the internal states. void Reset() { while (!schemaStack_.Empty()) PopSchema(); documentStack_.Clear(); valid_ = true; } //! Checks whether the current state is valid. // Implementation of ISchemaValidator virtual bool IsValid() const { return valid_; } //! Gets the JSON pointer pointed to the invalid schema. PointerType GetInvalidSchemaPointer() const { return schemaStack_.Empty() ? PointerType() : schemaDocument_->GetPointer(&CurrentSchema()); } //! Gets the keyword of invalid schema. const Ch* GetInvalidSchemaKeyword() const { return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; } //! Gets the JSON pointer pointed to the invalid value. PointerType GetInvalidDocumentPointer() const { return documentStack_.Empty() ? PointerType() : PointerType(documentStack_.template Bottom(), documentStack_.GetSize() / sizeof(Ch)); } #if RAPIDJSON_SCHEMA_VERBOSE #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ RAPIDJSON_MULTILINEMACRO_BEGIN\ *documentStack_.template Push() = '\0';\ documentStack_.template Pop(1);\ internal::PrintInvalidDocument(documentStack_.template Bottom());\ RAPIDJSON_MULTILINEMACRO_END #else #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() #endif #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ if (!valid_) return false; \ if (!BeginValue() || !CurrentSchema().method arg1) {\ RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ return valid_ = false;\ } #define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ for (Context* context = schemaStack_.template Bottom(); context != schemaStack_.template End(); context++) {\ if (context->hasher)\ static_cast(context->hasher)->method arg2;\ if (context->validators)\ for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ static_cast(context->validators[i_])->method arg2;\ if (context->patternPropertiesValidators)\ for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ static_cast(context->patternPropertiesValidators[i_])->method arg2;\ } #define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ return valid_ = EndValue() && outputHandler_.method arg2 #define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext() ), ( )); } bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } bool RawNumber(const Ch* str, SizeType length, bool copy) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } bool String(const Ch* str, SizeType length, bool copy) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } bool StartObject() { RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); return valid_ = outputHandler_.StartObject(); } bool Key(const Ch* str, SizeType len, bool copy) { if (!valid_) return false; AppendToken(str, len); if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); return valid_ = outputHandler_.Key(str, len, copy); } bool EndObject(SizeType memberCount) { if (!valid_) return false; RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); } bool StartArray() { RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); return valid_ = outputHandler_.StartArray(); } bool EndArray(SizeType elementCount) { if (!valid_) return false; RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); } #undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ #undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ #undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ #undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ // Implementation of ISchemaStateFactory virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, #if RAPIDJSON_SCHEMA_VERBOSE depth_ + 1, #endif &GetStateAllocator()); } virtual void DestroySchemaValidator(ISchemaValidator* validator) { GenericSchemaValidator* v = static_cast(validator); v->~GenericSchemaValidator(); StateAllocator::Free(v); } virtual void* CreateHasher() { return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); } virtual uint64_t GetHashCode(void* hasher) { return static_cast(hasher)->GetHashCode(); } virtual void DestroryHasher(void* hasher) { HasherType* h = static_cast(hasher); h->~HasherType(); StateAllocator::Free(h); } virtual void* MallocState(size_t size) { return GetStateAllocator().Malloc(size); } virtual void FreeState(void* p) { StateAllocator::Free(p); } private: typedef typename SchemaType::Context Context; typedef GenericValue, StateAllocator> HashCodeArray; typedef internal::Hasher HasherType; GenericSchemaValidator( const SchemaDocumentType& schemaDocument, const SchemaType& root, #if RAPIDJSON_SCHEMA_VERBOSE unsigned depth, #endif StateAllocator* allocator = 0, size_t schemaStackCapacity = kDefaultSchemaStackCapacity, size_t documentStackCapacity = kDefaultDocumentStackCapacity) : schemaDocument_(&schemaDocument), root_(root), stateAllocator_(allocator), ownStateAllocator_(0), schemaStack_(allocator, schemaStackCapacity), documentStack_(allocator, documentStackCapacity), outputHandler_(CreateNullHandler()), valid_(true) #if RAPIDJSON_SCHEMA_VERBOSE , depth_(depth) #endif { } StateAllocator& GetStateAllocator() { if (!stateAllocator_) stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator)(); return *stateAllocator_; } bool BeginValue() { if (schemaStack_.Empty()) PushSchema(root_); else { if (CurrentContext().inArray) internal::TokenHelper, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); if (!CurrentSchema().BeginValue(CurrentContext())) return false; SizeType count = CurrentContext().patternPropertiesSchemaCount; const SchemaType** sa = CurrentContext().patternPropertiesSchemas; typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; bool valueUniqueness = CurrentContext().valueUniqueness; RAPIDJSON_ASSERT(CurrentContext().valueSchema); PushSchema(*CurrentContext().valueSchema); if (count > 0) { CurrentContext().objectPatternValidatorType = patternValidatorType; ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; va = static_cast(MallocState(sizeof(ISchemaValidator*) * count)); for (SizeType i = 0; i < count; i++) va[validatorCount++] = CreateSchemaValidator(*sa[i]); } CurrentContext().arrayUniqueness = valueUniqueness; } return true; } bool EndValue() { if (!CurrentSchema().EndValue(CurrentContext())) return false; #if RAPIDJSON_SCHEMA_VERBOSE GenericStringBuffer sb; schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); *documentStack_.template Push() = '\0'; documentStack_.template Pop(1); internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); #endif uint64_t h = CurrentContext().arrayUniqueness ? static_cast(CurrentContext().hasher)->GetHashCode() : 0; PopSchema(); if (!schemaStack_.Empty()) { Context& context = CurrentContext(); if (context.valueUniqueness) { HashCodeArray* a = static_cast(context.arrayElementHashCodes); if (!a) CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) if (itr->GetUint64() == h) RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); a->PushBack(h, GetStateAllocator()); } } // Remove the last token of document pointer while (!documentStack_.Empty() && *documentStack_.template Pop(1) != '/') ; return true; } void AppendToken(const Ch* str, SizeType len) { documentStack_.template Reserve(1 + len * 2); // worst case all characters are escaped as two characters *documentStack_.template PushUnsafe() = '/'; for (SizeType i = 0; i < len; i++) { if (str[i] == '~') { *documentStack_.template PushUnsafe() = '~'; *documentStack_.template PushUnsafe() = '0'; } else if (str[i] == '/') { *documentStack_.template PushUnsafe() = '~'; *documentStack_.template PushUnsafe() = '1'; } else *documentStack_.template PushUnsafe() = str[i]; } } RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, &schema); } RAPIDJSON_FORCEINLINE void PopSchema() { Context* c = schemaStack_.template Pop(1); if (HashCodeArray* a = static_cast(c->arrayElementHashCodes)) { a->~HashCodeArray(); StateAllocator::Free(a); } c->~Context(); } const SchemaType& CurrentSchema() const { return *schemaStack_.template Top()->schema; } Context& CurrentContext() { return *schemaStack_.template Top(); } const Context& CurrentContext() const { return *schemaStack_.template Top(); } OutputHandler& CreateNullHandler() { return *(nullHandler_ = new (GetStateAllocator().Malloc(sizeof(OutputHandler))) OutputHandler); } static const size_t kDefaultSchemaStackCapacity = 1024; static const size_t kDefaultDocumentStackCapacity = 256; const SchemaDocumentType* schemaDocument_; const SchemaType& root_; StateAllocator* stateAllocator_; StateAllocator* ownStateAllocator_; internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) internal::Stack documentStack_; //!< stack to store the current path of validating document (Ch) OutputHandler& outputHandler_; OutputHandler* nullHandler_; bool valid_; #if RAPIDJSON_SCHEMA_VERBOSE unsigned depth_; #endif }; typedef GenericSchemaValidator SchemaValidator; /////////////////////////////////////////////////////////////////////////////// // SchemaValidatingReader //! A helper class for parsing with validation. /*! This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). \tparam parseFlags Combination of \ref ParseFlag. \tparam InputStream Type of input stream, implementing Stream concept. \tparam SourceEncoding Encoding of the input stream. \tparam SchemaDocumentType Type of schema document. \tparam StackAllocator Allocator type for stack. */ template < unsigned parseFlags, typename InputStream, typename SourceEncoding, typename SchemaDocumentType = SchemaDocument, typename StackAllocator = CrtAllocator> class SchemaValidatingReader { public: typedef typename SchemaDocumentType::PointerType PointerType; typedef typename InputStream::Ch Ch; //! Constructor /*! \param is Input stream. \param sd Schema document. */ SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), isValid_(true) {} template bool operator()(Handler& handler) { GenericReader reader; GenericSchemaValidator validator(sd_, handler); parseResult_ = reader.template Parse(is_, validator); isValid_ = validator.IsValid(); if (isValid_) { invalidSchemaPointer_ = PointerType(); invalidSchemaKeyword_ = 0; invalidDocumentPointer_ = PointerType(); } else { invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); } return parseResult_; } const ParseResult& GetParseResult() const { return parseResult_; } bool IsValid() const { return isValid_; } const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } private: InputStream& is_; const SchemaDocumentType& sd_; ParseResult parseResult_; PointerType invalidSchemaPointer_; const Ch* invalidSchemaKeyword_; PointerType invalidDocumentPointer_; bool isValid_; }; RAPIDJSON_NAMESPACE_END RAPIDJSON_DIAG_POP #endif // RAPIDJSON_SCHEMA_H_ ================================================ FILE: third-party/rapidjson/stream.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #include "rapidjson.h" #ifndef RAPIDJSON_STREAM_H_ #define RAPIDJSON_STREAM_H_ #include "encodings.h" RAPIDJSON_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// // Stream /*! \class rapidjson::Stream \brief Concept for reading and writing characters. For read-only stream, no need to implement PutBegin(), Put(), Flush() and PutEnd(). For write-only stream, only need to implement Put() and Flush(). \code concept Stream { typename Ch; //!< Character type of the stream. //! Read the current character from stream without moving the read cursor. Ch Peek() const; //! Read the current character from stream and moving the read cursor to next character. Ch Take(); //! Get the current read cursor. //! \return Number of characters read from start. size_t Tell(); //! Begin writing operation at the current read pointer. //! \return The begin writer pointer. Ch* PutBegin(); //! Write a character. void Put(Ch c); //! Flush the buffer. void Flush(); //! End the writing operation. //! \param begin The begin write pointer returned by PutBegin(). //! \return Number of characters written. size_t PutEnd(Ch* begin); } \endcode */ //! Provides additional information for stream. /*! By using traits pattern, this type provides a default configuration for stream. For custom stream, this type can be specialized for other configuration. See TEST(Reader, CustomStringStream) in readertest.cpp for example. */ template struct StreamTraits { //! Whether to make local copy of stream for optimization during parsing. /*! By default, for safety, streams do not use local copy optimization. Stream that can be copied fast should specialize this, like StreamTraits. */ enum { copyOptimization = 0 }; }; //! Reserve n characters for writing to a stream. template inline void PutReserve(Stream& stream, size_t count) { (void)stream; (void)count; } //! Write character to a stream, presuming buffer is reserved. template inline void PutUnsafe(Stream& stream, typename Stream::Ch c) { stream.Put(c); } //! Put N copies of a character to a stream. template inline void PutN(Stream& stream, Ch c, size_t n) { PutReserve(stream, n); for (size_t i = 0; i < n; i++) PutUnsafe(stream, c); } /////////////////////////////////////////////////////////////////////////////// // StringStream //! Read-only string stream. /*! \note implements Stream concept */ template struct GenericStringStream { typedef typename Encoding::Ch Ch; GenericStringStream(const Ch *src) : src_(src), head_(src) {} Ch Peek() const { return *src_; } Ch Take() { return *src_++; } size_t Tell() const { return static_cast(src_ - head_); } Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } void Put(Ch) { RAPIDJSON_ASSERT(false); } void Flush() { RAPIDJSON_ASSERT(false); } size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } const Ch* src_; //!< Current read position. const Ch* head_; //!< Original head of the string. }; template struct StreamTraits > { enum { copyOptimization = 1 }; }; //! String stream with UTF8 encoding. typedef GenericStringStream > StringStream; /////////////////////////////////////////////////////////////////////////////// // InsituStringStream //! A read-write string stream. /*! This string stream is particularly designed for in-situ parsing. \note implements Stream concept */ template struct GenericInsituStringStream { typedef typename Encoding::Ch Ch; GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} // Read Ch Peek() { return *src_; } Ch Take() { return *src_++; } size_t Tell() { return static_cast(src_ - head_); } // Write void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } Ch* PutBegin() { return dst_ = src_; } size_t PutEnd(Ch* begin) { return static_cast(dst_ - begin); } void Flush() {} Ch* Push(size_t count) { Ch* begin = dst_; dst_ += count; return begin; } void Pop(size_t count) { dst_ -= count; } Ch* src_; Ch* dst_; Ch* head_; }; template struct StreamTraits > { enum { copyOptimization = 1 }; }; //! Insitu string stream with UTF8 encoding. typedef GenericInsituStringStream > InsituStringStream; RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_STREAM_H_ ================================================ FILE: third-party/rapidjson/stringbuffer.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_STRINGBUFFER_H_ #define RAPIDJSON_STRINGBUFFER_H_ #include "stream.h" #include "internal/stack.h" #if RAPIDJSON_HAS_CXX11_RVALUE_REFS #include // std::move #endif #include "internal/stack.h" #if defined(__clang__) RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(c++98-compat) #endif RAPIDJSON_NAMESPACE_BEGIN //! Represents an in-memory output stream. /*! \tparam Encoding Encoding of the stream. \tparam Allocator type for allocating memory buffer. \note implements Stream concept */ template class GenericStringBuffer { public: typedef typename Encoding::Ch Ch; GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} #if RAPIDJSON_HAS_CXX11_RVALUE_REFS GenericStringBuffer(GenericStringBuffer&& rhs) : stack_(std::move(rhs.stack_)) {} GenericStringBuffer& operator=(GenericStringBuffer&& rhs) { if (&rhs != this) stack_ = std::move(rhs.stack_); return *this; } #endif void Put(Ch c) { *stack_.template Push() = c; } void PutUnsafe(Ch c) { *stack_.template PushUnsafe() = c; } void Flush() {} void Clear() { stack_.Clear(); } void ShrinkToFit() { // Push and pop a null terminator. This is safe. *stack_.template Push() = '\0'; stack_.ShrinkToFit(); stack_.template Pop(1); } void Reserve(size_t count) { stack_.template Reserve(count); } Ch* Push(size_t count) { return stack_.template Push(count); } Ch* PushUnsafe(size_t count) { return stack_.template PushUnsafe(count); } void Pop(size_t count) { stack_.template Pop(count); } const Ch* GetString() const { // Push and pop a null terminator. This is safe. *stack_.template Push() = '\0'; stack_.template Pop(1); return stack_.template Bottom(); } //! Get the size of string in bytes in the string buffer. size_t GetSize() const { return stack_.GetSize(); } //! Get the length of string in Ch in the string buffer. size_t GetLength() const { return stack_.GetSize() / sizeof(Ch); } static const size_t kDefaultCapacity = 256; mutable internal::Stack stack_; private: // Prohibit copy constructor & assignment operator. GenericStringBuffer(const GenericStringBuffer&); GenericStringBuffer& operator=(const GenericStringBuffer&); }; //! String buffer with UTF8 encoding typedef GenericStringBuffer > StringBuffer; template inline void PutReserve(GenericStringBuffer& stream, size_t count) { stream.Reserve(count); } template inline void PutUnsafe(GenericStringBuffer& stream, typename Encoding::Ch c) { stream.PutUnsafe(c); } //! Implement specialized version of PutN() with memset() for better performance. template<> inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { std::memset(stream.stack_.Push(n), c, n * sizeof(c)); } RAPIDJSON_NAMESPACE_END #if defined(__clang__) RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_STRINGBUFFER_H_ ================================================ FILE: third-party/rapidjson/writer.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #ifndef RAPIDJSON_WRITER_H_ #define RAPIDJSON_WRITER_H_ #include "stream.h" #include "internal/meta.h" #include "internal/stack.h" #include "internal/strfunc.h" #include "internal/dtoa.h" #include "internal/itoa.h" #include "stringbuffer.h" #include // placement new #if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) #include #pragma intrinsic(_BitScanForward) #endif #ifdef RAPIDJSON_SSE42 #include #elif defined(RAPIDJSON_SSE2) #include #elif defined(RAPIDJSON_NEON) #include #endif #ifdef _MSC_VER RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant #endif #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(padded) RAPIDJSON_DIAG_OFF(unreachable-code) RAPIDJSON_DIAG_OFF(c++98-compat) #endif RAPIDJSON_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// // WriteFlag /*! \def RAPIDJSON_WRITE_DEFAULT_FLAGS \ingroup RAPIDJSON_CONFIG \brief User-defined kWriteDefaultFlags definition. User can define this as any \c WriteFlag combinations. */ #ifndef RAPIDJSON_WRITE_DEFAULT_FLAGS #define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNoFlags #endif //! Combination of writeFlags enum WriteFlag { kWriteNoFlags = 0, //!< No flags are set. kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings. kWriteNanAndInfFlag = 2, //!< Allow writing of Infinity, -Infinity and NaN. kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS }; //! JSON writer /*! Writer implements the concept Handler. It generates JSON text by events to an output os. User may programmatically calls the functions of a writer to generate JSON text. On the other side, a writer can also be passed to objects that generates events, for example Reader::Parse() and Document::Accept(). \tparam OutputStream Type of output stream. \tparam SourceEncoding Encoding of source string. \tparam TargetEncoding Encoding of output stream. \tparam StackAllocator Type of allocator for allocating memory of stack. \note implements Handler concept */ template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> class Writer { public: typedef typename SourceEncoding::Ch Ch; static const int kDefaultMaxDecimalPlaces = 324; //! Constructor /*! \param os Output stream. \param stackAllocator User supplied allocator. If it is null, it will create a private one. \param levelDepth Initial capacity of stack. */ explicit Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} explicit Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} #if RAPIDJSON_HAS_CXX11_RVALUE_REFS Writer(Writer&& rhs) : os_(rhs.os_), level_stack_(std::move(rhs.level_stack_)), maxDecimalPlaces_(rhs.maxDecimalPlaces_), hasRoot_(rhs.hasRoot_) { rhs.os_ = 0; } #endif //! Reset the writer with a new stream. /*! This function reset the writer with a new stream and default settings, in order to make a Writer object reusable for output multiple JSONs. \param os New output stream. \code Writer writer(os1); writer.StartObject(); // ... writer.EndObject(); writer.Reset(os2); writer.StartObject(); // ... writer.EndObject(); \endcode */ void Reset(OutputStream& os) { os_ = &os; hasRoot_ = false; level_stack_.Clear(); } //! Checks whether the output is a complete JSON. /*! A complete JSON has a complete root object or array. */ bool IsComplete() const { return hasRoot_ && level_stack_.Empty(); } int GetMaxDecimalPlaces() const { return maxDecimalPlaces_; } //! Sets the maximum number of decimal places for double output. /*! This setting truncates the output with specified number of decimal places. For example, \code writer.SetMaxDecimalPlaces(3); writer.StartArray(); writer.Double(0.12345); // "0.123" writer.Double(0.0001); // "0.0" writer.Double(1.234567890123456e30); // "1.234567890123456e30" (do not truncate significand for positive exponent) writer.Double(1.23e-4); // "0.0" (do truncate significand for negative exponent) writer.EndArray(); \endcode The default setting does not truncate any decimal places. You can restore to this setting by calling \code writer.SetMaxDecimalPlaces(Writer::kDefaultMaxDecimalPlaces); \endcode */ void SetMaxDecimalPlaces(int maxDecimalPlaces) { maxDecimalPlaces_ = maxDecimalPlaces; } /*!@name Implementation of Handler \see Handler */ //@{ bool Null() { Prefix(kNullType); return EndValue(WriteNull()); } bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return EndValue(WriteBool(b)); } bool Int(int i) { Prefix(kNumberType); return EndValue(WriteInt(i)); } bool Uint(unsigned u) { Prefix(kNumberType); return EndValue(WriteUint(u)); } bool Int64(int64_t i64) { Prefix(kNumberType); return EndValue(WriteInt64(i64)); } bool Uint64(uint64_t u64) { Prefix(kNumberType); return EndValue(WriteUint64(u64)); } //! Writes the given \c double value to the stream /*! \param d The value to be written. \return Whether it is succeed. */ bool Double(double d) { Prefix(kNumberType); return EndValue(WriteDouble(d)); } bool RawNumber(const Ch* str, SizeType length, bool copy = false) { RAPIDJSON_ASSERT(str != 0); (void)copy; Prefix(kNumberType); return EndValue(WriteString(str, length)); } bool String(const Ch* str, SizeType length, bool copy = false) { RAPIDJSON_ASSERT(str != 0); (void)copy; Prefix(kStringType); return EndValue(WriteString(str, length)); } #if RAPIDJSON_HAS_STDSTRING bool String(const std::basic_string& str) { return String(str.data(), SizeType(str.size())); } #endif bool StartObject() { Prefix(kObjectType); new (level_stack_.template Push()) Level(false); return WriteStartObject(); } bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } #if RAPIDJSON_HAS_STDSTRING bool Key(const std::basic_string& str) { return Key(str.data(), SizeType(str.size())); } #endif bool EndObject(SizeType memberCount = 0) { (void)memberCount; RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); // not inside an Object RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); // currently inside an Array, not Object RAPIDJSON_ASSERT(0 == level_stack_.template Top()->valueCount % 2); // Object has a Key without a Value level_stack_.template Pop(1); return EndValue(WriteEndObject()); } bool StartArray() { Prefix(kArrayType); new (level_stack_.template Push()) Level(true); return WriteStartArray(); } bool EndArray(SizeType elementCount = 0) { (void)elementCount; RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); level_stack_.template Pop(1); return EndValue(WriteEndArray()); } //@} /*! @name Convenience extensions */ //@{ //! Simpler but slower overload. bool String(const Ch* const& str) { return String(str, internal::StrLen(str)); } bool Key(const Ch* const& str) { return Key(str, internal::StrLen(str)); } //@} //! Write a raw JSON value. /*! For user to write a stringified JSON as a value. \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. \param length Length of the json. \param type Type of the root of json. */ bool RawValue(const Ch* json, size_t length, Type type) { RAPIDJSON_ASSERT(json != 0); Prefix(type); return EndValue(WriteRawValue(json, length)); } //! Flush the output stream. /*! Allows the user to flush the output stream immediately. */ void Flush() { os_->Flush(); } protected: //! Information for each nested level struct Level { Level(bool inArray_) : valueCount(0), inArray(inArray_) {} size_t valueCount; //!< number of values in this level bool inArray; //!< true if in array, otherwise in object }; static const size_t kDefaultLevelDepth = 32; bool WriteNull() { PutReserve(*os_, 4); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); return true; } bool WriteBool(bool b) { if (b) { PutReserve(*os_, 4); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'r'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'e'); } else { PutReserve(*os_, 5); PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 's'); PutUnsafe(*os_, 'e'); } return true; } bool WriteInt(int i) { char buffer[11]; const char* end = internal::i32toa(i, buffer); PutReserve(*os_, static_cast(end - buffer)); for (const char* p = buffer; p != end; ++p) PutUnsafe(*os_, static_cast(*p)); return true; } bool WriteUint(unsigned u) { char buffer[10]; const char* end = internal::u32toa(u, buffer); PutReserve(*os_, static_cast(end - buffer)); for (const char* p = buffer; p != end; ++p) PutUnsafe(*os_, static_cast(*p)); return true; } bool WriteInt64(int64_t i64) { char buffer[21]; const char* end = internal::i64toa(i64, buffer); PutReserve(*os_, static_cast(end - buffer)); for (const char* p = buffer; p != end; ++p) PutUnsafe(*os_, static_cast(*p)); return true; } bool WriteUint64(uint64_t u64) { char buffer[20]; char* end = internal::u64toa(u64, buffer); PutReserve(*os_, static_cast(end - buffer)); for (char* p = buffer; p != end; ++p) PutUnsafe(*os_, static_cast(*p)); return true; } bool WriteDouble(double d) { if (internal::Double(d).IsNanOrInf()) { if (!(writeFlags & kWriteNanAndInfFlag)) return false; if (internal::Double(d).IsNan()) { PutReserve(*os_, 3); PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); return true; } if (internal::Double(d).Sign()) { PutReserve(*os_, 9); PutUnsafe(*os_, '-'); } else PutReserve(*os_, 8); PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); return true; } char buffer[25]; char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); PutReserve(*os_, static_cast(end - buffer)); for (char* p = buffer; p != end; ++p) PutUnsafe(*os_, static_cast(*p)); return true; } bool WriteString(const Ch* str, SizeType length) { static const typename OutputStream::Ch hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; static const char escape[256] = { #define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //0 1 2 3 4 5 6 7 8 9 A B C D E F 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 Z16, Z16, // 30~4F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF #undef Z16 }; if (TargetEncoding::supportUnicode) PutReserve(*os_, 2 + length * 6); // "\uxxxx..." else PutReserve(*os_, 2 + length * 12); // "\uxxxx\uyyyy..." PutUnsafe(*os_, '\"'); GenericStringStream is(str); while (ScanWriteUnescapedString(is, length)) { const Ch c = is.Peek(); if (!TargetEncoding::supportUnicode && static_cast(c) >= 0x80) { // Unicode escaping unsigned codepoint; if (RAPIDJSON_UNLIKELY(!SourceEncoding::Decode(is, &codepoint))) return false; PutUnsafe(*os_, '\\'); PutUnsafe(*os_, 'u'); if (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0xFFFF)) { PutUnsafe(*os_, hexDigits[(codepoint >> 12) & 15]); PutUnsafe(*os_, hexDigits[(codepoint >> 8) & 15]); PutUnsafe(*os_, hexDigits[(codepoint >> 4) & 15]); PutUnsafe(*os_, hexDigits[(codepoint ) & 15]); } else { RAPIDJSON_ASSERT(codepoint >= 0x010000 && codepoint <= 0x10FFFF); // Surrogate pair unsigned s = codepoint - 0x010000; unsigned lead = (s >> 10) + 0xD800; unsigned trail = (s & 0x3FF) + 0xDC00; PutUnsafe(*os_, hexDigits[(lead >> 12) & 15]); PutUnsafe(*os_, hexDigits[(lead >> 8) & 15]); PutUnsafe(*os_, hexDigits[(lead >> 4) & 15]); PutUnsafe(*os_, hexDigits[(lead ) & 15]); PutUnsafe(*os_, '\\'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, hexDigits[(trail >> 12) & 15]); PutUnsafe(*os_, hexDigits[(trail >> 8) & 15]); PutUnsafe(*os_, hexDigits[(trail >> 4) & 15]); PutUnsafe(*os_, hexDigits[(trail ) & 15]); } } else if ((sizeof(Ch) == 1 || static_cast(c) < 256) && RAPIDJSON_UNLIKELY(escape[static_cast(c)])) { is.Take(); PutUnsafe(*os_, '\\'); PutUnsafe(*os_, static_cast(escape[static_cast(c)])); if (escape[static_cast(c)] == 'u') { PutUnsafe(*os_, '0'); PutUnsafe(*os_, '0'); PutUnsafe(*os_, hexDigits[static_cast(c) >> 4]); PutUnsafe(*os_, hexDigits[static_cast(c) & 0xF]); } } else if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? Transcoder::Validate(is, *os_) : Transcoder::TranscodeUnsafe(is, *os_)))) return false; } PutUnsafe(*os_, '\"'); return true; } bool ScanWriteUnescapedString(GenericStringStream& is, size_t length) { return RAPIDJSON_LIKELY(is.Tell() < length); } bool WriteStartObject() { os_->Put('{'); return true; } bool WriteEndObject() { os_->Put('}'); return true; } bool WriteStartArray() { os_->Put('['); return true; } bool WriteEndArray() { os_->Put(']'); return true; } bool WriteRawValue(const Ch* json, size_t length) { PutReserve(*os_, length); for (size_t i = 0; i < length; i++) { RAPIDJSON_ASSERT(json[i] != '\0'); PutUnsafe(*os_, json[i]); } return true; } void Prefix(Type type) { (void)type; if (RAPIDJSON_LIKELY(level_stack_.GetSize() != 0)) { // this value is not at root Level* level = level_stack_.template Top(); if (level->valueCount > 0) { if (level->inArray) os_->Put(','); // add comma if it is not the first element in array else // in object os_->Put((level->valueCount % 2 == 0) ? ',' : ':'); } if (!level->inArray && level->valueCount % 2 == 0) RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name level->valueCount++; } else { RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root. hasRoot_ = true; } } // Flush the value if it is the top level one. bool EndValue(bool ret) { if (RAPIDJSON_UNLIKELY(level_stack_.Empty())) // end of json text Flush(); return ret; } OutputStream* os_; internal::Stack level_stack_; int maxDecimalPlaces_; bool hasRoot_; private: // Prohibit copy constructor & assignment operator. Writer(const Writer&); Writer& operator=(const Writer&); }; // Full specialization for StringStream to prevent memory copying template<> inline bool Writer::WriteInt(int i) { char *buffer = os_->Push(11); const char* end = internal::i32toa(i, buffer); os_->Pop(static_cast(11 - (end - buffer))); return true; } template<> inline bool Writer::WriteUint(unsigned u) { char *buffer = os_->Push(10); const char* end = internal::u32toa(u, buffer); os_->Pop(static_cast(10 - (end - buffer))); return true; } template<> inline bool Writer::WriteInt64(int64_t i64) { char *buffer = os_->Push(21); const char* end = internal::i64toa(i64, buffer); os_->Pop(static_cast(21 - (end - buffer))); return true; } template<> inline bool Writer::WriteUint64(uint64_t u) { char *buffer = os_->Push(20); const char* end = internal::u64toa(u, buffer); os_->Pop(static_cast(20 - (end - buffer))); return true; } template<> inline bool Writer::WriteDouble(double d) { if (internal::Double(d).IsNanOrInf()) { // Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag). if (!(kWriteDefaultFlags & kWriteNanAndInfFlag)) return false; if (internal::Double(d).IsNan()) { PutReserve(*os_, 3); PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); return true; } if (internal::Double(d).Sign()) { PutReserve(*os_, 9); PutUnsafe(*os_, '-'); } else PutReserve(*os_, 8); PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); return true; } char *buffer = os_->Push(25); char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); os_->Pop(static_cast(25 - (end - buffer))); return true; } #if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) template<> inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { if (length < 16) return RAPIDJSON_LIKELY(is.Tell() < length); if (!RAPIDJSON_LIKELY(is.Tell() < length)) return false; const char* p = is.src_; const char* end = is.head_ + length; const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); if (nextAligned > end) return true; while (p != nextAligned) if (*p < 0x20 || *p == '\"' || *p == '\\') { is.src_ = p; return RAPIDJSON_LIKELY(is.Tell() < length); } else os_->PutUnsafe(*p++); // The rest of string using SIMD static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); for (; p != endAligned; p += 16) { const __m128i s = _mm_load_si128(reinterpret_cast(p)); const __m128i t1 = _mm_cmpeq_epi8(s, dq); const __m128i t2 = _mm_cmpeq_epi8(s, bs); const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); unsigned short r = static_cast(_mm_movemask_epi8(x)); if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped SizeType len; #ifdef _MSC_VER // Find the index of first escaped unsigned long offset; _BitScanForward(&offset, r); len = offset; #else len = static_cast(__builtin_ffs(r) - 1); #endif char* q = reinterpret_cast(os_->PushUnsafe(len)); for (size_t i = 0; i < len; i++) q[i] = p[i]; p += len; break; } _mm_storeu_si128(reinterpret_cast<__m128i *>(os_->PushUnsafe(16)), s); } is.src_ = p; return RAPIDJSON_LIKELY(is.Tell() < length); } #elif defined(RAPIDJSON_NEON) template<> inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { if (length < 16) return RAPIDJSON_LIKELY(is.Tell() < length); if (!RAPIDJSON_LIKELY(is.Tell() < length)) return false; const char* p = is.src_; const char* end = is.head_ + length; const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); if (nextAligned > end) return true; while (p != nextAligned) if (*p < 0x20 || *p == '\"' || *p == '\\') { is.src_ = p; return RAPIDJSON_LIKELY(is.Tell() < length); } else os_->PutUnsafe(*p++); // The rest of string using SIMD const uint8x16_t s0 = vmovq_n_u8('"'); const uint8x16_t s1 = vmovq_n_u8('\\'); const uint8x16_t s2 = vmovq_n_u8('\b'); const uint8x16_t s3 = vmovq_n_u8(32); for (; p != endAligned; p += 16) { const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); uint8x16_t x = vceqq_u8(s, s0); x = vorrq_u8(x, vceqq_u8(s, s1)); x = vorrq_u8(x, vceqq_u8(s, s2)); x = vorrq_u8(x, vcltq_u8(s, s3)); x = vrev64q_u8(x); // Rev in 64 uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract SizeType len = 0; bool escaped = false; if (low == 0) { if (high != 0) { unsigned lz = (unsigned)__builtin_clzll(high); len = 8 + (lz >> 3); escaped = true; } } else { unsigned lz = (unsigned)__builtin_clzll(low); len = lz >> 3; escaped = true; } if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped char* q = reinterpret_cast(os_->PushUnsafe(len)); for (size_t i = 0; i < len; i++) q[i] = p[i]; p += len; break; } vst1q_u8(reinterpret_cast(os_->PushUnsafe(16)), s); } is.src_ = p; return RAPIDJSON_LIKELY(is.Tell() < length); } #endif // RAPIDJSON_NEON RAPIDJSON_NAMESPACE_END #ifdef _MSC_VER RAPIDJSON_DIAG_POP #endif #ifdef __clang__ RAPIDJSON_DIAG_POP #endif #endif // RAPIDJSON_RAPIDJSON_H_ ================================================ FILE: unittest/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2 FATAL_ERROR) project("napa-unittest") set(NAPA_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..) # Require Cxx14 features set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Test Files file(GLOB_RECURSE TEST_FILES main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/module/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platform/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/settings/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/zone/*.cpp) # Source files under test file(GLOB_RECURSE SOURCE_FILES ${NAPA_ROOT}/src/module/core-modules/node/file-system-helpers.cpp ${NAPA_ROOT}/src/module/loader/module-resolver-cache.cpp ${NAPA_ROOT}/src/module/loader/module-resolver.cpp ${NAPA_ROOT}/src/platform/filesystem.cpp ${NAPA_ROOT}/src/platform/os.cpp ${NAPA_ROOT}/src/platform/process.cpp ${NAPA_ROOT}/src/settings/settings-parser.cpp ${NAPA_ROOT}/src/zone/simple-thread-pool.cpp ${NAPA_ROOT}/src/zone/timer.cpp) # The target name set(TARGET_NAME ${PROJECT_NAME}) # The generated test executable add_executable(${TARGET_NAME} ${TEST_FILES} ${SOURCE_FILES} ${PLATFORM_SOURCE_FILES}) # Compiler definitions target_compile_definitions(${TARGET_NAME} PRIVATE NAPA_LOG_DISABLED) # Include directories target_include_directories(${TARGET_NAME} PRIVATE ${NAPA_ROOT}/inc ${NAPA_ROOT}/src ${NAPA_ROOT}/third-party) # Set output directory for dll/libs set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/build/test RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/build/test ) # GCC/Clang: enable std::thread via -pthread option. if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) target_link_libraries(${TARGET_NAME} PRIVATE Threads::Threads) endif() # Copy module tests artifacts add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/module/test-files ${CMAKE_CURRENT_SOURCE_DIR}/build/test) ================================================ FILE: unittest/main.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #define CATCH_CONFIG_MAIN #include ================================================ FILE: unittest/module/file-system-helpers-tests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include using namespace napa; using namespace napa::module; TEST_CASE("File system helpers reads/writes a file correctly.", "[file-system-helpers]") { const std::string dirname("file-system-helpers-test"); const std::string filename(dirname + platform::DIR_SEPARATOR + "file-system-helpers-test.dat"); file_system_helpers::MkdirSync(dirname); file_system_helpers::WriteFileSync(filename, dirname.data(), dirname.length()); REQUIRE(file_system_helpers::ExistsSync(filename)); auto content = file_system_helpers::ReadFileSync(filename); REQUIRE(content.compare(dirname) == 0); file_system_helpers::MkdirSync(dirname + platform::DIR_SEPARATOR + "1"); file_system_helpers::MkdirSync(dirname + platform::DIR_SEPARATOR + "2"); auto names = file_system_helpers::ReadDirectorySync(dirname); REQUIRE(names.size() == 3); } ================================================ FILE: unittest/module/module-resolver-cache-tests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include using namespace napa; using namespace napa::module; namespace { ModuleResolverCache& GetModuleResolverCache() { static ModuleResolverCache cache; return cache; } } TEST_CASE("module resolver cache works correctly.", "[module-resolver-cache]") { SECTION("cache not hit") { ModuleResolverCache cache; auto result = cache.Lookup("a", "/home/napajs/test/"); REQUIRE(result.type == ModuleType::NONE); } SECTION("cache hit") { ModuleResolverCache cache; cache.Insert("a", "/home/napajs/test/", ModuleInfo{ModuleType::JAVASCRIPT, "/home/napajs/test/a.js", std::string()}); auto result = cache.Lookup("a", "/home/napajs/test/"); REQUIRE(result.type == ModuleType::JAVASCRIPT); REQUIRE(result.fullPath == "/home/napajs/test/a.js"); } } ================================================ FILE: unittest/module/module-resolver-tests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include #include #include #include using namespace napa; using namespace napa::module; namespace { ModuleResolver& GetModuleResolver() { // Set environment variables before module resolver is initialized. static bool setNodePath = []() { std::ostringstream oss; oss << (filesystem::CurrentDirectory() / "resolve-env").String() << platform::ENV_DELIMITER << (filesystem::CurrentDirectory() / "child" / "node_modules" / "child").String(); return platform::SetEnv("NODE_PATH", oss.str().c_str()); }(); REQUIRE(setNodePath); static ModuleResolver resolver; return resolver; } void ResolveIt(const char* module, const char* expected, ModuleType type) { auto detail = GetModuleResolver().Resolve(module); REQUIRE(detail.fullPath == expected); REQUIRE(detail.type == type); } void ResolveIt(const char* module, const filesystem::Path& expected, ModuleType type) { auto detail = GetModuleResolver().Resolve(module); REQUIRE(detail.fullPath == expected); REQUIRE(detail.type == type); } void ResolveIt(const char* module, const filesystem::Path& expected, ModuleType type, const filesystem::Path& package) { auto detail = GetModuleResolver().Resolve(module); REQUIRE(detail.fullPath == expected); REQUIRE(detail.type == type); REQUIRE(detail.packageJsonPath == package.String()); } } // Namespace of anonymous namespace. TEST_CASE("resolve node modules correctly.", "[module-resolver]") { auto currentPath = filesystem::CurrentDirectory(); SECTION("resolve built-in or core modules") { // Set build-in and core modules. GetModuleResolver().SetAsCoreModule("console"); GetModuleResolver().SetAsCoreModule("fs"); // 'console' built-in module. ResolveIt("console", "console", ModuleType::CORE); // 'fs' core module. ResolveIt("fs", "fs", ModuleType::CORE); // No 'built-in' module. ResolveIt("built-in", "", ModuleType::NONE); } SECTION("resolve as a file") { // Starts with "./" and a file exists. ResolveIt("./resolve-file", currentPath / "resolve-file", ModuleType::JAVASCRIPT); // Starts with "./" and a javascript file exists. ResolveIt("./resolve-file-js", currentPath / "resolve-file-js.js", ModuleType::JAVASCRIPT); // Starts with "./" and a json file exists. ResolveIt("./resolve-file-json", currentPath / "resolve-file-json.json", ModuleType::JSON); // Starts with "./" and a binary file exists. ResolveIt("./resolve-file-napa", currentPath / "resolve-file-napa.napa", ModuleType::NAPA); // Starts with "./" and a javascript file exists. ResolveIt("./resolve-file-js.js", currentPath / "resolve-file-js.js", ModuleType::JAVASCRIPT); // Starts with "./" and a json file exists. ResolveIt("./resolve-file-js.json", currentPath / "resolve-file-js.json", ModuleType::JSON); // Starts with "./" and a binary file exists. ResolveIt("./resolve-file-js.napa", currentPath / "resolve-file-js.napa", ModuleType::NAPA); // Starts with "./", but a file doesn't exist. ResolveIt("./resolve-file-non-existent", "", ModuleType::NONE); // Start with "./", but a file doesn't exist and it doesn't resolve to ".js" file. ResolveIt("./resolve-file-no.json", "", ModuleType::NONE); // Start with "./", but a file doesn't exist and it doesn't resolve to ".js" file. ResolveIt("./resolve-file-no.napa", "", ModuleType::NONE); // Starts with "../" and a file exists. std::ostringstream oss; oss << "../" << currentPath.Filename().String() << "/resolve-file"; ResolveIt(oss.str().c_str(), currentPath / "resolve-file", ModuleType::JAVASCRIPT); // Starts with "/", but a file doesn't exist. ResolveIt("/resolve-file", "", ModuleType::NONE); // Use absolute path and a file exists. ResolveIt((currentPath / "resolve-file").c_str(), currentPath / "resolve-file", ModuleType::JAVASCRIPT); } SECTION("resolve as a directory") { // Starts with "./" and a directory with package.json exists. ResolveIt("./resolve-directory/resolver", currentPath / "resolve-directory" / "resolver" / "resolve-file", ModuleType::JAVASCRIPT, currentPath / "resolve-directory" / "resolver" / "package.json"); // Starts with "./" and a directory with index.js exists. ResolveIt("./resolve-directory/resolver-js", currentPath / "resolve-directory" / "resolver-js" / "index.js", ModuleType::JAVASCRIPT); // Starts with "./" and a directory with index.json exists. ResolveIt("./resolve-directory/resolver-json", currentPath / "resolve-directory" / "resolver-json" / "index.json", ModuleType::JSON); // Starts with "./" and a directory with index.napa exists. ResolveIt("./resolve-directory/resolver-napa", currentPath / "resolve-directory" / "resolver-napa" / "index.napa", ModuleType::NAPA); // Non existent directory. ResolveIt("./resolve-directory/resolver-non-existent", "", ModuleType::NONE); // Starts with "../" and a directory exists. std::ostringstream oss; oss << "../" << currentPath.Filename().String() << "/resolve-directory/resolver"; ResolveIt(oss.str().c_str(), currentPath / "resolve-directory" / "resolver" / "resolve-file", ModuleType::JAVASCRIPT, currentPath / "resolve-directory" / "resolver" / "package.json"); // Starts with "/", but a file doesn't exist. ResolveIt("/resolve-directory/resolver", "", ModuleType::NONE); // Use absolute path and a file exists. ResolveIt((currentPath / "resolve-directory" / "resolver").c_str(), currentPath / "resolve-directory" / "resolver" / "resolve-file", ModuleType::JAVASCRIPT, currentPath / "resolve-directory" / "resolver" / "package.json"); } SECTION("resolve from node_modules") { // A file exists. ResolveIt("resolve-nm-file", currentPath / "node_modules" / "resolve-nm-file", ModuleType::JAVASCRIPT); // A javascript file exists. ResolveIt("resolve-nm-file-js", currentPath / "node_modules" / "resolve-nm-file-js.js", ModuleType::JAVASCRIPT); // A json file exists. ResolveIt("resolve-nm-file-json", currentPath / "node_modules" / "resolve-nm-file-json.json", ModuleType::JSON); // A binary file exists. ResolveIt("resolve-nm-file-napa", currentPath / "node_modules" / "resolve-nm-file-napa.napa", ModuleType::NAPA); // A directory with package.json exists. ResolveIt("resolver-nm", currentPath / "node_modules" / "resolver-nm" / "resolve-file", ModuleType::JAVASCRIPT, currentPath / "node_modules" / "resolver-nm" / "package.json"); // A directory with index.js exists. ResolveIt("resolver-nm-js", currentPath / "node_modules" / "resolver-nm-js" / "index.js", ModuleType::JAVASCRIPT); // A directory with index.json exists. ResolveIt("resolver-nm-json", currentPath / "node_modules" / "resolver-nm-json" / "index.json", ModuleType::JSON); // A directory with index.napa exists. ResolveIt("resolver-nm-napa", currentPath / "node_modules" / "resolver-nm-napa" / "index.napa", ModuleType::NAPA); } SECTION("resolve from NODE_PATH") { // Starts with "./" and a file exists. ResolveIt("./resolve-env-file", currentPath / "resolve-env" / "resolve-env-file", ModuleType::JAVASCRIPT); // Starts with "./" and a javascript file exists. ResolveIt("./resolve-env-file-js", currentPath / "resolve-env" / "resolve-env-file-js.js", ModuleType::JAVASCRIPT); // Starts with "./" and a json file exists. ResolveIt("./resolve-env-file-json", currentPath / "resolve-env" / "resolve-env-file-json.json", ModuleType::JSON); // Starts with "./" and a binary file exists. ResolveIt("./resolve-env-file-napa", currentPath / "resolve-env" / "resolve-env-file-napa.napa", ModuleType::NAPA); // Starts with "../" and a file exists. ResolveIt("../resolve-env/resolve-env-file", currentPath / "resolve-env" / "resolve-env-file", ModuleType::JAVASCRIPT); // Starts with "./" and a directory with package.json exists. ResolveIt("./resolver-env", currentPath / "resolve-env" / "resolver-env" / "resolve-file", ModuleType::JAVASCRIPT, currentPath / "resolve-env" / "resolver-env" / "package.json"); // Starts with "./" and a directory with index.js exists. ResolveIt("./resolver-env-js", currentPath /"resolve-env" / "resolver-env-js" / "index.js", ModuleType::JAVASCRIPT); // Starts with "./" and a directory with index.json exists. ResolveIt("./resolver-env-json", currentPath / "resolve-env" / "resolver-env-json" / "index.json", ModuleType::JSON); // Starts with "./" and a directory with index.napa exists. ResolveIt("./resolver-env-napa", currentPath / "resolve-env" / "resolver-env-napa" / "index.napa", ModuleType::NAPA); // Starts with "../" and a directory exists. ResolveIt("../resolve-env/resolver-env", currentPath / "resolve-env" / "resolver-env" / "resolve-file", ModuleType::JAVASCRIPT, currentPath / "resolve-env" / "resolver-env" / "package.json"); } SECTION("resolve from parent node_modules") { // A file exists. ResolveIt("resolve-cc-file", currentPath / "child" / "node_modules" / "resolve-cc-file", ModuleType::JAVASCRIPT); // A javascript file exists. ResolveIt("resolve-cc-file-js", currentPath / "child" / "node_modules" / "resolve-cc-file-js.js", ModuleType::JAVASCRIPT); // A json file exists. ResolveIt("resolve-cc-file-json", currentPath / "child" / "node_modules" / "resolve-cc-file-json.json", ModuleType::JSON); // A binary file exists. ResolveIt("resolve-cc-file-napa", currentPath / "child" / "node_modules" / "resolve-cc-file-napa.napa", ModuleType::NAPA); // A directory with package.json exists. ResolveIt("resolver-cc", currentPath / "child" / "node_modules" / "resolver-cc" / "resolve-file", ModuleType::JAVASCRIPT, currentPath / "child" / "node_modules" / "resolver-cc" / "package.json"); // A directory with index.js exists. ResolveIt("resolver-cc-js", currentPath / "child" / "node_modules" / "resolver-cc-js" / "index.js", ModuleType::JAVASCRIPT); // A directory with index.json exists. ResolveIt("resolver-cc-json", currentPath / "child" / "node_modules" / "resolver-cc-json" / "index.json", ModuleType::JSON); // A directory with index.napa exists. ResolveIt("resolver-cc-napa", currentPath / "child" / "node_modules" / "resolver-cc-napa" / "index.napa", ModuleType::NAPA); } } ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file-js.js ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file-js.json ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file-js.napa ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file-json.json ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file-json.napa ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file-napa.napa ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file.js ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file.json ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolve-nm-file.napa ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm/index.js ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm/index.json ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm/package.json ================================================ { "name": "@napajs/resolve-directory", "version": "0.0.1", "author": "napajs", "main": "resolve-file" } ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm/resolve-file ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm-js/index.js ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm-js/index.json ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm-js/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm-json/index.json ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm-json/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/node_modules/resolver-nm-napa/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver/index.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver/index.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver/package.json ================================================ { "name": "@napajs/resolve-directory", "version": "0.0.1", "author": "napajs", "main": "resolve-file" } ================================================ FILE: unittest/module/test-files/resolve-directory/resolver/resolve-file ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver-js/index.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver-js/index.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver-js/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver-json/index.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver-json/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-directory/resolver-napa/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file-js.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file-js.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file-js.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file-json.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file-json.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file-napa.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolve-env-file.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env/index.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env/index.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env/package.json ================================================ { "name": "@napajs/resolve-directory", "version": "0.0.1", "author": "napajs", "main": "resolve-file" } ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env/resolve-file ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env-js/index.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env-js/index.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env-js/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env-json/index.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env-json/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-env/resolver-env-napa/index.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file-js.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file-js.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file-js.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file-json.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file-json.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file-napa.napa ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file-no.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file.js ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file.json ================================================ true ================================================ FILE: unittest/module/test-files/resolve-file.napa ================================================ true ================================================ FILE: unittest/platform/filesystem-tests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include using namespace napa; TEST_CASE("filesystem::Path", "[Path]") { SECTION("Empty path") { filesystem::Path path; REQUIRE(path.IsEmpty()); REQUIRE(!path.IsAbsolute()); REQUIRE(path.IsRelative()); REQUIRE(!path.IsFilenameDot()); REQUIRE(!path.IsFilenameDotDot()); REQUIRE(path.Dirname() == "."); REQUIRE(!path.HasFilename()); REQUIRE(path.Basename().IsEmpty()); REQUIRE(!path.HasExtension()); REQUIRE(path.ReplaceExtension(".exe").IsEmpty()); REQUIRE(path.Normalize().IsEmpty()); REQUIRE(path.Absolute().IsEmpty()); REQUIRE(path.Relative("a/b").IsEmpty()); REQUIRE(path.Parent().IsEmpty()); REQUIRE(path.String() == ""); REQUIRE(path.DriveSpec().IsEmpty()); REQUIRE(!path.HasUncPrefix()); } SECTION("Explicit relative path") { // ../a/c filesystem::Path path("../a\\b/../c"); REQUIRE(!path.IsEmpty()); REQUIRE(!path.IsAbsolute()); REQUIRE(path.IsRelative()); REQUIRE(!path.IsFilenameDot()); REQUIRE(!path.IsFilenameDotDot()); REQUIRE(path.Dirname() == "../a\\b/.."); REQUIRE(path.Filename() == "c"); REQUIRE(path.Basename() == "c"); REQUIRE(path.Extension().IsEmpty()); REQUIRE(path.ReplaceExtension(".exe").Extension().IsEmpty()); #ifdef _WIN32 REQUIRE(path.Normalize() == "..\\a\\c"); REQUIRE(path.Parent().Normalize() == "..\\a"); REQUIRE(path.Parent().Parent().Normalize() == ".."); REQUIRE(path.Parent().Parent().Parent().Normalize() == "..\\.."); REQUIRE(path.String() == "..\\a\\c"); #else REQUIRE(path.Normalize() == "../a/c"); REQUIRE(path.Parent().Normalize() == "../a"); REQUIRE(path.Parent().Parent().Normalize() == ".."); REQUIRE(path.Parent().Parent().Parent().Normalize() == "../.."); REQUIRE(path.String() == "../a/c"); #endif REQUIRE(path.GenericForm() == "../a/c"); REQUIRE(path.DriveSpec().IsEmpty()); REQUIRE(!path.HasUncPrefix()); } SECTION("Implicit relative path") { filesystem::Path path("a/b\\c\\../d\\.\\e.txt"); REQUIRE(!path.IsEmpty()); REQUIRE(!path.IsAbsolute()); REQUIRE(path.IsRelative()); REQUIRE(!path.IsFilenameDot()); REQUIRE(!path.IsFilenameDotDot()); REQUIRE(path.Dirname() == "a/b\\c\\../d\\."); REQUIRE(path.Filename() == "e.txt"); REQUIRE(path.Basename() == "e"); REQUIRE(path.Extension() == ".txt"); REQUIRE(path.ReplaceExtension(".exe").Extension() == ".exe"); #ifdef _WIN32 REQUIRE(path.Normalize() == "a\\b\\d\\e.exe"); REQUIRE(path.Parent().Normalize() == "a\\b\\d"); REQUIRE(path.Relative("a/b/./e") == "..\\d\\e.exe"); REQUIRE(path.Relative("b/c/../e") == "..\\..\\a\\b\\d\\e.exe"); REQUIRE(path.Relative(filesystem::CurrentDirectory() / "e" / "f") == "..\\..\\a\\b\\d\\e.exe"); REQUIRE(path.Relative("F:\\a\\b") == path.Absolute().Normalize()); REQUIRE(path.String() == "a\\b\\d\\e.exe"); #else REQUIRE(path.Normalize() == "a/b/d/e.exe"); REQUIRE(path.Parent().Normalize() == "a/b/d"); REQUIRE(path.Relative("a/b/./e") == "../d/e.exe"); REQUIRE(path.Relative("b/c/../e") == "../../a/b/d/e.exe"); REQUIRE(path.Relative(filesystem::CurrentDirectory() / "e" / "f") == "../../a/b/d/e.exe"); REQUIRE(path.String() == "a/b/d/e.exe"); #endif REQUIRE(path.Parent().Parent().Parent().Normalize() == "a"); REQUIRE(path.Parent().Parent().Parent().Parent().Normalize() == "."); REQUIRE(path.Parent().Parent().Parent().Parent().Parent().Normalize() == ".."); REQUIRE(path.GenericForm() == "a/b/d/e.exe"); REQUIRE(path.DriveSpec().IsEmpty()); REQUIRE(!path.HasUncPrefix()); } SECTION("Absolute path (posix)") { // /a filesystem::Path path("/a/./b/../c/.."); REQUIRE(!path.IsEmpty()); REQUIRE(path.IsAbsolute()); REQUIRE(!path.IsRelative()); REQUIRE(!path.IsFilenameDot()); REQUIRE(path.IsFilenameDotDot()); REQUIRE(path.Dirname() == "/a/./b/../c"); REQUIRE(path.Filename() == ".."); REQUIRE(path.Basename() == ""); REQUIRE(path.Extension() == ""); REQUIRE(path.ReplaceExtension(".exe").Extension().IsEmpty()); #ifdef _WIN32 REQUIRE(path.Normalize() == "\\a"); REQUIRE(path.Parent().Normalize().String() == "\\"); REQUIRE(path.String() == "\\a"); #else REQUIRE(path.Normalize() == "/a"); REQUIRE(path.Parent().Normalize().String() == "/"); REQUIRE(path.String() == "/a"); #endif REQUIRE(path.Parent().Parent().Normalize().IsEmpty()); REQUIRE(path.Relative("/") == "a"); REQUIRE(path.Relative("/a/c") == ".."); REQUIRE(path.GenericForm() == "/a"); REQUIRE(path.DriveSpec() == ""); REQUIRE(!path.HasUncPrefix()); } #ifdef _WIN32 SECTION("Absolute path (Windows)") { // C:/a/c/. filesystem::Path path("C:\\a\\./b\\..\\c/."); REQUIRE(!path.IsEmpty()); REQUIRE(path.IsAbsolute()); REQUIRE(!path.IsRelative()); REQUIRE(path.IsFilenameDot()); REQUIRE(!path.IsFilenameDotDot()); REQUIRE(path.Dirname() == "C:\\a\\./b\\..\\c"); REQUIRE(path.Filename() == "."); REQUIRE(path.Basename() == ""); REQUIRE(path.Extension() == ""); REQUIRE(path.ReplaceExtension(".exe").Extension().IsEmpty()); REQUIRE(path.Normalize() == "C:\\a\\c"); REQUIRE(path.Parent().Normalize() == "C:\\a"); REQUIRE(path.Parent().Parent().Normalize() == "C:\\"); REQUIRE(path.Parent().Parent().Parent().Normalize().IsEmpty()); REQUIRE(path.Relative("C:/") == "a\\c"); REQUIRE(path.Relative("C:/a/c") == ""); REQUIRE(path.Relative("C:/b/c") == "..\\..\\a\\c"); REQUIRE(path.Relative("F:\\a\\b") == path.Absolute().Normalize()); REQUIRE(path.String() == "C:\\a\\c"); REQUIRE(path.GenericForm() == "C:/a/c"); REQUIRE(path.DriveSpec() == "C:"); REQUIRE(!path.HasUncPrefix()); } SECTION("UNC path (windows)") { filesystem::Path path("\\\\?\\c:\\a\\b\\..\\c"); REQUIRE(!path.IsEmpty()); REQUIRE(path.IsAbsolute()); REQUIRE(!path.IsRelative()); REQUIRE(!path.IsFilenameDot()); REQUIRE(!path.IsFilenameDotDot()); REQUIRE(path.Dirname() == "\\\\?\\c:\\a\\b\\.."); REQUIRE(path.Filename() == "c"); REQUIRE(path.Basename() == "c"); REQUIRE(path.Extension() == ""); REQUIRE(path.ReplaceExtension(".exe").Extension().IsEmpty()); REQUIRE(path.Normalize() == "\\\\?\\c:\\a\\c"); REQUIRE(path.Parent().Normalize() == "\\\\?\\c:\\a"); REQUIRE(path.Parent().Parent().Normalize() == "\\\\?\\c:\\"); REQUIRE(path.Parent().Parent().Parent().Normalize().IsEmpty()); REQUIRE(path.Relative("C:/") == "a\\c"); REQUIRE(path.Relative("C:/a/c") == ""); REQUIRE(path.Relative("C:/b/c") == "..\\..\\a\\c"); REQUIRE(path.Relative("F:\\a\\b") == path.Absolute().Normalize()); REQUIRE(path.String() == "\\\\?\\c:\\a\\c"); REQUIRE(path.GenericForm() == "\\\\?\\c:\\a\\c"); REQUIRE(path.DriveSpec() == "c:"); REQUIRE(path.HasUncPrefix()); } #endif } TEST_CASE("filesystem::PathIterator", "[PathIterator]") { SECTION("Path not exist") { filesystem::PathIterator it("/a/b/c"); REQUIRE(!it.Next()); REQUIRE(*it == ""); REQUIRE(it->IsEmpty()); } SECTION("Path exists") { auto exePath = filesystem::ProgramPath(); auto exeDir = exePath.Parent().Normalize(); filesystem::PathIterator it(exeDir); REQUIRE(it.Next()); bool hasDot = false; bool hasDotDot = false; bool hasExe = false; do { REQUIRE(!it->IsEmpty()); REQUIRE(it->Dirname().Absolute().Normalize() == exeDir); if (it->IsFilenameDot()) { hasDot = true; } if (it->IsFilenameDotDot()) { hasDotDot = true; } if (*it == exePath) { hasExe = true; } } while (it.Next()); REQUIRE(!hasDot); REQUIRE(!hasDotDot); REQUIRE(hasExe); } } TEST_CASE("filesystem", "[Operations]") { SECTION("Exists") { REQUIRE(filesystem::Exists(".")); REQUIRE(filesystem::Exists(filesystem::ProgramPath())); } SECTION("IsDirectory") { REQUIRE(filesystem::IsDirectory(".")); } SECTION("IsRegularFile") { REQUIRE(filesystem::IsRegularFile(filesystem::ProgramPath())); } SECTION("MakeDirectory") { REQUIRE(filesystem::MakeDirectory("./abc")); REQUIRE(filesystem::IsDirectory("./abc")); } SECTION("MakeDirectories") { REQUIRE(filesystem::MakeDirectories("./a/b/c")); REQUIRE(filesystem::IsDirectory("./a/b/c")); } } ================================================ FILE: unittest/run.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. var path = require('path'); var childProcess = require('child_process'); try { childProcess.execFileSync( path.join(__dirname, 'build/test/', process.platform === 'win32'? 'napa-unittest.exe': 'napa-unittest'), [], { cwd: path.join(__dirname, 'build/test'), stdio: 'inherit' } ); } catch(err) { process.exit(1); // Error } ================================================ FILE: unittest/settings/parser-tests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include using namespace napa; TEST_CASE("Parsing nothing doesn't fail", "[settings-parser]") { settings::PlatformSettings settings; REQUIRE(settings::ParseFromString("", settings)); std::vector args = { "dummy.exe" }; REQUIRE(settings::ParseFromConsole(static_cast(args.size()), args.data(), settings)); } TEST_CASE("Parsing from string", "[settings-parser]") { settings::PlatformSettings settings; settings.loggingProvider = ""; REQUIRE(settings::ParseFromString("--loggingProvider myProvider", settings)); REQUIRE(settings.loggingProvider == "myProvider"); } TEST_CASE("Parsing from console", "[settings-parser]") { settings::PlatformSettings settings; settings.loggingProvider = ""; std::vector args = { "dummy.exe", "--loggingProvider", "myProvider" }; REQUIRE(settings::ParseFromConsole(static_cast(args.size()), args.data(), settings)); REQUIRE(settings.loggingProvider == "myProvider"); } TEST_CASE("Parsing non existing setting fails", "[settings-parser]") { settings::PlatformSettings settings; REQUIRE(settings::ParseFromString("--thisSettingDoesNotExist noValue", settings) == false); } TEST_CASE("Parsing does not change defaults if setting is not provided", "[settings-parser]") { settings::PlatformSettings settings; settings.metricProvider = "myMetricProvider"; REQUIRE(settings::ParseFromString("--loggingProvider myProvider", settings)); REQUIRE(settings.metricProvider == "myMetricProvider"); } TEST_CASE("Parsing with extra white spaces succeeds", "[settings-parser]") { settings::PlatformSettings settings; settings.loggingProvider = ""; REQUIRE(settings::ParseFromString(" --loggingProvider \t myProvider \t\t ", settings)); REQUIRE(settings.loggingProvider == "myProvider"); } TEST_CASE("Parsing with empty string succeeds", "[settings-parser]") { settings::PlatformSettings settings; REQUIRE(settings::ParseFromString("", settings) == true); } TEST_CASE("Parsing with different value type fails", "[settings-parser]") { settings::ZoneSettings settings; REQUIRE(settings::ParseFromString("--workers five", settings) == false); } ================================================ FILE: unittest/utils/string-tests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include using namespace napa; TEST_CASE("utils", "[string]") { SECTION("ReplaceAll - No match") { std::string str = "no match at all"; utils::string::ReplaceAll(str, "some", "other"); REQUIRE(str == "no match at all"); } SECTION("ReplaceAll - Matched") { std::string str = "this is a test"; utils::string::ReplaceAll(str, "is", "at"); REQUIRE(str == "that at a test"); } SECTION("ReplaceAllCopy") { auto str = utils::string::ReplaceAllCopy("this is a test", "is", "at"); REQUIRE(str == "that at a test"); } SECTION("Split: no compress") { std::vector items; utils::string::Split(" this is a \ttest ", items, " \t", false); std::vector expected = { "", "this", "is", "a", "", "", "test", "" }; REQUIRE(items == expected); } SECTION("Split: compress") { std::vector items; utils::string::Split(" this is a \ttest ", items, " \t", true); std::vector expected = { "this", "is", "a", "test" }; REQUIRE(items == expected); } SECTION("Split: iterator with compress") { std::string str = " this is a \ttest "; std::vector items; utils::string::Split(str.begin() + 5, str.end(), items, " \t", true); std::vector expected = { "is", "a", "test" }; REQUIRE(items == expected); } SECTION("ToLower") { std::string str = "This Is A Test"; utils::string::ToLower(str); REQUIRE(str == "this is a test"); } SECTION("ToLowerCopy") { REQUIRE(utils::string::ToLowerCopy("THIs Is A TesT") == "this is a test"); } SECTION("ToUpper") { std::string str = "This Is A Test"; utils::string::ToUpper(str); REQUIRE(str == "THIS IS A TEST"); } SECTION("ToUpperCopy") { REQUIRE(utils::string::ToUpperCopy("THIs Is A TesT") == "THIS IS A TEST"); } SECTION("CaseInsensitiveCompare") { REQUIRE(utils::string::CaseInsensitiveCompare("abc", "abd") < 0); REQUIRE(utils::string::CaseInsensitiveCompare("abc", "ABc") == 0); REQUIRE(utils::string::CaseInsensitiveCompare("abc", "AB") > 0); } SECTION("CaseInsensitiveEquals") { REQUIRE(!utils::string::CaseInsensitiveEquals("abc", "abd")); REQUIRE(utils::string::CaseInsensitiveEquals("abc", "ABc")); REQUIRE(!utils::string::CaseInsensitiveEquals("abc", "AB")); } } ================================================ FILE: unittest/zone/scheduler-tests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include #include #include #include using namespace napa; using namespace napa::zone; using namespace napa::settings; class TestTask : public Task { public: TestTask(std::function callback = []() {}) : numberOfExecutions(0), lastExecutedWorkerId(99), _callback(std::move(callback)) {} void SetCurrentWorkerId(WorkerId id) { lastExecutedWorkerId = id; } virtual void Execute() override { numberOfExecutions++; _callback(); } std::atomic numberOfExecutions; std::atomic lastExecutedWorkerId; private: std::function _callback; }; template class TestWorker { public: TestWorker(WorkerId id, const ZoneSettings &settings, std::function setupCompleteCallback, std::function idleCallback) : _id(id) { numberOfWorkers++; _idleNotificationCallback = idleCallback; setupCompleteCallback(id); } ~TestWorker() { for (auto& fut : _futures) { fut.get(); } } void Start() { _idleNotificationCallback(_id); } void Schedule(std::shared_ptr task, SchedulePhase phase=SchedulePhase::DefaultPhase) { auto testTask = std::dynamic_pointer_cast(task); testTask->SetCurrentWorkerId(_id); _futures.emplace_back(std::async(std::launch::async, [this, task]() { task->Execute(); _idleNotificationCallback(_id); })); } static uint32_t numberOfWorkers; private: WorkerId _id; std::vector> _futures; std::function _idleNotificationCallback; }; template uint32_t TestWorker::numberOfWorkers = 0; TEST_CASE("scheduler creates correct number of worker", "[scheduler]") { ZoneSettings settings; settings.workers = 3; auto scheduler = std::make_unique>>(settings, [](WorkerId) {}); REQUIRE(TestWorker<1>::numberOfWorkers == settings.workers); } TEST_CASE("scheduler dispatches worker setup complete callback correctly", "[scheduler]") { ZoneSettings settings; settings.workers = 3; WorkerId idSum = 0; auto scheduler = std::make_unique>>(settings, [&idSum](WorkerId id) { idSum += id; }); REQUIRE(idSum == settings.workers * (settings.workers - 1) / 2); } TEST_CASE("scheduler assigns tasks correctly", "[scheduler]") { ZoneSettings settings; settings.workers = 3; auto scheduler = std::make_unique>>(settings, [](WorkerId) {}); auto task = std::make_shared(); SECTION("schedules on exactly one worker") { scheduler->Schedule(task); scheduler = nullptr; // force draining all scheduled tasks REQUIRE(task->numberOfExecutions == 1); } SECTION("schedule on a specific worker") { scheduler->ScheduleOnWorker(2, task); scheduler = nullptr; // force draining all scheduled tasks REQUIRE(task->numberOfExecutions == 1); REQUIRE(task->lastExecutedWorkerId == 2); } SECTION("schedule on all workers") { scheduler->ScheduleOnAllWorkers(task); scheduler = nullptr; // force draining all scheduled tasks REQUIRE(task->numberOfExecutions == settings.workers); } } TEST_CASE("scheduler distributes and schedules all tasks", "[scheduler]") { ZoneSettings settings; settings.workers = 4; auto scheduler = std::make_unique>>(settings, [](WorkerId) {}); std::vector> tasks; for (size_t i = 0; i < 1000; i++) { auto task = std::make_shared(); tasks.push_back(task); scheduler->Schedule(task); } scheduler = nullptr; // force draining all scheduled tasks std::vector scheduledWorkersFlags = { false, false, false, false }; size_t notRun = 0; for (size_t i = 0; i < 1000; i++) { // Make sure that each task was executed once if (tasks[i]->numberOfExecutions == 0) { ++notRun; } //REQUIRE(tasks[i]->numberOfExecutions == 1); scheduledWorkersFlags[tasks[i]->lastExecutedWorkerId] = true; } REQUIRE(notRun == 0); // Make sure that all workers were participating for (auto flag: scheduledWorkersFlags) { REQUIRE(flag); } } ================================================ FILE: unittest/zone/timer-tests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include #include "zone/timer.h" #include #include #include using namespace napa::zone; using namespace std::chrono; using namespace std::chrono_literals; TEST_CASE("timer is triggered after provided timeout", "[timer][!mayfail]") { std::promise promise; auto future = promise.get_future(); Timer timer([&promise]() { promise.set_value(high_resolution_clock::now()); }, 50ms); auto startTime = high_resolution_clock::now(); timer.Start(); auto status = future.wait_for(100ms); REQUIRE(status != std::future_status::timeout); REQUIRE(future.get() - startTime >= 50ms); } TEST_CASE("timer is not called if stopped", "[timer]") { std::promise promise; auto future = promise.get_future(); Timer timer([&promise]() { promise.set_value(); }, 200ms); timer.Start(); std::this_thread::sleep_for(100ms); timer.Stop(); auto status = future.wait_for(200ms); REQUIRE(status == std::future_status::timeout); } TEST_CASE("timers are called by order", "[timer]") { std::atomic callOrder(0); std::promise promise1; auto future1 = promise1.get_future(); Timer timer1([&promise1, &callOrder]() { promise1.set_value(++callOrder); }, 300ms); std::promise promise2; auto future2 = promise2.get_future(); Timer timer2([&promise2, &callOrder]() { promise2.set_value(++callOrder); }, 200ms); std::promise promise3; auto future3 = promise3.get_future(); Timer timer3([&promise3, &callOrder]() { promise3.set_value(++callOrder); }, 100ms); timer1.Start(); timer2.Start(); timer3.Start(); // The order of which the timer callbacks are expected. REQUIRE(future1.get() == 3); REQUIRE(future2.get() == 2); REQUIRE(future3.get() == 1); }