├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── README.md ├── bo_app ├── CMakeLists.txt └── src │ ├── bo_version.h.in │ └── main.c └── libbo ├── CMakeLists.txt ├── build-tools ├── .gitignore └── build_character_flags.c ├── cmake └── BOConfig.cmake.in ├── include └── bo │ └── bo.h ├── src ├── bo_buffer.h ├── bo_internal.h ├── character_flags.h ├── library.c ├── library_version.h.in └── parser.c ├── test ├── CMakeLists.txt ├── cmake │ └── GoogleTest-CMakeLists.txt.in └── src │ ├── boolean.cpp │ ├── config.cpp │ ├── errors.cpp │ ├── float.cpp │ ├── input.cpp │ ├── main.cpp │ ├── output.cpp │ ├── span.cpp │ ├── string.cpp │ ├── test_helpers.cpp │ └── test_helpers.h └── wip.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | 5 | ### Version 1.0.1 6 | 7 | * Minor bigfixes 8 | * Revamped documentation 9 | * Stricter compiler flags 10 | 11 | 12 | ### Version 1.0.0 13 | 14 | * Initial Release 15 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | project(bo LANGUAGES C) 3 | 4 | include(GNUInstallDirs) 5 | 6 | enable_testing() 7 | 8 | add_subdirectory(libbo) 9 | add_subdirectory(bo_app) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BO (Binary Output) 2 | ================== 3 | 4 | The Swiss army knife of data examination and manipulation. 5 | 6 | This is the tool to use when you need to visualize or convert data in different formats. 7 | 8 | 9 | 10 | How It Works 11 | ------------ 12 | 13 | Think of bo as a data processor that takes in streams of commands and data, and outputs accordingly: 14 | 15 | Command line arguments --\ +------+ 16 | \ | | 17 | Files -----------------------> | bo | ---> stdout or file 18 | / | | 19 | stdin -------------------/ +------+ 20 | 21 | 22 | Input consists of whitespace separated commands and data, which can be passed in via command line arguments, files, and stdin. Regardless of input source(s), the syntax is exactly the same: whitespace separated commands. Commands determine how input data is to be interpreted, and how output is to be formatted. 23 | 24 | 25 | 26 | Examples 27 | -------- 28 | 29 | ### Brief Introduction 30 | 31 | This is a short introduction to understand the examples. See the `Commands` section below for a full description. 32 | 33 | 34 | #### Commands used in the examples: 35 | 36 | * i{parameters}: Set input type. 37 | * o{parameters}: Set output type. 38 | * P{type}: Set the prefix and suffix from a preset ("s" for space separators, and "c" for C-style separators). 39 | * "a string": Add a string value. 40 | 41 | The values i, o, P, and "a string" are used to initiate commands. If bo doesn't recognize a command initiator, it will attempt to interpret numeric data. 42 | 43 | #### Input and output type commands consist of multiple fields: 44 | 45 | * **Type**: What type to read data as, or what to output as (see below) 46 | * **Data Width**: The width of each datum in bytes (1, 2, 4, 8, 16) 47 | * **Endianness**: What endianness to use when reading or presenting data ("l" or "b" - only required for data width > 1) 48 | * **Print Width**: Minimum number of digits to use when printing numeric values (only for output. optional). 49 | 50 | ##### Types: 51 | 52 | * **Integer**: base 16 (h), 10 (i), 8 (o), 2 (b) 53 | * **Floating point** (f) 54 | * **String** with c-style escaping (s) 55 | * **Raw binary data** (B) 56 | 57 | For example, `ih2b` means set input type to hexadecimal, 2 bytes per value, big endian. `oo4l11` means set output type to octal, 4 bytes per value, little endian, 11 digits minimum. 58 | 59 | 60 | #### Guided Example: 61 | 62 | oh1l2 Ps if4l 35.01 10.5 ih1 9f 5a 63 | 64 | This stream of commands does the following: 65 | 66 | * set output to hex, 1 byte per value, little endian (only required to separate the last field), min 2 digits per value. 67 | * Set output preset to "s" (space separated). 68 | * Set input to float, 4 bytes per value, little endian. 69 | * Read 35.01 and store internally according to current input type (4 byte little endian float). 70 | * Read 10.5 and store internally according to current input type (4 byte little endian float). 71 | * Set input to hex, 1 byte per value (endianness not needed). 72 | * Read 9f and store internally according to current input type (1 byte hex). 73 | * Read 5a and store internally according to current input type (1 byte hex). 74 | 75 | And output will be `3d 0a 0c 42 00 00 28 41 9f 5a` 76 | 77 | 78 | 79 | ### Examples of what you can do with bo 80 | 81 | Note: the `-n` flag just appends a newline after processing. 82 | 83 | 84 | #### See the per-byte layout (_oh1_, _Ps_) of a 4-byte integer (_decimal 12345678_) in big (_ih4b_) or little endian (_ih4l_) format. 85 | 86 | $ bo -n "oh1 Ps ih4b 12345678" 87 | 12 34 56 78 88 | 89 | $ bo -n "oh1 Ps ih4l 12345678" 90 | 78 56 34 12 91 | 92 | 93 | #### Convert integers between bases. 94 | 95 | $ bo -n "oh8b16 Pc ii8b 1000000" 96 | 0x00000000000f4240 97 | 98 | $ bo -n "oi8b ih8b 7fffffffffffffff" 99 | 9223372036854775807 100 | 101 | $ bo -n "ob8b ih8b ffe846134453a8c2" 102 | 1111111111101000010001100001001101000100010100111010100011000010 103 | 104 | 105 | #### Examine part of a memory dump (faked in this case) as an array of 32-bit big-endian floats at precision 3. 106 | 107 | Fake a 12-byte memory dump as an example: 108 | 109 | $ bo "oB1 if4b 1.1 8.5 305.125" >memory_dump.bin 110 | 111 | What it looks like in memory: 112 | 113 | $ bo -n -i memory_dump.bin "oh1b2 Pc iB1" 114 | 0x3f, 0x8c, 0xcc, 0xcd, 0x41, 0x08, 0x00, 0x00, 0x43, 0x98, 0x90, 0x00 115 | 116 | What it looks like interpreted as floats: 117 | 118 | $ bo -n -i memory_dump.bin "of4b3 Pc iB1" 119 | 1.100, 8.500, 305.125 120 | 121 | 122 | #### Convert the endianness of a data dump. 123 | 124 | $ bo "oB1 ih2b 0123 4567 89ab" >data.bin 125 | 126 | $ bo -n -i data.bin "oh2l Pc iB1" 127 | 0x2301, 0x6745, 0xab89 128 | 129 | Note: Once input type is set to binary, bo stops parsing, and just passes input DIRECTLY to the output system using the current output type. 130 | 131 | 132 | #### Convert text files to C-friendly strings. 133 | 134 | example.txt: 135 | 136 | This is a test. 137 | There are tabs between these words. 138 | ¡8-Ⅎ⊥∩ sʇɹoddns osʃɐ ʇI 139 | 140 | Read in example.txt as raw binary, and output as a C-style escaped string. 141 | 142 | $ bo -n -i example.txt "os iB1" 143 | This is a test.\nThere are tabs\tbetween\tthese\twords.\n¡8-Ⅎ⊥∩ sʇɹoddns osʃɐ ʇI 144 | 145 | 146 | #### Reinterpret one type as another at the binary level (for whatever reason). 147 | 148 | $ bo -n "oi4b if4b 1.5" 149 | 1069547520 150 | 151 | 152 | #### Complex example: Build up a data packet with multiple data types. 153 | 154 | $ bo -n "oh1l2 Pc if4b 1.5 1.25 ii2b 1000 2000 3000 ih1 ff fe 7a ib1 10001011" 155 | 156 | Does the following: 157 | 158 | * Set output to hex, 2 ditis per entry (`oh1l2`). 159 | * Set output prefix/suffix using the "c" preset (`Pc`). 160 | * Input 1.5 and 1.25 as 4-byte floats, big endian (`if4b 1.5 1.25`). 161 | * Input 1000, 2000, and 3000 as 2-byte decimal integers, big endian (`ii2b 1000 2000 3000`) 162 | * Input ff, fe, and 7a as 1-byte hex integers (`ih1 ff fe 7a`) 163 | * Input 10001011 as 1-byte binary integer (`ib1 10001011`) 164 | 165 | Results in: 166 | 167 | 0x3f, 0xc0, 0x00, 0x00, 0x3f, 0xa0, 0x00, 0x00, 0x03, 0xe8, 0x07, 0xd0, 0x0b, 0xb8, 0xff, 0xfe, 0x7a, 0x8b 168 | 169 | 170 | 171 | Usage 172 | ----- 173 | 174 | bo [options] command [command] ... 175 | 176 | 177 | ### Options 178 | 179 | * -i [filename]: Read commands/data from a file (specifying "-" uses stdin). 180 | * -o [filename]: Write output to a file (specifying "-" uses stdout). 181 | * -n Write a newline after processing is complete. 182 | * -h Print help and exit. 183 | * -v Print version and exit. 184 | 185 | 186 | The `bo` command can take input from command line arguments, files (using the -i switch), and stdin (using `-i -`). You may specify as many `-i` switches as you like. Bo first reads all command line arguments, and then reads files in the order they were specified using the `-i` switch. 187 | 188 | For example: 189 | 190 | bo -i file1.txt -i - -i file2.txt "oh2b4 Pc" io4b "1 2 3" 191 | 192 | Bo will: 193 | 194 | * Parse the string `oh2b4 Pc` (set output type to 2-byte hex, big endian, min 4 digits, then use "c" preset) 195 | * Parse the string `io4b` (set input type to 4-byte octal, big endian) 196 | * Parse the string `1 2 3` (read 3 integers) 197 | * Parse from `file1.txt` (execute all commands contained in the file) 198 | * Parse from stdin (`-i -`) (execute all commands passed in via stdin) 199 | * Parse from `file2.txt` (execute all commands contained in the file) 200 | 201 | By default, bo outputs to stdout, but you can specify an output file using `-o`. 202 | 203 | 204 | 205 | Commands 206 | -------- 207 | 208 | Bo parses whitespace separated strings and interprets them as commands. The following commands are supported: 209 | 210 | * i{type}{data width}{endianness}: Specify how to interpret input data 211 | * o{type}{data width}{endianness}[print width]: Specify how to re-interpret data and how to print it 212 | * p{string}: Specify a prefix to prepend to each datum output 213 | * s{string}: Specify a suffix to append to each datum output (except for the last object) 214 | * P{type}: Specify a preset for prefix and suffix. 215 | * "...": Read a string value. 216 | * (numeric): Read a numeric value. 217 | 218 | Data is interpreted according to the input format, stored in an intermediary buffer as binary data, and then later re-interpreted and printed according to the output format. 219 | 220 | Strings are copied as bytes to the intermediary binary buffer, to be reinterpreted later by the output format. 221 | 222 | Before processing any data, both input and output types must be specified via `input type` and `output type` commands. You may later change the input or output types again via commands, even after interpreting data. Any time the output type is changed, all buffers are flushed. 223 | 224 | 225 | 226 | ### Input Type Command 227 | 228 | The input specifier command consists of 3 parts: 229 | 230 | * Type: How to interpret incoming data (integer, float, decimal, binary, etc) 231 | * Data Width: The width of each datum in bytes (1, 2, 4, 8, 16) 232 | * Endianness: What endianness to use when storing data (little or big endian) 233 | 234 | 235 | ### Output Type Command 236 | 237 | The output specifier command consists of 4 parts: 238 | 239 | * Type: How to format the data for printing (integer, float, decimal, string, etc) 240 | * Data Width: The width of each datum in bytes (1, 2, 4, 8, 16) 241 | * Endianness: What endianness to use when presenting data (little or big endian) 242 | * Print Width: Minimum number of digits to use when printing numeric values (optional). 243 | 244 | 245 | #### Type 246 | 247 | Determines the type to be used for interpreting incoming data, or presenting outgoing data. 248 | 249 | * Integer (i): Integer in base 10 250 | * Hexadecimal (h): Integer in base 16 251 | * Octal (o): Integer in base 8 252 | * Boolean (b): Integer in base 2 253 | * Float (f): IEEE 754 binary floating point 254 | * Decimal (d): IEEE 754 binary decimal (TODO) 255 | * String (s): String, with c-style encoding for escaped chars (tab, newline, hex, etc). 256 | * Binary (B): Data is interpreted or output using its binary representation rather than text. 257 | 258 | ##### Notes on the boolean type 259 | 260 | As the boolean type operates on sub-byte values, its behavior may seem surprising at first. In little endian systems, not only does byte 0 occur first in a word, but so does bit 0. Bo honors this bit ordering when printing. For example: 261 | 262 | $ bo ob2b ih2b 6 263 | 0000000000000110 264 | $ bo ob2l ih2l 6 265 | 0110000000000000 266 | 267 | Note, however, that when inputting textual representations of boolean values, they are ALWAYS read as big endian (same as with all other types), and then stored according to the input endianness: 268 | 269 | $ bo ob2b ib2b 1011 270 | 0000000000001011 271 | $ bo ob2l ib2l 1011 272 | 1101000000000000 273 | $ bo ob2b ib2l 1011 274 | 0000101100000000 275 | $ bo ob2l ib2b 1011 276 | 0000000011010000 277 | 278 | ##### Notes on the raw binary type 279 | 280 | If you set the input type to raw binary (B), bo will no longer parse input; rather, it will stream raw input directly to the output function. 281 | 282 | 283 | 284 | #### Data Width 285 | 286 | Determines how wide of a data field to store the data in: 287 | 288 | * 1 bytes (8-bit) 289 | * 2 bytes (16-bit) 290 | * 4 bytes (32-bit) 291 | * 8 bytes (64-bit) 292 | * 16 bytes (128-bit) 293 | 294 | 295 | #### Endianness 296 | 297 | Determines in which order bits and bytes are encoded: 298 | 299 | * Little Endian (l): Lowest bit and byte comes first 300 | * Big Endian (b): Highest bit and byte comes first 301 | 302 | The endianness field is optional when data width is 1, EXCEPT for output type boolean. 303 | 304 | 305 | #### Print Width 306 | 307 | Specifies the minimum number of digits to print when outputting numeric values. This is an optional field, and defaults to 1. 308 | 309 | For integer types, zeroes are prepended until the printed value has the specified number of digits. 310 | For floating point types, zeroes are appended until the fractional portion has the specified number of digits. The whole number portion is not used and has no effect in this calculation. 311 | 312 | 313 | #### Input and Output Type Examples 314 | 315 | * `ih2l`: Input type hexadecimal encoded integer, 2 bytes per value, little endian 316 | * `io4b`: Input type octal encoded integer, 4 bytes per value, big endian 317 | * `if4l`: Input type floating point, 4 bytes per value, little endian 318 | * `oi4l`: Output as 4-byte integers in base 10 with default minimum 1 digit (i.e. no zero padding) 319 | * `of8l10`: Interpret data as 8-byte floats and output with 10 digits after the decimal point. 320 | 321 | 322 | 323 | ### Prefix Specifier Command 324 | 325 | Defines what to prefix each output entry with. 326 | 327 | Note: Examples have escaped quotes since that's usually what you'll be doing with command line arguments. 328 | 329 | * `p\"0x\"`: Prefix all values with "0x". 330 | * `p\"The next value is: \"`: Prefix all values with "The next value is: " 331 | 332 | 333 | ### Suffix Specifier Command 334 | 335 | Defines what to suffix each output entry with (except for the last entry). 336 | 337 | Note: Examples have escaped quotes since that's usually what you'll be doing with command line arguments. 338 | 339 | * `s\", \"`: Suffix all values with ", ". 340 | * `s\" | \"`: Suffix all values with " | " 341 | 342 | 343 | ### Preset Command 344 | 345 | Presets define preset prefixes and suffixes for common tasks. Currently, the following are supported: 346 | 347 | * c: C-style prefix/suffix: ", " suffix, and prefix based on output type: 0x for hex, 0 for octal, and nothing for everything else. 348 | * s: Space separator between entries. 349 | 350 | 351 | 352 | Building 353 | -------- 354 | 355 | Requirements: 356 | 357 | * CMake 358 | * C/C++ compiler 359 | 360 | Commands: 361 | 362 | mkdir build 363 | cd build 364 | cmake .. 365 | make 366 | ./bo_app/bo -h 367 | 368 | 369 | 370 | Tests 371 | ----- 372 | 373 | ./libbo/test/libbo_test 374 | 375 | 376 | 377 | Libbo 378 | ----- 379 | 380 | All of bo's functionality is in the library libbo. The API is small (4 calls, 2 callbacks) and pretty straightforward since all commands and configurations are done through the parsed data. The basic process is: 381 | 382 | * Build a context object according to your needs. 383 | * Call one or more process functions. 384 | * Flush and destroy the context. 385 | * Gather output and errors via the callbacks. 386 | 387 | `test_helpers.cpp` shows how to parse strings, and `main.c` from bo_app shows how to use file streams. 388 | 389 | 390 | 391 | Issues 392 | ------ 393 | 394 | * IEEE754 decimal types are not yet implemented. 395 | * 128 bit values are not yet implemented. 396 | * 16-bit ieee754 floating point is not yet implemented. 397 | 398 | 399 | 400 | License 401 | ------- 402 | 403 | Copyright 2018 Karl Stenerud 404 | 405 | Released under MIT license: 406 | 407 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 408 | 409 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 410 | 411 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 412 | -------------------------------------------------------------------------------- /bo_app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | project(bo_app VERSION 1.0.1 LANGUAGES C) 3 | 4 | add_executable(bo src/main.c) 5 | 6 | target_compile_options(bo PRIVATE $<$: 7 | -Wall 8 | -Wextra 9 | # -Wduplicated-cond 10 | # -Wduplicated-branches 11 | -Wlogical-op 12 | # -Wrestrict 13 | # -Wnull-dereference 14 | -Wjump-misses-init 15 | -Wdouble-promotion 16 | -Wshadow 17 | -fstrict-aliasing 18 | -Wformat=2 19 | >) 20 | 21 | configure_file(src/bo_version.h.in bo_version.h) 22 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 23 | 24 | target_link_libraries(bo libbo) 25 | -------------------------------------------------------------------------------- /bo_app/src/bo_version.h.in: -------------------------------------------------------------------------------- 1 | #define BO_VERSION "${PROJECT_VERSION}" 2 | -------------------------------------------------------------------------------- /bo_app/src/main.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Karl Stenerud. All rights reserved. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall remain in place 11 | // in this source code. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | // 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "bo_version.h" 31 | 32 | 33 | #define STRINGIZE_PARAM_(arg) #arg 34 | #define STRINGIZE_PARAM(arg) STRINGIZE_PARAM_(arg) 35 | 36 | static const char* g_version = STRINGIZE_PARAM(BO_VERSION); 37 | 38 | static const char g_usage[] = 39 | "Usage: bo [options] command [command] ...\n" 40 | "\n" 41 | "The default is to read commands from cmdline arguments and print output to stdout.\n" 42 | "\n" 43 | "Options:\n" 44 | " -i [filename]: Read commands/data from a file (use \"-\" to read from stdin).\n" 45 | " -o [filename]: Write output to a file (use \"-\" to write to stdout).\n" 46 | " -n : Write a newline after processing is complete.\n" 47 | " -v : Print version and exit.\n" 48 | " -h : Print help and exit.\n" 49 | "\n" 50 | "Commands:\n" 51 | " i{type}{data width}{endianness}: Specify how to interpret input data\n" 52 | " o{type}{data width}{endianness}{print width}: Specify how to re-interpret data and how to print it\n" 53 | " p{string}: Specify a prefix to prepend to each datum output\n" 54 | " s{string}: Specify a suffix to append to each datum object (except for the last object)\n" 55 | " P{type}: Specify a preset for prefix and suffix.\n" 56 | "\n" 57 | "Types:\n" 58 | " i: Integer in base 10\n" 59 | " h: Integer in base 16\n" 60 | " o: Integer in base 8\n" 61 | " b: Integer in base 2\n" 62 | " f: IEEE 754 binary floating point\n" 63 | " d: IEEE 754 binary decimal\n" 64 | " s: C-style string (including escaping). This type does not use widths or endianness.\n" 65 | " B: Data is interpreted or output using its binary representation rather than text.\n" 66 | "\n" 67 | "Data Widths:\n" 68 | " 1 bytes (8-bit)\n" 69 | " 2 bytes (16-bit)\n" 70 | " 4 bytes (32-bit)\n" 71 | " 8 bytes (64-bit)\n" 72 | " 16 bytes (128-bit)\n" 73 | "\n" 74 | "Endianness:\n" 75 | " l: Little Endian\n" 76 | " b: Big Endian\n" 77 | "\n" 78 | "Print Width:\n" 79 | " Any integer representing the minimum number of digits to print.\n" 80 | " For floating point, the number of digits after the decimal point.\n" 81 | "\n" 82 | "Presets:\n" 83 | " c: C-style preset: \", \" suffix, 0x prefix for hexadecimal, 0 prefix for octal.\n" 84 | " s: Space preset: \" \" suffix.\n" 85 | "\n" 86 | "Notes:\n" 87 | " Presets must be applied AFTER setting the output type.\n" 88 | " Surrounding quoted quotes aren't necessary except for arguments containing string values.\n" 89 | " A single command line argument may contain multiple commands.\n" 90 | "\n" 91 | "Example: Convert the string \"Testing\" to its hex representation using \"space\" preset:\n" 92 | " bo \"oh1b2 Ps ih1b \\\"Testing\\\"\"\n" 93 | "\n" 94 | "Example: Convert the 32-bit float 1.5 to its little endian hex representation using \"C\" preset:\n" 95 | " bo oh1l2 if4l Pc 1.5\n" 96 | ; 97 | 98 | static void print_version() 99 | { 100 | printf("Bo version %s\n", g_version); 101 | } 102 | 103 | static void print_usage() 104 | { 105 | printf("\n%s", g_usage); 106 | } 107 | 108 | static void on_error(__attribute__ ((unused)) void* user_data, const char* message) 109 | { 110 | if(message == NULL || *message == 0) 111 | { 112 | return; 113 | } 114 | 115 | fprintf(stderr, "Error: %s\n", message); 116 | } 117 | 118 | static void perror_exit(const char* fmt, ...) 119 | { 120 | va_list args; 121 | va_start(args, fmt); 122 | vfprintf(stderr, fmt, args); 123 | va_end(args); 124 | perror(""); 125 | exit(1); 126 | } 127 | 128 | static FILE* new_output_stream(const char* filename) 129 | { 130 | if(filename == NULL) 131 | { 132 | return NULL; 133 | } 134 | if(strcmp(filename, "-") == 0) 135 | { 136 | return stdout; 137 | } 138 | 139 | FILE* stream = fopen(filename, "wb"); 140 | if(stream == NULL) 141 | { 142 | perror_exit("Could not open %s for writing", filename); 143 | } 144 | return stream; 145 | } 146 | 147 | static FILE* new_input_stream(const char* filename) 148 | { 149 | if(filename == NULL) 150 | { 151 | return NULL; 152 | } 153 | if(strcmp(filename, "-") == 0) 154 | { 155 | return stdin; 156 | } 157 | 158 | FILE* stream = fopen(filename, "rb"); 159 | if(stream == NULL) 160 | { 161 | perror_exit("Could not open %s for reading", filename); 162 | } 163 | return stream; 164 | } 165 | 166 | static void close_stream(FILE* stream) 167 | { 168 | if(stream == NULL || stream == stdin || stream == stdout) 169 | { 170 | return; 171 | } 172 | 173 | fclose(stream); 174 | } 175 | 176 | static void teardown(void* context, FILE* out_stream, const char** in_filenames, int in_filenames_count, bool should_print_newline) 177 | { 178 | if(context != NULL) 179 | { 180 | bo_flush_and_destroy_context(context); 181 | } 182 | if(out_stream != NULL && should_print_newline) 183 | { 184 | fprintf(out_stream, "\n"); 185 | } 186 | close_stream(out_stream); 187 | for(int i = 0; i < in_filenames_count; i++) 188 | { 189 | free((void*)in_filenames[i]); 190 | } 191 | } 192 | 193 | static bool on_output(void* void_user_data, char* data, int length) 194 | { 195 | FILE* output_stream = (FILE*)void_user_data; 196 | int bytes_written = fwrite(data, 1, length, output_stream); 197 | if(bytes_written != length) 198 | { 199 | perror("Error writing to putput stream"); 200 | return false; 201 | } 202 | return true; 203 | } 204 | 205 | static bool process_stream(void* context, FILE* stream) 206 | { 207 | char buffer[10000]; 208 | const int bytes_to_read = sizeof(buffer) - 1; 209 | int bytes_read; 210 | bool is_successful = true; 211 | 212 | while((bytes_read = fread(buffer, 1, bytes_to_read, stream)) > 0) 213 | { 214 | if(ferror(stream)) 215 | { 216 | perror("Error reading from input stream"); 217 | is_successful = false; 218 | } 219 | buffer[bytes_read] = 0; 220 | bo_data_segment_type segment_type = feof(stream) ? DATA_SEGMENT_LAST : DATA_SEGMENT_STREAM; 221 | bo_process(context, buffer, bytes_read, segment_type); 222 | } 223 | if(is_successful && ferror(stream)) 224 | { 225 | perror("Error reading from input stream"); 226 | is_successful = false; 227 | } 228 | 229 | return is_successful; 230 | } 231 | 232 | 233 | int main(int argc, char* argv[]) 234 | { 235 | void* context = NULL; 236 | const int max_file_count = 1000; 237 | const char* in_filenames[max_file_count]; 238 | int in_file_count = 0; 239 | FILE* out_stream = stdout; 240 | bool should_print_newline = false; 241 | bool has_args = false; 242 | bool is_flush_successful = false; 243 | 244 | int opt = 0; 245 | while((opt = getopt (argc, argv, "i:o:hnv")) != -1) 246 | { 247 | switch(opt) 248 | { 249 | case 'i': 250 | if(in_file_count >= max_file_count) 251 | { 252 | fprintf(stderr, "Too many input files. Maximum allowed is %d.\n", max_file_count); 253 | goto failed; 254 | } 255 | in_filenames[in_file_count++] = strdup(optarg); 256 | break; 257 | case 'o': 258 | close_stream(out_stream); // Just in case the user does something stupid 259 | out_stream = new_output_stream(optarg); 260 | break; 261 | case 'n': 262 | should_print_newline = true; 263 | break; 264 | case 'h': 265 | print_version(); 266 | print_usage(); 267 | goto success; 268 | case 'v': 269 | print_version(); 270 | goto success; 271 | default: 272 | print_version(); 273 | print_usage(); 274 | goto failed; 275 | } 276 | } 277 | has_args = optind < argc; 278 | 279 | if(!has_args && in_file_count == 0) 280 | { 281 | fprintf(stderr, "Must specify input string and/or input stream\n"); 282 | goto failed; 283 | } 284 | 285 | context = bo_new_context(out_stream, on_output, on_error); 286 | 287 | for(int i = optind; i < argc; i++) 288 | { 289 | if(bo_process(context, argv[i], strlen(argv[i]), DATA_SEGMENT_LAST) == NULL) 290 | { 291 | goto failed; 292 | } 293 | } 294 | 295 | for(int i = 0; i < in_file_count; i++) 296 | { 297 | FILE* in_stream = new_input_stream(in_filenames[i]); 298 | bool result = process_stream(context, in_stream); 299 | close_stream(in_stream); 300 | if(!result) 301 | { 302 | goto failed; 303 | } 304 | } 305 | 306 | is_flush_successful = bo_flush_and_destroy_context(context); 307 | context = NULL; 308 | 309 | if(!is_flush_successful) 310 | { 311 | goto failed; 312 | } 313 | 314 | success: 315 | teardown(context, out_stream, in_filenames, in_file_count, should_print_newline); 316 | return 0; 317 | 318 | failed: 319 | printf("Use bo -h for help.\n"); 320 | teardown(context, out_stream, in_filenames, in_file_count, false); 321 | return 1; 322 | } 323 | -------------------------------------------------------------------------------- /libbo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(libbo VERSION 1.0.1 LANGUAGES C) 2 | 3 | add_library(libbo 4 | src/library.c 5 | src/parser.c 6 | ) 7 | 8 | target_include_directories(libbo 9 | PUBLIC 10 | $ 11 | $ 12 | PRIVATE 13 | src 14 | ) 15 | 16 | target_compile_options(libbo PRIVATE $<$: 17 | -Wall 18 | -Wextra 19 | # -Wduplicated-cond 20 | # -Wduplicated-branches 21 | -Wlogical-op 22 | # -Wrestrict 23 | # -Wnull-dereference 24 | -Wjump-misses-init 25 | -Wdouble-promotion 26 | -Wshadow 27 | -fstrict-aliasing 28 | -Wformat=2 29 | >) 30 | 31 | configure_file(src/library_version.h.in library_version.h) 32 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 33 | 34 | include(GNUInstallDirs) 35 | 36 | # 'make install' to correct locations (GNUInstallDirs) 37 | install(TARGETS libbo EXPORT LibBoConfig 38 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 39 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 40 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) # This is for Windows 41 | 42 | install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 43 | 44 | # Make project importable from install directory 45 | install(EXPORT LibBoConfig DESTINATION share/LibBo/cmake) 46 | 47 | # Make project importable from build directory 48 | export(TARGETS libbo FILE LibBoConfig.cmake) 49 | 50 | add_subdirectory(test) 51 | -------------------------------------------------------------------------------- /libbo/build-tools/.gitignore: -------------------------------------------------------------------------------- 1 | build_character_flags 2 | -------------------------------------------------------------------------------- /libbo/build-tools/build_character_flags.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Karl Stenerud. All rights reserved. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall remain in place 11 | // in this source code. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | // 21 | 22 | 23 | // Generates a header file containing a table of meta information for all 24 | // basic characters. 25 | // 26 | // Just compile this file as a command line program and run. It will output the 27 | // enum and character table as a header file to stdout. 28 | 29 | 30 | // ------------- 31 | // Configuration 32 | // ------------- 33 | 34 | #define ENUM_NAME "character_flag" 35 | #define ENUM_PREFIX "CH_FLAG_" 36 | #define CHARACTER_FLAGS_TABLE_NAME "g_character_flags" 37 | #define CHARACTER_FLAGS_TABLE_TYPE unsigned char 38 | #define CHARACTER_FLAGS_TABLE_LENGTH 256 39 | 40 | // CHARACTER_FLAGS_TABLE_TYPE determines how many of these enum value get used. 41 | // An 8-bit table type will only have the first 8 flags. 42 | typedef enum 43 | { 44 | NONE, 45 | 46 | // Included symbols for 8-bit flags 47 | CONTROL, // Control character 48 | WHITESPACE, // Whitespace character 49 | BASE_8, // Can represent a number in base 8 50 | BASE_10, // Can represent a number in base 10 51 | BASE_16, // Can represent a number in base 16 52 | FP_NUMBER, // Includes digits, sign, decimal point, etc 53 | ALPHANUMERIC, // Letters and numbers ([a-zA-Z0-9]) 54 | PRINTABLE, // A single-byte printable symbol 55 | 56 | // Included symbols for 16-bit flags 57 | SYMBOL, // A symbol character 58 | BASE_2, // Can represent a number in base 2 59 | UTF_8, // UTF-8 multibyte octet of any kind 60 | UTF_8_2_BYTES, // UTF-8 2-byte initiator 61 | UTF_8_3_BYTES, // UTF-8 3-byte initiator 62 | UTF_8_4_BYTES, // UTF-8 4-byte initiator 63 | UTF_8_CONTINUATION, // UTF-8 continuation octet 64 | } character_flag; 65 | 66 | // The contents of g_character_flag_names can be defined in any order. 67 | // The [XYZ] = "XYZ" syntax ensures proper order matching to the enum. 68 | static const char* const g_character_flag_names[] = 69 | { 70 | [NONE] = "NONE", 71 | [CONTROL] = "CONTROL", 72 | [SYMBOL] = "SYMBOL", 73 | [WHITESPACE] = "WHITESPACE", 74 | [BASE_2] = "BASE_2", 75 | [BASE_8] = "BASE_8", 76 | [BASE_10] = "BASE_10", 77 | [BASE_16] = "BASE_16", 78 | [FP_NUMBER] = "FP_NUMBER", 79 | [ALPHANUMERIC] = "ALPHANUMERIC", 80 | [UTF_8] = "UTF_8", 81 | [UTF_8_2_BYTES] = "UTF_8_2_BYTES", 82 | [UTF_8_3_BYTES] = "UTF_8_3_BYTES", 83 | [UTF_8_4_BYTES] = "UTF_8_4_BYTES", 84 | [UTF_8_CONTINUATION] = "UTF_8_CONTINUATION", 85 | [PRINTABLE] = "PRINTABLE", 86 | }; 87 | 88 | 89 | 90 | // ----- 91 | // Setup 92 | // ----- 93 | 94 | #include 95 | #include 96 | #include 97 | #include 98 | 99 | #define STRINGIZE_PARAM_(arg) #arg 100 | #define STRINGIZE_PARAM(arg) STRINGIZE_PARAM_(arg) 101 | #define CHARACTER_FLAGS_TABLE_TYPE_NAME STRINGIZE_PARAM(CHARACTER_FLAGS_TABLE_TYPE) 102 | 103 | static int g_character_flag_names_count = sizeof(g_character_flag_names) / sizeof(*g_character_flag_names); 104 | 105 | 106 | 107 | // ---- 108 | // Data 109 | // ---- 110 | 111 | static uint64_t g_character_flags[256] = {0}; 112 | static char g_character_names[256][100] = {0}; 113 | 114 | 115 | 116 | // ------- 117 | // Utility 118 | // ------- 119 | 120 | static int get_flag_values_max() 121 | { 122 | return sizeof(CHARACTER_FLAGS_TABLE_TYPE) * 8; 123 | } 124 | 125 | static int get_flag_value(int flag_index) 126 | { 127 | return flag_index == 0 ? 0 : 1 << (flag_index - 1); 128 | } 129 | 130 | static const char* get_flag_name(int flag_index) 131 | { 132 | return g_character_flag_names[flag_index]; 133 | } 134 | 135 | static const char* get_character_name(int ch) 136 | { 137 | return g_character_names[ch]; 138 | } 139 | 140 | static void set_character_name(int ch, const char* const name) 141 | { 142 | strcpy(g_character_names[ch], name); 143 | } 144 | 145 | static bool is_character_flag_set(int ch, int flag_index) 146 | { 147 | return (g_character_flags[ch] & get_flag_value(flag_index)) != 0; 148 | } 149 | 150 | static void add_flag_to_character(character_flag flag, int ch) 151 | { 152 | g_character_flags[ch] |= get_flag_value(flag); 153 | } 154 | 155 | static void add_flag_to_characters_in_range(character_flag flag, int min_char, int max_char) 156 | { 157 | for(int ch = min_char; ch <= max_char; ch++) 158 | { 159 | add_flag_to_character(flag, ch); 160 | } 161 | } 162 | 163 | static void add_flag_to_characters(character_flag flag, const char* str) 164 | { 165 | for(; *str != 0; str++) 166 | { 167 | add_flag_to_character(flag, *str); 168 | } 169 | } 170 | 171 | static void set_character_names_literal(const int min_char, const int max_char) 172 | { 173 | char name[2] = {0}; 174 | for(int ch = min_char; ch <= max_char; ch++) 175 | { 176 | name[0] = ch; 177 | set_character_name(ch, name); 178 | } 179 | } 180 | 181 | 182 | 183 | // -------- 184 | // Printing 185 | // -------- 186 | 187 | static void print_flags(const int ch) 188 | { 189 | bool first_entry = true; 190 | bool did_find_flag = false; 191 | for(int i = 1; i <= get_flag_values_max() && i < g_character_flag_names_count; i++) 192 | { 193 | if(is_character_flag_set(ch, i)) 194 | { 195 | if(!first_entry) 196 | { 197 | printf(" | "); 198 | } 199 | first_entry = false; 200 | printf("%s%s", ENUM_PREFIX, get_flag_name(i)); 201 | did_find_flag = true; 202 | } 203 | } 204 | if(!did_find_flag) 205 | { 206 | printf("%s%s", ENUM_PREFIX, get_flag_name(NONE)); 207 | } 208 | } 209 | 210 | static void print_enum() 211 | { 212 | const int number_width = (int)sizeof(CHARACTER_FLAGS_TABLE_TYPE) * 2; 213 | int text_width = 0; 214 | for(int i = 0; i <= get_flag_values_max() && i < g_character_flag_names_count; i++) 215 | { 216 | int length = strlen(get_flag_name(i)); 217 | if(length > text_width) 218 | { 219 | text_width = length; 220 | } 221 | } 222 | 223 | printf("typedef enum\n{\n"); 224 | for(int i = 0; i <= get_flag_values_max() && i < g_character_flag_names_count; i++) 225 | { 226 | printf(" %s%*s = 0x%0*x,\n", 227 | ENUM_PREFIX, 228 | -text_width, 229 | get_flag_name(i), 230 | number_width, 231 | get_flag_value(i)); 232 | } 233 | printf("} %s;\n", ENUM_NAME); 234 | } 235 | 236 | static void print_table() 237 | { 238 | const int table_length = sizeof(g_character_flags) / sizeof(*g_character_flags); 239 | 240 | printf("static const %s %s[%d] =\n{\n", 241 | CHARACTER_FLAGS_TABLE_TYPE_NAME, 242 | CHARACTER_FLAGS_TABLE_NAME, 243 | CHARACTER_FLAGS_TABLE_LENGTH); 244 | 245 | for(int i = 0; i < table_length; i++) 246 | { 247 | printf(" /* %02x: %-5s */ (%s)(", i, get_character_name(i), CHARACTER_FLAGS_TABLE_TYPE_NAME); 248 | print_flags(i); 249 | printf("),\n"); 250 | } 251 | 252 | printf("};\n"); 253 | } 254 | 255 | 256 | 257 | // ----------- 258 | // Definitions 259 | // ----------- 260 | 261 | static void add_whitespace_flags() 262 | { 263 | add_flag_to_characters(WHITESPACE, " \t\n\v\f\r"); 264 | } 265 | 266 | static void add_number_flags() 267 | { 268 | add_flag_to_characters_in_range(FP_NUMBER, '0', '9'); 269 | add_flag_to_characters(FP_NUMBER, "+-.eE"); 270 | } 271 | 272 | static void add_control_flags() 273 | { 274 | add_flag_to_characters_in_range(CONTROL, 0x00, 0x1f); 275 | add_flag_to_character(CONTROL, 0x7f); 276 | } 277 | 278 | static void add_base_2_flags() 279 | { 280 | add_flag_to_characters_in_range(BASE_2, '0', '1'); 281 | } 282 | 283 | static void add_base_8_flags() 284 | { 285 | add_flag_to_characters_in_range(BASE_8, '0', '7'); 286 | } 287 | 288 | static void add_base_10_flags() 289 | { 290 | add_flag_to_characters_in_range(BASE_10, '0', '9'); 291 | } 292 | 293 | static void add_base_16_flags() 294 | { 295 | add_flag_to_characters_in_range(BASE_16, '0', '9'); 296 | add_flag_to_characters_in_range(BASE_16, 'a', 'f'); 297 | add_flag_to_characters_in_range(BASE_16, 'A', 'F'); 298 | } 299 | 300 | static void add_alphanumeric_flags() 301 | { 302 | add_flag_to_characters_in_range(ALPHANUMERIC, '0', '9'); 303 | add_flag_to_characters_in_range(ALPHANUMERIC, 'a', 'z'); 304 | add_flag_to_characters_in_range(ALPHANUMERIC, 'A', 'Z'); 305 | } 306 | 307 | static void add_symbol_flags() 308 | { 309 | add_flag_to_characters_in_range(SYMBOL, '!', '/'); 310 | add_flag_to_characters_in_range(SYMBOL, ':', '@'); 311 | add_flag_to_characters_in_range(SYMBOL, '[', '`'); 312 | add_flag_to_characters_in_range(SYMBOL, '{', '~'); 313 | } 314 | 315 | static void add_printable_flags() 316 | { 317 | add_flag_to_characters_in_range(PRINTABLE, '!', '~'); 318 | } 319 | 320 | static void add_utf_8_flags() 321 | { 322 | add_flag_to_characters_in_range(UTF_8, 0x80, 0xf7); 323 | add_flag_to_characters_in_range(UTF_8_2_BYTES, 0xc0, 0xdf); 324 | add_flag_to_characters_in_range(UTF_8_3_BYTES, 0xe0, 0xef); 325 | add_flag_to_characters_in_range(UTF_8_4_BYTES, 0xf0, 0xf7); 326 | add_flag_to_characters_in_range(UTF_8_CONTINUATION, 0x80, 0xbf); 327 | } 328 | 329 | static void set_character_names() 330 | { 331 | set_character_names_literal('!', '~'); 332 | set_character_name(0x00, "NUL"); 333 | set_character_name(0x01, "SOH"); 334 | set_character_name(0x02, "STX"); 335 | set_character_name(0x03, "ETX"); 336 | set_character_name(0x04, "EOT"); 337 | set_character_name(0x05, "ENQ"); 338 | set_character_name(0x06, "ACK"); 339 | set_character_name(0x07, "BEL"); 340 | set_character_name(0x08, "BS"); 341 | set_character_name(0x09, "HT"); 342 | set_character_name(0x0a, "LF"); 343 | set_character_name(0x0b, "VT"); 344 | set_character_name(0x0c, "FF"); 345 | set_character_name(0x0d, "CR"); 346 | set_character_name(0x0e, "SO"); 347 | set_character_name(0x0f, "SI"); 348 | set_character_name(0x10, "DLE"); 349 | set_character_name(0x11, "DC1"); 350 | set_character_name(0x12, "DC2"); 351 | set_character_name(0x13, "DC3"); 352 | set_character_name(0x14, "DC4"); 353 | set_character_name(0x15, "NAK"); 354 | set_character_name(0x16, "SYN"); 355 | set_character_name(0x17, "ETB"); 356 | set_character_name(0x18, "CAN"); 357 | set_character_name(0x19, "EM"); 358 | set_character_name(0x1a, "SUB"); 359 | set_character_name(0x1b, "ESC"); 360 | set_character_name(0x1c, "FS"); 361 | set_character_name(0x1d, "GS"); 362 | set_character_name(0x1e, "RS"); 363 | set_character_name(0x1f, "US"); 364 | set_character_name(' ', "Space"); 365 | set_character_name(0x7f, "DEL"); 366 | } 367 | 368 | 369 | 370 | // ------- 371 | // Program 372 | // ------- 373 | 374 | int main(void) 375 | { 376 | set_character_names(); 377 | add_control_flags(); 378 | add_whitespace_flags(); 379 | add_base_2_flags(); 380 | add_base_8_flags(); 381 | add_base_10_flags(); 382 | add_base_16_flags(); 383 | add_number_flags(); 384 | add_alphanumeric_flags(); 385 | add_symbol_flags(); 386 | add_printable_flags(); 387 | add_utf_8_flags(); 388 | 389 | printf( 390 | "/* This file generated by " __FILE__ " */\n" 391 | "\n" 392 | "#ifndef ks_character_flags_H\n" 393 | "#define ks_character_flags_H\n" 394 | "#ifdef __cplusplus\n" 395 | "extern \"C\" {\n" 396 | "#endif\n" 397 | "\n" 398 | "\n" 399 | ); 400 | 401 | print_enum(); 402 | printf("\n"); 403 | print_table(); 404 | 405 | printf( 406 | "\n" 407 | "\n" 408 | "#ifdef __cplusplus\n" 409 | "}\n" 410 | "#endif\n" 411 | "#endif // ks_character_flags_H\n" 412 | ); 413 | } 414 | -------------------------------------------------------------------------------- /libbo/cmake/BOConfig.cmake.in: -------------------------------------------------------------------------------- 1 | get_filename_component(BO_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 2 | include(CMakeFindDependencyMacro) 3 | 4 | list(APPEND CMAKE_MODULE_PATH ${BO_CMAKE_DIR}) 5 | 6 | if(NOT TARGET BO::BO) 7 | include("${BO_CMAKE_DIR}/BOTargets.cmake") 8 | endif() 9 | 10 | set(BO_LIBRARIES BO::BO) 11 | -------------------------------------------------------------------------------- /libbo/include/bo/bo.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Karl Stenerud. All rights reserved. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall remain in place 11 | // in this source code. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | // 21 | 22 | 23 | #ifndef bo_H 24 | #define bo_H 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | 30 | #include 31 | 32 | 33 | /** 34 | * Callback to notify of new output data. 35 | * 36 | * @param user_data The user data object that was passed to the context that generated this call. 37 | * @param data The data. 38 | * @param length The length of the data in bytes. 39 | * @return true if the receiver of this message successfully processed it. 40 | */ 41 | typedef bool (*output_callback)(void* user_data, char* data, int length); 42 | 43 | /** 44 | * Callback to notify that an error occured while processing input data. 45 | * 46 | * @param user_data The user data object that was passed to the context that generated this call. 47 | * @param message The error message. 48 | */ 49 | typedef void (*error_callback)(void* user_data, const char* message); 50 | 51 | typedef enum 52 | { 53 | DATA_SEGMENT_STREAM, // This is one data segment of many. 54 | DATA_SEGMENT_LAST, // This is the last (or only) data segment. 55 | } bo_data_segment_type; 56 | 57 | /** 58 | * Get the bo library version. Bo follows the semantic version format. 59 | * 60 | * @return The format string. 61 | */ 62 | const char* bo_version(); 63 | 64 | /** 65 | * Create a new bo context that calls a callback as it processes data. 66 | * 67 | * @param user_data User-specified contextual data. 68 | * @param on_output Called whenever there's processed output data. 69 | * @param on_error Called if an error occurs while processing. 70 | */ 71 | void* bo_new_context(void* user_data, output_callback on_output, error_callback on_error); 72 | 73 | /** 74 | * Flushes a context's output and destroys the context. 75 | * 76 | * @param context The context object. 77 | * @return True if flushing was successful. Context destruction succeeds regardless of return value. 78 | */ 79 | bool bo_flush_and_destroy_context(void* context); 80 | 81 | /** 82 | * Process a chunk of data, returning output via the output_callback set in the context. 83 | * 84 | * When streaming data (e.g. from a file or socket), use DATA_SEGMENT_STREAM, and then 85 | * DATA_SEGMENT_LAST for the last packet to process. 86 | * 87 | * When not streaming data (i.e. no chance for data to span process calls), use DATA_SEGMENT_LAST. 88 | * 89 | * When the segment type is DATA_SEGMENT_STREAM, bo will defer processing commands or data that 90 | * span data segments. The return value will show where processing stopped, so that you can copy 91 | * the remaining unprocessed data to the beginning of the buffer and fill the remainder from your 92 | * data source. 93 | * 94 | * Remember to call bo_flush_and_destroy_context() when all processing is finished. 95 | * 96 | * @param context A context created by bo_new_context(). 97 | * @param data The data to process. DATA WILL BE MODIFIED DURING PARSE! 98 | * @param data_length The length of the data. 99 | * @param data_segment_type Whether this is the middle or the end of a stream of data. 100 | * @return A pointer to one past the last byte processed. 101 | */ 102 | char* bo_process(void* context, char* data, int data_length, bo_data_segment_type data_segment_type); 103 | 104 | 105 | #ifdef __cplusplus 106 | } 107 | #endif 108 | #endif // bo_H 109 | -------------------------------------------------------------------------------- /libbo/src/bo_buffer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Karl Stenerud. All rights reserved. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall remain in place 11 | // in this source code. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | // 21 | 22 | 23 | #ifndef bo_buffer_H 24 | #define bo_buffer_H 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | 36 | typedef struct 37 | { 38 | uint8_t* start; 39 | uint8_t* end; 40 | uint8_t* pos; 41 | uint8_t* high_water; 42 | } bo_buffer; 43 | 44 | 45 | static inline bo_buffer buffer_alloc(int size, int overhead) 46 | { 47 | uint8_t* memory = malloc(size + overhead); 48 | bo_buffer buffer = 49 | { 50 | .start = memory, 51 | .pos = memory, 52 | .end = memory + size + overhead, 53 | .high_water = memory + size, 54 | }; 55 | return buffer; 56 | } 57 | 58 | static inline void buffer_free(bo_buffer* buffer) 59 | { 60 | free(buffer->start); 61 | buffer->start = buffer->pos = buffer->end = NULL; 62 | } 63 | 64 | static inline bool buffer_is_high_water(bo_buffer* buffer) 65 | { 66 | return buffer->pos >= buffer->high_water; 67 | } 68 | 69 | static inline bool buffer_is_initialized(bo_buffer* buffer) 70 | { 71 | return buffer->start != NULL; 72 | } 73 | 74 | static inline bool buffer_is_empty(bo_buffer* buffer) 75 | { 76 | return buffer->pos == buffer->start; 77 | } 78 | 79 | static inline uint8_t* buffer_get_start(bo_buffer* buffer) 80 | { 81 | return buffer->start; 82 | } 83 | 84 | static inline uint8_t* buffer_get_position(bo_buffer* buffer) 85 | { 86 | return buffer->pos; 87 | } 88 | 89 | static inline uint8_t* buffer_get_end(bo_buffer* buffer) 90 | { 91 | return buffer->end; 92 | } 93 | 94 | static inline int buffer_get_used(bo_buffer* buffer) 95 | { 96 | return buffer->pos - buffer->start; 97 | } 98 | 99 | static inline int buffer_get_remaining(bo_buffer* buffer) 100 | { 101 | return buffer->end - buffer->pos; 102 | } 103 | 104 | static inline void buffer_set_position(bo_buffer* buffer, uint8_t* position) 105 | { 106 | buffer->pos = position; 107 | } 108 | 109 | static inline void buffer_clear(bo_buffer* buffer) 110 | { 111 | buffer->pos = buffer->start; 112 | } 113 | 114 | static inline void buffer_use_space(bo_buffer* buffer, int bytes) 115 | { 116 | buffer->pos += bytes; 117 | } 118 | 119 | static inline void buffer_append_string(bo_buffer* buffer, const char* string) 120 | { 121 | char* position = (char*)buffer_get_position(buffer); 122 | strncpy(position, string, buffer_get_remaining(buffer)); 123 | buffer_use_space(buffer, strlen(position)); 124 | } 125 | 126 | static inline void buffer_append_bytes(bo_buffer* buffer, const uint8_t* bytes, int length) 127 | { 128 | memcpy(buffer_get_position(buffer), bytes, length); 129 | buffer_use_space(buffer, length); 130 | } 131 | 132 | 133 | #ifdef __cplusplus 134 | } 135 | #endif 136 | #endif // bo_buffer_H 137 | -------------------------------------------------------------------------------- /libbo/src/bo_internal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Karl Stenerud. All rights reserved. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall remain in place 11 | // in this source code. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | // 21 | 22 | 23 | #ifndef bo_internal_H 24 | #define bo_internal_H 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | 30 | #define BO_ENABLE_LOGGING 0 31 | 32 | 33 | #include 34 | #include 35 | 36 | #include "bo/bo.h" 37 | #include "bo_buffer.h" 38 | 39 | 40 | #if BO_ENABLE_LOGGING 41 | #include 42 | #define LOG(...) do {fprintf(stdout, "LOG: ");fprintf(stdout, __VA_ARGS__);fprintf(stdout, "\n");fflush(stdout);} while(0) 43 | #else 44 | #define LOG(FMT, ...) 45 | #endif 46 | 47 | 48 | typedef enum 49 | { 50 | TYPE_NONE = 0, 51 | TYPE_BINARY, 52 | TYPE_INT, 53 | TYPE_HEX, 54 | TYPE_OCTAL, 55 | TYPE_BOOLEAN, 56 | TYPE_FLOAT, 57 | TYPE_DECIMAL, 58 | TYPE_STRING, 59 | } bo_data_type; 60 | 61 | typedef enum 62 | { 63 | BO_ENDIAN_NONE = 0, 64 | BO_ENDIAN_LITTLE, 65 | BO_ENDIAN_BIG, 66 | } bo_endianness; 67 | 68 | typedef enum 69 | { 70 | WIDTH_1 = 1, 71 | WIDTH_2 = 2, 72 | WIDTH_4 = 4, 73 | WIDTH_8 = 8, 74 | WIDTH_16 = 16, 75 | } bo_data_width; 76 | 77 | typedef struct 78 | { 79 | bo_buffer src_buffer; 80 | bo_buffer work_buffer; 81 | bo_buffer output_buffer; 82 | struct 83 | { 84 | bo_data_type data_type; 85 | bo_data_width data_width; 86 | bo_endianness endianness; 87 | } input; 88 | struct 89 | { 90 | bo_data_type data_type; 91 | int data_width; 92 | int text_width; 93 | const char* prefix; 94 | const char* suffix; 95 | bo_endianness endianness; 96 | } output; 97 | error_callback on_error; 98 | output_callback on_output; 99 | void* user_data; 100 | 101 | bo_data_segment_type data_segment_type; 102 | bool is_at_end_of_input; 103 | bool is_error_condition; 104 | bool parse_should_continue; 105 | bool is_spanning_string; 106 | } bo_context; 107 | 108 | 109 | void bo_on_bytes(bo_context* context, uint8_t* data, int length); 110 | void bo_on_string(bo_context* context, const uint8_t* string_start, const uint8_t* string_end); 111 | void bo_on_number(bo_context* context, const uint8_t* string_value); 112 | 113 | void bo_on_preset(bo_context* context, const uint8_t* string_value); 114 | void bo_on_prefix(bo_context* context, const uint8_t* prefix); 115 | void bo_on_suffix(bo_context* context, const uint8_t* suffix); 116 | void bo_on_input_type(bo_context* context, bo_data_type data_type, int data_width, bo_endianness endianness); 117 | void bo_on_output_type(bo_context* context, bo_data_type data_type, int data_width, bo_endianness endianness, int print_width); 118 | 119 | void bo_notify_error(bo_context* context, const char* fmt, ...); 120 | 121 | 122 | static inline void stop_parsing(bo_context* context) 123 | { 124 | context->parse_should_continue = false; 125 | } 126 | 127 | static inline bool is_error_condition(bo_context* context) 128 | { 129 | return context->is_error_condition; 130 | } 131 | 132 | #ifdef __cplusplus 133 | } 134 | #endif 135 | #endif // bo_internal_H 136 | -------------------------------------------------------------------------------- /libbo/src/character_flags.h: -------------------------------------------------------------------------------- 1 | /* This file generated by build_character_flags.c */ 2 | 3 | #ifndef ks_character_flags_H 4 | #define ks_character_flags_H 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | 10 | typedef enum 11 | { 12 | CH_FLAG_NONE = 0x00, 13 | CH_FLAG_CONTROL = 0x01, 14 | CH_FLAG_WHITESPACE = 0x02, 15 | CH_FLAG_BASE_8 = 0x04, 16 | CH_FLAG_BASE_10 = 0x08, 17 | CH_FLAG_BASE_16 = 0x10, 18 | CH_FLAG_FP_NUMBER = 0x20, 19 | CH_FLAG_ALPHANUMERIC = 0x40, 20 | CH_FLAG_PRINTABLE = 0x80, 21 | } character_flag; 22 | 23 | static const unsigned char g_character_flags[256] = 24 | { 25 | /* 00: NUL */ (unsigned char)(CH_FLAG_CONTROL), 26 | /* 01: SOH */ (unsigned char)(CH_FLAG_CONTROL), 27 | /* 02: STX */ (unsigned char)(CH_FLAG_CONTROL), 28 | /* 03: ETX */ (unsigned char)(CH_FLAG_CONTROL), 29 | /* 04: EOT */ (unsigned char)(CH_FLAG_CONTROL), 30 | /* 05: ENQ */ (unsigned char)(CH_FLAG_CONTROL), 31 | /* 06: ACK */ (unsigned char)(CH_FLAG_CONTROL), 32 | /* 07: BEL */ (unsigned char)(CH_FLAG_CONTROL), 33 | /* 08: BS */ (unsigned char)(CH_FLAG_CONTROL), 34 | /* 09: HT */ (unsigned char)(CH_FLAG_CONTROL | CH_FLAG_WHITESPACE), 35 | /* 0a: LF */ (unsigned char)(CH_FLAG_CONTROL | CH_FLAG_WHITESPACE), 36 | /* 0b: VT */ (unsigned char)(CH_FLAG_CONTROL | CH_FLAG_WHITESPACE), 37 | /* 0c: FF */ (unsigned char)(CH_FLAG_CONTROL | CH_FLAG_WHITESPACE), 38 | /* 0d: CR */ (unsigned char)(CH_FLAG_CONTROL | CH_FLAG_WHITESPACE), 39 | /* 0e: SO */ (unsigned char)(CH_FLAG_CONTROL), 40 | /* 0f: SI */ (unsigned char)(CH_FLAG_CONTROL), 41 | /* 10: DLE */ (unsigned char)(CH_FLAG_CONTROL), 42 | /* 11: DC1 */ (unsigned char)(CH_FLAG_CONTROL), 43 | /* 12: DC2 */ (unsigned char)(CH_FLAG_CONTROL), 44 | /* 13: DC3 */ (unsigned char)(CH_FLAG_CONTROL), 45 | /* 14: DC4 */ (unsigned char)(CH_FLAG_CONTROL), 46 | /* 15: NAK */ (unsigned char)(CH_FLAG_CONTROL), 47 | /* 16: SYN */ (unsigned char)(CH_FLAG_CONTROL), 48 | /* 17: ETB */ (unsigned char)(CH_FLAG_CONTROL), 49 | /* 18: CAN */ (unsigned char)(CH_FLAG_CONTROL), 50 | /* 19: EM */ (unsigned char)(CH_FLAG_CONTROL), 51 | /* 1a: SUB */ (unsigned char)(CH_FLAG_CONTROL), 52 | /* 1b: ESC */ (unsigned char)(CH_FLAG_CONTROL), 53 | /* 1c: FS */ (unsigned char)(CH_FLAG_CONTROL), 54 | /* 1d: GS */ (unsigned char)(CH_FLAG_CONTROL), 55 | /* 1e: RS */ (unsigned char)(CH_FLAG_CONTROL), 56 | /* 1f: US */ (unsigned char)(CH_FLAG_CONTROL), 57 | /* 20: Space */ (unsigned char)(CH_FLAG_WHITESPACE), 58 | /* 21: ! */ (unsigned char)(CH_FLAG_PRINTABLE), 59 | /* 22: " */ (unsigned char)(CH_FLAG_PRINTABLE), 60 | /* 23: # */ (unsigned char)(CH_FLAG_PRINTABLE), 61 | /* 24: $ */ (unsigned char)(CH_FLAG_PRINTABLE), 62 | /* 25: % */ (unsigned char)(CH_FLAG_PRINTABLE), 63 | /* 26: & */ (unsigned char)(CH_FLAG_PRINTABLE), 64 | /* 27: ' */ (unsigned char)(CH_FLAG_PRINTABLE), 65 | /* 28: ( */ (unsigned char)(CH_FLAG_PRINTABLE), 66 | /* 29: ) */ (unsigned char)(CH_FLAG_PRINTABLE), 67 | /* 2a: * */ (unsigned char)(CH_FLAG_PRINTABLE), 68 | /* 2b: + */ (unsigned char)(CH_FLAG_FP_NUMBER | CH_FLAG_PRINTABLE), 69 | /* 2c: , */ (unsigned char)(CH_FLAG_PRINTABLE), 70 | /* 2d: - */ (unsigned char)(CH_FLAG_FP_NUMBER | CH_FLAG_PRINTABLE), 71 | /* 2e: . */ (unsigned char)(CH_FLAG_FP_NUMBER | CH_FLAG_PRINTABLE), 72 | /* 2f: / */ (unsigned char)(CH_FLAG_PRINTABLE), 73 | /* 30: 0 */ (unsigned char)(CH_FLAG_BASE_8 | CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 74 | /* 31: 1 */ (unsigned char)(CH_FLAG_BASE_8 | CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 75 | /* 32: 2 */ (unsigned char)(CH_FLAG_BASE_8 | CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 76 | /* 33: 3 */ (unsigned char)(CH_FLAG_BASE_8 | CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 77 | /* 34: 4 */ (unsigned char)(CH_FLAG_BASE_8 | CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 78 | /* 35: 5 */ (unsigned char)(CH_FLAG_BASE_8 | CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 79 | /* 36: 6 */ (unsigned char)(CH_FLAG_BASE_8 | CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 80 | /* 37: 7 */ (unsigned char)(CH_FLAG_BASE_8 | CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 81 | /* 38: 8 */ (unsigned char)(CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 82 | /* 39: 9 */ (unsigned char)(CH_FLAG_BASE_10 | CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 83 | /* 3a: : */ (unsigned char)(CH_FLAG_PRINTABLE), 84 | /* 3b: ; */ (unsigned char)(CH_FLAG_PRINTABLE), 85 | /* 3c: < */ (unsigned char)(CH_FLAG_PRINTABLE), 86 | /* 3d: = */ (unsigned char)(CH_FLAG_PRINTABLE), 87 | /* 3e: > */ (unsigned char)(CH_FLAG_PRINTABLE), 88 | /* 3f: ? */ (unsigned char)(CH_FLAG_PRINTABLE), 89 | /* 40: @ */ (unsigned char)(CH_FLAG_PRINTABLE), 90 | /* 41: A */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 91 | /* 42: B */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 92 | /* 43: C */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 93 | /* 44: D */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 94 | /* 45: E */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 95 | /* 46: F */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 96 | /* 47: G */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 97 | /* 48: H */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 98 | /* 49: I */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 99 | /* 4a: J */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 100 | /* 4b: K */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 101 | /* 4c: L */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 102 | /* 4d: M */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 103 | /* 4e: N */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 104 | /* 4f: O */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 105 | /* 50: P */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 106 | /* 51: Q */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 107 | /* 52: R */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 108 | /* 53: S */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 109 | /* 54: T */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 110 | /* 55: U */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 111 | /* 56: V */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 112 | /* 57: W */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 113 | /* 58: X */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 114 | /* 59: Y */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 115 | /* 5a: Z */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 116 | /* 5b: [ */ (unsigned char)(CH_FLAG_PRINTABLE), 117 | /* 5c: \ */ (unsigned char)(CH_FLAG_PRINTABLE), 118 | /* 5d: ] */ (unsigned char)(CH_FLAG_PRINTABLE), 119 | /* 5e: ^ */ (unsigned char)(CH_FLAG_PRINTABLE), 120 | /* 5f: _ */ (unsigned char)(CH_FLAG_PRINTABLE), 121 | /* 60: ` */ (unsigned char)(CH_FLAG_PRINTABLE), 122 | /* 61: a */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 123 | /* 62: b */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 124 | /* 63: c */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 125 | /* 64: d */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 126 | /* 65: e */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_FP_NUMBER | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 127 | /* 66: f */ (unsigned char)(CH_FLAG_BASE_16 | CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 128 | /* 67: g */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 129 | /* 68: h */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 130 | /* 69: i */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 131 | /* 6a: j */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 132 | /* 6b: k */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 133 | /* 6c: l */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 134 | /* 6d: m */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 135 | /* 6e: n */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 136 | /* 6f: o */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 137 | /* 70: p */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 138 | /* 71: q */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 139 | /* 72: r */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 140 | /* 73: s */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 141 | /* 74: t */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 142 | /* 75: u */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 143 | /* 76: v */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 144 | /* 77: w */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 145 | /* 78: x */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 146 | /* 79: y */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 147 | /* 7a: z */ (unsigned char)(CH_FLAG_ALPHANUMERIC | CH_FLAG_PRINTABLE), 148 | /* 7b: { */ (unsigned char)(CH_FLAG_PRINTABLE), 149 | /* 7c: | */ (unsigned char)(CH_FLAG_PRINTABLE), 150 | /* 7d: } */ (unsigned char)(CH_FLAG_PRINTABLE), 151 | /* 7e: ~ */ (unsigned char)(CH_FLAG_PRINTABLE), 152 | /* 7f: DEL */ (unsigned char)(CH_FLAG_CONTROL), 153 | /* 80: */ (unsigned char)(CH_FLAG_NONE), 154 | /* 81: */ (unsigned char)(CH_FLAG_NONE), 155 | /* 82: */ (unsigned char)(CH_FLAG_NONE), 156 | /* 83: */ (unsigned char)(CH_FLAG_NONE), 157 | /* 84: */ (unsigned char)(CH_FLAG_NONE), 158 | /* 85: */ (unsigned char)(CH_FLAG_NONE), 159 | /* 86: */ (unsigned char)(CH_FLAG_NONE), 160 | /* 87: */ (unsigned char)(CH_FLAG_NONE), 161 | /* 88: */ (unsigned char)(CH_FLAG_NONE), 162 | /* 89: */ (unsigned char)(CH_FLAG_NONE), 163 | /* 8a: */ (unsigned char)(CH_FLAG_NONE), 164 | /* 8b: */ (unsigned char)(CH_FLAG_NONE), 165 | /* 8c: */ (unsigned char)(CH_FLAG_NONE), 166 | /* 8d: */ (unsigned char)(CH_FLAG_NONE), 167 | /* 8e: */ (unsigned char)(CH_FLAG_NONE), 168 | /* 8f: */ (unsigned char)(CH_FLAG_NONE), 169 | /* 90: */ (unsigned char)(CH_FLAG_NONE), 170 | /* 91: */ (unsigned char)(CH_FLAG_NONE), 171 | /* 92: */ (unsigned char)(CH_FLAG_NONE), 172 | /* 93: */ (unsigned char)(CH_FLAG_NONE), 173 | /* 94: */ (unsigned char)(CH_FLAG_NONE), 174 | /* 95: */ (unsigned char)(CH_FLAG_NONE), 175 | /* 96: */ (unsigned char)(CH_FLAG_NONE), 176 | /* 97: */ (unsigned char)(CH_FLAG_NONE), 177 | /* 98: */ (unsigned char)(CH_FLAG_NONE), 178 | /* 99: */ (unsigned char)(CH_FLAG_NONE), 179 | /* 9a: */ (unsigned char)(CH_FLAG_NONE), 180 | /* 9b: */ (unsigned char)(CH_FLAG_NONE), 181 | /* 9c: */ (unsigned char)(CH_FLAG_NONE), 182 | /* 9d: */ (unsigned char)(CH_FLAG_NONE), 183 | /* 9e: */ (unsigned char)(CH_FLAG_NONE), 184 | /* 9f: */ (unsigned char)(CH_FLAG_NONE), 185 | /* a0: */ (unsigned char)(CH_FLAG_NONE), 186 | /* a1: */ (unsigned char)(CH_FLAG_NONE), 187 | /* a2: */ (unsigned char)(CH_FLAG_NONE), 188 | /* a3: */ (unsigned char)(CH_FLAG_NONE), 189 | /* a4: */ (unsigned char)(CH_FLAG_NONE), 190 | /* a5: */ (unsigned char)(CH_FLAG_NONE), 191 | /* a6: */ (unsigned char)(CH_FLAG_NONE), 192 | /* a7: */ (unsigned char)(CH_FLAG_NONE), 193 | /* a8: */ (unsigned char)(CH_FLAG_NONE), 194 | /* a9: */ (unsigned char)(CH_FLAG_NONE), 195 | /* aa: */ (unsigned char)(CH_FLAG_NONE), 196 | /* ab: */ (unsigned char)(CH_FLAG_NONE), 197 | /* ac: */ (unsigned char)(CH_FLAG_NONE), 198 | /* ad: */ (unsigned char)(CH_FLAG_NONE), 199 | /* ae: */ (unsigned char)(CH_FLAG_NONE), 200 | /* af: */ (unsigned char)(CH_FLAG_NONE), 201 | /* b0: */ (unsigned char)(CH_FLAG_NONE), 202 | /* b1: */ (unsigned char)(CH_FLAG_NONE), 203 | /* b2: */ (unsigned char)(CH_FLAG_NONE), 204 | /* b3: */ (unsigned char)(CH_FLAG_NONE), 205 | /* b4: */ (unsigned char)(CH_FLAG_NONE), 206 | /* b5: */ (unsigned char)(CH_FLAG_NONE), 207 | /* b6: */ (unsigned char)(CH_FLAG_NONE), 208 | /* b7: */ (unsigned char)(CH_FLAG_NONE), 209 | /* b8: */ (unsigned char)(CH_FLAG_NONE), 210 | /* b9: */ (unsigned char)(CH_FLAG_NONE), 211 | /* ba: */ (unsigned char)(CH_FLAG_NONE), 212 | /* bb: */ (unsigned char)(CH_FLAG_NONE), 213 | /* bc: */ (unsigned char)(CH_FLAG_NONE), 214 | /* bd: */ (unsigned char)(CH_FLAG_NONE), 215 | /* be: */ (unsigned char)(CH_FLAG_NONE), 216 | /* bf: */ (unsigned char)(CH_FLAG_NONE), 217 | /* c0: */ (unsigned char)(CH_FLAG_NONE), 218 | /* c1: */ (unsigned char)(CH_FLAG_NONE), 219 | /* c2: */ (unsigned char)(CH_FLAG_NONE), 220 | /* c3: */ (unsigned char)(CH_FLAG_NONE), 221 | /* c4: */ (unsigned char)(CH_FLAG_NONE), 222 | /* c5: */ (unsigned char)(CH_FLAG_NONE), 223 | /* c6: */ (unsigned char)(CH_FLAG_NONE), 224 | /* c7: */ (unsigned char)(CH_FLAG_NONE), 225 | /* c8: */ (unsigned char)(CH_FLAG_NONE), 226 | /* c9: */ (unsigned char)(CH_FLAG_NONE), 227 | /* ca: */ (unsigned char)(CH_FLAG_NONE), 228 | /* cb: */ (unsigned char)(CH_FLAG_NONE), 229 | /* cc: */ (unsigned char)(CH_FLAG_NONE), 230 | /* cd: */ (unsigned char)(CH_FLAG_NONE), 231 | /* ce: */ (unsigned char)(CH_FLAG_NONE), 232 | /* cf: */ (unsigned char)(CH_FLAG_NONE), 233 | /* d0: */ (unsigned char)(CH_FLAG_NONE), 234 | /* d1: */ (unsigned char)(CH_FLAG_NONE), 235 | /* d2: */ (unsigned char)(CH_FLAG_NONE), 236 | /* d3: */ (unsigned char)(CH_FLAG_NONE), 237 | /* d4: */ (unsigned char)(CH_FLAG_NONE), 238 | /* d5: */ (unsigned char)(CH_FLAG_NONE), 239 | /* d6: */ (unsigned char)(CH_FLAG_NONE), 240 | /* d7: */ (unsigned char)(CH_FLAG_NONE), 241 | /* d8: */ (unsigned char)(CH_FLAG_NONE), 242 | /* d9: */ (unsigned char)(CH_FLAG_NONE), 243 | /* da: */ (unsigned char)(CH_FLAG_NONE), 244 | /* db: */ (unsigned char)(CH_FLAG_NONE), 245 | /* dc: */ (unsigned char)(CH_FLAG_NONE), 246 | /* dd: */ (unsigned char)(CH_FLAG_NONE), 247 | /* de: */ (unsigned char)(CH_FLAG_NONE), 248 | /* df: */ (unsigned char)(CH_FLAG_NONE), 249 | /* e0: */ (unsigned char)(CH_FLAG_NONE), 250 | /* e1: */ (unsigned char)(CH_FLAG_NONE), 251 | /* e2: */ (unsigned char)(CH_FLAG_NONE), 252 | /* e3: */ (unsigned char)(CH_FLAG_NONE), 253 | /* e4: */ (unsigned char)(CH_FLAG_NONE), 254 | /* e5: */ (unsigned char)(CH_FLAG_NONE), 255 | /* e6: */ (unsigned char)(CH_FLAG_NONE), 256 | /* e7: */ (unsigned char)(CH_FLAG_NONE), 257 | /* e8: */ (unsigned char)(CH_FLAG_NONE), 258 | /* e9: */ (unsigned char)(CH_FLAG_NONE), 259 | /* ea: */ (unsigned char)(CH_FLAG_NONE), 260 | /* eb: */ (unsigned char)(CH_FLAG_NONE), 261 | /* ec: */ (unsigned char)(CH_FLAG_NONE), 262 | /* ed: */ (unsigned char)(CH_FLAG_NONE), 263 | /* ee: */ (unsigned char)(CH_FLAG_NONE), 264 | /* ef: */ (unsigned char)(CH_FLAG_NONE), 265 | /* f0: */ (unsigned char)(CH_FLAG_NONE), 266 | /* f1: */ (unsigned char)(CH_FLAG_NONE), 267 | /* f2: */ (unsigned char)(CH_FLAG_NONE), 268 | /* f3: */ (unsigned char)(CH_FLAG_NONE), 269 | /* f4: */ (unsigned char)(CH_FLAG_NONE), 270 | /* f5: */ (unsigned char)(CH_FLAG_NONE), 271 | /* f6: */ (unsigned char)(CH_FLAG_NONE), 272 | /* f7: */ (unsigned char)(CH_FLAG_NONE), 273 | /* f8: */ (unsigned char)(CH_FLAG_NONE), 274 | /* f9: */ (unsigned char)(CH_FLAG_NONE), 275 | /* fa: */ (unsigned char)(CH_FLAG_NONE), 276 | /* fb: */ (unsigned char)(CH_FLAG_NONE), 277 | /* fc: */ (unsigned char)(CH_FLAG_NONE), 278 | /* fd: */ (unsigned char)(CH_FLAG_NONE), 279 | /* fe: */ (unsigned char)(CH_FLAG_NONE), 280 | /* ff: */ (unsigned char)(CH_FLAG_NONE), 281 | }; 282 | 283 | 284 | #ifdef __cplusplus 285 | } 286 | #endif 287 | #endif // ks_character_flags_H 288 | -------------------------------------------------------------------------------- /libbo/src/library.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Karl Stenerud. All rights reserved. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall remain in place 11 | // in this source code. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | // 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "bo_internal.h" 30 | #include "library_version.h" 31 | 32 | 33 | // ------------- 34 | // Configuration 35 | // ------------- 36 | 37 | // The work buffer holds binary data that will be converted to whatever output format. 38 | // For best results, keep this a multiple of 16. 39 | #define WORK_BUFFER_SIZE 1600 40 | 41 | // An overhead size of 32 ensures that for object sizes up to 128 bits, 42 | // there's always room for 128 bits of zero filling at the end. 43 | #define WORK_BUFFER_OVERHEAD_SIZE 32 44 | 45 | // Use an overhead value large enough that the string printers won't blast past it in a single write. 46 | #define OUTPUT_BUFFER_OVERHEAD_SIZE 200 47 | 48 | 49 | #define BO_NATIVE_INT_ENDIANNESS (((__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) * BO_ENDIAN_LITTLE) + \ 50 | ((__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) * BO_ENDIAN_BIG)) 51 | #define BO_NATIVE_FLOAT_ENDIANNESS (((__FLOAT_WORD_ORDER__ == __ORDER_LITTLE_ENDIAN__) * BO_ENDIAN_LITTLE) + \ 52 | ((__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__) * BO_ENDIAN_BIG)) 53 | 54 | 55 | // --------------- 56 | // Error Reporting 57 | // --------------- 58 | 59 | static void mark_error_condition(bo_context* context) 60 | { 61 | context->is_error_condition = true; 62 | stop_parsing(context); 63 | } 64 | 65 | void bo_notify_error(bo_context* context, const char* fmt, ...) 66 | { 67 | char buffer[500]; 68 | 69 | va_list args; 70 | va_start(args, fmt); 71 | vsnprintf(buffer, sizeof(buffer), fmt, args); 72 | buffer[sizeof(buffer)-1] = 0; 73 | va_end(args); 74 | 75 | mark_error_condition(context); 76 | context->on_error(context->user_data, buffer); 77 | } 78 | 79 | 80 | 81 | // --------------- 82 | // String Printers 83 | // --------------- 84 | 85 | static inline void copy_swapped(uint8_t* dst, const uint8_t* src, int length) 86 | { 87 | for(int i = 0; i < length; i++) 88 | { 89 | dst[i] = src[length - i - 1]; 90 | } 91 | } 92 | 93 | /** 94 | * String printer. 95 | * Reads data from the source and writes to the destination. 96 | * 97 | * @param src Pointer to the source data. 98 | * @param dst Pointer to the destination buffer. 99 | * @param output_width in: Minimum bytes to print. out: Number of bytes written. 100 | * @return Number of bytes read. 101 | */ 102 | typedef int (*string_printer)(uint8_t* src, uint8_t* dst, int* output_width); 103 | 104 | // Force the compiler to generate handler code for unaligned accesses. 105 | #define DEFINE_SAFE_STRUCT(NAME, TYPE) typedef struct __attribute__((__packed__)) {TYPE contents;} NAME 106 | DEFINE_SAFE_STRUCT(safe_uint_1, uint8_t); 107 | DEFINE_SAFE_STRUCT(safe_uint_2, uint16_t); 108 | DEFINE_SAFE_STRUCT(safe_uint_4, uint32_t); 109 | DEFINE_SAFE_STRUCT(safe_uint_8, uint64_t); 110 | DEFINE_SAFE_STRUCT(safe_int_1, int8_t); 111 | DEFINE_SAFE_STRUCT(safe_int_2, int16_t); 112 | DEFINE_SAFE_STRUCT(safe_int_4, int32_t); 113 | DEFINE_SAFE_STRUCT(safe_int_8, int64_t); 114 | DEFINE_SAFE_STRUCT(safe_int_16, __int128); 115 | DEFINE_SAFE_STRUCT(safe_float_4, float); 116 | DEFINE_SAFE_STRUCT(safe_float_8, double); 117 | DEFINE_SAFE_STRUCT(safe_float_16, __float128); 118 | DEFINE_SAFE_STRUCT(safe_decimal_8, _Decimal64); 119 | DEFINE_SAFE_STRUCT(safe_decimal_16, _Decimal128); 120 | 121 | #define DEFINE_INT_STRING_PRINTER(NAMED_TYPE, REAL_TYPE, DATA_WIDTH, FORMAT) \ 122 | static int string_print_ ## NAMED_TYPE ## _ ## DATA_WIDTH (uint8_t* src, uint8_t* dst, int* output_width) \ 123 | { \ 124 | *output_width = sprintf((char*)dst, "%0*" #FORMAT, *output_width, ((safe_ ## REAL_TYPE ## _ ## DATA_WIDTH *)src)->contents); \ 125 | return DATA_WIDTH; \ 126 | } \ 127 | 128 | #define DEFINE_INT_STRING_PRINTER_SWAPPED(NAMED_TYPE, REAL_TYPE, DATA_WIDTH, FORMAT) \ 129 | DEFINE_INT_STRING_PRINTER(NAMED_TYPE, REAL_TYPE, DATA_WIDTH, FORMAT) \ 130 | static int string_print_ ## NAMED_TYPE ## _ ## DATA_WIDTH ## _swapped (uint8_t* src, uint8_t* dst, int* output_width) \ 131 | { \ 132 | uint8_t buffer[DATA_WIDTH]; \ 133 | copy_swapped(buffer, src, DATA_WIDTH); \ 134 | return string_print_ ## NAMED_TYPE ## _ ## DATA_WIDTH (buffer, dst, output_width); \ 135 | } 136 | DEFINE_INT_STRING_PRINTER(int, int, 1, d) 137 | DEFINE_INT_STRING_PRINTER_SWAPPED(int, int, 2, d) 138 | DEFINE_INT_STRING_PRINTER_SWAPPED(int, int, 4, d) 139 | DEFINE_INT_STRING_PRINTER_SWAPPED(int, int, 8, ld) 140 | // TODO: int-16 141 | DEFINE_INT_STRING_PRINTER(hex, uint, 1, x) 142 | DEFINE_INT_STRING_PRINTER_SWAPPED(hex, uint, 2, x) 143 | DEFINE_INT_STRING_PRINTER_SWAPPED(hex, uint, 4, x) 144 | DEFINE_INT_STRING_PRINTER_SWAPPED(hex, uint, 8, lx) 145 | // TODO: hex-16 146 | DEFINE_INT_STRING_PRINTER(octal, uint, 1, o) 147 | DEFINE_INT_STRING_PRINTER_SWAPPED(octal, uint, 2, o) 148 | DEFINE_INT_STRING_PRINTER_SWAPPED(octal, uint, 4, o) 149 | DEFINE_INT_STRING_PRINTER_SWAPPED(octal, uint, 8, lo) 150 | // TODO: octal-16 151 | 152 | static int print_boolean_be(uint8_t* src, uint8_t* dst, int data_width, int text_width) 153 | { 154 | int bit_width = data_width * 8; 155 | int total_width = (bit_width > text_width ? bit_width : text_width); 156 | int diff_width = total_width - bit_width; 157 | memset(dst, '0', bit_width); 158 | 159 | uint8_t* src_ptr = src; 160 | char* dst_ptr = (char*)dst + diff_width; 161 | 162 | for(int bits_remaining = bit_width;bits_remaining > 0;) 163 | { 164 | int value = *src_ptr; 165 | for(int i = 0; i < 8 && bits_remaining > 0; i++) 166 | { 167 | *dst_ptr++ = '0' + ((value >> (7-i)) & 1); 168 | bits_remaining--; 169 | } 170 | src_ptr++; 171 | } 172 | return total_width; 173 | } 174 | 175 | static int print_boolean_le(uint8_t* src, uint8_t* dst, int data_width, int text_width) 176 | { 177 | int bit_width = data_width * 8; 178 | int total_width = (bit_width > text_width ? bit_width : text_width); 179 | int diff_width = total_width - bit_width; 180 | memset(dst, '0', bit_width); 181 | 182 | uint8_t* src_ptr = src; 183 | char* dst_ptr = (char*)dst + diff_width; 184 | 185 | for(int bits_remaining = bit_width;bits_remaining > 0;) 186 | { 187 | int value = *src_ptr; 188 | for(int i = 0; i < 8 && bits_remaining > 0; i++) 189 | { 190 | *dst_ptr++ = '0' + ((value >> i) & 1); 191 | bits_remaining--; 192 | } 193 | src_ptr++; 194 | } 195 | return total_width; 196 | } 197 | #define DEFINE_BOOLEAN_STRING_PRINTER(DATA_WIDTH) \ 198 | static int string_print_boolean_ ## DATA_WIDTH ## _be (uint8_t* src, uint8_t* dst, int* output_width) \ 199 | { \ 200 | *output_width = print_boolean_be(src, dst, DATA_WIDTH, *output_width); \ 201 | return DATA_WIDTH; \ 202 | } \ 203 | static int string_print_boolean_ ## DATA_WIDTH ## _le (uint8_t* src, uint8_t* dst, int* output_width) \ 204 | { \ 205 | *output_width = print_boolean_le(src, dst, DATA_WIDTH, *output_width); \ 206 | return DATA_WIDTH; \ 207 | } 208 | DEFINE_BOOLEAN_STRING_PRINTER(1) 209 | DEFINE_BOOLEAN_STRING_PRINTER(2) 210 | DEFINE_BOOLEAN_STRING_PRINTER(4) 211 | DEFINE_BOOLEAN_STRING_PRINTER(8) 212 | DEFINE_BOOLEAN_STRING_PRINTER(16) 213 | 214 | #define DEFINE_FLOAT_STRING_PRINTER(NAMED_TYPE, REAL_TYPE, DATA_WIDTH, FORMAT) \ 215 | static int string_print_ ## NAMED_TYPE ## _ ## DATA_WIDTH (uint8_t* src, uint8_t* dst, int* output_width) \ 216 | { \ 217 | *output_width = sprintf((char*)dst, "%.*" #FORMAT, *output_width, (double)((safe_ ## REAL_TYPE ## _ ## DATA_WIDTH *)src)->contents); \ 218 | return DATA_WIDTH; \ 219 | } \ 220 | static int string_print_ ## NAMED_TYPE ## _ ## DATA_WIDTH ## _swapped (uint8_t* src, uint8_t* dst, int* output_width) \ 221 | { \ 222 | uint8_t buffer[DATA_WIDTH]; \ 223 | copy_swapped(buffer, src, DATA_WIDTH); \ 224 | return string_print_ ## NAMED_TYPE ## _ ## DATA_WIDTH (buffer, dst, output_width); \ 225 | } 226 | // TODO: float-2 227 | DEFINE_FLOAT_STRING_PRINTER(float, float, 4, f) 228 | DEFINE_FLOAT_STRING_PRINTER(float, float, 8, f) 229 | // TODO: float-16 230 | // TODO: decimal-8 231 | // TODO: decimal-16 232 | 233 | #define DEFINE_BINARY_PRINTER(DATA_WIDTH) \ 234 | static int binary_print_ ## DATA_WIDTH (uint8_t* src, uint8_t* dst, int* output_width) \ 235 | { \ 236 | memcpy(dst, src, DATA_WIDTH); \ 237 | *output_width = DATA_WIDTH; \ 238 | return DATA_WIDTH; \ 239 | } \ 240 | static int binary_print_ ## DATA_WIDTH ## _swapped(uint8_t* src, uint8_t* dst, int* output_width) \ 241 | { \ 242 | copy_swapped(dst, src, DATA_WIDTH); \ 243 | *output_width = DATA_WIDTH; \ 244 | return DATA_WIDTH; \ 245 | } 246 | DEFINE_BINARY_PRINTER(2) 247 | DEFINE_BINARY_PRINTER(4) 248 | DEFINE_BINARY_PRINTER(8) 249 | DEFINE_BINARY_PRINTER(16) 250 | static int binary_print_1(uint8_t* src, uint8_t* dst, int* output_width) 251 | { 252 | *dst = *src; 253 | *output_width = 1; 254 | return 1; 255 | } 256 | 257 | 258 | static const char g_hex_values[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 259 | 260 | static int get_utf8_length(uint8_t ch) 261 | { 262 | if((ch >> 5) == 0x06) return 2; 263 | if((ch >> 4) == 0x0e) return 3; 264 | if((ch >> 3) == 0x1e) return 4; 265 | return 0; 266 | } 267 | 268 | static int string_print_string(uint8_t* src, uint8_t* dst, int* output_width) 269 | { 270 | #define CASE_ESCAPE(VALUE, ESCAPED) \ 271 | case VALUE: \ 272 | dst[0] = '\\'; \ 273 | dst[1] = ESCAPED; \ 274 | *output_width = 2; \ 275 | return 1 276 | 277 | uint8_t ch = *src; 278 | switch(ch) 279 | { 280 | CASE_ESCAPE(0x07, 'a'); 281 | CASE_ESCAPE(0x08, 'b'); 282 | CASE_ESCAPE(0x09, 't'); 283 | CASE_ESCAPE(0x0a, 'n'); 284 | CASE_ESCAPE(0x0b, 'v'); 285 | CASE_ESCAPE(0x0c, 'f'); 286 | CASE_ESCAPE(0x0d, 'r'); 287 | CASE_ESCAPE('\\', '\\'); 288 | CASE_ESCAPE('\"', '\"'); 289 | CASE_ESCAPE('?', '?'); 290 | default: 291 | { 292 | if(ch >= 0x20 && ch < 0x7f) 293 | { 294 | *dst = *src; 295 | *output_width = 1; 296 | return 1; 297 | } 298 | int utf8_length = get_utf8_length(ch); 299 | if(utf8_length > 0) 300 | { 301 | for(int i = 0; i < utf8_length; i++) 302 | { 303 | *dst++ = *src++; 304 | } 305 | *output_width = utf8_length; 306 | return utf8_length; 307 | } 308 | *dst++ = '\\'; 309 | *dst++ = 'x'; 310 | if(ch > 15) 311 | { 312 | *dst++ = g_hex_values[ch>>4]; 313 | *output_width = 4; 314 | } 315 | else 316 | { 317 | *output_width = 3; 318 | } 319 | *dst++ = g_hex_values[ch&15]; 320 | return 1; 321 | } 322 | } 323 | } 324 | 325 | bool matches_endianness(bo_context* context) 326 | { 327 | return context->output.endianness == BO_NATIVE_INT_ENDIANNESS; 328 | } 329 | 330 | bool matches_float_endianness(bo_context* context) 331 | { 332 | return context->output.endianness == BO_NATIVE_FLOAT_ENDIANNESS; 333 | } 334 | 335 | bool is_output_bigendian(bo_context* context) 336 | { 337 | return context->output.endianness == BO_ENDIAN_BIG; 338 | } 339 | 340 | static string_printer get_string_printer(bo_context* context) 341 | { 342 | switch(context->output.data_type) 343 | { 344 | case TYPE_INT: 345 | { 346 | switch(context->output.data_width) 347 | { 348 | case 1: return string_print_int_1; 349 | case 2: return matches_endianness(context) ? string_print_int_2 : string_print_int_2_swapped; 350 | case 4: return matches_endianness(context) ? string_print_int_4 : string_print_int_4_swapped; 351 | case 8: return matches_endianness(context) ? string_print_int_8 : string_print_int_8_swapped; 352 | case 16: 353 | bo_notify_error(context, "TODO: INT 16 not implemented"); 354 | return NULL; 355 | default: 356 | bo_notify_error(context, "%d: invalid data width", context->output.data_width); 357 | return NULL; 358 | } 359 | } 360 | case TYPE_HEX: 361 | { 362 | switch(context->output.data_width) 363 | { 364 | case 1: return string_print_hex_1; 365 | case 2: return matches_endianness(context) ? string_print_hex_2 : string_print_hex_2_swapped; 366 | case 4: return matches_endianness(context) ? string_print_hex_4 : string_print_hex_4_swapped; 367 | case 8: return matches_endianness(context) ? string_print_hex_8 : string_print_hex_8_swapped; 368 | case 16: 369 | bo_notify_error(context, "TODO: HEX 16 not implemented"); 370 | return NULL; 371 | default: 372 | bo_notify_error(context, "%d: invalid data width", context->output.data_width); 373 | return NULL; 374 | } 375 | } 376 | case TYPE_OCTAL: 377 | { 378 | switch(context->output.data_width) 379 | { 380 | case 1: return string_print_octal_1; 381 | case 2: return matches_endianness(context) ? string_print_octal_2 : string_print_octal_2_swapped; 382 | case 4: return matches_endianness(context) ? string_print_octal_4 : string_print_octal_4_swapped; 383 | case 8: return matches_endianness(context) ? string_print_octal_8 : string_print_octal_8_swapped; 384 | case 16: 385 | bo_notify_error(context, "TODO: OCTAL 16 not implemented"); 386 | return NULL; 387 | default: 388 | bo_notify_error(context, "%d: invalid data width", context->output.data_width); 389 | return NULL; 390 | } 391 | } 392 | case TYPE_BOOLEAN: 393 | { 394 | switch(context->output.data_width) 395 | { 396 | case 1: return is_output_bigendian(context) ? string_print_boolean_1_be : string_print_boolean_1_le; 397 | case 2: return is_output_bigendian(context) ? string_print_boolean_2_be : string_print_boolean_2_le; 398 | case 4: return is_output_bigendian(context) ? string_print_boolean_4_be : string_print_boolean_4_le; 399 | case 8: return is_output_bigendian(context) ? string_print_boolean_8_be : string_print_boolean_8_le; 400 | case 16: return is_output_bigendian(context) ? string_print_boolean_16_be : string_print_boolean_16_le; 401 | default: 402 | bo_notify_error(context, "%d: invalid data width", context->output.data_width); 403 | return NULL; 404 | } 405 | } 406 | case TYPE_FLOAT: 407 | { 408 | switch(context->output.data_width) 409 | { 410 | case 2: 411 | bo_notify_error(context, "TODO: FLOAT 2 not implemented"); 412 | return NULL; 413 | case 4: return matches_float_endianness(context) ? string_print_float_4 : string_print_float_4_swapped; 414 | case 8: return matches_float_endianness(context) ? string_print_float_8 : string_print_float_8_swapped; 415 | case 16: 416 | bo_notify_error(context, "TODO: FLOAT 16 not implemented"); 417 | return NULL; 418 | default: 419 | bo_notify_error(context, "%d: invalid data width", context->output.data_width); 420 | return NULL; 421 | } 422 | } 423 | case TYPE_DECIMAL: 424 | bo_notify_error(context, "TODO: DECIMAL not implemented"); 425 | return NULL; 426 | case TYPE_BINARY: 427 | switch(context->output.data_width) 428 | { 429 | case 1: return binary_print_1; 430 | case 2: return matches_endianness(context) ? binary_print_2 : binary_print_2_swapped; 431 | case 4: return matches_endianness(context) ? binary_print_4 : binary_print_4_swapped; 432 | case 8: return matches_endianness(context) ? binary_print_8 : binary_print_8_swapped; 433 | case 16: return matches_endianness(context) ? binary_print_16 : binary_print_16_swapped; 434 | default: 435 | bo_notify_error(context, "%d: invalid data width", context->output.data_width); 436 | return NULL; 437 | } 438 | case TYPE_STRING: 439 | return string_print_string; 440 | case TYPE_NONE: 441 | bo_notify_error(context, "Must set output data type before passing data"); 442 | return NULL; 443 | default: 444 | bo_notify_error(context, "%d: Unknown data type", context->output.data_type); 445 | return NULL; 446 | } 447 | } 448 | 449 | 450 | 451 | // -------- 452 | // Internal 453 | // -------- 454 | 455 | static inline bool is_nonempty_string(const char* const string) 456 | { 457 | return string != NULL && *string != 0; 458 | } 459 | 460 | static inline bool check_can_input_numbers(bo_context* context, const uint8_t* string_value) 461 | { 462 | if(context->input.data_type == TYPE_NONE || context->input.data_type == TYPE_STRING) 463 | { 464 | bo_notify_error(context, "%s: Must set input type to numeric before adding numbers", (const char*)string_value); 465 | return false; 466 | } 467 | return true; 468 | } 469 | 470 | static int trim_length_to_object_boundary(int length, int object_size_in_bytes) 471 | { 472 | return length - (length % object_size_in_bytes); 473 | } 474 | 475 | static void clear_error_condition(bo_context* context) 476 | { 477 | context->is_error_condition = false; 478 | } 479 | 480 | 481 | 482 | // --------------- 483 | // Buffer Flushing 484 | // --------------- 485 | 486 | static void flush_buffer_to_output(bo_context* context, bo_buffer* buffer) 487 | { 488 | if(!context->on_output(context->user_data, (char*)buffer_get_start(buffer), buffer_get_used(buffer))) 489 | { 490 | mark_error_condition(context); 491 | } 492 | buffer_clear(buffer); 493 | } 494 | 495 | static void flush_output_buffer(bo_context* context) 496 | { 497 | LOG("Flush output buffer"); 498 | flush_buffer_to_output(context, &context->output_buffer); 499 | } 500 | 501 | static void flush_work_buffer_binary(bo_context* context) 502 | { 503 | flush_buffer_to_output(context, &context->work_buffer); 504 | } 505 | 506 | static void flush_work_buffer(bo_context* context, bool is_complete_flush) 507 | { 508 | LOG("Flush work buffer"); 509 | if(!buffer_is_initialized(&context->work_buffer) || buffer_is_empty(&context->work_buffer)) 510 | { 511 | return; 512 | } 513 | 514 | if(context->output.data_type == TYPE_BINARY) 515 | { 516 | flush_work_buffer_binary(context); 517 | return; 518 | } 519 | 520 | string_printer string_print = get_string_printer(context); 521 | if(is_error_condition(context)) 522 | { 523 | return; 524 | } 525 | 526 | bo_buffer* work_buffer = &context->work_buffer; 527 | bo_buffer* output_buffer = &context->output_buffer; 528 | 529 | int bytes_per_entry = context->output.data_width; 530 | int work_length = buffer_get_used(work_buffer); 531 | if(is_complete_flush) 532 | { 533 | // Ensure that the partial read at the end gets zeroes instead of random garbage. 534 | // WORK_BUFFER_OVERHEAD_SIZE makes sure this call doesn't run off the end of the buffer. 535 | memset(buffer_get_position(work_buffer), 0, 16); 536 | } 537 | else 538 | { 539 | work_length = trim_length_to_object_boundary(work_length, bytes_per_entry); 540 | } 541 | 542 | const bool has_prefix = is_nonempty_string(context->output.prefix); 543 | const bool has_suffix = is_nonempty_string(context->output.suffix); 544 | uint8_t* const start = buffer_get_start(work_buffer); 545 | uint8_t* const end = start + work_length; 546 | 547 | for(uint8_t* src = start; src < end;) 548 | { 549 | if(has_prefix) 550 | { 551 | buffer_append_string(output_buffer, context->output.prefix); 552 | } 553 | 554 | int output_width = context->output.text_width; 555 | int bytes_read = string_print(src, buffer_get_position(output_buffer), &output_width); 556 | if(output_width < 0) 557 | { 558 | bo_notify_error(context, "Error writing data"); 559 | return; 560 | } 561 | buffer_use_space(output_buffer, output_width); 562 | 563 | if(has_suffix) 564 | { 565 | bool is_last_entry = src + bytes_per_entry >= end; 566 | if(!is_last_entry) 567 | { 568 | buffer_append_string(output_buffer, context->output.suffix); 569 | } 570 | } 571 | 572 | if(buffer_is_high_water(output_buffer)) 573 | { 574 | flush_output_buffer(context); 575 | if(is_error_condition(context)) 576 | { 577 | return; 578 | } 579 | } 580 | src += bytes_read; 581 | } 582 | buffer_clear(work_buffer); 583 | } 584 | 585 | 586 | 587 | // ------------------ 588 | // Binary Data Adders 589 | // ------------------ 590 | 591 | static void add_bytes(bo_context* context, const uint8_t* ptr, int length) 592 | { 593 | if(buffer_is_high_water(&context->work_buffer)) 594 | { 595 | flush_work_buffer(context, false); 596 | } 597 | 598 | do 599 | { 600 | int copy_length = buffer_get_remaining(&context->work_buffer); 601 | if(copy_length > length) 602 | { 603 | copy_length = length; 604 | } 605 | buffer_append_bytes(&context->work_buffer, ptr, copy_length); 606 | if(buffer_is_high_water(&context->work_buffer)) 607 | { 608 | flush_work_buffer(context, false); 609 | } 610 | if(is_error_condition(context)) 611 | { 612 | return; 613 | } 614 | length -= copy_length; 615 | ptr += copy_length; 616 | } while(length > 0); 617 | } 618 | 619 | static void add_bytes_swapped(bo_context* context, const uint8_t* ptr, int length, const int width) 620 | { 621 | if(buffer_is_high_water(&context->work_buffer)) 622 | { 623 | flush_work_buffer(context, false); 624 | } 625 | 626 | const int remainder = length % width; 627 | const uint8_t* early_end = ptr + length - remainder; 628 | while(ptr < early_end) 629 | { 630 | copy_swapped(context->work_buffer.pos, ptr, width); 631 | buffer_use_space(&context->work_buffer, width); 632 | if(buffer_is_high_water(&context->work_buffer)) 633 | { 634 | flush_work_buffer(context, false); 635 | } 636 | if(is_error_condition(context)) 637 | { 638 | return; 639 | } 640 | ptr += width; 641 | } 642 | 643 | if(remainder > 0) 644 | { 645 | uint8_t bytes[width]; 646 | memset(bytes, 0, sizeof(bytes)); 647 | memcpy(bytes, ptr, remainder); 648 | copy_swapped(context->work_buffer.pos, bytes, width); 649 | buffer_use_space(&context->work_buffer, width); 650 | } 651 | } 652 | 653 | static void add_int(bo_context* context, uint64_t src_value) 654 | { 655 | switch(context->input.data_width) 656 | { 657 | case WIDTH_1: 658 | { 659 | uint8_t value = (uint8_t)src_value; 660 | add_bytes(context, (uint8_t*)&value, sizeof(value)); 661 | return; 662 | } 663 | case WIDTH_2: 664 | { 665 | uint16_t value = (uint16_t)src_value; 666 | if(context->input.endianness != BO_NATIVE_INT_ENDIANNESS) 667 | { 668 | uint8_t buff[sizeof(value)]; 669 | copy_swapped(buff, (uint8_t*)&value, sizeof(value)); 670 | add_bytes(context, buff, sizeof(value)); 671 | return; 672 | } 673 | add_bytes(context, (uint8_t*)&value, sizeof(value)); 674 | return; 675 | } 676 | case WIDTH_4: 677 | { 678 | uint32_t value = (uint32_t)src_value; 679 | if(context->input.endianness != BO_NATIVE_INT_ENDIANNESS) 680 | { 681 | uint8_t buff[sizeof(value)]; 682 | copy_swapped(buff, (uint8_t*)&value, sizeof(value)); 683 | add_bytes(context, buff, sizeof(value)); 684 | return; 685 | } 686 | add_bytes(context, (uint8_t*)&value, sizeof(value)); 687 | return; 688 | } 689 | case WIDTH_8: 690 | { 691 | uint64_t value = (uint64_t)src_value; 692 | if(context->input.endianness != BO_NATIVE_INT_ENDIANNESS) 693 | { 694 | uint8_t buff[sizeof(value)]; 695 | copy_swapped(buff, (uint8_t*)&value, sizeof(value)); 696 | add_bytes(context, buff, sizeof(value)); 697 | return; 698 | } 699 | add_bytes(context, (uint8_t*)&value, sizeof(value)); 700 | return; 701 | } 702 | case WIDTH_16: 703 | bo_notify_error(context, "TODO: Int width 16 not implemented yet"); 704 | return; 705 | default: 706 | bo_notify_error(context, "$d: Invalid int width", context->input.data_width); 707 | return; 708 | } 709 | } 710 | 711 | static void add_float(bo_context* context, double src_value) 712 | { 713 | switch(context->input.data_width) 714 | { 715 | case WIDTH_2: 716 | bo_notify_error(context, "TODO: Float width 2 not implemented yet"); 717 | return; 718 | case WIDTH_4: 719 | { 720 | float value = (float)src_value; 721 | if(context->input.endianness != BO_NATIVE_INT_ENDIANNESS) 722 | { 723 | uint8_t buff[sizeof(value)]; 724 | copy_swapped(buff, (uint8_t*)&value, sizeof(value)); 725 | add_bytes(context, buff, sizeof(value)); 726 | return; 727 | } 728 | add_bytes(context, (uint8_t*)&value, sizeof(value)); 729 | return; 730 | } 731 | case WIDTH_8: 732 | { 733 | double value = (double)src_value; 734 | if(context->input.endianness != BO_NATIVE_INT_ENDIANNESS) 735 | { 736 | uint8_t buff[sizeof(value)]; 737 | copy_swapped(buff, (uint8_t*)&value, sizeof(value)); 738 | add_bytes(context, buff, sizeof(value)); 739 | return; 740 | } 741 | add_bytes(context, (uint8_t*)&value, sizeof(value)); 742 | return; 743 | } 744 | case WIDTH_16: 745 | bo_notify_error(context, "TODO: Float width 16 not implemented yet"); 746 | return; 747 | default: 748 | bo_notify_error(context, "%d: Invalid float width", context->input.data_width); 749 | return; 750 | } 751 | } 752 | 753 | 754 | 755 | // ---------------- 756 | // Parser Callbacks 757 | // ---------------- 758 | 759 | void bo_on_bytes(bo_context* context, uint8_t* data, int length) 760 | { 761 | LOG("On bytes: %d", length); 762 | if(context->input.data_width > 1 && context->input.endianness != BO_NATIVE_INT_ENDIANNESS) 763 | { 764 | add_bytes_swapped(context, data, length, context->input.data_width); 765 | return; 766 | } 767 | add_bytes(context, data, length); 768 | } 769 | 770 | void bo_on_string(bo_context* context, const uint8_t* string_start, const uint8_t* string_end) 771 | { 772 | LOG("On string [%s]", string_start); 773 | add_bytes(context, string_start, string_end - string_start); 774 | } 775 | 776 | void bo_on_number(bo_context* context, const uint8_t* string_value) 777 | { 778 | LOG("On number [%s]", string_value); 779 | if(!check_can_input_numbers(context, string_value)) 780 | { 781 | return; 782 | } 783 | 784 | switch(context->input.data_type) 785 | { 786 | case TYPE_FLOAT: 787 | add_float(context, strtod((char*)string_value, NULL)); 788 | return; 789 | case TYPE_DECIMAL: 790 | bo_notify_error(context, "TODO: Unimplemented decimal type: %s", string_value); 791 | return; 792 | case TYPE_INT: 793 | add_int(context, strtoul((char*)string_value, NULL, 10)); 794 | return; 795 | case TYPE_HEX: 796 | add_int(context, strtoul((char*)string_value, NULL, 16)); 797 | return; 798 | case TYPE_OCTAL: 799 | add_int(context, strtoul((char*)string_value, NULL, 8)); 800 | return; 801 | case TYPE_BOOLEAN: 802 | add_int(context, strtoul((char*)string_value, NULL, 2)); 803 | return; 804 | default: 805 | bo_notify_error(context, "Unknown type %d for value [%s]", context->input.data_type, string_value); 806 | return; 807 | } 808 | } 809 | 810 | void bo_on_preset(bo_context* context, const uint8_t* string_value) 811 | { 812 | LOG("Set preset [%s]", string_value); 813 | if(*string_value == 0) 814 | { 815 | bo_notify_error(context, "Missing preset value"); 816 | return; 817 | } 818 | 819 | switch(*string_value) 820 | { 821 | case 's': 822 | bo_on_suffix(context, (uint8_t*)" "); 823 | break; 824 | case 'c': 825 | bo_on_suffix(context, (uint8_t*)", "); 826 | switch(context->output.data_type) 827 | { 828 | case TYPE_HEX: 829 | bo_on_prefix(context, (uint8_t*)"0x"); 830 | break; 831 | case TYPE_OCTAL: 832 | bo_on_prefix(context, (uint8_t*)"0"); 833 | break; 834 | default: 835 | // Nothing to do 836 | break; 837 | } 838 | break; 839 | default: 840 | bo_notify_error(context, "%s: Unknown prefix-suffix preset", string_value); 841 | return; 842 | } 843 | } 844 | 845 | void bo_on_prefix(bo_context* context, const uint8_t* prefix) 846 | { 847 | LOG("Set prefix [%s]", prefix); 848 | free((void*)context->output.prefix); 849 | context->output.prefix = strdup((char*)prefix); 850 | if(context->output.prefix == NULL) 851 | { 852 | bo_notify_error(context, "Could not clone string [%s]: %s", prefix, strerror(errno)); 853 | } 854 | } 855 | 856 | void bo_on_suffix(bo_context* context, const uint8_t* suffix) 857 | { 858 | LOG("Set suffix [%s]", suffix); 859 | free((void*)context->output.suffix); 860 | context->output.suffix = strdup((char*)suffix); 861 | if(context->output.suffix == NULL) 862 | { 863 | bo_notify_error(context, "Could not clone string [%s]: %s", suffix, strerror(errno)); 864 | } 865 | } 866 | 867 | void bo_on_input_type(bo_context* context, bo_data_type data_type, int data_width, bo_endianness endianness) 868 | { 869 | LOG("Set input type %d, width %d, endianness %d", data_type, data_width, endianness); 870 | context->input.data_type = data_type; 871 | context->input.data_width = data_width; 872 | context->input.endianness = endianness; 873 | } 874 | 875 | void bo_on_output_type(bo_context* context, bo_data_type data_type, int data_width, bo_endianness endianness, int print_width) 876 | { 877 | LOG("Set output type %d, width %d, endianness %d, print width %d", data_type, data_width, endianness, print_width); 878 | context->output.data_type = data_type; 879 | context->output.data_width = data_width; 880 | context->output.endianness = endianness; 881 | context->output.text_width = print_width; 882 | } 883 | 884 | 885 | 886 | // ---------- 887 | // Public API 888 | // ---------- 889 | 890 | const char* bo_version() 891 | { 892 | return BO_VERSION; 893 | } 894 | 895 | void* bo_new_context(void* user_data, output_callback on_output, error_callback on_error) 896 | { 897 | LOG("New callback context"); 898 | bo_context context = 899 | { 900 | .src_buffer = {0}, 901 | .work_buffer = buffer_alloc(WORK_BUFFER_SIZE, WORK_BUFFER_OVERHEAD_SIZE), 902 | .output_buffer = buffer_alloc(WORK_BUFFER_SIZE * 10, OUTPUT_BUFFER_OVERHEAD_SIZE), 903 | .input = 904 | { 905 | .data_type = TYPE_NONE, 906 | .data_width = 0, 907 | .endianness = BO_ENDIAN_NONE, 908 | }, 909 | .output = 910 | { 911 | .data_type = TYPE_NONE, 912 | .data_width = 0, 913 | .text_width = 0, 914 | .prefix = NULL, 915 | .suffix = NULL, 916 | .endianness = BO_ENDIAN_NONE, 917 | }, 918 | .on_error = on_error, 919 | .on_output = on_output, 920 | .user_data = user_data, 921 | 922 | .data_segment_type = DATA_SEGMENT_STREAM, 923 | .is_at_end_of_input = false, 924 | .is_error_condition = false, 925 | .parse_should_continue = false, 926 | .is_spanning_string = false, 927 | }; 928 | 929 | bo_context* heap_context = (bo_context*)malloc(sizeof(context)); 930 | *heap_context = context; 931 | return heap_context; 932 | } 933 | 934 | bool bo_flush_and_destroy_context(void* void_context) 935 | { 936 | LOG("Destroy context"); 937 | bo_context* context = (bo_context*)void_context; 938 | clear_error_condition(context); 939 | flush_work_buffer(context, true); 940 | flush_output_buffer(context); 941 | bool is_successful = !is_error_condition(context); 942 | buffer_free(&context->work_buffer); 943 | buffer_free(&context->output_buffer); 944 | free((void*)context->output.prefix); 945 | free((void*)context->output.suffix); 946 | free((void*)context); 947 | return is_successful; 948 | } 949 | -------------------------------------------------------------------------------- /libbo/src/library_version.h.in: -------------------------------------------------------------------------------- 1 | #define BO_VERSION "${PROJECT_VERSION}" 2 | -------------------------------------------------------------------------------- /libbo/src/parser.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Karl Stenerud. All rights reserved. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall remain in place 11 | // in this source code. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | // 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include "bo_internal.h" 27 | #include "character_flags.h" 28 | 29 | 30 | // ------- 31 | // Utility 32 | // ------- 33 | 34 | static const char* g_data_type_name[] = 35 | { 36 | [TYPE_NONE] = "none", 37 | [TYPE_BINARY] = "binary", 38 | [TYPE_INT] = "int", 39 | [TYPE_HEX] = "hex", 40 | [TYPE_OCTAL] = "octal", 41 | [TYPE_BOOLEAN] = "boolean", 42 | [TYPE_FLOAT] = "float", 43 | [TYPE_DECIMAL] = "decimal", 44 | [TYPE_STRING] = "string", 45 | }; 46 | 47 | static int g_min_data_widths[] = 48 | { 49 | [TYPE_NONE] = 0, 50 | [TYPE_BINARY] = 1, 51 | [TYPE_INT] = 1, 52 | [TYPE_HEX] = 1, 53 | [TYPE_OCTAL] = 1, 54 | [TYPE_BOOLEAN] = 1, 55 | [TYPE_FLOAT] = 2, 56 | [TYPE_DECIMAL] = 4, 57 | [TYPE_STRING] = 1, 58 | }; 59 | 60 | static inline bool should_continue_parsing(bo_context* context) 61 | { 62 | return context->parse_should_continue; 63 | } 64 | 65 | static inline void stop_parsing_at(bo_context* context, uint8_t* position) 66 | { 67 | context->src_buffer.pos = position; 68 | stop_parsing(context); 69 | } 70 | 71 | static bool verify_data_width(bo_context* context, bo_data_type data_type, int width) 72 | { 73 | if(width < g_min_data_widths[data_type]) 74 | { 75 | bo_notify_error(context, "Width %d cannot be used with data type %s", width, g_data_type_name[data_type]); 76 | return false; 77 | } 78 | return true; 79 | } 80 | 81 | static inline bool is_octal_character(int ch) 82 | { 83 | return g_character_flags[ch] & CH_FLAG_BASE_8; 84 | } 85 | 86 | static inline bool is_decimal_character(int ch) 87 | { 88 | return g_character_flags[ch] & CH_FLAG_BASE_10; 89 | } 90 | 91 | static inline bool is_hex_character(int ch) 92 | { 93 | return g_character_flags[ch] & CH_FLAG_BASE_16; 94 | } 95 | 96 | static inline bool is_numeric_character(int ch) 97 | { 98 | return g_character_flags[ch] & (CH_FLAG_FP_NUMBER | CH_FLAG_BASE_16); 99 | } 100 | 101 | static inline bool is_whitespace_character(int ch) 102 | { 103 | return g_character_flags[ch] & CH_FLAG_WHITESPACE; 104 | } 105 | 106 | static inline bool is_at_end_of_input(bo_context* context) 107 | { 108 | return context->is_at_end_of_input; 109 | } 110 | 111 | static inline bool is_last_data_segment(bo_context* context) 112 | { 113 | return context->data_segment_type == DATA_SEGMENT_LAST; 114 | } 115 | 116 | static inline void null_terminate_string(uint8_t* ptr) 117 | { 118 | *ptr = 0; 119 | } 120 | 121 | static bo_data_type extract_data_type(bo_context* context, uint8_t* token, int offset) 122 | { 123 | if(token + offset >= buffer_get_end(&context->src_buffer)) 124 | { 125 | bo_notify_error(context, "%s: offset %d: Missing data type", token, offset); 126 | return TYPE_NONE; 127 | } 128 | switch(token[offset]) 129 | { 130 | case 'B': 131 | return TYPE_BINARY; 132 | case 'i': 133 | return TYPE_INT; 134 | case 'h': 135 | return TYPE_HEX; 136 | case 'o': 137 | return TYPE_OCTAL; 138 | case 'b': 139 | return TYPE_BOOLEAN; 140 | case 'f': 141 | return TYPE_FLOAT; 142 | case 'd': 143 | return TYPE_DECIMAL; 144 | case 's': 145 | return TYPE_STRING; 146 | default: 147 | bo_notify_error(context, "%s: offset %d: %c is not a valid data type", token, offset, token[offset]); 148 | return TYPE_NONE; 149 | } 150 | } 151 | 152 | static int extract_data_width(bo_context* context, uint8_t* token, int offset) 153 | { 154 | if(token + offset >= buffer_get_end(&context->src_buffer)) 155 | { 156 | bo_notify_error(context, "%s: offset %d: Missing data width", token, offset); 157 | return 0; 158 | } 159 | switch(token[offset]) 160 | { 161 | case '1': 162 | switch(token[offset + 1]) 163 | { 164 | case '6': 165 | return 16; 166 | case '0': case '1': case '2': case '3': case '4': case '5': 167 | case '7': case '8': case '9': 168 | { 169 | unsigned int width = strtoul((char*)token + offset, NULL, 16); 170 | bo_notify_error(context, "%s: offset %d: %d is not a valid data width", token, offset, width); 171 | return 0; 172 | } 173 | default: 174 | return 1; 175 | } 176 | case '2': 177 | return 2; 178 | case '4': 179 | return 4; 180 | case '8': 181 | return 8; 182 | case '0': case '3': case '5': case '6': case '7': case '9': 183 | { 184 | unsigned int width = strtoul((char*)token + offset, NULL, 10); 185 | bo_notify_error(context, "%s: offset %d: %d is not a valid data width", token, offset, width); 186 | return 0; 187 | } 188 | default: 189 | bo_notify_error(context, "%s: offset %d: Not a valid data width", token, offset); 190 | return 0; 191 | } 192 | } 193 | 194 | static bo_endianness extract_endianness(bo_context* context, uint8_t* token, int offset) 195 | { 196 | if(token + offset >= buffer_get_end(&context->src_buffer)) 197 | { 198 | bo_notify_error(context, "%s: offset %d: Missing endianness", token, offset); 199 | return BO_ENDIAN_NONE; 200 | } 201 | 202 | switch(token[offset]) 203 | { 204 | case 'b': 205 | return BO_ENDIAN_BIG; 206 | case 'l': 207 | return BO_ENDIAN_LITTLE; 208 | default: 209 | bo_notify_error(context, "%s: offset %d: %c is not a valid endianness", token, offset, token[offset]); 210 | return BO_ENDIAN_NONE; 211 | } 212 | } 213 | 214 | 215 | 216 | // --------------- 217 | // General Parsing 218 | // --------------- 219 | 220 | /** 221 | * Terminate the token pointed to by the src buffer. 222 | * THIS FUNCTION MODIFIES MEMORY! 223 | * 224 | * Reads until the first whitespace and replaces the whitespace with null termination. 225 | * If no whitespace is encountered, and we're mid stream, ends parsing. 226 | * 227 | * @param context The context. 228 | * @return Pointer to one past the end of the token. 229 | */ 230 | static uint8_t* terminate_token(bo_context* context) 231 | { 232 | uint8_t* ptr = buffer_get_position(&context->src_buffer); 233 | for(; ptr < buffer_get_end(&context->src_buffer); ptr++) 234 | { 235 | if(is_whitespace_character(*ptr)) 236 | { 237 | null_terminate_string(ptr); 238 | return ptr; 239 | } 240 | } 241 | context->is_at_end_of_input = true; 242 | if(context->data_segment_type == DATA_SEGMENT_STREAM) 243 | { 244 | stop_parsing(context); 245 | } 246 | return ptr; 247 | } 248 | 249 | static inline uint8_t* handle_end_of_data(bo_context* context, 250 | uint8_t* interruption_point, 251 | const char* error_message) 252 | { 253 | if(is_last_data_segment(context)) 254 | { 255 | bo_notify_error(context, error_message); 256 | return NULL; 257 | } 258 | 259 | stop_parsing_at(context, interruption_point); 260 | return interruption_point; 261 | } 262 | 263 | /** 264 | * Parse a string. The input string must begin and end with double quotes ("). 265 | * THIS FUNCTION MODIFIES MEMORY! 266 | * 267 | * The string may contain escape sequences, which will be converted to the values they represent. 268 | * Upon successful completion, the memory at pointer string will contain an unescaped, unquoted, 269 | * null terminated string. 270 | * 271 | * @param context The context. 272 | * @param string The string to parse. 273 | * @return A pointer to the end of the string, or NULL if an error occurred. 274 | */ 275 | static uint8_t* parse_string(bo_context* context, int offset) 276 | { 277 | uint8_t* string = buffer_get_position(&context->src_buffer) + offset; 278 | uint8_t* write_pos = string; 279 | uint8_t* read_pos = string; 280 | uint8_t* read_end = buffer_get_end(&context->src_buffer); 281 | 282 | for(; read_pos < read_end; read_pos++) 283 | { 284 | switch(*read_pos) 285 | { 286 | case '"': 287 | null_terminate_string(write_pos); 288 | buffer_set_position(&context->src_buffer, read_pos); 289 | return write_pos; 290 | case '\\': 291 | { 292 | uint8_t* escape_pos = read_pos; 293 | int remaining_bytes = read_end - read_pos; 294 | if(remaining_bytes < 1) 295 | { 296 | return handle_end_of_data(context, escape_pos, "Unterminated escape sequence"); 297 | } 298 | 299 | read_pos++; 300 | switch(*read_pos) 301 | { 302 | case 'r': *write_pos++ = '\r'; break; 303 | case 'n': *write_pos++ = '\n'; break; 304 | case 't': *write_pos++ = '\t'; break; 305 | case '\\': *write_pos++ = '\\'; break; 306 | case '\"': *write_pos++ = '\"'; break; 307 | case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': 308 | { 309 | uint8_t number_buffer[4] = {*read_pos, 0, 0, 0}; 310 | read_pos++; 311 | if(is_octal_character(*read_pos)) 312 | { 313 | number_buffer[1] = *read_pos; 314 | read_pos++; 315 | } 316 | if(is_octal_character(*read_pos)) 317 | { 318 | number_buffer[2] = *read_pos; 319 | read_pos++; 320 | } 321 | *write_pos++ = (uint8_t)strtoul((char*)number_buffer, NULL, 8); 322 | read_pos--; 323 | break; 324 | } 325 | case 'x': 326 | { 327 | if(remaining_bytes < 2) 328 | { 329 | return handle_end_of_data(context, escape_pos, "Unterminated hex escape sequence"); 330 | } 331 | read_pos++; 332 | if(!is_hex_character(*read_pos)) 333 | { 334 | bo_notify_error(context, "Invalid hex escape sequence"); 335 | return NULL; 336 | } 337 | uint8_t number_buffer[3] = {*read_pos, 0, 0}; 338 | read_pos += 1; 339 | if(is_hex_character(*read_pos)) 340 | { 341 | number_buffer[1] = *read_pos; 342 | read_pos += 1; 343 | } 344 | *write_pos++ = (uint8_t)strtoul((char*)number_buffer, NULL, 16); 345 | read_pos--; 346 | break; 347 | } 348 | case 'u': 349 | { 350 | if(remaining_bytes < 5) 351 | { 352 | return handle_end_of_data(context, escape_pos, "Unterminated unicode escape sequence"); 353 | } 354 | if(!is_hex_character(read_pos[1]) 355 | || !is_hex_character(read_pos[2]) 356 | || !is_hex_character(read_pos[3]) 357 | || !is_hex_character(read_pos[4])) 358 | { 359 | bo_notify_error(context, "Invalid unicode escape sequence"); 360 | return NULL; 361 | } 362 | uint8_t number_buffer[5] = {read_pos[1], read_pos[2], read_pos[3], read_pos[4], 0}; 363 | read_pos += 4; 364 | unsigned int codepoint = strtoul((char*)number_buffer, NULL, 16); 365 | if(codepoint <= 0x7f) 366 | { 367 | *write_pos++ = (uint8_t)codepoint; 368 | } 369 | else if(codepoint <= 0x7ff) 370 | { 371 | *write_pos++ = (uint8_t)((codepoint >> 6) | 0xc0); 372 | *write_pos++ = (uint8_t)((codepoint & 0x3f) | 0x80); 373 | } 374 | else 375 | { 376 | *write_pos++ = (uint8_t)((codepoint >> 12) | 0xe0); 377 | *write_pos++ = (uint8_t)(((codepoint >> 6) & 0x3f) | 0x80); 378 | *write_pos++ = (uint8_t)((codepoint & 0x3f) | 0x80); 379 | } 380 | break; 381 | } 382 | default: 383 | bo_notify_error(context, "Invalid escape sequence"); 384 | return NULL; 385 | } 386 | break; 387 | } 388 | default: 389 | *write_pos++ = *read_pos; 390 | break; 391 | } 392 | } 393 | 394 | return handle_end_of_data(context, read_pos, "Unterminated string"); 395 | } 396 | 397 | 398 | 399 | // ------ 400 | // Events 401 | // ------ 402 | 403 | static void on_unknown_token(bo_context* context) 404 | { 405 | uint8_t* token = buffer_get_position(&context->src_buffer); 406 | terminate_token(context); 407 | if(!is_at_end_of_input(context)) 408 | { 409 | bo_notify_error(context, "%s: Unknown token", token); 410 | } 411 | 412 | int length = buffer_get_end(&context->src_buffer) - token; 413 | uint8_t* token_copy = malloc(length + 1); 414 | memcpy(token_copy, token, length); 415 | null_terminate_string(token_copy + length); 416 | bo_notify_error(context, "%s: Unknown token", token_copy); 417 | free(token_copy); 418 | } 419 | 420 | static void on_string(bo_context* context, int offset) 421 | { 422 | uint8_t* string_start = buffer_get_position(&context->src_buffer) + offset; 423 | uint8_t* string_end = parse_string(context, offset); 424 | if(is_error_condition(context)) return; 425 | if(string_end > string_start) 426 | { 427 | bo_on_string(context, string_start, string_end); 428 | } 429 | if(!should_continue_parsing(context)) 430 | { 431 | context->is_spanning_string = true; 432 | } 433 | } 434 | 435 | static uint8_t* prefix_suffix_process_common(bo_context* context) 436 | { 437 | uint8_t* token = buffer_get_position(&context->src_buffer); 438 | if(token[1] != '"') 439 | { 440 | terminate_token(context); 441 | bo_notify_error(context, "%s: Not a string", token + 1); 442 | return NULL; 443 | } 444 | 445 | int offset = 2; 446 | uint8_t* string = buffer_get_position(&context->src_buffer) + offset; 447 | parse_string(context, offset); 448 | if(!should_continue_parsing(context)) 449 | { 450 | buffer_set_position(&context->src_buffer, token); 451 | } 452 | return string; 453 | } 454 | 455 | static void on_prefix(bo_context* context) 456 | { 457 | uint8_t* string = prefix_suffix_process_common(context); 458 | if(!should_continue_parsing(context)) return; 459 | bo_on_prefix(context, string); 460 | } 461 | 462 | static void on_suffix(bo_context* context) 463 | { 464 | uint8_t* string = prefix_suffix_process_common(context); 465 | if(!should_continue_parsing(context)) return; 466 | bo_on_suffix(context, string); 467 | } 468 | 469 | static void on_input_type(bo_context* context) 470 | { 471 | uint8_t* end = terminate_token(context); 472 | if(!should_continue_parsing(context)) return; 473 | 474 | uint8_t* token = buffer_get_position(&context->src_buffer); 475 | int offset = 1; 476 | 477 | bo_data_type data_type = extract_data_type(context, token, offset); 478 | if(!should_continue_parsing(context)) return; 479 | offset += 1; 480 | 481 | int data_width = 1; 482 | bo_endianness endianness = BO_ENDIAN_NONE; 483 | 484 | if(data_type != TYPE_STRING) 485 | { 486 | data_width = extract_data_width(context, token, offset); 487 | if(!should_continue_parsing(context)) return; 488 | offset += data_width > 8 ? 2 : 1; 489 | 490 | if(data_width > 1) 491 | { 492 | endianness = extract_endianness(context, token, offset); 493 | if(!should_continue_parsing(context)) return; 494 | } 495 | } 496 | 497 | if(!verify_data_width(context, data_type, data_width)) return; 498 | 499 | bo_on_input_type(context, data_type, data_width, endianness); 500 | if(!should_continue_parsing(context)) return; 501 | buffer_set_position(&context->src_buffer, end); 502 | } 503 | 504 | static void on_output_type(bo_context* context) 505 | { 506 | uint8_t* end = terminate_token(context); 507 | if(!should_continue_parsing(context)) return; 508 | uint8_t* token = buffer_get_position(&context->src_buffer); 509 | int token_length = end - token; 510 | int offset = 1; 511 | 512 | bo_data_type data_type = extract_data_type(context, token, offset); 513 | if(!should_continue_parsing(context)) return; 514 | offset += 1; 515 | 516 | int data_width = 1; 517 | int print_width = 1; 518 | bo_endianness endianness = BO_ENDIAN_NONE; 519 | 520 | if(data_type != TYPE_STRING) 521 | { 522 | data_width = extract_data_width(context, token, offset); 523 | if(!should_continue_parsing(context)) return; 524 | offset += data_width > 8 ? 2 : 1; 525 | 526 | if(data_width > 1 || data_type == TYPE_BOOLEAN || token_length > offset) 527 | { 528 | endianness = extract_endianness(context, token, offset); 529 | if(!should_continue_parsing(context)) return; 530 | offset += 1; 531 | 532 | if(data_type != TYPE_BINARY && token + offset < end) 533 | { 534 | if(!is_decimal_character(token[offset])) 535 | { 536 | bo_notify_error(context, "%s: offset %d: Not a valid print width", token, offset); 537 | return; 538 | } 539 | 540 | print_width = strtoul((char*)token + offset, NULL, 10); 541 | } 542 | } 543 | } 544 | 545 | if(!verify_data_width(context, data_type, data_width)) return; 546 | 547 | bo_on_output_type(context, data_type, data_width, endianness, print_width); 548 | if(!should_continue_parsing(context)) return; 549 | buffer_set_position(&context->src_buffer, end); 550 | } 551 | 552 | static void on_preset(bo_context* context) 553 | { 554 | uint8_t* end = terminate_token(context); 555 | if(!should_continue_parsing(context)) return; 556 | 557 | uint8_t* token = buffer_get_position(&context->src_buffer); 558 | bo_on_preset(context, token + 1); 559 | if(!should_continue_parsing(context)) return; 560 | buffer_set_position(&context->src_buffer, end); 561 | } 562 | 563 | static void on_number(bo_context* context) 564 | { 565 | uint8_t* end = terminate_token(context); 566 | if(!should_continue_parsing(context)) return; 567 | 568 | uint8_t* token = buffer_get_position(&context->src_buffer); 569 | bo_on_number(context, token); 570 | if(!should_continue_parsing(context)) return; 571 | buffer_set_position(&context->src_buffer, end); 572 | } 573 | 574 | 575 | 576 | // --------- 577 | // Parse API 578 | // --------- 579 | 580 | char* bo_process(void* void_context, char* data, int data_length, bo_data_segment_type data_segment_type) 581 | { 582 | if(BO_ENABLE_LOGGING) 583 | { 584 | char ch = data[data_length]; 585 | data[data_length] = 0; 586 | LOG("bo_process [%s]", data); 587 | data[data_length] = ch; 588 | } 589 | 590 | if(data_length < 1) 591 | { 592 | LOG("No data to process"); 593 | return data; 594 | } 595 | 596 | bo_context* context = (bo_context*)void_context; 597 | context->src_buffer.start = context->src_buffer.pos = (uint8_t*)data; 598 | context->src_buffer.end = context->src_buffer.start + data_length; 599 | 600 | if(context->input.data_type == TYPE_BINARY) 601 | { 602 | bo_on_bytes(context, context->src_buffer.start, data_length); 603 | return (char*)buffer_get_end(&context->src_buffer); 604 | } 605 | 606 | context->data_segment_type = data_segment_type; 607 | context->is_at_end_of_input = false; 608 | context->is_error_condition = false; 609 | context->parse_should_continue = true; 610 | 611 | if(context->is_spanning_string) 612 | { 613 | context->is_spanning_string = false; 614 | on_string(context, 0); 615 | context->src_buffer.pos++; 616 | } 617 | 618 | const uint8_t* const end = context->src_buffer.end; 619 | for(;context->src_buffer.pos < end; context->src_buffer.pos++) 620 | { 621 | switch(*context->src_buffer.pos) 622 | { 623 | case '\n': 624 | // TODO: Line count 625 | break; 626 | case ' ': case '\t': case '\r': 627 | break; 628 | case '"': 629 | on_string(context, 1); 630 | break; 631 | case 'i': 632 | on_input_type(context); 633 | break; 634 | case 'o': 635 | on_output_type(context); 636 | break; 637 | case 'p': 638 | on_prefix(context); 639 | break; 640 | case 's': 641 | on_suffix(context); 642 | break; 643 | case 'P': 644 | on_preset(context); 645 | break; 646 | default: 647 | if(is_numeric_character(*context->src_buffer.pos)) 648 | { 649 | on_number(context); 650 | break; 651 | } 652 | on_unknown_token(context); 653 | break; 654 | } 655 | if(!should_continue_parsing(context)) 656 | { 657 | break; 658 | } 659 | if(is_at_end_of_input(context)) 660 | { 661 | break; 662 | } 663 | } 664 | 665 | // TODO: Check for end of parse and pass any remaining data in directly. 666 | if(is_error_condition(context)) 667 | { 668 | return NULL; 669 | } 670 | return (char*)buffer_get_position(&context->src_buffer); 671 | } 672 | -------------------------------------------------------------------------------- /libbo/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_language(CXX) 2 | 3 | # Download and unpack googletest at configure time 4 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GoogleTest-CMakeLists.txt.in ${CMAKE_BINARY_DIR}/googletest-download/CMakeLists.txt) 5 | execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . 6 | RESULT_VARIABLE result 7 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download ) 8 | if(result) 9 | message(FATAL_ERROR "CMake step for googletest failed: ${result}") 10 | endif() 11 | execute_process(COMMAND ${CMAKE_COMMAND} --build . 12 | RESULT_VARIABLE result 13 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download ) 14 | if(result) 15 | message(FATAL_ERROR "Build step for googletest failed: ${result}") 16 | endif() 17 | 18 | # Add googletest directly to our build. This defines 19 | # the gtest and gtest_main targets. 20 | add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src 21 | ${CMAKE_BINARY_DIR}/googletest-build 22 | EXCLUDE_FROM_ALL) 23 | 24 | # Now simply link against gtest or gtest_main as needed. Eg 25 | add_executable(libbo_test 26 | src/main.cpp 27 | src/test_helpers.cpp 28 | src/output.cpp 29 | src/errors.cpp 30 | src/input.cpp 31 | src/config.cpp 32 | src/span.cpp 33 | src/boolean.cpp 34 | src/string.cpp 35 | src/float.cpp 36 | ) 37 | 38 | target_compile_features(libbo_test PRIVATE cxx_auto_type) 39 | target_link_libraries(libbo_test gtest_main libbo) 40 | 41 | # Gain access to internal headers 42 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) 43 | -------------------------------------------------------------------------------- /libbo/test/cmake/GoogleTest-CMakeLists.txt.in: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5.0) 2 | 3 | project(googletest-download NONE) 4 | 5 | include(ExternalProject) 6 | ExternalProject_Add(googletest 7 | GIT_REPOSITORY https://github.com/google/googletest.git 8 | GIT_TAG master 9 | SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" 10 | BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" 11 | CONFIGURE_COMMAND "" 12 | BUILD_COMMAND "" 13 | INSTALL_COMMAND "" 14 | TEST_COMMAND "" 15 | UPDATE_DISCONNECTED 1 16 | ) 17 | -------------------------------------------------------------------------------- /libbo/test/src/boolean.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | 3 | TEST(BO_Boolean, boolean) 4 | { 5 | assert_conversion("ob2b1 ib2b 1011", "0000000000001011"); 6 | assert_conversion("ob2l1 ib2l 1011", "1101000000000000"); 7 | assert_conversion("ob2b1 ib2l 1011", "0000101100000000"); 8 | assert_conversion("ob2l1 ib2b 1011", "0000000011010000"); 9 | } 10 | -------------------------------------------------------------------------------- /libbo/test/src/config.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | 3 | TEST(BO_Config, input_config) 4 | { 5 | assert_failed_conversion(1000, "i"); 6 | assert_failed_conversion(1000, "if"); 7 | assert_failed_conversion(1000, "if4"); 8 | assert_conversion("if4b", ""); 9 | assert_conversion("ih1", ""); 10 | 11 | assert_failed_conversion(1000, "iB"); 12 | assert_conversion("iB1", ""); 13 | assert_conversion("iB2l", ""); 14 | 15 | assert_failed_conversion(1000, "iv4l"); 16 | assert_failed_conversion(1000, "if10l"); 17 | assert_failed_conversion(1000, "if4h"); 18 | assert_failed_conversion(1000, "if1l"); 19 | assert_failed_conversion(1000, "id1l"); 20 | assert_failed_conversion(1000, "id2l"); 21 | } 22 | 23 | TEST(BO_Config, output_config) 24 | { 25 | assert_failed_conversion(1000, "o"); 26 | assert_failed_conversion(1000, "oo"); 27 | assert_failed_conversion(1000, "oo8"); 28 | assert_conversion("oo8l", ""); 29 | assert_conversion("oo8l1", ""); 30 | 31 | assert_failed_conversion(1000, "oB2"); 32 | assert_conversion("oB2b", ""); 33 | 34 | assert_failed_conversion(1000, "oa8l2"); 35 | assert_failed_conversion(1000, "oo9l2"); 36 | assert_failed_conversion(1000, "oo32l2"); 37 | assert_failed_conversion(1000, "oo8j2"); 38 | assert_failed_conversion(1000, "oo8l-1"); 39 | } 40 | 41 | TEST(BO_Config, prefix) 42 | { 43 | assert_failed_conversion(1000, "p"); 44 | assert_failed_conversion(1000, "p\""); 45 | assert_conversion("p\"\"", ""); 46 | assert_failed_conversion(1000, "pp"); 47 | } 48 | 49 | TEST(BO_Config, suffix) 50 | { 51 | assert_failed_conversion(1000, "s"); 52 | assert_failed_conversion(1000, "s\""); 53 | assert_conversion("s\"\"", ""); 54 | assert_failed_conversion(1000, "sq"); 55 | } 56 | 57 | TEST(BO_Config, preset) 58 | { 59 | assert_failed_conversion(1000, "P"); 60 | assert_failed_conversion(1000, "P2"); 61 | assert_conversion("Pc", ""); 62 | assert_conversion("Ps", ""); 63 | } 64 | -------------------------------------------------------------------------------- /libbo/test/src/errors.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | 3 | // TEST(BO_Errors, too_small) 4 | // { 5 | // assert_failed_conversion(3, "oB ih1l 1 2 3 4"); 6 | // } 7 | 8 | TEST(BO_Errors, bad_src_width) 9 | { 10 | assert_failed_conversion(3, "oB ii3l"); 11 | } 12 | 13 | TEST(BO_Errors, bad_dst_width) 14 | { 15 | assert_failed_conversion(3, "oh9l10 ii1l"); 16 | } 17 | 18 | TEST(BO_Errors, no_input_or_output_config) 19 | { 20 | assert_failed_conversion(1000, "1 2 3 4"); 21 | } 22 | 23 | TEST(BO_Errors, no_input_config) 24 | { 25 | assert_failed_conversion(1000, "oB 1 2 3 4"); 26 | } 27 | 28 | TEST(BO_Errors, no_output_config) 29 | { 30 | assert_failed_conversion(1000, "ii1l 1 2 3 4"); 31 | } 32 | -------------------------------------------------------------------------------- /libbo/test/src/float.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | 3 | TEST(BO_Float, float32) 4 | { 5 | assert_conversion("of4l3 if4l Ps 1.1 8.5 305.125 2", "1.100 8.500 305.125 2.000"); 6 | } 7 | -------------------------------------------------------------------------------- /libbo/test/src/input.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | 3 | TEST(BO_Input, hex_1_2_1_le) 4 | { 5 | assert_conversion("oh1l2 s\" \" ih1l ab cd ef", "ab cd ef"); 6 | } 7 | 8 | TEST(BO_Input, hex_1_2_2_le) 9 | { 10 | assert_conversion("oh1l2 s\" \" ih2l abcd 1234", "cd ab 34 12"); 11 | } 12 | 13 | TEST(BO_Input, hex_1_2_4_le) 14 | { 15 | assert_conversion("oh1l2 s\" \" ih4l 01234567 89abcdef", "67 45 23 01 ef cd ab 89"); 16 | } 17 | 18 | TEST(BO_Input, hex_1_2_8_le) 19 | { 20 | assert_conversion("oh1l2 s\" \" ih8l 0123456789abcdef 1122334455667788", "ef cd ab 89 67 45 23 01 88 77 66 55 44 33 22 11"); 21 | } 22 | 23 | TEST(BO_Input, int_1_2_1_le) 24 | { 25 | assert_conversion("oh1l2 s\" \" ii1l 10 20 30", "0a 14 1e"); 26 | } 27 | 28 | TEST(BO_Input, int_1_2_2_le) 29 | { 30 | assert_conversion("oh1l2 s\" \" ii2l 1000 2000 3000", "e8 03 d0 07 b8 0b"); 31 | } 32 | 33 | TEST(BO_Input, int_1_2_4_le) 34 | { 35 | assert_conversion("oh1l2 s\" \" ii4l 1000000000 2000000000", "00 ca 9a 3b 00 94 35 77"); 36 | } 37 | 38 | TEST(BO_Input, int_1_2_8_be) 39 | { 40 | assert_conversion("oh8b16 s\" \" ii8b 1000000000000000000 2000000000000000000", "0de0b6b3a7640000 1bc16d674ec80000"); 41 | } 42 | 43 | TEST(BO_Input, octal_1_2_1_le) 44 | { 45 | assert_conversion("oh1l2 s\" \" io1l 17 44", "0f 24"); 46 | } 47 | 48 | TEST(BO_Input, octal_1_2_2_le) 49 | { 50 | assert_conversion("oh1l2 s\" \" io2l 34323", "d3 38"); 51 | } 52 | 53 | TEST(BO_Input, octal_1_2_4_le) 54 | { 55 | assert_conversion("oh1l2 s\" \" io4l 6412642441", "21 45 2b 34"); 56 | } 57 | 58 | TEST(BO_Input, octal_1_2_8_le) 59 | { 60 | assert_conversion("oh1l2 s\" \" io8l 1142501502145044200512", "4a 01 91 28 23 34 a8 98"); 61 | } 62 | 63 | TEST(BO_Input, boolean_1_2_1_le) 64 | { 65 | assert_conversion("oh1l2 s\" \" ib1l 10 11", "02 03"); 66 | } 67 | 68 | TEST(BO_Input, boolean_1_2_2_le) 69 | { 70 | assert_conversion("oh1l2 s\" \" ib2l 10 11", "02 00 03 00"); 71 | } 72 | 73 | TEST(BO_Input, boolean_1_2_4_le) 74 | { 75 | assert_conversion("oh1l2 s\" \" ib4l 10 11", "02 00 00 00 03 00 00 00"); 76 | } 77 | 78 | TEST(BO_Input, boolean_1_2_8_le) 79 | { 80 | assert_conversion("oh1l2 s\" \" ib8l 10 11", "02 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00"); 81 | } 82 | 83 | TEST(BO_Input, float_1_2_4_le) 84 | { 85 | assert_conversion("oh1l2 s\" \" if4l -59.18", "52 b8 6c c2"); 86 | } 87 | 88 | TEST(BO_Input, float_1_2_8_le) 89 | { 90 | assert_conversion("oh1l2 s\" \" if8l 948334.662", "fc a9 f1 52 dd f0 2c 41"); 91 | } 92 | 93 | TEST(BO_Input, negative_hex_le) 94 | { 95 | assert_conversion("oh1l2 s\" \" ih8l -2", "fe ff ff ff ff ff ff ff"); 96 | } 97 | 98 | TEST(BO_Input, negative_octal_le) 99 | { 100 | assert_conversion("oh1l2 s\" \" io8l -2", "fe ff ff ff ff ff ff ff"); 101 | } 102 | 103 | TEST(BO_Input, negative_boolean_le) 104 | { 105 | assert_conversion("oh1l2 s\" \" ib8l -10", "fe ff ff ff ff ff ff ff"); 106 | } 107 | -------------------------------------------------------------------------------- /libbo/test/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char **argv) 4 | { 5 | ::testing::InitGoogleTest(&argc, argv); 6 | return RUN_ALL_TESTS(); 7 | } 8 | -------------------------------------------------------------------------------- /libbo/test/src/output.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | 3 | TEST(BO_Output, hex_1_1_le_no_prefix_no_suffix) 4 | { 5 | assert_conversion("oh1l1 ih1l 1 2 3 4 a b cd", "1234abcd"); 6 | } 7 | 8 | TEST(BO_Output, hex_1_1_le_no_prefix) 9 | { 10 | assert_conversion("oh1l1 s\"|\" ih1l 1 2 3 4 a b cd", "1|2|3|4|a|b|cd"); 11 | } 12 | 13 | TEST(BO_Output, hex_1_1_le_no_suffix) 14 | { 15 | assert_conversion("oh1l1 p\" \" ih1l 1 2 3 4 a b cd", " 1 2 3 4 a b cd"); 16 | } 17 | 18 | TEST(BO_Output, hex_1_1_2_le) 19 | { 20 | assert_conversion("oh1l2 p\"0x\" s\", \" ih1l 1 2 3 4 a b cd", "0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0xcd"); 21 | } 22 | 23 | TEST(BO_Output, hex_1_1_3_le) 24 | { 25 | assert_conversion("oh1l3 s\" \" ih1l 10 11 12", "010 011 012"); 26 | } 27 | 28 | TEST(BO_Output, hex_1_1_4_le) 29 | { 30 | assert_conversion("oh1l4 s\" \" ih1l 10 11 12", "0010 0011 0012"); 31 | } 32 | 33 | TEST(BO_Output, hex_2_2_4_le) 34 | { 35 | assert_conversion("oh2l4 s\" \" ih2l 1000 1100 1200", "1000 1100 1200"); 36 | } 37 | 38 | TEST(BO_Output, hex_1_2_4_le_b) 39 | { 40 | assert_conversion("oh2l4 s\" \" ih1l 10 01 f8 99 12 43", "0110 99f8 4312"); 41 | } 42 | 43 | TEST(BO_Output, hex_1_4_8_le) 44 | { 45 | assert_conversion("oh4l8 s\" \" ih1l 01 02 03 04", "04030201"); 46 | } 47 | 48 | TEST(BO_Output, hex_1_8_16_le) 49 | { 50 | assert_conversion("oh8l16 s\" \" ih1l 01 02 03 04 05 06 07 08", "0807060504030201"); 51 | } 52 | 53 | TEST(BO_Output, float_1_4_6_le) 54 | { 55 | assert_conversion("of4l6 s\", \" ih1l 00 00 60 40", "3.500000"); 56 | } 57 | 58 | TEST(BO_Output, float_8_8_3_le) 59 | { 60 | assert_conversion("of8l3 s\", \" ih8l 4052880000000000", "74.125"); 61 | } 62 | 63 | TEST(BO_Output, hex_1_2_4_be) 64 | { 65 | assert_conversion("oh2b4 s\" \" ih1l 10 00 11 00 12 00", "1000 1100 1200"); 66 | } 67 | 68 | TEST(BO_Output, hex_2_2_4_be) 69 | { 70 | assert_conversion("oh2b4 s\" \" ih2l 1234 5678", "3412 7856"); 71 | } 72 | 73 | TEST(BO_Output, hex_4_4_8_be) 74 | { 75 | assert_conversion("oh4b8 s\" \" ih4l 12345678", "78563412"); 76 | } 77 | 78 | TEST(BO_Output, hex_4_2_4_be) 79 | { 80 | assert_conversion("oh2b4 s\" \" ih4l 12345678", "7856 3412"); 81 | } 82 | 83 | TEST(BO_Output, string) 84 | { 85 | assert_conversion("oB1l \"This is a string\"", "This is a string"); 86 | } 87 | -------------------------------------------------------------------------------- /libbo/test/src/span.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | 3 | TEST(BO_Span, input) 4 | { 5 | assert_spanning_conversion("ih1", 1, 0, ""); 6 | assert_spanning_conversion("ih1", 2, 0, ""); 7 | assert_spanning_conversion("ih2l", 3, 0, ""); 8 | assert_spanning_conversion("ih2l", 4, 0, ""); 9 | assert_spanning_conversion("ih2l ", 5, 5, ""); 10 | } 11 | 12 | TEST(BO_Span, output) 13 | { 14 | assert_spanning_conversion("oB1", 1, 0, ""); 15 | assert_spanning_conversion("oB1", 2, 0, ""); 16 | assert_spanning_conversion("of4b6", 3, 0, ""); 17 | assert_spanning_conversion("of4b6", 4, 0, ""); 18 | assert_spanning_conversion("of4b6", 5, 0, ""); 19 | assert_spanning_conversion("of4b6 ", 6, 6, ""); 20 | } 21 | 22 | TEST(BO_Span, prefix) 23 | { 24 | assert_spanning_conversion("p\"abcd\"", 1, 0, ""); 25 | assert_spanning_conversion("p\"abcd\"", 2, 0, ""); 26 | assert_spanning_conversion("p\"abcd\"", 3, 0, ""); 27 | assert_spanning_conversion("p\"abcd\"", 4, 0, ""); 28 | assert_spanning_conversion("p\"abcd\"", 5, 0, ""); 29 | assert_spanning_conversion("p\"abcd\"", 6, 0, ""); 30 | assert_spanning_conversion("p\"abcd\"", 7, 7, ""); 31 | } 32 | 33 | TEST(BO_Span, suffix) 34 | { 35 | assert_spanning_conversion("s\"abcd\"", 1, 0, ""); 36 | assert_spanning_conversion("s\"abcd\"", 2, 0, ""); 37 | assert_spanning_conversion("s\"abcd\"", 3, 0, ""); 38 | assert_spanning_conversion("s\"abcd\"", 4, 0, ""); 39 | assert_spanning_conversion("s\"abcd\"", 5, 0, ""); 40 | assert_spanning_conversion("s\"abcd\"", 6, 0, ""); 41 | assert_spanning_conversion("s\"abcd\"", 7, 7, ""); 42 | } 43 | 44 | TEST(BO_Span, preset) 45 | { 46 | assert_spanning_conversion("Pc", 1, 0, ""); 47 | assert_spanning_conversion("Pc", 2, 0, ""); 48 | assert_spanning_conversion("Pc ", 3, 3, ""); 49 | } 50 | 51 | TEST(BO_Span, number) 52 | { 53 | assert_spanning_conversion("ih1 oh1l2 12 ", 11, 10, ""); 54 | assert_spanning_conversion("ih1 oh1l2 12 ", 12, 10, ""); 55 | assert_spanning_conversion("ih1 oh1l2 12 ", 13, 13, "12"); 56 | } 57 | 58 | TEST(BO_Span, string) 59 | { 60 | assert_spanning_conversion("oB1 \"abcd\"", 5, 5, ""); 61 | assert_spanning_conversion("oB1 \"abcd\"", 6, 6, "a"); 62 | assert_spanning_conversion("oB1 \"abcd\"", 7, 7, "ab"); 63 | assert_spanning_conversion("oB1 \"abcd\"", 8, 8, "abc"); 64 | assert_spanning_conversion("oB1 \"abcd\"", 9, 9, "abcd"); 65 | assert_spanning_conversion("oB1 \"abcd\"", 10, 10, "abcd"); 66 | } 67 | 68 | TEST(BO_Span, string2) 69 | { 70 | assert_spanning_conversion("oh1l2 \"abcd\"", 8, 8, "61"); 71 | assert_spanning_conversion("oh1l2 \"abcd\"", 9, 9, "6162"); 72 | assert_spanning_conversion("oh1l2 \"abcd\"", 10, 10, "616263"); 73 | assert_spanning_conversion("oh1l2 \"abcd\"", 11, 11, "61626364"); 74 | } 75 | 76 | TEST(BO_Span, string_continuation) 77 | { 78 | assert_spanning_continuation("oB1 \"abcd\"", 5, 5, "abcd"); 79 | assert_spanning_continuation("oB1 \"abcd\"", 6, 6, "abcd"); 80 | assert_spanning_continuation("oB1 \"abcd\"", 7, 7, "abcd"); 81 | assert_spanning_continuation("oB1 \"abcd\"", 8, 8, "abcd"); 82 | assert_spanning_continuation("oB1 \"abcd\"", 9, 9, "abcd"); 83 | assert_spanning_continuation("oB1 \"abcd\"", 10, 10, "abcd"); 84 | assert_spanning_continuation("oB1 \"abcd\" \"ab\"", 15, 15, "abcdab"); 85 | } 86 | -------------------------------------------------------------------------------- /libbo/test/src/string.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | 3 | TEST(BO_String, string) 4 | { 5 | assert_conversion("os ih1 \"Testing\" 11 02 \"ß\" 5", "Testing\\x11\\x2ß\\x5"); 6 | assert_conversion("os is \"\\101\\x42\\u263a\"", "AB☺"); 7 | } 8 | -------------------------------------------------------------------------------- /libbo/test/src/test_helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | #include 3 | #include 4 | 5 | 6 | #define ENABLE_LOGGING 1 7 | 8 | #if ENABLE_LOGGING 9 | #define LOG(...) do {fprintf(stdout, "LOG: ");fprintf(stdout, __VA_ARGS__);fprintf(stdout, "\n");fflush(stdout);} while(0) 10 | #else 11 | #define LOG(FMT, ...) 12 | #endif 13 | 14 | 15 | static bool g_has_errors = false; 16 | 17 | static void on_error(void* user_data, const char* message) 18 | { 19 | printf("BO Error: %s\n", message); 20 | fflush(stdout); 21 | g_has_errors = true; 22 | } 23 | 24 | typedef struct 25 | { 26 | char* pos; 27 | 28 | } test_context; 29 | 30 | static test_context new_test_context(char* buffer) 31 | { 32 | test_context context = 33 | { 34 | .pos = buffer 35 | }; 36 | *context.pos = 0; 37 | return context; 38 | } 39 | 40 | static bool on_output(void* user_data, char* data, int length) 41 | { 42 | test_context* context = (test_context*)user_data; 43 | memcpy(context->pos, data, length); 44 | context->pos[length] = 0; 45 | context->pos += length; 46 | return true; 47 | } 48 | 49 | static bool has_errors() 50 | { 51 | return g_has_errors; 52 | } 53 | 54 | static void reset_errors() 55 | { 56 | g_has_errors = false; 57 | } 58 | 59 | char* process_and_terminate(void* context, char* data, int data_length, bo_data_segment_type data_segment_type) 60 | { 61 | char* result = bo_process(context, data, data_length, data_segment_type); 62 | if(result != NULL) 63 | { 64 | *result = 0; 65 | } 66 | return result; 67 | } 68 | 69 | static bool check_processed_all_data(void* context, char* data, int data_length, bo_data_segment_type data_segment_type) 70 | { 71 | char* result = process_and_terminate(context, data, data_length, data_segment_type); 72 | return result == data + data_length; 73 | } 74 | 75 | void assert_conversion(const char* input, const char* expected_output) 76 | { 77 | reset_errors(); 78 | char buffer[10000]; 79 | test_context test_context = new_test_context(buffer); 80 | char* input_copy = strdup(input); 81 | void* context = bo_new_context(&test_context, on_output, on_error); 82 | bool process_success = check_processed_all_data(context, input_copy, strlen(input_copy), DATA_SEGMENT_LAST); 83 | bool flush_success = bo_flush_and_destroy_context(context); 84 | ASSERT_TRUE(process_success); 85 | ASSERT_TRUE(flush_success); 86 | ASSERT_FALSE(has_errors()); 87 | ASSERT_STREQ(expected_output, buffer); 88 | free((void*)input_copy); 89 | } 90 | 91 | void assert_spanning_conversion(const char* input, int split_point, int expected_offset, const char* expected_output) 92 | { 93 | reset_errors(); 94 | char buffer[10000]; 95 | test_context test_context = new_test_context(buffer); 96 | char* input_copy = strdup(input); 97 | void* context = bo_new_context(&test_context, on_output, on_error); 98 | char* processed_to = process_and_terminate(context, input_copy, split_point, DATA_SEGMENT_STREAM); 99 | bool flush_success = bo_flush_and_destroy_context(context); 100 | ASSERT_TRUE(flush_success); 101 | ASSERT_TRUE(processed_to != NULL); 102 | char* expected_ptr = input_copy + expected_offset; 103 | ASSERT_EQ(expected_ptr, processed_to); 104 | ASSERT_FALSE(has_errors()); 105 | ASSERT_STREQ(expected_output, buffer); 106 | free((void*)input_copy); 107 | } 108 | 109 | void assert_spanning_continuation(const char* input, int split_point, int expected_offset, const char* expected_output) 110 | { 111 | reset_errors(); 112 | char buffer[10000]; 113 | test_context test_context = new_test_context(buffer); 114 | char* input_copy = strdup(input); 115 | void* context = bo_new_context(&test_context, on_output, on_error); 116 | char* processed_to = bo_process(context, input_copy, split_point, DATA_SEGMENT_STREAM); 117 | ASSERT_TRUE(processed_to != NULL); 118 | char* expected_ptr = input_copy + expected_offset; 119 | ASSERT_EQ(expected_ptr, processed_to); 120 | ASSERT_FALSE(has_errors()); 121 | 122 | memmove(input_copy, processed_to, strlen(processed_to) + 1); 123 | bool process_success = check_processed_all_data(context, input_copy, strlen(input_copy), DATA_SEGMENT_LAST); 124 | bool flush_success = bo_flush_and_destroy_context(context); 125 | ASSERT_TRUE(process_success); 126 | ASSERT_TRUE(flush_success); 127 | 128 | ASSERT_STREQ(expected_output, buffer); 129 | free((void*)input_copy); 130 | } 131 | 132 | void assert_failed_conversion(int buffer_length, const char* input) 133 | { 134 | reset_errors(); 135 | char buffer[10000]; 136 | test_context test_context = new_test_context(buffer); 137 | char* input_copy = strdup(input); 138 | void* context = bo_new_context(&test_context, on_output, on_error); 139 | bool process_success = check_processed_all_data(context, input_copy, strlen(input_copy), DATA_SEGMENT_LAST); 140 | bool flush_success = bo_flush_and_destroy_context(context); 141 | bool is_successful = process_success && flush_success; 142 | ASSERT_FALSE(is_successful); 143 | ASSERT_TRUE(has_errors()); 144 | free((void*)input_copy); 145 | } 146 | -------------------------------------------------------------------------------- /libbo/test/src/test_helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void assert_conversion(const char* input, const char* expected_output); 7 | 8 | void assert_spanning_conversion(const char* input, int split_point, int expected_offset, const char* expected_output); 9 | 10 | void assert_spanning_continuation(const char* input, int split_point, int expected_offset, const char* expected_output); 11 | 12 | void assert_failed_conversion(int buffer_length, const char* input); 13 | -------------------------------------------------------------------------------- /libbo/wip.md: -------------------------------------------------------------------------------- 1 | *(number): repeat previous value (number - 1) times 2 | --------------------------------------------------------------------------------