Message ID | 20220427140929.429977-1-hanlinchen@chromium.org |
---|---|
State | Accepted |
Headers | show |
Series |
|
Related | show |
Hi Han-Lin, Thank you for the patch. On Wed, Apr 27, 2022 at 10:09:27PM +0800, Han-Lin Chen via libcamera-devel wrote: > Introduce YamlParser as a helper to convert contents of a yaml file to > a tree based structure for easier reading, and to avoid writing parser > with raw yaml tokens. The class is based on libyaml, and only support > reading but not writing a yaml file. > > The interface is inspired by Json::Value class from jsoncpp: > http://jsoncpp.sourceforge.net/class_json_1_1_value.html > > Signed-off-by: Han-Lin Chen <hanlinchen@chromium.org> > --- > README.rst | 4 +- > include/libcamera/internal/meson.build | 1 + > include/libcamera/internal/yaml_parser.h | 87 +++ > src/libcamera/meson.build | 3 + > src/libcamera/yaml_parser.cpp | 679 +++++++++++++++++++++++ > 5 files changed, 772 insertions(+), 2 deletions(-) > create mode 100644 include/libcamera/internal/yaml_parser.h > create mode 100644 src/libcamera/yaml_parser.cpp [snip] > diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp > new file mode 100644 > index 00000000..4a047494 > --- /dev/null > +++ b/src/libcamera/yaml_parser.cpp > @@ -0,0 +1,679 @@ [snip] > +/** > + * \fn YamlParserContext::parseDictionaryOrList() > + * \brief A helper function to abstract the common part of parsing dictionary or list > + * > + * \param[in] isDictionary True for parsing a dictionary, and false for a list > + * \param[in] parseItem The callback to handle an item > + * > + * A helper function to abstract parsing an item from a dictionary or a list. > + * The differences of them in a YAML event stream are: > + * > + * 1. The start and end event types are different > + * 2. There is a leading scalar string as key in the items of a dictionary > + * > + * The caller should handle the leading key string in its callback parseItem > + * when it's a dictionary. > + * > + * \return 0 on success or a negative error code otherwise > + * \retval -EINVAL The parser is failed to initialize > + */ > +int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, > + const std::function<int(EventPtr event)> &parseItem) > +{ > + yaml_event_type_t endEventType = YAML_SEQUENCE_END_EVENT; > + if (type == YamlObject::Dictionary) > + endEventType = YAML_MAPPING_END_EVENT; > + > + /* > + * Add a safety counter to make sure we don't loop indefinitely in case > + * the YAML file is malformed. > + */ > + for (unsigned int sentinel = 1000; sentinel; sentinel--) { > + auto evt = nextEvent(); > + if (!evt) > + return -EINVAL; > + > + if (evt->type == endEventType) > + return 0; > + > + int ret = parseItem(std::move(evt)); > + if (ret) > + return ret; > + } > + > + LOG(YamlParser, Error) << "The YAML file contains a List or Dictionary" > + " whose size exceeding the parser's limit(1000)"; s/exceeding/exceeds/ > + > + return -EINVAL; > +} > + > +/** > + * \fn YamlParserContext::parseNextYamlObject() > + * \brief Parse next YAML event and read it as a YamlObject > + * \param[in] yamlObject The result of YamlObject > + * \param[in] event The leading event of the object > + * > + * Parse next YAML object separately as a value, list or dictionary. > + * > + * \return 0 on success or a negative error code otherwise > + * \retval -EINVAL Fail to parse the YAML file. > + */ > +int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr event) > +{ > + if (!event) > + return -EINVAL; > + > + switch (event->type) { > + case YAML_SCALAR_EVENT: > + yamlObject.type_ = YamlObject::Value; > + readValue(yamlObject.value_, std::move(event)); > + return 0; > + > + case YAML_SEQUENCE_START_EVENT: { > + yamlObject.type_ = YamlObject::List; > + auto &list = yamlObject.list_; > + auto handler = [this, &list](EventPtr evt) { > + list.emplace_back(new YamlObject()); > + return parseNextYamlObject(*list.back(), std::move(evt)); > + }; > + return parseDictionaryOrList(YamlObject::List, handler); > + } > + > + case YAML_MAPPING_START_EVENT: { > + yamlObject.type_ = YamlObject::Dictionary; > + auto &dictionary = yamlObject.dictionary_; > + auto handler = [this, &dictionary](EventPtr evtKey) { > + /* Parse key */ > + if (evtKey->type != YAML_SCALAR_EVENT) { > + LOG(YamlParser, Error) << "Expect key at line: " > + << evtKey->start_mark.line > + << " column: " > + << evtKey->start_mark.column; > + return -EINVAL; > + } > + > + std::string key; > + readValue(key, std::move(evtKey)); > + > + /* Parse value */ > + EventPtr evtValue = nextEvent(); > + if (!evtValue) > + return -EINVAL; > + > + auto elem = dictionary.emplace(key, std::make_unique<YamlObject>()); > + return parseNextYamlObject(*elem.first->second.get(), std::move(evtValue)); > + }; > + return parseDictionaryOrList(YamlObject::Dictionary, handler); > + } Missing blank line. I'll fix these when applying. > + default: > + LOG(YamlParser, Error) << "Invalid YAML file"; > + return -EINVAL; > + } > +} [snip]
On Mon, May 09, 2022 at 08:10:28PM +0300, Laurent Pinchart via libcamera-devel wrote: > Hi Han-Lin, > > Thank you for the patch. > > On Wed, Apr 27, 2022 at 10:09:27PM +0800, Han-Lin Chen via libcamera-devel wrote: > > Introduce YamlParser as a helper to convert contents of a yaml file to > > a tree based structure for easier reading, and to avoid writing parser > > with raw yaml tokens. The class is based on libyaml, and only support > > reading but not writing a yaml file. > > > > The interface is inspired by Json::Value class from jsoncpp: > > http://jsoncpp.sourceforge.net/class_json_1_1_value.html > > > > Signed-off-by: Han-Lin Chen <hanlinchen@chromium.org> > > --- > > README.rst | 4 +- > > include/libcamera/internal/meson.build | 1 + > > include/libcamera/internal/yaml_parser.h | 87 +++ > > src/libcamera/meson.build | 3 + > > src/libcamera/yaml_parser.cpp | 679 +++++++++++++++++++++++ > > 5 files changed, 772 insertions(+), 2 deletions(-) > > create mode 100644 include/libcamera/internal/yaml_parser.h > > create mode 100644 src/libcamera/yaml_parser.cpp > > [snip] > > > diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp > > new file mode 100644 > > index 00000000..4a047494 > > --- /dev/null > > +++ b/src/libcamera/yaml_parser.cpp > > @@ -0,0 +1,679 @@ > > [snip] > > > +/** > > + * \fn YamlParserContext::parseDictionaryOrList() > > + * \brief A helper function to abstract the common part of parsing dictionary or list > > + * > > + * \param[in] isDictionary True for parsing a dictionary, and false for a list > > + * \param[in] parseItem The callback to handle an item > > + * > > + * A helper function to abstract parsing an item from a dictionary or a list. > > + * The differences of them in a YAML event stream are: > > + * > > + * 1. The start and end event types are different > > + * 2. There is a leading scalar string as key in the items of a dictionary > > + * > > + * The caller should handle the leading key string in its callback parseItem > > + * when it's a dictionary. > > + * > > + * \return 0 on success or a negative error code otherwise > > + * \retval -EINVAL The parser is failed to initialize > > + */ > > +int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, > > + const std::function<int(EventPtr event)> &parseItem) > > +{ > > + yaml_event_type_t endEventType = YAML_SEQUENCE_END_EVENT; > > + if (type == YamlObject::Dictionary) > > + endEventType = YAML_MAPPING_END_EVENT; > > + > > + /* > > + * Add a safety counter to make sure we don't loop indefinitely in case > > + * the YAML file is malformed. > > + */ > > + for (unsigned int sentinel = 1000; sentinel; sentinel--) { > > + auto evt = nextEvent(); > > + if (!evt) > > + return -EINVAL; > > + > > + if (evt->type == endEventType) > > + return 0; > > + > > + int ret = parseItem(std::move(evt)); > > + if (ret) > > + return ret; > > + } > > + > > + LOG(YamlParser, Error) << "The YAML file contains a List or Dictionary" > > + " whose size exceeding the parser's limit(1000)"; > > s/exceeding/exceeds/ > > > + > > + return -EINVAL; > > +} > > + > > +/** > > + * \fn YamlParserContext::parseNextYamlObject() > > + * \brief Parse next YAML event and read it as a YamlObject > > + * \param[in] yamlObject The result of YamlObject > > + * \param[in] event The leading event of the object > > + * > > + * Parse next YAML object separately as a value, list or dictionary. > > + * > > + * \return 0 on success or a negative error code otherwise > > + * \retval -EINVAL Fail to parse the YAML file. > > + */ > > +int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr event) > > +{ > > + if (!event) > > + return -EINVAL; > > + > > + switch (event->type) { > > + case YAML_SCALAR_EVENT: > > + yamlObject.type_ = YamlObject::Value; > > + readValue(yamlObject.value_, std::move(event)); > > + return 0; > > + > > + case YAML_SEQUENCE_START_EVENT: { > > + yamlObject.type_ = YamlObject::List; > > + auto &list = yamlObject.list_; > > + auto handler = [this, &list](EventPtr evt) { > > + list.emplace_back(new YamlObject()); > > + return parseNextYamlObject(*list.back(), std::move(evt)); > > + }; > > + return parseDictionaryOrList(YamlObject::List, handler); > > + } > > + > > + case YAML_MAPPING_START_EVENT: { > > + yamlObject.type_ = YamlObject::Dictionary; > > + auto &dictionary = yamlObject.dictionary_; > > + auto handler = [this, &dictionary](EventPtr evtKey) { > > + /* Parse key */ > > + if (evtKey->type != YAML_SCALAR_EVENT) { > > + LOG(YamlParser, Error) << "Expect key at line: " > > + << evtKey->start_mark.line > > + << " column: " > > + << evtKey->start_mark.column; > > + return -EINVAL; > > + } > > + > > + std::string key; > > + readValue(key, std::move(evtKey)); > > + > > + /* Parse value */ > > + EventPtr evtValue = nextEvent(); > > + if (!evtValue) > > + return -EINVAL; > > + > > + auto elem = dictionary.emplace(key, std::make_unique<YamlObject>()); > > + return parseNextYamlObject(*elem.first->second.get(), std::move(evtValue)); > > + }; > > + return parseDictionaryOrList(YamlObject::Dictionary, handler); > > + } > > Missing blank line. > > I'll fix these when applying. And Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> :-) > > + default: > > + LOG(YamlParser, Error) << "Invalid YAML file"; > > + return -EINVAL; > > + } > > +} > > [snip]
Quoting Han-Lin Chen via libcamera-devel (2022-04-27 15:09:27) > Introduce YamlParser as a helper to convert contents of a yaml file to > a tree based structure for easier reading, and to avoid writing parser > with raw yaml tokens. The class is based on libyaml, and only support > reading but not writing a yaml file. > > The interface is inspired by Json::Value class from jsoncpp: > http://jsoncpp.sourceforge.net/class_json_1_1_value.html > > Signed-off-by: Han-Lin Chen <hanlinchen@chromium.org> This causes compile failures on GCC-8. (I haven't yet tested further than that, as it's the first failure that hits). FAILED: src/libcamera/libcamera.so.0.0.0.p/yaml_parser.cpp.o g++-8 -Isrc/libcamera/libcamera.so.0.0.0.p -Isrc/libcamera -I../../../src/libcamera/src/libcamera -Iinclude -I../../../src/libcamera/include -Iinclude/libcamera -Iinclude/libcamera/ipa -Iinclude/libcamera/internal -Isrc/libcamera/proxy -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wnon-virtual-dtor -Wextra -Werror -std=c++17 -g -Wl,--start-group -lstdc++fs -Wl,--end-group -Wshadow -include config.h -fPIC -DLIBCAMERA_BASE_PRIVATE -MD -MQ src/libcamera/libcamera.so.0.0.0.p/yaml_parser.cpp.o -MF src/libcamera/libcamera.so.0.0.0.p/yaml_parser.cpp.o.d -o src/libcamera/libcamera.so.0.0.0.p/yaml_parser.cpp.o -c ../../../src/libcamera/src/libcamera/yaml_parser.cpp ../../../src/libcamera/src/libcamera/yaml_parser.cpp:360:18: error: ‘function’ in namespace ‘std’ does not name a template type const std::function<int(EventPtr event)> &parseItem); ^~~~~~~~ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:360:13: note: ‘std::function’ is defined in header ‘<functional>’; did you forget to ‘#include <functional>’? ../../../src/libcamera/src/libcamera/yaml_parser.cpp:16:1: +#include <functional> ../../../src/libcamera/src/libcamera/yaml_parser.cpp:360:13: const std::function<int(EventPtr event)> &parseItem); ^~~ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:360:26: error: expected ‘,’ or ‘...’ before ‘<’ token const std::function<int(EventPtr event)> &parseItem); ^ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:509:22: error: ‘function’ in namespace ‘std’ does not name a template type const std::function<int(EventPtr event)> &parseItem) ^~~~~~~~ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:509:17: note: ‘std::function’ is defined in header ‘<functional>’; did you forget to ‘#include <functional>’? const std::function<int(EventPtr event)> &parseItem) ^~~ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:509:30: error: expected ‘,’ or ‘...’ before ‘<’ token const std::function<int(EventPtr event)> &parseItem) ^ ../../../src/libcamera/src/libcamera/yaml_parser.cpp: In member function ‘int libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, int)’: ../../../src/libcamera/src/libcamera/yaml_parser.cpp:527:13: error: ‘parseItem’ was not declared in this scope int ret = parseItem(std::move(evt)); ^~~~~~~~~ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:527:13: note: suggested alternative: ‘parser_’ int ret = parseItem(std::move(evt)); ^~~~~~~~~ parser_ ../../../src/libcamera/src/libcamera/yaml_parser.cpp: In member function ‘int libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)’: ../../../src/libcamera/src/libcamera/yaml_parser.cpp:567:57: error: no matching function for call to ‘libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)::<lambda(libcamera::YamlParserContext::EventPtr)>&)’ return parseDictionaryOrList(YamlObject::List, handler); ^ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:508:5: note: candidate: ‘int libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, int)’ int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, ^~~~~~~~~~~~~~~~~ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:508:5: note: no known conversion for argument 2 from ‘libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)::<lambda(libcamera::YamlParserContext::EventPtr)>’ to ‘int’ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:594:63: error: no matching function for call to ‘libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)::<lambda(libcamera::YamlParserContext::EventPtr)>&)’ return parseDictionaryOrList(YamlObject::Dictionary, handler); ^ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:508:5: note: candidate: ‘int libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, int)’ int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, ^~~~~~~~~~~~~~~~~ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:508:5: note: no known conversion for argument 2 from ‘libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)::<lambda(libcamera::YamlParserContext::EventPtr)>’ to ‘int’ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:567:58: error: this statement may fall through [-Werror=implicit-fallthrough=] return parseDictionaryOrList(YamlObject::List, handler); ^ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:570:2: note: here case YAML_MAPPING_START_EVENT: { ^~~~ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:594:64: error: this statement may fall through [-Werror=implicit-fallthrough=] return parseDictionaryOrList(YamlObject::Dictionary, handler); ^ ../../../src/libcamera/src/libcamera/yaml_parser.cpp:597:2: note: here default: ^~~~~~~ cc1plus: all warnings being treated as errors Are we still supporting gcc-8 ? -- Kieran > --- > README.rst | 4 +- > include/libcamera/internal/meson.build | 1 + > include/libcamera/internal/yaml_parser.h | 87 +++ > src/libcamera/meson.build | 3 + > src/libcamera/yaml_parser.cpp | 679 +++++++++++++++++++++++ > 5 files changed, 772 insertions(+), 2 deletions(-) > create mode 100644 include/libcamera/internal/yaml_parser.h > create mode 100644 src/libcamera/yaml_parser.cpp > > diff --git a/README.rst b/README.rst > index aae6b79f..b5a2d448 100644 > --- a/README.rst > +++ b/README.rst > @@ -60,7 +60,7 @@ Meson Build system: [required] > pip3 install --user --upgrade meson > > for the libcamera core: [required] > - python3-yaml python3-ply python3-jinja2 > + libyaml-dev python3-yaml python3-ply python3-jinja2 > > for IPA module signing: [required] > libgnutls28-dev openssl > @@ -98,7 +98,7 @@ for tracing with lttng: [optional] > liblttng-ust-dev python3-jinja2 lttng-tools > > for android: [optional] > - libexif-dev libjpeg-dev libyaml-dev > + libexif-dev libjpeg-dev > > for lc-compliance: [optional] > libevent-dev > diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build > index c9e055d4..7a780d48 100644 > --- a/include/libcamera/internal/meson.build > +++ b/include/libcamera/internal/meson.build > @@ -42,4 +42,5 @@ libcamera_internal_headers = files([ > 'v4l2_pixelformat.h', > 'v4l2_subdevice.h', > 'v4l2_videodevice.h', > + 'yaml_parser.h', > ]) > diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h > new file mode 100644 > index 00000000..3a4f3052 > --- /dev/null > +++ b/include/libcamera/internal/yaml_parser.h > @@ -0,0 +1,87 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2022, Google Inc. > + * > + * yaml_parser.h - libcamera YAML parsing helper > + */ > + > +#pragma once > + > +#include <cstdio> > +#include <map> > +#include <string> > +#include <vector> > + > +#include <libcamera/base/class.h> > + > +#include <libcamera/geometry.h> > + > +namespace libcamera { > + > +class YamlParserContext; > + > +class YamlObject > +{ > +public: > + YamlObject(); > + ~YamlObject(); > + > + bool isValue() const > + { > + return type_ == Value; > + } > + bool isList() const > + { > + return type_ == List; > + } > + bool isDictionary() const > + { > + return type_ == Dictionary; > + } > + > +#ifndef __DOXYGEN__ > + template<typename T, > + typename std::enable_if_t< > + std::is_same<bool, T>::value || > + std::is_same<double, T>::value || > + std::is_same<int32_t, T>::value || > + std::is_same<uint32_t, T>::value || > + std::is_same<std::string, T>::value || > + std::is_same<Size, T>::value> * = nullptr> > +#else > + template<typename T> > +#endif > + T get(const T &defaultValue, bool *ok = nullptr) const; > + > + std::size_t size() const; > + const YamlObject &operator[](std::size_t index) const; > + > + bool contains(const std::string &key) const; > + const YamlObject &operator[](const std::string &key) const; > + std::vector<std::string> memberNames() const; > + > +private: > + LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject) > + > + friend class YamlParserContext; > + > + enum Type { > + Dictionary, > + List, > + Value, > + }; > + > + Type type_; > + > + std::string value_; > + std::vector<std::unique_ptr<YamlObject>> list_; > + std::map<const std::string, std::unique_ptr<YamlObject>> dictionary_; > +}; > + > +class YamlParser final > +{ > +public: > + static std::unique_ptr<YamlObject> parse(std::FILE *fh); > +}; > + > +} /* namespace libcamera */ > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > index 26912ca1..f8e18e03 100644 > --- a/src/libcamera/meson.build > +++ b/src/libcamera/meson.build > @@ -46,6 +46,7 @@ libcamera_sources = files([ > 'v4l2_pixelformat.cpp', > 'v4l2_subdevice.cpp', > 'v4l2_videodevice.cpp', > + 'yaml_parser.cpp', > ]) > > libcamera_sources += libcamera_public_headers > @@ -66,6 +67,7 @@ subdir('proxy') > libdl = cc.find_library('dl') > libgnutls = cc.find_library('gnutls', required : true) > libudev = dependency('libudev', required : false) > +libyaml = dependency('yaml-0.1', required : true) > > if libgnutls.found() > config_h.set('HAVE_GNUTLS', 1) > @@ -126,6 +128,7 @@ libcamera_deps = [ > libgnutls, > liblttng, > libudev, > + libyaml, > ] > > # We add '/' to the build_rpath as a 'safe' path to act as a boolean flag. > diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp > new file mode 100644 > index 00000000..4a047494 > --- /dev/null > +++ b/src/libcamera/yaml_parser.cpp > @@ -0,0 +1,679 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2022, Google Inc. > + * > + * yaml_parser.cpp - libcamera YAML parsing helper > + */ > + > +#include "libcamera/internal/yaml_parser.h" > + > +#include <cerrno> > +#include <cstdlib> > + > +#include <libcamera/base/log.h> > + > +#include <yaml.h> > + > +/** > + * \file libcamera/internal/yaml_parser.h > + * \brief A YAML parser helper > + */ > + > +namespace libcamera { > + > +LOG_DEFINE_CATEGORY(YamlParser) > + > +namespace { > + > +/* Empty static YamlObject as a safe result for invalid operations */ > +static const YamlObject empty; > + > +void setOk(bool *ok, bool result) > +{ > + if (ok) > + *ok = result; > +} > + > +} /* namespace */ > + > +/** > + * \class YamlObject > + * \brief A class representing the tree structure of the YAML content > + * > + * The YamlObject class represents the tree structure of YAML content. A > + * YamlObject can be a dictionary or list of YamlObjects or a value if a tree > + * leaf. > + */ > + > +YamlObject::YamlObject() > + : type_(Value) > +{ > +} > + > +YamlObject::~YamlObject() = default; > + > +/** > + * \fn YamlObject::isValue() > + * \brief Return whether the YamlObject is a value > + * > + * \return True if the YamlObject is a value, false otherwise > + */ > + > +/** > + * \fn YamlObject::isList() > + * \brief Return whether the YamlObject is a list > + * > + * \return True if the YamlObject is a list, false otherwise > + */ > + > +/** > + * \fn YamlObject::isDictionary() > + * \brief Return whether the YamlObject is a dictionary > + * > + * \return True if the YamlObject is a dictionary, false otherwise > + */ > + > +/** > + * \fn template<typename T> YamlObject::get<T>( > + * const T &defaultValue, bool *ok) const > + * \brief Parse the YamlObject as a \a T value > + * \param[in] defaultValue The default value when failing to parse > + * \param[out] ok The result of whether the parse succeeded > + * > + * This function parses the value of the YamlObject as a \a T object, and > + * returns the value. If parsing fails (usually because the YamlObject doesn't > + * store a \a T value), the \a defaultValue is returned, and \a ok is set to > + * false. Otherwise, the YamlObject value is returned, and \a ok is set to true. > + * > + * The \a ok pointer is optional and can be a nullptr if the caller doesn't > + * need to know if parsing succeeded. > + * > + * \return Value as a bool type > + */ > + > +#ifndef __DOXYGEN__ > + > +template<> > +bool YamlObject::get(const bool &defaultValue, bool *ok) const > +{ > + setOk(ok, false); > + > + if (type_ != Value) > + return defaultValue; > + > + if (value_ == "true") { > + setOk(ok, true); > + return true; > + } else if (value_ == "false") { > + setOk(ok, true); > + return false; > + } > + > + return defaultValue; > +} > + > +template<> > +int32_t YamlObject::get(const int32_t &defaultValue, bool *ok) const > +{ > + setOk(ok, false); > + > + if (type_ != Value) > + return defaultValue; > + > + if (value_ == "") > + return defaultValue; > + > + char *end; > + > + errno = 0; > + int32_t value = std::strtol(value_.c_str(), &end, 10); > + > + if ('\0' != *end || errno == ERANGE) > + return defaultValue; > + > + setOk(ok, true); > + return value; > +} > + > +template<> > +uint32_t YamlObject::get(const uint32_t &defaultValue, bool *ok) const > +{ > + setOk(ok, false); > + > + if (type_ != Value) > + return defaultValue; > + > + if (value_ == "") > + return defaultValue; > + > + /* > + * libyaml parses all scalar values as strings. When a string has > + * leading spaces before a minus sign, for example " -10", strtoul > + * skips leading spaces, accepts the leading minus sign, and the > + * calculated digits are negated as if by unary minus. Rule it out in > + * case the user gets a large number when the value is negative. > + */ > + std::size_t found = value_.find_first_not_of(" \t"); > + if (found != std::string::npos && value_[found] == '-') > + return defaultValue; > + > + char *end; > + > + errno = 0; > + uint32_t value = std::strtoul(value_.c_str(), &end, 10); > + > + if ('\0' != *end || errno == ERANGE) > + return defaultValue; > + > + setOk(ok, true); > + return value; > +} > + > +template<> > +double YamlObject::get(const double &defaultValue, bool *ok) const > +{ > + setOk(ok, false); > + > + if (type_ != Value) > + return defaultValue; > + > + if (value_ == "") > + return defaultValue; > + > + char *end; > + > + errno = 0; > + double value = std::strtod(value_.c_str(), &end); > + > + if ('\0' != *end || errno == ERANGE) > + return defaultValue; > + > + setOk(ok, true); > + return value; > +} > + > +template<> > +std::string YamlObject::get(const std::string &defaultValue, bool *ok) const > +{ > + setOk(ok, false); > + > + if (type_ != Value) > + return defaultValue; > + > + setOk(ok, true); > + return value_; > +} > + > +template<> > +Size YamlObject::get(const Size &defaultValue, bool *ok) const > +{ > + setOk(ok, false); > + > + if (type_ != List) > + return defaultValue; > + > + if (list_.size() != 2) > + return defaultValue; > + > + /* > + * Add a local variable to validate each dimension in case > + * that ok == nullptr. > + */ > + bool valid; > + uint32_t width = list_[0]->get<uint32_t>(0, &valid); > + if (!valid) > + return defaultValue; > + > + uint32_t height = list_[1]->get<uint32_t>(0, &valid); > + if (!valid) > + return defaultValue; > + > + setOk(ok, true); > + return Size(width, height); > +} > + > +#endif /* __DOXYGEN__ */ > + > +/** > + * \fn YamlObject::size() > + * \brief Retrieve the number of elements in a list YamlObject > + * > + * This function retrieves the size of the YamlObject, defined as the number of > + * child elements it contains. Only YamlObject instances of List type have a > + * size, calling this function on other types of instances is invalid and > + * results in undefined behaviour. > + * > + * \return The size of the YamlObject > + */ > +std::size_t YamlObject::size() const > +{ > + if (type_ != List) > + return 0; > + > + return list_.size(); > +} > + > +/** > + * \fn YamlObject::operator[](std::size_t index) const > + * \brief Retrieve the element from list YamlObject by index > + * > + * This function retrieves an element of the YamlObject. Only YamlObject > + * instances of List type associate elements with index, calling this function > + * on other types of instances is invalid and results in undefined behaviour. > + * > + * \return The YamlObject as an element of the list > + */ > +const YamlObject &YamlObject::operator[](std::size_t index) const > +{ > + if (type_ != List || index >= size()) > + return empty; > + > + return *list_[index]; > +} > + > +/** > + * \fn YamlObject::contains() > + * \brief Check if an element of a dictionary exists > + * > + * This function check if the YamlObject contains an element. Only YamlObject > + * instances of Dictionary type associate elements with names, calling this > + * function on other types of instances is invalid and results in undefined > + * behaviour. > + * > + * \return True if an element exists, false otherwise > + */ > +bool YamlObject::contains(const std::string &key) const > +{ > + if (dictionary_.find(key) == dictionary_.end()) > + return false; > + > + return true; > +} > + > +/** > + * \fn YamlObject::memberNames() > + * \brief Retrieve all member names of the dictionary > + * > + * This function retrieve member names of a YamlObject. Only YamlObject > + * instances of Dictionary type associate elements with names, calling this > + * function on other types of instances is invalid and results in undefined > + * behaviour. > + * > + * \todo Replace this function with an iterator-based API > + * > + * \return A vector of string as the member names > + */ > +std::vector<std::string> YamlObject::memberNames() const > +{ > + std::vector<std::string> memberNames; > + for (auto &[key, _] : dictionary_) > + memberNames.push_back(key); > + > + return memberNames; > +} > + > +/** > + * \fn YamlObject::operator[](const std::string &key) const > + * \brief Retrieve a member by name from the dictionary > + * > + * This function retrieve a member of a YamlObject by name. Only YamlObject > + * instances of Dictionary type associate elements with names, calling this > + * function on other types of instances is invalid and results in undefined > + * behaviour. > + * > + * \return The YamlObject corresponding to the \a key member > + */ > +const YamlObject &YamlObject::operator[](const std::string &key) const > +{ > + if (type_ != Dictionary || !contains(key)) > + return empty; > + > + auto iter = dictionary_.find(key); > + return *iter->second; > +} > + > +#ifndef __DOXYGEN__ > + > +class YamlParserContext > +{ > +public: > + YamlParserContext(); > + ~YamlParserContext(); > + > + int init(std::FILE *fh); > + int parseContent(YamlObject &yamlObject); > + > +private: > + struct EventDeleter { > + void operator()(yaml_event_t *event) const > + { > + yaml_event_delete(event); > + delete event; > + } > + }; > + using EventPtr = std::unique_ptr<yaml_event_t, EventDeleter>; > + > + EventPtr nextEvent(); > + > + void readValue(std::string &value, EventPtr event); > + int parseDictionaryOrList(YamlObject::Type type, > + const std::function<int(EventPtr event)> &parseItem); > + int parseNextYamlObject(YamlObject &yamlObject, EventPtr event); > + > + bool parserValid_; > + yaml_parser_t parser_; > +}; > + > +/** > + * \class YamlParserContext > + * \brief Class for YamlParser parsing and context data > + * > + * The YamlParserContext class stores the internal yaml_parser_t and provides > + * helper functions to do event-based parsing for YAML files. > + */ > +YamlParserContext::YamlParserContext() > + : parserValid_(false) > +{ > +} > + > +/** > + * \class YamlParserContext > + * \brief Destructor of YamlParserContext > + */ > +YamlParserContext::~YamlParserContext() > +{ > + if (parserValid_) { > + yaml_parser_delete(&parser_); > + parserValid_ = false; > + } > +} > + > +/** > + * \fn YamlParserContext::init() > + * \brief Initialize a parser with an opened file for parsing > + * \param[in] fh The YAML file to parse > + * > + * Prior to parsing the YAML content, the YamlParserContext must be initialized > + * with an opened FILE to create an internal parser. The FILE needs to stay > + * valid during the process. > + * > + * \return 0 on success or a negative error code otherwise > + * \retval -EINVAL The parser has failed to initialize > + */ > +int YamlParserContext::init(std::FILE *fh) > +{ > + /* yaml_parser_initialize returns 1 when it succeededs */ > + if (!yaml_parser_initialize(&parser_)) { > + LOG(YamlParser, Error) << "Failed to initialize YAML parser"; > + return -EINVAL; > + } > + parserValid_ = true; > + yaml_parser_set_input_file(&parser_, fh); > + > + return 0; > +} > + > +/** > + * \fn YamlParserContext::nextEvent() > + * \brief Get the next event > + * > + * Get the next event in the current YAML event stream, and return nullptr when > + * there is no more event. > + * > + * \return The next event on success or nullptr otherwise > + */ > +YamlParserContext::EventPtr YamlParserContext::nextEvent() > +{ > + EventPtr event(new yaml_event_t); > + > + /* yaml_parser_parse returns 1 when it succeeds */ > + if (!yaml_parser_parse(&parser_, event.get())) > + return nullptr; > + > + return event; > +} > + > +/** > + * \fn YamlParserContext::parseContent() > + * \brief Parse the content of a YAML document > + * \param[in] yamlObject The result of YamlObject > + * > + * Check YAML start and end events of a YAML document, and parse the root object > + * of the YAML document into a YamlObject. > + * > + * \return 0 on success or a negative error code otherwise > + * \retval -EINVAL The parser has failed to validate end of a YAML file > + */ > +int YamlParserContext::parseContent(YamlObject &yamlObject) > +{ > + /* Check start of the YAML file. */ > + EventPtr event = nextEvent(); > + if (!event || event->type != YAML_STREAM_START_EVENT) > + return -EINVAL; > + > + event = nextEvent(); > + if (!event || event->type != YAML_DOCUMENT_START_EVENT) > + return -EINVAL; > + > + /* Parse the root object. */ > + event = nextEvent(); > + if (parseNextYamlObject(yamlObject, std::move(event))) > + return -EINVAL; > + > + /* Check end of the YAML file. */ > + event = nextEvent(); > + if (!event || event->type != YAML_DOCUMENT_END_EVENT) > + return -EINVAL; > + > + event = nextEvent(); > + if (!event || event->type != YAML_STREAM_END_EVENT) > + return -EINVAL; > + > + return 0; > +} > + > +/** > + * \fn YamlParserContext::readValue() > + * \brief Parse event scalar and fill its content into a string > + * \param[in] value The string reference to fill value > + * > + * A helper function to parse a scalar event as string. The caller needs to > + * guarantee the event is of scaler type. > + */ > +void YamlParserContext::readValue(std::string &value, EventPtr event) > +{ > + value.assign(reinterpret_cast<char *>(event->data.scalar.value), > + event->data.scalar.length); > +} > + > +/** > + * \fn YamlParserContext::parseDictionaryOrList() > + * \brief A helper function to abstract the common part of parsing dictionary or list > + * > + * \param[in] isDictionary True for parsing a dictionary, and false for a list > + * \param[in] parseItem The callback to handle an item > + * > + * A helper function to abstract parsing an item from a dictionary or a list. > + * The differences of them in a YAML event stream are: > + * > + * 1. The start and end event types are different > + * 2. There is a leading scalar string as key in the items of a dictionary > + * > + * The caller should handle the leading key string in its callback parseItem > + * when it's a dictionary. > + * > + * \return 0 on success or a negative error code otherwise > + * \retval -EINVAL The parser is failed to initialize > + */ > +int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, > + const std::function<int(EventPtr event)> &parseItem) > +{ > + yaml_event_type_t endEventType = YAML_SEQUENCE_END_EVENT; > + if (type == YamlObject::Dictionary) > + endEventType = YAML_MAPPING_END_EVENT; > + > + /* > + * Add a safety counter to make sure we don't loop indefinitely in case > + * the YAML file is malformed. > + */ > + for (unsigned int sentinel = 1000; sentinel; sentinel--) { > + auto evt = nextEvent(); > + if (!evt) > + return -EINVAL; > + > + if (evt->type == endEventType) > + return 0; > + > + int ret = parseItem(std::move(evt)); > + if (ret) > + return ret; > + } > + > + LOG(YamlParser, Error) << "The YAML file contains a List or Dictionary" > + " whose size exceeding the parser's limit(1000)"; > + > + return -EINVAL; > +} > + > +/** > + * \fn YamlParserContext::parseNextYamlObject() > + * \brief Parse next YAML event and read it as a YamlObject > + * \param[in] yamlObject The result of YamlObject > + * \param[in] event The leading event of the object > + * > + * Parse next YAML object separately as a value, list or dictionary. > + * > + * \return 0 on success or a negative error code otherwise > + * \retval -EINVAL Fail to parse the YAML file. > + */ > +int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr event) > +{ > + if (!event) > + return -EINVAL; > + > + switch (event->type) { > + case YAML_SCALAR_EVENT: > + yamlObject.type_ = YamlObject::Value; > + readValue(yamlObject.value_, std::move(event)); > + return 0; > + > + case YAML_SEQUENCE_START_EVENT: { > + yamlObject.type_ = YamlObject::List; > + auto &list = yamlObject.list_; > + auto handler = [this, &list](EventPtr evt) { > + list.emplace_back(new YamlObject()); > + return parseNextYamlObject(*list.back(), std::move(evt)); > + }; > + return parseDictionaryOrList(YamlObject::List, handler); > + } > + > + case YAML_MAPPING_START_EVENT: { > + yamlObject.type_ = YamlObject::Dictionary; > + auto &dictionary = yamlObject.dictionary_; > + auto handler = [this, &dictionary](EventPtr evtKey) { > + /* Parse key */ > + if (evtKey->type != YAML_SCALAR_EVENT) { > + LOG(YamlParser, Error) << "Expect key at line: " > + << evtKey->start_mark.line > + << " column: " > + << evtKey->start_mark.column; > + return -EINVAL; > + } > + > + std::string key; > + readValue(key, std::move(evtKey)); > + > + /* Parse value */ > + EventPtr evtValue = nextEvent(); > + if (!evtValue) > + return -EINVAL; > + > + auto elem = dictionary.emplace(key, std::make_unique<YamlObject>()); > + return parseNextYamlObject(*elem.first->second.get(), std::move(evtValue)); > + }; > + return parseDictionaryOrList(YamlObject::Dictionary, handler); > + } > + default: > + LOG(YamlParser, Error) << "Invalid YAML file"; > + return -EINVAL; > + } > +} > + > +#endif /* __DOXYGEN__ */ > + > +/** > + * \class YamlParser > + * \brief A helper class for parsing a YAML file > + * > + * The YamlParser class provides an easy interface to parse the contents of a > + * YAML file into a tree of YamlObject instances. > + * > + * Example usage: > + * > + * \code{.unparsed} > + * > + * name: > + * "John" > + * numbers: > + * - 1 > + * - 2 > + * > + * \endcode > + * > + * The following code illustrates how to parse the above YAML file: > + * > + * \code{.cpp} > + * > + * std::unique_ptr<YamlObject> root = YamlParser::parse(fh); > + * if (!root) > + * return; > + * > + * if (!root->isDictionary()) > + * return; > + * > + * const YamlObject &name = (*root)["name"]; > + * std::cout << name.get<std::string>("") << std::endl; > + * > + * const YamlObject &numbers = (*root)["numbers"]; > + * if (!numbers.isList()) > + * return; > + * > + * for (std::size_t i = 0; i < numbers.size(); i++) > + * std::cout << numbers[i].get<int32_t>(0) << std::endl; > + * > + * \endcode > + * > + * The YamlParser::parse() function takes an open FILE, parses its contents, and > + * returns a pointer to a YamlObject corresponding to the root node of the YAML > + * document. > + */ > + > +/** > + * \fn YamlParser::parse() > + * \brief Parse a YAML file as a YamlObject > + * \param[in] fh The YAML file to parse > + * > + * The YamlParser::parse() function takes an open FILE, parses its contents, and > + * returns a pointer to a YamlObject corresponding to the root node of the YAML > + * document. The caller is responsible for closing the file. > + * > + * \return Pointer to result YamlObject on success or nullptr otherwise > + */ > +std::unique_ptr<YamlObject> YamlParser::parse(std::FILE *fh) > +{ > + YamlParserContext context; > + > + if (context.init(fh)) > + return nullptr; > + > + std::unique_ptr<YamlObject> root(new YamlObject()); > + > + if (context.parseContent(*root)) { > + LOG(YamlParser, Error) << "Failed to parse YAML content"; > + return nullptr; > + } > + > + return root; > +} > + > +} /* namespace libcamera */ > -- > 2.36.0.rc2.479.g8af0fa9b8e-goog >
On Mon, May 09, 2022 at 09:51:46PM +0100, Kieran Bingham via libcamera-devel wrote: > Quoting Han-Lin Chen via libcamera-devel (2022-04-27 15:09:27) > > Introduce YamlParser as a helper to convert contents of a yaml file to > > a tree based structure for easier reading, and to avoid writing parser > > with raw yaml tokens. The class is based on libyaml, and only support > > reading but not writing a yaml file. > > > > The interface is inspired by Json::Value class from jsoncpp: > > http://jsoncpp.sourceforge.net/class_json_1_1_value.html > > > > Signed-off-by: Han-Lin Chen <hanlinchen@chromium.org> > > This causes compile failures on GCC-8. (I haven't yet tested further > than that, as it's the first failure that hits). > > FAILED: src/libcamera/libcamera.so.0.0.0.p/yaml_parser.cpp.o > g++-8 -Isrc/libcamera/libcamera.so.0.0.0.p -Isrc/libcamera -I../../../src/libcamera/src/libcamera -Iinclude -I../../../src/libcamera/include -Iinclude/libcamera -Iinclude/libcamera/ipa -Iinclude/libcamera/internal -Isrc/libcamera/proxy -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wnon-virtual-dtor -Wextra -Werror -std=c++17 -g -Wl,--start-group -lstdc++fs -Wl,--end-group -Wshadow -include config.h -fPIC -DLIBCAMERA_BASE_PRIVATE -MD -MQ src/libcamera/libcamera.so.0.0.0.p/yaml_parser.cpp.o -MF src/libcamera/libcamera.so.0.0.0.p/yaml_parser.cpp.o.d -o src/libcamera/libcamera.so.0.0.0.p/yaml_parser.cpp.o -c ../../../src/libcamera/src/libcamera/yaml_parser.cpp > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:360:18: error: ‘function’ in namespace ‘std’ does not name a template type > const std::function<int(EventPtr event)> &parseItem); > ^~~~~~~~ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:360:13: note: ‘std::function’ is defined in header ‘<functional>’; did you forget to ‘#include <functional>’? > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:16:1: > +#include <functional> This indeed fixes the failure for me. > > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:360:13: > const std::function<int(EventPtr event)> &parseItem); > ^~~ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:360:26: error: expected ‘,’ or ‘...’ before ‘<’ token > const std::function<int(EventPtr event)> &parseItem); > ^ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:509:22: error: ‘function’ in namespace ‘std’ does not name a template type > const std::function<int(EventPtr event)> &parseItem) > ^~~~~~~~ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:509:17: note: ‘std::function’ is defined in header ‘<functional>’; did you forget to ‘#include <functional>’? > const std::function<int(EventPtr event)> &parseItem) > ^~~ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:509:30: error: expected ‘,’ or ‘...’ before ‘<’ token > const std::function<int(EventPtr event)> &parseItem) > ^ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp: In member function ‘int libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, int)’: > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:527:13: error: ‘parseItem’ was not declared in this scope > int ret = parseItem(std::move(evt)); > ^~~~~~~~~ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:527:13: note: suggested alternative: ‘parser_’ > int ret = parseItem(std::move(evt)); > ^~~~~~~~~ > parser_ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp: In member function ‘int libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)’: > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:567:57: error: no matching function for call to ‘libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)::<lambda(libcamera::YamlParserContext::EventPtr)>&)’ > return parseDictionaryOrList(YamlObject::List, handler); > ^ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:508:5: note: candidate: ‘int libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, int)’ > int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, > ^~~~~~~~~~~~~~~~~ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:508:5: note: no known conversion for argument 2 from ‘libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)::<lambda(libcamera::YamlParserContext::EventPtr)>’ to ‘int’ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:594:63: error: no matching function for call to ‘libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)::<lambda(libcamera::YamlParserContext::EventPtr)>&)’ > return parseDictionaryOrList(YamlObject::Dictionary, handler); > ^ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:508:5: note: candidate: ‘int libcamera::YamlParserContext::parseDictionaryOrList(libcamera::YamlObject::Type, int)’ > int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, > ^~~~~~~~~~~~~~~~~ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:508:5: note: no known conversion for argument 2 from ‘libcamera::YamlParserContext::parseNextYamlObject(libcamera::YamlObject&, libcamera::YamlParserContext::EventPtr)::<lambda(libcamera::YamlParserContext::EventPtr)>’ to ‘int’ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:567:58: error: this statement may fall through [-Werror=implicit-fallthrough=] > return parseDictionaryOrList(YamlObject::List, handler); > ^ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:570:2: note: here > case YAML_MAPPING_START_EVENT: { > ^~~~ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:594:64: error: this statement may fall through [-Werror=implicit-fallthrough=] > return parseDictionaryOrList(YamlObject::Dictionary, handler); > ^ > ../../../src/libcamera/src/libcamera/yaml_parser.cpp:597:2: note: here > default: > ^~~~~~~ > cc1plus: all warnings being treated as errors > > Are we still supporting gcc-8 ? Yes we are. I'll fix locally in my branch, no need to resubmit. > > --- > > README.rst | 4 +- > > include/libcamera/internal/meson.build | 1 + > > include/libcamera/internal/yaml_parser.h | 87 +++ > > src/libcamera/meson.build | 3 + > > src/libcamera/yaml_parser.cpp | 679 +++++++++++++++++++++++ > > 5 files changed, 772 insertions(+), 2 deletions(-) > > create mode 100644 include/libcamera/internal/yaml_parser.h > > create mode 100644 src/libcamera/yaml_parser.cpp > > > > diff --git a/README.rst b/README.rst > > index aae6b79f..b5a2d448 100644 > > --- a/README.rst > > +++ b/README.rst > > @@ -60,7 +60,7 @@ Meson Build system: [required] > > pip3 install --user --upgrade meson > > > > for the libcamera core: [required] > > - python3-yaml python3-ply python3-jinja2 > > + libyaml-dev python3-yaml python3-ply python3-jinja2 > > > > for IPA module signing: [required] > > libgnutls28-dev openssl > > @@ -98,7 +98,7 @@ for tracing with lttng: [optional] > > liblttng-ust-dev python3-jinja2 lttng-tools > > > > for android: [optional] > > - libexif-dev libjpeg-dev libyaml-dev > > + libexif-dev libjpeg-dev > > > > for lc-compliance: [optional] > > libevent-dev > > diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build > > index c9e055d4..7a780d48 100644 > > --- a/include/libcamera/internal/meson.build > > +++ b/include/libcamera/internal/meson.build > > @@ -42,4 +42,5 @@ libcamera_internal_headers = files([ > > 'v4l2_pixelformat.h', > > 'v4l2_subdevice.h', > > 'v4l2_videodevice.h', > > + 'yaml_parser.h', > > ]) > > diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h > > new file mode 100644 > > index 00000000..3a4f3052 > > --- /dev/null > > +++ b/include/libcamera/internal/yaml_parser.h > > @@ -0,0 +1,87 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2022, Google Inc. > > + * > > + * yaml_parser.h - libcamera YAML parsing helper > > + */ > > + > > +#pragma once > > + > > +#include <cstdio> > > +#include <map> > > +#include <string> > > +#include <vector> > > + > > +#include <libcamera/base/class.h> > > + > > +#include <libcamera/geometry.h> > > + > > +namespace libcamera { > > + > > +class YamlParserContext; > > + > > +class YamlObject > > +{ > > +public: > > + YamlObject(); > > + ~YamlObject(); > > + > > + bool isValue() const > > + { > > + return type_ == Value; > > + } > > + bool isList() const > > + { > > + return type_ == List; > > + } > > + bool isDictionary() const > > + { > > + return type_ == Dictionary; > > + } > > + > > +#ifndef __DOXYGEN__ > > + template<typename T, > > + typename std::enable_if_t< > > + std::is_same<bool, T>::value || > > + std::is_same<double, T>::value || > > + std::is_same<int32_t, T>::value || > > + std::is_same<uint32_t, T>::value || > > + std::is_same<std::string, T>::value || > > + std::is_same<Size, T>::value> * = nullptr> > > +#else > > + template<typename T> > > +#endif > > + T get(const T &defaultValue, bool *ok = nullptr) const; > > + > > + std::size_t size() const; > > + const YamlObject &operator[](std::size_t index) const; > > + > > + bool contains(const std::string &key) const; > > + const YamlObject &operator[](const std::string &key) const; > > + std::vector<std::string> memberNames() const; > > + > > +private: > > + LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject) > > + > > + friend class YamlParserContext; > > + > > + enum Type { > > + Dictionary, > > + List, > > + Value, > > + }; > > + > > + Type type_; > > + > > + std::string value_; > > + std::vector<std::unique_ptr<YamlObject>> list_; > > + std::map<const std::string, std::unique_ptr<YamlObject>> dictionary_; > > +}; > > + > > +class YamlParser final > > +{ > > +public: > > + static std::unique_ptr<YamlObject> parse(std::FILE *fh); > > +}; > > + > > +} /* namespace libcamera */ > > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > > index 26912ca1..f8e18e03 100644 > > --- a/src/libcamera/meson.build > > +++ b/src/libcamera/meson.build > > @@ -46,6 +46,7 @@ libcamera_sources = files([ > > 'v4l2_pixelformat.cpp', > > 'v4l2_subdevice.cpp', > > 'v4l2_videodevice.cpp', > > + 'yaml_parser.cpp', > > ]) > > > > libcamera_sources += libcamera_public_headers > > @@ -66,6 +67,7 @@ subdir('proxy') > > libdl = cc.find_library('dl') > > libgnutls = cc.find_library('gnutls', required : true) > > libudev = dependency('libudev', required : false) > > +libyaml = dependency('yaml-0.1', required : true) > > > > if libgnutls.found() > > config_h.set('HAVE_GNUTLS', 1) > > @@ -126,6 +128,7 @@ libcamera_deps = [ > > libgnutls, > > liblttng, > > libudev, > > + libyaml, > > ] > > > > # We add '/' to the build_rpath as a 'safe' path to act as a boolean flag. > > diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp > > new file mode 100644 > > index 00000000..4a047494 > > --- /dev/null > > +++ b/src/libcamera/yaml_parser.cpp > > @@ -0,0 +1,679 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2022, Google Inc. > > + * > > + * yaml_parser.cpp - libcamera YAML parsing helper > > + */ > > + > > +#include "libcamera/internal/yaml_parser.h" > > + > > +#include <cerrno> > > +#include <cstdlib> > > + > > +#include <libcamera/base/log.h> > > + > > +#include <yaml.h> > > + > > +/** > > + * \file libcamera/internal/yaml_parser.h > > + * \brief A YAML parser helper > > + */ > > + > > +namespace libcamera { > > + > > +LOG_DEFINE_CATEGORY(YamlParser) > > + > > +namespace { > > + > > +/* Empty static YamlObject as a safe result for invalid operations */ > > +static const YamlObject empty; > > + > > +void setOk(bool *ok, bool result) > > +{ > > + if (ok) > > + *ok = result; > > +} > > + > > +} /* namespace */ > > + > > +/** > > + * \class YamlObject > > + * \brief A class representing the tree structure of the YAML content > > + * > > + * The YamlObject class represents the tree structure of YAML content. A > > + * YamlObject can be a dictionary or list of YamlObjects or a value if a tree > > + * leaf. > > + */ > > + > > +YamlObject::YamlObject() > > + : type_(Value) > > +{ > > +} > > + > > +YamlObject::~YamlObject() = default; > > + > > +/** > > + * \fn YamlObject::isValue() > > + * \brief Return whether the YamlObject is a value > > + * > > + * \return True if the YamlObject is a value, false otherwise > > + */ > > + > > +/** > > + * \fn YamlObject::isList() > > + * \brief Return whether the YamlObject is a list > > + * > > + * \return True if the YamlObject is a list, false otherwise > > + */ > > + > > +/** > > + * \fn YamlObject::isDictionary() > > + * \brief Return whether the YamlObject is a dictionary > > + * > > + * \return True if the YamlObject is a dictionary, false otherwise > > + */ > > + > > +/** > > + * \fn template<typename T> YamlObject::get<T>( > > + * const T &defaultValue, bool *ok) const > > + * \brief Parse the YamlObject as a \a T value > > + * \param[in] defaultValue The default value when failing to parse > > + * \param[out] ok The result of whether the parse succeeded > > + * > > + * This function parses the value of the YamlObject as a \a T object, and > > + * returns the value. If parsing fails (usually because the YamlObject doesn't > > + * store a \a T value), the \a defaultValue is returned, and \a ok is set to > > + * false. Otherwise, the YamlObject value is returned, and \a ok is set to true. > > + * > > + * The \a ok pointer is optional and can be a nullptr if the caller doesn't > > + * need to know if parsing succeeded. > > + * > > + * \return Value as a bool type > > + */ > > + > > +#ifndef __DOXYGEN__ > > + > > +template<> > > +bool YamlObject::get(const bool &defaultValue, bool *ok) const > > +{ > > + setOk(ok, false); > > + > > + if (type_ != Value) > > + return defaultValue; > > + > > + if (value_ == "true") { > > + setOk(ok, true); > > + return true; > > + } else if (value_ == "false") { > > + setOk(ok, true); > > + return false; > > + } > > + > > + return defaultValue; > > +} > > + > > +template<> > > +int32_t YamlObject::get(const int32_t &defaultValue, bool *ok) const > > +{ > > + setOk(ok, false); > > + > > + if (type_ != Value) > > + return defaultValue; > > + > > + if (value_ == "") > > + return defaultValue; > > + > > + char *end; > > + > > + errno = 0; > > + int32_t value = std::strtol(value_.c_str(), &end, 10); > > + > > + if ('\0' != *end || errno == ERANGE) > > + return defaultValue; > > + > > + setOk(ok, true); > > + return value; > > +} > > + > > +template<> > > +uint32_t YamlObject::get(const uint32_t &defaultValue, bool *ok) const > > +{ > > + setOk(ok, false); > > + > > + if (type_ != Value) > > + return defaultValue; > > + > > + if (value_ == "") > > + return defaultValue; > > + > > + /* > > + * libyaml parses all scalar values as strings. When a string has > > + * leading spaces before a minus sign, for example " -10", strtoul > > + * skips leading spaces, accepts the leading minus sign, and the > > + * calculated digits are negated as if by unary minus. Rule it out in > > + * case the user gets a large number when the value is negative. > > + */ > > + std::size_t found = value_.find_first_not_of(" \t"); > > + if (found != std::string::npos && value_[found] == '-') > > + return defaultValue; > > + > > + char *end; > > + > > + errno = 0; > > + uint32_t value = std::strtoul(value_.c_str(), &end, 10); > > + > > + if ('\0' != *end || errno == ERANGE) > > + return defaultValue; > > + > > + setOk(ok, true); > > + return value; > > +} > > + > > +template<> > > +double YamlObject::get(const double &defaultValue, bool *ok) const > > +{ > > + setOk(ok, false); > > + > > + if (type_ != Value) > > + return defaultValue; > > + > > + if (value_ == "") > > + return defaultValue; > > + > > + char *end; > > + > > + errno = 0; > > + double value = std::strtod(value_.c_str(), &end); > > + > > + if ('\0' != *end || errno == ERANGE) > > + return defaultValue; > > + > > + setOk(ok, true); > > + return value; > > +} > > + > > +template<> > > +std::string YamlObject::get(const std::string &defaultValue, bool *ok) const > > +{ > > + setOk(ok, false); > > + > > + if (type_ != Value) > > + return defaultValue; > > + > > + setOk(ok, true); > > + return value_; > > +} > > + > > +template<> > > +Size YamlObject::get(const Size &defaultValue, bool *ok) const > > +{ > > + setOk(ok, false); > > + > > + if (type_ != List) > > + return defaultValue; > > + > > + if (list_.size() != 2) > > + return defaultValue; > > + > > + /* > > + * Add a local variable to validate each dimension in case > > + * that ok == nullptr. > > + */ > > + bool valid; > > + uint32_t width = list_[0]->get<uint32_t>(0, &valid); > > + if (!valid) > > + return defaultValue; > > + > > + uint32_t height = list_[1]->get<uint32_t>(0, &valid); > > + if (!valid) > > + return defaultValue; > > + > > + setOk(ok, true); > > + return Size(width, height); > > +} > > + > > +#endif /* __DOXYGEN__ */ > > + > > +/** > > + * \fn YamlObject::size() > > + * \brief Retrieve the number of elements in a list YamlObject > > + * > > + * This function retrieves the size of the YamlObject, defined as the number of > > + * child elements it contains. Only YamlObject instances of List type have a > > + * size, calling this function on other types of instances is invalid and > > + * results in undefined behaviour. > > + * > > + * \return The size of the YamlObject > > + */ > > +std::size_t YamlObject::size() const > > +{ > > + if (type_ != List) > > + return 0; > > + > > + return list_.size(); > > +} > > + > > +/** > > + * \fn YamlObject::operator[](std::size_t index) const > > + * \brief Retrieve the element from list YamlObject by index > > + * > > + * This function retrieves an element of the YamlObject. Only YamlObject > > + * instances of List type associate elements with index, calling this function > > + * on other types of instances is invalid and results in undefined behaviour. > > + * > > + * \return The YamlObject as an element of the list > > + */ > > +const YamlObject &YamlObject::operator[](std::size_t index) const > > +{ > > + if (type_ != List || index >= size()) > > + return empty; > > + > > + return *list_[index]; > > +} > > + > > +/** > > + * \fn YamlObject::contains() > > + * \brief Check if an element of a dictionary exists > > + * > > + * This function check if the YamlObject contains an element. Only YamlObject > > + * instances of Dictionary type associate elements with names, calling this > > + * function on other types of instances is invalid and results in undefined > > + * behaviour. > > + * > > + * \return True if an element exists, false otherwise > > + */ > > +bool YamlObject::contains(const std::string &key) const > > +{ > > + if (dictionary_.find(key) == dictionary_.end()) > > + return false; > > + > > + return true; > > +} > > + > > +/** > > + * \fn YamlObject::memberNames() > > + * \brief Retrieve all member names of the dictionary > > + * > > + * This function retrieve member names of a YamlObject. Only YamlObject > > + * instances of Dictionary type associate elements with names, calling this > > + * function on other types of instances is invalid and results in undefined > > + * behaviour. > > + * > > + * \todo Replace this function with an iterator-based API > > + * > > + * \return A vector of string as the member names > > + */ > > +std::vector<std::string> YamlObject::memberNames() const > > +{ > > + std::vector<std::string> memberNames; > > + for (auto &[key, _] : dictionary_) > > + memberNames.push_back(key); > > + > > + return memberNames; > > +} > > + > > +/** > > + * \fn YamlObject::operator[](const std::string &key) const > > + * \brief Retrieve a member by name from the dictionary > > + * > > + * This function retrieve a member of a YamlObject by name. Only YamlObject > > + * instances of Dictionary type associate elements with names, calling this > > + * function on other types of instances is invalid and results in undefined > > + * behaviour. > > + * > > + * \return The YamlObject corresponding to the \a key member > > + */ > > +const YamlObject &YamlObject::operator[](const std::string &key) const > > +{ > > + if (type_ != Dictionary || !contains(key)) > > + return empty; > > + > > + auto iter = dictionary_.find(key); > > + return *iter->second; > > +} > > + > > +#ifndef __DOXYGEN__ > > + > > +class YamlParserContext > > +{ > > +public: > > + YamlParserContext(); > > + ~YamlParserContext(); > > + > > + int init(std::FILE *fh); > > + int parseContent(YamlObject &yamlObject); > > + > > +private: > > + struct EventDeleter { > > + void operator()(yaml_event_t *event) const > > + { > > + yaml_event_delete(event); > > + delete event; > > + } > > + }; > > + using EventPtr = std::unique_ptr<yaml_event_t, EventDeleter>; > > + > > + EventPtr nextEvent(); > > + > > + void readValue(std::string &value, EventPtr event); > > + int parseDictionaryOrList(YamlObject::Type type, > > + const std::function<int(EventPtr event)> &parseItem); > > + int parseNextYamlObject(YamlObject &yamlObject, EventPtr event); > > + > > + bool parserValid_; > > + yaml_parser_t parser_; > > +}; > > + > > +/** > > + * \class YamlParserContext > > + * \brief Class for YamlParser parsing and context data > > + * > > + * The YamlParserContext class stores the internal yaml_parser_t and provides > > + * helper functions to do event-based parsing for YAML files. > > + */ > > +YamlParserContext::YamlParserContext() > > + : parserValid_(false) > > +{ > > +} > > + > > +/** > > + * \class YamlParserContext > > + * \brief Destructor of YamlParserContext > > + */ > > +YamlParserContext::~YamlParserContext() > > +{ > > + if (parserValid_) { > > + yaml_parser_delete(&parser_); > > + parserValid_ = false; > > + } > > +} > > + > > +/** > > + * \fn YamlParserContext::init() > > + * \brief Initialize a parser with an opened file for parsing > > + * \param[in] fh The YAML file to parse > > + * > > + * Prior to parsing the YAML content, the YamlParserContext must be initialized > > + * with an opened FILE to create an internal parser. The FILE needs to stay > > + * valid during the process. > > + * > > + * \return 0 on success or a negative error code otherwise > > + * \retval -EINVAL The parser has failed to initialize > > + */ > > +int YamlParserContext::init(std::FILE *fh) > > +{ > > + /* yaml_parser_initialize returns 1 when it succeededs */ > > + if (!yaml_parser_initialize(&parser_)) { > > + LOG(YamlParser, Error) << "Failed to initialize YAML parser"; > > + return -EINVAL; > > + } > > + parserValid_ = true; > > + yaml_parser_set_input_file(&parser_, fh); > > + > > + return 0; > > +} > > + > > +/** > > + * \fn YamlParserContext::nextEvent() > > + * \brief Get the next event > > + * > > + * Get the next event in the current YAML event stream, and return nullptr when > > + * there is no more event. > > + * > > + * \return The next event on success or nullptr otherwise > > + */ > > +YamlParserContext::EventPtr YamlParserContext::nextEvent() > > +{ > > + EventPtr event(new yaml_event_t); > > + > > + /* yaml_parser_parse returns 1 when it succeeds */ > > + if (!yaml_parser_parse(&parser_, event.get())) > > + return nullptr; > > + > > + return event; > > +} > > + > > +/** > > + * \fn YamlParserContext::parseContent() > > + * \brief Parse the content of a YAML document > > + * \param[in] yamlObject The result of YamlObject > > + * > > + * Check YAML start and end events of a YAML document, and parse the root object > > + * of the YAML document into a YamlObject. > > + * > > + * \return 0 on success or a negative error code otherwise > > + * \retval -EINVAL The parser has failed to validate end of a YAML file > > + */ > > +int YamlParserContext::parseContent(YamlObject &yamlObject) > > +{ > > + /* Check start of the YAML file. */ > > + EventPtr event = nextEvent(); > > + if (!event || event->type != YAML_STREAM_START_EVENT) > > + return -EINVAL; > > + > > + event = nextEvent(); > > + if (!event || event->type != YAML_DOCUMENT_START_EVENT) > > + return -EINVAL; > > + > > + /* Parse the root object. */ > > + event = nextEvent(); > > + if (parseNextYamlObject(yamlObject, std::move(event))) > > + return -EINVAL; > > + > > + /* Check end of the YAML file. */ > > + event = nextEvent(); > > + if (!event || event->type != YAML_DOCUMENT_END_EVENT) > > + return -EINVAL; > > + > > + event = nextEvent(); > > + if (!event || event->type != YAML_STREAM_END_EVENT) > > + return -EINVAL; > > + > > + return 0; > > +} > > + > > +/** > > + * \fn YamlParserContext::readValue() > > + * \brief Parse event scalar and fill its content into a string > > + * \param[in] value The string reference to fill value > > + * > > + * A helper function to parse a scalar event as string. The caller needs to > > + * guarantee the event is of scaler type. > > + */ > > +void YamlParserContext::readValue(std::string &value, EventPtr event) > > +{ > > + value.assign(reinterpret_cast<char *>(event->data.scalar.value), > > + event->data.scalar.length); > > +} > > + > > +/** > > + * \fn YamlParserContext::parseDictionaryOrList() > > + * \brief A helper function to abstract the common part of parsing dictionary or list > > + * > > + * \param[in] isDictionary True for parsing a dictionary, and false for a list > > + * \param[in] parseItem The callback to handle an item > > + * > > + * A helper function to abstract parsing an item from a dictionary or a list. > > + * The differences of them in a YAML event stream are: > > + * > > + * 1. The start and end event types are different > > + * 2. There is a leading scalar string as key in the items of a dictionary > > + * > > + * The caller should handle the leading key string in its callback parseItem > > + * when it's a dictionary. > > + * > > + * \return 0 on success or a negative error code otherwise > > + * \retval -EINVAL The parser is failed to initialize > > + */ > > +int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, > > + const std::function<int(EventPtr event)> &parseItem) > > +{ > > + yaml_event_type_t endEventType = YAML_SEQUENCE_END_EVENT; > > + if (type == YamlObject::Dictionary) > > + endEventType = YAML_MAPPING_END_EVENT; > > + > > + /* > > + * Add a safety counter to make sure we don't loop indefinitely in case > > + * the YAML file is malformed. > > + */ > > + for (unsigned int sentinel = 1000; sentinel; sentinel--) { > > + auto evt = nextEvent(); > > + if (!evt) > > + return -EINVAL; > > + > > + if (evt->type == endEventType) > > + return 0; > > + > > + int ret = parseItem(std::move(evt)); > > + if (ret) > > + return ret; > > + } > > + > > + LOG(YamlParser, Error) << "The YAML file contains a List or Dictionary" > > + " whose size exceeding the parser's limit(1000)"; > > + > > + return -EINVAL; > > +} > > + > > +/** > > + * \fn YamlParserContext::parseNextYamlObject() > > + * \brief Parse next YAML event and read it as a YamlObject > > + * \param[in] yamlObject The result of YamlObject > > + * \param[in] event The leading event of the object > > + * > > + * Parse next YAML object separately as a value, list or dictionary. > > + * > > + * \return 0 on success or a negative error code otherwise > > + * \retval -EINVAL Fail to parse the YAML file. > > + */ > > +int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr event) > > +{ > > + if (!event) > > + return -EINVAL; > > + > > + switch (event->type) { > > + case YAML_SCALAR_EVENT: > > + yamlObject.type_ = YamlObject::Value; > > + readValue(yamlObject.value_, std::move(event)); > > + return 0; > > + > > + case YAML_SEQUENCE_START_EVENT: { > > + yamlObject.type_ = YamlObject::List; > > + auto &list = yamlObject.list_; > > + auto handler = [this, &list](EventPtr evt) { > > + list.emplace_back(new YamlObject()); > > + return parseNextYamlObject(*list.back(), std::move(evt)); > > + }; > > + return parseDictionaryOrList(YamlObject::List, handler); > > + } > > + > > + case YAML_MAPPING_START_EVENT: { > > + yamlObject.type_ = YamlObject::Dictionary; > > + auto &dictionary = yamlObject.dictionary_; > > + auto handler = [this, &dictionary](EventPtr evtKey) { > > + /* Parse key */ > > + if (evtKey->type != YAML_SCALAR_EVENT) { > > + LOG(YamlParser, Error) << "Expect key at line: " > > + << evtKey->start_mark.line > > + << " column: " > > + << evtKey->start_mark.column; > > + return -EINVAL; > > + } > > + > > + std::string key; > > + readValue(key, std::move(evtKey)); > > + > > + /* Parse value */ > > + EventPtr evtValue = nextEvent(); > > + if (!evtValue) > > + return -EINVAL; > > + > > + auto elem = dictionary.emplace(key, std::make_unique<YamlObject>()); > > + return parseNextYamlObject(*elem.first->second.get(), std::move(evtValue)); > > + }; > > + return parseDictionaryOrList(YamlObject::Dictionary, handler); > > + } > > + default: > > + LOG(YamlParser, Error) << "Invalid YAML file"; > > + return -EINVAL; > > + } > > +} > > + > > +#endif /* __DOXYGEN__ */ > > + > > +/** > > + * \class YamlParser > > + * \brief A helper class for parsing a YAML file > > + * > > + * The YamlParser class provides an easy interface to parse the contents of a > > + * YAML file into a tree of YamlObject instances. > > + * > > + * Example usage: > > + * > > + * \code{.unparsed} > > + * > > + * name: > > + * "John" > > + * numbers: > > + * - 1 > > + * - 2 > > + * > > + * \endcode > > + * > > + * The following code illustrates how to parse the above YAML file: > > + * > > + * \code{.cpp} > > + * > > + * std::unique_ptr<YamlObject> root = YamlParser::parse(fh); > > + * if (!root) > > + * return; > > + * > > + * if (!root->isDictionary()) > > + * return; > > + * > > + * const YamlObject &name = (*root)["name"]; > > + * std::cout << name.get<std::string>("") << std::endl; > > + * > > + * const YamlObject &numbers = (*root)["numbers"]; > > + * if (!numbers.isList()) > > + * return; > > + * > > + * for (std::size_t i = 0; i < numbers.size(); i++) > > + * std::cout << numbers[i].get<int32_t>(0) << std::endl; > > + * > > + * \endcode > > + * > > + * The YamlParser::parse() function takes an open FILE, parses its contents, and > > + * returns a pointer to a YamlObject corresponding to the root node of the YAML > > + * document. > > + */ > > + > > +/** > > + * \fn YamlParser::parse() > > + * \brief Parse a YAML file as a YamlObject > > + * \param[in] fh The YAML file to parse > > + * > > + * The YamlParser::parse() function takes an open FILE, parses its contents, and > > + * returns a pointer to a YamlObject corresponding to the root node of the YAML > > + * document. The caller is responsible for closing the file. > > + * > > + * \return Pointer to result YamlObject on success or nullptr otherwise > > + */ > > +std::unique_ptr<YamlObject> YamlParser::parse(std::FILE *fh) > > +{ > > + YamlParserContext context; > > + > > + if (context.init(fh)) > > + return nullptr; > > + > > + std::unique_ptr<YamlObject> root(new YamlObject()); > > + > > + if (context.parseContent(*root)) { > > + LOG(YamlParser, Error) << "Failed to parse YAML content"; > > + return nullptr; > > + } > > + > > + return root; > > +} > > + > > +} /* namespace libcamera */
diff --git a/README.rst b/README.rst index aae6b79f..b5a2d448 100644 --- a/README.rst +++ b/README.rst @@ -60,7 +60,7 @@ Meson Build system: [required] pip3 install --user --upgrade meson for the libcamera core: [required] - python3-yaml python3-ply python3-jinja2 + libyaml-dev python3-yaml python3-ply python3-jinja2 for IPA module signing: [required] libgnutls28-dev openssl @@ -98,7 +98,7 @@ for tracing with lttng: [optional] liblttng-ust-dev python3-jinja2 lttng-tools for android: [optional] - libexif-dev libjpeg-dev libyaml-dev + libexif-dev libjpeg-dev for lc-compliance: [optional] libevent-dev diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index c9e055d4..7a780d48 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -42,4 +42,5 @@ libcamera_internal_headers = files([ 'v4l2_pixelformat.h', 'v4l2_subdevice.h', 'v4l2_videodevice.h', + 'yaml_parser.h', ]) diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h new file mode 100644 index 00000000..3a4f3052 --- /dev/null +++ b/include/libcamera/internal/yaml_parser.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Google Inc. + * + * yaml_parser.h - libcamera YAML parsing helper + */ + +#pragma once + +#include <cstdio> +#include <map> +#include <string> +#include <vector> + +#include <libcamera/base/class.h> + +#include <libcamera/geometry.h> + +namespace libcamera { + +class YamlParserContext; + +class YamlObject +{ +public: + YamlObject(); + ~YamlObject(); + + bool isValue() const + { + return type_ == Value; + } + bool isList() const + { + return type_ == List; + } + bool isDictionary() const + { + return type_ == Dictionary; + } + +#ifndef __DOXYGEN__ + template<typename T, + typename std::enable_if_t< + std::is_same<bool, T>::value || + std::is_same<double, T>::value || + std::is_same<int32_t, T>::value || + std::is_same<uint32_t, T>::value || + std::is_same<std::string, T>::value || + std::is_same<Size, T>::value> * = nullptr> +#else + template<typename T> +#endif + T get(const T &defaultValue, bool *ok = nullptr) const; + + std::size_t size() const; + const YamlObject &operator[](std::size_t index) const; + + bool contains(const std::string &key) const; + const YamlObject &operator[](const std::string &key) const; + std::vector<std::string> memberNames() const; + +private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject) + + friend class YamlParserContext; + + enum Type { + Dictionary, + List, + Value, + }; + + Type type_; + + std::string value_; + std::vector<std::unique_ptr<YamlObject>> list_; + std::map<const std::string, std::unique_ptr<YamlObject>> dictionary_; +}; + +class YamlParser final +{ +public: + static std::unique_ptr<YamlObject> parse(std::FILE *fh); +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 26912ca1..f8e18e03 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -46,6 +46,7 @@ libcamera_sources = files([ 'v4l2_pixelformat.cpp', 'v4l2_subdevice.cpp', 'v4l2_videodevice.cpp', + 'yaml_parser.cpp', ]) libcamera_sources += libcamera_public_headers @@ -66,6 +67,7 @@ subdir('proxy') libdl = cc.find_library('dl') libgnutls = cc.find_library('gnutls', required : true) libudev = dependency('libudev', required : false) +libyaml = dependency('yaml-0.1', required : true) if libgnutls.found() config_h.set('HAVE_GNUTLS', 1) @@ -126,6 +128,7 @@ libcamera_deps = [ libgnutls, liblttng, libudev, + libyaml, ] # We add '/' to the build_rpath as a 'safe' path to act as a boolean flag. diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp new file mode 100644 index 00000000..4a047494 --- /dev/null +++ b/src/libcamera/yaml_parser.cpp @@ -0,0 +1,679 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Google Inc. + * + * yaml_parser.cpp - libcamera YAML parsing helper + */ + +#include "libcamera/internal/yaml_parser.h" + +#include <cerrno> +#include <cstdlib> + +#include <libcamera/base/log.h> + +#include <yaml.h> + +/** + * \file libcamera/internal/yaml_parser.h + * \brief A YAML parser helper + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(YamlParser) + +namespace { + +/* Empty static YamlObject as a safe result for invalid operations */ +static const YamlObject empty; + +void setOk(bool *ok, bool result) +{ + if (ok) + *ok = result; +} + +} /* namespace */ + +/** + * \class YamlObject + * \brief A class representing the tree structure of the YAML content + * + * The YamlObject class represents the tree structure of YAML content. A + * YamlObject can be a dictionary or list of YamlObjects or a value if a tree + * leaf. + */ + +YamlObject::YamlObject() + : type_(Value) +{ +} + +YamlObject::~YamlObject() = default; + +/** + * \fn YamlObject::isValue() + * \brief Return whether the YamlObject is a value + * + * \return True if the YamlObject is a value, false otherwise + */ + +/** + * \fn YamlObject::isList() + * \brief Return whether the YamlObject is a list + * + * \return True if the YamlObject is a list, false otherwise + */ + +/** + * \fn YamlObject::isDictionary() + * \brief Return whether the YamlObject is a dictionary + * + * \return True if the YamlObject is a dictionary, false otherwise + */ + +/** + * \fn template<typename T> YamlObject::get<T>( + * const T &defaultValue, bool *ok) const + * \brief Parse the YamlObject as a \a T value + * \param[in] defaultValue The default value when failing to parse + * \param[out] ok The result of whether the parse succeeded + * + * This function parses the value of the YamlObject as a \a T object, and + * returns the value. If parsing fails (usually because the YamlObject doesn't + * store a \a T value), the \a defaultValue is returned, and \a ok is set to + * false. Otherwise, the YamlObject value is returned, and \a ok is set to true. + * + * The \a ok pointer is optional and can be a nullptr if the caller doesn't + * need to know if parsing succeeded. + * + * \return Value as a bool type + */ + +#ifndef __DOXYGEN__ + +template<> +bool YamlObject::get(const bool &defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != Value) + return defaultValue; + + if (value_ == "true") { + setOk(ok, true); + return true; + } else if (value_ == "false") { + setOk(ok, true); + return false; + } + + return defaultValue; +} + +template<> +int32_t YamlObject::get(const int32_t &defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != Value) + return defaultValue; + + if (value_ == "") + return defaultValue; + + char *end; + + errno = 0; + int32_t value = std::strtol(value_.c_str(), &end, 10); + + if ('\0' != *end || errno == ERANGE) + return defaultValue; + + setOk(ok, true); + return value; +} + +template<> +uint32_t YamlObject::get(const uint32_t &defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != Value) + return defaultValue; + + if (value_ == "") + return defaultValue; + + /* + * libyaml parses all scalar values as strings. When a string has + * leading spaces before a minus sign, for example " -10", strtoul + * skips leading spaces, accepts the leading minus sign, and the + * calculated digits are negated as if by unary minus. Rule it out in + * case the user gets a large number when the value is negative. + */ + std::size_t found = value_.find_first_not_of(" \t"); + if (found != std::string::npos && value_[found] == '-') + return defaultValue; + + char *end; + + errno = 0; + uint32_t value = std::strtoul(value_.c_str(), &end, 10); + + if ('\0' != *end || errno == ERANGE) + return defaultValue; + + setOk(ok, true); + return value; +} + +template<> +double YamlObject::get(const double &defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != Value) + return defaultValue; + + if (value_ == "") + return defaultValue; + + char *end; + + errno = 0; + double value = std::strtod(value_.c_str(), &end); + + if ('\0' != *end || errno == ERANGE) + return defaultValue; + + setOk(ok, true); + return value; +} + +template<> +std::string YamlObject::get(const std::string &defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != Value) + return defaultValue; + + setOk(ok, true); + return value_; +} + +template<> +Size YamlObject::get(const Size &defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != List) + return defaultValue; + + if (list_.size() != 2) + return defaultValue; + + /* + * Add a local variable to validate each dimension in case + * that ok == nullptr. + */ + bool valid; + uint32_t width = list_[0]->get<uint32_t>(0, &valid); + if (!valid) + return defaultValue; + + uint32_t height = list_[1]->get<uint32_t>(0, &valid); + if (!valid) + return defaultValue; + + setOk(ok, true); + return Size(width, height); +} + +#endif /* __DOXYGEN__ */ + +/** + * \fn YamlObject::size() + * \brief Retrieve the number of elements in a list YamlObject + * + * This function retrieves the size of the YamlObject, defined as the number of + * child elements it contains. Only YamlObject instances of List type have a + * size, calling this function on other types of instances is invalid and + * results in undefined behaviour. + * + * \return The size of the YamlObject + */ +std::size_t YamlObject::size() const +{ + if (type_ != List) + return 0; + + return list_.size(); +} + +/** + * \fn YamlObject::operator[](std::size_t index) const + * \brief Retrieve the element from list YamlObject by index + * + * This function retrieves an element of the YamlObject. Only YamlObject + * instances of List type associate elements with index, calling this function + * on other types of instances is invalid and results in undefined behaviour. + * + * \return The YamlObject as an element of the list + */ +const YamlObject &YamlObject::operator[](std::size_t index) const +{ + if (type_ != List || index >= size()) + return empty; + + return *list_[index]; +} + +/** + * \fn YamlObject::contains() + * \brief Check if an element of a dictionary exists + * + * This function check if the YamlObject contains an element. Only YamlObject + * instances of Dictionary type associate elements with names, calling this + * function on other types of instances is invalid and results in undefined + * behaviour. + * + * \return True if an element exists, false otherwise + */ +bool YamlObject::contains(const std::string &key) const +{ + if (dictionary_.find(key) == dictionary_.end()) + return false; + + return true; +} + +/** + * \fn YamlObject::memberNames() + * \brief Retrieve all member names of the dictionary + * + * This function retrieve member names of a YamlObject. Only YamlObject + * instances of Dictionary type associate elements with names, calling this + * function on other types of instances is invalid and results in undefined + * behaviour. + * + * \todo Replace this function with an iterator-based API + * + * \return A vector of string as the member names + */ +std::vector<std::string> YamlObject::memberNames() const +{ + std::vector<std::string> memberNames; + for (auto &[key, _] : dictionary_) + memberNames.push_back(key); + + return memberNames; +} + +/** + * \fn YamlObject::operator[](const std::string &key) const + * \brief Retrieve a member by name from the dictionary + * + * This function retrieve a member of a YamlObject by name. Only YamlObject + * instances of Dictionary type associate elements with names, calling this + * function on other types of instances is invalid and results in undefined + * behaviour. + * + * \return The YamlObject corresponding to the \a key member + */ +const YamlObject &YamlObject::operator[](const std::string &key) const +{ + if (type_ != Dictionary || !contains(key)) + return empty; + + auto iter = dictionary_.find(key); + return *iter->second; +} + +#ifndef __DOXYGEN__ + +class YamlParserContext +{ +public: + YamlParserContext(); + ~YamlParserContext(); + + int init(std::FILE *fh); + int parseContent(YamlObject &yamlObject); + +private: + struct EventDeleter { + void operator()(yaml_event_t *event) const + { + yaml_event_delete(event); + delete event; + } + }; + using EventPtr = std::unique_ptr<yaml_event_t, EventDeleter>; + + EventPtr nextEvent(); + + void readValue(std::string &value, EventPtr event); + int parseDictionaryOrList(YamlObject::Type type, + const std::function<int(EventPtr event)> &parseItem); + int parseNextYamlObject(YamlObject &yamlObject, EventPtr event); + + bool parserValid_; + yaml_parser_t parser_; +}; + +/** + * \class YamlParserContext + * \brief Class for YamlParser parsing and context data + * + * The YamlParserContext class stores the internal yaml_parser_t and provides + * helper functions to do event-based parsing for YAML files. + */ +YamlParserContext::YamlParserContext() + : parserValid_(false) +{ +} + +/** + * \class YamlParserContext + * \brief Destructor of YamlParserContext + */ +YamlParserContext::~YamlParserContext() +{ + if (parserValid_) { + yaml_parser_delete(&parser_); + parserValid_ = false; + } +} + +/** + * \fn YamlParserContext::init() + * \brief Initialize a parser with an opened file for parsing + * \param[in] fh The YAML file to parse + * + * Prior to parsing the YAML content, the YamlParserContext must be initialized + * with an opened FILE to create an internal parser. The FILE needs to stay + * valid during the process. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser has failed to initialize + */ +int YamlParserContext::init(std::FILE *fh) +{ + /* yaml_parser_initialize returns 1 when it succeededs */ + if (!yaml_parser_initialize(&parser_)) { + LOG(YamlParser, Error) << "Failed to initialize YAML parser"; + return -EINVAL; + } + parserValid_ = true; + yaml_parser_set_input_file(&parser_, fh); + + return 0; +} + +/** + * \fn YamlParserContext::nextEvent() + * \brief Get the next event + * + * Get the next event in the current YAML event stream, and return nullptr when + * there is no more event. + * + * \return The next event on success or nullptr otherwise + */ +YamlParserContext::EventPtr YamlParserContext::nextEvent() +{ + EventPtr event(new yaml_event_t); + + /* yaml_parser_parse returns 1 when it succeeds */ + if (!yaml_parser_parse(&parser_, event.get())) + return nullptr; + + return event; +} + +/** + * \fn YamlParserContext::parseContent() + * \brief Parse the content of a YAML document + * \param[in] yamlObject The result of YamlObject + * + * Check YAML start and end events of a YAML document, and parse the root object + * of the YAML document into a YamlObject. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser has failed to validate end of a YAML file + */ +int YamlParserContext::parseContent(YamlObject &yamlObject) +{ + /* Check start of the YAML file. */ + EventPtr event = nextEvent(); + if (!event || event->type != YAML_STREAM_START_EVENT) + return -EINVAL; + + event = nextEvent(); + if (!event || event->type != YAML_DOCUMENT_START_EVENT) + return -EINVAL; + + /* Parse the root object. */ + event = nextEvent(); + if (parseNextYamlObject(yamlObject, std::move(event))) + return -EINVAL; + + /* Check end of the YAML file. */ + event = nextEvent(); + if (!event || event->type != YAML_DOCUMENT_END_EVENT) + return -EINVAL; + + event = nextEvent(); + if (!event || event->type != YAML_STREAM_END_EVENT) + return -EINVAL; + + return 0; +} + +/** + * \fn YamlParserContext::readValue() + * \brief Parse event scalar and fill its content into a string + * \param[in] value The string reference to fill value + * + * A helper function to parse a scalar event as string. The caller needs to + * guarantee the event is of scaler type. + */ +void YamlParserContext::readValue(std::string &value, EventPtr event) +{ + value.assign(reinterpret_cast<char *>(event->data.scalar.value), + event->data.scalar.length); +} + +/** + * \fn YamlParserContext::parseDictionaryOrList() + * \brief A helper function to abstract the common part of parsing dictionary or list + * + * \param[in] isDictionary True for parsing a dictionary, and false for a list + * \param[in] parseItem The callback to handle an item + * + * A helper function to abstract parsing an item from a dictionary or a list. + * The differences of them in a YAML event stream are: + * + * 1. The start and end event types are different + * 2. There is a leading scalar string as key in the items of a dictionary + * + * The caller should handle the leading key string in its callback parseItem + * when it's a dictionary. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser is failed to initialize + */ +int YamlParserContext::parseDictionaryOrList(YamlObject::Type type, + const std::function<int(EventPtr event)> &parseItem) +{ + yaml_event_type_t endEventType = YAML_SEQUENCE_END_EVENT; + if (type == YamlObject::Dictionary) + endEventType = YAML_MAPPING_END_EVENT; + + /* + * Add a safety counter to make sure we don't loop indefinitely in case + * the YAML file is malformed. + */ + for (unsigned int sentinel = 1000; sentinel; sentinel--) { + auto evt = nextEvent(); + if (!evt) + return -EINVAL; + + if (evt->type == endEventType) + return 0; + + int ret = parseItem(std::move(evt)); + if (ret) + return ret; + } + + LOG(YamlParser, Error) << "The YAML file contains a List or Dictionary" + " whose size exceeding the parser's limit(1000)"; + + return -EINVAL; +} + +/** + * \fn YamlParserContext::parseNextYamlObject() + * \brief Parse next YAML event and read it as a YamlObject + * \param[in] yamlObject The result of YamlObject + * \param[in] event The leading event of the object + * + * Parse next YAML object separately as a value, list or dictionary. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL Fail to parse the YAML file. + */ +int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr event) +{ + if (!event) + return -EINVAL; + + switch (event->type) { + case YAML_SCALAR_EVENT: + yamlObject.type_ = YamlObject::Value; + readValue(yamlObject.value_, std::move(event)); + return 0; + + case YAML_SEQUENCE_START_EVENT: { + yamlObject.type_ = YamlObject::List; + auto &list = yamlObject.list_; + auto handler = [this, &list](EventPtr evt) { + list.emplace_back(new YamlObject()); + return parseNextYamlObject(*list.back(), std::move(evt)); + }; + return parseDictionaryOrList(YamlObject::List, handler); + } + + case YAML_MAPPING_START_EVENT: { + yamlObject.type_ = YamlObject::Dictionary; + auto &dictionary = yamlObject.dictionary_; + auto handler = [this, &dictionary](EventPtr evtKey) { + /* Parse key */ + if (evtKey->type != YAML_SCALAR_EVENT) { + LOG(YamlParser, Error) << "Expect key at line: " + << evtKey->start_mark.line + << " column: " + << evtKey->start_mark.column; + return -EINVAL; + } + + std::string key; + readValue(key, std::move(evtKey)); + + /* Parse value */ + EventPtr evtValue = nextEvent(); + if (!evtValue) + return -EINVAL; + + auto elem = dictionary.emplace(key, std::make_unique<YamlObject>()); + return parseNextYamlObject(*elem.first->second.get(), std::move(evtValue)); + }; + return parseDictionaryOrList(YamlObject::Dictionary, handler); + } + default: + LOG(YamlParser, Error) << "Invalid YAML file"; + return -EINVAL; + } +} + +#endif /* __DOXYGEN__ */ + +/** + * \class YamlParser + * \brief A helper class for parsing a YAML file + * + * The YamlParser class provides an easy interface to parse the contents of a + * YAML file into a tree of YamlObject instances. + * + * Example usage: + * + * \code{.unparsed} + * + * name: + * "John" + * numbers: + * - 1 + * - 2 + * + * \endcode + * + * The following code illustrates how to parse the above YAML file: + * + * \code{.cpp} + * + * std::unique_ptr<YamlObject> root = YamlParser::parse(fh); + * if (!root) + * return; + * + * if (!root->isDictionary()) + * return; + * + * const YamlObject &name = (*root)["name"]; + * std::cout << name.get<std::string>("") << std::endl; + * + * const YamlObject &numbers = (*root)["numbers"]; + * if (!numbers.isList()) + * return; + * + * for (std::size_t i = 0; i < numbers.size(); i++) + * std::cout << numbers[i].get<int32_t>(0) << std::endl; + * + * \endcode + * + * The YamlParser::parse() function takes an open FILE, parses its contents, and + * returns a pointer to a YamlObject corresponding to the root node of the YAML + * document. + */ + +/** + * \fn YamlParser::parse() + * \brief Parse a YAML file as a YamlObject + * \param[in] fh The YAML file to parse + * + * The YamlParser::parse() function takes an open FILE, parses its contents, and + * returns a pointer to a YamlObject corresponding to the root node of the YAML + * document. The caller is responsible for closing the file. + * + * \return Pointer to result YamlObject on success or nullptr otherwise + */ +std::unique_ptr<YamlObject> YamlParser::parse(std::FILE *fh) +{ + YamlParserContext context; + + if (context.init(fh)) + return nullptr; + + std::unique_ptr<YamlObject> root(new YamlObject()); + + if (context.parseContent(*root)) { + LOG(YamlParser, Error) << "Failed to parse YAML content"; + return nullptr; + } + + return root; +} + +} /* namespace libcamera */
Introduce YamlParser as a helper to convert contents of a yaml file to a tree based structure for easier reading, and to avoid writing parser with raw yaml tokens. The class is based on libyaml, and only support reading but not writing a yaml file. The interface is inspired by Json::Value class from jsoncpp: http://jsoncpp.sourceforge.net/class_json_1_1_value.html Signed-off-by: Han-Lin Chen <hanlinchen@chromium.org> --- README.rst | 4 +- include/libcamera/internal/meson.build | 1 + include/libcamera/internal/yaml_parser.h | 87 +++ src/libcamera/meson.build | 3 + src/libcamera/yaml_parser.cpp | 679 +++++++++++++++++++++++ 5 files changed, 772 insertions(+), 2 deletions(-) create mode 100644 include/libcamera/internal/yaml_parser.h create mode 100644 src/libcamera/yaml_parser.cpp