Line data Source code
1 : /*
2 : ArduPilot JSON parser, based on picojson.h cloned from:
3 : https://github.com/kazuho/picojson
4 : */
5 : /*
6 : Picojson copyright:
7 :
8 : * Copyright 2009-2010 Cybozu Labs, Inc.
9 : * Copyright 2011-2014 Kazuho Oku
10 : * All rights reserved.
11 : *
12 : * Redistribution and use in source and binary forms, with or without
13 : * modification, are permitted provided that the following conditions are met:
14 : *
15 : * 1. Redistributions of source code must retain the above copyright notice,
16 : * this list of conditions and the following disclaimer.
17 : *
18 : * 2. Redistributions in binary form must reproduce the above copyright notice,
19 : * this list of conditions and the following disclaimer in the documentation
20 : * and/or other materials provided with the distribution.
21 : *
22 : * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 : * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 : * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 : * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26 : * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 : * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 : * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 : * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 : * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 : * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 : * POSSIBILITY OF SUCH DAMAGE.
33 : */
34 :
35 : #pragma GCC optimize("Os")
36 :
37 : #define AP_MATH_ALLOW_DOUBLE_FUNCTIONS 1
38 :
39 : #include "AP_JSON.h"
40 : #include <AP_Filesystem/AP_Filesystem.h>
41 : #include <AP_Math/AP_Math.h>
42 : #include <stdio.h>
43 :
44 : /*
45 : load JSON file, returning a value object or nullptr on failure
46 : */
47 2123 : AP_JSON::value *AP_JSON::load_json(const char *filename)
48 : {
49 : struct stat st;
50 2123 : if (AP::FS().stat(filename, &st) != 0) {
51 0 : ::printf("No such json file %s\n", filename);
52 0 : return nullptr;
53 : }
54 2123 : int fd = AP::FS().open(filename, O_RDONLY);
55 2123 : if (fd == -1) {
56 0 : ::printf("failed to open json %s\n", filename);
57 0 : return nullptr;
58 : }
59 2123 : char *buf = NEW_NOTHROW char[st.st_size+1];
60 2123 : if (buf == nullptr) {
61 0 : AP::FS().close(fd);
62 0 : ::printf("failed to allocate json %s\n", filename);
63 0 : return nullptr;
64 : }
65 2123 : if (AP::FS().read(fd, buf, st.st_size) != st.st_size) {
66 0 : ::printf("failed to read json %s\n", filename);
67 0 : delete[] buf;
68 0 : AP::FS().close(fd);
69 0 : return nullptr;
70 : }
71 2123 : AP::FS().close(fd);
72 :
73 2123 : char *start = strchr(buf, '{');
74 2123 : if (!start) {
75 0 : ::printf("Invalid json %s\n", filename);
76 0 : delete[] buf;
77 0 : return nullptr;
78 : }
79 :
80 : /*
81 : remove comments, as not allowed by the parser
82 : */
83 2906 : for (char *p = strchr(start,'#'); p; p=strchr(p+1, '#')) {
84 : // clear to end of line
85 : do {
86 21561 : *p++ = ' ';
87 21561 : } while (*p != '\n' && *p != '\r' && *p);
88 : }
89 :
90 2123 : AP_JSON::value *obj = NEW_NOTHROW AP_JSON::value;
91 2123 : if (obj == nullptr) {
92 0 : ::printf("Invalid allocate json for %s\n", filename);
93 0 : delete[] buf;
94 0 : return nullptr;
95 : }
96 2123 : std::string err = AP_JSON::parse(*obj, start);
97 2123 : if (!err.empty()) {
98 0 : ::printf("parse failed for json %s\n", filename);
99 0 : delete obj;
100 0 : delete[] buf;
101 0 : return nullptr;
102 : }
103 :
104 2123 : delete[] buf;
105 2123 : return obj;
106 2123 : }
107 :
108 : typedef AP_JSON::value::array array;
109 : typedef AP_JSON::value::object object;
110 : typedef AP_JSON::value value;
111 : typedef AP_JSON::null null;
112 :
113 : enum {
114 : null_type,
115 : boolean_type,
116 : number_type,
117 : string_type,
118 : array_type,
119 : object_type
120 : };
121 :
122 :
123 1154490 : AP_JSON::value::value() : type_(null_type), u_()
124 : {
125 1154490 : }
126 :
127 1122413 : AP_JSON::value::value(int type, bool) : type_(type), u_()
128 : {
129 1122413 : switch (type) {
130 : #define INIT(p, v) \
131 : case p##type: \
132 : u_.p = v; \
133 : break
134 : INIT(boolean_, false);
135 : INIT(number_, 0.0);
136 688926 : INIT(string_, NEW_NOTHROW std::string());
137 180084 : INIT(array_, NEW_NOTHROW array());
138 253403 : INIT(object_, NEW_NOTHROW object());
139 : #undef INIT
140 : default:
141 : break;
142 : }
143 1122413 : }
144 :
145 29316 : AP_JSON::value::value(bool b) : type_(boolean_type), u_()
146 : {
147 29316 : u_.boolean_ = b;
148 29316 : }
149 :
150 638 : AP_JSON::value::value(double n) : type_(number_type), u_()
151 : {
152 638 : u_.number_ = n;
153 638 : }
154 :
155 0 : AP_JSON::value::value(const std::string &s) : type_(string_type), u_()
156 : {
157 0 : u_.string_ = NEW_NOTHROW std::string(s);
158 0 : }
159 :
160 0 : AP_JSON::value::value(const array &a) : type_(array_type), u_()
161 : {
162 0 : u_.array_ = NEW_NOTHROW array(a);
163 0 : }
164 :
165 0 : AP_JSON::value::value(const object &o) : type_(object_type), u_()
166 : {
167 0 : u_.object_ = NEW_NOTHROW object(o);
168 0 : }
169 :
170 0 : AP_JSON::value::value(std::string &&s) : type_(string_type), u_()
171 : {
172 0 : u_.string_ = NEW_NOTHROW std::string(std::move(s));
173 0 : }
174 :
175 0 : AP_JSON::value::value(array &&a) : type_(array_type), u_()
176 : {
177 0 : u_.array_ = NEW_NOTHROW array(std::move(a));
178 0 : }
179 :
180 0 : AP_JSON::value::value(object &&o) : type_(object_type), u_()
181 : {
182 0 : u_.object_ = NEW_NOTHROW object(std::move(o));
183 0 : }
184 :
185 0 : AP_JSON::value::value(const char *s) : type_(string_type), u_()
186 : {
187 0 : u_.string_ = NEW_NOTHROW std::string(s);
188 0 : }
189 :
190 0 : AP_JSON::value::value(const char *s, size_t len) : type_(string_type), u_()
191 : {
192 0 : u_.string_ = NEW_NOTHROW std::string(s, len);
193 0 : }
194 :
195 2937515 : void AP_JSON::value::clear()
196 : {
197 2937515 : switch (type_) {
198 : #define DEINIT(p) \
199 : case p##type: \
200 : delete u_.p; \
201 : break
202 931830 : DEINIT(string_);
203 180084 : DEINIT(array_);
204 253403 : DEINIT(object_);
205 : #undef DEINIT
206 : default:
207 : break;
208 : }
209 2937515 : }
210 :
211 2937515 : AP_JSON::value::~value()
212 : {
213 2937515 : clear();
214 2937515 : }
215 :
216 247022 : AP_JSON::value::value(const value &x) : type_(x.type_), u_()
217 : {
218 247022 : switch (type_) {
219 : #define INIT(p, v) \
220 : case p##type: \
221 : u_.p = v; \
222 : break
223 242904 : INIT(string_, NEW_NOTHROW std::string(*x.u_.string_));
224 0 : INIT(array_, NEW_NOTHROW array(*x.u_.array_));
225 0 : INIT(object_, NEW_NOTHROW object(*x.u_.object_));
226 : #undef INIT
227 4118 : default:
228 4118 : u_ = x.u_;
229 4118 : break;
230 : }
231 247022 : }
232 :
233 0 : value &AP_JSON::value::operator=(const value &x)
234 : {
235 0 : if (this != &x) {
236 0 : value t(x);
237 0 : swap(t);
238 0 : }
239 0 : return *this;
240 : }
241 :
242 385296 : AP_JSON::value::value(value &&x) : type_(null_type), u_()
243 : {
244 385296 : swap(x);
245 385296 : }
246 1152367 : value &AP_JSON::value::operator=(value &&x)
247 : {
248 1152367 : swap(x);
249 1152367 : return *this;
250 : }
251 :
252 1537663 : void AP_JSON::value::swap(value &x)
253 : {
254 1537663 : std::swap(type_, x.type_);
255 1537663 : std::swap(u_, x.u_);
256 1537663 : }
257 :
258 : #define IS(ctype, jtype) \
259 : template <> bool AP_JSON::value::is<ctype>() const { \
260 : return type_ == jtype##_type; \
261 : }
262 5574 : IS(null, null)
263 0 : IS(bool, boolean)
264 5186 : IS(std::string, string)
265 621 : IS(array, array)
266 5782 : IS(object, object)
267 : #undef IS
268 638 : template <> bool AP_JSON::value::is<double>() const
269 : {
270 638 : return type_ == number_type;
271 : }
272 :
273 : #define GET(ctype, var) \
274 : template <> const ctype &AP_JSON::value::get<ctype>() const { \
275 : return var; \
276 : } \
277 : template <> ctype &AP_JSON::value::get<ctype>() { \
278 : return var; \
279 : }
280 0 : GET(bool, u_.boolean_)
281 692221 : GET(std::string, *u_.string_)
282 385917 : GET(array, *u_.array_)
283 765097 : GET(object, *u_.object_)
284 638 : GET(double, u_.number_)
285 : #undef GET
286 :
287 : #define SET(ctype, jtype, setter) \
288 : template <> void AP_JSON::value::set<ctype>(const ctype &_val) { \
289 : clear(); \
290 : type_ = jtype##_type; \
291 : setter \
292 : }
293 0 : SET(bool, boolean, u_.boolean_ = _val;)
294 0 : SET(std::string, string, u_.string_ = NEW_NOTHROW std::string(_val);)
295 0 : SET(array, array, u_.array_ = NEW_NOTHROW array(_val);)
296 0 : SET(object, object, u_.object_ = NEW_NOTHROW object(_val);)
297 0 : SET(double, number, u_.number_ = _val;)
298 : #undef SET
299 :
300 : #define MOVESET(ctype, jtype, setter) \
301 : template <> void AP_JSON::value::set<ctype>(ctype && _val) { \
302 : clear(); \
303 : type_ = jtype##_type; \
304 : setter \
305 : }
306 0 : MOVESET(std::string, string, u_.string_ = NEW_NOTHROW std::string(std::move(_val));)
307 0 : MOVESET(array, array, u_.array_ = NEW_NOTHROW array(std::move(_val));)
308 0 : MOVESET(object, object, u_.object_ = NEW_NOTHROW object(std::move(_val));)
309 : #undef MOVESET
310 :
311 0 : bool AP_JSON::value::evaluate_as_boolean() const
312 : {
313 0 : switch (type_) {
314 : case null_type:
315 : return false;
316 0 : case boolean_type:
317 0 : return u_.boolean_;
318 0 : case number_type:
319 0 : return !is_zero(u_.number_);
320 0 : case string_type:
321 0 : return !u_.string_->empty();
322 0 : default:
323 0 : return true;
324 : }
325 : }
326 :
327 0 : const value &AP_JSON::value::get(const size_t idx) const
328 : {
329 0 : static value s_null;
330 0 : return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null;
331 : }
332 :
333 0 : value &AP_JSON::value::get(const size_t idx)
334 : {
335 0 : static value s_null;
336 0 : return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null;
337 : }
338 :
339 9833 : const value &AP_JSON::value::get(const std::string &key) const
340 : {
341 9833 : static value s_null;
342 9833 : object::const_iterator i = u_.object_->find(key);
343 9833 : return i != u_.object_->end() ? i->second : s_null;
344 : }
345 :
346 3480 : value &AP_JSON::value::get(const std::string &key)
347 : {
348 3480 : static value s_null;
349 3480 : object::iterator i = u_.object_->find(key);
350 3480 : return i != u_.object_->end() ? i->second : s_null;
351 : }
352 :
353 0 : bool AP_JSON::value::contains(const size_t idx) const
354 : {
355 0 : return idx < u_.array_->size();
356 : }
357 :
358 4308 : bool AP_JSON::value::contains(const std::string &key) const
359 : {
360 4308 : object::const_iterator i = u_.object_->find(key);
361 4308 : return i != u_.object_->end();
362 : }
363 :
364 0 : std::string AP_JSON::value::to_str() const
365 : {
366 0 : switch (type_) {
367 0 : case null_type:
368 0 : return "null";
369 0 : case boolean_type:
370 0 : return u_.boolean_ ? "true" : "false";
371 0 : case number_type: {
372 : char buf[256];
373 : double tmp;
374 0 : snprintf(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && is_zero(modf(u_.number_, &tmp)) ? "%.f" : "%.17g", u_.number_);
375 0 : return buf;
376 : }
377 0 : case string_type:
378 0 : return *u_.string_;
379 0 : case array_type:
380 0 : return "array";
381 0 : case object_type:
382 0 : return "object";
383 : default:
384 : break;
385 : }
386 0 : return std::string();
387 : }
388 :
389 : template <typename Iter> void copy(const std::string &s, Iter oi)
390 : {
391 : std::copy(s.begin(), s.end(), oi);
392 : }
393 :
394 : template <typename Iter> class input
395 : {
396 : protected:
397 : Iter cur_, end_;
398 : bool consumed_;
399 : int line_;
400 :
401 : public:
402 2123 : input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1)
403 : {
404 2123 : }
405 64122947 : int getc()
406 : {
407 64122947 : if (consumed_) {
408 58553093 : if (*cur_ == '\n') {
409 1549148 : ++line_;
410 : }
411 58553093 : ++cur_;
412 : }
413 64122947 : if (cur_ == end_) {
414 0 : consumed_ = false;
415 0 : return -1;
416 : }
417 64122947 : consumed_ = true;
418 64122947 : return *cur_ & 0xff;
419 : }
420 5567731 : void ungetc()
421 : {
422 5567731 : consumed_ = false;
423 5567731 : }
424 2123 : Iter cur() const
425 : {
426 2123 : if (consumed_) {
427 : input<Iter> *self = const_cast<input<Iter> *>(this);
428 2123 : self->consumed_ = false;
429 2123 : ++self->cur_;
430 : }
431 2123 : return cur_;
432 : }
433 0 : int line() const
434 : {
435 0 : return line_;
436 : }
437 4699481 : void skip_ws()
438 : {
439 : while (1) {
440 30651136 : int ch = getc();
441 30651136 : if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) {
442 4699481 : ungetc();
443 : break;
444 : }
445 : }
446 4699481 : }
447 3547114 : bool expect(const int expected)
448 : {
449 3547114 : skip_ws();
450 3547114 : if (getc() != expected) {
451 866974 : ungetc();
452 866974 : return false;
453 : }
454 : return true;
455 : }
456 29316 : bool match(const std::string &pattern)
457 : {
458 117264 : for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) {
459 87948 : if (getc() != *pi) {
460 0 : ungetc();
461 0 : return false;
462 : }
463 : }
464 29316 : return true;
465 : }
466 : };
467 :
468 : template <typename Iter> int _parse_quadhex(input<Iter> &in)
469 : {
470 : int uni_ch = 0, hex;
471 : for (int i = 0; i < 4; i++) {
472 : if ((hex = in.getc()) == -1) {
473 : return -1;
474 : }
475 : if ('0' <= hex && hex <= '9') {
476 : hex -= '0';
477 : } else if ('A' <= hex && hex <= 'F') {
478 : hex -= 'A' - 0xa;
479 : } else if ('a' <= hex && hex <= 'f') {
480 : hex -= 'a' - 0xa;
481 : } else {
482 : in.ungetc();
483 : return -1;
484 : }
485 : uni_ch = uni_ch * 16 + hex;
486 : }
487 : return uni_ch;
488 : }
489 :
490 1453874 : template <typename String, typename Iter> bool _parse_string(String &out, input<Iter> &in)
491 : {
492 : while (1) {
493 28681442 : int ch = in.getc();
494 28681442 : if (ch < ' ') {
495 0 : in.ungetc();
496 0 : return false;
497 28681442 : } else if (ch == '"') {
498 : return true;
499 27227568 : } else if (ch == '\\') {
500 0 : if ((ch = in.getc()) == -1) {
501 : return false;
502 : }
503 0 : switch (ch) {
504 : #define MAP(sym, val) \
505 : case sym: \
506 : out.push_back(val); \
507 : break
508 0 : MAP('"', '\"');
509 0 : MAP('\\', '\\');
510 0 : MAP('/', '/');
511 0 : MAP('b', '\b');
512 0 : MAP('f', '\f');
513 0 : MAP('n', '\n');
514 0 : MAP('r', '\r');
515 0 : MAP('t', '\t');
516 : #undef MAP
517 : default:
518 : return false;
519 : }
520 : } else {
521 27227568 : out.push_back(static_cast<char>(ch));
522 : }
523 : }
524 : return false;
525 : }
526 :
527 180084 : template <typename Context, typename Iter> bool _parse_array(Context &ctx, input<Iter> &in)
528 : {
529 180084 : if (!ctx.parse_array_start()) {
530 : return false;
531 : }
532 : size_t idx = 0;
533 180084 : if (in.expect(']')) {
534 0 : return ctx.parse_array_stop(idx);
535 : }
536 : do {
537 385296 : if (!ctx.parse_array_item(in, idx)) {
538 : return false;
539 : }
540 385296 : idx++;
541 385296 : } while (in.expect(','));
542 180084 : return in.expect(']') && ctx.parse_array_stop(idx);
543 : }
544 :
545 253403 : template <typename Context, typename Iter> bool _parse_object(Context &ctx, input<Iter> &in)
546 : {
547 253403 : if (!ctx.parse_object_start()) {
548 : return false;
549 : }
550 253403 : if (in.expect('}')) {
551 : return true;
552 : }
553 : do {
554 764948 : std::string key;
555 764948 : if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) {
556 0 : return false;
557 : }
558 764948 : if (!ctx.parse_object_item(in, key)) {
559 : return false;
560 : }
561 764948 : } while (in.expect(','));
562 253403 : return in.expect('}');
563 : }
564 :
565 638 : template <typename Iter> std::string _parse_number(input<Iter> &in)
566 : {
567 638 : std::string num_str;
568 : while (1) {
569 2940 : int ch = in.getc();
570 2940 : if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') {
571 1897 : num_str.push_back(static_cast<char>(ch));
572 1043 : } else if (ch == '.') {
573 405 : num_str.push_back('.');
574 : } else {
575 638 : in.ungetc();
576 : break;
577 : }
578 : }
579 638 : return num_str;
580 : }
581 :
582 1152367 : template <typename Context, typename Iter> bool _parse(Context &ctx, input<Iter> &in)
583 : {
584 1152367 : in.skip_ws();
585 1152367 : int ch = in.getc();
586 1152367 : switch (ch) {
587 : #define IS(ch, text, op) \
588 : case ch: \
589 : if (in.match(text) && op) { \
590 : return true; \
591 : } else { \
592 : return false; \
593 : }
594 0 : IS('n', "ull", ctx.set_null());
595 0 : IS('f', "alse", ctx.set_bool(false));
596 29316 : IS('t', "rue", ctx.set_bool(true));
597 : #undef IS
598 688926 : case '"':
599 688926 : return ctx.parse_string(in);
600 180084 : case '[':
601 180084 : return _parse_array(ctx, in);
602 253403 : case '{':
603 253403 : return _parse_object(ctx, in);
604 638 : default:
605 638 : if (('0' <= ch && ch <= '9') || ch == '-') {
606 : double f;
607 : char *endp;
608 638 : in.ungetc();
609 638 : std::string num_str(_parse_number(in));
610 638 : if (num_str.empty()) {
611 : return false;
612 : }
613 638 : f = strtod(num_str.c_str(), &endp);
614 638 : if (endp == num_str.c_str() + num_str.size()) {
615 638 : ctx.set_number(f);
616 638 : return true;
617 : }
618 : return false;
619 638 : }
620 : break;
621 : }
622 0 : in.ungetc();
623 0 : return false;
624 : }
625 :
626 : class default_parse_context
627 : {
628 : protected:
629 : value *out_;
630 :
631 : public:
632 1152367 : default_parse_context(value *out) : out_(out)
633 : {
634 1152367 : }
635 0 : bool set_null()
636 : {
637 0 : *out_ = value();
638 0 : return true;
639 : }
640 29316 : bool set_bool(bool b)
641 : {
642 29316 : *out_ = value(b);
643 29316 : return true;
644 : }
645 638 : bool set_number(double f)
646 : {
647 638 : *out_ = value(f);
648 638 : return true;
649 : }
650 688926 : template <typename Iter> bool parse_string(input<Iter> &in)
651 : {
652 688926 : *out_ = value(string_type, false);
653 688926 : return _parse_string(out_->get<std::string>(), in);
654 : }
655 180084 : bool parse_array_start()
656 : {
657 180084 : *out_ = value(array_type, false);
658 180084 : return true;
659 : }
660 385296 : template <typename Iter> bool parse_array_item(input<Iter> &in, size_t)
661 : {
662 385296 : array &a = out_->get<array>();
663 385296 : a.push_back(value());
664 385296 : default_parse_context ctx(&a.back());
665 385296 : return _parse(ctx, in);
666 : }
667 180084 : bool parse_array_stop(size_t)
668 : {
669 180084 : return true;
670 : }
671 253403 : bool parse_object_start()
672 : {
673 253403 : *out_ = value(object_type, false);
674 253403 : return true;
675 : }
676 764948 : template <typename Iter> bool parse_object_item(input<Iter> &in, const std::string &key)
677 : {
678 764948 : object &o = out_->get<object>();
679 764948 : default_parse_context ctx(&o[key]);
680 764948 : return _parse(ctx, in);
681 : }
682 :
683 : private:
684 : default_parse_context(const default_parse_context &);
685 : default_parse_context &operator=(const default_parse_context &);
686 : };
687 :
688 2123 : template <typename Context, typename Iter> Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err)
689 : {
690 2123 : input<Iter> in(first, last);
691 2123 : if (!_parse(ctx, in) && err != NULL) {
692 : char buf[64];
693 0 : snprintf(buf, sizeof(buf), "syntax error at line %d near: ", in.line());
694 0 : *err = buf;
695 : while (1) {
696 0 : int ch = in.getc();
697 0 : if (ch == -1 || ch == '\n') {
698 : break;
699 0 : } else if (ch >= ' ') {
700 0 : err->push_back(static_cast<char>(ch));
701 : }
702 : }
703 : }
704 2123 : return in.cur();
705 : }
706 :
707 2123 : template <typename Iter> Iter parse(value &out, const Iter &first, const Iter &last, std::string *err)
708 : {
709 2123 : default_parse_context ctx(&out);
710 2123 : return _parse(ctx, first, last, err);
711 : }
712 :
713 2123 : std::string AP_JSON::parse(value &out, const std::string &s)
714 : {
715 2123 : std::string err;
716 2123 : ::parse(out, s.begin(), s.end(), &err);
717 2123 : return err;
718 : }
|