├── .gitignore
├── .vscode
└── extensions.json
├── LICENSE
├── README.md
├── include
└── README
├── lib
└── json11
│ ├── CMakeLists.txt
│ ├── LICENSE.txt
│ ├── Makefile
│ ├── README.md
│ ├── json11.cpp
│ ├── json11.hpp
│ ├── json11.pc.in
│ └── test.cpp
├── platformio.ini
├── schematic.pdf
├── src
├── display_task.cpp
├── display_task.h
├── event.h
├── gif_player.cpp
├── gif_player.h
├── logger.h
├── main.cpp
├── main_task.cpp
├── main_task.h
├── semaphore_guard.h
└── task.h
└── test
└── README
/.gitignore:
--------------------------------------------------------------------------------
1 | .pio
2 | .pioenvs
3 | .piolibdeps
4 | .vscode/.browse.c_cpp.db*
5 | .vscode/c_cpp_properties.json
6 | .vscode/launch.json
7 | .vscode/settings.json
8 |
9 | # KiCAD files
10 | *.000
11 | *.bak
12 | *.bck
13 | *.kicad_pcb-bak
14 | *.sch-bak
15 | *.net
16 | *.dsn
17 | fp-info-cache
18 |
19 | src/boot_gif.h
20 | secrets.h
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "platformio.platformio-ide"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Reference code for my homemade Nintendo Switch ornament:
2 |
3 |
4 |
5 | Plays animated gifs from the SD card using the `bitbank2/AnimatedGIF` library and `TFT_eSPI` display driver.
6 |
7 | Wifi and other settings (time zone, debug log visibility) are configured via a `config.json` file at the root of the SD card. Firmware can be updated by putting a `firmware.bin` file at the root of the SD card, or over wifi by entering the credits screen (click the right button) which enables ArduinoOTA.
8 |
--------------------------------------------------------------------------------
/include/README:
--------------------------------------------------------------------------------
1 |
2 | This directory is intended for project header files.
3 |
4 | A header file is a file containing C declarations and macro definitions
5 | to be shared between several project source files. You request the use of a
6 | header file in your project source file (C, C++, etc) located in `src` folder
7 | by including it, with the C preprocessing directive `#include'.
8 |
9 | ```src/main.c
10 |
11 | #include "header.h"
12 |
13 | int main (void)
14 | {
15 | ...
16 | }
17 | ```
18 |
19 | Including a header file produces the same results as copying the header file
20 | into each source file that needs it. Such copying would be time-consuming
21 | and error-prone. With a header file, the related declarations appear
22 | in only one place. If they need to be changed, they can be changed in one
23 | place, and programs that include the header file will automatically use the
24 | new version when next recompiled. The header file eliminates the labor of
25 | finding and changing all the copies as well as the risk that a failure to
26 | find one copy will result in inconsistencies within a program.
27 |
28 | In C, the usual convention is to give header files names that end with `.h'.
29 | It is most portable to use only letters, digits, dashes, and underscores in
30 | header file names, and at most one dot.
31 |
32 | Read more about using header files in official GCC documentation:
33 |
34 | * Include Syntax
35 | * Include Operation
36 | * Once-Only Headers
37 | * Computed Includes
38 |
39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
40 |
--------------------------------------------------------------------------------
/lib/json11/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8)
2 | if (CMAKE_VERSION VERSION_LESS "3")
3 | project(json11 CXX)
4 | else()
5 | cmake_policy(SET CMP0048 NEW)
6 | project(json11 VERSION 1.0.0 LANGUAGES CXX)
7 | endif()
8 |
9 | enable_testing()
10 |
11 | option(JSON11_BUILD_TESTS "Build unit tests" OFF)
12 | option(JSON11_ENABLE_DR1467_CANARY "Enable canary test for DR 1467" OFF)
13 |
14 | if(CMAKE_VERSION VERSION_LESS "3")
15 | add_definitions(-std=c++11)
16 | else()
17 | set(CMAKE_CXX_STANDARD 11)
18 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
19 | endif()
20 |
21 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
22 | set(CMAKE_INSTALL_PREFIX /usr)
23 | endif()
24 |
25 | add_library(json11 json11.cpp)
26 | target_include_directories(json11 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
27 | target_compile_options(json11
28 | PRIVATE -fPIC -fno-rtti -fno-exceptions -Wall)
29 |
30 | # Set warning flags, which may vary per platform
31 | include(CheckCXXCompilerFlag)
32 | set(_possible_warnings_flags /W4 /WX -Wextra -Werror)
33 | foreach(_warning_flag ${_possible_warnings_flags})
34 | unset(_flag_supported)
35 | CHECK_CXX_COMPILER_FLAG(${_warning_flag} _flag_supported)
36 | if(${_flag_supported})
37 | target_compile_options(json11 PRIVATE ${_warning_flag})
38 | endif()
39 | endforeach()
40 |
41 | configure_file("json11.pc.in" "json11.pc" @ONLY)
42 |
43 | if (JSON11_BUILD_TESTS)
44 |
45 | # enable test for DR1467, described here: https://llvm.org/bugs/show_bug.cgi?id=23812
46 | if(JSON11_ENABLE_DR1467_CANARY)
47 | add_definitions(-D JSON11_ENABLE_DR1467_CANARY=1)
48 | else()
49 | add_definitions(-D JSON11_ENABLE_DR1467_CANARY=0)
50 | endif()
51 |
52 | add_executable(json11_test test.cpp)
53 | target_link_libraries(json11_test json11)
54 | endif()
55 |
56 | install(TARGETS json11 DESTINATION lib/${CMAKE_LIBRARY_ARCHITECTURE})
57 | install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/json11.hpp" DESTINATION include/${CMAKE_LIBRARY_ARCHITECTURE})
58 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/json11.pc" DESTINATION lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig)
59 |
--------------------------------------------------------------------------------
/lib/json11/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Dropbox, Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/lib/json11/Makefile:
--------------------------------------------------------------------------------
1 | # Environment variable to enable or disable code which demonstrates the behavior change
2 | # in Xcode 7 / Clang 3.7, introduced by DR1467 and described here:
3 | # https://llvm.org/bugs/show_bug.cgi?id=23812
4 | # Defaults to on in order to act as a warning to anyone who's unaware of the issue.
5 | ifneq ($(JSON11_ENABLE_DR1467_CANARY),)
6 | CANARY_ARGS = -DJSON11_ENABLE_DR1467_CANARY=$(JSON11_ENABLE_DR1467_CANARY)
7 | endif
8 |
9 | test: json11.cpp json11.hpp test.cpp
10 | $(CXX) $(CANARY_ARGS) -O -std=c++11 json11.cpp test.cpp -o test -fno-rtti -fno-exceptions
11 |
12 | clean:
13 | if [ -e test ]; then rm test; fi
14 |
15 | .PHONY: clean
16 |
--------------------------------------------------------------------------------
/lib/json11/README.md:
--------------------------------------------------------------------------------
1 | json11
2 | ------
3 |
4 | json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
5 |
6 | The core object provided by the library is json11::Json. A Json object represents any JSON
7 | value: null, bool, number (int or double), string (std::string), array (std::vector), or
8 | object (std::map).
9 |
10 | Json objects act like values. They can be assigned, copied, moved, compared for equality or
11 | order, and so on. There are also helper methods Json::dump, to serialize a Json to a string, and
12 | Json::parse (static) to parse a std::string as a Json object.
13 |
14 | It's easy to make a JSON object with C++11's new initializer syntax:
15 |
16 | Json my_json = Json::object {
17 | { "key1", "value1" },
18 | { "key2", false },
19 | { "key3", Json::array { 1, 2, 3 } },
20 | };
21 | std::string json_str = my_json.dump();
22 |
23 | There are also implicit constructors that allow standard and user-defined types to be
24 | automatically converted to JSON. For example:
25 |
26 | class Point {
27 | public:
28 | int x;
29 | int y;
30 | Point (int x, int y) : x(x), y(y) {}
31 | Json to_json() const { return Json::array { x, y }; }
32 | };
33 |
34 | std::vector points = { { 1, 2 }, { 10, 20 }, { 100, 200 } };
35 | std::string points_json = Json(points).dump();
36 |
37 | JSON values can have their values queried and inspected:
38 |
39 | Json json = Json::array { Json::object { { "k", "v" } } };
40 | std::string str = json[0]["k"].string_value();
41 |
42 | For more documentation see json11.hpp.
43 |
--------------------------------------------------------------------------------
/lib/json11/json11.cpp:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2013 Dropbox, Inc.
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | * THE SOFTWARE.
20 | */
21 |
22 | #include "json11.hpp"
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | namespace json11 {
30 |
31 | static const int max_depth = 200;
32 |
33 | using std::string;
34 | using std::vector;
35 | using std::map;
36 | using std::make_shared;
37 | using std::initializer_list;
38 | using std::move;
39 |
40 | /* Helper for representing null - just a do-nothing struct, plus comparison
41 | * operators so the helpers in JsonValue work. We can't use nullptr_t because
42 | * it may not be orderable.
43 | */
44 | struct NullStruct {
45 | bool operator==(NullStruct) const { return true; }
46 | bool operator<(NullStruct) const { return false; }
47 | };
48 |
49 | /* * * * * * * * * * * * * * * * * * * *
50 | * Serialization
51 | */
52 |
53 | static void dump(NullStruct, string &out) {
54 | out += "null";
55 | }
56 |
57 | static void dump(double value, string &out) {
58 | if (std::isfinite(value)) {
59 | char buf[32];
60 | snprintf(buf, sizeof buf, "%.17g", value);
61 | out += buf;
62 | } else {
63 | out += "null";
64 | }
65 | }
66 |
67 | static void dump(int value, string &out) {
68 | char buf[32];
69 | snprintf(buf, sizeof buf, "%d", value);
70 | out += buf;
71 | }
72 |
73 | static void dump(bool value, string &out) {
74 | out += value ? "true" : "false";
75 | }
76 |
77 | static void dump(const string &value, string &out) {
78 | out += '"';
79 | for (size_t i = 0; i < value.length(); i++) {
80 | const char ch = value[i];
81 | if (ch == '\\') {
82 | out += "\\\\";
83 | } else if (ch == '"') {
84 | out += "\\\"";
85 | } else if (ch == '\b') {
86 | out += "\\b";
87 | } else if (ch == '\f') {
88 | out += "\\f";
89 | } else if (ch == '\n') {
90 | out += "\\n";
91 | } else if (ch == '\r') {
92 | out += "\\r";
93 | } else if (ch == '\t') {
94 | out += "\\t";
95 | } else if (static_cast(ch) <= 0x1f) {
96 | char buf[8];
97 | snprintf(buf, sizeof buf, "\\u%04x", ch);
98 | out += buf;
99 | } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80
100 | && static_cast(value[i+2]) == 0xa8) {
101 | out += "\\u2028";
102 | i += 2;
103 | } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80
104 | && static_cast(value[i+2]) == 0xa9) {
105 | out += "\\u2029";
106 | i += 2;
107 | } else {
108 | out += ch;
109 | }
110 | }
111 | out += '"';
112 | }
113 |
114 | static void dump(const Json::array &values, string &out) {
115 | bool first = true;
116 | out += "[";
117 | for (const auto &value : values) {
118 | if (!first)
119 | out += ", ";
120 | value.dump(out);
121 | first = false;
122 | }
123 | out += "]";
124 | }
125 |
126 | static void dump(const Json::object &values, string &out) {
127 | bool first = true;
128 | out += "{";
129 | for (const auto &kv : values) {
130 | if (!first)
131 | out += ", ";
132 | dump(kv.first, out);
133 | out += ": ";
134 | kv.second.dump(out);
135 | first = false;
136 | }
137 | out += "}";
138 | }
139 |
140 | void Json::dump(string &out) const {
141 | m_ptr->dump(out);
142 | }
143 |
144 | /* * * * * * * * * * * * * * * * * * * *
145 | * Value wrappers
146 | */
147 |
148 | template
149 | class Value : public JsonValue {
150 | protected:
151 |
152 | // Constructors
153 | explicit Value(const T &value) : m_value(value) {}
154 | explicit Value(T &&value) : m_value(move(value)) {}
155 |
156 | // Get type tag
157 | Json::Type type() const override {
158 | return tag;
159 | }
160 |
161 | // Comparisons
162 | bool equals(const JsonValue * other) const override {
163 | return m_value == static_cast *>(other)->m_value;
164 | }
165 | bool less(const JsonValue * other) const override {
166 | return m_value < static_cast *>(other)->m_value;
167 | }
168 |
169 | const T m_value;
170 | void dump(string &out) const override { json11::dump(m_value, out); }
171 | };
172 |
173 | class JsonDouble final : public Value {
174 | double number_value() const override { return m_value; }
175 | int int_value() const override { return static_cast(m_value); }
176 | bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
177 | bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
178 | public:
179 | explicit JsonDouble(double value) : Value(value) {}
180 | };
181 |
182 | class JsonInt final : public Value {
183 | double number_value() const override { return m_value; }
184 | int int_value() const override { return m_value; }
185 | bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
186 | bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
187 | public:
188 | explicit JsonInt(int value) : Value(value) {}
189 | };
190 |
191 | class JsonBoolean final : public Value {
192 | bool bool_value() const override { return m_value; }
193 | public:
194 | explicit JsonBoolean(bool value) : Value(value) {}
195 | };
196 |
197 | class JsonString final : public Value {
198 | const string &string_value() const override { return m_value; }
199 | public:
200 | explicit JsonString(const string &value) : Value(value) {}
201 | explicit JsonString(string &&value) : Value(move(value)) {}
202 | };
203 |
204 | class JsonArray final : public Value {
205 | const Json::array &array_items() const override { return m_value; }
206 | const Json & operator[](size_t i) const override;
207 | public:
208 | explicit JsonArray(const Json::array &value) : Value(value) {}
209 | explicit JsonArray(Json::array &&value) : Value(move(value)) {}
210 | };
211 |
212 | class JsonObject final : public Value {
213 | const Json::object &object_items() const override { return m_value; }
214 | const Json & operator[](const string &key) const override;
215 | public:
216 | explicit JsonObject(const Json::object &value) : Value(value) {}
217 | explicit JsonObject(Json::object &&value) : Value(move(value)) {}
218 | };
219 |
220 | class JsonNull final : public Value {
221 | public:
222 | JsonNull() : Value({}) {}
223 | };
224 |
225 | /* * * * * * * * * * * * * * * * * * * *
226 | * Static globals - static-init-safe
227 | */
228 | struct Statics {
229 | const std::shared_ptr null = make_shared();
230 | const std::shared_ptr t = make_shared(true);
231 | const std::shared_ptr f = make_shared(false);
232 | const string empty_string;
233 | const vector empty_vector;
234 | const map empty_map;
235 | Statics() {}
236 | };
237 |
238 | static const Statics & statics() {
239 | static const Statics s {};
240 | return s;
241 | }
242 |
243 | static const Json & static_null() {
244 | // This has to be separate, not in Statics, because Json() accesses statics().null.
245 | static const Json json_null;
246 | return json_null;
247 | }
248 |
249 | /* * * * * * * * * * * * * * * * * * * *
250 | * Constructors
251 | */
252 |
253 | Json::Json() noexcept : m_ptr(statics().null) {}
254 | Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {}
255 | Json::Json(double value) : m_ptr(make_shared(value)) {}
256 | Json::Json(int value) : m_ptr(make_shared(value)) {}
257 | Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {}
258 | Json::Json(const string &value) : m_ptr(make_shared(value)) {}
259 | Json::Json(string &&value) : m_ptr(make_shared(move(value))) {}
260 | Json::Json(const char * value) : m_ptr(make_shared(value)) {}
261 | Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {}
262 | Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {}
263 | Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {}
264 | Json::Json(Json::object &&values) : m_ptr(make_shared(move(values))) {}
265 |
266 | /* * * * * * * * * * * * * * * * * * * *
267 | * Accessors
268 | */
269 |
270 | Json::Type Json::type() const { return m_ptr->type(); }
271 | double Json::number_value() const { return m_ptr->number_value(); }
272 | int Json::int_value() const { return m_ptr->int_value(); }
273 | bool Json::bool_value() const { return m_ptr->bool_value(); }
274 | const string & Json::string_value() const { return m_ptr->string_value(); }
275 | const vector & Json::array_items() const { return m_ptr->array_items(); }
276 | const map & Json::object_items() const { return m_ptr->object_items(); }
277 | const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; }
278 | const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; }
279 |
280 | double JsonValue::number_value() const { return 0; }
281 | int JsonValue::int_value() const { return 0; }
282 | bool JsonValue::bool_value() const { return false; }
283 | const string & JsonValue::string_value() const { return statics().empty_string; }
284 | const vector & JsonValue::array_items() const { return statics().empty_vector; }
285 | const map & JsonValue::object_items() const { return statics().empty_map; }
286 | const Json & JsonValue::operator[] (size_t) const { return static_null(); }
287 | const Json & JsonValue::operator[] (const string &) const { return static_null(); }
288 |
289 | const Json & JsonObject::operator[] (const string &key) const {
290 | auto iter = m_value.find(key);
291 | return (iter == m_value.end()) ? static_null() : iter->second;
292 | }
293 | const Json & JsonArray::operator[] (size_t i) const {
294 | if (i >= m_value.size()) return static_null();
295 | else return m_value[i];
296 | }
297 |
298 | /* * * * * * * * * * * * * * * * * * * *
299 | * Comparison
300 | */
301 |
302 | bool Json::operator== (const Json &other) const {
303 | if (m_ptr == other.m_ptr)
304 | return true;
305 | if (m_ptr->type() != other.m_ptr->type())
306 | return false;
307 |
308 | return m_ptr->equals(other.m_ptr.get());
309 | }
310 |
311 | bool Json::operator< (const Json &other) const {
312 | if (m_ptr == other.m_ptr)
313 | return false;
314 | if (m_ptr->type() != other.m_ptr->type())
315 | return m_ptr->type() < other.m_ptr->type();
316 |
317 | return m_ptr->less(other.m_ptr.get());
318 | }
319 |
320 | /* * * * * * * * * * * * * * * * * * * *
321 | * Parsing
322 | */
323 |
324 | /* esc(c)
325 | *
326 | * Format char c suitable for printing in an error message.
327 | */
328 | static inline string esc(char c) {
329 | char buf[12];
330 | if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) {
331 | snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
332 | } else {
333 | snprintf(buf, sizeof buf, "(%d)", c);
334 | }
335 | return string(buf);
336 | }
337 |
338 | static inline bool in_range(long x, long lower, long upper) {
339 | return (x >= lower && x <= upper);
340 | }
341 |
342 | namespace {
343 | /* JsonParser
344 | *
345 | * Object that tracks all state of an in-progress parse.
346 | */
347 | struct JsonParser final {
348 |
349 | /* State
350 | */
351 | const string &str;
352 | size_t i;
353 | string &err;
354 | bool failed;
355 | const JsonParse strategy;
356 |
357 | /* fail(msg, err_ret = Json())
358 | *
359 | * Mark this parse as failed.
360 | */
361 | Json fail(string &&msg) {
362 | return fail(move(msg), Json());
363 | }
364 |
365 | template
366 | T fail(string &&msg, const T err_ret) {
367 | if (!failed)
368 | err = std::move(msg);
369 | failed = true;
370 | return err_ret;
371 | }
372 |
373 | /* consume_whitespace()
374 | *
375 | * Advance until the current character is non-whitespace.
376 | */
377 | void consume_whitespace() {
378 | while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
379 | i++;
380 | }
381 |
382 | /* consume_comment()
383 | *
384 | * Advance comments (c-style inline and multiline).
385 | */
386 | bool consume_comment() {
387 | bool comment_found = false;
388 | if (str[i] == '/') {
389 | i++;
390 | if (i == str.size())
391 | return fail("unexpected end of input after start of comment", false);
392 | if (str[i] == '/') { // inline comment
393 | i++;
394 | // advance until next line, or end of input
395 | while (i < str.size() && str[i] != '\n') {
396 | i++;
397 | }
398 | comment_found = true;
399 | }
400 | else if (str[i] == '*') { // multiline comment
401 | i++;
402 | if (i > str.size()-2)
403 | return fail("unexpected end of input inside multi-line comment", false);
404 | // advance until closing tokens
405 | while (!(str[i] == '*' && str[i+1] == '/')) {
406 | i++;
407 | if (i > str.size()-2)
408 | return fail(
409 | "unexpected end of input inside multi-line comment", false);
410 | }
411 | i += 2;
412 | comment_found = true;
413 | }
414 | else
415 | return fail("malformed comment", false);
416 | }
417 | return comment_found;
418 | }
419 |
420 | /* consume_garbage()
421 | *
422 | * Advance until the current character is non-whitespace and non-comment.
423 | */
424 | void consume_garbage() {
425 | consume_whitespace();
426 | if(strategy == JsonParse::COMMENTS) {
427 | bool comment_found = false;
428 | do {
429 | comment_found = consume_comment();
430 | if (failed) return;
431 | consume_whitespace();
432 | }
433 | while(comment_found);
434 | }
435 | }
436 |
437 | /* get_next_token()
438 | *
439 | * Return the next non-whitespace character. If the end of the input is reached,
440 | * flag an error and return 0.
441 | */
442 | char get_next_token() {
443 | consume_garbage();
444 | if (failed) return static_cast(0);
445 | if (i == str.size())
446 | return fail("unexpected end of input", static_cast(0));
447 |
448 | return str[i++];
449 | }
450 |
451 | /* encode_utf8(pt, out)
452 | *
453 | * Encode pt as UTF-8 and add it to out.
454 | */
455 | void encode_utf8(long pt, string & out) {
456 | if (pt < 0)
457 | return;
458 |
459 | if (pt < 0x80) {
460 | out += static_cast(pt);
461 | } else if (pt < 0x800) {
462 | out += static_cast((pt >> 6) | 0xC0);
463 | out += static_cast((pt & 0x3F) | 0x80);
464 | } else if (pt < 0x10000) {
465 | out += static_cast((pt >> 12) | 0xE0);
466 | out += static_cast(((pt >> 6) & 0x3F) | 0x80);
467 | out += static_cast((pt & 0x3F) | 0x80);
468 | } else {
469 | out += static_cast((pt >> 18) | 0xF0);
470 | out += static_cast(((pt >> 12) & 0x3F) | 0x80);
471 | out += static_cast(((pt >> 6) & 0x3F) | 0x80);
472 | out += static_cast((pt & 0x3F) | 0x80);
473 | }
474 | }
475 |
476 | /* parse_string()
477 | *
478 | * Parse a string, starting at the current position.
479 | */
480 | string parse_string() {
481 | string out;
482 | long last_escaped_codepoint = -1;
483 | while (true) {
484 | if (i == str.size())
485 | return fail("unexpected end of input in string", "");
486 |
487 | char ch = str[i++];
488 |
489 | if (ch == '"') {
490 | encode_utf8(last_escaped_codepoint, out);
491 | return out;
492 | }
493 |
494 | if (in_range(ch, 0, 0x1f))
495 | return fail("unescaped " + esc(ch) + " in string", "");
496 |
497 | // The usual case: non-escaped characters
498 | if (ch != '\\') {
499 | encode_utf8(last_escaped_codepoint, out);
500 | last_escaped_codepoint = -1;
501 | out += ch;
502 | continue;
503 | }
504 |
505 | // Handle escapes
506 | if (i == str.size())
507 | return fail("unexpected end of input in string", "");
508 |
509 | ch = str[i++];
510 |
511 | if (ch == 'u') {
512 | // Extract 4-byte escape sequence
513 | string esc = str.substr(i, 4);
514 | // Explicitly check length of the substring. The following loop
515 | // relies on std::string returning the terminating NUL when
516 | // accessing str[length]. Checking here reduces brittleness.
517 | if (esc.length() < 4) {
518 | return fail("bad \\u escape: " + esc, "");
519 | }
520 | for (size_t j = 0; j < 4; j++) {
521 | if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F')
522 | && !in_range(esc[j], '0', '9'))
523 | return fail("bad \\u escape: " + esc, "");
524 | }
525 |
526 | long codepoint = strtol(esc.data(), nullptr, 16);
527 |
528 | // JSON specifies that characters outside the BMP shall be encoded as a pair
529 | // of 4-hex-digit \u escapes encoding their surrogate pair components. Check
530 | // whether we're in the middle of such a beast: the previous codepoint was an
531 | // escaped lead (high) surrogate, and this is a trail (low) surrogate.
532 | if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF)
533 | && in_range(codepoint, 0xDC00, 0xDFFF)) {
534 | // Reassemble the two surrogate pairs into one astral-plane character, per
535 | // the UTF-16 algorithm.
536 | encode_utf8((((last_escaped_codepoint - 0xD800) << 10)
537 | | (codepoint - 0xDC00)) + 0x10000, out);
538 | last_escaped_codepoint = -1;
539 | } else {
540 | encode_utf8(last_escaped_codepoint, out);
541 | last_escaped_codepoint = codepoint;
542 | }
543 |
544 | i += 4;
545 | continue;
546 | }
547 |
548 | encode_utf8(last_escaped_codepoint, out);
549 | last_escaped_codepoint = -1;
550 |
551 | if (ch == 'b') {
552 | out += '\b';
553 | } else if (ch == 'f') {
554 | out += '\f';
555 | } else if (ch == 'n') {
556 | out += '\n';
557 | } else if (ch == 'r') {
558 | out += '\r';
559 | } else if (ch == 't') {
560 | out += '\t';
561 | } else if (ch == '"' || ch == '\\' || ch == '/') {
562 | out += ch;
563 | } else {
564 | return fail("invalid escape character " + esc(ch), "");
565 | }
566 | }
567 | }
568 |
569 | /* parse_number()
570 | *
571 | * Parse a double.
572 | */
573 | Json parse_number() {
574 | size_t start_pos = i;
575 |
576 | if (str[i] == '-')
577 | i++;
578 |
579 | // Integer part
580 | if (str[i] == '0') {
581 | i++;
582 | if (in_range(str[i], '0', '9'))
583 | return fail("leading 0s not permitted in numbers");
584 | } else if (in_range(str[i], '1', '9')) {
585 | i++;
586 | while (in_range(str[i], '0', '9'))
587 | i++;
588 | } else {
589 | return fail("invalid " + esc(str[i]) + " in number");
590 | }
591 |
592 | if (str[i] != '.' && str[i] != 'e' && str[i] != 'E'
593 | && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) {
594 | return std::atoi(str.c_str() + start_pos);
595 | }
596 |
597 | // Decimal part
598 | if (str[i] == '.') {
599 | i++;
600 | if (!in_range(str[i], '0', '9'))
601 | return fail("at least one digit required in fractional part");
602 |
603 | while (in_range(str[i], '0', '9'))
604 | i++;
605 | }
606 |
607 | // Exponent part
608 | if (str[i] == 'e' || str[i] == 'E') {
609 | i++;
610 |
611 | if (str[i] == '+' || str[i] == '-')
612 | i++;
613 |
614 | if (!in_range(str[i], '0', '9'))
615 | return fail("at least one digit required in exponent");
616 |
617 | while (in_range(str[i], '0', '9'))
618 | i++;
619 | }
620 |
621 | return std::strtod(str.c_str() + start_pos, nullptr);
622 | }
623 |
624 | /* expect(str, res)
625 | *
626 | * Expect that 'str' starts at the character that was just read. If it does, advance
627 | * the input and return res. If not, flag an error.
628 | */
629 | Json expect(const string &expected, Json res) {
630 | assert(i != 0);
631 | i--;
632 | if (str.compare(i, expected.length(), expected) == 0) {
633 | i += expected.length();
634 | return res;
635 | } else {
636 | return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length()));
637 | }
638 | }
639 |
640 | /* parse_json()
641 | *
642 | * Parse a JSON object.
643 | */
644 | Json parse_json(int depth) {
645 | if (depth > max_depth) {
646 | return fail("exceeded maximum nesting depth");
647 | }
648 |
649 | char ch = get_next_token();
650 | if (failed)
651 | return Json();
652 |
653 | if (ch == '-' || (ch >= '0' && ch <= '9')) {
654 | i--;
655 | return parse_number();
656 | }
657 |
658 | if (ch == 't')
659 | return expect("true", true);
660 |
661 | if (ch == 'f')
662 | return expect("false", false);
663 |
664 | if (ch == 'n')
665 | return expect("null", Json());
666 |
667 | if (ch == '"')
668 | return parse_string();
669 |
670 | if (ch == '{') {
671 | map data;
672 | ch = get_next_token();
673 | if (ch == '}')
674 | return data;
675 |
676 | while (1) {
677 | if (ch != '"')
678 | return fail("expected '\"' in object, got " + esc(ch));
679 |
680 | string key = parse_string();
681 | if (failed)
682 | return Json();
683 |
684 | ch = get_next_token();
685 | if (ch != ':')
686 | return fail("expected ':' in object, got " + esc(ch));
687 |
688 | data[std::move(key)] = parse_json(depth + 1);
689 | if (failed)
690 | return Json();
691 |
692 | ch = get_next_token();
693 | if (ch == '}')
694 | break;
695 | if (ch != ',')
696 | return fail("expected ',' in object, got " + esc(ch));
697 |
698 | ch = get_next_token();
699 | }
700 | return data;
701 | }
702 |
703 | if (ch == '[') {
704 | vector data;
705 | ch = get_next_token();
706 | if (ch == ']')
707 | return data;
708 |
709 | while (1) {
710 | i--;
711 | data.push_back(parse_json(depth + 1));
712 | if (failed)
713 | return Json();
714 |
715 | ch = get_next_token();
716 | if (ch == ']')
717 | break;
718 | if (ch != ',')
719 | return fail("expected ',' in list, got " + esc(ch));
720 |
721 | ch = get_next_token();
722 | (void)ch;
723 | }
724 | return data;
725 | }
726 |
727 | return fail("expected value, got " + esc(ch));
728 | }
729 | };
730 | }//namespace {
731 |
732 | Json Json::parse(const string &in, string &err, JsonParse strategy) {
733 | JsonParser parser { in, 0, err, false, strategy };
734 | Json result = parser.parse_json(0);
735 |
736 | // Check for any trailing garbage
737 | parser.consume_garbage();
738 | if (parser.failed)
739 | return Json();
740 | if (parser.i != in.size())
741 | return parser.fail("unexpected trailing " + esc(in[parser.i]));
742 |
743 | return result;
744 | }
745 |
746 | // Documented in json11.hpp
747 | vector Json::parse_multi(const string &in,
748 | std::string::size_type &parser_stop_pos,
749 | string &err,
750 | JsonParse strategy) {
751 | JsonParser parser { in, 0, err, false, strategy };
752 | parser_stop_pos = 0;
753 | vector json_vec;
754 | while (parser.i != in.size() && !parser.failed) {
755 | json_vec.push_back(parser.parse_json(0));
756 | if (parser.failed)
757 | break;
758 |
759 | // Check for another object
760 | parser.consume_garbage();
761 | if (parser.failed)
762 | break;
763 | parser_stop_pos = parser.i;
764 | }
765 | return json_vec;
766 | }
767 |
768 | /* * * * * * * * * * * * * * * * * * * *
769 | * Shape-checking
770 | */
771 |
772 | bool Json::has_shape(const shape & types, string & err) const {
773 | if (!is_object()) {
774 | err = "expected JSON object, got " + dump();
775 | return false;
776 | }
777 |
778 | const auto& obj_items = object_items();
779 | for (auto & item : types) {
780 | const auto it = obj_items.find(item.first);
781 | if (it == obj_items.cend() || it->second.type() != item.second) {
782 | err = "bad type for " + item.first + " in " + dump();
783 | return false;
784 | }
785 | }
786 |
787 | return true;
788 | }
789 |
790 | } // namespace json11
791 |
--------------------------------------------------------------------------------
/lib/json11/json11.hpp:
--------------------------------------------------------------------------------
1 | /* json11
2 | *
3 | * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
4 | *
5 | * The core object provided by the library is json11::Json. A Json object represents any JSON
6 | * value: null, bool, number (int or double), string (std::string), array (std::vector), or
7 | * object (std::map).
8 | *
9 | * Json objects act like values: they can be assigned, copied, moved, compared for equality or
10 | * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
11 | * Json::parse (static) to parse a std::string as a Json object.
12 | *
13 | * Internally, the various types of Json object are represented by the JsonValue class
14 | * hierarchy.
15 | *
16 | * A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
17 | * so some JSON implementations distinguish between integers and floating-point numbers, while
18 | * some don't. In json11, we choose the latter. Because some JSON implementations (namely
19 | * Javascript itself) treat all numbers as the same type, distinguishing the two leads
20 | * to JSON that will be *silently* changed by a round-trip through those implementations.
21 | * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
22 | * provides integer helpers.
23 | *
24 | * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
25 | * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
26 | * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
27 | * will be exact for +/- 275 years.)
28 | */
29 |
30 | /* Copyright (c) 2013 Dropbox, Inc.
31 | *
32 | * Permission is hereby granted, free of charge, to any person obtaining a copy
33 | * of this software and associated documentation files (the "Software"), to deal
34 | * in the Software without restriction, including without limitation the rights
35 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36 | * copies of the Software, and to permit persons to whom the Software is
37 | * furnished to do so, subject to the following conditions:
38 | *
39 | * The above copyright notice and this permission notice shall be included in
40 | * all copies or substantial portions of the Software.
41 | *
42 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
48 | * THE SOFTWARE.
49 | */
50 |
51 | #pragma once
52 |
53 | #include
54 | #include
55 | #include