From 5795a7e00d699c7998d5e38c3a06f6fa6d45f8be Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Thu, 1 Feb 2024 15:18:12 +1100 Subject: [PATCH] AP_JSON: added JSON parsing library --- libraries/AP_JSON/AP_JSON.cpp | 718 +++++++++++++++++++ libraries/AP_JSON/AP_JSON.h | 86 +++ libraries/AP_JSON/examples/XPlane/XPlane.cpp | 49 ++ libraries/AP_JSON/examples/XPlane/wscript | 7 + 4 files changed, 860 insertions(+) create mode 100644 libraries/AP_JSON/AP_JSON.cpp create mode 100644 libraries/AP_JSON/AP_JSON.h create mode 100644 libraries/AP_JSON/examples/XPlane/XPlane.cpp create mode 100644 libraries/AP_JSON/examples/XPlane/wscript diff --git a/libraries/AP_JSON/AP_JSON.cpp b/libraries/AP_JSON/AP_JSON.cpp new file mode 100644 index 00000000000000..823de3512538aa --- /dev/null +++ b/libraries/AP_JSON/AP_JSON.cpp @@ -0,0 +1,718 @@ +/* + ArduPilot JSON parser, based on picojson.h cloned from: + https://github.com/kazuho/picojson + */ +/* + Picojson copyright: + + * Copyright 2009-2010 Cybozu Labs, Inc. + * Copyright 2011-2014 Kazuho Oku + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma GCC optimize("Os") + +#define ALLOW_DOUBLE_MATH_FUNCTIONS + +#include "AP_JSON.h" +#include +#include +#include + +/* + load JSON file, returning a value object or nullptr on failure + */ +AP_JSON::value *AP_JSON::load_json(const char *filename) +{ + struct stat st; + if (AP::FS().stat(filename, &st) != 0) { + ::printf("No such json file %s\n", filename); + return nullptr; + } + int fd = AP::FS().open(filename, O_RDONLY); + if (fd == -1) { + ::printf("failed to open json %s\n", filename); + return nullptr; + } + char *buf = new char[st.st_size+1]; + if (buf == nullptr) { + AP::FS().close(fd); + ::printf("failed to allocate json %s\n", filename); + return nullptr; + } + if (AP::FS().read(fd, buf, st.st_size) != st.st_size) { + ::printf("failed to read json %s\n", filename); + delete[] buf; + AP::FS().close(fd); + return nullptr; + } + AP::FS().close(fd); + + char *start = strchr(buf, '{'); + if (!start) { + ::printf("Invalid json %s", filename); + delete[] buf; + return nullptr; + } + + /* + remove comments, as not allowed by the parser + */ + for (char *p = strchr(start,'#'); p; p=strchr(p+1, '#')) { + // clear to end of line + do { + *p++ = ' '; + } while (*p != '\n' && *p != '\r' && *p); + } + + AP_JSON::value *obj = new AP_JSON::value; + if (obj == nullptr) { + ::printf("Invalid allocate json for %s", filename); + delete[] buf; + return nullptr; + } + std::string err = AP_JSON::parse(*obj, start); + if (!err.empty()) { + ::printf("parse failed for json %s", filename); + delete obj; + delete[] buf; + return nullptr; + } + + delete[] buf; + return obj; +} + +typedef AP_JSON::value::array array; +typedef AP_JSON::value::object object; +typedef AP_JSON::value value; +typedef AP_JSON::null null; + +enum { + null_type, + boolean_type, + number_type, + string_type, + array_type, + object_type +}; + + +AP_JSON::value::value() : type_(null_type), u_() +{ +} + +AP_JSON::value::value(int type, bool) : type_(type), u_() +{ + switch (type) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(boolean_, false); + INIT(number_, 0.0); + INIT(string_, new std::string()); + INIT(array_, new array()); + INIT(object_, new object()); +#undef INIT + default: + break; + } +} + +AP_JSON::value::value(bool b) : type_(boolean_type), u_() +{ + u_.boolean_ = b; +} + +AP_JSON::value::value(double n) : type_(number_type), u_() +{ + u_.number_ = n; +} + +AP_JSON::value::value(const std::string &s) : type_(string_type), u_() +{ + u_.string_ = new std::string(s); +} + +AP_JSON::value::value(const array &a) : type_(array_type), u_() +{ + u_.array_ = new array(a); +} + +AP_JSON::value::value(const object &o) : type_(object_type), u_() +{ + u_.object_ = new object(o); +} + +AP_JSON::value::value(std::string &&s) : type_(string_type), u_() +{ + u_.string_ = new std::string(std::move(s)); +} + +AP_JSON::value::value(array &&a) : type_(array_type), u_() +{ + u_.array_ = new array(std::move(a)); +} + +AP_JSON::value::value(object &&o) : type_(object_type), u_() +{ + u_.object_ = new object(std::move(o)); +} + +AP_JSON::value::value(const char *s) : type_(string_type), u_() +{ + u_.string_ = new std::string(s); +} + +AP_JSON::value::value(const char *s, size_t len) : type_(string_type), u_() +{ + u_.string_ = new std::string(s, len); +} + +void AP_JSON::value::clear() +{ + switch (type_) { +#define DEINIT(p) \ + case p##type: \ + delete u_.p; \ + break + DEINIT(string_); + DEINIT(array_); + DEINIT(object_); +#undef DEINIT + default: + break; + } +} + +AP_JSON::value::~value() +{ + clear(); +} + +AP_JSON::value::value(const value &x) : type_(x.type_), u_() +{ + switch (type_) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(string_, new std::string(*x.u_.string_)); + INIT(array_, new array(*x.u_.array_)); + INIT(object_, new object(*x.u_.object_)); +#undef INIT + default: + u_ = x.u_; + break; + } +} + +value &AP_JSON::value::operator=(const value &x) +{ + if (this != &x) { + value t(x); + swap(t); + } + return *this; +} + +AP_JSON::value::value(value &&x) : type_(null_type), u_() +{ + swap(x); +} +value &AP_JSON::value::operator=(value &&x) +{ + swap(x); + return *this; +} + +void AP_JSON::value::swap(value &x) +{ + std::swap(type_, x.type_); + std::swap(u_, x.u_); +} + +#define IS(ctype, jtype) \ + template <> bool AP_JSON::value::is() const { \ + return type_ == jtype##_type; \ + } +IS(null, null) +IS(bool, boolean) +IS(std::string, string) +IS(array, array) +IS(object, object) +#undef IS +template <> bool AP_JSON::value::is() const +{ + return type_ == number_type; +} + +#define GET(ctype, var) \ + template <> const ctype &AP_JSON::value::get() const { \ + return var; \ + } \ + template <> ctype &AP_JSON::value::get() { \ + return var; \ + } +GET(bool, u_.boolean_) +GET(std::string, *u_.string_) +GET(array, *u_.array_) +GET(object, *u_.object_) +GET(double, u_.number_) +#undef GET + +#define SET(ctype, jtype, setter) \ + template <> void AP_JSON::value::set(const ctype &_val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ + } +SET(bool, boolean, u_.boolean_ = _val;) +SET(std::string, string, u_.string_ = new std::string(_val);) +SET(array, array, u_.array_ = new array(_val);) +SET(object, object, u_.object_ = new object(_val);) +SET(double, number, u_.number_ = _val;) +#undef SET + +#define MOVESET(ctype, jtype, setter) \ + template <> void AP_JSON::value::set(ctype && _val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ + } +MOVESET(std::string, string, u_.string_ = new std::string(std::move(_val));) +MOVESET(array, array, u_.array_ = new array(std::move(_val));) +MOVESET(object, object, u_.object_ = new object(std::move(_val));) +#undef MOVESET + +bool AP_JSON::value::evaluate_as_boolean() const +{ + switch (type_) { + case null_type: + return false; + case boolean_type: + return u_.boolean_; + case number_type: + return !is_zero(u_.number_); + case string_type: + return !u_.string_->empty(); + default: + return true; + } +} + +const value &AP_JSON::value::get(const size_t idx) const +{ + static value s_null; + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +value &AP_JSON::value::get(const size_t idx) +{ + static value s_null; + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +const value &AP_JSON::value::get(const std::string &key) const +{ + static value s_null; + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +value &AP_JSON::value::get(const std::string &key) +{ + static value s_null; + object::iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +bool AP_JSON::value::contains(const size_t idx) const +{ + return idx < u_.array_->size(); +} + +bool AP_JSON::value::contains(const std::string &key) const +{ + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end(); +} + +std::string AP_JSON::value::to_str() const +{ + switch (type_) { + case null_type: + return "null"; + case boolean_type: + return u_.boolean_ ? "true" : "false"; + case number_type: { + char buf[256]; + double tmp; + snprintf(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && is_zero(modf(u_.number_, &tmp)) ? "%.f" : "%.17g", u_.number_); + return buf; + } + case string_type: + return *u_.string_; + case array_type: + return "array"; + case object_type: + return "object"; + default: + break; + } + return std::string(); +} + +template void copy(const std::string &s, Iter oi) +{ + std::copy(s.begin(), s.end(), oi); +} + +template class input +{ +protected: + Iter cur_, end_; + bool consumed_; + int line_; + +public: + input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1) + { + } + int getc() + { + if (consumed_) { + if (*cur_ == '\n') { + ++line_; + } + ++cur_; + } + if (cur_ == end_) { + consumed_ = false; + return -1; + } + consumed_ = true; + return *cur_ & 0xff; + } + void ungetc() + { + consumed_ = false; + } + Iter cur() const + { + if (consumed_) { + input *self = const_cast *>(this); + self->consumed_ = false; + ++self->cur_; + } + return cur_; + } + int line() const + { + return line_; + } + void skip_ws() + { + while (1) { + int ch = getc(); + if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { + ungetc(); + break; + } + } + } + bool expect(const int expected) + { + skip_ws(); + if (getc() != expected) { + ungetc(); + return false; + } + return true; + } + bool match(const std::string &pattern) + { + for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { + if (getc() != *pi) { + ungetc(); + return false; + } + } + return true; + } +}; + +template int _parse_quadhex(input &in) +{ + int uni_ch = 0, hex; + for (int i = 0; i < 4; i++) { + if ((hex = in.getc()) == -1) { + return -1; + } + if ('0' <= hex && hex <= '9') { + hex -= '0'; + } else if ('A' <= hex && hex <= 'F') { + hex -= 'A' - 0xa; + } else if ('a' <= hex && hex <= 'f') { + hex -= 'a' - 0xa; + } else { + in.ungetc(); + return -1; + } + uni_ch = uni_ch * 16 + hex; + } + return uni_ch; +} + +template bool _parse_string(String &out, input &in) +{ + while (1) { + int ch = in.getc(); + if (ch < ' ') { + in.ungetc(); + return false; + } else if (ch == '"') { + return true; + } else if (ch == '\\') { + if ((ch = in.getc()) == -1) { + return false; + } + switch (ch) { +#define MAP(sym, val) \ + case sym: \ + out.push_back(val); \ + break + MAP('"', '\"'); + MAP('\\', '\\'); + MAP('/', '/'); + MAP('b', '\b'); + MAP('f', '\f'); + MAP('n', '\n'); + MAP('r', '\r'); + MAP('t', '\t'); +#undef MAP + default: + return false; + } + } else { + out.push_back(static_cast(ch)); + } + } + return false; +} + +template bool _parse_array(Context &ctx, input &in) +{ + if (!ctx.parse_array_start()) { + return false; + } + size_t idx = 0; + if (in.expect(']')) { + return ctx.parse_array_stop(idx); + } + do { + if (!ctx.parse_array_item(in, idx)) { + return false; + } + idx++; + } while (in.expect(',')); + return in.expect(']') && ctx.parse_array_stop(idx); +} + +template bool _parse_object(Context &ctx, input &in) +{ + if (!ctx.parse_object_start()) { + return false; + } + if (in.expect('}')) { + return true; + } + do { + std::string key; + if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) { + return false; + } + if (!ctx.parse_object_item(in, key)) { + return false; + } + } while (in.expect(',')); + return in.expect('}'); +} + +template std::string _parse_number(input &in) +{ + std::string num_str; + while (1) { + int ch = in.getc(); + if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { + num_str.push_back(static_cast(ch)); + } else if (ch == '.') { + num_str.push_back('.'); + } else { + in.ungetc(); + break; + } + } + return num_str; +} + +template bool _parse(Context &ctx, input &in) +{ + in.skip_ws(); + int ch = in.getc(); + switch (ch) { +#define IS(ch, text, op) \ + case ch: \ + if (in.match(text) && op) { \ + return true; \ + } else { \ + return false; \ + } + IS('n', "ull", ctx.set_null()); + IS('f', "alse", ctx.set_bool(false)); + IS('t', "rue", ctx.set_bool(true)); +#undef IS + case '"': + return ctx.parse_string(in); + case '[': + return _parse_array(ctx, in); + case '{': + return _parse_object(ctx, in); + default: + if (('0' <= ch && ch <= '9') || ch == '-') { + double f; + char *endp; + in.ungetc(); + std::string num_str(_parse_number(in)); + if (num_str.empty()) { + return false; + } + f = strtod(num_str.c_str(), &endp); + if (endp == num_str.c_str() + num_str.size()) { + ctx.set_number(f); + return true; + } + return false; + } + break; + } + in.ungetc(); + return false; +} + +class default_parse_context +{ +protected: + value *out_; + +public: + default_parse_context(value *out) : out_(out) + { + } + bool set_null() + { + *out_ = value(); + return true; + } + bool set_bool(bool b) + { + *out_ = value(b); + return true; + } + bool set_number(double f) + { + *out_ = value(f); + return true; + } + template bool parse_string(input &in) + { + *out_ = value(string_type, false); + return _parse_string(out_->get(), in); + } + bool parse_array_start() + { + *out_ = value(array_type, false); + return true; + } + template bool parse_array_item(input &in, size_t) + { + array &a = out_->get(); + a.push_back(value()); + default_parse_context ctx(&a.back()); + return _parse(ctx, in); + } + bool parse_array_stop(size_t) + { + return true; + } + bool parse_object_start() + { + *out_ = value(object_type, false); + return true; + } + template bool parse_object_item(input &in, const std::string &key) + { + object &o = out_->get(); + default_parse_context ctx(&o[key]); + return _parse(ctx, in); + } + +private: + default_parse_context(const default_parse_context &); + default_parse_context &operator=(const default_parse_context &); +}; + +template Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err) +{ + input in(first, last); + if (!_parse(ctx, in) && err != NULL) { + char buf[64]; + snprintf(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); + *err = buf; + while (1) { + int ch = in.getc(); + if (ch == -1 || ch == '\n') { + break; + } else if (ch >= ' ') { + err->push_back(static_cast(ch)); + } + } + } + return in.cur(); +} + +template Iter parse(value &out, const Iter &first, const Iter &last, std::string *err) +{ + default_parse_context ctx(&out); + return _parse(ctx, first, last, err); +} + +std::string AP_JSON::parse(value &out, const std::string &s) +{ + std::string err; + ::parse(out, s.begin(), s.end(), &err); + return err; +} diff --git a/libraries/AP_JSON/AP_JSON.h b/libraries/AP_JSON/AP_JSON.h new file mode 100644 index 00000000000000..c70a76a2dcae1f --- /dev/null +++ b/libraries/AP_JSON/AP_JSON.h @@ -0,0 +1,86 @@ +/* + ArduPilot JSON parser + */ + +#pragma once + +#include +#include +#include + +/* + this avoids an issue on stm32 with std::string + */ +#undef _GLIBCXX_USE_C99_STDIO +#include +#define _GLIBCXX_USE_C99_STDIO 1 + +class AP_JSON +{ +public: + struct null {}; + + class value + { + public: + typedef std::vector array; + typedef std::map object; + union _storage { + bool boolean_; + double number_; + std::string *string_; + array *array_; + object *object_; + }; + + + protected: + int type_; + _storage u_; + + public: + value(); + value(int type, bool); + explicit value(bool b); + explicit value(double n); + explicit value(const std::string &s); + explicit value(const array &a); + explicit value(const object &o); + explicit value(std::string &&s); + explicit value(array &&a); + explicit value(object &&o); + + explicit value(const char *s); + value(const char *s, size_t len); + ~value(); + value(const value &x); + value &operator=(const value &x); + value(value &&x); + value &operator=(value &&x); + void swap(value &x); + template bool is() const; + template const T &get() const; + template T &get(); + template void set(const T &); + template void set(T &&); + bool evaluate_as_boolean() const; + const value &get(const size_t idx) const; + const value &get(const std::string &key) const; + value &get(const size_t idx); + value &get(const std::string &key); + + bool contains(const size_t idx) const; + bool contains(const std::string &key) const; + std::string to_str() const; + + private: + template value(const T *); // intentionally defined to block implicit conversion of pointer to bool + void clear(); + }; + + static std::string parse(value &out, const std::string &s); + + // load a json file, returning a value object + static value *load_json(const char *filename); +}; + diff --git a/libraries/AP_JSON/examples/XPlane/XPlane.cpp b/libraries/AP_JSON/examples/XPlane/XPlane.cpp new file mode 100644 index 00000000000000..d7af16a4f57221 --- /dev/null +++ b/libraries/AP_JSON/examples/XPlane/XPlane.cpp @@ -0,0 +1,49 @@ +// +// tests for the AP_JSON parser +// + +#include +#include + +#include + +void setup(); +void loop(); + +const AP_HAL::HAL& hal = AP_HAL::get_HAL(); + +static void test_xplane(void) +{ + const uint32_t m1 = hal.util->available_memory(); + auto *obj = AP_JSON::load_json("@ROMFS/models/xplane_plane.json"); + if (obj == nullptr) { + ::printf("Failed to parse json\n"); + } + const uint32_t m2 = hal.util->available_memory(); + ::printf("Used %u bytes\n", unsigned(m1-m2)); + + const AP_JSON::value::object& o = obj->get(); + for (AP_JSON::value::object::const_iterator i = o.begin(); + i != o.end(); + ++i) { + const char *label = i->first.c_str(); + ::printf("Label: %s\n", label); + } + delete obj; +} + +/* + * euler angle tests + */ +void setup(void) +{ + hal.console->printf("AP_JSON tests\n"); +} + +void loop(void) +{ + ::printf("Memory: %u\n", (unsigned)hal.util->available_memory()); + test_xplane(); +} + +AP_HAL_MAIN(); diff --git a/libraries/AP_JSON/examples/XPlane/wscript b/libraries/AP_JSON/examples/XPlane/wscript new file mode 100644 index 00000000000000..719ec151ba9d4b --- /dev/null +++ b/libraries/AP_JSON/examples/XPlane/wscript @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# encoding: utf-8 + +def build(bld): + bld.ap_example( + use='ap', + )