Mercurial > hg > orthanc-stone
changeset 520:7a16fb9a4ba5 am-touch-events
merge bgo-commands-codegen
author | Alain Mazy <alain@mazy.be> |
---|---|
date | Tue, 12 Mar 2019 14:50:14 +0100 |
parents | 77e0eb83ff63 (current diff) 17106b29ed6d (diff) |
children | 6c745c59eea3 |
files | Applications/Samples/samples-library.js README |
diffstat | 81 files changed, 12963 insertions(+), 416 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Tue Feb 26 12:58:03 2019 +0100 +++ b/.hgignore Tue Mar 12 14:50:14 2019 +0100 @@ -4,4 +4,18 @@ Applications/Samples/ThirdPartyDownloads/ Applications/Samples/build-wasm/ Applications/Samples/build-web/ +Applications/Samples/node_modules/ +Resources/CommandTool/protoc-tests/node_modules/ +Resources/CommandTool/protoc-tests/generated_js/ +Resources/CommandTool/protoc-tests/generated_ts/ +Resources/CommandTool/flatc-tests/basic/build/ .vscode/ +Resources/CodeGeneration/__pycache__ +Resources/CodeGeneration/build/ +Resources/CodeGeneration/build_browser/ +Resources/CodeGeneration/testCppHandler/build/ +Resources/CodeGeneration/testCppHandler/build_msbuild/ +syntax: glob +Resources/CodeGeneration/testWasmIntegrated/build-wasm/ +Resources/CodeGeneration/testWasmIntegrated/build-tsc/ +Resources/CodeGeneration/testWasmIntegrated/build-final/
--- a/Applications/Commands/ICommand.h Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Commands/ICommand.h Tue Mar 12 14:50:14 2019 +0100 @@ -35,8 +35,9 @@ : name_(name) {} public: + virtual ~ICommand() + {} virtual void Execute() = 0; - virtual ~ICommand() {} // virtual void Configure(const Json::Value& arguments) = 0; const std::string& GetName() const {
--- a/Applications/Generic/NativeStoneApplicationContext.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Generic/NativeStoneApplicationContext.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -51,7 +51,7 @@ stopped_(true), updateDelayInMs_(100) // By default, 100ms between each refresh of the content { - srand(time(NULL)); + srand(static_cast<unsigned int>(time(NULL))); }
--- a/Applications/IStoneApplication.h Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/IStoneApplication.h Tue Mar 12 14:50:14 2019 +0100 @@ -66,6 +66,8 @@ virtual BaseCommandBuilder& GetCommandBuilder() = 0; + virtual void HandleSerializedMessage(const char* data) {}; + virtual void ExecuteCommand(ICommand& command) { }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/CMakeLists.txt.old Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,248 @@ +# Usage: see README file + +cmake_minimum_required(VERSION 2.8.3) + +# Automatically link Qt executables to qtmain target on Windows +# ("OLD" == do not link) +if(POLICY CMP0020) + cmake_policy(SET CMP0020 OLD) +endif() + +# Only interpret if() arguments as variables or keywords when unquoted. +# NEW = do NOT dereference *quoted* variables +if(POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) +endif() + +project(OrthancStone) + +include(../../Resources/CMake/OrthancStoneParameters.cmake) + +#set(ENABLE_DCMTK ON) + +set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") +set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") +set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") + +# TODO: replace or compute STONE_SOURCES_DIR from CMAKE_CURRENT_LIST_FILE + +if (ENABLE_WASM) + ##################################################################### + ## Configuration of the Emscripten compiler for WebAssembly target + ##################################################################### + + set(WASM_FLAGS "-s WASM=1 -O0 -g0") + message("*****************************************************************************") + message("WARNING: optimizations are disabled in emcc!!! Enable them for production use") + message("*****************************************************************************") + set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + + # Handling of memory + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000") # 512MB + resize + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize + + # To debug exceptions + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") + + add_definitions(-DORTHANC_ENABLE_WASM=1) + set(ORTHANC_SANDBOXED ON) + +elseif (ENABLE_QT OR ENABLE_SDL) + + set(ENABLE_NATIVE ON) + set(ORTHANC_SANDBOXED OFF) + set(ENABLE_CRYPTO_OPTIONS ON) + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_WEB_CLIENT ON) + +endif() + +##################################################################### +## Configuration for Orthanc +##################################################################### + +# include(../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.4.1") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +##################################################################### +## Build all the sample applications +##################################################################### + +include_directories(${ORTHANC_STONE_ROOT}) + +# files common to all samples +list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ) + +if (ENABLE_QT) + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleQtApplicationRunner.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.cpp + ) + + ORTHANC_QT_WRAP_UI(SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.ui + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.ui + ) + + ORTHANC_QT_WRAP_CPP(SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.h + ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.h + ) +endif() + +if (ENABLE_NATIVE) + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainNative.cpp + ) + +elseif (ENABLE_WASM) + + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp + ${STONE_WASM_SOURCES} + ) +endif() + + +macro(BuildSingleFileSample Target Header Sample) + add_executable(${Target} + ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} + ${SAMPLE_APPLICATIONS_SOURCES} + ) + set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) + target_link_libraries(${Target} OrthancStone) + + if (ENABLE_QT AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) + message("(ENABLE_QT and (CMAKE_SYSTEM_NAME matches \"Windows\")) is true") + add_custom_command( + TARGET ${Target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core> $<TARGET_FILE_DIR:${Target}> + COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets> $<TARGET_FILE_DIR:${Target}> + COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui> $<TARGET_FILE_DIR:${Target}> + ) + endif() +endmacro() + +#BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1) +#BuildSingleFileSample(OrthancStoneTestPattern TestPatternApplication.h 2) +BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) +#BuildSingleFileSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) +#BuildSingleFileSample(OrthancStoneBasicPetCtFusion 5) +#BuildSingleFileSample(OrthancStoneSynchronizedSeries 6) +#BuildSingleFileSample(OrthancStoneLayoutPetCtFusion 7) +BuildSingleFileSample(OrthancStoneSimpleViewerSingleFile SimpleViewerApplicationSingleFile.h 8) # we keep that one just as a sample before we convert another sample to this pattern +BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) + +##### SimpleViewer sample (Qt and WASM only) ####### + +if (ENABLE_QT OR ENABLE_WASM) + + # GenerateCodeFromFlatBufferSchema("${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ApplicationCommands.fbs") + + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${FLATC_AUTOGENERATED_SOURCES}) + message(STATUS "SIMPLE_VIEWER_APPLICATION_SOURCES = ${SIMPLE_VIEWER_APPLICATION_SOURCES}") + message(STATUS "FLATC_AUTOGENERATED_SOURCES = ${FLATC_AUTOGENERATED_SOURCES}") + + if (ENABLE_QT) + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/mainQt.cpp + ) + + ORTHANC_QT_WRAP_UI(SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui + ) + + ORTHANC_QT_WRAP_CPP(SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.h + ) + +elseif (ENABLE_WASM) + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp + ${STONE_WASM_SOURCES} + ) + endif() + + add_executable(OrthancStoneSimpleViewer + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Messages.h + ${SIMPLE_VIEWER_APPLICATION_SOURCES} + ) + target_link_libraries(OrthancStoneSimpleViewer OrthancStone) + +endif() + +##################################################################### +## Build the unit tests +##################################################################### + +if (ENABLE_NATIVE) + add_executable(UnitTests + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp + ) + + target_link_libraries(UnitTests OrthancStone) +endif() + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + +find_package(Doxygen) +if (DOXYGEN_FOUND) + configure_file( + ${ORTHANC_STONE_ROOT}/Resources/OrthancStone.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + @ONLY) + + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + COMMENT "Generating documentation with Doxygen" VERBATIM + ) +else() + message("Doxygen not found. The documentation will not be built.") +endif()
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Tue Mar 12 14:50:14 2019 +0100 @@ -18,7 +18,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ - #pragma once #include "Framework/Widgets/IWorldSceneInteractor.h" @@ -40,6 +39,9 @@ { } + /** + WorldSceneWidget: + */ virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, const ViewportGeometry& view, MouseButton button,
--- a/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -35,4 +35,4 @@ OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, IStoneApplication* application) { return new SimpleViewer::SimpleViewerWasmApplicationAdapter(broker, *(dynamic_cast<SimpleViewer::SimpleViewerApplication*>(application))); -} \ No newline at end of file +}
--- a/Applications/Samples/SimpleViewer/Wasm/simple-viewer.ts Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/SimpleViewer/Wasm/simple-viewer.ts Tue Mar 12 14:50:14 2019 +0100 @@ -1,6 +1,6 @@ -///<reference path='../../../../Platforms/Wasm/wasm-application-runner.ts'/> +import wasmApplicationRunner = require('../../../../Platforms/Wasm/wasm-application-runner'); -InitializeWasmApplication("OrthancStoneSimpleViewer", "/orthanc"); +wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSimpleViewer", "/orthanc"); function SelectTool(toolName: string) { var command = { @@ -9,8 +9,7 @@ args: { } }; - SendMessageToStoneApplication(JSON.stringify(command)); - + wasmApplicationRunner.SendCommandToStoneApplication(JSON.stringify(command)); } function PerformAction(actionName: string) { @@ -20,7 +19,7 @@ args: { } }; - SendMessageToStoneApplication(JSON.stringify(command)); + wasmApplicationRunner.SendCommandToStoneApplication(JSON.stringify(command)); } class SimpleViewerUI { @@ -31,9 +30,7 @@ public constructor() { // install "SelectTool" handlers document.querySelectorAll("[tool-selector]").forEach((e) => { - console.log(e); (e as HTMLButtonElement).addEventListener("click", () => { - console.log(e); SelectTool(e.attributes["tool-selector"].value); }); }); @@ -62,8 +59,8 @@ // this method is called "from the C++ code" when the StoneApplication is updated. // it can be used to update the UI of the application -function UpdateWebApplication(statusUpdateMessageString: string) { - console.log("updating web application: ", statusUpdateMessageString); +function UpdateWebApplicationWithString(statusUpdateMessageString: string) { + console.log("updating web application with string: ", statusUpdateMessageString); let statusUpdateMessage = JSON.parse(statusUpdateMessageString); if ("event" in statusUpdateMessage) { @@ -73,3 +70,12 @@ } } } + +function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { + console.log("updating web application with serialized message: ", statusUpdateMessageString); + console.log("<not supported in the simple viewer!>"); +} + +// make it available to other js scripts in the application +(<any> window).UpdateWebApplicationWithString = UpdateWebApplicationWithString; +(<any> window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage;
--- a/Applications/Samples/SimpleViewer/Wasm/tsconfig-simple-viewer.json Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/SimpleViewer/Wasm/tsconfig-simple-viewer.json Tue Mar 12 14:50:14 2019 +0100 @@ -1,9 +1,9 @@ { "extends" : "../../Web/tsconfig-samples", "compilerOptions": { - "outFile": "../../build-web/simple-viewer/app-simple-viewer.js" }, "include" : [ - "simple-viewer.ts" + "simple-viewer.ts", + "../../build-wasm/ApplicationCommands_generated.ts" ] -} \ No newline at end of file +}
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h Tue Mar 12 14:50:14 2019 +0100 @@ -205,7 +205,12 @@ { } - virtual void HandleMessageFromWeb(std::string& output, const std::string& input) + virtual void HandleSerializedMessageFromWeb(std::string& output, const std::string& input) + { + // the simple viewer does not use the serialized messages facilities + } + + virtual void HandleCommandFromWeb(std::string& output, const std::string& input) { if (input == "select-tool:line-measure") { @@ -221,9 +226,14 @@ output = "ok"; } + virtual void NotifySerializedMessageFromCppToWeb(const std::string& statusUpdateMessage) + { + UpdateStoneApplicationStatusFromCppWithSerializedMessage(statusUpdateMessage.c_str()); + } + virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) { - UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str()); + UpdateStoneApplicationStatusFromCppWithString(statusUpdateMessage.c_str()); } };
--- a/Applications/Samples/Web/simple-viewer-single-file.ts Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/Web/simple-viewer-single-file.ts Tue Mar 12 14:50:14 2019 +0100 @@ -1,6 +1,6 @@ -///<reference path='../../../Platforms/Wasm/wasm-application-runner.ts'/> +import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); -InitializeWasmApplication("OrthancStoneSimpleViewerSingleFile", "/orthanc"); +wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSimpleViewerSingleFile", "/orthanc"); function SelectTool(toolName: string) { var command = { @@ -9,7 +9,7 @@ toolName: toolName } }; - SendMessageToStoneApplication(JSON.stringify(command)); + wasmApplicationRunner.SendCommandToStoneApplication(JSON.stringify(command)); } @@ -19,7 +19,7 @@ commandType: "simple", args: {} }; - SendMessageToStoneApplication(JSON.stringify(command)); + wasmApplicationRunner.SendCommandToStoneApplication(JSON.stringify(command)); } //initializes the buttons @@ -42,10 +42,20 @@ // this method is called "from the C++ code" when the StoneApplication is updated. // it can be used to update the UI of the application -function UpdateWebApplication(statusUpdateMessage: string) { +function UpdateWebApplicationWithString(statusUpdateMessage: string) { console.log(statusUpdateMessage); if (statusUpdateMessage.startsWith("series-description=")) { document.getElementById("series-description").innerText = statusUpdateMessage.split("=")[1]; } } + +function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { + console.log("updating web application with serialized message: ", statusUpdateMessageString); + console.log("<not supported in the simple viewer (single file)!>"); +} + +// make it available to other js scripts in the application +(<any> window).UpdateWebApplicationWithString = UpdateWebApplicationWithString; + +(<any> window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage;
--- a/Applications/Samples/Web/simple-viewer-single-file.tsconfig.json Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/Web/simple-viewer-single-file.tsconfig.json Tue Mar 12 14:50:14 2019 +0100 @@ -1,7 +1,7 @@ { "extends" : "./tsconfig-samples", "compilerOptions": { - "outFile": "../build-web/app-simple-viewer-single-file.js" + // "outFile": "../build-web/app-simple-viewer-single-file.js" }, "include" : [ "simple-viewer-single-file.ts"
--- a/Applications/Samples/Web/single-frame-editor.ts Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/Web/single-frame-editor.ts Tue Mar 12 14:50:14 2019 +0100 @@ -1,3 +1,3 @@ -///<reference path='../../../Platforms/Wasm/wasm-application-runner.ts'/> +import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); -InitializeWasmApplication("OrthancStoneSingleFrameEditor", "/orthanc"); +wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSingleFrameEditor", "/orthanc");
--- a/Applications/Samples/Web/single-frame-editor.tsconfig.json Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/Web/single-frame-editor.tsconfig.json Tue Mar 12 14:50:14 2019 +0100 @@ -1,9 +1,8 @@ { "extends" : "./tsconfig-samples", "compilerOptions": { - "outFile": "../build-web/app-single-frame-editor.js" }, "include" : [ "single-frame-editor.ts" ] -} \ No newline at end of file +}
--- a/Applications/Samples/Web/single-frame.ts Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/Web/single-frame.ts Tue Mar 12 14:50:14 2019 +0100 @@ -1,3 +1,4 @@ -///<reference path='../../../Platforms/Wasm/wasm-application-runner.ts'/> +import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); -InitializeWasmApplication("OrthancStoneSingleFrame", "/orthanc"); +wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSingleFrame", "/orthanc"); +
--- a/Applications/Samples/Web/single-frame.tsconfig.json Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/Web/single-frame.tsconfig.json Tue Mar 12 14:50:14 2019 +0100 @@ -1,9 +1,8 @@ { "extends" : "./tsconfig-samples", "compilerOptions": { - "outFile": "../build-web/app-single-frame.js" }, "include" : [ "single-frame.ts" ] -} \ No newline at end of file +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/build-wasm.sh.old Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,33 @@ +#!/bin/bash +# +# usage: +# to build all targets: +# ./build-wasm.sh +# +# to build a single target: +# ./build-wasm.sh OrthancStoneSingleFrameEditor + +set -e + +target=${1:-all} + +currentDir=$(pwd) +samplesRootDir=$(pwd) + +mkdir -p $samplesRootDir/build-wasm +cd $samplesRootDir/build-wasm + +source ~/apps/emsdk/emsdk_env.sh +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone \ + -DORTHANC_FRAMEWORK_SOURCE=path \ + -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc \ + -DALLOW_DOWNLOADS=ON .. \ + -DENABLE_WASM=ON + +ninja $target + +echo "-- building the web application -- " +cd $currentDir +./build-web.sh
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/build-web-ext.sh Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +target=${1:-all} +# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ + +currentDir=$(pwd) + +scriptDirRel=$(dirname $0) +#echo $scriptDirRel +scriptDirAbs=$(realpath $scriptDirRel) +echo $scriptDirAbs + +samplesRootDir=scriptDirAbs + +outputDir=$samplesRootDir/build-web/ +mkdir -p $outputDir + +# files used by all single files samples +cp $samplesRootDir/Web/index.html $outputDir +cp $samplesRootDir/Web/samples-styles.css $outputDir + +# build simple-viewer-single-file (obsolete project) +if [[ $target == "all" || $target == "OrthancStoneSimpleViewerSingleFile" ]]; then + cp $samplesRootDir/Web/simple-viewer-single-file.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm $outputDir +fi + +# build single-frame +if [[ $target == "all" || $target == "OrthancStoneSingleFrame" ]]; then + cp $samplesRootDir/Web/single-frame.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/single-frame.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSingleFrame.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSingleFrame.wasm $outputDir +fi + +# build single-frame-editor +if [[ $target == "all" || $target == "OrthancStoneSingleFrameEditor" ]]; then + cp $samplesRootDir/Web/single-frame-editor.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/single-frame-editor.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm $outputDir +fi + +# build simple-viewer project +if [[ $target == "all" || $target == "OrthancStoneSimpleViewer" ]]; then + mkdir -p $outputDir/simple-viewer/ + cp $samplesRootDir/SimpleViewer/Wasm/simple-viewer.html $outputDir/simple-viewer/ + cp $samplesRootDir/SimpleViewer/Wasm/styles.css $outputDir/simple-viewer/ + tsc --allowJs --project $samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json + cp $currentDir/build-wasm/OrthancStoneSimpleViewer.js $outputDir/simple-viewer/ + cp $currentDir/build-wasm/OrthancStoneSimpleViewer.wasm $outputDir/simple-viewer/ +fi + +cd $currentDir
--- a/Applications/Samples/build-web.sh Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/Samples/build-web.sh Tue Mar 12 14:50:14 2019 +0100 @@ -8,35 +8,51 @@ currentDir=$(pwd) samplesRootDir=$(pwd) +echo "*************************************************************************" +echo "samplesRootDir = $samplesRootDir" +echo "*************************************************************************" + outputDir=$samplesRootDir/build-web/ -mkdir -p $outputDir +mkdir -p "$outputDir" # files used by all single files samples -cp $samplesRootDir/Web/index.html $outputDir -cp $samplesRootDir/Web/samples-styles.css $outputDir +cp "$samplesRootDir/Web/index.html" "$outputDir" +cp "$samplesRootDir/Web/samples-styles.css" "$outputDir" # build simple-viewer-single-file (obsolete project) if [[ $target == "all" || $target == "OrthancStoneSimpleViewerSingleFile" ]]; then cp $samplesRootDir/Web/simple-viewer-single-file.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm $outputDir + tsc --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json --outDir "$outputDir" + browserify \ + "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ + "$outputDir/Applications/Samples/Web/simple-viewer-single-file.js" \ + -o "$outputDir/app-simple-viewer-single-file.js" + cp "$currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js" $outputDir + cp "$currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm" $outputDir fi # build single-frame if [[ $target == "all" || $target == "OrthancStoneSingleFrame" ]]; then cp $samplesRootDir/Web/single-frame.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/single-frame.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSingleFrame.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSingleFrame.wasm $outputDir + tsc --project $samplesRootDir/Web/single-frame.tsconfig.json --outDir "$outputDir" + browserify \ + "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ + "$outputDir/Applications/Samples/Web/single-frame.js" \ + -o "$outputDir/app-single-frame.js" + cp "$currentDir/build-wasm/OrthancStoneSingleFrame.js" $outputDir + cp "$currentDir/build-wasm/OrthancStoneSingleFrame.wasm" $outputDir fi # build single-frame-editor if [[ $target == "all" || $target == "OrthancStoneSingleFrameEditor" ]]; then cp $samplesRootDir/Web/single-frame-editor.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/single-frame-editor.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm $outputDir + tsc --project $samplesRootDir/Web/single-frame-editor.tsconfig.json --outDir "$outputDir" + browserify \ + "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ + "$outputDir/Applications/Samples/Web/single-frame-editor.js" \ + -o "$outputDir/app-single-frame-editor.js" + cp "$currentDir/build-wasm/OrthancStoneSingleFrameEditor.js" $outputDir + cp "$currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm" $outputDir fi # build simple-viewer project @@ -44,9 +60,15 @@ mkdir -p $outputDir/simple-viewer/ cp $samplesRootDir/SimpleViewer/Wasm/simple-viewer.html $outputDir/simple-viewer/ cp $samplesRootDir/SimpleViewer/Wasm/styles.css $outputDir/simple-viewer/ - tsc --allowJs --project $samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json - cp $currentDir/build-wasm/OrthancStoneSimpleViewer.js $outputDir/simple-viewer/ - cp $currentDir/build-wasm/OrthancStoneSimpleViewer.wasm $outputDir/simple-viewer/ + + # the root dir must contain all the source files for the whole project + tsc --module commonjs --allowJs --project "$samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json" --rootDir "$samplesRootDir/../.." --outDir "$outputDir/simple-viewer/" + browserify \ + "$outputDir/simple-viewer/Platforms/Wasm/wasm-application-runner.js" \ + "$outputDir/simple-viewer/Applications/Samples/SimpleViewer/Wasm/simple-viewer.js" \ + -o "$outputDir/simple-viewer/app-simple-viewer.js" + cp "$currentDir/build-wasm/OrthancStoneSimpleViewer.js" "$outputDir/simple-viewer/" + cp "$currentDir/build-wasm/OrthancStoneSimpleViewer.wasm" "$outputDir/simple-viewer/" fi cd $currentDir
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/get-requirements-windows.ps1 Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,50 @@ + +if ($true) { + + Write-Error "This script is obsolete. Please work under WSL and run build-wasm.sh" + +} else { + + param( + [IO.DirectoryInfo] $EmsdkRootDir = "C:\Emscripten", + [bool] $Overwrite = $false + ) + + if (Test-Path -Path $EmsdkRootDir) { + if( $Override) { + Remove-Item -Path $EmsdkRootDir -Force -Recurse + } else { + throw "The `"$EmsdkRootDir`" folder may not exist! Use the Overwrite flag to bypass this check." + } + } + + # TODO: detect whether git is installed + # choco install -y git + + Write-Host "Will retrieve the Emscripten SDK to the `"$EmsdkRootDir`" folder" + + $EmsdkParentDir = split-path -Parent $EmsdkRootDir + $EmsdkRootName = split-path -Leaf $EmsdkRootDir + + Push-Location $EmsdkParentDir + + git clone https://github.com/juj/emsdk.git $EmsdkRootName + cd $EmsdkRootName + + git pull + + ./emsdk install latest + + ./emsdk activate latest + + echo "INFO: the ~/.emscripten file has been configured for this installation of Emscripten." + + Write-Host "emsdk is now installed in $EmsdkRootDir" + + Pop-Location + +} + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/package-lock.json Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "typescript": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==" + } + } +}
--- a/Applications/Samples/samples-library.js Tue Feb 26 12:58:03 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -// this file contains the JS method you want to expose to C++ code - -// mergeInto(LibraryManager.library, { -// ScheduleRedraw: function() { -// ScheduleRedraw(); -// } -// }); - \ No newline at end of file
--- a/Applications/StoneApplicationContext.h Tue Feb 26 12:58:03 2019 +0100 +++ b/Applications/StoneApplicationContext.h Tue Mar 12 14:50:14 2019 +0100 @@ -26,6 +26,25 @@ #include "../Framework/Toolbox/OrthancApiClient.h" #include "../Framework/Viewport/WidgetViewport.h" + +#ifdef _MSC_VER + #if _MSC_VER > 1910 + #define orthanc_override override + #else + #define orthanc_override + #endif +#elif defined __GNUC__ + #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +/* Test for GCC > 3.2.0 */ + #if GCC_VERSION > 40900 + #define orthanc_override override + else + #define orthanc_override + #endif +#else + #define orthanc_override +#endif + #include <list> namespace OrthancStone
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Framework/Layers/GrayscaleFrameRenderer.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -121,8 +121,8 @@ bool isFullQuality) : FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality), frame_(Orthanc::Image::Clone(frame)), - defaultWindowCenter_(converter.GetDefaultWindowCenter()), - defaultWindowWidth_(converter.GetDefaultWindowWidth()), + defaultWindowCenter_(static_cast<float>(converter.GetDefaultWindowCenter())), + defaultWindowWidth_(static_cast<float>(converter.GetDefaultWindowWidth())), photometric_(converter.GetPhotometricInterpretation()) { if (frame_.get() == NULL)
--- a/Framework/Radiography/RadiographyScene.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Framework/Radiography/RadiographyScene.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -562,8 +562,8 @@ Extent2D extent = GetSceneExtent(); - int w = std::ceil(extent.GetWidth() / pixelSpacingX); - int h = std::ceil(extent.GetHeight() / pixelSpacingY); + int w = static_cast<int>(std::ceil(extent.GetWidth() / pixelSpacingX)); + int h = static_cast<int>(std::ceil(extent.GetHeight() / pixelSpacingY)); if (w < 0 || h < 0) {
--- a/Framework/Radiography/RadiographyWidget.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Framework/Radiography/RadiographyWidget.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -32,10 +32,14 @@ bool RadiographyWidget::IsInvertedInternal() const { - return (scene_->GetPreferredPhotomotricDisplayMode() == PhotometricDisplayMode_Monochrome1) ^ invert_; // MONOCHROME1 images must be inverted and the user can invert the image too -> XOR the two + // MONOCHROME1 images must be inverted and the user can invert the + // image, too -> XOR the two + return (scene_->GetPreferredPhotomotricDisplayMode() == + PhotometricDisplayMode_Monochrome1) ^ invert_; } - void RadiographyWidget::RenderBackground(Orthanc::ImageAccessor& image, float minValue, float maxValue) + void RadiographyWidget::RenderBackground( + Orthanc::ImageAccessor& image, float minValue, float maxValue) { // wipe background before rendering float backgroundValue = minValue; @@ -59,7 +63,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - Orthanc::ImageProcessing::Set(image, backgroundValue); + Orthanc::ImageProcessing::Set(image, static_cast<int64_t>(backgroundValue)); } bool RadiographyWidget::RenderInternal(unsigned int width, @@ -82,7 +86,8 @@ floatBuffer_->GetWidth() != width || floatBuffer_->GetHeight() != height) { - floatBuffer_.reset(new Orthanc::Image(Orthanc::PixelFormat_Float32, width, height, false)); + floatBuffer_.reset(new Orthanc::Image( + Orthanc::PixelFormat_Float32, width, height, false)); } if (cairoBuffer_.get() == NULL || @@ -168,7 +173,8 @@ if (hasSelection_) { - scene_->DrawBorder(context, selectedLayer_, view.GetZoom()); + scene_->DrawBorder( + context, static_cast<unsigned int>(selectedLayer_), view.GetZoom()); } return true;
--- a/Framework/Toolbox/DicomFrameConverter.h Tue Feb 26 12:58:03 2019 +0100 +++ b/Framework/Toolbox/DicomFrameConverter.h Tue Mar 12 14:50:14 2019 +0100 @@ -59,6 +59,17 @@ SetDefaultParameters(); } + ~DicomFrameConverter() + { + // TODO: check whether this dtor is called or not + // An MSVC warning explains that declaring an + // std::auto_ptr with a forward-declared type + // prevents its dtor from being called. Does not + // seem an issue here (only POD types inside), but + // definitely something to keep an eye on. + (void)0; + } + Orthanc::PixelFormat GetExpectedPixelFormat() const { return expectedPixelFormat_;
--- a/Framework/Toolbox/DicomStructureSet.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Framework/Toolbox/DicomStructureSet.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -754,7 +754,11 @@ double x1, y1, x2, y2; if (polygon->Project(x1, y1, x2, y2, slice)) { - projected.push_back(CreateRectangle(x1, y1, x2, y2)); + projected.push_back(CreateRectangle( + static_cast<float>(x1), + static_cast<float>(y1), + static_cast<float>(x2), + static_cast<float>(y2))); } }
--- a/Framework/Toolbox/FiniteProjectiveCamera.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Framework/Toolbox/FiniteProjectiveCamera.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -332,7 +332,7 @@ LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slices->GetSliceCount(); const OrthancStone::CoordinateSystem3D& slice = slices->GetSlice(z); - OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, z); + OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, static_cast<unsigned int>(z)); SourceReader pixelReader(sliceReader.GetAccessor());
--- a/Framework/Toolbox/ImageGeometry.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Framework/Toolbox/ImageGeometry.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -76,9 +76,7 @@ } else { - int tmp; - - tmp = std::floor(extent.GetX1()); + int tmp = static_cast<int>(std::floor(extent.GetX1())); if (tmp < 0) { x1 = 0; @@ -88,7 +86,7 @@ x1 = static_cast<unsigned int>(tmp); } - tmp = std::floor(extent.GetY1()); + tmp = static_cast<int>(std::floor(extent.GetY1())); if (tmp < 0) { y1 = 0; @@ -98,7 +96,7 @@ y1 = static_cast<unsigned int>(tmp); } - tmp = std::ceil(extent.GetX2()); + tmp = static_cast<int>(std::ceil(extent.GetX2())); if (tmp < 0) { return false; @@ -112,7 +110,7 @@ x2 = static_cast<unsigned int>(tmp); } - tmp = std::ceil(extent.GetY2()); + tmp = static_cast<int>(std::ceil(extent.GetY2())); if (tmp < 0) { return false; @@ -395,8 +393,8 @@ Reader reader(source); unsigned int x1, y1, x2, y2; - const float floatWidth = source.GetWidth(); - const float floatHeight = source.GetHeight(); + const float floatWidth = static_cast<float>(source.GetWidth()); + const float floatHeight = static_cast<float>(source.GetHeight()); if (GetProjectiveTransformExtent(x1, y1, x2, y2, a, source.GetWidth(), source.GetHeight(),
--- a/Platforms/Wasm/Defaults.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/Defaults.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -342,20 +342,35 @@ viewport->MouseLeave(); } - const char* EMSCRIPTEN_KEEPALIVE SendMessageToStoneApplication(const char* message) + const char* EMSCRIPTEN_KEEPALIVE SendSerializedMessageToStoneApplication(const char* message) { static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread) - printf("SendMessageToStoneApplication (JS -> C++): %s\n", message); + printf("SendSerializedMessageToStoneApplication\n"); + printf("%s", message); if (applicationWasmAdapter.get() != NULL) { - applicationWasmAdapter->HandleMessageFromWeb(output, std::string(message)); + applicationWasmAdapter->HandleSerializedMessageFromWeb(output, std::string(message)); return output.c_str(); } - printf("This stone application does not have a Web Adapter\n"); + printf("This Stone application does not have a Web Adapter"); return NULL; } + const char* EMSCRIPTEN_KEEPALIVE SendCommandToStoneApplication(const char* message) + { + static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread) + + printf("SendCommandToStoneApplication\n"); + printf("%s", message); + + if (applicationWasmAdapter.get() != NULL) { + applicationWasmAdapter->HandleCommandFromWeb(output, std::string(message)); + return output.c_str(); + } + printf("This Stone application does not have a Web Adapter"); + return NULL; + } #ifdef __cplusplus }
--- a/Platforms/Wasm/Defaults.h Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/Defaults.h Tue Mar 12 14:50:14 2019 +0100 @@ -16,7 +16,8 @@ // JS methods accessible from C++ extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle); - extern void UpdateStoneApplicationStatusFromCpp(const char* statusUpdateMessage); + extern void UpdateStoneApplicationStatusFromCppWithString(const char* statusUpdateMessage); + extern void UpdateStoneApplicationStatusFromCppWithSerializedMessage(const char* statusUpdateMessage); // C++ methods accessible from JS extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle);
--- a/Platforms/Wasm/WasmDelayedCallExecutor.js Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/WasmDelayedCallExecutor.js Tue Mar 12 14:50:14 2019 +0100 @@ -1,7 +1,7 @@ mergeInto(LibraryManager.library, { WasmDelayedCallExecutor_Schedule: function(callable, timeoutInMs/*, payload*/) { setTimeout(function() { - WasmDelayedCallExecutor_ExecuteCallback(callable/*, payload*/); + window.WasmDelayedCallExecutor_ExecuteCallback(callable/*, payload*/); }, timeoutInMs); } });
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -8,44 +8,83 @@ namespace OrthancStone { - WasmPlatformApplicationAdapter::WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application) - : IObserver(broker), - application_(application) + WasmPlatformApplicationAdapter::WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application) + : IObserver(broker), + application_(application) + { + } + + void WasmPlatformApplicationAdapter::HandleCommandFromWeb(std::string& output, const std::string& input) + { + try { - } - - void WasmPlatformApplicationAdapter::HandleMessageFromWeb(std::string& output, const std::string& input) - { - try + Json::Value inputJson; + // if the message is a command, build it and execute it + if (MessagingToolbox::ParseJson(inputJson, input.c_str(), input.size())) { - Json::Value inputJson; - // if the message is a command, build it and execute it - if (MessagingToolbox::ParseJson(inputJson, input.c_str(), input.size())) - { - std::unique_ptr<ICommand> command(application_.GetCommandBuilder().CreateFromJson(inputJson)); - if (command.get() == NULL) - printf("Could not parse command: '%s'\n", input.c_str()); - else - application_.ExecuteCommand(*command); - } - } - catch (StoneException& exc) - { - printf("Error while handling message from web (error code = %d):\n", exc.GetErrorCode()); - printf("While interpreting input: '%s'\n", input.c_str()); - } + std::unique_ptr<ICommand> command(application_.GetCommandBuilder().CreateFromJson(inputJson)); + if (command.get() == NULL) + printf("Could not parse command: '%s'\n", input.c_str()); + else + application_.ExecuteCommand(*command); + } + } + catch (StoneException& exc) + { + printf("Error while handling command from web (error code = %d):\n", exc.GetErrorCode()); + printf("While interpreting input: '%s'\n", input.c_str()); + output = std::string("ERROR : "); + } + catch (std::exception& exc) + { + printf("Error while handling message from web (error text = %s):\n", exc.what()); + printf("While interpreting input: '%s'\n", input.c_str()); + output = std::string("ERROR : "); + } + } + + void WasmPlatformApplicationAdapter::HandleSerializedMessageFromWeb(std::string& output, const std::string& input) + { + try + { + application_.HandleSerializedMessage(input.c_str()); } + catch (StoneException& exc) + { + printf("Error while handling message from web (error code = %d):\n", exc.GetErrorCode()); + printf("While interpreting input: '%s'\n", input.c_str()); + output = std::string("ERROR : "); + } + catch (std::exception& exc) + { + printf("Error while handling message from web (error text = %s):\n", exc.what()); + printf("While interpreting input: '%s'\n", input.c_str()); + output = std::string("ERROR : "); + } + } - void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) + void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage) + { + try { - try - { - UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str()); - } - catch (...) - { - printf("Error while handling message to web\n"); - } + UpdateStoneApplicationStatusFromCppWithString(statusUpdateMessage.c_str()); + } + catch (...) + { + printf("Error while handling string message to web\n"); } + } + + void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWebWithSerializedMessage(const std::string& statusUpdateMessage) + { + try + { + UpdateStoneApplicationStatusFromCppWithSerializedMessage(statusUpdateMessage.c_str()); + } + catch (...) + { + printf("Error while handling serialized message to web\n"); + } + } } \ No newline at end of file
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.h Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.h Tue Mar 12 14:50:14 2019 +0100 @@ -12,7 +12,9 @@ public: WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application); - virtual void HandleMessageFromWeb(std::string& output, const std::string& input); - virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage); + virtual void HandleSerializedMessageFromWeb(std::string& output, const std::string& input); + virtual void HandleCommandFromWeb(std::string& output, const std::string& input); + virtual void NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage); + virtual void NotifyStatusUpdateFromCppToWebWithSerializedMessage(const std::string& statusUpdateMessage); }; } \ No newline at end of file
--- a/Platforms/Wasm/WasmWebService.js Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/WasmWebService.js Tue Mar 12 14:50:14 2019 +0100 @@ -24,10 +24,10 @@ // TODO - Is "new Uint8Array()" necessary? This copies the // answer to the WebAssembly stack, hence necessitating // increasing the TOTAL_STACK parameter of Emscripten - WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), + window.WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), this.response.byteLength, headers, payload); } else { - WasmWebService_NotifyError(callableFailure, url_, payload); + window.WasmWebService_NotifyError(callableFailure, url_, payload); } } } @@ -37,7 +37,7 @@ WasmWebService_ScheduleLaterCachedSuccessNotification: function (brol) { setTimeout(function() { - WasmWebService_NotifyCachedSuccess(brol); + window.WasmWebService_NotifyCachedSuccess(brol); }, 0); }, @@ -62,10 +62,10 @@ var headers = _malloc(s.length + 1); stringToUTF8(s, headers, s.length + 1); - WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), + window.WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), this.response.byteLength, headers, payload); } else { - WasmWebService_NotifyError(callableFailure, url_, payload); + window.WasmWebService_NotifyError(callableFailure, url_, payload); } } } @@ -94,10 +94,10 @@ var headers = _malloc(s.length + 1); stringToUTF8(s, headers, s.length + 1); - WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), + window.WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), this.response.byteLength, headers, payload); } else { - WasmWebService_NotifyError(callableFailure, url_, payload); + window.WasmWebService_NotifyError(callableFailure, url_, payload); } } }
--- a/Platforms/Wasm/default-library.js Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/default-library.js Tue Mar 12 14:50:14 2019 +0100 @@ -2,15 +2,20 @@ mergeInto(LibraryManager.library, { ScheduleWebViewportRedrawFromCpp: function(cppViewportHandle) { - ScheduleWebViewportRedraw(cppViewportHandle); + window.ScheduleWebViewportRedraw(cppViewportHandle); }, CreateWasmViewportFromCpp: function(htmlCanvasId) { - return CreateWasmViewport(htmlCanvasId); + return window.CreateWasmViewport(htmlCanvasId); }, - // each time the StoneApplication updates its status, it may signal it through this method. i.e, to change the status of a button in the web interface - UpdateStoneApplicationStatusFromCpp: function(statusUpdateMessage) { + // each time the StoneApplication updates its status, it may signal it + // through this method. i.e, to change the status of a button in the web interface + UpdateStoneApplicationStatusFromCppWithString: function(statusUpdateMessage) { var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); - UpdateWebApplication(statusUpdateMessage_); + window.UpdateWebApplicationWithString(statusUpdateMessage_); + }, + // same, but with a serialized message + UpdateStoneApplicationStatusFromCppWithSerializedMessage: function(statusUpdateMessage) { + var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); + window.UpdateWebApplicationWithSerializedMessage(statusUpdateMessage_); } }); - \ No newline at end of file
--- a/Platforms/Wasm/stone-framework-loader.ts Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/stone-framework-loader.ts Tue Mar 12 14:50:14 2019 +0100 @@ -1,96 +1,95 @@ -module Stone { - /** - * This file contains primitives to interface with WebAssembly and - * with the Stone framework. - **/ - - export declare type InitializationCallback = () => void; - - export declare var StoneFrameworkModule : any; - - //const ASSETS_FOLDER : string = "assets/lib"; - //const WASM_FILENAME : string = "orthanc-framework"; - - - export class Framework - { - private static singleton_ : Framework = null; - private static wasmModuleName_ : string = null; +/** + * This file contains primitives to interface with WebAssembly and + * with the Stone framework. + **/ + +export declare type InitializationCallback = () => void; + +//export declare var StoneFrameworkModule : any; +export var StoneFrameworkModule : any; + +//const ASSETS_FOLDER : string = "assets/lib"; +//const WASM_FILENAME : string = "orthanc-framework"; + +export class Framework +{ + private static singleton_ : Framework = null; + private static wasmModuleName_ : string = null; + + public static Configure(wasmModuleName: string) { + this.wasmModuleName_ = wasmModuleName; + } - public static Configure(wasmModuleName: string) { - this.wasmModuleName_ = wasmModuleName; - } + private constructor(verbose : boolean) + { + //this.ccall('Initialize', null, [ 'number' ], [ verbose ]); + } + + + public ccall( name: string, + returnType: string, + argTypes: Array<string>, + argValues: Array<any>) : any + { + return (<any> window).StoneFrameworkModule.ccall(name, returnType, argTypes, argValues); + } + + + public cwrap( name: string, + returnType: string, + argTypes: Array<string>) : any + { + return (<any> window).StoneFrameworkModule.cwrap(name, returnType, argTypes); + } - private constructor(verbose : boolean) - { - //this.ccall('Initialize', null, [ 'number' ], [ verbose ]); - } - - - public ccall(name: string, - returnType: string, - argTypes: Array<string>, - argValues: Array<any>) : any - { - return StoneFrameworkModule.ccall(name, returnType, argTypes, argValues); - } - - - public cwrap(name: string, - returnType: string, - argTypes: Array<string>) : any - { - return StoneFrameworkModule.cwrap(name, returnType, argTypes); - } - - - public static GetInstance() : Framework - { - if (Framework.singleton_ == null) { - throw new Error('The WebAssembly module is not loaded yet'); - } else { - return Framework.singleton_; + + public static GetInstance() : Framework + { + if (Framework.singleton_ == null) { + throw new Error('The WebAssembly module is not loaded yet'); + } else { + return Framework.singleton_; + } + } + + + public static Initialize( verbose: boolean, + callback: InitializationCallback) + { + console.log('Initializing WebAssembly Module'); + + // (<any> window). + (<any> window).StoneFrameworkModule = { + preRun: [ + function() { + console.log('Loading the Stone Framework using WebAssembly'); } - } - - - public static Initialize(verbose: boolean, - callback: InitializationCallback) - { - console.log('Initializing WebAssembly Module'); - - (<any> window).StoneFrameworkModule = { - preRun: [ - function() { - console.log('Loading the Stone Framework using WebAssembly'); - } - ], - postRun: [ - function() { - // This function is called by ".js" wrapper once the ".wasm" - // WebAssembly module has been loaded and compiled by the - // browser - console.log('WebAssembly is ready'); - Framework.singleton_ = new Framework(verbose); - callback(); - } - ], - print: function(text : string) { - console.log(text); - }, - printErr: function(text : string) { - console.error(text); - }, - totalDependencies: 0 - }; - - // Dynamic loading of the JavaScript wrapper around WebAssembly - var script = document.createElement('script'); - script.type = 'application/javascript'; - //script.src = "orthanc-stone.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; - script.src = this.wasmModuleName_ + ".js";// "OrthancStoneSimpleViewer.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; - script.async = true; - document.head.appendChild(script); - } - } -} \ No newline at end of file + ], + postRun: [ + function() { + // This function is called by ".js" wrapper once the ".wasm" + // WebAssembly module has been loaded and compiled by the + // browser + console.log('WebAssembly is ready'); + Framework.singleton_ = new Framework(verbose); + callback(); + } + ], + print: function(text : string) { + console.log(text); + }, + printErr: function(text : string) { + console.error(text); + }, + totalDependencies: 0 + }; + + // Dynamic loading of the JavaScript wrapper around WebAssembly + var script = document.createElement('script'); + script.type = 'application/javascript'; + //script.src = "orthanc-stone.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; + script.src = this.wasmModuleName_ + ".js";// "OrthancStoneSimpleViewer.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; + script.async = true; + document.head.appendChild(script); + } +}
--- a/Platforms/Wasm/wasm-application-runner.ts Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/wasm-application-runner.ts Tue Mar 12 14:50:14 2019 +0100 @@ -1,11 +1,12 @@ -///<reference path='stone-framework-loader.ts'/> -///<reference path='wasm-viewport.ts'/> +import Stone = require('./stone-framework-loader'); +import StoneViewport = require('./wasm-viewport'); if (!('WebAssembly' in window)) { alert('Sorry, your browser does not support WebAssembly :('); } -declare var StoneFrameworkModule : Stone.Framework; +//var StoneFrameworkModule : Stone.Framework = (<any>window).StoneFrameworkModule; +//export declare var StoneFrameworkModule : Stone.Framework; // global functions var WasmWebService_NotifyError: Function = null; @@ -15,11 +16,11 @@ var WasmDoAnimation: Function = null; var SetStartupParameter: Function = null; var CreateWasmApplication: Function = null; -var CreateCppViewport: Function = null; +export var CreateCppViewport: Function = null; var ReleaseCppViewport: Function = null; var StartWasmApplication: Function = null; -var SendMessageToStoneApplication: Function = null; - +export var SendSerializedMessageToStoneApplication: Function = null; +export var SendCommandToStoneApplication: Function = null; function DoAnimationThread() { if (WasmDoAnimation != null) { @@ -29,7 +30,6 @@ setTimeout(DoAnimationThread, 100); // Update the viewport content every 100ms if need be } - function GetUriParameters(): Map<string, string> { var parameters = window.location.search.substr(1); @@ -72,12 +72,12 @@ StartWasmApplication(orthancBaseUrl); // trigger a first resize of the canvas that has just been initialized - Stone.WasmViewport.ResizeAll(); + StoneViewport.WasmViewport.ResizeAll(); DoAnimationThread(); } -function InitializeWasmApplication(wasmModuleName: string, orthancBaseUrl: string) { +export function InitializeWasmApplication(wasmModuleName: string, orthancBaseUrl: string) { Stone.Framework.Configure(wasmModuleName); @@ -87,19 +87,21 @@ console.log("Connecting C++ methods to JS methods"); - SetStartupParameter = StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']); - CreateWasmApplication = StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']); - CreateCppViewport = StoneFrameworkModule.cwrap('CreateCppViewport', 'number', []); - ReleaseCppViewport = StoneFrameworkModule.cwrap('ReleaseCppViewport', null, ['number']); - StartWasmApplication = StoneFrameworkModule.cwrap('StartWasmApplication', null, ['string']); + SetStartupParameter = (<any> window).StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']); + CreateWasmApplication = (<any> window).StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']); + CreateCppViewport = (<any> window).StoneFrameworkModule.cwrap('CreateCppViewport', 'number', []); + ReleaseCppViewport = (<any> window).StoneFrameworkModule.cwrap('ReleaseCppViewport', null, ['number']); + StartWasmApplication = (<any> window).StoneFrameworkModule.cwrap('StartWasmApplication', null, ['string']); - WasmWebService_NotifyCachedSuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifyCachedSuccess', null, ['number']); - WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); - WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']); - WasmDelayedCallExecutor_ExecuteCallback = StoneFrameworkModule.cwrap('WasmDelayedCallExecutor_ExecuteCallback', null, ['number']); - WasmDoAnimation = StoneFrameworkModule.cwrap('WasmDoAnimation', null, []); + (<any> window).WasmWebService_NotifyCachedSuccess = (<any> window).StoneFrameworkModule.cwrap('WasmWebService_NotifyCachedSuccess', null, ['number']); + (<any> window).WasmWebService_NotifySuccess = (<any> window).StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); + (<any> window).WasmWebService_NotifyError = (<any> window).StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']); + (<any> window).WasmDelayedCallExecutor_ExecuteCallback = (<any> window).StoneFrameworkModule.cwrap('WasmDelayedCallExecutor_ExecuteCallback', null, ['number']); + // no need to put this into the globals for it's only used in this very module + WasmDoAnimation = (<any> window).StoneFrameworkModule.cwrap('WasmDoAnimation', null, []); - SendMessageToStoneApplication = StoneFrameworkModule.cwrap('SendMessageToStoneApplication', 'string', ['string']); + SendSerializedMessageToStoneApplication = (<any> window).StoneFrameworkModule.cwrap('SendSerializedMessageToStoneApplication', 'string', ['string']); + SendCommandToStoneApplication = (<any> window).StoneFrameworkModule.cwrap('SendCommandToStoneApplication', 'string', ['string']); console.log("Connecting C++ methods to JS methods - done"); @@ -110,4 +112,10 @@ _InitializeWasmApplication(orthancBaseUrl); }); -} \ No newline at end of file +} + + +// exports.InitializeWasmApplication = InitializeWasmApplication; + + +
--- a/Platforms/Wasm/wasm-viewport.ts Tue Feb 26 12:58:03 2019 +0100 +++ b/Platforms/Wasm/wasm-viewport.ts Tue Mar 12 14:50:14 2019 +0100 @@ -1,3 +1,6 @@ +import wasmApplicationRunner = require('./wasm-application-runner'); +//import stoneFrameworkLoader = require('./stone-framework-loader'); + var isPendingRedraw = false; function ScheduleWebViewportRedraw(cppViewportHandle: any) : void @@ -7,24 +10,26 @@ console.log('Scheduling a refresh of the viewport, as its content changed'); window.requestAnimationFrame(function() { isPendingRedraw = false; - Stone.WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); + WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); }); } } +(<any>window).ScheduleWebViewportRedraw = ScheduleWebViewportRedraw; + declare function UTF8ToString(any): string; function CreateWasmViewport(htmlCanvasId: string) : any { - var cppViewportHandle = CreateCppViewport(); + var cppViewportHandle = wasmApplicationRunner.CreateCppViewport(); var canvasId = UTF8ToString(htmlCanvasId); - var webViewport = new Stone.WasmViewport(StoneFrameworkModule, canvasId, cppViewportHandle); // viewports are stored in a static map in WasmViewport -> won't be deleted + var webViewport = new WasmViewport((<any> window).StoneFrameworkModule, canvasId, cppViewportHandle); // viewports are stored in a static map in WasmViewport -> won't be deleted webViewport.Initialize(); return cppViewportHandle; } + +(<any>window).CreateWasmViewport = CreateWasmViewport; -module Stone { - // export declare type InitializationCallback = () => void; // export declare var StoneFrameworkModule : any; @@ -32,7 +37,7 @@ //const ASSETS_FOLDER : string = "assets/lib"; //const WASM_FILENAME : string = "orthanc-framework"; - export class WasmViewport { +export class WasmViewport { private static viewportsMapByCppHandle_ : Map<number, WasmViewport> = new Map<number, WasmViewport>(); // key = the C++ handle private static viewportsMapByCanvasId_ : Map<string, WasmViewport> = new Map<string, WasmViewport>(); // key = the canvasId @@ -402,5 +407,5 @@ } } -} + \ No newline at end of file
--- a/README Tue Feb 26 12:58:03 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,167 +0,0 @@ -Stone of Orthanc -================ - - -General Information -------------------- - -This repository contains the source code of the Stone of Orthanc. - -Stone of Orthanc is a lightweight, cross-platform C++ framework for -the CPU-based rendering of medical images. It notably features support -for MPR (multiplanar reconstruction of volume images), PET-CT fusion, -and accurate physical world coordinates. - -Stone of Orthanc comes bundled with its own software-based rendering -engine (based upon pixman). This engine will use CPU hardware -acceleration if available (notably SSE2, SSSE3, and NEON instruction -sets), but not the GPU. This makes Stone a highly versatile framework -that can run even on low-performance platforms. Note that Stone is -able to display DICOM series without having to entirely store them in -the RAM (i.e. frame by frame). - -Thanks to its standalone rendering engine, Stone of Orthanc is also -compatible with any GUI framework (such as Qt, wxWidgets, MFC...). The -provided sample applications use the SDL framework. - -Stone is conceived as a companion toolbox to the Orthanc VNA (vendor -neutral archive, i.e. DICOM server). As a consequence, Stone will -smoothly interface with Orthanc out of the box. Interestingly, Stone -does not contain any DICOM toolkit: It entirely relies on the REST API -of Orthanc to parse/decode DICOM images. However, thanks to the -object-oriented architecture of Stone, it is possible to avoid this -dependency upon Orthanc, e.g. to download DICOM datasets using -DICOMweb. - - -Comparison ----------- - -Pay attention to the fact that Stone of Orthanc is a toolkit, and not -a fully-featured application for the visualization of medical images -(such as Horos/OsiriX or Ginkgo CADx). However, such applications -could be built on the top of Stone of Orthanc. - -Stone of Orthanc is quite similar to two other well-known toolkits: - -* Cornerstone, a client-side JavaScript toolkit to display medical - images in Web browsers, by Chris Hafey <chafey@gmail.com>: - https://github.com/chafey/cornerstone - - Contrarily to Cornerstone, Stone of Orthanc can be embedded into - native, heavyweight applications. - -* VTK, a C++ toolkit for scientific visualization, by Kitware: - http://www.vtk.org/ - - Contrarily to VTK, Stone of Orthanc is focused on CPU-based, 2D - rendering: The GPU will not be used. - - -Dependencies ------------- - -Stone of Orthanc is based upon the following projects: - -* Orthanc, a lightweight Vendor Neutral Archive (DICOM server): - http://www.orthanc-server.com/ - -* Cairo and pixman, a cross-platform 2D graphics library: - https://www.cairographics.org/ - -* Optionally, SDL, a cross-platform multimedia library: - https://www.libsdl.org/ - -Prerequisites to compile on Ubuntu: -``` -sudo apt-get install -y libcairo-dev libpixman-1-dev libsdl2-dev -``` - -Installation and usage ----------------------- - -Build instructions are similar to that of Orthanc: -https://orthanc.chu.ulg.ac.be/book/faq/compiling.html - -Usage details are available as part of the Orthanc Book: -http://book.orthanc-server.com/developers/stone.html - -Stone of Orthanc comes with several sample applications in the -"Samples" folder. These samples can be compiled into Web Assembly -or into native SDL applications. - -to build the WASM samples: -------------------------- -``` -cd ~/orthanc-stone/Applications/Samples -./build-wasm.sh -``` - -to serve the WASM samples: -``` -# launch an Orthanc listening on 8042 port: -Orthanc - -# launch an nginx that will serve the WASM static files and reverse proxy Orthanc -sudo nginx -p $(pwd) -c nginx.local.conf -``` -Now, you can open the samples in http://localhost:9977 - -to build the SDL native samples (SimpleViewer only): -------------------------------- -``` -mkdir -p ~/builds/orthanc-stone-build -cd ~/builds/orthanc-stone-build -cmake -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ~/orthanc-stone/Applications/Samples/ -cmake --build . --target OrthancStoneSimpleViewer -- -j 5 -``` - -to execute the native samples: -``` -# launch an Orthanc listening on 8042 port: -Orthanc - -# launch the sample -./OrthancStoneSimpleViewer --studyId=XX -``` - -to build the Qt native samples on Windows: ------------------------------------------ - -In a VS2015 x64 Native Tools Command Prompt: - -``` -mkdir -p C:\Users\alain\osimis\builds\orthanc-stone-build -cd C:\Users\alain\osimis\builds\orthanc-stone-build -cmake -G Ninja -DSTATIC_BUILD=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=C:\Users\alain\osimis\orthanc -DCMAKE_PREFIX_PATH=C:\Qt\5.9.3\msvc2015_64 ..\..\orthanc-stone\Applications\Samples - -cmake -G Ninja -DALLOW_DOWNLOADS=ON -DENABLE_QT=ON ~/orthanc-stone/Applications/Samples/ -cmake --build . --target OrthancStoneSimpleViewer -- -j 5 -``` - - -Licensing ---------- - -Stone of Orthanc is licensed under the AGPL license. - -We also kindly ask scientific works and clinical studies that make -use of Orthanc to cite Orthanc in their associated publications. -Similarly, we ask open-source and closed-source products that make -use of Orthanc to warn us about this use. You can cite our work -using the following BibTeX entry: - -@Article{Jodogne2018, - author="Jodogne, S{\'e}bastien", - title="The {O}rthanc Ecosystem for Medical Imaging", - journal="Journal of Digital Imaging", - year="2018", - month="Jun", - day="01", - volume="31", - number="3", - pages="341--352", - issn="1618-727X", - doi="10.1007/s10278-018-0082-y", - url="https://doi.org/10.1007/s10278-018-0082-y" -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,198 @@ +Stone of Orthanc +================ + +General Information +------------------- + +This repository contains the source code of the Stone of Orthanc. + +Stone of Orthanc is a lightweight, cross-platform C++ framework for +the CPU-based rendering of medical images. It notably features support +for MPR (multiplanar reconstruction of volume images), PET-CT fusion, +and accurate physical world coordinates. + +Stone of Orthanc comes bundled with its own software-based rendering +engine (based upon pixman). This engine will use CPU hardware +acceleration if available (notably SSE2, SSSE3, and NEON instruction +sets), but not the GPU. This makes Stone a highly versatile framework +that can run even on low-performance platforms. Note that Stone is +able to display DICOM series without having to entirely store them in +the RAM (i.e. frame by frame). + +Thanks to its standalone rendering engine, Stone of Orthanc is also +compatible with any GUI framework (such as Qt, wxWidgets, MFC...). The +provided sample applications use the SDL framework. + +Stone is conceived as a companion toolbox to the Orthanc VNA (vendor +neutral archive, i.e. DICOM server). As a consequence, Stone will +smoothly interface with Orthanc out of the box. Interestingly, Stone +does not contain any DICOM toolkit: It entirely relies on the REST API +of Orthanc to parse/decode DICOM images. However, thanks to the +object-oriented architecture of Stone, it is possible to avoid this +dependency upon Orthanc, e.g. to download DICOM datasets using +DICOMweb. + + +Comparison +---------- + +Pay attention to the fact that Stone of Orthanc is a toolkit, and not +a fully-featured application for the visualization of medical images +(such as Horos/OsiriX or Ginkgo CADx). However, such applications +can be built on the top of Stone of Orthanc. + +Stone of Orthanc is quite similar to two other well-known toolkits: + +* Cornerstone, a client-side JavaScript toolkit to display medical + images in Web browsers, by Chris Hafey <chafey@gmail.com>: + https://github.com/chafey/cornerstone + + Contrarily to Cornerstone, Stone of Orthanc can be embedded into + native, heavyweight applications. + +* VTK, a C++ toolkit for scientific visualization, by Kitware: + http://www.vtk.org/ + + Contrarily to VTK, Stone of Orthanc is focused on CPU-based, 2D + rendering: The GPU will not be used. + + +Dependencies +------------ + +Stone of Orthanc is based upon the following projects: + +* Orthanc, a lightweight Vendor Neutral Archive (DICOM server): + http://www.orthanc-server.com/ + +* Cairo and pixman, a cross-platform 2D graphics library: + https://www.cairographics.org/ + +* Optionally, SDL, a cross-platform multimedia library: + https://www.libsdl.org/ + +Prerequisites to compile natively on Ubuntu: +``` +sudo apt-get install -y libcairo-dev libpixman-1-dev libsdl2-dev +``` + +The emscripten SDK is required for the WASM build. Please install it +in `~/apps/emsdk`. If you wish to use it in another way, please edit +the `build-wasm.sh` file. + +ninja (`sudo apt-get install -y ninja-build`) is used instead of make, for performance reasons. + +Installation and usage ---------------------- + +Build instructions are similar to that of Orthanc: +http://book.orthanc-server.com/faq/compiling.html + +Usage details are available as part of the Orthanc Book: +http://book.orthanc-server.com/developers/stone.html + +Stone of Orthanc comes with several sample applications in the +`Samples` folder. These samples can be compiled into Web Assembly or +into native SDL applications. + +The following assumes that the source code to be downloaded in +`~/orthanc-stone` and Orthanc source code to be checked out in +`~/orthanc`. + +Building the WASM samples +------------------------------------- +``` +cd ~/orthanc-stone/Applications/Samples +./build-wasm.sh +``` + +Serving the WASM samples +------------------------------------ +``` +# launch an Orthanc listening on 8042 port: +Orthanc + +# launch an nginx that will serve the WASM static files and reverse +# proxy +sudo nginx -p $(pwd) -c nginx.local.conf +``` + +You can now open the samples in http://localhost:9977 + +Building the SDL native samples (SimpleViewer only) +--------------------------------------------------- + +The following also assumes that you have checked out the Orthanc +source code in an `orthanc` folder next to the Stone of Orthanc +repository, please enter the following: + +``` +mkdir -p ~/builds/orthanc-stone-build +cd ~/builds/orthanc-stone-build +cmake -DORTHANC_FRAMEWORK_SOURCE=path \ + -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc \ + -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON \ + ~/orthanc-stone/Applications/Samples/ +``` + +If you are working on Windows, add the correct generator option to +cmake to, for instance, generate msbuild files for Visual Studio. + +Then, under Linux: +``` +cmake --build . --target OrthancStoneSimpleViewer -- -j 5 +``` + +Note: replace `$($pwd)` with the current directory when not using Powershell + +Building the Qt native samples (SimpleViewer only) under Windows: +------------------------------------------------------------------ +For instance, if Qt is installed in `C:\Qt\5.12.0\msvc2017_64` + +`cmake -DSTATIC_BUILD=ON -DCMAKE_PREFIX_PATH=C:\Qt\5.12.0\msvc2017_64 -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_QT=ON -G "Visual Studio 15 2017 Win64" ../orthanc-stone/Applications/Samples/` + +Note: replace `$($pwd)` with the current directory when not using Powershell + + + +Building the SDL native samples (SimpleViewer only) under Windows: +------------------------------------------------------------------ +`cmake -DSTATIC_BUILD=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON -G "Visual Studio 15 2017 Win64" ../orthanc-stone/Applications/Samples/` + +Note: replace `$($pwd)` with the current directory when not using Powershell + +Executing the native samples: +-------------------------------- +``` +# launch an Orthanc listening on 8042 port: +Orthanc + +# launch the sample +./OrthancStoneSimpleViewer --studyId=XX +``` + +Licensing +--------- + +Stone of Orthanc is licensed under the AGPL license. + +We also kindly ask scientific works and clinical studies that make +use of Orthanc to cite Orthanc in their associated publications. +Similarly, we ask open-source and closed-source products that make +use of Orthanc to warn us about this use. You can cite our work +using the following BibTeX entry: + +@Article{Jodogne2018, + author="Jodogne, S{\'e}bastien", + title="The {O}rthanc Ecosystem for Medical Imaging", + journal="Journal of Digital Imaging", + year="2018", + month="Jun", + day="01", + volume="31", + number="3", + pages="341--352", + issn="1618-727X", + doi="10.1007/s10278-018-0082-y", + url="https://doi.org/10.1007/s10278-018-0082-y" +} +
--- a/Resources/CMake/BoostExtendedConfiguration.cmake Tue Feb 26 12:58:03 2019 +0100 +++ b/Resources/CMake/BoostExtendedConfiguration.cmake Tue Mar 12 14:50:14 2019 +0100 @@ -29,6 +29,9 @@ ${BOOST_SOURCES_DIR}/libs/program_options/src/utf8_codecvt_facet.cpp ${BOOST_SOURCES_DIR}/libs/program_options/src/value_semantic.cpp ${BOOST_SOURCES_DIR}/libs/program_options/src/variables_map.cpp + ${BOOST_SOURCES_DIR}/libs/chrono/src/thread_clock.cpp + ${BOOST_SOURCES_DIR}/libs/chrono/src/chrono.cpp + ${BOOST_SOURCES_DIR}/libs/chrono/src/process_cpu_clocks.cpp #${BOOST_SOURCES_DIR}/libs/program_options/src/winmain.cpp ) add_definitions(-DBOOST_PROGRAM_OPTIONS_NO_LIB)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/ProtobufCodeGeneration.cmake Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,77 @@ +# HOW TO USE: +# the GenerateCodeFromProtobufSchema will generate files in ${CMAKE_BINARY_DIR} and will +# populate PROTOBUF_AUTOGENERATED_SOURCES with the list of generated files +# AS OF 2019-01-30, it requires protoc (version 3.6.1.x) to be available in the path +set(PROTOBUF_AUTOGENERATED_SOURCES) + +# TODO: use find_program (<VAR> name1 [path1 path2 ...]) to located the protobuf compiler +# TODO: automated the TS plugin installation + +macro(GenerateCodeFromProtobufSchema schemaFilePath outputBaseDirectory) + # extract file name + GetFilePathWithoutLastExtension(schemaFilePathWithoutExt ${schemaFilePath}) + + # remove extension + GetFilenameFromPath(schemaFileNameWithoutExt ${schemaFilePathWithoutExt}) + + set(generatedFilePathWithoutExtension "${CMAKE_BINARY_DIR}/AUTOGENERATED/${schemaFileNameWithoutExt}") + set(generatedCppSourceFilePath "${generatedFilePathWithoutExtension}.pb.cc") + set(generatedCppHeaderFilePath "${generatedFilePathWithoutExtension}.pb.h") + set(generatedJsFilePath "${generatedFilePathWithoutExtension}_pb.js") + set(generatedTsFilePath "${generatedFilePathWithoutExtension}_pb.d.ts") + # set(generatedJsFileName "${generatedFilePathWithoutExtension}.js") + + # set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED") + # set(AUTOGENERATED_SOURCES) + + set(PROTOC_EXECUTABLE "PROTOC") + find_program(PROTOC_EXECUTABLE_SEARCH ${FLATC_EXECUTABLE}) + if(NOT PROTOC_EXECUTABLE_SEARCH) + message(FATAL_ERROR "The Protocol Buffers compiler (protoc[.exe]) cannot be found!") + endif() + + # TODO CUSTOMIZE FOR TYPESCRIPT + set(SCRIPT_CPP_OPTIONS) + list(APPEND SCRIPT_CPP_OPTIONS "----cpp_out=${CMAKE_BINARY_DIR}/AUTOGENERATED") + # list(APPEND SCRIPT_CPP_OPTIONS "gnagna") + + set(SCRIPT_TS_OPTIONS) + + list(APPEND SCRIPT_TS_OPTIONS "--ts") + list(APPEND SCRIPT_TS_OPTIONS "gnagna") + + add_custom_command( + OUTPUT + ${generatedCppSourceFilePath} + ${generatedCppHeaderFilePath} + COMMAND + ${PROTOC_EXECUTABLE} ${SCRIPT_CPP_OPTIONS} ${schemaFilePath} + DEPENDS + ${schemaFilePath} + ) + + add_custom_command( + OUTPUT + ${generatedTsFileName} + ${generatedJsFilePath} + COMMAND + ${PROTOC_EXECUTABLE} ${SCRIPT_TS_OPTIONS} ${schemaFilePath} + DEPENDS + ${schemaFilePath} + ) + + # add_custom_command( + # OUTPUT + # ${generatedJsFileName} + # COMMAND + # ${FLATC_EXECUTABLE} ${SCRIPT_JS_OPTIONS} ${schemaFilePath} + # DEPENDS + # ${schemaFilePath} + # ) + + list(APPEND FLATC_AUTOGENERATED_SOURCES "${generatedCppFileName}") + # list(APPEND FLATC_AUTOGENERATED_SOURCES "${generatedJsFileName}") + list(APPEND FLATC_AUTOGENERATED_SOURCES "${generatedTsFileName}") + +endmacro() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/Utilities.cmake Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,37 @@ + + +macro(GetFilenameFromPath TargetVariable Path) +#message(STATUS "GetFilenameFromPath (1): Path = ${Path} TargetVariable = ${${TargetVariable}}") +string(REPLACE "\\" "/" PathWithFwdSlashes "${Path}") +string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${PathWithFwdSlashes}") +#message(STATUS "GetFilenameFromPath (2): Path = ${Path} Path = ${PathWithFwdSlashes} TargetVariable = ${TargetVariable}") +endmacro() + +macro(GetFilePathWithoutLastExtension TargetVariable FilePath) +string(REGEX REPLACE "(^.*)\\.([^\\.]+)" "\\1" ${TargetVariable} "${FilePath}") +#message(STATUS "GetFileNameWithoutLastExtension: FilePath = ${FilePath} TargetVariable = ${${TargetVariable}}") +endmacro() + +macro(Test_GetFilePathWithoutLastExtension) +set(tmp "/prout/zi/goui.goui.cpp") +GetFilePathWithoutLastExtension(TargetVariable "${tmp}") +if(NOT ("${TargetVariable}" STREQUAL "/prout/zi/goui.goui")) + message(FATAL_ERROR "Test_GetFilePathWithoutLastExtension failed (1)") +else() + #message(STATUS "Test_GetFilePathWithoutLastExtension: <<OK>>") +endif() +endmacro() + +Test_GetFilePathWithoutLastExtension() + +macro(Test_GetFilenameFromPath) +set(tmp "/prout/../../dada/zi/goui.goui.cpp") +GetFilenameFromPath(TargetVariable "${tmp}") +if(NOT ("${TargetVariable}" STREQUAL "goui.goui.cpp")) + message(FATAL_ERROR "Test_GetFilenameFromPath failed") +else() + #message(STATUS "Test_GetFilenameFromPath: <<OK>>") +endif() +endmacro() + +Test_GetFilenameFromPath()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/Graveyard/playground.ts Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,259 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 +*/ + +namespace VsolStuff +{ + enum EnumMonth0 + { + January, + February, + March + }; + + // interface Serializer + // { + // Serialize(value: number): string; + // Serialize(value: string): string; + // Serialize(value: EnumMonth0): string; + // }; + function printf(value: any):void + { + console.log(value) + } + + // function StoneSerialize(value: string) : string; + // function StoneSerialize(value: number) : string; + // function StoneSerialize(value: EnumMonth0) : string; + function StoneSerialize<T>(value: T[]) : string; + + function StoneSerialize<T>(value: T[] | EnumMonth0) : string + { + let valueType = typeof value; + printf(`About to serialize value. Type is ${valueType}`) + printf(`About to serialize value. Type is ${typeof value}`) + return "Choucroute"; + } + + function main():number + { + enum Color {Red = 1, Green = 2, Blue = 4} + let color: Color = Color.Green; + printf("---------------------------"); + printf(`typeof color: ${typeof color}`); + printf("---------------------------"); + let colors: Color[] = [] + colors.push(Color.Green); + colors.push(Color.Red); + printf(`typeof colors: ${typeof colors}`); + printf(`Array.isArray(colors): ${Array.isArray(colors)}`); + printf("---------------------------"); + + + let toto:EnumMonth0[] = []; + + toto.push(EnumMonth0.February); + toto.push(EnumMonth0.March); + + printf(JSON.stringify(toto)); + + return 0; + + } + + main() + +// string StoneSerialize_number(int32_t value) +// { + +// Json::Value result(value); +// return result; +// } + +// Json::Value StoneSerialize(double value) +// { +// Json::Value result(value); +// return result; +// } + +// Json::Value StoneSerialize(bool value) +// { +// Json::Value result(value); +// return result; +// } + +// Json::Value StoneSerialize(const std::string& value) +// { +// // the following is better than +// Json::Value result(value.data(),value.data()+value.size()); +// return result; +// } + +// template<typename T> +// Json::Value StoneSerialize(const std::map<std::string,T>& value) +// { +// Json::Value result(Json::objectValue); + +// for (std::map<std::string, T>::const_iterator it = value.cbegin(); +// it != value.cend(); ++it) +// { +// // it->first it->second +// result[it->first] = StoneSerialize(it->second); +// } +// return result; +// } + +// template<typename T> +// Json::Value StoneSerialize(const std::vector<T>& value) +// { +// Json::Value result(Json::arrayValue); +// for (size_t i = 0; i < value.size(); ++i) +// { +// result.append(StoneSerialize(value[i])); +// } +// return result; +// } + +// enum EnumMonth0 +// { +// January, +// February, +// March +// }; + +// std::string ToString(EnumMonth0 value) +// { +// switch(value) +// { +// case January: +// return "January"; +// case February: +// return "February"; +// case March: +// return "March"; +// default: +// { +// std::stringstream ss; +// ss << "Unrecognized EnumMonth0 value (" << static_cast<int64_t>(value) << ")"; +// throw std::runtime_error(ss.str()); +// } +// } +// } + +// void FromString(EnumMonth0& value, std::string strValue) +// { +// if (strValue == "January" || strValue == "EnumMonth0_January") +// { +// return January; +// } +// else if (strValue == "February" || strValue == "EnumMonth0_February") +// { +// return February; +// } +// #error Not implemented yet +// } + +// Json::Value StoneSerialize(const EnumMonth0& value) +// { +// return StoneSerialize(ToString(value)); +// } +// struct Message1 +// { +// int32_t a; +// std::string b; +// EnumMonth0 c; +// bool d; +// }; + +// struct Message2 +// { +// std::string toto; +// std::vector<Message1> tata; +// std::vector<std::string> tutu; +// std::map<std::string, std::string> titi; +// std::map<std::string, Message1> lulu; +// }; + +// Json::Value StoneSerialize(const Message1& value) +// { +// Json::Value result(Json::objectValue); +// result["a"] = StoneSerialize(value.a); +// result["b"] = StoneSerialize(value.b); +// result["c"] = StoneSerialize(value.c); +// result["d"] = StoneSerialize(value.d); +// return result; +// } + +// Json::Value StoneSerialize(const Message2& value) +// { +// Json::Value result(Json::objectValue); +// result["toto"] = StoneSerialize(value.toto); +// result["tata"] = StoneSerialize(value.tata); +// result["tutu"] = StoneSerialize(value.tutu); +// result["titi"] = StoneSerialize(value.titi); +// result["lulu"] = StoneSerialize(value.lulu); +// return result; +// } +// } + +// int main() +// { +// VsolStuff::Message1 msg1_0; +// msg1_0.a = 42; +// msg1_0.b = "Benjamin"; +// msg1_0.c = VsolStuff::January; +// msg1_0.d = true; + +// VsolStuff::Message1 msg1_1; +// msg1_1.a = 43; +// msg1_1.b = "Sandrine"; +// msg1_1.c = VsolStuff::March; +// msg1_0.d = false; + +// // std::string toto; +// // std::vector<Message1> tata; +// // std::vector<std::string> tutu; +// // std::map<int32_t, std::string> titi; +// // std::map<int32_t, Message1> lulu; + +// VsolStuff::Message2 msg2_0; +// msg2_0.toto = "Prout zizi"; +// msg2_0.tata.push_back(msg1_0); +// msg2_0.tata.push_back(msg1_1); +// msg2_0.tutu.push_back("Mercadet"); +// msg2_0.tutu.push_back("Poisson"); +// msg2_0.titi["44"] = "key 44"; +// msg2_0.titi["45"] = "key 45"; +// msg2_0.lulu["54"] = msg1_1; +// msg2_0.lulu["55"] = msg1_0; +// auto result = VsolStuff::StoneSerialize(msg2_0); +// auto resultStr = result.toStyledString(); + +// Json::Value readValue; + +// Json::CharReaderBuilder builder; +// Json::CharReader* reader = builder.newCharReader(); +// std::string errors; + +// bool ok = reader->parse( +// resultStr.c_str(), +// resultStr.c_str() + resultStr.size(), +// &readValue, +// &errors +// ); +// delete reader; + +// if (!ok) +// { +// std::stringstream ss; +// ss << "Json parsing error: " << errors; +// throw std::runtime_error(ss.str()); +// } +// std::cout << readValue.get("toto", "Default Value").asString() << std::endl; +// return 0; +// } + + +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/Graveyard/playground2.ts Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,72 @@ +class Greeter { + greeting: string; + constructor(message: string) { + this.greeting = message; + } + greet() { + return "Hello, " + this.greeting; + } +} +enum Color { + Red, + Green, + Blue, +}; + +function ColorToString(value: Color) +{ + switch (value) + { + case Color.Red: + return "Red"; + case Color.Green: + return "Green"; + case Color.Blue: + return "Blue"; + default: + throw new Error(`Unrecognized Color value(${value})`); + } +} + +let color: Color = Color.Red; + +document.body.textContent = "<p>---------------------</p>" +document.body.textContent += "<p>********************************</p>" + +class TestMessage { + s1: string; + s2: Array<string>; + s3: Array<Array<string>>; + s4: Map<string, number>; + s5: Map<number, Array<string>>; + s6: Color; + s7: boolean; +} + +let tm = new TestMessage(); +tm.s2 = new Array<string>() +tm.s2.push("toto"); +tm.s2.push("toto2"); +tm.s2.push("toto3"); +tm.s4 = new Map<string, number>(); +tm.s4["toto"] = 42; +tm.s4["toto"] = 1999; +tm.s4["tatata"] = 1999; +tm.s6 = Color.Red; +tm.s7 = true + +let txt = JSON.stringify(tm) +let txtElem = document.createElement('textarea'); +txtElem.value = txt; + +document.body.appendChild(txtElem); + +let greeter = new Greeter("world"); + +let button = document.createElement('button'); +button.textContent = "Say Hello"; +button.onclick = function() { + alert(greeter.greet()); +} + +document.body.appendChild(button);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/Graveyard/playground3.ts Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,275 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 +*/ + +namespace VsolStuff222 { + export enum EnumMonth0 { + January, + February, + March + }; + + export class Message1 { + a: number; + b: string; + c: EnumMonth0; + d: boolean; + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolStuff.Message1'; + container['value'] = this; + return JSON.stringify(container); + } + }; + + export class Message2 { + toto: string; + tata: Message1[]; + tutu: string[]; + titi: Map<string, string>; + lulu: Map<string, Message1>; + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolStuff.Message2'; + container['value'] = this; + return JSON.stringify(container); + } + }; +} + +function printf(value: any): void { + console.log(value) +} + +function main(): number { + + let msg1_0 = new VsolStuff.Message1(); + msg1_0.a = 42; + msg1_0.b = "Benjamin"; + msg1_0.c = VsolStuff.EnumMonth0.January; + msg1_0.d = true; + + let msg1_1 = new VsolStuff.Message1(); + msg1_1.a = 43; + msg1_1.b = "Sandrine"; + msg1_1.c = VsolStuff.EnumMonth0.March; + msg1_0.d = false; + + // std::string toto; + // std::vector<Message1> tata; + // std::vector<std::string> tutu; + // std::map<int32_t, std::string> titi; + // std::map<int32_t, Message1> lulu; + + let msg2_0 = new VsolStuff.Message2(); + msg2_0.toto = "Prout zizi"; + msg2_0.tata = new Array<VsolStuff.Message1>(); + msg2_0.tata.push(msg1_0); + msg2_0.tata.push(msg1_1); + msg2_0.tutu.push("Mercadet"); + msg2_0.tutu.push("Poisson");ing + msg2_0.titi["44"] = "key 44"; + msg2_0.titi["45"] = "key 45"; + msg2_0.lulu["54"] = msg1_1; + msg2_0.lulu["55"] = msg1_0; + let result:string = VsolStuff.StoneSerialize(msg2_0); + return 0; +} + +main() + +// string StoneSerialize_number(int32_t value) +// { + +// Json::Value result(value); +// return result; +// } + +// Json::Value StoneSerialize(double value) +// { +// Json::Value result(value); +// return result; +// } + +// Json::Value StoneSerialize(bool value) +// { +// Json::Value result(value); +// return result; +// } + +// Json::Value StoneSerialize(const std::string& value) +// { +// // the following is better than +// Json::Value result(value.data(),value.data()+value.size()); +// return result; +// } + +// template<typename T> +// Json::Value StoneSerialize(const std::map<std::string,T>& value) +// { +// Json::Value result(Json::objectValue); + +// for (std::map<std::string, T>::const_iterator it = value.cbegin(); +// it != value.cend(); ++it) +// { +// // it->first it->second +// result[it->first] = StoneSerialize(it->second); +// } +// return result; +// } + +// template<typename T> +// Json::Value StoneSerialize(const std::vector<T>& value) +// { +// Json::Value result(Json::arrayValue); +// for (size_t i = 0; i < value.size(); ++i) +// { +// result.append(StoneSerialize(value[i])); +// } +// return result; +// } + +// enum EnumMonth0 +// { +// January, +// February, +// March +// }; + +// std::string ToString(EnumMonth0 value) +// { +// switch(value) +// { +// case January: +// return "January"; +// case February: +// return "February"; +// case March: +// return "March"; +// default: +// { +// std::stringstream ss; +// ss << "Unrecognized EnumMonth0 value (" << static_cast<int64_t>(value) << ")"; +// throw std::runtime_error(ss.str()); +// } +// } +// } + +// void FromString(EnumMonth0& value, std::string strValue) +// { +// if (strValue == "January" || strValue == "EnumMonth0_January") +// { +// return January; +// } +// else if (strValue == "February" || strValue == "EnumMonth0_February") +// { +// return February; +// } +// #error Not implemented yet +// } + +// Json::Value StoneSerialize(const EnumMonth0& value) +// { +// return StoneSerialize(ToString(value)); +// } +// struct Message1 +// { +// int32_t a; +// std::string b; +// EnumMonth0 c; +// bool d; +// }; + +// struct Message2 +// { +// std::string toto; +// std::vector<Message1> tata; +// std::vector<std::string> tutu; +// std::map<std::string, std::string> titi; +// std::map<std::string, Message1> lulu; +// }; + +// Json::Value StoneSerialize(const Message1& value) +// { +// Json::Value result(Json::objectValue); +// result["a"] = StoneSerialize(value.a); +// result["b"] = StoneSerialize(value.b); +// result["c"] = StoneSerialize(value.c); +// result["d"] = StoneSerialize(value.d); +// return result; +// } + +// Json::Value StoneSerialize(const Message2& value) +// { +// Json::Value result(Json::objectValue); +// result["toto"] = StoneSerialize(value.toto); +// result["tata"] = StoneSerialize(value.tata); +// result["tutu"] = StoneSerialize(value.tutu); +// result["titi"] = StoneSerialize(value.titi); +// result["lulu"] = StoneSerialize(value.lulu); +// return result; +// } +// } + +// int main() +// { +// VsolStuff::Message1 msg1_0; +// msg1_0.a = 42; +// msg1_0.b = "Benjamin"; +// msg1_0.c = VsolStuff::January; +// msg1_0.d = true; + +// VsolStuff::Message1 msg1_1; +// msg1_1.a = 43; +// msg1_1.b = "Sandrine"; +// msg1_1.c = VsolStuff::March; +// msg1_0.d = false; + +// // std::string toto; +// // std::vector<Message1> tata; +// // std::vector<std::string> tutu; +// // std::map<int32_t, std::string> titi; +// // std::map<int32_t, Message1> lulu; + +// VsolStuff::Message2 msg2_0; +// msg2_0.toto = "Prout zizi"; +// msg2_0.tata.push_back(msg1_0); +// msg2_0.tata.push_back(msg1_1); +// msg2_0.tutu.push_back("Mercadet"); +// msg2_0.tutu.push_back("Poisson"); +// msg2_0.titi["44"] = "key 44"; +// msg2_0.titi["45"] = "key 45"; +// msg2_0.lulu["54"] = msg1_1; +// msg2_0.lulu["55"] = msg1_0; +// auto result = VsolStuff::StoneSerialize(msg2_0); +// auto resultStr = result.toStyledString(); + +// Json::Value readValue; + +// Json::CharReaderBuilder builder; +// Json::CharReader* reader = builder.newCharReader(); +// std::string errors; + +// bool ok = reader->parse( +// resultStr.c_str(), +// resultStr.c_str() + resultStr.size(), +// &readValue, +// &errors +// ); +// delete reader; + +// if (!ok) +// { +// std::stringstream ss; +// ss << "Json parsing error: " << errors; +// throw std::runtime_error(ss.str()); +// } +// std::cout << readValue.get("toto", "Default Value").asString() << std::endl; +// return 0; +// } + + +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/Graveyard/playground4.py Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,68 @@ +testYaml = """ +enum SomeEnum: + - january + - feb + +struct Message0: + a: string + +struct Message1: + a: string + b: int32 + c: vector<Message0> + d: SomeEnum = january + e: SomeEnum= january + f: SomeEnum=january + g: SomeEnum =january + + +# github.com/AlDanial/cloc +header2 : + cloc_version : 1.67 + elapsed_seconds : int32_t + +header : + cloc_version : 1.67 + elapsed_seconds : int32_t + cloc_url : vector<map<string,int32>> + n_files : 1 + n_lines : 3 + files_per_second : 221.393718659277 + lines_per_second : 664.181155977831 + report_file : IDL.idl.yaml +IDL : + nFiles: 1 + blank: 0 + comment: 2 + code: 1 +EnumSUM: + - aaa + - bbb + +SUM: + blank: 0 + comment: 2 + code: 1 + nFiles: 1 +""" + +import yaml + +b = yaml.load(testYaml) +print(b) + +c = { + 'enum SomeEnum': ['january', 'feb'], + 'struct Message0': {'a': 'string'}, + 'struct Message1': { + 'a': 'string', + 'b': 'int32', + 'c': 'vector<Message0>', + 'd': 'vector<map<string,int32>>', + 'e': 'SomeEnum= january', + 'f': 'SomeEnum=january', + 'g': 'SomeEnum =january' + }, +} + +print(c) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/Graveyard/runts.ps1 Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,57 @@ +# echo "+----------------------+" +# echo "| playground.ts |" +# echo "+----------------------+" + +# tsc -t ES2015 .\playground.ts; node .\playground.js + +# echo "+----------------------+" +# echo "| playground3.ts |" +# echo "+----------------------+" + +# tsc -t ES2015 .\playground3.ts; node .\playground3.js + +echo "+----------------------+" +echo "| stonegen |" +echo "+----------------------+" + +if(-not (test-Path "build")) { + mkdir "build" +} + +echo "Generate the TS and CPP wrapper... (to build/)" +python stonegentool.py -o "." test_data/test1.yaml +if($LASTEXITCODE -ne 0) { + Write-Error ("Code generation failed!") + exit $LASTEXITCODE +} + +echo "Compile the TS wrapper to JS... (in build/)" +tsc --module commonjs --sourceMap -t ES2015 --outDir "build/" VsolMessages_generated.ts +if($LASTEXITCODE -ne 0) { + Write-Error ("Code compilation failed!") + exit $LASTEXITCODE +} + +echo "Compile the test app..." +tsc --module commonjs --sourceMap -t ES2015 --outDir "build/" test_stonegen.ts +if($LASTEXITCODE -ne 0) { + Write-Error ("Code compilation failed!") + exit $LASTEXITCODE +} + +browserify "build/test_stonegen.js" "build/VsolMessages_generated.js" -o "build_browser/test_stonegen_fused.js" + +cp .\test_stonegen.html .\build_browser\ + +echo "Run the test app..." +Push-Location +cd build_browser +node .\test_stonegen_fused.js +Pop-Location +if($LASTEXITCODE -ne 0) { + Write-Error ("Code execution failed!") + exit $LASTEXITCODE +} + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/Graveyard/test_stonegen.html Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,1 @@ +<script type="text/javascript" src="test_stonegen_fused.js"></script>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/Graveyard/test_stonegen.ts Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,206 @@ +import * as VsolMessages from "./VsolMessages_generated"; + +function TEST_StoneGen_SerializeComplex() { + let msg1_0 = new VsolMessages.Message1(); + msg1_0.a = 42; + msg1_0.b = "Benjamin"; + msg1_0.c = VsolMessages.EnumMonth0.January; + msg1_0.d = true; + let msg1_1 = new VsolMessages.Message1(); + msg1_1.a = 43; + msg1_1.b = "Sandrine"; + msg1_1.c = VsolMessages.EnumMonth0.March; + msg1_0.d = false; + let result1_0 = msg1_0.StoneSerialize(); + let resultStr1_0 = JSON.stringify(result1_0); + let result1_1 = msg1_1.StoneSerialize(); + let resultStr1_1 = JSON.stringify(result1_1); + // std::string toto; + // std::vector<Message1> tata; + // std::vector<std::string> tutu; + // std::map<int32_t, std::string> titi; + // std::map<int32_t, Message1> lulu; + let msg2_0 = new VsolMessages.Message2(); + msg2_0.toto = "Prout zizi"; + msg2_0.tata.push(msg1_0); + msg2_0.tata.push(msg1_1); + msg2_0.tutu.push("Mercadet"); + msg2_0.tutu.push("Poisson"); + msg2_0.titi["44"] = "key 44"; + msg2_0.titi["45"] = "key 45"; + msg2_0.lulu["54"] = msg1_1; + msg2_0.lulu["55"] = msg1_0; + let result2 = msg2_0.StoneSerialize(); + let resultStr2 = JSON.stringify(result2); + let refResult2 = `{ +"type" : "VsolMessages.Message2", +"value" : +{ + "lulu" : + { + "54" : + { + "a" : 43, + "b" : "Sandrine", + "c" : 2, + "d" : true + }, + "55" : + { + "a" : 42, + "b" : "Benjamin", + "c" : 0, + "d" : false + } + }, + "tata" : + [ + { + "a" : 42, + "b" : "Benjamin", + "c" : 0, + "d" : false + }, + { + "a" : 43, + "b" : "Sandrine", + "c" : 2, + "d" : true + } + ], + "titi" : + { + "44" : "key 44", + "45" : "key 45" + }, + "toto" : "Prout zizi", + "tutu" : + [ + "Mercadet", + "Poisson" + ] +} +} +`; + let refResult2Obj = JSON.parse(refResult2); + let resultStr2Obj = JSON.parse(resultStr2); + if (false) { + if (refResult2Obj !== resultStr2Obj) { + console.log("Results are different!"); + console.log(`refResult2Obj['value']['lulu']['54'] = ${refResult2Obj['value']['lulu']['54']}`); + console.log(`refResult2Obj['value']['lulu']['54']['a'] = ${refResult2Obj['value']['lulu']['54']['a']}`); + console.log("************************************************************"); + console.log("** REFERENCE OBJ **"); + console.log("************************************************************"); + console.log(refResult2Obj); + console.log("************************************************************"); + console.log("** ACTUAL OBJ **"); + console.log("************************************************************"); + console.log(resultStr2Obj); + console.log("************************************************************"); + console.log("** REFERENCE **"); + console.log("************************************************************"); + console.log(refResult2); + console.log("************************************************************"); + console.log("** ACTUAL **"); + console.log("************************************************************"); + console.log(resultStr2); + throw new Error("Wrong serialization"); + } + } + let refResultValue = JSON.parse(resultStr2); + console.log(refResultValue); +} +class MyDispatcher { + message1: VsolMessages.Message1; + message2: VsolMessages.Message2; + + HandleMessage1(value: VsolMessages.Message1) { + this.message1 = value; + return true; + } + HandleMessage2(value: VsolMessages.Message2) { + this.message2 = value; + return true; + } + HandleA(value) { + return true; + } + HandleB(value) { + return true; + } + HandleC(value) { + return true; + } +} +; +function TEST_StoneGen_DeserializeOkAndNok() { + let serializedMessage = `{ +"type" : "VsolMessages.Message2", +"value" : +{ + "lulu" : + { + "54" : + { + "a" : 43, + "b" : "Sandrine", + "c" : 2, + "d" : true + }, + "55" : + { + "a" : 42, + "b" : "Benjamin", + "c" : 0, + "d" : false + } + }, + "tata" : + [ + { + "a" : 42, + "b" : "Benjamin", + "c" : 0, + "d" : false + }, + { + "a" : 43, + "b" : "Sandrine", + "c" : 2, + "d" : true + } + ], + "titi" : + { + "44" : "key 44", + "45" : "key 45" + }, + "toto" : "Prout zizi", + "tutu" : + [ + "Mercadet", + "Poisson" + ] +} +}`; + let myDispatcher = new MyDispatcher(); + let ok = VsolMessages.StoneDispatchToHandler(serializedMessage, myDispatcher); + if (!ok) { + throw Error("Error when dispatching message!"); + } + if (myDispatcher.message1 != undefined) { + throw Error("(myDispatcher.Message1 != undefined)"); + } + if (myDispatcher.message2 == undefined) { + throw Error("(myDispatcher.Message2 == undefined)"); + } + console.log("TEST_StoneGen_DeserializeOkAndNok: OK!"); +} +function main() { + console.log("Entering main()"); + TEST_StoneGen_SerializeComplex(); + TEST_StoneGen_DeserializeOkAndNok(); + return 0; +} +console.log(`Exit code is: ${main()}`); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/Graveyard/tsconfig.json Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,22 @@ +{ + // "extends": "../../../../../orthanc-stone/Platforms/Wasm/tsconfig-stone", + "compilerOptions": { + // "outFile": "../../../WebApplication-build/to-embed/app.js", + // "module": "system", + // "sourceMap": false, + "lib": [ + "es2017", + "es2017", + "dom", + "dom.iterable" + ] + }, + "include": [ + // "commands/*.ts", + // "logger.ts", + // "app.ts", + // "main.ts", + // "ui.ts", + // "popup.ts" + ] +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/README.md Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,17 @@ +Requirements +---------------- + +Install Node and npm. + +Then: +- `npm install browserify` +- `npm install typescript` +- `npm install tsify` + +`testCppHandler` contains a C++ project that produces an executable +slurping a set of text files representing messages defined against +the `test_data/test1.yaml' schema and dumping them to `cout`. + +'testWasmIntegrated` contains a small Web app demonstrating the +interaction between TypeScript and C++ in WASM. +source ~/apps/emsdk/emsdk_env.sh \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/stonegentool.py Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,546 @@ +import json +import yaml +import re +import os +import sys +from jinja2 import Template +from io import StringIO +import time +import datetime + +""" + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 +""" + +# see https://stackoverflow.com/a/2504457/2927708 +def trim(docstring): + if not docstring: + return '' + # Convert tabs to spaces (following the normal Python rules) + # and split into a list of lines: + lines = docstring.expandtabs().splitlines() + # Determine minimum indentation (first line doesn't count): + indent = sys.maxsize + for line in lines[1:]: + stripped = line.lstrip() + if stripped: + indent = min(indent, len(line) - len(stripped)) + # Remove indentation (first line is special): + trimmed = [lines[0].strip()] + if indent < sys.maxsize: + for line in lines[1:]: + trimmed.append(line[indent:].rstrip()) + # Strip off trailing and leading blank lines: + while trimmed and not trimmed[-1]: + trimmed.pop() + while trimmed and not trimmed[0]: + trimmed.pop(0) + # Return a single string: + return '\n'.join(trimmed) + +class JsonHelpers: + """A set of utilities to perform JSON operations""" + + @staticmethod + def removeCommentsFromJsonContent(string): + """ + Remove comments from a JSON file + + Comments are not allowed in JSON but, i.e., Orthanc configuration files + contains C++ like comments that we need to remove before python can + parse the file + """ + # remove all occurrence streamed comments (/*COMMENT */) from string + string = re.sub(re.compile("/\*.*?\*/", re.DOTALL), "", string) + + # remove all occurrence singleline comments (//COMMENT\n ) from string + string = re.sub(re.compile("//.*?\n"), "", string) + + return string + + @staticmethod + def loadJsonWithComments(path): + """ + Reads a JSON file that may contain C++ like comments + """ + with open(path, "r") as fp: + fileContent = fp.read() + fileContent = JsonHelpers.removeCommentsFromJsonContent(fileContent) + return json.loads(fileContent) + + +def LoadSchemaFromJson(filePath): + return JsonHelpers.loadJsonWithComments(filePath) + +def CanonToCpp(canonicalTypename): + # C++: prefix map vector and string with std::map, std::vector and + # std::string + # replace int32 by int32_t + # replace float32 by float + # replace float64 by double + retVal = canonicalTypename + retVal = retVal.replace("map", "std::map") + retVal = retVal.replace("vector", "std::vector") + retVal = retVal.replace("string", "std::string") + retVal = retVal.replace("int32", "int32_t") + retVal = retVal.replace("float32", "float") + retVal = retVal.replace("float64", "double") + retVal = retVal.replace("json", "Json::Value") + return retVal + +def CanonToTs(canonicalTypename): + # TS: replace vector with Array and map with Map + # string remains string + # replace int32 by number + # replace float32 by number + # replace float64 by number + retVal = canonicalTypename + retVal = retVal.replace("map", "Map") + retVal = retVal.replace("vector", "Array") + retVal = retVal.replace("int32", "number") + retVal = retVal.replace("float32", "number") + retVal = retVal.replace("float64", "number") + retVal = retVal.replace("bool", "boolean") + retVal = retVal.replace("json", "Object") + return retVal + +def NeedsTsConstruction(enums, tsType): + if tsType == 'boolean': + return False + elif tsType == 'number': + return False + elif tsType == 'string': + return False + else: + enumNames = [] + for enum in enums: + enumNames.append(enum['name']) + if tsType in enumNames: + return False + return True + +def NeedsCppConstruction(canonTypename): + return False + +def RegisterTemplateFunction(template,func): + """Makes a function callable by a jinja2 template""" + template.globals[func.__name__] = func + return func + +def MakeTemplate(templateStr): + template = Template(templateStr) + RegisterTemplateFunction(template,CanonToCpp) + RegisterTemplateFunction(template,CanonToTs) + RegisterTemplateFunction(template,NeedsTsConstruction) + RegisterTemplateFunction(template,NeedsCppConstruction) + return template + +def MakeTemplateFromFile(templateFileName): + templateFile = open(templateFileName, "r") + templateFileContents = templateFile.read() + return MakeTemplate(templateFileContents) + templateFile.close() + +def EatToken(sentence): + """splits "A,B,C" into "A" and "B,C" where A, B and C are type names + (including templates) like "int32", "TotoTutu", or + "map<map<int32,vector<string>>,map<string,int32>>" """ + + if sentence.count("<") != sentence.count(">"): + raise Exception( + "Error in the partial template type list " + str(sentence) + "." + + " The number of < and > do not match!" + ) + + # the template level we're currently in + templateLevel = 0 + for i in range(len(sentence)): + if (sentence[i] == ",") and (templateLevel == 0): + return (sentence[0:i], sentence[i + 1 :]) + elif sentence[i] == "<": + templateLevel += 1 + elif sentence[i] == ">": + templateLevel -= 1 + return (sentence, "") + + +def SplitListOfTypes(typename): + """Splits something like + vector<string>,int32,map<string,map<string,int32>> + in: + - vector<string> + - int32 + map<string,map<string,int32>> + + This is not possible with a regex so + """ + stillStuffToEat = True + tokenList = [] + restOfString = typename + while stillStuffToEat: + firstToken, restOfString = EatToken(restOfString) + tokenList.append(firstToken) + if restOfString == "": + stillStuffToEat = False + return tokenList + + +templateRegex = \ + re.compile(r"([a-zA-Z0-9_]*[a-zA-Z0-9_]*)<([a-zA-Z0-9_,:<>]+)>") + + +def ParseTemplateType(typename): + """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", + then it returns (true,"SOMETHING","SOME<THING,EL<SE>>") + otherwise it returns (false,"","")""" + + # let's remove all whitespace from the type + # split without argument uses any whitespace string as separator + # (space, tab, newline, return or formfeed) + typename = "".join(typename.split()) + matches = templateRegex.match(typename) + if matches == None: + return (False, "", []) + else: + m = matches + assert len(m.groups()) == 2 + # we need to split with the commas that are outside of the + # defined types. Simply splitting at commas won't work + listOfDependentTypes = SplitListOfTypes(m.group(2)) + return (True, m.group(1), listOfDependentTypes) + +def GetStructFields(struct): + """This filters out the special metadata key from the struct fields""" + return [k for k in struct.keys() if k != '__handler'] + +def ComputeOrderFromTypeTree( + ancestors, + genOrder, + shortTypename, schema): + + if shortTypename in ancestors: + raise Exception( + "Cyclic dependency chain found: the last of " + str(ancestors) + + + " depends on " + str(shortTypename) + " that is already in the list." + ) + + if not (shortTypename in genOrder): + (isTemplate, _, dependentTypenames) = ParseTemplateType(shortTypename) + if isTemplate: + # if it is a template, it HAS dependent types... They can be + # anything (primitive, collection, enum, structs..). + # Let's process them! + for dependentTypename in dependentTypenames: + # childAncestors = ancestors.copy() NO TEMPLATE ANCESTOR!!! + # childAncestors.append(typename) + ComputeOrderFromTypeTree( + ancestors, genOrder, dependentTypename, schema + ) + else: + # If it is not template, we are only interested if it is a + # dependency that we must take into account in the dep graph, + # i.e., a struct. + if IsShortStructType(shortTypename, schema): + struct = schema[GetLongTypename(shortTypename, schema)] + # The keys in the struct dict are the member names + # The values in the struct dict are the member types + if struct: + # we reach this if struct is not None AND not empty + for field in GetStructFields(struct): + # we fill the chain of dependent types (starting here) + ancestors.append(shortTypename) + ComputeOrderFromTypeTree( + ancestors, genOrder, struct[field], schema) + # don't forget to restore it! + ancestors.pop() + + # now we're pretty sure our dependencies have been processed, + # we can start marking our code for generation (it might + # already have been done if someone referenced us earlier) + if not shortTypename in genOrder: + genOrder.append(shortTypename) + +# +-----------------------+ +# | Utility functions | +# +-----------------------+ + +def IsShortStructType(typename, schema): + fullStructName = "struct " + typename + return (fullStructName in schema) + +def GetLongTypename(shortTypename, schema): + if shortTypename.startswith("enum "): + raise RuntimeError('shortTypename.startswith("enum "):') + enumName = "enum " + shortTypename + isEnum = enumName in schema + + if shortTypename.startswith("struct "): + raise RuntimeError('shortTypename.startswith("struct "):') + structName = "struct " + shortTypename + isStruct = ("struct " + shortTypename) in schema + + if isEnum and isStruct: + raise RuntimeError('Enums and structs cannot have the same name') + + if isEnum: + return enumName + if isStruct: + return structName + +def IsTypename(fullName): + return (fullName.startswith("enum ") or fullName.startswith("struct ")) + +def IsEnumType(fullName): + return fullName.startswith("enum ") + +def IsStructType(fullName): + return fullName.startswith("struct ") + +def GetShortTypename(fullTypename): + if fullTypename.startswith("struct "): + return fullTypename[7:] + elif fullTypename.startswith("enum"): + return fullTypename[5:] + else: + raise RuntimeError \ + ('fullTypename should start with either "struct " or "enum "') + +def CheckSchemaSchema(schema): + if not "rootName" in schema: + raise Exception("schema lacks the 'rootName' key") + for name in schema.keys(): + if (not IsEnumType(name)) and (not IsStructType(name)) and \ + (name != 'rootName'): + raise RuntimeError \ + ('Type "' + str(name) + '" should start with "enum " or "struct "') + + # TODO: check enum fields are unique (in whole namespace) + # TODO: check struct fields are unique (in each struct) + # TODO: check that in the source schema, there are spaces after each colon + +nonTypeKeys = ['rootName'] +def GetTypesInSchema(schema): + """Returns the top schema keys that are actual type names""" + typeList = [k for k in schema if k not in nonTypeKeys] + return typeList + +# +-----------------------+ +# | Main processing logic | +# +-----------------------+ + +def ComputeRequiredDeclarationOrder(schema): + # sanity check + CheckSchemaSchema(schema) + + # we traverse the type dependency graph and we fill a queue with + # the required struct types, in a bottom-up fashion, to compute + # the declaration order + # The genOrder list contains the struct full names in the order + # where they must be defined. + # We do not care about the enums here... They do not depend upon + # anything and we'll handle them, in their original declaration + # order, at the start + genOrder = [] + for fullName in GetTypesInSchema(schema): + if IsStructType(fullName): + realName = GetShortTypename(fullName) + ancestors = [] + ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema) + return genOrder + +def GetStructFields(fieldDict): + """Returns the regular (non __handler) struct fields""" + # the following happens for empty structs + if fieldDict == None: + return fieldDict + ret = {} + for k,v in fieldDict.items(): + if k != "__handler": + ret[k] = v + if k.startswith("__") and k != "__handler": + raise RuntimeError("Fields starting with __ (double underscore) are reserved names!") + return ret + +def GetStructMetadata(fieldDict): + """Returns the __handler struct fields (there are default values that + can be overridden by entries in the schema + Not tested because it's a fail-safe: if something is broken in this, + dependent projects will not build.""" + metadataDict = {} + metadataDict['handleInCpp'] = False + metadataDict['handleInTypescript'] = False + + if fieldDict != None: + for k,v in fieldDict.items(): + if k.startswith("__") and k != "__handler": + raise RuntimeError("Fields starting with __ (double underscore) are reserved names") + if k == "__handler": + if type(v) == list: + for i in v: + if i == "cpp": + metadataDict['handleInCpp'] = True + elif i == "ts": + metadataDict['handleInTypescript'] = True + else: + raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\"") + elif type(v) == str: + if v == "cpp": + metadataDict['handleInCpp'] = True + elif v == "ts": + metadataDict['handleInTypescript'] = True + else: + raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\" (or a list of both)") + else: + raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\" (or a list of both)") + return metadataDict + +def ProcessSchema(schema, genOrder): + # sanity check + CheckSchemaSchema(schema) + + # let's doctor the schema to clean it up a bit + # order DOES NOT matter for enums, even though it's a list + enums = [] + for fullName in schema.keys(): + if IsEnumType(fullName): + # convert "enum Toto" to "Toto" + typename = GetShortTypename(fullName) + enum = {} + enum['name'] = typename + assert(type(schema[fullName]) == list) + enum['fields'] = schema[fullName] # must be a list + enums.append(enum) + + # now that the order has been established, we actually store\ + # the structs in the correct order + # the structs are like: + # example = [ + # { + # "name": "Message1", + # "fields": { + # "someMember":"int32", + # "someOtherMember":"vector<string>" + # } + # }, + # { + # "name": "Message2", + # "fields": { + # "someMember":"int32", + # "someOtherMember22":"vector<Message1>" + # } + # } + # ] + + structs = [] + for i in range(len(genOrder)): + # this is already the short name + typename = genOrder[i] + fieldDict = schema["struct " + typename] + struct = {} + struct['name'] = typename + struct['fields'] = GetStructFields(fieldDict) + struct['__meta__'] = GetStructMetadata(fieldDict) + structs.append(struct) + + templatingDict = {} + templatingDict['enums'] = enums + templatingDict['structs'] = structs + templatingDict['rootName'] = schema['rootName'] + + return templatingDict + +# +-----------------------+ +# | Write to files | +# +-----------------------+ + +# def WriteStreamsToFiles(rootName: str, genc: Dict[str, StringIO]) \ +# -> None: +# pass + +def LoadSchema(fn): + # latin-1 is a trick, when we do NOT care about NON-ascii chars but + # we wish to avoid using a decoding error handler + # (see http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html#files-in-an-ascii-compatible-encoding-best-effort-is-acceptable) + # TL;DR: all 256 values are mapped to characters in latin-1 so the file + # contents never cause an error. + with open(fn, 'r', encoding='latin-1') as f: + schemaText = f.read() + assert(type(schemaText) == str) + # ensure there is a space after each colon. Otherwise, dicts could be + # erroneously recognized as an array of strings containing ':' + for i in range(len(schemaText)-1): + ch = schemaText[i] + nextCh = schemaText[i+1] + if ch == ':': + if not (nextCh == ' ' or nextCh == '\n'): + lineNumber = schemaText.count("\n",0,i) + 1 + raise RuntimeError("Error at line " + str(lineNumber) + " in the schema: colons must be followed by a space or a newline!") + schema = yaml.load(schemaText) + return schema + +def GetTemplatingDictFromSchemaFilename(fn): + obj = LoadSchema(fn) + genOrder = ComputeRequiredDeclarationOrder(obj) + templatingDict = ProcessSchema(obj, genOrder) + currentDT = datetime.datetime.now() + templatingDict['currentDatetime'] = str(currentDT) + return templatingDict + +# +-----------------------+ +# | ENTRY POINT | +# +-----------------------+ +def Process(schemaFile, outDir): + tdico = GetTemplatingDictFromSchemaFilename(schemaFile) + + tsTemplateFile = \ + os.path.join(os.path.dirname(__file__), 'template.in.ts.j2') + template = MakeTemplateFromFile(tsTemplateFile) + renderedTsCode = template.render(**tdico) + outputTsFile = os.path.join( \ + outDir,str(tdico['rootName']) + "_generated.ts") + with open(outputTsFile,"wt",encoding='utf8') as outFile: + outFile.write(renderedTsCode) + + cppTemplateFile = \ + os.path.join(os.path.dirname(__file__), 'template.in.h.j2') + template = MakeTemplateFromFile(cppTemplateFile) + renderedCppCode = template.render(**tdico) + outputCppFile = os.path.join( \ + outDir, str(tdico['rootName']) + "_generated.hpp") + with open(outputCppFile,"wt",encoding='utf8') as outFile: + outFile.write(renderedCppCode) + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + usage="""stonegentool.py [-h] [-o OUT_DIR] [-v] input_schema + EXAMPLE: python stonegentool.py -o "generated_files/" """ + + """ "mainSchema.yaml,App Specific Commands.json" """ + ) + parser.add_argument("input_schema", type=str, \ + help="path to the schema file") + parser.add_argument( + "-o", + "--out_dir", + type=str, + default=".", + help="""path of the directory where the files + will be generated. Default is current + working folder""", + ) + parser.add_argument( + "-v", + "--verbosity", + action="count", + default=0, + help="""increase output verbosity (0 == errors + only, 1 == some verbosity, 2 == nerd + mode""", + ) + + args = parser.parse_args() + schemaFile = args.input_schema + outDir = args.out_dir + Process(schemaFile, outDir)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/stonegentool_test.py Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,408 @@ +# +# 1 2 3 4 5 6 7 8 +# 345678901234567890123456789012345678901234567890123456789012345678901234567890 +# + +from stonegentool import \ +EatToken,SplitListOfTypes,ParseTemplateType,ProcessSchema, \ +CheckSchemaSchema,LoadSchema,trim,ComputeRequiredDeclarationOrder, \ +GetTemplatingDictFromSchemaFilename,MakeTemplate,MakeTemplateFromFile +import unittest +import os +import re +import pprint +from jinja2 import Template + +def RemoveDateTimeLine(s : str): + # regex are non-multiline by default, and $ does NOT match the end of the line + s2 = re.sub(r"^// autogenerated by stonegentool on .*\n","",s) + return s2 + +class TestStonegentool(unittest.TestCase): + def test_EatToken_empty(self): + c = r"" + a,b = EatToken(c) + self.assertEqual(a,r"") + self.assertEqual(b,r"") + + def test_EatToken_simpleNonTemplate(self): + c = r"int32" + a,b = EatToken(c) + self.assertEqual(a,r"int32") + self.assertEqual(b,r"") + + def test_EatToken_simpleTemplate(self): + c = r"vector<string>" + a,b = EatToken(c) + self.assertEqual(a,r"vector<string>") + self.assertEqual(b,r"") + + def test_EatToken_complexTemplate(self): + c = r"vector<map<int64,string>>,vector<map<int32,string>>" + a,b = EatToken(c) + self.assertEqual(a,r"vector<map<int64,string>>") + self.assertEqual(b,r"vector<map<int32,string>>") + + def test_EatToken_complexTemplates(self): + c = r"vector<map<vector<string>,map<int32,string>>>,map<int32,string>,map<map<int32,string>,string>" + a,b = EatToken(c) + self.assertEqual(a,r"vector<map<vector<string>,map<int32,string>>>") + self.assertEqual(b,r"map<int32,string>,map<map<int32,string>,string>") + a,b = EatToken(b) + self.assertEqual(a,r"map<int32,string>") + self.assertEqual(b,r"map<map<int32,string>,string>") + + def test_SplitListOfTypes(self): + c = r"vector<map<vector<string>,map<int32,string>>>,map<int32,string>,map<map<int32,string>,string>" + lot = SplitListOfTypes(c) + self.assertEqual(3,len(lot)) + self.assertEqual("vector<map<vector<string>,map<int32,string>>>",lot[0]) + self.assertEqual("map<int32,string>",lot[1]) + self.assertEqual("map<map<int32,string>,string>",lot[2]) + + def test_SplitListOfTypes_bogus(self): + c = r"vector<map<vector<string>,map<int32,string>>,map<int32,string>,map<map<int32,string>,string" + self.assertRaises(Exception,SplitListOfTypes,c) # the argument c must be passed to assertRaises, not as a normal call of SplitListOfTypes + + def test_ParseTemplateType_true(self): + c = "map<vector<map<int,vector<string>>>,map<vector<int>,vector<string>>>" + (ok,a,b) = ParseTemplateType(c) + self.assertEqual(ok,True) + self.assertEqual(a,"map") + self.assertEqual(b,["vector<map<int,vector<string>>>","map<vector<int>,vector<string>>"]) + + (ok2,a2,b2) = ParseTemplateType(b[0]) + self.assertEqual(ok2,True) + self.assertEqual(a2,"vector") + self.assertEqual(b2,["map<int,vector<string>>"]) + + (ok3,a3,b3) = ParseTemplateType(b[1]) + self.assertEqual(ok3,True) + self.assertEqual(a3,"map") + self.assertEqual(b3,["vector<int>","vector<string>"]) + + (ok4,a4,b4) = ParseTemplateType(b2[0]) + self.assertEqual(ok4,True) + self.assertEqual(a4,"map") + self.assertEqual(b4,["int","vector<string>"]) + + def test_ParseSchema(self): + fn = os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml') + obj = LoadSchema(fn) + # we're happy if it does not crash :) + CheckSchemaSchema(obj) + + def test_ComputeRequiredDeclarationOrder(self): + fn = os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml') + obj = LoadSchema(fn) + genOrder: str = ComputeRequiredDeclarationOrder(obj) + self.assertEqual(5,len(genOrder)) + self.assertEqual("A",genOrder[0]) + self.assertEqual("B",genOrder[1]) + self.assertEqual("C",genOrder[2]) + self.assertEqual("Message1",genOrder[3]) + self.assertEqual("Message2",genOrder[4]) + + # def test_GeneratePreambleEnumerationAndStructs(self): + # fn = os.path.join(os.path.dirname(__file__), 'test', 'test1.jsonc') + # obj = LoadSchema(fn) + # (_,genc,_) = ProcessSchema(obj) + + def test_genEnums(self): + self.maxDiff = None + fn = os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml') + obj = LoadSchema(fn) + genOrder: str = ComputeRequiredDeclarationOrder(obj) + processedSchema = ProcessSchema(obj, genOrder) + self.assertTrue('rootName' in processedSchema) + + structs = {} + for v in processedSchema['structs']: + structs[v['name']] = v + enums = {} + for v in processedSchema['enums']: + enums[v['name']] = v + + self.assertTrue('C' in structs) + self.assertTrue('someBs' in structs['C']['fields']) + self.assertTrue('CrispType' in enums) + self.assertTrue('Message1' in structs) + message1Struct = structs['Message1'] + self.assertDictEqual(message1Struct, + { + 'name':'Message1', + 'fields': { + 'a': 'int32', + 'b': 'string', + 'c': 'EnumMonth0', + 'd': 'bool' + } + }) + + def test_GenerateTypeScriptEnums(self): + fn = os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml') + tdico = GetTemplatingDictFromSchemaFilename(fn) + template = Template(""" // end of generic methods +{% for enum in enums%} export enum {{enum['name']}} { +{% for key in enum['fields']%} {{key}}, +{%endfor%} }; + +{%endfor%}""") + renderedCode = template.render(**tdico) + renderedCodeRef = """ // end of generic methods + export enum MovieType { + RomCom, + Horror, + ScienceFiction, + Vegetables, + }; + + export enum CrispType { + SaltAndPepper, + CreamAndChives, + Paprika, + Barbecue, + }; + +""" + self.assertEqual(renderedCodeRef,renderedCode) + + def test_GenerateCplusplusEnums(self): + fn = os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml') + tdico = GetTemplatingDictFromSchemaFilename(fn) + template = Template(""" // end of generic methods +{% for enum in enums%} enum {{enum['name']}} { +{% for key in enum['fields']%} {{key}}, +{%endfor%} }; + +{%endfor%}""") + renderedCode = template.render(**tdico) + renderedCodeRef = """ // end of generic methods + enum MovieType { + RomCom, + Horror, + ScienceFiction, + Vegetables, + }; + + enum CrispType { + SaltAndPepper, + CreamAndChives, + Paprika, + Barbecue, + }; + +""" + self.assertEqual(renderedCodeRef,renderedCode) + + def test_generateTsStructType(self): + fn = os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml') + tdico = GetTemplatingDictFromSchemaFilename(fn) + ref = """ export class Message1 { + a: number; + b: string; + c: EnumMonth0; + d: boolean; + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolStuff.Message1'; + container['value'] = this; + return JSON.stringify(container); + } + }; + + export class Message2 { + toto: string; + tata: Message1[]; + tutu: string[]; + titi: Map<string, string>; + lulu: Map<string, Message1>; + + constructor() + { + this.tata = new Array<Message1>(); + this.tutu = new Array<string>(); + this.titi = new Map<string, string>(); + this.lulu = new Map<string, Message1>(); + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolStuff.Message2'; + container['value'] = this; + return JSON.stringify(container); + } + }; + +""" +# template = MakeTemplate(""" // end of generic methods +# {% for struct in struct%} export class {{struct['name']}} { +# {% for key in struct['fields']%} {{key}}:{{struct['fields'][key]}}, +# {% endfor %} +# constructor() { +# {% for key in struct['fields']%} +# {% if NeedsConstruction(struct['fields']['key'])} +# {{key}} = new {{CanonToTs(struct['fields']['key'])}}; +# {% end if %} +# {% endfor %} +# } +# {% endfor %} +# public StoneSerialize(): string { +# let container: object = {}; +# container['type'] = '{{rootName}}.{{struct['name']}}'; +# container['value'] = this; +# return JSON.stringify(container); +# } };""") + template = MakeTemplate(""" // end of generic methods +{% for struct in structs%} export class {{struct['name']}} { +{% for key in struct['fields']%} {{key}}:{{CanonToTs(struct['fields'][key])}}; +{% endfor %} + constructor() { +{% for key in struct['fields']%} this.{{key}} = new {{CanonToTs(struct['fields'][key])}}(); +{% endfor %} } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = '{{rootName}}.{{struct['name']}}'; + container['value'] = this; + return JSON.stringify(container); + } + }; + +{% endfor %}""") + renderedCode = template.render(**tdico) + renderedCodeRef = """ // end of generic methods + export class A { + someStrings:Array<string>; + someInts2:Array<number>; + movies:Array<MovieType>; + + constructor() { + someStrings = new Array<string>(); + someInts2 = new Array<number>(); + movies = new Array<MovieType>(); + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolMessages.A'; + container['value'] = this; + return JSON.stringify(container); + } + }; + + export class B { + someAs:Array<A>; + someInts:Array<number>; + + constructor() { + someAs = new Array<A>(); + someInts = new Array<number>(); + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolMessages.B'; + container['value'] = this; + return JSON.stringify(container); + } + }; + + export class C { + someBs:Array<B>; + ddd:Array<string>; + + constructor() { + someBs = new Array<B>(); + ddd = new Array<string>(); + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolMessages.C'; + container['value'] = this; + return JSON.stringify(container); + } + }; + + export class Message1 { + a:number; + b:string; + c:EnumMonth0; + d:boolean; + + constructor() { + a = new number(); + b = new string(); + c = new EnumMonth0(); + d = new boolean(); + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolMessages.Message1'; + container['value'] = this; + return JSON.stringify(container); + } + }; + + export class Message2 { + toto:string; + tata:Array<Message1>; + tutu:Array<string>; + titi:Map<string, string>; + lulu:Map<string, Message1>; + + constructor() { + toto = new string(); + tata = new Array<Message1>(); + tutu = new Array<string>(); + titi = new Map<string, string>(); + lulu = new Map<string, Message1>(); + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'VsolMessages.Message2'; + container['value'] = this; + return JSON.stringify(container); + } + }; + +""" + # print(renderedCode) + self.maxDiff = None + self.assertEqual(renderedCodeRef, renderedCode) + + def test_generateWholeTsFile(self): + schemaFile = \ + os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml') + tdico = GetTemplatingDictFromSchemaFilename(schemaFile) + tsTemplateFile = \ + os.path.join(os.path.dirname(__file__), 'template.in.ts.j2') + template = MakeTemplateFromFile(tsTemplateFile) + renderedCode = template.render(**tdico) + print(renderedCode) + + def test_GenerateTypeScriptHandlerInterface(self): + pass + + def test_GenerateCppHandlerInterface(self): + pass + + def test_GenerateTypeScriptDispatcher(self): + pass + + def test_GenerateCppDispatcher(self): + pass + +# def test(self): +# s = 'hello world' +# self.assertEqual(s.split(), ['hello', 'world']) +# # check that s.split fails when the separator is not a string +# with self.assertRaises(TypeError): +# s.split(2) + +if __name__ == '__main__': + unittest.main() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/template.in.h.j2 Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,413 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on {{currentDatetime}} by stonegentool + +*/ +#pragma once + +#include <exception> +#include <iostream> +#include <string> +#include <sstream> +#include <assert.h> +#include <memory> +#include <json/json.h> + +//#define STONEGEN_NO_CPP11 1 + +#ifdef STONEGEN_NO_CPP11 +#define StoneSmartPtr std::auto_ptr +#else +#define StoneSmartPtr std::unique_ptr +#endif + +namespace {{rootName}} +{ + /** Throws in case of problem */ + inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asInt(); + } + + inline Json::Value _StoneSerializeValue(int32_t value) + { + Json::Value result(value); + return result; + } + + inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue; + } + + inline Json::Value _StoneSerializeValue(Json::Value value) + { + return value; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asDouble(); + } + + inline Json::Value _StoneSerializeValue(double value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asBool(); + } + + inline Json::Value _StoneSerializeValue(bool value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue( + std::string& destValue + , const Json::Value& jsonValue) + { + destValue = jsonValue.asString(); + } + + inline Json::Value _StoneSerializeValue(const std::string& value) + { + // the following is better than + Json::Value result(value.data(),value.data()+value.size()); + return result; + } + + inline std::string MakeIndent(int indent) + { + char* txt = reinterpret_cast<char*>(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!! + for(size_t i = 0; i < indent; ++i) + txt[i] = ' '; + txt[indent] = 0; + std::string retVal(txt); + free(txt); // NO EXCEPTION ABOVE !!!!!!!!!! + return retVal; + } + + // generic dumper + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const T& value, int indent) + { + out << MakeIndent(indent) << value; + return out; + } + + // string dumper + inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, int indent) + { + out << MakeIndent(indent) << "\"" << value << "\""; + return out; + } + + /** Throws in case of problem */ + template<typename T> + void _StoneDeserializeValue( + std::map<std::string, T>& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + for ( + Json::Value::const_iterator itr = jsonValue.begin(); + itr != jsonValue.end(); + itr++) + { + std::string key; + _StoneDeserializeValue(key, itr.key()); + + T innerDestValue; + _StoneDeserializeValue(innerDestValue, *itr); + + destValue[key] = innerDestValue; + } + } + + template<typename T> + Json::Value _StoneSerializeValue(const std::map<std::string,T>& value) + { + Json::Value result(Json::objectValue); + + for (typename std::map<std::string, T>::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + // it->first it->second + result[it->first] = _StoneSerializeValue(it->second); + } + return result; + } + + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const std::map<std::string,T>& value, int indent) + { + out << MakeIndent(indent) << "{\n"; + for (typename std::map<std::string, T>::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + out << MakeIndent(indent+2) << "\"" << it->first << "\" : "; + StoneDumpValue(out, it->second, indent+2); + } + out << MakeIndent(indent) << "}\n"; + return out; + } + + /** Throws in case of problem */ + template<typename T> + void _StoneDeserializeValue( + std::vector<T>& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + destValue.reserve(jsonValue.size()); + for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) + { + T innerDestValue; + _StoneDeserializeValue(innerDestValue, jsonValue[i]); + destValue.push_back(innerDestValue); + } + } + + template<typename T> + Json::Value _StoneSerializeValue(const std::vector<T>& value) + { + Json::Value result(Json::arrayValue); + for (size_t i = 0; i < value.size(); ++i) + { + result.append(_StoneSerializeValue(value[i])); + } + return result; + } + + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const std::vector<T>& value, int indent) + { + out << MakeIndent(indent) << "[\n"; + for (size_t i = 0; i < value.size(); ++i) + { + StoneDumpValue(out, value[i], indent+2); + } + out << MakeIndent(indent) << "]\n"; + return out; + } + + inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value) + { + if ((!value.isMember("type")) || (!value["type"].isString())) + { + std::stringstream ss; + ss << "Cannot deserialize value ('type' key invalid)"; + throw std::runtime_error(ss.str()); + } + } + + inline void StoneCheckSerializedValueType( + const Json::Value& value, std::string typeStr) + { + StoneCheckSerializedValueTypeGeneric(value); + + std::string actTypeStr = value["type"].asString(); + if (actTypeStr != typeStr) + { + std::stringstream ss; + ss << "Cannot deserialize type" << actTypeStr + << "into " << typeStr; + throw std::runtime_error(ss.str()); + } + } + + // end of generic methods + +// end of generic methods +{% for enum in enums%} + enum {{enum['name']}} { +{% for key in enum['fields']%} {{enum['name']}}_{{key}}, +{%endfor%} }; + + inline std::string ToString(const {{enum['name']}}& value) + { +{% for key in enum['fields']%} if( value == {{enum['name']}}_{{key}}) + { + return std::string("{{key}}"); + } +{%endfor%} std::stringstream ss; + ss << "Value \"" << value << "\" cannot be converted to {{enum['name']}}. Possible values are: " +{% for key in enum['fields']%} << " {{key}} = " << static_cast<int64_t>({{enum['name']}}_{{key}}) << ", " +{% endfor %} << std::endl; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + inline void FromString({{enum['name']}}& value, std::string strValue) + { +{% for key in enum['fields']%} if( strValue == std::string("{{key}}") ) + { + value = {{enum['name']}}_{{key}}; + return; + } +{%endfor%} + std::stringstream ss; + ss << "String \"" << strValue << "\" cannot be converted to {{enum['name']}}. Possible values are: {% for key in enum['fields']%}{{key}} {% endfor %}"; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + + inline void _StoneDeserializeValue( + {{enum['name']}}& destValue, const Json::Value& jsonValue) + { + FromString(destValue, jsonValue.asString()); + } + + inline Json::Value _StoneSerializeValue(const {{enum['name']}}& value) + { + std::string strValue = ToString(value); + return Json::Value(strValue); + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const {{enum['name']}}& value, int indent = 0) + { +{% for key in enum['fields']%} if( value == {{enum['name']}}_{{key}}) + { + out << MakeIndent(indent) << "{{key}}" << std::endl; + } +{%endfor%} return out; + } + +{%endfor%} +{% for struct in structs%} +#ifdef _MSC_VER +#pragma region {{struct['name']}} +#endif //_MSC_VER + + struct {{struct['name']}} + { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} {{CanonToCpp(struct['fields'][key])}} {{key}}; +{% endfor %}{% endif %}{% endif %} + {{struct['name']}}({% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}{{CanonToCpp(struct['fields'][key])}} {{key}} = {{CanonToCpp(struct['fields'][key])}}(){{ ", " if not loop.last }}{% endfor %}{% endif %}{% endif %}) + { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} this->{{key}} = {{key}}; +{% endfor %}{% endif %}{% endif %} } + }; + + inline void _StoneDeserializeValue({{struct['name']}}& destValue, const Json::Value& value) + { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} _StoneDeserializeValue(destValue.{{key}}, value["{{key}}"]); +{% endfor %}{% endif %}{% endif %} } + + inline Json::Value _StoneSerializeValue(const {{struct['name']}}& value) + { + Json::Value result(Json::objectValue); +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} result["{{key}}"] = _StoneSerializeValue(value.{{key}}); +{% endfor %}{% endif %}{% endif %} + return result; + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const {{struct['name']}}& value, int indent = 0) + { + out << MakeIndent(indent) << "{\n"; +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} out << MakeIndent(indent) << "{{key}}:\n"; + StoneDumpValue(out, value.{{key}},indent+2); + out << "\n"; +{% endfor %}{% endif %}{% endif %} + out << MakeIndent(indent) << "}\n"; + return out; + } + + inline void StoneDeserialize({{struct['name']}}& destValue, const Json::Value& value) + { + StoneCheckSerializedValueType(value, "{{rootName}}.{{struct['name']}}"); + _StoneDeserializeValue(destValue, value["value"]); + } + + inline Json::Value StoneSerializeToJson(const {{struct['name']}}& value) + { + Json::Value result(Json::objectValue); + result["type"] = "{{rootName}}.{{struct['name']}}"; + result["value"] = _StoneSerializeValue(value); + return result; + } + + inline std::string StoneSerialize(const {{struct['name']}}& value) + { + Json::Value resultJson = StoneSerializeToJson(value); + std::string resultStr = resultJson.toStyledString(); + return resultStr; + } + +#ifdef _MSC_VER +#pragma endregion {{struct['name']}} +#endif //_MSC_VER +{% endfor %} +#ifdef _MSC_VER +#pragma region Dispatching code +#endif //_MSC_VER + + class IHandler + { + public: +{% for struct in structs%}{% if struct['__meta__'].handleInCpp %} virtual bool Handle(const {{struct['name']}}& value) = 0; +{% endif %}{% endfor %} }; + + /** Service function for StoneDispatchToHandler */ + inline bool StoneDispatchJsonToHandler( + const Json::Value& jsonValue, IHandler* handler) + { + StoneCheckSerializedValueTypeGeneric(jsonValue); + std::string type = jsonValue["type"].asString(); + if (type == "") + { + // this should never ever happen + throw std::runtime_error("Caught empty type while dispatching"); + } +{% for struct in structs%}{% if struct['__meta__'].handleInCpp %} else if (type == "{{rootName}}.{{struct['name']}}") + { + {{struct['name']}} value; + _StoneDeserializeValue(value, jsonValue["value"]); + return handler->Handle(value); + } +{% endif %}{% endfor %} else + { + return false; + } + } + + /** Takes a serialized type and passes this to the handler */ + inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler) + { + Json::Value readValue; + + Json::CharReaderBuilder builder; + Json::CharReader* reader = builder.newCharReader(); + + StoneSmartPtr<Json::CharReader> ptr(reader); + + std::string errors; + + bool ok = reader->parse( + strValue.c_str(), + strValue.c_str() + strValue.size(), + &readValue, + &errors + ); + if (!ok) + { + std::stringstream ss; + ss << "Jsoncpp parsing error: " << errors; + throw std::runtime_error(ss.str()); + } + return StoneDispatchJsonToHandler(readValue, handler); + } + +#ifdef _MSC_VER +#pragma endregion Dispatching code +#endif //_MSC_VER +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/template.in.ts.j2 Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,134 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on {{currentDatetime}} by stonegentool + +*/ + +function StoneCheckSerializedValueType(value: any, typeStr: string) +{ + StoneCheckSerializedValueTypeGeneric(value); + + if (value['type'] != typeStr) + { + throw new Error( + `Cannot deserialize type ${value['type']} into ${typeStr}`); + } +} + +function isString(val: any) :boolean +{ + return ((typeof val === 'string') || (val instanceof String)); +} + +function StoneCheckSerializedValueTypeGeneric(value: any) +{ + // console.//log("+-------------------------------------------------+"); + // console.//log("| StoneCheckSerializedValueTypeGeneric |"); + // console.//log("+-------------------------------------------------+"); + // console.//log("value = "); + // console.//log(value); + if ( (!('type' in value)) || (!isString(value.type)) ) + { + throw new Error( + "Cannot deserialize value ('type' key invalid)"); + } +} + +// end of generic methods +{% for enum in enums%} +export enum {{enum['name']}} { +{% for key in enum['fields']%} {{key}} = "{{key}}"{% if not loop.last %},{%endif%} +{%endfor%}}; + +export function {{enum['name']}}_FromString(strValue:string) : {{enum['name']}} +{ +{% for key in enum['fields'] %} if( strValue == "{{key}}" ) + { + return {{enum['name']}}.{{key}}; + } +{%endfor%} + let msg : string = `String ${strValue} cannot be converted to {{enum['name']}}. Possible values are: {% for key in enum['fields']%}{{key}}{% if not loop.last %}, {%endif%}{% endfor %}`; + throw new Error(msg); +} + +export function {{enum['name']}}_ToString(value:{{enum['name']}}) : string +{ +{% for key in enum['fields'] %} if( value == {{enum['name']}}.{{key}} ) + { + return "{{key}}"; + } +{%endfor%} + let msg : string = `Value ${value} cannot be converted to {{enum['name']}}. Possible values are: `; +{% for key in enum['fields']%} { + let _{{key}}_enumValue : string = {{enum['name']}}.{{key}}; // enums are strings in stonecodegen, so this will work. + let msg_{{key}} : string = `{{key}} (${_{{key}}_enumValue}){% if not loop.last %}, {%endif%}`; + msg = msg + msg_{{key}}; + } +{%endfor%} throw new Error(msg); +} +{%endfor%} + + +{% for struct in structs%}export class {{struct['name']}} { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} {{key}}:{{CanonToTs(struct['fields'][key])}}; +{% endfor %}{% endif %}{% endif %} + constructor() { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}{% if NeedsTsConstruction(enums,CanonToTs(struct['fields'][key])) %} this.{{key}} = new {{CanonToTs(struct['fields'][key])}}(); +{% endif %}{% endfor %}{% endif %}{% endif %} } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = '{{rootName}}.{{struct['name']}}'; + container['value'] = this; + return JSON.stringify(container); + } + + public static StoneDeserialize(valueStr: string) : {{struct['name']}} + { + let value: any = JSON.parse(valueStr); + StoneCheckSerializedValueType(value, '{{rootName}}.{{struct['name']}}'); + let result: {{struct['name']}} = value['value'] as {{struct['name']}}; + return result; + } +} +{% endfor %} +export interface IHandler { +{% for struct in structs%}{% if struct['__meta__'].handleInTypescript %} Handle{{struct['name']}}(value: {{struct['name']}}): boolean; +{% endif %}{% endfor %}}; + +/** Service function for StoneDispatchToHandler */ +export function StoneDispatchJsonToHandler( + jsonValue: any, handler: IHandler): boolean +{ + StoneCheckSerializedValueTypeGeneric(jsonValue); + let type: string = jsonValue["type"]; + if (type == "") + { + // this should never ever happen + throw new Error("Caught empty type while dispatching"); + } +{% for struct in structs%}{% if struct['__meta__'].handleInTypescript %} else if (type == "{{rootName}}.{{struct['name']}}") + { + let value = jsonValue["value"] as {{struct['name']}}; + return handler.Handle{{struct['name']}}(value); + } +{% endif %}{% endfor %} else + { + return false; + } +} + +/** Takes a serialized type and passes this to the handler */ +export function StoneDispatchToHandler( + strValue: string, handler: IHandler): boolean +{ + // console.//log("+------------------------------------------------+"); + // console.//log("| StoneDispatchToHandler |"); + // console.//log("+------------------------------------------------+"); + // console.//log("strValue = "); + // console.//log(strValue); + let jsonValue: any = JSON.parse(strValue) + return StoneDispatchJsonToHandler(jsonValue, handler); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testCppHandler/CMakeLists.txt Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 2.8) + +project(testCppHandler) + +set(testCppHandler_Codegen_Deps + ${CMAKE_CURRENT_LIST_DIR}/../test_data/test1.yaml + ${CMAKE_CURRENT_LIST_DIR}/../template.in.h.j2 +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/VsolMessages_generated.hpp + COMMAND python ${CMAKE_CURRENT_LIST_DIR}/../stonegentool.py -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/../test_data/test1.yaml + DEPENDS ${testCppHandler_Codegen_Deps} +) + +include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake) +conan_basic_setup() + +add_executable(testCppHandler main.cpp ${CMAKE_CURRENT_BINARY_DIR}/VsolMessages_generated.hpp ${testCppHandler_Codegen_Deps}) + +target_include_directories(testCppHandler PUBLIC ${CMAKE_BINARY_DIR}) + +conan_target_link_libraries(testCppHandler) + +set_property(TARGET testCppHandler PROPERTY CXX_STANDARD 17) + +install(TARGETS testCppHandler DESTINATION bin) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testCppHandler/README.md Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,32 @@ +Requirements +============== +- Install Python 3.x (tested with 3.7) +- Install conan with `pip install conan` (tested with 1.12.2) +- Install CMake (tested with 3.12) +- Under Windows: Visual Studio 2017 +- Under *nix*: Ninja + +How to build under *nix* +=============================== +- Navigate to `testCppHandler` folder +- `conan install . -g cmake` +- `mkdir build` +- `cd build` +- `cmake -G "Ninja" ..` +- `cmake --build . --config Debug` or - `cmake --build . --config Release` + +How to build under Windows with Visual Studio +============================================== +- Navigate to repo root +- `mkdir build` +- `cd build` +- `conan install .. -g cmake_multi -s build_type=Release` +- `conan install .. -g cmake_multi -s build_type=Debug` +- `cmake -G "Visual Studio 15 2017 Win64" ..` (modify for your current Visual Studio version) +- `cmake --build . --config Debug` or - `cmake --build . --config Release` + + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testCppHandler/conanfile.txt Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,4 @@ +[requires] +jsoncpp/1.8.4@theirix/stable +gtest/1.8.1@bincrafters/stable +boost/1.69.0@conan/stable
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testCppHandler/main.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,139 @@ +#include <string> +#include <fstream> +#include <filesystem> +#include <regex> +using namespace std; +namespace fs = std::filesystem; + +#include <boost/program_options.hpp> +using namespace boost::program_options; + +#include "VsolMessages_generated.hpp" + +/** +Transforms `str` by replacing occurrences of `oldStr` with `newStr`, using +plain text (*not* regular expressions.) +*/ +static inline void ReplaceInString( + string& str, + const std::string& oldStr, + const std::string& newStr) +{ + std::string::size_type pos = 0u; + while ((pos = str.find(oldStr, pos)) != std::string::npos) { + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } +} + +string SlurpFile(const string& fileName) +{ + ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate); + + ifstream::pos_type fileSize = ifs.tellg(); + ifs.seekg(0, ios::beg); + + vector<char> bytes(fileSize); + ifs.read(bytes.data(), fileSize); + + return string(bytes.data(), fileSize); +} + +class MyHandler : public VsolMessages::IHandler +{ +public: + virtual bool Handle(const VsolMessages::A& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } + virtual bool Handle(const VsolMessages::B& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } + virtual bool Handle(const VsolMessages::C& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } + virtual bool Handle(const VsolMessages::Message1& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } + virtual bool Handle(const VsolMessages::Message2& value) override + { + VsolMessages::StoneDumpValue(cout, value); + return true; + } +}; + +template<typename T> +void ProcessPath(T filePath) +{ + cout << "+--------------------------------------------+\n"; + cout << "| Processing: " << filePath.path().string() << "\n"; + cout << "+--------------------------------------------+\n"; + MyHandler handler; + auto contents = SlurpFile(filePath.path().string()); + VsolMessages::StoneDispatchToHandler(contents, &handler); +} + +int main(int argc, char** argv) +{ + try + { + + options_description desc("Allowed options"); + desc.add_options() + // First parameter describes option name/short name + // The second is parameter to option + // The third is description + ("help,h", "print usage message") + ("pattern,p", value<string>(), "pattern for input") + ; + + variables_map vm; + store(parse_command_line(argc, argv, desc), vm); + + if (vm.count("help")) + { + cout << desc << "\n"; + return 0; + } + + notify(vm); + + string pattern = vm["pattern"].as<string>(); + + // tranform globbing pattern into regex + // we should deal with -, ., *... + string regexPatternStr = pattern; + cout << "Pattern is: " << regexPatternStr << endl; + ReplaceInString(regexPatternStr, "\\", "\\\\"); + ReplaceInString(regexPatternStr, "-", "\\-"); + ReplaceInString(regexPatternStr, ".", "\\."); + ReplaceInString(regexPatternStr, "*", ".*"); + ReplaceInString(regexPatternStr, "?", "."); + cout << "Corresponding regex is: " << regexPatternStr << endl; + + regex regexPattern(regexPatternStr); + + for (auto& p : fs::directory_iterator(".")) + { + auto fileName = p.path().filename().string(); + if (regex_match(fileName, regexPattern)) + { + ProcessPath(p); + } + } + return 0; + + + } + catch (exception& e) + { + cerr << e.what() << "\n"; + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testCppHandler/test_data/test_Message2.json Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,40 @@ +{ + "type": "VsolMessages.Message2", + "value": { + "tata": [ + { + "a": 42, + "b": "Benjamin", + "c": 0, + "d": false + }, + { + "a": 43, + "b": "Sandrine", + "c": 2 + } + ], + "tutu": [ + "Mercadet", + "Poisson" + ], + "titi": { + "44": "key 44", + "45": "key 45" + }, + "lulu": { + "54": { + "a": 43, + "b": "Sandrine", + "c": 2 + }, + "55": { + "a": 42, + "b": "Benjamin", + "c": 0, + "d": false + } + }, + "toto": "Prout zizi" + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/CMakeLists.txt Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 2.8) + +project(testWasmIntegratedCpp) + +set(WASM_FLAGS "-s WASM=1 -O0 -g0") +set(WASM_MODULE_NAME "TestWasmIntegratedModule" CACHE STRING "Name of the WebAssembly module") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${CMAKE_CURRENT_LIST_DIR}/DefaultLibrary.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0 -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000") # 512MB + resize + +add_definitions(-DORTHANC_ENABLE_WASM=1) + +set(testWasmIntegratedCpp_Codegen_Deps + ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml + ${CMAKE_CURRENT_LIST_DIR}/../template.in.h.j2 + ${CMAKE_CURRENT_LIST_DIR}/../template.in.ts.j2 +) + +set(jsoncppRootDir ${CMAKE_CURRENT_LIST_DIR}/jsoncpp-1.8.4) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.hpp ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.ts + COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/../stonegentool.py -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml + DEPENDS ${testCppHandler_Codegen_Deps} ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml +) + +add_executable(testWasmIntegratedCpp + main.cpp + ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.hpp + ${jsoncppRootDir}/jsoncpp.cpp + ${testCppHandler_Codegen_Deps}) + +target_include_directories(testWasmIntegratedCpp PUBLIC ${CMAKE_BINARY_DIR}) +target_include_directories(testWasmIntegratedCpp PUBLIC ${jsoncppRootDir}) + +set_property(TARGET testWasmIntegratedCpp PROPERTY CXX_STANDARD 11) + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/DefaultLibrary.js Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,15 @@ +// this file contains the JS method you want to expose to C++ code + +mergeInto(LibraryManager.library, { + // each time the Application updates its status, it may signal it through this method. i.e, to change the status of a button in the web interface + // It needs to be put in this file so that the emscripten SDK linker knows where to find it. + SendFreeTextFromCppJS: function(statusUpdateMessage) { + var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); + window.SendFreeTextFromCpp(statusUpdateMessage_); + }, + SendMessageFromCppJS: function(statusUpdateMessage) { + var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); + SendMessageFromCpp(statusUpdateMessage_); + } +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/build-web.sh Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +mkdir -p build-final + +# compile TS to JS +tsc --module commonjs --sourceMap -t ES2015 --outDir "build-tsc/" build-wasm/testWasmIntegratedCpp_generated.ts testWasmIntegrated.ts + +# bundle JS files to final build dir +browserify "build-tsc/build-wasm/testWasmIntegratedCpp_generated.js" "build-tsc/testWasmIntegrated.js" -o "build-final/testWasmIntegratedApp.js" + +# copy WASM loader JS file to final build dir +cp build-wasm/testWasmIntegratedCpp.js build-final/ + +# copy HTML start page to output dir +cp testWasmIntegrated.html build-final/ + + +# copy styles to output dir +cp styles.css build-final/ + +# copy WASM binary to output dir +cp build-wasm/testWasmIntegratedCpp.wasm build-final/ + +echo "...Serving files at http://127.0.0.1:8080/" +echo "Please open build_final/testWasmIntegrated.html" + +sudo python3 serve.py +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/build.sh Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +mkdir -p build-wasm +cd build-wasm + +# shellcheck source="$HOME/apps/emsdk/emsdk_env.sh" +source "$HOME/apps/emsdk/emsdk_env.sh" +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_WASM=ON .. + +ninja + +cd .. + +./build-web.sh +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/jsoncpp-1.8.4/json/json-forwards.h Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,344 @@ +/// Json-cpp amalgamated forward header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json-forwards.h" +/// This header provides forward declaration for all JsonCpp types. + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +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. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED +# define JSON_FORWARD_AMALGAMATED_H_INCLUDED +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED +#include <cstddef> +#include <cstdint> +#include <istream> +#include <memory> +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of +/// std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgamated header. +// #define JSON_IS_AMALGAMATION + +#ifdef JSON_IN_CPPTL +#include <cpptl/config.h> +#ifndef JSON_USE_CPPTL +#define JSON_USE_CPPTL 1 +#endif +#endif + +#ifdef JSON_IN_CPPTL +#define JSON_API CPPTL_API +#elif defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#define JSON_API +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// As recommended at +// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +extern JSON_API int +msvc_pre1900_c99_snprintf(char* outBuf, size_t size, const char* format, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf +#else +#define jsoncpp_snprintf std::snprintf +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +#if defined(_MSC_VER) // MSVC +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // defined(_MSC_VER) + +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override + +#if __cplusplus >= 201103L +#define JSONCPP_NOEXCEPT noexcept +#define JSONCPP_OP_EXPLICIT explicit +#elif defined(_MSC_VER) && _MSC_VER < 1900 +#define JSONCPP_NOEXCEPT throw() +#define JSONCPP_OP_EXPLICIT explicit +#elif defined(_MSC_VER) && _MSC_VER >= 1900 +#define JSONCPP_NOEXCEPT noexcept +#define JSONCPP_OP_EXPLICIT explicit +#else +#define JSONCPP_NOEXCEPT throw() +#define JSONCPP_OP_EXPLICIT +#endif + +#ifndef JSON_HAS_RVALUE_REFERENCES + +#if defined(_MSC_VER) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // MSVC >= 2013 + +#ifdef __clang__ +#if __has_feature(cxx_rvalue_references) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // has_feature + +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // GXX_EXPERIMENTAL + +#endif // __clang__ || __GNUC__ + +#endif // not defined JSON_HAS_RVALUE_REFERENCES + +#ifndef JSON_HAS_RVALUE_REFERENCES +#define JSON_HAS_RVALUE_REFERENCES 0 +#endif + +#ifdef __clang__ +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#endif +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#endif // __clang__ || __GNUC__ + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if __GNUC__ >= 6 +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif + +#if !defined(JSON_IS_AMALGAMATION) + +#include "allocator.h" +#include "version.h" + +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { +typedef int Int; +typedef unsigned int UInt; +#if defined(JSON_NO_INT64) +typedef int LargestInt; +typedef unsigned int LargestUInt; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) +// For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#else // if defined(_MSC_VER) // Other platforms, use long long +typedef int64_t Int64; +typedef uint64_t UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) + +template <typename T> +using Allocator = typename std::conditional<JSONCPP_USING_SECURE_MEMORY, + SecureAllocator<T>, + std::allocator<T>>::type; +using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>; +using IStringStream = std::basic_istringstream<String::value_type, + String::traits_type, + String::allocator_type>; +using OStringStream = std::basic_ostringstream<String::value_type, + String::traits_type, + String::allocator_type>; +using IStream = std::istream; +using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// writer.h +class FastWriter; +class StyledWriter; + +// reader.h +class Reader; + +// features.h +class Features; + +// value.h +typedef unsigned int ArrayIndex; +class StaticString; +class Path; +class PathArgument; +class Value; +class ValueIteratorBase; +class ValueIterator; +class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/jsoncpp-1.8.4/json/json.h Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,2366 @@ +/// Json-cpp amalgamated header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +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. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_AMALGAMATED_H_INCLUDED +# define JSON_AMALGAMATED_H_INCLUDED +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + +// DO NOT EDIT. This file (and "version") is generated by CMake. +// Run CMake configure step to update it. +#ifndef JSON_VERSION_H_INCLUDED +#define JSON_VERSION_H_INCLUDED + +#define JSONCPP_VERSION_STRING "1.8.4" +#define JSONCPP_VERSION_MAJOR 1 +#define JSONCPP_VERSION_MINOR 8 +#define JSONCPP_VERSION_PATCH 4 +#define JSONCPP_VERSION_QUALIFIER +#define JSONCPP_VERSION_HEXA \ + ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ + (JSONCPP_VERSION_PATCH << 8)) + +#ifdef JSONCPP_USING_SECURE_MEMORY +#undef JSONCPP_USING_SECURE_MEMORY +#endif +#define JSONCPP_USING_SECURE_MEMORY 0 +// If non-zero, the library zeroes any memory that it has allocated before +// it frees its memory. + +#endif // JSON_VERSION_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_ALLOCATOR_H_INCLUDED +#define CPPTL_JSON_ALLOCATOR_H_INCLUDED + +#include <cstring> +#include <memory> + +#pragma pack(push, 8) + +namespace Json { +template <typename T> class SecureAllocator { +public: + // Type definitions + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + /** + * Allocate memory for N items using the standard allocator. + */ + pointer allocate(size_type n) { + // allocate using "global operator new" + return static_cast<pointer>(::operator new(n * sizeof(T))); + } + + /** + * Release memory which was allocated for N items at pointer P. + * + * The memory block is filled with zeroes before being released. + * The pointer argument is tagged as "volatile" to prevent the + * compiler optimizing out this critical step. + */ + void deallocate(volatile pointer p, size_type n) { + std::memset(p, 0, n * sizeof(T)); + // free using "global operator delete" + ::operator delete(p); + } + + /** + * Construct an item in-place at pointer P. + */ + template <typename... Args> void construct(pointer p, Args&&... args) { + // construct using "placement new" and "perfect forwarding" + ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...); + } + + size_type max_size() const { return size_t(-1) / sizeof(T); } + + pointer address(reference x) const { return std::addressof(x); } + + const_pointer address(const_reference x) const { return std::addressof(x); } + + /** + * Destroy an item in-place at pointer P. + */ + void destroy(pointer p) { + // destroy using "explicit destructor" + p->~T(); + } + + // Boilerplate + SecureAllocator() {} + template <typename U> SecureAllocator(const SecureAllocator<U>&) {} + template <typename U> struct rebind { using other = SecureAllocator<U>; }; +}; + +template <typename T, typename U> +bool operator==(const SecureAllocator<T>&, const SecureAllocator<U>&) { + return true; +} + +template <typename T, typename U> +bool operator!=(const SecureAllocator<T>&, const SecureAllocator<U>&) { + return false; +} + +} // namespace Json + +#pragma pack(pop) + +#endif // CPPTL_JSON_ALLOCATOR_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED +#include <cstddef> +#include <cstdint> +#include <istream> +#include <memory> +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of +/// std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgamated header. +// #define JSON_IS_AMALGAMATION + +#ifdef JSON_IN_CPPTL +#include <cpptl/config.h> +#ifndef JSON_USE_CPPTL +#define JSON_USE_CPPTL 1 +#endif +#endif + +#ifdef JSON_IN_CPPTL +#define JSON_API CPPTL_API +#elif defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#define JSON_API +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// As recommended at +// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +extern JSON_API int +msvc_pre1900_c99_snprintf(char* outBuf, size_t size, const char* format, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf +#else +#define jsoncpp_snprintf std::snprintf +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +#if defined(_MSC_VER) // MSVC +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // defined(_MSC_VER) + +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override + +#if __cplusplus >= 201103L +#define JSONCPP_NOEXCEPT noexcept +#define JSONCPP_OP_EXPLICIT explicit +#elif defined(_MSC_VER) && _MSC_VER < 1900 +#define JSONCPP_NOEXCEPT throw() +#define JSONCPP_OP_EXPLICIT explicit +#elif defined(_MSC_VER) && _MSC_VER >= 1900 +#define JSONCPP_NOEXCEPT noexcept +#define JSONCPP_OP_EXPLICIT explicit +#else +#define JSONCPP_NOEXCEPT throw() +#define JSONCPP_OP_EXPLICIT +#endif + +#ifndef JSON_HAS_RVALUE_REFERENCES + +#if defined(_MSC_VER) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // MSVC >= 2013 + +#ifdef __clang__ +#if __has_feature(cxx_rvalue_references) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // has_feature + +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // GXX_EXPERIMENTAL + +#endif // __clang__ || __GNUC__ + +#endif // not defined JSON_HAS_RVALUE_REFERENCES + +#ifndef JSON_HAS_RVALUE_REFERENCES +#define JSON_HAS_RVALUE_REFERENCES 0 +#endif + +#ifdef __clang__ +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#endif +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#endif // __clang__ || __GNUC__ + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if __GNUC__ >= 6 +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif + +#if !defined(JSON_IS_AMALGAMATION) + +#include "allocator.h" +#include "version.h" + +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { +typedef int Int; +typedef unsigned int UInt; +#if defined(JSON_NO_INT64) +typedef int LargestInt; +typedef unsigned int LargestUInt; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) +// For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#else // if defined(_MSC_VER) // Other platforms, use long long +typedef int64_t Int64; +typedef uint64_t UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) + +template <typename T> +using Allocator = typename std::conditional<JSONCPP_USING_SECURE_MEMORY, + SecureAllocator<T>, + std::allocator<T>>::type; +using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>; +using IStringStream = std::basic_istringstream<String::value_type, + String::traits_type, + String::allocator_type>; +using OStringStream = std::basic_ostringstream<String::value_type, + String::traits_type, + String::allocator_type>; +using IStream = std::istream; +using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// writer.h +class FastWriter; +class StyledWriter; + +// reader.h +class Reader; + +// features.h +class Features; + +// value.h +typedef unsigned int ArrayIndex; +class StaticString; +class Path; +class PathArgument; +class Value; +class ValueIteratorBase; +class ValueIterator; +class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_FEATURES_H_INCLUDED +#define CPPTL_JSON_FEATURES_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +#pragma pack(push, 8) + +namespace Json { + +/** \brief Configuration passed to reader and writer. + * This configuration object can be used to force the Reader or Writer + * to behave in a standard conforming way. + */ +class JSON_API Features { +public: + /** \brief A configuration that allows all features and assumes all strings + * are UTF-8. + * - C & C++ comments are allowed + * - Root object can be any JSON value + * - Assumes Value strings are encoded in UTF-8 + */ + static Features all(); + + /** \brief A configuration that is strictly compatible with the JSON + * specification. + * - Comments are forbidden. + * - Root object must be either an array or an object value. + * - Assumes Value strings are encoded in UTF-8 + */ + static Features strictMode(); + + /** \brief Initialize the configuration like JsonConfig::allFeatures; + */ + Features(); + + /// \c true if comments are allowed. Default: \c true. + bool allowComments_{true}; + + /// \c true if root must be either an array or an object value. Default: \c + /// false. + bool strictRoot_{false}; + + /// \c true if dropped null placeholders are allowed. Default: \c false. + bool allowDroppedNullPlaceholders_{false}; + + /// \c true if numeric object key are allowed. Default: \c false. + bool allowNumericKeys_{false}; +}; + +} // namespace Json + +#pragma pack(pop) + +#endif // CPPTL_JSON_FEATURES_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_H_INCLUDED +#define CPPTL_JSON_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include <exception> +#include <string> +#include <vector> + +#ifndef JSON_USE_CPPTL_SMALLMAP +#include <map> +#else +#include <cpptl/smallmap.h> +#endif +#ifdef JSON_USE_CPPTL +#include <cpptl/forwards.h> +#endif + +// Conditional NORETURN attribute on the throw functions would: +// a) suppress false positives from static code analysis +// b) possibly improve optimization opportunities. +#if !defined(JSONCPP_NORETURN) +#if defined(_MSC_VER) +#define JSONCPP_NORETURN __declspec(noreturn) +#elif defined(__GNUC__) +#define JSONCPP_NORETURN __attribute__((__noreturn__)) +#else +#define JSONCPP_NORETURN +#endif +#endif + +// Disable warning C4251: <data member>: <type> needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + +/** Base class for all exceptions we throw. + * + * We use nothing but these internally. Of course, STL can throw others. + */ +class JSON_API Exception : public std::exception { +public: + Exception(String msg); + ~Exception() JSONCPP_NOEXCEPT override; + char const* what() const JSONCPP_NOEXCEPT override; + +protected: + String msg_; +}; + +/** Exceptions which the user cannot easily avoid. + * + * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input + * + * \remark derived from Json::Exception + */ +class JSON_API RuntimeError : public Exception { +public: + RuntimeError(String const& msg); +}; + +/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. + * + * These are precondition-violations (user bugs) and internal errors (our bugs). + * + * \remark derived from Json::Exception + */ +class JSON_API LogicError : public Exception { +public: + LogicError(String const& msg); +}; + +/// used internally +JSONCPP_NORETURN void throwRuntimeError(String const& msg); +/// used internally +JSONCPP_NORETURN void throwLogicError(String const& msg); + +/** \brief Type of the value held by a Value object. + */ +enum ValueType { + nullValue = 0, ///< 'null' value + intValue, ///< signed integer value + uintValue, ///< unsigned integer value + realValue, ///< double value + stringValue, ///< UTF-8 string value + booleanValue, ///< bool value + arrayValue, ///< array value (ordered list) + objectValue ///< object value (collection of name/value pairs). +}; + +enum CommentPlacement { + commentBefore = 0, ///< a comment placed on the line before a value + commentAfterOnSameLine, ///< a comment just after a value on the same line + commentAfter, ///< a comment on the line after a value (only make sense for + /// root value) + numberOfCommentPlacement +}; + +/** \brief Type of precision for formatting of real values. + */ +enum PrecisionType { + significantDigits = 0, ///< we set max number of significant digits in string + decimalPlaces ///< we set max number of digits after "." in string +}; + +//# ifdef JSON_USE_CPPTL +// typedef CppTL::AnyEnumerator<const char *> EnumMemberNames; +// typedef CppTL::AnyEnumerator<const Value &> EnumValues; +//# endif + +/** \brief Lightweight wrapper to tag static string. + * + * Value constructor and objectValue member assignment takes advantage of the + * StaticString and avoid the cost of string duplication when storing the + * string or the member name. + * + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ +class JSON_API StaticString { +public: + explicit StaticString(const char* czstring) : c_str_(czstring) {} + + operator const char*() const { return c_str_; } + + const char* c_str() const { return c_str_; } + +private: + const char* c_str_; +}; + +/** \brief Represents a <a HREF="http://www.json.org">JSON</a> value. + * + * This class is a discriminated union wrapper that can represents a: + * - signed integer [range: Value::minInt - Value::maxInt] + * - unsigned integer (range: 0 - Value::maxUInt) + * - double + * - UTF-8 string + * - boolean + * - 'null' + * - an ordered list of Value + * - collection of name/value pairs (javascript object) + * + * The type of the held value is represented by a #ValueType and + * can be obtained using type(). + * + * Values of an #objectValue or #arrayValue can be accessed using operator[]() + * methods. + * Non-const methods will automatically create the a #nullValue element + * if it does not exist. + * The sequence of an #arrayValue will be automatically resized and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtain default value in the case the + * required element does not exist. + * + * It is possible to iterate over the list of member keys of an object using + * the getMemberNames() method. + * + * \note #Value string-length fit in size_t, but keys must be < 2^30. + * (The reason is an implementation detail.) A #CharReader will raise an + * exception if a bound is exceeded to avoid security holes in your app, + * but the Value API does *not* check bounds. That is the responsibility + * of the caller. + */ +class JSON_API Value { + friend class ValueIteratorBase; + +public: + typedef std::vector<String> Members; + typedef ValueIterator iterator; + typedef ValueConstIterator const_iterator; + typedef Json::UInt UInt; + typedef Json::Int Int; +#if defined(JSON_HAS_INT64) + typedef Json::UInt64 UInt64; + typedef Json::Int64 Int64; +#endif // defined(JSON_HAS_INT64) + typedef Json::LargestInt LargestInt; + typedef Json::LargestUInt LargestUInt; + typedef Json::ArrayIndex ArrayIndex; + + // Required for boost integration, e. g. BOOST_TEST + typedef std::string value_type; + + static const Value& null; ///< We regret this reference to a global instance; + ///< prefer the simpler Value(). + static const Value& nullRef; ///< just a kludge for binary-compatibility; same + ///< as null + static Value const& nullSingleton(); ///< Prefer this to null or nullRef. + + /// Minimum signed integer value that can be stored in a Json::Value. + static const LargestInt minLargestInt; + /// Maximum signed integer value that can be stored in a Json::Value. + static const LargestInt maxLargestInt; + /// Maximum unsigned integer value that can be stored in a Json::Value. + static const LargestUInt maxLargestUInt; + + /// Minimum signed int value that can be stored in a Json::Value. + static const Int minInt; + /// Maximum signed int value that can be stored in a Json::Value. + static const Int maxInt; + /// Maximum unsigned int value that can be stored in a Json::Value. + static const UInt maxUInt; + +#if defined(JSON_HAS_INT64) + /// Minimum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 minInt64; + /// Maximum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 maxInt64; + /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. + static const UInt64 maxUInt64; +#endif // defined(JSON_HAS_INT64) + + /// Default precision for real value for string representation. + static const UInt defaultRealPrecision; + +// Workaround for bug in the NVIDIAs CUDA 9.1 nvcc compiler +// when using gcc and clang backend compilers. CZString +// cannot be defined as private. See issue #486 +#ifdef __NVCC__ +public: +#else +private: +#endif +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + class CZString { + public: + enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy }; + CZString(ArrayIndex index); + CZString(char const* str, unsigned length, DuplicationPolicy allocate); + CZString(CZString const& other); +#if JSON_HAS_RVALUE_REFERENCES + CZString(CZString&& other); +#endif + ~CZString(); + CZString& operator=(const CZString& other); + +#if JSON_HAS_RVALUE_REFERENCES + CZString& operator=(CZString&& other); +#endif + + bool operator<(CZString const& other) const; + bool operator==(CZString const& other) const; + ArrayIndex index() const; + // const char* c_str() const; ///< \deprecated + char const* data() const; + unsigned length() const; + bool isStaticString() const; + + private: + void swap(CZString& other); + + struct StringStorage { + unsigned policy_ : 2; + unsigned length_ : 30; // 1GB max + }; + + char const* cstr_; // actually, a prefixed string, unless policy is noDup + union { + ArrayIndex index_; + StringStorage storage_; + }; + }; + +public: +#ifndef JSON_USE_CPPTL_SMALLMAP + typedef std::map<CZString, Value> ObjectValues; +#else + typedef CppTL::SmallMap<CZString, Value> ObjectValues; +#endif // ifndef JSON_USE_CPPTL_SMALLMAP +#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + +public: + /** \brief Create a default Value of the given type. + + This is a very useful constructor. + To create an empty array, pass arrayValue. + To create an empty object, pass objectValue. + Another Value can then be set to this one by assignment. +This is useful since clear() and resize() will not alter types. + + Examples: +\code +Json::Value null_value; // null +Json::Value arr_value(Json::arrayValue); // [] +Json::Value obj_value(Json::objectValue); // {} +\endcode + */ + Value(ValueType type = nullValue); + Value(Int value); + Value(UInt value); +#if defined(JSON_HAS_INT64) + Value(Int64 value); + Value(UInt64 value); +#endif // if defined(JSON_HAS_INT64) + Value(double value); + Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) + Value(const char* begin, const char* end); ///< Copy all, incl zeroes. + /** \brief Constructs a value from a static string. + + * Like other value string constructor but do not duplicate the string for + * internal storage. The given string must remain alive after the call to this + * constructor. + * \note This works only for null-terminated strings. (We cannot change the + * size of this class, so we have nowhere to store the length, + * which might be computed later for various operations.) + * + * Example of usage: + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode + */ + Value(const StaticString& value); + Value(const String& value); ///< Copy data() til size(). Embedded + ///< zeroes too. +#ifdef JSON_USE_CPPTL + Value(const CppTL::ConstString& value); +#endif + Value(bool value); + Value(const Value& other); + Value(Value&& other); + ~Value(); + + /// \note Overwrite existing comments. To preserve comments, use + /// #swapPayload(). + Value& operator=(const Value& other); + Value& operator=(Value&& other); + + /// Swap everything. + void swap(Value& other); + /// Swap values but leave comments and source offsets in place. + void swapPayload(Value& other); + + /// copy everything. + void copy(const Value& other); + /// copy values but leave comments and source offsets in place. + void copyPayload(const Value& other); + + ValueType type() const; + + /// Compare payload only, not comments etc. + bool operator<(const Value& other) const; + bool operator<=(const Value& other) const; + bool operator>=(const Value& other) const; + bool operator>(const Value& other) const; + bool operator==(const Value& other) const; + bool operator!=(const Value& other) const; + int compare(const Value& other) const; + + const char* asCString() const; ///< Embedded zeroes could cause you trouble! +#if JSONCPP_USING_SECURE_MEMORY + unsigned getCStringLength() const; // Allows you to understand the length of + // the CString +#endif + String asString() const; ///< Embedded zeroes are possible. + /** Get raw char* of string-value. + * \return false if !string. (Seg-fault if str or end are NULL.) + */ + bool getString(char const** begin, char const** end) const; +#ifdef JSON_USE_CPPTL + CppTL::ConstString asConstString() const; +#endif + Int asInt() const; + UInt asUInt() const; +#if defined(JSON_HAS_INT64) + Int64 asInt64() const; + UInt64 asUInt64() const; +#endif // if defined(JSON_HAS_INT64) + LargestInt asLargestInt() const; + LargestUInt asLargestUInt() const; + float asFloat() const; + double asDouble() const; + bool asBool() const; + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isInt64() const; + bool isUInt() const; + bool isUInt64() const; + bool isIntegral() const; + bool isDouble() const; + bool isNumeric() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + bool isConvertibleTo(ValueType other) const; + + /// Number of values in array or object + ArrayIndex size() const; + + /// \brief Return true if empty array, empty object, or null; + /// otherwise, false. + bool empty() const; + + /// Return !isNull() + JSONCPP_OP_EXPLICIT operator bool() const; + + /// Remove all object members and array elements. + /// \pre type() is arrayValue, objectValue, or nullValue + /// \post type() is unchanged + void clear(); + + /// Resize the array to newSize elements. + /// New elements are initialized to null. + /// May only be called on nullValue or arrayValue. + /// \pre type() is arrayValue or nullValue + /// \post type() is arrayValue + void resize(ArrayIndex newSize); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](ArrayIndex index); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](int index); + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](ArrayIndex index) const; + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](int index) const; + + /// If the array contains at least index+1 elements, returns the element + /// value, + /// otherwise returns defaultValue. + Value get(ArrayIndex index, const Value& defaultValue) const; + /// Return true if index < size(). + bool isValidIndex(ArrayIndex index) const; + /// \brief Append value to array at the end. + /// + /// Equivalent to jsonvalue[jsonvalue.size()] = value; + Value& append(const Value& value); + +#if JSON_HAS_RVALUE_REFERENCES + Value& append(Value&& value); +#endif + + /// Access an object value by name, create a null member if it does not exist. + /// \note Because of our implementation, keys are limited to 2^30 -1 chars. + /// Exceeding that will cause an exception. + Value& operator[](const char* key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const char* key) const; + /// Access an object value by name, create a null member if it does not exist. + /// \param key may contain embedded nulls. + Value& operator[](const String& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](const String& key) const; + /** \brief Access an object value by name, create a null member if it does not + exist. + + * If the object has no entry for that name, then the member name used to + store + * the new entry is not duplicated. + * Example of use: + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + Value& operator[](const StaticString& key); +#ifdef JSON_USE_CPPTL + /// Access an object value by name, create a null member if it does not exist. + Value& operator[](const CppTL::ConstString& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const CppTL::ConstString& key) const; +#endif + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const char* key, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \note key may contain embedded nulls. + Value + get(const char* begin, const char* end, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \param key may contain embedded nulls. + Value get(const String& key, const Value& defaultValue) const; +#ifdef JSON_USE_CPPTL + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const CppTL::ConstString& key, const Value& defaultValue) const; +#endif + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of object-mutators. + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. + Value const* demand(char const* begin, char const* end); + /// \brief Remove and return the named member. + /// + /// Do nothing if it did not exist. + /// \pre type() is objectValue or nullValue + /// \post type() is unchanged + void removeMember(const char* key); + /// Same as removeMember(const char*) + /// \param key may contain embedded nulls. + void removeMember(const String& key); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); + /** \brief Remove the named map member. + + Update 'removed' iff removed. + \param key may contain embedded nulls. + \return true iff removed (no exceptions) + */ + bool removeMember(String const& key, Value* removed); + /// Same as removeMember(String const& key, Value* removed) + bool removeMember(const char* begin, const char* end, Value* removed); + /** \brief Remove the indexed array element. + + O(n) expensive operations. + Update 'removed' iff removed. + \return true if removed (no exceptions) + */ + bool removeIndex(ArrayIndex index, Value* removed); + + /// Return true if the object has a member named key. + /// \note 'key' must be null-terminated. + bool isMember(const char* key) const; + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(const String& key) const; + /// Same as isMember(String const& key)const + bool isMember(const char* begin, const char* end) const; +#ifdef JSON_USE_CPPTL + /// Return true if the object has a member named key. + bool isMember(const CppTL::ConstString& key) const; +#endif + + /// \brief Return a list of the member names. + /// + /// If null, return an empty list. + /// \pre type() is objectValue or nullValue + /// \post if type() was nullValue, it remains nullValue + Members getMemberNames() const; + + //# ifdef JSON_USE_CPPTL + // EnumMemberNames enumMemberNames() const; + // EnumValues enumValues() const; + //# endif + + /// \deprecated Always pass len. + JSONCPP_DEPRECATED("Use setComment(String const&) instead.") + void setComment(const char* comment, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const char* comment, size_t len, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const String& comment, CommentPlacement placement); + bool hasComment(CommentPlacement placement) const; + /// Include delimiters and embedded newlines. + String getComment(CommentPlacement placement) const; + + String toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + + // Accessors for the [start, limit) range of bytes within the JSON text from + // which this value was parsed, if any. + void setOffsetStart(ptrdiff_t start); + void setOffsetLimit(ptrdiff_t limit); + ptrdiff_t getOffsetStart() const; + ptrdiff_t getOffsetLimit() const; + +private: + void setType(ValueType v) { bits_.value_type_ = v; } + bool isAllocated() const { return bits_.allocated_; } + void setIsAllocated(bool v) { bits_.allocated_ = v; } + + void initBasic(ValueType type, bool allocated = false); + void dupPayload(const Value& other); + void releasePayload(); + void dupMeta(const Value& other); + + Value& resolveReference(const char* key); + Value& resolveReference(const char* key, const char* end); + + struct CommentInfo { + CommentInfo(); + ~CommentInfo(); + + void setComment(const char* text, size_t len); + + char* comment_{nullptr}; + }; + + // struct MemberNamesTransform + //{ + // typedef const char *result_type; + // const char *operator()( const CZString &name ) const + // { + // return name.c_str(); + // } + //}; + + union ValueHolder { + LargestInt int_; + LargestUInt uint_; + double real_; + bool bool_; + char* string_; // if allocated_, ptr to { unsigned, char[] }. + ObjectValues* map_; + } value_; + + struct { + // Really a ValueType, but types should agree for bitfield packing. + unsigned int value_type_ : 8; + // Unless allocated_, string_ must be null-terminated. + unsigned int allocated_ : 1; + } bits_; + + CommentInfo* comments_; + + // [start, limit) byte offsets in the source JSON text from which this Value + // was extracted. + ptrdiff_t start_; + ptrdiff_t limit_; +}; + +/** \brief Experimental and untested: represents an element of the "path" to + * access a node. + */ +class JSON_API PathArgument { +public: + friend class Path; + + PathArgument(); + PathArgument(ArrayIndex index); + PathArgument(const char* key); + PathArgument(const String& key); + +private: + enum Kind { kindNone = 0, kindIndex, kindKey }; + String key_; + ArrayIndex index_{}; + Kind kind_{kindNone}; +}; + +/** \brief Experimental and untested: represents a "path" to access a node. + * + * Syntax: + * - "." => root node + * - ".[n]" => elements at index 'n' of root node (an array value) + * - ".name" => member named 'name' of root node (an object value) + * - ".name1.name2.name3" + * - ".[0][1][2].name1[3]" + * - ".%" => member name is provided as parameter + * - ".[%]" => index is provied as parameter + */ +class JSON_API Path { +public: + Path(const String& path, + const PathArgument& a1 = PathArgument(), + const PathArgument& a2 = PathArgument(), + const PathArgument& a3 = PathArgument(), + const PathArgument& a4 = PathArgument(), + const PathArgument& a5 = PathArgument()); + + const Value& resolve(const Value& root) const; + Value resolve(const Value& root, const Value& defaultValue) const; + /// Creates the "path" to access the specified node and returns a reference on + /// the node. + Value& make(Value& root) const; + +private: + typedef std::vector<const PathArgument*> InArgs; + typedef std::vector<PathArgument> Args; + + void makePath(const String& path, const InArgs& in); + void addPathInArg(const String& path, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind); + static void invalidPath(const String& path, int location); + + Args args_; +}; + +/** \brief base class for Value iterators. + * + */ +class JSON_API ValueIteratorBase { +public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef unsigned int size_t; + typedef int difference_type; + typedef ValueIteratorBase SelfType; + + bool operator==(const SelfType& other) const { return isEqual(other); } + + bool operator!=(const SelfType& other) const { return !isEqual(other); } + + difference_type operator-(const SelfType& other) const { + return other.computeDistance(*this); + } + + /// Return either the index or the member name of the referenced value as a + /// Value. + Value key() const; + + /// Return the index of the referenced Value, or -1 if it is not an + /// arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value, or "" if it is not an + /// objectValue. + /// \note Avoid `c_str()` on result, as embedded zeroes are possible. + String name() const; + + /// Return the member name of the referenced Value. "" if it is not an + /// objectValue. + /// \deprecated This cannot be used for UTF-8 strings, since there can be + /// embedded nulls. + JSONCPP_DEPRECATED("Use `key = name();` instead.") + char const* memberName() const; + /// Return the member name of the referenced Value, or NULL if it is not an + /// objectValue. + /// \note Better version than memberName(). Allows embedded nulls. + char const* memberName(char const** end) const; + +protected: + Value& deref() const; + + void increment(); + + void decrement(); + + difference_type computeDistance(const SelfType& other) const; + + bool isEqual(const SelfType& other) const; + + void copy(const SelfType& other); + +private: + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_{true}; + +public: + // For some reason, BORLAND needs these at the end, rather + // than earlier. No idea why. + ValueIteratorBase(); + explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); +}; + +/** \brief const iterator for object and array value. + * + */ +class JSON_API ValueConstIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef const Value value_type; + // typedef unsigned int size_t; + // typedef int difference_type; + typedef const Value& reference; + typedef const Value* pointer; + typedef ValueConstIterator SelfType; + + ValueConstIterator(); + ValueConstIterator(ValueIterator const& other); + +private: + /*! \internal Use by Value to create an iterator. + */ + explicit ValueConstIterator(const Value::ObjectValues::iterator& current); + +public: + SelfType& operator=(const ValueIteratorBase& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +/** \brief Iterator for object and array value. + */ +class JSON_API ValueIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef Value value_type; + typedef unsigned int size_t; + typedef int difference_type; + typedef Value& reference; + typedef Value* pointer; + typedef ValueIterator SelfType; + + ValueIterator(); + explicit ValueIterator(const ValueConstIterator& other); + ValueIterator(const ValueIterator& other); + +private: + /*! \internal Use by Value to create an iterator. + */ + explicit ValueIterator(const Value::ObjectValues::iterator& current); + +public: + SelfType& operator=(const SelfType& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +inline void swap(Value& a, Value& b) { a.swap(b); } + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_READER_H_INCLUDED +#define CPPTL_JSON_READER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "features.h" +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include <deque> +#include <iosfwd> +#include <istream> +#include <stack> +#include <string> + +// Disable warning C4251: <data member>: <type> needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +namespace Json { + +/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a + *Value. + * + * \deprecated Use CharReader and CharReaderBuilder. + */ +class JSON_API Reader { +public: + typedef char Char; + typedef const Char* Location; + + /** \brief An error tagged with where in the JSON text it was encountered. + * + * The offsets give the [start, limit) range of bytes within the text. Note + * that this is bytes, not codepoints. + * + */ + struct StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + + /** \brief Constructs a Reader allowing all features + * for parsing. + */ + JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") + Reader(); + + /** \brief Constructs a Reader allowing the specified feature set + * for parsing. + */ + JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") + Reader(const Features& features); + + /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> + * document. + * \param document UTF-8 encoded string containing the document to read. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + * back during + * serialization, \c false to discard comments. + * This parameter is ignored if + * Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + * error occurred. + */ + bool + parse(const std::string& document, Value& root, bool collectComments = true); + + /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> + document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + back during + * serialization, \c false to discard comments. + * This parameter is ignored if + Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + + /// \brief Parse from input stream. + /// \see Json::operator>>(std::istream&, Json::Value&). + bool parse(IStream& is, Value& root, bool collectComments = true); + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + * \deprecated Use getFormattedErrorMessages() instead (typo fix). + */ + JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") + String getFormatedErrorMessages() const; + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + */ + String getFormattedErrorMessages() const; + + /** \brief Returns a vector of structured erros encounted while parsing. + * \return A (possibly empty) vector of StructuredError objects. Currently + * only one error can be returned, but the caller should tolerate + * multiple + * errors. This can occur if the parser recovers from a non-fatal + * parse error and then encounters additional errors. + */ + std::vector<StructuredError> getStructuredErrors() const; + + /** \brief Add a semantic error message. + * \param value JSON Value location associated with the error + * \param message The error message. + * \return \c true if the error was successfully added, \c false if the + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const String& message); + + /** \brief Add a semantic error message with extra context. + * \param value JSON Value location associated with the error + * \param message The error message. + * \param extra Additional JSON Value location to contextualize the error + * \return \c true if the error was successfully added, \c false if either + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const String& message, const Value& extra); + + /** \brief Return whether there are any errors. + * \return \c true if there are no errors to report \c false if + * errors have occurred. + */ + bool good() const; + +private: + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + String message_; + Location extra_; + }; + + typedef std::deque<ErrorInfo> Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + void readNumber(); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, String& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const String& message, Token& token, Location extra = nullptr); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const String& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + String getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + static bool containsNewLine(Location begin, Location end); + static String normalizeEOL(Location begin, Location end); + + typedef std::stack<Value*> Nodes; + Nodes nodes_; + Errors errors_; + String document_; + Location begin_{}; + Location end_{}; + Location current_{}; + Location lastValueEnd_{}; + Value* lastValue_{}; + String commentsBefore_; + Features features_; + bool collectComments_{}; +}; // Reader + +/** Interface for reading JSON from a char array. + */ +class JSON_API CharReader { +public: + virtual ~CharReader() = default; + /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> + document. + * The document must be a UTF-8 encoded string containing the document to + read. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param errs [out] Formatted error messages (if not NULL) + * a user friendly string that lists errors in the parsed + * document. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + virtual bool parse(char const* beginDoc, + char const* endDoc, + Value* root, + String* errs) = 0; + + class JSON_API Factory { + public: + virtual ~Factory() = default; + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual CharReader* newCharReader() const = 0; + }; // Factory +}; // CharReader + +/** \brief Build a CharReader implementation. + +Usage: +\code + using namespace Json; + CharReaderBuilder builder; + builder["collectComments"] = false; + Value value; + String errs; + bool ok = parseFromStream(builder, std::cin, &value, &errs); +\endcode +*/ +class JSON_API CharReaderBuilder : public CharReader::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + These are case-sensitive. + Available settings (case-sensitive): + - `"collectComments": false or true` + - true to collect comment and allow writing them + back during serialization, false to discard comments. + This parameter is ignored if allowComments is false. + - `"allowComments": false or true` + - true if comments are allowed. + - `"strictRoot": false or true` + - true if root must be either an array or an object value + - `"allowDroppedNullPlaceholders": false or true` + - true if dropped null placeholders are allowed. (See + StreamWriterBuilder.) + - `"allowNumericKeys": false or true` + - true if numeric object keys are allowed. + - `"allowSingleQuotes": false or true` + - true if '' are allowed for strings (both keys and values) + - `"stackLimit": integer` + - Exceeding stackLimit (recursive depth of `readValue()`) will + cause an exception. + - This is a security issue (seg-faults caused by deeply nested JSON), + so the default is low. + - `"failIfExtra": false or true` + - If true, `parse()` returns false when extra non-whitespace trails + the JSON value in the input string. + - `"rejectDupKeys": false or true` + - If true, `parse()` returns false when a key is duplicated within an + object. + - `"allowSpecialFloats": false or true` + - If true, special float values (NaNs and infinities) are allowed + and their values are lossfree restorable. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + CharReaderBuilder(); + ~CharReaderBuilder() override; + + CharReader* newCharReader() const override; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + + /** A simple way to update a specific setting. + */ + Value& operator[](const String& key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults + */ + static void setDefaults(Json::Value* settings); + /** Same as old Features::strictMode(). + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode + */ + static void strictMode(Json::Value* settings); +}; + +/** Consume entire stream and use its begin/end. + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool JSON_API parseFromStream(CharReader::Factory const&, + IStream&, + Value* root, + std::string* errs); + +/** \brief Read from 'sin' into 'root'. + + Always keep comments from the input JSON. + + This can be used to read a file into a particular sub-object. + For example: + \code + Json::Value root; + cin >> root["dir"]["file"]; + cout << root; + \endcode + Result: + \verbatim + { + "dir": { + "file": { + // The input stream JSON would be nested here. + } + } + } + \endverbatim + \throw std::exception on parse error. + \see Json::operator<<() +*/ +JSON_API IStream& operator>>(IStream&, Value&); + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_READER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_WRITER_H_INCLUDED +#define JSON_WRITER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include <ostream> +#include <string> +#include <vector> + +// Disable warning C4251: <data member>: <type> needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) && defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +namespace Json { + +class Value; + +/** + +Usage: +\code + using namespace Json; + void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { + std::unique_ptr<StreamWriter> const writer( + factory.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush + } +\endcode +*/ +class JSON_API StreamWriter { +protected: + OStream* sout_; // not owned; will not delete +public: + StreamWriter(); + virtual ~StreamWriter(); + /** Write Value into document as configured in sub-class. + Do not take ownership of sout, but maintain a reference during function. + \pre sout != NULL + \return zero on success (For now, we always return zero, so check the + stream instead.) \throw std::exception possibly, depending on configuration + */ + virtual int write(Value const& root, OStream* sout) = 0; + + /** \brief A simple abstract factory. + */ + class JSON_API Factory { + public: + virtual ~Factory(); + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const = 0; + }; // Factory +}; // StreamWriter + +/** \brief Write into stringstream, then return string, for convenience. + * A StreamWriter will be created from the factory, used, and then deleted. + */ +String JSON_API writeString(StreamWriter::Factory const& factory, + Value const& root); + +/** \brief Build a StreamWriter implementation. + +Usage: +\code + using namespace Json; + Value value = ...; + StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = " "; // or whatever you like + std::unique_ptr<Json::StreamWriter> writer( + builder.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush +\endcode +*/ +class JSON_API StreamWriterBuilder : public StreamWriter::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + Available settings (case-sensitive): + - "commentStyle": "None" or "All" + - "indentation": "<anything>". + - Setting this to an empty string also omits newline characters. + - "enableYAMLCompatibility": false or true + - slightly change the whitespace around colons + - "dropNullPlaceholders": false or true + - Drop the "null" string from the writer's output for nullValues. + Strictly speaking, this is not valid JSON. But when the output is being + fed to a browser's JavaScript, it makes for smaller output and the + browser can handle the output just fine. + - "useSpecialFloats": false or true + - If true, outputs non-finite floating point values in the following way: + NaN values as "NaN", positive infinity as "Infinity", and negative + infinity as "-Infinity". + - "precision": int + - Number of precision digits for formatting of real values. + - "precisionType": "significant"(default) or "decimal" + - Type of precision for formatting of real values. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + StreamWriterBuilder(); + ~StreamWriterBuilder() override; + + /** + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + StreamWriter* newStreamWriter() const override; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** A simple way to update a specific setting. + */ + Value& operator[](const String& key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults + */ + static void setDefaults(Json::Value* settings); +}; + +/** \brief Abstract class for writers. + * \deprecated Use StreamWriter. (And really, this is an implementation detail.) + */ +class JSONCPP_DEPRECATED("Use StreamWriter instead") JSON_API Writer { +public: + virtual ~Writer(); + + virtual String write(const Value& root) = 0; +}; + +/** \brief Outputs a Value in <a HREF="http://www.json.org">JSON</a> format + *without formatting (not human friendly). + * + * The JSON document is written in a single line. It is not intended for 'human' + *consumption, + * but may be useful to support feature such as RPC where bandwidth is limited. + * \sa Reader, Value + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter + : public Writer { +public: + FastWriter(); + ~FastWriter() override = default; + + void enableYAMLCompatibility(); + + /** \brief Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's JavaScript, it makes for smaller output and the + * browser can handle the output just fine. + */ + void dropNullPlaceholders(); + + void omitEndingLineFeed(); + +public: // overridden from Writer + String write(const Value& root) override; + +private: + void writeValue(const Value& value); + + String document_; + bool yamlCompatibilityEnabled_{false}; + bool dropNullPlaceholders_{false}; + bool omitEndingLineFeed_{false}; +}; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a + *human friendly way. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + *line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + *types, + * and all the values fit on one lines, then print the array on a single + *line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + *#CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API + StyledWriter : public Writer { +public: + StyledWriter(); + ~StyledWriter() override = default; + +public: // overridden from Writer + /** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + String write(const Value& root) override; + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultilineArray(const Value& value); + void pushValue(const String& value); + void writeIndent(); + void writeWithIndent(const String& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + static bool hasCommentForValue(const Value& value); + static String normalizeEOL(const String& text); + + typedef std::vector<String> ChildValues; + + ChildValues childValues_; + String document_; + String indentString_; + unsigned int rightMargin_{74}; + unsigned int indentSize_{3}; + bool addChildValues_{false}; +}; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a + human friendly way, + to a stream rather than to a string. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + types, + * and all the values fit on one lines, then print the array on a single + line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + #CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API + StyledStreamWriter { +public: + /** + * \param indentation Each level will be indented by this amount extra. + */ + StyledStreamWriter(String indentation = "\t"); + ~StyledStreamWriter() = default; + +public: + /** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format. + * \param out Stream to write to. (Can be ostringstream, e.g.) + * \param root Value to serialize. + * \note There is no point in deriving from Writer, since write() should not + * return a value. + */ + void write(OStream& out, const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultilineArray(const Value& value); + void pushValue(const String& value); + void writeIndent(); + void writeWithIndent(const String& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + static bool hasCommentForValue(const Value& value); + static String normalizeEOL(const String& text); + + typedef std::vector<String> ChildValues; + + ChildValues childValues_; + OStream* document_; + String indentString_; + unsigned int rightMargin_{74}; + String indentation_; + bool addChildValues_ : 1; + bool indented_ : 1; +}; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(JSON_HAS_INT64) +String JSON_API valueToString(Int value); +String JSON_API valueToString(UInt value); +#endif // if defined(JSON_HAS_INT64) +String JSON_API valueToString(LargestInt value); +String JSON_API valueToString(LargestUInt value); +String JSON_API +valueToString(double value, + unsigned int precision = Value::defaultRealPrecision, + PrecisionType precisionType = PrecisionType::significantDigits); +String JSON_API valueToString(bool value); +String JSON_API valueToQuotedString(const char* value); + +/// \brief Output using the StyledStreamWriter. +/// \see Json::operator>>() +JSON_API OStream& operator<<(OStream&, const Value& root); + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_WRITER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED +#define CPPTL_JSON_ASSERTIONS_H_INCLUDED + +#include <cstdlib> +#include <sstream> + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +/** It should not be possible for a maliciously designed file to + * cause an abort() or seg-fault, so these macros are used only + * for pre-condition violations and internal logic errors. + */ +#if JSON_USE_EXCEPTION + +// @todo <= add detail about condition in exception +#define JSON_ASSERT(condition) \ + { \ + if (!(condition)) { \ + Json::throwLogicError("assert json failed"); \ + } \ + } + +#define JSON_FAIL_MESSAGE(message) \ + { \ + OStringStream oss; \ + oss << message; \ + Json::throwLogicError(oss.str()); \ + abort(); \ + } + +#else // JSON_USE_EXCEPTION + +#define JSON_ASSERT(condition) assert(condition) + +// The call to assert() will show the failure message in debug builds. In +// release builds we abort, for a core-dump or debugger. +#define JSON_FAIL_MESSAGE(message) \ + { \ + OStringStream oss; \ + oss << message; \ + assert(false && oss.str().c_str()); \ + abort(); \ + } + +#endif + +#define JSON_ASSERT_MESSAGE(condition, message) \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } + +#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_AMALGAMATED_H_INCLUDED
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/jsoncpp-1.8.4/jsoncpp.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,5418 @@ +/// Json-cpp amalgamated source (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +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. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + + +#include "json/json.h" + +#ifndef JSON_IS_AMALGAMATION +#error "Compile with -I PATH_TO_JSON_DIRECTORY" +#endif + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED +#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include <json/config.h> +#endif + +// Also support old flag NO_LOCALE_SUPPORT +#ifdef NO_LOCALE_SUPPORT +#define JSONCPP_NO_LOCALE_SUPPORT +#endif + +#ifndef JSONCPP_NO_LOCALE_SUPPORT +#include <clocale> +#endif + +/* This header provides common string manipulation support, such as UTF-8, + * portable conversion from/to string... + * + * It is an internal header that must not be exposed. + */ + +namespace Json { +static inline char getDecimalPoint() { +#ifdef JSONCPP_NO_LOCALE_SUPPORT + return '\0'; +#else + struct lconv* lc = localeconv(); + return lc ? *(lc->decimal_point) : '\0'; +#endif +} + +/// Converts a unicode code-point to UTF-8. +static inline String codePointToUTF8(unsigned int cp) { + String result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) { + result.resize(1); + result[0] = static_cast<char>(cp); + } else if (cp <= 0x7FF) { + result.resize(2); + result[1] = static_cast<char>(0x80 | (0x3f & cp)); + result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6))); + } else if (cp <= 0xFFFF) { + result.resize(3); + result[2] = static_cast<char>(0x80 | (0x3f & cp)); + result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 6))); + result[0] = static_cast<char>(0xE0 | (0xf & (cp >> 12))); + } else if (cp <= 0x10FFFF) { + result.resize(4); + result[3] = static_cast<char>(0x80 | (0x3f & cp)); + result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast<char>(0xF0 | (0x7 & (cp >> 18))); + } + + return result; +} + +enum { + /// Constant that specify the size of the buffer that must be passed to + /// uintToString. + uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 +}; + +// Defines a char buffer for use with uintToString(). +typedef char UIntToStringBuffer[uintToStringBufferSize]; + +/** Converts an unsigned integer to string. + * @param value Unsigned integer to convert to string + * @param current Input/Output string buffer. + * Must have at least uintToStringBufferSize chars free. + */ +static inline void uintToString(LargestUInt value, char*& current) { + *--current = 0; + do { + *--current = static_cast<char>(value % 10U + static_cast<unsigned>('0')); + value /= 10; + } while (value != 0); +} + +/** Change ',' to '.' everywhere in buffer. + * + * We had a sophisticated way, but it did not work in WinCE. + * @see https://github.com/open-source-parsers/jsoncpp/pull/9 + */ +template <typename Iter> Iter fixNumericLocale(Iter begin, Iter end) { + for (; begin != end; ++begin) { + if (*begin == ',') { + *begin = '.'; + } + } + return begin; +} + +template <typename Iter> void fixNumericLocaleInput(Iter begin, Iter end) { + char decimalPoint = getDecimalPoint(); + if (decimalPoint == '\0' || decimalPoint == '.') { + return; + } + for (; begin != end; ++begin) { + if (*begin == '.') { + *begin = decimalPoint; + } + } +} + +/** + * Return iterator that would be the new end of the range [begin,end), if we + * were to delete zeros in the end of string, but not the last zero before '.'. + */ +template <typename Iter> Iter fixZerosInTheEnd(Iter begin, Iter end) { + for (; begin != end; --end) { + if (*(end - 1) != '0') { + return end; + } + // Don't delete the last zero before the decimal point. + if (begin != (end - 1) && *(end - 2) == '.') { + return end; + } + } + return end; +} + +} // namespace Json + +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors +// Copyright (C) 2016 InfoTeCS JSC. All rights reserved. +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include "json_tool.h" +#include <json/assertions.h> +#include <json/reader.h> +#include <json/value.h> +#endif // if !defined(JSON_IS_AMALGAMATION) +#include <cassert> +#include <cstring> +#include <istream> +#include <limits> +#include <memory> +#include <set> +#include <sstream> +#include <utility> + +#include <cstdio> +#if __cplusplus >= 201103L + +#if !defined(sscanf) +#define sscanf std::sscanf +#endif + +#endif //__cplusplus + +#if defined(_MSC_VER) +#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) +#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES +#endif //_MSC_VER + +#if defined(_MSC_VER) +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile +// time to change the stack limit +#if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) +#define JSONCPP_DEPRECATED_STACK_LIMIT 1000 +#endif + +static size_t const stackLimit_g = + JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() + +namespace Json { + +#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) +typedef std::unique_ptr<CharReader> CharReaderPtr; +#else +typedef std::auto_ptr<CharReader> CharReaderPtr; +#endif + +// Implementation of class Features +// //////////////////////////////// + +Features::Features() = default; + +Features Features::all() { return {}; } + +Features Features::strictMode() { + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + features.allowDroppedNullPlaceholders_ = false; + features.allowNumericKeys_ = false; + return features; +} + +// Implementation of class Reader +// //////////////////////////////// + +bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { + for (; begin < end; ++begin) + if (*begin == '\n' || *begin == '\r') + return true; + return false; +} + +// Class Reader +// ////////////////////////////////////////////////////////////////// + +Reader::Reader() + : errors_(), document_(), commentsBefore_(), features_(Features::all()) {} + +Reader::Reader(const Features& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool Reader::parse(const std::string& document, + Value& root, + bool collectComments) { + document_.assign(document.begin(), document.end()); + const char* begin = document_.c_str(); + const char* end = begin + document_.length(); + return parse(begin, end, root, collectComments); +} + +bool Reader::parse(std::istream& is, Value& root, bool collectComments) { + // std::istream_iterator<char> begin(is); + // std::istream_iterator<char> end; + // Those would allow streamed input from a file, if parse() were a + // template function. + + // Since String is reference-counted, this at least does not + // create an extra copy. + String doc; + std::getline(is, doc, (char)EOF); + return parse(doc.data(), doc.data() + doc.size(), root, collectComments); +} + +bool Reader::parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; + commentsBefore_.clear(); + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; +} + +bool Reader::readValue() { + // readValue() may call itself only if it calls readObject() or ReadArray(). + // These methods execute nodes_.push() just before and nodes_.pop)() just + // after calling readValue(). parse() executes one nodes_.push(), so > instead + // of >=. + if (nodes_.size() > stackLimit_g) + throwRuntimeError("Exceeded stackLimit in readValue()."); + + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenFalse: { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNull: { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // Else, fall through... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; +} + +void Reader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool Reader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + +void Reader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool Reader::match(Location pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; +} + +bool Reader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { + String normalized; + normalized.reserve(static_cast<size_t>(end - begin)); + Reader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void Reader::addComment(Location begin, + Location end, + CommentPlacement placement) { + assert(collectComments_); + const String& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != nullptr); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool Reader::readCStyleComment() { + while ((current_ + 1) < end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool Reader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +void Reader::readNumber() { + const char* p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } +} + +bool Reader::readString() { + Char c = '\0'; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + +bool Reader::readObject(Token& token) { + Token tokenName; + String name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name.clear(); + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = String(numberName.asCString()); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover("Missing ',' or '}' in object declaration", + comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); +} + +bool Reader::readArray(Token& token) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + skipSpaces(); + if (current_ != end_ && *current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token currentToken; + // Accept Comment after last item in the array. + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) { + ok = readToken(currentToken); + } + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); + } + if (currentToken.type_ == tokenArrayEnd) + break; + } + return true; +} + +bool Reader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of + // them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + auto digit(static_cast<Value::UInt>(c - '0')); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative && value == maxIntegerValue) + decoded = Value::minLargestInt; + else if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool Reader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + String buffer(token.start_, token.end_); + IStringStream is(buffer); + if (!(is >> value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + decoded = value; + return true; +} + +bool Reader::decodeString(Token& token) { + String decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeString(Token& token, String& decoded) { + decoded.reserve(static_cast<size_t>(token.end_ - token.start_ - 2)); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + else if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } else { + decoded += c; + } + } + return true; +} + +bool Reader::decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, current); + if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } else + return false; + } else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, current); + } + return true; +} + +bool Reader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& ret_unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", token, + current); + int unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, current); + } + ret_unicode = static_cast<unsigned int>(unicode); + return true; +} + +bool Reader::addError(const String& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool Reader::recoverFromError(TokenType skipUntilToken) { + size_t const errorCount = errors_.size(); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; +} + +bool Reader::addErrorAndRecover(const String& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& Reader::currentValue() { return *(nodes_.top()); } + +Reader::Char Reader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void Reader::getLocationLineAndColumn(Location location, + int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + +String Reader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; +} + +// Deprecated. Preserved for backward compatibility +String Reader::getFormatedErrorMessages() const { + return getFormattedErrorMessages(); +} + +String Reader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector<Reader::StructuredError> Reader::getStructuredErrors() const { + std::vector<Reader::StructuredError> allErrors; + for (const auto& error : errors_) { + Reader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool Reader::pushError(const Value& value, const String& message) { + ptrdiff_t const length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = nullptr; + errors_.push_back(info); + return true; +} + +bool Reader::pushError(const Value& value, + const String& message, + const Value& extra) { + ptrdiff_t const length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length || + extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool Reader::good() const { return errors_.empty(); } + +// exact copy of Features +class OurFeatures { +public: + static OurFeatures all(); + bool allowComments_; + bool strictRoot_; + bool allowDroppedNullPlaceholders_; + bool allowNumericKeys_; + bool allowSingleQuotes_; + bool failIfExtra_; + bool rejectDupKeys_; + bool allowSpecialFloats_; + size_t stackLimit_; +}; // OurFeatures + +// exact copy of Implementation of class Features +// //////////////////////////////// + +OurFeatures OurFeatures::all() { return {}; } + +// Implementation of class Reader +// //////////////////////////////// + +// exact copy of Reader, renamed to OurReader +class OurReader { +public: + typedef char Char; + typedef const Char* Location; + struct StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + + OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + String getFormattedErrorMessages() const; + std::vector<StructuredError> getStructuredErrors() const; + bool pushError(const Value& value, const String& message); + bool pushError(const Value& value, const String& message, const Value& extra); + bool good() const; + +private: + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl + + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenNaN, + tokenPosInf, + tokenNegInf, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + String message_; + Location extra_; + }; + + typedef std::deque<ErrorInfo> Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + bool readStringSingleQuote(); + bool readNumber(bool checkInf); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, String& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const String& message, Token& token, Location extra = nullptr); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const String& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + String getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + static String normalizeEOL(Location begin, Location end); + static bool containsNewLine(Location begin, Location end); + + typedef std::stack<Value*> Nodes; + Nodes nodes_; + Errors errors_; + String document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + String commentsBefore_; + + OurFeatures const features_; + bool collectComments_; +}; // OurReader + +// complete copy of Read impl, for OurReader + +bool OurReader::containsNewLine(OurReader::Location begin, + OurReader::Location end) { + for (; begin < end; ++begin) + if (*begin == '\n' || *begin == '\r') + return true; + return false; +} + +OurReader::OurReader(OurFeatures const& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool OurReader::parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; + commentsBefore_.clear(); + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (features_.failIfExtra_) { + if ((features_.strictRoot_ || token.type_ != tokenError) && + token.type_ != tokenEndOfStream) { + addError("Extra non-whitespace after JSON value.", token); + return false; + } + } + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; +} + +bool OurReader::readValue() { + // To preserve the old behaviour we cast size_t to int. + if (nodes_.size() > features_.stackLimit_) + throwRuntimeError("Exceeded stackLimit in readValue()."); + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenFalse: { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNull: { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNaN: { + Value v(std::numeric_limits<double>::quiet_NaN()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenPosInf: { + Value v(std::numeric_limits<double>::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNegInf: { + Value v(-std::numeric_limits<double>::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // else, fall through ... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; +} + +void OurReader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool OurReader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '\'': + if (features_.allowSingleQuotes_) { + token.type_ = tokenString; + ok = readStringSingleQuote(); + break; + } // else fall through + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + token.type_ = tokenNumber; + readNumber(false); + break; + case '-': + if (readNumber(true)) { + token.type_ = tokenNumber; + } else { + token.type_ = tokenNegInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case 'N': + if (features_.allowSpecialFloats_) { + token.type_ = tokenNaN; + ok = match("aN", 2); + } else { + ok = false; + } + break; + case 'I': + if (features_.allowSpecialFloats_) { + token.type_ = tokenPosInf; + ok = match("nfinity", 7); + } else { + ok = false; + } + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + +void OurReader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool OurReader::match(Location pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; +} + +bool OurReader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +String OurReader::normalizeEOL(OurReader::Location begin, + OurReader::Location end) { + String normalized; + normalized.reserve(static_cast<size_t>(end - begin)); + OurReader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void OurReader::addComment(Location begin, + Location end, + CommentPlacement placement) { + assert(collectComments_); + const String& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != nullptr); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool OurReader::readCStyleComment() { + while ((current_ + 1) < end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool OurReader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +bool OurReader::readNumber(bool checkInf) { + const char* p = current_; + if (checkInf && p != end_ && *p == 'I') { + current_ = ++p; + return false; + } + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + return true; +} +bool OurReader::readString() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + +bool OurReader::readStringSingleQuote() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '\'') + break; + } + return c == '\''; +} + +bool OurReader::readObject(Token& token) { + Token tokenName; + String name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name.clear(); + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); + } + if (name.length() >= (1U << 30)) + throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) { + String msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover(msg, tokenName, tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover("Missing ',' or '}' in object declaration", + comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); +} + +bool OurReader::readArray(Token& token) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + skipSpaces(); + if (current_ != end_ && *current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token currentToken; + // Accept Comment after last item in the array. + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) { + ok = readToken(currentToken); + } + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); + } + if (currentToken.type_ == tokenArrayEnd) + break; + } + return true; +} + +bool OurReader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of + // them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::minLargestInt) + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + auto digit(static_cast<Value::UInt>(c - '0')); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool OurReader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + const int bufferSize = 32; + int count; + ptrdiff_t const length = token.end_ - token.start_; + + // Sanity check to avoid buffer overflow exploits. + if (length < 0) { + return addError("Unable to parse token length", token); + } + auto const ulength = static_cast<size_t>(length); + + // Avoid using a string constant for the format control string given to + // sscanf, as this can cause hard to debug crashes on OS X. See here for more + // info: + // + // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html + char format[] = "%lf"; + + if (length <= bufferSize) { + Char buffer[bufferSize + 1]; + memcpy(buffer, token.start_, ulength); + buffer[length] = 0; + fixNumericLocaleInput(buffer, buffer + length); + count = sscanf(buffer, format, &value); + } else { + String buffer(token.start_, token.end_); + count = sscanf(buffer.c_str(), format, &value); + } + + if (count != 1) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + decoded = value; + return true; +} + +bool OurReader::decodeString(Token& token) { + String decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeString(Token& token, String& decoded) { + decoded.reserve(static_cast<size_t>(token.end_ - token.start_ - 2)); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + else if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } else { + decoded += c; + } + } + return true; +} + +bool OurReader::decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, current); + if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } else + return false; + } else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, current); + } + return true; +} + +bool OurReader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& ret_unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", token, + current); + int unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, current); + } + ret_unicode = static_cast<unsigned int>(unicode); + return true; +} + +bool OurReader::addError(const String& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool OurReader::recoverFromError(TokenType skipUntilToken) { + size_t errorCount = errors_.size(); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; +} + +bool OurReader::addErrorAndRecover(const String& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& OurReader::currentValue() { return *(nodes_.top()); } + +OurReader::Char OurReader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void OurReader::getLocationLineAndColumn(Location location, + int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + +String OurReader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; +} + +String OurReader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector<OurReader::StructuredError> OurReader::getStructuredErrors() const { + std::vector<OurReader::StructuredError> allErrors; + for (const auto& error : errors_) { + OurReader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool OurReader::pushError(const Value& value, const String& message) { + ptrdiff_t length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = nullptr; + errors_.push_back(info); + return true; +} + +bool OurReader::pushError(const Value& value, + const String& message, + const Value& extra) { + ptrdiff_t length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length || + extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool OurReader::good() const { return errors_.empty(); } + +class OurCharReader : public CharReader { + bool const collectComments_; + OurReader reader_; + +public: + OurCharReader(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) {} + bool parse(char const* beginDoc, + char const* endDoc, + Value* root, + String* errs) override { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; + } +}; + +CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } +CharReaderBuilder::~CharReaderBuilder() = default; +CharReader* CharReaderBuilder::newCharReader() const { + bool collectComments = settings_["collectComments"].asBool(); + OurFeatures features = OurFeatures::all(); + features.allowComments_ = settings_["allowComments"].asBool(); + features.strictRoot_ = settings_["strictRoot"].asBool(); + features.allowDroppedNullPlaceholders_ = + settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); + features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); +#if defined(JSON_HAS_INT64) + features.stackLimit_ = settings_["stackLimit"].asUInt64(); +#else + features.stackLimit_ = settings_["stackLimit"].asUInt(); +#endif + features.failIfExtra_ = settings_["failIfExtra"].asBool(); + features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); + features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); + return new OurCharReader(collectComments, features); +} +static void getValidReaderKeys(std::set<String>* valid_keys) { + valid_keys->clear(); + valid_keys->insert("collectComments"); + valid_keys->insert("allowComments"); + valid_keys->insert("strictRoot"); + valid_keys->insert("allowDroppedNullPlaceholders"); + valid_keys->insert("allowNumericKeys"); + valid_keys->insert("allowSingleQuotes"); + valid_keys->insert("stackLimit"); + valid_keys->insert("failIfExtra"); + valid_keys->insert("rejectDupKeys"); + valid_keys->insert("allowSpecialFloats"); +} +bool CharReaderBuilder::validate(Json::Value* invalid) const { + Json::Value my_invalid; + if (!invalid) + invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set<String> valid_keys; + getValidReaderKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + String const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return inv.empty(); +} +Value& CharReaderBuilder::operator[](const String& key) { + return settings_[key]; +} +// static +void CharReaderBuilder::strictMode(Json::Value* settings) { + //! [CharReaderBuilderStrictMode] + (*settings)["allowComments"] = false; + (*settings)["strictRoot"] = true; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = true; + (*settings)["allowSpecialFloats"] = false; + //! [CharReaderBuilderStrictMode] +} +// static +void CharReaderBuilder::setDefaults(Json::Value* settings) { + //! [CharReaderBuilderDefaults] + (*settings)["collectComments"] = true; + (*settings)["allowComments"] = true; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = false; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; + //! [CharReaderBuilderDefaults] +} + +////////////////////////////////// +// global functions + +bool parseFromStream(CharReader::Factory const& fact, + IStream& sin, + Value* root, + String* errs) { + OStringStream ssin; + ssin << sin.rdbuf(); + String doc = ssin.str(); + char const* begin = doc.data(); + char const* end = begin + doc.size(); + // Note that we do not actually need a null-terminator. + CharReaderPtr const reader(fact.newCharReader()); + return reader->parse(begin, end, root, errs); +} + +IStream& operator>>(IStream& sin, Value& root) { + CharReaderBuilder b; + String errs; + bool ok = parseFromStream(b, sin, &root, &errs); + if (!ok) { + throwRuntimeError(errs); + } + return sin; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIteratorBase +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIteratorBase::ValueIteratorBase() : current_() {} + +ValueIteratorBase::ValueIteratorBase( + const Value::ObjectValues::iterator& current) + : current_(current), isNull_(false) {} + +Value& ValueIteratorBase::deref() const { return current_->second; } + +void ValueIteratorBase::increment() { ++current_; } + +void ValueIteratorBase::decrement() { --current_; } + +ValueIteratorBase::difference_type +ValueIteratorBase::computeDistance(const SelfType& other) const { +#ifdef JSON_USE_CPPTL_SMALLMAP + return other.current_ - current_; +#else + // Iterator for null value are initialized using the default + // constructor, which initialize current_ to the default + // std::map::iterator. As begin() and end() are two instance + // of the default std::map::iterator, they can not be compared. + // To allow this, we handle this comparison specifically. + if (isNull_ && other.isNull_) { + return 0; + } + + // Usage of std::distance is not portable (does not compile with Sun Studio 12 + // RogueWave STL, + // which is the one used by default). + // Using a portable hand-made version for non random iterator instead: + // return difference_type( std::distance( current_, other.current_ ) ); + difference_type myDistance = 0; + for (Value::ObjectValues::iterator it = current_; it != other.current_; + ++it) { + ++myDistance; + } + return myDistance; +#endif +} + +bool ValueIteratorBase::isEqual(const SelfType& other) const { + if (isNull_) { + return other.isNull_; + } + return current_ == other.current_; +} + +void ValueIteratorBase::copy(const SelfType& other) { + current_ = other.current_; + isNull_ = other.isNull_; +} + +Value ValueIteratorBase::key() const { + const Value::CZString czstring = (*current_).first; + if (czstring.data()) { + if (czstring.isStaticString()) + return Value(StaticString(czstring.data())); + return Value(czstring.data(), czstring.data() + czstring.length()); + } + return Value(czstring.index()); +} + +UInt ValueIteratorBase::index() const { + const Value::CZString czstring = (*current_).first; + if (!czstring.data()) + return czstring.index(); + return Value::UInt(-1); +} + +String ValueIteratorBase::name() const { + char const* keey; + char const* end; + keey = memberName(&end); + if (!keey) + return String(); + return String(keey, end); +} + +char const* ValueIteratorBase::memberName() const { + const char* cname = (*current_).first.data(); + return cname ? cname : ""; +} + +char const* ValueIteratorBase::memberName(char const** end) const { + const char* cname = (*current_).first.data(); + if (!cname) { + *end = nullptr; + return nullptr; + } + *end = cname + (*current_).first.length(); + return cname; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueConstIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueConstIterator::ValueConstIterator() = default; + +ValueConstIterator::ValueConstIterator( + const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueConstIterator::ValueConstIterator(ValueIterator const& other) + : ValueIteratorBase(other) {} + +ValueConstIterator& ValueConstIterator:: +operator=(const ValueIteratorBase& other) { + copy(other); + return *this; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIterator::ValueIterator() = default; + +ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueIterator::ValueIterator(const ValueConstIterator& other) + : ValueIteratorBase(other) { + throwRuntimeError("ConstIterator to Iterator should never be allowed."); +} + +ValueIterator::ValueIterator(const ValueIterator& other) = default; + +ValueIterator& ValueIterator::operator=(const SelfType& other) { + copy(other); + return *this; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include <json/assertions.h> +#include <json/value.h> +#include <json/writer.h> +#endif // if !defined(JSON_IS_AMALGAMATION) +#include <cassert> +#include <cmath> +#include <cstring> +#include <sstream> +#include <utility> +#ifdef JSON_USE_CPPTL +#include <cpptl/conststring.h> +#endif +#include <algorithm> // min() +#include <cstddef> // size_t + +// Provide implementation equivalent of std::snprintf for older _MSC compilers +#if defined(_MSC_VER) && _MSC_VER < 1900 +#include <stdarg.h> +static int msvc_pre1900_c99_vsnprintf(char* outBuf, + size_t size, + const char* format, + va_list ap) { + int count = -1; + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + return count; +} + +int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, + size_t size, + const char* format, + ...) { + va_list ap; + va_start(ap, format); + const int count = msvc_pre1900_c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + return count; +} +#endif + +// Disable warning C4702 : unreachable code +#if defined(_MSC_VER) +#pragma warning(disable : 4702) +#endif + +#define JSON_ASSERT_UNREACHABLE assert(false) + +namespace Json { + +// This is a walkaround to avoid the static initialization of Value::null. +// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of +// 8 (instead of 4) as a bit of future-proofing. +#if defined(__ARMEL__) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#else +#define ALIGNAS(byte_alignment) +#endif +// static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 }; +// const unsigned char& kNullRef = kNull[0]; +// const Value& Value::null = reinterpret_cast<const Value&>(kNullRef); +// const Value& Value::nullRef = null; + +// static +Value const& Value::nullSingleton() { + static Value const nullStatic; + return nullStatic; +} + +// for backwards compatibility, we'll leave these global references around, but +// DO NOT use them in JSONCPP library code any more! +Value const& Value::null = Value::nullSingleton(); +Value const& Value::nullRef = Value::nullSingleton(); + +const Int Value::minInt = Int(~(UInt(-1) / 2)); +const Int Value::maxInt = Int(UInt(-1) / 2); +const UInt Value::maxUInt = UInt(-1); +#if defined(JSON_HAS_INT64) +const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2)); +const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2); +const UInt64 Value::maxUInt64 = UInt64(-1); +// The constant is hard-coded because some compiler have trouble +// converting Value::maxUInt64 to a double correctly (AIX/xlC). +// Assumes that UInt64 is a 64 bits integer. +static const double maxUInt64AsDouble = 18446744073709551615.0; +#endif // defined(JSON_HAS_INT64) +const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); +const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); +const LargestUInt Value::maxLargestUInt = LargestUInt(-1); + +const UInt Value::defaultRealPrecision = 17; + +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +template <typename T, typename U> +static inline bool InRange(double d, T min, U max) { + // The casts can lose precision, but we are looking only for + // an approximate range. Might fail on edge cases though. ~cdunn + // return d >= static_cast<double>(min) && d <= static_cast<double>(max); + return d >= min && d <= max; +} +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +static inline double integerToDouble(Json::UInt64 value) { + return static_cast<double>(Int64(value / 2)) * 2.0 + + static_cast<double>(Int64(value & 1)); +} + +template <typename T> static inline double integerToDouble(T value) { + return static_cast<double>(value); +} + +template <typename T, typename U> +static inline bool InRange(double d, T min, U max) { + return d >= integerToDouble(min) && d <= integerToDouble(max); +} +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + +/** Duplicates the specified string value. + * @param value Pointer to the string to duplicate. Must be zero-terminated if + * length is "unknown". + * @param length Length of the value. if equals to unknown, then it will be + * computed using strlen(value). + * @return Pointer on the duplicate instance of string. + */ +static inline char* duplicateStringValue(const char* value, size_t length) { + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + if (length >= static_cast<size_t>(Value::maxInt)) + length = Value::maxInt - 1; + + char* newString = static_cast<char*>(malloc(length + 1)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); + } + memcpy(newString, value, length); + newString[length] = 0; + return newString; +} + +/* Record the length as a prefix. + */ +static inline char* duplicateAndPrefixStringValue(const char* value, + unsigned int length) { + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + JSON_ASSERT_MESSAGE(length <= static_cast<unsigned>(Value::maxInt) - + sizeof(unsigned) - 1U, + "in Json::Value::duplicateAndPrefixStringValue(): " + "length too big for prefixing"); + unsigned actualLength = length + static_cast<unsigned>(sizeof(unsigned)) + 1U; + char* newString = static_cast<char*>(malloc(actualLength)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): " + "Failed to allocate string value buffer"); + } + *reinterpret_cast<unsigned*>(newString) = length; + memcpy(newString + sizeof(unsigned), value, length); + newString[actualLength - 1U] = + 0; // to avoid buffer over-run accidents by users later + return newString; +} +inline static void decodePrefixedString(bool isPrefixed, + char const* prefixed, + unsigned* length, + char const** value) { + if (!isPrefixed) { + *length = static_cast<unsigned>(strlen(prefixed)); + *value = prefixed; + } else { + *length = *reinterpret_cast<unsigned const*>(prefixed); + *value = prefixed + sizeof(unsigned); + } +} +/** Free the string duplicated by + * duplicateStringValue()/duplicateAndPrefixStringValue(). + */ +#if JSONCPP_USING_SECURE_MEMORY +static inline void releasePrefixedStringValue(char* value) { + unsigned length = 0; + char const* valueDecoded; + decodePrefixedString(true, value, &length, &valueDecoded); + size_t const size = sizeof(unsigned) + length + 1U; + memset(value, 0, size); + free(value); +} +static inline void releaseStringValue(char* value, unsigned length) { + // length==0 => we allocated the strings memory + size_t size = (length == 0) ? strlen(value) : length; + memset(value, 0, size); + free(value); +} +#else // !JSONCPP_USING_SECURE_MEMORY +static inline void releasePrefixedStringValue(char* value) { free(value); } +static inline void releaseStringValue(char* value, unsigned) { free(value); } +#endif // JSONCPP_USING_SECURE_MEMORY + +} // namespace Json + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ValueInternals... +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +#if !defined(JSON_IS_AMALGAMATION) + +#include "json_valueiterator.inl" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +Exception::Exception(String msg) : msg_(std::move(msg)) {} +Exception::~Exception() JSONCPP_NOEXCEPT {} +char const* Exception::what() const JSONCPP_NOEXCEPT { return msg_.c_str(); } +RuntimeError::RuntimeError(String const& msg) : Exception(msg) {} +LogicError::LogicError(String const& msg) : Exception(msg) {} +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { + throw RuntimeError(msg); +} +JSONCPP_NORETURN void throwLogicError(String const& msg) { + throw LogicError(msg); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CommentInfo +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +Value::CommentInfo::CommentInfo() = default; + +Value::CommentInfo::~CommentInfo() { + if (comment_) + releaseStringValue(comment_, 0u); +} + +void Value::CommentInfo::setComment(const char* text, size_t len) { + if (comment_) { + releaseStringValue(comment_, 0u); + comment_ = nullptr; + } + JSON_ASSERT(text != nullptr); + JSON_ASSERT_MESSAGE( + text[0] == '\0' || text[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + // It seems that /**/ style comments are acceptable as well. + comment_ = duplicateStringValue(text, len); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CZString +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +// Notes: policy_ indicates if the string was allocated when +// a string is stored. + +Value::CZString::CZString(ArrayIndex index) : cstr_(nullptr), index_(index) {} + +Value::CZString::CZString(char const* str, + unsigned length, + DuplicationPolicy allocate) + : cstr_(str) { + // allocate != duplicate + storage_.policy_ = allocate & 0x3; + storage_.length_ = length & 0x3FFFFFFF; +} + +Value::CZString::CZString(const CZString& other) { + cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_); + storage_.policy_ = + static_cast<unsigned>( + other.cstr_ + ? (static_cast<DuplicationPolicy>(other.storage_.policy_) == + noDuplication + ? noDuplication + : duplicate) + : static_cast<DuplicationPolicy>(other.storage_.policy_)) & + 3U; + storage_.length_ = other.storage_.length_; +} + +#if JSON_HAS_RVALUE_REFERENCES +Value::CZString::CZString(CZString&& other) + : cstr_(other.cstr_), index_(other.index_) { + other.cstr_ = nullptr; +} +#endif + +Value::CZString::~CZString() { + if (cstr_ && storage_.policy_ == duplicate) { + releaseStringValue(const_cast<char*>(cstr_), + storage_.length_ + 1u); // +1 for null terminating + // character for sake of + // completeness but not actually + // necessary + } +} + +void Value::CZString::swap(CZString& other) { + std::swap(cstr_, other.cstr_); + std::swap(index_, other.index_); +} + +Value::CZString& Value::CZString::operator=(const CZString& other) { + cstr_ = other.cstr_; + index_ = other.index_; + return *this; +} + +#if JSON_HAS_RVALUE_REFERENCES +Value::CZString& Value::CZString::operator=(CZString&& other) { + cstr_ = other.cstr_; + index_ = other.index_; + other.cstr_ = nullptr; + return *this; +} +#endif + +bool Value::CZString::operator<(const CZString& other) const { + if (!cstr_) + return index_ < other.index_; + // return strcmp(cstr_, other.cstr_) < 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + unsigned min_len = std::min<unsigned>(this_len, other_len); + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, min_len); + if (comp < 0) + return true; + if (comp > 0) + return false; + return (this_len < other_len); +} + +bool Value::CZString::operator==(const CZString& other) const { + if (!cstr_) + return index_ == other.index_; + // return strcmp(cstr_, other.cstr_) == 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + if (this_len != other_len) + return false; + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, this_len); + return comp == 0; +} + +ArrayIndex Value::CZString::index() const { return index_; } + +// const char* Value::CZString::c_str() const { return cstr_; } +const char* Value::CZString::data() const { return cstr_; } +unsigned Value::CZString::length() const { return storage_.length_; } +bool Value::CZString::isStaticString() const { + return storage_.policy_ == noDuplication; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::Value +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +/*! \internal Default constructor initialization must be equivalent to: + * memset( this, 0, sizeof(Value) ) + * This optimization is used in ValueInternalMap fast allocator. + */ +Value::Value(ValueType type) { + static char const emptyString[] = ""; + initBasic(type); + switch (type) { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + // allocated_ == false, so this is safe. + value_.string_ = const_cast<char*>(static_cast<char const*>(emptyString)); + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +Value::Value(Int value) { + initBasic(intValue); + value_.int_ = value; +} + +Value::Value(UInt value) { + initBasic(uintValue); + value_.uint_ = value; +} +#if defined(JSON_HAS_INT64) +Value::Value(Int64 value) { + initBasic(intValue); + value_.int_ = value; +} +Value::Value(UInt64 value) { + initBasic(uintValue); + value_.uint_ = value; +} +#endif // defined(JSON_HAS_INT64) + +Value::Value(double value) { + initBasic(realValue); + value_.real_ = value; +} + +Value::Value(const char* value) { + initBasic(stringValue, true); + JSON_ASSERT_MESSAGE(value != nullptr, + "Null Value Passed to Value Constructor"); + value_.string_ = duplicateAndPrefixStringValue( + value, static_cast<unsigned>(strlen(value))); +} + +Value::Value(const char* begin, const char* end) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(begin, static_cast<unsigned>(end - begin)); +} + +Value::Value(const String& value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue( + value.data(), static_cast<unsigned>(value.length())); +} + +Value::Value(const StaticString& value) { + initBasic(stringValue); + value_.string_ = const_cast<char*>(value.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value::Value(const CppTL::ConstString& value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue( + value, static_cast<unsigned>(value.length())); +} +#endif + +Value::Value(bool value) { + initBasic(booleanValue); + value_.bool_ = value; +} + +Value::Value(const Value& other) { + dupPayload(other); + dupMeta(other); +} + +Value::Value(Value&& other) { + initBasic(nullValue); + swap(other); +} + +Value::~Value() { + releasePayload(); + delete[] comments_; + value_.uint_ = 0; +} + +Value& Value::operator=(const Value& other) { + Value(other).swap(*this); + return *this; +} + +Value& Value::operator=(Value&& other) { + other.swap(*this); + return *this; +} + +void Value::swapPayload(Value& other) { + std::swap(bits_, other.bits_); + std::swap(value_, other.value_); +} + +void Value::copyPayload(const Value& other) { + releasePayload(); + dupPayload(other); +} + +void Value::swap(Value& other) { + swapPayload(other); + std::swap(comments_, other.comments_); + std::swap(start_, other.start_); + std::swap(limit_, other.limit_); +} + +void Value::copy(const Value& other) { + copyPayload(other); + delete[] comments_; + dupMeta(other); +} + +ValueType Value::type() const { + return static_cast<ValueType>(bits_.value_type_); +} + +int Value::compare(const Value& other) const { + if (*this < other) + return -1; + if (*this > other) + return 1; + return 0; +} + +bool Value::operator<(const Value& other) const { + int typeDelta = type() - other.type(); + if (typeDelta) + return typeDelta < 0 ? true : false; + switch (type()) { + case nullValue: + return false; + case intValue: + return value_.int_ < other.value_.int_; + case uintValue: + return value_.uint_ < other.value_.uint_; + case realValue: + return value_.real_ < other.value_.real_; + case booleanValue: + return value_.bool_ < other.value_.bool_; + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { + if (other.value_.string_) + return true; + else + return false; + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); + unsigned min_len = std::min<unsigned>(this_len, other_len); + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, min_len); + if (comp < 0) + return true; + if (comp > 0) + return false; + return (this_len < other_len); + } + case arrayValue: + case objectValue: { + int delta = int(value_.map_->size() - other.value_.map_->size()); + if (delta) + return delta < 0; + return (*value_.map_) < (*other.value_.map_); + } + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator<=(const Value& other) const { return !(other < *this); } + +bool Value::operator>=(const Value& other) const { return !(*this < other); } + +bool Value::operator>(const Value& other) const { return other < *this; } + +bool Value::operator==(const Value& other) const { + if (type() != other.type()) + return false; + switch (type()) { + case nullValue: + return true; + case intValue: + return value_.int_ == other.value_.int_; + case uintValue: + return value_.uint_ == other.value_.uint_; + case realValue: + return value_.real_ == other.value_.real_; + case booleanValue: + return value_.bool_ == other.value_.bool_; + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { + return (value_.string_ == other.value_.string_); + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); + if (this_len != other_len) + return false; + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, this_len); + return comp == 0; + } + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() && + (*value_.map_) == (*other.value_.map_); + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator!=(const Value& other) const { return !(*this == other); } + +const char* Value::asCString() const { + JSON_ASSERT_MESSAGE(type() == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == nullptr) + return nullptr; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return this_str; +} + +#if JSONCPP_USING_SECURE_MEMORY +unsigned Value::getCStringLength() const { + JSON_ASSERT_MESSAGE(type() == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) + return 0; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return this_len; +} +#endif + +bool Value::getString(char const** begin, char const** end) const { + if (type() != stringValue) + return false; + if (value_.string_ == nullptr) + return false; + unsigned length; + decodePrefixedString(this->isAllocated(), this->value_.string_, &length, + begin); + *end = *begin + length; + return true; +} + +String Value::asString() const { + switch (type()) { + case nullValue: + return ""; + case stringValue: { + if (value_.string_ == nullptr) + return ""; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return String(this_str, this_len); + } + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + return valueToString(value_.int_); + case uintValue: + return valueToString(value_.uint_); + case realValue: + return valueToString(value_.real_); + default: + JSON_FAIL_MESSAGE("Type is not convertible to string"); + } +} + +#ifdef JSON_USE_CPPTL +CppTL::ConstString Value::asConstString() const { + unsigned len; + char const* str; + decodePrefixedString(isAllocated(), value_.string_, &len, &str); + return CppTL::ConstString(str, len); +} +#endif + +Value::Int Value::asInt() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), + "double out of Int range"); + return Int(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int."); +} + +Value::UInt Value::asUInt() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), + "double out of UInt range"); + return UInt(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt."); +} + +#if defined(JSON_HAS_INT64) + +Value::Int64 Value::asInt64() const { + switch (type()) { + case intValue: + return Int64(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); + return Int64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), + "double out of Int64 range"); + return Int64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int64."); +} + +Value::UInt64 Value::asUInt64() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); + return UInt64(value_.int_); + case uintValue: + return UInt64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), + "double out of UInt64 range"); + return UInt64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); +} +#endif // if defined(JSON_HAS_INT64) + +LargestInt Value::asLargestInt() const { +#if defined(JSON_NO_INT64) + return asInt(); +#else + return asInt64(); +#endif +} + +LargestUInt Value::asLargestUInt() const { +#if defined(JSON_NO_INT64) + return asUInt(); +#else + return asUInt64(); +#endif +} + +double Value::asDouble() const { + switch (type()) { + case intValue: + return static_cast<double>(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast<double>(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to double."); +} + +float Value::asFloat() const { + switch (type()) { + case intValue: + return static_cast<float>(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast<float>(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + // This can fail (silently?) if the value is bigger than MAX_FLOAT. + return static_cast<float>(integerToDouble(value_.uint_)); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast<float>(value_.real_); + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0f : 0.0f; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to float."); +} + +bool Value::asBool() const { + switch (type()) { + case booleanValue: + return value_.bool_; + case nullValue: + return false; + case intValue: + return value_.int_ ? true : false; + case uintValue: + return value_.uint_ ? true : false; + case realValue: + // This is kind of strange. Not recommended. + return (value_.real_ != 0.0) ? true : false; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to bool."); +} + +bool Value::isConvertibleTo(ValueType other) const { + switch (other) { + case nullValue: + return (isNumeric() && asDouble() == 0.0) || + (type() == booleanValue && value_.bool_ == false) || + (type() == stringValue && asString().empty()) || + (type() == arrayValue && value_.map_->empty()) || + (type() == objectValue && value_.map_->empty()) || + type() == nullValue; + case intValue: + return isInt() || + (type() == realValue && InRange(value_.real_, minInt, maxInt)) || + type() == booleanValue || type() == nullValue; + case uintValue: + return isUInt() || + (type() == realValue && InRange(value_.real_, 0, maxUInt)) || + type() == booleanValue || type() == nullValue; + case realValue: + return isNumeric() || type() == booleanValue || type() == nullValue; + case booleanValue: + return isNumeric() || type() == booleanValue || type() == nullValue; + case stringValue: + return isNumeric() || type() == booleanValue || type() == stringValue || + type() == nullValue; + case arrayValue: + return type() == arrayValue || type() == nullValue; + case objectValue: + return type() == objectValue || type() == nullValue; + } + JSON_ASSERT_UNREACHABLE; + return false; +} + +/// Number of values in array or object +ArrayIndex Value::size() const { + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + case stringValue: + return 0; + case arrayValue: // size of the array is highest index + 1 + if (!value_.map_->empty()) { + ObjectValues::const_iterator itLast = value_.map_->end(); + --itLast; + return (*itLast).first.index() + 1; + } + return 0; + case objectValue: + return ArrayIndex(value_.map_->size()); + } + JSON_ASSERT_UNREACHABLE; + return 0; // unreachable; +} + +bool Value::empty() const { + if (isNull() || isArray() || isObject()) + return size() == 0u; + else + return false; +} + +Value::operator bool() const { return !isNull(); } + +void Value::clear() { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue || + type() == objectValue, + "in Json::Value::clear(): requires complex value"); + start_ = 0; + limit_ = 0; + switch (type()) { + case arrayValue: + case objectValue: + value_.map_->clear(); + break; + default: + break; + } +} + +void Value::resize(ArrayIndex newSize) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::resize(): requires arrayValue"); + if (type() == nullValue) + *this = Value(arrayValue); + ArrayIndex oldSize = size(); + if (newSize == 0) + clear(); + else if (newSize > oldSize) + this->operator[](newSize - 1); + else { + for (ArrayIndex index = newSize; index < oldSize; ++index) { + value_.map_->erase(index); + } + JSON_ASSERT(size() == newSize); + } +} + +Value& Value::operator[](ArrayIndex index) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == arrayValue, + "in Json::Value::operator[](ArrayIndex): requires arrayValue"); + if (type() == nullValue) + *this = Value(arrayValue); + CZString key(index); + auto it = value_.map_->lower_bound(key); + if (it != value_.map_->end() && (*it).first == key) + return (*it).second; + + ObjectValues::value_type defaultValue(key, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + return (*it).second; +} + +Value& Value::operator[](int index) { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index): index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +const Value& Value::operator[](ArrayIndex index) const { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == arrayValue, + "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); + if (type() == nullValue) + return nullSingleton(); + CZString key(index); + ObjectValues::const_iterator it = value_.map_->find(key); + if (it == value_.map_->end()) + return nullSingleton(); + return (*it).second; +} + +const Value& Value::operator[](int index) const { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index) const: index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +void Value::initBasic(ValueType type, bool allocated) { + setType(type); + setIsAllocated(allocated); + comments_ = nullptr; + start_ = 0; + limit_ = 0; +} + +void Value::dupPayload(const Value& other) { + setType(other.type()); + setIsAllocated(false); + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.isAllocated()) { + unsigned len; + char const* str; + decodePrefixedString(other.isAllocated(), other.value_.string_, &len, + &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + setIsAllocated(true); + } else { + value_.string_ = other.value_.string_; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +void Value::releasePayload() { + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (isAllocated()) + releasePrefixedStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +void Value::dupMeta(const Value& other) { + if (other.comments_) { + comments_ = new CommentInfo[numberOfCommentPlacement]; + for (int comment = 0; comment < numberOfCommentPlacement; ++comment) { + const CommentInfo& otherComment = other.comments_[comment]; + if (otherComment.comment_) + comments_[comment].setComment(otherComment.comment_, + strlen(otherComment.comment_)); + } + } else { + comments_ = nullptr; + } + start_ = other.start_; + limit_ = other.limit_; +} + +// Access an object value by name, create a null member if it does not exist. +// @pre Type of '*this' is object or null. +// @param key is null-terminated. +Value& Value::resolveReference(const char* key) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::resolveReference(): requires objectValue"); + if (type() == nullValue) + *this = Value(objectValue); + CZString actualKey(key, static_cast<unsigned>(strlen(key)), + CZString::noDuplication); // NOTE! + auto it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +// @param key is not null-terminated. +Value& Value::resolveReference(char const* key, char const* end) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::resolveReference(key, end): requires objectValue"); + if (type() == nullValue) + *this = Value(objectValue); + CZString actualKey(key, static_cast<unsigned>(end - key), + CZString::duplicateOnCopy); + auto it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +Value Value::get(ArrayIndex index, const Value& defaultValue) const { + const Value* value = &((*this)[index]); + return value == &nullSingleton() ? defaultValue : *value; +} + +bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } + +Value const* Value::find(char const* begin, char const* end) const { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::find(key, end, found): requires " + "objectValue or nullValue"); + if (type() == nullValue) + return nullptr; + CZString actualKey(begin, static_cast<unsigned>(end - begin), + CZString::noDuplication); + ObjectValues::const_iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return nullptr; + return &(*it).second; +} +const Value& Value::operator[](const char* key) const { + Value const* found = find(key, key + strlen(key)); + if (!found) + return nullSingleton(); + return *found; +} +Value const& Value::operator[](const String& key) const { + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) + return nullSingleton(); + return *found; +} + +Value& Value::operator[](const char* key) { + return resolveReference(key, key + strlen(key)); +} + +Value& Value::operator[](const String& key) { + return resolveReference(key.data(), key.data() + key.length()); +} + +Value& Value::operator[](const StaticString& key) { + return resolveReference(key.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value& Value::operator[](const CppTL::ConstString& key) { + return resolveReference(key.c_str(), key.end_c_str()); +} +Value const& Value::operator[](CppTL::ConstString const& key) const { + Value const* found = find(key.c_str(), key.end_c_str()); + if (!found) + return nullSingleton(); + return *found; +} +#endif + +Value& Value::append(const Value& value) { return (*this)[size()] = value; } + +#if JSON_HAS_RVALUE_REFERENCES +Value& Value::append(Value&& value) { + return (*this)[size()] = std::move(value); +} +#endif + +Value Value::get(char const* begin, + char const* end, + Value const& defaultValue) const { + Value const* found = find(begin, end); + return !found ? defaultValue : *found; +} +Value Value::get(char const* key, Value const& defaultValue) const { + return get(key, key + strlen(key), defaultValue); +} +Value Value::get(String const& key, Value const& defaultValue) const { + return get(key.data(), key.data() + key.length(), defaultValue); +} + +bool Value::removeMember(const char* begin, const char* end, Value* removed) { + if (type() != objectValue) { + return false; + } + CZString actualKey(begin, static_cast<unsigned>(end - begin), + CZString::noDuplication); + auto it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return false; + if (removed) +#if JSON_HAS_RVALUE_REFERENCES + *removed = std::move(it->second); +#else + *removed = it->second; +#endif + value_.map_->erase(it); + return true; +} +bool Value::removeMember(const char* key, Value* removed) { + return removeMember(key, key + strlen(key), removed); +} +bool Value::removeMember(String const& key, Value* removed) { + return removeMember(key.data(), key.data() + key.length(), removed); +} +void Value::removeMember(const char* key) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type() == nullValue) + return; + + CZString actualKey(key, unsigned(strlen(key)), CZString::noDuplication); + value_.map_->erase(actualKey); +} +void Value::removeMember(const String& key) { removeMember(key.c_str()); } + +bool Value::removeIndex(ArrayIndex index, Value* removed) { + if (type() != arrayValue) { + return false; + } + CZString key(index); + auto it = value_.map_->find(key); + if (it == value_.map_->end()) { + return false; + } + if (removed) + *removed = it->second; + ArrayIndex oldSize = size(); + // shift left all items left, into the place of the "removed" + for (ArrayIndex i = index; i < (oldSize - 1); ++i) { + CZString keey(i); + (*value_.map_)[keey] = (*this)[i + 1]; + } + // erase the last one ("leftover") + CZString keyLast(oldSize - 1); + auto itLast = value_.map_->find(keyLast); + value_.map_->erase(itLast); + return true; +} + +#ifdef JSON_USE_CPPTL +Value Value::get(const CppTL::ConstString& key, + const Value& defaultValue) const { + return get(key.c_str(), key.end_c_str(), defaultValue); +} +#endif + +bool Value::isMember(char const* begin, char const* end) const { + Value const* value = find(begin, end); + return nullptr != value; +} +bool Value::isMember(char const* key) const { + return isMember(key, key + strlen(key)); +} +bool Value::isMember(String const& key) const { + return isMember(key.data(), key.data() + key.length()); +} + +#ifdef JSON_USE_CPPTL +bool Value::isMember(const CppTL::ConstString& key) const { + return isMember(key.c_str(), key.end_c_str()); +} +#endif + +Value::Members Value::getMemberNames() const { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::getMemberNames(), value must be objectValue"); + if (type() == nullValue) + return Value::Members(); + Members members; + members.reserve(value_.map_->size()); + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for (; it != itEnd; ++it) { + members.push_back(String((*it).first.data(), (*it).first.length())); + } + return members; +} +// +//# ifdef JSON_USE_CPPTL +// EnumMemberNames +// Value::enumMemberNames() const +//{ +// if ( type() == objectValue ) +// { +// return CppTL::Enum::any( CppTL::Enum::transform( +// CppTL::Enum::keys( *(value_.map_), CppTL::Type<const CZString &>() ), +// MemberNamesTransform() ) ); +// } +// return EnumMemberNames(); +//} +// +// +// EnumValues +// Value::enumValues() const +//{ +// if ( type() == objectValue || type() == arrayValue ) +// return CppTL::Enum::anyValues( *(value_.map_), +// CppTL::Type<const Value &>() ); +// return EnumValues(); +//} +// +//# endif + +static bool IsIntegral(double d) { + double integral_part; + return modf(d, &integral_part) == 0.0; +} + +bool Value::isNull() const { return type() == nullValue; } + +bool Value::isBool() const { return type() == booleanValue; } + +bool Value::isInt() const { + switch (type()) { + case intValue: +#if defined(JSON_HAS_INT64) + return value_.int_ >= minInt && value_.int_ <= maxInt; +#else + return true; +#endif + case uintValue: + return value_.uint_ <= UInt(maxInt); + case realValue: + return value_.real_ >= minInt && value_.real_ <= maxInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isUInt() const { + switch (type()) { + case intValue: +#if defined(JSON_HAS_INT64) + return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); +#else + return value_.int_ >= 0; +#endif + case uintValue: +#if defined(JSON_HAS_INT64) + return value_.uint_ <= maxUInt; +#else + return true; +#endif + case realValue: + return value_.real_ >= 0 && value_.real_ <= maxUInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isInt64() const { +#if defined(JSON_HAS_INT64) + switch (type()) { + case intValue: + return true; + case uintValue: + return value_.uint_ <= UInt64(maxInt64); + case realValue: + // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a + // double, so double(maxInt64) will be rounded up to 2^63. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isUInt64() const { +#if defined(JSON_HAS_INT64) + switch (type()) { + case intValue: + return value_.int_ >= 0; + case uintValue: + return true; + case realValue: + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble && + IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isIntegral() const { + switch (type()) { + case intValue: + case uintValue: + return true; + case realValue: +#if defined(JSON_HAS_INT64) + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); +#else + return value_.real_ >= minInt && value_.real_ <= maxUInt && + IsIntegral(value_.real_); +#endif // JSON_HAS_INT64 + default: + break; + } + return false; +} + +bool Value::isDouble() const { + return type() == intValue || type() == uintValue || type() == realValue; +} + +bool Value::isNumeric() const { return isDouble(); } + +bool Value::isString() const { return type() == stringValue; } + +bool Value::isArray() const { return type() == arrayValue; } + +bool Value::isObject() const { return type() == objectValue; } + +void Value::setComment(const char* comment, + size_t len, + CommentPlacement placement) { + if (!comments_) + comments_ = new CommentInfo[numberOfCommentPlacement]; + if ((len > 0) && (comment[len - 1] == '\n')) { + // Always discard trailing newline, to aid indentation. + len -= 1; + } + comments_[placement].setComment(comment, len); +} + +void Value::setComment(const char* comment, CommentPlacement placement) { + setComment(comment, strlen(comment), placement); +} + +void Value::setComment(const String& comment, CommentPlacement placement) { + setComment(comment.c_str(), comment.length(), placement); +} + +bool Value::hasComment(CommentPlacement placement) const { + return comments_ != nullptr && comments_[placement].comment_ != nullptr; +} + +String Value::getComment(CommentPlacement placement) const { + if (hasComment(placement)) + return comments_[placement].comment_; + return ""; +} + +void Value::setOffsetStart(ptrdiff_t start) { start_ = start; } + +void Value::setOffsetLimit(ptrdiff_t limit) { limit_ = limit; } + +ptrdiff_t Value::getOffsetStart() const { return start_; } + +ptrdiff_t Value::getOffsetLimit() const { return limit_; } + +String Value::toStyledString() const { + StreamWriterBuilder builder; + + String out = this->hasComment(commentBefore) ? "\n" : ""; + out += Json::writeString(builder, *this); + out += '\n'; + + return out; +} + +Value::const_iterator Value::begin() const { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->begin()); + break; + default: + break; + } + return {}; +} + +Value::const_iterator Value::end() const { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->end()); + break; + default: + break; + } + return {}; +} + +Value::iterator Value::begin() { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->begin()); + break; + default: + break; + } + return iterator(); +} + +Value::iterator Value::end() { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->end()); + break; + default: + break; + } + return iterator(); +} + +// class PathArgument +// ////////////////////////////////////////////////////////////////// + +PathArgument::PathArgument() : key_() {} + +PathArgument::PathArgument(ArrayIndex index) + : key_(), index_(index), kind_(kindIndex) {} + +PathArgument::PathArgument(const char* key) + : key_(key), index_(), kind_(kindKey) {} + +PathArgument::PathArgument(const String& key) + : key_(key.c_str()), index_(), kind_(kindKey) {} + +// class Path +// ////////////////////////////////////////////////////////////////// + +Path::Path(const String& path, + const PathArgument& a1, + const PathArgument& a2, + const PathArgument& a3, + const PathArgument& a4, + const PathArgument& a5) { + InArgs in; + in.reserve(5); + in.push_back(&a1); + in.push_back(&a2); + in.push_back(&a3); + in.push_back(&a4); + in.push_back(&a5); + makePath(path, in); +} + +void Path::makePath(const String& path, const InArgs& in) { + const char* current = path.c_str(); + const char* end = current + path.length(); + auto itInArg = in.begin(); + while (current != end) { + if (*current == '[') { + ++current; + if (*current == '%') + addPathInArg(path, in, itInArg, PathArgument::kindIndex); + else { + ArrayIndex index = 0; + for (; current != end && *current >= '0' && *current <= '9'; ++current) + index = index * 10 + ArrayIndex(*current - '0'); + args_.push_back(index); + } + if (current == end || *++current != ']') + invalidPath(path, int(current - path.c_str())); + } else if (*current == '%') { + addPathInArg(path, in, itInArg, PathArgument::kindKey); + ++current; + } else if (*current == '.' || *current == ']') { + ++current; + } else { + const char* beginName = current; + while (current != end && !strchr("[.", *current)) + ++current; + args_.push_back(String(beginName, current)); + } + } +} + +void Path::addPathInArg(const String& /*path*/, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind) { + if (itInArg == in.end()) { + // Error: missing argument %d + } else if ((*itInArg)->kind_ != kind) { + // Error: bad argument type + } else { + args_.push_back(**itInArg++); + } +} + +void Path::invalidPath(const String& /*path*/, int /*location*/) { + // Error: invalid path. +} + +const Value& Path::resolve(const Value& root) const { + const Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) { + // Error: unable to resolve path (array value expected at position... + return Value::null; + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: unable to resolve path (object value expected at position...) + return Value::null; + } + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) { + // Error: unable to resolve path (object has no member named '' at + // position...) + return Value::null; + } + } + } + return *node; +} + +Value Path::resolve(const Value& root, const Value& defaultValue) const { + const Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) + return defaultValue; + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) + return defaultValue; + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) + return defaultValue; + } + } + return *node; +} + +Value& Path::make(Value& root) const { + Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray()) { + // Error: node is not an array at position ... + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: node is not an object at position... + } + node = &((*node)[arg.key_]); + } + } + return *node; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include "json_tool.h" +#include <json/writer.h> +#endif // if !defined(JSON_IS_AMALGAMATION) +#include <cassert> +#include <cstring> +#include <iomanip> +#include <memory> +#include <set> +#include <sstream> +#include <utility> + +#if __cplusplus >= 201103L +#include <cmath> +#include <cstdio> + +#if !defined(isnan) +#define isnan std::isnan +#endif + +#if !defined(isfinite) +#define isfinite std::isfinite +#endif + +#else +#include <cmath> +#include <cstdio> + +#if defined(_MSC_VER) +#if !defined(isnan) +#include <float.h> +#define isnan _isnan +#endif + +#if !defined(isfinite) +#include <float.h> +#define isfinite _finite +#endif + +#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) +#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES + +#endif //_MSC_VER + +#if defined(__sun) && defined(__SVR4) // Solaris +#if !defined(isfinite) +#include <ieeefp.h> +#define isfinite finite +#endif +#endif + +#if defined(__hpux) +#if !defined(isfinite) +#if defined(__ia64) && !defined(finite) +#define isfinite(x) \ + ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) +#endif +#endif +#endif + +#if !defined(isnan) +// IEEE standard states that NaN values will not compare to themselves +#define isnan(x) (x != x) +#endif + +#if !defined(__APPLE__) +#if !defined(isfinite) +#define isfinite finite +#endif +#endif +#endif + +#if defined(_MSC_VER) +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +namespace Json { + +#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) +typedef std::unique_ptr<StreamWriter> StreamWriterPtr; +#else +typedef std::auto_ptr<StreamWriter> StreamWriterPtr; +#endif + +String valueToString(LargestInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + if (value == Value::minLargestInt) { + uintToString(LargestUInt(Value::maxLargestInt) + 1, current); + *--current = '-'; + } else if (value < 0) { + uintToString(LargestUInt(-value), current); + *--current = '-'; + } else { + uintToString(LargestUInt(value), current); + } + assert(current >= buffer); + return current; +} + +String valueToString(LargestUInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + uintToString(value, current); + assert(current >= buffer); + return current; +} + +#if defined(JSON_HAS_INT64) + +String valueToString(Int value) { return valueToString(LargestInt(value)); } + +String valueToString(UInt value) { return valueToString(LargestUInt(value)); } + +#endif // # if defined(JSON_HAS_INT64) + +namespace { +String valueToString(double value, + bool useSpecialFloats, + unsigned int precision, + PrecisionType precisionType) { + // Print into the buffer. We need not request the alternative representation + // that always has a decimal point because JSON doesn't distinguish the + // concepts of reals and integers. + if (!isfinite(value)) { + static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, + {"null", "-1e+9999", "1e+9999"}}; + return reps[useSpecialFloats ? 0 : 1] + [isnan(value) ? 0 : (value < 0) ? 1 : 2]; + } + + String buffer(size_t(36), '\0'); + while (true) { + int len = jsoncpp_snprintf( + &*buffer.begin(), buffer.size(), + (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", + precision, value); + assert(len >= 0); + auto wouldPrint = static_cast<size_t>(len); + if (wouldPrint >= buffer.size()) { + buffer.resize(wouldPrint + 1); + continue; + } + buffer.resize(wouldPrint); + break; + } + + buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); + + // strip the zero padding from the right + if (precisionType == PrecisionType::decimalPlaces) { + buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end()); + } + + // try to ensure we preserve the fact that this was given to us as a double on + // input + if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { + buffer += ".0"; + } + return buffer; +} +} // namespace + +String valueToString(double value, + unsigned int precision, + PrecisionType precisionType) { + return valueToString(value, false, precision, precisionType); +} + +String valueToString(bool value) { return value ? "true" : "false"; } + +static bool isAnyCharRequiredQuoting(char const* s, size_t n) { + assert(s || !n); + + char const* const end = s + n; + for (char const* cur = s; cur < end; ++cur) { + if (*cur == '\\' || *cur == '\"' || *cur < ' ' || + static_cast<unsigned char>(*cur) < 0x80) + return true; + } + return false; +} + +static unsigned int utf8ToCodepoint(const char*& s, const char* e) { + const unsigned int REPLACEMENT_CHARACTER = 0xFFFD; + + unsigned int firstByte = static_cast<unsigned char>(*s); + + if (firstByte < 0x80) + return firstByte; + + if (firstByte < 0xE0) { + if (e - s < 2) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = + ((firstByte & 0x1F) << 6) | (static_cast<unsigned int>(s[1]) & 0x3F); + s += 1; + // oversized encoded characters are invalid + return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated; + } + + if (firstByte < 0xF0) { + if (e - s < 3) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x0F) << 12) | + ((static_cast<unsigned int>(s[1]) & 0x3F) << 6) | + (static_cast<unsigned int>(s[2]) & 0x3F); + s += 2; + // surrogates aren't valid codepoints itself + // shouldn't be UTF-8 encoded + if (calculated >= 0xD800 && calculated <= 0xDFFF) + return REPLACEMENT_CHARACTER; + // oversized encoded characters are invalid + return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated; + } + + if (firstByte < 0xF8) { + if (e - s < 4) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x07) << 18) | + ((static_cast<unsigned int>(s[1]) & 0x3F) << 12) | + ((static_cast<unsigned int>(s[2]) & 0x3F) << 6) | + (static_cast<unsigned int>(s[3]) & 0x3F); + s += 3; + // oversized encoded characters are invalid + return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated; + } + + return REPLACEMENT_CHARACTER; +} + +static const char hex2[] = "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + +static String toHex16Bit(unsigned int x) { + const unsigned int hi = (x >> 8) & 0xff; + const unsigned int lo = x & 0xff; + String result(4, ' '); + result[0] = hex2[2 * hi]; + result[1] = hex2[2 * hi + 1]; + result[2] = hex2[2 * lo]; + result[3] = hex2[2 * lo + 1]; + return result; +} + +static String valueToQuotedStringN(const char* value, unsigned length) { + if (value == nullptr) + return ""; + + if (!isAnyCharRequiredQuoting(value, length)) + return String("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to String is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL + String result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + char const* end = value + length; + for (const char* c = value; c != end; ++c) { + switch (*c) { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + // case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something.) + // blep notes: actually escaping \/ may be useful in javascript to avoid </ + // sequence. + // Should add a flag to allow this compatibility mode and prevent this + // sequence from occurring. + default: { + unsigned int cp = utf8ToCodepoint(c, end); + // don't escape non-control characters + // (short escape sequence are applied above) + if (cp < 0x80 && cp >= 0x20) + result += static_cast<char>(cp); + else if (cp < 0x10000) { // codepoint is in Basic Multilingual Plane + result += "\\u"; + result += toHex16Bit(cp); + } else { // codepoint is not in Basic Multilingual Plane + // convert to surrogate pair first + cp -= 0x10000; + result += "\\u"; + result += toHex16Bit((cp >> 10) + 0xD800); + result += "\\u"; + result += toHex16Bit((cp & 0x3FF) + 0xDC00); + } + } break; + } + } + result += "\""; + return result; +} + +String valueToQuotedString(const char* value) { + return valueToQuotedStringN(value, static_cast<unsigned int>(strlen(value))); +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +Writer::~Writer() = default; + +// Class FastWriter +// ////////////////////////////////////////////////////////////////// + +FastWriter::FastWriter() + + = default; + +void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; } + +void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } + +void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } + +String FastWriter::write(const Value& root) { + document_.clear(); + writeValue(root); + if (!omitEndingLineFeed_) + document_ += '\n'; + return document_; +} + +void FastWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + if (!dropNullPlaceholders_) + document_ += "null"; + break; + case intValue: + document_ += valueToString(value.asLargestInt()); + break; + case uintValue: + document_ += valueToString(value.asLargestUInt()); + break; + case realValue: + document_ += valueToString(value.asDouble()); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + document_ += valueToQuotedStringN(str, static_cast<unsigned>(end - str)); + break; + } + case booleanValue: + document_ += valueToString(value.asBool()); + break; + case arrayValue: { + document_ += '['; + ArrayIndex size = value.size(); + for (ArrayIndex index = 0; index < size; ++index) { + if (index > 0) + document_ += ','; + writeValue(value[index]); + } + document_ += ']'; + } break; + case objectValue: { + Value::Members members(value.getMemberNames()); + document_ += '{'; + for (auto it = members.begin(); it != members.end(); ++it) { + const String& name = *it; + if (it != members.begin()) + document_ += ','; + document_ += valueToQuotedStringN(name.data(), + static_cast<unsigned>(name.length())); + document_ += yamlCompatibilityEnabled_ ? ": " : ":"; + writeValue(value[name]); + } + document_ += '}'; + } break; + } +} + +// Class StyledWriter +// ////////////////////////////////////////////////////////////////// + +StyledWriter::StyledWriter() = default; + +String StyledWriter::write(const Value& root) { + document_.clear(); + addChildValues_ = false; + indentString_.clear(); + writeCommentBeforeValue(root); + writeValue(root); + writeCommentAfterValueOnSameLine(root); + document_ += '\n'; + return document_; +} + +void StyledWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + const String& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + document_ += " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void StyledWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultilineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + writeIndent(); + writeValue(childValue); + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + document_ += "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + document_ += ", "; + document_ += childValues_[index]; + } + document_ += " ]"; + } + } +} + +bool StyledWriter::isMultilineArray(const Value& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast<ArrayIndex>(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledWriter::pushValue(const String& value) { + if (addChildValues_) + childValues_.push_back(value); + else + document_ += value; +} + +void StyledWriter::writeIndent() { + if (!document_.empty()) { + char last = document_[document_.length() - 1]; + if (last == ' ') // already indented + return; + if (last != '\n') // Comments may add new-line + document_ += '\n'; + } + document_ += indentString_; +} + +void StyledWriter::writeWithIndent(const String& value) { + writeIndent(); + document_ += value; +} + +void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); } + +void StyledWriter::unindent() { + assert(indentString_.size() >= indentSize_); + indentString_.resize(indentString_.size() - indentSize_); +} + +void StyledWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + document_ += '\n'; + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + document_ += *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + writeIndent(); + ++iter; + } + + // Comments are stripped of trailing newlines, so add one here + document_ += '\n'; +} + +void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + document_ += " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + document_ += '\n'; + document_ += root.getComment(commentAfter); + document_ += '\n'; + } +} + +bool StyledWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +// Class StyledStreamWriter +// ////////////////////////////////////////////////////////////////// + +StyledStreamWriter::StyledStreamWriter(String indentation) + : document_(nullptr), indentation_(std::move(indentation)), + addChildValues_(), indented_(false) {} + +void StyledStreamWriter::write(OStream& out, const Value& root) { + document_ = &out; + addChildValues_ = false; + indentString_.clear(); + indented_ = true; + writeCommentBeforeValue(root); + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *document_ << "\n"; + document_ = nullptr; // Forget the stream, for safety. +} + +void StyledStreamWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + const String& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + *document_ << " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void StyledStreamWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultilineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *document_ << "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *document_ << ", "; + *document_ << childValues_[index]; + } + *document_ << " ]"; + } + } +} + +bool StyledStreamWriter::isMultilineArray(const Value& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast<ArrayIndex>(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledStreamWriter::pushValue(const String& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *document_ << value; +} + +void StyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + *document_ << '\n' << indentString_; +} + +void StyledStreamWriter::writeWithIndent(const String& value) { + if (!indented_) + writeIndent(); + *document_ << value; + indented_ = false; +} + +void StyledStreamWriter::indent() { indentString_ += indentation_; } + +void StyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *document_ << *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would include newline + *document_ << indentString_; + ++iter; + } + indented_ = false; +} + +void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + *document_ << ' ' << root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *document_ << root.getComment(commentAfter); + } + indented_ = false; +} + +bool StyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +////////////////////////// +// BuiltStyledStreamWriter + +/// Scoped enums are not available until C++11. +struct CommentStyle { + /// Decide whether to write comments. + enum Enum { + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. + }; +}; + +struct BuiltStyledStreamWriter : public StreamWriter { + BuiltStyledStreamWriter(String indentation, + CommentStyle::Enum cs, + String colonSymbol, + String nullSymbol, + String endingLineFeedSymbol, + bool useSpecialFloats, + unsigned int precision, + PrecisionType precisionType); + int write(Value const& root, OStream* sout) override; + +private: + void writeValue(Value const& value); + void writeArrayValue(Value const& value); + bool isMultilineArray(Value const& value); + void pushValue(String const& value); + void writeIndent(); + void writeWithIndent(String const& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(Value const& root); + void writeCommentAfterValueOnSameLine(Value const& root); + static bool hasCommentForValue(const Value& value); + + typedef std::vector<String> ChildValues; + + ChildValues childValues_; + String indentString_; + unsigned int rightMargin_; + String indentation_; + CommentStyle::Enum cs_; + String colonSymbol_; + String nullSymbol_; + String endingLineFeedSymbol_; + bool addChildValues_ : 1; + bool indented_ : 1; + bool useSpecialFloats_ : 1; + unsigned int precision_; + PrecisionType precisionType_; +}; +BuiltStyledStreamWriter::BuiltStyledStreamWriter(String indentation, + CommentStyle::Enum cs, + String colonSymbol, + String nullSymbol, + String endingLineFeedSymbol, + bool useSpecialFloats, + unsigned int precision, + PrecisionType precisionType) + : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs), + colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)), + endingLineFeedSymbol_(std::move(endingLineFeedSymbol)), + addChildValues_(false), indented_(false), + useSpecialFloats_(useSpecialFloats), precision_(precision), + precisionType_(precisionType) {} +int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) { + sout_ = sout; + addChildValues_ = false; + indented_ = true; + indentString_.clear(); + writeCommentBeforeValue(root); + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *sout_ << endingLineFeedSymbol_; + sout_ = nullptr; + return 0; +} +void BuiltStyledStreamWriter::writeValue(Value const& value) { + switch (value.type()) { + case nullValue: + pushValue(nullSymbol_); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, + precisionType_)); + break; + case stringValue: { + // Is NULL is possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + String const& name = *it; + Value const& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedStringN( + name.data(), static_cast<unsigned>(name.length()))); + *sout_ << colonSymbol_; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value); + if (isMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + Value const& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *sout_ << "["; + if (!indentation_.empty()) + *sout_ << " "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *sout_ << ((!indentation_.empty()) ? ", " : ","); + *sout_ << childValues_[index]; + } + if (!indentation_.empty()) + *sout_ << " "; + *sout_ << "]"; + } + } +} + +bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + Value const& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast<ArrayIndex>(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void BuiltStyledStreamWriter::pushValue(String const& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *sout_ << value; +} + +void BuiltStyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + + if (!indentation_.empty()) { + // In this case, drop newlines too. + *sout_ << '\n' << indentString_; + } +} + +void BuiltStyledStreamWriter::writeWithIndent(String const& value) { + if (!indented_) + writeIndent(); + *sout_ << value; + indented_ = false; +} + +void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } + +void BuiltStyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { + if (cs_ == CommentStyle::None) + return; + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *sout_ << *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would write extra newline + *sout_ << indentString_; + ++iter; + } + indented_ = false; +} + +void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine( + Value const& root) { + if (cs_ == CommentStyle::None) + return; + if (root.hasComment(commentAfterOnSameLine)) + *sout_ << " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *sout_ << root.getComment(commentAfter); + } +} + +// static +bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +/////////////// +// StreamWriter + +StreamWriter::StreamWriter() : sout_(nullptr) {} +StreamWriter::~StreamWriter() = default; +StreamWriter::Factory::~Factory() = default; +StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); } +StreamWriterBuilder::~StreamWriterBuilder() = default; +StreamWriter* StreamWriterBuilder::newStreamWriter() const { + String indentation = settings_["indentation"].asString(); + String cs_str = settings_["commentStyle"].asString(); + String pt_str = settings_["precisionType"].asString(); + bool eyc = settings_["enableYAMLCompatibility"].asBool(); + bool dnp = settings_["dropNullPlaceholders"].asBool(); + bool usf = settings_["useSpecialFloats"].asBool(); + unsigned int pre = settings_["precision"].asUInt(); + CommentStyle::Enum cs = CommentStyle::All; + if (cs_str == "All") { + cs = CommentStyle::All; + } else if (cs_str == "None") { + cs = CommentStyle::None; + } else { + throwRuntimeError("commentStyle must be 'All' or 'None'"); + } + PrecisionType precisionType(significantDigits); + if (pt_str == "significant") { + precisionType = PrecisionType::significantDigits; + } else if (pt_str == "decimal") { + precisionType = PrecisionType::decimalPlaces; + } else { + throwRuntimeError("precisionType must be 'significant' or 'decimal'"); + } + String colonSymbol = " : "; + if (eyc) { + colonSymbol = ": "; + } else if (indentation.empty()) { + colonSymbol = ":"; + } + String nullSymbol = "null"; + if (dnp) { + nullSymbol.clear(); + } + if (pre > 17) + pre = 17; + String endingLineFeedSymbol; + return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, + endingLineFeedSymbol, usf, pre, + precisionType); +} +static void getValidWriterKeys(std::set<String>* valid_keys) { + valid_keys->clear(); + valid_keys->insert("indentation"); + valid_keys->insert("commentStyle"); + valid_keys->insert("enableYAMLCompatibility"); + valid_keys->insert("dropNullPlaceholders"); + valid_keys->insert("useSpecialFloats"); + valid_keys->insert("precision"); + valid_keys->insert("precisionType"); +} +bool StreamWriterBuilder::validate(Json::Value* invalid) const { + Json::Value my_invalid; + if (!invalid) + invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set<String> valid_keys; + getValidWriterKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + String const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return inv.empty(); +} +Value& StreamWriterBuilder::operator[](const String& key) { + return settings_[key]; +} +// static +void StreamWriterBuilder::setDefaults(Json::Value* settings) { + //! [StreamWriterBuilderDefaults] + (*settings)["commentStyle"] = "All"; + (*settings)["indentation"] = "\t"; + (*settings)["enableYAMLCompatibility"] = false; + (*settings)["dropNullPlaceholders"] = false; + (*settings)["useSpecialFloats"] = false; + (*settings)["precision"] = 17; + (*settings)["precisionType"] = "significant"; + //! [StreamWriterBuilderDefaults] +} + +String writeString(StreamWriter::Factory const& factory, Value const& root) { + OStringStream sout; + StreamWriterPtr const writer(factory.newStreamWriter()); + writer->write(root, &sout); + return sout.str(); +} + +OStream& operator<<(OStream& sout, Value const& root) { + StreamWriterBuilder builder; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/main.cpp Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,130 @@ +#include <iostream> +#include <sstream> +#include <emscripten/emscripten.h> +#include "testWasmIntegratedCpp_generated.hpp" + +using std::stringstream; + +int main() +{ + std::cout << "Hello world from testWasmIntegrated! (this is sent from C++)" << std::endl; + try + { + const char* jsonData = R"bgo({"definition": + { + "val" : [ "berk", 42 ], + "zozo" : { "23": "zloutch", "lalala": 42} + } + })bgo"; + std::string strValue(jsonData); + + Json::Value readValue; + + Json::CharReaderBuilder builder; + Json::CharReader* reader = builder.newCharReader(); + + StoneSmartPtr<Json::CharReader> ptr(reader); + + std::string errors; + + bool ok = reader->parse( + strValue.c_str(), + strValue.c_str() + strValue.size(), + &readValue, + &errors + ); + if (!ok) + { + std::stringstream ss; + ss << "Jsoncpp parsing error: " << errors; + throw std::runtime_error(ss.str()); + } + std::cout << "Json parsing OK" << std::endl; + std::cout << readValue << std::endl; + } + catch(std::exception& e) + { + std::cout << "Json parsing THROW" << std::endl; + std::cout << "e.what() = " << e.what() << std::endl; + } +} + +extern "C" void SendMessageFromCppJS(const char* message); +extern "C" void SendFreeTextFromCppJS(const char* message); + +#define HANDLE_MESSAGE(Type,value) \ + stringstream ss; \ + ss << "Received an instance of:\n" #Type "\n. Here's the dump:\n"; \ + testWasmIntegratedCpp::StoneDumpValue(ss, value, 0); \ + SendFreeTextFromCppJS(ss.str().c_str()); \ + return true; + +class MyHandler : public testWasmIntegratedCpp::IHandler +{ + public: + virtual bool Handle(const testWasmIntegratedCpp::A& value) override + { + HANDLE_MESSAGE(testWasmIntegratedCpp::A,value) + } + virtual bool Handle(const testWasmIntegratedCpp::B& value) override + { + HANDLE_MESSAGE(testWasmIntegratedCpp::B,value) + } + + virtual bool Handle(const testWasmIntegratedCpp::Message1& value) override + { + HANDLE_MESSAGE(testWasmIntegratedCpp::Message1,value) + } + + virtual bool Handle(const testWasmIntegratedCpp::Message2& value) override + { + HANDLE_MESSAGE(testWasmIntegratedCpp::Message2,value) + } + + virtual bool Handle(const testWasmIntegratedCpp::C& value) override + { + HANDLE_MESSAGE(testWasmIntegratedCpp::C,value) + } +}; + +extern "C" void EMSCRIPTEN_KEEPALIVE SendMessageToCpp(const char* message) +{ + MyHandler handler; + try + { + bool handled = testWasmIntegratedCpp::StoneDispatchToHandler(message,&handler); + if(!handled) + { + SendFreeTextFromCppJS("This message is valid JSON, but was not recognized!"); + } + } + catch(std::exception& e) + { + stringstream ss; + ss << "Error while parsing message: " << e.what() << "\n"; + SendFreeTextFromCppJS(ss.str().c_str()); + } +} + +void EMSCRIPTEN_KEEPALIVE StartWasmApplication(const char* baseUri) +{ + printf("Hello! (this is sent from C++)\n"); + +// // recreate a command line from uri arguments and parse it +// boost::program_options::variables_map parameters; +// boost::program_options::options_description options; +// application->DeclareStartupOptions(options); +// startupParametersBuilder.GetStartupParameters(parameters, options); + +// context.reset(new OrthancStone::StoneApplicationContext(broker)); +// context->SetOrthancBaseUrl(baseUri); +// printf("Base URL to Orthanc API: [%s]\n", baseUri); +// context->SetWebService(OrthancStone::WasmWebService::GetInstance()); +// context->SetDelayedCallExecutor(OrthancStone::WasmDelayedCallExecutor::GetInstance()); +// application->Initialize(context.get(), statusBar_, parameters); +// application->InitializeWasm(); + +// // viewport->SetSize(width_, height_); +// printf("StartWasmApplication - completed\n"); + SendFreeTextFromCppJS("Hello world from C++!"); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/serve.py Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# tested on python 3.4 ,python of lower version has different module organization. +# from https://gist.github.com/HaiyangXu/ec88cbdce3cdbac7b8d5 +import http.server +from http.server import HTTPServer, BaseHTTPRequestHandler +import socketserver + +PORT = 8080 + +Handler = http.server.SimpleHTTPRequestHandler + +Handler.extensions_map = { + '.manifest': 'text/cache-manifest', + '.html': 'text/html', + '.png': 'image/png', + '.jpg': 'image/jpg', + '.svg': 'image/svg+xml', + '.wasm': 'application/wasm', + '.css': 'text/css', + '.js': 'application/x-javascript', + '': 'application/octet-stream', # Default +} + +httpd = socketserver.TCPServer(("", PORT), Handler) + +print("serving at port", PORT) +httpd.serve_forever()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/styles.css Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,64 @@ +.TestWasm-grid-container { + display: grid; + grid-template-columns: 0.55fr 0.55fr 0.55fr 0.55fr 0.6fr 1.1fr 1.1fr; + grid-template-rows: 1.1fr 0.9fr 0.2fr 0.3fr 0.1fr 0.3fr 0.1fr; + grid-template-areas: + "SerializedInput SerializedInput SerializedInput SerializedInput ButtonContainer CppOutput CppOutput" + "SerializedInput SerializedInput SerializedInput SerializedInput ButtonContainer CppOutput CppOutput" + ". . . . . . ." + "Test1 Test2 Test3 Test4 . . ." + ". . . . . . ." + "Test5 Test6 Test7 Test8 . . ." + ". . . . . . ." + ". . . . . . ." + ; + height: 480px; + } + + .TestWasm-ButtonContainer { + display: grid; + grid-template-columns: 0.2fr 0.8fr 0.2fr; + grid-template-rows: 0.2fr 0.5fr 0.2fr 0.5fr 0.2fr 0.5fr 0.2fr; + grid-template-areas: + ". . ." + ". TriggerButton ." + ". . ." + ". ClearButton ." + ". . ." + ". ShowSchemaButton ." + ". . ." + ; + } + + .TestWasm-TriggerButton { grid-area: TriggerButton; } + + .TestWasm-ClearButton { grid-area: ClearButton; } + + .TestWasm-ShowSchemaButton { grid-area: ShowSchemaButton; } + + +.TestWasm-SerializedInput { grid-area: SerializedInput; } + +.TestWasm-CppOutput { grid-area: CppOutput; } + +.TestWasm-ButtonContainer { grid-area: ButtonContainer; } + +.TestWasm-Test1 { grid-area: Test1; } + +.TestWasm-Test2 { grid-area: Test2; } + +.TestWasm-Test3 { grid-area: Test3; } + +.TestWasm-Test4 { grid-area: Test4; } + +.TestWasm-Test5 { grid-area: Test5; } + +.TestWasm-Test6 { grid-area: Test6; } + +.TestWasm-Test7 { grid-area: Test7; } + +.TestWasm-Test8 { grid-area: Test8; } + +.TestWasm-button { + width:80px; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.html Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,79 @@ +<!doctype html> + +<html lang="us"> + <head> + <meta charset="utf-8" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + + <title>Javascript to WASM message passing</title> + <link href="styles.css" rel="stylesheet" /> + <!-- <link href="styles2.css" rel="stylesheet" /> --> + <!-- + <link href="testwasm_bootstrap.css" rel="stylesheet" /> + --> + <body> + <div class="TestWasm-grid-container"> + <textarea id="TestWasm-SerializedInput" class="TestWasm-SerializedInput">Serialized data should be put here.</textarea> + <textarea id="TestWasm-CppOutput" class="TestWasm-CppOutput">Free text and messages from C++ will appear here.</textarea> + <div class="TestWasm-ButtonContainer"> + <div class="TestWasm-TriggerButton"> + <button class="TestWasm-button" tool-selector="Trigger">Send message</button> + </div> + <div class="TestWasm-ClearButton"> + <button class="TestWasm-button" tool-selector="Clear">Clear</button> + </div> + <div class="TestWasm-ShowSchemaButton"> + <button class="TestWasm-button" tool-selector="ShowSchema">Show schema</button> + </div> + <!-- <button class="TestWasm-button TestWasm-hvcenter" tool-selector="Trigger">Send message</button> + <button class="TestWasm-button TestWasm-hvcenter" tool-selector="Clear">Clear</button> + <button class="TestWasm-button TestWasm-hvcenter" tool-selector="Show schema">Show schema</button> --> + </div> + + <div class="TestWasm-Test1"> + <button class="TestWasm-button" tool-selector="Test 1">Test 1</button> + </div> + <div class="TestWasm-Test2"> + <button class="TestWasm-button" tool-selector="Test 2">Test 2</button> + </div> + <div class="TestWasm-Test3"> + <button class="TestWasm-button" tool-selector="Test 3">Test 3</button> + </div> + <div class="TestWasm-Test4"> + <button class="TestWasm-button" tool-selector="Test 4">Test 4</button> + </div> + <div class="TestWasm-Test5"> + <button class="TestWasm-button" tool-selector="Test 5">Test 5</button> + </div> + <div class="TestWasm-Test6"> + <button class="TestWasm-button" tool-selector="Test 6">Test 6</button> + </div> + <div class="TestWasm-Test7"> + <button class="TestWasm-button" tool-selector="Test 7">Test 7</button> + </div> + <div class="TestWasm-Test8"> + <button class="TestWasm-button" tool-selector="Test 8">Test 8</button> + </div> + + <!-- <button class="TestWasm-button" class="TestWasm-Test1" tool-selector="Test 1">Test 1</button> + <button class="TestWasm-button" class="TestWasm-Test2" tool-selector="Test 2">Test 2</button> + <button class="TestWasm-button" class="TestWasm-Test3" tool-selector="Test 3">Test 3</button> + <button class="TestWasm-button" class="TestWasm-Test4" tool-selector="Test 4">Test 4</button> --> + + + + <!-- <div class="Trigger"></div> + <div class="Test1"></div> + <div class="Test2"></div> + <div class="Test3"></div> + <div class="Test4"></div> --> + </div> + + <!-- <div id="toolbox" style="height: 50px"> + <button tool-selector="line-measure" class="tool-selector">line</button> + </div> --> + <script type="text/javascript" src="testWasmIntegratedCpp.js"></script> + <script type="text/javascript" src="testWasmIntegratedApp.js"></script> +</body> + +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.ts Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,257 @@ +var SendMessageToCpp: Function = null; +export var TestWasmIntegratedModule : any; + +/* ++--------------------------------------------------+ +| install emscripten handlers | ++--------------------------------------------------+ +*/ + +// (<any> window).Module = { +// preRun: [ +// function() { +// console.log('Loading the Stone Framework using WebAssembly'); +// } +// ], +// postRun: [ +// function() { +// // This function is called by ".js" wrapper once the ".wasm" +// // WebAssembly module has been loaded and compiled by the +// // browser +// console.log('WebAssembly is ready'); +// // window.SendMessageToCpp = (<any> window).Module.cwrap('SendMessageToCpp', 'string', ['string']); +// // window.SendFreeTextToCpp = (<any> window).Module.cwrap('SendFreeTextToCpp', 'string', ['string']); +// } +// ], +// print: function(text : string) { +// console.log(text); +// }, +// printErr: function(text : string) { +// console.error(text); +// }, +// totalDependencies: 0 +// }; + +/* ++--------------------------------------------------+ +| install handlers | ++--------------------------------------------------+ +*/ +document.querySelectorAll(".TestWasm-button").forEach((e) => { + (e as HTMLButtonElement).addEventListener("click", () => { + ButtonClick(e.attributes["tool-selector"].value); + }); +}); + +/* ++--------------------------------------------------+ +| define stock messages | ++--------------------------------------------------+ +*/ +let schemaText: string = `rootName: testWasmIntegratedCpp + +struct B: + someAs: vector<A> + someInts: vector<int32> + +struct C: + someBs: vector<B> + ddd: vector<string> + definition: vector<json> + +struct A: + someStrings: vector<string> + someInts2: vector<int32> + movies: vector<MovieType> + +struct Message1: + a: int32 + b: string + c: EnumMonth0 + d: bool + +struct Message2: + toto: string + tata: vector<Message1> + tutu: vector<string> + titi: map<string, string> + lulu: map<string, Message1> + movieType: MovieType + definition: json + +enum MovieType: + - RomCom + - Horror + - ScienceFiction + - Vegetables + +enum CrispType: + - SaltAndPepper + - CreamAndChives + - Paprika + - Barbecue + +enum EnumMonth0: + - January + - February + - March +`; + +let stockSerializedMessages = new Map<string,string>(); +stockSerializedMessages["Test 1"] = `{ + "type" : "testWasmIntegratedCpp.Message2", + "value" : + { + "lulu" : + { + "54" : + { + "a" : 43, + "b" : "Sandrine", + "c" : "March", + "d" : true + }, + "55" : + { + "a" : 42, + "b" : "Benjamin", + "c" : "January", + "d" : false + } + }, + "tata" : + [ + { + "a" : 42, + "b" : "Benjamin", + "c" : "March", + "d" : false + }, + { + "a" : 43, + "b" : "Sandrine", + "c" : "January", + "d" : false + } + ], + "titi" : + { + "44" : "key 44", + "45" : "key 45" + }, + "toto" : "Prout zizi", + "tutu" : + [ + "Mercadet", + "Poisson" + ], + "definition": + { + "val" : [ "berk", 42 ], + "zozo" : + { + "23": "zloutch", + "lalala": 42 + } + } + } +}`; +stockSerializedMessages["Test 2"] = ` { + "type" : "testWasmIntegratedCpp.Message1", + "value" : { + "a" : -987, + "b" : "Salomé", + "c" : 2, + "d" : true + } +}`; +stockSerializedMessages["Test 3"] = "Test 3 stock message sdfsfsdfsdf"; +stockSerializedMessages["Test 4"] = "Test 4 stock message 355345345"; +stockSerializedMessages["Test 5"] = "Test 5 stock message 34535"; +stockSerializedMessages["Test 6"] = "Test 6 stock message xcvcxvx"; +stockSerializedMessages["Test 7"] = "Test 7 stock message fgwqewqdgg"; +stockSerializedMessages["Test 8"] = "Test 8 stock message fgfsdfsdgg"; + +/* ++--------------------------------------------------+ +| define handler | ++--------------------------------------------------+ +*/ + +function setSerializedInputValue(text: string) { + let e : HTMLTextAreaElement = document.getElementById('TestWasm-SerializedInput') as HTMLTextAreaElement; + e.value = text; +} + +function getSerializedInputValue(): string { + let e : HTMLTextAreaElement = document.getElementById('TestWasm-SerializedInput') as HTMLTextAreaElement; + return e.value; +} + +function setCppOutputValue(text: string) { + let e : HTMLTextAreaElement = document.getElementById('TestWasm-CppOutput') as HTMLTextAreaElement; + e.value = text; +} + +function getCppOutputValue(): string { + let e : HTMLTextAreaElement = document.getElementById('TestWasm-CppOutput') as HTMLTextAreaElement; + return e.value; +} + +function SendFreeTextFromCpp(txt: string):string +{ + setCppOutputValue(getCppOutputValue() + "\n" + txt); + return ""; +} +(<any> window).SendFreeTextFromCpp = SendFreeTextFromCpp; + + +function ButtonClick(buttonName: string) { + if (buttonName.startsWith('Test ')) { + setSerializedInputValue(stockSerializedMessages[buttonName]); + } + else if(buttonName == 'Trigger') + { + let serializedInputValue:string = getSerializedInputValue(); + + let SendMessageToCppLocal = (<any> window).Module.cwrap('SendMessageToCpp', 'string', ['string']); + SendMessageToCppLocal(serializedInputValue); + } + else if(buttonName == 'Clear') + { + setCppOutputValue(""); + } + else if(buttonName == 'ShowSchema') + { + setCppOutputValue(schemaText); + } + else + { + throw new Error("Internal error!"); + } +} + + + +// this method is called "from the C++ code" when the StoneApplication is updated. +// it can be used to update the UI of the application +function UpdateWebApplicationWithString(statusUpdateMessageString: string) { + console.log("updating web application (string): ", statusUpdateMessageString); + let statusUpdateMessage = JSON.parse(statusUpdateMessageString); + + if ("event" in statusUpdateMessage) + { + let eventName = statusUpdateMessage["event"]; + if (eventName == "appStatusUpdated") + { + //ui.onAppStatusUpdated(statusUpdateMessage["data"]); + } + } +} + + +function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { + console.log("updating web application (serialized message): ", statusUpdateMessageString); + console.log("<not supported!>"); +} + \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegratedCpp_api.yaml Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,58 @@ +# +# 1 2 3 4 5 6 7 8 +# 345678901234567890123456789012345678901234567890123456789012345678901234567890 +# +rootName: testWasmIntegratedCpp + +struct B: + someAs: vector<A> + someInts: vector<int32> + +struct C: + someBs: vector<B> + ddd: vector<string> + definition: vector<json> + +struct A: + someStrings: vector<string> + someInts2: vector<int32> + movies: vector<MovieType> + +struct Message1: + a: int32 + b: string + c: EnumMonth0 + d: bool + +struct Message2: + toto: string + tata: vector<Message1> + tutu: vector<string> + titi: map<string, string> + lulu: map<string, Message1> + movieType: MovieType + definition: json + +enum MovieType: + - RomCom + - Horror + - ScienceFiction + - Vegetables + +enum CrispType: + - SaltAndPepper + - CreamAndChives + - Barbecue + - Paprika + +enum EnumMonth0: + - January + - February + - March + +enum Tata: + - Lolo + - Rrrrrrrrrrrr + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/test_data/test1.jsonc Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,87 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 +*/ +{ + "root_name":"test1", + "types": [ + { + "name":"B", + "kind":"struct", + "fields": [ + { + "name":"someAs", + "type":"vector<A>" + }, + { + "name":"someInts", + "type":"vector<int32>" + } + ] + }, + { + "name":"C", + "kind":"struct", + "fields": [ + { + "name":"someBs", + "type":"vector<B>" + }, + { + "name":"ddd", + "type":"vector<D>" + } + ] + }, + { + "name":"A", + "kind":"struct", + "fields": [ + { + "name":"someStrings", + "type":"vector<string>" + }, + { + "name":"someInts2", + "type":"vector<int32>" + } + ] + }, + { + "name":"MovieType", + "kind":"enum", + "fields": [ + { + "name":"Romcom" + }, + { + "name":"Horror" + }, + { + "name":"ScienceFiction" + }, + { + "name":"Vegetables" + } + ] + }, + { + "name":"CrispType", + "kind":"enum", + "fields": [ + { + "name":"SaltAndPepper" + }, + { + "name":"CreamAndChives" + }, + { + "name":"Paprika" + }, + { + "name":"Barbecue" + } + ] + } + ] +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/test_data/test1.yaml Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,49 @@ +# +# 1 2 3 4 5 6 7 8 +# 345678901234567890123456789012345678901234567890123456789012345678901234567890 +# +rootName: VsolMessages + +struct B: + someAs: vector<A> + someInts: vector<int32> + +struct C: + someBs: vector<B> + ddd: vector<string> + +struct A: + someStrings: vector<string> + someInts2: vector<int32> + movies: vector<MovieType> + +struct Message1: + a: int32 + b: string + c: EnumMonth0 + d: bool + +struct Message2: + toto: string + tata: vector<Message1> + tutu: vector<string> + titi: map<string, string> + lulu: map<string, Message1> + movieType: MovieType + +enum MovieType: + - RomCom + - Horror + - ScienceFiction + - Vegetables + +enum CrispType: + - SaltAndPepper + - CreamAndChives + - Paprika + - Barbecue + +enum EnumMonth0: + - January + - February + - March
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/test_data/test1_bogus_json.jsonc Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,55 @@ +{ + "root_name":"test1", + "types": [ + { + "name":"B", + "kind":"struct", + "fields": [ + { + "name":"someAs", + "type":"vector<A>" + }}, + { + "name":"someInts", + "type":"vector<int32>" + } + ] + }, + { + "name":"A", + "kind":"struct", + "fields": [ + { + "name":"someStrings", + "type":"vector<string>" + }, + { + "name":"someInts2", + "type":"vector<int32>" + } + ] + }, + { + "name":"MovieType", + "kind":"enum", + "fields": [ + { + "name":"Romcom", + }, + { + "name":"Horror", + }, + { + "name":"ScienceFiction", + }, + { + "name":"Vegetables", + } + } + ] +} + +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 +*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/test_data/test1_bogus_schema.jsonc Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,56 @@ +{ + "root_name":"test1", + "types": [ + { + "name":"B", + "kind":"struct", + "fields": [ + { + "name":"someAs", + "type":"vector<A>" + }, + { + "name":"someInts", + "type":"vector<int32>" + } + ] + }, + { + "name":"A", + "kiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiind":"struct", + "fields": [ + { + "name":"someStrings", + "type":"vector<string>" + }, + { + "name":"someInts2", + "type":"vector<int32>" + } + ] + }, + { + "name":"MovieType", + "kind":"enum", + "fields": [ + { + "naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaame":"Romcom" + }, + { + "name":"Horror" + }, + { + "name":"ScienceFiction" + }, + { + "name":"Vegetables" + } + ] + } + ] +} + +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 +*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/test_data/test2.yaml Tue Mar 12 14:50:14 2019 +0100 @@ -0,0 +1,17 @@ +enum EnumMonth0: + - January + - February + - Month + +struct Message1: + a: int32 + b: string + c: EnumMonth0 + d: bool + +struct Message2: + toto: string + tata: vector<Message1> + tutu: vector<string> + titi: map<string, string> + lulu: map<string, Message1>