Mercurial > hg > piper-cpp
changeset 150:bf8e3e7dd7de
Move some things around, and add overall test script
author | Chris Cannam <cannam@all-day-breakfast.com> |
---|---|
date | Fri, 20 Jan 2017 17:45:54 +0000 |
parents | 70bf40743d6a |
children | 255403a4b321 |
files | .gitignore .travis.yml Makefile base-n/LICENSE base-n/README.md base-n/example/basen_example.cpp base-n/include/basen.hpp ext/base-n/LICENSE ext/base-n/README.md ext/base-n/example/basen_example.cpp ext/base-n/include/basen.hpp ext/json11/CMakeLists.txt ext/json11/LICENSE.txt ext/json11/Makefile ext/json11/README.md ext/json11/json11.cpp ext/json11/json11.hpp ext/json11/test.cpp json11/CMakeLists.txt json11/LICENSE.txt json11/Makefile json11/README.md json11/json11.cpp json11/json11.hpp json11/test.cpp test.sh test/test-server.sh vamp-client/AutoPlugin.h vamp-client/ProcessQtTransport.h vamp-client/client.cpp vamp-client/client.pro vamp-client/qt/AutoPlugin.h vamp-client/qt/ProcessQtTransport.h vamp-client/qt/test.cpp vamp-client/qt/test.pro vamp-server/test.sh vamp-support/LoaderRequests.h |
diffstat | 37 files changed, 2595 insertions(+), 2519 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.gitignore Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,8 @@ +*.o +*.a +o/ +.qmake.stash +*~ +vamp-client/qt/Makefile +vamp-client/qt/test +bin/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.travis.yml Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,32 @@ +dist: + - trusty + +language: + - cpp + +sudo: + - false + +os: + - linux + - osx + +addons: + apt: + packages: + - qt5-default + +before_install: + - ( cd ../ ; hg clone https://code.soundsoftware.ac.uk/hg/vamp-plugin-sdk ) + - ( cd ../vamp-plugin-sdk ; ./configure && make sdkstatic ) + - ( cd ../ ; git clone https://github.com/sandstorm-io/capnproto ) + - ( cd ../capnproto/c++ ; ./setup-autotools.sh && autoreconf -i ) + - ( cd ../capnproto/c++ ; ./configure && make && sudo make install ) + - if [[ "$TRAVIS_OS_NAME" = "osx" ]] ; then brew install qt5 ; fi + +install: + - sudo pip install jsonschema + +script: + - ./test.sh +
--- a/Makefile Thu Jan 19 14:05:21 2017 +0000 +++ b/Makefile Fri Jan 20 17:45:54 2017 +0000 @@ -3,7 +3,7 @@ PIPER_DIR := ../piper CXXFLAGS := -Wall -Wextra -Werror -g3 -std=c++11 -INCFLAGS := -I$(VAMPSDK_DIR) -I. -I/usr/local/include +INCFLAGS := -Iext -I$(VAMPSDK_DIR) -I. -I/usr/local/include #LDFLAGS := -L$(VAMPSDK_DIR) -L/usr/local/lib -lvamp-hostsdk -lcapnp -lkj LDFLAGS := $(VAMPSDK_DIR)/libvamp-hostsdk.a -lcapnp -lkj @@ -24,13 +24,10 @@ bin/piper-vamp-simple-server: o/simple-server.o o/json11.o o/piper.capnp.o c++ $(CXXFLAGS) $^ -o $@ $(LDFLAGS) -#vamp-capnp/piper.capnp.h: $(PIPER_DIR)/capnp/piper.capnp -# capnp compile -oc++:vamp-capnp --src-prefix=$(PIPER_DIR)/capnp $< - o/piper.capnp.o: vamp-capnp/piper.capnp.c++ vamp-capnp/piper.capnp.h c++ $(CXXFLAGS) $(INCFLAGS) -c $< -o $@ -o/json11.o: json11/json11.cpp +o/json11.o: ext/json11/json11.cpp c++ $(CXXFLAGS) -c $< -o $@ o/convert.o: vamp-server/convert.cpp vamp-capnp/piper.capnp.h vamp-capnp/VampnProto.h vamp-json/VampJson.h @@ -40,7 +37,7 @@ c++ $(CXXFLAGS) $(INCFLAGS) -c $< -o $@ test: all - test/test-server.sh + vamp-server/test.sh clean: rm -f */*.o
--- a/base-n/LICENSE Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com) - -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.
--- a/base-n/README.md Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -base-n provides encoding/decoding support for BaseX encoding schemes accessible through a standard STL-like iterator interface. Standard Base16, Base32, and Base64 are available out-of-the-box. Adding or modifying custom schemes is supported. - -# Usage overview # - -base-n is a small, single-header library which provides standard Base16, Base32, Base64, and custom Base-N encoding support. - -The main functionality is delivered by the following functions in `bn` namespace: -``` -template<class Iter1, class Iter2> -void encode_b16(Iter1 start, Iter1 end, Iter2 out); - - -template<class Iter1, class Iter2> -void encode_b32(Iter1 start, Iter1 end, Iter2 out); - - -template<class Iter1, class Iter2> -void encode_b64(Iter1 start, Iter1 end, Iter2 out); - - -template<class Iter1, class Iter2> -void decode_b16(Iter1 start, Iter1 end, Iter2 out); - - -template<class Iter1, class Iter2> -void decode_b32(Iter1 start, Iter1 end, Iter2 out); - - -template<class Iter1, class Iter2> -void decode_b64(Iter1 start, Iter1 end, Iter2 out); -``` - -In order to encode and decode data in `std::string` variable `in`, you can do the following: -``` -bn::encode_b64(in.begin(), in.end(), back_inserter(encoded)); -bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); -``` - -Should you find yourself lacking some encoding scheme or the default character mapping rules are not good for your use case, you can easily provide your own encoder. For that, you need to define a struct type which will describe the new encoding. Sample below: -``` -struct b8_custom -{ - static size_t group_length() - { - return 3; - } - - static char encode(int index) - { - const char* const dictionary = "01234567"; - assert(index < strlen(dictionary)); - return dictionary[index]; - } - - static char decode(char c) - { - if (c >= '0' && c <= '7') { - return c - '0'; - } - return -1; - } -}; -... -string encoded; -bn::impl::encode<b8_custom>(in.begin(), in.end(), back_inserter(encoded)); -bn::impl::decode<b8_custom>(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); -``` - -For a full working example, see `basen_example.cpp` file in `example` directory.
--- a/base-n/example/basen_example.cpp Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -/** - * base-n, 1.0 - * Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com) - * - * 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. -**/ -#include <cassert> -#include <cstring> -#include <iostream> -#include <iterator> -#include <string> - -#include "basen.hpp" - -int main() -{ - using namespace std; - string in = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."; - cout << in << endl; - - { - string encoded; - bn::encode_b64(in.begin(), in.end(), back_inserter(encoded)); - bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); - cout << endl; - } - { - string encoded; - bn::encode_b32(in.begin(), in.end(), back_inserter(encoded)); - bn::decode_b32(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); - cout << endl; - } - { - string encoded; - bn::encode_b16(in.begin(), in.end(), back_inserter(encoded)); - bn::decode_b16(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); - cout << endl; - } - { - string encoded = "#TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\n" - "IHNpbmd1bGFyIHBhc3Npb24gZnJvbS@BvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\n" - " dGhlIG1(pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\n" - "\rdWVkIGFuZCBpbmRlZmF0aWdhY*mxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\n" - "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4"; - bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); - cout << endl; - } - { - // move the struct definition outside of main() for non-C++11 compilers - struct b8_custom - { - static size_t group_length() - { - return 3; - } - - static char encode(unsigned int index) - { - const char* const dictionary = "01234567"; - assert(index < strlen(dictionary)); - return dictionary[index]; - } - - static char decode(char c) - { - if (c >= '0' && c <= '7') { - return c - '0'; - } - return -1; - } - }; - string encoded; - bn::impl::encode<b8_custom>(in.begin(), in.end(), back_inserter(encoded)); - bn::impl::decode<b8_custom>(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); - cout << endl; - } -} -
--- a/base-n/include/basen.hpp Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,290 +0,0 @@ -/** - * base-n, 1.0 - * Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com) - * - * 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. -**/ -#ifndef BASEN_HPP -#define BASEN_HPP - -#include <algorithm> -#include <cctype> -#include <cassert> -#include <cstring> - -namespace bn -{ - -template<class Iter1, class Iter2> -void encode_b16(Iter1 start, Iter1 end, Iter2 out); - -template<class Iter1, class Iter2> -void encode_b32(Iter1 start, Iter1 end, Iter2 out); - -template<class Iter1, class Iter2> -void encode_b64(Iter1 start, Iter1 end, Iter2 out); - -template<class Iter1, class Iter2> -void decode_b16(Iter1 start, Iter1 end, Iter2 out); - -template<class Iter1, class Iter2> -void decode_b32(Iter1 start, Iter1 end, Iter2 out); - -template<class Iter1, class Iter2> -void decode_b64(Iter1 start, Iter1 end, Iter2 out); - -namespace impl -{ - -const int ERROR = -1; - -namespace { - -char extract_partial_bits(char value, unsigned int start_bit, unsigned int bits_count) -{ - assert(start_bit + bits_count < 8); - // shift extracted bits to the beginning of the byte - char t1 = value >> (8 - bits_count - start_bit); - // mask out bits on the left - char t2 = t1 & ~(-1U << bits_count); - return t2; -} - -char extract_overlapping_bits(char previous, char next, unsigned int start_bit, unsigned int bits_count) -{ - assert(start_bit + bits_count < 16); - int bits_count_in_previous = 8 - start_bit; - int bits_count_in_next = bits_count - bits_count_in_previous; - char t1 = previous << bits_count_in_next; - char t2 = next >> (8 - bits_count_in_next) & ~(-1U << bits_count_in_next) ; - return (t1 | t2) & ~(-1U << bits_count); -} - -} - -struct b16_conversion_traits -{ - static size_t group_length() - { - return 4; - } - - static char encode(unsigned int index) - { - const char* const dictionary = "0123456789ABCDEF"; - assert(index < strlen(dictionary)); - return dictionary[index]; - } - - static char decode(char c) - { - if (c >= '0' && c <= '9') { - return c - '0'; - } else if (c >= 'A' && c <= 'F') { - return c - 'A' + 10; - } - return ERROR; - } -}; - -struct b32_conversion_traits -{ - static size_t group_length() - { - return 5; - } - - static char encode(unsigned int index) - { - const char * dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - assert(index < strlen(dictionary)); - return dictionary[index]; - } - - static char decode(char c) - { - if (c >= 'A' && c <= 'Z') { - return c - 'A'; - } else if (c >= '2' && c <= '7') { - return c - '2' + 26; - } - return ERROR; - } -}; - -struct b64_conversion_traits -{ - static size_t group_length() - { - return 6; - } - - static char encode(unsigned int index) - { - const char* const dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - assert(index < strlen(dictionary)); - return dictionary[index]; - } - - static char decode(char c) - { - const int alph_len = 26; - if (c >= 'A' && c <= 'Z') { - return c - 'A'; - } else if (c >= 'a' && c <= 'z') { - return c - 'a' + alph_len * 1; - } else if (c >= '0' && c <= '9') { - return c - '0' + alph_len * 2; - } else if (c == '+') { - return c - '+' + alph_len * 2 + 10; - } else if (c == '/') { - return c - '/' + alph_len * 2 + 11; - } - return ERROR; - } -}; - -template<class ConversionTraits, class Iter1, class Iter2> -void decode(Iter1 start, Iter1 end, Iter2 out) -{ - Iter1 iter = start; - int output_current_bit = 0; - char buffer = 0; - - while (iter != end) { - if (std::isspace(*iter)) { - ++iter; - continue; - } - char value = ConversionTraits::decode(*iter); - if (value == ERROR) { - // malformed data, but let's go on... - ++iter; - continue; - } - unsigned int bits_in_current_byte = std::min<int>(output_current_bit + ConversionTraits::group_length(), 8) - output_current_bit; - if (bits_in_current_byte == ConversionTraits::group_length()) { - // the value fits within current byte, so we can extract it directly - buffer |= value << (8 - output_current_bit - ConversionTraits::group_length()); - output_current_bit += ConversionTraits::group_length(); - // check if we filled up current byte completely; in such case we flush output and continue - if (output_current_bit == 8) { - *out++ = buffer; - buffer = 0; - output_current_bit = 0; - } - } else { - // the value spans across the current and the next byte - int bits_in_next_byte = ConversionTraits::group_length() - bits_in_current_byte; - // fill the current byte and flush it to our output - buffer |= value >> bits_in_next_byte; - *out++ = buffer; - buffer = 0; - // save the remainder of our value in the buffer; it will be flushed - // during next iterations - buffer |= value << (8 - bits_in_next_byte); - output_current_bit = bits_in_next_byte; - } - ++iter; - } -} - -template<class ConversionTraits, class Iter1, class Iter2> -void encode(Iter1 start, Iter1 end, Iter2 out) -{ - Iter1 iter = start; - int start_bit = 0; - bool has_backlog = false; - char backlog = 0; - - while (has_backlog || iter != end) { - if (!has_backlog) { - if (start_bit + ConversionTraits::group_length() < 8) { - // the value fits within single byte, so we can extract it - // directly - char v = extract_partial_bits(*iter, start_bit, ConversionTraits::group_length()); - *out++ = ConversionTraits::encode(v); - // since we know that start_bit + ConversionTraits::group_length() < 8 we don't need to go - // to the next byte - start_bit += ConversionTraits::group_length(); - } else { - // our bits are spanning across byte border; we need to keep the - // starting point and move over to next byte. - backlog = *iter++; - has_backlog = true; - } - } else { - // encode value which is made from bits spanning across byte - // boundary - char v; - if (iter == end) - v = extract_overlapping_bits(backlog, 0, start_bit, ConversionTraits::group_length()); - else - v = extract_overlapping_bits(backlog, *iter, start_bit, ConversionTraits::group_length()); - *out++ = ConversionTraits::encode(v); - has_backlog = false; - start_bit = (start_bit + ConversionTraits::group_length()) % 8; - } - } -} - -} // impl - -using namespace bn::impl; - -template<class Iter1, class Iter2> -void encode_b16(Iter1 start, Iter1 end, Iter2 out) -{ - encode<b16_conversion_traits>(start, end, out); -} - -template<class Iter1, class Iter2> -void encode_b32(Iter1 start, Iter1 end, Iter2 out) -{ - encode<b32_conversion_traits>(start, end, out); -} - -template<class Iter1, class Iter2> -void encode_b64(Iter1 start, Iter1 end, Iter2 out) -{ - encode<b64_conversion_traits>(start, end, out); -} - -template<class Iter1, class Iter2> -void decode_b16(Iter1 start, Iter1 end, Iter2 out) -{ - decode<b16_conversion_traits>(start, end, out); -} - -template<class Iter1, class Iter2> -void decode_b32(Iter1 start, Iter1 end, Iter2 out) -{ - decode<b32_conversion_traits>(start, end, out); -} - -template<class Iter1, class Iter2> -void decode_b64(Iter1 start, Iter1 end, Iter2 out) -{ - decode<b64_conversion_traits>(start, end, out); -} - -} // bn - -#endif // BASEN_HPP -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/base-n/LICENSE Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,19 @@ +Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com) + +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.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/base-n/README.md Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,69 @@ +base-n provides encoding/decoding support for BaseX encoding schemes accessible through a standard STL-like iterator interface. Standard Base16, Base32, and Base64 are available out-of-the-box. Adding or modifying custom schemes is supported. + +# Usage overview # + +base-n is a small, single-header library which provides standard Base16, Base32, Base64, and custom Base-N encoding support. + +The main functionality is delivered by the following functions in `bn` namespace: +``` +template<class Iter1, class Iter2> +void encode_b16(Iter1 start, Iter1 end, Iter2 out); + + +template<class Iter1, class Iter2> +void encode_b32(Iter1 start, Iter1 end, Iter2 out); + + +template<class Iter1, class Iter2> +void encode_b64(Iter1 start, Iter1 end, Iter2 out); + + +template<class Iter1, class Iter2> +void decode_b16(Iter1 start, Iter1 end, Iter2 out); + + +template<class Iter1, class Iter2> +void decode_b32(Iter1 start, Iter1 end, Iter2 out); + + +template<class Iter1, class Iter2> +void decode_b64(Iter1 start, Iter1 end, Iter2 out); +``` + +In order to encode and decode data in `std::string` variable `in`, you can do the following: +``` +bn::encode_b64(in.begin(), in.end(), back_inserter(encoded)); +bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); +``` + +Should you find yourself lacking some encoding scheme or the default character mapping rules are not good for your use case, you can easily provide your own encoder. For that, you need to define a struct type which will describe the new encoding. Sample below: +``` +struct b8_custom +{ + static size_t group_length() + { + return 3; + } + + static char encode(int index) + { + const char* const dictionary = "01234567"; + assert(index < strlen(dictionary)); + return dictionary[index]; + } + + static char decode(char c) + { + if (c >= '0' && c <= '7') { + return c - '0'; + } + return -1; + } +}; +... +string encoded; +bn::impl::encode<b8_custom>(in.begin(), in.end(), back_inserter(encoded)); +bn::impl::decode<b8_custom>(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); +``` + +For a full working example, see `basen_example.cpp` file in `example` directory.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/base-n/example/basen_example.cpp Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,94 @@ +/** + * base-n, 1.0 + * Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com) + * + * 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. +**/ +#include <cassert> +#include <cstring> +#include <iostream> +#include <iterator> +#include <string> + +#include "basen.hpp" + +int main() +{ + using namespace std; + string in = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."; + cout << in << endl; + + { + string encoded; + bn::encode_b64(in.begin(), in.end(), back_inserter(encoded)); + bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); + cout << endl; + } + { + string encoded; + bn::encode_b32(in.begin(), in.end(), back_inserter(encoded)); + bn::decode_b32(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); + cout << endl; + } + { + string encoded; + bn::encode_b16(in.begin(), in.end(), back_inserter(encoded)); + bn::decode_b16(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); + cout << endl; + } + { + string encoded = "#TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\n" + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbS@BvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\n" + " dGhlIG1(pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\n" + "\rdWVkIGFuZCBpbmRlZmF0aWdhY*mxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\n" + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4"; + bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); + cout << endl; + } + { + // move the struct definition outside of main() for non-C++11 compilers + struct b8_custom + { + static size_t group_length() + { + return 3; + } + + static char encode(unsigned int index) + { + const char* const dictionary = "01234567"; + assert(index < strlen(dictionary)); + return dictionary[index]; + } + + static char decode(char c) + { + if (c >= '0' && c <= '7') { + return c - '0'; + } + return -1; + } + }; + string encoded; + bn::impl::encode<b8_custom>(in.begin(), in.end(), back_inserter(encoded)); + bn::impl::decode<b8_custom>(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, "")); + cout << endl; + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/base-n/include/basen.hpp Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,290 @@ +/** + * base-n, 1.0 + * Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com) + * + * 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. +**/ +#ifndef BASEN_HPP +#define BASEN_HPP + +#include <algorithm> +#include <cctype> +#include <cassert> +#include <cstring> + +namespace bn +{ + +template<class Iter1, class Iter2> +void encode_b16(Iter1 start, Iter1 end, Iter2 out); + +template<class Iter1, class Iter2> +void encode_b32(Iter1 start, Iter1 end, Iter2 out); + +template<class Iter1, class Iter2> +void encode_b64(Iter1 start, Iter1 end, Iter2 out); + +template<class Iter1, class Iter2> +void decode_b16(Iter1 start, Iter1 end, Iter2 out); + +template<class Iter1, class Iter2> +void decode_b32(Iter1 start, Iter1 end, Iter2 out); + +template<class Iter1, class Iter2> +void decode_b64(Iter1 start, Iter1 end, Iter2 out); + +namespace impl +{ + +const int ERROR = -1; + +namespace { + +char extract_partial_bits(char value, unsigned int start_bit, unsigned int bits_count) +{ + assert(start_bit + bits_count < 8); + // shift extracted bits to the beginning of the byte + char t1 = value >> (8 - bits_count - start_bit); + // mask out bits on the left + char t2 = t1 & ~(-1U << bits_count); + return t2; +} + +char extract_overlapping_bits(char previous, char next, unsigned int start_bit, unsigned int bits_count) +{ + assert(start_bit + bits_count < 16); + int bits_count_in_previous = 8 - start_bit; + int bits_count_in_next = bits_count - bits_count_in_previous; + char t1 = previous << bits_count_in_next; + char t2 = next >> (8 - bits_count_in_next) & ~(-1U << bits_count_in_next) ; + return (t1 | t2) & ~(-1U << bits_count); +} + +} + +struct b16_conversion_traits +{ + static size_t group_length() + { + return 4; + } + + static char encode(unsigned int index) + { + const char* const dictionary = "0123456789ABCDEF"; + assert(index < strlen(dictionary)); + return dictionary[index]; + } + + static char decode(char c) + { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return ERROR; + } +}; + +struct b32_conversion_traits +{ + static size_t group_length() + { + return 5; + } + + static char encode(unsigned int index) + { + const char * dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + assert(index < strlen(dictionary)); + return dictionary[index]; + } + + static char decode(char c) + { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= '2' && c <= '7') { + return c - '2' + 26; + } + return ERROR; + } +}; + +struct b64_conversion_traits +{ + static size_t group_length() + { + return 6; + } + + static char encode(unsigned int index) + { + const char* const dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + assert(index < strlen(dictionary)); + return dictionary[index]; + } + + static char decode(char c) + { + const int alph_len = 26; + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return c - 'a' + alph_len * 1; + } else if (c >= '0' && c <= '9') { + return c - '0' + alph_len * 2; + } else if (c == '+') { + return c - '+' + alph_len * 2 + 10; + } else if (c == '/') { + return c - '/' + alph_len * 2 + 11; + } + return ERROR; + } +}; + +template<class ConversionTraits, class Iter1, class Iter2> +void decode(Iter1 start, Iter1 end, Iter2 out) +{ + Iter1 iter = start; + int output_current_bit = 0; + char buffer = 0; + + while (iter != end) { + if (std::isspace(*iter)) { + ++iter; + continue; + } + char value = ConversionTraits::decode(*iter); + if (value == ERROR) { + // malformed data, but let's go on... + ++iter; + continue; + } + unsigned int bits_in_current_byte = std::min<int>(output_current_bit + ConversionTraits::group_length(), 8) - output_current_bit; + if (bits_in_current_byte == ConversionTraits::group_length()) { + // the value fits within current byte, so we can extract it directly + buffer |= value << (8 - output_current_bit - ConversionTraits::group_length()); + output_current_bit += ConversionTraits::group_length(); + // check if we filled up current byte completely; in such case we flush output and continue + if (output_current_bit == 8) { + *out++ = buffer; + buffer = 0; + output_current_bit = 0; + } + } else { + // the value spans across the current and the next byte + int bits_in_next_byte = ConversionTraits::group_length() - bits_in_current_byte; + // fill the current byte and flush it to our output + buffer |= value >> bits_in_next_byte; + *out++ = buffer; + buffer = 0; + // save the remainder of our value in the buffer; it will be flushed + // during next iterations + buffer |= value << (8 - bits_in_next_byte); + output_current_bit = bits_in_next_byte; + } + ++iter; + } +} + +template<class ConversionTraits, class Iter1, class Iter2> +void encode(Iter1 start, Iter1 end, Iter2 out) +{ + Iter1 iter = start; + int start_bit = 0; + bool has_backlog = false; + char backlog = 0; + + while (has_backlog || iter != end) { + if (!has_backlog) { + if (start_bit + ConversionTraits::group_length() < 8) { + // the value fits within single byte, so we can extract it + // directly + char v = extract_partial_bits(*iter, start_bit, ConversionTraits::group_length()); + *out++ = ConversionTraits::encode(v); + // since we know that start_bit + ConversionTraits::group_length() < 8 we don't need to go + // to the next byte + start_bit += ConversionTraits::group_length(); + } else { + // our bits are spanning across byte border; we need to keep the + // starting point and move over to next byte. + backlog = *iter++; + has_backlog = true; + } + } else { + // encode value which is made from bits spanning across byte + // boundary + char v; + if (iter == end) + v = extract_overlapping_bits(backlog, 0, start_bit, ConversionTraits::group_length()); + else + v = extract_overlapping_bits(backlog, *iter, start_bit, ConversionTraits::group_length()); + *out++ = ConversionTraits::encode(v); + has_backlog = false; + start_bit = (start_bit + ConversionTraits::group_length()) % 8; + } + } +} + +} // impl + +using namespace bn::impl; + +template<class Iter1, class Iter2> +void encode_b16(Iter1 start, Iter1 end, Iter2 out) +{ + encode<b16_conversion_traits>(start, end, out); +} + +template<class Iter1, class Iter2> +void encode_b32(Iter1 start, Iter1 end, Iter2 out) +{ + encode<b32_conversion_traits>(start, end, out); +} + +template<class Iter1, class Iter2> +void encode_b64(Iter1 start, Iter1 end, Iter2 out) +{ + encode<b64_conversion_traits>(start, end, out); +} + +template<class Iter1, class Iter2> +void decode_b16(Iter1 start, Iter1 end, Iter2 out) +{ + decode<b16_conversion_traits>(start, end, out); +} + +template<class Iter1, class Iter2> +void decode_b32(Iter1 start, Iter1 end, Iter2 out) +{ + decode<b32_conversion_traits>(start, end, out); +} + +template<class Iter1, class Iter2> +void decode_b64(Iter1 start, Iter1 end, Iter2 out) +{ + decode<b64_conversion_traits>(start, end, out); +} + +} // bn + +#endif // BASEN_HPP +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/json11/CMakeLists.txt Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,21 @@ +project(json11) + +cmake_minimum_required(VERSION 2.8) + +enable_testing() + +add_definitions( + -std=c++11 + -fno-rtti + -fno-exceptions + -Wall + -Wextra + -Werror) + +set(json11_SRCS json11.cpp) + +add_library(json11 STATIC ${json11_SRCS}) + +add_test(json11_test json11_test) + +add_executable(json11_test ${json11_SRCS} test.cpp)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/json11/LICENSE.txt Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,19 @@ +Copyright (c) 2013 Dropbox, Inc. + +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.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/json11/Makefile Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,7 @@ +test: json11.cpp json11.hpp test.cpp + $(CXX) -O -std=c++11 json11.cpp test.cpp -o test -fno-rtti -fno-exceptions + +clean: + if [ -e test ]; then rm test; fi + +.PHONY: clean
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/json11/README.md Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,42 @@ +json11 +------ + +json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + +The core object provided by the library is json11::Json. A Json object represents any JSON +value: null, bool, number (int or double), string (std::string), array (std::vector), or +object (std::map). + +Json objects act like values. They can be assigned, copied, moved, compared for equality or +order, and so on. There are also helper methods Json::dump, to serialize a Json to a string, and +Json::parse (static) to parse a std::string as a Json object. + +It's easy to make a JSON object with C++11's new initializer syntax: + + Json my_json = Json::object { + { "key1", "value1" }, + { "key2", false }, + { "key3", Json::array { 1, 2, 3 } }, + }; + std::string json_str = my_json.dump(); + +There are also implicit constructors that allow standard and user-defined types to be +automatically converted to JSON. For example: + + class Point { + public: + int x; + int y; + Point (int x, int y) : x(x), y(y) {} + Json to_json() const { return Json::array { x, y }; } + }; + + std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } }; + std::string points_json = Json(points).dump(); + +JSON values can have their values queried and inspected: + + Json json = Json::array { Json::object { { "k", "v" } } }; + std::string str = json[0]["k"].string_value(); + +More documentation is still to come. For now, see json11.hpp.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/json11/json11.cpp Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,769 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * 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. + */ + +#include "json11.hpp" +#include <cassert> +#include <cmath> +#include <cstdlib> +#include <cstdio> +#include <limits> + +namespace json11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(std::nullptr_t, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast<uint8_t>(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 + && static_cast<uint8_t>(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 + && static_cast<uint8_t>(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template <Json::Type tag, typename T> +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast<const Value<tag, T> *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast<const Value<tag, T> *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { json11::dump(m_value, out); } +}; + +class JsonDouble final : public Value<Json::NUMBER, double> { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast<int>(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value<Json::NUMBER, int> { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value<Json::BOOL, bool> { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value<Json::STRING, string> { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(move(value)) {} +}; + +class JsonArray final : public Value<Json::ARRAY, Json::array> { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(move(value)) {} +}; + +class JsonObject final : public Value<Json::OBJECT, Json::object> { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(move(value)) {} +}; + +class JsonNull final : public Value<Json::NUL, std::nullptr_t> { +public: + JsonNull() : Value(nullptr) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr<JsonValue> null = make_shared<JsonNull>(); + const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true); + const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false); + const string empty_string; + const vector<Json> empty_vector; + const map<string, Json> empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {} +Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {} +Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector<Json> & Json::array_items() const { return m_ptr->array_items(); } +const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; } +const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(move(msg), Json()); + } + + template <typename T> + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input inside comment", 0); + if (str[i] == '/') { // inline comment + i++; + if (i == str.size()) + return fail("unexpected end of input inside inline comment", 0); + // advance until next line + while (str[i] != '\n') { + i++; + if (i == str.size()) + return fail("unexpected end of input inside inline comment", 0); + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", 0); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", 0); + } + i += 2; + if (i == str.size()) + return fail( + "unexpected end of input inside multi-line comment", 0); + comment_found = true; + } + else + return fail("malformed comment", 0); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (i == str.size()) + return fail("unexpected end of input", 0); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast<char>(pt); + } else if (pt < 0x800) { + out += static_cast<char>((pt >> 6) | 0xC0); + out += static_cast<char>((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast<char>((pt >> 12) | 0xE0); + out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); + out += static_cast<char>((pt & 0x3F) | 0x80); + } else { + out += static_cast<char>((pt >> 18) | 0xF0); + out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80); + out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); + out += static_cast<char>((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (int j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map<string, Json> data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector<Json> data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in json11.hpp +vector<Json> Json::parse_multi(const string &in, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + + vector<Json> json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + // Check for another object + parser.consume_garbage(); + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + for (auto & item : types) { + if ((*this)[item.first].type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace json11
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/json11/json11.hpp Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,211 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * 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. + */ + +#pragma once + +#include <string> +#include <vector> +#include <map> +#include <memory> +#include <initializer_list> + +namespace json11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector<Json> array; + typedef std::map<std::string, Json> object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template <class T, class = decltype(&T::to_json)> + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template <class M, typename std::enable_if< + std::is_constructible<std::string, typename M::key_type>::value + && std::is_constructible<Json, typename M::mapped_type>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template <class V, typename std::enable_if< + std::is_constructible<Json, typename V::value_type>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector<Json> parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list<std::pair<std::string, Type>> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr<JsonValue> m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace json11
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/json11/test.cpp Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,205 @@ +#include <string> +#include <cstdio> +#include <cstring> +#include <iostream> +#include <sstream> +#include "json11.hpp" +#include <cassert> +#include <list> +#include <set> +#include <unordered_map> + +using namespace json11; +using std::string; + +// Check that Json has the properties we want. +#include <type_traits> +#define CHECK_TRAIT(x) static_assert(std::x::value, #x) +CHECK_TRAIT(is_nothrow_constructible<Json>); +CHECK_TRAIT(is_nothrow_default_constructible<Json>); +CHECK_TRAIT(is_copy_constructible<Json>); +CHECK_TRAIT(is_nothrow_move_constructible<Json>); +CHECK_TRAIT(is_copy_assignable<Json>); +CHECK_TRAIT(is_nothrow_move_assignable<Json>); +CHECK_TRAIT(is_nothrow_destructible<Json>); + +void parse_from_stdin() { + string buf; + string line; + while (std::getline(std::cin, line)) { + buf += line + "\n"; + } + + string err; + auto json = Json::parse(buf, err); + if (!err.empty()) { + printf("Failed: %s\n", err.c_str()); + } else { + printf("Result: %s\n", json.dump().c_str()); + } +} + +int main(int argc, char **argv) { + if (argc == 2 && argv[1] == string("--stdin")) { + parse_from_stdin(); + return 0; + } + + const string simple_test = + R"({"k1":"v1", "k2":42, "k3":["a",123,true,false,null]})"; + + string err; + auto json = Json::parse(simple_test, err); + + std::cout << "k1: " << json["k1"].string_value() << "\n"; + std::cout << "k3: " << json["k3"].dump() << "\n"; + + for (auto &k : json["k3"].array_items()) { + std::cout << " - " << k.dump() << "\n"; + } + + const string comment_test = R"({ + // comment /* with nested comment */ + "a": 1, + // comment + // continued + "b": "text", + /* multi + line + comment */ + // and single-line comment + "c": [1, 2, 3] + })"; + + string err_comment; + auto json_comment = Json::parse( + comment_test, err_comment, JsonParse::COMMENTS); + if (!err_comment.empty()) { + printf("Failed: %s\n", err_comment.c_str()); + } else { + printf("Result: %s\n", json_comment.dump().c_str()); + } + + string failing_comment_test = R"({ + /* bad comment + "a": 1, + })"; + + string err_failing_comment; + auto json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + failing_comment_test = R"({ + / / bad comment })"; + + json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + failing_comment_test = R"({// bad comment })"; + + json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + failing_comment_test = R"({ + "a": 1 + }/)"; + + json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + failing_comment_test = R"({/* bad + comment *})"; + + json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + std::list<int> l1 { 1, 2, 3 }; + std::vector<int> l2 { 1, 2, 3 }; + std::set<int> l3 { 1, 2, 3 }; + assert(Json(l1) == Json(l2)); + assert(Json(l2) == Json(l3)); + + std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } }; + std::unordered_map<string, string> m2 { { "k1", "v1" }, { "k2", "v2" } }; + assert(Json(m1) == Json(m2)); + + // Json literals + Json obj = Json::object({ + { "k1", "v1" }, + { "k2", 42.0 }, + { "k3", Json::array({ "a", 123.0, true, false, nullptr }) }, + }); + + std::cout << "obj: " << obj.dump() << "\n"; + + assert(Json("a").number_value() == 0); + assert(Json("a").string_value() == "a"); + assert(Json().number_value() == 0); + + assert(obj == json); + assert(Json(42) == Json(42.0)); + assert(Json(42) != Json(42.1)); + + const string unicode_escape_test = + R"([ "blah\ud83d\udca9blah\ud83dblah\udca9blah\u0000blah\u1234" ])"; + + const char utf8[] = "blah" "\xf0\x9f\x92\xa9" "blah" "\xed\xa0\xbd" "blah" + "\xed\xb2\xa9" "blah" "\0" "blah" "\xe1\x88\xb4"; + + Json uni = Json::parse(unicode_escape_test, err); + assert(uni[0].string_value().size() == (sizeof utf8) - 1); + assert(std::memcmp(uni[0].string_value().data(), utf8, sizeof utf8) == 0); + + // Demonstrates the behavior change in Xcode 7 / Clang 3.7 described + // here: https://llvm.org/bugs/show_bug.cgi?id=23812 + Json nested_array = Json::array { Json::array { 1, 2, 3 } }; + assert(nested_array.is_array()); + assert(nested_array.array_items().size() == 1); + assert(nested_array.array_items()[0].is_array()); + assert(nested_array.array_items()[0].array_items().size() == 3); + + Json my_json = Json::object { + { "key1", "value1" }, + { "key2", false }, + { "key3", Json::array { 1, 2, 3 } }, + }; + std::string json_str = my_json.dump(); + printf("%s\n", json_str.c_str()); + + class Point { + public: + int x; + int y; + Point (int x, int y) : x(x), y(y) {} + Json to_json() const { return Json::array { x, y }; } + }; + + std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } }; + std::string points_json = Json(points).dump(); + printf("%s\n", points_json.c_str()); +}
--- a/json11/CMakeLists.txt Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -project(json11) - -cmake_minimum_required(VERSION 2.8) - -enable_testing() - -add_definitions( - -std=c++11 - -fno-rtti - -fno-exceptions - -Wall - -Wextra - -Werror) - -set(json11_SRCS json11.cpp) - -add_library(json11 STATIC ${json11_SRCS}) - -add_test(json11_test json11_test) - -add_executable(json11_test ${json11_SRCS} test.cpp)
--- a/json11/LICENSE.txt Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -Copyright (c) 2013 Dropbox, Inc. - -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.
--- a/json11/Makefile Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -test: json11.cpp json11.hpp test.cpp - $(CXX) -O -std=c++11 json11.cpp test.cpp -o test -fno-rtti -fno-exceptions - -clean: - if [ -e test ]; then rm test; fi - -.PHONY: clean
--- a/json11/README.md Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -json11 ------- - -json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. - -The core object provided by the library is json11::Json. A Json object represents any JSON -value: null, bool, number (int or double), string (std::string), array (std::vector), or -object (std::map). - -Json objects act like values. They can be assigned, copied, moved, compared for equality or -order, and so on. There are also helper methods Json::dump, to serialize a Json to a string, and -Json::parse (static) to parse a std::string as a Json object. - -It's easy to make a JSON object with C++11's new initializer syntax: - - Json my_json = Json::object { - { "key1", "value1" }, - { "key2", false }, - { "key3", Json::array { 1, 2, 3 } }, - }; - std::string json_str = my_json.dump(); - -There are also implicit constructors that allow standard and user-defined types to be -automatically converted to JSON. For example: - - class Point { - public: - int x; - int y; - Point (int x, int y) : x(x), y(y) {} - Json to_json() const { return Json::array { x, y }; } - }; - - std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } }; - std::string points_json = Json(points).dump(); - -JSON values can have their values queried and inspected: - - Json json = Json::array { Json::object { { "k", "v" } } }; - std::string str = json[0]["k"].string_value(); - -More documentation is still to come. For now, see json11.hpp.
--- a/json11/json11.cpp Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,769 +0,0 @@ -/* Copyright (c) 2013 Dropbox, Inc. - * - * 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. - */ - -#include "json11.hpp" -#include <cassert> -#include <cmath> -#include <cstdlib> -#include <cstdio> -#include <limits> - -namespace json11 { - -static const int max_depth = 200; - -using std::string; -using std::vector; -using std::map; -using std::make_shared; -using std::initializer_list; -using std::move; - -/* * * * * * * * * * * * * * * * * * * * - * Serialization - */ - -static void dump(std::nullptr_t, string &out) { - out += "null"; -} - -static void dump(double value, string &out) { - if (std::isfinite(value)) { - char buf[32]; - snprintf(buf, sizeof buf, "%.17g", value); - out += buf; - } else { - out += "null"; - } -} - -static void dump(int value, string &out) { - char buf[32]; - snprintf(buf, sizeof buf, "%d", value); - out += buf; -} - -static void dump(bool value, string &out) { - out += value ? "true" : "false"; -} - -static void dump(const string &value, string &out) { - out += '"'; - for (size_t i = 0; i < value.length(); i++) { - const char ch = value[i]; - if (ch == '\\') { - out += "\\\\"; - } else if (ch == '"') { - out += "\\\""; - } else if (ch == '\b') { - out += "\\b"; - } else if (ch == '\f') { - out += "\\f"; - } else if (ch == '\n') { - out += "\\n"; - } else if (ch == '\r') { - out += "\\r"; - } else if (ch == '\t') { - out += "\\t"; - } else if (static_cast<uint8_t>(ch) <= 0x1f) { - char buf[8]; - snprintf(buf, sizeof buf, "\\u%04x", ch); - out += buf; - } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 - && static_cast<uint8_t>(value[i+2]) == 0xa8) { - out += "\\u2028"; - i += 2; - } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 - && static_cast<uint8_t>(value[i+2]) == 0xa9) { - out += "\\u2029"; - i += 2; - } else { - out += ch; - } - } - out += '"'; -} - -static void dump(const Json::array &values, string &out) { - bool first = true; - out += "["; - for (const auto &value : values) { - if (!first) - out += ", "; - value.dump(out); - first = false; - } - out += "]"; -} - -static void dump(const Json::object &values, string &out) { - bool first = true; - out += "{"; - for (const auto &kv : values) { - if (!first) - out += ", "; - dump(kv.first, out); - out += ": "; - kv.second.dump(out); - first = false; - } - out += "}"; -} - -void Json::dump(string &out) const { - m_ptr->dump(out); -} - -/* * * * * * * * * * * * * * * * * * * * - * Value wrappers - */ - -template <Json::Type tag, typename T> -class Value : public JsonValue { -protected: - - // Constructors - explicit Value(const T &value) : m_value(value) {} - explicit Value(T &&value) : m_value(move(value)) {} - - // Get type tag - Json::Type type() const override { - return tag; - } - - // Comparisons - bool equals(const JsonValue * other) const override { - return m_value == static_cast<const Value<tag, T> *>(other)->m_value; - } - bool less(const JsonValue * other) const override { - return m_value < static_cast<const Value<tag, T> *>(other)->m_value; - } - - const T m_value; - void dump(string &out) const override { json11::dump(m_value, out); } -}; - -class JsonDouble final : public Value<Json::NUMBER, double> { - double number_value() const override { return m_value; } - int int_value() const override { return static_cast<int>(m_value); } - bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } - bool less(const JsonValue * other) const override { return m_value < other->number_value(); } -public: - explicit JsonDouble(double value) : Value(value) {} -}; - -class JsonInt final : public Value<Json::NUMBER, int> { - double number_value() const override { return m_value; } - int int_value() const override { return m_value; } - bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } - bool less(const JsonValue * other) const override { return m_value < other->number_value(); } -public: - explicit JsonInt(int value) : Value(value) {} -}; - -class JsonBoolean final : public Value<Json::BOOL, bool> { - bool bool_value() const override { return m_value; } -public: - explicit JsonBoolean(bool value) : Value(value) {} -}; - -class JsonString final : public Value<Json::STRING, string> { - const string &string_value() const override { return m_value; } -public: - explicit JsonString(const string &value) : Value(value) {} - explicit JsonString(string &&value) : Value(move(value)) {} -}; - -class JsonArray final : public Value<Json::ARRAY, Json::array> { - const Json::array &array_items() const override { return m_value; } - const Json & operator[](size_t i) const override; -public: - explicit JsonArray(const Json::array &value) : Value(value) {} - explicit JsonArray(Json::array &&value) : Value(move(value)) {} -}; - -class JsonObject final : public Value<Json::OBJECT, Json::object> { - const Json::object &object_items() const override { return m_value; } - const Json & operator[](const string &key) const override; -public: - explicit JsonObject(const Json::object &value) : Value(value) {} - explicit JsonObject(Json::object &&value) : Value(move(value)) {} -}; - -class JsonNull final : public Value<Json::NUL, std::nullptr_t> { -public: - JsonNull() : Value(nullptr) {} -}; - -/* * * * * * * * * * * * * * * * * * * * - * Static globals - static-init-safe - */ -struct Statics { - const std::shared_ptr<JsonValue> null = make_shared<JsonNull>(); - const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true); - const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false); - const string empty_string; - const vector<Json> empty_vector; - const map<string, Json> empty_map; - Statics() {} -}; - -static const Statics & statics() { - static const Statics s {}; - return s; -} - -static const Json & static_null() { - // This has to be separate, not in Statics, because Json() accesses statics().null. - static const Json json_null; - return json_null; -} - -/* * * * * * * * * * * * * * * * * * * * - * Constructors - */ - -Json::Json() noexcept : m_ptr(statics().null) {} -Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} -Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {} -Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {} -Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} -Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {} -Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {} -Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {} -Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {} -Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {} -Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {} -Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {} - -/* * * * * * * * * * * * * * * * * * * * - * Accessors - */ - -Json::Type Json::type() const { return m_ptr->type(); } -double Json::number_value() const { return m_ptr->number_value(); } -int Json::int_value() const { return m_ptr->int_value(); } -bool Json::bool_value() const { return m_ptr->bool_value(); } -const string & Json::string_value() const { return m_ptr->string_value(); } -const vector<Json> & Json::array_items() const { return m_ptr->array_items(); } -const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); } -const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } -const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } - -double JsonValue::number_value() const { return 0; } -int JsonValue::int_value() const { return 0; } -bool JsonValue::bool_value() const { return false; } -const string & JsonValue::string_value() const { return statics().empty_string; } -const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; } -const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; } -const Json & JsonValue::operator[] (size_t) const { return static_null(); } -const Json & JsonValue::operator[] (const string &) const { return static_null(); } - -const Json & JsonObject::operator[] (const string &key) const { - auto iter = m_value.find(key); - return (iter == m_value.end()) ? static_null() : iter->second; -} -const Json & JsonArray::operator[] (size_t i) const { - if (i >= m_value.size()) return static_null(); - else return m_value[i]; -} - -/* * * * * * * * * * * * * * * * * * * * - * Comparison - */ - -bool Json::operator== (const Json &other) const { - if (m_ptr->type() != other.m_ptr->type()) - return false; - - return m_ptr->equals(other.m_ptr.get()); -} - -bool Json::operator< (const Json &other) const { - if (m_ptr->type() != other.m_ptr->type()) - return m_ptr->type() < other.m_ptr->type(); - - return m_ptr->less(other.m_ptr.get()); -} - -/* * * * * * * * * * * * * * * * * * * * - * Parsing - */ - -/* esc(c) - * - * Format char c suitable for printing in an error message. - */ -static inline string esc(char c) { - char buf[12]; - if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) { - snprintf(buf, sizeof buf, "'%c' (%d)", c, c); - } else { - snprintf(buf, sizeof buf, "(%d)", c); - } - return string(buf); -} - -static inline bool in_range(long x, long lower, long upper) { - return (x >= lower && x <= upper); -} - -/* JsonParser - * - * Object that tracks all state of an in-progress parse. - */ -struct JsonParser { - - /* State - */ - const string &str; - size_t i; - string &err; - bool failed; - const JsonParse strategy; - - /* fail(msg, err_ret = Json()) - * - * Mark this parse as failed. - */ - Json fail(string &&msg) { - return fail(move(msg), Json()); - } - - template <typename T> - T fail(string &&msg, const T err_ret) { - if (!failed) - err = std::move(msg); - failed = true; - return err_ret; - } - - /* consume_whitespace() - * - * Advance until the current character is non-whitespace. - */ - void consume_whitespace() { - while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') - i++; - } - - /* consume_comment() - * - * Advance comments (c-style inline and multiline). - */ - bool consume_comment() { - bool comment_found = false; - if (str[i] == '/') { - i++; - if (i == str.size()) - return fail("unexpected end of input inside comment", 0); - if (str[i] == '/') { // inline comment - i++; - if (i == str.size()) - return fail("unexpected end of input inside inline comment", 0); - // advance until next line - while (str[i] != '\n') { - i++; - if (i == str.size()) - return fail("unexpected end of input inside inline comment", 0); - } - comment_found = true; - } - else if (str[i] == '*') { // multiline comment - i++; - if (i > str.size()-2) - return fail("unexpected end of input inside multi-line comment", 0); - // advance until closing tokens - while (!(str[i] == '*' && str[i+1] == '/')) { - i++; - if (i > str.size()-2) - return fail( - "unexpected end of input inside multi-line comment", 0); - } - i += 2; - if (i == str.size()) - return fail( - "unexpected end of input inside multi-line comment", 0); - comment_found = true; - } - else - return fail("malformed comment", 0); - } - return comment_found; - } - - /* consume_garbage() - * - * Advance until the current character is non-whitespace and non-comment. - */ - void consume_garbage() { - consume_whitespace(); - if(strategy == JsonParse::COMMENTS) { - bool comment_found = false; - do { - comment_found = consume_comment(); - consume_whitespace(); - } - while(comment_found); - } - } - - /* get_next_token() - * - * Return the next non-whitespace character. If the end of the input is reached, - * flag an error and return 0. - */ - char get_next_token() { - consume_garbage(); - if (i == str.size()) - return fail("unexpected end of input", 0); - - return str[i++]; - } - - /* encode_utf8(pt, out) - * - * Encode pt as UTF-8 and add it to out. - */ - void encode_utf8(long pt, string & out) { - if (pt < 0) - return; - - if (pt < 0x80) { - out += static_cast<char>(pt); - } else if (pt < 0x800) { - out += static_cast<char>((pt >> 6) | 0xC0); - out += static_cast<char>((pt & 0x3F) | 0x80); - } else if (pt < 0x10000) { - out += static_cast<char>((pt >> 12) | 0xE0); - out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); - out += static_cast<char>((pt & 0x3F) | 0x80); - } else { - out += static_cast<char>((pt >> 18) | 0xF0); - out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80); - out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); - out += static_cast<char>((pt & 0x3F) | 0x80); - } - } - - /* parse_string() - * - * Parse a string, starting at the current position. - */ - string parse_string() { - string out; - long last_escaped_codepoint = -1; - while (true) { - if (i == str.size()) - return fail("unexpected end of input in string", ""); - - char ch = str[i++]; - - if (ch == '"') { - encode_utf8(last_escaped_codepoint, out); - return out; - } - - if (in_range(ch, 0, 0x1f)) - return fail("unescaped " + esc(ch) + " in string", ""); - - // The usual case: non-escaped characters - if (ch != '\\') { - encode_utf8(last_escaped_codepoint, out); - last_escaped_codepoint = -1; - out += ch; - continue; - } - - // Handle escapes - if (i == str.size()) - return fail("unexpected end of input in string", ""); - - ch = str[i++]; - - if (ch == 'u') { - // Extract 4-byte escape sequence - string esc = str.substr(i, 4); - // Explicitly check length of the substring. The following loop - // relies on std::string returning the terminating NUL when - // accessing str[length]. Checking here reduces brittleness. - if (esc.length() < 4) { - return fail("bad \\u escape: " + esc, ""); - } - for (int j = 0; j < 4; j++) { - if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') - && !in_range(esc[j], '0', '9')) - return fail("bad \\u escape: " + esc, ""); - } - - long codepoint = strtol(esc.data(), nullptr, 16); - - // JSON specifies that characters outside the BMP shall be encoded as a pair - // of 4-hex-digit \u escapes encoding their surrogate pair components. Check - // whether we're in the middle of such a beast: the previous codepoint was an - // escaped lead (high) surrogate, and this is a trail (low) surrogate. - if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) - && in_range(codepoint, 0xDC00, 0xDFFF)) { - // Reassemble the two surrogate pairs into one astral-plane character, per - // the UTF-16 algorithm. - encode_utf8((((last_escaped_codepoint - 0xD800) << 10) - | (codepoint - 0xDC00)) + 0x10000, out); - last_escaped_codepoint = -1; - } else { - encode_utf8(last_escaped_codepoint, out); - last_escaped_codepoint = codepoint; - } - - i += 4; - continue; - } - - encode_utf8(last_escaped_codepoint, out); - last_escaped_codepoint = -1; - - if (ch == 'b') { - out += '\b'; - } else if (ch == 'f') { - out += '\f'; - } else if (ch == 'n') { - out += '\n'; - } else if (ch == 'r') { - out += '\r'; - } else if (ch == 't') { - out += '\t'; - } else if (ch == '"' || ch == '\\' || ch == '/') { - out += ch; - } else { - return fail("invalid escape character " + esc(ch), ""); - } - } - } - - /* parse_number() - * - * Parse a double. - */ - Json parse_number() { - size_t start_pos = i; - - if (str[i] == '-') - i++; - - // Integer part - if (str[i] == '0') { - i++; - if (in_range(str[i], '0', '9')) - return fail("leading 0s not permitted in numbers"); - } else if (in_range(str[i], '1', '9')) { - i++; - while (in_range(str[i], '0', '9')) - i++; - } else { - return fail("invalid " + esc(str[i]) + " in number"); - } - - if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' - && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) { - return std::atoi(str.c_str() + start_pos); - } - - // Decimal part - if (str[i] == '.') { - i++; - if (!in_range(str[i], '0', '9')) - return fail("at least one digit required in fractional part"); - - while (in_range(str[i], '0', '9')) - i++; - } - - // Exponent part - if (str[i] == 'e' || str[i] == 'E') { - i++; - - if (str[i] == '+' || str[i] == '-') - i++; - - if (!in_range(str[i], '0', '9')) - return fail("at least one digit required in exponent"); - - while (in_range(str[i], '0', '9')) - i++; - } - - return std::strtod(str.c_str() + start_pos, nullptr); - } - - /* expect(str, res) - * - * Expect that 'str' starts at the character that was just read. If it does, advance - * the input and return res. If not, flag an error. - */ - Json expect(const string &expected, Json res) { - assert(i != 0); - i--; - if (str.compare(i, expected.length(), expected) == 0) { - i += expected.length(); - return res; - } else { - return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); - } - } - - /* parse_json() - * - * Parse a JSON object. - */ - Json parse_json(int depth) { - if (depth > max_depth) { - return fail("exceeded maximum nesting depth"); - } - - char ch = get_next_token(); - if (failed) - return Json(); - - if (ch == '-' || (ch >= '0' && ch <= '9')) { - i--; - return parse_number(); - } - - if (ch == 't') - return expect("true", true); - - if (ch == 'f') - return expect("false", false); - - if (ch == 'n') - return expect("null", Json()); - - if (ch == '"') - return parse_string(); - - if (ch == '{') { - map<string, Json> data; - ch = get_next_token(); - if (ch == '}') - return data; - - while (1) { - if (ch != '"') - return fail("expected '\"' in object, got " + esc(ch)); - - string key = parse_string(); - if (failed) - return Json(); - - ch = get_next_token(); - if (ch != ':') - return fail("expected ':' in object, got " + esc(ch)); - - data[std::move(key)] = parse_json(depth + 1); - if (failed) - return Json(); - - ch = get_next_token(); - if (ch == '}') - break; - if (ch != ',') - return fail("expected ',' in object, got " + esc(ch)); - - ch = get_next_token(); - } - return data; - } - - if (ch == '[') { - vector<Json> data; - ch = get_next_token(); - if (ch == ']') - return data; - - while (1) { - i--; - data.push_back(parse_json(depth + 1)); - if (failed) - return Json(); - - ch = get_next_token(); - if (ch == ']') - break; - if (ch != ',') - return fail("expected ',' in list, got " + esc(ch)); - - ch = get_next_token(); - (void)ch; - } - return data; - } - - return fail("expected value, got " + esc(ch)); - } -}; - -Json Json::parse(const string &in, string &err, JsonParse strategy) { - JsonParser parser { in, 0, err, false, strategy }; - Json result = parser.parse_json(0); - - // Check for any trailing garbage - parser.consume_garbage(); - if (parser.i != in.size()) - return parser.fail("unexpected trailing " + esc(in[parser.i])); - - return result; -} - -// Documented in json11.hpp -vector<Json> Json::parse_multi(const string &in, - string &err, - JsonParse strategy) { - JsonParser parser { in, 0, err, false, strategy }; - - vector<Json> json_vec; - while (parser.i != in.size() && !parser.failed) { - json_vec.push_back(parser.parse_json(0)); - // Check for another object - parser.consume_garbage(); - } - return json_vec; -} - -/* * * * * * * * * * * * * * * * * * * * - * Shape-checking - */ - -bool Json::has_shape(const shape & types, string & err) const { - if (!is_object()) { - err = "expected JSON object, got " + dump(); - return false; - } - - for (auto & item : types) { - if ((*this)[item.first].type() != item.second) { - err = "bad type for " + item.first + " in " + dump(); - return false; - } - } - - return true; -} - -} // namespace json11
--- a/json11/json11.hpp Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,211 +0,0 @@ -/* json11 - * - * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. - * - * The core object provided by the library is json11::Json. A Json object represents any JSON - * value: null, bool, number (int or double), string (std::string), array (std::vector), or - * object (std::map). - * - * Json objects act like values: they can be assigned, copied, moved, compared for equality or - * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and - * Json::parse (static) to parse a std::string as a Json object. - * - * Internally, the various types of Json object are represented by the JsonValue class - * hierarchy. - * - * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, - * so some JSON implementations distinguish between integers and floating-point numbers, while - * some don't. In json11, we choose the latter. Because some JSON implementations (namely - * Javascript itself) treat all numbers as the same type, distinguishing the two leads - * to JSON that will be *silently* changed by a round-trip through those implementations. - * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also - * provides integer helpers. - * - * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the - * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 - * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch - * will be exact for +/- 275 years.) - */ - -/* Copyright (c) 2013 Dropbox, Inc. - * - * 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. - */ - -#pragma once - -#include <string> -#include <vector> -#include <map> -#include <memory> -#include <initializer_list> - -namespace json11 { - -enum JsonParse { - STANDARD, COMMENTS -}; - -class JsonValue; - -class Json final { -public: - // Types - enum Type { - NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT - }; - - // Array and object typedefs - typedef std::vector<Json> array; - typedef std::map<std::string, Json> object; - - // Constructors for the various types of JSON value. - Json() noexcept; // NUL - Json(std::nullptr_t) noexcept; // NUL - Json(double value); // NUMBER - Json(int value); // NUMBER - Json(bool value); // BOOL - Json(const std::string &value); // STRING - Json(std::string &&value); // STRING - Json(const char * value); // STRING - Json(const array &values); // ARRAY - Json(array &&values); // ARRAY - Json(const object &values); // OBJECT - Json(object &&values); // OBJECT - - // Implicit constructor: anything with a to_json() function. - template <class T, class = decltype(&T::to_json)> - Json(const T & t) : Json(t.to_json()) {} - - // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) - template <class M, typename std::enable_if< - std::is_constructible<std::string, typename M::key_type>::value - && std::is_constructible<Json, typename M::mapped_type>::value, - int>::type = 0> - Json(const M & m) : Json(object(m.begin(), m.end())) {} - - // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) - template <class V, typename std::enable_if< - std::is_constructible<Json, typename V::value_type>::value, - int>::type = 0> - Json(const V & v) : Json(array(v.begin(), v.end())) {} - - // This prevents Json(some_pointer) from accidentally producing a bool. Use - // Json(bool(some_pointer)) if that behavior is desired. - Json(void *) = delete; - - // Accessors - Type type() const; - - bool is_null() const { return type() == NUL; } - bool is_number() const { return type() == NUMBER; } - bool is_bool() const { return type() == BOOL; } - bool is_string() const { return type() == STRING; } - bool is_array() const { return type() == ARRAY; } - bool is_object() const { return type() == OBJECT; } - - // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not - // distinguish between integer and non-integer numbers - number_value() and int_value() - // can both be applied to a NUMBER-typed object. - double number_value() const; - int int_value() const; - - // Return the enclosed value if this is a boolean, false otherwise. - bool bool_value() const; - // Return the enclosed string if this is a string, "" otherwise. - const std::string &string_value() const; - // Return the enclosed std::vector if this is an array, or an empty vector otherwise. - const array &array_items() const; - // Return the enclosed std::map if this is an object, or an empty map otherwise. - const object &object_items() const; - - // Return a reference to arr[i] if this is an array, Json() otherwise. - const Json & operator[](size_t i) const; - // Return a reference to obj[key] if this is an object, Json() otherwise. - const Json & operator[](const std::string &key) const; - - // Serialize. - void dump(std::string &out) const; - std::string dump() const { - std::string out; - dump(out); - return out; - } - - // Parse. If parse fails, return Json() and assign an error message to err. - static Json parse(const std::string & in, - std::string & err, - JsonParse strategy = JsonParse::STANDARD); - static Json parse(const char * in, - std::string & err, - JsonParse strategy = JsonParse::STANDARD) { - if (in) { - return parse(std::string(in), err, strategy); - } else { - err = "null input"; - return nullptr; - } - } - // Parse multiple objects, concatenated or separated by whitespace - static std::vector<Json> parse_multi( - const std::string & in, - std::string & err, - JsonParse strategy = JsonParse::STANDARD); - - bool operator== (const Json &rhs) const; - bool operator< (const Json &rhs) const; - bool operator!= (const Json &rhs) const { return !(*this == rhs); } - bool operator<= (const Json &rhs) const { return !(rhs < *this); } - bool operator> (const Json &rhs) const { return (rhs < *this); } - bool operator>= (const Json &rhs) const { return !(*this < rhs); } - - /* has_shape(types, err) - * - * Return true if this is a JSON object and, for each item in types, has a field of - * the given type. If not, return false and set err to a descriptive message. - */ - typedef std::initializer_list<std::pair<std::string, Type>> shape; - bool has_shape(const shape & types, std::string & err) const; - -private: - std::shared_ptr<JsonValue> m_ptr; -}; - -// Internal class hierarchy - JsonValue objects are not exposed to users of this API. -class JsonValue { -protected: - friend class Json; - friend class JsonInt; - friend class JsonDouble; - virtual Json::Type type() const = 0; - virtual bool equals(const JsonValue * other) const = 0; - virtual bool less(const JsonValue * other) const = 0; - virtual void dump(std::string &out) const = 0; - virtual double number_value() const; - virtual int int_value() const; - virtual bool bool_value() const; - virtual const std::string &string_value() const; - virtual const Json::array &array_items() const; - virtual const Json &operator[](size_t i) const; - virtual const Json::object &object_items() const; - virtual const Json &operator[](const std::string &key) const; - virtual ~JsonValue() {} -}; - -} // namespace json11
--- a/json11/test.cpp Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ -#include <string> -#include <cstdio> -#include <cstring> -#include <iostream> -#include <sstream> -#include "json11.hpp" -#include <cassert> -#include <list> -#include <set> -#include <unordered_map> - -using namespace json11; -using std::string; - -// Check that Json has the properties we want. -#include <type_traits> -#define CHECK_TRAIT(x) static_assert(std::x::value, #x) -CHECK_TRAIT(is_nothrow_constructible<Json>); -CHECK_TRAIT(is_nothrow_default_constructible<Json>); -CHECK_TRAIT(is_copy_constructible<Json>); -CHECK_TRAIT(is_nothrow_move_constructible<Json>); -CHECK_TRAIT(is_copy_assignable<Json>); -CHECK_TRAIT(is_nothrow_move_assignable<Json>); -CHECK_TRAIT(is_nothrow_destructible<Json>); - -void parse_from_stdin() { - string buf; - string line; - while (std::getline(std::cin, line)) { - buf += line + "\n"; - } - - string err; - auto json = Json::parse(buf, err); - if (!err.empty()) { - printf("Failed: %s\n", err.c_str()); - } else { - printf("Result: %s\n", json.dump().c_str()); - } -} - -int main(int argc, char **argv) { - if (argc == 2 && argv[1] == string("--stdin")) { - parse_from_stdin(); - return 0; - } - - const string simple_test = - R"({"k1":"v1", "k2":42, "k3":["a",123,true,false,null]})"; - - string err; - auto json = Json::parse(simple_test, err); - - std::cout << "k1: " << json["k1"].string_value() << "\n"; - std::cout << "k3: " << json["k3"].dump() << "\n"; - - for (auto &k : json["k3"].array_items()) { - std::cout << " - " << k.dump() << "\n"; - } - - const string comment_test = R"({ - // comment /* with nested comment */ - "a": 1, - // comment - // continued - "b": "text", - /* multi - line - comment */ - // and single-line comment - "c": [1, 2, 3] - })"; - - string err_comment; - auto json_comment = Json::parse( - comment_test, err_comment, JsonParse::COMMENTS); - if (!err_comment.empty()) { - printf("Failed: %s\n", err_comment.c_str()); - } else { - printf("Result: %s\n", json_comment.dump().c_str()); - } - - string failing_comment_test = R"({ - /* bad comment - "a": 1, - })"; - - string err_failing_comment; - auto json_failing_comment = Json::parse( - failing_comment_test, err_failing_comment, JsonParse::COMMENTS); - if (!err_failing_comment.empty()) { - printf("Failed: %s\n", err_failing_comment.c_str()); - } else { - printf("Result: %s\n", json_failing_comment.dump().c_str()); - } - - failing_comment_test = R"({ - / / bad comment })"; - - json_failing_comment = Json::parse( - failing_comment_test, err_failing_comment, JsonParse::COMMENTS); - if (!err_failing_comment.empty()) { - printf("Failed: %s\n", err_failing_comment.c_str()); - } else { - printf("Result: %s\n", json_failing_comment.dump().c_str()); - } - - failing_comment_test = R"({// bad comment })"; - - json_failing_comment = Json::parse( - failing_comment_test, err_failing_comment, JsonParse::COMMENTS); - if (!err_failing_comment.empty()) { - printf("Failed: %s\n", err_failing_comment.c_str()); - } else { - printf("Result: %s\n", json_failing_comment.dump().c_str()); - } - - failing_comment_test = R"({ - "a": 1 - }/)"; - - json_failing_comment = Json::parse( - failing_comment_test, err_failing_comment, JsonParse::COMMENTS); - if (!err_failing_comment.empty()) { - printf("Failed: %s\n", err_failing_comment.c_str()); - } else { - printf("Result: %s\n", json_failing_comment.dump().c_str()); - } - - failing_comment_test = R"({/* bad - comment *})"; - - json_failing_comment = Json::parse( - failing_comment_test, err_failing_comment, JsonParse::COMMENTS); - if (!err_failing_comment.empty()) { - printf("Failed: %s\n", err_failing_comment.c_str()); - } else { - printf("Result: %s\n", json_failing_comment.dump().c_str()); - } - - std::list<int> l1 { 1, 2, 3 }; - std::vector<int> l2 { 1, 2, 3 }; - std::set<int> l3 { 1, 2, 3 }; - assert(Json(l1) == Json(l2)); - assert(Json(l2) == Json(l3)); - - std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } }; - std::unordered_map<string, string> m2 { { "k1", "v1" }, { "k2", "v2" } }; - assert(Json(m1) == Json(m2)); - - // Json literals - Json obj = Json::object({ - { "k1", "v1" }, - { "k2", 42.0 }, - { "k3", Json::array({ "a", 123.0, true, false, nullptr }) }, - }); - - std::cout << "obj: " << obj.dump() << "\n"; - - assert(Json("a").number_value() == 0); - assert(Json("a").string_value() == "a"); - assert(Json().number_value() == 0); - - assert(obj == json); - assert(Json(42) == Json(42.0)); - assert(Json(42) != Json(42.1)); - - const string unicode_escape_test = - R"([ "blah\ud83d\udca9blah\ud83dblah\udca9blah\u0000blah\u1234" ])"; - - const char utf8[] = "blah" "\xf0\x9f\x92\xa9" "blah" "\xed\xa0\xbd" "blah" - "\xed\xb2\xa9" "blah" "\0" "blah" "\xe1\x88\xb4"; - - Json uni = Json::parse(unicode_escape_test, err); - assert(uni[0].string_value().size() == (sizeof utf8) - 1); - assert(std::memcmp(uni[0].string_value().data(), utf8, sizeof utf8) == 0); - - // Demonstrates the behavior change in Xcode 7 / Clang 3.7 described - // here: https://llvm.org/bugs/show_bug.cgi?id=23812 - Json nested_array = Json::array { Json::array { 1, 2, 3 } }; - assert(nested_array.is_array()); - assert(nested_array.array_items().size() == 1); - assert(nested_array.array_items()[0].is_array()); - assert(nested_array.array_items()[0].array_items().size() == 3); - - Json my_json = Json::object { - { "key1", "value1" }, - { "key2", false }, - { "key3", Json::array { 1, 2, 3 } }, - }; - std::string json_str = my_json.dump(); - printf("%s\n", json_str.c_str()); - - class Point { - public: - int x; - int y; - Point (int x, int y) : x(x), y(y) {} - Json to_json() const { return Json::array { x, y }; } - }; - - std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } }; - std::string points_json = Json(points).dump(); - printf("%s\n", points_json.c_str()); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test.sh Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,19 @@ +#!/bin/bash + +set -eu + +mypath=$(dirname "$0") + +echo "Building and testing simple server..." + +make -f "$mypath"/Makefile clean all test + +echo +echo "Building and running test client..." + +( cd "$mypath"/vamp-client/qt && qmake && make && + ./test ../../bin/piper-vamp-simple-server ) + +echo +echo "Done" +echo
--- a/test/test-server.sh Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -#!/bin/bash - -set -eu - -piperdir=../piper -vampsdkdir=../vamp-plugin-sdk -schemadir="$piperdir"/json/schema - -if [ ! -d "$schemadir" ]; then - echo "WARNING: schema directory $schemadir not found, won't be validating JSON schema" 1>&2 -fi - -tmpdir=$(mktemp -d) - -if [ ! -d "$tmpdir" ]; then - echo "Temp directory creation failed" 1>&2 - exit 1 -fi - -trap "rm -rf $tmpdir" 0 - -reqfile="$tmpdir/req.json" -respfile="$tmpdir/resp.json" -allrespfile="$tmpdir/resp.all" -input="$tmpdir/input" -expected="$tmpdir/expected" -obtained="$tmpdir/obtained" - -validate() { - local file="$1" - local schemaname="$2" - if [ -d "$schemadir" ]; then - jsonschema -i "$file" "$schemadir/$schemaname.json" 1>&2 && \ - echo "validated $schemaname" 1>&2 || \ - echo "failed to validate $schemaname" 1>&2 - else - echo "schema directory $schemadir not found, skipping validation" 1>&2 - fi -} - -validate_request() { - local json="$1" - echo "$json" > "$reqfile" - validate "$reqfile" "rpcrequest" -} - -validate_response() { - local json="$1" - echo "$json" > "$respfile" - validate "$respfile" "rpcresponse" -} - -cat > "$input" <<EOF -{"method":"list"} -{"method":"list","params": {"from":["vamp-example-plugins","something-nonexistent"]}} -{"method":"list","params": {"from":["something-nonexistent"]}} -{"method":"load","id":6,"params": {"key":"vamp-example-plugins:percussiononsets","inputSampleRate":44100,"adapterFlags":["AdaptInputDomain","AdaptBufferSize"]}} -{"method":"configure","id":"weevil","params":{"handle":1,"configuration":{"blockSize": 8, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 8}}} -{"method":"process","params": {"handle": 1, "processInput": { "timestamp": {"s": 0, "n": 0}, "inputBuffers": [ [1,2,3,4,5,6,7,8] ]}}} -{"method":"finish","params": {"handle": 1}} -EOF - -# Expected output, apart from the plugin list which seems a bit -# fragile to check here -cat > "$expected" <<EOF -{"id": 6, "jsonrpc": "2.0", "method": "load", "result": {"defaultConfiguration": {"blockSize": 1024, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 1024}, "handle": 1, "staticData": {"basic": {"description": "Detect percussive note onsets by identifying broadband energy rises", "identifier": "percussiononsets", "name": "Simple Percussion Onset Detector"}, "basicOutputInfo": [{"description": "Percussive note onset locations", "identifier": "onsets", "name": "Onsets"}, {"description": "Broadband energy rise detection function", "identifier": "detectionfunction", "name": "Detection Function"}], "category": ["Time", "Onsets"], "copyright": "Code copyright 2006 Queen Mary, University of London, after Dan Barry et al 2005. Freely redistributable (BSD license)", "inputDomain": "TimeDomain", "key": "vamp-example-plugins:percussiononsets", "maker": "Vamp SDK Example Plugins", "maxChannelCount": 1, "minChannelCount": 1, "parameters": [{"basic": {"description": "Energy rise within a frequency bin necessary to count toward broadband total", "identifier": "threshold", "name": "Energy rise threshold"}, "defaultValue": 3, "extents": {"max": 20, "min": 0}, "unit": "dB", "valueNames": []}, {"basic": {"description": "Sensitivity of peak detector applied to broadband detection function", "identifier": "sensitivity", "name": "Sensitivity"}, "defaultValue": 40, "extents": {"max": 100, "min": 0}, "unit": "%", "valueNames": []}], "programs": [], "version": 2}}} -{"id": "weevil", "jsonrpc": "2.0", "method": "configure", "result": {"handle": 1, "outputList": [{"basic": {"description": "Percussive note onset locations", "identifier": "onsets", "name": "Onsets"}, "configured": {"binCount": 0, "binNames": [], "hasDuration": false, "sampleRate": 44100, "sampleType": "VariableSampleRate", "unit": ""}}, {"basic": {"description": "Broadband energy rise detection function", "identifier": "detectionfunction", "name": "Detection Function"}, "configured": {"binCount": 1, "binNames": [""], "hasDuration": false, "quantizeStep": 1, "sampleRate": 86.1328125, "sampleType": "FixedSampleRate", "unit": ""}}]}} -{"jsonrpc": "2.0", "method": "process", "result": {"features": {}, "handle": 1}} -{"jsonrpc": "2.0", "method": "finish", "result": {"features": {"detectionfunction": [{"featureValues": [0], "timestamp": {"n": 11609977, "s": 0}}]}, "handle": 1}} -EOF - -# We run the whole test twice, once with the server in Capnp mode -# (converting to JSON using piper-convert) and once with it directly -# in JSON mode - -for format in json capnp ; do - - ( export VAMP_PATH="$vampsdkdir"/examples ; - while read request ; do - validate_request "$request" - echo "$request" - done | - if [ "$format" = "json" ]; then - bin/piper-vamp-simple-server -d json - else - bin/piper-convert request -i json -o capnp | - bin/piper-vamp-simple-server -d capnp | - bin/piper-convert response -i capnp -o json - fi | - while read response ; do - echo "$response" >> "$allrespfile" - validate_response "$response" - done - ) < "$input" - - # Skip plugin lists - tail -n +4 "$allrespfile" > "$obtained" - - echo "Checking response contents against expected contents..." - if ! cmp "$obtained" "$expected"; then - diff -u1 "$obtained" "$expected" - else - echo "OK" - fi - - echo "Checking plugin counts from list responses..." - - # Now check the plugin lists, but as the descriptions etc are - # probably a bit fragile, let's just count the number of plugins - - # First, with no "from" arg to the list call - list_no_from=$(head -n +1 "$allrespfile" | fmt -1 | grep '"key"' | wc -l) - - # Now with a "from" arg that includes the library that exists - list_with_good_from=$(tail -n +2 "$allrespfile" | head -n +1 | fmt -1 | - grep '"key"' | wc -l) - - # Now with a "from" arg that doesn't include any real library - list_with_bad_from=$(tail -n +3 "$allrespfile" | head -n +1 | fmt -1 | - grep '"key"' | wc -l) - - if [ "$list_no_from" != "6" ]; then - echo "Wrong number of plugins from list response without \"from\" arg" - echo "Expected 6, obtained $list_no_from" - false - fi - if [ "$list_with_good_from" != "6" ]; then - echo "Wrong number of plugins from list response with good \"from\" arg" - echo "Expected 6, obtained $list_with_good_from" - false - fi - if [ "$list_with_bad_from" != "0" ]; then - echo "Wrong number of plugins from list response with bad \"from\" arg" - echo "Expected 0, obtained $list_with_bad_from" - false - fi - echo OK - - rm "$allrespfile" - -done - -echo "Tests succeeded" # set -e at top should ensure we don't get here otherwise
--- a/vamp-client/AutoPlugin.h Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ -/* - Piper C++ - - An API for audio analysis and feature extraction plugins. - - Centre for Digital Music, Queen Mary, University of London. - Copyright 2006-2016 Chris Cannam and QMUL. - - 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 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. - - Except as contained in this notice, the names of the Centre for - Digital Music; Queen Mary, University of London; and Chris Cannam - shall not be used in advertising or otherwise to promote the sale, - use or other dealings in this Software without prior written - authorization. -*/ - -#ifndef PIPER_AUTO_PLUGIN_H -#define PIPER_AUTO_PLUGIN_H - -#include "ProcessQtTransport.h" -#include "CapnpRRClient.h" - -#include <cstdint> - -namespace piper_vamp { -namespace client { - -class AutoPlugin : public Vamp::Plugin -{ -public: - AutoPlugin(std::string serverName, - std::string pluginKey, - float inputSampleRate, - int adapterFlags, - LogCallback *logger) : // logger may be nullptr for cerr - Vamp::Plugin(inputSampleRate), - m_logger(logger), - m_transport(serverName, "capnp", logger), - m_client(&m_transport, logger) - { - LoadRequest req; - req.pluginKey = pluginKey; - req.inputSampleRate = inputSampleRate; - req.adapterFlags = adapterFlags; - try { - LoadResponse resp = m_client.loadPlugin(req); - m_plugin = resp.plugin; - } catch (ServerCrashed c) { - log(std::string("AutoPlugin: Server crashed: ") + c.what()); - m_plugin = 0; - } - } - - virtual ~AutoPlugin() { - delete m_plugin; - } - - bool isOK() const { - return (m_plugin != nullptr); - } - - virtual std::string getIdentifier() const { - return getPlugin()->getIdentifier(); - } - - virtual std::string getName() const { - return getPlugin()->getName(); - } - - virtual std::string getDescription() const { - return getPlugin()->getDescription(); - } - - virtual std::string getMaker() const { - return getPlugin()->getMaker(); - } - - virtual std::string getCopyright() const { - return getPlugin()->getCopyright(); - } - - virtual int getPluginVersion() const { - return getPlugin()->getPluginVersion(); - } - - virtual ParameterList getParameterDescriptors() const { - return getPlugin()->getParameterDescriptors(); - } - - virtual float getParameter(std::string name) const { - return getPlugin()->getParameter(name); - } - - virtual void setParameter(std::string name, float value) { - getPlugin()->setParameter(name, value); - } - - virtual ProgramList getPrograms() const { - return getPlugin()->getPrograms(); - } - - virtual std::string getCurrentProgram() const { - return getPlugin()->getCurrentProgram(); - } - - virtual void selectProgram(std::string program) { - getPlugin()->selectProgram(program); - } - - virtual bool initialise(size_t inputChannels, - size_t stepSize, - size_t blockSize) { - return getPlugin()->initialise(inputChannels, stepSize, blockSize); - } - - virtual void reset() { - getPlugin()->reset(); - } - - virtual InputDomain getInputDomain() const { - return getPlugin()->getInputDomain(); - } - - virtual size_t getPreferredBlockSize() const { - return getPlugin()->getPreferredBlockSize(); - } - - virtual size_t getPreferredStepSize() const { - return getPlugin()->getPreferredStepSize(); - } - - virtual size_t getMinChannelCount() const { - return getPlugin()->getMinChannelCount(); - } - - virtual size_t getMaxChannelCount() const { - return getPlugin()->getMaxChannelCount(); - } - - virtual OutputList getOutputDescriptors() const { - return getPlugin()->getOutputDescriptors(); - } - - virtual FeatureSet process(const float *const *inputBuffers, - Vamp::RealTime timestamp) { - return getPlugin()->process(inputBuffers, timestamp); - } - - virtual FeatureSet getRemainingFeatures() { - return getPlugin()->getRemainingFeatures(); - } - -private: - LogCallback *m_logger; - ProcessQtTransport m_transport; - CapnpRRClient m_client; - Vamp::Plugin *m_plugin; - Vamp::Plugin *getPlugin() const { - if (!m_plugin) { - log("AutoPlugin: getPlugin() failed (caller should have called AutoPlugin::isOK)"); - throw std::logic_error("Plugin load failed"); - } - return m_plugin; - } - - void log(std::string message) const { - if (m_logger) m_logger->log(message); - else std::cerr << message << std::endl; - } -}; - -} -} - -#endif - -
--- a/vamp-client/ProcessQtTransport.h Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,260 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ -/* - Piper C++ - - An API for audio analysis and feature extraction plugins. - - Centre for Digital Music, Queen Mary, University of London. - Copyright 2006-2016 Chris Cannam and QMUL. - - 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 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. - - Except as contained in this notice, the names of the Centre for - Digital Music; Queen Mary, University of London; and Chris Cannam - shall not be used in advertising or otherwise to promote the sale, - use or other dealings in this Software without prior written - authorization. -*/ - -#ifndef PIPER_PROCESS_QT_TRANSPORT_H -#define PIPER_PROCESS_QT_TRANSPORT_H - -#include "SynchronousTransport.h" - -#include <QProcess> -#include <QString> -#include <QMutex> -#include <QTime> - -#include <iostream> - -//#define DEBUG_TRANSPORT 1 - -namespace piper_vamp { -namespace client { - -/** - * A SynchronousTransport implementation that spawns a sub-process - * using Qt's QProcess abstraction and talks to it via stdin/stdout - * channels. Calls are completely serialized; the protocol only - * supports one call in process at a time, and therefore the transport - * only allows one at a time. - * - * This class is thread-safe, but in practice you can only use it from - * within a single thread, because the underlying QProcess does not - * support switching threads. - */ -class ProcessQtTransport : public SynchronousTransport -{ -public: - ProcessQtTransport(std::string processName, - std::string formatArg, - LogCallback *logger) : // logger may be nullptr for cerr - m_logger(logger), - m_completenessChecker(0), - m_crashed(false) { - - m_process = new QProcess(); - m_process->setReadChannel(QProcess::StandardOutput); - m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); - - m_process->start(QString::fromStdString(processName), - { QString::fromStdString(formatArg) }); - - if (!m_process->waitForStarted()) { - if (m_process->state() == QProcess::NotRunning) { - QProcess::ProcessError err = m_process->error(); - if (err == QProcess::FailedToStart) { - log("Unable to start server process " + processName); - } else if (err == QProcess::Crashed) { - log("Server process " + processName + " crashed on startup"); - } else { - QString e = QString("%1").arg(err); - log("Server process " + processName + - " failed on startup with error code " + e.toStdString()); - } - delete m_process; - m_process = nullptr; - } - } - - if (m_process) { - log("Server process " + processName + " started OK"); - } - } - - ~ProcessQtTransport() { - if (m_process) { - if (m_process->state() != QProcess::NotRunning) { - m_process->closeWriteChannel(); - m_process->waitForFinished(200); - m_process->close(); - m_process->waitForFinished(); - log("Server process exited normally"); - } - delete m_process; - } - } - - void - setCompletenessChecker(MessageCompletenessChecker *checker) override { - m_completenessChecker = checker; - } - - bool - isOK() const override { - return (m_process != nullptr) && !m_crashed; - } - - std::vector<char> - call(const char *ptr, size_t size, std::string type, bool slow) override { - - QMutexLocker locker(&m_mutex); - - if (!m_completenessChecker) { - log("call: No completeness checker set on transport"); - throw std::logic_error("No completeness checker set on transport"); - } - if (!isOK()) { - log("call: Transport is not OK"); - throw std::logic_error("Transport is not OK"); - } - -#ifdef DEBUG_TRANSPORT - std::cerr << "writing " << size << " bytes to server" << std::endl; -#endif - m_process->write(ptr, size); - m_process->waitForBytesWritten(); - - std::vector<char> buffer; - bool complete = false; - - QTime t; - t.start(); - - // We don't like to timeout at all while waiting for a - // response -- we'd like to wait as long as the server - // continues running. - // - int beforeResponseTimeout = 0; // ms, 0 = no timeout - - // But if the call is marked as fast (i.e. just retrieving - // info rather than calculating something) we will time out - // after a bit. - // - if (!slow) beforeResponseTimeout = 10000; // ms, 0 = no timeout - - // But we do timeout if the server sends part of a reply and - // then gets stuck. It's reasonable to assume that a server - // that's already prepared its message and started sending has - // finished doing any real work. In each case the timeout is - // measured since data was last read. - // - int duringResponseTimeout = 5000; // ms, 0 = no timeout - - while (!complete) { - - bool responseStarted = !buffer.empty(); // already have something - int ms = t.elapsed(); // time since start or since last read - - qint64 byteCount = m_process->bytesAvailable(); - - if (!byteCount) { - - if (responseStarted) { - if (duringResponseTimeout > 0 && ms > duringResponseTimeout) { - log("Server timed out during response"); - throw std::runtime_error("Request timed out"); - } - } else { - if (beforeResponseTimeout > 0 && ms > beforeResponseTimeout) { - log("Server timed out before response"); - throw std::runtime_error("Request timed out"); - } - } - -#ifdef DEBUG_TRANSPORT - std::cerr << "waiting for data from server (slow = " << slow << ")..." << std::endl; -#endif - if (slow) { - m_process->waitForReadyRead(1000); - } else { -#ifdef _WIN32 - // This is most unsatisfactory -- if we give a non-zero - // arg here, then we end up sleeping way beyond the arrival - // of the data to read -- can end up using less than 10% - // CPU during processing which is crazy. So for Windows - // only, we busy-wait during "fast" calls. It works out - // much faster in the end. Could do with a simpler native - // blocking API really. - m_process->waitForReadyRead(0); -#else - m_process->waitForReadyRead(100); -#endif - } - if (m_process->state() == QProcess::NotRunning && - // don't give up until we've read all that's been buffered! - !m_process->bytesAvailable()) { - QProcess::ProcessError err = m_process->error(); - if (err == QProcess::Crashed) { - log("Server crashed during " + type + " request"); - } else { - QString e = QString("%1").arg(err); - log("Server failed during " + type - + " request with error code " + e.toStdString()); - } - m_crashed = true; - throw ServerCrashed(); - } - } else { - size_t formerSize = buffer.size(); - buffer.resize(formerSize + byteCount); - m_process->read(buffer.data() + formerSize, byteCount); - switch (m_completenessChecker->check(buffer)) { - case MessageCompletenessChecker::Complete: complete = true; break; - case MessageCompletenessChecker::Incomplete: break; - case MessageCompletenessChecker::Invalid: - throw std::runtime_error - ("Invalid message received: corrupt stream from server?"); - } - (void)t.restart(); // reset timeout when we read anything - } - } - - return buffer; - } - -private: - LogCallback *m_logger; - MessageCompletenessChecker *m_completenessChecker; //!!! I don't own this (currently) - QProcess *m_process; // I own this - QMutex m_mutex; - bool m_crashed; - - void log(std::string message) const { - if (m_logger) m_logger->log(message); - else std::cerr << message << std::endl; - } -}; - -} -} - -#endif
--- a/vamp-client/client.cpp Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ -/* - Piper C++ - - An API for audio analysis and feature extraction plugins. - - Centre for Digital Music, Queen Mary, University of London. - Copyright 2006-2016 Chris Cannam and QMUL. - - 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 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. - - Except as contained in this notice, the names of the Centre for - Digital Music; Queen Mary, University of London; and Chris Cannam - shall not be used in advertising or otherwise to promote the sale, - use or other dealings in this Software without prior written - authorization. -*/ - -#include "ProcessQtTransport.h" -#include "CapnpRRClient.h" -#include "AutoPlugin.h" - -#include <stdexcept> - -using std::cerr; -using std::endl; - -int main(int, char **) -{ - piper_vamp::client::ProcessQtTransport transport - ("../bin/piper-vamp-simple-server", "capnp", nullptr); - - piper_vamp::client::CapnpRRClient client(&transport, nullptr); - - piper_vamp::ListResponse lr = client.listPluginData({}); - cerr << "Plugins available:" << endl; - int i = 1; - for (const auto &p: lr.available) { - cerr << i++ << ". [" << p.pluginKey << "] " << p.basic.name << endl; - } - - piper_vamp::LoadRequest req; - req.pluginKey = "vamp-example-plugins:zerocrossing"; - req.inputSampleRate = 16; - piper_vamp::LoadResponse resp = client.loadPlugin(req); - Vamp::Plugin *plugin = resp.plugin; - - if (!plugin->initialise(1, 4, 4)) { - cerr << "initialisation failed" << endl; - } else { - std::vector<float> buf = { 1.0, -1.0, 1.0, -1.0 }; - float *bd = buf.data(); - Vamp::Plugin::FeatureSet features = plugin->process - (&bd, Vamp::RealTime::zeroTime); - cerr << "results for output 0:" << endl; - auto fl(features[0]); - for (const auto &f: fl) { - cerr << f.values[0] << endl; - } - } - - (void)plugin->getRemainingFeatures(); - - cerr << "calling reset..." << endl; - plugin->reset(); - cerr << "...round 2!" << endl; - - std::vector<float> buf = { 1.0, -1.0, 1.0, -1.0 }; - float *bd = buf.data(); - Vamp::Plugin::FeatureSet features = plugin->process - (&bd, Vamp::RealTime::zeroTime); - cerr << "results for output 0:" << endl; - auto fl(features[0]); - for (const auto &f: fl) { - cerr << f.values[0] << endl; - } - - (void)plugin->getRemainingFeatures(); - - delete plugin; - - // Let's try a crazy AutoPlugin - - piper_vamp::client::AutoPlugin ap - ("../bin/piper-vamp-simple-server", - "vamp-example-plugins:zerocrossing", 16, 0, nullptr); - - if (!ap.isOK()) { - cerr << "AutoPlugin creation failed" << endl; - } else { - if (!ap.initialise(1, 4, 4)) { - cerr << "initialisation failed" << endl; - } else { - std::vector<float> buf = { 1.0, -1.0, 1.0, -1.0 }; - float *bd = buf.data(); - Vamp::Plugin::FeatureSet features = ap.process - (&bd, Vamp::RealTime::zeroTime); - cerr << "results for output 0:" << endl; - auto fl(features[0]); - for (const auto &f: fl) { - cerr << f.values[0] << endl; - } - } - } -} -
--- a/vamp-client/client.pro Thu Jan 19 14:05:21 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ - -TEMPLATE = app - -CONFIG += qt stl c++11 exceptions console warn_on -QT -= xml network gui widgets - -!win32 { - QMAKE_CXXFLAGS += -Werror -} - -OBJECTS_DIR = ../o -MOC_DIR = ../o - -VAMPSDK_DIR = ../../vamp-plugin-sdk -PIPER_DIR = ../../piper - -QMAKE_CXXFLAGS = -I$$VAMPSDK_DIR -I.. - -LIBS += -lcapnp -lkj -lvamp-hostsdk - -# Using the "console" CONFIG flag above should ensure this happens for -# normal Windows builds, but this may be necessary when cross-compiling -win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console - -macx*: CONFIG -= app_bundle - -TARGET = client - -SOURCES += \ - client.cpp \ - ../vamp-capnp/piper-capnp.cpp - -HEADERS += \ - ProcessQtTransport.h \ - CapnpRRClient.h \ - Loader.h \ - PluginClient.h \ - PluginStub.h \ - AutoPlugin.h \ - SynchronousTransport.h - -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp-client/qt/AutoPlugin.h Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,196 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Piper C++ + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006-2016 Chris Cannam and QMUL. + + 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 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. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef PIPER_AUTO_PLUGIN_H +#define PIPER_AUTO_PLUGIN_H + +#include "ProcessQtTransport.h" +#include "CapnpRRClient.h" + +#include <cstdint> + +namespace piper_vamp { +namespace client { + +class AutoPlugin : public Vamp::Plugin +{ +public: + AutoPlugin(std::string serverName, + std::string pluginKey, + float inputSampleRate, + int adapterFlags, + LogCallback *logger) : // logger may be nullptr for cerr + Vamp::Plugin(inputSampleRate), + m_logger(logger), + m_transport(serverName, "capnp", logger), + m_client(&m_transport, logger) + { + LoadRequest req; + req.pluginKey = pluginKey; + req.inputSampleRate = inputSampleRate; + req.adapterFlags = adapterFlags; + try { + LoadResponse resp = m_client.loadPlugin(req); + m_plugin = resp.plugin; + } catch (ServerCrashed c) { + log(std::string("AutoPlugin: Server crashed: ") + c.what()); + m_plugin = 0; + } + } + + virtual ~AutoPlugin() { + delete m_plugin; + } + + bool isOK() const { + return (m_plugin != nullptr); + } + + virtual std::string getIdentifier() const { + return getPlugin()->getIdentifier(); + } + + virtual std::string getName() const { + return getPlugin()->getName(); + } + + virtual std::string getDescription() const { + return getPlugin()->getDescription(); + } + + virtual std::string getMaker() const { + return getPlugin()->getMaker(); + } + + virtual std::string getCopyright() const { + return getPlugin()->getCopyright(); + } + + virtual int getPluginVersion() const { + return getPlugin()->getPluginVersion(); + } + + virtual ParameterList getParameterDescriptors() const { + return getPlugin()->getParameterDescriptors(); + } + + virtual float getParameter(std::string name) const { + return getPlugin()->getParameter(name); + } + + virtual void setParameter(std::string name, float value) { + getPlugin()->setParameter(name, value); + } + + virtual ProgramList getPrograms() const { + return getPlugin()->getPrograms(); + } + + virtual std::string getCurrentProgram() const { + return getPlugin()->getCurrentProgram(); + } + + virtual void selectProgram(std::string program) { + getPlugin()->selectProgram(program); + } + + virtual bool initialise(size_t inputChannels, + size_t stepSize, + size_t blockSize) { + return getPlugin()->initialise(inputChannels, stepSize, blockSize); + } + + virtual void reset() { + getPlugin()->reset(); + } + + virtual InputDomain getInputDomain() const { + return getPlugin()->getInputDomain(); + } + + virtual size_t getPreferredBlockSize() const { + return getPlugin()->getPreferredBlockSize(); + } + + virtual size_t getPreferredStepSize() const { + return getPlugin()->getPreferredStepSize(); + } + + virtual size_t getMinChannelCount() const { + return getPlugin()->getMinChannelCount(); + } + + virtual size_t getMaxChannelCount() const { + return getPlugin()->getMaxChannelCount(); + } + + virtual OutputList getOutputDescriptors() const { + return getPlugin()->getOutputDescriptors(); + } + + virtual FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp) { + return getPlugin()->process(inputBuffers, timestamp); + } + + virtual FeatureSet getRemainingFeatures() { + return getPlugin()->getRemainingFeatures(); + } + +private: + LogCallback *m_logger; + ProcessQtTransport m_transport; + CapnpRRClient m_client; + Vamp::Plugin *m_plugin; + Vamp::Plugin *getPlugin() const { + if (!m_plugin) { + log("AutoPlugin: getPlugin() failed (caller should have called AutoPlugin::isOK)"); + throw std::logic_error("Plugin load failed"); + } + return m_plugin; + } + + void log(std::string message) const { + if (m_logger) m_logger->log(message); + else std::cerr << message << std::endl; + } +}; + +} +} + +#endif + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp-client/qt/ProcessQtTransport.h Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,260 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Piper C++ + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006-2016 Chris Cannam and QMUL. + + 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 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. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef PIPER_PROCESS_QT_TRANSPORT_H +#define PIPER_PROCESS_QT_TRANSPORT_H + +#include "SynchronousTransport.h" + +#include <QProcess> +#include <QString> +#include <QMutex> +#include <QTime> + +#include <iostream> + +//#define DEBUG_TRANSPORT 1 + +namespace piper_vamp { +namespace client { + +/** + * A SynchronousTransport implementation that spawns a sub-process + * using Qt's QProcess abstraction and talks to it via stdin/stdout + * channels. Calls are completely serialized; the protocol only + * supports one call in process at a time, and therefore the transport + * only allows one at a time. + * + * This class is thread-safe, but in practice you can only use it from + * within a single thread, because the underlying QProcess does not + * support switching threads. + */ +class ProcessQtTransport : public SynchronousTransport +{ +public: + ProcessQtTransport(std::string processName, + std::string formatArg, + LogCallback *logger) : // logger may be nullptr for cerr + m_logger(logger), + m_completenessChecker(0), + m_crashed(false) { + + m_process = new QProcess(); + m_process->setReadChannel(QProcess::StandardOutput); + m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); + + m_process->start(QString::fromStdString(processName), + { QString::fromStdString(formatArg) }); + + if (!m_process->waitForStarted()) { + if (m_process->state() == QProcess::NotRunning) { + QProcess::ProcessError err = m_process->error(); + if (err == QProcess::FailedToStart) { + log("Unable to start server process " + processName); + } else if (err == QProcess::Crashed) { + log("Server process " + processName + " crashed on startup"); + } else { + QString e = QString("%1").arg(err); + log("Server process " + processName + + " failed on startup with error code " + e.toStdString()); + } + delete m_process; + m_process = nullptr; + } + } + + if (m_process) { + log("Server process " + processName + " started OK"); + } + } + + ~ProcessQtTransport() { + if (m_process) { + if (m_process->state() != QProcess::NotRunning) { + m_process->closeWriteChannel(); + m_process->waitForFinished(200); + m_process->close(); + m_process->waitForFinished(); + log("Server process exited normally"); + } + delete m_process; + } + } + + void + setCompletenessChecker(MessageCompletenessChecker *checker) override { + m_completenessChecker = checker; + } + + bool + isOK() const override { + return (m_process != nullptr) && !m_crashed; + } + + std::vector<char> + call(const char *ptr, size_t size, std::string type, bool slow) override { + + QMutexLocker locker(&m_mutex); + + if (!m_completenessChecker) { + log("call: No completeness checker set on transport"); + throw std::logic_error("No completeness checker set on transport"); + } + if (!isOK()) { + log("call: Transport is not OK"); + throw std::logic_error("Transport is not OK"); + } + +#ifdef DEBUG_TRANSPORT + std::cerr << "writing " << size << " bytes to server" << std::endl; +#endif + m_process->write(ptr, size); + m_process->waitForBytesWritten(); + + std::vector<char> buffer; + bool complete = false; + + QTime t; + t.start(); + + // We don't like to timeout at all while waiting for a + // response -- we'd like to wait as long as the server + // continues running. + // + int beforeResponseTimeout = 0; // ms, 0 = no timeout + + // But if the call is marked as fast (i.e. just retrieving + // info rather than calculating something) we will time out + // after a bit. + // + if (!slow) beforeResponseTimeout = 10000; // ms, 0 = no timeout + + // But we do timeout if the server sends part of a reply and + // then gets stuck. It's reasonable to assume that a server + // that's already prepared its message and started sending has + // finished doing any real work. In each case the timeout is + // measured since data was last read. + // + int duringResponseTimeout = 5000; // ms, 0 = no timeout + + while (!complete) { + + bool responseStarted = !buffer.empty(); // already have something + int ms = t.elapsed(); // time since start or since last read + + qint64 byteCount = m_process->bytesAvailable(); + + if (!byteCount) { + + if (responseStarted) { + if (duringResponseTimeout > 0 && ms > duringResponseTimeout) { + log("Server timed out during response"); + throw std::runtime_error("Request timed out"); + } + } else { + if (beforeResponseTimeout > 0 && ms > beforeResponseTimeout) { + log("Server timed out before response"); + throw std::runtime_error("Request timed out"); + } + } + +#ifdef DEBUG_TRANSPORT + std::cerr << "waiting for data from server (slow = " << slow << ")..." << std::endl; +#endif + if (slow) { + m_process->waitForReadyRead(1000); + } else { +#ifdef _WIN32 + // This is most unsatisfactory -- if we give a non-zero + // arg here, then we end up sleeping way beyond the arrival + // of the data to read -- can end up using less than 10% + // CPU during processing which is crazy. So for Windows + // only, we busy-wait during "fast" calls. It works out + // much faster in the end. Could do with a simpler native + // blocking API really. + m_process->waitForReadyRead(0); +#else + m_process->waitForReadyRead(100); +#endif + } + if (m_process->state() == QProcess::NotRunning && + // don't give up until we've read all that's been buffered! + !m_process->bytesAvailable()) { + QProcess::ProcessError err = m_process->error(); + if (err == QProcess::Crashed) { + log("Server crashed during " + type + " request"); + } else { + QString e = QString("%1").arg(err); + log("Server failed during " + type + + " request with error code " + e.toStdString()); + } + m_crashed = true; + throw ServerCrashed(); + } + } else { + size_t formerSize = buffer.size(); + buffer.resize(formerSize + byteCount); + m_process->read(buffer.data() + formerSize, byteCount); + switch (m_completenessChecker->check(buffer)) { + case MessageCompletenessChecker::Complete: complete = true; break; + case MessageCompletenessChecker::Incomplete: break; + case MessageCompletenessChecker::Invalid: + throw std::runtime_error + ("Invalid message received: corrupt stream from server?"); + } + (void)t.restart(); // reset timeout when we read anything + } + } + + return buffer; + } + +private: + LogCallback *m_logger; + MessageCompletenessChecker *m_completenessChecker; //!!! I don't own this (currently) + QProcess *m_process; // I own this + QMutex m_mutex; + bool m_crashed; + + void log(std::string message) const { + if (m_logger) m_logger->log(message); + else std::cerr << message << std::endl; + } +}; + +} +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp-client/qt/test.cpp Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,139 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Piper C++ + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006-2016 Chris Cannam and QMUL. + + 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 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. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "ProcessQtTransport.h" +#include "CapnpRRClient.h" +#include "AutoPlugin.h" + +#include <stdexcept> + +using std::cerr; +using std::endl; +using std::exception; +using std::vector; + +int main(int argc, char **argv) +{ + if (argc != 2) { + cerr << "Usage: " << argv[0] << " <server-executable-path>" << endl; + return 2; + } + + try { + piper_vamp::client::ProcessQtTransport transport(argv[1], "capnp", nullptr); + if (!transport.isOK()) { + cerr << "ERROR: Transport failed to start" << endl; + return 1; + } + + piper_vamp::client::CapnpRRClient client(&transport, nullptr); + + piper_vamp::ListResponse lr = client.listPluginData({}); + cerr << "Plugins available:" << endl; + int i = 1; + for (const auto &p: lr.available) { + cerr << i++ << ". [" << p.pluginKey << "] " << p.basic.name << endl; + } + + piper_vamp::LoadRequest req; + req.pluginKey = "vamp-example-plugins:zerocrossing"; + req.inputSampleRate = 16; + piper_vamp::LoadResponse resp = client.loadPlugin(req); + Vamp::Plugin *plugin = resp.plugin; + + if (!plugin->initialise(1, 4, 4)) { + cerr << "initialisation failed" << endl; + } else { + vector<float> buf = { 1.0, -1.0, 1.0, -1.0 }; + float *bd = buf.data(); + Vamp::Plugin::FeatureSet features = plugin->process + (&bd, Vamp::RealTime::zeroTime); + cerr << "results for output 0:" << endl; + auto fl(features[0]); + for (const auto &f: fl) { + cerr << f.values[0] << endl; + } + } + + (void)plugin->getRemainingFeatures(); + + cerr << "calling reset..." << endl; + plugin->reset(); + cerr << "...round 2!" << endl; + + vector<float> buf = { 1.0, -1.0, 1.0, -1.0 }; + float *bd = buf.data(); + Vamp::Plugin::FeatureSet features = plugin->process + (&bd, Vamp::RealTime::zeroTime); + cerr << "results for output 0:" << endl; + auto fl(features[0]); + for (const auto &f: fl) { + cerr << f.values[0] << endl; + } + + (void)plugin->getRemainingFeatures(); + + delete plugin; + + // Let's try a crazy AutoPlugin + + piper_vamp::client::AutoPlugin ap + (argv[1], "vamp-example-plugins:zerocrossing", 16, 0, nullptr); + + if (!ap.isOK()) { + cerr << "AutoPlugin creation failed" << endl; + } else { + if (!ap.initialise(1, 4, 4)) { + cerr << "initialisation failed" << endl; + } else { + vector<float> buf = { 1.0, -1.0, 1.0, -1.0 }; + float *bd = buf.data(); + Vamp::Plugin::FeatureSet features = ap.process + (&bd, Vamp::RealTime::zeroTime); + cerr << "results for output 0:" << endl; + auto fl(features[0]); + for (const auto &f: fl) { + cerr << f.values[0] << endl; + } + } + } + } catch (const exception &e) { + cerr << "ERROR: Exception caught: " << e.what() << endl; + return 1; + } + + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp-client/qt/test.pro Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,42 @@ + +TEMPLATE = app + +CONFIG += qt stl c++11 exceptions console warn_on +QT -= xml network gui widgets + +!win32 { + QMAKE_CXXFLAGS += -Werror +} + +OBJECTS_DIR = ../o +MOC_DIR = ../o + +VAMPSDK_DIR = ../../../vamp-plugin-sdk +PIPER_DIR = ../../../piper + +QMAKE_CXXFLAGS = -I$$VAMPSDK_DIR -I.. -I../.. + +LIBS += -lcapnp -lkj -lvamp-hostsdk + +# Using the "console" CONFIG flag above should ensure this happens for +# normal Windows builds, but this may be necessary when cross-compiling +win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console + +macx*: CONFIG -= app_bundle + +TARGET = test + +SOURCES += \ + test.cpp \ + ../../vamp-capnp/piper-capnp.cpp + +HEADERS += \ + ProcessQtTransport.h \ + AutoPlugin.h \ + ../CapnpRRClient.h \ + ../Loader.h \ + ../PluginClient.h \ + ../PluginStub.h \ + ../SynchronousTransport.h + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp-server/test.sh Fri Jan 20 17:45:54 2017 +0000 @@ -0,0 +1,147 @@ +#!/bin/bash + +set -eu + +piperdir=../piper +vampsdkdir=../vamp-plugin-sdk +schemadir="$piperdir"/json/schema + +if [ ! -d "$schemadir" ]; then + echo "WARNING: schema directory $schemadir not found, won't be validating JSON schema" 1>&2 +fi + +tmpdir=$(mktemp -d) + +if [ ! -d "$tmpdir" ]; then + echo "Temp directory creation failed" 1>&2 + exit 1 +fi + +trap "rm -rf $tmpdir" 0 + +reqfile="$tmpdir/req.json" +respfile="$tmpdir/resp.json" +allrespfile="$tmpdir/resp.all" +input="$tmpdir/input" +expected="$tmpdir/expected" +obtained="$tmpdir/obtained" + +validate() { + local file="$1" + local schemaname="$2" + if [ -d "$schemadir" ]; then + echo " * validating against schema $schemaname... " 1>&2 + jsonschema -i "$file" "$schemadir/$schemaname.json" 1>&2 && \ + echo " -> validated against schema $schemaname" 1>&2 || \ + echo " !! failed to validate $schemaname!" 1>&2 + else + echo "(schema directory $schemadir not found, skipping validation)" 1>&2 + fi +} + +validate_request() { + local json="$1" + echo "$json" > "$reqfile" + validate "$reqfile" "rpcrequest" +} + +validate_response() { + local json="$1" + echo "$json" > "$respfile" + validate "$respfile" "rpcresponse" +} + +cat > "$input" <<EOF +{"method":"list"} +{"method":"list","params": {"from":["vamp-example-plugins","something-nonexistent"]}} +{"method":"list","params": {"from":["something-nonexistent"]}} +{"method":"load","id":6,"params": {"key":"vamp-example-plugins:percussiononsets","inputSampleRate":44100,"adapterFlags":["AdaptInputDomain","AdaptBufferSize"]}} +{"method":"configure","id":"weevil","params":{"handle":1,"configuration":{"blockSize": 8, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 8}}} +{"method":"process","params": {"handle": 1, "processInput": { "timestamp": {"s": 0, "n": 0}, "inputBuffers": [ [1,2,3,4,5,6,7,8] ]}}} +{"method":"finish","params": {"handle": 1}} +EOF + +# Expected output, apart from the plugin list which seems a bit +# fragile to check here +cat > "$expected" <<EOF +{"id": 6, "jsonrpc": "2.0", "method": "load", "result": {"defaultConfiguration": {"blockSize": 1024, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 1024}, "handle": 1, "staticData": {"basic": {"description": "Detect percussive note onsets by identifying broadband energy rises", "identifier": "percussiononsets", "name": "Simple Percussion Onset Detector"}, "basicOutputInfo": [{"description": "Percussive note onset locations", "identifier": "onsets", "name": "Onsets"}, {"description": "Broadband energy rise detection function", "identifier": "detectionfunction", "name": "Detection Function"}], "category": ["Time", "Onsets"], "copyright": "Code copyright 2006 Queen Mary, University of London, after Dan Barry et al 2005. Freely redistributable (BSD license)", "inputDomain": "TimeDomain", "key": "vamp-example-plugins:percussiononsets", "maker": "Vamp SDK Example Plugins", "maxChannelCount": 1, "minChannelCount": 1, "parameters": [{"basic": {"description": "Energy rise within a frequency bin necessary to count toward broadband total", "identifier": "threshold", "name": "Energy rise threshold"}, "defaultValue": 3, "extents": {"max": 20, "min": 0}, "unit": "dB", "valueNames": []}, {"basic": {"description": "Sensitivity of peak detector applied to broadband detection function", "identifier": "sensitivity", "name": "Sensitivity"}, "defaultValue": 40, "extents": {"max": 100, "min": 0}, "unit": "%", "valueNames": []}], "programs": [], "version": 2}}} +{"id": "weevil", "jsonrpc": "2.0", "method": "configure", "result": {"handle": 1, "outputList": [{"basic": {"description": "Percussive note onset locations", "identifier": "onsets", "name": "Onsets"}, "configured": {"binCount": 0, "binNames": [], "hasDuration": false, "sampleRate": 44100, "sampleType": "VariableSampleRate", "unit": ""}}, {"basic": {"description": "Broadband energy rise detection function", "identifier": "detectionfunction", "name": "Detection Function"}, "configured": {"binCount": 1, "binNames": [""], "hasDuration": false, "quantizeStep": 1, "sampleRate": 86.1328125, "sampleType": "FixedSampleRate", "unit": ""}}]}} +{"jsonrpc": "2.0", "method": "process", "result": {"features": {}, "handle": 1}} +{"jsonrpc": "2.0", "method": "finish", "result": {"features": {"detectionfunction": [{"featureValues": [0], "timestamp": {"n": 11609977, "s": 0}}]}, "handle": 1}} +EOF + +# We run the whole test twice, once with the server in Capnp mode +# (converting to JSON using piper-convert) and once with it directly +# in JSON mode + +#debugflag=-d +debugflag= + +for format in json capnp ; do + + ( export VAMP_PATH="$vampsdkdir"/examples ; + while read request ; do + validate_request "$request" + echo "$request" + done | + if [ "$format" = "json" ]; then + bin/piper-vamp-simple-server $debugflag json + else + bin/piper-convert request -i json -o capnp | + bin/piper-vamp-simple-server $debugflag capnp | + bin/piper-convert response -i capnp -o json + fi | + while read response ; do + echo "$response" >> "$allrespfile" + validate_response "$response" + done + ) < "$input" + + # Skip plugin lists + tail -n +4 "$allrespfile" > "$obtained" + + echo "Checking response contents against expected contents..." + if ! cmp "$obtained" "$expected"; then + diff -u1 "$obtained" "$expected" + else + echo "OK" + fi + + echo "Checking plugin counts from list responses..." + + # Now check the plugin lists, but as the descriptions etc are + # probably a bit fragile, let's just count the number of plugins + + # First, with no "from" arg to the list call + list_no_from=$(head -n +1 "$allrespfile" | fmt -1 | grep '"key"' | wc -l) + + # Now with a "from" arg that includes the library that exists + list_with_good_from=$(tail -n +2 "$allrespfile" | head -n +1 | fmt -1 | + grep '"key"' | wc -l) + + # Now with a "from" arg that doesn't include any real library + list_with_bad_from=$(tail -n +3 "$allrespfile" | head -n +1 | fmt -1 | + grep '"key"' | wc -l) + + if [ "$list_no_from" != "6" ]; then + echo "Wrong number of plugins from list response without \"from\" arg" + echo "Expected 6, obtained $list_no_from" + false + fi + if [ "$list_with_good_from" != "6" ]; then + echo "Wrong number of plugins from list response with good \"from\" arg" + echo "Expected 6, obtained $list_with_good_from" + false + fi + if [ "$list_with_bad_from" != "0" ]; then + echo "Wrong number of plugins from list response with bad \"from\" arg" + echo "Expected 0, obtained $list_with_bad_from" + false + fi + echo OK + + rm "$allrespfile" + +done + +echo "Tests succeeded" # set -e at top should ensure we don't get here otherwise
--- a/vamp-support/LoaderRequests.h Thu Jan 19 14:05:21 2017 +0000 +++ b/vamp-support/LoaderRequests.h Fri Jan 20 17:45:54 2017 +0000 @@ -56,7 +56,7 @@ auto loader = Vamp::HostExt::PluginLoader::getInstance(); - std::cerr << "listPluginData: about to ask loader to list plugins" << std::endl; +// std::cerr << "listPluginData: about to ask loader to list plugins" << std::endl; std::vector<std::string> keys; if (req.from.empty()) { @@ -65,11 +65,11 @@ keys = loader->listPluginsIn(req.from); } - std::cerr << "listPluginData: loader listed " << keys.size() << " plugins" << std::endl; +// std::cerr << "listPluginData: loader listed " << keys.size() << " plugins" << std::endl; ListResponse response; for (std::string key: keys) { - std::cerr << "listPluginData: loading plugin and querying static data: " << key << std::endl; +// std::cerr << "listPluginData: loading plugin and querying static data: " << key << std::endl; Vamp::Plugin *p = loader->loadPlugin(key, 44100, 0); if (!p) continue; auto category = loader->getPluginCategory(key);