├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── presentation ├── array_diagram.svg ├── ben_notes.org ├── breadth_first_diagram.svg ├── bryce_tweet.png ├── constexpr_operator.png ├── constexpr_problem.png ├── constexpr_trace.png ├── constexpr_vector.png ├── cpp14_murmur.png ├── jason_notes.md ├── json_number.png ├── json_string.png ├── object_diagram.svg ├── presentation-inksaver.css ├── presentation.css ├── presentation.html ├── presentation.js ├── presentation.org ├── string_size_diagram.svg ├── submission.org └── title.png └── src ├── include ├── algorithms │ ├── cx_mod_seq.h │ └── cx_nonmod_seq.h ├── cx_algorithm.h ├── cx_iterator.h ├── cx_json_parser.h ├── cx_json_value.h ├── cx_map.h ├── cx_optional.h ├── cx_pair.h ├── cx_parser.h ├── cx_string.h └── cx_vector.h └── test ├── CMakeLists.txt ├── algorithm.cpp ├── json.cpp └── main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlinesLeft: false 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: Inline 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | AlwaysBreakTemplateDeclarations: false 19 | BinPackArguments: true 20 | BinPackParameters: true 21 | BraceWrapping: 22 | AfterClass: true 23 | AfterControlStatement: true 24 | AfterEnum: true 25 | AfterFunction: true 26 | AfterNamespace: true 27 | AfterObjCDeclaration: true 28 | AfterStruct: true 29 | AfterUnion: true 30 | BeforeCatch: true 31 | BeforeElse: true 32 | IndentBraces: false 33 | BreakBeforeBinaryOperators: All 34 | BreakBeforeBraces: Custom 35 | BreakBeforeTernaryOperators: true 36 | BreakConstructorInitializersBeforeComma: false 37 | ColumnLimit: 100 38 | CommentPragmas: '^ IWYU pragma:' 39 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 40 | ConstructorInitializerIndentWidth: 2 41 | ContinuationIndentWidth: 4 42 | Cpp11BracedListStyle: true 43 | DerivePointerAlignment: false 44 | DisableFormat: false 45 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 46 | IncludeCategories: 47 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 48 | Priority: 2 49 | - Regex: '^(<|"(gtest|isl|json)/)' 50 | Priority: 3 51 | - Regex: '.*' 52 | Priority: 1 53 | IndentCaseLabels: true 54 | IndentWidth: 2 55 | IndentWrappedFunctionNames: false 56 | KeepEmptyLinesAtTheStartOfBlocks: true 57 | MacroBlockBegin: '' 58 | MacroBlockEnd: '' 59 | MaxEmptyLinesToKeep: 1 60 | NamespaceIndentation: None 61 | ObjCBlockIndentWidth: 2 62 | ObjCSpaceAfterProperty: false 63 | ObjCSpaceBeforeProtocolList: true 64 | PenaltyBreakBeforeFirstCallParameter: 19 65 | PenaltyBreakComment: 300 66 | PenaltyBreakFirstLessLess: 120 67 | PenaltyBreakString: 1000 68 | PenaltyExcessCharacter: 1000000 69 | PenaltyReturnTypeOnItsOwnLine: 60 70 | PointerAlignment: Left 71 | ReflowComments: true 72 | SortIncludes: true 73 | SpaceAfterCStyleCast: false 74 | SpaceBeforeAssignmentOperators: true 75 | SpaceBeforeParens: ControlStatements 76 | SpaceInEmptyParentheses: false 77 | SpacesBeforeTrailingComments: 1 78 | SpacesInAngles: false 79 | SpacesInContainerLiterals: true 80 | SpacesInCStyleCastParentheses: false 81 | SpacesInParentheses: false 82 | SpacesInSquareBrackets: false 83 | Standard: Cpp11 84 | TabWidth: 4 85 | UseTab: Never 86 | ... 87 | 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | TAGS 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "presentation/reveal.js"] 2 | path = presentation/reveal.js 3 | url = git@github.com:hakimel/reveal.js.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8) 2 | project (constexpr-all-the-things) 3 | 4 | # Includes for this project 5 | include_directories ("${PROJECT_SOURCE_DIR}/src/include") 6 | 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 8 | 9 | # Default C++ standard: C++1z 10 | if(CXX_STD) 11 | else() 12 | set(CXX_STD 1z) 13 | endif() 14 | 15 | # Use local libc++ 16 | if($ENV{USE_LOCAL_LIBCXX}) 17 | message("Using local libc++ (assumed in /usr/local)") 18 | add_compile_options(-nostdinc++) 19 | add_compile_options(-I/usr/local/include/c++/v1) 20 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi -L/usr/local/lib") 21 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/usr/local/lib") 22 | endif() 23 | 24 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 25 | set(MY_CXX_FLAGS_LIST 26 | ) 27 | string(REPLACE ";" " " MY_CXX_FLAGS "${MY_CXX_FLAGS_LIST}") 28 | 29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MY_CXX_FLAGS}") 30 | else() 31 | set(MY_CXX_FLAGS_LIST 32 | -ftemplate-backtrace-limit=0 33 | -ffunction-sections 34 | -Wall -Wextra -Werror -pedantic-errors 35 | -Wno-gnu-string-literal-operator-template 36 | -Wcast-align 37 | -Wcast-qual 38 | -Wctor-dtor-privacy 39 | -Wdisabled-optimization 40 | -Wformat=2 41 | -Winit-self 42 | -Wmissing-include-dirs 43 | -Wold-style-cast 44 | -Woverloaded-virtual 45 | -Wredundant-decls 46 | -Wshadow 47 | -Wsign-conversion 48 | -Wsign-promo 49 | -Wstrict-overflow=5 50 | -Wswitch-default 51 | -Wundef 52 | ) 53 | string(REPLACE ";" " " MY_CXX_FLAGS "${MY_CXX_FLAGS_LIST}") 54 | 55 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++${CXX_STD} ${MY_CXX_FLAGS}") 56 | 57 | # Debug/Release 58 | set(CMAKE_CXX_FLAGS_DEBUG "-O0 -fno-inline -g3 -fstack-protector-all") 59 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -g0 -march=native -mtune=native -DNDEBUG") 60 | set(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage") 61 | endif() 62 | 63 | # Clang/GCC specifics 64 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 65 | if(SAN) 66 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined,integer -fno-omit-frame-pointer -fno-sanitize=unsigned-integer-overflow") 67 | endif() 68 | elseif(CMAKE_COMPILER_IS_GNUCXX) 69 | endif() 70 | 71 | add_subdirectory (src/test) 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Ben Deane & Jason Turner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # constexpr ALL the things 2 | 3 | constexpr experiments in search of a C++Now talk 4 | 5 | by Jason Turner & Ben Deane 6 | -------------------------------------------------------------------------------- /presentation/array_diagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 29 | 35 | 36 | 44 | 50 | 51 | 59 | 65 | 66 | 74 | 80 | 81 | 89 | 95 | 96 | 104 | 110 | 111 | 119 | 125 | 126 | 127 | 150 | 152 | 153 | 155 | image/svg+xml 156 | 158 | 159 | 160 | 161 | 162 | 167 | 175 | 183 | 191 | 199 | 207 | 215 | 226 | 237 | [1,2,5] 248 | 1 259 | [1,2] 270 | 2 281 | 3 292 | 4 303 | 0 314 | 1 325 | 2 336 | 3 347 | 4 358 | 5 369 | array 380 | 391 | array 402 | number 413 | number 424 | number 435 | number 446 | 452 | 458 | 464 | 470 | 476 | 477 | 478 | -------------------------------------------------------------------------------- /presentation/ben_notes.org: -------------------------------------------------------------------------------- 1 | * constexpr JSON parsing 2 | 3 | ** First attempt 4 | 5 | JSON_Value is a recursive template that decrements its Depth argument with each 6 | recursion, so it has a max depth. 7 | 8 | Parsed by JSON::value_recur. Methods in a struct can be mutually recursive, 9 | They are templates because they have to be (JSON_Value is). 10 | 11 | Recursive template instantiation must have a base case: 12 | value_recur::value_parser<0>. fail() (the parser that always fails) is an easy 13 | way to provide the right type. 14 | 15 | Major source of slowness is template depth. 16 | 17 | Wrapping the body of value_recur::value_parser in a lambda is a good compilation 18 | speed boost. Note the use of "if false (fail(...))" to help the compiler deduce 19 | the return type of the lambda... necessary to break the recursion otherwise 20 | resulting. 21 | 22 | Whitespace is handled in a disciplined way (we could sprinkle skip_whitespace() 23 | more or less everywhere and it would work... but I don't like that). So 24 | skip_whitespace() conventionally appears before each parse. i.e. before 25 | value_parser, before the commas that separate values in arrays and objects, 26 | before the colon that separates strings and values in object kv-pairs, and 27 | before the closing brackets/curly of an array/object. That suffices. 28 | 29 | ** Second attempt 30 | 31 | *** The Idea 32 | First, compute the total number of sub-values required by the JSON value 33 | (including itself) - hopefully this is a much faster operation than a full parse 34 | into objects. 35 | 36 | Then, use the size information to right-size an array of values. 37 | 38 | Then, re-parse the object into the array. Values which are themselves 39 | arrays/objects will point into the owning array somehow. 40 | 41 | *** Counting number of objects 42 | Easy, having solved JSON parsing. Instead of aggregating objects, we just sum up 43 | a size_t. 44 | 45 | Note: numobjects_recur is a template so that we can use auto (deduced return 46 | types) for the member functions. Otherwise mutual recursion doesn't work? But 47 | only a single template instantiation is necessary. 48 | 49 | Note in value_parser we had multiple fmap() calls combined in alternation to 50 | provide for different returns (true, false, null). Since the parsers for true, 51 | false and null are all of the same type, and the numobjects parser returns the 52 | same thing for each (i.e. 1), the alternation is pushed down, so we have fewer 53 | fmap() calls. We do still fmap() individually for string_parser and 54 | number_parser because they have different types: we can't alternate them 55 | directly. 56 | 57 | The number-of-objects literal _json_size uses the templated version of the UDL. 58 | We will want to use the return value as a template argument - since function 59 | arguments are not constexpr, we could not use the result of numobjects() called 60 | on a function argument as a template argument. So numobjects simply unpacks the 61 | template parameter pack into an initializer_list from which it makes a 62 | string_view. 63 | 64 | *** Building the non-recursive JSON value 65 | JSON_Value2 is not a template this time. (It still has max array and object sizes.) 66 | 67 | Hopefully this means faster compiles even though we parse the literal twice 68 | (once for size, once for real). We could introduce a straight-up max array size 69 | I suppose and not do the size parse... 70 | 71 | The UDL _json2 uses numobjects on its template arguments to find out the right 72 | total number of JSON values we need to build, then instantiates value2_recur 73 | which contains a vector. 74 | 75 | Like numobjects_recur, value2_recur is itself a template, so the member 76 | functions don't need to be. Member functions are static for ease of use with 77 | parser machinery (is this necessary? not sure). 78 | 79 | Constructing a value2_recur does the parsing, then _json2 simply returns the 80 | resulting vector. 81 | 82 | Each parse branch pushes a value into the vector we're building, and returns its 83 | index. 84 | 85 | Constructing arrays and objects is a little more involved. They both basically 86 | work the same way. Note that the opening bracket (or curly) parse is hoisted 87 | into the value_parser, and that both compound type parsers now have their bodies 88 | wrapped in lambdas. This is because we need to insert a top-level array or 89 | object value into the output vector, and we only want to do it if the opener 90 | parse succeeds. The lambda wrapping gives us the laziness we want. 91 | 92 | Constructing a JSON_Value2 - or a JSON_Value for that matter - as an empty 93 | array/object is not yet there, but an easy addition. Would make pushing the 94 | array/object value nicer. 95 | 96 | Discovery: compilers don't really like constexpr pointer trickery. I first tried 97 | making the array of values inside the JSON_Value2 use JSON_Value2* as its 98 | value_type, and pointing it directly to the appropriate siblings in the vec. No 99 | luck there - a one-way ticket to "value is not constexpr". (And besides, I 100 | realised, having internal pointers makes it not a value type any more - copying 101 | would be... interesting?) Abandoned that. 102 | 103 | So indexes it is. In particular, offsets - we know the index of the base array 104 | object, and we can accumulate and store the offsets of its children from it. 105 | JSON_Value2 therefore assumes it's in a vector of values (that we build) and 106 | performs arithmetic on its this pointer to return references to its children. 107 | Not pretty, but it works. (Probably there are safety issues also! Handle with 108 | care.) 109 | 110 | That's it. _json2 returns the vector of nested values, correctly sized. Since it 111 | is not recursive, JSON_Value2 can handle much deeper nesting than JSON_Value can. 112 | 113 | I'm tempted to think that perhaps this technique is further applicable to the 114 | insides of a JSON_Value2 - could we externalize the storage of the vector/map 115 | inside it? But perhaps not: rightsizing those containers would involve 116 | templating JSON_Value2... or type erasing it (what a concept, constexpr type 117 | erasure? I don't think it's possible yet - or maybe ever). 118 | 119 | But hm, if we could make the container ranges contiguous in the external 120 | storage, they could be represented as two indices. Because JSON values can be 121 | arbitrarily nested, this might be hard to do - some kind of breadth-first parse 122 | is indicated perhaps, to contiguous-ize siblings. 123 | 124 | There is probably a lot of cleanup to do to makes things nicer. But it's 2am 125 | here. So, future me and/or Jason, you just get these notes for now. 126 | 127 | *** Thoughts on the external storage technique 128 | External storage can be refined by wrapping the array and providing proxy 129 | accessors to the JSON::value(s) inside. This also simplifies the value 130 | interface, allowing removal of indexing operators, and we no longer store 131 | offsets: now we store real indices, because the wrapper is able to provide the 132 | array to index. 133 | 134 | The fact that JSON::value has its array/object types with indices into the 135 | external storage means that it is effectively read-only, at least as far as the 136 | tree structure goes. Strings, bools, numbers, nulls are still contained value 137 | types, so can be altered. 138 | 139 | At the cost of more parses, and the constraint of making contained strings read 140 | only, the string storage could also be externalized by providing a parser that 141 | returns the required string size for an object (to store any contained strings 142 | and keys). The string type would be stored as offset and length, and the 143 | to_String accessor could then return a string_view into the storage. 144 | 145 | The efficiency of external storage comes with the constraint of read-only 146 | accessors for some contained types. This could perhaps be alleviated by mutators 147 | that actually set other types (e.g. parse to a string_view, read string_view, on 148 | write, change to a string). Obviously constexpr parsing results in a read-only 149 | value at runtime, but runtime parsing could use this strategy for alternative 150 | writable types? 151 | 152 | ** third attempt - improvements and further thoughts 153 | 154 | The string size parser allows external storage of strings, meaning that only a 155 | static_string or string_view need be contained within the json value. 156 | 157 | The json value still contains 2 arbitrarily-sized "arrays" - the value array 158 | type and the value object type. These also need to be externalized. 159 | 160 | To externalize the arrays means to store offset + extent in the json value 161 | itself, which means that the storage must be contiguous in the external buffer. 162 | This means that the parse cannot output values depth-first: it must work 163 | breadth-first. Which means we need the ability to put to-be-parsed objects 164 | (children of the current parse) on a queue. We know an upper bound on queue size 165 | because we know the storage size required for the objects. We need some way to 166 | (cheaply?) skip over to-be-parsed objects - we need an object extent parser 167 | which will just return monostate and we can calculate the extent from the 168 | current position and the leftover position. 169 | 170 | If we have a way to cheaply skip to-be-parsed objects, perhaps we have the 171 | ability to do lazy parsing. 172 | 173 | *** Externalizing arrays 174 | The extent parser returns the string_view representing the value text. 175 | 176 | The value parser as a whole returns the used size of the external storage (i.e. 177 | the past-the-end index). 178 | 179 | When parsing a value, we need to know the used size of the external storage and 180 | the index of the current value we're parsing. Initially, these values are 1 and 181 | 0 (an empty value - note not an empty object/array/string/etc - is illegal). 182 | 183 | When we parse an array of values, the current value index becomes the array 184 | value. We then parse the extents of the subvalues, and effectively push_back 185 | unparsed values into the storage. Then we can go back to the array value and 186 | fill in its offset and extent. Then we can reparse all the stored subvalues in 187 | order, keeping track of the past-the-end index resulting from each parse. 188 | 189 | ** Surfacing parsing errors 190 | 191 | The best place to do this is in the numobjects parser, assuming that's the first 192 | pass through the value literal. 193 | 194 | A compile error can be produced by using throw, but it's hard to get an actual 195 | string in the compiler output (other than just showing the line of the error). 196 | 197 | There are a few different errors: 198 | - missing close ] on array 199 | - missing close } on object 200 | - wrong key type (not string) on object value 201 | - missing : separating key and value in object value 202 | 203 | Of these the first two are easy at least to indentify the point in the parsing 204 | machinery where they can be raised. The last two are harder to give errors for 205 | without compromising the ability to parse empty objects/arrays ({} or []). 206 | 207 | ** Final outcome 208 | 209 | The number-of-objects parser and the string-size parser have been merged 210 | together to obtain both values in one initial pass over the literal. 211 | 212 | (This revealed that structural bindings don't work with constexpr). 213 | 214 | Given that, two arrays can be sized properly to contain the totality of strings 215 | and objects. 216 | 217 | - Using the externalization scheme outlined above, unparsed objects are 218 | represented as string_views into the literal yet-to-be-parsed. 219 | - Boolean, null and number values are represented inline in the JSON value. 220 | - Strings are represented as string_views into the string storage. 221 | - Arrays are represented as a view (offset + extent) into the object storage. 222 | - Objects are represented as a view (offset + extent) into the object storage. 223 | The values referenced alternate as keys and values. The keys are themselves 224 | string values, so string_views into the string storage. The values are just 225 | values of whatever type. 226 | 227 | Results: 228 | - Parsing any value requires 2 passes. 229 | - For objects and arrays, further passes are required to obtain the extents of 230 | subvalues. 231 | - Values are sized exactly at compile time. 232 | - There are no coded restrictions on string length, array size or object size. 233 | (There may be compiler limitations.) 234 | 235 | *** Parsing an array example 236 | 237 | Input: [1, 2, [3]] 238 | String size: 0 239 | Number of values: 5 240 | Resulting value layout: 241 | 242 | |--------------+------------+------------+--------------+------------| 243 | | 0 | 1 | 2 | 3 | 4 | 244 | |--------------+------------+------------+--------------+------------| 245 | | Array {1, 3} | Number {1} | Number {2} | Array {4, 1} | Number {3} | 246 | |--------------+------------+------------+--------------+------------| 247 | | [...] | 1 | 2 | [...] | 3 | 248 | |--------------+------------+------------+--------------+------------| 249 | 250 | *** Parsing an object example 251 | 252 | Input: { "a":1, "b":true, "c":["hello"]} 253 | String size: 8 254 | Number of values: 8 255 | 256 | Resulting string layout: 257 | 258 | |---+---+---+---+---+---+---+---| 259 | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 260 | |---+---+---+---+---+---+---+---| 261 | | a | b | c | h | e | l | l | o | 262 | |---+---+---+---+---+---+---+---| 263 | 264 | Resulting value layout: 265 | 266 | |---------------+---------------+------------+--------------+----------------+--------------+--------------+--------------| 267 | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 268 | |---------------+---------------+------------+--------------+----------------+--------------+--------------+--------------| 269 | | Object {1, 6} | String {0, 1} | Number {1} | String {1,1} | Boolean {true} | String {2,1} | Array {7, 1} | String {3,5} | 270 | |---------------+---------------+------------+--------------+----------------+--------------+--------------+--------------| 271 | | {...} | "a" | 1 | "b" | true | "c" | [...] | "hello" | 272 | |---------------+---------------+------------+--------------+----------------+--------------+--------------+--------------| 273 | -------------------------------------------------------------------------------- /presentation/breadth_first_diagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 29 | 35 | 36 | 44 | 50 | 51 | 59 | 65 | 66 | 74 | 80 | 81 | 89 | 95 | 96 | 104 | 110 | 111 | 119 | 125 | 126 | 134 | 140 | 141 | 142 | 165 | 167 | 168 | 170 | image/svg+xml 171 | 173 | 174 | 175 | 176 | 177 | 182 | 190 | 198 | 206 | 214 | 222 | 230 | 241 | 252 | {1,3} 263 | {1,1} 274 | {4,6} 285 | {12,1} 296 | 0 307 | 1 318 | 2 329 | 3 340 | 4 351 | 5 362 | array 373 | 384 | unparsed 395 | unparsed 406 | unparsed 417 | 423 | 429 | [ 440 | 1 451 | , 462 | 473 | [ 484 | 2 495 | , 506 | 3 517 | ] 528 | , 539 | 4 550 | ] 561 | 570 | 579 | 588 | 597 | 606 | 615 | 624 | 633 | 642 | 651 | 660 | 669 | 678 | 687 | 692 | 697 | 702 | 1 713 | 3 724 | 2 735 | 741 | 742 | 743 | -------------------------------------------------------------------------------- /presentation/bryce_tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/bryce_tweet.png -------------------------------------------------------------------------------- /presentation/constexpr_operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/constexpr_operator.png -------------------------------------------------------------------------------- /presentation/constexpr_problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/constexpr_problem.png -------------------------------------------------------------------------------- /presentation/constexpr_trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/constexpr_trace.png -------------------------------------------------------------------------------- /presentation/constexpr_vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/constexpr_vector.png -------------------------------------------------------------------------------- /presentation/cpp14_murmur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/cpp14_murmur.png -------------------------------------------------------------------------------- /presentation/jason_notes.md: -------------------------------------------------------------------------------- 1 | # Implementation Notes 2 | 3 | * Earlier misunderstanding with compiler prevented use of `union` in a `constexpr` context. That was resolved. Getting initialization right can be "hard" 4 | * Without `constexpr` support for lambdas in C++17, much of this would be much harder 5 | * Many algorithms that are not currently `constexpr` could be made such. 6 | - `find_if` 7 | - `mismatch` 8 | - `equal` 9 | - `copy` 10 | - `move` (not the cast operator) - interesting side note, `std::make_move_iterator` *is* `constexpr` in c++17 11 | * `std::optional` 12 | - Gets the hard part right: it's trivially destructable if the contained type is 13 | - However, gets everything else wrong - non-`constexpr` copy/move/assignment 14 | - Making it functionally useless in a `constexpr` context 15 | * `std::variant` 16 | - See `std::optional` 17 | * `map` and `vector` is easy to implement, even with iterator support. The only thing that makes it hard is deciding the size ahead of time. 18 | * I personally found the `static_string` class for a fixed-size wrapper around a `char *` literal to be very helpful. It is like a `string_view` but with more restrictions so you know you can store it (I think). I believe there's a proposal floating around similar to this. 19 | 20 | # General Notes 21 | 22 | * Debugging issues is hard 23 | * Tests are surprisingly easy. There's no risk of something going wrong at runtime, if a test compiles, it worked! 24 | * What about use cases for mixed-mode `constexpr` with fixed-size storage that optionally expands (is Eric Neibler around? He worked on this with his string class) 25 | * Compile times are tricky to manage, but it could be worse. The hard part - which Ben will speak about - is building / flattening trees. Really this is not a `constexpr` issue, but a template issue in our cases. 26 | 27 | # Allocators 28 | 29 | * It should be possible to make a `constexpr` allocator, and containers that support both `constexpr` and not allocators 30 | 31 | This comes with some difficulties, however. 32 | 33 | * Placement `new` is not supported in `constexpr` (but this is something other people have been discussing) 34 | * But this is largely irrelevant anyhow, as with `constexpr` data the contained thing must have already been allocated anyhow 35 | * But this requires care to choose placement `new` when required, and not otherwise 36 | * Growing the container is difficult because there really does not exist scratch allocator space since we are working with a very limited size storage that must be known at compile time 37 | * This suggests some amount of awareness of the part of the container to allocate as much as it can up front, for a fixed-size allocator 38 | * Cleaning up becomes almost impossible because a non-trivial destructor is impossible. This would mean that we need some kind of non-`constexpr` allocator wrapper that can clean up allocated memory on exit 39 | * This should be possible, but adds some more complexity, but should work in the same way that `std::variant`'s `constexpr` destructor does 40 | * What would be awesome is if an empty, but user defined, destructor could be considered "trivial" for the purposes of `constexpr`. This would allow us to `if constexpr` out the body of the destructor when possible 41 | 42 | How to make a constexpr-safe destructor: 43 | 44 | ```cpp 45 | #include 46 | #include 47 | 48 | 49 | template 50 | struct CleanUp 51 | { 52 | ~CleanUp() { 53 | static_cast(this)->cleanup(); 54 | } 55 | }; 56 | 57 | template 58 | struct CleanUpTrivial 59 | 60 | { 61 | // nothing to do! 62 | }; 63 | 64 | template 65 | struct Container : std::conditional_t< 66 | std::is_trivially_destructible_v, 67 | CleanUpTrivial>, 68 | CleanUp> 69 | > 70 | { 71 | void cleanup() { 72 | // do cleanup stuff 73 | } 74 | }; 75 | 76 | int main() 77 | { 78 | static_assert(std::is_trivially_destructible_v>); 79 | static_assert(!std::is_trivially_destructible_v>>); 80 | } 81 | ``` 82 | 83 | 84 | # The Future 85 | 86 | * What about overloading on `constexpr` usage? 87 | * Can we remove the restriction on trivial destructors? 88 | * What is the status on allowing dynamic allocations? 89 | 90 | -------------------------------------------------------------------------------- /presentation/json_number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/json_number.png -------------------------------------------------------------------------------- /presentation/json_string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/json_string.png -------------------------------------------------------------------------------- /presentation/object_diagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 29 | 35 | 36 | 44 | 50 | 51 | 59 | 65 | 66 | 74 | 80 | 81 | 89 | 95 | 96 | 104 | 110 | 111 | 119 | 125 | 126 | 127 | 150 | 152 | 153 | 155 | image/svg+xml 156 | 158 | 159 | 160 | 161 | 162 | 167 | 175 | 183 | 191 | 199 | 207 | 218 | 229 | {1,4} 240 | {0,6} 251 | 2 262 | {6,8} 273 | 6 284 | 0 295 | 1 306 | 2 317 | 3 328 | 4 339 | object 350 | 361 | number 372 | string 383 | string 394 | number 405 | 411 | 417 | M 428 | c 439 | K 450 | e 461 | r 472 | n 483 | M 494 | c 505 | G 516 | o 527 | o 538 | h 549 | a 560 | n 571 | 580 | 589 | 598 | 607 | 616 | 625 | 634 | 643 | 652 | 661 | 670 | 679 | 688 | 697 | 702 | 707 | 1 718 | 3 729 | 730 | 731 | -------------------------------------------------------------------------------- /presentation/presentation-inksaver.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Inconsolata"; 3 | font-style: normal; 4 | font-weight: normal; 5 | src: local("Inconsolata"), url("http://themes.googleusercontent.com/static/fonts/inconsolata/v3/BjAYBlHtW3CJxDcjzrnZCIbN6UDyHWBl620a-IRfuBk.woff") format("woff"); 6 | } 7 | 8 | .reveal code { 9 | font-family: Inconsolata; 10 | text-transform: initial; 11 | color: rgb(231, 173, 82); 12 | } 13 | 14 | .reveal a { 15 | text-transform: initial; 16 | } 17 | 18 | .reveal pre { 19 | font-family: Inconsolata; 20 | font-size: 24px; 21 | } 22 | 23 | .org-src-container pre { 24 | padding: 20px; 25 | color: rgb(101,123,131); 26 | background-color: #fdf6e3; 27 | } 28 | 29 | #sec-title-slide h1, 30 | .reveal p, 31 | .reveal li, 32 | .reveal h1, 33 | .reveal h2, 34 | .reveal h3, 35 | .reveal h4 { 36 | color: black; 37 | } 38 | .reveal p code { 39 | background-color: white; 40 | } 41 | 42 | #sec-title-slide h2, 43 | #sec-title-slide h3 { 44 | font-size: 60px; 45 | color: black; 46 | } 47 | 48 | .reveal .slide-number { 49 | position: absolute; 50 | bottom: 20px; 51 | left: 20px; 52 | font-family: Inconsolata; 53 | font-size: 28px; 54 | font-weight: bold; 55 | color: rgb(231, 173, 82); 56 | background-color: transparent; 57 | } 58 | 59 | .reveal .controls { 60 | color: rgb(231, 173, 82); 61 | } 62 | 63 | .reveal .progress span { 64 | background: rgb(231, 173, 82); 65 | } 66 | 67 | .reveal a { 68 | color: rgb(231, 173, 82); 69 | } 70 | 71 | #left { 72 | left:-8.33%; 73 | text-align: left; 74 | float: left; 75 | width:50%; 76 | z-index:-10; 77 | } 78 | 79 | #right { 80 | left:31.25%; 81 | top: 75px; 82 | float: right; 83 | text-align: right; 84 | z-index:-10; 85 | width:50%; 86 | } 87 | -------------------------------------------------------------------------------- /presentation/presentation.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Inconsolata"; 3 | font-style: normal; 4 | font-weight: normal; 5 | src: local("Inconsolata"), url("http://themes.googleusercontent.com/static/fonts/inconsolata/v3/BjAYBlHtW3CJxDcjzrnZCIbN6UDyHWBl620a-IRfuBk.woff") format("woff"); 6 | } 7 | 8 | .reveal code { 9 | font-family: Inconsolata; 10 | text-transform: initial; 11 | color: rgb(231, 173, 82); 12 | } 13 | 14 | .reveal p code { 15 | background-color: #222222; 16 | } 17 | 18 | .reveal a { 19 | text-transform: initial; 20 | } 21 | 22 | .reveal pre { 23 | font-family: Inconsolata; 24 | font-size: 28px; 25 | } 26 | 27 | .org-src-container pre { 28 | padding: 20px; 29 | color: rgb(101,123,131); 30 | background-color: #fdf6e3; 31 | } 32 | 33 | .reveal h2, 34 | .reveal h3 { 35 | color: rgb(238, 238, 238); 36 | } 37 | 38 | #sec-title-slide h2, 39 | #sec-title-slide h3 { 40 | font-size: 60px; 41 | color: rgb(238, 238, 238); 42 | } 43 | 44 | .reveal .slide-number { 45 | position: absolute; 46 | bottom: 20px; 47 | left: 20px; 48 | font-family: Inconsolata; 49 | font-size: 28px; 50 | font-weight: bold; 51 | color: rgb(231, 173, 82); 52 | background-color: transparent; 53 | } 54 | 55 | .reveal .controls { 56 | color: rgb(231, 173, 82); 57 | } 58 | 59 | .reveal .progress span { 60 | background: rgb(231, 173, 82); 61 | } 62 | 63 | .reveal a { 64 | color: rgb(231, 173, 82); 65 | } 66 | 67 | #left { 68 | left:-8.33%; 69 | text-align: left; 70 | float: left; 71 | width:50%; 72 | z-index:-10; 73 | } 74 | 75 | #right { 76 | left:31.25%; 77 | top: 75px; 78 | float: right; 79 | text-align: right; 80 | z-index:-10; 81 | width:50%; 82 | } 83 | -------------------------------------------------------------------------------- /presentation/presentation.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | 'use strict'; 3 | 4 | window.addEventListener('load', function () { 5 | window.Reveal.addEventListener('slidechanged', function (event) { 6 | // event.previousSlide, event.currentSlide, event.indexh, event.indexv 7 | }); 8 | }); 9 | 10 | })(window); 11 | -------------------------------------------------------------------------------- /presentation/submission.org: -------------------------------------------------------------------------------- 1 | For submission: http://cppnow.org/submission/ 2 | 3 | * Speaker bios 4 | 5 | ** Jason Turner 6 | jason@emptycrate.com 7 | 8 | CppCast / C++ Weekly 9 | 10 | Jason is a C++ developer, trainer and speaker; host of C++Weekly 11 | https://www.youtube.com/c/JasonTurner-lefticus; co-host of CppCast 12 | http://cppcast.com; co-creator and maintainer of the embedded scripting language 13 | for C++, ChaiScript http://chaiscript.com; and author and curator of the 14 | forkable coding standards document http://cppbestpractices.com. 15 | 16 | Jason is the presenter of several previous C++Now and CppCon talks including 17 | CppCon 2016's plenary session, "Rich Code for Tiny Computers: a Simple C64 Game 18 | in C++17". 19 | 20 | ** Ben Deane 21 | bdeane@blizzard.com 22 | 23 | Blizzard Entertainment 24 | 25 | Ben has been writing games for over 20 years, and in C++ for most of that. He is 26 | currently a Principal Engineer at Blizzard Entertainment where he works on the 27 | Battle.net team. He's always looking for useful new techniques in C++, and he 28 | likes functional programming. 29 | 30 | Ben is the presenter of several previous C++Now and CppCon talks, most recently 31 | "Using Types Effectively" at CppCon 2016. 32 | 33 | * Title 34 | 35 | constexpr ALL the things! 36 | 37 | * Tags 38 | 39 | constexpr, zero-cost abstractions, compile-time computation, C++17, performance 40 | 41 | * Session type 42 | 43 | Tutorial 44 | 45 | * Session length 46 | 47 | Preferred: 90 mins 48 | Min/Max: 90 mins 49 | 50 | * Session level 51 | 52 | Intermediate/Advanced 53 | 54 | * Audience description 55 | 56 | Library authors, application developers 57 | 58 | * Abstract 59 | 60 | constexpr: in C++11, a curiosity; in C++14, viable for more uses; now with added 61 | power, in C++17 will it become an important tool in the programmer's toolkit? 62 | 63 | In this talk we will examine the possibilities and power of constexpr and 64 | explore what can (and what should) be done at compile-time with C++17. We'll 65 | present techniques for building constexpr data structures and algorithms, and 66 | look at what the standard provides and where it can improve. We'll also explore 67 | constexpr use of user defined literals for expressive compile-time abstractions. 68 | 69 | Compile-time computation offers perhaps the ultimate zero-cost abstraction, and 70 | this talk attempts to gauge the power available with C++17 constexpr. 71 | 72 | 73 | * Session Material 74 | 75 | Source Code 76 | 77 | 78 | * Outline 79 | 80 | ** Introduction 81 | - brief constexpr history & evolution 82 | - current constexpr concerns 83 | - the goal: a constexpr JSON UDL 84 | 85 | ** Representing JSON 86 | - constexpr strings 87 | - constexpr vectors 88 | - constexpr maps 89 | - algorithms and other STL shortcomings 90 | - a constexpr JSON value 91 | - constexpr allocator possibilities 92 | 93 | ** constexpr Parsing 94 | - simple parse functions 95 | - building parser combinators 96 | - parsing JSON, first cut 97 | 98 | ** Parsing Refinements 99 | - more complex parsing 100 | - multiple passes 101 | - improving compile times 102 | - removing storage and recursion limits 103 | 104 | ** Conclusion & Future Directions 105 | - constexpr constraints 106 | - containers and iterators 107 | - algorithms 108 | - compile-time performance 109 | 110 | * Video URLs 111 | 112 | Ben Deane - Using Types Effectively CppCon2016 - https://www.youtube.com/watch?v=ojZbFIQSdl8 113 | Jason Turner - Rich Code For Tiny Computer CppCon2016 - https://www.youtube.com/watch?v=zBkNBP00wJE 114 | 115 | Ben Deane & Jason Turner - constexpr ALL the Things! C++Now 2017 - https://www.youtube.com/watch?v=HMB9oXFobJc 116 | 117 | * Comments for the Programming Committee 118 | 119 | This talk was 90 minutes at C++Now; to fit the 1h slot we will condense some of 120 | the front and end matter and concentrate on the techniques of building data 121 | structures and parsing. And in our experience, CppCon talks run more quickly 122 | than C++Now with typically less frequent audience interaction. 123 | -------------------------------------------------------------------------------- /presentation/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lefticus/constexpr_all_the_things/2c2cd6b65ea2eb076195b088703914e633190903/presentation/title.png -------------------------------------------------------------------------------- /src/include/algorithms/cx_mod_seq.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cx 4 | { 5 | template 6 | constexpr OutputIt copy(InputIt first, InputIt last, 7 | OutputIt d_first) 8 | { 9 | while (first != last) { 10 | *d_first++ = *first++; 11 | } 12 | return d_first; 13 | } 14 | 15 | template 16 | constexpr OutputIt copy_if(InputIt first, InputIt last, 17 | OutputIt d_first, UnaryPredicate pred) 18 | { 19 | while (first != last) { 20 | if (pred(*first)) { 21 | *d_first++ = *first; 22 | } 23 | ++first; 24 | } 25 | return d_first; 26 | } 27 | 28 | template 29 | constexpr OutputIt copy_n(InputIt first, Size count, OutputIt result) 30 | { 31 | if (count > 0) { 32 | *result++ = *first; 33 | for (Size i = 1; i < count; ++i) { 34 | *result++ = *++first; 35 | } 36 | } 37 | return result; 38 | } 39 | 40 | template 41 | constexpr BidirIt2 copy_backward(BidirIt1 first, BidirIt1 last, BidirIt2 d_last) 42 | { 43 | while (first != last) { 44 | *(--d_last) = *(--last); 45 | } 46 | return d_last; 47 | } 48 | 49 | template 50 | constexpr OutputIt move(InputIt first, InputIt last, OutputIt d_first) 51 | { 52 | while (first != last) { 53 | *d_first++ = std::move(*first++); 54 | } 55 | return d_first; 56 | } 57 | 58 | template 59 | constexpr BidirIt2 move_backward(BidirIt1 first, 60 | BidirIt1 last, 61 | BidirIt2 d_last) 62 | { 63 | while (first != last) { 64 | *(--d_last) = std::move(*(--last)); 65 | } 66 | return d_last; 67 | } 68 | 69 | template 70 | constexpr void fill(ForwardIt first, ForwardIt last, const T& value) 71 | { 72 | for (; first != last; ++first) { 73 | *first = value; 74 | } 75 | } 76 | 77 | template 78 | constexpr OutputIt fill_n(OutputIt first, Size count, const T& value) 79 | { 80 | for (Size i = 0; i < count; i++) { 81 | *first++ = value; 82 | } 83 | return first; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/include/algorithms/cx_nonmod_seq.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cx_pair.h" 4 | 5 | #include 6 | 7 | namespace cx 8 | { 9 | // Necessary because C++17 does not contain a constexpr version of find_if 10 | // probably not for any technical reason 11 | template 12 | constexpr InputIt find_if(InputIt first, InputIt last, UnaryPredicate p) 13 | { 14 | for (; first != last; ++first) { 15 | if (p(*first)) { 16 | return first; 17 | } 18 | } 19 | return last; 20 | } 21 | 22 | // And the same for all the functions here... 23 | 24 | template 25 | constexpr InputIt find(InputIt first, InputIt last, const T& value) 26 | { 27 | for (; first != last; ++first) { 28 | if (*first == value) { 29 | return first; 30 | } 31 | } 32 | return last; 33 | } 34 | 35 | template 36 | constexpr InputIt find_if_not(InputIt first, InputIt last, UnaryPredicate p) 37 | { 38 | for (; first != last; ++first) { 39 | if (!p(*first)) { 40 | return first; 41 | } 42 | } 43 | return last; 44 | } 45 | 46 | template 47 | constexpr bool all_of(InputIt first, InputIt last, UnaryPredicate p) 48 | { 49 | return cx::find_if_not(first, last, p) == last; 50 | } 51 | 52 | template 53 | constexpr bool any_of(InputIt first, InputIt last, UnaryPredicate p) 54 | { 55 | return cx::find_if(first, last, p) != last; 56 | } 57 | 58 | template 59 | constexpr bool none_of(InputIt first, InputIt last, UnaryPredicate p) 60 | { 61 | return cx::find_if(first, last, p) == last; 62 | } 63 | 64 | template 65 | constexpr typename std::iterator_traits::difference_type 66 | count(InputIt first, InputIt last, const T& value) 67 | { 68 | typename std::iterator_traits::difference_type ret = 0; 69 | for (; first != last; ++first) { 70 | if (*first == value) { 71 | ret++; 72 | } 73 | } 74 | return ret; 75 | } 76 | 77 | template 78 | constexpr typename std::iterator_traits::difference_type 79 | count_if(InputIt first, InputIt last, UnaryPredicate p) 80 | { 81 | typename std::iterator_traits::difference_type ret = 0; 82 | for (; first != last; ++first) { 83 | if (p(*first)) { 84 | ret++; 85 | } 86 | } 87 | return ret; 88 | } 89 | 90 | template 91 | constexpr cx::pair mismatch(InputIt1 first1, InputIt1 last1, 92 | InputIt2 first2, InputIt2 last2) 93 | { 94 | while (first1 != last1 && first2 != last2 && *first1 == *first2) { 95 | ++first1, ++first2; 96 | } 97 | return cx::pair{first1, first2}; 98 | } 99 | 100 | template 101 | constexpr bool equal(InputIt1 first1, InputIt1 last1, 102 | InputIt2 first2, InputIt2 last2) 103 | { 104 | while (first1 != last1 && first2 != last2 && *first1 == *first2) { 105 | ++first1, ++first2; 106 | } 107 | return first1 == last1 && first2 == last2; 108 | } 109 | 110 | // for_each compiles as constexpr, but I can't see a use for it... we can't 111 | // carry a state inside the UnaryFunction if it's constexpr, and we can't 112 | // really do anything to a constexpr capture (see the useless test code) 113 | 114 | template 115 | constexpr UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f) 116 | { 117 | for (; first != last; ++first) { 118 | f(*first); 119 | } 120 | return f; 121 | } 122 | 123 | // for_each_n also compiles but is similarly useless as constexpr... an 124 | // expensive way to do something like std::next? 125 | 126 | template 127 | constexpr InputIt for_each_n(InputIt first, Size n, UnaryFunction f) 128 | { 129 | for (Size i = 0; i < n; ++first, (void) ++i) { 130 | f(*first); 131 | } 132 | return first; 133 | } 134 | 135 | template 136 | constexpr ForwardIt1 search(ForwardIt1 first, ForwardIt1 last, 137 | ForwardIt2 s_first, ForwardIt2 s_last) 138 | { 139 | for (; ; ++first) { 140 | ForwardIt1 it = first; 141 | for (ForwardIt2 s_it = s_first; ; ++it, ++s_it) { 142 | if (s_it == s_last) { 143 | return first; 144 | } 145 | if (it == last) { 146 | return last; 147 | } 148 | if (!(*it == *s_it)) { 149 | break; 150 | } 151 | } 152 | } 153 | } 154 | 155 | template 156 | constexpr ForwardIt1 find_end(ForwardIt1 first, ForwardIt1 last, 157 | ForwardIt2 s_first, ForwardIt2 s_last) 158 | { 159 | if (s_first == s_last) 160 | return last; 161 | ForwardIt1 result = last; 162 | while (1) { 163 | ForwardIt1 new_result = cx::search(first, last, s_first, s_last); 164 | if (new_result == last) { 165 | return result; 166 | } else { 167 | result = new_result; 168 | first = result; 169 | ++first; 170 | } 171 | } 172 | return result; 173 | } 174 | 175 | template 176 | constexpr InputIt find_first_of(InputIt first, InputIt last, 177 | ForwardIt s_first, ForwardIt s_last) 178 | { 179 | for (; first != last; ++first) { 180 | for (ForwardIt it = s_first; it != s_last; ++it) { 181 | if (*first == *it) { 182 | return first; 183 | } 184 | } 185 | } 186 | return last; 187 | } 188 | 189 | 190 | // this implementation of search_n (from cppreference.com) has a constexpr 191 | // problem - my version of GCC gives the error: constexpr loop iteration count 192 | // exceeds limit of 262144 (use -fconstexpr-loop-limit= to increase the limit) 193 | template 194 | constexpr ForwardIt bad_search_n(ForwardIt first, ForwardIt last, 195 | Size count, const T& value) 196 | { 197 | for(; first != last; ++first) { 198 | if (!(*first == value)) { 199 | continue; 200 | } 201 | 202 | ForwardIt candidate = first; 203 | Size cur_count = 0; 204 | 205 | while (true) { 206 | ++cur_count; 207 | if (cur_count == count) { 208 | // success 209 | return candidate; 210 | } 211 | ++first; 212 | if (first == last) { 213 | // exhausted the list 214 | return last; 215 | } 216 | if (!(*first == value)) { 217 | // too few in a row 218 | break; 219 | } 220 | } 221 | } 222 | return last; 223 | } 224 | 225 | // this implementation works - maybe the nested loop and/or continue of the 226 | // other version is confusing the compiler somehow? 227 | template 228 | constexpr ForwardIt search_n(ForwardIt first, ForwardIt last, 229 | Size count, const T& value) 230 | { 231 | for(; first != last; ++first) { 232 | ForwardIt candidate = first; 233 | Size cur_count = 0; 234 | 235 | while (*first == value) { 236 | ++cur_count; 237 | if (cur_count == count) { 238 | // success 239 | return candidate; 240 | } 241 | ++first; 242 | if (first == last) { 243 | // exhausted the list 244 | return last; 245 | } 246 | } 247 | } 248 | return last; 249 | } 250 | 251 | template 252 | constexpr ForwardIt adjacent_find(ForwardIt first, ForwardIt last) 253 | { 254 | if (first == last) { 255 | return last; 256 | } 257 | ForwardIt next = first; 258 | ++next; 259 | for (; next != last; ++next, ++first) { 260 | if (*first == *next) { 261 | return first; 262 | } 263 | } 264 | return last; 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /src/include/cx_algorithm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "algorithms/cx_nonmod_seq.h" 4 | #include "algorithms/cx_mod_seq.h" 5 | -------------------------------------------------------------------------------- /src/include/cx_iterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cx 4 | { 5 | 6 | // Possibly std::back_insert_iterator could be made mostly constexpr? 7 | 8 | template 9 | struct back_insert_iterator 10 | { 11 | constexpr explicit back_insert_iterator(Container& c) 12 | : m_c(c) {} 13 | 14 | constexpr back_insert_iterator& operator=(const typename Container::value_type& value) { 15 | m_c.push_back(value); 16 | return *this; 17 | } 18 | 19 | constexpr back_insert_iterator& operator*() { return *this; } 20 | constexpr back_insert_iterator& operator++() { return *this; } 21 | constexpr back_insert_iterator& operator++(int) { return *this; } 22 | 23 | Container& m_c; 24 | }; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/include/cx_json_parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace JSON 16 | { 17 | using namespace cx; 18 | using namespace cx::parser; 19 | 20 | //---------------------------------------------------------------------------- 21 | // JSON value parsers 22 | 23 | // parse a JSON boolean value 24 | 25 | constexpr auto bool_parser() 26 | { 27 | using namespace std::literals; 28 | return fmap([] (std::string_view) { return true; }, 29 | make_string_parser("true"sv)) 30 | | fmap([] (std::string_view) { return false; }, 31 | make_string_parser("false"sv)); 32 | } 33 | 34 | // parse a JSON null value 35 | 36 | constexpr auto null_parser() 37 | { 38 | using namespace std::literals; 39 | return fmap([] (std::string_view) { return std::monostate{}; }, 40 | make_string_parser("null"sv)); 41 | } 42 | 43 | // parse a JSON number 44 | 45 | constexpr auto number_parser() 46 | { 47 | constexpr auto neg_parser = option('+', make_char_parser('-')); 48 | constexpr auto integral_parser = 49 | combine(neg_parser, 50 | fmap([] (char) { return 0; }, make_char_parser('0')) | int1_parser(), 51 | [] (char sign, int i) { return sign == '+' ? i : -i; }); 52 | 53 | constexpr auto frac_parser = make_char_parser('.') < int0_parser(); 54 | 55 | constexpr auto mantissa_parser = combine( 56 | integral_parser, option(0, frac_parser), 57 | [] (int i, int f) -> double { 58 | double d = 0; 59 | while (f > 0) { 60 | d += f % 10; 61 | d /= 10; 62 | f /= 10; 63 | } 64 | return i + d; 65 | }); 66 | 67 | constexpr auto e_parser = make_char_parser('e') | make_char_parser('E'); 68 | constexpr auto sign_parser = make_char_parser('+') | neg_parser; 69 | constexpr auto exponent_parser = 70 | bind(e_parser < sign_parser, 71 | [] (const char sign, const auto& sv) { 72 | return fmap([sign] (int j) { return sign == '+' ? j : -j; }, 73 | int0_parser())(sv); 74 | }); 75 | 76 | return combine( 77 | mantissa_parser, option(0, exponent_parser), 78 | [] (double mantissa, int exp) { 79 | if (exp > 0) { 80 | while (exp--) { 81 | mantissa *= 10; 82 | } 83 | } else { 84 | while (exp++) { 85 | mantissa /= 10; 86 | } 87 | } 88 | return mantissa; 89 | }); 90 | } 91 | 92 | // --------------------------------------------------------------------------- 93 | // parsing JSON strings 94 | 95 | // parse a JSON string char 96 | 97 | // When parsing a JSON string, in general multiple chars in the input may 98 | // result in different chars in the output (for example, an escaped char, or a 99 | // unicode code point) - which means we can't just return part of the input as 100 | // a sub-string_view: we need to actually build a string. So we will use 101 | // cx::string<4> as the return type of the char parsers to allow for the max 102 | // utf-8 conversion (note that all char parsers return the same thing so that 103 | // we can use the alternation combinator), and we will build up a cx::string<> 104 | // as the return type of the string parser. 105 | 106 | // if a char is escaped, simply convert it to the appropriate thing 107 | constexpr auto convert_escaped_char(char c) 108 | { 109 | switch (c) { 110 | case 'b': return '\b'; 111 | case 'f': return '\f'; 112 | case 'n': return '\n'; 113 | case 'r': return '\r'; 114 | case 't': return '\t'; 115 | default: return c; 116 | } 117 | } 118 | 119 | // convert a unicode code point to utf-8 120 | constexpr auto to_utf8(uint32_t hexcode) 121 | { 122 | cx::basic_string s; 123 | if (hexcode <= 0x7f) { 124 | s.push_back(static_cast(hexcode)); 125 | } else if (hexcode <= 0x7ff) { 126 | s.push_back(static_cast(0xC0 | (hexcode >> 6))); 127 | s.push_back(static_cast(0x80 | (hexcode & 0x3f))); 128 | } else if (hexcode <= 0xffff) { 129 | s.push_back(static_cast(0xE0 | (hexcode >> 12))); 130 | s.push_back(static_cast(0x80 | ((hexcode >> 6) & 0x3f))); 131 | s.push_back(static_cast(0x80 | (hexcode & 0x3f))); 132 | } else if (hexcode <= 0x10ffff) { 133 | s.push_back(static_cast(0xF0 | (hexcode >> 18))); 134 | s.push_back(static_cast(0x80 | ((hexcode >> 12) & 0x3f))); 135 | s.push_back(static_cast(0x80 | ((hexcode >> 6) & 0x3f))); 136 | s.push_back(static_cast(0x80 | (hexcode & 0x3f))); 137 | } 138 | return s; 139 | } 140 | 141 | constexpr auto to_hex(char c) 142 | { 143 | if (c >= '0' && c <= '9') return static_cast(c - '0'); 144 | if (c >= 'a' && c <= 'f') return static_cast(c - 'a' + 10); 145 | return static_cast(c - 'A' + 10); 146 | } 147 | 148 | constexpr auto unicode_point_parser() 149 | { 150 | using namespace std::literals; 151 | constexpr auto p = 152 | make_char_parser('\\') < 153 | make_char_parser('u') < 154 | exactly_n( 155 | one_of("0123456789abcdefABCDEF"sv), 156 | 4, 0u, 157 | [] (uint16_t hexcode, char c) -> uint16_t { 158 | return (hexcode << 4) + to_hex(c); 159 | }); 160 | return fmap(to_utf8, p); 161 | } 162 | 163 | constexpr auto string_char_parser() 164 | { 165 | using namespace std::literals; 166 | constexpr auto slash_parser = make_char_parser('\\'); 167 | constexpr auto special_char_parser = 168 | make_char_parser('"') 169 | | make_char_parser('\\') 170 | | make_char_parser('/') 171 | | make_char_parser('b') 172 | | make_char_parser('f') 173 | | make_char_parser('n') 174 | | make_char_parser('r') 175 | | make_char_parser('t'); 176 | constexpr auto escaped_char_parser = fmap( 177 | convert_escaped_char, slash_parser < special_char_parser); 178 | constexpr auto p = escaped_char_parser | none_of("\\\""sv); 179 | 180 | return fmap([] (auto c) { 181 | cx::basic_string s; 182 | s.push_back(c); 183 | return s; 184 | }, p) | unicode_point_parser(); 185 | } 186 | 187 | // parse a JSON string 188 | 189 | // See the comment about the char parsers above. Here we accumulate a 190 | // cx::string<> (which is arbitrarily sized at 32). 191 | 192 | constexpr auto string_parser() 193 | { 194 | constexpr auto quote_parser = make_char_parser('"'); 195 | constexpr auto str_parser = 196 | many(string_char_parser(), 197 | cx::string{}, 198 | [] (auto acc, const auto& str) { 199 | cx::copy(str.cbegin(), str.cend(), cx::back_insert_iterator(acc)); 200 | return acc; 201 | }); 202 | return quote_parser < str_parser > quote_parser; 203 | } 204 | 205 | // --------------------------------------------------------------------------- 206 | // parse the size of a JSON string 207 | 208 | constexpr std::size_t to_utf8_count(uint32_t hexcode) 209 | { 210 | if (hexcode <= 0x7f) { 211 | return 1; 212 | } else if (hexcode <= 0x7ff) { 213 | return 2; 214 | } else if (hexcode <= 0xffff) { 215 | return 3; 216 | } 217 | return 4; 218 | } 219 | 220 | constexpr auto unicode_point_count_parser() 221 | { 222 | using namespace std::literals; 223 | constexpr auto p = 224 | make_char_parser('\\') < 225 | make_char_parser('u') < 226 | exactly_n( 227 | one_of("0123456789abcdefABCDEF"sv), 228 | 4, 0u, 229 | [] (uint16_t hexcode, char c) -> uint16_t { 230 | return (hexcode << 4) + to_hex(c); 231 | }); 232 | return fmap(to_utf8_count, p); 233 | } 234 | 235 | constexpr auto string_char_count_parser() 236 | { 237 | using namespace std::literals; 238 | constexpr auto slash_parser = make_char_parser('\\'); 239 | constexpr auto special_char_parser = 240 | make_char_parser('"') 241 | | make_char_parser('\\') 242 | | make_char_parser('/') 243 | | make_char_parser('b') 244 | | make_char_parser('f') 245 | | make_char_parser('n') 246 | | make_char_parser('r') 247 | | make_char_parser('t'); 248 | constexpr auto escaped_char_parser = fmap( 249 | convert_escaped_char, slash_parser < special_char_parser); 250 | constexpr auto p = escaped_char_parser | none_of("\\\""sv); 251 | 252 | return fmap([] (auto) -> std::size_t { return 1; }, p) 253 | | unicode_point_count_parser(); 254 | } 255 | 256 | constexpr auto string_size_parser() 257 | { 258 | constexpr auto quote_parser = make_char_parser('"'); 259 | constexpr auto str_parser = 260 | many(string_char_count_parser(), std::size_t{}, std::plus<>{}); 261 | return quote_parser < str_parser > quote_parser; 262 | } 263 | 264 | //---------------------------------------------------------------------------- 265 | // JSON number-of-objects-required and string-size-required parser 266 | // 267 | // An array is 1 + number of objects in the array 268 | // An object is 1 + number of objects in the object + 1 for each key 269 | // Anything else is just 1 270 | // 271 | // A string is its own size 272 | // An array is the sum of value sizes within it 273 | // An object is the sum of key sizes and value sizes within it 274 | // Anything else is just 0 275 | 276 | struct Sizes 277 | { 278 | std::size_t num_objects; 279 | std::size_t string_size; 280 | }; 281 | 282 | constexpr Sizes operator+(const Sizes& x, const Sizes& y) 283 | { 284 | return {x.num_objects + y.num_objects, 285 | x.string_size + y.string_size}; 286 | } 287 | 288 | template 289 | struct sizes_recur 290 | { 291 | // Because the lambda returned from value_parser has no captures, it can 292 | // decay to a function pointer. This helps clang deduce the return type of 293 | // value_parser here. 294 | using P = auto (*)(const parse_input_t&) -> parse_result_t; 295 | 296 | // parse a JSON value 297 | 298 | static constexpr auto value_parser() -> P 299 | { 300 | using namespace std::literals; 301 | return [] (const auto& sv) -> parse_result_t { 302 | constexpr auto p = 303 | fmap([] (auto) { return Sizes{1, 0}; }, 304 | make_string_parser("true"sv) | make_string_parser("false"sv) 305 | | make_string_parser("null"sv)) 306 | | fmap([] (auto) { return Sizes{1, 0}; }, 307 | number_parser()) 308 | | fmap([] (std::size_t len) { return Sizes{1, len}; }, 309 | string_size_parser()) 310 | | array_parser() 311 | | object_parser(); 312 | return (skip_whitespace() < p)(sv); 313 | }; 314 | } 315 | 316 | // parse a JSON array 317 | 318 | static constexpr auto array_parser() 319 | { 320 | return make_char_parser('[') < 321 | separated_by_val(value_parser(), 322 | skip_whitespace() < make_char_parser(','), 323 | Sizes{1, 0}, std::plus<>{}) 324 | > skip_whitespace() 325 | > (make_char_parser(']') | fail(']', [] { throw "expected ]"; })); 326 | } 327 | 328 | // parse a JSON object 329 | 330 | static constexpr auto key_value_parser() 331 | { 332 | // would like to give sensible errors here about expecting ':' or 333 | // expecting string key, but this parser needs to be able to fail without 334 | // error to deal with empty objects ({}) 335 | constexpr auto p = 336 | skip_whitespace() < string_size_parser() > skip_whitespace() > make_char_parser(':'); 337 | return bind(p, 338 | [&] (std::size_t len, const auto& sv) { 339 | return fmap( 340 | [len] (const Sizes& s) { 341 | return Sizes{s.num_objects + 1, s.string_size + len}; }, 342 | value_parser())(sv); 343 | }); 344 | } 345 | 346 | static constexpr auto object_parser() 347 | { 348 | return make_char_parser('{') < 349 | separated_by_val(key_value_parser(), 350 | skip_whitespace() < make_char_parser(','), 351 | Sizes{1, 0}, std::plus<>{}) 352 | > skip_whitespace() 353 | > (make_char_parser('}') | fail('}', [] { throw "expected }"; })); 354 | } 355 | 356 | }; 357 | 358 | // provide the sizes parser outside the struct qualification 359 | constexpr auto sizes_parser = sizes_recur<>::value_parser; 360 | 361 | template 362 | constexpr auto sizes() 363 | { 364 | std::initializer_list il{Cs...}; 365 | return sizes_parser()(std::string_view(il.begin(), il.size()))->first; 366 | } 367 | 368 | //---------------------------------------------------------------------------- 369 | // JSON extent parser 370 | // Returns the string_view that represents the value literal 371 | 372 | template 373 | struct extent_recur 374 | { 375 | // As with sizes_recur, the lambda returned from value_parser has no 376 | // captures, so it can decay to a function pointer. This helps clang deduce 377 | // the return type of value_parser here. 378 | using P = auto (*)(const parse_input_t&) -> parse_result_t; 379 | 380 | // parse a JSON value 381 | 382 | static constexpr auto value_parser() -> P 383 | { 384 | using namespace std::literals; 385 | using R = parse_result_t; 386 | return [] (const auto& sv) -> R { 387 | constexpr auto p = 388 | fmap([] (auto) { return std::monostate{}; }, 389 | make_string_parser("true"sv) | make_string_parser("false"sv) 390 | | make_string_parser("null"sv)) 391 | | fmap([] (auto) { return std::monostate{}; }, 392 | number_parser()) 393 | | fmap([] (auto) { return std::monostate{}; }, 394 | string_size_parser()) 395 | | array_parser() 396 | | object_parser(); 397 | auto r = (skip_whitespace() < p)(sv); 398 | if (!r) return std::nullopt; 399 | std::size_t len = static_cast(r->second.data() - sv.data()); 400 | return R(cx::make_pair(std::string_view{sv.data(), len}, r->second)); 401 | }; 402 | } 403 | 404 | // parse a JSON array 405 | 406 | static constexpr auto array_parser() 407 | { 408 | return make_char_parser('[') < 409 | separated_by_val(value_parser(), 410 | skip_whitespace() < make_char_parser(','), 411 | std::monostate{}, [] (auto x, auto) { return x; }) 412 | > skip_whitespace() > make_char_parser(']'); 413 | } 414 | 415 | // parse a JSON object 416 | 417 | static constexpr auto key_value_parser() 418 | { 419 | return skip_whitespace() < string_size_parser() 420 | < skip_whitespace() < make_char_parser(':') < value_parser(); 421 | } 422 | 423 | static constexpr auto object_parser() 424 | { 425 | return make_char_parser('{') < 426 | separated_by_val(key_value_parser(), 427 | skip_whitespace() < make_char_parser(','), 428 | std::monostate{}, [] (auto x, auto) { return x; }) 429 | > skip_whitespace() > make_char_parser('}'); 430 | } 431 | 432 | }; 433 | 434 | // provide the extent parser outside the struct qualification 435 | constexpr auto extent_parser = extent_recur<>::value_parser; 436 | 437 | //---------------------------------------------------------------------------- 438 | // JSON parser 439 | 440 | // parse into a vector 441 | // return the past-the-end index into the vector resulting from parsing 442 | 443 | template 444 | struct value_recur 445 | { 446 | using V = value[NObj]; 447 | using S = cx::basic_string; 448 | 449 | // Here, value_parser returns a lambda with captures, so it can't decay to a 450 | // function pointer type. clang cannot deduce the return type of 451 | // value_parser properly when it uses a lambda, but we can make our own 452 | // "lambda object" and then it works... 453 | #ifdef __clang__ 454 | struct lambda 455 | { 456 | constexpr lambda(V& v_, S& s_, const std::size_t& idx_, const std::size_t& max_) 457 | : v(v_), s(s_), idx(idx_), max(max_) 458 | {} 459 | 460 | constexpr auto operator()(const parse_input_t& sv) -> parse_result_t 461 | { 462 | using namespace std::literals; 463 | const auto p = 464 | fmap([&v = v, idx = idx, max = max] (auto) { v[idx].to_Boolean() = true; return max; }, 465 | make_string_parser("true"sv)) 466 | | fmap([&v = v, idx = idx, max = max] (auto) { v[idx].to_Boolean() = false; return max; }, 467 | make_string_parser("false"sv)) 468 | | fmap([&v = v, idx = idx, max = max] (auto) { v[idx].to_Null(); return max; }, 469 | make_string_parser("null"sv)) 470 | | fmap([&v = v, idx = idx, max = max] (double d) { v[idx].to_Number() = d; return max; }, 471 | number_parser()) 472 | | fmap([&v = v, idx = idx, max = max] (const value::ExternalView& ev) { 473 | v[idx].to_String() = ev; 474 | return max; 475 | }, string_parser(s)) 476 | | (make_char_parser('[') < array_parser(v, s, idx, max)) 477 | | (make_char_parser('{') < object_parser(v, s, idx, max)) 478 | ; 479 | return (skip_whitespace() < p)(sv); 480 | } 481 | 482 | V& v; 483 | S& s; 484 | const std::size_t& idx; 485 | const std::size_t& max; 486 | }; 487 | #endif // __clang__ 488 | 489 | // parse a JSON value 490 | 491 | // note idx and max are const refs here because otherwise they are "not a 492 | // constant expression"? 493 | // idx is the index of the thing we're currently parsing 494 | // max is the one-past-the-end index into the vector (ie. where it can grow) 495 | static constexpr auto value_parser(V& v, S& s, 496 | const std::size_t& idx, 497 | const std::size_t& max) 498 | { 499 | #ifdef __clang__ 500 | return lambda(v, s, idx, max); 501 | #else 502 | using namespace std::literals; 503 | return [&] (const auto& sv) -> parse_result_t { 504 | const auto p = 505 | fmap([&] (auto) { v[idx].to_Boolean() = true; return max; }, 506 | make_string_parser("true"sv)) 507 | | fmap([&] (auto) { v[idx].to_Boolean() = false; return max; }, 508 | make_string_parser("false"sv)) 509 | | fmap([&] (auto) { v[idx].to_Null(); return max; }, 510 | make_string_parser("null"sv)) 511 | | fmap([&] (double d) { v[idx].to_Number() = d; return max; }, 512 | number_parser()) 513 | | fmap([&] (const value::ExternalView& ev) { 514 | v[idx].to_String() = ev; 515 | return max; 516 | }, string_parser(s)) 517 | | (make_char_parser('[') < array_parser(v, s, idx, max)) 518 | | (make_char_parser('{') < object_parser(v, s, idx, max)) 519 | ; 520 | return (skip_whitespace() < p)(sv); 521 | }; 522 | #endif 523 | } 524 | 525 | // a string parser which accumulates its string into external storage - we 526 | // use the max value of size_t as a sentinel to lazily evaluate the 527 | // beginning of the string... I'm not proud 528 | 529 | static constexpr auto string_parser(S& s) 530 | { 531 | constexpr auto quote_parser = make_char_parser('"'); 532 | const auto str_parser = 533 | many(string_char_parser(), 534 | value::ExternalView{std::numeric_limits::max(), 0}, 535 | [&] (auto ev, const auto& str) { 536 | auto offset = ev.offset == std::numeric_limits::max() 537 | ? s.size() : ev.offset; 538 | cx::copy(str.cbegin(), str.cend(), cx::back_insert_iterator(s)); 539 | return value::ExternalView{offset, ev.extent + str.size()}; 540 | }); 541 | return quote_parser < str_parser > quote_parser; 542 | } 543 | 544 | // parse a JSON array 545 | 546 | static constexpr auto array_parser(V& v, S& s, 547 | const std::size_t& idx, 548 | const std::size_t& max) 549 | { 550 | using R = parse_result_t; 551 | return [&] (const auto& sv) -> R { 552 | // parse the extent of each subvalue and put it into storage to 553 | // be parsed later 554 | const auto p = separated_by_val( 555 | extent_parser(), skip_whitespace() < make_char_parser(','), 556 | std::size_t{max}, [&] (std::size_t i, const std::string_view& extent) { 557 | v[i].to_Unparsed() = extent; 558 | return i+1; 559 | }) 560 | > skip_whitespace() > make_char_parser(']'); 561 | auto r = p(sv); 562 | if (!r) return std::nullopt; 563 | // set up the array value 564 | v[idx].to_Array() = 565 | value::ExternalView{ max, r->first - max }; 566 | // now properly parse the subvalues 567 | std::size_t m = r->first; 568 | for (auto i = max; i < r->first; ++i) { 569 | auto subr = value_parser(v, s, i, m)(v[i].to_Unparsed()); 570 | if (!subr) return std::nullopt; 571 | m = subr->first; 572 | } 573 | // finally return the current extent of the storage 574 | return R(cx::make_pair(m, r->second)); 575 | }; 576 | } 577 | 578 | // parse a JSON object 579 | 580 | struct kv_extent 581 | { 582 | value::ExternalView key; 583 | std::string_view val; 584 | }; 585 | 586 | // parse a key-value pair as the string key and the extent of the value 587 | static constexpr auto key_value_extent_parser(S& s) 588 | { 589 | const auto p = 590 | skip_whitespace() < string_parser(s) > skip_whitespace() > make_char_parser(':'); 591 | return bind(p, 592 | [] (const value::ExternalView& key, const auto& sv) { 593 | return fmap([&] (const std::string_view& val) { 594 | return kv_extent{ key, val }; 595 | }, 596 | extent_parser())(sv); 597 | }); 598 | } 599 | 600 | static constexpr auto object_parser(V& v, S& s, 601 | const std::size_t& idx, 602 | const std::size_t& max) 603 | { 604 | using R = parse_result_t; 605 | return [&] (const auto& sv) -> R { 606 | // parse the extent of each subvalue and put it into storage to 607 | // be parsed later 608 | const auto p = separated_by_val( 609 | key_value_extent_parser(s), skip_whitespace() < make_char_parser(','), 610 | std::size_t{max}, [&] (std::size_t i, const auto& kve) { 611 | v[i].to_String() = kve.key; 612 | v[i+1].to_Unparsed() = kve.val; 613 | return i+2; 614 | }) > skip_whitespace() > make_char_parser('}'); 615 | auto r = p(sv); 616 | if (!r) return std::nullopt; 617 | // set up the object value 618 | v[idx].to_Object() = 619 | value::ExternalView{ max, r->first - max }; 620 | // now properly parse the subvalues 621 | std::size_t m = r->first; 622 | for (auto i = max; i < r->first; i += 2) { 623 | auto subr = value_parser(v, s, i+1, m)(v[i+1].to_Unparsed()); 624 | if (!subr) return std::nullopt; 625 | m = subr->first; 626 | } 627 | // finally return the current extent of the storage 628 | return R(cx::make_pair(m, r->second)); 629 | }; 630 | } 631 | 632 | }; 633 | 634 | // A value_wrapper wraps a parsed JSON::value and contains the externalized 635 | // storage. 636 | template 637 | struct value_wrapper 638 | { 639 | constexpr void construct(parse_input_t s) 640 | { 641 | value_recur::value_parser( 642 | object_storage, string_storage, 0, 1)(s); 643 | } 644 | 645 | constexpr operator value_proxy, 646 | const cx::basic_string>() const { 647 | return value_proxy{0, object_storage, string_storage}; 648 | } 649 | constexpr operator value_proxy, 650 | cx::basic_string>() { 651 | return value_proxy{0, object_storage, string_storage}; 652 | } 653 | 654 | using Value_Proxy = value_proxy>; 656 | 657 | template ::value, int> = 0> 659 | constexpr auto operator[](const K& s) const { 660 | return Value_Proxy{0, object_storage, string_storage}[s]; 661 | } 662 | template ::value, int> = 0> 664 | constexpr auto operator[](const K& s) { 665 | return Value_Proxy{0, object_storage, string_storage}[s]; 666 | } 667 | constexpr auto object_Size() const { 668 | return Value_Proxy{0, object_storage, string_storage}.object_Size(); 669 | } 670 | 671 | constexpr auto operator[](std::size_t idx) const { 672 | return Value_Proxy{0, object_storage, string_storage}[idx]; 673 | } 674 | constexpr auto operator[](std::size_t idx) { 675 | return Value_Proxy{0, object_storage, string_storage}[idx]; 676 | } 677 | constexpr auto array_Size() const { 678 | return Value_Proxy{0, object_storage, string_storage}.array_Size(); 679 | } 680 | 681 | constexpr auto is_Null() const { return object_storage[0].is_Null(); } 682 | 683 | constexpr decltype(auto) to_String() const { 684 | return Value_Proxy{0, object_storage, string_storage}.to_String(); 685 | } 686 | constexpr decltype(auto) to_String() { 687 | return Value_Proxy{0, object_storage, string_storage}.to_String(); 688 | } 689 | constexpr auto string_Size() const { 690 | return Value_Proxy{0, object_storage, string_storage}.string_Size(); 691 | } 692 | 693 | constexpr decltype(auto) to_Number() const { return object_storage[0].to_Number(); } 694 | constexpr decltype(auto) to_Number() { return object_storage[0].to_Number(); } 695 | 696 | constexpr decltype(auto) to_Boolean() const { return object_storage[0].to_Boolean(); } 697 | constexpr decltype(auto) to_Boolean() { return object_storage[0].to_Boolean(); } 698 | 699 | constexpr auto num_objects() const { return NumObjects; } 700 | constexpr auto string_size() const { return StringSize; } 701 | 702 | private: 703 | // when this is a cx::vector, GCC ICEs... 704 | value object_storage[NumObjects]; 705 | cx::basic_string string_storage; 706 | }; 707 | 708 | namespace literals 709 | { 710 | 711 | // why cannot we get regular literal operator template here? 712 | template 713 | constexpr auto operator "" _json() 714 | { 715 | const std::initializer_list il{Ts...}; 716 | // I tried using structured bindings here, but g++ says: 717 | // "error: decomposition declaration cannot be declared 'constexpr'" 718 | constexpr auto S = sizes(); 719 | auto val = value_wrapper{}; 720 | val.construct(std::string_view(il.begin(), il.size())); 721 | return val; 722 | } 723 | 724 | } 725 | 726 | } 727 | -------------------------------------------------------------------------------- /src/include/cx_json_value.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cx_algorithm.h" 4 | #include "cx_map.h" 5 | #include "cx_string.h" 6 | #include "cx_vector.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace JSON 12 | { 13 | 14 | // --------------------------------------------------------------------------- 15 | // non-recursive definition of a JSON value 16 | 17 | struct value 18 | { 19 | struct ExternalView 20 | { 21 | std::size_t offset; 22 | std::size_t extent; 23 | }; 24 | 25 | union Data 26 | { 27 | std::string_view unparsed; 28 | ExternalView external; 29 | double number; 30 | bool boolean; 31 | 32 | constexpr Data() : boolean(false) {} 33 | constexpr Data(const std::string_view& sv) : unparsed(sv) {} 34 | constexpr Data(bool b) : boolean(b) {} 35 | constexpr Data(double d) : number(d) {} 36 | constexpr Data(const ExternalView& ev) : external(ev) {} 37 | }; 38 | 39 | enum class Type 40 | { 41 | Unparsed, 42 | String, 43 | Number, 44 | Array, 45 | Object, 46 | Boolean, 47 | Null 48 | }; 49 | 50 | Type type = Type::Null; 51 | Data data{}; 52 | 53 | constexpr value() = default; 54 | 55 | constexpr value(const std::string_view& extent) 56 | : type(Type::Unparsed), data(extent) 57 | {} 58 | 59 | constexpr value(const double t_d) { 60 | to_Number() = t_d; 61 | } 62 | 63 | constexpr value(const bool t_b) { 64 | to_Boolean() = t_b; 65 | } 66 | 67 | constexpr value(const std::monostate) { 68 | type = Type::Null; 69 | } 70 | 71 | constexpr value(ExternalView t_s) { 72 | to_String() = std::move(t_s); 73 | } 74 | 75 | constexpr decltype(auto) to_Object() const 76 | { 77 | assert_type(Type::Object); 78 | return (data.external); 79 | } 80 | 81 | constexpr decltype(auto) to_Object() 82 | { 83 | if (type != Type::Object) { 84 | type = Type::Object; 85 | data = Data(ExternalView{0,0}); 86 | } 87 | return (data.external); 88 | } 89 | 90 | // objects are stored contiguously in storage as alternate string, value, 91 | // string, value, etc 92 | constexpr auto object_Size() const 93 | { 94 | assert_type(Type::Object); 95 | return data.external.extent / 2; 96 | } 97 | 98 | constexpr void assert_type(Type t) const 99 | { 100 | if (type != t) throw std::runtime_error("Incorrect type"); 101 | } 102 | 103 | constexpr bool is_Null() const 104 | { 105 | return type == Type::Null; 106 | } 107 | 108 | constexpr void to_Null() 109 | { 110 | if (type != Type::Null) { 111 | type = Type::Null; 112 | data = Data{}; 113 | } 114 | } 115 | 116 | constexpr const std::string_view& to_Unparsed() const 117 | { 118 | assert_type(Type::Unparsed); 119 | return data.unparsed; 120 | } 121 | 122 | constexpr std::string_view& to_Unparsed() 123 | { 124 | if (type != Type::Unparsed) { 125 | type = Type::Unparsed; 126 | data = Data(std::string_view{}); 127 | } 128 | return data.unparsed; 129 | } 130 | 131 | constexpr const ExternalView& to_Array() const 132 | { 133 | assert_type(Type::Array); 134 | return data.external; 135 | } 136 | 137 | constexpr ExternalView& to_Array() 138 | { 139 | if (type != Type::Array) { 140 | type = Type::Array; 141 | data = Data(ExternalView{0,0}); 142 | } 143 | return data.external; 144 | } 145 | 146 | constexpr auto array_Size() const 147 | { 148 | assert_type(Type::Array); 149 | return data.external.extent; 150 | } 151 | 152 | constexpr const ExternalView& to_String() const 153 | { 154 | assert_type(Type::String); 155 | return data.external; 156 | } 157 | 158 | constexpr ExternalView& to_String() 159 | { 160 | if (type != Type::String) { 161 | type = Type::String; 162 | data = Data(ExternalView{0,0}); 163 | } 164 | return data.external; 165 | } 166 | 167 | constexpr auto string_Size() const 168 | { 169 | assert_type(Type::String); 170 | return data.external.extent; 171 | } 172 | 173 | constexpr const double& to_Number() const 174 | { 175 | assert_type(Type::Number); 176 | return data.number; 177 | } 178 | 179 | constexpr double& to_Number() 180 | { 181 | if (type != Type::Number) { 182 | type = Type::Number; 183 | data = Data(0.0); 184 | } 185 | return data.number; 186 | } 187 | 188 | constexpr const bool& to_Boolean() const 189 | { 190 | assert_type(Type::Boolean); 191 | return data.boolean; 192 | } 193 | 194 | constexpr bool& to_Boolean() 195 | { 196 | if (type != Type::Boolean) { 197 | type = Type::Boolean; 198 | data = Data(false); 199 | } 200 | return data.boolean; 201 | } 202 | }; 203 | 204 | // A value_proxy provides an interface to the value, decoupling the external 205 | // storage. 206 | template 207 | struct value_proxy 208 | { 209 | // Using a transparent comparison operator will allow us to index by any 210 | // kind of "string" (cx::static_string, cx::string, etc) 211 | struct StringCompare 212 | { 213 | template 214 | constexpr bool operator()(const S1& s1, const S2& s2) { 215 | return cx::equal(std::cbegin(s1), std::cend(s1), 216 | std::cbegin(s2), std::cend(s2)); 217 | } 218 | 219 | // const char arrays are tricky because their length includes the null 220 | // terminator, so we use N-1 as the length 221 | template 222 | constexpr bool operator()(const char (&s2)[N], const S1& s1) { 223 | return cx::equal(std::cbegin(s1), std::cend(s1), 224 | s2, &s2[N-1]); 225 | } 226 | template 227 | constexpr bool operator()(const S1& s1, const char (&s2)[N]) { 228 | return cx::equal(std::cbegin(s1), std::cend(s1), 229 | s2, &s2[N-1]); 230 | } 231 | }; 232 | 233 | // The use of "notfound" in these functions serves no purpose; but if the 234 | // throw expression is not guarded, it is evaluated, even though the 235 | // function returns early and it should be unevaluated. And the final return 236 | // here is because clang complains about control flow reaching the end of 237 | // this non-void function, although it never will. 238 | template ::value, int> = 0> 240 | constexpr auto operator[](const K& s) const { 241 | const auto& ext = object_storage[index].to_Object(); 242 | bool notfound = true; 243 | for (auto i = ext.offset; i < ext.offset + ext.extent; i += 2) { 244 | const auto& str = object_storage[i].to_String(); 245 | cx::static_string k { &string_storage[str.offset], str.extent }; 246 | if (StringCompare{}(k, s)) 247 | return value_proxy{i+1, object_storage, string_storage}; 248 | } 249 | if (notfound) throw std::runtime_error("Key not found in object"); 250 | return value_proxy{0, object_storage, string_storage}; 251 | } 252 | template ::value, int> = 0> 254 | constexpr auto operator[](const K& s) { 255 | const auto& ext = object_storage[index].to_Object(); 256 | bool notfound = true; 257 | for (auto i = ext.offset; i < ext.offset + ext.extent; i += 2) { 258 | const auto& str = object_storage[i].to_String(); 259 | cx::static_string k { &string_storage[str.offset], str.extent }; 260 | if (StringCompare{}(k, s)) 261 | return value_proxy{i+1, object_storage, string_storage}; 262 | } 263 | if (notfound) throw std::runtime_error("Key not found in object"); 264 | return value_proxy{0, object_storage, string_storage}; 265 | } 266 | constexpr auto object_Size() const { 267 | return object_storage[index].object_Size(); 268 | } 269 | 270 | constexpr auto operator[](std::size_t idx) const { 271 | auto& ext = object_storage[index].to_Array(); 272 | if (idx > ext.extent) throw std::runtime_error("Index past end of array"); 273 | return value_proxy{ext.offset + idx, object_storage, string_storage}; 274 | } 275 | constexpr auto operator[](std::size_t idx) { 276 | auto& ext = object_storage[index].to_Array(); 277 | if (idx > ext.extent) throw std::runtime_error("Index past end of array"); 278 | return value_proxy{ext.offset + idx, object_storage, string_storage}; 279 | } 280 | constexpr auto array_Size() const { 281 | return object_storage[index].array_Size(); 282 | } 283 | 284 | constexpr auto is_Null() const { return object_storage[index].is_Null(); } 285 | 286 | constexpr auto to_String() const { 287 | auto s = object_storage[index].to_String(); 288 | return cx::static_string { &string_storage[s.offset], s.extent }; 289 | } 290 | constexpr auto to_String() { 291 | auto s = object_storage[index].to_String(); 292 | return cx::static_string { &string_storage[s.offset], s.extent }; 293 | } 294 | constexpr auto string_Size() const { 295 | return object_storage[index].string_Size(); 296 | } 297 | 298 | constexpr decltype(auto) to_Number() const { return object_storage[index].to_Number(); } 299 | constexpr decltype(auto) to_Number() { return object_storage[index].to_Number(); } 300 | 301 | constexpr decltype(auto) to_Boolean() const { return object_storage[index].to_Boolean(); } 302 | constexpr decltype(auto) to_Boolean() { return object_storage[index].to_Boolean(); } 303 | 304 | std::size_t index; 305 | T& object_storage; 306 | S& string_storage; 307 | }; 308 | 309 | template 310 | value_proxy(std::size_t i, const value(&v)[NumObjects], 311 | const cx::basic_string& s) 312 | -> value_proxy>; 314 | 315 | template 316 | value_proxy(std::size_t i, value(&v)[NumObjects], 317 | cx::basic_string& s) 318 | -> value_proxy>; 320 | 321 | } 322 | -------------------------------------------------------------------------------- /src/include/cx_map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cx_algorithm.h" 4 | #include "cx_pair.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cx 13 | { 14 | // detect is_transparent in the Compare type to allow the template find 15 | // operations to take part in overloads 16 | template 17 | using is_transparent = typename T::is_transparent; 18 | template 19 | struct has_is_transparent : public std::false_type {}; 20 | template 21 | struct has_is_transparent>> : public std::true_type {}; 22 | 23 | template > 25 | class map 26 | { 27 | public: 28 | constexpr auto begin() const { return m_data.begin(); } 29 | constexpr auto begin() { return m_data.begin(); } 30 | 31 | // We would have prefered to use `std::next`, however it does not seem 32 | // to be enabled for constexpr use for std::array in this version 33 | // of gcc. TODO: reevaluate this 34 | constexpr auto end() const { return m_data.begin() + m_size; } 35 | constexpr auto end() { return m_data.begin() + m_size; } 36 | 37 | constexpr auto cbegin() const { return m_data.begin(); } 38 | constexpr auto cend() const { return m_data.begin() + m_size; } 39 | 40 | constexpr auto find(const Key &k) 41 | { 42 | return find_impl(*this, k); 43 | } 44 | 45 | template ::value, int> = 0> 47 | constexpr auto find(const K &k) 48 | { 49 | return find_impl(*this, k); 50 | } 51 | 52 | constexpr auto find(const Key &k) const 53 | { 54 | return find_impl(*this, k); 55 | } 56 | 57 | template ::value, int> = 0> 59 | constexpr auto find(const K &k) const 60 | { 61 | return find_impl(*this, k); 62 | } 63 | 64 | constexpr const Value &at(const Key &k) const 65 | { 66 | const auto itr = find(k); 67 | if (itr != end()) { return itr->second; } 68 | else { throw std::range_error("Key not found"); } 69 | } 70 | 71 | template ::value, int> = 0> 73 | constexpr const Value &at(const K &k) const 74 | { 75 | const auto itr = find(k); 76 | if (itr != end()) { return itr->second; } 77 | else { throw std::range_error("Key not found"); } 78 | } 79 | 80 | constexpr Value &operator[](const Key &k) { 81 | const auto itr = find(k); 82 | if (itr == end()) { 83 | auto &data = m_data[m_size++]; 84 | data.first = k; 85 | return data.second; 86 | } else { 87 | return itr->second; 88 | } 89 | } 90 | 91 | template ::value, int> = 0> 93 | constexpr Value &operator[](const K &k) { 94 | const auto itr = find(k); 95 | if (itr == end()) { 96 | auto &data = m_data[m_size++]; 97 | data.first = k; 98 | return data.second; 99 | } else { 100 | return itr->second; 101 | } 102 | } 103 | 104 | constexpr auto size() const { return m_size; } 105 | constexpr auto empty() const { return m_size == 0; } 106 | 107 | private: 108 | template 109 | static constexpr auto find_impl(This &&t, const Key &k) 110 | { 111 | return cx::find_if(t.begin(), t.end(), 112 | [&k] (const auto &d) { return Compare{}(d.first, k); }); 113 | } 114 | 115 | template ::value, int> = 0> 117 | static constexpr auto find_impl(This &&t, const K &k) 118 | { 119 | return cx::find_if(t.begin(), t.end(), 120 | [&k] (const auto &d) { return Compare{}(d.first, k); }); 121 | } 122 | 123 | std::array, Size> m_data{}; // for constexpr use, the std::array must be initialized 124 | std::size_t m_size{0}; 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /src/include/cx_optional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cx 7 | { 8 | template 9 | struct optional 10 | { 11 | using value_type = T; 12 | 13 | constexpr optional(std::nullopt_t) {} 14 | constexpr explicit optional(const T& t) 15 | : m_valid(true), m_t(t) 16 | {} 17 | 18 | constexpr explicit operator bool() const { return m_valid; } 19 | 20 | constexpr const T* operator->() const { return &m_t; } 21 | constexpr T* operator->() { return &m_t; } 22 | constexpr const T& operator*() const { return m_t; } 23 | constexpr T& operator*() { return m_t; } 24 | 25 | private: 26 | bool m_valid = false; 27 | T m_t{}; 28 | }; 29 | 30 | template 31 | constexpr auto make_optional(T&& t) 32 | { 33 | return optional(std::forward(t)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/include/cx_pair.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // necessary because std::pair seems to be disabling the move assignment for 4 | // some unclear reason. It's not clear if this is a bug in the gcc impl 5 | 6 | namespace cx 7 | { 8 | template 9 | struct pair 10 | { 11 | using first_type = First; 12 | using second_type = Second; 13 | First first; 14 | Second second; 15 | }; 16 | 17 | template 18 | constexpr auto make_pair(First f, Second s) 19 | { 20 | return pair{f, s}; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/include/cx_parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace cx 14 | { 15 | namespace parser 16 | { 17 | 18 | //---------------------------------------------------------------------------- 19 | // A parser for T is a callable thing that takes a "string" and returns 20 | // (optionally) an T plus the "leftover string". 21 | using parse_input_t = std::string_view; 22 | template 23 | using parse_result_t = cx::optional>; 24 | 25 | // Get various types out of a parser 26 | template 27 | using opt_pair_parse_t = std::result_of_t; 28 | template 29 | using pair_parse_t = typename opt_pair_parse_t

