├── .github └── workflows │ └── ci-build.yml ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── codecov.yml ├── scripts └── coverity.sh ├── src ├── CMakeLists.txt ├── json-dom.c ├── json-dom.h ├── json-ptr.c ├── json-ptr.h ├── json.c ├── json.h ├── value.c └── value.h ├── tests ├── CMakeLists.txt ├── acutest.h └── test-json.c └── utils ├── CMakeLists.txt ├── cmdline.c ├── cmdline.h ├── json-err.c ├── json-err.h └── json-parse.c /.github/workflows/ci-build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | jobs: 8 | linux: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Configure 13 | run: CFLAGS='--coverage -g -O0' cmake -DCMAKE_BUILD_TYPE=Debug -G 'Unix Makefiles' . 14 | - name: Build 15 | run: make VERBOSE=1 16 | - name: Test 17 | run: ./test-json 18 | - name: Code Coverage Report 19 | if: ${{ github.event_name == 'push' }} 20 | continue-on-error: true 21 | run: | 22 | sudo apt-get install -y lcov 23 | lcov --directory . --capture --output-file coverage.info 24 | lcov --remove coverage.info '/usr/*' --output-file coverage.info 25 | lcov --list coverage.info 26 | bash <(curl -s https://codecov.io/bash) 27 | 28 | windows-32: 29 | runs-on: windows-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: microsoft/setup-msbuild@v1.3.1 33 | - name: Configure 34 | run: cmake -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022" -A Win32 . 35 | - name: Build 36 | run: msbuild.exe centijson.sln /p:Configuration=Release /p:Platform=Win32 37 | - name: Test 38 | run: .\Release\test-json.exe 39 | 40 | windows-64: 41 | runs-on: windows-latest 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: microsoft/setup-msbuild@v1.3.1 45 | - name: Configure 46 | run: cmake -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022" -A x64 . 47 | - name: Build 48 | run: msbuild.exe centijson.sln /p:Configuration=Release /p:Platform=x64 49 | - name: Test 50 | run: .\Release\test-json.exe 51 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.5) 3 | project(centijson C) 4 | 5 | 6 | set(CMAKE_CONFIGURATION_TYPES Debug Release RelWithDebInfo MinSizeRel) 7 | if("${CMAKE_BUILD_TYPE}" STREQUAL "") 8 | set(CMAKE_BUILD_TYPE $ENV{CMAKE_BUILD_TYPE}) 9 | 10 | if("${CMAKE_BUILD_TYPE}" STREQUAL "") 11 | set(CMAKE_BUILD_TYPE "Release") 12 | endif() 13 | endif() 14 | 15 | 16 | if(${CMAKE_C_COMPILER_ID} MATCHES GNU|Clang) 17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") 18 | elseif(MSVC) 19 | # Disable warnings about the so-called unsecured functions: 20 | add_definitions(/D_CRT_SECURE_NO_WARNINGS) 21 | endif() 22 | 23 | 24 | set(EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}") 25 | set(LIBRARY_OUTPUT_PATH "${PROJECT_BINARY_DIR}") 26 | 27 | add_subdirectory(src) 28 | add_subdirectory(tests) 29 | add_subdirectory(utils) 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | # The MIT License (MIT) 3 | 4 | Copyright © 2018 Martin Mitáš 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the “Software”), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # CentiJSON Readme 3 | 4 | * Home: http://github.com/mity/centijson 5 | 6 | 7 | ## What is JSON 8 | 9 | From http://json.org: 10 | 11 | > JSON (JavaScript Object Notation) is a lightweight data-interchange format. 12 | > It is easy for humans to read and write. It is easy for machines to parse 13 | > and generate. It is based on a subset of the JavaScript Programming Language, 14 | > Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is 15 | > completely language independent but uses conventions that are familiar to 16 | > programmers of the C-family of languages, including C, C++, C#, Java, 17 | > JavaScript, Perl, Python, and many others. These properties make JSON an ideal 18 | > data-interchange language. 19 | 20 | 21 | ## Main features: 22 | 23 | * **Size:** The code size and memory footprint is relatively small. 24 | 25 | * **Standard compliance** High emphasis is put on correctness and compliance 26 | with the JSON standards [ECMA-404], [RFC-8259] and [RFC-6901]. That includes: 27 | 28 | * **Full input validation:** During the parsing, CentiJSON verifies that the 29 | input forms valid JSON. 30 | 31 | * **String validation:** CentiJSON verifies that all strings are valid UTF-8 32 | (including corner cases like two Unicode escapes forming surrogate pairs). 33 | All JSON escape sequences are automatically translated to their respective 34 | Unicode counterparts. 35 | 36 | * **Diagnostics:** In the case of an invalid input, you get more than just some 37 | failure flag, but also an information about nature of the issue and about its 38 | location in the document where it has been detected (offset as well as the 39 | line and column numbers are provided). 40 | 41 | * **Security:** CentiJSON is intended to be usable even in situations where 42 | your application reads JSON from an untrusted source. That includes: 43 | 44 | * **Thorough testing and high code quality:** Having a lot of tests and 45 | maintaining their high code coverage, static code analysis and fuzz testing 46 | are all tools used commonly during the development and maintenance of 47 | CentiJSON. 48 | 49 | * **DoS mitigation:** The API allows good flexibility in imposing limits on 50 | the parsed input, including but not limited to, total length of the input, 51 | count of all the data records, maximal length of object keys or string 52 | values, maximal level of array/object nesting etc. This provides high degree 53 | of flexibility how to define policies for mitigation of Denial-of-Service 54 | attacks. 55 | 56 | * **Modularity:** Do you need just SAX-like parser? Take just that. Do you 57 | need full DOM parser and AST representation? Take it all, it's still just 58 | few reasonably-sized C files (and corresponding headers). 59 | 60 | * **SAX-like parser:** Take just `json.h` + `json.c` and you have complete 61 | SAX-like parser. It's smart enough to verify JSON correctness, validate 62 | UTF-8, resolve escape sequences and all the boring stuff. You can easily 63 | build DOM structure which fits your special needs or process the data on 64 | the fly. 65 | 66 | * **Full DOM parser:** `json-dom.h` + `json-dom.c` implements such a DOM 67 | builder on top of the SAX-like parser which populates the data storage 68 | implemented in `value.h` + `value.c`. 69 | 70 | * **Data storage:** The data storage module, `value.h` + `value.c` from 71 | [C Reusables](http://github.com/mity/c-reusables) is very versatile and 72 | it is not bound to the JSON parser implementation in any way, so you can 73 | reuse it for other purposes. 74 | 75 | * **JSON pointer:** JSON pointer module, `json-ptr.h` + `json-ptr.c`, which 76 | allows to query the data storage (`value.h` + `value.c`) as specified by 77 | [RFC-6901]. 78 | 79 | * **Streaming:** Ability to feed the parser with JSON input block by block 80 | (the blocks can be of an arbitrary size). 81 | 82 | * **Serialization:** 83 | 84 | * **Low-level serialization:** `json.h` provides functions for outputting the 85 | non-trivial stuff like strings or numbers from C numeric types. 86 | 87 | * **High-level:** `json-dom.h` provides function `json_dom_dump()` which is 88 | capable to serialize whole DOM hierarchy. 89 | 90 | 91 | ## Performance 92 | 93 | To be honest, we more focus on correctness and guaranteeing reasonable parsing 94 | times for crazy input (the worst case) rather than for a simple uncomplicated 95 | input. We should therefore be usable even for application reading the JSON from 96 | an untrusted sources. 97 | 98 | That for example means the objects in the DOM hierarchy are implemented as a 99 | red-black tree and we can provide reasonable member lookup times (`log(n)`) no 100 | matter how heavily populated the objects are. 101 | 102 | Of course, building the RB-trees takes some CPU time and this may show in some 103 | benchmarks, especially if they measure just the parsing and never perform any 104 | lookup in heavily populated objects. 105 | 106 | Also the support for the parsing block by block, in the streaming fashion, 107 | means we cannot have as tight loops as some parsers which do not support this, 108 | and this gives us a smaller space for some optimizations. 109 | 110 | But even so, some preliminary tests we have done so far seem to indicate that 111 | we are quite competitive. 112 | 113 | (We will likely publish some real data on this in some foreseeable future.) 114 | 115 | 116 | ## Why Yet Another JSON Parser? 117 | 118 | Indeed, there are already hundreds (if not thousands) JSON parsers written in 119 | C out there. But as far as I know, they mostly fall into one of two categories. 120 | 121 | The parsers in the 1st category are very small and simple (and quite often they 122 | also take pride in it). They then usually have one or more shortcomings from 123 | the following list: 124 | 125 | * They usually expect full in-memory document to parse and do not allow parsing 126 | block by block; 127 | 128 | * They usually allow no or very minimal configuration; 129 | 130 | * They in almost all cases use an array or linked list for storing the children 131 | of JSON arrays as well as of JSON objects (and sometimes even for **all** the 132 | data in the JSON document), so searching the object by the key is operation 133 | of linear complexity. 134 | 135 | (That may be good enough if you really **know** that all the input will be 136 | always small. But allow any black hat feed it with some bigger beast and you 137 | have Denial of Service faster than you can spell it.) 138 | 139 | * They often lack any possibility of modifying the tree of the data, like e.g. 140 | adding a new item into an array or an object, or removing an item from there. 141 | 142 | * They often perform minimal or no UTF-8 encoding validation, do not perform 143 | full escape sequence resolution, or fall into troubles if any string contains 144 | U+0000 (`"foo\u0000bar"`). 145 | 146 | The parsers in the 2nd category are far less numerous. They are usually very 147 | huge beasts which provide many scores of functions, complicated abstraction 148 | layers and/or baroque interfaces, and they are simply too big and complicated 149 | for my taste or needs or will to incorporate them in my projects. 150 | 151 | CentiJSON aims to reside somewhere in the no man's land, between the two 152 | categories. 153 | 154 | 155 | ## Using CentiJSON 156 | 157 | ### SAX-like Parser 158 | 159 | (Disclaimer: If you don't know what "SAX-like parser" means, you likely want 160 | to see the section below about the DOM parser and ignore this section.) 161 | 162 | If you want to use just the SAX-like parser, follow these steps: 163 | 164 | 1. Incorporate `src/json.h` and `src/json.c` into your project. 165 | 166 | 2. Use `#include "json.h"` in all relevant sources of your projects where 167 | you deal with JSON parsing. 168 | 169 | 3. Implement callback function, which is called anytime a scalar value (`null`, 170 | `false`, `true`, number or string) are encountered; or whenever a begin 171 | or end of a container (array or object) are encountered. 172 | 173 | To help with the implementation of the callback, you may call some utility 174 | functions to e.g. analyze a number found in the JSON input or to convert 175 | it to particular C types (see functions like e.g. `json_number_to_int32()`). 176 | 177 | 4. To parse a JSON input part by part (e.g. if you read the input by some 178 | blocks from a file), use `json_init()` + `json_feed()` + `json_fini()`. 179 | Or alternatively, if you have whole input in a single buffer, you may use 180 | `json_parse()` which wraps the three functions. 181 | 182 | Note that CentiJSON fully verifies correctness of the input. But it is done on 183 | the fly. Hence, if you feed the parser with broken JSON file, your callback 184 | function can see e.g. a beginning of an array but not its end, if in the mean 185 | time the parser aborts due to an error. 186 | 187 | Hence, if the parsing as a whole fails (`json_fini()` or `json_parse()` returns 188 | non-zero), you may still likely need to release any resources you allocated so 189 | far as the callback has been called through out the process; and the code 190 | dealing with that has to be ready the parsing is aborted at any point between 191 | the calls of the callback. 192 | 193 | See comments in `src/json.h` for more details about the API. 194 | 195 | ### DOM Parser 196 | 197 | To use just the DOM parser, follow these steps: 198 | 199 | 1. Incorporate the sources `json.h`, `json.c`, `json-dom.h`, `json-dom.c`, 200 | `value.h` and `value.c` in the `src` directory into your project. 201 | 202 | 2. Use `#include "json-dom.h"` in all relevant sources of your projects where 203 | you deal with JSON parsing, and `#include "value.h"` in all sources where 204 | you query the parsed data. 205 | 206 | 3. To parse a JSON input part by part (e.g. if you read the input by some 207 | blocks from a file), use `json_dom_init()` + `json_dom_feed()` + 208 | `json_dom_fini()`. Or alternatively, if you have whole input in a single 209 | buffer, you may use `json_dom_parse()` which wraps the three functions. 210 | 211 | 4. If the parsing succeeds, the result (document object model or DOM) forms 212 | tree hierarchy of `VALUE` structures. Use all the power of the API in 213 | `value.h` to query (or modify) the data stored in it. 214 | 215 | See comments in `src/json-dom.h` and `src/value.h` for more details about the 216 | API. 217 | 218 | ### JSON Pointer 219 | 220 | The JSON pointer module is an optional module on top of the DOM parser. To use 221 | it, follow the instructions for the DOM parser, and add also the sources 222 | `json-ptr.h` and `json-ptr.c` into your project. 223 | 224 | ### Outputting JSON 225 | 226 | If you also need to output JSON, you may use low-level helper utilities 227 | in `src/json.h` which are capable to output JSON numbers from C numeric types, 228 | or JSON strings from C strings, handling all the hard stuff of the JSON syntax 229 | like escaping of problematic characters. Writing the simple stuff like array or 230 | object brackets, value delimiters etc. is kept on the application's shoulders. 231 | 232 | Or, if you have DOM model represented by the `VALUE` structure hierarchy (as 233 | provided by the DOM parser or crafted manually), call just `json_dom_dump()`. 234 | This function, provided in `src/json-dom.h`, dumps the complete data hierarchy 235 | into a JSON stream. It supports few options for formatting the output in the 236 | desired way: E.g. it can indent the output to reflect the nesting of objects 237 | and arrays; and it can also minimize the output by skipping any non-meaningful 238 | whitespace altogether. 239 | 240 | In either cases, you have to implement a writer callback, which is capable to 241 | simply write down some sequence of bytes. This way, the application may save 242 | the output into a file; send it over a network or whatever it wishes to do 243 | with the stream. 244 | 245 | 246 | ## FAQ 247 | 248 | **Q: Why `value.h` does not provide any API for objects?** 249 | 250 | **A:** That module is not designed to be JSON-specific. The term "object", as 251 | used in JSON context, is somewhat misleading outside of the context. Therefore 252 | `value.h` instead uses more descriptive term "dictionary". 253 | 254 | The following table shows how are JSON types translated to their counterparts 255 | in `value.h`: 256 | 257 | | JSON type | `json.h` type | `value.h` type | 258 | |-----------|-------------------------------------|-----------------------| 259 | | null | `JSON_NULL` | `VALUE_NULL` | 260 | | false | `JSON_FALSE` | `VALUE_BOOL` | 261 | | true | `JSON_TRUE` | `VALUE_BOOL` | 262 | | number | `JSON_NUMBER` | see the next question | 263 | | string | `JSON_STRING` | `VALUE_STRING` | 264 | | array | `JSON_ARRAY_BEG`+`JSON_ARRAY_END` | `VALUE_ARRAY` | 265 | | object | `JSON_OBJECT_BEG`+`JSON_OBJECT_END` | `VALUE_DICT` | 266 | 267 | **Q: How does CentiJSON deal with numbers?** 268 | 269 | **A:** It's true that the untyped notion of the number type, as specified by 270 | JSON standards, is a little bit complicated to deal with for languages like C. 271 | 272 | On the SAX-like parser level, the syntax of numbers is verified accordingly 273 | to the JSON standards and provided to the callback as a verbatim string. 274 | 275 | The provided DOM builder (`json-dom.h`) tries to guess the most appropriate C 276 | type how to store the number to mitigate any data loss by applying the rules 277 | (the first applicable rule is used): 278 | 1. If there is no fraction and no exponent part and the integer fits into 279 | `VALUE_INT32`, then it shall be `VALUE_INT32`. 280 | 2. If there is no minus sign, no fraction or exponent part and the integer fits 281 | into `VALUE_UINT32`, then it shall be `VALUE_UINT32`. 282 | 3. If there is no fraction and no exponent part and the integer fits into 283 | `VALUE_INT64`, then it shall be `VALUE_INT64`. 284 | 4. If there is no minus sign, no fraction or exponent part and the integer fits 285 | into `VALUE_UINT64`, then it shall be `VALUE_UINT64`. 286 | 5. In all other cases, it shall be `VALUE_DOUBLE`. 287 | 288 | That said, note that whatever numeric type is actually used for storing the 289 | value, the getter functions of all those numeric values are capable to convert 290 | the value into another C numeric types. 291 | 292 | For example, you may use getter function `value_get_int32()` not only for values 293 | of the type `VALUE_INT32`, but also for the other numeric values, e.g. 294 | `VALUE_INT64` or `VALUE_DOUBLE`. 295 | 296 | Naturally, the conversion may exhibit similar limitations as C casting, 297 | including data loss (e.g. in the overflow situation) or rounding errors (e.g. 298 | in double to integer conversion). 299 | 300 | See the comments in the header `value.h` for more details. 301 | 302 | **Q: Are there any hard-coded limits?** 303 | 304 | **A:** No. There are only soft limits, configurable in run time by the 305 | application and intended to be used as mitigation against Denial-of-Service 306 | attacks. 307 | 308 | Application can instruct the parser to use no limits by appropriate setup of 309 | the structure `JSON_CONFIG` passed to `json_init()`. The only limitations are 310 | then imposed by properties of your machine and OS. 311 | 312 | **Q: Is CentiJSON thread-safe?** 313 | 314 | **A:** Yes. You may parse as many documents in parallel as you like or your 315 | machine is capable of. There is no global state and no need to synchronize 316 | as long as each thread uses different parser instance. 317 | 318 | (Of course, do not try parallelizing parsing of a single document. That makes 319 | no sense, given the nature of JSON format.) 320 | 321 | **Q: CentiJSON? Why such a horrible name?** 322 | 323 | **A:** First, because I am poor in naming things. Second, because CentiJSON is 324 | bigger than all those picojsons, nanojsons or microjsons; yet it's still quite 325 | small, as the prefix suggests. Third, because it begins with the letter 'C', 326 | and that refers to the C language. Forth, because the name reminds centipedes 327 | and centipedes belong to Arthropods. The characteristic feature of this group 328 | is their segmented body; similarly I see the modularity of CentiJSON as an 329 | important competitive advantage in its niche. And last but not least, because 330 | it seems it's not yet used for any other JSON implementation. 331 | 332 | 333 | ## License 334 | 335 | CentiJSON is covered with MIT license, see the file `LICENSE.md`. 336 | 337 | 338 | ## Reporting Bugs 339 | 340 | If you encounter any bug, please be so kind and report it. Unheard bugs cannot 341 | get fixed. You can submit bug reports here: 342 | 343 | * http://github.com/mity/centijson/issues 344 | 345 | 346 | 347 | [ECMA-404]: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf 348 | [RFC-8259]: https://tools.ietf.org/html/rfc8259 349 | [RFC-6901]: https://tools.ietf.org/html/rfc6901 350 | 351 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # YAML definition for codecov.io code coverage reports. 2 | 3 | ignore: 4 | # These are tested in upstream, no need to duplicate the tests (and care 5 | # about their coverage) here. 6 | - "src/value.h" 7 | - "src/value.c" 8 | 9 | - "tests" 10 | 11 | -------------------------------------------------------------------------------- /scripts/coverity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This scripts attempts to build the project via cov-build utility, and prepare 4 | # a package for uploading to the coverity scan service. 5 | # 6 | # (See http://scan.coverity.com for more info.) 7 | 8 | set -e 9 | 10 | # Check presence of coverity static analyzer. 11 | if ! which cov-build; then 12 | echo "Utility cov-build not found in PATH." 13 | exit 1 14 | fi 15 | 16 | # Choose a build system (ninja or GNU make). 17 | if which ninja; then 18 | BUILD_TOOL=ninja 19 | GENERATOR=Ninja 20 | elif which make; then 21 | BUILD_TOOL=make 22 | GENERATOR="MSYS Makefiles" 23 | else 24 | echo "No suitable build system found." 25 | exit 1 26 | fi 27 | 28 | # Choose a zip tool. 29 | if which 7za; then 30 | MKZIP="7za a -r -mx9" 31 | elif which 7z; then 32 | MKZIP="7z a -r -mx9" 33 | elif which zip; then 34 | MKZIP="zip -r" 35 | else 36 | echo "No suitable zip utility found" 37 | exit 1 38 | fi 39 | 40 | # Change dir to project root. 41 | cd `dirname "$0"`/.. 42 | 43 | CWD=`pwd` 44 | ROOT_DIR="$CWD" 45 | BUILD_DIR="$CWD/coverity" 46 | OUTPUT="$CWD/cov-int.zip" 47 | 48 | # Sanity checks. 49 | if [ ! -x "$ROOT_DIR/scripts/coverity.sh" ]; then 50 | echo "There is some path mismatch." 51 | exit 1 52 | fi 53 | if [ -e "$BUILD_DIR" ]; then 54 | echo "Path $BUILD_DIR already exists. Delete it and retry." 55 | exit 1 56 | fi 57 | if [ -e "$OUTPUT" ]; then 58 | echo "Path $OUTPUT already exists. Delete it and retry." 59 | exit 1 60 | fi 61 | 62 | # Build the project with the Coverity analyzes enabled. 63 | mkdir -p "$BUILD_DIR" 64 | cd "$BUILD_DIR" 65 | cmake -G "$GENERATOR" "$ROOT_DIR" 66 | cov-build --dir cov-int "$BUILD_TOOL" 67 | $MKZIP "$OUTPUT" "cov-int" 68 | cd "$ROOT_DIR" 69 | rm -rf "$BUILD_DIR" 70 | 71 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG") 3 | 4 | add_library(json STATIC 5 | json.c 6 | json.h 7 | json-dom.c 8 | json-dom.h 9 | json-ptr.c 10 | json-ptr.h 11 | value.c 12 | value.h 13 | ) 14 | -------------------------------------------------------------------------------- /src/json-dom.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CentiJSON 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include "json-dom.h" 27 | 28 | #include 29 | 30 | 31 | static int 32 | init_number(VALUE* v, const char* data, size_t data_size) 33 | { 34 | int is_int32_compatible; 35 | int is_uint32_compatible; 36 | int is_int64_compatible; 37 | int is_uint64_compatible; 38 | 39 | json_analyze_number(data, data_size, 40 | &is_int32_compatible, &is_uint32_compatible, 41 | &is_int64_compatible, &is_uint64_compatible); 42 | 43 | if(is_int32_compatible) { 44 | return value_init_int32(v, json_number_to_int32(data, data_size)); 45 | } else if(is_uint32_compatible) { 46 | return value_init_uint32(v, json_number_to_uint32(data, data_size)); 47 | } else if(is_int64_compatible) { 48 | return value_init_int64(v, json_number_to_int64(data, data_size)); 49 | } else if(is_uint64_compatible) { 50 | return value_init_uint64(v, json_number_to_uint64(data, data_size)); 51 | } else { 52 | double d; 53 | int err; 54 | err = json_number_to_double(data, data_size, &d); 55 | if(err != 0) 56 | return err; 57 | return value_init_double(v, d); 58 | } 59 | } 60 | 61 | static int 62 | json_dom_process(JSON_TYPE type, const char* data, size_t data_size, void* user_data) 63 | { 64 | JSON_DOM_PARSER* dom_parser = (JSON_DOM_PARSER*) user_data; 65 | VALUE* new_value; 66 | int init_val_ret = 0; 67 | 68 | if(type == JSON_ARRAY_END || type == JSON_OBJECT_END) { 69 | /* Reached end of current array or object? Just pop-up in the path. */ 70 | dom_parser->path_size--; 71 | return 0; 72 | } 73 | 74 | if(type == JSON_KEY) { 75 | /* Object key: We just store it until we get the value to use it with. */ 76 | if(value_init_string_(&dom_parser->key, data, data_size) != 0) 77 | return JSON_ERR_OUTOFMEMORY; 78 | return 0; 79 | } 80 | 81 | /* We have to create a new value. We need to add it into the enclosing 82 | * array or object; or we are not in one, then store it directly into the 83 | * root. */ 84 | if(dom_parser->path_size > 0) { 85 | VALUE* parent = dom_parser->path[dom_parser->path_size - 1]; 86 | 87 | if(value_type(parent) == VALUE_ARRAY) { 88 | new_value = value_array_append(parent); 89 | if(new_value == NULL) 90 | return JSON_ERR_OUTOFMEMORY; 91 | } else { 92 | new_value = value_dict_get_or_add_(parent, 93 | value_string(&dom_parser->key), 94 | value_string_length(&dom_parser->key)); 95 | value_fini(&dom_parser->key); 96 | 97 | if(new_value == NULL) 98 | return JSON_ERR_OUTOFMEMORY; 99 | 100 | if(!value_is_new(new_value)) { 101 | /* We have already set value for this key. */ 102 | switch(dom_parser->flags & JSON_DOM_DUPKEY_MASK) { 103 | case JSON_DOM_DUPKEY_USEFIRST: return 0; 104 | case JSON_DOM_DUPKEY_USELAST: value_fini(new_value); break; 105 | case JSON_DOM_DUPKEY_ABORT: /* Pass through. */ 106 | default: return JSON_DOM_ERR_DUPKEY; 107 | } 108 | } 109 | } 110 | } else { 111 | new_value = &dom_parser->root; 112 | } 113 | 114 | /* Initialize the new value. */ 115 | switch(type) { 116 | case JSON_NULL: value_init_null(new_value); break; 117 | case JSON_FALSE: value_init_bool(new_value, 0); break; 118 | case JSON_TRUE: value_init_bool(new_value, 1); break; 119 | case JSON_NUMBER: init_val_ret = init_number(new_value, data, data_size); break; 120 | case JSON_STRING: init_val_ret = value_init_string_(new_value, data, data_size); break; 121 | case JSON_ARRAY_BEG: init_val_ret = value_init_array(new_value); break; 122 | case JSON_OBJECT_BEG: init_val_ret = value_init_dict_ex(new_value, NULL, dom_parser->dict_flags); break; 123 | default: return JSON_ERR_INTERNAL; 124 | } 125 | 126 | if(init_val_ret != 0) 127 | return JSON_ERR_OUTOFMEMORY; 128 | 129 | if(type == JSON_ARRAY_BEG || type == JSON_OBJECT_BEG) { 130 | /* Push the array or object to the path, so we know where to 131 | * append their values. */ 132 | if(dom_parser->path_size >= dom_parser->path_alloc) { 133 | VALUE** new_path; 134 | size_t new_path_alloc = dom_parser->path_alloc * 2; 135 | 136 | if(new_path_alloc == 0) 137 | new_path_alloc = 32; 138 | new_path = (VALUE**) realloc(dom_parser->path, new_path_alloc * sizeof(VALUE*)); 139 | if(new_path == NULL) 140 | return JSON_ERR_OUTOFMEMORY; 141 | 142 | dom_parser->path = new_path; 143 | dom_parser->path_alloc = new_path_alloc; 144 | } 145 | 146 | dom_parser->path[dom_parser->path_size++] = new_value; 147 | } 148 | 149 | return 0; 150 | } 151 | 152 | int 153 | json_dom_init(JSON_DOM_PARSER* dom_parser, const JSON_CONFIG* config, unsigned dom_flags) 154 | { 155 | static const JSON_CALLBACKS callbacks = { 156 | json_dom_process 157 | }; 158 | 159 | dom_parser->path = NULL; 160 | dom_parser->path_size = 0; 161 | dom_parser->path_alloc = 0; 162 | value_init_null(&dom_parser->root); 163 | value_init_null(&dom_parser->key); 164 | dom_parser->flags = dom_flags; 165 | dom_parser->dict_flags = (dom_flags & JSON_DOM_MAINTAINDICTORDER) ? VALUE_DICT_MAINTAINORDER : 0; 166 | 167 | return json_init(&dom_parser->parser, &callbacks, config, (void*) dom_parser); 168 | } 169 | 170 | int 171 | json_dom_feed(JSON_DOM_PARSER* dom_parser, const char* input, size_t size) 172 | { 173 | return json_feed(&dom_parser->parser, input, size); 174 | } 175 | 176 | int 177 | json_dom_fini(JSON_DOM_PARSER* dom_parser, VALUE* p_root, JSON_INPUT_POS* p_pos) 178 | { 179 | int ret; 180 | 181 | ret = json_fini(&dom_parser->parser, p_pos); 182 | 183 | if(ret == 0) { 184 | memcpy(p_root, &dom_parser->root, sizeof(VALUE)); 185 | } else { 186 | value_init_null(p_root); 187 | value_fini(&dom_parser->root); 188 | } 189 | 190 | value_fini(&dom_parser->key); 191 | free(dom_parser->path); 192 | 193 | return ret; 194 | } 195 | 196 | int 197 | json_dom_parse(const char* input, size_t size, const JSON_CONFIG* config, 198 | unsigned dom_flags, VALUE* p_root, JSON_INPUT_POS* p_pos) 199 | { 200 | JSON_DOM_PARSER dom_parser; 201 | int ret; 202 | 203 | ret = json_dom_init(&dom_parser, config, dom_flags); 204 | if(ret != 0) 205 | return ret; 206 | 207 | /* We rely on propagation of any error code into json_fini(). */ 208 | json_dom_feed(&dom_parser, input, size); 209 | 210 | return json_dom_fini(&dom_parser, p_root, p_pos); 211 | } 212 | 213 | typedef struct JSON_DOM_DUMP_PARAMS { 214 | JSON_DUMP_CALLBACK write_func; 215 | void* user_data; 216 | unsigned tab_width; 217 | unsigned flags; 218 | } JSON_DOM_DUMP_PARAMS; 219 | 220 | static int 221 | json_dom_dump_indent(unsigned nest_level, JSON_DOM_DUMP_PARAMS* params) 222 | { 223 | static const char tabs[] = "\t\t\t\t\t\t\t\t"; 224 | static const unsigned n_tabs = sizeof(tabs) - 1; 225 | static const char spaces[] = " "; 226 | static const unsigned n_spaces = sizeof(spaces) - 1; 227 | 228 | unsigned i; 229 | unsigned n; 230 | unsigned run; 231 | const char* str; 232 | 233 | if((params->flags & JSON_DOM_DUMP_MINIMIZE) || nest_level == 0) 234 | return 0; 235 | 236 | if(params->flags & JSON_DOM_DUMP_INDENTWITHSPACES) { 237 | n = nest_level * params->tab_width; 238 | run = n_spaces; 239 | str = spaces; 240 | } else { 241 | n = nest_level; 242 | run = n_tabs; 243 | str = tabs; 244 | } 245 | 246 | for(i = 0; i < n; i += run) { 247 | int ret = params->write_func(str, (run > n - i) ? n - i : run, params->user_data); 248 | if(ret != 0) 249 | return ret; 250 | } 251 | 252 | return 0; 253 | } 254 | 255 | static int 256 | json_dom_dump_newline(JSON_DOM_DUMP_PARAMS* params) 257 | { 258 | if(params->flags & JSON_DOM_DUMP_MINIMIZE) 259 | return 0; 260 | 261 | if(params->flags & JSON_DOM_DUMP_FORCECLRF) 262 | return params->write_func("\r\n", 2, params->user_data); 263 | else 264 | return params->write_func("\n", 1, params->user_data); 265 | } 266 | 267 | static int 268 | json_dom_dump_helper(const VALUE* node, int nest_level, 269 | JSON_DOM_DUMP_PARAMS* params) 270 | { 271 | int ret; 272 | 273 | if(nest_level >= 0) { 274 | ret = json_dom_dump_indent(nest_level, params); 275 | if(ret != 0) 276 | return ret; 277 | } else { 278 | nest_level = -nest_level; 279 | } 280 | 281 | switch(value_type(node)) { 282 | case VALUE_NULL: 283 | ret = params->write_func("null", 4, params->user_data); 284 | break; 285 | 286 | case VALUE_BOOL: 287 | if(value_bool(node)) 288 | ret = params->write_func("true", 4, params->user_data); 289 | else 290 | ret = params->write_func("false", 5, params->user_data); 291 | break; 292 | 293 | case VALUE_INT32: 294 | ret = json_dump_int32(value_int32(node), 295 | params->write_func, params->user_data); 296 | break; 297 | 298 | case VALUE_UINT32: 299 | ret = json_dump_uint32(value_uint32(node), 300 | params->write_func, params->user_data); 301 | break; 302 | 303 | case VALUE_INT64: 304 | ret = json_dump_int64(value_int64(node), 305 | params->write_func, params->user_data); 306 | break; 307 | 308 | case VALUE_UINT64: 309 | ret = json_dump_uint64(value_uint64(node), 310 | params->write_func, params->user_data); 311 | break; 312 | 313 | case VALUE_FLOAT: 314 | case VALUE_DOUBLE: 315 | ret = json_dump_double(value_double(node), 316 | params->write_func, params->user_data); 317 | break; 318 | 319 | case VALUE_STRING: 320 | ret = json_dump_string(value_string(node), value_string_length(node), 321 | params->write_func, params->user_data); 322 | break; 323 | 324 | case VALUE_ARRAY: 325 | { 326 | const VALUE* values; 327 | size_t i, n; 328 | 329 | ret = params->write_func("[", 1, params->user_data); 330 | if(ret != 0) 331 | return ret; 332 | 333 | ret = json_dom_dump_newline(params); 334 | if(ret != 0) 335 | return ret; 336 | 337 | n = value_array_size(node); 338 | values = value_array_get_all(node); 339 | for(i = 0; i < n; i++) { 340 | ret = json_dom_dump_helper(&values[i], nest_level+1, params); 341 | if(ret != 0) 342 | return ret; 343 | 344 | if(i < n - 1) { 345 | ret = params->write_func(",", 1, params->user_data); 346 | if(ret != 0) 347 | return ret; 348 | } 349 | 350 | ret = json_dom_dump_newline(params); 351 | if(ret != 0) 352 | return ret; 353 | } 354 | 355 | ret = json_dom_dump_indent(nest_level, params); 356 | if(ret != 0) 357 | return ret; 358 | 359 | ret = params->write_func("]", 1, params->user_data); 360 | break; 361 | } 362 | 363 | case VALUE_DICT: 364 | { 365 | const VALUE** keys; 366 | size_t i, n; 367 | 368 | ret = params->write_func("{", 1, params->user_data); 369 | if(ret != 0) 370 | return ret; 371 | 372 | ret = json_dom_dump_newline(params); 373 | if(ret != 0) 374 | return ret; 375 | 376 | n = value_dict_size(node); 377 | if(n > 0) { 378 | keys = malloc(sizeof(VALUE*) * n); 379 | if(keys == NULL) 380 | return JSON_ERR_OUTOFMEMORY; 381 | 382 | if((params->flags & JSON_DOM_DUMP_PREFERDICTORDER) && 383 | (value_dict_flags(node) & VALUE_DICT_MAINTAINORDER)) 384 | value_dict_keys_ordered(node, keys, n); 385 | else 386 | value_dict_keys_sorted(node, keys, n); 387 | 388 | for(i = 0; i < n; i++) { 389 | VALUE* value; 390 | 391 | ret = json_dom_dump_helper(keys[i], nest_level+1, params); 392 | if(ret != 0) 393 | break; 394 | 395 | ret = params->write_func(": ", 396 | (params->flags & JSON_DOM_DUMP_MINIMIZE) ? 1 : 2, 397 | params->user_data); 398 | if(ret != 0) 399 | break; 400 | 401 | value = value_dict_get_(node, value_string(keys[i]), value_string_length(keys[i])); 402 | ret = json_dom_dump_helper(value, -(nest_level+1), params); 403 | if(ret != 0) 404 | break; 405 | 406 | if(i < n - 1) { 407 | ret = params->write_func(",", 1, params->user_data); 408 | if(ret != 0) 409 | break; 410 | } 411 | 412 | ret = json_dom_dump_newline(params); 413 | if(ret != 0) 414 | break; 415 | } 416 | 417 | free(keys); 418 | if(ret != 0) 419 | return ret; 420 | } 421 | 422 | ret = json_dom_dump_indent(nest_level, params); 423 | if(ret != 0) 424 | return ret; 425 | 426 | ret = params->write_func("}", 1, params->user_data); 427 | break; 428 | } 429 | } 430 | 431 | return ret; 432 | } 433 | 434 | int 435 | json_dom_dump(const VALUE* root, JSON_DUMP_CALLBACK write_func, 436 | void* user_data, unsigned tab_width, unsigned flags) 437 | { 438 | JSON_DOM_DUMP_PARAMS params = { write_func, user_data, tab_width, flags }; 439 | int ret; 440 | 441 | ret = json_dom_dump_helper(root, 0, ¶ms); 442 | if(ret != 0) 443 | return ret; 444 | 445 | ret = json_dom_dump_newline(¶ms); 446 | return ret; 447 | } 448 | -------------------------------------------------------------------------------- /src/json-dom.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CentiJSON 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef JSON_DOM_H 27 | #define JSON_DOM_H 28 | 29 | #include "json.h" 30 | #include "value.h" 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | 37 | /* DOM-specific error codes 38 | * 39 | * The DOM paring functions can return any from json.h and additionally these. 40 | */ 41 | #define JSON_DOM_ERR_DUPKEY (-1000) 42 | 43 | 44 | /* Flags for json_dom_init() 45 | */ 46 | 47 | /* Policy how to deal if the JSON contains object with duplicate key: */ 48 | #define JSON_DOM_DUPKEY_ABORT 0x0000 49 | #define JSON_DOM_DUPKEY_USEFIRST 0x0001 50 | #define JSON_DOM_DUPKEY_USELAST 0x0002 51 | 52 | #define JSON_DOM_DUPKEY_MASK \ 53 | (JSON_DOM_DUPKEY_ABORT | JSON_DOM_DUPKEY_USEFIRST | JSON_DOM_DUPKEY_USELAST) 54 | 55 | /* When creating VALUE_DICT (for JSON_OBJECT), use flag VALUE_DICT_MAINTAINORDER. */ 56 | #define JSON_DOM_MAINTAINDICTORDER 0x0010 57 | 58 | 59 | /* Structure holding parsing state. Do not access it directly. 60 | */ 61 | typedef struct JSON_DOM_PARSER { 62 | JSON_PARSER parser; 63 | VALUE** path; 64 | size_t path_size; 65 | size_t path_alloc; 66 | VALUE root; 67 | VALUE key; 68 | unsigned flags; 69 | unsigned dict_flags; 70 | } JSON_DOM_PARSER; 71 | 72 | 73 | /* Initialize the DOM parser structure. 74 | * 75 | * The parameter `config` is propagated into json_init(). 76 | */ 77 | int json_dom_init(JSON_DOM_PARSER* dom_parser, const JSON_CONFIG* config, unsigned dom_flags); 78 | 79 | /* Feed the parser with more input. 80 | */ 81 | int json_dom_feed(JSON_DOM_PARSER* dom_parser, const char* input, size_t size); 82 | 83 | /* Finish the parsing and free any resources associated with the parser. 84 | * 85 | * On success, zero is returned and the VALUE pointed by `p_dom` is initialized 86 | * accordingly to the root of the data in the JSON input (typically array or 87 | * object), and it contains all the data from the JSON input. 88 | * 89 | * On failure, the error code is returned; info about position of the issue in 90 | * the input is filled in the structure pointed by `p_pos` (if `p_pos` is not 91 | * NULL and if it is a parsing kind of error); and the value pointed by `p_dom` 92 | * is initialized to VALUE_NULL. 93 | */ 94 | int json_dom_fini(JSON_DOM_PARSER* dom_parser, VALUE* p_dom, JSON_INPUT_POS* p_pos); 95 | 96 | 97 | /* Simple wrapper for json_dom_init() + json_dom_feed() + json_dom_fini(), 98 | * usable when the provided input contains complete JSON document. 99 | */ 100 | int json_dom_parse(const char* input, size_t size, const JSON_CONFIG* config, 101 | unsigned dom_flags, VALUE* p_root, JSON_INPUT_POS* p_pos); 102 | 103 | 104 | /* Dump recursively all the DOM hierarchy out, via the provided writing 105 | * callback. 106 | * 107 | * The provided writing function must write all the data provided to it 108 | * and return zero to indicate success, or non-zero to indicate an error 109 | * and abort the operation. 110 | * 111 | * Returns zero on success, JSON_ERR_OUTOFMEMORY, or an error the code returned 112 | * from writing callback. 113 | */ 114 | #define JSON_DOM_DUMP_MINIMIZE 0x0001 /* Do not indent, do not use no extra whitespace including new lines. */ 115 | #define JSON_DOM_DUMP_FORCECLRF 0x0002 /* Use "\r\n" instead of just "\n". */ 116 | #define JSON_DOM_DUMP_INDENTWITHSPACES 0x0004 /* Indent with `tab_width` spaces instead of with '\t'. */ 117 | #define JSON_DOM_DUMP_PREFERDICTORDER 0x0008 /* Prefer original dictionary order, if available. */ 118 | 119 | int json_dom_dump(const VALUE* root, 120 | JSON_DUMP_CALLBACK write_func, void* user_data, 121 | unsigned tab_width, unsigned flags); 122 | 123 | 124 | #ifdef __cplusplus 125 | } /* extern "C" { */ 126 | #endif 127 | 128 | #endif /* JSON_DOM_H */ 129 | -------------------------------------------------------------------------------- /src/json-ptr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CentiJSON 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include "json-ptr.h" 27 | 28 | 29 | typedef enum JSON_PTR_OP { 30 | JSON_PTR_GET, 31 | JSON_PTR_ADD, 32 | JSON_PTR_GET_OR_ADD 33 | } JSON_PTR_OP; 34 | 35 | 36 | #define IS_DIGIT(ch) (('0') <= (ch) && (ch) <= '9') 37 | 38 | 39 | static int 40 | json_ptr_is_index(const char* tok, const char* end, int* p_neg, size_t* p_index) 41 | { 42 | if(tok < end && *tok == '-') { 43 | *p_neg = 1; 44 | tok++; 45 | } else { 46 | *p_neg = 0; 47 | } 48 | 49 | if(tok == end) 50 | return 0; 51 | 52 | /* The 1st digit can be zero only if no other digit follows. */ 53 | *p_index = 0; 54 | if(*tok == '0' && tok+1 < end) 55 | return 0; 56 | 57 | while(tok < end) { 58 | if(!IS_DIGIT(*tok)) 59 | return 0; 60 | *p_index = (*p_index * 10) + (*tok - '0'); 61 | tok++; 62 | } 63 | return 1; 64 | } 65 | 66 | static VALUE* 67 | json_ptr_impl(VALUE* root, const char* pointer, JSON_PTR_OP op) 68 | { 69 | const char* tok_beg = pointer; 70 | const char* tok_end; 71 | VALUE* v = root; 72 | int is_new = 0; 73 | int is_index; 74 | size_t index; 75 | 76 | if(*tok_beg == '\0') 77 | return (op != JSON_PTR_ADD) ? root : NULL; 78 | 79 | if(*tok_beg== '/') 80 | tok_beg++; 81 | 82 | while(v != NULL) { 83 | tok_end = tok_beg; 84 | while(*tok_end != '\0' && *tok_end != '/') 85 | tok_end++; 86 | 87 | /* Determine if the token is array index. */ 88 | if(tok_end - tok_beg == 1 && *tok_beg == '-') { 89 | is_index = 1; 90 | index = value_array_size(v); 91 | } else { 92 | int is_neg; 93 | 94 | is_index = json_ptr_is_index(tok_beg, tok_end, &is_neg, &index); 95 | if(is_index && is_neg) { 96 | size_t size = value_array_size(v); 97 | if(index <= size) 98 | index = size - index; 99 | else 100 | return NULL; 101 | } 102 | } 103 | 104 | if(is_index) { 105 | VALUE* tmp; 106 | 107 | if(is_new) 108 | value_init_array(v); 109 | if(value_type(v) != VALUE_ARRAY) 110 | return NULL; 111 | 112 | tmp = value_array_get(v, index); 113 | if(tmp == NULL && op != JSON_PTR_GET && index == value_array_size(v)) { 114 | tmp = value_array_append(v); 115 | is_new = 1; 116 | } else { 117 | is_new = 0; 118 | } 119 | 120 | v = tmp; 121 | } else { 122 | int has_escape; 123 | char* key; 124 | const char* tok_ptr; 125 | char* key_ptr; 126 | size_t len; 127 | 128 | if(is_new) 129 | value_init_dict(v); 130 | if(value_type(v) != VALUE_DICT) 131 | return NULL; 132 | 133 | /* The RFC-6901 allows some escape sequences: 134 | * -- "~0" means '~' 135 | * -- "~1" means '/' 136 | */ 137 | has_escape = 0; 138 | for(tok_ptr = tok_beg; tok_ptr < tok_end; tok_ptr++) { 139 | if(*tok_ptr == '~') { 140 | if(tok_ptr+1 == tok_end || (*(tok_ptr+1) != '0' && *(tok_ptr+1) != '1')) { 141 | /* invalid escape. */ 142 | return NULL; 143 | } 144 | 145 | has_escape = 1; 146 | break; 147 | } 148 | } 149 | 150 | if(has_escape) { 151 | key = (char*) malloc(tok_end - tok_beg); 152 | if(key == NULL) 153 | return NULL; 154 | 155 | tok_ptr = tok_beg; 156 | key_ptr = key; 157 | while(tok_ptr < tok_end) { 158 | if(*tok_ptr == '~') { 159 | *key_ptr = (*(tok_ptr+1) == '0' ? '~' : '/'); 160 | tok_ptr += 2; 161 | } else { 162 | *key_ptr = *tok_ptr; 163 | tok_ptr += 1; 164 | } 165 | key_ptr++; 166 | } 167 | len = key_ptr - key; 168 | } else { 169 | key = (char*) tok_beg; 170 | len = tok_end - tok_beg; 171 | } 172 | 173 | if(op == JSON_PTR_GET) { 174 | v = value_dict_get_(v, key, len); 175 | is_new = 0; 176 | } else { 177 | v = value_dict_get_or_add_(v, key, len); 178 | is_new = value_is_new(v); 179 | } 180 | 181 | if(has_escape) 182 | free(key); 183 | } 184 | 185 | if(*tok_end == '\0') 186 | break; 187 | 188 | tok_beg = tok_end+1; 189 | } 190 | 191 | if(op == JSON_PTR_ADD && !is_new) { 192 | /* The caller wanted to add a new value. */ 193 | v = NULL; 194 | } 195 | 196 | return v; 197 | } 198 | 199 | VALUE* 200 | json_ptr_get(const VALUE* root, const char* pointer) 201 | { 202 | return json_ptr_impl((VALUE*) root, pointer, JSON_PTR_GET); 203 | } 204 | 205 | VALUE* 206 | json_ptr_add(VALUE* root, const char* pointer) 207 | { 208 | return json_ptr_impl((VALUE*) root, pointer, JSON_PTR_ADD); 209 | } 210 | 211 | VALUE* 212 | json_ptr_get_or_add(VALUE* root, const char* pointer) 213 | { 214 | return json_ptr_impl((VALUE*) root, pointer, JSON_PTR_GET_OR_ADD); 215 | } 216 | -------------------------------------------------------------------------------- /src/json-ptr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CentiJSON 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef JSON_PTR_H 27 | #define JSON_PTR_H 28 | 29 | #include "value.h" 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | 36 | /* This implements RFC-6901: JSON Pointer 37 | * (see https://tools.ietf.org/html/rfc6901) 38 | * 39 | * Limitations: 40 | * 41 | * -- These functions cannot deal with object keys which contain U+0000. 42 | * 43 | * -- To prevent surprising results, the pointer parts which, according to the 44 | * RFC-6901, can specify an array index (numbers, and also "-") are ALWAYS 45 | * understood as an array index. 46 | * 47 | * I.e. if the given subpart of the pointer refers actually to anything but 48 | * array (e.g. an object, even if the object has key which looks as the 49 | * given array index) it shall lead to a failure (NULL shall be returned). 50 | * 51 | * Extension: 52 | * 53 | * -- We allow negative array indexes, counted in the reverse manner. 54 | * E.g. consider "/foo/-0" where "-0" refers to the last element of 55 | * an array keyed in the root object as "foo". Similarly "/foo/-1" is 56 | * the array element just before it. 57 | */ 58 | 59 | 60 | /* Get a value on the given pointer; or NULL of no such value exists. 61 | * 62 | * Unlike the other functions, this function never modifies the VALUE 63 | * hierarchy. 64 | */ 65 | VALUE* json_ptr_get(const VALUE* root, const char* pointer); 66 | 67 | /* Add a new value on the given pointer. The new value is initialized to 68 | * VALUE_NULL with the new flag set. Caller is supposed to re-initialize the 69 | * new value to reset the flag. 70 | * 71 | * Returns NULL in case of a failure. Note that that includes the situation if 72 | * the value specified with the pointer already exists. 73 | * 74 | * If some parent array or object does not exist (as expected from the pointer 75 | * specification), they are automatically created on the fly. Note in case of 76 | * a failure some of these parents may or may not be created, depending on the 77 | * nature of the failure. 78 | */ 79 | VALUE* json_ptr_add(VALUE* root, const char* pointer); 80 | 81 | /* Get a value on the given pointer. If the value does not exist, it is added. 82 | * In such case the new value is initialized to VALUE_NULL with the new flag 83 | * set (test for it with value_is_new()) and caller is supposed to re-initialize 84 | * the new value to reset the flag. 85 | * 86 | * Returns NULL in case of a failure. 87 | * 88 | * If some parent array or object does not exist (as expected from the pointer 89 | * specification), they are automatically created on the fly. Note in case of 90 | * a failure some of these parents may or may not be created, depending on the 91 | * nature of the failure. 92 | */ 93 | VALUE* json_ptr_get_or_add(VALUE* root, const char* pointer); 94 | 95 | 96 | #ifdef __cplusplus 97 | } /* extern "C" { */ 98 | #endif 99 | 100 | #endif /* JSON_PTR_H */ 101 | -------------------------------------------------------------------------------- /src/json.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CentiJSON 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef JSON_H 27 | #define JSON_H 28 | 29 | #include 30 | #include 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | 37 | /* JSON data types. 38 | * 39 | * Note that we distinguish beginning/end of the arrays and objects for 40 | * the purposes of the processing. 41 | */ 42 | typedef enum JSON_TYPE { 43 | JSON_NULL, 44 | JSON_FALSE, 45 | JSON_TRUE, 46 | JSON_NUMBER, 47 | JSON_STRING, 48 | JSON_KEY, /* String in the specific role of an object key. */ 49 | JSON_ARRAY_BEG, 50 | JSON_ARRAY_END, 51 | JSON_OBJECT_BEG, 52 | JSON_OBJECT_END 53 | } JSON_TYPE; 54 | 55 | 56 | /* Error codes. 57 | */ 58 | #define JSON_ERR_SUCCESS 0 59 | #define JSON_ERR_INTERNAL (-1) /* This should never happen. If you see it, report bug ;-) */ 60 | #define JSON_ERR_OUTOFMEMORY (-2) 61 | #define JSON_ERR_SYNTAX (-4) /* Generic syntax error. (More specific error codes are preferred.) */ 62 | #define JSON_ERR_BADCLOSER (-5) /* Mismatch in brackets (e.g. "{ ]" or "[ }") */ 63 | #define JSON_ERR_BADROOTTYPE (-6) /* Root type not allowed by CONFIG::flags. */ 64 | #define JSON_ERR_EXPECTEDVALUE (-7) /* Something unexpected where value has to be. */ 65 | #define JSON_ERR_EXPECTEDKEY (-8) /* Something unexpected where key has to be. */ 66 | #define JSON_ERR_EXPECTEDVALUEORCLOSER (-9) /* Something unexpected where value or array/object closer has to be. */ 67 | #define JSON_ERR_EXPECTEDKEYORCLOSER (-10) /* Something unexpected where key or array/object closer has to be. */ 68 | #define JSON_ERR_EXPECTEDCOLON (-11) /* Something unexpected where colon has to be. */ 69 | #define JSON_ERR_EXPECTEDCOMMAORCLOSER (-12) /* Something unexpected where comma or array/object has to be. */ 70 | #define JSON_ERR_EXPECTEDEOF (-13) /* Something unexpected where end-of-file has to be. */ 71 | #define JSON_ERR_MAXTOTALLEN (-14) /* Reached JSON_CONFIG::max_total_len */ 72 | #define JSON_ERR_MAXTOTALVALUES (-15) /* Reached JSON_CONFIG::max_total_values */ 73 | #define JSON_ERR_MAXNESTINGLEVEL (-16) /* Reached JSON_CONFIG::max_nesting_level */ 74 | #define JSON_ERR_MAXNUMBERLEN (-17) /* Reached JSON_CONFIG::max_number_len */ 75 | #define JSON_ERR_MAXSTRINGLEN (-18) /* Reached JSON_CONFIG::max_string_len */ 76 | #define JSON_ERR_MAXKEYLEN (-19) /* Reached JSON_CONFIG::max_key_len */ 77 | #define JSON_ERR_UNCLOSEDSTRING (-20) /* Unclosed string */ 78 | #define JSON_ERR_UNESCAPEDCONTROL (-21) /* Unescaped control character (in a string) */ 79 | #define JSON_ERR_INVALIDESCAPE (-22) /* Invalid/unknown escape sequence (in a string) */ 80 | #define JSON_ERR_INVALIDUTF8 (-23) /* Invalid UTF-8 (in a string) */ 81 | 82 | 83 | /* Bits for JSON_CONFIG::flags. 84 | */ 85 | #define JSON_NONULLASROOT 0x0001 /* Disallow null to be root value */ 86 | #define JSON_NOBOOLASROOT 0x0002 /* Disallow false or true to be root value */ 87 | #define JSON_NONUMBERASROOT 0x0004 /* Disallow number to be root value */ 88 | #define JSON_NOSTRINGASROOT 0x0008 /* Disallow string to be root value */ 89 | #define JSON_NOARRAYASROOT 0x0010 /* Disallow array to be root value */ 90 | #define JSON_NOOBJECTASROOT 0x0020 /* Disallow object to be root value */ 91 | 92 | #define JSON_NOSCALARROOT (JSON_NONULLASROOT | JSON_NOBOOLASROOT | \ 93 | JSON_NONUMBERASROOT | JSON_NOSTRINGASROOT) 94 | #define JSON_NOVECTORROOT (JSON_NOARRAYASROOT | JSON_NOOBJECTASROOT) 95 | 96 | #define JSON_IGNOREILLUTF8KEY 0x0100 /* Ignore ill-formed UTF-8 (for keys). */ 97 | #define JSON_FIXILLUTF8KEY 0x0200 /* Replace ill-formed UTF-8 char with replacement char (for keys). */ 98 | #define JSON_IGNOREILLUTF8VALUE 0x0400 /* Ignore ill-formed UTF-8 (for string values). */ 99 | #define JSON_FIXILLUTF8VALUE 0x0800 /* Replace ill-formed UTF-8 char with replacement char (for string values). */ 100 | 101 | 102 | 103 | /* Parser options, passed into json_init(). 104 | * 105 | * If NULL is passed to json_init(), default values are used. 106 | */ 107 | typedef struct JSON_CONFIG { 108 | size_t max_total_len; /* zero means no limit; default: 10 MB */ 109 | size_t max_total_values; /* zero means no limit; default: 0 */ 110 | size_t max_number_len; /* zero means no limit; default: 512 */ 111 | size_t max_string_len; /* zero means no limit; default: 65536 */ 112 | size_t max_key_len; /* zero means no limit; default: 512 */ 113 | unsigned max_nesting_level; /* zero means no limit; default: 512 */ 114 | unsigned flags; /* default: 0 */ 115 | } JSON_CONFIG; 116 | 117 | 118 | /* Helper structure describing position in the input. 119 | * 120 | * It is used to specify where in the input a parsing error occurred for 121 | * better diagnostics. 122 | */ 123 | typedef struct JSON_INPUT_POS { 124 | size_t offset; 125 | unsigned line_number; 126 | unsigned column_number; 127 | } JSON_INPUT_POS; 128 | 129 | 130 | /* Callbacks the application has to implement, to process the parsed data. 131 | */ 132 | typedef struct JSON_CALLBACKS { 133 | /* Data processing callback. For now (and maybe forever) the only callback. 134 | * 135 | * Note that `data` and `data_size` are set only for JSON_KEY, JSON_STRING 136 | * and JSON_NUMBER. (For the other types the callback always gets NULL and 137 | * 0). 138 | * 139 | * Inside an object, the application is guaranteed to get keys and their 140 | * corresponding values in the alternating fashion (i.e. in the order 141 | * as they are in the JSON input.). 142 | * 143 | * Application can abort the parsing operation by returning a non-zero. 144 | * Note the non-zero return value of the callback is propagated to 145 | * json_feed() and json_fini(). 146 | */ 147 | int (*process)(JSON_TYPE /*type*/, const char* /*data*/, 148 | size_t /*data_size*/, void* /*user_data*/); 149 | } JSON_CALLBACKS; 150 | 151 | 152 | /* Internal parser state. Use pointer to this structure as an opaque handle. 153 | */ 154 | typedef struct JSON_PARSER { 155 | JSON_CALLBACKS callbacks; 156 | JSON_CONFIG config; 157 | void* user_data; 158 | 159 | JSON_INPUT_POS pos; 160 | JSON_INPUT_POS value_pos; 161 | JSON_INPUT_POS err_pos; 162 | 163 | int errcode; 164 | 165 | size_t value_counter; 166 | 167 | char* nesting_stack; 168 | size_t nesting_level; 169 | size_t nesting_stack_size; 170 | 171 | unsigned automaton; 172 | unsigned state; 173 | unsigned substate; 174 | 175 | uint32_t codepoint[2]; 176 | 177 | char* buf; 178 | size_t buf_used; 179 | size_t buf_alloced; 180 | 181 | size_t last_cl_offset; /* Offset of most recently seen '\r' */ 182 | } JSON_PARSER; 183 | 184 | 185 | 186 | /* Fill `config` with options used by default. 187 | */ 188 | void json_default_config(JSON_CONFIG* config); 189 | 190 | 191 | /* Initialize the parser, associate it with the given callbacks and 192 | * configuration. Returns zero on success, non-zero on an error. 193 | * 194 | * If `config` is NULL, default values are used. 195 | */ 196 | int json_init(JSON_PARSER* parser, 197 | const JSON_CALLBACKS* callbacks, 198 | const JSON_CONFIG* config, 199 | void* user_data); 200 | 201 | /* Feed the parser with more input. 202 | * 203 | * Returns zero on success. 204 | * 205 | * If an error occurs it returns non-zero and any attempt to call json_feed() 206 | * again shall just fail with the same error code. Note the application should 207 | * still call json_fini() to release all resources allocated by the parser. 208 | */ 209 | int json_feed(JSON_PARSER* parser, const char* input, size_t size); 210 | 211 | /* Finish parsing of the document (note it can still call some callbacks); and 212 | * release any resources held by the parser. 213 | * 214 | * Returns zero on success, or non-zero on failure. 215 | * 216 | * If `p_pos` is not NULL, it is filled with info about reached position in the 217 | * input. It can help in diagnostics if the parsing failed. 218 | * 219 | * Note that if the preceding call to json_feed() failed, the error status also 220 | * propagates into json_fini(). 221 | * 222 | * Also note this function may still fail even when all preceding calls to 223 | * json_feed() succeeded. This typically happens when the parser was fed with 224 | * an incomplete JSON document. 225 | */ 226 | int json_fini(JSON_PARSER* parser, JSON_INPUT_POS* p_pos); 227 | 228 | 229 | /* Simple wrapper function for json_init() + json_feed() + json_fini(), usable 230 | * when the provided input contains complete JSON document. 231 | */ 232 | int json_parse(const char* input, size_t size, 233 | const JSON_CALLBACKS* callbacks, const JSON_CONFIG* config, 234 | void* user_data, JSON_INPUT_POS* p_pos); 235 | 236 | 237 | /* Converts error code to human readable error message 238 | */ 239 | const char* json_error_str(int err_code); 240 | 241 | 242 | /***************** 243 | *** Utilities *** 244 | *****************/ 245 | 246 | /* When implementing the callback processing the parsed data, these utilities 247 | * below may come handy. 248 | */ 249 | 250 | /* Analyze the string holding a JSON number, and analyze whether it can 251 | * fit into integer types. 252 | * 253 | * (Note it says "no" in cases the number string contains any fraction or 254 | * exponent part.) 255 | */ 256 | void json_analyze_number(const char* num, size_t num_size, 257 | int* p_is_int32_compatible, 258 | int* p_is_uint32_compatible, 259 | int* p_is_int64_compatible, 260 | int* p_is_uint64_compatible); 261 | 262 | /* Convert the string holding JSON number to the given C type. 263 | * 264 | * Note the conversion to any of the integer types is undefined unless 265 | * json_analyze_number() says it's fine. 266 | * 267 | * Also note that json_number_to_double() can fail with JSON_ERR_OUTOFMEMORY. 268 | * Hence its prototype differs. 269 | */ 270 | int32_t json_number_to_int32(const char* num, size_t num_size); 271 | uint32_t json_number_to_uint32(const char* num, size_t num_size); 272 | int64_t json_number_to_int64(const char* num, size_t num_size); 273 | uint64_t json_number_to_uint64(const char* num, size_t num_size); 274 | int json_number_to_double(const char* num, size_t num_size, double* p_result); 275 | 276 | 277 | typedef int (*JSON_DUMP_CALLBACK)(const char* /*str*/, size_t /*size*/, void* /*user_data*/); 278 | 279 | /* Helpers for writing numbers and strings in JSON-compatible format. 280 | * 281 | * Note that json_dump_string() assumes the string is a well-formed UTF-8 282 | * string which needs no additional Unicode validation. The function "only" 283 | * handles proper escaping of control characters. 284 | * 285 | * The provided writer callback must write all the data provided to it and 286 | * return zero to indicate success, or non-zero to indicate an error and abort 287 | * the operation. 288 | * 289 | * All these return zero on success, JSON_ERR_OUTOFMEMORY, or an error code 290 | * propagated from the writer callback. 291 | * 292 | * (Given that all the other JSON stuff is trivial to output, the application 293 | * is supposed to implement that manually.) 294 | */ 295 | int json_dump_int32(int32_t i32, JSON_DUMP_CALLBACK write_func, void* user_data); 296 | int json_dump_uint32(uint32_t u32, JSON_DUMP_CALLBACK write_func, void* user_data); 297 | int json_dump_int64(int64_t i64, JSON_DUMP_CALLBACK write_func, void* user_data); 298 | int json_dump_uint64(uint64_t u64, JSON_DUMP_CALLBACK write_func, void* user_data); 299 | int json_dump_double(double dbl, JSON_DUMP_CALLBACK write_func, void* user_data); 300 | int json_dump_string(const char* str, size_t size, JSON_DUMP_CALLBACK write_func, void* user_data); 301 | 302 | 303 | #ifdef __cplusplus 304 | } /* extern "C" { */ 305 | #endif 306 | 307 | #endif /* JSON_H */ 308 | -------------------------------------------------------------------------------- /src/value.c: -------------------------------------------------------------------------------- 1 | /* 2 | * C Reusables 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include "value.h" 27 | 28 | #include 29 | #include 30 | 31 | 32 | #define TYPE_MASK 0x0f 33 | #define IS_NEW 0x10 /* only for VALUE_NULL */ 34 | #define HAS_REDCOLOR 0x10 /* only for VALUE_STRING (when used as RBTREE::key) */ 35 | #define HAS_ORDERLIST 0x10 /* only for VALUE_DICT */ 36 | #define HAS_CUSTOMCMP 0x20 /* only for VALUE_DICT */ 37 | #define IS_MALLOCED 0x80 38 | 39 | 40 | typedef struct ARRAY_tag ARRAY; 41 | struct ARRAY_tag { 42 | VALUE* value_buf; 43 | size_t size; 44 | size_t alloc; 45 | }; 46 | 47 | typedef struct RBTREE_tag RBTREE; 48 | struct RBTREE_tag { 49 | /* We store color by using the flag HAS_REDCOLOR of the key. */ 50 | VALUE key; 51 | VALUE value; 52 | RBTREE* left; 53 | RBTREE* right; 54 | 55 | /* These are present only if HAS_ORDERLIST. */ 56 | RBTREE* order_prev; 57 | RBTREE* order_next; 58 | }; 59 | 60 | /* Maximal height of the RB-tree. Given we can never allocate more nodes 61 | * then 2^(sizeof(void*) * 8) and given the longest root<-->leaf path cannot 62 | * be longer then twice the shortest one in the RB-tree, the following 63 | * is guaranteed to be large enough. */ 64 | #define RBTREE_MAX_HEIGHT (2 * 8 * sizeof(void*)) 65 | 66 | typedef struct DICT_tag DICT; 67 | struct DICT_tag { 68 | RBTREE* root; 69 | size_t size; 70 | 71 | /* These are present only when flags VALUE_DICT_MAINTAINORDER or 72 | * custom_cmp_func is used. */ 73 | RBTREE* order_head; 74 | RBTREE* order_tail; 75 | int (*cmp_func)(const char*, size_t, const char*, size_t); 76 | }; 77 | 78 | 79 | #if defined offsetof 80 | #define OFFSETOF(type, member) offsetof(type, member) 81 | #elif defined __GNUC__ && __GNUC__ >= 4 82 | #define OFFSETOF(type, member) __builtin_offsetof(type, member) 83 | #else 84 | #define OFFSETOF(type, member) ((size_t) &((type*)0)->member) 85 | #endif 86 | 87 | 88 | /*************** 89 | *** Helpers *** 90 | ***************/ 91 | 92 | /* We don't want to include just because of roundf() and round(). 93 | * Especially as on some systems it requires explicit linking with math 94 | * library (-lm). */ 95 | #define ROUNDF(inttype, x) ((inttype)((x) >= 0.0f ? (x) + 0.5f : (x) - 0.5f)) 96 | #define ROUNDD(inttype, x) ((inttype)((x) >= 0.0 ? (x) + 0.5 : (x) - 0.5)) 97 | 98 | 99 | static void* 100 | value_init_ex(VALUE* v, VALUE_TYPE type, size_t size, size_t align) 101 | { 102 | v->data[0] = (uint8_t) type; 103 | 104 | if(size + align <= sizeof(VALUE)) { 105 | return &v->data[align]; 106 | } else { 107 | void* buf; 108 | 109 | v->data[0] |= IS_MALLOCED; 110 | buf = malloc(size); 111 | if(buf == NULL) { 112 | v->data[0] = (uint8_t) VALUE_NULL; 113 | return NULL; 114 | } 115 | 116 | *((void**) &v->data[sizeof(void*)]) = buf; 117 | return buf; 118 | } 119 | } 120 | 121 | static void* 122 | value_init(VALUE* v, VALUE_TYPE type, size_t size) 123 | { 124 | return value_init_ex(v, type, size, 1); 125 | } 126 | 127 | static int 128 | value_init_simple(VALUE* v, VALUE_TYPE type, const void* data, size_t size) 129 | { 130 | void* payload; 131 | 132 | payload = value_init(v, type, size); 133 | if(payload == NULL) 134 | return -1; 135 | 136 | memcpy(payload, data, size); 137 | return 0; 138 | } 139 | 140 | static uint8_t* 141 | value_payload_ex(VALUE* v, size_t align) 142 | { 143 | if(v == NULL) 144 | return NULL; 145 | 146 | if(!(v->data[0] & IS_MALLOCED)) 147 | return (void*)(v->data + align); 148 | else 149 | return *(void**)(v->data + sizeof(void*)); 150 | } 151 | 152 | static uint8_t* 153 | value_payload(VALUE* v) 154 | { 155 | return value_payload_ex(v, 1); 156 | } 157 | 158 | 159 | /******************** 160 | *** Generic info *** 161 | ********************/ 162 | 163 | VALUE_TYPE 164 | value_type(const VALUE* v) 165 | { 166 | if(v == NULL) 167 | return VALUE_NULL; 168 | return (VALUE_TYPE)(v->data[0] & TYPE_MASK); 169 | } 170 | 171 | int 172 | value_is_compatible(const VALUE* v, VALUE_TYPE type) 173 | { 174 | if(value_type(v) == type) 175 | return 1; 176 | 177 | /* We say any numeric value is compatible with another numeric value as 178 | * long as the conversion does not loose much information. */ 179 | switch(value_type(v)) { 180 | case VALUE_INT32: 181 | return (type == VALUE_INT64 || type == VALUE_FLOAT || type == VALUE_DOUBLE) || 182 | (type == VALUE_UINT32 && value_int32(v) >= 0) || 183 | (type == VALUE_UINT64 && value_int32(v) >= 0); 184 | break; 185 | 186 | case VALUE_UINT32: 187 | return (type == VALUE_INT64 || type == VALUE_UINT64 || type == VALUE_FLOAT || type == VALUE_DOUBLE) || 188 | (type == VALUE_INT32 && value_uint32(v) <= INT32_MAX); 189 | break; 190 | 191 | case VALUE_INT64: 192 | return (type == VALUE_FLOAT || type == VALUE_DOUBLE) || 193 | (type == VALUE_INT32 && value_int64(v) >= INT32_MIN && value_int64(v) <= INT32_MAX) || 194 | (type == VALUE_UINT32 && value_int64(v) >= 0 && value_int64(v) <= UINT32_MAX) || 195 | (type == VALUE_UINT64 && value_int64(v) >= 0); 196 | break; 197 | 198 | case VALUE_UINT64: 199 | return (type == VALUE_FLOAT || type == VALUE_DOUBLE) || 200 | (type == VALUE_INT32 && value_uint64(v) <= INT32_MAX) || 201 | (type == VALUE_UINT32 && value_uint64(v) <= UINT32_MAX) || 202 | (type == VALUE_INT64 && value_uint64(v) <= INT64_MAX); 203 | break; 204 | 205 | case VALUE_FLOAT: 206 | return (type == VALUE_DOUBLE) || 207 | (type == VALUE_INT32 && value_float(v) == (float)value_int32(v)) || 208 | (type == VALUE_UINT32 && value_float(v) == (float)value_uint32(v)) || 209 | (type == VALUE_INT64 && value_float(v) == (float)value_int64(v)) || 210 | (type == VALUE_UINT64 && value_float(v) == (float)value_uint64(v)); 211 | break; 212 | 213 | case VALUE_DOUBLE: 214 | return (type == VALUE_FLOAT) || 215 | (type == VALUE_INT32 && value_double(v) == (double)value_int32(v)) || 216 | (type == VALUE_UINT32 && value_double(v) == (double)value_uint32(v)) || 217 | (type == VALUE_INT64 && value_double(v) == (double)value_int64(v)) || 218 | (type == VALUE_UINT64 && value_double(v) == (double)value_uint64(v)); 219 | break; 220 | 221 | default: 222 | break; 223 | } 224 | 225 | return 0; 226 | } 227 | 228 | int 229 | value_is_new(const VALUE* v) 230 | { 231 | return (v != NULL && value_type(v) == VALUE_NULL && (v->data[0] & IS_NEW)); 232 | } 233 | 234 | VALUE* 235 | value_path(VALUE* root, const char* path) 236 | { 237 | const char* token_beg = path; 238 | const char* token_end; 239 | VALUE* v = root; 240 | 241 | while(1) { 242 | token_end = token_beg; 243 | while(*token_end != '\0' && *token_end != '/') 244 | token_end++; 245 | 246 | if(token_end - token_beg > 2 && token_beg[0] == '[' && token_end[-1] == ']') { 247 | size_t index = 0; 248 | 249 | token_beg++; 250 | while('0' <= *token_beg && *token_beg <= '9') { 251 | index = index * 10 + (*token_beg - '0'); 252 | token_beg++; 253 | } 254 | if(*token_beg != ']') 255 | return NULL; 256 | 257 | v = value_array_get(v, index); 258 | } else if(token_end - token_beg > 0) { 259 | v = value_dict_get_(v, token_beg, token_end - token_beg); 260 | } 261 | 262 | if(v == NULL) 263 | return NULL; 264 | 265 | if(*token_end == '\0') 266 | return v; 267 | 268 | token_beg = token_end+1; 269 | } 270 | } 271 | 272 | 273 | /******************** 274 | *** Initializers *** 275 | ********************/ 276 | 277 | void 278 | value_init_null(VALUE* v) 279 | { 280 | if(v != NULL) 281 | v->data[0] = (uint8_t) VALUE_NULL; 282 | } 283 | 284 | static void 285 | value_init_new(VALUE* v) 286 | { 287 | v->data[0] = ((uint8_t) VALUE_NULL) | IS_NEW; 288 | } 289 | 290 | int 291 | value_init_bool(VALUE* v, int b) 292 | { 293 | if(v == NULL) 294 | return -1; 295 | 296 | v->data[0] = (uint8_t) VALUE_BOOL; 297 | v->data[1] = (b != 0) ? 1 : 0; 298 | 299 | return 0; 300 | } 301 | 302 | int 303 | value_init_int32(VALUE* v, int32_t i32) 304 | { 305 | if(v == NULL) 306 | return -1; 307 | 308 | return value_init_simple(v, VALUE_INT32, &i32, sizeof(int32_t)); 309 | } 310 | 311 | int 312 | value_init_uint32(VALUE* v, uint32_t u32) 313 | { 314 | if(v == NULL) 315 | return -1; 316 | 317 | return value_init_simple(v, VALUE_UINT32, &u32, sizeof(uint32_t)); 318 | } 319 | 320 | int 321 | value_init_int64(VALUE* v, int64_t i64) 322 | { 323 | if(v == NULL) 324 | return -1; 325 | 326 | return value_init_simple(v, VALUE_INT64, &i64, sizeof(int64_t)); 327 | } 328 | 329 | int 330 | value_init_uint64(VALUE* v, uint64_t u64) 331 | { 332 | if(v == NULL) 333 | return -1; 334 | 335 | return value_init_simple(v, VALUE_UINT64, &u64, sizeof(uint64_t)); 336 | } 337 | 338 | int 339 | value_init_float(VALUE* v, float f) 340 | { 341 | if(v == NULL) 342 | return -1; 343 | 344 | return value_init_simple(v, VALUE_FLOAT, &f, sizeof(float)); 345 | } 346 | 347 | int 348 | value_init_double(VALUE* v, double d) 349 | { 350 | if(v == NULL) 351 | return -1; 352 | 353 | return value_init_simple(v, VALUE_DOUBLE, &d, sizeof(double)); 354 | } 355 | 356 | int 357 | value_init_string_(VALUE* v, const char* str, size_t len) 358 | { 359 | uint8_t* payload; 360 | size_t tmplen; 361 | size_t off; 362 | 363 | if(v == NULL) 364 | return -1; 365 | 366 | tmplen = len; 367 | off = 0; 368 | while(tmplen >= 128) { 369 | off++; 370 | tmplen = tmplen >> 7; 371 | } 372 | off++; 373 | 374 | payload = value_init(v, VALUE_STRING, off + len + 1); 375 | if(payload == NULL) 376 | return -1; 377 | 378 | tmplen = len; 379 | off = 0; 380 | while(tmplen >= 128) { 381 | payload[off++] = 0x80 | (tmplen & 0x7f); 382 | tmplen = tmplen >> 7; 383 | } 384 | payload[off++] = tmplen & 0x7f; 385 | 386 | memcpy(payload + off, str, len); 387 | payload[off + len] = '\0'; 388 | return 0; 389 | } 390 | 391 | int 392 | value_init_string(VALUE* v, const char* str) 393 | { 394 | return value_init_string_(v, str, (str != NULL) ? strlen(str) : 0); 395 | } 396 | 397 | int 398 | value_init_array(VALUE* v) 399 | { 400 | uint8_t* payload; 401 | 402 | if(v == NULL) 403 | return -1; 404 | 405 | payload = value_init_ex(v, VALUE_ARRAY, sizeof(ARRAY), sizeof(void*)); 406 | if(payload == NULL) 407 | return -1; 408 | memset(payload, 0, sizeof(ARRAY)); 409 | 410 | return 0; 411 | } 412 | 413 | int 414 | value_init_dict(VALUE* v) 415 | { 416 | return value_init_dict_ex(v, NULL, 0); 417 | } 418 | 419 | int 420 | value_init_dict_ex(VALUE* v, 421 | int (*custom_cmp_func)(const char*, size_t, const char*, size_t), 422 | unsigned flags) 423 | { 424 | uint8_t* payload; 425 | size_t payload_size; 426 | 427 | if(v == NULL) 428 | return -1; 429 | 430 | if(custom_cmp_func != NULL || (flags & VALUE_DICT_MAINTAINORDER)) 431 | payload_size = sizeof(DICT); 432 | else 433 | payload_size = OFFSETOF(DICT, order_head); 434 | 435 | payload = value_init_ex(v, VALUE_DICT, payload_size, sizeof(void*)); 436 | if(payload == NULL) 437 | return -1; 438 | memset(payload, 0, payload_size); 439 | 440 | if(custom_cmp_func != NULL) { 441 | v->data[0] |= HAS_CUSTOMCMP; 442 | ((DICT*)payload)->cmp_func = custom_cmp_func; 443 | } 444 | 445 | if(flags & VALUE_DICT_MAINTAINORDER) 446 | v->data[0] |= HAS_ORDERLIST; 447 | 448 | return 0; 449 | } 450 | 451 | void 452 | value_fini(VALUE* v) 453 | { 454 | if(v == NULL) 455 | return; 456 | 457 | if(value_type(v) == VALUE_ARRAY) 458 | value_array_clean(v); 459 | 460 | if(value_type(v) == VALUE_DICT) 461 | value_dict_clean(v); 462 | 463 | if(v->data[0] & 0x80) 464 | free(value_payload(v)); 465 | 466 | v->data[0] = VALUE_NULL; 467 | } 468 | 469 | 470 | /************************** 471 | *** Basic type getters *** 472 | **************************/ 473 | 474 | int 475 | value_bool(const VALUE* v) 476 | { 477 | if(value_type(v) != VALUE_BOOL) 478 | return -1; 479 | 480 | return v->data[1]; 481 | } 482 | 483 | int32_t 484 | value_int32(const VALUE* v) 485 | { 486 | uint8_t* payload = value_payload((VALUE*) v); 487 | union { 488 | int32_t i32; 489 | uint32_t u32; 490 | int64_t i64; 491 | uint64_t u64; 492 | float f; 493 | double d; 494 | } ret; 495 | 496 | switch(value_type(v)) { 497 | case VALUE_INT32: memcpy(&ret.i32, payload, sizeof(int32_t)); return (int32_t) ret.i32; 498 | case VALUE_UINT32: memcpy(&ret.u32, payload, sizeof(uint32_t)); return (int32_t) ret.u32; 499 | case VALUE_INT64: memcpy(&ret.i64, payload, sizeof(int64_t)); return (int32_t) ret.i64; 500 | case VALUE_UINT64: memcpy(&ret.u64, payload, sizeof(uint64_t)); return (int32_t) ret.u64; 501 | case VALUE_FLOAT: memcpy(&ret.f, payload, sizeof(float)); return ROUNDF(int32_t, ret.f); 502 | case VALUE_DOUBLE: memcpy(&ret.d, payload, sizeof(double)); return ROUNDD(int32_t, ret.d); 503 | default: return -1; 504 | } 505 | } 506 | 507 | uint32_t 508 | value_uint32(const VALUE* v) 509 | { 510 | uint8_t* payload = value_payload((VALUE*) v); 511 | union { 512 | int32_t i32; 513 | uint32_t u32; 514 | int64_t i64; 515 | uint64_t u64; 516 | float f; 517 | double d; 518 | } ret; 519 | 520 | switch(value_type(v)) { 521 | case VALUE_INT32: memcpy(&ret.i32, payload, sizeof(int32_t)); return (uint32_t) ret.i32; 522 | case VALUE_UINT32: memcpy(&ret.u32, payload, sizeof(uint32_t)); return (uint32_t) ret.u32; 523 | case VALUE_INT64: memcpy(&ret.i64, payload, sizeof(int64_t)); return (uint32_t) ret.i64; 524 | case VALUE_UINT64: memcpy(&ret.u64, payload, sizeof(uint64_t)); return (uint32_t) ret.u64; 525 | case VALUE_FLOAT: memcpy(&ret.f, payload, sizeof(float)); return ROUNDF(uint32_t, ret.f); 526 | case VALUE_DOUBLE: memcpy(&ret.d, payload, sizeof(double)); return ROUNDD(uint32_t, ret.d); 527 | default: return UINT32_MAX; 528 | } 529 | } 530 | 531 | int64_t 532 | value_int64(const VALUE* v) 533 | { 534 | uint8_t* payload = value_payload((VALUE*) v); 535 | union { 536 | int32_t i32; 537 | uint32_t u32; 538 | int64_t i64; 539 | uint64_t u64; 540 | float f; 541 | double d; 542 | } ret; 543 | 544 | switch(value_type(v)) { 545 | case VALUE_INT32: memcpy(&ret.i32, payload, sizeof(int32_t)); return (int64_t) ret.i32; 546 | case VALUE_UINT32: memcpy(&ret.u32, payload, sizeof(uint32_t)); return (int64_t) ret.u32; 547 | case VALUE_INT64: memcpy(&ret.i64, payload, sizeof(int64_t)); return (int64_t) ret.i64; 548 | case VALUE_UINT64: memcpy(&ret.u64, payload, sizeof(uint64_t)); return (int64_t) ret.u64; 549 | case VALUE_FLOAT: memcpy(&ret.f, payload, sizeof(float)); return ROUNDF(int64_t, ret.f); 550 | case VALUE_DOUBLE: memcpy(&ret.d, payload, sizeof(double)); return ROUNDD(int64_t, ret.d); 551 | default: return -1; 552 | } 553 | } 554 | 555 | uint64_t 556 | value_uint64(const VALUE* v) 557 | { 558 | uint8_t* payload = value_payload((VALUE*) v); 559 | union { 560 | int32_t i32; 561 | uint32_t u32; 562 | int64_t i64; 563 | uint64_t u64; 564 | float f; 565 | double d; 566 | } ret; 567 | 568 | switch(value_type(v)) { 569 | case VALUE_INT32: memcpy(&ret.i32, payload, sizeof(int32_t)); return (uint64_t) ret.i32; 570 | case VALUE_UINT32: memcpy(&ret.u32, payload, sizeof(uint32_t)); return (uint64_t) ret.u32; 571 | case VALUE_INT64: memcpy(&ret.i64, payload, sizeof(int64_t)); return (uint64_t) ret.i64; 572 | case VALUE_UINT64: memcpy(&ret.u64, payload, sizeof(uint64_t)); return (uint64_t) ret.u64; 573 | case VALUE_FLOAT: memcpy(&ret.f, payload, sizeof(float)); return ROUNDF(uint64_t, ret.f); 574 | case VALUE_DOUBLE: memcpy(&ret.d, payload, sizeof(double)); return ROUNDD(uint64_t, ret.d); 575 | default: return UINT64_MAX; 576 | } 577 | } 578 | 579 | float 580 | value_float(const VALUE* v) 581 | { 582 | uint8_t* payload = value_payload((VALUE*) v); 583 | union { 584 | int32_t i32; 585 | uint32_t u32; 586 | int64_t i64; 587 | uint64_t u64; 588 | float f; 589 | double d; 590 | } ret; 591 | 592 | switch(value_type(v)) { 593 | case VALUE_INT32: memcpy(&ret.i32, payload, sizeof(int32_t)); return (float) ret.i32; 594 | case VALUE_UINT32: memcpy(&ret.u32, payload, sizeof(uint32_t)); return (float) ret.u32; 595 | case VALUE_INT64: memcpy(&ret.i64, payload, sizeof(int64_t)); return (float) ret.i64; 596 | case VALUE_UINT64: memcpy(&ret.u64, payload, sizeof(uint64_t)); return (float) ret.u64; 597 | case VALUE_FLOAT: memcpy(&ret.f, payload, sizeof(float)); return ret.f; 598 | case VALUE_DOUBLE: memcpy(&ret.d, payload, sizeof(double)); return (float) ret.d; 599 | default: return -1.0f; /* FIXME: NaN would be likely better but we do not include */ 600 | } 601 | } 602 | 603 | double 604 | value_double(const VALUE* v) 605 | { 606 | uint8_t* payload = value_payload((VALUE*) v); 607 | union { 608 | int32_t i32; 609 | uint32_t u32; 610 | int64_t i64; 611 | uint64_t u64; 612 | float f; 613 | double d; 614 | } ret; 615 | 616 | switch(value_type(v)) { 617 | case VALUE_INT32: memcpy(&ret.i32, payload, sizeof(int32_t)); return (double) ret.i32; 618 | case VALUE_UINT32: memcpy(&ret.u32, payload, sizeof(uint32_t)); return (double) ret.u32; 619 | case VALUE_INT64: memcpy(&ret.i64, payload, sizeof(int64_t)); return (double) ret.i64; 620 | case VALUE_UINT64: memcpy(&ret.u64, payload, sizeof(uint64_t)); return (double) ret.u64; 621 | case VALUE_FLOAT: memcpy(&ret.f, payload, sizeof(float)); return (double) ret.f; 622 | case VALUE_DOUBLE: memcpy(&ret.d, payload, sizeof(double)); return ret.d; 623 | default: return -1.0; /* FIXME: NaN would be likely better but we do not include */ 624 | } 625 | } 626 | 627 | const char* 628 | value_string(const VALUE* v) 629 | { 630 | uint8_t* payload; 631 | size_t off = 0; 632 | 633 | if(value_type(v) != VALUE_STRING) 634 | return NULL; 635 | 636 | payload = value_payload((VALUE*) v); 637 | while(payload[off] & 0x80) 638 | off++; 639 | off++; 640 | 641 | return (char*) payload + off; 642 | } 643 | 644 | size_t 645 | value_string_length(const VALUE* v) 646 | { 647 | uint8_t* payload; 648 | size_t off = 0; 649 | size_t len = 0; 650 | unsigned shift = 0; 651 | 652 | if(value_type(v) != VALUE_STRING) 653 | return 0; 654 | 655 | payload = value_payload((VALUE*) v); 656 | while(payload[off] & 0x80) { 657 | len |= (payload[off] & 0x7f) << shift; 658 | shift += 7; 659 | off++; 660 | } 661 | len |= payload[off] << shift; 662 | 663 | return len; 664 | } 665 | 666 | 667 | /******************* 668 | *** VALUE_ARRAY *** 669 | *******************/ 670 | 671 | static ARRAY* 672 | value_array_payload(VALUE* v) 673 | { 674 | if(value_type(v) != VALUE_ARRAY) 675 | return NULL; 676 | 677 | return (ARRAY*) value_payload_ex(v, sizeof(void*)); 678 | } 679 | 680 | static int 681 | value_array_realloc(ARRAY* a, size_t alloc) 682 | { 683 | VALUE* value_buf; 684 | 685 | value_buf = (VALUE*) realloc(a->value_buf, alloc * sizeof(VALUE)); 686 | if(value_buf == NULL) 687 | return -1; 688 | 689 | a->value_buf = value_buf; 690 | a->alloc = alloc; 691 | return 0; 692 | } 693 | 694 | VALUE* 695 | value_array_get(const VALUE* v, size_t index) 696 | { 697 | ARRAY* a = value_array_payload((VALUE*) v); 698 | 699 | if(a != NULL && index < a->size) 700 | return &a->value_buf[index]; 701 | else 702 | return NULL; 703 | } 704 | 705 | VALUE* 706 | value_array_get_all(const VALUE* v) 707 | { 708 | ARRAY* a = value_array_payload((VALUE*) v); 709 | 710 | if(a != NULL) 711 | return a->value_buf; 712 | else 713 | return NULL; 714 | } 715 | 716 | size_t 717 | value_array_size(const VALUE* v) 718 | { 719 | ARRAY* a = value_array_payload((VALUE*) v); 720 | 721 | if(a != NULL) 722 | return a->size; 723 | else 724 | return 0; 725 | } 726 | 727 | VALUE* 728 | value_array_append(VALUE* v) 729 | { 730 | return value_array_insert(v, value_array_size(v)); 731 | } 732 | 733 | VALUE* 734 | value_array_insert(VALUE* v, size_t index) 735 | { 736 | ARRAY* a = value_array_payload(v); 737 | 738 | if(a == NULL || index > a->size) 739 | return NULL; 740 | 741 | if(a->size >= a->alloc) { 742 | if(value_array_realloc(a, (a->alloc > 0) ? a->alloc * 2 : 1) != 0) 743 | return NULL; 744 | } 745 | 746 | if(index < a->size) { 747 | memmove(a->value_buf + index + 1, a->value_buf + index, 748 | (a->size - index) * sizeof(VALUE)); 749 | } 750 | value_init_new(&a->value_buf[index]); 751 | a->size++; 752 | return &a->value_buf[index]; 753 | } 754 | 755 | int 756 | value_array_remove(VALUE* v, size_t index) 757 | { 758 | return value_array_remove_range(v, index, 1); 759 | } 760 | 761 | int 762 | value_array_remove_range(VALUE* v, size_t index, size_t count) 763 | { 764 | ARRAY* a = value_array_payload(v); 765 | size_t i; 766 | 767 | if(a == NULL || index + count > a->size) 768 | return -1; 769 | 770 | for(i = index; i < index + count; i++) 771 | value_fini(&a->value_buf[i]); 772 | 773 | if(index + count < a->size) { 774 | memmove(a->value_buf + index, a->value_buf + index + count, 775 | (a->size - (index + count)) * sizeof(VALUE)); 776 | } 777 | a->size -= count; 778 | 779 | if(4 * a->size < a->alloc) 780 | value_array_realloc(a, a->alloc / 2); 781 | 782 | return 0; 783 | } 784 | 785 | void 786 | value_array_clean(VALUE* v) 787 | { 788 | ARRAY* a = value_array_payload(v); 789 | size_t i; 790 | 791 | if(a == NULL) 792 | return; 793 | 794 | for(i = 0; i < a->size; i++) 795 | value_fini(&a->value_buf[i]); 796 | 797 | free(a->value_buf); 798 | memset(a, 0, sizeof(ARRAY)); 799 | } 800 | 801 | 802 | /****************** 803 | *** VALUE_DICT *** 804 | ******************/ 805 | 806 | #define MAKE_RED(node) do { (node)->key.data[0] |= HAS_REDCOLOR; } while(0) 807 | #define MAKE_BLACK(node) do { (node)->key.data[0] &= ~HAS_REDCOLOR; } while(0) 808 | #define TOGGLE_COLOR(node) do { (node)->key.data[0] ^= HAS_REDCOLOR; } while(0) 809 | #define IS_RED(node) ((node)->key.data[0] & HAS_REDCOLOR) 810 | #define IS_BLACK(node) (!IS_RED(node)) 811 | 812 | static DICT* 813 | value_dict_payload(VALUE* v) 814 | { 815 | if(value_type(v) != VALUE_DICT) 816 | return NULL; 817 | 818 | return (DICT*) value_payload_ex(v, sizeof(void*)); 819 | } 820 | 821 | static int 822 | value_dict_default_cmp(const char* key1, size_t len1, const char* key2, size_t len2) 823 | { 824 | /* Comparing lengths 1st might be in general especially if the keys are 825 | * long, but it would break value_dict_walk_sorted(). 826 | * 827 | * In most apps keys are short and ASCII. It is nice to follow 828 | * lexicographic order at least in such cases as that's what most 829 | * people expect. And real world, the keys are usually quite short so 830 | * the cost should be acceptable. 831 | */ 832 | 833 | size_t min_len = (len1 < len2) ? len1 : len2; 834 | int cmp; 835 | 836 | cmp = memcmp(key1, key2, min_len); 837 | if(cmp == 0 && len1 != len2) 838 | cmp = (len1 < len2) ? -1 : +1; 839 | 840 | return cmp; 841 | } 842 | 843 | static int 844 | value_dict_cmp(const VALUE* v, const DICT* d, 845 | const char* key1, size_t len1, const char* key2, size_t len2) 846 | { 847 | if(!(v->data[0] & HAS_CUSTOMCMP)) 848 | return value_dict_default_cmp(key1, len1, key2, len2); 849 | else 850 | return d->cmp_func(key1, len1, key2, len2); 851 | } 852 | 853 | static int 854 | value_dict_leftmost_path(RBTREE** path, RBTREE* node) 855 | { 856 | int n = 0; 857 | 858 | while(node != NULL) { 859 | path[n++] = node; 860 | node = node->left; 861 | } 862 | 863 | return n; 864 | } 865 | 866 | unsigned 867 | value_dict_flags(const VALUE* v) 868 | { 869 | DICT* d = value_dict_payload((VALUE*) v); 870 | unsigned flags = 0; 871 | 872 | if(d != NULL && (v->data[0] & HAS_ORDERLIST)) 873 | flags |= VALUE_DICT_MAINTAINORDER; 874 | 875 | return flags; 876 | } 877 | 878 | size_t 879 | value_dict_size(const VALUE* v) 880 | { 881 | DICT* d = value_dict_payload((VALUE*) v); 882 | 883 | if(d != NULL) 884 | return d->size; 885 | else 886 | return 0; 887 | } 888 | 889 | size_t 890 | value_dict_keys_sorted(const VALUE* v, const VALUE** buffer, size_t buffer_size) 891 | { 892 | DICT* d = value_dict_payload((VALUE*) v); 893 | RBTREE* stack[RBTREE_MAX_HEIGHT]; 894 | int stack_size = 0; 895 | RBTREE* node; 896 | size_t n = 0; 897 | 898 | if(d == NULL) 899 | return 0; 900 | 901 | stack_size = value_dict_leftmost_path(stack, d->root); 902 | 903 | while(stack_size > 0 && n < buffer_size) { 904 | node = stack[--stack_size]; 905 | buffer[n++] = &node->key; 906 | stack_size += value_dict_leftmost_path(stack + stack_size, node->right); 907 | } 908 | 909 | return n; 910 | } 911 | 912 | size_t 913 | value_dict_keys_ordered(const VALUE* v, const VALUE** buffer, size_t buffer_size) 914 | { 915 | DICT* d = value_dict_payload((VALUE*) v); 916 | RBTREE* node; 917 | size_t n = 0; 918 | 919 | if(d == NULL || !(v->data[0] & HAS_ORDERLIST)) 920 | return 0; 921 | 922 | node = d->order_head; 923 | while(node != NULL && n < buffer_size) { 924 | buffer[n++] = &node->key; 925 | node = node->order_next; 926 | } 927 | 928 | return n; 929 | } 930 | 931 | VALUE* 932 | value_dict_get_(const VALUE* v, const char* key, size_t key_len) 933 | { 934 | DICT* d = value_dict_payload((VALUE*) v); 935 | RBTREE* node = (d != NULL) ? d->root : NULL; 936 | int cmp; 937 | 938 | while(node != NULL) { 939 | cmp = value_dict_cmp(v, d, key, key_len, value_string(&node->key), value_string_length(&node->key)); 940 | 941 | if(cmp < 0) 942 | node = node->left; 943 | else if(cmp > 0) 944 | node = node->right; 945 | else 946 | return &node->value; 947 | } 948 | 949 | return NULL; 950 | } 951 | 952 | VALUE* 953 | value_dict_get(const VALUE* v, const char* key) 954 | { 955 | return value_dict_get_(v, key, (key != NULL) ? strlen(key) : 0); 956 | } 957 | 958 | static void 959 | value_dict_rotate_left(DICT* d, RBTREE* parent, RBTREE* node) 960 | { 961 | RBTREE* tmp = node->right; 962 | node->right = tmp->left; 963 | tmp->left = node; 964 | 965 | if(parent != NULL) { 966 | if(parent->left == node) 967 | parent->left = tmp; 968 | else if(parent->right == node) 969 | parent->right = tmp; 970 | } else { 971 | d->root = tmp; 972 | } 973 | } 974 | 975 | static void 976 | value_dict_rotate_right(DICT* d, RBTREE* parent, RBTREE* node) 977 | { 978 | RBTREE* tmp = node->left; 979 | node->left = tmp->right; 980 | tmp->right = node; 981 | 982 | if(parent != NULL) { 983 | if(parent->right == node) 984 | parent->right = tmp; 985 | else if(parent->left == node) 986 | parent->left = tmp; 987 | } else { 988 | d->root = tmp; 989 | } 990 | } 991 | 992 | /* Fixes the tree after inserting (red) node path[path_len-1]. */ 993 | static void 994 | value_dict_fix_after_insert(DICT* d, RBTREE** path, int path_len) 995 | { 996 | RBTREE* node; 997 | RBTREE* parent; 998 | RBTREE* grandparent; 999 | RBTREE* grandgrandparent; 1000 | RBTREE* uncle; 1001 | 1002 | while(1) { 1003 | node = path[path_len-1]; 1004 | parent = (path_len > 1) ? path[path_len-2] : NULL; 1005 | if(parent == NULL) { 1006 | MAKE_BLACK(node); 1007 | d->root = node; 1008 | break; 1009 | } 1010 | 1011 | if(IS_BLACK(parent)) 1012 | break; 1013 | 1014 | /* If we reach here, there is a double-red issue: The node as well as 1015 | * the parent are red. 1016 | * 1017 | * Note grandparent has to exist (implied from red parent). 1018 | */ 1019 | grandparent = path[path_len-3]; 1020 | uncle = (grandparent->left == parent) ? grandparent->right : grandparent->left; 1021 | if(uncle == NULL || IS_BLACK(uncle)) { 1022 | /* Black uncle. */ 1023 | grandgrandparent = (path_len > 3) ? path[path_len-4] : NULL; 1024 | if(grandparent->left != NULL && grandparent->left->right == node) { 1025 | value_dict_rotate_left(d, grandparent, parent); 1026 | parent = node; 1027 | node = node->left; 1028 | } else if(grandparent->right != NULL && grandparent->right->left == node) { 1029 | value_dict_rotate_right(d, grandparent, parent); 1030 | parent = node; 1031 | node = node->right; 1032 | } 1033 | if(parent->left == node) 1034 | value_dict_rotate_right(d, grandgrandparent, grandparent); 1035 | else 1036 | value_dict_rotate_left(d, grandgrandparent, grandparent); 1037 | 1038 | /* Note that `parent` now, after the rotations, points to where 1039 | * the grand-parent was originally in the tree hierarchy, and 1040 | * `grandparent` is now its child and also parent of the `uncle`. 1041 | * 1042 | * We switch their colors and hence make sure the upper `parent` 1043 | * is now black. */ 1044 | MAKE_BLACK(parent); 1045 | MAKE_RED(grandparent); 1046 | break; 1047 | } 1048 | 1049 | /* Red uncle. This allows us to make both the parent and the uncle 1050 | * black and propagate the red up to grandparent. */ 1051 | MAKE_BLACK(parent); 1052 | MAKE_BLACK(uncle); 1053 | MAKE_RED(grandparent); 1054 | 1055 | /* But it means we could just move the double-red issue two levels 1056 | * up, so we have to continue re-balancing there. */ 1057 | path_len -= 2; 1058 | } 1059 | } 1060 | 1061 | VALUE* 1062 | value_dict_add_(VALUE* v, const char* key, size_t key_len) 1063 | { 1064 | VALUE* res; 1065 | 1066 | res = value_dict_get_or_add_(v, key, key_len); 1067 | return (value_is_new(res) ? res : NULL); 1068 | } 1069 | 1070 | VALUE* value_dict_add(VALUE* v, const char* key) 1071 | { 1072 | return value_dict_add_(v, key, strlen(key)); 1073 | } 1074 | 1075 | VALUE* 1076 | value_dict_get_or_add_(VALUE* v, const char* key, size_t key_len) 1077 | { 1078 | DICT* d = value_dict_payload((VALUE*) v); 1079 | RBTREE* node = (d != NULL) ? d->root : NULL; 1080 | RBTREE* path[RBTREE_MAX_HEIGHT]; 1081 | int path_len = 0; 1082 | int cmp; 1083 | 1084 | if(d == NULL) 1085 | return NULL; 1086 | 1087 | while(node != NULL) { 1088 | cmp = value_dict_cmp(v, d, key, key_len, 1089 | value_string(&node->key), value_string_length(&node->key)); 1090 | 1091 | path[path_len++] = node; 1092 | 1093 | if(cmp < 0) 1094 | node = node->left; 1095 | else if(cmp > 0) 1096 | node = node->right; 1097 | else 1098 | return &node->value; 1099 | } 1100 | 1101 | /* Add new node into the tree. */ 1102 | node = (RBTREE*) malloc((v->data[0] & HAS_ORDERLIST) ? 1103 | sizeof(RBTREE) : OFFSETOF(RBTREE, order_prev)); 1104 | if(node == NULL) 1105 | return NULL; 1106 | if(value_init_string_(&node->key, key, key_len) != 0) { 1107 | free(node); 1108 | return NULL; 1109 | } 1110 | value_init_new(&node->value); 1111 | node->left = NULL; 1112 | node->right = NULL; 1113 | MAKE_RED(node); 1114 | 1115 | /* Update order_list. */ 1116 | if(v->data[0] & HAS_ORDERLIST) { 1117 | node->order_prev = d->order_tail; 1118 | node->order_next = NULL; 1119 | 1120 | if(d->order_tail != NULL) 1121 | d->order_tail->order_next = node; 1122 | else 1123 | d->order_head = node; 1124 | d->order_tail = node; 1125 | } 1126 | 1127 | /* Insert the new node. */ 1128 | if(path_len > 0) { 1129 | if(cmp < 0) 1130 | path[path_len - 1]->left = node; 1131 | else 1132 | path[path_len - 1]->right = node; 1133 | } else { 1134 | d->root = node; 1135 | } 1136 | 1137 | /* Re-balance. */ 1138 | path[path_len++] = node; 1139 | value_dict_fix_after_insert(d, path, path_len); 1140 | 1141 | d->size++; 1142 | 1143 | return &node->value; 1144 | } 1145 | 1146 | VALUE* 1147 | value_dict_get_or_add(VALUE* v, const char* key) 1148 | { 1149 | return value_dict_get_or_add_(v, key, (key != NULL) ? strlen(key) : 0); 1150 | } 1151 | 1152 | /* Fixes the tree after making the given path one black node shorter. 1153 | * (Note that that the path may end with NULL if the removed node had no child.) */ 1154 | static void 1155 | value_dict_fix_after_remove(DICT* d, RBTREE** path, int path_len) 1156 | { 1157 | RBTREE* node; 1158 | RBTREE* parent; 1159 | RBTREE* grandparent; 1160 | RBTREE* sibling; 1161 | 1162 | while(1) { 1163 | node = path[path_len-1]; 1164 | if(node != NULL && IS_RED(node)) { 1165 | MAKE_BLACK(node); 1166 | break; 1167 | } 1168 | 1169 | parent = (path_len > 1) ? path[path_len-2] : NULL; 1170 | if(parent == NULL) 1171 | break; 1172 | 1173 | /* Sibling has to exist because its subtree must have black 1174 | * count as our subtree + 1. */ 1175 | sibling = (parent->left == node) ? parent->right : parent->left; 1176 | grandparent = (path_len > 2) ? path[path_len-3] : NULL; 1177 | if(IS_RED(sibling)) { 1178 | /* Red sibling: Convert to black sibling case. */ 1179 | if(parent->left == node) 1180 | value_dict_rotate_left(d, grandparent, parent); 1181 | else 1182 | value_dict_rotate_right(d, grandparent, parent); 1183 | 1184 | MAKE_BLACK(sibling); 1185 | MAKE_RED(parent); 1186 | path[path_len-2] = sibling; 1187 | path[path_len-1] = parent; 1188 | path[path_len++] = node; 1189 | continue; 1190 | } 1191 | 1192 | if((sibling->left != NULL && IS_RED(sibling->left)) || 1193 | (sibling->right != NULL && IS_RED(sibling->right))) { 1194 | /* Black sibling having at least one red child. */ 1195 | if(node == parent->left && (sibling->right == NULL || IS_BLACK(sibling->right))) { 1196 | MAKE_RED(sibling); 1197 | MAKE_BLACK(sibling->left); 1198 | value_dict_rotate_right(d, parent, sibling); 1199 | sibling = parent->right; 1200 | } else if(node == parent->right && (sibling->left == NULL || IS_BLACK(sibling->left))) { 1201 | MAKE_RED(sibling); 1202 | MAKE_BLACK(sibling->right); 1203 | value_dict_rotate_left(d, parent, sibling); 1204 | sibling = parent->left; 1205 | } 1206 | 1207 | if(IS_RED(sibling) != IS_RED(parent)) 1208 | TOGGLE_COLOR(sibling); 1209 | MAKE_BLACK(parent); 1210 | if(node == parent->left) { 1211 | MAKE_BLACK(sibling->right); 1212 | value_dict_rotate_left(d, grandparent, parent); 1213 | } else { 1214 | MAKE_BLACK(sibling->left); 1215 | value_dict_rotate_right(d, grandparent, parent); 1216 | } 1217 | break; 1218 | } 1219 | 1220 | /* Black sibling with both children black. Make sibling subtree one 1221 | * black shorter to match our subtree and try to resolve the black 1222 | * deficit at the parent level. */ 1223 | if(IS_RED(parent)) { 1224 | MAKE_RED(sibling); 1225 | MAKE_BLACK(parent); 1226 | break; 1227 | } else { 1228 | /* Fix the black deficit higher in the tree. */ 1229 | MAKE_RED(sibling); 1230 | path_len--; 1231 | } 1232 | } 1233 | } 1234 | 1235 | int 1236 | value_dict_remove_(VALUE* v, const char* key, size_t key_len) 1237 | { 1238 | DICT* d = value_dict_payload((VALUE*) v); 1239 | RBTREE* node = (d != NULL) ? d->root : NULL; 1240 | RBTREE* single_child; 1241 | RBTREE* path[RBTREE_MAX_HEIGHT]; 1242 | int path_len = 0; 1243 | int cmp; 1244 | 1245 | /* Find the node to remove. */ 1246 | while(node != NULL) { 1247 | cmp = value_dict_cmp(v, d, key, key_len, 1248 | value_string(&node->key), value_string_length(&node->key)); 1249 | 1250 | path[path_len++] = node; 1251 | 1252 | if(cmp < 0) 1253 | node = node->left; 1254 | else if(cmp > 0) 1255 | node = node->right; 1256 | else 1257 | break; 1258 | } 1259 | if(node == NULL) 1260 | return -1; 1261 | 1262 | /* It is far more easier to remove a node at the bottom of the tree, if it 1263 | * has at most one child. Therefore, if we are not at the bottom, we switch 1264 | * our place with another node, which is our direct successor; i.e. with 1265 | * the minimal value of the right subtree. */ 1266 | if(node->right != NULL) { 1267 | RBTREE* successor; 1268 | int node_index = path_len-1; 1269 | 1270 | if(node->right->left != NULL) { 1271 | RBTREE* tmp; 1272 | 1273 | path_len += value_dict_leftmost_path(path + path_len, node->right); 1274 | successor = path[path_len-1]; 1275 | 1276 | tmp = successor->right; 1277 | successor->right = node->right; 1278 | node->right = tmp; 1279 | 1280 | if(path[path_len-2]->left == successor) 1281 | path[path_len-2]->left = node; 1282 | else 1283 | path[path_len-2]->right = node; 1284 | 1285 | path[node_index] = successor; 1286 | path[path_len-1] = node; 1287 | } else if(node->left != NULL) { 1288 | /* node->right is the successor. Must be handled specially as the 1289 | * code above would entangle the pointers. */ 1290 | successor = node->right; 1291 | 1292 | node->right = successor->right; 1293 | successor->right = node; 1294 | 1295 | path[path_len-1] = successor; 1296 | path[path_len++] = node; 1297 | } else { 1298 | /* node->left == NULL; i.e. node has at most one child. 1299 | * The code below is capable to handle this. */ 1300 | successor = NULL; 1301 | } 1302 | 1303 | if(successor != NULL) { 1304 | /* Common work for the two active code paths above. */ 1305 | successor->left = node->left; 1306 | node->left = NULL; 1307 | 1308 | if(node_index > 0) { 1309 | if(path[node_index-1]->left == node) 1310 | path[node_index-1]->left = successor; 1311 | else 1312 | path[node_index-1]->right = successor; 1313 | } else { 1314 | d->root = successor; 1315 | } 1316 | 1317 | if(IS_RED(successor) != IS_RED(node)) { 1318 | TOGGLE_COLOR(successor); 1319 | TOGGLE_COLOR(node); 1320 | } 1321 | } 1322 | } 1323 | 1324 | /* The node now cannot have more then one child. Move it upwards 1325 | * to the node's place. */ 1326 | single_child = (node->left != NULL) ? node->left : node->right; 1327 | if(path_len > 1) { 1328 | if(path[path_len-2]->left == node) 1329 | path[path_len-2]->left = single_child; 1330 | else 1331 | path[path_len-2]->right = single_child; 1332 | } else { 1333 | d->root = single_child; 1334 | } 1335 | path[path_len-1] = single_child; 1336 | 1337 | /* Node is now successfully disconnected. But the tree may need 1338 | * re-balancing if we have removed black node. */ 1339 | if(IS_BLACK(node)) 1340 | value_dict_fix_after_remove(d, path, path_len); 1341 | 1342 | /* Kill the node */ 1343 | if(v->data[0] & HAS_ORDERLIST) { 1344 | if(node->order_prev != NULL) 1345 | node->order_prev->order_next = node->order_next; 1346 | else 1347 | d->order_head = node->order_next; 1348 | 1349 | if(node->order_next != NULL) 1350 | node->order_next->order_prev = node->order_prev; 1351 | else 1352 | d->order_tail = node->order_prev; 1353 | } 1354 | value_fini(&node->key); 1355 | value_fini(&node->value); 1356 | free(node); 1357 | d->size--; 1358 | 1359 | return 0; 1360 | } 1361 | 1362 | int 1363 | value_dict_remove(VALUE* v, const char* key) 1364 | { 1365 | return value_dict_remove_(v, key, (key != NULL) ? strlen(key) : 0); 1366 | } 1367 | 1368 | int 1369 | value_dict_walk_ordered(const VALUE* v, int (*visit_func)(const VALUE*, VALUE*, void*), void* ctx) 1370 | { 1371 | DICT* d = value_dict_payload((VALUE*) v); 1372 | RBTREE* node; 1373 | int ret; 1374 | 1375 | if(d == NULL || !(v->data[0] & HAS_ORDERLIST)) 1376 | return -1; 1377 | 1378 | node = d->order_head; 1379 | while(node != NULL) { 1380 | ret = visit_func(&node->key, &node->value, ctx); 1381 | if(ret != 0) 1382 | return ret; 1383 | node = node->order_next; 1384 | } 1385 | 1386 | return 0; 1387 | } 1388 | 1389 | int 1390 | value_dict_walk_sorted(const VALUE* v, int (*visit_func)(const VALUE*, VALUE*, void*), void* ctx) 1391 | { 1392 | DICT* d = value_dict_payload((VALUE*) v); 1393 | RBTREE* stack[RBTREE_MAX_HEIGHT]; 1394 | int stack_size = 0; 1395 | RBTREE* node; 1396 | int ret; 1397 | 1398 | if(d == NULL) 1399 | return -1; 1400 | 1401 | stack_size = value_dict_leftmost_path(stack, d->root); 1402 | 1403 | while(stack_size > 0) { 1404 | node = stack[--stack_size]; 1405 | ret = visit_func(&node->key, &node->value, ctx); 1406 | if(ret != 0) 1407 | return ret; 1408 | stack_size += value_dict_leftmost_path(stack + stack_size, node->right); 1409 | } 1410 | 1411 | return 0; 1412 | } 1413 | 1414 | void 1415 | value_dict_clean(VALUE* v) 1416 | { 1417 | DICT* d = value_dict_payload((VALUE*) v); 1418 | RBTREE* stack[RBTREE_MAX_HEIGHT]; 1419 | int stack_size; 1420 | RBTREE* node; 1421 | RBTREE* right; 1422 | 1423 | if(d == NULL) 1424 | return; 1425 | 1426 | stack_size = value_dict_leftmost_path(stack, d->root); 1427 | 1428 | while(stack_size > 0) { 1429 | node = stack[--stack_size]; 1430 | right = node->right; 1431 | 1432 | value_fini(&node->key); 1433 | value_fini(&node->value); 1434 | free(node); 1435 | 1436 | stack_size += value_dict_leftmost_path(stack + stack_size, right); 1437 | } 1438 | 1439 | if(v->data[0] & HAS_ORDERLIST) 1440 | memset(d, 0, OFFSETOF(DICT, cmp_func)); 1441 | else 1442 | memset(d, 0, OFFSETOF(DICT, order_head)); 1443 | } 1444 | 1445 | 1446 | 1447 | #ifdef CRE_TEST 1448 | /* Verification of RB-tree correctness. */ 1449 | 1450 | /* Returns black height of the tree, or -1 on an error. */ 1451 | static int 1452 | value_dict_verify_recurse(RBTREE* node) 1453 | { 1454 | int left_black_height; 1455 | int right_black_height; 1456 | int black_height; 1457 | 1458 | if(node->left != NULL) { 1459 | if(IS_RED(node) && IS_RED(node->left)) 1460 | return -1; 1461 | 1462 | left_black_height = value_dict_verify_recurse(node->left); 1463 | if(left_black_height < 0) 1464 | return left_black_height; 1465 | } else { 1466 | left_black_height = 1; 1467 | } 1468 | 1469 | if(node->right != NULL) { 1470 | if(IS_RED(node) && IS_RED(node->right)) 1471 | return -1; 1472 | 1473 | right_black_height = value_dict_verify_recurse(node->right); 1474 | if(right_black_height < 0) 1475 | return right_black_height; 1476 | } else { 1477 | right_black_height = 1; 1478 | } 1479 | 1480 | if(left_black_height != right_black_height) 1481 | return -1; 1482 | 1483 | black_height = left_black_height; 1484 | if(IS_BLACK(node)) 1485 | black_height++; 1486 | return black_height; 1487 | } 1488 | 1489 | /* Returns 0 if ok, or -1 on an error. */ 1490 | int 1491 | value_dict_verify(VALUE* v) 1492 | { 1493 | DICT* d = value_dict_payload(v); 1494 | if(d == NULL) 1495 | return -1; 1496 | 1497 | if(d->root == NULL) 1498 | return 0; 1499 | 1500 | if(IS_RED(d->root)) 1501 | return -1; 1502 | 1503 | return (value_dict_verify_recurse(d->root) > 0) ? 0 : -1; 1504 | } 1505 | 1506 | #endif /* #ifdef CRE_TEST */ 1507 | -------------------------------------------------------------------------------- /src/value.h: -------------------------------------------------------------------------------- 1 | /* 2 | * C Reusables 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef CRE_VALUE_H 27 | #define CRE_VALUE_H 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | #include 34 | #include 35 | 36 | 37 | /* The value structure. 38 | * Use as opaque. 39 | */ 40 | typedef struct VALUE { 41 | /* We need at least 2 * sizeof(void*). Sixteen bytes covers that on 64-bit 42 | * platforms and it seems as a good compromise allowing to "inline" all 43 | * numeric types as well as short strings; which is good idea: most dict 44 | * keys as well as many string values are in practice quite short. */ 45 | uint8_t data[16]; 46 | } VALUE; 47 | 48 | 49 | /* Value types. 50 | */ 51 | typedef enum VALUE_TYPE { 52 | VALUE_NULL = 0, 53 | VALUE_BOOL, 54 | VALUE_INT32, 55 | VALUE_UINT32, 56 | VALUE_INT64, 57 | VALUE_UINT64, 58 | VALUE_FLOAT, 59 | VALUE_DOUBLE, 60 | VALUE_STRING, 61 | VALUE_ARRAY, 62 | VALUE_DICT 63 | } VALUE_TYPE; 64 | 65 | 66 | /* Free any resources the value holds. 67 | * For ARRAY and DICT it is recursive. 68 | */ 69 | void value_fini(VALUE* v); 70 | 71 | /* Get value type. 72 | */ 73 | VALUE_TYPE value_type(const VALUE* v); 74 | 75 | /* Check whether the value is "compatible" with the given type. 76 | * 77 | * This is especially useful for determining whether a numeric value can be 78 | * "casted" to other numeric type. The function does some basic checking 79 | * whether such conversion looses substantial information. 80 | * 81 | * For example, value initialized with init_float(&v, 1.0f) is considered 82 | * compatible with INT32, because 1.0f has zero fraction and 1 fits between 83 | * INT32_MIN and INT32_MAX. Therefore calling int32_value(&v) gets sensible 84 | * result. 85 | */ 86 | int value_is_compatible(const VALUE* v, VALUE_TYPE type); 87 | 88 | /* Values newly added into array or dictionary are of type VALUE_NULL. 89 | * 90 | * Additionally, for such newly created values, an internal flag is used to 91 | * mark that the value was never explicitly initialized by the application. 92 | * 93 | * This function checks value of the flag, and allows thus the caller to 94 | * distinguish whether the value was just added; or whether the value was 95 | * explicitly initialized as VALUE_NULL with value_init_null(). 96 | * 97 | * Caller is supposed to initialize all such newly added value with any of the 98 | * value_init_XXX() functions, and hence reset the flag. 99 | */ 100 | int value_is_new(const VALUE* v); 101 | 102 | /* Simple recursive getter, capable to get a value dwelling deep in the 103 | * hierarchy formed by nested arrays and dictionaries. 104 | * 105 | * Limitations: The function is not capable to deal with object keys which 106 | * contain zero byte '\0', slash '/' or brackets '[' ']' because those are 107 | * interpreted by the function as special characters: 108 | * 109 | * -- '/' delimits dictionary keys (and optionally also array indexes; 110 | * paths "foo/[4]" and "foo[4]" are treated as equivalent.) 111 | * -- '[' ']' enclose array indexes (for distinguishing from numbered 112 | * dictionary keys). Note that negative indexes are supported here; 113 | * '[-1]' refers to the last element in the array, '[-2]' to the element 114 | * before the last element etc. 115 | * -- '\0' terminates the whole path (as is normal with C strings). 116 | * 117 | * Examples: 118 | * 119 | * (1) value_path(root, "") gets directly the root. 120 | * 121 | * (2) value_path(root, "foo") gets value keyed with 'foo' if root is a 122 | * dictionary having such value, or NULL otherwise. 123 | * 124 | * (3) value_path(root, "[4]") gets value with index 4 if root is an array 125 | * having so many members, or NULL otherwise. 126 | * 127 | * (4) value_path(root, "foo[2]/bar/baz[3]") walks deeper and deeper and 128 | * returns a value stored there assuming these all conditions are true: 129 | * -- root is dictionary having the key "foo"; 130 | * -- that value is a nested list having the index [2]; 131 | * -- that value is a nested dictionary having the key "bar"; 132 | * -- that value is a nested dictionary having the key "baz"; 133 | * -- and finally, that is a list having the index [3]. 134 | * If any of those is not fulfilled, then NULL is returned. 135 | */ 136 | VALUE* value_path(VALUE* root, const char* path); 137 | 138 | /* value_build_path() is similar to value_path(); but allows easy populating 139 | * of value hierarchies. 140 | * 141 | * If all values along the path already exist, the behavior is exactly the same 142 | * as value_path(). 143 | * 144 | * But when a value corresponding to any component of the path does not exist 145 | * then, instead of returning NULL, new value is added into the parent 146 | * container (assuming the parent existing container has correct type as 147 | * assumed by the path.) 148 | * 149 | * Caller may use empty "[]" to always enforce appending a new value into an 150 | * array. E.g. value_build_path(root, "multiple_values/[]/name") makes sure the 151 | * root contains an array under the key "multiple_values", and a new dictionary 152 | * is appended at the end of the array. This new dictionary gets a new value 153 | * under the key "name". Assuming the function succeeds, the caller can now be 154 | * sure the "name" is initialized as VALUE_NULL because the new dictionary has 155 | * been just created and added as the last element if the list. 156 | * 157 | * If such new value does not correspond to the last path component, the new 158 | * value gets initialized as the right type so subsequent path component can 159 | * be treated the same way. 160 | * 161 | * If the function creates the value corresponding to the last component of the 162 | * path, it is initialized as VALUE_NULL and the "new flag" is set for it, so 163 | * caller can test this condition with value_is_new(). 164 | * 165 | * Returns NULL if the path cannot be resolved because any existing value 166 | * has a type incompatible with the path; if creation of any value along the 167 | * path fails; or if an array index is out of bounds. 168 | */ 169 | VALUE* value_build_path(VALUE* root, const char* path); 170 | 171 | 172 | /****************** 173 | *** VALUE_NULL *** 174 | ******************/ 175 | 176 | /* Note it is guaranteed that VALUE_NULL does not need any explicit clean-up; 177 | * i.e. application may avoid calling value_fini(). 178 | * 179 | * But it is allowed to. value_fini() for VALUE_NULL is a noop. 180 | */ 181 | 182 | 183 | /* Static initializer. 184 | */ 185 | #define VALUE_NULL_INITIALIZER { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } 186 | 187 | void value_init_null(VALUE* v); 188 | 189 | 190 | /****************** 191 | *** VALUE_BOOL *** 192 | ******************/ 193 | 194 | int value_init_bool(VALUE* v, int b); 195 | 196 | int value_bool(const VALUE* v); 197 | 198 | 199 | /********************* 200 | *** Numeric types *** 201 | *********************/ 202 | 203 | 204 | /* Initializers. 205 | */ 206 | int value_init_int32(VALUE* v, int32_t i32); 207 | int value_init_uint32(VALUE* v, uint32_t u32); 208 | int value_init_int64(VALUE* v, int64_t i64); 209 | int value_init_uint64(VALUE* v, uint64_t u64); 210 | int value_init_float(VALUE* v, float f); 211 | int value_init_double(VALUE* v, double d); 212 | 213 | /* Getters. 214 | * 215 | * Note you may use any of the getter function for any numeric value. These 216 | * functions perform required conversions under the hood. The conversion may 217 | * have have the same side/limitations as C casting. 218 | * 219 | * However application may use value_is_compatible() to verify whether the 220 | * conversion should provide a reasonable result. 221 | */ 222 | int32_t value_int32(const VALUE* v); 223 | uint32_t value_uint32(const VALUE* v); 224 | int64_t value_int64(const VALUE* v); 225 | uint64_t value_uint64(const VALUE* v); 226 | float value_float(const VALUE* v); 227 | double value_double(const VALUE* v); 228 | 229 | 230 | /******************** 231 | *** VALUE_STRING *** 232 | ********************/ 233 | 234 | /* Note VALUE_STRING allows to store any sequences of any bytes, even a binary 235 | * data. No particular encoding of the string is assumed. Even zero bytes are 236 | * allowed (but then the caller has to use value_init_string_() and specify 237 | * the string length explicitly). 238 | */ 239 | 240 | /* The function value_init_string_() initializes the VALUE_STRING with any 241 | * sequence of bytes, of any length. It also adds automatically one zero byte 242 | * (not counted in the length of the string). 243 | * 244 | * The function value_init_string() is equivalent to calling directly 245 | * value_init_string_(str, strlen(str)). 246 | * 247 | * The parameter str is allowed to be NULL (then the functions behave the same 248 | * way as if it is points to an empty string). 249 | */ 250 | int value_init_string_(VALUE* v, const char* str, size_t len); 251 | int value_init_string(VALUE* v, const char* str); 252 | 253 | /* Get pointer to the internal buffer holding the string. The caller may assume 254 | * the returned string is always zero-terminated. 255 | */ 256 | const char* value_string(const VALUE* v); 257 | 258 | /* Get length of the string. (The implicit zero terminator does not count.) 259 | */ 260 | size_t value_string_length(const VALUE* v); 261 | 262 | 263 | /******************* 264 | *** VALUE_ARRAY *** 265 | *******************/ 266 | 267 | /* Array of values. 268 | * 269 | * Note that any new value added into the array with value_array_append() or 270 | * value_array_insert() is initially of the type VALUE_NULL and that it has 271 | * an internal flag marking the value as new (so that value_is_new() returns 272 | * non-zero for it). Application is supposed to initialize the newly added 273 | * value by any of the value initialization functions. 274 | * 275 | * WARNING: Modifying contents of an array (i.e. inserting, appending and also 276 | * removing a value) can lead to reallocation of internal array buffer. 277 | * Hence, consider all VALUE* pointers invalid after modifying the array. 278 | * That includes the return values of value_array_get(), value_array_get_all(), 279 | * but also preceding calls of value_array_append() and value_array_insert(). 280 | */ 281 | int value_init_array(VALUE* v); 282 | 283 | /* Get count of items in the array. 284 | */ 285 | size_t value_array_size(const VALUE* v); 286 | 287 | /* Get the specified item. 288 | */ 289 | VALUE* value_array_get(const VALUE* v, size_t index); 290 | 291 | /* Get pointer to internal C array of all items. 292 | */ 293 | VALUE* value_array_get_all(const VALUE* v); 294 | 295 | /* Append/insert new item. 296 | */ 297 | VALUE* value_array_append(VALUE* v); 298 | VALUE* value_array_insert(VALUE* v, size_t index); 299 | 300 | /* Remove an item (or range of items). 301 | */ 302 | int value_array_remove(VALUE* v, size_t index); 303 | int value_array_remove_range(VALUE* v, size_t index, size_t count); 304 | 305 | /* Remove and destroy all members (recursively). 306 | */ 307 | void value_array_clean(VALUE* v); 308 | 309 | 310 | /****************** 311 | *** VALUE_DICT *** 312 | ******************/ 313 | 314 | /* Dictionary of values. (Internally implemented as red-black tree.) 315 | * 316 | * Note that any new value added into the dictionary is initially of the type 317 | * VALUE_NULL and that it has an internal flag marking the value as new 318 | * (so that value_is_new() returns non-zero for it). Application is supposed 319 | * to initialize the newly added value by any of the value initialization 320 | * functions. 321 | * 322 | * Note that all the functions adding/removing any items may invalidate all 323 | * pointers into the dictionary. 324 | */ 325 | 326 | 327 | /* Flag for init_dict_ex() asking to maintain the order in which the dictionary 328 | * is populated and enabling dict_walk_ordered(). 329 | * 330 | * If used, the dictionary consumes more memory. 331 | */ 332 | #define VALUE_DICT_MAINTAINORDER 0x0001 333 | 334 | /* Initialize the value as a (empty) dictionary. 335 | * 336 | * value_init_dict_ex() allows to specify custom comparer function (may be NULL) 337 | * or flags changing the default behavior of the dictionary. 338 | */ 339 | int value_init_dict(VALUE* v); 340 | int value_init_dict_ex(VALUE* v, 341 | int (*custom_cmp_func)(const char* /*key1*/, size_t /*len1*/, 342 | const char* /*key2*/, size_t /*len2*/), 343 | unsigned flags); 344 | 345 | /* Get flags of the dictionary. 346 | */ 347 | unsigned value_dict_flags(const VALUE* v); 348 | 349 | /* Get count of items in the dictionary. 350 | */ 351 | size_t value_dict_size(const VALUE* v); 352 | 353 | /* Get all keys. 354 | * 355 | * If the buffer provided by the caller is too small, only subset of keys shall 356 | * be retrieved. 357 | * 358 | * Returns count of retrieved keys. 359 | */ 360 | size_t value_dict_keys_sorted(const VALUE* v, const VALUE** buffer, size_t buffer_size); 361 | size_t value_dict_keys_ordered(const VALUE* v, const VALUE** buffer, size_t buffer_size); 362 | 363 | /* Find an item with the given key, or return NULL of no such item exists. 364 | */ 365 | VALUE* value_dict_get_(const VALUE* v, const char* key, size_t key_len); 366 | VALUE* value_dict_get(const VALUE* v, const char* key); 367 | 368 | /* Add new item with the given key of type VALUE_NULL. 369 | * 370 | * Returns NULL if the key is already used. 371 | */ 372 | VALUE* value_dict_add_(VALUE* v, const char* key, size_t key_len); 373 | VALUE* value_dict_add(VALUE* v, const char* key); 374 | 375 | /* This is combined operation of value_dict_get() and value_dict_add(). 376 | * 377 | * Get value of the given key. If no such value exists, new one is added. 378 | * Application can check for such situation with value_is_new(). 379 | * 380 | * NULL is returned only in an out-of-memory situation. 381 | */ 382 | VALUE* value_dict_get_or_add_(VALUE* v, const char* key, size_t key_len); 383 | VALUE* value_dict_get_or_add(VALUE* v, const char* key); 384 | 385 | /* Remove and destroy (recursively) the given item from the dictionary. 386 | */ 387 | int value_dict_remove_(VALUE* v, const char* key, size_t key_len); 388 | int value_dict_remove(VALUE* v, const char* key); 389 | 390 | /* Walking over all items in the dictionary. The callback function is called 391 | * for every item in the dictionary, providing key and value and propagating 392 | * the user data into it. If the callback returns non-zero, the function 393 | * aborts immediately. 394 | * 395 | * Note dict_walk_ordered() is supported only if DICT_MAINTAINORDER 396 | * flag was used in init_dict(). 397 | */ 398 | int value_dict_walk_ordered(const VALUE* v, 399 | int (*visit_func)(const VALUE*, VALUE*, void*), void* ctx); 400 | int value_dict_walk_sorted(const VALUE* v, 401 | int (*visit_func)(const VALUE*, VALUE*, void*), void* ctx); 402 | 403 | /* Remove and destroy all members (recursively). 404 | */ 405 | void value_dict_clean(VALUE* v); 406 | 407 | 408 | #ifdef __cplusplus 409 | } 410 | #endif 411 | 412 | #endif /* CRE_VALUE_H */ 413 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | if(CMAKE_COMPILER_IS_GNUCC) 3 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") 4 | if(WIN32) 5 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mconsole -static-libgcc") 6 | endif() 7 | elseif(MSVC) 8 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") 9 | add_definitions(/D_CRT_SECURE_NO_WARNINGS) 10 | endif() 11 | 12 | add_executable(test-json acutest.h test-json.c) 13 | target_include_directories(test-json PRIVATE ../src) 14 | target_link_libraries(test-json json) 15 | -------------------------------------------------------------------------------- /tests/acutest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Acutest -- Another C/C++ Unit Test facility 3 | * 4 | * 5 | * Copyright (c) 2013-2019 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef ACUTEST_H__ 27 | #define ACUTEST_H__ 28 | 29 | 30 | /************************ 31 | *** Public interface *** 32 | ************************/ 33 | 34 | /* By default, "acutest.h" provides the main program entry point (function 35 | * main()). However, if the test suite is composed of multiple source files 36 | * which include "acutest.h", then this causes a problem of multiple main() 37 | * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all 38 | * compilation units but one. 39 | */ 40 | 41 | /* Macro to specify list of unit tests in the suite. 42 | * The unit test implementation MUST provide list of unit tests it implements 43 | * with this macro: 44 | * 45 | * TEST_LIST = { 46 | * { "test1_name", test1_func_ptr }, 47 | * { "test2_name", test2_func_ptr }, 48 | * ... 49 | * { 0 } 50 | * }; 51 | * 52 | * The list specifies names of each test (must be unique) and pointer to 53 | * a function implementing it. The function does not take any arguments 54 | * and has no return values, i.e. every test function has to be compatible 55 | * with this prototype: 56 | * 57 | * void test_func(void); 58 | */ 59 | #define TEST_LIST const struct test__ test_list__[] 60 | 61 | 62 | /* Macros for testing whether an unit test succeeds or fails. These macros 63 | * can be used arbitrarily in functions implementing the unit tests. 64 | * 65 | * If any condition fails throughout execution of a test, the test fails. 66 | * 67 | * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows 68 | * also to specify an error message to print out if the condition fails. 69 | * (It expects printf-like format string and its parameters). The macros 70 | * return non-zero (condition passes) or 0 (condition fails). 71 | * 72 | * That can be useful when more conditions should be checked only if some 73 | * preceding condition passes, as illustrated in this code snippet: 74 | * 75 | * SomeStruct* ptr = allocate_some_struct(); 76 | * if(TEST_CHECK(ptr != NULL)) { 77 | * TEST_CHECK(ptr->member1 < 100); 78 | * TEST_CHECK(ptr->member2 > 200); 79 | * } 80 | */ 81 | #define TEST_CHECK_(cond,...) test_check__((cond), __FILE__, __LINE__, __VA_ARGS__) 82 | #define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond) 83 | 84 | 85 | /* Sometimes it is useful to split execution of more complex unit tests to some 86 | * smaller parts and associate those parts with some names. 87 | * 88 | * This is especially handy if the given unit test is implemented as a loop 89 | * over some vector of multiple testing inputs. Using these macros allow to use 90 | * sort of subtitle for each iteration of the loop (e.g. outputting the input 91 | * itself or a name associated to it), so that if any TEST_CHECK condition 92 | * fails in the loop, it can be easily seen which iteration triggers the 93 | * failure, without the need to manually output the iteration-specific data in 94 | * every single TEST_CHECK inside the loop body. 95 | * 96 | * TEST_CASE allows to specify only single string as the name of the case, 97 | * TEST_CASE_ provides all the power of printf-like string formatting. 98 | * 99 | * Note that the test cases cannot be nested. Starting a new test case ends 100 | * implicitly the previous one. To end the test case explicitly (e.g. to end 101 | * the last test case after exiting the loop), you may use TEST_CASE(NULL). 102 | */ 103 | #define TEST_CASE_(...) test_case__(__VA_ARGS__) 104 | #define TEST_CASE(name) test_case__("%s", name); 105 | 106 | 107 | /* printf-like macro for outputting an extra information about a failure. 108 | * 109 | * Note it does not output anything if there was not (yet) failed condition 110 | * in the current test. Intended use is to output some computed output 111 | * versus the expected value, e.g. like this: 112 | * 113 | * if(!TEST_CHECK(produced == expected)) { 114 | * TEST_MSG("Expected: %d", expected); 115 | * TEST_MSG("Produced: %d", produced); 116 | * } 117 | * 118 | * The macro can deal with multi-line output fairly well. It also automatically 119 | * adds a final new-line if there is none present. 120 | */ 121 | #define TEST_MSG(...) test_message__(__VA_ARGS__) 122 | 123 | 124 | /* Maximal output per TEST_MSG call. Longer messages are cut. 125 | * You may define another limit prior including "acutest.h" 126 | */ 127 | #ifndef TEST_MSG_MAXSIZE 128 | #define TEST_MSG_MAXSIZE 1024 129 | #endif 130 | 131 | 132 | /********************** 133 | *** Implementation *** 134 | **********************/ 135 | 136 | /* The unit test files should not rely on anything below. */ 137 | 138 | #include 139 | #include 140 | #include 141 | #include 142 | 143 | #if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) 144 | #define ACUTEST_UNIX__ 1 145 | #include 146 | #include 147 | #include 148 | #include 149 | #include 150 | #include 151 | 152 | #if defined CLOCK_PROCESS_CPUTIME_ID && defined CLOCK_MONOTONIC 153 | #define ACUTEST_HAS_POSIX_TIMER__ 1 154 | #endif 155 | #endif 156 | 157 | #if defined(__gnu_linux__) 158 | #define ACUTEST_LINUX__ 1 159 | #include 160 | #include 161 | #endif 162 | 163 | #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) 164 | #define ACUTEST_WIN__ 1 165 | #include 166 | #include 167 | #endif 168 | 169 | #ifdef __cplusplus 170 | #include 171 | #endif 172 | 173 | 174 | /* Note our global private identifiers end with '__' to mitigate risk of clash 175 | * with the unit tests implementation. */ 176 | 177 | 178 | #ifdef __cplusplus 179 | extern "C" { 180 | #endif 181 | 182 | 183 | struct test__ { 184 | const char* name; 185 | void (*func)(void); 186 | }; 187 | 188 | extern const struct test__ test_list__[]; 189 | 190 | int test_check__(int cond, const char* file, int line, const char* fmt, ...); 191 | void test_case__(const char* fmt, ...); 192 | void test_message__(const char* fmt, ...); 193 | 194 | 195 | #ifndef TEST_NO_MAIN 196 | 197 | static char* test_argv0__ = NULL; 198 | static size_t test_list_size__ = 0; 199 | static const struct test__** tests__ = NULL; 200 | static char* test_flags__ = NULL; 201 | static size_t test_count__ = 0; 202 | static int test_no_exec__ = -1; 203 | static int test_no_summary__ = 0; 204 | static int test_tap__ = 0; 205 | static int test_skip_mode__ = 0; 206 | static int test_worker__ = 0; 207 | static int test_worker_index__ = 0; 208 | 209 | static int test_stat_failed_units__ = 0; 210 | static int test_stat_run_units__ = 0; 211 | 212 | static const struct test__* test_current_unit__ = NULL; 213 | static int test_current_index__ = 0; 214 | static char test_case_name__[64] = ""; 215 | static int test_current_already_logged__ = 0; 216 | static int test_case_current_already_logged__ = 0; 217 | static int test_verbose_level__ = 2; 218 | static int test_current_failures__ = 0; 219 | static int test_colorize__ = 0; 220 | static int test_timer__ = 0; 221 | 222 | #if defined ACUTEST_WIN__ 223 | static LARGE_INTEGER test_timer_freq__; 224 | static LARGE_INTEGER test_timer_start__; 225 | static LARGE_INTEGER test_timer_end__; 226 | 227 | static void 228 | test_timer_init__(void) 229 | { 230 | QueryPerformanceFrequency(&test_timer_freq__); 231 | } 232 | 233 | static void 234 | test_timer_get_time__(LARGE_INTEGER* ts) 235 | { 236 | QueryPerformanceCounter(ts); 237 | } 238 | 239 | static void 240 | test_timer_print_diff__(void) 241 | { 242 | double duration = test_timer_end__.QuadPart - test_timer_start__.QuadPart; 243 | duration /= test_timer_freq__.QuadPart; 244 | printf("%.6lf secs", duration); 245 | } 246 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 247 | static clockid_t test_timer_id__; 248 | struct timespec test_timer_start__; 249 | struct timespec test_timer_end__; 250 | 251 | static void 252 | test_timer_init__(void) 253 | { 254 | if(test_timer__ == 1) 255 | #ifdef CLOCK_MONOTONIC_RAW 256 | /* linux specific; not subject of NTP adjustements or adjtime() */ 257 | test_timer_id__ = CLOCK_MONOTONIC_RAW; 258 | #else 259 | test_timer_id__ = CLOCK_MONOTONIC; 260 | #endif 261 | else if(test_timer__ == 2) 262 | test_timer_id__ = CLOCK_PROCESS_CPUTIME_ID; 263 | } 264 | 265 | static void 266 | test_timer_get_time__(struct timespec* ts) 267 | { 268 | clock_gettime(test_timer_id__, ts); 269 | } 270 | 271 | static void 272 | test_timer_print_diff__(void) 273 | { 274 | double duration = ((double) test_timer_end__.tv_sec + 275 | (double) test_timer_end__.tv_nsec * 10e-9) 276 | - 277 | ((double) test_timer_start__.tv_sec + 278 | (double) test_timer_start__.tv_nsec * 10e-9); 279 | printf("%.6lf secs", duration); 280 | } 281 | #else 282 | static int test_timer_start__; 283 | static int test_timer_end__; 284 | 285 | void 286 | test_timer_init__(void) 287 | {} 288 | 289 | static void 290 | test_timer_get_time__(int* ts) 291 | { 292 | (void) ts; 293 | } 294 | 295 | static void 296 | test_timer_print_diff__(void) 297 | {} 298 | #endif 299 | 300 | #define TEST_COLOR_DEFAULT__ 0 301 | #define TEST_COLOR_GREEN__ 1 302 | #define TEST_COLOR_RED__ 2 303 | #define TEST_COLOR_DEFAULT_INTENSIVE__ 3 304 | #define TEST_COLOR_GREEN_INTENSIVE__ 4 305 | #define TEST_COLOR_RED_INTENSIVE__ 5 306 | 307 | static int 308 | test_print_in_color__(int color, const char* fmt, ...) 309 | { 310 | va_list args; 311 | char buffer[256]; 312 | int n; 313 | 314 | va_start(args, fmt); 315 | vsnprintf(buffer, sizeof(buffer), fmt, args); 316 | va_end(args); 317 | buffer[sizeof(buffer)-1] = '\0'; 318 | 319 | if(!test_colorize__) { 320 | return printf("%s", buffer); 321 | } 322 | 323 | #if defined ACUTEST_UNIX__ 324 | { 325 | const char* col_str; 326 | switch(color) { 327 | case TEST_COLOR_GREEN__: col_str = "\033[0;32m"; break; 328 | case TEST_COLOR_RED__: col_str = "\033[0;31m"; break; 329 | case TEST_COLOR_GREEN_INTENSIVE__: col_str = "\033[1;32m"; break; 330 | case TEST_COLOR_RED_INTENSIVE__: col_str = "\033[1;31m"; break; 331 | case TEST_COLOR_DEFAULT_INTENSIVE__: col_str = "\033[1m"; break; 332 | default: col_str = "\033[0m"; break; 333 | } 334 | printf("%s", col_str); 335 | n = printf("%s", buffer); 336 | printf("\033[0m"); 337 | return n; 338 | } 339 | #elif defined ACUTEST_WIN__ 340 | { 341 | HANDLE h; 342 | CONSOLE_SCREEN_BUFFER_INFO info; 343 | WORD attr; 344 | 345 | h = GetStdHandle(STD_OUTPUT_HANDLE); 346 | GetConsoleScreenBufferInfo(h, &info); 347 | 348 | switch(color) { 349 | case TEST_COLOR_GREEN__: attr = FOREGROUND_GREEN; break; 350 | case TEST_COLOR_RED__: attr = FOREGROUND_RED; break; 351 | case TEST_COLOR_GREEN_INTENSIVE__: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; 352 | case TEST_COLOR_RED_INTENSIVE__: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; 353 | case TEST_COLOR_DEFAULT_INTENSIVE__: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; 354 | default: attr = 0; break; 355 | } 356 | if(attr != 0) 357 | SetConsoleTextAttribute(h, attr); 358 | n = printf("%s", buffer); 359 | SetConsoleTextAttribute(h, info.wAttributes); 360 | return n; 361 | } 362 | #else 363 | n = printf("%s", buffer); 364 | return n; 365 | #endif 366 | } 367 | 368 | static void 369 | test_begin_test_line__(const struct test__* test) 370 | { 371 | if(!test_tap__) { 372 | if(test_verbose_level__ >= 3) { 373 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n", test->name); 374 | test_current_already_logged__++; 375 | } else if(test_verbose_level__ >= 1) { 376 | int n; 377 | char spaces[48]; 378 | 379 | n = test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s... ", test->name); 380 | memset(spaces, ' ', sizeof(spaces)); 381 | if(n < (int) sizeof(spaces)) 382 | printf("%.*s", (int) sizeof(spaces) - n, spaces); 383 | } else { 384 | test_current_already_logged__ = 1; 385 | } 386 | } 387 | } 388 | 389 | static void 390 | test_finish_test_line__(int result) 391 | { 392 | if(test_tap__) { 393 | const char* str = (result == 0) ? "ok" : "not ok"; 394 | 395 | printf("%s %u - %s\n", str, test_current_index__ + 1, test_current_unit__->name); 396 | 397 | if(result == 0 && test_timer__) { 398 | printf("# Duration: "); 399 | test_timer_print_diff__(); 400 | printf("\n"); 401 | } 402 | } else { 403 | int color = (result == 0) ? TEST_COLOR_GREEN_INTENSIVE__ : TEST_COLOR_RED_INTENSIVE__; 404 | const char* str = (result == 0) ? "OK" : "FAILED"; 405 | printf("[ "); 406 | test_print_in_color__(color, str); 407 | printf(" ]"); 408 | 409 | if(result == 0 && test_timer__) { 410 | printf(" "); 411 | test_timer_print_diff__(); 412 | } 413 | 414 | printf("\n"); 415 | } 416 | } 417 | 418 | static void 419 | test_line_indent__(int level) 420 | { 421 | static const char spaces[] = " "; 422 | int n = level * 2; 423 | 424 | if(test_tap__ && n > 0) { 425 | n--; 426 | printf("#"); 427 | } 428 | 429 | while(n > 16) { 430 | printf("%s", spaces); 431 | n -= 16; 432 | } 433 | printf("%.*s", n, spaces); 434 | } 435 | 436 | int 437 | test_check__(int cond, const char* file, int line, const char* fmt, ...) 438 | { 439 | const char *result_str; 440 | int result_color; 441 | int verbose_level; 442 | 443 | if(cond) { 444 | result_str = "ok"; 445 | result_color = TEST_COLOR_GREEN__; 446 | verbose_level = 3; 447 | } else { 448 | if(!test_current_already_logged__ && test_current_unit__ != NULL) 449 | test_finish_test_line__(-1); 450 | 451 | result_str = "failed"; 452 | result_color = TEST_COLOR_RED__; 453 | verbose_level = 2; 454 | test_current_failures__++; 455 | test_current_already_logged__++; 456 | } 457 | 458 | if(test_verbose_level__ >= verbose_level) { 459 | va_list args; 460 | 461 | if(!test_case_current_already_logged__ && test_case_name__[0]) { 462 | test_line_indent__(1); 463 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__); 464 | test_current_already_logged__++; 465 | test_case_current_already_logged__++; 466 | } 467 | 468 | test_line_indent__(test_case_name__[0] ? 2 : 1); 469 | if(file != NULL) { 470 | if(test_verbose_level__ < 3) { 471 | #ifdef ACUTEST_WIN__ 472 | const char* lastsep1 = strrchr(file, '\\'); 473 | const char* lastsep2 = strrchr(file, '/'); 474 | if(lastsep1 == NULL) 475 | lastsep1 = file-1; 476 | if(lastsep2 == NULL) 477 | lastsep2 = file-1; 478 | file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1; 479 | #else 480 | const char* lastsep = strrchr(file, '/'); 481 | if(lastsep != NULL) 482 | file = lastsep+1; 483 | #endif 484 | } 485 | printf("%s:%d: Check ", file, line); 486 | } 487 | 488 | va_start(args, fmt); 489 | vprintf(fmt, args); 490 | va_end(args); 491 | 492 | printf("... "); 493 | test_print_in_color__(result_color, result_str); 494 | printf("\n"); 495 | test_current_already_logged__++; 496 | } 497 | 498 | return (cond != 0); 499 | } 500 | 501 | void 502 | test_case__(const char* fmt, ...) 503 | { 504 | va_list args; 505 | 506 | if(test_verbose_level__ < 2) 507 | return; 508 | 509 | if(test_case_name__[0]) { 510 | test_case_current_already_logged__ = 0; 511 | test_case_name__[0] = '\0'; 512 | } 513 | 514 | if(fmt == NULL) 515 | return; 516 | 517 | va_start(args, fmt); 518 | vsnprintf(test_case_name__, sizeof(test_case_name__) - 1, fmt, args); 519 | va_end(args); 520 | test_case_name__[sizeof(test_case_name__) - 1] = '\0'; 521 | 522 | if(test_verbose_level__ >= 3) { 523 | test_line_indent__(1); 524 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__); 525 | test_current_already_logged__++; 526 | test_case_current_already_logged__++; 527 | } 528 | } 529 | 530 | void 531 | test_message__(const char* fmt, ...) 532 | { 533 | char buffer[TEST_MSG_MAXSIZE]; 534 | char* line_beg; 535 | char* line_end; 536 | va_list args; 537 | 538 | if(test_verbose_level__ < 2) 539 | return; 540 | 541 | /* We allow extra message only when something is already wrong in the 542 | * current test. */ 543 | if(!test_current_already_logged__ || test_current_unit__ == NULL) 544 | return; 545 | 546 | va_start(args, fmt); 547 | vsnprintf(buffer, TEST_MSG_MAXSIZE, fmt, args); 548 | va_end(args); 549 | buffer[TEST_MSG_MAXSIZE-1] = '\0'; 550 | 551 | line_beg = buffer; 552 | while(1) { 553 | line_end = strchr(line_beg, '\n'); 554 | if(line_end == NULL) 555 | break; 556 | test_line_indent__(test_case_name__[0] ? 3 : 2); 557 | printf("%.*s\n", (int)(line_end - line_beg), line_beg); 558 | line_beg = line_end + 1; 559 | } 560 | if(line_beg[0] != '\0') { 561 | test_line_indent__(test_case_name__[0] ? 3 : 2); 562 | printf("%s\n", line_beg); 563 | } 564 | } 565 | 566 | static void 567 | test_list_names__(void) 568 | { 569 | const struct test__* test; 570 | 571 | printf("Unit tests:\n"); 572 | for(test = &test_list__[0]; test->func != NULL; test++) 573 | printf(" %s\n", test->name); 574 | } 575 | 576 | static void 577 | test_remember__(int i) 578 | { 579 | if(test_flags__[i]) 580 | return; 581 | else 582 | test_flags__[i] = 1; 583 | 584 | tests__[test_count__] = &test_list__[i]; 585 | test_count__++; 586 | } 587 | 588 | static int 589 | test_name_contains_word__(const char* name, const char* pattern) 590 | { 591 | static const char word_delim[] = " \t-_."; 592 | const char* substr; 593 | size_t pattern_len; 594 | int starts_on_word_boundary; 595 | int ends_on_word_boundary; 596 | 597 | pattern_len = strlen(pattern); 598 | 599 | substr = strstr(name, pattern); 600 | while(substr != NULL) { 601 | starts_on_word_boundary = (substr == name || strchr(word_delim, substr[-1]) != NULL); 602 | ends_on_word_boundary = (substr[pattern_len] == '\0' || strchr(word_delim, substr[pattern_len]) != NULL); 603 | 604 | if(starts_on_word_boundary && ends_on_word_boundary) 605 | return 1; 606 | 607 | substr = strstr(substr+1, pattern); 608 | } 609 | 610 | return 0; 611 | } 612 | 613 | static int 614 | test_lookup__(const char* pattern) 615 | { 616 | int i; 617 | int n = 0; 618 | 619 | /* Try exact match. */ 620 | for(i = 0; i < (int) test_list_size__; i++) { 621 | if(strcmp(test_list__[i].name, pattern) == 0) { 622 | test_remember__(i); 623 | n++; 624 | break; 625 | } 626 | } 627 | if(n > 0) 628 | return n; 629 | 630 | /* Try word match. */ 631 | for(i = 0; i < (int) test_list_size__; i++) { 632 | if(test_name_contains_word__(test_list__[i].name, pattern)) { 633 | test_remember__(i); 634 | n++; 635 | } 636 | } 637 | if(n > 0) 638 | return n; 639 | 640 | /* Try relaxed match. */ 641 | for(i = 0; i < (int) test_list_size__; i++) { 642 | if(strstr(test_list__[i].name, pattern) != NULL) { 643 | test_remember__(i); 644 | n++; 645 | } 646 | } 647 | 648 | return n; 649 | } 650 | 651 | 652 | /* Called if anything goes bad in Acutest, or if the unit test ends in other 653 | * way then by normal returning from its function (e.g. exception or some 654 | * abnormal child process termination). */ 655 | static void 656 | test_error__(const char* fmt, ...) 657 | { 658 | va_list args; 659 | 660 | if(test_verbose_level__ == 0) 661 | return; 662 | 663 | if(test_verbose_level__ <= 2 && !test_current_already_logged__ && test_current_unit__ != NULL) { 664 | if(test_tap__) { 665 | test_finish_test_line__(-1); 666 | } else { 667 | printf("[ "); 668 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED"); 669 | printf(" ]\n"); 670 | } 671 | } 672 | 673 | if(test_verbose_level__ >= 2) { 674 | test_line_indent__(1); 675 | if(test_verbose_level__ >= 3) 676 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "ERROR: "); 677 | va_start(args, fmt); 678 | vprintf(fmt, args); 679 | va_end(args); 680 | printf("\n"); 681 | } 682 | 683 | if(test_verbose_level__ >= 3) { 684 | printf("\n"); 685 | } 686 | } 687 | 688 | /* Call directly the given test unit function. */ 689 | static int 690 | test_do_run__(const struct test__* test, int index) 691 | { 692 | test_current_unit__ = test; 693 | test_current_index__ = index; 694 | test_current_failures__ = 0; 695 | test_current_already_logged__ = 0; 696 | 697 | test_timer_init__(); 698 | 699 | test_begin_test_line__(test); 700 | 701 | #ifdef __cplusplus 702 | try { 703 | #endif 704 | 705 | /* This is good to do for case the test unit e.g. crashes. */ 706 | fflush(stdout); 707 | fflush(stderr); 708 | 709 | test_timer_get_time__(&test_timer_start__); 710 | test->func(); 711 | test_timer_get_time__(&test_timer_end__); 712 | 713 | if(test_verbose_level__ >= 3) { 714 | test_line_indent__(1); 715 | if(test_current_failures__ == 0) { 716 | test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS: "); 717 | printf("All conditions have passed.\n"); 718 | 719 | if(test_timer__) { 720 | test_line_indent__(1); 721 | printf("Duration: "); 722 | test_timer_print_diff__(); 723 | printf("\n"); 724 | } 725 | } else { 726 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED: "); 727 | printf("%d condition%s %s failed.\n", 728 | test_current_failures__, 729 | (test_current_failures__ == 1) ? "" : "s", 730 | (test_current_failures__ == 1) ? "has" : "have"); 731 | } 732 | printf("\n"); 733 | } else if(test_verbose_level__ >= 1 && test_current_failures__ == 0) { 734 | test_finish_test_line__(0); 735 | } 736 | 737 | test_case__(NULL); 738 | test_current_unit__ = NULL; 739 | return (test_current_failures__ == 0) ? 0 : -1; 740 | 741 | #ifdef __cplusplus 742 | } catch(std::exception& e) { 743 | const char* what = e.what(); 744 | if(what != NULL) 745 | test_error__("Threw std::exception: %s", what); 746 | else 747 | test_error__("Threw std::exception"); 748 | return -1; 749 | } catch(...) { 750 | test_error__("Threw an exception"); 751 | return -1; 752 | } 753 | #endif 754 | } 755 | 756 | /* Trigger the unit test. If possible (and not suppressed) it starts a child 757 | * process who calls test_do_run__(), otherwise it calls test_do_run__() 758 | * directly. */ 759 | static void 760 | test_run__(const struct test__* test, int index) 761 | { 762 | int failed = 1; 763 | 764 | test_current_unit__ = test; 765 | test_current_already_logged__ = 0; 766 | 767 | if(!test_no_exec__) { 768 | 769 | #if defined(ACUTEST_UNIX__) 770 | 771 | pid_t pid; 772 | int exit_code; 773 | 774 | /* Make sure the child starts with empty I/O buffers. */ 775 | fflush(stdout); 776 | fflush(stderr); 777 | 778 | pid = fork(); 779 | if(pid == (pid_t)-1) { 780 | test_error__("Cannot fork. %s [%d]", strerror(errno), errno); 781 | failed = 1; 782 | } else if(pid == 0) { 783 | /* Child: Do the test. */ 784 | failed = (test_do_run__(test, index) != 0); 785 | exit(failed ? 1 : 0); 786 | } else { 787 | /* Parent: Wait until child terminates and analyze its exit code. */ 788 | waitpid(pid, &exit_code, 0); 789 | if(WIFEXITED(exit_code)) { 790 | switch(WEXITSTATUS(exit_code)) { 791 | case 0: failed = 0; break; /* test has passed. */ 792 | case 1: /* noop */ break; /* "normal" failure. */ 793 | default: test_error__("Unexpected exit code [%d]", WEXITSTATUS(exit_code)); 794 | } 795 | } else if(WIFSIGNALED(exit_code)) { 796 | char tmp[32]; 797 | const char* signame; 798 | switch(WTERMSIG(exit_code)) { 799 | case SIGINT: signame = "SIGINT"; break; 800 | case SIGHUP: signame = "SIGHUP"; break; 801 | case SIGQUIT: signame = "SIGQUIT"; break; 802 | case SIGABRT: signame = "SIGABRT"; break; 803 | case SIGKILL: signame = "SIGKILL"; break; 804 | case SIGSEGV: signame = "SIGSEGV"; break; 805 | case SIGILL: signame = "SIGILL"; break; 806 | case SIGTERM: signame = "SIGTERM"; break; 807 | default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break; 808 | } 809 | test_error__("Test interrupted by %s", signame); 810 | } else { 811 | test_error__("Test ended in an unexpected way [%d]", exit_code); 812 | } 813 | } 814 | 815 | #elif defined(ACUTEST_WIN__) 816 | 817 | char buffer[512] = {0}; 818 | STARTUPINFOA startupInfo; 819 | PROCESS_INFORMATION processInfo; 820 | DWORD exitCode; 821 | 822 | /* Windows has no fork(). So we propagate all info into the child 823 | * through a command line arguments. */ 824 | _snprintf(buffer, sizeof(buffer)-1, 825 | "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"", 826 | test_argv0__, index, test_timer__ ? "--timer" : "", 827 | test_tap__ ? "--tap" : "", test_verbose_level__, 828 | test_colorize__ ? "always" : "never", 829 | test->name); 830 | memset(&startupInfo, 0, sizeof(startupInfo)); 831 | startupInfo.cb = sizeof(STARTUPINFO); 832 | if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) { 833 | WaitForSingleObject(processInfo.hProcess, INFINITE); 834 | GetExitCodeProcess(processInfo.hProcess, &exitCode); 835 | CloseHandle(processInfo.hThread); 836 | CloseHandle(processInfo.hProcess); 837 | failed = (exitCode != 0); 838 | } else { 839 | test_error__("Cannot create unit test subprocess [%ld].", GetLastError()); 840 | failed = 1; 841 | } 842 | 843 | #else 844 | 845 | /* A platform where we don't know how to run child process. */ 846 | failed = (test_do_run__(test, index) != 0); 847 | 848 | #endif 849 | 850 | } else { 851 | /* Child processes suppressed through --no-exec. */ 852 | failed = (test_do_run__(test, index) != 0); 853 | } 854 | 855 | test_current_unit__ = NULL; 856 | 857 | test_stat_run_units__++; 858 | if(failed) 859 | test_stat_failed_units__++; 860 | } 861 | 862 | #if defined(ACUTEST_WIN__) 863 | /* Callback for SEH events. */ 864 | static LONG CALLBACK 865 | test_exception_filter__(EXCEPTION_POINTERS *ptrs) 866 | { 867 | test_error__("Unhandled SEH exception %08lx at %p.", 868 | ptrs->ExceptionRecord->ExceptionCode, 869 | ptrs->ExceptionRecord->ExceptionAddress); 870 | fflush(stdout); 871 | fflush(stderr); 872 | return EXCEPTION_EXECUTE_HANDLER; 873 | } 874 | #endif 875 | 876 | 877 | #define TEST_CMDLINE_OPTFLAG_OPTIONALARG__ 0x0001 878 | #define TEST_CMDLINE_OPTFLAG_REQUIREDARG__ 0x0002 879 | 880 | #define TEST_CMDLINE_OPTID_NONE__ 0 881 | #define TEST_CMDLINE_OPTID_UNKNOWN__ (-0x7fffffff + 0) 882 | #define TEST_CMDLINE_OPTID_MISSINGARG__ (-0x7fffffff + 1) 883 | #define TEST_CMDLINE_OPTID_BOGUSARG__ (-0x7fffffff + 2) 884 | 885 | typedef struct TEST_CMDLINE_OPTION__ { 886 | char shortname; 887 | const char* longname; 888 | int id; 889 | unsigned flags; 890 | } TEST_CMDLINE_OPTION__; 891 | 892 | static int 893 | test_cmdline_handle_short_opt_group__(const TEST_CMDLINE_OPTION__* options, 894 | const char* arggroup, 895 | int (*callback)(int /*optval*/, const char* /*arg*/)) 896 | { 897 | const TEST_CMDLINE_OPTION__* opt; 898 | int i; 899 | int ret = 0; 900 | 901 | for(i = 0; arggroup[i] != '\0'; i++) { 902 | for(opt = options; opt->id != 0; opt++) { 903 | if(arggroup[i] == opt->shortname) 904 | break; 905 | } 906 | 907 | if(opt->id != 0 && !(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) { 908 | ret = callback(opt->id, NULL); 909 | } else { 910 | /* Unknown option. */ 911 | char badoptname[3]; 912 | badoptname[0] = '-'; 913 | badoptname[1] = arggroup[i]; 914 | badoptname[2] = '\0'; 915 | ret = callback((opt->id != 0 ? TEST_CMDLINE_OPTID_MISSINGARG__ : TEST_CMDLINE_OPTID_UNKNOWN__), 916 | badoptname); 917 | } 918 | 919 | if(ret != 0) 920 | break; 921 | } 922 | 923 | return ret; 924 | } 925 | 926 | #define TEST_CMDLINE_AUXBUF_SIZE__ 32 927 | 928 | static int 929 | test_cmdline_read__(const TEST_CMDLINE_OPTION__* options, int argc, char** argv, 930 | int (*callback)(int /*optval*/, const char* /*arg*/)) 931 | { 932 | 933 | const TEST_CMDLINE_OPTION__* opt; 934 | char auxbuf[TEST_CMDLINE_AUXBUF_SIZE__+1]; 935 | int after_doubledash = 0; 936 | int i = 1; 937 | int ret = 0; 938 | 939 | auxbuf[TEST_CMDLINE_AUXBUF_SIZE__] = '\0'; 940 | 941 | while(i < argc) { 942 | if(after_doubledash || strcmp(argv[i], "-") == 0) { 943 | /* Non-option argument. */ 944 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 945 | } else if(strcmp(argv[i], "--") == 0) { 946 | /* End of options. All the remaining members are non-option arguments. */ 947 | after_doubledash = 1; 948 | } else if(argv[i][0] != '-') { 949 | /* Non-option argument. */ 950 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 951 | } else { 952 | for(opt = options; opt->id != 0; opt++) { 953 | if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) { 954 | size_t len = strlen(opt->longname); 955 | if(strncmp(argv[i]+2, opt->longname, len) == 0) { 956 | /* Regular long option. */ 957 | if(argv[i][2+len] == '\0') { 958 | /* with no argument provided. */ 959 | if(!(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) 960 | ret = callback(opt->id, NULL); 961 | else 962 | ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]); 963 | break; 964 | } else if(argv[i][2+len] == '=') { 965 | /* with an argument provided. */ 966 | if(opt->flags & (TEST_CMDLINE_OPTFLAG_OPTIONALARG__ | TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) { 967 | ret = callback(opt->id, argv[i]+2+len+1); 968 | } else { 969 | sprintf(auxbuf, "--%s", opt->longname); 970 | ret = callback(TEST_CMDLINE_OPTID_BOGUSARG__, auxbuf); 971 | } 972 | break; 973 | } else { 974 | continue; 975 | } 976 | } 977 | } else if(opt->shortname != '\0' && argv[i][0] == '-') { 978 | if(argv[i][1] == opt->shortname) { 979 | /* Regular short option. */ 980 | if(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__) { 981 | if(argv[i][2] != '\0') 982 | ret = callback(opt->id, argv[i]+2); 983 | else if(i+1 < argc) 984 | ret = callback(opt->id, argv[++i]); 985 | else 986 | ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]); 987 | break; 988 | } else { 989 | ret = callback(opt->id, NULL); 990 | 991 | /* There might be more (argument-less) short options 992 | * grouped together. */ 993 | if(ret == 0 && argv[i][2] != '\0') 994 | ret = test_cmdline_handle_short_opt_group__(options, argv[i]+2, callback); 995 | break; 996 | } 997 | } 998 | } 999 | } 1000 | 1001 | if(opt->id == 0) { /* still not handled? */ 1002 | if(argv[i][0] != '-') { 1003 | /* Non-option argument. */ 1004 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 1005 | } else { 1006 | /* Unknown option. */ 1007 | char* badoptname = argv[i]; 1008 | 1009 | if(strncmp(badoptname, "--", 2) == 0) { 1010 | /* Strip any argument from the long option. */ 1011 | char* assignement = strchr(badoptname, '='); 1012 | if(assignement != NULL) { 1013 | size_t len = assignement - badoptname; 1014 | if(len > TEST_CMDLINE_AUXBUF_SIZE__) 1015 | len = TEST_CMDLINE_AUXBUF_SIZE__; 1016 | strncpy(auxbuf, badoptname, len); 1017 | auxbuf[len] = '\0'; 1018 | badoptname = auxbuf; 1019 | } 1020 | } 1021 | 1022 | ret = callback(TEST_CMDLINE_OPTID_UNKNOWN__, badoptname); 1023 | } 1024 | } 1025 | } 1026 | 1027 | if(ret != 0) 1028 | return ret; 1029 | i++; 1030 | } 1031 | 1032 | return ret; 1033 | } 1034 | 1035 | static void 1036 | test_help__(void) 1037 | { 1038 | printf("Usage: %s [options] [test...]\n", test_argv0__); 1039 | printf("\n"); 1040 | printf("Run the specified unit tests; or if the option '--skip' is used, run all\n"); 1041 | printf("tests in the suite but those listed. By default, if no tests are specified\n"); 1042 | printf("on the command line, all unit tests in the suite are run.\n"); 1043 | printf("\n"); 1044 | printf("Options:\n"); 1045 | printf(" -s, --skip Execute all unit tests but the listed ones\n"); 1046 | printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n"); 1047 | printf(" (WHEN is one of 'auto', 'always', 'never')\n"); 1048 | #if defined ACUTEST_WIN__ 1049 | printf(" -t, --timer Measure test duration\n"); 1050 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 1051 | printf(" -t, --timer Measure test duration (real time)\n"); 1052 | printf(" --timer=TIMER Measure test duration, using given timer\n"); 1053 | printf(" (TIMER is one of 'real', 'cpu')\n"); 1054 | #endif 1055 | printf(" -E, --no-exec Same as --exec=never\n"); 1056 | printf(" --no-summary Suppress printing of test results summary\n"); 1057 | printf(" --tap Produce TAP-compliant output\n"); 1058 | printf(" (See https://testanything.org/)\n"); 1059 | printf(" -l, --list List unit tests in the suite and exit\n"); 1060 | printf(" -v, --verbose Make output more verbose\n"); 1061 | printf(" --verbose=LEVEL Set verbose level to LEVEL:\n"); 1062 | printf(" 0 ... Be silent\n"); 1063 | printf(" 1 ... Output one line per test (and summary)\n"); 1064 | printf(" 2 ... As 1 and failed conditions (this is default)\n"); 1065 | printf(" 3 ... As 1 and all conditions (and extended summary)\n"); 1066 | printf(" --color[=WHEN] Enable colorized output\n"); 1067 | printf(" (WHEN is one of 'auto', 'always', 'never')\n"); 1068 | printf(" --no-color Same as --color=never\n"); 1069 | printf(" -h, --help Display this help and exit\n"); 1070 | 1071 | if(test_list_size__ < 16) { 1072 | printf("\n"); 1073 | test_list_names__(); 1074 | } 1075 | } 1076 | 1077 | static const TEST_CMDLINE_OPTION__ test_cmdline_options__[] = { 1078 | { 's', "skip", 's', 0 }, 1079 | { 0, "exec", 'e', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1080 | { 'E', "no-exec", 'E', 0 }, 1081 | #if defined ACUTEST_WIN__ 1082 | { 't', "timer", 't', 0 }, 1083 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 1084 | { 't', "timer", 't', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1085 | #endif 1086 | { 0, "no-summary", 'S', 0 }, 1087 | { 0, "tap", 'T', 0 }, 1088 | { 'l', "list", 'l', 0 }, 1089 | { 'v', "verbose", 'v', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1090 | { 0, "color", 'c', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1091 | { 0, "no-color", 'C', 0 }, 1092 | { 'h', "help", 'h', 0 }, 1093 | { 0, "worker", 'w', TEST_CMDLINE_OPTFLAG_REQUIREDARG__ }, /* internal */ 1094 | { 0, NULL, 0, 0 } 1095 | }; 1096 | 1097 | static int 1098 | test_cmdline_callback__(int id, const char* arg) 1099 | { 1100 | switch(id) { 1101 | case 's': 1102 | test_skip_mode__ = 1; 1103 | break; 1104 | 1105 | case 'e': 1106 | if(arg == NULL || strcmp(arg, "always") == 0) { 1107 | test_no_exec__ = 0; 1108 | } else if(strcmp(arg, "never") == 0) { 1109 | test_no_exec__ = 1; 1110 | } else if(strcmp(arg, "auto") == 0) { 1111 | /*noop*/ 1112 | } else { 1113 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", test_argv0__, arg); 1114 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1115 | exit(2); 1116 | } 1117 | break; 1118 | 1119 | case 'E': 1120 | test_no_exec__ = 1; 1121 | break; 1122 | 1123 | case 't': 1124 | #if defined ACUTEST_WIN__ || defined ACUTEST_HAS_POSIX_TIMER__ 1125 | if(arg == NULL || strcmp(arg, "real") == 0) { 1126 | test_timer__ = 1; 1127 | #ifndef ACUTEST_WIN__ 1128 | } else if(strcmp(arg, "cpu") == 0) { 1129 | test_timer__ = 2; 1130 | #endif 1131 | } else { 1132 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --timer.\n", test_argv0__, arg); 1133 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1134 | exit(2); 1135 | } 1136 | #endif 1137 | break; 1138 | 1139 | case 'S': 1140 | test_no_summary__ = 1; 1141 | break; 1142 | 1143 | case 'T': 1144 | test_tap__ = 1; 1145 | break; 1146 | 1147 | case 'l': 1148 | test_list_names__(); 1149 | exit(0); 1150 | 1151 | case 'v': 1152 | test_verbose_level__ = (arg != NULL ? atoi(arg) : test_verbose_level__+1); 1153 | break; 1154 | 1155 | case 'c': 1156 | if(arg == NULL || strcmp(arg, "always") == 0) { 1157 | test_colorize__ = 1; 1158 | } else if(strcmp(arg, "never") == 0) { 1159 | test_colorize__ = 0; 1160 | } else if(strcmp(arg, "auto") == 0) { 1161 | /*noop*/ 1162 | } else { 1163 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", test_argv0__, arg); 1164 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1165 | exit(2); 1166 | } 1167 | break; 1168 | 1169 | case 'C': 1170 | test_colorize__ = 0; 1171 | break; 1172 | 1173 | case 'h': 1174 | test_help__(); 1175 | exit(0); 1176 | 1177 | case 'w': 1178 | test_worker__ = 1; 1179 | test_worker_index__ = atoi(arg); 1180 | break; 1181 | 1182 | case 0: 1183 | if(test_lookup__(arg) == 0) { 1184 | fprintf(stderr, "%s: Unrecognized unit test '%s'\n", test_argv0__, arg); 1185 | fprintf(stderr, "Try '%s --list' for list of unit tests.\n", test_argv0__); 1186 | exit(2); 1187 | } 1188 | break; 1189 | 1190 | case TEST_CMDLINE_OPTID_UNKNOWN__: 1191 | fprintf(stderr, "Unrecognized command line option '%s'.\n", arg); 1192 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1193 | exit(2); 1194 | 1195 | case TEST_CMDLINE_OPTID_MISSINGARG__: 1196 | fprintf(stderr, "The command line option '%s' requires an argument.\n", arg); 1197 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1198 | exit(2); 1199 | 1200 | case TEST_CMDLINE_OPTID_BOGUSARG__: 1201 | fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg); 1202 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1203 | exit(2); 1204 | } 1205 | 1206 | return 0; 1207 | } 1208 | 1209 | 1210 | #ifdef ACUTEST_LINUX__ 1211 | static int 1212 | test_is_tracer_present__(void) 1213 | { 1214 | char buf[256+32+1]; 1215 | int tracer_present = 0; 1216 | int fd; 1217 | ssize_t n_read; 1218 | 1219 | fd = open("/proc/self/status", O_RDONLY); 1220 | if(fd == -1) 1221 | return 0; 1222 | 1223 | n_read = read(fd, buf, sizeof(buf)-1); 1224 | while(n_read > 0) { 1225 | static const char pattern[] = "TracerPid:"; 1226 | const char* field; 1227 | 1228 | buf[n_read] = '\0'; 1229 | field = strstr(buf, pattern); 1230 | if(field != NULL && field < buf + sizeof(buf) - 32) { 1231 | pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1); 1232 | tracer_present = (tracer_pid != 0); 1233 | break; 1234 | } 1235 | 1236 | if(n_read == sizeof(buf)-1) { 1237 | memmove(buf, buf + sizeof(buf)-1 - 32, 32); 1238 | n_read = read(fd, buf+32, sizeof(buf)-1-32); 1239 | if(n_read > 0) 1240 | n_read += 32; 1241 | } 1242 | } 1243 | 1244 | close(fd); 1245 | return tracer_present; 1246 | } 1247 | #endif 1248 | 1249 | int 1250 | main(int argc, char** argv) 1251 | { 1252 | int i; 1253 | test_argv0__ = argv[0]; 1254 | 1255 | #if defined ACUTEST_UNIX__ 1256 | test_colorize__ = isatty(STDOUT_FILENO); 1257 | #elif defined ACUTEST_WIN__ 1258 | #if defined __BORLANDC__ 1259 | test_colorize__ = isatty(_fileno(stdout)); 1260 | #else 1261 | test_colorize__ = _isatty(_fileno(stdout)); 1262 | #endif 1263 | #else 1264 | test_colorize__ = 0; 1265 | #endif 1266 | 1267 | /* Count all test units */ 1268 | test_list_size__ = 0; 1269 | for(i = 0; test_list__[i].func != NULL; i++) 1270 | test_list_size__++; 1271 | 1272 | tests__ = (const struct test__**) malloc(sizeof(const struct test__*) * test_list_size__); 1273 | test_flags__ = (char*) malloc(sizeof(char) * test_list_size__); 1274 | if(tests__ == NULL || test_flags__ == NULL) { 1275 | fprintf(stderr, "Out of memory.\n"); 1276 | exit(2); 1277 | } 1278 | memset((void*) test_flags__, 0, sizeof(char) * test_list_size__); 1279 | 1280 | /* Parse options */ 1281 | test_cmdline_read__(test_cmdline_options__, argc, argv, test_cmdline_callback__); 1282 | 1283 | #if defined(ACUTEST_WIN__) 1284 | SetUnhandledExceptionFilter(test_exception_filter__); 1285 | #endif 1286 | 1287 | /* By default, we want to run all tests. */ 1288 | if(test_count__ == 0) { 1289 | for(i = 0; test_list__[i].func != NULL; i++) 1290 | tests__[i] = &test_list__[i]; 1291 | test_count__ = test_list_size__; 1292 | } 1293 | 1294 | /* Guess whether we want to run unit tests as child processes. */ 1295 | if(test_no_exec__ < 0) { 1296 | test_no_exec__ = 0; 1297 | 1298 | if(test_count__ <= 1) { 1299 | test_no_exec__ = 1; 1300 | } else { 1301 | #ifdef ACUTEST_WIN__ 1302 | if(IsDebuggerPresent()) 1303 | test_no_exec__ = 1; 1304 | #endif 1305 | #ifdef ACUTEST_LINUX__ 1306 | if(test_is_tracer_present__()) 1307 | test_no_exec__ = 1; 1308 | #endif 1309 | } 1310 | } 1311 | 1312 | if(test_tap__) { 1313 | /* TAP requires we know test result ("ok", "not ok") before we output 1314 | * anything about the test, and this gets problematic for larger verbose 1315 | * levels. */ 1316 | if(test_verbose_level__ > 2) 1317 | test_verbose_level__ = 2; 1318 | 1319 | /* TAP harness should provide some summary. */ 1320 | test_no_summary__ = 1; 1321 | 1322 | if(!test_worker__) 1323 | printf("1..%d\n", (int) test_count__); 1324 | } 1325 | 1326 | /* Run the tests */ 1327 | if(!test_skip_mode__) { 1328 | /* Run the listed tests. */ 1329 | for(i = 0; i < (int) test_count__; i++) 1330 | test_run__(tests__[i], test_worker_index__ + i); 1331 | } else { 1332 | /* Run all tests except those listed. */ 1333 | int index = test_worker_index__; 1334 | for(i = 0; test_list__[i].func != NULL; i++) { 1335 | if(!test_flags__[i]) 1336 | test_run__(&test_list__[i], index++); 1337 | } 1338 | } 1339 | 1340 | /* Write a summary */ 1341 | if(!test_no_summary__ && test_verbose_level__ >= 1) { 1342 | if(test_verbose_level__ >= 3) { 1343 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Summary:\n"); 1344 | 1345 | printf(" Count of all unit tests: %4d\n", (int) test_list_size__); 1346 | printf(" Count of run unit tests: %4d\n", test_stat_run_units__); 1347 | printf(" Count of failed unit tests: %4d\n", test_stat_failed_units__); 1348 | printf(" Count of skipped unit tests: %4d\n", (int) test_list_size__ - test_stat_run_units__); 1349 | } 1350 | 1351 | if(test_stat_failed_units__ == 0) { 1352 | test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS:"); 1353 | printf(" All unit tests have passed.\n"); 1354 | } else { 1355 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED:"); 1356 | printf(" %d of %d unit tests %s failed.\n", 1357 | test_stat_failed_units__, test_stat_run_units__, 1358 | (test_stat_failed_units__ == 1) ? "has" : "have"); 1359 | } 1360 | 1361 | if(test_verbose_level__ >= 3) 1362 | printf("\n"); 1363 | } 1364 | 1365 | free((void*) tests__); 1366 | free((void*) test_flags__); 1367 | 1368 | return (test_stat_failed_units__ == 0) ? 0 : 1; 1369 | } 1370 | 1371 | 1372 | #endif /* #ifndef TEST_NO_MAIN */ 1373 | 1374 | #ifdef __cplusplus 1375 | } /* extern "C" */ 1376 | #endif 1377 | 1378 | 1379 | #endif /* #ifndef ACUTEST_H__ */ 1380 | -------------------------------------------------------------------------------- /utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | if(CMAKE_COMPILER_IS_GNUCC) 3 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") 4 | if(WIN32) 5 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mconsole -static-libgcc") 6 | endif() 7 | elseif(MSVC) 8 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") 9 | add_definitions(/D_CRT_SECURE_NO_WARNINGS) 10 | endif() 11 | 12 | add_executable(json-parse cmdline.c cmdline.h json-err.c json-err.h json-parse.c) 13 | target_include_directories(json-parse PRIVATE ../src) 14 | target_link_libraries(json-parse json) 15 | -------------------------------------------------------------------------------- /utils/cmdline.c: -------------------------------------------------------------------------------- 1 | /* 2 | * C Reusables 3 | * 4 | * 5 | * Copyright (c) 2017 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include "cmdline.h" 27 | 28 | #include 29 | #include 30 | 31 | 32 | #ifdef _WIN32 33 | #define snprintf _snprintf 34 | #endif 35 | 36 | 37 | #define CMDLINE_AUXBUF_SIZE 32 38 | 39 | 40 | 41 | static int 42 | cmdline_handle_short_opt_group(const CMDLINE_OPTION* options, const char* arggroup, 43 | int (*callback)(int /*optval*/, const char* /*arg*/, void* /*userdata*/), 44 | void* userdata) 45 | { 46 | const CMDLINE_OPTION* opt; 47 | int i; 48 | int ret = 0; 49 | 50 | for(i = 0; arggroup[i] != '\0'; i++) { 51 | for(opt = options; opt->id != 0; opt++) { 52 | if(arggroup[i] == opt->shortname) 53 | break; 54 | } 55 | 56 | if(opt->id != 0 && !(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG)) { 57 | ret = callback(opt->id, NULL, userdata); 58 | } else { 59 | /* Unknown option. */ 60 | char badoptname[3]; 61 | badoptname[0] = '-'; 62 | badoptname[1] = arggroup[i]; 63 | badoptname[2] = '\0'; 64 | ret = callback((opt->id != 0 ? CMDLINE_OPTID_MISSINGARG : CMDLINE_OPTID_UNKNOWN), 65 | badoptname, userdata); 66 | } 67 | 68 | if(ret != 0) 69 | break; 70 | } 71 | 72 | return ret; 73 | } 74 | 75 | int 76 | cmdline_read(const CMDLINE_OPTION* options, int argc, char** argv, 77 | int (*callback)(int /*optval*/, const char* /*arg*/, void* /*userdata*/), 78 | void* userdata) 79 | { 80 | const CMDLINE_OPTION* opt; 81 | char auxbuf[CMDLINE_AUXBUF_SIZE+1]; 82 | int fast_optarg_decision = 1; 83 | int after_doubledash = 0; 84 | int i = 1; 85 | int ret = 0; 86 | 87 | auxbuf[CMDLINE_AUXBUF_SIZE] = '\0'; 88 | 89 | /* Check whether there is any CMDLINE_OPTFLAG_COMPILERLIKE option with 90 | * a name not starting with '-'. That would need we have to check 91 | * for non-option arguments only after refusing all such options. */ 92 | for(opt = options; opt->id != 0; opt++) { 93 | if((opt->flags & CMDLINE_OPTFLAG_COMPILERLIKE) && opt->longname[0] != '-') 94 | fast_optarg_decision = 1; 95 | } 96 | 97 | while(i < argc) { 98 | if(after_doubledash || strcmp(argv[i], "-") == 0) { 99 | /* Non-option argument. */ 100 | ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata); 101 | } else if(strcmp(argv[i], "--") == 0) { 102 | /* End of options. All the remaining members are non-otion arguments. */ 103 | after_doubledash = 1; 104 | } else if(fast_optarg_decision && argv[i][0] != '-') { 105 | /* Non-option argument. */ 106 | ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata); 107 | } else { 108 | for(opt = options; opt->id != 0; opt++) { 109 | if(opt->flags & CMDLINE_OPTFLAG_COMPILERLIKE) { 110 | size_t len = strlen(opt->longname); 111 | if(strncmp(argv[i], opt->longname, len) == 0) { 112 | /* Compiler-like option. */ 113 | if(argv[i][len] != '\0') 114 | ret = callback(opt->id, argv[i] + len, userdata); 115 | else if(i+1 < argc) 116 | ret = callback(opt->id, argv[++i], userdata); 117 | else 118 | ret = callback(CMDLINE_OPTID_MISSINGARG, opt->longname, userdata); 119 | break; 120 | } 121 | } else if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) { 122 | size_t len = strlen(opt->longname); 123 | if(strncmp(argv[i]+2, opt->longname, len) == 0) { 124 | /* Regular long option. */ 125 | if(argv[i][2+len] == '\0') { 126 | /* with no argument provided. */ 127 | if(!(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG)) 128 | ret = callback(opt->id, NULL, userdata); 129 | else 130 | ret = callback(CMDLINE_OPTID_MISSINGARG, argv[i], userdata); 131 | break; 132 | } else if(argv[i][2+len] == '=') { 133 | /* with an argument provided. */ 134 | if(opt->flags & (CMDLINE_OPTFLAG_OPTIONALARG | CMDLINE_OPTFLAG_REQUIREDARG)) { 135 | ret = callback(opt->id, argv[i]+2+len+1, userdata); 136 | } else { 137 | snprintf(auxbuf, CMDLINE_AUXBUF_SIZE, "--%s", opt->longname); 138 | ret = callback(CMDLINE_OPTID_BOGUSARG, auxbuf, userdata); 139 | } 140 | break; 141 | } else { 142 | continue; 143 | } 144 | } 145 | } else if(opt->shortname != '\0' && argv[i][0] == '-') { 146 | if(argv[i][1] == opt->shortname) { 147 | /* Regular short option. */ 148 | if(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG) { 149 | if(argv[i][2] != '\0') 150 | ret = callback(opt->id, argv[i]+2, userdata); 151 | else if(i+1 < argc) 152 | ret = callback(opt->id, argv[++i], userdata); 153 | else 154 | ret = callback(CMDLINE_OPTID_MISSINGARG, argv[i], userdata); 155 | break; 156 | } else { 157 | ret = callback(opt->id, NULL, userdata); 158 | 159 | /* There might be more (argument-less) short options 160 | * grouped together. */ 161 | if(ret == 0 && argv[i][2] != '\0') 162 | ret = cmdline_handle_short_opt_group(options, argv[i]+2, callback, userdata); 163 | break; 164 | } 165 | } 166 | } 167 | } 168 | 169 | if(opt->id == 0) { /* still not handled? */ 170 | if(argv[i][0] != '-') { 171 | /* Non-option argument. */ 172 | ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata); 173 | } else { 174 | /* Unknown option. */ 175 | char* badoptname = argv[i]; 176 | 177 | if(strncmp(badoptname, "--", 2) == 0) { 178 | /* Strip any argument from the long option. */ 179 | char* assignement = strchr(badoptname, '='); 180 | if(assignement != NULL) { 181 | size_t len = assignement - badoptname; 182 | if(len > CMDLINE_AUXBUF_SIZE) 183 | len = CMDLINE_AUXBUF_SIZE; 184 | strncpy(auxbuf, badoptname, len); 185 | auxbuf[len] = '\0'; 186 | badoptname = auxbuf; 187 | } 188 | } 189 | 190 | ret = callback(CMDLINE_OPTID_UNKNOWN, badoptname, userdata); 191 | } 192 | } 193 | } 194 | 195 | if(ret != 0) 196 | return ret; 197 | i++; 198 | } 199 | 200 | return ret; 201 | } 202 | 203 | -------------------------------------------------------------------------------- /utils/cmdline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * C Reusables 3 | * 4 | * 5 | * Copyright (c) 2017 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef CRE_CMDLINE_H 27 | #define CRE_CMDLINE_H 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | 34 | /* The option may have an argument. (Affects only long option.) */ 35 | #define CMDLINE_OPTFLAG_OPTIONALARG 0x0001 36 | 37 | /* The option must have an argument. 38 | * Such short option cannot be grouped within single '-abc'. */ 39 | #define CMDLINE_OPTFLAG_REQUIREDARG 0x0002 40 | 41 | /* Enable special compiler-like mode for the long option. 42 | * 43 | * Note ::shortname is not supported with this flag. CMDLINE_OPTION::shortname 44 | * is silently ignored if the flag is used. 45 | * 46 | * With this flag, CMDLINE_OPTION::longname is treated differently as follows: 47 | * 48 | * 1. The option matches if the CMDLINE_OPTION::longname is the exact prefix 49 | * of the argv[i] from commandline. 50 | * 51 | * 2. Double dash ("--") is not automatically prepended to 52 | * CMDLINE_OPTION::longname. (If you desire any leadin dash, include it 53 | * explicitly in CMDLINE_OPTION initialization.) 54 | * 55 | * 3. An argument (optionally after a whitespace) is required (the flag 56 | * CMDLINE_OPTFLAG_COMPILERLIKE implicitly implies also the flag 57 | * CMDLINE_OPTFLAG_REQUIREDARG). 58 | * 59 | * But there is no delimiter expected (no "=" between the option and its 60 | * argument). Whitespace is optional between the option and its argument. 61 | * 62 | * Intended use is for options similar to what many compilers accept. 63 | * For example: 64 | * -DDEBUG=0 (-D is the option, DEBUG=0 is the argument). 65 | * -Isrc/include (-I is the option, src/include is the argument). 66 | * -isystem /usr/include (-isystem is the option, /usr/include is the argument). 67 | * -lmath (-l is the option, math is the argument). 68 | */ 69 | #define CMDLINE_OPTFLAG_COMPILERLIKE 0x0004 70 | 71 | 72 | /* Special (reserved) option IDs. Do not use these for any CMDLINE_OPTION::id. 73 | * See documention of cmdline_read() to get info about their meaning. 74 | */ 75 | #define CMDLINE_OPTID_NONE 0 76 | #define CMDLINE_OPTID_UNKNOWN (-0x7fffffff + 0) 77 | #define CMDLINE_OPTID_MISSINGARG (-0x7fffffff + 1) 78 | #define CMDLINE_OPTID_BOGUSARG (-0x7fffffff + 2) 79 | 80 | 81 | typedef struct CMDLINE_OPTION { 82 | char shortname; /* Short (single char) option or 0. */ 83 | const char* longname; /* Long name (after "--") or NULL. */ 84 | int id; /* Non-zero ID to identify the option in the callback; or zero to denote end of options list. */ 85 | unsigned flags; /* Bitmask of CMDLINE_OPTFLAG_xxxx flags. */ 86 | } CMDLINE_OPTION; 87 | 88 | 89 | /* Parse all options and their arguments as specified by argc, argv accordingly 90 | * with the given options. The array of supported options has to be ended 91 | * with member whose CMDLINE_OPTION::id is zero. 92 | * 93 | * Note argv[0] is ignored. 94 | * 95 | * The callback is called for each (validly matching) option. 96 | * It is also called for any positional argument (with id set to zero). 97 | * 98 | * Special cases (errorneous command line) are reported to the callback by 99 | * negative id: 100 | * 101 | * -- CMDLINE_OPTID_UNKNOWN: The given option name does not exist. 102 | * 103 | * -- CMDLINE_OPTID_MISSINGARG: The option requires an argument but none 104 | * is present on the command line. 105 | * 106 | * -- CMDLINE_OPTID_BOGUSARG: The option expects no argument but some 107 | * is provided. 108 | * 109 | * In all those cases, name of the affected command line option is provided 110 | * in arg. 111 | * 112 | * On success, zero is returned. 113 | * 114 | * If the callback returns non-zero cmdline_read() aborts any subsequent 115 | * parsing and it returns the same value to the caller. 116 | */ 117 | 118 | int cmdline_read(const CMDLINE_OPTION* options, int argc, char** argv, 119 | int (*callback)(int /*id*/, const char* /*arg*/, void* /*userdata*/), 120 | void* userdata); 121 | 122 | 123 | #ifdef __cplusplus 124 | } /* extern "C" { */ 125 | #endif 126 | 127 | #endif /* CRE_CMDLINE_H */ 128 | -------------------------------------------------------------------------------- /utils/json-err.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CentiJSON 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include "json.h" 27 | #include "json-dom.h" 28 | #include "json-err.h" 29 | 30 | #include 31 | 32 | 33 | void 34 | json_err(int errcode, JSON_INPUT_POS* pos) 35 | { 36 | switch(errcode) { 37 | /* Branches for errors not related to the given location use return instead of break. */ 38 | case JSON_ERR_SUCCESS: fprintf(stderr, "Success.\n"); return; 39 | case JSON_ERR_OUTOFMEMORY: fprintf(stderr, "Out of memory.\n"); return; 40 | 41 | /* For these, position should provide reasonable pointer to the input. */ 42 | case JSON_ERR_INTERNAL: fprintf(stderr, "Internal error.\n"); break; 43 | case JSON_ERR_SYNTAX: fprintf(stderr, "Syntax error.\n"); break; 44 | case JSON_ERR_BADCLOSER: fprintf(stderr, "Object/array closer mismatch.\n"); break; 45 | case JSON_ERR_BADROOTTYPE: fprintf(stderr, "Prohibited root value type.\n"); break; 46 | case JSON_ERR_EXPECTEDVALUE: fprintf(stderr, "Value expected.\n"); break; 47 | case JSON_ERR_EXPECTEDKEY: fprintf(stderr, "Key expected.\n"); break; 48 | case JSON_ERR_EXPECTEDVALUEORCLOSER: fprintf(stderr, "Value or closer expected.\n"); break; 49 | case JSON_ERR_EXPECTEDKEYORCLOSER: fprintf(stderr, "Key or closer expected.\n"); break; 50 | case JSON_ERR_EXPECTEDCOLON: fprintf(stderr, "Colon ':' expected.\n"); break; 51 | case JSON_ERR_EXPECTEDCOMMAORCLOSER: fprintf(stderr, "Comma ',' or closer expected.\n"); break; 52 | case JSON_ERR_EXPECTEDEOF: fprintf(stderr, "End of file expected.\n"); break; 53 | case JSON_ERR_MAXTOTALLEN: fprintf(stderr, "Input file too long.\n"); break; 54 | case JSON_ERR_MAXTOTALVALUES: fprintf(stderr, "Too many data records.\n"); break; 55 | case JSON_ERR_MAXNESTINGLEVEL: fprintf(stderr, "Too deep object/array nesting.\n"); break; 56 | case JSON_ERR_MAXNUMBERLEN: fprintf(stderr, "Too long number.\n"); break; 57 | case JSON_ERR_MAXSTRINGLEN: fprintf(stderr, "Too long string.\n"); break; 58 | case JSON_ERR_MAXKEYLEN: fprintf(stderr, "Too long key.\n"); break; 59 | case JSON_ERR_UNCLOSEDSTRING: fprintf(stderr, "Unclosed string.\n"); break; 60 | case JSON_ERR_UNESCAPEDCONTROL: fprintf(stderr, "Unescaped control character.\n"); break; 61 | case JSON_ERR_INVALIDESCAPE: fprintf(stderr, "Invalid escape sequence.\n"); break; 62 | case JSON_ERR_INVALIDUTF8: fprintf(stderr, "Ill formed UTF-8.\n"); break; 63 | default: fprintf(stderr, "Unknown parsing error.\n"); break; 64 | } 65 | 66 | if(pos != NULL) { 67 | fprintf(stderr, "Offset: %lu\n", (unsigned long) pos->offset); 68 | fprintf(stderr, "Line: %u\n", pos->line_number); 69 | fprintf(stderr, "Column: %u\n", pos->column_number); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /utils/json-err.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CentiJSON 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef JSON_ERR_H 27 | #define JSON_ERR_H 28 | 29 | #include "json.h" 30 | 31 | 32 | void json_err(int errcode, JSON_INPUT_POS* pos); 33 | 34 | 35 | #endif /* JSON_ERR_H */ 36 | -------------------------------------------------------------------------------- /utils/json-parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CentiJSON 3 | * 4 | * 5 | * Copyright (c) 2018 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR 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 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include "cmdline.h" 27 | #include "json-dom.h" 28 | #include "json-err.h" 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | 35 | static const char* output_path = NULL; 36 | static const char* input_path = NULL; 37 | static int minimize = 0; 38 | static const char* argv0; 39 | 40 | 41 | static void 42 | print_usage(void) 43 | { 44 | printf("Usage: %s [OPTION]... [FILE]\n", argv0); 45 | printf("Parse and write down JSON file.\n"); 46 | printf(" -h, --help %s\n", "Display this help and exit"); 47 | 48 | printf("\n"); 49 | printf("Disclaimer: This utility is more meant for purposes like testing, "); 50 | printf("benchmarking, or providing an example code rather then as a serious "); 51 | printf("generally useful utility.\n"); 52 | 53 | exit(EXIT_SUCCESS); 54 | } 55 | static const CMDLINE_OPTION cmdline_options[] = { 56 | { 'o', "output", 'o', CMDLINE_OPTFLAG_REQUIREDARG }, 57 | { 'm', "minimize", 'm', 0 }, 58 | { 'h', "help", 'h', 0 }, 59 | { 0 } 60 | }; 61 | 62 | static int 63 | cmdline_callback(int id, const char* arg, void* userdata) 64 | { 65 | switch(id) { 66 | /* Options */ 67 | case 'o': output_path = arg; break; 68 | case 'm': minimize = 1; break; 69 | case 'h': print_usage(); break; 70 | 71 | /* Non-option arguments */ 72 | case 0: 73 | if(input_path) { 74 | fprintf(stderr, "Too many arguments. Only one input file can be specified.\n"); 75 | fprintf(stderr, "Use --help for more info.\n"); 76 | exit(1); 77 | } 78 | input_path = arg; 79 | break; 80 | 81 | /* Errors */ 82 | case CMDLINE_OPTID_UNKNOWN: 83 | fprintf(stderr, "Unrecognized command line option '%s'.", arg); 84 | exit(EXIT_FAILURE); 85 | case CMDLINE_OPTID_MISSINGARG: 86 | fprintf(stderr, "The command line option '%s' requires an argument.", arg); 87 | exit(EXIT_FAILURE); 88 | case CMDLINE_OPTID_BOGUSARG: 89 | fprintf(stderr, "The command line option '%s' does not expect an argument.", arg); 90 | exit(EXIT_FAILURE); 91 | } 92 | 93 | return 0; 94 | } 95 | 96 | 97 | static int 98 | write_callback(const char* data, size_t size, void* userdata) 99 | { 100 | FILE* out = (FILE*) userdata; 101 | 102 | if(fwrite(data, size, 1, out) == 0) { 103 | fprintf(stderr, "Output error.\n"); 104 | return -1; 105 | } 106 | 107 | return 0; 108 | } 109 | 110 | #define BUFFER_SIZE 4096 111 | 112 | static int 113 | process_file(FILE* in, FILE* out) 114 | { 115 | JSON_DOM_PARSER parser; 116 | JSON_INPUT_POS pos; 117 | VALUE root; 118 | char* buffer; 119 | size_t n; 120 | int ret = -1; 121 | unsigned dom_flags; 122 | 123 | buffer = (char*) malloc(BUFFER_SIZE); 124 | if(buffer == NULL) { 125 | fprintf(stderr, "Out of memory.\n"); 126 | goto err_malloc; 127 | } 128 | 129 | if(json_dom_init(&parser, NULL, 0) != 0) 130 | goto err_init; 131 | 132 | while(1) { 133 | n = fread(buffer, 1, BUFFER_SIZE, in); 134 | if(n == 0) 135 | break; 136 | 137 | if(json_dom_feed(&parser, buffer, n) != 0) 138 | break; 139 | } 140 | 141 | ret = json_dom_fini(&parser, &root, &pos); 142 | if(ret != 0) { 143 | json_err(ret, &pos); 144 | goto err_parse; 145 | } 146 | 147 | dom_flags = (minimize ? JSON_DOM_DUMP_MINIMIZE : 0); 148 | if(json_dom_dump(&root, write_callback, (void*) out, 0, dom_flags) != 0) 149 | goto err_dump; 150 | ret = 0; 151 | 152 | err_dump: 153 | value_fini(&root); 154 | err_parse: 155 | err_init: 156 | free(buffer); 157 | err_malloc: 158 | return ret; 159 | } 160 | 161 | int 162 | main(int argc, char** argv) 163 | { 164 | FILE* in = stdin; 165 | FILE* out = stdout; 166 | int ret = -1; 167 | 168 | argv0 = argv[0]; 169 | 170 | cmdline_read(cmdline_options, argc, argv, cmdline_callback, NULL); 171 | 172 | if(input_path != NULL && strcmp(input_path, "-") != 0) { 173 | in = fopen(input_path, "rb"); 174 | if(in == NULL) { 175 | fprintf(stderr, "Cannot open %s.\n", input_path); 176 | exit(1); 177 | } 178 | } 179 | if(output_path != NULL && strcmp(output_path, "-") != 0) { 180 | out = fopen(output_path, "wt"); 181 | if(out == NULL) { 182 | fprintf(stderr, "Cannot open %s.\n", input_path); 183 | exit(1); 184 | } 185 | } 186 | 187 | ret = process_file(in, out); 188 | 189 | if(in != stdin) 190 | fclose(in); 191 | if(out != stdout) 192 | fclose(out); 193 | 194 | return (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE); 195 | } 196 | 197 | --------------------------------------------------------------------------------