blob: 861e69546a253c0888e43f15de61592634ef2e80 [file] [log] [blame]
Nigel Taod0b16cb2020-03-14 10:15:54 +11001// Copyright 2020 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// ----------------
16
17/*
18jsonfindptrs reads UTF-8 JSON from stdin and writes every node's JSON Pointer
19(RFC 6901) to stdout.
20
Nigel Taod60815c2020-03-26 14:32:35 +110021See the "const char* g_usage" string below for details.
Nigel Taod0b16cb2020-03-14 10:15:54 +110022
23----
24
25This program uses Wuffs' JSON decoder at a relatively high level, building
26in-memory representations of JSON 'things' (e.g. numbers, strings, objects).
27After the entire input has been converted, walking the tree prints the output
Nigel Taocf6c5782020-08-03 23:43:45 +100028(in sorted order). The wuffs_aux::DecodeJson library function converts the
29lower level token stream to higher level callbacks. This .cc file deals only
30with those callbacks, not with tokens per se.
Nigel Taod0b16cb2020-03-14 10:15:54 +110031
32This approach is centered around JSON things. Each JSON thing comprises one or
33more JSON tokens.
34
35An alternative, lower-level approach is in the sibling example/jsonptr program.
36Neither approach is better or worse per se, but when studying this program, be
37aware that there are multiple ways to use Wuffs' JSON decoder.
38
39The two programs, jsonfindptrs and jsonptr, also demonstrate different
40trade-offs with regard to JSON object duplicate keys. The JSON spec permits
41different implementations to allow or reject duplicate keys. It is not always
42clear which approach is safer. Rejecting them is certainly unambiguous, and
43security bugs can lurk in ambiguous corners of a file format, if two different
44implementations both silently accept a file but differ on how to interpret it.
45On the other hand, in the worst case, detecting duplicate keys requires O(N)
46memory, where N is the size of the (potentially untrusted) input.
47
48This program (jsonfindptrs) rejects duplicate keys.
49
50----
51
Nigel Tao50bfab92020-08-05 11:39:09 +100052To run:
Nigel Taod0b16cb2020-03-14 10:15:54 +110053
54$CXX jsonfindptrs.cc && ./a.out < ../../test/data/github-tags.json; rm -f a.out
55
56for a C++ compiler $CXX, such as clang++ or g++.
57*/
58
Nigel Tao721190a2020-04-03 22:25:21 +110059#if defined(__cplusplus) && (__cplusplus < 201103L)
60#error "This C++ program requires -std=c++11 or later"
61#endif
62
Nigel Taocf6c5782020-08-03 23:43:45 +100063#include <stdio.h>
Nigel Tao6b7ce302020-07-07 16:19:46 +100064
Nigel Taod0b16cb2020-03-14 10:15:54 +110065#include <iostream>
66#include <map>
67#include <string>
68#include <vector>
69
70// Wuffs ships as a "single file C library" or "header file library" as per
71// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
72//
73// To use that single file as a "foo.c"-like implementation, instead of a
74// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
75// compiling it.
76#define WUFFS_IMPLEMENTATION
77
78// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
79// release/c/etc.c whitelist which parts of Wuffs to build. That file contains
80// the entire Wuffs standard library, implementing a variety of codecs and file
81// formats. Without this macro definition, an optimizing compiler or linker may
82// very well discard Wuffs code for unused codecs, but listing the Wuffs
83// modules we use makes that process explicit. Preprocessing means that such
84// code simply isn't compiled.
85#define WUFFS_CONFIG__MODULES
Nigel Taocf6c5782020-08-03 23:43:45 +100086#define WUFFS_CONFIG__MODULE__AUX__BASE
87#define WUFFS_CONFIG__MODULE__AUX__JSON
Nigel Taod0b16cb2020-03-14 10:15:54 +110088#define WUFFS_CONFIG__MODULE__BASE
89#define WUFFS_CONFIG__MODULE__JSON
90
91// If building this program in an environment that doesn't easily accommodate
92// relative includes, you can use the script/inline-c-relative-includes.go
93// program to generate a stand-alone C++ file.
94#include "../../release/c/wuffs-unsupported-snapshot.c"
95
96#define TRY(error_msg) \
97 do { \
98 std::string z = error_msg; \
99 if (!z.empty()) { \
100 return z; \
101 } \
102 } while (false)
103
Nigel Taod60815c2020-03-26 14:32:35 +1100104static const char* g_usage =
Nigel Taod0b16cb2020-03-14 10:15:54 +1100105 "Usage: jsonfindptrs -flags input.json\n"
106 "\n"
107 "Flags:\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100108 " -d=NUM -max-output-depth=NUM\n"
Nigel Tao8aac6762020-08-12 22:47:45 +1000109 " -q=STR -query=STR\n"
Nigel Tao0a5940d2020-08-07 13:15:41 +1000110 " -input-allow-comments\n"
111 " -input-allow-extra-comma\n"
112 " -input-allow-inf-nan-numbers\n"
Nigel Taoecadf722020-07-13 08:22:34 +1000113 " -strict-json-pointer-syntax\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100114 "\n"
115 "The input.json filename is optional. If absent, it reads from stdin.\n"
116 "\n"
117 "----\n"
118 "\n"
119 "jsonfindptrs reads UTF-8 JSON from stdin and writes every node's JSON\n"
120 "Pointer (RFC 6901) to stdout.\n"
121 "\n"
122 "For example, given RFC 6901 section 5's sample input\n"
123 "(https://tools.ietf.org/rfc/rfc6901.txt), this command:\n"
124 " jsonfindptrs rfc-6901-json-pointer.json\n"
125 "will print:\n"
126 " \n"
127 " /\n"
128 " / \n"
129 " /a~1b\n"
130 " /c%d\n"
131 " /e^f\n"
132 " /foo\n"
133 " /foo/0\n"
134 " /foo/1\n"
135 " /g|h\n"
136 " /i\\j\n"
137 " /k\"l\n"
138 " /m~0n\n"
139 "\n"
140 "The first three lines are (1) a 0-byte \"\", (2) a 1-byte \"/\" and (3)\n"
141 "a 2-byte \"/ \". Unlike a file system, the \"/\" JSON Pointer does not\n"
142 "identify the root. Instead, \"\" is the root and \"/\" is the child (the\n"
143 "value in a key-value pair) of the root whose key is the empty string.\n"
144 "Similarly, \"/xyz\" and \"/xyz/\" are two different nodes.\n"
145 "\n"
146 "----\n"
147 "\n"
148 "The JSON specification (https://json.org/) permits implementations that\n"
149 "allow duplicate keys, but this one does not. Conversely, it prints keys\n"
150 "in sorted order, but the overall output is not necessarily sorted\n"
151 "lexicographically. For example, \"/a/9\" would come before \"/a/10\",\n"
152 "and \"/b/c\", a child of \"/b\", would come before \"/b+\".\n"
153 "\n"
154 "This JSON implementation also rejects integer values outside ±M, where\n"
155 "M is ((1<<53)-1), also known as JavaScript's Number.MAX_SAFE_INTEGER.\n"
156 "\n"
Nigel Tao0a5940d2020-08-07 13:15:41 +1000157 "The -input-allow-comments flag allows \"/*slash-star*/\" and\n"
158 "\"//slash-slash\" C-style comments within JSON input.\n"
159 "\n"
160 "The -input-allow-extra-comma flag allows input like \"[1,2,]\", with a\n"
Nigel Taoc766bb72020-07-09 12:59:32 +1000161 "comma after the final element of a JSON list or dictionary.\n"
162 "\n"
Nigel Tao0a5940d2020-08-07 13:15:41 +1000163 "The -input-allow-inf-nan-numbers flag allows non-finite floating point\n"
164 "numbers (infinities and not-a-numbers) within JSON input.\n"
165 "\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100166 "----\n"
167 "\n"
Nigel Taoecadf722020-07-13 08:22:34 +1000168 "The -strict-json-pointer-syntax flag restricts the output lines to\n"
169 "exactly RFC 6901, with only two escape sequences: \"~0\" and \"~1\" for\n"
170 "\"~\" and \"/\". Without this flag, this program also lets \"~n\" and\n"
171 "\"~r\" escape the New Line and Carriage Return ASCII control characters,\n"
172 "which can work better with line oriented Unix tools that assume exactly\n"
173 "one value (i.e. one JSON Pointer string) per line. With this flag, it\n"
174 "fails if the input JSON's keys contain \"\\u000A\" or \"\\u000D\".\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100175 "\n"
176 "----\n"
177 "\n"
178 "The JSON specification permits implementations to set their own maximum\n"
179 "input depth. This JSON implementation sets it to 1024.\n"
180 "\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100181 "The -d=NUM or -max-output-depth=NUM flag gives the maximum (inclusive)\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100182 "output depth. JSON containers ([] arrays and {} objects) can hold other\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100183 "containers. A bare -d or -max-output-depth is equivalent to -d=1,\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100184 "analogous to the Unix ls command. The flag's absence is equivalent to an\n"
185 "unlimited output depth, analogous to the Unix find command (and hence\n"
186 "the name of this program: jsonfindptrs).";
187
188// ----
189
Nigel Taocf6c5782020-08-03 23:43:45 +1000190std::vector<uint32_t> g_quirks;
191
Nigel Tao8098d962020-08-29 10:41:05 +1000192std::string g_dst;
193
Nigel Taod0b16cb2020-03-14 10:15:54 +1100194struct {
195 int remaining_argc;
196 char** remaining_argv;
197
Nigel Taod0b16cb2020-03-14 10:15:54 +1100198 bool strict_json_pointer_syntax;
Nigel Tao0a0c7d62020-08-18 23:31:27 +1000199
200 uint32_t max_output_depth;
201
202 char* query_c_string;
Nigel Taod60815c2020-03-26 14:32:35 +1100203} g_flags = {0};
Nigel Taod0b16cb2020-03-14 10:15:54 +1100204
205std::string //
206parse_flags(int argc, char** argv) {
Nigel Taod60815c2020-03-26 14:32:35 +1100207 g_flags.max_output_depth = 0xFFFFFFFF;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100208
209 int c = (argc > 0) ? 1 : 0; // Skip argv[0], the program name.
210 for (; c < argc; c++) {
211 char* arg = argv[c];
212 if (*arg++ != '-') {
213 break;
214 }
215
216 // A double-dash "--foo" is equivalent to a single-dash "-foo". As special
217 // cases, a bare "-" is not a flag (some programs may interpret it as
218 // stdin) and a bare "--" means to stop parsing flags.
219 if (*arg == '\x00') {
220 break;
221 } else if (*arg == '-') {
222 arg++;
223 if (*arg == '\x00') {
224 c++;
225 break;
226 }
227 }
228
Nigel Tao94440cf2020-04-02 22:28:24 +1100229 if (!strcmp(arg, "d") || !strcmp(arg, "max-output-depth")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100230 g_flags.max_output_depth = 1;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100231 continue;
Nigel Tao94440cf2020-04-02 22:28:24 +1100232 } else if (!strncmp(arg, "d=", 2) ||
Nigel Taod0b16cb2020-03-14 10:15:54 +1100233 !strncmp(arg, "max-output-depth=", 16)) {
234 while (*arg++ != '=') {
235 }
236 wuffs_base__result_u64 u = wuffs_base__parse_number_u64(
Nigel Tao6b7ce302020-07-07 16:19:46 +1000237 wuffs_base__make_slice_u8((uint8_t*)arg, strlen(arg)),
238 WUFFS_BASE__PARSE_NUMBER_XXX__DEFAULT_OPTIONS);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100239 if (wuffs_base__status__is_ok(&u.status) && (u.value <= 0xFFFFFFFF)) {
Nigel Taod60815c2020-03-26 14:32:35 +1100240 g_flags.max_output_depth = (uint32_t)(u.value);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100241 continue;
242 }
Nigel Taod60815c2020-03-26 14:32:35 +1100243 return g_usage;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100244 }
Nigel Tao0a5940d2020-08-07 13:15:41 +1000245 if (!strcmp(arg, "input-allow-comments")) {
246 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_COMMENT_BLOCK);
247 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_COMMENT_LINE);
248 continue;
249 }
250 if (!strcmp(arg, "input-allow-extra-comma")) {
Nigel Taocf6c5782020-08-03 23:43:45 +1000251 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_EXTRA_COMMA);
Nigel Taoc766bb72020-07-09 12:59:32 +1000252 continue;
253 }
Nigel Tao0a5940d2020-08-07 13:15:41 +1000254 if (!strcmp(arg, "input-allow-inf-nan-numbers")) {
255 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_INF_NAN_NUMBERS);
256 continue;
257 }
Nigel Tao8aac6762020-08-12 22:47:45 +1000258 if (!strncmp(arg, "q=", 2) || !strncmp(arg, "query=", 6)) {
259 while (*arg++ != '=') {
260 }
261 g_flags.query_c_string = arg;
262 continue;
263 }
Nigel Taoecadf722020-07-13 08:22:34 +1000264 if (!strcmp(arg, "strict-json-pointer-syntax")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100265 g_flags.strict_json_pointer_syntax = true;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100266 continue;
267 }
268
Nigel Taod60815c2020-03-26 14:32:35 +1100269 return g_usage;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100270 }
271
Nigel Taod60815c2020-03-26 14:32:35 +1100272 g_flags.remaining_argc = argc - c;
273 g_flags.remaining_argv = argv + c;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100274 return "";
275}
276
Nigel Tao6b7ce302020-07-07 16:19:46 +1000277// ----
Nigel Taod0b16cb2020-03-14 10:15:54 +1100278
Nigel Taod0b16cb2020-03-14 10:15:54 +1100279class JsonThing {
280 public:
Nigel Taod0b16cb2020-03-14 10:15:54 +1100281 using Vector = std::vector<JsonThing>;
282
283 // We use a std::map in this example program to avoid dependencies outside of
284 // the C++ standard library. If you're copy/pasting this JsonThing code,
285 // consider a more efficient data structure such as an absl::btree_map.
286 //
287 // See CppCon 2014: Chandler Carruth "Efficiency with Algorithms, Performance
288 // with Data Structures" at https://www.youtube.com/watch?v=fHNmRkzxHWs
289 using Map = std::map<std::string, JsonThing>;
290
291 enum class Kind {
292 Null,
293 Bool,
294 Int64,
295 Float64,
296 String,
297 Array,
298 Object,
299 } kind = Kind::Null;
300
301 struct Value {
302 bool b = false;
303 int64_t i = 0;
304 double f = 0;
305 std::string s;
306 Vector a;
307 Map o;
308 } value;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100309};
310
Nigel Taod0b16cb2020-03-14 10:15:54 +1100311// ----
312
313std::string //
314escape(std::string s) {
315 for (char& c : s) {
316 if ((c == '~') || (c == '/') || (c == '\n') || (c == '\r')) {
317 goto escape_needed;
318 }
319 }
320 return s;
321
322escape_needed:
323 std::string e;
324 e.reserve(8 + s.length());
325 for (char& c : s) {
326 switch (c) {
327 case '~':
328 e += "~0";
329 break;
330 case '/':
331 e += "~1";
332 break;
333 case '\n':
Nigel Taod60815c2020-03-26 14:32:35 +1100334 if (g_flags.strict_json_pointer_syntax) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100335 return "";
336 }
337 e += "~n";
338 break;
339 case '\r':
Nigel Taod60815c2020-03-26 14:32:35 +1100340 if (g_flags.strict_json_pointer_syntax) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100341 return "";
342 }
343 e += "~r";
344 break;
345 default:
346 e += c;
347 break;
348 }
349 }
350 return e;
351}
352
353std::string //
Nigel Tao8098d962020-08-29 10:41:05 +1000354print_json_pointers(JsonThing& jt, uint32_t depth) {
355 std::cout << g_dst << '\n';
Nigel Taod60815c2020-03-26 14:32:35 +1100356 if (depth++ >= g_flags.max_output_depth) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100357 return "";
358 }
359
Nigel Tao8098d962020-08-29 10:41:05 +1000360 size_t n = g_dst.size();
Nigel Taod0b16cb2020-03-14 10:15:54 +1100361 switch (jt.kind) {
362 case JsonThing::Kind::Array:
Nigel Tao8098d962020-08-29 10:41:05 +1000363 g_dst += "/";
Nigel Taod0b16cb2020-03-14 10:15:54 +1100364 for (size_t i = 0; i < jt.value.a.size(); i++) {
Nigel Tao8098d962020-08-29 10:41:05 +1000365 g_dst += std::to_string(i);
366 TRY(print_json_pointers(jt.value.a[i], depth));
367 g_dst.resize(n + 1);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100368 }
Nigel Tao8098d962020-08-29 10:41:05 +1000369 g_dst.resize(n);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100370 break;
371 case JsonThing::Kind::Object:
Nigel Tao8098d962020-08-29 10:41:05 +1000372 g_dst += "/";
Nigel Taod0b16cb2020-03-14 10:15:54 +1100373 for (auto& kv : jt.value.o) {
374 std::string e = escape(kv.first);
375 if (e.empty() && !kv.first.empty()) {
376 return "main: unsupported \"\\u000A\" or \"\\u000D\" in object key";
377 }
Nigel Tao8098d962020-08-29 10:41:05 +1000378 g_dst += e;
379 TRY(print_json_pointers(kv.second, depth));
380 g_dst.resize(n + 1);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100381 }
Nigel Tao8098d962020-08-29 10:41:05 +1000382 g_dst.resize(n);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100383 break;
Nigel Tao18ef5b42020-03-16 10:37:47 +1100384 default:
385 break;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100386 }
387 return "";
388}
389
Nigel Taocf6c5782020-08-03 23:43:45 +1000390// ----
391
392class Callbacks : public wuffs_aux::DecodeJsonCallbacks {
393 public:
394 struct Entry {
395 Entry(JsonThing&& jt)
396 : thing(std::move(jt)), has_map_key(false), map_key() {}
397
398 JsonThing thing;
399 bool has_map_key;
400 std::string map_key;
401 };
402
403 Callbacks() = default;
404
405 std::string Append(JsonThing&& jt) {
406 if (m_stack.empty()) {
407 m_stack.push_back(Entry(std::move(jt)));
408 return "";
409 }
410 Entry& top = m_stack.back();
411 switch (top.thing.kind) {
412 case JsonThing::Kind::Array:
413 top.thing.value.a.push_back(std::move(jt));
414 return "";
415 case JsonThing::Kind::Object:
416 if (top.has_map_key) {
417 top.has_map_key = false;
418 auto iter = top.thing.value.o.find(top.map_key);
419 if (iter != top.thing.value.o.end()) {
420 return "main: duplicate key: " + top.map_key;
421 }
422 top.thing.value.o.insert(
423 iter, JsonThing::Map::value_type(std::move(top.map_key),
424 std::move(jt)));
425 return "";
426 } else if (jt.kind == JsonThing::Kind::String) {
427 top.has_map_key = true;
428 top.map_key = std::move(jt.value.s);
429 return "";
430 }
431 return "main: internal error: non-string map key";
Nigel Tao7fd3bb62020-08-24 21:34:53 +1000432 default:
433 return "main: internal error: non-container stack entry";
Nigel Taocf6c5782020-08-03 23:43:45 +1000434 }
Nigel Taocf6c5782020-08-03 23:43:45 +1000435 }
436
Nigel Taoca5da1f2020-08-10 15:26:29 +1000437 std::string AppendNull() override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000438 JsonThing jt;
439 jt.kind = JsonThing::Kind::Null;
440 return Append(std::move(jt));
441 }
442
Nigel Taoca5da1f2020-08-10 15:26:29 +1000443 std::string AppendBool(bool val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000444 JsonThing jt;
445 jt.kind = JsonThing::Kind::Bool;
446 jt.value.b = val;
447 return Append(std::move(jt));
448 }
449
Nigel Taoca5da1f2020-08-10 15:26:29 +1000450 std::string AppendI64(int64_t val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000451 JsonThing jt;
452 jt.kind = JsonThing::Kind::Int64;
453 jt.value.i = val;
454 return Append(std::move(jt));
455 }
456
Nigel Taoca5da1f2020-08-10 15:26:29 +1000457 std::string AppendF64(double val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000458 JsonThing jt;
459 jt.kind = JsonThing::Kind::Float64;
460 jt.value.f = val;
461 return Append(std::move(jt));
462 }
463
Nigel Taoca5da1f2020-08-10 15:26:29 +1000464 std::string AppendTextString(std::string&& val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000465 JsonThing jt;
466 jt.kind = JsonThing::Kind::String;
467 jt.value.s = std::move(val);
468 return Append(std::move(jt));
469 }
470
Nigel Taoca5da1f2020-08-10 15:26:29 +1000471 std::string Push(uint32_t flags) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000472 if (flags & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST) {
473 JsonThing jt;
474 jt.kind = JsonThing::Kind::Array;
475 m_stack.push_back(std::move(jt));
476 return "";
477 } else if (flags & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT) {
478 JsonThing jt;
479 jt.kind = JsonThing::Kind::Object;
480 m_stack.push_back(std::move(jt));
481 return "";
482 }
483 return "main: internal error: bad push";
484 }
485
Nigel Taoca5da1f2020-08-10 15:26:29 +1000486 std::string Pop(uint32_t flags) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000487 if (m_stack.empty()) {
488 return "main: internal error: bad pop";
489 }
490 JsonThing jt = std::move(m_stack.back().thing);
491 m_stack.pop_back();
492 return Append(std::move(jt));
493 }
494
Nigel Taoca5da1f2020-08-10 15:26:29 +1000495 void Done(wuffs_aux::DecodeJsonResult& result,
496 wuffs_aux::sync_io::Input& input,
497 wuffs_aux::IOBuffer& buffer) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000498 if (!result.error_message.empty()) {
499 return;
500 } else if (m_stack.size() != 1) {
501 result.error_message = "main: internal error: bad depth";
502 return;
503 }
Nigel Tao8098d962020-08-29 10:41:05 +1000504 result.error_message = print_json_pointers(m_stack.back().thing, 0);
Nigel Taocf6c5782020-08-03 23:43:45 +1000505 }
506
507 private:
508 std::vector<Entry> m_stack;
509};
510
511// ----
512
Nigel Taod0b16cb2020-03-14 10:15:54 +1100513std::string //
514main1(int argc, char** argv) {
515 TRY(parse_flags(argc, argv));
Nigel Taob3438432020-08-13 00:06:56 +1000516 if (!g_flags.strict_json_pointer_syntax) {
517 g_quirks.push_back(WUFFS_JSON__QUIRK_JSON_POINTER_ALLOW_TILDE_R_TILDE_N);
518 }
Nigel Taod0b16cb2020-03-14 10:15:54 +1100519
Nigel Taocf6c5782020-08-03 23:43:45 +1000520 FILE* in = stdin;
Nigel Taod60815c2020-03-26 14:32:35 +1100521 if (g_flags.remaining_argc > 1) {
522 return g_usage;
523 } else if (g_flags.remaining_argc == 1) {
Nigel Taocf6c5782020-08-03 23:43:45 +1000524 in = fopen(g_flags.remaining_argv[0], "r");
525 if (!in) {
526 return std::string("main: cannot read input file");
Nigel Taod0b16cb2020-03-14 10:15:54 +1100527 }
528 }
529
Nigel Tao2742a4f2020-08-17 00:02:49 +1000530 Callbacks callbacks;
531 wuffs_aux::sync_io::FileInput input(in);
Nigel Taocf6c5782020-08-03 23:43:45 +1000532 return wuffs_aux::DecodeJson(
Nigel Tao2742a4f2020-08-17 00:02:49 +1000533 callbacks, input,
Nigel Tao8aac6762020-08-12 22:47:45 +1000534 wuffs_base__make_slice_u32(g_quirks.data(), g_quirks.size()),
535 (g_flags.query_c_string ? g_flags.query_c_string : ""))
Nigel Taocf6c5782020-08-03 23:43:45 +1000536 .error_message;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100537}
538
539// ----
540
541int //
542compute_exit_code(std::string status_msg) {
543 if (status_msg.empty()) {
544 return 0;
545 }
Nigel Taofeded882020-08-29 10:32:12 +1000546 std::cerr << status_msg << '\n';
Nigel Taod0b16cb2020-03-14 10:15:54 +1100547 // Return an exit code of 1 for regular (forseen) errors, e.g. badly
548 // formatted or unsupported input.
549 //
550 // Return an exit code of 2 for internal (exceptional) errors, e.g. defensive
551 // run-time checks found that an internal invariant did not hold.
552 //
553 // Automated testing, including badly formatted inputs, can therefore
554 // discriminate between expected failure (exit code 1) and unexpected failure
555 // (other non-zero exit codes). Specifically, exit code 2 for internal
556 // invariant violation, exit code 139 (which is 128 + SIGSEGV on x86_64
557 // linux) for a segmentation fault (e.g. null pointer dereference).
558 return (status_msg.find("internal error:") != std::string::npos) ? 2 : 1;
559}
560
561int //
562main(int argc, char** argv) {
563 std::string z = main1(argc, argv);
564 int exit_code = compute_exit_code(z);
565 return exit_code;
566}