::value_type; 30 | template 31 | using parse_t = typename pair_parse_t

::first_type; 32 | 33 | //---------------------------------------------------------------------------- 34 | // parsers as monads 35 | 36 | // fmap a function into a parser. F :: parse_t

-> a 37 | template 38 | constexpr auto fmap(F&& f, P&& p) 39 | { 40 | using R = parse_result_t)>>; 41 | return [f = std::forward(f), 42 | p = std::forward

(p)] (parse_input_t i) -> R { 43 | const auto r = p(i); 44 | if (!r) return std::nullopt; 45 | return R(cx::make_pair(f(r->first), r->second)); 46 | }; 47 | } 48 | 49 | // bind a function into a parser. F :: (parse_t

, parse_input_t) -> a 50 | template 51 | constexpr auto bind(P&& p, F&& f) 52 | { 53 | using R = std::result_of_t, parse_input_t)>; 54 | return [=] (parse_input_t i) -> R { 55 | const auto r = p(i); 56 | if (!r) return std::nullopt; 57 | return f(r->first, r->second); 58 | }; 59 | } 60 | 61 | // lift a value into a parser 62 | template 63 | constexpr auto lift(T&& t) 64 | { 65 | return [t = std::forward(t)] (parse_input_t s) { 66 | return parse_result_t( 67 | cx::make_pair(std::move(t), s)); 68 | }; 69 | } 70 | 71 | // the parser that always fails 72 | template 73 | constexpr auto fail(T) 74 | { 75 | return [=] (parse_input_t) -> parse_result_t { 76 | return std::nullopt; 77 | }; 78 | } 79 | 80 | template 81 | constexpr auto fail(T, ErrorFn f) 82 | { 83 | return [=] (parse_input_t) -> parse_result_t { 84 | f(); 85 | return std::nullopt; 86 | }; 87 | } 88 | 89 | //---------------------------------------------------------------------------- 90 | // parser combinators 91 | 92 | // alternation: try the first parser, and if it fails, do the second. They 93 | // must both return the same type. 94 | template , parse_t>>> 96 | constexpr auto operator|(P1&& p1, P2&& p2) { 97 | return [=] (parse_input_t i) { 98 | const auto r1 = p1(i); 99 | if (r1) return r1; 100 | return p2(i); 101 | }; 102 | } 103 | 104 | // accumulation: run two parsers in sequence and combine the outputs using the 105 | // given function. Both parsers must succeed. 106 | template , parse_t)>> 108 | constexpr auto combine(P1&& p1, P2&& p2, F&& f) { 109 | return [=] (parse_input_t i) -> parse_result_t { 110 | const auto r1 = p1(i); 111 | if (!r1) return std::nullopt; 112 | const auto r2 = p2(r1->second); 113 | if (!r2) return std::nullopt; 114 | return parse_result_t( 115 | cx::make_pair(f(r1->first, r2->first), r2->second)); 116 | }; 117 | } 118 | 119 | // for convenience, overload < and > to mean sequencing parsers 120 | // and returning the result of the chosen one 121 | template , typename = parse_t> 123 | constexpr auto operator<(P1&& p1, P2&& p2) { 124 | return combine(std::forward(p1), 125 | std::forward(p2), 126 | [] (auto, const auto& r) { return r; }); 127 | } 128 | 129 | template , typename = parse_t> 131 | constexpr auto operator>(P1&& p1, P2&& p2) { 132 | return combine(std::forward(p1), 133 | std::forward(p2), 134 | [] (const auto& r, auto) { return r; }); 135 | } 136 | 137 | // apply ? (zero or one) of a parser 138 | template 139 | constexpr auto zero_or_one(P&& p) 140 | { 141 | using R = parse_result_t; 142 | return [p = std::forward

