├── README.md ├── tests ├── sax_reader.cpp ├── file_stream.cpp ├── optional.cpp ├── unit_test.h ├── match.cpp ├── iostream_adaptor.cpp ├── variant.cpp ├── schema.cpp ├── stream.cpp ├── json_writer.cpp ├── dom.h ├── reader_writer_stream.cpp ├── buffered_stream.cpp ├── base64_stream.cpp ├── debug_checks_reader.cpp ├── cbor_writer.cpp ├── tutorial.cpp ├── debug_checks_writer.cpp ├── cbor_reader.cpp ├── json_reader.cpp └── tests.vcxproj ├── ParsingComparison.png ├── SerializeComparison.png ├── .gitignore ├── LICENSE ├── inc └── goldfish │ ├── match.h │ ├── file_stream.h │ ├── tags.h │ ├── common.h │ ├── array_ref.h │ ├── debug_checks.h │ ├── iostream_adaptor.h │ ├── reader_writer_stream.h │ ├── schema.h │ ├── debug_checks_reader.h │ ├── buffered_stream.h │ ├── cbor_writer.h │ ├── base64_stream.h │ ├── optional.h │ ├── debug_checks_writer.h │ ├── sax_writer.h │ ├── json_writer.h │ └── sax_reader.h ├── perf ├── main.cpp └── perf.vcxproj ├── goldfish.sln └── src └── src.vcxproj /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneNoteDev/GoldFish/master/README.md -------------------------------------------------------------------------------- /tests/sax_reader.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneNoteDev/GoldFish/master/tests/sax_reader.cpp -------------------------------------------------------------------------------- /ParsingComparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneNoteDev/GoldFish/master/ParsingComparison.png -------------------------------------------------------------------------------- /SerializeComparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneNoteDev/GoldFish/master/SerializeComparison.png -------------------------------------------------------------------------------- /tests/file_stream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace goldfish { namespace stream 4 | { 5 | }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | Debug/ 3 | Release/ 4 | x64/ 5 | src/Debug/ 6 | src/Release/ 7 | src/x64/ 8 | tests/Debug/ 9 | tests/Release/ 10 | tests/x64/ 11 | ipch/ 12 | TestResults/ 13 | 14 | *.sdf 15 | *.VC.opendb 16 | *.vcxproj.filters 17 | *.vcxproj.user -------------------------------------------------------------------------------- /tests/optional.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "unit_test.h" 3 | 4 | namespace goldfish 5 | { 6 | struct with_invalid_state 7 | { 8 | int x; 9 | struct invalid_state 10 | { 11 | static void set(std::aligned_storage_t& data) 12 | { 13 | reinterpret_cast(data) = -1; 14 | } 15 | }; 16 | }; 17 | TEST_CASE(default_constructor) 18 | { 19 | test(optional() == nullopt); 20 | test(optional() == nullopt); 21 | } 22 | } -------------------------------------------------------------------------------- /tests/unit_test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CppUnitTest.h" 4 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 5 | 6 | #define TEST_CASE(name) \ 7 | TEST_CLASS(C##name) \ 8 | { public: \ 9 | TEST_METHOD(name); \ 10 | }; \ 11 | void C##name::name() 12 | 13 | inline void test(bool x) 14 | { 15 | if (!x) 16 | throw 0; 17 | } 18 | 19 | template void expect_exception(Lambda&& l) 20 | { 21 | try 22 | { 23 | l(); 24 | throw "Exception not thrown"; 25 | } 26 | catch (const Exception&) 27 | { 28 | } 29 | } -------------------------------------------------------------------------------- /tests/match.cpp: -------------------------------------------------------------------------------- 1 | #include "unit_test.h" 2 | #include 3 | 4 | namespace goldfish 5 | { 6 | struct A {}; 7 | struct B : A {}; 8 | struct C : A {}; 9 | 10 | TEST_CASE(test_match) 11 | { 12 | auto first = first_match( 13 | [](B&&) { return 1; }, 14 | [](A&&) { return 2; }, 15 | [](C&&) { return 3; }); 16 | 17 | auto best = best_match( 18 | [](B&&) { return 1; }, 19 | [](A&&) { return 2; }, 20 | [](C&&) { return 3; }); 21 | 22 | test(first(A{}) == 2); test(best(A{}) == 2); 23 | test(first(B{}) == 1); test(best(B{}) == 1); 24 | test(first(C{}) == 2); test(best(C{}) == 3); 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/iostream_adaptor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "unit_test.h" 4 | 5 | namespace goldfish 6 | { 7 | TEST_CASE(istream_reader) 8 | { 9 | std::stringstream s("Hello"); 10 | test(stream::read_all_as_string(stream::istream_reader_ref{ s }) == "Hello"); 11 | } 12 | TEST_CASE(ostream_writer) 13 | { 14 | std::stringstream s; 15 | stream::ostream_writer_ref writer{ s }; 16 | stream::copy(stream::read_string("Hello"), writer); 17 | writer.flush(); 18 | test(s.str() == "Hello"); 19 | } 20 | TEST_CASE(test_create_istream) 21 | { 22 | auto s = stream::make_istream<4>(stream::read_string("Hello world")); 23 | 24 | std::string word; 25 | s >> word; 26 | test(word == "Hello"); 27 | 28 | s >> word; 29 | test(word == "world"); 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Goldfish 2 | Copyright (c) Microsoft Corporation 3 | All rights reserved. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the ""Software""), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. -------------------------------------------------------------------------------- /tests/variant.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "unit_test.h" 3 | 4 | namespace goldfish 5 | { 6 | static_assert(details::index_of::value == 0, ""); 7 | static_assert(std::is_trivially_destructible>::value, ""); 8 | static_assert(std::is_trivially_move_constructible>::value, ""); 9 | 10 | static_assert(details::is_one_of::value, ""); 11 | 12 | TEST_CASE(variant_with_single_type) 13 | { 14 | variant x; 15 | test(x.as() == 0); 16 | x = 3; 17 | test(x.as() == 3); 18 | } 19 | 20 | TEST_CASE(copy_variant) 21 | { 22 | { 23 | variant x(42); 24 | auto y = x; 25 | test(y.as() == 42); 26 | 27 | x = 0.5; 28 | y = std::move(x); 29 | test(y.as() == 0.5); 30 | } 31 | 32 | { 33 | variant x("42"); 34 | auto y = x; 35 | test(y.as() == "42"); 36 | 37 | x = 42; 38 | y = x; 39 | test(y.as() == 42); 40 | } 41 | } 42 | 43 | TEST_CASE(variant_with_two_types) 44 | { 45 | variant v; 46 | v = std::string("foo"); 47 | test(v.is()); 48 | test(v.as() == "foo"); 49 | v = 3; 50 | test(v.is()); 51 | test(v.as() == 3); 52 | } 53 | 54 | TEST_CASE(variant_wrong_type) 55 | { 56 | variant v(3); 57 | expect_exception([&] { v.as(); }); 58 | expect_exception([&] { std::move(v).as(); }); 59 | expect_exception([&] { static_cast&>(v).as(); }); 60 | } 61 | } -------------------------------------------------------------------------------- /inc/goldfish/match.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace goldfish 7 | { 8 | namespace details 9 | { 10 | template struct is_callable 11 | { 12 | struct yes {}; 13 | struct no {}; 14 | template static yes test(decltype(std::declval()(std::declval()...))*) { return{}; } 15 | template static no test(...) { return{}; } 16 | enum { value = std::is_same(nullptr))>::value }; 17 | }; 18 | 19 | template struct lambda_trait {}; 20 | template struct lambda_trait 21 | { 22 | template struct first_callable_helper {}; 23 | template struct first_callable_index 24 | { 25 | enum { value = first_callable_helper::value, Args...>::value }; 26 | }; 27 | 28 | template struct first_callable_helper { enum { value = 0 }; }; 29 | template struct first_callable_helper { enum { value = 1 + lambda_trait::first_callable_index::value }; }; 30 | }; 31 | 32 | template struct best_match_object : T... 33 | { 34 | template best_match_object(Args&&... args) 35 | : T(std::forward(args))... 36 | {} 37 | }; 38 | } 39 | 40 | template auto first_match(Lambdas&&... lambdas) 41 | { 42 | return [lambdas = std::make_tuple(std::forward(lambdas)...)](auto&&... args) -> decltype(auto) 43 | { 44 | return std::get::first_callable_index::value>(lambdas)(std::forward(args)...); 45 | }; 46 | } 47 | 48 | template auto best_match(Lambdas&&... lambdas) 49 | { 50 | return details::best_match_object(std::forward(lambdas)...); 51 | } 52 | } -------------------------------------------------------------------------------- /tests/schema.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dom.h" 4 | #include "unit_test.h" 5 | 6 | namespace goldfish 7 | { 8 | struct library_misused {}; 9 | struct throw_on_error 10 | { 11 | static void on_error() { throw library_misused{}; } 12 | }; 13 | 14 | TEST_CASE(test_filtered_map_empty_map) 15 | { 16 | auto map = json::read(stream::read_string("{}")).as_map("10", "20", "30"); 17 | 18 | test(map.read_by_schema_index(0) == nullopt); 19 | 20 | seek_to_end(map); 21 | } 22 | 23 | TEST_CASE(test_filtered_map) 24 | { 25 | auto map = json::read( 26 | stream::read_string("{\"10\":1,\"15\":2,\"a\":\"b\",\"40\":3,\"50\":4,\"60\":5,\"80\":6}")). 27 | as_map("10", "20", "30", "40", "50", "60", "70", "80", "90"); 28 | 29 | // Reading the very first key 30 | test(dom::load_in_memory(*map.read_by_schema_index(0)) == 1ull); 31 | 32 | // Reading index 1 will force to skip the entry 15 and go to entry 40 33 | test(map.read_by_schema_index(1) == nullopt); 34 | 35 | // Reading index 2 will fail because we are already at index 3 of the schema 36 | test(map.read_by_schema_index(2) == nullopt); 37 | 38 | // We are currently at index 3 but are asking for index 5, that should skip the pair 40:3 and 50:4 and find 60:5 39 | test(dom::load_in_memory(*map.read_by_schema_index(5)) == 5ull); 40 | 41 | // We ask for index 6, which brings to index 7 (and returns null) 42 | // Asking for index 7 should return the value on an already read key 43 | test(map.read_by_schema_index(6) == nullopt); 44 | test(dom::load_in_memory(*map.read_by_schema_index(7)) == 6ull); 45 | 46 | // finally, ask for index 8, but we reach the end of the map before we find it 47 | test(map.read_by_schema_index(8) == nullopt); 48 | 49 | seek_to_end(map); 50 | } 51 | 52 | TEST_CASE(filtered_map_skip_while_on_value) 53 | { 54 | auto map = json::read(stream::read_string("{\"20\":1}")).as_map("10", "20"); 55 | 56 | test(map.read_by_schema_index(0) == nullopt); 57 | seek_to_end(map); 58 | } 59 | 60 | TEST_CASE(test_filtered_map_by_value) 61 | { 62 | auto map = json::read(stream::read_string("{\"B\":1}")).as_map("A", "B"); 63 | test(dom::load_in_memory(*map.read("B")) == 1ull); 64 | seek_to_end(map); 65 | } 66 | 67 | TEST_CASE(test_missing_seek_to_end_err) 68 | { 69 | auto a = json::read(stream::read_string("[{}]"), throw_on_error{}).as_array(); 70 | 71 | auto map = a.read().value().as_map("A", "B"); 72 | test(map.read("A") == nullopt); 73 | 74 | // Even though in this particular example, the map reached the end, it's still invalid to read from a 75 | // because map.read("A") might have returned null because "A" wasn't found (and "B" might still be in the map) 76 | expect_exception([&] { a.read(); }); 77 | } 78 | } -------------------------------------------------------------------------------- /inc/goldfish/file_stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "array_ref.h" 4 | #include "common.h" 5 | #include 6 | 7 | namespace goldfish { namespace stream 8 | { 9 | namespace details 10 | { 11 | class file_handle 12 | { 13 | public: 14 | file_handle(const char* path, const char* mode) 15 | { 16 | if (auto error = fopen_s(&m_fp, path, mode)) 17 | throw io_exception_with_error_code{ "Error during file open", error }; 18 | } 19 | file_handle(const wchar_t* path, const wchar_t* wmode) 20 | { 21 | if (auto error = _wfopen_s(&m_fp, path, wmode)) 22 | throw io_exception_with_error_code{ "Error during file open", error }; 23 | } 24 | file_handle(const std::string& path, const char* mode) 25 | : file_handle(path.c_str(), mode) 26 | {} 27 | file_handle(const std::wstring& path, const wchar_t* wmode) 28 | : file_handle(path.c_str(), wmode) 29 | {} 30 | 31 | file_handle(file_handle&& rhs) 32 | : m_fp(rhs.m_fp) 33 | { 34 | rhs.m_fp = nullptr; 35 | } 36 | ~file_handle() 37 | { 38 | if (m_fp) 39 | fclose(m_fp); 40 | } 41 | file_handle(const file_handle&) = delete; 42 | file_handle& operator = (const file_handle&) = delete; 43 | 44 | FILE* get() const { return m_fp; } 45 | private: 46 | FILE* m_fp; 47 | }; 48 | } 49 | 50 | class file_reader 51 | { 52 | public: 53 | file_reader(const char* path) 54 | : m_file(path, "rb") 55 | {} 56 | file_reader(const wchar_t* path) 57 | : m_file(path, L"rb") 58 | {} 59 | file_reader(const std::string& path) 60 | : m_file(path, "rb") 61 | {} 62 | file_reader(const std::wstring& path) 63 | : m_file(path, L"rb") 64 | {} 65 | size_t read_partial_buffer(buffer_ref data) 66 | { 67 | auto cb = fread(data.data(), 1 /*size*/, data.size() /*count*/, m_file.get()); 68 | if (cb != data.size()) 69 | { 70 | if (auto error = ferror(m_file.get())) 71 | throw io_exception_with_error_code{ "Error during file read", error }; 72 | } 73 | return cb; 74 | } 75 | private: 76 | details::file_handle m_file; 77 | }; 78 | 79 | class file_writer 80 | { 81 | public: 82 | file_writer(const char* path) 83 | : m_file(path, "wb") 84 | {} 85 | file_writer(const wchar_t* path) 86 | : m_file(path, L"wb") 87 | {} 88 | file_writer(const std::string& path) 89 | : m_file(path, "wb") 90 | {} 91 | file_writer(const std::wstring& path) 92 | : m_file(path, L"wb") 93 | {} 94 | 95 | void write_buffer(const_buffer_ref data) 96 | { 97 | if (fwrite(data.data(), 1 /*size*/, data.size() /*count*/, m_file.get()) != data.size()) 98 | throw io_exception_with_error_code{ "Error during file write", ferror(m_file.get()) }; 99 | } 100 | void flush() { } 101 | private: 102 | details::file_handle m_file; 103 | }; 104 | }} -------------------------------------------------------------------------------- /inc/goldfish/tags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common.h" 5 | #include 6 | 7 | namespace goldfish { namespace tags 8 | { 9 | template struct is_tag : std::false_type {}; 10 | struct binary {}; template <> struct is_tag : std::true_type {}; 11 | struct string {}; template <> struct is_tag : std::true_type {}; 12 | struct array {}; template <> struct is_tag : std::true_type {}; 13 | struct map {}; template <> struct is_tag : std::true_type {}; 14 | struct undefined {}; template <> struct is_tag : std::true_type {}; 15 | struct floating_point {}; template <> struct is_tag : std::true_type {}; 16 | struct unsigned_int {}; template <> struct is_tag : std::true_type {}; 17 | struct signed_int {}; template <> struct is_tag : std::true_type {}; 18 | struct boolean {}; template <> struct is_tag : std::true_type {}; 19 | struct null {}; template <> struct is_tag : std::true_type {}; 20 | struct document {}; template <> struct is_tag : std::true_type {}; 21 | using object = map; 22 | 23 | template struct tag { using type = typename T::tag; }; 24 | template <> struct tag { using type = unsigned_int; }; 25 | template <> struct tag { using type = signed_int; }; 26 | template <> struct tag { using type = boolean; }; 27 | template <> struct tag { using type = null; }; 28 | template <> struct tag { using type = floating_point; }; 29 | template <> struct tag { using type = undefined; }; 30 | template using tag_t = typename tag::type; 31 | 32 | template constexpr auto get_tag(T&&) { return tag_t>{}; } 33 | template using has_tag = std::is_same, Tag>; 34 | 35 | template struct contains_tag 36 | { 37 | enum { value = disjunction...>::value }; 38 | }; 39 | 40 | template struct type_with_tag {}; 41 | template struct type_with_tag_helper {}; 42 | template struct type_with_tag_helper 43 | { 44 | static_assert(!contains_tag::value, "Duplicate tag info"); 45 | using type = Head; 46 | }; 47 | template struct type_with_tag_helper { using type = typename type_with_tag::type; }; 48 | template struct type_with_tag 49 | { 50 | using type = typename type_with_tag_helper::value, Head, Tail...>::type; 51 | }; 52 | template using type_with_tag_t = typename type_with_tag::type; 53 | }} 54 | -------------------------------------------------------------------------------- /perf/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | using namespace goldfish; 14 | 15 | template 16 | auto measure_one(const Lambda& l) 17 | { 18 | auto before = chrono::high_resolution_clock::now(); 19 | l(); 20 | return chrono::high_resolution_clock::now() - before; 21 | } 22 | 23 | template 24 | void measure(Lambda&& l, size_t document_size) 25 | { 26 | vector durations; 27 | auto start = chrono::high_resolution_clock::now(); 28 | do 29 | { 30 | durations.push_back(chrono::duration_cast(measure_one(l))); 31 | } while (chrono::high_resolution_clock::now() - start < chrono::seconds(10)); 32 | 33 | sort(durations.begin(), durations.end()); 34 | double average_duration = (double)accumulate(durations.begin(), durations.end(), chrono::milliseconds(0)).count() / durations.size(); 35 | cout << "average: " << average_duration << "ms (" << document_size / (average_duration * 1000) << "MB/s) on " << durations.size() << " samples\n"; 36 | cout << "best: " << durations.front().count() << "ms\tworst: " << durations.back().count() << "ms\n"; 37 | } 38 | 39 | template int64_t sum_ints(Document&& t) 40 | { 41 | return t.visit(first_match( 42 | [](auto x, tags::unsigned_int) -> int64_t { return x; }, 43 | [](auto x, tags::signed_int) { return x; }, 44 | [](auto& x, tags::array) 45 | { 46 | int64_t sum = 0; 47 | while (auto d = x.read()) 48 | sum += sum_ints(*d); 49 | return sum; 50 | }, 51 | [](auto& x, tags::map) 52 | { 53 | int64_t sum = 0; 54 | while (auto key = x.read_key()) 55 | { 56 | goldfish::seek_to_end(*key); 57 | sum += sum_ints(x.read_value()); 58 | } 59 | return sum; 60 | }, 61 | [](auto& x, auto tag) { goldfish::seek_to_end(x); return 0ll; })); 62 | } 63 | 64 | int main(int argc, char* argv[]) 65 | { 66 | if (argc != 2) 67 | { 68 | cout << "This program requires a single argument which is the path to a JSON file"; 69 | return 0; 70 | } 71 | 72 | auto json_data = stream::read_all(stream::file_reader(argv[1])); 73 | auto cbor_data = [&] 74 | { 75 | auto document = json::read(stream::read_buffer_ref(json_data)); 76 | 77 | stream::vector_writer output_stream; 78 | cbor::create_writer(stream::ref(output_stream)).write(document); 79 | return output_stream.flush(); 80 | }(); 81 | 82 | cout << "\nSTREAMING MODE\n"; 83 | 84 | cout << "\nDeserialize CBOR in streaming mode\n"; 85 | measure([&] 86 | { 87 | return sum_ints(cbor::read(stream::read_buffer_ref(cbor_data))); 88 | }, cbor_data.size()); 89 | 90 | cout << "\nDeserialize JSON in streaming mode\n"; 91 | measure([&] 92 | { 93 | return sum_ints(json::read(stream::read_buffer_ref(json_data))); 94 | }, json_data.size()); 95 | } 96 | 97 | -------------------------------------------------------------------------------- /tests/stream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "unit_test.h" 3 | 4 | namespace goldfish { namespace stream { 5 | 6 | static_assert(is_reader::value, "const_buffer_ref_reader is a reader"); 7 | static_assert(!is_reader::value, "vector_writer is not a reader"); 8 | static_assert(!is_writer::value, "const_buffer_ref_reader is not a writer"); 9 | static_assert(is_writer::value, "vector_writer is a writer"); 10 | 11 | TEST_CASE(test_skip) 12 | { 13 | struct fake_stream 14 | { 15 | fake_stream(size_t size) 16 | : m_size(size) 17 | {} 18 | 19 | size_t m_size; 20 | 21 | size_t read_partial_buffer(buffer_ref buffer) 22 | { 23 | // pretend we filled buffer with data 24 | auto cb = std::min(buffer.size(), m_size); 25 | m_size -= cb; 26 | return cb; 27 | } 28 | }; 29 | static const size_t chunk_size = 8 * 1024; 30 | auto t = [&](size_t initial, size_t to_skip) 31 | { 32 | fake_stream s { initial }; 33 | test(seek(s, to_skip) == std::min(initial, to_skip)); 34 | test(s.m_size == (initial > to_skip ? initial - to_skip : 0)); 35 | }; 36 | 37 | t(0, 0); 38 | t(0, 1); 39 | t(0, typical_buffer_length); 40 | t(0, typical_buffer_length + 1); 41 | 42 | t(1, 0); 43 | t(1, 1); 44 | t(1, 2); 45 | t(1, typical_buffer_length); 46 | t(1, typical_buffer_length + 1); 47 | 48 | t(typical_buffer_length, 0); 49 | t(typical_buffer_length, 1); 50 | t(typical_buffer_length, typical_buffer_length - 1); 51 | t(typical_buffer_length, typical_buffer_length); 52 | t(typical_buffer_length, typical_buffer_length + 1); 53 | 54 | t(typical_buffer_length + 1, 0); 55 | t(typical_buffer_length + 1, 1); 56 | t(typical_buffer_length + 1, typical_buffer_length); 57 | t(typical_buffer_length + 1, typical_buffer_length + 1); 58 | t(typical_buffer_length + 1, typical_buffer_length + 2); 59 | 60 | t(typical_buffer_length * 2, 0); 61 | t(typical_buffer_length * 2, 1); 62 | t(typical_buffer_length * 2, typical_buffer_length * 2 - 1); 63 | t(typical_buffer_length * 2, typical_buffer_length * 2); 64 | t(typical_buffer_length * 2, typical_buffer_length * 2 + 1); 65 | 66 | t(typical_buffer_length * 2 + 1, 0); 67 | t(typical_buffer_length * 2 + 1, 1); 68 | t(typical_buffer_length * 2 + 1, typical_buffer_length * 2); 69 | t(typical_buffer_length * 2 + 1, typical_buffer_length * 2 + 1); 70 | t(typical_buffer_length * 2 + 1, typical_buffer_length * 2 + 2); 71 | } 72 | 73 | TEST_CASE(test_copy) 74 | { 75 | { 76 | string_writer w; 77 | copy(read_string("Hello"), w); 78 | test(w.flush() == "Hello"); 79 | } 80 | 81 | auto test_string_of_size = [](auto size) 82 | { 83 | string_writer w; 84 | copy(read_string(std::string(size, 'a')), w); 85 | test(w.flush() == std::string(size, 'a')); 86 | }; 87 | test_string_of_size(typical_buffer_length - 1); 88 | test_string_of_size(typical_buffer_length); 89 | test_string_of_size(typical_buffer_length + 1); 90 | test_string_of_size(typical_buffer_length * 2 - 1); 91 | test_string_of_size(typical_buffer_length * 2); 92 | test_string_of_size(typical_buffer_length * 2 + 1); 93 | } 94 | 95 | }} -------------------------------------------------------------------------------- /goldfish.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "src", "src\src.vcxproj", "{E76234F7-8867-4F75-B6CF-958C76E307C9}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ECD9AE6E-BDDD-41F5-8941-80FEC112EA34}" 9 | ProjectSection(SolutionItems) = preProject 10 | LICENSE = LICENSE 11 | ParsingComparison.png = ParsingComparison.png 12 | README.md = README.md 13 | SerializeComparison.png = SerializeComparison.png 14 | EndProjectSection 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests\tests.vcxproj", "{B7B1799C-2A04-472B-A607-13A9C5A5022E}" 17 | EndProject 18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "perf", "perf\perf.vcxproj", "{F5066149-B2C9-4132-9274-2442FD8C52F4}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|x64 = Debug|x64 23 | Debug|x86 = Debug|x86 24 | Release|x64 = Release|x64 25 | Release|x86 = Release|x86 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {E76234F7-8867-4F75-B6CF-958C76E307C9}.Debug|x64.ActiveCfg = Debug|x64 29 | {E76234F7-8867-4F75-B6CF-958C76E307C9}.Debug|x64.Build.0 = Debug|x64 30 | {E76234F7-8867-4F75-B6CF-958C76E307C9}.Debug|x86.ActiveCfg = Debug|Win32 31 | {E76234F7-8867-4F75-B6CF-958C76E307C9}.Debug|x86.Build.0 = Debug|Win32 32 | {E76234F7-8867-4F75-B6CF-958C76E307C9}.Release|x64.ActiveCfg = Release|x64 33 | {E76234F7-8867-4F75-B6CF-958C76E307C9}.Release|x64.Build.0 = Release|x64 34 | {E76234F7-8867-4F75-B6CF-958C76E307C9}.Release|x86.ActiveCfg = Release|Win32 35 | {E76234F7-8867-4F75-B6CF-958C76E307C9}.Release|x86.Build.0 = Release|Win32 36 | {B7B1799C-2A04-472B-A607-13A9C5A5022E}.Debug|x64.ActiveCfg = Debug|x64 37 | {B7B1799C-2A04-472B-A607-13A9C5A5022E}.Debug|x64.Build.0 = Debug|x64 38 | {B7B1799C-2A04-472B-A607-13A9C5A5022E}.Debug|x86.ActiveCfg = Debug|Win32 39 | {B7B1799C-2A04-472B-A607-13A9C5A5022E}.Debug|x86.Build.0 = Debug|Win32 40 | {B7B1799C-2A04-472B-A607-13A9C5A5022E}.Release|x64.ActiveCfg = Release|x64 41 | {B7B1799C-2A04-472B-A607-13A9C5A5022E}.Release|x64.Build.0 = Release|x64 42 | {B7B1799C-2A04-472B-A607-13A9C5A5022E}.Release|x86.ActiveCfg = Release|Win32 43 | {B7B1799C-2A04-472B-A607-13A9C5A5022E}.Release|x86.Build.0 = Release|Win32 44 | {F5066149-B2C9-4132-9274-2442FD8C52F4}.Debug|x64.ActiveCfg = Debug|x64 45 | {F5066149-B2C9-4132-9274-2442FD8C52F4}.Debug|x64.Build.0 = Debug|x64 46 | {F5066149-B2C9-4132-9274-2442FD8C52F4}.Debug|x86.ActiveCfg = Debug|Win32 47 | {F5066149-B2C9-4132-9274-2442FD8C52F4}.Debug|x86.Build.0 = Debug|Win32 48 | {F5066149-B2C9-4132-9274-2442FD8C52F4}.Release|x64.ActiveCfg = Release|x64 49 | {F5066149-B2C9-4132-9274-2442FD8C52F4}.Release|x64.Build.0 = Release|x64 50 | {F5066149-B2C9-4132-9274-2442FD8C52F4}.Release|x86.ActiveCfg = Release|Win32 51 | {F5066149-B2C9-4132-9274-2442FD8C52F4}.Release|x86.Build.0 = Release|Win32 52 | EndGlobalSection 53 | GlobalSection(SolutionProperties) = preSolution 54 | HideSolutionNode = FALSE 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /inc/goldfish/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace goldfish 8 | { 9 | using byte = uint8_t; 10 | 11 | inline uint16_t from_big_endian(uint16_t x) { return _byteswap_ushort(x); } 12 | inline uint32_t from_big_endian(uint32_t x) { return _byteswap_ulong(x); } 13 | inline uint64_t from_big_endian(uint64_t x) { return _byteswap_uint64(x); } 14 | 15 | inline uint16_t to_big_endian(uint16_t x) { return from_big_endian(x); } 16 | inline uint32_t to_big_endian(uint32_t x) { return from_big_endian(x); } 17 | inline uint64_t to_big_endian(uint64_t x) { return from_big_endian(x); } 18 | 19 | // All goldfish exceptions subclass this exception 20 | class exception : public std::exception 21 | { 22 | public: 23 | exception(const char* w) 24 | : m_what(w) 25 | {} 26 | const char* what() const noexcept override { return m_what; } 27 | private: 28 | const char* m_what; 29 | }; 30 | 31 | // Base class for all formatting errors that happen while parsing a document 32 | struct ill_formatted : exception { using exception::exception; }; 33 | 34 | // Specifically for IO errors, thrown by file_reader/writer and istream_reader/writer 35 | struct io_exception : exception { using exception::exception; }; 36 | struct io_exception_with_error_code : io_exception 37 | { 38 | io_exception_with_error_code(const char* w, int _error_code) 39 | : io_exception(w), error_code(_error_code) 40 | {} 41 | 42 | int error_code; 43 | }; 44 | 45 | // CBOR supports an "undefined" type, which is represented at runtime by the C++ type below 46 | struct undefined {}; 47 | inline bool operator == (const undefined&, const undefined&) { return true; } 48 | inline bool operator < (const undefined&, const undefined&) { return false; } 49 | 50 | // VC++ has a make_unchecked_array_iterator API to allow using raw iterators in APIs like std::copy or std::equal 51 | // We implement our own that forwards to VC++ implementation or is identity depending on the compiler 52 | template auto make_unchecked_array_iterator(T&& t) { return stdext::make_unchecked_array_iterator(std::forward(t)); } 53 | template auto get_array_iterator_from_unchecked(T&& t) { return t.base(); } 54 | 55 | template struct largest {}; 56 | template struct largest { enum { value = x }; }; 57 | template struct largest { enum { value = x > y ? x : y }; }; 58 | template struct largest { enum { value = largest::value>::value }; }; 59 | 60 | template struct conjunction {}; 61 | template <> struct conjunction<> { enum { value = true }; }; 62 | template struct conjunction { enum { value = Head::value && conjunction::value }; }; 63 | 64 | template struct disjunction {}; 65 | template <> struct disjunction<> { enum { value = false }; }; 66 | template struct disjunction { enum { value = Head::value || disjunction::value }; }; 67 | 68 | // Used for all cases where we need to read from a stream in chunks (default implementation of seek, stream copy, etc...) 69 | static const int typical_buffer_length = 8 * 1024; 70 | } -------------------------------------------------------------------------------- /tests/json_writer.cpp: -------------------------------------------------------------------------------- 1 | #include "dom.h" 2 | #include 3 | #include 4 | #include 5 | #include "unit_test.h" 6 | 7 | namespace goldfish { namespace dom 8 | { 9 | TEST_CASE(test_string) 10 | { 11 | auto w = [&](const document& d) 12 | { 13 | return json::create_writer(stream::string_writer{}).write(d); 14 | }; 15 | 16 | test(w(true) == "true"); 17 | test(w(false) == "false"); 18 | test(w(nullptr) == "null"); 19 | test(w(undefined{}) == "null"); 20 | test(w(0ull) == "0"); 21 | test(w(1ull) == "1"); 22 | test(w(std::numeric_limits::max()) == "18446744073709551615"); 23 | test(w(0ll) == "0"); 24 | test(w(1ll) == "1"); 25 | test(w(-1ll) == "-1"); 26 | test(w(std::numeric_limits::max()) == "9223372036854775807"); 27 | test(w(std::numeric_limits::min()) == "-9223372036854775808"); 28 | test(w("") == "\"\""); 29 | test(w(u8"a\u0001\b\n\r\t\"\\/") == "\"a\\u0001\\b\\n\\r\\t\\\"\\\\/\""); 30 | test(w(array{}) == "[]"); 31 | test(w(array{ 1ull }) == "[1]"); 32 | test(w(array{ 1ull, "abc", array{} }) == "[1,\"abc\",[]]"); 33 | test(w(map{}) == "{}"); 34 | test(w(map{ { "a", 1ull } }) == "{\"a\":1}"); 35 | test(w(map{ { "a", 1ull }, { "b", 2ull } }) == "{\"a\":1,\"b\":2}"); 36 | 37 | test(w(std::vector{}) == "\"\""); 38 | test(w(std::vector({ 1 })) == "\"AQ==\""); 39 | test(w(std::vector({ 1, 2, 3 })) == "\"AQID\""); 40 | test(w(map{ { 1ull, 1ull } }) == "{\"1\":1}"); 41 | } 42 | 43 | TEST_CASE(test_roundtrip) 44 | { 45 | auto run = [](const char* data) 46 | { 47 | test(json::create_writer(stream::string_writer{}).write(json::read(stream::read_string_ref(data))) == data); 48 | }; 49 | 50 | run("[null]"); 51 | run("[true]"); 52 | run("[false]"); 53 | run("[0]"); 54 | run("[\"foo\"]"); 55 | run("[]"); 56 | run("{}"); 57 | run("[0,1]"); 58 | run("{\"foo\":\"bar\"}"); 59 | run("{\"a\":null,\"foo\":\"bar\"}"); 60 | run("[-1]"); 61 | run("[-2147483648]"); 62 | run("[-1234567890123456789]"); 63 | run("[-9223372036854775808]"); 64 | run("[1]"); 65 | run("[2147483647]"); 66 | run("[4294967295]"); 67 | run("[1234567890123456789]"); 68 | run("[9223372036854775807]"); 69 | } 70 | 71 | TEST_CASE(non_string_key) 72 | { 73 | auto map = json::create_writer(stream::string_writer{}).start_map(); 74 | map.write(1ull, 1); 75 | map.write(-1ll, 2); 76 | map.write(.5, 3); 77 | map.write(stream::read_string("Key"), 4); 78 | map.write("Key", 5); 79 | 80 | test(map.flush() == R"({"1":1,"-1":2,"0.500000":3,"S2V5":4,"Key":5})"); 81 | } 82 | 83 | TEST_CASE(test_lossless_floating_point) 84 | { 85 | auto run = [](const char* data) 86 | { 87 | auto original_float = json::read(stream::read_string_ref(data)).as_double(); 88 | auto round_tripped = json::create_writer(stream::string_writer{}).write(original_float); 89 | auto new_float = json::read(stream::read_string_ref(round_tripped.c_str())).as_double(); 90 | test(original_float == new_float); 91 | }; 92 | run("0.0"); 93 | //run("-0.0"); 94 | run("1.2345"); 95 | run("-1.2345"); 96 | run("5e-324"); 97 | //run("2.225073858507201e-308"); 98 | //run("2.2250738585072014e-308"); 99 | //run("1.7976931348623157e308"); 100 | } 101 | }} -------------------------------------------------------------------------------- /tests/dom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace goldfish { namespace dom 10 | { 11 | struct document; 12 | 13 | using array = std::vector; 14 | using map = std::vector>; 15 | 16 | using map_key = variant, std::string>; 17 | using document_variant = variant< 18 | bool, 19 | nullptr_t, 20 | undefined, 21 | uint64_t, 22 | int64_t, 23 | double, 24 | std::vector, 25 | std::string, 26 | array, 27 | map>; 28 | 29 | // This struct is necessary in order to be able to forward declare "document", 30 | // which is necessary in order to define array 31 | struct document : private document_variant 32 | { 33 | document(const char* s) 34 | : document(std::string(s)) 35 | {} 36 | template document(Args&&... args) 37 | : document_variant(std::forward(args)...) 38 | {} 39 | using document_variant::as; 40 | using document_variant::is; 41 | using document_variant::visit; 42 | 43 | 44 | // variant::operator== is dangerous: x == "" resolves as 45 | // x == (bool)"". By forcing the conversion to document, we avoid this problem 46 | friend bool operator == (const document& lhs, const document& rhs) 47 | { 48 | return static_cast(lhs) == static_cast(rhs); 49 | } 50 | }; 51 | 52 | template auto serialize_to_goldfish(Writer& writer, const dom::document& document) 53 | { 54 | return document.visit(best_match( 55 | [&](bool x) { return writer.write(x); }, 56 | [&](nullptr_t x) { return writer.write(x); }, 57 | [&](undefined x) { return writer.write(x); }, 58 | [&](uint64_t x) { return writer.write(x); }, 59 | [&](int64_t x) { return writer.write(x); }, 60 | [&](double x) { return writer.write(x); }, 61 | [&](const std::vector& x) { return writer.write(const_buffer_ref{ x }); }, 62 | [&](const std::string& x) { return writer.write(x); }, 63 | [&](const dom::array& x) 64 | { 65 | auto array_writer = writer.start_array(x.size()); 66 | for (auto&& y : x) 67 | array_writer.write(y); 68 | return array_writer.flush(); 69 | }, 70 | [&](const dom::map& x) 71 | { 72 | auto map_writer = writer.start_map(x.size()); 73 | for (auto&& y : x) 74 | map_writer.write(y.first, y.second); 75 | return map_writer.flush(); 76 | })); 77 | } 78 | 79 | template std::enable_if_t, tags::document>::value, document> load_in_memory(D&& reader) 80 | { 81 | return std::forward(reader).visit(first_match( 82 | [](auto&& d, tags::binary) -> document { return stream::read_all(d); }, 83 | [](auto&& d, tags::string) -> document { return stream::read_all_as_string(d); }, 84 | [](auto&& d, tags::array) -> document 85 | { 86 | array result; 87 | while (auto x = d.read()) 88 | result.emplace_back(load_in_memory(*x)); 89 | return result; 90 | }, 91 | [](auto&& d, tags::map) -> document 92 | { 93 | map result; 94 | while (auto x = d.read_key()) 95 | { 96 | auto key = load_in_memory(*x); 97 | result.emplace_back(key, load_in_memory(d.read_value())); 98 | } 99 | return result; 100 | }, 101 | [](auto&& x, auto) -> document { return std::forward(x); } 102 | )); 103 | } 104 | }} -------------------------------------------------------------------------------- /tests/reader_writer_stream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "unit_test.h" 5 | 6 | namespace goldfish 7 | { 8 | TEST_CASE(test_reader_writer_one_byte) 9 | { 10 | auto rws = stream::create_reader_writer_stream(); 11 | 12 | std::thread reader([&] 13 | { 14 | test(stream::read(rws.reader) == 'a'); 15 | }); 16 | std::thread writer([&] 17 | { 18 | stream::write(rws.writer, 'a'); 19 | rws.writer.flush(); 20 | }); 21 | 22 | reader.join(); 23 | writer.join(); 24 | } 25 | TEST_CASE(test_reader_writer_empty_stream) 26 | { 27 | auto rws = stream::create_reader_writer_stream(); 28 | 29 | std::thread reader([&] 30 | { 31 | std::array buffer; 32 | test(rws.reader.read_partial_buffer(buffer) == 0); 33 | }); 34 | std::thread writer([&] 35 | { 36 | rws.writer.flush(); 37 | }); 38 | 39 | reader.join(); 40 | writer.join(); 41 | } 42 | TEST_CASE(test_read_empty_buffer) 43 | { 44 | auto rws = stream::create_reader_writer_stream(); 45 | 46 | std::thread reader([&] 47 | { 48 | test(rws.reader.read_partial_buffer({}) == 0); 49 | test(stream::read(rws.reader) == 'a'); 50 | }); 51 | std::thread writer([&] 52 | { 53 | stream::write(rws.writer, 'a'); 54 | rws.writer.flush(); 55 | }); 56 | 57 | reader.join(); 58 | writer.join(); 59 | } 60 | TEST_CASE(test_write_empty_buffer) 61 | { 62 | auto rws = stream::create_reader_writer_stream(); 63 | 64 | std::thread reader([&] 65 | { 66 | test(stream::read(rws.reader) == 'a'); 67 | }); 68 | std::thread writer([&] 69 | { 70 | rws.writer.write_buffer({}); 71 | stream::write(rws.writer, 'a'); 72 | rws.writer.flush(); 73 | }); 74 | 75 | reader.join(); 76 | writer.join(); 77 | } 78 | 79 | TEST_CASE(test_reader_buffer_too_small) 80 | { 81 | auto rws = stream::create_reader_writer_stream(); 82 | 83 | std::thread reader([&] 84 | { 85 | test(stream::read(rws.reader) == 'h'); 86 | test(stream::read(rws.reader) == 'e'); 87 | test(stream::read(rws.reader) == 'l'); 88 | test(stream::read(rws.reader) == 'l'); 89 | test(stream::read(rws.reader) == 'o'); 90 | }); 91 | std::thread writer([&] 92 | { 93 | stream::write(rws.writer, std::array{ 'h', 'e', 'l', 'l', 'o' }); 94 | rws.writer.flush(); 95 | }); 96 | 97 | reader.join(); 98 | writer.join(); 99 | } 100 | TEST_CASE(test_write_buffer_too_small) 101 | { 102 | auto rws = stream::create_reader_writer_stream(); 103 | 104 | std::thread reader([&] 105 | { 106 | std::array buffer; 107 | test(rws.reader.read_partial_buffer(buffer) == 1); 108 | test(buffer[0] == 'a'); 109 | }); 110 | std::thread writer([&] 111 | { 112 | stream::write(rws.writer, 'a'); 113 | rws.writer.flush(); 114 | }); 115 | 116 | reader.join(); 117 | writer.join(); 118 | } 119 | TEST_CASE(test_writer_throws_after_reader_leave) 120 | { 121 | auto rws = stream::create_reader_writer_stream(); 122 | 123 | std::thread reader([reader = std::move(rws.reader)]() mutable 124 | { 125 | test(stream::read(reader) == 'h'); 126 | }); 127 | std::thread writer([writer = std::move(rws.writer)]() mutable 128 | { 129 | expect_exception([&] 130 | { 131 | stream::write(writer, std::array{ 'h', 'e', 'l', 'l', 'o' }); 132 | }); 133 | }); 134 | 135 | reader.join(); 136 | writer.join(); 137 | } 138 | } -------------------------------------------------------------------------------- /inc/goldfish/array_ref.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "common.h" 6 | #include 7 | #include 8 | 9 | namespace goldfish 10 | { 11 | template 12 | class array_ref 13 | { 14 | public: 15 | constexpr array_ref() 16 | : m_begin(nullptr) 17 | , m_end(nullptr) 18 | {} 19 | constexpr array_ref(T* begin, T* end) 20 | : m_begin(begin) 21 | , m_end(end) 22 | {} 23 | constexpr array_ref(T* begin, size_t size) 24 | : m_begin(begin) 25 | , m_end(m_begin + size) 26 | {} 27 | template constexpr array_ref(std::array& rhs) 28 | : m_begin(rhs.data()) 29 | , m_end(rhs.data() + N) 30 | {} 31 | template constexpr array_ref(const std::array& rhs) 32 | : m_begin(rhs.data()) 33 | , m_end(rhs.data() + N) 34 | {} 35 | template constexpr array_ref(U(&rhs)[N]) 36 | : m_begin(rhs) 37 | , m_end(rhs + N) 38 | {} 39 | template array_ref(std::vector& rhs) 40 | : m_begin(rhs.data()) 41 | , m_end(rhs.data() + rhs.size()) 42 | {} 43 | template array_ref(const std::vector& rhs) 44 | : m_begin(rhs.data()) 45 | , m_end(rhs.data() + rhs.size()) 46 | {} 47 | template constexpr array_ref(array_ref rhs) 48 | : m_begin(rhs.begin()) 49 | , m_end(rhs.end()) 50 | {} 51 | 52 | constexpr T* begin() const { return m_begin; } 53 | constexpr T* end() const { return m_end; } 54 | constexpr T* data() const { return m_begin; } 55 | constexpr size_t size() const { return m_end - m_begin; } 56 | constexpr bool empty() const { return m_begin == m_end; } 57 | constexpr T& front() const { assert(!empty()); return *m_begin; } 58 | constexpr T& back() const { assert(!empty()); return *(m_end - 1); } 59 | T& pop_front() { assert(!empty()); return *(m_begin++); } 60 | constexpr T& operator[](size_t i) const { assert(i < size()); return m_begin[i]; } 61 | 62 | void clear() 63 | { 64 | m_begin = m_end; 65 | } 66 | array_ref remove_front(size_t n) 67 | { 68 | assert(n <= size()); 69 | auto b = m_begin; 70 | m_begin += n; 71 | return{ b, m_begin }; 72 | } 73 | constexpr array_ref slice_from_front(size_t c) const 74 | { 75 | assert(c <= size()); 76 | return{ m_begin, m_begin + c }; 77 | } 78 | constexpr array_ref without_front(size_t c) const 79 | { 80 | assert(s <= size()); 81 | return{ m_begin + c, m_end }; 82 | } 83 | constexpr array_ref without_end(size_t c) const 84 | { 85 | assert(c <= size()); 86 | return{ m_begin, m_end - c }; 87 | } 88 | constexpr array_ref slice(size_t from, size_t to) const 89 | { 90 | assert(to <= size()); 91 | assert(from <= to); 92 | return{ m_begin + from, m_begin + to }; 93 | } 94 | 95 | private: 96 | T* m_begin; 97 | T* m_end; 98 | }; 99 | 100 | template 101 | size_t copy(array_ref from, array_ref to) 102 | { 103 | assert(from.size() == to.size()); 104 | std::copy(from.begin(), from.end(), make_unchecked_array_iterator(to.begin())); 105 | return from.size(); 106 | } 107 | 108 | using const_buffer_ref = array_ref; 109 | using buffer_ref = array_ref; 110 | 111 | template const_buffer_ref constexpr to_buffer(const T& t) { return{ reinterpret_cast(&t), reinterpret_cast(&t + 1) }; } 112 | template buffer_ref constexpr to_buffer(T& t) { return{ reinterpret_cast(&t), reinterpret_cast(&t + 1) }; } 113 | 114 | template constexpr const_buffer_ref string_literal_to_non_null_terminated_buffer(const char(&text)[N]) 115 | { 116 | static_assert(N > 0, "expect null terminated strings"); 117 | assert(text[N - 1] == 0); 118 | return{ reinterpret_cast(text), N - 1 }; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /inc/goldfish/debug_checks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace goldfish { namespace debug_checks 8 | { 9 | struct no_check {}; 10 | struct terminate_on_error 11 | { 12 | static void on_error() { std::terminate(); } 13 | }; 14 | 15 | #ifndef GOLDFISH_DEFAULT_SHIP_ERROR_HANDLER 16 | #define GOLDFISH_DEFAULT_SHIP_ERROR_HANDLER no_check 17 | #endif 18 | 19 | #ifndef NDEBUG 20 | using default_error_handler = terminate_on_error; 21 | #else 22 | using default_error_handler = GOLDFISH_DEFAULT_SHIP_ERROR_HANDLER; 23 | #endif 24 | 25 | template class container_base 26 | { 27 | public: 28 | container_base(container_base* p) 29 | : m_parent_address_and_bits(reinterpret_cast(p)) 30 | { 31 | static_assert(alignof(container_base) % 4 == 0, "container_base need to be 4 byte aligned at least so we can use 2 bits of the address to store additional data"); 32 | assert(parent() == p); 33 | assert(!is_locked()); 34 | assert(!has_flag()); 35 | if (p) 36 | p->lock(); 37 | } 38 | container_base(const container_base&) = delete; 39 | container_base(container_base&& rhs) 40 | : m_parent_address_and_bits(rhs.m_parent_address_and_bits) 41 | { 42 | rhs.err_if_locked(); 43 | rhs.m_parent_address_and_bits = 1; 44 | assert(rhs.is_locked()); 45 | assert(rhs.parent() == nullptr); 46 | } 47 | container_base& operator = (const container_base&) = delete; 48 | container_base& operator = (container_base&&) = delete; 49 | 50 | void lock_parent() 51 | { 52 | if (auto p = parent()) 53 | p->lock(); 54 | } 55 | void unlock_parent() 56 | { 57 | assert(!is_locked()); 58 | if (auto p = parent()) 59 | p->unlock(); 60 | } 61 | protected: 62 | void set_flag() { m_parent_address_and_bits |= 2; } 63 | void clear_flag() { m_parent_address_and_bits &= ~static_cast(2); } 64 | void err_if_flag_set() const 65 | { 66 | if (has_flag()) 67 | error_handler::on_error(); 68 | } 69 | void err_if_flag_not_set() const 70 | { 71 | if (!has_flag()) 72 | error_handler::on_error(); 73 | } 74 | void err_if_locked() const 75 | { 76 | if (is_locked()) 77 | error_handler::on_error(); 78 | } 79 | void unlock_parent_and_lock_self() 80 | { 81 | unlock_parent(); 82 | lock(); 83 | } 84 | void lock() 85 | { 86 | m_parent_address_and_bits |= 1; 87 | } 88 | container_base* parent() const 89 | { 90 | return reinterpret_cast(m_parent_address_and_bits & (~static_cast(3))); 91 | } 92 | private: 93 | bool is_locked() const { return (m_parent_address_and_bits & 1) != 0; } 94 | bool has_flag() const { return (m_parent_address_and_bits & 2) != 0; } 95 | 96 | void unlock() { m_parent_address_and_bits &= ~static_cast(1); } 97 | uintptr_t m_parent_address_and_bits; 98 | }; 99 | 100 | template static std::true_type test_has_lock_parent(decltype(std::declval().lock_parent())*) { return{}; } 101 | template static std::false_type test_has_lock_parent(...) { return{}; } 102 | template struct has_lock_parent : decltype(test_has_lock_parent(nullptr)) {}; 103 | 104 | template static std::true_type test_has_unlock_parent(decltype(std::declval().unlock_parent())*) { return{}; } 105 | template static std::false_type test_has_unlock_parent(...) { return{}; } 106 | template struct has_unlock_parent : decltype(test_has_unlock_parent(nullptr)) {}; 107 | 108 | template std::enable_if_t< has_lock_parent::value, void> lock_parent(T& t) { t.lock_parent(); } 109 | template std::enable_if_t::value, void> lock_parent(T& t) { } 110 | 111 | template std::enable_if_t< has_unlock_parent::value, void> unlock_parent(T& t) { t.unlock_parent(); } 112 | template std::enable_if_t::value, void> unlock_parent(T& t) { } 113 | }} 114 | -------------------------------------------------------------------------------- /inc/goldfish/iostream_adaptor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "array_ref.h" 5 | #include "stream.h" 6 | 7 | namespace goldfish { namespace stream 8 | { 9 | class istream_reader_ref 10 | { 11 | public: 12 | istream_reader_ref(std::istream& stream) 13 | : m_stream(stream) 14 | {} 15 | size_t read_partial_buffer(buffer_ref buffer) 16 | { 17 | if (buffer.empty()) 18 | return 0; 19 | 20 | if (auto cb = m_stream.readsome(reinterpret_cast(buffer.data()), buffer.size())) 21 | return static_cast(cb); // static_cast is OK because cb <= buffer.size(), which is a size_t 22 | 23 | m_stream.read(reinterpret_cast(buffer.data()), 1); 24 | if (m_stream.bad() || (m_stream.fail() && !m_stream.eof())) 25 | throw io_exception{ "istream read failed" }; 26 | 27 | return static_cast(m_stream.gcount()); 28 | } 29 | private: 30 | std::istream& m_stream; 31 | }; 32 | class ostream_writer_ref 33 | { 34 | public: 35 | ostream_writer_ref(std::ostream& stream) 36 | : m_stream(stream) 37 | {} 38 | void write_buffer(const_buffer_ref buffer) 39 | { 40 | m_stream.write(reinterpret_cast(buffer.data()), buffer.size()); 41 | if (m_stream.fail()) 42 | throw io_exception{ "ostream write failed" }; 43 | } 44 | void flush() 45 | { 46 | m_stream.flush(); 47 | if (m_stream.fail()) 48 | throw io_exception{ "ostream flush failed" }; 49 | } 50 | private: 51 | std::ostream& m_stream; 52 | }; 53 | 54 | template class streambuf_on_reader : public std::streambuf 55 | { 56 | public: 57 | streambuf_on_reader(inner&& stream) 58 | : m_stream(std::move(stream)) 59 | { 60 | set_cb(0); 61 | } 62 | streambuf_on_reader(streambuf_on_reader&& rhs) 63 | : m_stream(std::move(rhs.m_stream)) 64 | { 65 | set_cb(rhs.get_cb()); 66 | std::copy(rhs.gptr(), rhs.egptr(), make_unchecked_array_iterator(gptr())); 67 | } 68 | streambuf_on_reader(const streambuf_on_reader&) = delete; 69 | streambuf_on_reader& operator = (const streambuf_on_reader&) = delete; 70 | streambuf_on_reader& operator = (streambuf_on_reader&&) = delete; 71 | 72 | protected: 73 | int_type underflow() override 74 | { 75 | if (gptr() < egptr()) // buffer not exhausted 76 | return traits_type::to_int_type(*gptr()); 77 | 78 | auto cb = m_stream.read_partial_buffer({ 79 | reinterpret_cast(m_buffer.data() + 1 /*putback space*/), 80 | reinterpret_cast(m_buffer.data() + m_buffer.size()) }); 81 | if (cb == 0) 82 | return traits_type::eof(); 83 | 84 | set_cb(cb); 85 | return traits_type::to_int_type(*gptr()); 86 | } 87 | private: 88 | size_t get_cb() const 89 | { 90 | return std::distance(gptr(), egptr()); 91 | } 92 | void set_cb(size_t cb) 93 | { 94 | setg( 95 | m_buffer.data(), 96 | m_buffer.data() + 1 /*putback space*/, 97 | m_buffer.data() + 1 /*putback space*/ + cb); 98 | } 99 | 100 | inner m_stream; 101 | std::array m_buffer; 102 | }; 103 | template streambuf_on_reader, N> make_streambuf(inner&& stream) { return{ std::forward(stream) }; } 104 | 105 | template class istream_with_specific_streambuf : public std::istream 106 | { 107 | public: 108 | istream_with_specific_streambuf(StreamBuf&& streambuf) 109 | : std::istream(&m_streambuf) 110 | , m_streambuf(std::move(streambuf)) 111 | {} 112 | istream_with_specific_streambuf(const istream_with_specific_streambuf&) = delete; 113 | istream_with_specific_streambuf(istream_with_specific_streambuf&& rhs) 114 | : std::istream(&m_streambuf) 115 | , m_streambuf(std::move(rhs.m_streambuf)) 116 | {} 117 | private: 118 | StreamBuf m_streambuf; 119 | }; 120 | template istream_with_specific_streambuf> make_istream_on_streambuf(StreamBuf&& streambuf) 121 | { 122 | return{ std::forward(streambuf) }; 123 | } 124 | 125 | template 126 | auto make_istream(inner&& reader) 127 | { 128 | return make_istream_on_streambuf(make_streambuf(std::forward(reader))); 129 | } 130 | }} 131 | 132 | template goldfish::stream::enable_if_reader_t operator << (std::ostream& s, Stream&& reader) 133 | { 134 | goldfish::stream::copy(reader, goldfish::stream::ostream_writer_ref{ s }); 135 | return s; 136 | } -------------------------------------------------------------------------------- /tests/buffered_stream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "unit_test.h" 4 | 5 | namespace goldfish { namespace stream 6 | { 7 | TEST_CASE(test_buffered_reader_single_character_read) 8 | { 9 | auto s = buffer<3>(read_string("abcd")); 10 | test(s.peek() == 'a'); test(stream::read(s) == 'a'); 11 | test(s.peek() == 'b'); test(stream::read(s) == 'b'); test(s.peek>() == nullopt); 12 | test(s.peek() == 'c'); test(stream::read(s) == 'c'); test(s.peek>() == nullopt); 13 | test(s.peek() == 'd'); test(stream::read(s) == 'd'); test(s.peek>() == nullopt); 14 | test(s.peek() == nullopt); 15 | } 16 | TEST_CASE(test_buffered_reader_two_character_peek_and_read) 17 | { 18 | auto s = buffer<3>(read_string("abcdef")); 19 | test(s.peek>() == std::array{ 'a', 'b' }); test(stream::read>(s) == std::array{ 'a', 'b' }); 20 | test(s.peek>() == std::array{ 'c', 'd' }); test(stream::read>(s) == std::array{ 'c', 'd' }); 21 | test(s.peek>() == std::array{ 'e', 'f' }); test(stream::read>(s) == std::array{ 'e', 'f' }); 22 | } 23 | TEST_CASE(test_buffered_reader_two_character_read) 24 | { 25 | auto s = buffer<3>(read_string("abcdef")); 26 | test(stream::read>(s) == std::array{ 'a', 'b' }); 27 | test(stream::read>(s) == std::array{ 'c', 'd' }); 28 | test(stream::read>(s) == std::array{ 'e', 'f' }); 29 | } 30 | TEST_CASE(test_buffered_reader_three_character_read) 31 | { 32 | auto s = buffer<3>(read_string("abcdefghijk")); 33 | test(s.peek>() == std::array{ 'a', 'b', 'c' }); test(stream::read>(s) == std::array{ 'a', 'b', 'c' }); 34 | test(stream::read(s) == 'd'); 35 | test(s.peek>() == std::array{ 'e', 'f', 'g' }); test(stream::read>(s) == std::array{ 'e', 'f', 'g' }); 36 | test(stream::read(s) == 'h'); 37 | test(s.peek>() == std::array{ 'i', 'j', 'k' }); test(stream::read>(s) == std::array{ 'i', 'j', 'k' }); 38 | } 39 | TEST_CASE(test_buffered_reader_four_character_read) 40 | { 41 | auto s = buffer<3>(read_string("abcdefghijkl")); 42 | test(stream::read>(s) == std::array{ 'a', 'b', 'c', 'd' }); 43 | test(stream::read>(s) == std::array{ 'e', 'f', 'g', 'h' }); 44 | test(stream::read>(s) == std::array{ 'i', 'j', 'k', 'l' }); 45 | } 46 | TEST_CASE(test_buffered_reader_read_buffer) 47 | { 48 | auto s = buffer<3>(read_string("abcdefghijkl")); 49 | { 50 | std::array buffer; 51 | test(s.read_partial_buffer(buffer) == 1); 52 | test(buffer == std::array{'a'}); 53 | } 54 | { 55 | std::array buffer; 56 | test(s.read_partial_buffer(buffer) == 2); 57 | test(buffer == std::array{'b', 'c'}); 58 | } 59 | { 60 | std::array buffer; 61 | test(s.read_partial_buffer(buffer) == 3); 62 | test(buffer == std::array{'d', 'e', 'f'}); 63 | } 64 | { 65 | std::array buffer = { 'X', 'X', 'X', 'X' }; 66 | test(s.read_partial_buffer(buffer) == 3); 67 | test(buffer == std::array{'g', 'h', 'i', 'X' }); 68 | 69 | test(s.read_partial_buffer(buffer) == 3); 70 | test(buffer == std::array{'j', 'k', 'l', 'X' }); 71 | } 72 | } 73 | TEST_CASE(test_buffered_seek) 74 | { 75 | auto s = buffer<3>(read_string("abcdef")); 76 | test(stream::seek(s, 1) == 1); 77 | test(s.peek() == 'b'); 78 | test(stream::seek(s, 1) == 1); 79 | test(s.peek() == 'c'); 80 | test(stream::seek(s, 5) == 4); 81 | test(stream::seek(s, 1) == 0); 82 | } 83 | TEST_CASE(test_move_buffered_reader) 84 | { 85 | auto s = buffer<3>(read_string("abcdef")); 86 | test(stream::read(s) == 'a'); 87 | auto t = std::move(s); 88 | test(stream::read(t) == 'b'); 89 | } 90 | TEST_CASE(test_buffered_writer) 91 | { 92 | vector_writer x; 93 | auto stream = buffer<2>(ref(x)); 94 | stream.write(1); 95 | test(x.data().empty()); 96 | 97 | stream.write(2); 98 | test(x.data().empty()); 99 | 100 | stream.flush(); 101 | 102 | test(x.data() == std::vector{1, 2}); 103 | } 104 | }} -------------------------------------------------------------------------------- /tests/base64_stream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "unit_test.h" 3 | 4 | namespace goldfish { namespace stream 5 | { 6 | std::string my_base64_encode(const std::string& data) 7 | { 8 | auto s = encode_base64_to(string_writer{}); 9 | s.write_buffer({ reinterpret_cast(data.data()), data.size() }); 10 | return s.flush(); 11 | } 12 | std::string my_base64_decode(const std::string& data) 13 | { 14 | return read_all_as_string(decode_base64(read_string_ref(data.c_str()))); 15 | } 16 | 17 | TEST_CASE(base64_encode_0) { test(my_base64_encode("") == ""); } 18 | TEST_CASE(base64_encode_1) { test(my_base64_encode("s") == "cw=="); } 19 | TEST_CASE(base64_encode_2) { test(my_base64_encode("su") == "c3U="); } 20 | TEST_CASE(base64_encode_3) { test(my_base64_encode("Man") == "TWFu"); } 21 | TEST_CASE(base64_encode_6) { test(my_base64_encode("ManMan") == "TWFuTWFu"); } 22 | TEST_CASE(base64_encode_20) { test(my_base64_encode("any carnal pleasure.") == "YW55IGNhcm5hbCBwbGVhc3VyZS4="); } 23 | 24 | TEST_CASE(base64_decode_0) { test(my_base64_decode("") == ""); } 25 | TEST_CASE(base64_decode_1) { test(my_base64_decode("cw==") == "s"); } 26 | TEST_CASE(base64_decode_2) { test(my_base64_decode("c3U=") == "su"); } 27 | TEST_CASE(base64_decode_3) { test(my_base64_decode("TWFu") == "Man"); } 28 | TEST_CASE(base64_decode_6) { test(my_base64_decode("TWFuTWFu") == "ManMan"); } 29 | TEST_CASE(base64_decode_20) { test(my_base64_decode("YW55IGNhcm5hbCBwbGVhc3VyZS4=") == "any carnal pleasure."); } 30 | 31 | TEST_CASE(base64_decode_0_no_padding) { test(my_base64_decode("") == ""); } 32 | TEST_CASE(base64_decode_1_no_padding) { test(my_base64_decode("cw") == "s"); } 33 | TEST_CASE(base64_decode_2_no_padding) { test(my_base64_decode("c3U") == "su"); } 34 | TEST_CASE(base64_decode_20_no_padding) { test(my_base64_decode("YW55IGNhcm5hbCBwbGVhc3VyZS4") == "any carnal pleasure."); } 35 | 36 | TEST_CASE(base64_decode_wrong_padding_size_1) { expect_exception([] { my_base64_decode("="); }); } 37 | TEST_CASE(base64_decode_wrong_padding_size_2) { expect_exception([] { my_base64_decode("cw="); }); } 38 | TEST_CASE(base64_decode_wrong_padding_size_3) { expect_exception([] { my_base64_decode("===="); }); } 39 | TEST_CASE(base64_padding_in_middle) { expect_exception([] { my_base64_decode("cw==cw=="); }); } 40 | 41 | TEST_CASE(decode_partial_buffer) 42 | { 43 | auto s = decode_base64(read_string("YW55IGNhcm5hbCBwbGVhc3VyZS4")); 44 | 45 | // read one at a time (this tests reading 0 and 1 bytes with left overs 0,1,2) 46 | { 47 | test(s.read_partial_buffer({}) == 0); 48 | test(stream::read(s) == 'a'); 49 | test(s.read_partial_buffer({}) == 0); 50 | test(stream::read(s) == 'n'); 51 | test(s.read_partial_buffer({}) == 0); 52 | test(stream::read(s) == 'y'); 53 | } 54 | 55 | // read two at a time (this tests reading 2 bytes with left overs 0,2,1) 56 | { 57 | byte buffer[2]; 58 | test(s.read_partial_buffer(buffer) == 2 && buffer[0] == ' ' && buffer[1] == 'c'); 59 | test(s.read_partial_buffer(buffer) == 2 && buffer[0] == 'a' && buffer[1] == 'r'); 60 | test(s.read_partial_buffer(buffer) == 2 && buffer[0] == 'n' && buffer[1] == 'a'); 61 | } 62 | 63 | // read three at a time (this tests reading 3 bytes with left overs 0,1,2) 64 | { 65 | byte buffer[3]; 66 | test(s.read_partial_buffer(buffer) == 3 && buffer[0] == 'l' && buffer[1] == ' ' && buffer[2] == 'p'); 67 | test(stream::read(s) == 'l'); 68 | test(s.read_partial_buffer(buffer) == 3 && buffer[0] == 'e' && buffer[1] == 'a' && buffer[2] == 's'); 69 | test(stream::read(s) == 'u'); 70 | test(s.read_partial_buffer(buffer) == 3 && buffer[0] == 'r' && buffer[1] == 'e' && buffer[2] == '.'); 71 | } 72 | } 73 | 74 | TEST_CASE(encode_partial_buffer) 75 | { 76 | auto s = encode_base64_to(string_writer{}); 77 | 78 | // write one at a time (this tests writing 0 and 1 byte with left overs 0,1,2) 79 | { 80 | s.write_buffer({}); 81 | stream::write(s, 'a'); 82 | s.write_buffer({}); 83 | stream::write(s, 'n'); 84 | s.write_buffer({}); 85 | stream::write(s, 'y'); 86 | } 87 | 88 | // write two at a time (this tests writing 2 bytes with left overs 0,2,1) 89 | { 90 | byte buffer[2]; 91 | buffer[0] = ' '; buffer[1] = 'c'; s.write_buffer(buffer); 92 | buffer[0] = 'a'; buffer[1] = 'r'; s.write_buffer(buffer); 93 | buffer[0] = 'n'; buffer[1] = 'a'; s.write_buffer(buffer); 94 | } 95 | 96 | // write three at a time (this tests writing 3 bytes with left overs) 97 | { 98 | byte buffer[3]; 99 | buffer[0] = 'l'; buffer[1] = ' '; buffer[2] = 'p'; s.write_buffer(buffer); 100 | stream::write(s, 'l'); 101 | buffer[0] = 'e'; buffer[1] = 'a'; buffer[2] = 's'; s.write_buffer(buffer); 102 | stream::write(s, 'u'); 103 | buffer[0] = 'r'; buffer[1] = 'e'; buffer[2] = '.'; s.write_buffer(buffer); 104 | } 105 | 106 | test(s.flush() == "YW55IGNhcm5hbCBwbGVhc3VyZS4="); 107 | } 108 | }} -------------------------------------------------------------------------------- /inc/goldfish/reader_writer_stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "array_ref.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace goldfish { namespace stream 10 | { 11 | struct reader_writer_stream_closed : exception { using exception::exception; }; 12 | 13 | namespace details 14 | { 15 | /* This acts in a similar manner to a producer consumer queue */ 16 | class reader_writer_stream 17 | { 18 | public: 19 | size_t read_partial_buffer(buffer_ref data) 20 | { 21 | auto original_size = data.size(); 22 | if (original_size == 0) 23 | return 0; 24 | 25 | std::unique_lock lock(m_mutex); 26 | assert(m_state != state::opened || m_read_buffer == nullptr); 27 | m_read_buffer = &data; 28 | m_condition_variable.notify_one(); // Wake up the writer (now that m_read_buffer is set) 29 | m_condition_variable.wait(lock, [&] { return m_state != state::opened || m_read_buffer == nullptr; }); 30 | if (m_state == state::terminated) 31 | throw reader_writer_stream_closed{ "Failed to read from the reader writer stream because the writer was closed" }; 32 | return original_size - data.size(); 33 | } 34 | 35 | void write_buffer(const_buffer_ref data) 36 | { 37 | std::unique_lock lock(m_mutex); 38 | assert(m_state != state::flushed); 39 | 40 | while (!data.empty()) 41 | { 42 | m_condition_variable.wait(lock, [&] { return m_state == state::terminated || m_read_buffer != nullptr; }); 43 | if (m_state == state::terminated) 44 | throw reader_writer_stream_closed{ "Failed to write to the reader writer stream because the reader was closed" }; 45 | assert(m_read_buffer != nullptr); 46 | 47 | auto to_copy = std::min(m_read_buffer->size(), data.size()); 48 | copy(data.remove_front(to_copy), m_read_buffer->remove_front(to_copy)); 49 | m_read_buffer = nullptr; 50 | 51 | // Wake up the reader now that m_read_buffer is null 52 | m_condition_variable.notify_one(); 53 | } 54 | } 55 | void flush() 56 | { 57 | std::unique_lock lock(m_mutex); 58 | assert(m_state != state::flushed); 59 | if (m_state == state::terminated) 60 | throw reader_writer_stream_closed{ "Failed to flush to the reader writer stream because the reader was closed" }; 61 | 62 | m_state = state::flushed; 63 | m_condition_variable.notify_one(); 64 | } 65 | void terminate() 66 | { 67 | std::unique_lock lock(m_mutex); 68 | if (m_state == state::opened) 69 | { 70 | m_state = state::terminated; 71 | m_condition_variable.notify_one(); 72 | } 73 | } 74 | 75 | private: 76 | std::mutex m_mutex; 77 | std::condition_variable m_condition_variable; 78 | buffer_ref* m_read_buffer; 79 | 80 | enum class state 81 | { 82 | opened, 83 | flushed, 84 | terminated, 85 | } m_state = state::opened; 86 | }; 87 | } 88 | 89 | class reader_on_reader_writer 90 | { 91 | public: 92 | reader_on_reader_writer(std::shared_ptr stream) 93 | : m_stream(std::move(stream)) 94 | {} 95 | reader_on_reader_writer(const reader_on_reader_writer&) = delete; 96 | reader_on_reader_writer(reader_on_reader_writer&& rhs) 97 | : m_stream(std::move(rhs.m_stream)) 98 | { 99 | rhs.m_stream = nullptr; 100 | } 101 | reader_on_reader_writer& operator = (const reader_on_reader_writer&) = delete; 102 | reader_on_reader_writer& operator = (reader_on_reader_writer&& rhs) 103 | { 104 | std::swap(m_stream, rhs.m_stream); 105 | return *this; 106 | } 107 | 108 | ~reader_on_reader_writer() 109 | { 110 | if (m_stream) 111 | m_stream->terminate(); 112 | } 113 | size_t read_partial_buffer(buffer_ref data) 114 | { 115 | return m_stream->read_partial_buffer(data); 116 | } 117 | 118 | private: 119 | std::shared_ptr m_stream; 120 | }; 121 | class writer_on_reader_writer 122 | { 123 | public: 124 | writer_on_reader_writer(std::shared_ptr stream) 125 | : m_stream(std::move(stream)) 126 | {} 127 | writer_on_reader_writer(const writer_on_reader_writer&) = delete; 128 | writer_on_reader_writer(writer_on_reader_writer&& rhs) 129 | : m_stream(std::move(rhs.m_stream)) 130 | { 131 | rhs.m_stream = nullptr; 132 | } 133 | writer_on_reader_writer& operator = (const writer_on_reader_writer&) = delete; 134 | writer_on_reader_writer& operator = (writer_on_reader_writer&& rhs) 135 | { 136 | std::swap(m_stream, rhs.m_stream); 137 | return *this; 138 | } 139 | ~writer_on_reader_writer() 140 | { 141 | if (m_stream) 142 | m_stream->terminate(); 143 | } 144 | void write_buffer(const_buffer_ref data) 145 | { 146 | m_stream->write_buffer(data); 147 | } 148 | void flush() 149 | { 150 | return m_stream->flush(); 151 | } 152 | 153 | private: 154 | std::shared_ptr m_stream; 155 | }; 156 | 157 | struct reader_writer_pair 158 | { 159 | reader_on_reader_writer reader; 160 | writer_on_reader_writer writer; 161 | }; 162 | inline reader_writer_pair create_reader_writer_stream() 163 | { 164 | auto inner = std::make_shared(); 165 | return{ 166 | reader_on_reader_writer{inner}, 167 | writer_on_reader_writer{inner} 168 | }; 169 | } 170 | }} 171 | -------------------------------------------------------------------------------- /inc/goldfish/schema.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "array_ref.h" 4 | #include "optional.h" 5 | #include "tags.h" 6 | #include 7 | 8 | namespace goldfish 9 | { 10 | namespace details 11 | { 12 | template constexpr const_buffer_ref make_key(const char(&text)[N]) 13 | { 14 | return string_literal_to_non_null_terminated_buffer(text); 15 | } 16 | template class schema 17 | { 18 | public: 19 | template schema(Args&&... args) 20 | : m_keys{ std::forward(args)... } 21 | {} 22 | 23 | optional search_key(const_buffer_ref key, size_t start_index_in_schema) const 24 | { 25 | return search_impl(key, start_index_in_schema); 26 | } 27 | template std::enable_if_t::value, optional> search(Document& d, size_t start_index_in_schema) const 28 | { 29 | return d.visit(first_match( 30 | [&](auto& text, tags::string) -> optional 31 | { 32 | byte buffer[max_length]; 33 | auto length = read_full_buffer(text, buffer); 34 | if (stream::seek(text, std::numeric_limits::max()) != 0) 35 | return nullopt; 36 | 37 | return search_impl({ buffer, length }, start_index_in_schema); 38 | }, 39 | [&](auto&, auto) -> optional 40 | { 41 | seek_to_end(d); 42 | return nullopt; /*We currently only support text strings as keys*/ 43 | })); 44 | } 45 | private: 46 | optional search_impl(const_buffer_ref text, size_t start_index_in_schema) const 47 | { 48 | auto it = std::find_if(m_keys.begin() + start_index_in_schema, m_keys.end(), [&](auto&& key) 49 | { 50 | return key.size() == text.size() && std::equal(key.begin(), key.end(), make_unchecked_array_iterator(text.begin())); 51 | }); 52 | if (it == m_keys.end()) 53 | return nullopt; 54 | else 55 | return std::distance(m_keys.begin(), it); 56 | } 57 | 58 | std::array m_keys; 59 | }; 60 | } 61 | template constexpr auto make_schema(T&&... keys) 62 | { 63 | return details::schema< 64 | sizeof...(T), 65 | largest::value - 1 // remove the null terminator 66 | >(details::make_key(std::forward(keys))...); 67 | } 68 | 69 | template class map_with_schema 70 | { 71 | public: 72 | map_with_schema(Map&& map, const Schema& schema) 73 | : m_map(std::move(map)) 74 | , m_schema(schema) 75 | {} 76 | optional().read_value())> read_by_schema_index(size_t index) 77 | { 78 | #ifndef NDEBUG 79 | if (m_last_queried_index) 80 | assert(*m_last_queried_index < index); 81 | m_last_queried_index = index; 82 | #endif 83 | 84 | if (m_index > index) 85 | return nullopt; 86 | 87 | if (m_on_value) 88 | { 89 | m_on_value = false; 90 | if (m_index == index) 91 | return m_map.read_value(); 92 | else 93 | seek_to_end(m_map.read_value()); 94 | } 95 | assert(!m_on_value); 96 | 97 | while (auto key = m_map.read_key()) 98 | { 99 | if (auto new_index = m_schema.search(*key, m_index /*start_index_in_schema*/)) 100 | { 101 | m_index = *new_index; 102 | } 103 | else 104 | { 105 | seek_to_end(m_map.read_value()); 106 | continue; 107 | } 108 | 109 | // We found a key in the schema, is it the right one? 110 | if (m_index == index) 111 | { 112 | // That's the key we were looking for, return its value 113 | // at that point, we assume not being on value any more because the caller will process the value 114 | assert(!m_on_value); 115 | return m_map.read_value(); 116 | } 117 | else if (m_index > index) 118 | { 119 | // Our key was not found (we found a key later in the list of keys) 120 | // We are on the value of that later key 121 | m_on_value = true; 122 | return nullopt; 123 | } 124 | else 125 | { 126 | // We found a key that is still before the one we are looking for, skip the value and keep searching 127 | seek_to_end(m_map.read_value()); 128 | } 129 | } 130 | 131 | // If m_map has debug checks, reading a nullptr would unlock the parent 132 | // However, the user of our class has no way to know whether the nullopt that we return indicates that we reached the end 133 | // of the map, or if it just means the key was not found. Relock the parent to ensure a call to seek_to_end is made 134 | debug_checks::lock_parent(m_map); 135 | return nullopt; 136 | } 137 | template auto read(Key&& key) 138 | { 139 | if (auto index = m_schema.search_key(details::make_key(std::forward(key)), 0 /*start_index_in_schema*/)) 140 | return read_by_schema_index(*index); 141 | else 142 | std::terminate(); 143 | } 144 | friend void seek_to_end(map_with_schema& m) 145 | { 146 | if (m.m_on_value) 147 | { 148 | goldfish::seek_to_end(m.m_map.read_value()); 149 | m.m_on_value = false; 150 | } 151 | 152 | goldfish::seek_to_end(m.m_map); 153 | debug_checks::unlock_parent(m.m_map); 154 | } 155 | private: 156 | Map m_map; 157 | Schema m_schema; 158 | size_t m_index = 0; 159 | bool m_on_value = false; 160 | 161 | #ifndef NDEBUG 162 | optional m_last_queried_index; 163 | #endif 164 | }; 165 | template map_with_schema, Schema> apply_schema(Map&& map, const Schema& s) 166 | { 167 | return{ std::forward(map), s }; 168 | } 169 | } -------------------------------------------------------------------------------- /tests/debug_checks_reader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "unit_test.h" 4 | 5 | namespace goldfish 6 | { 7 | struct library_misused {}; 8 | struct throw_on_error 9 | { 10 | static void on_error() { throw library_misused{}; } 11 | }; 12 | 13 | TEST_CASE(reading_parent_before_stream_end) 14 | { 15 | auto document = json::read(stream::read_string("[\"hello\"]"), throw_on_error{}).as_array(); 16 | 17 | auto string = document.read()->as_string(); 18 | test(stream::read(string) == 'h'); 19 | test(stream::seek(string, 1) == 1); 20 | expect_exception([&] { document.read(); }); 21 | } 22 | TEST_CASE(reading_parent_after_reading_all_ok) 23 | { 24 | auto document = json::read(stream::read_string("[\"hello\"]"), throw_on_error{}).as_array(); 25 | 26 | auto string = document.read()->as_string(); 27 | test(stream::read_all_as_string(string) == "hello"); 28 | test(document.read() == nullopt); 29 | } 30 | TEST_CASE(reading_parent_after_seeking_to_exactly_end_throws) 31 | { 32 | auto document = json::read(stream::read_string("[\"hello\"]"), throw_on_error{}).as_array(); 33 | 34 | auto string = document.read()->as_string(); 35 | test(stream::seek(string, 5) == 5); 36 | expect_exception([&] { document.read(); }); 37 | } 38 | TEST_CASE(reading_parent_after_seeking_past_end_ok) 39 | { 40 | auto document = json::read(stream::read_string("[\"hello\"]"), throw_on_error{}).as_array(); 41 | 42 | auto string = document.read()->as_string(); 43 | test(stream::seek(string, 6) == 5); 44 | test(document.read() == nullopt); 45 | } 46 | 47 | TEST_CASE(reading_parent_before_end_of_array_throws) 48 | { 49 | auto document = json::read(stream::read_string("[[1, 2]]"), throw_on_error{}).as_array(); 50 | 51 | auto array = document.read()->as_array(); 52 | test(array.read()->as_uint64() == 1); 53 | expect_exception([&] { document.read(); }); 54 | } 55 | TEST_CASE(reading_parent_at_exactly_end_of_array_throws) 56 | { 57 | auto document = json::read(stream::read_string("[[1, 2]]"), throw_on_error{}).as_array(); 58 | 59 | auto array = document.read()->as_array(); 60 | test(array.read()->as_uint64() == 1); 61 | test(array.read()->as_uint64() == 2); 62 | expect_exception([&] { document.read(); }); 63 | } 64 | TEST_CASE(reading_parent_passed_end_of_array_ok) 65 | { 66 | auto document = json::read(stream::read_string("[[1, 2]]"), throw_on_error{}).as_array(); 67 | 68 | auto array = document.read()->as_array(); 69 | test(array.read()->as_uint64() == 1); 70 | test(array.read()->as_uint64() == 2); 71 | test(array.read() == nullopt); 72 | test(document.read() == nullopt); 73 | } 74 | 75 | TEST_CASE(reading_parent_before_end_of_map_throws) 76 | { 77 | auto document = json::read(stream::read_string("[{\"a\":1, \"b\":2}]"), throw_on_error{}).as_array(); 78 | 79 | auto map = document.read()->as_map(); 80 | test(stream::read_all_as_string(map.read_key()->as_string()) == "a"); 81 | expect_exception([&] { document.read(); }); 82 | } 83 | TEST_CASE(reading_parent_at_exactly_end_of_map_throws) 84 | { 85 | auto document = json::read(stream::read_string("[{\"a\":1, \"b\":2}]"), throw_on_error{}).as_array(); 86 | 87 | auto map = document.read()->as_map(); 88 | test(stream::read_all_as_string(map.read_key()->as_string()) == "a"); 89 | test(map.read_value().as_uint64() == 1); 90 | test(stream::read_all_as_string(map.read_key()->as_string()) == "b"); 91 | test(map.read_value().as_uint64() == 2); 92 | expect_exception([&] { document.read(); }); 93 | } 94 | TEST_CASE(reading_parent_passed_end_of_map_ok) 95 | { 96 | auto document = json::read(stream::read_string("[{\"a\":1, \"b\":2}]"), throw_on_error{}).as_array(); 97 | 98 | auto map = document.read()->as_map(); 99 | test(stream::read_all_as_string(map.read_key()->as_string()) == "a"); 100 | test(map.read_value().as_uint64() == 1); 101 | test(stream::read_all_as_string(map.read_key()->as_string()) == "b"); 102 | test(map.read_value().as_uint64() == 2); 103 | test(map.read_key() == nullopt); 104 | 105 | test(document.read() == nullopt); 106 | } 107 | TEST_CASE(reading_value_before_finishing_key_in_map) 108 | { 109 | auto document = json::read(stream::read_string("[{\"a\":1, \"b\":2}]"), throw_on_error{}).as_array(); 110 | 111 | auto map = document.read()->as_map(); 112 | map.read_key(); 113 | expect_exception([&] { map.read_value(); }); 114 | } 115 | TEST_CASE(reading_key_before_finishing_value_in_map) 116 | { 117 | auto document = json::read(stream::read_string("[{\"a\":\"1\", \"b\":2}]"), throw_on_error{}).as_array(); 118 | 119 | auto map = document.read()->as_map(); 120 | test(stream::read_all_as_string(map.read_key()->as_string()) == "a"); 121 | map.read_value(); 122 | expect_exception([&] { map.read_key(); }); 123 | } 124 | TEST_CASE(reading_value_instead_of_key_in_map) 125 | { 126 | auto document = json::read(stream::read_string("[{\"a\":1, \"b\":2}]"), throw_on_error{}).as_array(); 127 | 128 | auto map = document.read()->as_map(); 129 | expect_exception([&] { map.read_value(); }); 130 | } 131 | TEST_CASE(reading_key_instead_of_value_in_map) 132 | { 133 | auto document = json::read(stream::read_string("[{\"a\":1, \"b\":2}]"), throw_on_error{}).as_array(); 134 | 135 | auto map = document.read()->as_map(); 136 | test(stream::read_all_as_string(map.read_key()->as_string()) == "a"); 137 | expect_exception([&] { map.read_key(); }); 138 | } 139 | } -------------------------------------------------------------------------------- /tests/cbor_writer.cpp: -------------------------------------------------------------------------------- 1 | #include "dom.h" 2 | #include 3 | #include 4 | #include "unit_test.h" 5 | 6 | namespace goldfish { namespace dom 7 | { 8 | static std::string to_hex_string(const std::vector& data) 9 | { 10 | std::string result; 11 | for (auto&& x : data) 12 | { 13 | result += "0123456789abcdef"[x >> 4]; 14 | result += "0123456789abcdef"[x & 0b1111]; 15 | } 16 | return result; 17 | } 18 | static uint8_t to_hex(char c) 19 | { 20 | if ('0' <= c && c <= '9') return c - '0'; 21 | else if ('a' <= c && c <= 'f') return c - 'a' + 10; 22 | else if ('A' <= c && c <= 'F') return c - 'A' + 10; 23 | else std::terminate(); 24 | }; 25 | static auto to_vector(const std::string& input) 26 | { 27 | std::vector data; 28 | for (auto it = input.begin(); it != input.end(); it += 2) 29 | { 30 | uint8_t high = to_hex(*it); 31 | uint8_t low = to_hex(*next(it)); 32 | data.push_back((high << 4) | low); 33 | } 34 | return data; 35 | }; 36 | TEST_CASE(write_valid_examples) 37 | { 38 | auto w = [&](const document& d) 39 | { 40 | return to_hex_string(cbor::create_writer(stream::vector_writer{}).write(d)); 41 | }; 42 | test(w(0ull) == "00"); 43 | test(w(1ull) == "01"); 44 | test(w(10ull) == "0a"); 45 | test(w(23ull) == "17"); 46 | test(w(24ull) == "1818"); 47 | test(w(25ull) == "1819"); 48 | test(w(100ull) == "1864"); 49 | test(w(1000ull) == "1903e8"); 50 | test(w(1000000ull) == "1a000f4240"); 51 | test(w(1000000000000ull) == "1b000000e8d4a51000"); 52 | test(w(18446744073709551615ull) == "1bffffffffffffffff"); 53 | test(w(-9223372036854775808ll) == "3b7fffffffffffffff"); 54 | 55 | test(w(-1ll) == "20"); 56 | test(w(1ll) == "01"); 57 | test(w(-10ll) == "29"); 58 | test(w(-100ll) == "3863"); 59 | test(w(-1000ll) == "3903e7"); 60 | test(w(-1000000ll) == "3a000f423f"); 61 | 62 | test(w(1.1) == "fb3ff199999999999a"); 63 | test(w(100000.0) == "fa47c35000"); 64 | test(w(3.4028234663852886e+38) == "fa7f7fffff"); 65 | test(w(1.0e+300) == "fb7e37e43c8800759c"); 66 | test(w(-4.1) == "fbc010666666666666"); 67 | test(w(std::numeric_limits::quiet_NaN()) == "fb7ff8000000000000"); 68 | test(w(std::numeric_limits::infinity()) == "fa7f800000"); 69 | test(w(-std::numeric_limits::infinity()) == "faff800000"); 70 | 71 | test(w(false) == "f4"); 72 | test(w(true) == "f5"); 73 | test(w(nullptr) == "f6"); 74 | test(w(undefined{}) == "f7"); 75 | 76 | test(w(to_vector("")) == "40"); 77 | test(w(to_vector("01020304")) == "4401020304"); 78 | 79 | test(w("") == "60"); 80 | test(w("a") == "6161"); 81 | test(w("IETF") == "6449455446"); 82 | test(w("\"\\") == "62225c"); 83 | test(w(u8"\u00fc") == "62c3bc"); 84 | test(w(u8"\u6c34") == "63e6b0b4"); 85 | 86 | test(w(array{}) == "80"); 87 | test(w(array{ 1ull, 2ull, 3ull }) == "83010203"); 88 | test(w(array{ 1ull, array{ 2ull, 3ull }, array{ 4ull, 5ull } }) == "8301820203820405"); 89 | test(w(array{ 90 | 1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull, 8ull, 9ull, 91 | 10ull, 11ull, 12ull, 13ull, 14ull, 15ull, 16ull, 92 | 17ull, 18ull, 19ull, 20ull, 21ull, 22ull, 23ull, 93 | 24ull, 25ull }) == "98190102030405060708090a0b0c0d0e0f101112131415161718181819"); 94 | 95 | test(w(map{}) == "a0"); 96 | test(w(map{ { 1ull, 2ull },{ 3ull, 4ull } }) == "a201020304"); 97 | test(w(map{ 98 | { "a", 1ull }, 99 | { "b", array{ 2ull, 3ull } } 100 | }) == "a26161016162820203"); 101 | test(w(array{ "a", map{ { "b", "c" } } }) == "826161a161626163"); 102 | test(w(map{ 103 | { "a", "A" }, 104 | { "b", "B" }, 105 | { "c", "C" }, 106 | { "d", "D" }, 107 | { "e", "E" } 108 | }) == "a56161614161626142616361436164614461656145"); 109 | } 110 | 111 | TEST_CASE(write_infinite_array) 112 | { 113 | auto w = [&](const std::vector& data) 114 | { 115 | stream::vector_writer s; 116 | auto array = cbor::create_writer(stream::ref(s)).start_array(); 117 | for (auto d : data) 118 | array.write(d); 119 | array.flush(); 120 | return to_hex_string(s.flush()); 121 | }; 122 | test(w({}) == "9fff"); 123 | 124 | test(w({ 125 | 1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull, 8ull, 126 | 9ull, 10ull, 11ull, 12ull, 13ull, 14ull, 15ull, 127 | 16ull, 17ull, 18ull, 19ull, 20ull, 21ull, 22ull, 128 | 23ull, 24ull, 25ull }) == "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"); 129 | } 130 | 131 | TEST_CASE(write_infinite_map) 132 | { 133 | auto w = [&](const std::vector>& data) 134 | { 135 | stream::vector_writer s; 136 | auto map = cbor::create_writer(stream::ref(s)).start_map(); 137 | for (auto&& d : data) 138 | map.write(d.first, d.second); 139 | 140 | map.flush(); 141 | return to_hex_string(s.flush()); 142 | }; 143 | 144 | test(w({ 145 | {"Fun", true}, 146 | {"Amt", -2ll} 147 | }) == "bf6346756ef563416d7421ff"); 148 | } 149 | 150 | TEST_CASE(write_infinite_string) 151 | { 152 | auto w = [&](const std::vector& data) 153 | { 154 | stream::vector_writer s; 155 | auto string = cbor::create_writer(stream::ref(s)).start_string(); 156 | for (auto s : data) 157 | string.write_buffer({ reinterpret_cast(s.data()), s.size() }); 158 | string.flush(); 159 | return to_hex_string(s.flush()); 160 | }; 161 | 162 | test(w({ "strea", "ming" }) == "7f657374726561646d696e67ff"); 163 | } 164 | 165 | TEST_CASE(write_infinite_binary_string) 166 | { 167 | auto w = [&](const std::vector>& data) 168 | { 169 | stream::vector_writer s; 170 | auto binary = cbor::create_writer(stream::ref(s)).start_binary(); 171 | for (auto d : data) 172 | binary.write_buffer(d); 173 | binary.flush(); 174 | return to_hex_string(s.flush()); 175 | }; 176 | 177 | test(w({ to_vector("0102"), to_vector("030405") }) == "5f42010243030405ff"); 178 | } 179 | }} -------------------------------------------------------------------------------- /inc/goldfish/debug_checks_reader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "array_ref.h" 4 | #include "debug_checks.h" 5 | #include "sax_reader.h" 6 | #include "tags.h" 7 | #include 8 | 9 | namespace goldfish { namespace debug_checks 10 | { 11 | template class string; 12 | template class array; 13 | template class map; 14 | 15 | template struct document : document_impl< 16 | Document::does_json_conversions, 17 | bool, 18 | nullptr_t, 19 | uint64_t, 20 | int64_t, 21 | double, 22 | undefined, 23 | string, tags::string>, 24 | string, tags::binary>, 25 | array>, 26 | map>> 27 | { 28 | using document_impl::document_impl; 29 | }; 30 | 31 | template document> add_read_checks_impl(container_base* parent, Document&& t); 32 | 33 | template class string : private container_base 34 | { 35 | public: 36 | using tag = _tag; 37 | string(container_base* parent, T&& inner) 38 | : container_base(parent) 39 | , m_inner(std::move(inner)) 40 | {} 41 | 42 | size_t read_partial_buffer(buffer_ref buffer) 43 | { 44 | if (buffer.empty()) 45 | return 0; 46 | 47 | auto result = m_inner.read_partial_buffer(buffer); 48 | if (result == 0) 49 | unlock_parent(); 50 | return result; 51 | } 52 | uint64_t seek(uint64_t cb) 53 | { 54 | auto skipped = stream::seek(m_inner, cb); 55 | if (skipped < cb) 56 | unlock_parent(); 57 | return skipped; 58 | } 59 | private: 60 | T m_inner; 61 | }; 62 | template string, Tag> make_string(container_base* parent, T&& inner) { return{ parent, std::forward(inner) }; } 63 | 64 | template class array : private container_base 65 | { 66 | public: 67 | using tag = tags::array; 68 | 69 | array(container_base* parent, T&& inner) 70 | : container_base(parent) 71 | , m_inner(std::move(inner)) 72 | {} 73 | 74 | optional*>(nullptr) /*parent*/, *std::declval().read()))> read() 75 | { 76 | err_if_locked(); 77 | 78 | auto d = m_inner.read(); 79 | if (d) 80 | { 81 | return add_read_checks_impl(this /*parent*/, std::move(*d)); 82 | } 83 | else 84 | { 85 | unlock_parent(); 86 | return nullopt; 87 | } 88 | } 89 | private: 90 | T m_inner; 91 | }; 92 | template array> make_array(container_base* parent, T&& inner) { return{ parent, std::forward(inner) }; } 93 | 94 | // The inheritance is public so that schema.h can use container_base to more aggressively lock the parent 95 | template class map : public container_base 96 | { 97 | public: 98 | using tag = tags::map; 99 | 100 | map(container_base* parent, T&& inner) 101 | : container_base(parent) 102 | , m_inner(std::move(inner)) 103 | {} 104 | 105 | optional*>(nullptr) /*parent*/, *std::declval().read_key()))> read_key() 106 | { 107 | err_if_locked(); 108 | err_if_flag_set(); 109 | 110 | if (auto d = m_inner.read_key()) 111 | { 112 | set_flag(); 113 | return add_read_checks_impl(this /*parent*/, std::move(*d)); 114 | } 115 | else 116 | { 117 | unlock_parent(); 118 | return nullopt; 119 | } 120 | } 121 | auto read_value() 122 | { 123 | err_if_locked(); 124 | err_if_flag_not_set(); 125 | clear_flag(); 126 | return add_read_checks_impl(this /*parent*/, m_inner.read_value()); 127 | } 128 | private: 129 | T m_inner; 130 | }; 131 | template map> make_map(container_base* parent, T&& inner) { return{ parent, std::forward(inner) }; } 132 | 133 | template document> add_read_checks_impl(container_base* parent, Document&& t) 134 | { 135 | return std::forward(t).visit(first_match( 136 | [&](auto&& x, tags::map) -> document> 137 | { 138 | return make_map(parent, std::forward(x)); 139 | }, 140 | [&](auto&& x, tags::array) -> document> 141 | { 142 | return make_array(parent, std::forward(x)); 143 | }, 144 | [&](auto&& x, tags::binary) -> document> 145 | { 146 | return make_string(parent, std::forward(x)); 147 | }, 148 | [&](auto&& x, tags::string) -> document> 149 | { 150 | return make_string(parent, std::forward(x)); 151 | }, 152 | [](auto&& x, auto) -> document> 153 | { 154 | return std::forward(x); 155 | })); 156 | } 157 | 158 | template auto add_read_checks(Document&& t, error_handler) 159 | { 160 | return add_read_checks_impl(static_cast*>(nullptr) /*parent*/, std::forward(t)); 161 | } 162 | template auto add_read_checks(Document&& t, no_check) 163 | { 164 | return std::forward(t); 165 | } 166 | }} 167 | -------------------------------------------------------------------------------- /tests/tutorial.cpp: -------------------------------------------------------------------------------- 1 | #include "unit_test.h" 2 | 3 | #include 4 | #include 5 | 6 | TEST_CASE(convert_json_to_cbor) 7 | { 8 | using namespace goldfish; 9 | 10 | // Read the string literal as a stream and parse it as a JSON document 11 | // This doesn't really do any work, the stream will be read as we parse the document 12 | auto document = json::read(stream::read_string("{\"A\":[1,2,3],\"B\":true}")); 13 | 14 | // Generate a stream on a vector, a CBOR writer around that stream and write 15 | // the JSON document to it 16 | // Note that all the streams need to be flushed to ensure that there any potentially 17 | // buffered data is serialized. 18 | auto cbor_document = cbor::create_writer(stream::vector_writer{}).write(document); 19 | test(cbor_document == std::vector{ 20 | 0xbf, // start map 21 | 0x61,0x41, // key: "A" 22 | 0x9f,0x01,0x02,0x03,0xff,// value : [1, 2, 3] 23 | 0x61,0x42, // key : "B" 24 | 0xf5, // value : true 25 | 0xff // end map 26 | }); 27 | } 28 | 29 | #include 30 | #include 31 | 32 | TEST_CASE(parse_simple) 33 | { 34 | using namespace goldfish; 35 | 36 | auto document = json::read(stream::read_string("{\"a\":1,\"c\":3.5}")).as_map("a", "b", "c"); 37 | assert(document.read("a").value().as_uint64() == 1); 38 | assert(document.read("b") == nullopt); 39 | assert(document.read("c").value().as_double() == 3.5); 40 | seek_to_end(document); 41 | } 42 | 43 | #include // to be able to output streams to cout 44 | 45 | TEST_CASE(parse_complex) 46 | { 47 | using namespace goldfish; 48 | 49 | auto document = json::read(stream::read_string( 50 | R"([ 51 | {"name":"Alice","friends":["Bob","Charlie"]}, 52 | {"name":"Bob","friends":["Alice"]} 53 | ])")).as_array(); 54 | 55 | std::stringstream output; 56 | while (auto entry_document = document.read()) 57 | { 58 | auto entry = entry_document->as_map("name", "friends"); 59 | output << entry.read("name").value().as_string() << " has the following friends: "; 60 | 61 | auto friends = entry.read("friends").value().as_array(); 62 | while (auto friend_name = friends.read()) 63 | output << friend_name->as_string() << " "; 64 | 65 | output << "\n"; 66 | seek_to_end(entry); 67 | } 68 | 69 | test(output.str() == 70 | "Alice has the following friends: Bob Charlie \n" 71 | "Bob has the following friends: Alice \n"); 72 | } 73 | 74 | #include 75 | 76 | TEST_CASE(generate_json_document) 77 | { 78 | using namespace goldfish; 79 | 80 | auto map = json::create_writer(stream::string_writer{}).start_map(); 81 | map.write("A", 1); 82 | map.write("B", "text"); 83 | map.write("C", stream::read_string("Hello world!")); 84 | 85 | // Streams are serialized as binary 64 data in JSON 86 | test(map.flush() == "{\"A\":1,\"B\":\"text\",\"C\":\"SGVsbG8gd29ybGQh\"}"); 87 | } 88 | 89 | #include 90 | 91 | TEST_CASE(generate_cbor_document) 92 | { 93 | using namespace goldfish; 94 | 95 | auto map = cbor::create_writer(stream::vector_writer{}).start_map(); 96 | map.write("A", 1); 97 | map.write("B", "text"); 98 | map.write("C", stream::read_string("Hello world!")); 99 | 100 | test(map.flush() == std::vector{ 101 | 0xbf, // start map marker 102 | 0x61,0x41, // key: "A" 103 | 0x01, // value : uint 1 104 | 0x61,0x42, // key : "B" 105 | 0x64,0x74,0x65,0x78,0x74, // value : "text" 106 | 0x61,0x43, // key : "C" 107 | 0x4c,0x48,0x65,0x6c,0x6c,0x6f,0x20, 108 | 0x77,0x6f,0x72,0x6c,0x64,0x21, // value : binary blob "Hello world!" 109 | 0xff // end of map 110 | }); 111 | } 112 | 113 | struct my_handler 114 | { 115 | template const char* operator()(Stream& s, goldfish::tags::binary) { return "binary"; } 116 | template const char* operator()(Stream& s, goldfish::tags::string) { return "string"; } 117 | template const char* operator()(ArrayReader& s, goldfish::tags::array) { return "array"; } 118 | template const char* operator()(MapReader& s, goldfish::tags::map) { return "map"; } 119 | const char* operator()(goldfish::undefined, goldfish::tags::undefined) { return "undefined"; } 120 | const char* operator()(double, goldfish::tags::floating_point) { return "floating point"; } 121 | const char* operator()(uint64_t, goldfish::tags::unsigned_int) { return "uint"; } 122 | const char* operator()(int64_t, goldfish::tags::signed_int) { return "int"; } 123 | const char* operator()(bool, goldfish::tags::boolean) { return "bool"; } 124 | const char* operator()(nullptr_t, goldfish::tags::null) { return "null"; } 125 | }; 126 | 127 | TEST_CASE(test_visit) 128 | { 129 | using namespace goldfish; 130 | my_handler sink; 131 | test(json::read(stream::read_string("true")).visit(sink) == "bool"); 132 | } 133 | 134 | TEST_CASE(test_visit_with_best_match) 135 | { 136 | using namespace goldfish; 137 | test(json::read(stream::read_string("true")).visit(best_match( 138 | [](auto&&, tags::binary) { return "binary"; }, 139 | [](auto&&, tags::string) { return "string"; }, 140 | [](auto&&, tags::array) { return "array"; }, 141 | [](auto&&, tags::map) { return "map"; }, 142 | [](undefined, tags::undefined) { return "undefined"; }, 143 | [](double, tags::floating_point) { return "floating point"; }, 144 | [](uint64_t, tags::unsigned_int) { return "uint"; }, 145 | [](int64_t, tags::signed_int) { return "int"; }, 146 | [](bool, tags::boolean) { return "bool"; }, 147 | [](nullptr_t, tags::null) { return "null"; } 148 | )) == "bool"); 149 | } 150 | 151 | TEST_CASE(test_visit_with_first_match) 152 | { 153 | using namespace goldfish; 154 | test(json::read(stream::read_string("true")).visit(first_match( 155 | [](bool, tags::boolean) { return "bool"; }, 156 | [](auto&&, auto) { return "not a bool"; } 157 | )) == "bool"); 158 | } -------------------------------------------------------------------------------- /perf/perf.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {F5066149-B2C9-4132-9274-2442FD8C52F4} 23 | perf 24 | 8.1 25 | 26 | 27 | 28 | Application 29 | true 30 | v140 31 | MultiByte 32 | 33 | 34 | Application 35 | false 36 | v140 37 | true 38 | MultiByte 39 | 40 | 41 | Application 42 | true 43 | v140 44 | MultiByte 45 | 46 | 47 | Application 48 | false 49 | v140 50 | true 51 | MultiByte 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | $(VC_IncludePath);$(WindowsSDK_IncludePath);..\inc 73 | 74 | 75 | $(VC_IncludePath);$(WindowsSDK_IncludePath);..\inc 76 | 77 | 78 | $(VC_IncludePath);$(WindowsSDK_IncludePath);..\inc 79 | 80 | 81 | $(VC_IncludePath);$(WindowsSDK_IncludePath);..\inc 82 | 83 | 84 | 85 | Level3 86 | Disabled 87 | true 88 | 89 | 90 | 91 | 92 | Level3 93 | Disabled 94 | true 95 | 96 | 97 | 98 | 99 | Level3 100 | MaxSpeed 101 | true 102 | true 103 | true 104 | _MBCS;NDEBUG;%(PreprocessorDefinitions) 105 | 106 | 107 | true 108 | true 109 | 110 | 111 | 112 | 113 | Level3 114 | MaxSpeed 115 | true 116 | true 117 | true 118 | _MBCS;NDEBUG;%(PreprocessorDefinitions) 119 | 120 | 121 | true 122 | true 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /tests/debug_checks_writer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "unit_test.h" 4 | 5 | namespace goldfish 6 | { 7 | struct library_misused {}; 8 | struct throw_on_error 9 | { 10 | static void on_error() { throw library_misused{}; } 11 | }; 12 | 13 | TEST_CASE(write_multiple_documents_on_same_writer) 14 | { 15 | stream::vector_writer output; 16 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}); 17 | writer.write(1ull); 18 | expect_exception([&] { writer.write(1ull); }); 19 | } 20 | TEST_CASE(write_on_parent_before_stream_flushed) 21 | { 22 | stream::vector_writer output; 23 | auto array = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 24 | auto stream = array.start_string(); 25 | expect_exception([&] { array.append(); }); 26 | } 27 | TEST_CASE(write_to_stream_after_flush) 28 | { 29 | stream::vector_writer output; 30 | auto array = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 31 | auto stream = array.start_string(); 32 | stream.flush(); 33 | expect_exception([&] { stream::write(stream, 'a'); }); 34 | } 35 | TEST_CASE(flush_stream_twice) 36 | { 37 | stream::vector_writer output; 38 | auto array = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 39 | auto stream = array.start_string(); 40 | stream.flush(); 41 | expect_exception([&] { stream.flush(); }); 42 | } 43 | TEST_CASE(flush_stream_without_writing_all) 44 | { 45 | stream::vector_writer output; 46 | auto array = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 47 | auto stream = array.start_string(2); 48 | stream::write(stream, 'a'); 49 | expect_exception([&] { stream.flush(); }); 50 | } 51 | TEST_CASE(write_too_much_to_stream) 52 | { 53 | stream::vector_writer output; 54 | auto array = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 55 | auto stream = array.start_string(1); 56 | stream::write(stream, 'a'); 57 | expect_exception([&] { stream::write(stream, 'b'); }); 58 | } 59 | 60 | TEST_CASE(write_on_parent_before_array_flushed) 61 | { 62 | stream::vector_writer output; 63 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 64 | auto array = writer.start_array(); 65 | expect_exception([&] { writer.append(); }); 66 | } 67 | TEST_CASE(write_to_array_after_flush) 68 | { 69 | stream::vector_writer output; 70 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 71 | auto array = writer.start_array(); 72 | array.flush(); 73 | expect_exception([&] { array.append(); }); 74 | } 75 | TEST_CASE(append_to_array_without_writing) 76 | { 77 | stream::vector_writer output; 78 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 79 | auto array = writer.start_array(); 80 | array.append(); 81 | expect_exception([&] { array.flush(); }); 82 | } 83 | TEST_CASE(flush_array_twice) 84 | { 85 | stream::vector_writer output; 86 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 87 | auto array = writer.start_array(); 88 | array.flush(); 89 | expect_exception([&] { array.flush(); }); 90 | } 91 | TEST_CASE(flush_array_without_writing_all) 92 | { 93 | stream::vector_writer output; 94 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 95 | auto array = writer.start_array(2); 96 | array.write(1ull); 97 | expect_exception([&] { array.flush(); }); 98 | } 99 | TEST_CASE(write_too_much_to_array) 100 | { 101 | stream::vector_writer output; 102 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 103 | auto array = writer.start_array(1); 104 | array.write(1ull); 105 | expect_exception([&] { array.append(); }); 106 | } 107 | 108 | TEST_CASE(write_on_parent_before_map_flushed) 109 | { 110 | stream::vector_writer output; 111 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 112 | auto map = writer.start_map(); 113 | expect_exception([&] { writer.append(); }); 114 | } 115 | TEST_CASE(write_to_map_after_flush) 116 | { 117 | stream::vector_writer output; 118 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 119 | auto map = writer.start_map(); 120 | map.flush(); 121 | expect_exception([&] { map.append_key(); }); 122 | } 123 | TEST_CASE(append_to_map_without_writing) 124 | { 125 | stream::vector_writer output; 126 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 127 | auto map = writer.start_map(); 128 | map.append_key(); 129 | expect_exception([&] { map.append_value(); }); 130 | } 131 | TEST_CASE(flush_map_twice) 132 | { 133 | stream::vector_writer output; 134 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 135 | auto map = writer.start_map(); 136 | map.flush(); 137 | expect_exception([&] { map.flush(); }); 138 | } 139 | TEST_CASE(flush_map_without_writing_all) 140 | { 141 | stream::vector_writer output; 142 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 143 | auto map = writer.start_map(2); 144 | map.write_key(1ull); 145 | map.write_value(1ull); 146 | expect_exception([&] { map.flush(); }); 147 | } 148 | TEST_CASE(write_too_much_to_map) 149 | { 150 | stream::vector_writer output; 151 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 152 | auto map = writer.start_map(1); 153 | map.write_key(1ull); 154 | map.write_value(1ull); 155 | expect_exception([&] { map.append_key(); }); 156 | } 157 | TEST_CASE(write_value_to_map_when_key_expected) 158 | { 159 | stream::vector_writer output; 160 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 161 | auto map = writer.start_map(); 162 | expect_exception([&] { map.append_value(); }); 163 | } 164 | TEST_CASE(write_key_to_map_when_value_expected) 165 | { 166 | stream::vector_writer output; 167 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 168 | auto map = writer.start_map(); 169 | map.write_key(1ull); 170 | expect_exception([&] { map.append_key(); }); 171 | } 172 | TEST_CASE(flush_map_when_value_expected) 173 | { 174 | stream::vector_writer output; 175 | auto writer = json::create_writer(stream::ref(output), throw_on_error{}).start_array(); 176 | auto map = writer.start_map(); 177 | map.write_key(1ull); 178 | expect_exception([&] { map.flush(); }); 179 | } 180 | } -------------------------------------------------------------------------------- /src/src.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {E76234F7-8867-4F75-B6CF-958C76E307C9} 47 | src 48 | 8.1 49 | 50 | 51 | 52 | Application 53 | true 54 | v140 55 | MultiByte 56 | 57 | 58 | Application 59 | false 60 | v140 61 | true 62 | MultiByte 63 | 64 | 65 | Application 66 | true 67 | v140 68 | MultiByte 69 | 70 | 71 | Application 72 | false 73 | v140 74 | true 75 | MultiByte 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | 102 | 103 | 104 | 105 | Level3 106 | Disabled 107 | true 108 | 109 | 110 | 111 | 112 | Level3 113 | MaxSpeed 114 | true 115 | true 116 | true 117 | 118 | 119 | true 120 | true 121 | 122 | 123 | 124 | 125 | Level3 126 | MaxSpeed 127 | true 128 | true 129 | true 130 | 131 | 132 | true 133 | true 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /inc/goldfish/buffered_stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stream.h" 4 | 5 | namespace goldfish { namespace stream 6 | { 7 | template class buffered_reader 8 | { 9 | public: 10 | buffered_reader(inner&& stream) 11 | : m_stream(std::move(stream)) 12 | {} 13 | buffered_reader(buffered_reader&& rhs) 14 | : m_stream(std::move(rhs.m_stream)) 15 | { 16 | m_buffered = buffer_ref(m_buffer_data.data(), rhs.m_buffered.size()); 17 | copy(rhs.m_buffered, m_buffered); 18 | } 19 | buffered_reader& operator = (const buffered_reader&) = delete; 20 | 21 | template std::enable_if_t::value, T> read() 22 | { 23 | return read_helper(std::integral_constant(), std::bool_constant()); 24 | } 25 | template std::enable_if_t::value && sizeof(T) <= N, optional> peek() 26 | { 27 | return peek_helper(std::integral_constant()); 28 | } 29 | 30 | size_t read_partial_buffer(buffer_ref data) 31 | { 32 | if (data.empty()) 33 | return 0; 34 | 35 | if (m_buffered.empty()) 36 | fill_in_buffer(); 37 | 38 | auto cb = std::min(m_buffered.size(), data.size()); 39 | copy(m_buffered.remove_front(cb), buffer_ref{ data.begin(), cb }); 40 | return cb; 41 | } 42 | 43 | uint64_t seek(uint64_t x) 44 | { 45 | if (x <= m_buffered.size()) 46 | { 47 | m_buffered.remove_front(static_cast(x)); 48 | return x; 49 | } 50 | else 51 | { 52 | auto skipped = m_buffered.size() + stream::seek(m_stream, x - m_buffered.size()); 53 | m_buffered.clear(); 54 | return skipped; 55 | } 56 | } 57 | private: 58 | template T read_helper(std::integral_constant, std::bool_constant) 59 | { 60 | T t; 61 | if (read_full_buffer(*this, { reinterpret_cast(&t), sizeof(t) }) != sizeof(t)) 62 | throw unexpected_end_of_stream(); 63 | return t; 64 | } 65 | template T read_helper(std::integral_constant /*alignment*/, std::bool_constant /*fits*/) 66 | { 67 | if (m_buffered.size() < sizeof(T)) 68 | fill_in_buffer_ensure_size(sizeof(T)); 69 | auto* data = m_buffered.data(); 70 | m_buffered.remove_front(sizeof(T)); 71 | return reinterpret_cast(*data); 72 | } 73 | template T read_helper(std::integral_constant, std::bool_constant /*fits*/) 74 | { 75 | if (m_buffered.size() < sizeof(T)) 76 | fill_in_buffer_ensure_size(sizeof(T)); 77 | T t; 78 | memcpy(&t, m_buffered.data(), sizeof(t)); 79 | m_buffered.remove_front(sizeof(t)); 80 | return t; 81 | } 82 | template optional peek_helper(std::integral_constant) 83 | { 84 | if (m_buffered.size() < sizeof(T) && !try_fill_in_buffer_ensure_size(sizeof(T))) 85 | return nullopt; 86 | return reinterpret_cast(*m_buffered.data()); 87 | } 88 | template optional peek_helper(std::integral_constant) 89 | { 90 | if (m_buffered.size() < sizeof(T) && !try_fill_in_buffer_ensure_size(sizeof(T))) 91 | return nullopt; 92 | T t; 93 | memcpy(&t, m_buffered.data(), sizeof(t)); 94 | return t; 95 | } 96 | 97 | void fill_in_buffer() 98 | { 99 | assert(m_buffered.empty()); 100 | m_buffered = { m_buffer_data.data(), m_stream.read_partial_buffer(m_buffer_data) }; 101 | } 102 | bool try_fill_in_buffer_ensure_size(size_t s) 103 | { 104 | assert(s <= N); 105 | memmove(m_buffer_data.data(), m_buffered.data(), m_buffered.size()); 106 | m_buffered = { m_buffer_data.data(), m_buffered.size() }; 107 | 108 | while (m_buffered.size() < s) 109 | { 110 | auto cb = m_stream.read_partial_buffer({ m_buffered.end(), m_buffer_data.data() + N }); 111 | if (cb == 0) 112 | return false; 113 | m_buffered = { m_buffered.begin(), m_buffered.end() + cb }; 114 | } 115 | 116 | return true; 117 | } 118 | void fill_in_buffer_ensure_size(size_t s) 119 | { 120 | if (!try_fill_in_buffer_ensure_size(s)) 121 | throw unexpected_end_of_stream(); 122 | } 123 | 124 | inner m_stream; 125 | buffer_ref m_buffered; 126 | std::array m_buffer_data; 127 | }; 128 | 129 | template 130 | class buffered_writer 131 | { 132 | public: 133 | buffered_writer(inner&& stream) 134 | : m_stream(std::move(stream)) 135 | , m_begin_free_space(m_buffer_data.data()) 136 | {} 137 | buffered_writer(buffered_writer&& rhs) 138 | : m_buffer_data(rhs.m_buffer_data) 139 | , m_begin_free_space(m_buffer_data.data() + std::distance(rhs.m_buffer_data.data(), rhs.m_begin_free_space)) 140 | , m_stream(std::move(rhs.m_stream)) 141 | {} 142 | buffered_writer& operator = (const buffered_writer&) = delete; 143 | 144 | template std::enable_if_t::value, void> write(const T& t) 145 | { 146 | write_static(reinterpret_cast(&t), std::bool_constant<(sizeof(t) < N)>()); 147 | } 148 | void write_buffer(const_buffer_ref data) 149 | { 150 | if (data.size() <= cb_free()) 151 | { 152 | m_begin_free_space = std::copy(data.begin(), data.end(), m_begin_free_space); 153 | return; 154 | } 155 | 156 | if (m_begin_free_space != m_buffer_data.data()) // If not all of the buffer is free 157 | { 158 | auto cb = cb_free(); 159 | m_begin_free_space = std::copy(data.begin(), data.begin() + cb, m_begin_free_space); 160 | data.remove_front(cb); 161 | if (data.empty()) 162 | return; 163 | send_data(); 164 | } 165 | assert(m_begin_free_space == m_buffer_data.data()); 166 | 167 | if (data.size() >= m_buffer_data.size()) 168 | m_stream.write_buffer(data); 169 | else 170 | m_begin_free_space = std::copy(data.begin(), data.end(), m_begin_free_space); 171 | } 172 | auto flush() 173 | { 174 | send_data(); 175 | return m_stream.flush(); 176 | } 177 | private: 178 | size_t cb_free() const { return m_buffer_data.data() + N - m_begin_free_space; } 179 | template void write_static(const byte* t, std::false_type /*small*/) { write_buffer({ t, cb }); } 180 | template void write_static(const byte* t, std::true_type /*small*/) 181 | { 182 | if (cb_free() < cb) 183 | send_data(); 184 | m_begin_free_space = std::copy(t, t + cb, m_begin_free_space); 185 | } 186 | template <> void write_static<1>(const byte* t, std::true_type /*small*/) 187 | { 188 | if (m_begin_free_space == m_buffer_data.data() + N) 189 | send_data(); 190 | *(m_begin_free_space++) = *t; 191 | } 192 | 193 | void send_data() 194 | { 195 | m_stream.write_buffer({ 196 | m_buffer_data.data(), 197 | m_begin_free_space 198 | }); 199 | m_begin_free_space = m_buffer_data.data(); 200 | } 201 | std::array m_buffer_data; 202 | byte* m_begin_free_space; 203 | inner m_stream; 204 | }; 205 | template enable_if_reader_t>> buffer(inner&& stream) { return{ std::forward(stream) }; } 206 | template enable_if_writer_t>> buffer(inner&& stream) { return{ std::forward(stream) }; } 207 | }} -------------------------------------------------------------------------------- /tests/cbor_reader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "dom.h" 3 | #include 4 | #include "unit_test.h" 5 | 6 | #include 7 | 8 | namespace goldfish { namespace dom 9 | { 10 | static uint8_t to_hex(char c) 11 | { 12 | if ('0' <= c && c <= '9') return c - '0'; 13 | else if ('a' <= c && c <= 'f') return c - 'a' + 10; 14 | else if ('A' <= c && c <= 'F') return c - 'A' + 10; 15 | else std::terminate(); 16 | }; 17 | static auto to_vector(const std::string& input) 18 | { 19 | std::vector data; 20 | for (auto it = input.begin(); it != input.end(); it += 2) 21 | { 22 | uint8_t high = to_hex(*it); 23 | uint8_t low = to_hex(*next(it)); 24 | data.push_back((high << 4) | low); 25 | } 26 | return data; 27 | } 28 | static auto r(std::string input) 29 | { 30 | auto binary = to_vector(input); 31 | stream::const_buffer_ref_reader s(binary); 32 | auto result = load_in_memory(cbor::read(stream::ref(s))); 33 | test(seek(s, 1) == 0); 34 | return result; 35 | }; 36 | 37 | TEST_CASE(read_valid_examples) 38 | { 39 | test(r("00") == 0ull); 40 | test(r("01") == 1ull); 41 | test(r("0a") == 10ull); 42 | test(r("17") == 23ull); 43 | test(r("1818") == 24ull); 44 | test(r("1819") == 25ull); 45 | test(r("1864") == 100ull); 46 | test(r("1903e8") == 1000ull); 47 | test(r("1a000f4240") == 1000000ull); 48 | test(r("1b000000e8d4a51000") == 1000000000000ull); 49 | test(r("1bffffffffffffffff") == 18446744073709551615ull); 50 | 51 | test(r("c249010000000000000000") == to_vector("010000000000000000")); 52 | test(r("3b7fffffffffffffff") == -9223372036854775808ll); 53 | expect_exception([&] { r("3b8000000000000000"); }); // overflow 54 | test(r("c349010000000000000000") == to_vector("010000000000000000")); 55 | 56 | test(r("20") == -1ll); 57 | test(r("29") == -10ll); 58 | test(r("3863") == -100ll); 59 | test(r("3903e7") == -1000ll); 60 | test(r("3a000f423F") == -1000000ll); 61 | 62 | test(r("f90000") == 0.0); 63 | test(r("f98000") == -0.0); 64 | test(r("f93c00") == 1.0); 65 | test(r("fb3ff199999999999a") == 1.1); 66 | test(r("f93e00") == 1.5); 67 | test(r("f97bff") == 65504.0); 68 | test(r("fa47c35000") == 100000.0); 69 | test(r("fa7f7fffff") == 3.4028234663852886e+38); 70 | test(r("fb7e37e43c8800759c") == 1.0e+300); 71 | test(r("f90001") == 5.960464477539063e-8); 72 | test(r("f90400") == 0.00006103515625); 73 | test(r("f9c400") == -4.0); 74 | test(r("fbc010666666666666") == -4.1); 75 | test(r("f97c00") == std::numeric_limits::infinity()); 76 | test(isnan(r("f97e00").as())); 77 | test(r("f9fc00") == -std::numeric_limits::infinity()); 78 | test(r("fa7f800000") == std::numeric_limits::infinity()); 79 | test(isnan(r("fa7fc00000").as())); 80 | test(r("faff800000") == -std::numeric_limits::infinity()); 81 | test(r("fb7ff0000000000000") == std::numeric_limits::infinity()); 82 | test(isnan(r("fb7ff8000000000000").as())); 83 | test(r("fbfff0000000000000") == -std::numeric_limits::infinity()); 84 | 85 | test(r("f4") == false); 86 | test(r("f5") == true); 87 | test(r("f6") == nullptr); 88 | test(r("f7") == undefined{}); 89 | 90 | test(r("c074323031332d30332d32315432303a30343a30305a") == "2013-03-21T20:04:00Z"); 91 | 92 | test(r("c11a514b67b0") == 1363896240ull); 93 | test(r("c1fb41d452d9ec200000") == 1363896240.5); 94 | test(r("c074323031332d30332d32315432303a30343a30305a") == "2013-03-21T20:04:00Z"); 95 | test(r("c074323031332d30332d32315432303a30343a30305a") == "2013-03-21T20:04:00Z"); 96 | test(r("d74401020304") == to_vector("01020304")); 97 | test(r("d818456449455446") == to_vector("6449455446")); 98 | test(r("d82076687474703a2f2f7777772e6578616d706c652e636f6d") == "http://www.example.com"); 99 | 100 | test(r("40") == to_vector("")); 101 | test(r("4401020304") == to_vector("01020304")); 102 | 103 | test(r("60") == ""); 104 | test(r("6161") == "a"); 105 | test(r("6449455446") == "IETF"); 106 | test(r("62225c") == "\"\\"); 107 | test(r("62c3bc") == u8"\u00fc"); 108 | test(r("63e6b0b4") == u8"\u6c34"); 109 | 110 | test(r("80") == array{}); 111 | test(r("83010203") == array{ 1ull, 2ull, 3ull }); 112 | test(r("8301820203820405") == array{ 1ull, array{ 2ull, 3ull }, array{ 4ull, 5ull } }); 113 | test(r("98190102030405060708090a0b0c0d0e0f101112131415161718181819") == array{ 114 | 1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull, 8ull, 9ull, 115 | 10ull, 11ull, 12ull, 13ull, 14ull, 15ull, 16ull, 116 | 17ull, 18ull, 19ull, 20ull, 21ull, 22ull, 23ull, 117 | 24ull, 25ull }); 118 | 119 | test(r("a0") == map{}); 120 | test(r("a201020304") == map{ { 1ull, 2ull }, { 3ull, 4ull } }); 121 | test(r("a26161016162820203") == map{ 122 | { "a", 1ull }, 123 | { "b", array{ 2ull, 3ull } } 124 | }); 125 | test(r("826161a161626163") == array{ "a", map{ { "b", "c" } } }); 126 | test(r("a56161614161626142616361436164614461656145") == map{ 127 | { "a", "A" }, 128 | { "b", "B" }, 129 | { "c", "C" }, 130 | { "d", "D" }, 131 | { "e", "E" } 132 | }); 133 | test(r("5f42010243030405ff") == to_vector("0102030405")); 134 | test(r("7f657374726561646d696e67ff") == "streaming"); 135 | test(r("9fff") == array{}); 136 | test(r("9f018202039f0405ffff") == array{ 1ull, array{ 2ull, 3ull }, array{ 4ull, 5ull }}); 137 | test(r("9f01820203820405ff") == array{ 1ull, array{ 2ull, 3ull }, array{ 4ull, 5ull }}); 138 | test(r("83018202039f0405ff") == array{ 1ull, array{ 2ull, 3ull }, array{ 4ull, 5ull }}); 139 | test(r("83019f0203ff820405") == array{ 1ull, array{ 2ull, 3ull }, array{ 4ull, 5ull }}); 140 | test(r("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff") == array{ 141 | 1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull, 8ull, 142 | 9ull, 10ull, 11ull, 12ull, 13ull, 14ull, 15ull, 143 | 16ull, 17ull, 18ull, 19ull, 20ull, 21ull, 22ull, 144 | 23ull, 24ull, 25ull }); 145 | test(r("bf61610161629f0203ffff") == map{ 146 | { "a", 1ull }, 147 | { "b", array{ 2ull, 3ull } } 148 | }); 149 | test(r("826161bf61626163ff") == array{ "a", map{ { "b", "c" } } }); 150 | test(r("bf6346756ef563416d7421ff") == map{ { "Fun", true }, { "Amt", -2ll } }); 151 | } 152 | 153 | TEST_CASE(seek_in_finite_string) 154 | { 155 | auto binary = to_vector("6449455446"); // IETF 156 | stream::const_buffer_ref_reader s(binary); 157 | auto result = cbor::read(stream::ref(s)).as_string(); 158 | 159 | test(stream::seek(result, 0) == 0); 160 | test(stream::read(result) == 'I'); 161 | test(stream::seek(result, 1) == 1); 162 | test(stream::read(result) == 'T'); 163 | test(stream::seek(result, 2) == 1); 164 | } 165 | 166 | TEST_CASE(seek_in_chunked_string_to_beginning_of_block) 167 | { 168 | auto binary = to_vector("7f657374726561646d696e67ff"); // "strea" "ming" 169 | stream::const_buffer_ref_reader s(binary); 170 | auto result = cbor::read(stream::ref(s)).as_string(); 171 | 172 | test(stream::seek(result, 0) == 0); 173 | test(stream::read(result) == 's'); 174 | test(stream::seek(result, 1) == 1); 175 | test(stream::read(result) == 'r'); 176 | test(stream::seek(result, 2) == 2); // seek to exactly the beginning of a block 177 | test(stream::read(result) == 'm'); 178 | test(stream::seek(result, 4) == 3); 179 | } 180 | 181 | TEST_CASE(seek_in_chunked_string_across_block) 182 | { 183 | auto binary = to_vector("7f657374726561646d696e67ff"); // "strea" "ming" 184 | stream::const_buffer_ref_reader s(binary); 185 | auto result = cbor::read(stream::ref(s)).as_string(); 186 | 187 | test(stream::seek(result, 8) == 8); 188 | test(stream::read(result) == 'g'); 189 | test(stream::seek(result, 1) == 0); 190 | } 191 | 192 | TEST_CASE(seek_in_chunked_string_all) 193 | { 194 | auto binary = to_vector("7f657374726561646d696e67ff"); // "strea" "ming" 195 | stream::const_buffer_ref_reader s(binary); 196 | test(stream::seek(cbor::read(stream::ref(s)).as_string(), 10) == 9); 197 | } 198 | }} -------------------------------------------------------------------------------- /inc/goldfish/cbor_writer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "array_ref.h" 5 | #include "common.h" 6 | #include "debug_checks_writer.h" 7 | #include 8 | #include "sax_writer.h" 9 | #include "stream.h" 10 | 11 | namespace goldfish { namespace cbor 12 | { 13 | namespace details 14 | { 15 | template void write_integer(Stream& s, uint64_t x) 16 | { 17 | if (x <= 23) 18 | { 19 | stream::write(s, static_cast((major << 5) | x)); 20 | } 21 | else if (x <= std::numeric_limits::max()) 22 | { 23 | stream::write(s, static_cast((major << 5) | 24)); 24 | stream::write(s, static_cast(x)); 25 | } 26 | else if (x <= std::numeric_limits::max()) 27 | { 28 | stream::write(s, static_cast((major << 5) | 25)); 29 | stream::write(s, to_big_endian(static_cast(x))); 30 | } 31 | else if (x <= std::numeric_limits::max()) 32 | { 33 | stream::write(s, static_cast((major << 5) | 26)); 34 | stream::write(s, to_big_endian(static_cast(x))); 35 | } 36 | else 37 | { 38 | stream::write(s, static_cast((major << 5) | 27)); 39 | stream::write(s, to_big_endian(x)); 40 | } 41 | } 42 | } 43 | 44 | template class indefinite_stream_writer 45 | { 46 | public: 47 | indefinite_stream_writer(Stream&& s) 48 | : m_stream(std::move(s)) 49 | {} 50 | void write_buffer(const_buffer_ref buffer) 51 | { 52 | details::write_integer(m_stream, buffer.size()); 53 | m_stream.write_buffer(buffer); 54 | } 55 | auto flush() 56 | { 57 | stream::write(m_stream, static_cast(0xFF)); 58 | return m_stream.flush(); 59 | } 60 | private: 61 | Stream m_stream; 62 | }; 63 | 64 | template class array_writer; 65 | template class indefinite_array_writer; 66 | template class map_writer; 67 | template class indefinite_map_writer; 68 | 69 | template class document_writer 70 | { 71 | public: 72 | document_writer(Stream&& s) 73 | : m_stream(std::move(s)) 74 | {} 75 | auto write(bool x) 76 | { 77 | if (x) stream::write(m_stream, static_cast((7 << 5) | 21)); 78 | else stream::write(m_stream, static_cast((7 << 5) | 20)); 79 | return m_stream.flush(); 80 | } 81 | auto write(nullptr_t) 82 | { 83 | stream::write(m_stream, static_cast((7 << 5) | 22)); 84 | return m_stream.flush(); 85 | } 86 | auto write(double x) 87 | { 88 | if (static_cast(x) == x) 89 | return write(static_cast(x)); 90 | 91 | static_assert(sizeof(double) == sizeof(uint64_t), "Expect 64 bit doubles"); 92 | stream::write(m_stream, static_cast((7 << 5) | 27)); 93 | auto i = *reinterpret_cast(&x); 94 | stream::write(m_stream, to_big_endian(i)); 95 | return m_stream.flush(); 96 | } 97 | auto write(float x) 98 | { 99 | static_assert(sizeof(float) == sizeof(uint32_t), "Expect 32 bit floats"); 100 | stream::write(m_stream, static_cast((7 << 5) | 26)); 101 | auto i = *reinterpret_cast(&x); 102 | stream::write(m_stream, to_big_endian(i)); 103 | return m_stream.flush(); 104 | } 105 | auto write(undefined) 106 | { 107 | stream::write(m_stream, static_cast((7 << 5) | 23)); 108 | return m_stream.flush(); 109 | } 110 | 111 | auto write(uint64_t x) 112 | { 113 | details::write_integer<0>(m_stream, x); 114 | return m_stream.flush(); 115 | } 116 | auto write(int64_t x) 117 | { 118 | if (x < 0) 119 | { 120 | details::write_integer<1>(m_stream, static_cast(-1ll - x)); 121 | return m_stream.flush(); 122 | } 123 | else 124 | { 125 | return write(static_cast(x)); 126 | } 127 | } 128 | 129 | auto start_binary(uint64_t cb) 130 | { 131 | details::write_integer<2>(m_stream, cb); 132 | return std::move(m_stream); 133 | } 134 | indefinite_stream_writer start_binary() 135 | { 136 | stream::write(m_stream, static_cast((2 << 5) | 31)); 137 | return{ std::move(m_stream) }; 138 | } 139 | 140 | auto start_string(uint64_t cb) 141 | { 142 | details::write_integer<3>(m_stream, cb); 143 | return std::move(m_stream); 144 | } 145 | indefinite_stream_writer start_string() 146 | { 147 | stream::write(m_stream, static_cast((3 << 5) | 31)); 148 | return{ std::move(m_stream) }; 149 | } 150 | 151 | array_writer start_array(uint64_t size); 152 | indefinite_array_writer start_array(); 153 | 154 | map_writer start_map(uint64_t size); 155 | indefinite_map_writer start_map(); 156 | private: 157 | Stream m_stream; 158 | }; 159 | template document_writer> create_writer_no_debug_check(Stream&& s) 160 | { 161 | return{ std::forward(s) }; 162 | } 163 | template auto create_writer(Stream&& s, error_handler e) 164 | { 165 | return sax::make_writer(debug_checks::add_write_checks(create_writer_no_debug_check(std::forward(s)), e)); 166 | } 167 | template auto create_writer(Stream&& s) 168 | { 169 | return create_writer(std::forward(s), debug_checks::default_error_handler{}); 170 | } 171 | 172 | template class array_writer 173 | { 174 | public: 175 | array_writer(Stream&& s) 176 | : m_stream(std::move(s)) 177 | {} 178 | 179 | auto append() { return create_writer_no_debug_check(stream::ref(m_stream)); } 180 | auto flush() { return m_stream.flush(); } 181 | private: 182 | Stream m_stream; 183 | }; 184 | template class indefinite_array_writer 185 | { 186 | public: 187 | indefinite_array_writer(Stream&& s) 188 | : m_stream(std::move(s)) 189 | {} 190 | auto append() { return create_writer_no_debug_check(stream::ref(m_stream)); } 191 | auto flush() 192 | { 193 | stream::write(m_stream, static_cast(0xFF)); 194 | return m_stream.flush(); 195 | } 196 | private: 197 | Stream m_stream; 198 | }; 199 | template array_writer document_writer::start_array(uint64_t size) 200 | { 201 | details::write_integer<4>(m_stream, size); 202 | return{ std::move(m_stream) }; 203 | } 204 | template indefinite_array_writer document_writer::start_array() 205 | { 206 | stream::write(m_stream, static_cast((4 << 5) | 31)); 207 | return{ std::move(m_stream) }; 208 | } 209 | 210 | template class map_writer 211 | { 212 | public: 213 | map_writer(Stream&& s) 214 | : m_stream(std::move(s)) 215 | {} 216 | document_writer> append_key() { return{ stream::ref(m_stream) }; } 217 | document_writer> append_value() { return{ stream::ref(m_stream) }; } 218 | auto flush() { return m_stream.flush(); } 219 | private: 220 | Stream m_stream; 221 | }; 222 | template class indefinite_map_writer 223 | { 224 | public: 225 | indefinite_map_writer(Stream&& s) 226 | : m_stream(std::move(s)) 227 | {} 228 | document_writer> append_key() { return{ stream::ref(m_stream) }; } 229 | document_writer> append_value() { return{ stream::ref(m_stream) }; } 230 | auto flush() 231 | { 232 | stream::write(m_stream, static_cast(0xFF)); 233 | return m_stream.flush(); 234 | } 235 | private: 236 | Stream m_stream; 237 | }; 238 | template map_writer document_writer::start_map(uint64_t size) 239 | { 240 | details::write_integer<5>(m_stream, size); 241 | return{ std::move(m_stream) }; 242 | } 243 | template indefinite_map_writer document_writer::start_map() 244 | { 245 | stream::write(m_stream, static_cast((5 << 5) | 31)); 246 | return{ std::move(m_stream) }; 247 | } 248 | }} -------------------------------------------------------------------------------- /inc/goldfish/base64_stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "array_ref.h" 4 | #include "stream.h" 5 | 6 | namespace goldfish { namespace stream 7 | { 8 | struct ill_formatted_base64_data : ill_formatted { using ill_formatted::ill_formatted; }; 9 | 10 | // Reads binary data assuming inner reads base64 11 | template class base64_reader 12 | { 13 | public: 14 | base64_reader(inner&& stream) 15 | : m_stream(std::move(stream)) 16 | {} 17 | 18 | size_t read_partial_buffer(buffer_ref data) 19 | { 20 | auto original_size = data.size(); 21 | read_from_already_parsed(data); 22 | while (data.size() >= 3) 23 | { 24 | auto c_read = deserialize_up_to_3_bytes(data); 25 | data.remove_front(c_read); 26 | if (c_read != 3) 27 | return original_size - data.size(); 28 | } 29 | 30 | if (!data.empty()) 31 | { 32 | assert(m_cb_already_parsed == 0); // because there is left over in data, read_from_already_parsed emptied m_already_parsed 33 | m_cb_already_parsed = deserialize_up_to_3_bytes(m_already_parsed); 34 | read_from_already_parsed(data); 35 | } 36 | return original_size - data.size(); 37 | } 38 | private: 39 | void read_from_already_parsed(buffer_ref& data) 40 | { 41 | auto cb_to_copy = static_cast(std::min(data.size(), m_cb_already_parsed)); 42 | copy(const_buffer_ref{ m_already_parsed.data(), m_already_parsed.data() + cb_to_copy }, data.remove_front(cb_to_copy)); 43 | m_cb_already_parsed -= cb_to_copy; 44 | std::copy(m_already_parsed.begin() + cb_to_copy, m_already_parsed.end(), m_already_parsed.begin()); 45 | } 46 | 47 | // Read up to 4 characters (or the end of stream), remove the potential padding (base64 can be padded with '=' characters at the end) 48 | // and generate up to 3 bytes of data 49 | uint8_t deserialize_up_to_3_bytes(buffer_ref output) 50 | { 51 | byte buffer[4]; 52 | auto c_read = read_full_buffer(m_stream, buffer); 53 | if (c_read == 4 && buffer[3] == '=') // Presence of padding means the stream is made of blocks of 4 bytes 54 | { 55 | if (buffer[2] == '=') 56 | c_read = 2; 57 | else 58 | c_read = 3; 59 | 60 | if (stream::seek(m_stream, 1) != 0) 61 | throw ill_formatted_base64_data{ "'=' is only allowed at the end of a base64 stream" }; 62 | } 63 | 64 | if (c_read == 0) 65 | return 0; 66 | if (c_read == 1) 67 | throw ill_formatted_base64_data{ "Unexpected number of characters in base64 stream" }; 68 | 69 | auto a = character_to_6bits(buffer[0]); 70 | auto b = character_to_6bits(buffer[1]); 71 | output[0] = ((a << 2) | (b >> 4)); 72 | if (c_read == 2) 73 | { 74 | if (b & 0xF) 75 | throw ill_formatted_base64_data{ "Invalid character at end of base64 stream" }; 76 | return 1; 77 | } 78 | 79 | auto c = character_to_6bits(buffer[2]); 80 | output[1] = (((b & 0xF) << 4) | (c >> 2)); 81 | if (c_read == 3) 82 | { 83 | if (c & 0x3) 84 | throw ill_formatted_base64_data{ "Invalid character at end of base64 stream" }; 85 | return 2; 86 | } 87 | 88 | auto d = character_to_6bits(buffer[3]); 89 | output[2] = (((c & 0x3) << 6) | d); 90 | return 3; 91 | } 92 | byte character_to_6bits(byte c) 93 | { 94 | static const byte lookup_table[] = { 95 | 64,64,64,64,64,64,64,64,64,64, 96 | 64,64,64,64,64,64,64,64,64,64, 97 | 64,64,64,64,64,64,64,64,64,64, 98 | 64,64,64,64,64,64,64,64,64,64, 99 | 64,64,64,62,64,64,64,63,52,53, 100 | 54,55,56,57,58,59,60,61,64,64, 101 | 64,65,64,64,64, 0, 1, 2, 3, 4, 102 | 5, 6, 7, 8, 9,10,11,12,13,14, 103 | 15,16,17,18,19,20,21,22,23,24, 104 | 25,64,64,64,64,64,64,26,27,28, 105 | 29,30,31,32,33,34,35,36,37,38, 106 | 39,40,41,42,43,44,45,46,47,48, 107 | 49,50,51,64,64,64,64,64,64,64, 108 | 64,64,64,64,64,64,64,64,64,64, 109 | 64,64,64,64,64,64,64,64,64,64, 110 | 64,64,64,64,64,64,64,64,64,64, 111 | 64,64,64,64,64,64,64,64,64,64, 112 | 64,64,64,64,64,64,64,64,64,64, 113 | 64,64,64,64,64,64,64,64,64,64, 114 | 64,64,64,64,64,64,64,64,64,64, 115 | 64,64,64,64,64,64,64,64,64,64, 116 | 64,64,64,64,64,64,64,64,64,64, 117 | 64,64,64,64,64,64,64,64,64,64, 118 | 64,64,64,64,64,64,64,64,64,64, 119 | 64,64,64,64,64,64,64,64,64,64, 120 | 64,64,64,64,64,64 121 | }; 122 | static_assert(sizeof(lookup_table) == 256, ""); 123 | auto result = lookup_table[c]; 124 | if (result >= 64) 125 | throw ill_formatted_base64_data{ "Invalid character in base64 stream" }; 126 | return result; 127 | } 128 | inner m_stream; 129 | std::array m_already_parsed; 130 | uint8_t m_cb_already_parsed = 0; 131 | }; 132 | 133 | // Write base64 data to inner when binary data is provided 134 | template class base64_writer 135 | { 136 | public: 137 | base64_writer(inner&& stream) 138 | : m_stream(std::move(stream)) 139 | {} 140 | base64_writer(base64_writer&&) = default; 141 | base64_writer(const base64_writer&) = delete; 142 | base64_writer& operator = (const base64_writer&) = delete; 143 | 144 | void write_buffer(const_buffer_ref data) 145 | { 146 | if (data.empty()) 147 | return; 148 | 149 | if (m_cb_pending_encoding == 1) 150 | { 151 | if (data.size() >= 2) 152 | { 153 | write_triplet(m_pending_encoding[0], data[0], data[1]); 154 | data.remove_front(2); 155 | m_cb_pending_encoding = 0; 156 | } 157 | else 158 | { 159 | m_pending_encoding[1] = data.front(); 160 | ++m_cb_pending_encoding; 161 | return; 162 | } 163 | } 164 | else if (m_cb_pending_encoding == 2) 165 | { 166 | write_triplet(m_pending_encoding[0], m_pending_encoding[1], data[0]); 167 | data.remove_front(1); 168 | m_cb_pending_encoding = 0; 169 | } 170 | 171 | while (data.size() >= 3) 172 | { 173 | write_triplet(data[0], data[1], data[2]); 174 | data.remove_front(3); 175 | } 176 | 177 | std::copy(data.begin(), data.end(), m_pending_encoding.begin()); 178 | m_cb_pending_encoding = static_cast(data.size()); 179 | } 180 | auto flush() 181 | { 182 | flush_no_inner_stream_flush(); 183 | return m_stream.flush(); 184 | } 185 | void flush_no_inner_stream_flush() 186 | { 187 | if (m_cb_pending_encoding == 1) 188 | write_triplet_flush(m_pending_encoding[0]); 189 | else if (m_cb_pending_encoding == 2) 190 | write_triplet_flush(m_pending_encoding[0], m_pending_encoding[1]); 191 | m_cb_pending_encoding = 0; 192 | } 193 | auto& inner_stream() { return m_stream; } 194 | private: 195 | byte character_from_6bits(byte x) 196 | { 197 | static const char table[65] = 198 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 199 | "abcdefghijklmnopqrstuvwxyz" 200 | "0123456789+/"; 201 | return static_cast(table[x]); 202 | } 203 | void write_triplet(uint32_t a, uint32_t b, uint32_t c) 204 | { 205 | uint32_t x = (a << 16) | (b << 8) | c; 206 | stream::write(m_stream, character_from_6bits((x >> 18) & 63)); 207 | stream::write(m_stream, character_from_6bits((x >> 12) & 63)); 208 | stream::write(m_stream, character_from_6bits((x >> 6 ) & 63)); 209 | stream::write(m_stream, character_from_6bits((x ) & 63)); 210 | } 211 | void write_triplet_flush(uint32_t a) 212 | { 213 | stream::write(m_stream, character_from_6bits((a >> 2) & 63)); 214 | stream::write(m_stream, character_from_6bits((a & 3) << 4)); 215 | stream::write(m_stream, '='); 216 | stream::write(m_stream, '='); 217 | } 218 | void write_triplet_flush(uint32_t a, uint32_t b) 219 | { 220 | uint32_t x = (a << 8) | b; 221 | stream::write(m_stream, character_from_6bits((x >> 10) & 63)); 222 | stream::write(m_stream, character_from_6bits((x >> 4) & 63)); 223 | stream::write(m_stream, character_from_6bits((x & 15) << 2)); 224 | stream::write(m_stream, '='); 225 | } 226 | 227 | inner m_stream; 228 | std::array m_pending_encoding; 229 | uint8_t m_cb_pending_encoding = 0; 230 | }; 231 | 232 | template enable_if_reader_t>> decode_base64(inner&& stream) { return{ std::forward(stream) }; } 233 | template enable_if_writer_t>> encode_base64_to(inner&& stream) { return{ std::forward(stream) }; } 234 | }} -------------------------------------------------------------------------------- /inc/goldfish/optional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "variant.h" 4 | 5 | namespace goldfish 6 | { 7 | struct nullopt_t {}; 8 | constexpr nullopt_t nullopt{}; 9 | 10 | struct bad_optional_access : exception { bad_optional_access() : exception("Dereference of nullopt") {} }; 11 | 12 | // Useful for SFINAE: enable_if_exists_t is always void, but if the expression "T" isn't valid, SFINAE will 13 | // discard the overload 14 | template struct enable_if_exists { using type = U; }; 15 | template using enable_if_exists_t = typename enable_if_exists::type; 16 | 17 | template class optional_with_invalid_base 18 | { 19 | protected: 20 | using invalid_state = typename T::invalid_state; 21 | public: 22 | optional_with_invalid_base() 23 | { 24 | invalid_state::set(m_data); 25 | } 26 | optional_with_invalid_base(const optional_with_invalid_base& rhs) 27 | { 28 | if (invalid_state::is(rhs.m_data)) 29 | invalid_state::set(m_data); 30 | else 31 | new (&m_data) T(reinterpret_cast(rhs.m_data)); 32 | } 33 | optional_with_invalid_base(optional_with_invalid_base&& rhs) 34 | { 35 | if (invalid_state::is(rhs.m_data)) 36 | invalid_state::set(m_data); 37 | else 38 | new (&m_data) T(std::move(reinterpret_cast(rhs.m_data))); 39 | } 40 | ~optional_with_invalid_base() 41 | { 42 | if (!invalid_state::is(m_data)) 43 | reinterpret_cast(m_data).~T(); 44 | } 45 | optional_with_invalid_base& operator = (const optional_with_invalid_base&) = delete; 46 | 47 | protected: 48 | std::aligned_storage_t m_data; 49 | }; 50 | template class optional_with_invalid_base 51 | { 52 | protected: 53 | optional_with_invalid_base() 54 | { 55 | invalid_state::set(m_data); 56 | } 57 | using invalid_state = typename T::invalid_state; 58 | std::aligned_storage_t m_data; 59 | }; 60 | 61 | template 62 | class optional 63 | { 64 | public: 65 | optional() = default; 66 | 67 | optional(nullopt_t) {} 68 | optional(const T& t) : m_data(t) {} 69 | optional(T&& t) : m_data(std::move(t)) {} 70 | optional& operator = (nullopt_t) { m_data = nullopt; return *this; } 71 | optional& operator = (const T& t) { m_data = t; return *this; } 72 | optional& operator = (T&& t) { m_data = std::move(t); return *this; } 73 | 74 | T& operator*() & noexcept { return m_data.as_unchecked(); } 75 | const T& operator*() const & noexcept { return m_data.as_unchecked(); } 76 | T&& operator*() && noexcept { return std::move(m_data.as_unchecked()); } 77 | 78 | T* operator ->() { return &m_data.as_unchecked(); } 79 | const T* operator ->() const { return &m_data.as_unchecked(); } 80 | explicit operator bool() const { return m_data.is(); } 81 | 82 | T& value() & { if (*this) return **this; else throw bad_optional_access{}; } 83 | const T& value() const & { if (*this) return **this; else throw bad_optional_access{}; } 84 | T&& value() && { if (*this) return *std::move(*this); else throw bad_optional_access{}; } 85 | 86 | friend bool operator == (const optional& lhs, nullopt_t) { return lhs.m_data.is(); } 87 | friend bool operator == (const optional& lhs, const T& t) { return lhs.m_data.is() && lhs.m_data.as_unchecked() == t; } 88 | friend bool operator == (const optional& lhs, const optional& rhs) { return lhs.m_data == rhs.m_data; } 89 | 90 | friend bool operator != (const optional& lhs, nullopt_t) { return !lhs.m_data.is(); } 91 | friend bool operator != (const optional& lhs, const T& t) { return !lhs.m_data.is() || lhs.m_data.as_unchecked() != t; } 92 | friend bool operator != (const optional& lhs, const optional& rhs) { return lhs.m_data != rhs.m_data; } 93 | 94 | private: 95 | variant m_data; 96 | }; 97 | 98 | 99 | template class optional> : 100 | public optional_with_invalid_base::value && 102 | std::is_trivially_move_constructible::value && 103 | std::is_trivially_destructible::value> 104 | { 105 | public: 106 | optional() = default; 107 | optional(const optional&) = default; 108 | optional(optional&&) = default; 109 | 110 | optional& operator = (const optional& rhs) 111 | { 112 | if (rhs) 113 | return *this = *rhs; 114 | else 115 | return *this = nullopt; 116 | } 117 | optional& operator = (optional&& rhs) 118 | { 119 | if (rhs) 120 | return *this = *std::move(rhs); 121 | else 122 | return *this = nullopt; 123 | } 124 | 125 | optional(nullopt_t) {} 126 | optional(const T& t) 127 | { 128 | // Work around VC++ bug: the new operator would do a null check on &m_data 129 | // Because most of our objects are trivially copy constructible, we can "just" memcpy and bypass that nullcheck 130 | __pragma(warning(suppress:4127)) 131 | if (std::is_trivially_copy_constructible::value) 132 | memcpy(&m_data, &t, sizeof(T)); 133 | else 134 | new (&m_data) T(t); 135 | assert(*this); 136 | } 137 | optional(T&& t) 138 | { 139 | // Work around VC++ bug: the new operator would do a null check on &m_data 140 | // Because most of our objects are trivially copy constructible, we can "just" memcpy and bypass that nullcheck 141 | __pragma(warning(suppress:4127)) 142 | if (std::is_trivially_move_constructible::value) 143 | memcpy(&m_data, &t, sizeof(T)); 144 | else 145 | new (&m_data) T(std::move(t)); 146 | assert(*this); 147 | } 148 | optional& operator = (nullopt_t) 149 | { 150 | if (*this) 151 | (*this)->~T(); 152 | invalid_state::set(m_data); 153 | assert(!*this); 154 | return *this; 155 | } 156 | optional& operator = (const T& t) 157 | { 158 | if (*this) 159 | { 160 | *(*this) = t; 161 | return *this; 162 | } 163 | 164 | // Work around VC++ bug: the new operator would do a null check on &m_data 165 | // Because most of our objects are trivially copy constructible, we can "just" memcpy and bypass that nullcheck 166 | __pragma(warning(suppress:4127)) 167 | if (std::is_trivially_copy_assignable::value) 168 | memcpy(&m_data, &data, sizeof(T)); 169 | else 170 | new (&m_data) T(data); 171 | assert(*this); 172 | return *this; 173 | } 174 | optional& operator = (T&& t) 175 | { 176 | if (*this) 177 | { 178 | *(*this) = std::move(t); 179 | return *this; 180 | } 181 | 182 | // Work around VC++ bug: the new operator would do a null check on &m_data 183 | // Because most of our objects are trivially copy constructible, we can "just" memcpy and bypass that nullcheck 184 | __pragma(warning(suppress:4127)) 185 | if (std::is_trivially_move_assignable::value) 186 | memcpy(&m_data, &data, sizeof(T)); 187 | else 188 | new (&m_data) T(std::move(data)); 189 | assert(*this); 190 | return *this; 191 | } 192 | 193 | T& operator*() & { assert(*this); return reinterpret_cast(m_data); } 194 | const T& operator*() const & { assert(*this); return reinterpret_cast(m_data); } 195 | T&& operator*() && { assert(*this); return std::move(reinterpret_cast(m_data)); } 196 | 197 | T* operator ->() { assert(*this); return &reinterpret_cast(m_data); } 198 | const T* operator ->() const { assert(*this); return &reinterpret_cast(m_data); } 199 | 200 | explicit operator bool() const { return !invalid_state::is(m_data); } 201 | 202 | T& value() & { if (*this) return **this; else throw bad_optional_access{}; } 203 | const T& value() const & { if (*this) return **this; else throw bad_optional_access{}; } 204 | T&& value() && { if (*this) return *std::move(*this); else throw bad_optional_access{}; } 205 | 206 | friend bool operator == (const optional& lhs, nullopt_t) { return !static_cast(lhs); } 207 | friend bool operator == (const optional& lhs, const T& t) { return lhs && *lhs == t; } 208 | friend bool operator == (const optional& lhs, const optional& rhs) 209 | { 210 | if (rhs) 211 | return lhs == *rhs; 212 | else 213 | return lhs == nullopt; 214 | } 215 | 216 | friend bool operator != (const optional& lhs, nullopt_t) { return !(lhs == nullopt); } 217 | friend bool operator != (const optional& lhs, const T& t) { return !(lhs == t); } 218 | friend bool operator != (const optional& lhs, const optional& rhs) { return !(lhs == rhs); } 219 | }; 220 | } -------------------------------------------------------------------------------- /inc/goldfish/debug_checks_writer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "debug_checks.h" 4 | #include "array_ref.h" 5 | #include "tags.h" 6 | 7 | namespace goldfish { namespace debug_checks 8 | { 9 | template class document_writer; 10 | template document_writer> add_write_checks_impl(container_base* parent, inner&& t); 11 | 12 | template class stream_writer : private container_base 13 | { 14 | public: 15 | stream_writer(container_base* parent, inner writer) 16 | : container_base(parent) 17 | , m_writer(std::move(writer)) 18 | {} 19 | void write_buffer(const_buffer_ref buffer) 20 | { 21 | err_if_locked(); 22 | m_writer.write_buffer(buffer); 23 | } 24 | auto flush() 25 | { 26 | err_if_locked(); 27 | unlock_parent_and_lock_self(); 28 | return m_writer.flush(); 29 | } 30 | private: 31 | inner m_writer; 32 | }; 33 | template stream_writer> add_write_checks_on_stream(container_base* parent, inner&& w) { return{ parent, std::forward(w) }; } 34 | 35 | template class check_size_of_stream_writer 36 | { 37 | public: 38 | check_size_of_stream_writer(inner writer, uint64_t cb) 39 | : m_writer(std::move(writer)) 40 | , m_cb_left(cb) 41 | {} 42 | void write_buffer(const_buffer_ref buffer) 43 | { 44 | if (m_cb_left < buffer.size()) 45 | error_handler::on_error(); 46 | 47 | m_writer.write_buffer(buffer); 48 | m_cb_left -= buffer.size(); 49 | } 50 | auto flush() 51 | { 52 | if (m_cb_left != 0) 53 | error_handler::on_error(); 54 | 55 | return m_writer.flush(); 56 | } 57 | private: 58 | inner m_writer; 59 | uint64_t m_cb_left; 60 | }; 61 | template check_size_of_stream_writer> check_size_of_stream(inner&& w, uint64_t cb) { return{ std::forward(w), cb }; } 62 | 63 | template class array_writer : private container_base 64 | { 65 | public: 66 | array_writer(container_base* parent, inner writer) 67 | : container_base(parent) 68 | , m_writer(std::move(writer)) 69 | {} 70 | 71 | auto append() 72 | { 73 | err_if_locked(); 74 | return add_write_checks_impl(this, m_writer.append()); 75 | } 76 | auto flush() 77 | { 78 | err_if_locked(); 79 | unlock_parent_and_lock_self(); 80 | return m_writer.flush(); 81 | } 82 | private: 83 | inner m_writer; 84 | }; 85 | template array_writer> add_write_checks_on_array(container_base* parent, inner&& w) { return{ parent, std::forward(w) }; } 86 | 87 | template class check_size_of_array_writer 88 | { 89 | public: 90 | check_size_of_array_writer(inner writer, uint64_t c) 91 | : m_writer(std::move(writer)) 92 | , m_count_left(c) 93 | {} 94 | 95 | auto append() 96 | { 97 | if (m_count_left == 0) 98 | error_handler::on_error(); 99 | --m_count_left; 100 | return m_writer.append(); 101 | } 102 | auto flush() 103 | { 104 | if (m_count_left != 0) 105 | error_handler::on_error(); 106 | return m_writer.flush(); 107 | } 108 | private: 109 | inner m_writer; 110 | uint64_t m_count_left; 111 | }; 112 | template check_size_of_array_writer> check_size_of_array(inner&& w, uint64_t c) { return{ std::forward(w), c }; } 113 | 114 | template class map_writer : private container_base 115 | { 116 | public: 117 | map_writer(container_base* parent, inner writer) 118 | : container_base(parent) 119 | , m_writer(std::move(writer)) 120 | {} 121 | 122 | auto append_key() 123 | { 124 | err_if_locked(); 125 | err_if_flag_set(); 126 | set_flag(); 127 | return add_write_checks_impl(this, m_writer.append_key()); 128 | } 129 | auto append_value() 130 | { 131 | err_if_locked(); 132 | err_if_flag_not_set(); 133 | clear_flag(); 134 | return add_write_checks_impl(this, m_writer.append_value()); 135 | } 136 | auto flush() 137 | { 138 | err_if_locked(); 139 | err_if_flag_set(); 140 | unlock_parent_and_lock_self(); 141 | return m_writer.flush(); 142 | } 143 | private: 144 | inner m_writer; 145 | }; 146 | template map_writer> add_write_checks_on_map(container_base* parent, inner&& w) { return{ parent, std::forward(w) }; } 147 | 148 | template class check_size_of_map_writer 149 | { 150 | public: 151 | check_size_of_map_writer(inner writer, uint64_t c) 152 | : m_writer(std::move(writer)) 153 | , m_c_left(c) 154 | {} 155 | 156 | auto append_key() 157 | { 158 | if (m_c_left == 0) 159 | error_handler::on_error(); 160 | --m_c_left; 161 | return m_writer.append_key(); 162 | } 163 | auto append_value() 164 | { 165 | return m_writer.append_value(); 166 | } 167 | auto flush() 168 | { 169 | if (m_c_left != 0) 170 | error_handler::on_error(); 171 | return m_writer.flush(); 172 | } 173 | private: 174 | inner m_writer; 175 | uint64_t m_c_left; 176 | }; 177 | template check_size_of_map_writer> check_size_of_map(inner&& w, uint64_t c) { return{ std::forward(w), c }; } 178 | 179 | template class document_writer : private container_base 180 | { 181 | public: 182 | document_writer(container_base* parent, inner writer) 183 | : container_base(parent) 184 | , m_writer(std::move(writer)) 185 | {} 186 | template auto write(T&& t) 187 | { 188 | err_if_locked(); 189 | unlock_parent_and_lock_self(); 190 | return m_writer.write(std::forward(t)); 191 | } 192 | 193 | auto start_binary(uint64_t cb) 194 | { 195 | err_if_locked(); 196 | auto result = check_size_of_stream(add_write_checks_on_stream(parent(), m_writer.start_binary(cb)), cb); 197 | lock(); 198 | return result; 199 | } 200 | auto start_string(uint64_t cb) 201 | { 202 | err_if_locked(); 203 | auto result = check_size_of_stream(add_write_checks_on_stream(parent(), m_writer.start_string(cb)), cb); 204 | lock(); 205 | return result; 206 | } 207 | auto start_binary() 208 | { 209 | err_if_locked(); 210 | auto result = add_write_checks_on_stream(parent(), m_writer.start_binary()); 211 | lock(); 212 | return result; 213 | } 214 | auto start_string() 215 | { 216 | err_if_locked(); 217 | auto result = add_write_checks_on_stream(parent(), m_writer.start_string()); 218 | lock(); 219 | return result; 220 | } 221 | 222 | auto start_array(uint64_t size) 223 | { 224 | err_if_locked(); 225 | auto result = check_size_of_array(add_write_checks_on_array(parent(), m_writer.start_array(size)), size); 226 | lock(); 227 | return result; 228 | } 229 | auto start_array() 230 | { 231 | err_if_locked(); 232 | auto result = add_write_checks_on_array(parent(), m_writer.start_array()); 233 | lock(); 234 | return result; 235 | } 236 | 237 | auto start_map(uint64_t size) 238 | { 239 | err_if_locked(); 240 | auto result = check_size_of_map(add_write_checks_on_map(parent(), m_writer.start_map(size)), size); 241 | lock(); 242 | return result; 243 | } 244 | auto start_map() 245 | { 246 | err_if_locked(); 247 | auto result = add_write_checks_on_map(parent(), m_writer.start_map()); 248 | lock(); 249 | return result; 250 | } 251 | private: 252 | inner m_writer; 253 | }; 254 | 255 | template document_writer> add_write_checks_impl(container_base* parent, inner&& t) 256 | { 257 | return{ parent, std::forward(t) }; 258 | } 259 | 260 | template auto add_write_checks(Document&& t, error_handler) 261 | { 262 | return add_write_checks_impl(nullptr /*parent*/, std::forward(t)); 263 | } 264 | template auto add_write_checks(Document&& t, no_check) 265 | { 266 | return std::forward(t); 267 | } 268 | }} -------------------------------------------------------------------------------- /inc/goldfish/sax_writer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "match.h" 4 | #include "stream.h" 5 | #include "tags.h" 6 | #include 7 | 8 | namespace goldfish { namespace sax 9 | { 10 | template class document_writer; 11 | template document_writer> make_writer(inner&& writer); 12 | 13 | template class array_writer 14 | { 15 | public: 16 | array_writer(inner&& writer) 17 | : m_writer(std::move(writer)) 18 | {} 19 | 20 | template void write(T&& t) { append().write(std::forward(t)); } 21 | auto start_binary(uint64_t cb) { return append().start_binary(cb); } 22 | auto start_binary() { return append().start_binary(); } 23 | auto start_string(uint64_t cb) { return append().start_string(cb); } 24 | auto start_string() { return append().start_string(); } 25 | auto start_array(uint64_t size) { return append().start_array(size); } 26 | auto start_array() { return append().start_array(); } 27 | auto start_map(uint64_t size) { return append().start_map(size); } 28 | auto start_map() { return append().start_map(); } 29 | 30 | auto append() { return make_writer(m_writer.append()); } 31 | auto flush() { return m_writer.flush(); } 32 | private: 33 | inner m_writer; 34 | }; 35 | template array_writer> make_array_writer(inner&& writer) { return{ std::forward(writer) }; } 36 | 37 | template class map_writer 38 | { 39 | public: 40 | map_writer(inner&& writer) 41 | : m_writer(std::move(writer)) 42 | {} 43 | 44 | auto append_key() { return make_writer(m_writer.append_key()); } 45 | auto append_value() { return make_writer(m_writer.append_value()); } 46 | 47 | // Write the key, don't start the value 48 | template void write_key(T&& t) { append_key().write(std::forward(t)); } 49 | auto start_binary_key(uint64_t cb) { return append_key().start_binary(cb); } 50 | auto start_binary_key() { return append_key().start_binary(); } 51 | auto start_string_key(uint64_t cb) { return append_key().start_string(cb); } 52 | auto start_string_key() { return append_key().start_string(); } 53 | auto start_array_key(uint64_t size) { return append_key().start_array(size); } 54 | auto start_array_key() { return append_key().start_array(); } 55 | auto start_map_key(uint64_t size) { return append_key().start_map(size); } 56 | auto start_map_key() { return append_key().start_map(); } 57 | 58 | // Write the value after having used one of the above 59 | template void write_value(T&& t) { append_value().write(std::forward(t)); } 60 | auto start_binary_value(uint64_t cb) { return append_value().start_binary(cb); } 61 | auto start_binary_value() { return append_value().start_binary(); } 62 | auto start_string_value(uint64_t cb) { return append_value().start_string(cb); } 63 | auto start_string_value() { return append_value().start_string(); } 64 | auto start_array_value(uint64_t size) { return append_value().start_array(size); } 65 | auto start_array_value() { return append_value().start_array(); } 66 | auto start_map_value(uint64_t size) { return append_value().start_map(size); } 67 | auto start_map_value() { return append_value().start_map(); } 68 | 69 | // Write the key, start the value 70 | template auto append(T&& t) 71 | { 72 | append_key().write(std::forward(t)); 73 | return append_value(); 74 | } 75 | template auto start_binary(T&& key, uint64_t cb) { write_key(std::forward(key)); return start_binary_value(cb); } 76 | template auto start_binary(T&& key) { write_key(std::forward(key)); return start_binary_value(); } 77 | template auto start_string(T&& key, uint64_t cb) { write_key(std::forward(key)); return start_string_value(cb); } 78 | template auto start_string(T&& key) { write_key(std::forward(key)); return start_string_value(); } 79 | template auto start_array(T&& key, uint64_t size) { write_key(std::forward(key)); return start_array_value(size); } 80 | template auto start_array(T&& key) { write_key(std::forward(key)); return start_array_value(); } 81 | template auto start_map(T&& key, uint64_t size) { write_key(std::forward(key)); return start_map_value(size); } 82 | template auto start_map(T&& key) { write_key(std::forward(key)); return start_map_value(); } 83 | 84 | // Write both the key and the value 85 | template void write(T&& key, U&& value) 86 | { 87 | write_key(std::forward(key)); 88 | write_value(std::forward(value)); 89 | } 90 | 91 | auto flush() { return m_writer.flush(); } 92 | private: 93 | inner m_writer; 94 | }; 95 | template map_writer> make_map_writer(inner&& writer) { return{ std::forward(writer) }; } 96 | 97 | template class document_writer 98 | { 99 | public: 100 | document_writer(inner&& writer) 101 | : m_writer(std::move(writer)) 102 | {} 103 | 104 | auto write(bool x) { return m_writer.write(x); } 105 | auto write(nullptr_t x) { return m_writer.write(x); } 106 | auto write(double x) { return m_writer.write(x); } 107 | auto write(undefined x) { return m_writer.write(x); } 108 | 109 | auto write(uint64_t x) { return m_writer.write(x); } 110 | auto write(uint32_t x) { return m_writer.write(static_cast(x)); } 111 | auto write(uint16_t x) { return m_writer.write(static_cast(x)); } 112 | auto write(uint8_t x) { return m_writer.write(static_cast(x)); } 113 | 114 | auto write(int64_t x) { return m_writer.write(x); } 115 | auto write(int32_t x) { return m_writer.write(static_cast(x)); } 116 | auto write(int16_t x) { return m_writer.write(static_cast(x)); } 117 | auto write(int8_t x) { return m_writer.write(static_cast(x)); } 118 | 119 | auto start_binary(uint64_t cb) { return m_writer.start_binary(cb); } 120 | auto start_binary() { return m_writer.start_binary(); } 121 | auto write(const_buffer_ref x) 122 | { 123 | auto stream = start_binary(x.size()); 124 | stream.write_buffer(x); 125 | return stream.flush(); 126 | } 127 | 128 | auto start_string(uint64_t cb) { return m_writer.start_string(cb); } 129 | auto start_string() { return m_writer.start_string(); } 130 | auto write(const char* text) { return write(text, strlen(text)); } 131 | auto write(const std::string& text) { return write(text.data(), text.size()); } 132 | template auto write(const char(&text)[N]) 133 | { 134 | static_assert(N > 0, "Expect null terminated strings"); 135 | assert(text[N - 1] == 0); 136 | return write(text, N - 1); 137 | } 138 | 139 | auto start_array(uint64_t size) { return make_array_writer(m_writer.start_array(size)); } 140 | auto start_array() { return make_array_writer(m_writer.start_array()); } 141 | 142 | auto start_map(uint64_t size) { return make_map_writer(m_writer.start_map(size)); } 143 | auto start_map() { return make_map_writer(m_writer.start_map()); } 144 | 145 | template auto write(T&& s, std::enable_if_t>::value>* = nullptr) 146 | { 147 | return copy(s, [&](size_t cb) { return start_binary(cb); }, [&] { return start_binary(); }); 148 | } 149 | 150 | template auto write(T&& document, std::enable_if_t::tag, tags::document>::value>* = nullptr) 151 | { 152 | return document.visit(best_match( 153 | [&](auto&& x, tags::binary) { return write(x); }, 154 | [&](auto&& x, tags::string) { return copy(x, [&](size_t cb) { return start_string(cb); }, [&] { return start_string(); }); }, 155 | [&](auto&& x, tags::array) 156 | { 157 | auto array_writer = start_array(); 158 | while (auto element = x.read()) 159 | array_writer.write(*element); 160 | return array_writer.flush(); 161 | }, 162 | [&](auto&& x, tags::map) 163 | { 164 | auto map_writer = start_map(); 165 | while (auto key = x.read_key()) 166 | { 167 | map_writer.write_key(*key); 168 | map_writer.write_value(x.read_value()); 169 | } 170 | return map_writer.flush(); 171 | }, 172 | [&](auto&& x, tags::undefined) { return write(x); }, 173 | [&](auto&& x, tags::floating_point) { return write(x); }, 174 | [&](auto&& x, tags::unsigned_int) { return write(x); }, 175 | [&](auto&& x, tags::signed_int) { return write(x); }, 176 | [&](auto&& x, tags::boolean) { return write(x); }, 177 | [&](auto&& x, tags::null) { return write(x); } 178 | )); 179 | } 180 | 181 | template decltype(serialize_to_goldfish(std::declval&>(), std::declval())) write(T&& t) 182 | { 183 | return serialize_to_goldfish(*this, std::forward(t)); 184 | } 185 | private: 186 | template 187 | auto copy(Stream& s, CreateWriterWithSize&& create_writer_with_size, CreateWriterWithoutSize&& create_writer_without_size) 188 | { 189 | byte buffer[typical_buffer_length]; 190 | auto cb = stream::read_full_buffer(s, buffer); 191 | if (cb < sizeof(buffer)) 192 | { 193 | // We read the entire stream 194 | auto output_stream = create_writer_with_size(cb); 195 | output_stream.write_buffer({ buffer, cb }); 196 | return output_stream.flush(); 197 | } 198 | else 199 | { 200 | // We read only a portion of the stream 201 | auto output_stream = create_writer_without_size(); 202 | output_stream.write_buffer(buffer); 203 | stream::copy(s, output_stream); 204 | return output_stream.flush(); 205 | } 206 | } 207 | 208 | auto write(const char* text, size_t length) 209 | { 210 | auto stream = start_string(length); 211 | stream.write_buffer({ reinterpret_cast(text), length }); 212 | return stream.flush(); 213 | } 214 | inner m_writer; 215 | }; 216 | 217 | template document_writer> make_writer(inner&& writer) { return{ std::forward(writer) }; } 218 | }} 219 | -------------------------------------------------------------------------------- /tests/json_reader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "dom.h" 3 | #include "unit_test.h" 4 | 5 | namespace goldfish { namespace dom 6 | { 7 | TEST_CASE(json_reader) 8 | { 9 | auto r = [&](std::string input) 10 | { 11 | stream::const_buffer_ref_reader s({ reinterpret_cast(input.data()), input.size() }); 12 | auto result = load_in_memory(json::read(stream::ref(s))); 13 | test(seek(s, 1) == 0); 14 | return result; 15 | }; 16 | using namespace std::string_literals; 17 | 18 | test(r("\"\"") == ""); 19 | test(r("\"a\"") == "a"); 20 | test(r("\"a\\u0001\\b\\n\\r\\t\\\"\\/\""s) == u8"a\u0001\b\n\r\t\"/"); 21 | test(r("\"\\uD801\\uDC37\""s) == u8"\U00010437"); 22 | test(r("\"\\uE000\""s) == u8"\U0000E000"); 23 | 24 | expect_exception([&] { r("\"\\uD801\""s); }); 25 | expect_exception([&] { r("\"\\uD801a\""s); }); 26 | expect_exception([&] { r("\"\\uD801\\n\""s); }); 27 | expect_exception([&] { r("\"\\uDC41\\uDC37\""s); }); 28 | 29 | test(r("true") == true); 30 | test(r("false") == false); 31 | test(r("null") == nullptr); 32 | 33 | test(r("0") == 0ull); 34 | test(r("1") == 1ull); 35 | test(r("10") == 10ull); 36 | test(r("4294967295") == 4294967295ull); 37 | test(r("18446744073709551615") == 18446744073709551615ull); 38 | expect_exception([&] { r("18446744073709551616"s); }); 39 | expect_exception([&] { r("18446744073709551617"s); }); 40 | expect_exception([&] { r("18446744073709551618"s); }); 41 | expect_exception([&] { r("18446744073709551619"s); }); 42 | //expect_exception([&] { r("[00]"s); }); 43 | 44 | test(r("-0") == 0ll); 45 | test(r("-1") == -1ll); 46 | test(r("-10") == -10ll); 47 | test(r("-2147483647") == -2147483647ll); 48 | test(r("-2147483648") == -2147483648ll); 49 | test(r("-9223372036854775808") == -9223372036854775808ll); 50 | expect_exception([&] { r("-9223372036854775809"s); }); 51 | 52 | test(r("0.0") == 0.); 53 | test(r("0.5") == 0.5); 54 | test(r("0.50") == 0.5); 55 | test(r("0.05") == 0.05); 56 | test(r("50.05") == 50.05); 57 | test(r("-0.0") == 0.); 58 | test(r("-0.5") == -0.5); 59 | 60 | test(r("-0.5e1") == -5.); 61 | test(r("-0.5e01") == -5.); 62 | test(r("-0.5e001") == -5.); 63 | test(r("-0.5e+1") == -5.); 64 | test(r("-0.5E+1") == -5.); 65 | test(r("-0.5E+10") == -5000000000.); 66 | 67 | test(r("1.7976931348623158e+308") == 1.7976931348623158e+308); 68 | test(r("2.2204460492503131e-016") == 2.2204460492503131e-016); 69 | test(r("2.2250738585072014e-308") == 2.2250738585072014e-308); 70 | test(r("1e309") == std::numeric_limits::infinity()); 71 | test(r("1e-309") == 0.); 72 | 73 | expect_exception([&] { r("0."); }); 74 | expect_exception([&] { r("[0.]"); }); 75 | 76 | test(r("-5E-1") == -0.5); 77 | 78 | test(r("[]") == array{}); 79 | test(r("[ ]") == array{}); 80 | test(r(" [ ]") == array{}); 81 | 82 | test(r("[1]") == array{ 1ull }); 83 | test(r("[1,2]") == array{ 1ull, 2ull }); 84 | test(r("[[]]") == array{ dom::document(array{}) }); 85 | 86 | test(r("{}") == map{}); 87 | test(r("{\"foo\":1}") == map{ { "foo", 1ull } }); 88 | expect_exception([&] { r("{1:1}"); }); 89 | } 90 | 91 | TEST_CASE(json_parse_key) 92 | { 93 | // From uint 94 | test(json::read(stream::read_string("{\"1\":1}")).as_map().read_key()->as_int64() == 1); 95 | test(json::read(stream::read_string("{\"1\":1}")).as_map().read_key()->as_uint64() == 1); 96 | test(json::read(stream::read_string("{\"1\":1}")).as_map().read_key()->as_double() == 1); 97 | 98 | // From int 99 | test(json::read(stream::read_string("{\"-1\":1}")).as_map().read_key()->as_int64() == -1); 100 | test(json::read(stream::read_string("{\"-1\":1}")).as_map().read_key()->as_double() == -1.0); 101 | 102 | // From double 103 | test(json::read(stream::read_string("{\"-1.5\":1}")).as_map().read_key()->as_double() == -1.5); 104 | 105 | // As binary 106 | test(stream::read_all_as_string(json::read(stream::read_string("{\"TWFu\":1}")).as_map().read_key()->as_binary()) == "Man"); 107 | } 108 | 109 | TEST_CASE(json_read_string_char_by_char) 110 | { 111 | auto r = [](auto input) 112 | { 113 | auto stream = json::read(stream::read_string_ref(input)).as_string(); 114 | std::string result; 115 | byte buffer[1]; 116 | while (stream.read_partial_buffer(buffer)) 117 | result.push_back(static_cast(buffer[0])); 118 | return result; 119 | }; 120 | test(r("\"\\uD801\\uDC37\"") == u8"\U00010437"); 121 | } 122 | 123 | struct data_partially_parsed {}; 124 | 125 | template 126 | void run_failure(const char* text) 127 | { 128 | auto s = stream::read_string(text); 129 | expect_exception([&] 130 | { 131 | dom::load_in_memory(json::read(stream::ref(s))); 132 | if (seek(s, 1) == 1) 133 | throw data_partially_parsed {}; 134 | }); 135 | } 136 | 137 | TEST_CASE(json_failures) 138 | { 139 | run_failure("[\"Unclosed array\""); 140 | run_failure("{unquoted_key: \"keys must be quoted\"}"); 141 | run_failure("[\"extra comma\",]"); 142 | run_failure("[\"double extra comma\",,]"); 143 | run_failure("[ , \"<--missing value\"]"); 144 | run_failure("[\"Comma after the close\"],"); 145 | run_failure("[\"Extra close\"]]"); 146 | run_failure("{\"Extra comma\": true, }"); 147 | run_failure("{\"Extra value after close\": true} \"misplaced quoted value\""); 148 | run_failure("{\"Illegal expression\": 1 + 2}"); 149 | run_failure("{\"Illegal invocation\": alert()}"); 150 | run_failure("{\"Numbers cannot have leading zeroes\": 013}"); 151 | run_failure("{\"Numbers cannot be hex\": 0x14}"); 152 | run_failure("[\"Illegal backslash escape : \x15\"]"); 153 | run_failure("[\naked]"); 154 | run_failure("[\"Illegal backslash escape : \017\"]"); 155 | run_failure("{\"Missing colon\" null}"); 156 | run_failure("{\"Double colon\":: null}"); 157 | run_failure("{\"Comma instead of colon\", null}"); 158 | run_failure("[\"Colon instead of comma\": false]"); 159 | run_failure("[\"Bad value\", truth]"); 160 | run_failure("['single quote']"); 161 | run_failure("[\"\ttab\tcharacter\tin\tstring\t\"]"); 162 | run_failure("[\"tab\\ character\\ in\\ string\\ \"]"); 163 | run_failure("[\"line\nbreak\"]"); 164 | run_failure("[\"line\\\nbreak\"]"); 165 | run_failure("[0e]"); 166 | run_failure("[0e+]"); 167 | run_failure("[0e+-1]"); 168 | run_failure("{\"Comma instead if closing brace\": true,"); 169 | run_failure("[\"mismatch\"}"); 170 | } 171 | 172 | TEST_CASE(test_success) 173 | { 174 | auto run = [](const char* text) 175 | { 176 | auto s = stream::read_string(text); 177 | dom::load_in_memory(json::read(stream::ref(s))); 178 | test(json::details::peek_non_space(s) == nullopt); 179 | }; 180 | 181 | run(R"json([ 182 | "JSON Test Pattern pass1", 183 | {"object with 1 member":["array with 1 element"]}, 184 | {}, 185 | [], 186 | -42, 187 | true, 188 | false, 189 | null, 190 | { 191 | "integer": 1234567890, 192 | "real": -9876.543210, 193 | "e": 0.123456789e-12, 194 | "E": 1.234567890E+34, 195 | "": 23456789012E66, 196 | "zero": 0, 197 | "one": 1, 198 | "space": " ", 199 | "quote": "\"", 200 | "backslash": "\\", 201 | "controls": "\b\f\n\r\t", 202 | "slash": "/ & \/", 203 | "alpha": "abcdefghijklmnopqrstuvwyz", 204 | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", 205 | "digit": "0123456789", 206 | "0123456789": "digit", 207 | "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", 208 | "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", 209 | "true": true, 210 | "false": false, 211 | "null": null, 212 | "array":[ ], 213 | "object":{ }, 214 | "address": "50 St. James Street", 215 | "url": "http://www.JSON.org/", 216 | "comment": "// /* */": " ", 218 | " s p a c e d " :[1,2 , 3 219 | 220 | , 221 | 222 | 4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], 223 | "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", 224 | "quotes": "" \u0022 %22 0x22 034 "", 225 | "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" 226 | : "A key can be any string" 227 | }, 228 | 0.5 ,98.6 229 | , 230 | 99.44 231 | , 232 | 233 | 1066, 234 | 1e1, 235 | 0.1e1, 236 | 1e-1, 237 | 1e00,2e+00,2e-00 238 | ,"rosebud"])json"); 239 | run(R"json([[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]])json"); 240 | run(R"json({ 241 | "JSON Test Pattern pass3": { 242 | "The outermost value": "must be an object or array.", 243 | "In this test": "It is an object." 244 | } 245 | } 246 | )json"); 247 | } 248 | 249 | }} -------------------------------------------------------------------------------- /tests/tests.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {B7B1799C-2A04-472B-A607-13A9C5A5022E} 23 | Win32Proj 24 | tests 25 | 8.1 26 | 27 | 28 | 29 | DynamicLibrary 30 | true 31 | v140 32 | Unicode 33 | false 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v140 39 | true 40 | Unicode 41 | false 42 | 43 | 44 | DynamicLibrary 45 | true 46 | v140 47 | Unicode 48 | false 49 | 50 | 51 | DynamicLibrary 52 | false 53 | v140 54 | true 55 | Unicode 56 | false 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | true 78 | $(VC_IncludePath);$(WindowsSDK_IncludePath);..\inc 79 | 80 | 81 | true 82 | $(VC_IncludePath);$(WindowsSDK_IncludePath);..\inc 83 | 84 | 85 | true 86 | $(VC_IncludePath);$(WindowsSDK_IncludePath);..\inc 87 | 88 | 89 | true 90 | $(VC_IncludePath);$(WindowsSDK_IncludePath);..\inc 91 | 92 | 93 | 94 | NotUsing 95 | Level3 96 | Disabled 97 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 98 | WIN32;_DEBUG;%(PreprocessorDefinitions) 99 | true 100 | 101 | 102 | 103 | 104 | Windows 105 | true 106 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 107 | 108 | 109 | 110 | 111 | NotUsing 112 | Level3 113 | Disabled 114 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 115 | _DEBUG;%(PreprocessorDefinitions) 116 | true 117 | 118 | 119 | 120 | 121 | Windows 122 | true 123 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 124 | 125 | 126 | 127 | 128 | Level3 129 | NotUsing 130 | MaxSpeed 131 | true 132 | true 133 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 134 | WIN32;NDEBUG;%(PreprocessorDefinitions) 135 | true 136 | 137 | 138 | 139 | 140 | Windows 141 | true 142 | true 143 | true 144 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 145 | 146 | 147 | 148 | 149 | Level3 150 | NotUsing 151 | MaxSpeed 152 | true 153 | true 154 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 155 | NDEBUG;%(PreprocessorDefinitions) 156 | true 157 | 158 | 159 | 160 | 161 | Windows 162 | true 163 | true 164 | true 165 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /inc/goldfish/json_writer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "array_ref.h" 5 | #include "base64_stream.h" 6 | #include "debug_checks_writer.h" 7 | #include "sax_writer.h" 8 | #include "stream.h" 9 | 10 | namespace goldfish { namespace json 11 | { 12 | struct invalid_key_type : exception { using exception::exception; }; 13 | template class document_writer; 14 | template class key_writer; 15 | 16 | template class text_writer 17 | { 18 | public: 19 | text_writer(Stream&& s) 20 | : m_stream(std::move(s)) 21 | { 22 | stream::write(m_stream, '"'); 23 | } 24 | void write_buffer(const_buffer_ref buffer) 25 | { 26 | enum category : uint8_t 27 | { 28 | F, /* forward to inner stream */ 29 | B, /* \b */ 30 | N, /* \n */ 31 | R, /* \r */ 32 | T, /* \t */ 33 | Q, /* \" */ 34 | S, /* \\ */ 35 | U, /* \u00?? */ 36 | }; 37 | static const category lookup[] = { 38 | /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ 39 | /*0x00*/ U,U,U,U,U,U,U,U,B,T,N,U,U,R,U,U, 40 | /*0x10*/ U,U,U,U,U,U,U,U,U,U,U,U,U,U,U,U, 41 | /*0x20*/ F,F,Q,F,F,F,F,F,F,F,F,F,F,F,F,F, 42 | /*0x30*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 43 | /*0x40*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 44 | /*0x50*/ F,F,F,F,F,F,F,F,F,F,F,F,S,F,F,F, 45 | /*0x60*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 46 | /*0x70*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 47 | /*0x80*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 48 | /*0x90*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 49 | /*0xA0*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 50 | /*0xB0*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 51 | /*0xC0*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 52 | /*0xD0*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 53 | /*0xE0*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 54 | /*0xF0*/ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, 55 | }; 56 | static_assert(sizeof(lookup) / sizeof(lookup[0]) == 256, "The lookup table should have 256 entries"); 57 | 58 | auto it = buffer.begin(); 59 | for (;;) 60 | { 61 | auto prev = it; 62 | while (it != buffer.end() && lookup[*it] == F) 63 | ++it; 64 | m_stream.write_buffer({ prev, it }); 65 | if (it == buffer.end()) 66 | break; 67 | 68 | switch (lookup[*it]) 69 | { 70 | case B: stream::write(m_stream, '\\'); stream::write(m_stream, 'b'); break; 71 | case N: stream::write(m_stream, '\\'); stream::write(m_stream, 'n'); break; 72 | case R: stream::write(m_stream, '\\'); stream::write(m_stream, 'r'); break; 73 | case T: stream::write(m_stream, '\\'); stream::write(m_stream, 't'); break; 74 | case Q: stream::write(m_stream, '\\'); stream::write(m_stream, '"'); break; 75 | case S: stream::write(m_stream, '\\'); stream::write(m_stream, '\\'); break; 76 | case U: 77 | { 78 | char data[6] = { '\\', 'u', '0', '0', "0123456789ABCDEF"[*it / 16], "0123456789ABCDEF"[*it % 16] }; 79 | m_stream.write_buffer({ reinterpret_cast(data), 6 }); 80 | } 81 | break; 82 | } 83 | ++it; 84 | } 85 | } 86 | auto flush() 87 | { 88 | stream::write(m_stream, '"'); 89 | return m_stream.flush(); 90 | } 91 | private: 92 | Stream m_stream; 93 | }; 94 | 95 | template class binary_writer 96 | { 97 | public: 98 | binary_writer(Stream&& s) 99 | : m_stream(std::move(s)) 100 | { 101 | stream::write(m_stream.inner_stream(), '"'); 102 | } 103 | void write_buffer(const_buffer_ref buffer) 104 | { 105 | m_stream.write_buffer(buffer); 106 | } 107 | auto flush() 108 | { 109 | m_stream.flush_no_inner_stream_flush(); 110 | stream::write(m_stream.inner_stream(), '"'); 111 | return m_stream.inner_stream().flush(); 112 | } 113 | private: 114 | stream::base64_writer m_stream; 115 | }; 116 | 117 | template class array_writer 118 | { 119 | public: 120 | array_writer(Stream&& s) 121 | : m_stream(std::move(s)) 122 | { 123 | stream::write(m_stream, '['); 124 | } 125 | 126 | document_writer> append(); 127 | auto flush() 128 | { 129 | stream::write(m_stream, ']'); 130 | return m_stream.flush(); 131 | } 132 | private: 133 | Stream m_stream; 134 | bool m_first = true; 135 | }; 136 | 137 | namespace details 138 | { 139 | template void serialize_number(Stream& s, uint64_t x) 140 | { 141 | if (x < 10) 142 | { 143 | stream::write(s, static_cast('0' + x)); 144 | } 145 | else 146 | { 147 | // 12345678901234567890 148 | static_assert(18446744073709551615 == std::numeric_limits::max(), "The max value of uint64 fits on 20 base 10 digits"); 149 | uint8_t buffer[20]; 150 | uint8_t* it = std::end(buffer); 151 | do 152 | { 153 | --it; 154 | *it = '0' + (x % 10); 155 | x /= 10; 156 | } while (x != 0); 157 | s.write_buffer({ it, std::end(buffer) }); 158 | } 159 | } 160 | template void serialize_number(Stream& s, int64_t x) 161 | { 162 | if (x < 0) 163 | { 164 | stream::write(s, '-'); 165 | serialize_number(s, static_cast(-x)); 166 | } 167 | else 168 | { 169 | serialize_number(s, static_cast(x)); 170 | } 171 | } 172 | template void serialize_number(Stream& s, double x) 173 | { 174 | auto string = std::to_string(x); 175 | s.write_buffer({ reinterpret_cast(string.data()), string.size() }); 176 | } 177 | } 178 | 179 | template class map_writer 180 | { 181 | public: 182 | map_writer(Stream&& s) 183 | : m_stream(std::move(s)) 184 | { 185 | stream::write(m_stream, '{'); 186 | } 187 | 188 | key_writer> append_key(); 189 | document_writer> append_value(); 190 | auto flush() 191 | { 192 | stream::write(m_stream, '}'); 193 | return m_stream.flush(); 194 | } 195 | private: 196 | Stream m_stream; 197 | bool m_first = true; 198 | }; 199 | 200 | template class key_writer 201 | { 202 | public: 203 | key_writer(Stream&& s) 204 | : m_stream(std::move(s)) 205 | {} 206 | auto write(bool x) 207 | { 208 | if (x) m_stream.write_buffer(string_literal_to_non_null_terminated_buffer("\"true\"")); 209 | else m_stream.write_buffer(string_literal_to_non_null_terminated_buffer("\"false\"")); 210 | return m_stream.flush(); 211 | } 212 | auto write(nullptr_t) 213 | { 214 | m_stream.write_buffer(string_literal_to_non_null_terminated_buffer("\"null\"")); 215 | return m_stream.flush(); 216 | } 217 | auto write(undefined) 218 | { 219 | return write(nullptr); 220 | } 221 | auto write(uint64_t x) 222 | { 223 | stream::write(m_stream, '"'); 224 | details::serialize_number(m_stream, x); 225 | stream::write(m_stream, '"'); 226 | return m_stream.flush(); 227 | } 228 | auto write(int64_t x) 229 | { 230 | stream::write(m_stream, '"'); 231 | details::serialize_number(m_stream, x); 232 | stream::write(m_stream, '"'); 233 | return m_stream.flush(); 234 | } 235 | auto write(double x) 236 | { 237 | stream::write(m_stream, '"'); 238 | details::serialize_number(m_stream, x); 239 | stream::write(m_stream, '"'); 240 | return m_stream.flush(); 241 | } 242 | 243 | auto start_binary(uint64_t cb) { return start_binary(); } 244 | auto start_string(uint64_t cb) { return start_string(); } 245 | binary_writer start_binary() { return{ std::move(m_stream) }; } 246 | text_writer start_string() { return{ std::move(m_stream) }; } 247 | 248 | array_writer start_array(uint64_t size) { throw invalid_key_type{ "An array cannot be a JSON key" }; } 249 | array_writer start_array() { throw invalid_key_type{ "An array cannot be a JSON key" }; } 250 | map_writer start_map(uint64_t size) { throw invalid_key_type{ "A map cannot be a JSON key" }; } 251 | map_writer start_map() { throw invalid_key_type{ "A map cannot be a JSON key" }; } 252 | 253 | private: 254 | Stream m_stream; 255 | }; 256 | 257 | template class document_writer 258 | { 259 | public: 260 | document_writer(Stream&& s) 261 | : m_stream(std::move(s)) 262 | {} 263 | auto write(bool x) 264 | { 265 | if (x) m_stream.write_buffer({ reinterpret_cast("true"), 4 }); 266 | else m_stream.write_buffer({ reinterpret_cast("false"), 5 }); 267 | return m_stream.flush(); 268 | } 269 | auto write(nullptr_t) 270 | { 271 | m_stream.write_buffer({ reinterpret_cast("null"), 4 }); 272 | return m_stream.flush(); 273 | } 274 | auto write(undefined) 275 | { 276 | return write(nullptr); 277 | } 278 | auto write(uint64_t x) 279 | { 280 | details::serialize_number(m_stream, x); 281 | return m_stream.flush(); 282 | } 283 | auto write(int64_t x) 284 | { 285 | details::serialize_number(m_stream, x); 286 | return m_stream.flush(); 287 | } 288 | auto write(double x) 289 | { 290 | details::serialize_number(m_stream, x); 291 | return m_stream.flush(); 292 | } 293 | 294 | auto start_binary(uint64_t cb) { return start_binary(); } 295 | auto start_string(uint64_t cb) { return start_string(); } 296 | binary_writer start_binary() { return{ std::move(m_stream) }; } 297 | text_writer start_string() { return{ std::move(m_stream) }; } 298 | 299 | auto start_array(uint64_t size) { return start_array(); } 300 | array_writer start_array() { return{ std::move(m_stream) }; } 301 | 302 | auto start_map(uint64_t size) { return start_map(); } 303 | map_writer start_map() { return{ std::move(m_stream) }; } 304 | 305 | private: 306 | Stream m_stream; 307 | }; 308 | template document_writer> create_writer_no_debug_check(Stream&& s) { return{ std::forward(s) }; } 309 | template auto create_writer(Stream&& s, error_handler e) 310 | { 311 | return sax::make_writer(debug_checks::add_write_checks(create_writer_no_debug_check(std::forward(s)), e)); 312 | } 313 | template auto create_writer(Stream&& s) 314 | { 315 | return create_writer(std::forward(s), debug_checks::default_error_handler{}); 316 | } 317 | 318 | template document_writer> array_writer::append() 319 | { 320 | if (m_first) 321 | m_first = false; 322 | else 323 | stream::write(m_stream, ','); 324 | 325 | return{ stream::ref(m_stream) }; 326 | } 327 | 328 | template key_writer> map_writer::append_key() 329 | { 330 | if (m_first) 331 | m_first = false; 332 | else 333 | stream::write(m_stream, ','); 334 | return{ stream::ref(m_stream) }; 335 | } 336 | template document_writer> map_writer::append_value() 337 | { 338 | stream::write(m_stream, ':'); 339 | return{ stream::ref(m_stream) }; 340 | } 341 | }} -------------------------------------------------------------------------------- /inc/goldfish/sax_reader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tags.h" 4 | #include "stream.h" 5 | #include "optional.h" 6 | #include "base64_stream.h" 7 | #include "buffered_stream.h" 8 | #include "schema.h" 9 | #include 10 | 11 | namespace goldfish 12 | { 13 | struct integer_overflow_while_casting : exception { integer_overflow_while_casting() : exception("Integer too large") {} }; 14 | 15 | template 16 | class document_impl 17 | { 18 | public: 19 | using tag = tags::document; 20 | template using type_with_tag_t = tags::type_with_tag_t; 21 | enum { does_json_conversions = _does_json_conversions }; 22 | 23 | template document_impl(Args&&... args) 24 | : m_data(std::forward(args)...) 25 | {} 26 | 27 | template decltype(auto) visit(Lambda&& l) & 28 | { 29 | assert(!m_moved_from); 30 | return m_data.visit([&](auto& x) -> decltype(auto) 31 | { 32 | return l(x, tags::get_tag(x)); 33 | }); 34 | } 35 | template decltype(auto) visit(Lambda&& l) && 36 | { 37 | assert(!m_moved_from); 38 | return std::move(m_data).visit([&](auto&& x) -> decltype(auto) 39 | { 40 | return l(std::forward(x), tags::get_tag(x)); 41 | }); 42 | } 43 | auto as_string() 44 | { 45 | assert(!m_moved_from); 46 | #ifndef NDEBUG 47 | m_moved_from = true; 48 | #endif 49 | return std::move(m_data).as>(); 50 | } 51 | auto as_binary() { return as_binary(std::integral_constant()); } 52 | auto as_array() 53 | { 54 | assert(!m_moved_from); 55 | #ifndef NDEBUG 56 | m_moved_from = true; 57 | #endif 58 | return std::move(m_data).as>(); 59 | } 60 | auto as_map() 61 | { 62 | assert(!m_moved_from); 63 | #ifndef NDEBUG 64 | m_moved_from = true; 65 | #endif 66 | return std::move(m_data).as>(); 67 | } 68 | template auto as_map(FirstKey&& first_key, OtherKeys&&... other_keys) 69 | { 70 | return apply_schema(as_map(), make_schema(std::forward(first_key), std::forward(other_keys)...)); 71 | } 72 | template auto as_object(Args&&... args) { return as_map(std::forward(args)...); } 73 | 74 | // Floating point can be converted from an int 75 | auto as_double() 76 | { 77 | assert(!m_moved_from); 78 | auto result = visit(first_match( 79 | [](double x, tags::floating_point) { return x; }, 80 | [](auto&& x, tags::unsigned_int) { return static_cast(x); }, 81 | [](auto&& x, tags::signed_int) { return static_cast(x); }, 82 | [](auto&& x, tags::string) 83 | { 84 | // We need to buffer the stream because read_number uses "peek" 85 | auto s = stream::buffer<1>(stream::ref(x)); 86 | try 87 | { 88 | auto result = json::read_number(s, stream::read(s)).visit([](auto&& x) -> double { return static_cast(x); }); 89 | if (stream::seek(s, 1) != 0) 90 | throw bad_variant_access{}; 91 | return result; 92 | } 93 | catch (const json::ill_formatted_json_data&) 94 | { 95 | throw bad_variant_access{}; 96 | } 97 | catch (const stream::unexpected_end_of_stream&) 98 | { 99 | throw bad_variant_access{}; 100 | } 101 | }, 102 | [](auto&&, auto) -> double { throw bad_variant_access{}; } 103 | )); 104 | #ifndef NDEBUG 105 | m_moved_from = true; 106 | #endif 107 | return result; 108 | } 109 | 110 | // Unsigned ints can be converted from signed ints 111 | uint64_t as_uint64() 112 | { 113 | assert(!m_moved_from); 114 | auto result = visit(first_match( 115 | [](auto&& x, tags::unsigned_int) { return x; }, 116 | [](auto&& x, tags::signed_int) { return cast_signed_to_unsigned(x); }, 117 | [](auto&& x, tags::floating_point) { return cast_double_to_unsigned(x); }, 118 | [](auto&& x, tags::string) 119 | { 120 | // We need to buffer the stream because read_number uses "peek" 121 | auto s = stream::buffer<1>(stream::ref(x)); 122 | try 123 | { 124 | auto result = json::read_number(s, stream::read(s)).visit(best_match( 125 | [](uint64_t x) { return x; }, 126 | [](int64_t x) { return cast_signed_to_unsigned(x); }, 127 | [](double x) { return cast_double_to_unsigned(x); })); 128 | if (stream::seek(s, 1) != 0) 129 | throw bad_variant_access{}; 130 | return result; 131 | } 132 | catch (const json::ill_formatted_json_data&) 133 | { 134 | throw bad_variant_access{}; 135 | } 136 | catch (const stream::unexpected_end_of_stream&) 137 | { 138 | throw bad_variant_access{}; 139 | } 140 | }, 141 | [](auto&&, auto) -> uint64_t { throw bad_variant_access{}; } 142 | )); 143 | #ifndef NDEBUG 144 | m_moved_from = true; 145 | #endif 146 | return result; 147 | } 148 | uint32_t as_uint32() 149 | { 150 | auto x = as_uint64(); 151 | if (x > std::numeric_limits::max()) 152 | throw integer_overflow_while_casting{}; 153 | return static_cast(x); 154 | } 155 | uint16_t as_uint16() 156 | { 157 | auto x = as_uint64(); 158 | if (x > std::numeric_limits::max()) 159 | throw integer_overflow_while_casting{}; 160 | return static_cast(x); 161 | } 162 | uint8_t as_uint8() 163 | { 164 | auto x = as_uint64(); 165 | if (x > std::numeric_limits::max()) 166 | throw integer_overflow_while_casting{}; 167 | return static_cast(x); 168 | } 169 | 170 | // Signed ints can be converted from unsigned ints 171 | int64_t as_int64() 172 | { 173 | assert(!m_moved_from); 174 | auto result = visit(first_match( 175 | [](auto&& x, tags::signed_int) { return x; }, 176 | [](auto&& x, tags::unsigned_int) { return cast_unsigned_to_signed(x); }, 177 | [](auto&& x, tags::floating_point) { return cast_double_to_signed(x); }, 178 | [](auto&& x, tags::string) 179 | { 180 | // We need to buffer the stream because read_number uses "peek" 181 | auto s = stream::buffer<1>(stream::ref(x)); 182 | try 183 | { 184 | auto result = json::read_number(s, stream::read(s)).visit(best_match( 185 | [](int64_t x) { return x; }, 186 | [](uint64_t x) { return cast_unsigned_to_signed(x); }, 187 | [](double x) { return cast_double_to_signed(x); } 188 | )); 189 | if (stream::seek(s, 1) != 0) 190 | throw bad_variant_access{}; 191 | return result; 192 | } 193 | catch (const json::ill_formatted_json_data&) 194 | { 195 | throw bad_variant_access{}; 196 | } 197 | catch (const stream::unexpected_end_of_stream&) 198 | { 199 | throw bad_variant_access{}; 200 | } 201 | }, 202 | [](auto&&, auto) -> int64_t { throw bad_variant_access{}; } 203 | )); 204 | #ifndef NDEBUG 205 | m_moved_from = true; 206 | #endif 207 | return result; 208 | } 209 | int32_t as_int32() 210 | { 211 | auto x = as_int64(); 212 | if (x < std::numeric_limits::min() || x > std::numeric_limits::max()) 213 | throw integer_overflow_while_casting{}; 214 | return static_cast(x); 215 | } 216 | int16_t as_int16() 217 | { 218 | auto x = as_int64(); 219 | if (x < std::numeric_limits::min() || x > std::numeric_limits::max()) 220 | throw integer_overflow_while_casting{}; 221 | return static_cast(x); 222 | } 223 | int8_t as_int8() 224 | { 225 | auto x = as_int64(); 226 | if (x < std::numeric_limits::min() || x > std::numeric_limits::max()) 227 | throw integer_overflow_while_casting{}; 228 | return static_cast(x); 229 | } 230 | 231 | auto as_bool() 232 | { 233 | assert(!m_moved_from); 234 | auto result = visit(first_match( 235 | [](auto&& x, tags::boolean) { return x; }, 236 | [](auto&& x, tags::string) 237 | { 238 | byte buffer[6]; 239 | auto cb = read_full_buffer(x, buffer); 240 | if (cb == 4 && std::equal(buffer, buffer + 4, "true")) 241 | return true; 242 | else if (cb == 5 && std::equal(buffer, buffer + 5, "false")) 243 | return false; 244 | else 245 | throw bad_variant_access{}; 246 | }, 247 | [](auto&&, auto) -> bool { throw bad_variant_access{}; } 248 | )); 249 | #ifndef NDEBUG 250 | m_moved_from = true; 251 | #endif 252 | return result; 253 | } 254 | bool is_undefined_or_null() const { return m_data.is() || m_data.is(); } 255 | bool is_null() const { return m_data.is(); } 256 | 257 | template bool is_exactly() { return m_data.is>(); } 258 | 259 | #ifdef NDEBUG 260 | using invalid_state = typename variant::invalid_state; 261 | #endif 262 | private: 263 | auto as_binary(std::true_type /*does_json_conversion*/) { return stream::decode_base64(as_string()); } 264 | auto as_binary(std::false_type /*does_json_conversion*/) { return std::move(m_data).as>(); } 265 | 266 | static uint64_t cast_signed_to_unsigned(int64_t x) 267 | { 268 | if (x < 0) 269 | throw integer_overflow_while_casting{}; 270 | return static_cast(x); 271 | } 272 | static int64_t cast_unsigned_to_signed(uint64_t x) 273 | { 274 | if (x > static_cast(std::numeric_limits::max())) 275 | throw integer_overflow_while_casting{}; 276 | return static_cast(x); 277 | } 278 | static uint64_t cast_double_to_unsigned(double x) 279 | { 280 | if (x == static_cast(x)) 281 | return static_cast(x); 282 | else 283 | throw integer_overflow_while_casting{}; 284 | } 285 | static int64_t cast_double_to_signed(double x) 286 | { 287 | if (x == static_cast(x)) 288 | return static_cast(x); 289 | else 290 | throw integer_overflow_while_casting{}; 291 | } 292 | 293 | #ifndef NDEBUG 294 | bool m_moved_from = false; 295 | #endif 296 | variant m_data; 297 | }; 298 | 299 | template std::enable_if_t, tags::document>::value, void> seek_to_end(Document&& d) 300 | { 301 | d.visit([&](auto&& x, auto) { seek_to_end(std::forward(x)); }); 302 | } 303 | template std::enable_if_t, tags::undefined>::value, void> seek_to_end(type&&) {} 304 | template std::enable_if_t, tags::floating_point>::value, void> seek_to_end(type&&) {} 305 | template std::enable_if_t, tags::unsigned_int>::value, void> seek_to_end(type&&) {} 306 | template std::enable_if_t, tags::signed_int>::value, void> seek_to_end(type&&) {} 307 | template std::enable_if_t, tags::boolean>::value, void> seek_to_end(type&&) {} 308 | template std::enable_if_t, tags::null>::value, void> seek_to_end(type&&) {} 309 | template std::enable_if_t, tags::binary>::value, void> seek_to_end(type&& x) 310 | { 311 | stream::seek(x, std::numeric_limits::max()); 312 | } 313 | template std::enable_if_t, tags::string>::value, void> seek_to_end(type&& x) 314 | { 315 | stream::seek(x, std::numeric_limits::max()); 316 | } 317 | template std::enable_if_t, tags::array>::value, void> seek_to_end(type&& x) 318 | { 319 | while (auto d = x.read()) 320 | seek_to_end(*d); 321 | } 322 | template std::enable_if_t, tags::map>::value, void> seek_to_end(type&& x) 323 | { 324 | while (auto d = x.read_key()) 325 | { 326 | seek_to_end(*d); 327 | seek_to_end(x.read_value()); 328 | } 329 | } 330 | } --------------------------------------------------------------------------------