(p)] (parse_input_t s) -> R { 143 | const auto r = p(s); 144 | if (r) return r; 145 | return R(cx::make_pair(parse_input_t(s.data(), 0), s)); 146 | }; 147 | } 148 | 149 | namespace detail 150 | { 151 | template 152 | constexpr cx::pair accumulate_parse( 153 | parse_input_t s, P&& p, T init, F&& f) 154 | { 155 | while (!s.empty()) { 156 | const auto r = p(s); 157 | if (!r) return cx::make_pair(init, s); 158 | init = f(init, r->first); 159 | s = r->second; 160 | } 161 | return cx::make_pair(init, s); 162 | } 163 | 164 | template 165 | constexpr cx::pair accumulate_n_parse( 166 | parse_input_t s, P&& p, std::size_t n, T init, F&& f) 167 | { 168 | while (n != 0) { 169 | const auto r = p(s); 170 | if (!r) return cx::make_pair(init, s); 171 | init = f(init, r->first); 172 | s = r->second; 173 | --n; 174 | } 175 | return cx::make_pair(init, s); 176 | } 177 | } 178 | 179 | // apply * (zero or more) of a parser, accumulating the results according to a 180 | // function F. F :: T -> (parse_t

, parse_input_t) -> T 181 | template 182 | constexpr auto many(P&& p, T&& init, F&& f) 183 | { 184 | return [p = std::forward

(p), init = std::forward(init), 185 | f = std::forward(f)] (parse_input_t s) { 186 | return parse_result_t( 187 | detail::accumulate_parse(s, p, init, f)); 188 | }; 189 | } 190 | 191 | // apply + (one or more) of a parser, accumulating the results according to a 192 | // function F. F :: T -> (parse_t

, parse_input_t) -> T 193 | template 194 | constexpr auto many1(P&& p, T&& init, F&& f) 195 | { 196 | return [p = std::forward

(p), init = std::forward(init), 197 | f = std::forward(f)] (parse_input_t s) -> parse_result_t { 198 | const auto r = p(s); 199 | if (!r) return std::nullopt; 200 | return parse_result_t( 201 | detail::accumulate_parse(r->second, p, f(init, r->first), f)); 202 | }; 203 | } 204 | 205 | // apply a parser exactly n times, accumulating the results according to a 206 | // function F. F :: T -> (parse_t

, parse_input_t) -> T 207 | template 208 | constexpr auto exactly_n(P&& p, std::size_t n, T&& init, F&& f) 209 | { 210 | return [p = std::forward

(p), n, init = std::forward(init), 211 | f = std::forward(f)] (parse_input_t s) { 212 | return parse_result_t( 213 | detail::accumulate_n_parse(s, p, n, init, f)); 214 | }; 215 | } 216 | 217 | // try to apply a parser, and if it fails, return a default 218 | template > 219 | constexpr auto option(T&& def, P&& p) 220 | { 221 | return [p = std::forward

(p), 222 | def = std::forward(def)] (parse_input_t s) { 223 | const auto r = p(s); 224 | if (r) return r; 225 | return parse_result_t(cx::make_pair(def, s)); 226 | }; 227 | } 228 | 229 | // apply many1 instances of a parser, with another parser interleaved. 230 | // accumulate the results with the given function. 231 | template 232 | constexpr auto separated_by(P1&& p1, P2&& p2, F&& f) 233 | { 234 | using T = parse_t; 235 | return [p1 = std::forward(p1), p2 = std::forward(p2), 236 | f = std::forward(f)] ( 237 | parse_input_t s) -> parse_result_t { 238 | const auto r = p1(s); 239 | if (!r) return std::nullopt; 240 | const auto p = p2 < p1; 241 | return parse_result_t( 242 | detail::accumulate_parse(r->second, p, r->first, f)); 243 | }; 244 | } 245 | 246 | // apply many instances of a parser, with another parser interleaved. 247 | // accumulate the results with the given function. 248 | template 249 | constexpr auto separated_by(P1&& p1, P2&& p2, F0&& init, F&& f) 250 | { 251 | using R = parse_result_t>; 252 | return [p1 = std::forward(p1), p2 = std::forward(p2), 253 | init = std::forward(init), f = std::forward(f)] ( 254 | parse_input_t s) -> R { 255 | const auto r = p1(s); 256 | if (!r) return R(cx::make_pair(init(), s)); 257 | const auto p = p2 < p1; 258 | return R(detail::accumulate_parse(r->second, p, f(init(), r->first), f)); 259 | }; 260 | } 261 | 262 | // apply many instances of a parser, with another parser interleaved. 263 | // accumulate the results with the given function. 264 | template 265 | constexpr auto separated_by_val(P1&& p1, P2&& p2, T&& init, F&& f) 266 | { 267 | using R = parse_result_t>; 268 | return [p1 = std::forward(p1), p2 = std::forward(p2), 269 | init = std::forward(init), f = std::forward(f)] ( 270 | parse_input_t s) -> R { 271 | const auto r = p1(s); 272 | if (!r) return R(cx::make_pair(init, s)); 273 | const auto p = p2 < p1; 274 | return R(detail::accumulate_parse(r->second, p, f(init, r->first), f)); 275 | }; 276 | } 277 | 278 | //---------------------------------------------------------------------------- 279 | // parsers for various types 280 | 281 | // parse a given char 282 | constexpr auto make_char_parser(char c) 283 | { 284 | return [=] (parse_input_t s) -> parse_result_t { 285 | if (s.empty() || s[0] != c) return std::nullopt; 286 | return parse_result_t( 287 | cx::make_pair(c, parse_input_t(s.data()+1, s.size()-1))); 288 | }; 289 | } 290 | 291 | // parse one of a set of chars 292 | constexpr auto one_of(std::string_view chars) 293 | { 294 | return [=] (parse_input_t s) -> parse_result_t { 295 | if (s.empty()) return std::nullopt; 296 | // basic_string_view::find is supposed to be constexpr, but no... 297 | auto j = cx::find(chars.cbegin(), chars.cend(), s[0]); 298 | if (j != chars.cend()) { 299 | return parse_result_t( 300 | cx::make_pair(s[0], parse_input_t(s.data()+1, s.size()-1))); 301 | } 302 | return std::nullopt; 303 | }; 304 | } 305 | 306 | // parse none of a set of chars 307 | constexpr auto none_of(std::string_view chars) 308 | { 309 | return [=] (parse_input_t s) -> parse_result_t { 310 | if (s.empty()) return std::nullopt; 311 | // basic_string_view::find is supposed to be constexpr, but no... 312 | auto j = cx::find(chars.cbegin(), chars.cend(), s[0]); 313 | if (j == chars.cend()) { 314 | return parse_result_t( 315 | cx::make_pair(s[0], parse_input_t(s.data()+1, s.size()-1))); 316 | } 317 | return std::nullopt; 318 | }; 319 | } 320 | 321 | // parse a given string 322 | constexpr auto make_string_parser(std::string_view str) 323 | { 324 | return [=] (parse_input_t s) -> parse_result_t { 325 | const auto p = cx::mismatch(str.cbegin(), str.cend(), s.cbegin(), s.cend()); 326 | if (p.first == str.cend()) { 327 | // std::distance is not constexpr? 328 | const auto len = static_cast( 329 | s.cend() - p.second); 330 | return parse_result_t( 331 | cx::make_pair(str, parse_input_t(p.second, len))); 332 | } 333 | return std::nullopt; 334 | }; 335 | } 336 | 337 | // parse an int (may begin with 0) 338 | constexpr auto int0_parser() 339 | { 340 | using namespace std::literals; 341 | return many1(one_of("0123456789"sv), 342 | 0, 343 | [] (int acc, char c) { return (acc*10) + (c-'0'); }); 344 | } 345 | 346 | // parse an int (may not begin with 0) 347 | constexpr auto int1_parser() 348 | { 349 | using namespace std::literals; 350 | return bind(one_of("123456789"sv), 351 | [] (char x, parse_input_t rest) { 352 | return many(one_of("0123456789"sv), 353 | static_cast(x - '0'), 354 | [] (int acc, char c) { return (acc*10) + (c-'0'); })(rest); 355 | }); 356 | } 357 | 358 | // a parser for skipping whitespace 359 | constexpr auto skip_whitespace() 360 | { 361 | constexpr auto ws_parser = 362 | make_char_parser(' ') 363 | | make_char_parser('\t') 364 | | make_char_parser('\n') 365 | | make_char_parser('\r'); 366 | return many(ws_parser, std::monostate{}, [] (auto m, auto) { return m; }); 367 | } 368 | 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/include/cx_string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "cx_vector.h" 8 | 9 | namespace cx 10 | { 11 | struct static_string 12 | { 13 | template 14 | constexpr static_string(const char (&str)[N]) 15 | : m_size(N-1), m_data(&str[0]) 16 | { 17 | } 18 | constexpr static_string(const char* str, std::size_t s) 19 | : m_size(s), m_data(str) 20 | { 21 | } 22 | 23 | constexpr static_string() = default; 24 | 25 | constexpr size_t size() const { 26 | return m_size; 27 | } 28 | 29 | constexpr const char *c_str() const { 30 | return m_data; 31 | } 32 | 33 | constexpr const char *begin() const { 34 | return m_data; 35 | } 36 | 37 | constexpr const char *end() const { 38 | return m_data + m_size; 39 | } 40 | 41 | std::size_t m_size{0}; 42 | const char *m_data = nullptr; 43 | }; 44 | 45 | constexpr bool operator==(const static_string &x, const static_string &y) 46 | { 47 | return cx::equal(x.begin(), x.end(), y.begin(), y.end()); 48 | } 49 | 50 | 51 | // note that this works because vector is implicitly null terminated with its data initializer 52 | template 53 | struct basic_string : vector 54 | { 55 | constexpr basic_string(const static_string &s) 56 | : vector(s.begin(), s.end()) 57 | { 58 | } 59 | constexpr basic_string(const std::string_view &s) 60 | : vector(s.cbegin(), s.cend()) 61 | { 62 | } 63 | 64 | constexpr basic_string() = default; 65 | 66 | constexpr basic_string &operator=(const static_string &s) { 67 | return *this = basic_string(s); 68 | } 69 | 70 | constexpr basic_string &operator=(const std::string_view &s) { 71 | return *this = basic_string(s); 72 | } 73 | 74 | constexpr const char *c_str() const { 75 | return this->data(); 76 | } 77 | }; 78 | 79 | template 80 | constexpr bool operator==(const basic_string &lhs, const static_string &rhs) 81 | { 82 | return cx::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 83 | } 84 | 85 | using string = basic_string; 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/include/cx_vector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cx_algorithm.h" 4 | #include "cx_iterator.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace cx 12 | { 13 | template 14 | class vector 15 | { 16 | using storage_t = std::array; 17 | public: 18 | using iterator = typename storage_t::iterator; 19 | using const_iterator = typename storage_t::const_iterator; 20 | using value_type = Value; 21 | using reference = typename storage_t::reference; 22 | using const_reference = typename storage_t::const_reference; 23 | 24 | template 25 | constexpr vector(Itr begin, const Itr &end) 26 | { 27 | while (begin != end) { 28 | push_back(*begin); 29 | ++begin; 30 | } 31 | } 32 | constexpr vector(std::initializer_list init) 33 | : vector(init.begin(), init.end()) 34 | { 35 | } 36 | 37 | constexpr vector() = default; 38 | 39 | constexpr auto begin() const { return m_data.begin(); } 40 | constexpr auto begin() { return m_data.begin(); } 41 | 42 | // We would have prefered to use `std::next`, however it does not seem to be 43 | // enabled for constexpr use for std::array in this version of gcc. As of 44 | // September 2017 this is fixed in GCC trunk but not in GCC 7.2. 45 | constexpr auto end() const { return m_data.begin() + m_size; } 46 | constexpr auto end() { return m_data.begin() + m_size; } 47 | 48 | constexpr auto cbegin() const { return m_data.begin(); } 49 | constexpr auto cend() const { return m_data.begin() + m_size; } 50 | 51 | constexpr const Value &operator[](const std::size_t t_pos) const { 52 | return m_data[t_pos]; 53 | } 54 | constexpr Value &operator[](const std::size_t t_pos) { 55 | return m_data[t_pos]; 56 | } 57 | 58 | constexpr Value &at(const std::size_t t_pos) { 59 | if (t_pos >= m_size) { 60 | // This is allowed in constexpr context, but if the constexpr evaluation 61 | // hits this exception the compile would fail 62 | throw std::range_error("Index past end of vector"); 63 | } else { 64 | return m_data[t_pos]; 65 | } 66 | } 67 | constexpr const Value &at(const std::size_t t_pos) const { 68 | if (t_pos >= m_size) { 69 | throw std::range_error("Index past end of vector"); 70 | } else { 71 | return m_data[t_pos]; 72 | } 73 | } 74 | 75 | constexpr Value& push_back(Value t_v) { 76 | if (m_size >= Size) { 77 | throw std::range_error("Index past end of vector"); 78 | } else { 79 | Value& v = m_data[m_size++]; 80 | v = std::move(t_v); 81 | return v; 82 | } 83 | } 84 | 85 | constexpr const Value &back() const { 86 | if (empty()) { 87 | throw std::range_error("Index past end of vector"); 88 | } else { 89 | return m_data[m_size - 1]; 90 | } 91 | } 92 | constexpr Value &back() { 93 | if (empty()) { 94 | throw std::range_error("Index past end of vector"); 95 | } else { 96 | return m_data[m_size - 1]; 97 | } 98 | } 99 | 100 | constexpr auto capacity() const { return Size; } 101 | constexpr auto size() const { return m_size; } 102 | constexpr auto empty() const { return m_size == 0; } 103 | 104 | constexpr void clear() { m_size = 0; } 105 | 106 | constexpr const Value* data() const { 107 | return m_data.data(); 108 | } 109 | 110 | private: 111 | storage_t m_data{}; 112 | std::size_t m_size{0}; 113 | }; 114 | 115 | template 116 | constexpr bool operator==(const vector &x, const vector &y) 117 | { 118 | return cx::equal(x.begin(), x.end(), y.begin(), y.end()); 119 | } 120 | 121 | 122 | // Is addition (concatenation) on strings useful? Not sure yet. But it does 123 | // allow us to carry the type information properly. 124 | 125 | template 126 | constexpr auto operator+(vector a, vector b) { 127 | vector v; 128 | copy(a.cbegin(), a.cend(), back_insert_iterator(v)); 129 | copy(b.cbegin(), b.cend(), back_insert_iterator(v)); 130 | return v; 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable (test_${PROJECT_NAME} algorithm.cpp json.cpp main.cpp) 2 | -------------------------------------------------------------------------------- /src/test/algorithm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | void algo_tests_nonmod() 7 | { 8 | 9 | { 10 | constexpr int arr[] = {1, 3, 5, 7, 9}; 11 | constexpr bool b = cx::all_of(std::cbegin(arr), std::cend(arr), 12 | [] (auto i) { return i % 2 == 1; }); 13 | static_assert(b, "all_of fail"); 14 | } 15 | 16 | { 17 | constexpr int arr[] = {1, 3, 5, 8, 9}; 18 | constexpr bool b = cx::any_of(std::cbegin(arr), std::cend(arr), 19 | [] (auto i) { return i % 2 == 0; }); 20 | static_assert(b, "any_of fail"); 21 | } 22 | 23 | { 24 | constexpr int arr[] = {1, 3, 5, 7, 9}; 25 | constexpr bool b = cx::none_of(std::cbegin(arr), std::cend(arr), 26 | [] (auto i) { return i % 2 == 0; }); 27 | static_assert(b, "none_of fail"); 28 | } 29 | 30 | { 31 | constexpr auto vec = [] () { 32 | int arr[] = {1, 3, 5, 7, 9}; 33 | cx::vector v; 34 | cx::for_each(std::cbegin(arr), std::cend(arr), 35 | [&] (int i) { v.push_back(i+1); }); 36 | return v; 37 | }(); 38 | static_assert(vec.size() == 5 && vec[0] == 2, "for_each fail"); 39 | } 40 | 41 | { 42 | constexpr int arr[] = {1, 3, 5, 7, 9}; 43 | constexpr auto n = cx::count_if(std::cbegin(arr), std::cend(arr), 44 | [] (auto i) { return i % 2 == 1; }); 45 | static_assert(n == 5, "count_if fail"); 46 | } 47 | 48 | { 49 | using namespace std::literals; 50 | constexpr std::string_view s1 = "hello"sv; 51 | constexpr std::string_view s2 = "helllo"sv; 52 | constexpr auto p = cx::mismatch(s1.cbegin(), s1.cend(), s2.cbegin(), s2.cend()); 53 | static_assert(p.first == s1.cend() - 1 54 | && p.second == s2.cend() - 2, "mismatch fail"); 55 | } 56 | 57 | { 58 | using namespace std::literals; 59 | constexpr std::string_view s1 = "hello"sv; 60 | constexpr std::string_view s2 = "hallo"sv; 61 | constexpr auto eq = cx::equal(s1.cbegin(), s1.cend(), s1.cbegin(), s1.cend()); 62 | static_assert(eq, "equal fail"); 63 | constexpr auto noteq = cx::equal(s1.cbegin(), s1.cend(), s2.cbegin(), s2.cend()); 64 | static_assert(!noteq, "equal fail"); 65 | } 66 | 67 | { 68 | using namespace std::literals; 69 | constexpr std::string_view haystack = "rhythmic"sv; 70 | constexpr std::string_view needles = "aeiou"sv; 71 | constexpr auto it = cx::find_first_of(haystack.cbegin(), haystack.cend(), 72 | needles.cbegin(), needles.cend()); 73 | static_assert(it == haystack.cend() - 2, "find_first_of fail"); 74 | } 75 | 76 | { 77 | using namespace std::literals; 78 | constexpr std::string_view haystack = "banana"sv; 79 | constexpr std::string_view subseq = "ana"sv; 80 | constexpr auto it = cx::find_end(haystack.cbegin(), haystack.cend(), 81 | subseq.cbegin(), subseq.cend()); 82 | static_assert(it == haystack.cend() - 3, "find_end fail"); 83 | } 84 | 85 | { 86 | using namespace std::literals; 87 | constexpr std::string_view haystack = "banana"sv; 88 | constexpr std::string_view subseq = "ana"sv; 89 | constexpr auto it = cx::search(haystack.cbegin(), haystack.cend(), 90 | subseq.cbegin(), subseq.cend()); 91 | static_assert(it == haystack.cbegin() + 1, "search fail"); 92 | } 93 | 94 | { 95 | using namespace std::literals; 96 | 97 | // see the comments on search_n and bad_search_n 98 | 99 | // starting at the beginning is fine 100 | constexpr std::string_view haystack = "111110"sv; 101 | constexpr auto it = cx::search_n(haystack.cbegin(), haystack.cend(), 102 | 5, '1'); 103 | static_assert(it == haystack.cbegin(), "search_n fail"); 104 | 105 | // failing is fine 106 | constexpr auto it2 = cx::search_n(haystack.cbegin(), haystack.cend(), 107 | 6, '1'); 108 | static_assert(it2 == haystack.cend(), "search_n fail"); 109 | 110 | // but as soon as the search has to skip even one character, bad_search_n 111 | // gives up the ghost 112 | constexpr std::string_view haystack2 = "011111"sv; 113 | constexpr auto it3 = cx::search_n(haystack2.cbegin(), haystack2.cend(), 114 | 5, '1'); 115 | static_assert(it3 == haystack2.cbegin()+1, "search_n fail"); 116 | 117 | constexpr auto it4 = cx::search_n(haystack2.cbegin(), haystack2.cend(), 118 | 6, '1'); 119 | static_assert(it4 == haystack2.cend(), "search_n fail"); 120 | 121 | } 122 | 123 | { 124 | using namespace std::literals; 125 | constexpr std::string_view haystack = "wildebeest"sv; 126 | constexpr auto it = cx::adjacent_find(haystack.cbegin(), haystack.cend()); 127 | static_assert(it == haystack.cend() - 4, "adjacent_find fail"); 128 | } 129 | } 130 | 131 | void algo_tests_mod() 132 | { 133 | using std::cbegin; 134 | using std::cend; 135 | using std::begin; 136 | using std::end; 137 | 138 | 139 | { 140 | 141 | constexpr auto vec = [&] () { 142 | auto il = {1, 3, 5, 7, 9}; 143 | cx::vector v; 144 | cx::copy(cbegin(il), cend(il), cx::back_insert_iterator(v)); 145 | return v; 146 | }(); 147 | static_assert(vec.size() == 5, "copy fail"); 148 | } 149 | 150 | { 151 | constexpr auto vec = [&] () { 152 | auto il = {1, 2, 5, 7, 4}; 153 | cx::vector v; 154 | cx::copy_if(cbegin(il), cend(il), cx::back_insert_iterator(v), 155 | [] (auto i) { return i % 2 == 0; }); 156 | return v; 157 | }(); 158 | static_assert(vec.size() == 2, "copy_if fail"); 159 | } 160 | 161 | { 162 | constexpr auto vec = [&] () { 163 | auto il = {1, 3, 5, 7, 9}; 164 | cx::vector v; 165 | cx::copy_n(cbegin(il), 3, cx::back_insert_iterator(v)); 166 | return v; 167 | }(); 168 | static_assert(vec.size() == 3, "copy_n fail"); 169 | } 170 | 171 | { 172 | constexpr auto vec = [&] () { 173 | auto il = {1, 3, 5, 7, 9}; 174 | cx::vector v = {0,0,0,0,0}; 175 | cx::copy_backward(cbegin(il), cend(il), v.end()); 176 | return v; 177 | }(); 178 | static_assert(vec.size() == 5 && vec[0] == 1 && vec[4] == 9, "copy_backward fail"); 179 | } 180 | 181 | { 182 | constexpr auto vec = [&] () { 183 | auto il = {1, 3, 5, 7, 9}; 184 | cx::vector v; 185 | cx::move(begin(il), end(il), cx::back_insert_iterator(v)); 186 | return v; 187 | }(); 188 | static_assert(vec.size() == 5, "move fail"); 189 | } 190 | 191 | { 192 | constexpr auto vec = [&] () { 193 | auto il = {1, 3, 5, 7, 9}; 194 | cx::vector v = {0,0,0,0,0}; 195 | cx::move_backward(begin(il), end(il), v.end()); 196 | return v; 197 | }(); 198 | static_assert(vec.size() == 5 && vec[0] == 1 && vec[4] == 9, "move_backward fail"); 199 | } 200 | 201 | { 202 | constexpr auto vec = [] () { 203 | cx::vector v{1,2,3,4,5}; 204 | cx::fill(v.begin(), v.end(), 5); 205 | return v; 206 | }(); 207 | static_assert(vec.size() == 5 && vec[0] == 5, "fill fail"); 208 | } 209 | 210 | { 211 | constexpr auto vec = [] () { 212 | cx::vector v; 213 | cx::fill_n(cx::back_insert_iterator(v), 3, 5); 214 | return v; 215 | }(); 216 | static_assert(vec.size() == 3 217 | && vec[0] == 5 && vec[1] == 5 && vec[2] == 5, "fill_n fail"); 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /src/test/json.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std::literals; 11 | 12 | void simple_parse_tests() 13 | { 14 | // test true, false and null literals 15 | constexpr auto true_val = JSON::bool_parser()("true"sv); 16 | static_assert(true_val && true_val->first); 17 | 18 | constexpr auto false_val = JSON::bool_parser()("false"sv); 19 | static_assert(false_val && !false_val->first); 20 | 21 | constexpr auto null_val = JSON::null_parser()("null"sv); 22 | static_assert(null_val); 23 | } 24 | 25 | void string_parse_tests() 26 | { 27 | // test strings (unicode chars unsupported as yet) 28 | constexpr auto char_val = JSON::string_char_parser()("t"sv); 29 | static_assert(char_val && char_val->first[0] == 't'); 30 | 31 | constexpr auto echar_val = JSON::string_char_parser()("\t"sv); 32 | static_assert(echar_val && echar_val->first[0] == '\t'); 33 | 34 | { 35 | constexpr auto str_val = JSON::string_parser()(R"("")"sv); 36 | static_assert(str_val && str_val->first.empty()); 37 | } 38 | 39 | { 40 | // the implementation of operator==(string_view, string_view) isn't 41 | // constexpr yet, so we'll use a homegrown constexpr equal 42 | constexpr auto str_val = JSON::string_parser()(R"("hello")"sv); 43 | constexpr auto expected_str = "hello"sv; 44 | static_assert(str_val && 45 | cx::equal(str_val->first.cbegin(), str_val->first.cend(), 46 | expected_str.cbegin(), expected_str.cend())); 47 | } 48 | 49 | // unicode points: should come out as utf-8 50 | // U+2603 is the snowman 51 | { 52 | constexpr auto u = JSON::unicode_point_parser()("\\u2603"sv); 53 | static_assert(u && u->first.size() == 3 54 | && u->first[0] == static_cast(0xe2) 55 | && u->first[1] == static_cast(0x98) 56 | && u->first[2] == static_cast(0x83)); 57 | } 58 | } 59 | 60 | void number_parse_tests() 61 | { 62 | // test numbers 63 | { 64 | constexpr auto number_val = JSON::number_parser()("0"sv); 65 | static_assert(number_val && number_val->first == 0); 66 | } 67 | 68 | { 69 | constexpr auto number_val = JSON::number_parser()("123"sv); 70 | static_assert(number_val && number_val->first == 123.0); 71 | } 72 | 73 | { 74 | constexpr auto number_val = JSON::number_parser()("-123"sv); 75 | static_assert(number_val && number_val->first == -123.0); 76 | } 77 | 78 | { 79 | constexpr auto number_val = JSON::number_parser()(".123"sv); 80 | static_assert(!number_val); 81 | } 82 | 83 | { 84 | constexpr auto number_val = JSON::number_parser()("0.123"sv); 85 | static_assert(number_val && number_val->first == 0.123); 86 | } 87 | 88 | { 89 | constexpr auto number_val = JSON::number_parser()("456.123"sv); 90 | static_assert(number_val && number_val->first == 456.123); 91 | } 92 | 93 | { 94 | constexpr auto number_val = JSON::number_parser()("456.123e1"sv); 95 | static_assert(number_val && number_val->first == 456.123e1); 96 | } 97 | 98 | { 99 | constexpr auto number_val = JSON::number_parser()("456.123e-1"sv); 100 | static_assert(number_val && number_val->first == 456.123e-1); 101 | } 102 | } 103 | 104 | void numobjects_tests() 105 | { 106 | // test number of JSON objects parsing 107 | { 108 | constexpr auto d = JSON::sizes_parser()("true"sv); 109 | static_assert(d && d->first.num_objects == 1); 110 | } 111 | { 112 | constexpr auto d = JSON::sizes_parser()("[]"sv); 113 | static_assert(d && d->first.num_objects == 1); 114 | } 115 | { 116 | constexpr auto d = JSON::sizes_parser()("[1,2,3,4]"sv); 117 | static_assert(d && d->first.num_objects == 5); 118 | } 119 | { 120 | constexpr auto d = JSON::sizes_parser()(R"({"a":1, "b":2})"sv); 121 | static_assert(d && d->first.num_objects == 5); 122 | } 123 | } 124 | 125 | void stringsize_tests() 126 | { 127 | // test string size of JSON objects parsing 128 | { 129 | constexpr auto d = JSON::sizes_parser()(R"("a")"sv); 130 | static_assert(d && d->first.string_size == 1); 131 | } 132 | { 133 | constexpr auto d = JSON::sizes_parser()("true"sv); 134 | static_assert(d && d->first.string_size == 0); 135 | } 136 | { 137 | constexpr auto d = JSON::sizes_parser()(R"(["a", "b"])"sv); 138 | static_assert(d && d->first.string_size == 2); 139 | } 140 | { 141 | constexpr auto d = JSON::sizes_parser()(R"({"a":1, "b":2})"sv); 142 | static_assert(d && d->first.string_size == 2); 143 | } 144 | } 145 | 146 | void extent_tests() 147 | { 148 | // test JSON object extent parsing 149 | { 150 | constexpr auto sv = "true"sv; 151 | constexpr auto r = JSON::extent_parser()(sv); 152 | static_assert(r && 153 | cx::equal(r->first.cbegin(), r->first.cend(), 154 | sv.cbegin(), sv.cend())); 155 | } 156 | { 157 | constexpr auto sv = R"("hello")"sv; 158 | constexpr auto r = JSON::extent_parser()(sv); 159 | static_assert(r && 160 | cx::equal(r->first.cbegin(), r->first.cend(), 161 | sv.cbegin(), sv.cend())); 162 | } 163 | { 164 | constexpr auto sv = "123.456"sv; 165 | constexpr auto r = JSON::extent_parser()(sv); 166 | static_assert(r && 167 | cx::equal(r->first.cbegin(), r->first.cend(), 168 | sv.cbegin(), sv.cend())); 169 | } 170 | { 171 | constexpr auto sv = "[1,2,3]"sv; 172 | constexpr auto r = JSON::extent_parser()(sv); 173 | static_assert(r && 174 | cx::equal(r->first.cbegin(), r->first.cend(), 175 | sv.cbegin(), sv.cend())); 176 | } 177 | { 178 | constexpr auto sv = R"({"a":1, "b":2})"sv; 179 | constexpr auto r = JSON::extent_parser()(sv); 180 | static_assert(r && 181 | cx::equal(r->first.cbegin(), r->first.cend(), 182 | sv.cbegin(), sv.cend())); 183 | } 184 | } 185 | 186 | void simple_value_tests() 187 | { 188 | // test JSON value parsing 189 | using namespace JSON::literals; 190 | 191 | { 192 | constexpr auto jsv = "true"_json; 193 | static_assert(jsv.to_Boolean()); 194 | } 195 | { 196 | constexpr auto jsv = "false"_json; 197 | static_assert(!jsv.to_Boolean()); 198 | } 199 | { 200 | constexpr auto jsv = "null"_json; 201 | static_assert(jsv.is_Null()); 202 | } 203 | { 204 | constexpr auto jsv = "123.456"_json; 205 | static_assert(jsv.to_Number() == 123.456); 206 | } 207 | { 208 | constexpr auto jsv = R"("hello")"_json; 209 | static_assert(jsv.to_String() == "hello"); 210 | } 211 | { 212 | // strings can be arbitrary size... 213 | constexpr auto jsv = R"("01234567890123456789012345678901234567890123456789")"_json; 214 | static_assert(jsv.to_String() == "01234567890123456789012345678901234567890123456789"); 215 | static_assert(jsv.string_size() == 50); 216 | } 217 | } 218 | 219 | void array_value_tests() 220 | { 221 | // test JSON array value parsing 222 | using namespace JSON::literals; 223 | 224 | { 225 | constexpr auto jsv = "[]"_json; 226 | static_assert(jsv.array_Size() == 0); 227 | } 228 | { 229 | constexpr auto jsv = "[1, true, 3]"_json; 230 | static_assert(jsv[0].to_Number() == 1); 231 | static_assert(jsv[1].to_Boolean()); 232 | static_assert(jsv[2].to_Number() == 3); 233 | } 234 | { 235 | constexpr auto jsv = "[1, [true, false], [2, 3]]"_json; 236 | static_assert(jsv[0].to_Number() == 1); 237 | static_assert(jsv[1][0].to_Boolean()); 238 | static_assert(!jsv[1][1].to_Boolean()); 239 | static_assert(jsv[2][0].to_Number() == 2); 240 | static_assert(jsv[2][1].to_Number() == 3); 241 | } 242 | { 243 | constexpr auto jsv = "[1, null, true, [2]]"_json; 244 | static_assert(jsv.array_Size() == 4); 245 | static_assert(jsv[0].to_Number() == 1); 246 | static_assert(jsv[1].is_Null()); 247 | static_assert(jsv[2].to_Boolean()); 248 | static_assert(jsv[3][0].to_Number() == 2); 249 | } 250 | { 251 | // arrays can go arbitrarily deep... 252 | constexpr auto jsv = "[[[[[[[[[[[[1]]]]]]]]]]]]"_json; 253 | static_assert(jsv[0][0][0][0][0][0][0][0][0][0][0][0].to_Number() == 1); 254 | } 255 | { 256 | // and arbitrarily wide... 257 | constexpr auto jsv = "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]"_json; 258 | static_assert(jsv[0].to_Number() == 1 && jsv[15].to_Number() == 16); 259 | } 260 | } 261 | 262 | void object_value_tests() 263 | { 264 | // test JSON object value parsing 265 | using namespace JSON::literals; 266 | 267 | { 268 | constexpr auto jsv = R"({})"_json; 269 | static_assert(jsv.object_Size() == 0); 270 | } 271 | { 272 | constexpr auto jsv = R"({"a":1})"_json; 273 | static_assert(jsv.object_Size() == 1); 274 | static_assert(jsv["a"].to_Number() == 1); 275 | } 276 | { 277 | constexpr auto jsv = R"({"a":1, "b":true, "c":2})"_json; 278 | static_assert(jsv.object_Size() == 3); 279 | static_assert(jsv["a"].to_Number() == 1); 280 | static_assert(jsv["b"].to_Boolean()); 281 | static_assert(jsv["c"].to_Number() == 2); 282 | } 283 | { 284 | constexpr auto jsv = R"({"a":{}})"_json; 285 | static_assert(jsv["a"].object_Size() == 0); 286 | } 287 | { 288 | constexpr auto jsv = R"({"a":1, "b":true, "c":["hello"]})"_json; 289 | static_assert(jsv["a"].to_Number() == 1); 290 | static_assert(jsv["b"].to_Boolean()); 291 | static_assert(jsv["c"][0].to_String() == "hello"); 292 | } 293 | { 294 | constexpr auto jsv = R"( [ 295 | 1 , null , true , [ 2 ] , 296 | { "a" : 3.14 } , "hello" 297 | ] )"_json; 298 | static_assert(jsv[0].to_Number() == 1); 299 | static_assert(jsv[1].is_Null()); 300 | static_assert(jsv[2].to_Boolean()); 301 | static_assert(jsv[3][0].to_Number() == 2); 302 | static_assert(jsv[4]["a"].to_Number() == 3.14); 303 | static_assert(jsv[5].to_String() == "hello"); 304 | } 305 | { 306 | // objects can contain an arbitrary number of children... 307 | constexpr auto jsv = R"({"a":1, "b":2, "c":3, "d":4, "e":5, "f":6, 308 | "g":7, "h":8, "i":9, "j":10,"k":11,"l":12, 309 | "m":13,"n":14,"o":15,"p":16,"q":17,"r":18, 310 | "s":19,"t":20,"u":21,"v":22,"w":23,"x":24, 311 | "y":25,"z":26})"_json; 312 | static_assert(jsv["z"].to_Number() == 26); 313 | } 314 | { 315 | // what's the point of all this? 316 | 317 | // constexpr can be used as template parameters! 318 | constexpr auto jsv = R"({"a":0, "b":1})"_json; 319 | constexpr std::tuple t{ 5.2, 33 }; 320 | static_assert(std::get(t) == 33); 321 | 322 | // configuration can be processed at compile time 323 | // (this would be even better with file literals - P0373) 324 | constexpr auto config = R"( 325 | { 326 | "feature-x-enabled": true 327 | } 328 | )"_json; 329 | if constexpr (config["feature-x-enabled"].to_Boolean()) { 330 | // do something 331 | std::cout << "feature-x-enabled\n"; 332 | } else { 333 | // do something else 334 | std::cout << "feature-x-disabled\n"; 335 | } 336 | } 337 | } 338 | 339 | void fail_tests() 340 | { 341 | // intentionally failing parse tests 342 | using namespace JSON::literals; 343 | 344 | // constexpr auto jsv1 = R"({)"_json; 345 | // constexpr auto jsv2 = R"([)"_json; 346 | // constexpr auto jsv3 = R"({"a")"_json; 347 | // constexpr auto jsv4 = R"({1)"_json; 348 | // constexpr auto jsv5 = R"({"a":1)"_json; 349 | // constexpr auto jsv6 = R"([1,])"_json; 350 | } 351 | -------------------------------------------------------------------------------- /src/test/main.cpp: -------------------------------------------------------------------------------- 1 | void object_value_tests(); 2 | 3 | int main(void) 4 | { 5 | object_value_tests(); 6 | } 7 | --------------------------------------------------------------------------------