From patchwork Wed Apr 27 14:09:27 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15730 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 34798C3256 for ; Wed, 27 Apr 2022 14:09:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7298065644; Wed, 27 Apr 2022 16:09:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1651068578; bh=fpnbwl1GakD6NLpMiQzeGHuGOvdXKU+D8ZEiPrXyb0E=; h=To:Date:Subject:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=hmhxDg3XM7jms8TEnuh0WlxySZFgFWOxcgCKxULal9EQj2vXSomPSg4KF0IEUCc5h Gdq9WuGhbPhuaLHbNIgao7j+lMUtSCU6tdL4JvN9uUhWaeKxk1RZQ01uYYLu11DwDI 0aPgXEVt0jc09HHCNnbfBEnR1JNF2SKCz0qokBGnFbJIuN7umEe8N7INjyzBhwocbO Gs3Q8L0Xhw72uE75J3VnTc1T5WnEMxqIwRQ/Qpvy1uGyU01yyuUGgdJD7gGOvzdAan rpDBFFJ03Aueyez8X03+j76q6GUkAEh106PWbJtqPm0bw5/8T/jsJu0cOyoM5/CJBo t2NPPt4jGB5Wg== Received: from mail-pf1-x431.google.com (mail-pf1-x431.google.com [IPv6:2607:f8b0:4864:20::431]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 233A76042F for ; Wed, 27 Apr 2022 16:09:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="WLaaQnmo"; dkim-atps=neutral Received: by mail-pf1-x431.google.com with SMTP id p8so1678547pfh.8 for ; Wed, 27 Apr 2022 07:09:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=WK3Ftai6s/9/0GbZ7N/nhleKOHPelPCS9TOJy4MxNgE=; b=WLaaQnmolbm45rjsPOStGzBYJV/D94OJKCstWm/tCLYF/fcT9dI65dePoJTWbWpQk1 XkzWx8F3+xOjED7qv1INX4PyOW/cwjSQwnP0CQe2n/8+4nxCBYeZMRGAp1z4xqoxrICS BSThIuKKzQWpcrdfvnl6Gcgg5BfeywyAgxoCU= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=WK3Ftai6s/9/0GbZ7N/nhleKOHPelPCS9TOJy4MxNgE=; b=nFJexB9xWoVn3q6xjQD7FbJ5ThfDBKb1HuANTx9N9uv43A0uQoVcko1G2EnDQ/ukOz zYw4Xyu3HDZqbuYAxhYxNdZlI76as4YLew/qicycZeuPM5zU++J+ygrDjPg8DA9zP1d0 6WFa+xsUwtGuFXut+YjOCUYoJfP0qohI1xZqnjdCKv0wJRv5rhW+RkdAE63Ww/CSsJgq jPn1IiAJSrn6/kaVjUvzk+5UmjFiWh03Tl/iM2Rr72+sH0dvXMohSByHkHAl0vR+fJ7e UQ31qUwFXusJ87OUwQvX9NSxJvCBM0a+7lvokfnSOAQY4mMuq1p2SgGZw+SPAUqT8i6W fUOA== X-Gm-Message-State: AOAM5324z5QmA4KhxWd1IcdCXyW9PbNhl0YaYA/7d6s3X4xXlUMb0KZE 1EX55XJdDkU+aZ++6JKEjSXc+vTAaj2mjg== X-Google-Smtp-Source: ABdhPJxqA2gN39F+zMx+O2ebcxX7B6ge8hfldlI3LpjXv0nem3G5a1jtUXh31uFipxqZ74F1Ge5kdg== X-Received: by 2002:a63:6a88:0:b0:3c1:4a6c:be3 with SMTP id f130-20020a636a88000000b003c14a6c0be3mr2348441pgc.13.1651068573566; Wed, 27 Apr 2022 07:09:33 -0700 (PDT) Received: from localhost ([2401:fa00:1:17:47e:a9de:8ea1:3876]) by smtp.gmail.com with UTF8SMTPSA id y198-20020a62cecf000000b0050d436ad942sm10635833pfg.16.2022.04.27.07.09.32 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 27 Apr 2022 07:09:33 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Wed, 27 Apr 2022 22:09:27 +0800 Message-Id: <20220427140929.429977-1-hanlinchen@chromium.org> X-Mailer: git-send-email 2.36.0.rc2.479.g8af0fa9b8e-goog MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 1/3] libcamera: Introduce YamlParser as a helper to parse yaml files X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Han-Lin Chen via libcamera-devel From: Hanlin Chen Reply-To: Han-Lin Chen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" 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 Reviewed-by: Laurent Pinchart --- 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 +#include +#include +#include + +#include + +#include + +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::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value> * = nullptr> +#else + template +#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 memberNames() const; + +private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject) + + friend class YamlParserContext; + + enum Type { + Dictionary, + List, + Value, + }; + + Type type_; + + std::string value_; + std::vector> list_; + std::map> dictionary_; +}; + +class YamlParser final +{ +public: + static std::unique_ptr 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 +#include + +#include + +#include + +/** + * \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 YamlObject::get( + * 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(0, &valid); + if (!valid) + return defaultValue; + + uint32_t height = list_[1]->get(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 YamlObject::memberNames() const +{ + std::vector 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; + + EventPtr nextEvent(); + + void readValue(std::string &value, EventPtr event); + int parseDictionaryOrList(YamlObject::Type type, + const std::function &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(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 &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()); + 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 root = YamlParser::parse(fh); + * if (!root) + * return; + * + * if (!root->isDictionary()) + * return; + * + * const YamlObject &name = (*root)["name"]; + * std::cout << name.get("") << 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(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 YamlParser::parse(std::FILE *fh) +{ + YamlParserContext context; + + if (context.init(fh)) + return nullptr; + + std::unique_ptr root(new YamlObject()); + + if (context.parseContent(*root)) { + LOG(YamlParser, Error) << "Failed to parse YAML content"; + return nullptr; + } + + return root; +} + +} /* namespace libcamera */ From patchwork Wed Apr 27 14:09:28 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15731 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 64CE6C3256 for ; Wed, 27 Apr 2022 14:09:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2091A6564B; Wed, 27 Apr 2022 16:09:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1651068581; bh=7ppwUTApfsRk05zIklJeAUfhKKZ6Ti5B1xLNzfCjAec=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=c3ZxWV2uMQ/hG4fVFpkb+5jggqjqD6KDrS0pjmx5yF8GrTRXU5S+2Mbwh4MVXVQ2r KDDg96x6m/srAjBA/oRAtozc41fxEk1qzgK6TrvrOs7/L4g+SMMCxLEhflk6KmHhOb rEEhV3W2UCSovK2qtkfnLvsvdUnBOlQXZQC5v/kjDflxcxJmSjVydLa9x+2RFxssRc ILHQmUQVeWenXCCDBdnesmwl+jtNtjXW3EFdL2WVBtaFrJ6LNvJ4TIUoFRQxa5ZbkI ewk6Lc8UVnVJj7E9/x3h8xGP/1FfM1XsEORU9KHo/eWZH+ZSuJeniehiJP3BRdDtAh QfPhxhPKxJY5Q== Received: from mail-pg1-x529.google.com (mail-pg1-x529.google.com [IPv6:2607:f8b0:4864:20::529]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BD37265647 for ; Wed, 27 Apr 2022 16:09:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="ENLpp7f3"; dkim-atps=neutral Received: by mail-pg1-x529.google.com with SMTP id z21so1561600pgj.1 for ; Wed, 27 Apr 2022 07:09:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=ov2nrY9RN7eTkFBmoTdK9AcqT3YhKHYcAfF+RuBVOXM=; b=ENLpp7f3AQzfClppkThUPPP//nQqLESquL1CYrVAB+B6ZGY3sDv+tQIxte2ZV7QfUA YL/ezj6HwPXvWKBlWG89dZFM3uVsbGFioCunJeDR/StNaV6FfTJAtL4rzOQkLNGiFZ/e o8McHPJR15sBGE+RASMR324WlK6bheOSVgLW4= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=ov2nrY9RN7eTkFBmoTdK9AcqT3YhKHYcAfF+RuBVOXM=; b=TI9I5tpP1m6YnQvqHfKFcvk5war/7ZXuvP1HGCL9YRrjYsS2Ss6NY3HbbrcUvMGxKr wPOdco6X7q/gHxstOKPOcu9aNH9K2lAACPoePAbSlrB2Ss11A/s5bDOq58wBwdtSIObX lyoTd2usXRHdTeYUr+CmH1t39+dN+U4l5HrSDyqL2m0FMIJM5JSPdkICKNaprVvGWLVY jAibTPYfwcWODphkAVu4ei5/Pvtt6NwgQ7uIE+h1MzNn2pZBzffunkkr2xVPxAMA+nX6 cSBETLBibYjVtpMwU9uYZIdIC70WIEdd2koTXlXoBjAkJzzC+8QAYKols7VKchuHW5OO AfhQ== X-Gm-Message-State: AOAM530VFaKM5ef6MlEncDux/AWIuGKQKbFS1Nh9wLKR7Qddd+b38wVN OFohJ0XNO2YULnBPjpFYLGc0kD9uDsfmZA== X-Google-Smtp-Source: ABdhPJyCx1NpBufqg8i2Z7cI/VdfQTA2UvuH/mehSEeR3t157G0R0wPSyI1L0+eQM7geDDXbAPI2Mw== X-Received: by 2002:a63:2b45:0:b0:3ab:971b:be5e with SMTP id r66-20020a632b45000000b003ab971bbe5emr6200914pgr.265.1651068576829; Wed, 27 Apr 2022 07:09:36 -0700 (PDT) Received: from localhost ([2401:fa00:1:17:47e:a9de:8ea1:3876]) by smtp.gmail.com with UTF8SMTPSA id s4-20020a056a00194400b004fb358ffe84sm19372612pfk.104.2022.04.27.07.09.35 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 27 Apr 2022 07:09:36 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Wed, 27 Apr 2022 22:09:28 +0800 Message-Id: <20220427140929.429977-2-hanlinchen@chromium.org> X-Mailer: git-send-email 2.36.0.rc2.479.g8af0fa9b8e-goog In-Reply-To: <20220427140929.429977-1-hanlinchen@chromium.org> References: <20220427140929.429977-1-hanlinchen@chromium.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 2/3] test: Add YamlParser test X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Han-Lin Chen via libcamera-devel From: Hanlin Chen Reply-To: Han-Lin Chen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a unit test to exercise the API of the YamlParser class. Signed-off-by: Han-Lin Chen Reviewed-by: Laurent Pinchart --- test/meson.build | 1 + test/yaml-parser.cpp | 494 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 495 insertions(+) create mode 100644 test/yaml-parser.cpp diff --git a/test/meson.build b/test/meson.build index fd4c5ca0..9c68fae1 100644 --- a/test/meson.build +++ b/test/meson.build @@ -54,6 +54,7 @@ internal_tests = [ ['timer-thread', 'timer-thread.cpp'], ['unique-fd', 'unique-fd.cpp'], ['utils', 'utils.cpp'], + ['yaml-parser', 'yaml-parser.cpp'], ] internal_non_parallel_tests = [ diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp new file mode 100644 index 00000000..e00489db --- /dev/null +++ b/test/yaml-parser.cpp @@ -0,0 +1,494 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2022, Google Inc. + * + * yaml-parser.cpp - YAML parser operations tests + */ + +#include +#include +#include + +#include + +#include "test.h" + +using namespace std; +using namespace libcamera; + +static const string testYaml = + "string: libcamera\n" + "double: 3.14159\n" + "uint32_t: 100\n" + "int32_t: -100\n" + "size: [1920, 1080]\n" + "list:\n" + " - James\n" + " - Mary\n" + "dictionary:\n" + " a: 1\n" + " b: 2\n" + " c: 3\n" + "level1:\n" + " level2:\n" + " - [1, 2]\n" + " - {one: 1, two: 2}\n"; + +static const string invalidYaml = + "Invalid : - YAML : - Content"; + +class YamlParserTest : public Test +{ +protected: + bool createFile(const string &content, string &filename) + { + filename = "/tmp/libcamera.test.XXXXXX"; + int fd = mkstemp(&filename.front()); + if (fd == -1) + return false; + + FILE *fh = fdopen(fd, "w"); + fputs(content.c_str(), fh); + + fclose(fh); + + return true; + } + + int init() + { + if (!createFile(testYaml, testYamlFile_)) + return TestFail; + + if (!createFile(invalidYaml, invalidYamlFile_)) + return TestFail; + + return TestPass; + } + + int run() + { + /* Test invalid YAML file */ + FILE *fh = fopen(invalidYamlFile_.c_str(), "r"); + if (!fh) { + cerr << "Fail to open invalid YAML file" << std::endl; + return TestFail; + } + + std::unique_ptr root = YamlParser::parse(fh); + fclose(fh); + + if (root) { + cerr << "Invalid YAML file parse successfully" << std::endl; + return TestFail; + } + + /* Test YAML file */ + fh = fopen(testYamlFile_.c_str(), "r"); + if (!fh) { + cerr << "Fail to open test YAML file" << std::endl; + return TestFail; + } + + root = YamlParser::parse(fh); + fclose(fh); + + if (!root) { + cerr << "Fail to parse test YAML file: " << std::endl; + return TestFail; + } + + if (!root->isDictionary()) { + cerr << "YAML root is not dictionary" << std::endl; + return TestFail; + } + + if (!root->contains("string")) { + cerr << "Missing string object in YAML root" << std::endl; + return TestFail; + } + + if (!root->contains("double")) { + cerr << "Missing double object in YAML root" << std::endl; + return TestFail; + } + + if (!root->contains("int32_t")) { + cerr << "Missing int32_t object in YAML root" << std::endl; + return TestFail; + } + + if (!root->contains("uint32_t")) { + cerr << "Missing uint32_t object in YAML root" << std::endl; + return TestFail; + } + + if (!root->contains("size")) { + cerr << "Missing Size object in YAML root" << std::endl; + return TestFail; + } + + if (!root->contains("list")) { + cerr << "Missing list object in YAML root" << std::endl; + return TestFail; + } + + if (!root->contains("dictionary")) { + cerr << "Missing dictionary object in YAML root" << std::endl; + return TestFail; + } + + if (!root->contains("level1")) { + cerr << "Missing leveled object in YAML root" << std::endl; + return TestFail; + } + + /* Test string object */ + bool ok; + auto &strObj = (*root)["string"]; + + if (strObj.isDictionary()) { + cerr << "String object parse as Dictionary" << std::endl; + return TestFail; + } + + if (strObj.isList()) { + cerr << "String object parse as List" << std::endl; + return TestFail; + } + + if (strObj.get("", &ok) != "libcamera" || !ok) { + cerr << "String object parse as wrong content" << std::endl; + return TestFail; + } + + if (strObj.get(-1, &ok) != -1 || ok) { + cerr << "String object parse as integer" << std::endl; + return TestFail; + } + + if (strObj.get(1, &ok) != 1 || ok) { + cerr << "String object parse as unsigned integer" << std::endl; + return TestFail; + } + + if (strObj.get(1.0, &ok) != 1.0 || ok) { + cerr << "String object parse as double" << std::endl; + return TestFail; + } + + if (strObj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "String object parse as Size" << std::endl; + return TestFail; + } + + /* Test int32_t object */ + auto &int32Obj = (*root)["int32_t"]; + + if (int32Obj.isDictionary()) { + cerr << "Integer object parse as Dictionary" << std::endl; + return TestFail; + } + + if (int32Obj.isList()) { + cerr << "Integer object parse as Integer" << std::endl; + return TestFail; + } + + if (int32Obj.get(-100, &ok) != -100 || !ok) { + cerr << "Integer object parse as wrong value" << std::endl; + return TestFail; + } + + if (int32Obj.get("", &ok) != "-100" || !ok) { + cerr << "Integer object fail to parse as string" << std::endl; + return TestFail; + } + + if (int32Obj.get(1.0, &ok) != -100.0 || !ok) { + cerr << "Integer object fail to parse as double" << std::endl; + return TestFail; + } + + if (int32Obj.get(1, &ok) != 1 || ok) { + cerr << "Negative integer object parse as unsigned integer" << std::endl; + return TestFail; + } + + if (int32Obj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "Integer object parse as Size" << std::endl; + return TestFail; + } + + /* Test uint32_t object */ + auto &uint32Obj = (*root)["uint32_t"]; + + if (uint32Obj.isDictionary()) { + cerr << "Unsigned integer object parse as Dictionary" << std::endl; + return TestFail; + } + + if (uint32Obj.isList()) { + cerr << "Unsigned integer object parse as List" << std::endl; + return TestFail; + } + + if (uint32Obj.get(-1, &ok) != 100 || !ok) { + cerr << "Unsigned integer object fail to parse as integer" << std::endl; + return TestFail; + } + + if (uint32Obj.get("", &ok) != "100" || !ok) { + cerr << "Unsigned integer object fail to parse as string" << std::endl; + return TestFail; + } + + if (uint32Obj.get(1.0, &ok) != 100.0 || !ok) { + cerr << "Unsigned integer object fail to parse as double" << std::endl; + return TestFail; + } + + if (uint32Obj.get(100, &ok) != 100 || !ok) { + cerr << "Unsigned integer object parsed as wrong value" << std::endl; + return TestFail; + } + + if (uint32Obj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "Unsigned integer object parsed as Size" << std::endl; + return TestFail; + } + + /* Test double value */ + auto &doubleObj = (*root)["double"]; + + if (doubleObj.isDictionary()) { + cerr << "Double object parse as Dictionary" << std::endl; + return TestFail; + } + + if (doubleObj.isList()) { + cerr << "Double object parse as List" << std::endl; + return TestFail; + } + + if (doubleObj.get("", &ok) != "3.14159" || !ok) { + cerr << "Double object fail to parse as string" << std::endl; + return TestFail; + } + + if (doubleObj.get(1.0, &ok) != 3.14159 || !ok) { + cerr << "Double object parse as wrong value" << std::endl; + return TestFail; + } + + if (doubleObj.get(-1, &ok) != -1 || ok) { + cerr << "Double object parse as integer" << std::endl; + return TestFail; + } + + if (doubleObj.get(1, &ok) != 1 || ok) { + cerr << "Double object parse as unsigned integer" << std::endl; + return TestFail; + } + + if (doubleObj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "Double object parse as Size" << std::endl; + return TestFail; + } + + /* Test Size value */ + auto &sizeObj = (*root)["size"]; + + if (sizeObj.isDictionary()) { + cerr << "Size object parse as Dictionary" << std::endl; + return TestFail; + } + + if (!sizeObj.isList()) { + cerr << "Size object parse as List" << std::endl; + return TestFail; + } + + if (sizeObj.get("", &ok) != "" || ok) { + cerr << "Size object parse as string" << std::endl; + return TestFail; + } + + if (sizeObj.get(1.0, &ok) != 1.0 || ok) { + cerr << "Size object parse as double" << std::endl; + return TestFail; + } + + if (sizeObj.get(-1, &ok) != -1 || ok) { + cerr << "Size object parse as integer" << std::endl; + return TestFail; + } + + if (sizeObj.get(1, &ok) != 1 || ok) { + cerr << "Size object parse as unsigned integer" << std::endl; + return TestFail; + } + + if (sizeObj.get(Size(0, 0), &ok) != Size(1920, 1080) || !ok) { + cerr << "Size object parse as wrong value" << std::endl; + return TestFail; + } + + /* Test list object */ + auto &listObj = (*root)["list"]; + + if (listObj.isDictionary()) { + cerr << "List object parse as Dictionary" << std::endl; + return TestFail; + } + + if (!listObj.isList()) { + cerr << "List object fail to parse as List" << std::endl; + return TestFail; + } + + if (listObj.get("", &ok) != "" || ok) { + cerr << "List object parse as string" << std::endl; + return TestFail; + } + + if (listObj.get(1.0, &ok) != 1.0 || ok) { + cerr << "List object parse as double" << std::endl; + return TestFail; + } + + if (listObj.get(-1, &ok) != -1 || ok) { + cerr << "List object parse as integer" << std::endl; + return TestFail; + } + + if (listObj.get(1, &ok) != 1 || ok) { + cerr << "List object parse as unsigne integer" << std::endl; + return TestFail; + } + + if (listObj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "String list object parse as Size" << std::endl; + return TestFail; + } + + if (listObj.size() > 2) { + cerr << "List object parse with wrong size" << std::endl; + return TestFail; + } + + if (listObj[0].get("") != "James" || + listObj[1].get("") != "Mary") { + cerr << "List object parse as wrong value" << std::endl; + return TestFail; + } + + /* Test dictionary object */ + auto &dictObj = (*root)["dictionary"]; + + if (!dictObj.isDictionary()) { + cerr << "Dictionary object fail to parse as Dictionary" << std::endl; + return TestFail; + } + + if (dictObj.isList()) { + cerr << "Dictionary object parse as List" << std::endl; + return TestFail; + } + + if (dictObj.get("", &ok) != "" || ok) { + cerr << "Dictionary object parse as string" << std::endl; + return TestFail; + } + + if (dictObj.get(1.0, &ok) != 1.0 || ok) { + cerr << "Dictionary object parse as double" << std::endl; + return TestFail; + } + + if (dictObj.get(-1, &ok) != -1 || ok) { + cerr << "Dictionary object parse as integer" << std::endl; + return TestFail; + } + + if (dictObj.get(1, &ok) != 1 || ok) { + cerr << "Dictionary object parse as unsigned integer" << std::endl; + return TestFail; + } + + if (dictObj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "Dictionary object parse as Size" << std::endl; + return TestFail; + } + + auto memeberNames = dictObj.memberNames(); + sort(memeberNames.begin(), memeberNames.end()); + + if (memeberNames.size() != 3) { + cerr << "Dictionary object fail to extra member names" << std::endl; + return TestFail; + } + + if (memeberNames[0] != "a" || + memeberNames[1] != "b" || + memeberNames[2] != "c") { + cerr << "Dictionary object fail to parse member names" << std::endl; + return TestFail; + } + + if (dictObj["a"].get(0) != 1 || + dictObj["b"].get(0) != 2 || + dictObj["c"].get(0) != 3) { + cerr << "Dictionary object fail to parse member value" << std::endl; + return TestFail; + } + + /* Test leveled objects */ + auto &level1Obj = (*root)["level1"]; + + if (!level1Obj.isDictionary()) { + cerr << "level1 object fail to parse as Dictionary" << std::endl; + return TestFail; + } + + auto &level2Obj = level1Obj["level2"]; + + if (!level2Obj.isList() || level2Obj.size() != 2) { + cerr << "level2 object should be 2 element list" << std::endl; + return TestFail; + } + + auto &firstElement = level2Obj[0]; + if (!firstElement.isList() || + firstElement.size() != 2 || + firstElement[0].get(0) != 1 || + firstElement[1].get(0) != 2) { + cerr << "The first element of level2 object fail to parse as integer list" << std::endl; + return TestFail; + } + + auto &secondElement = level2Obj[1]; + if (!secondElement.isDictionary() || + !secondElement.contains("one") || + !secondElement.contains("two") || + secondElement["one"].get(0) != 1 || + secondElement["two"].get(0) != 2) { + cerr << "The second element of level2 object fail to parse as dictionary" << std::endl; + return TestFail; + } + + return TestPass; + } + + void cleanup() + { + unlink(testYamlFile_.c_str()); + unlink(invalidYamlFile_.c_str()); + } + +private: + std::string testYamlFile_; + std::string invalidYamlFile_; +}; + +TEST_REGISTER(YamlParserTest) From patchwork Wed Apr 27 14:09:29 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15732 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id CB84DC3256 for ; Wed, 27 Apr 2022 14:09:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 885116564D; Wed, 27 Apr 2022 16:09:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1651068582; bh=RZ1lBlP+JOOdeMYHbep+7QDCbaUYxlRiYT3H0tOtrd4=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=FbuibpZbpt2nQA6jSIshosi2/Dv0mR/By++dVH9OsDmakV6LIOKoUTlR38mpXlcv0 eEveIjAUK141qev1QhBI+7H/6c3lo0z0QdQh/UvL6jVuuSVksMq/rbEjnuwSJmQzAU PaMWacR6k/NIGJYB6/zDUiRnnRZDbLv/UGVIM0yHIu07A21B/V2gkKAmGDMuuIwdvh fc9p7fRgbn0qOyusD0dgFHEsLG+bt/OBCnU9RuTr0FVmr18nXu8Va//hA/9ZzNnpRz Vhw8jnnYnAAiuzf3FFOwp4RRu0eXnL2gdqvO4ZM3pnVOI6ZTrYPwAaXiDHUokKm9qJ Cfm9JtAzuVkGA== Received: from mail-pg1-x530.google.com (mail-pg1-x530.google.com [IPv6:2607:f8b0:4864:20::530]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 71DD06563F for ; Wed, 27 Apr 2022 16:09:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="aMkHxd+W"; dkim-atps=neutral Received: by mail-pg1-x530.google.com with SMTP id 15so1556824pgf.4 for ; Wed, 27 Apr 2022 07:09:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=6wABVHzsSBKOt9B7CPwWHZ9d2BU1wdfp0ug045CdiHw=; b=aMkHxd+WDxGtRvJtZR6Gqx0HN/RfXQhcNU1ivgCzdbyXt1RmoBZNVfIje+rgIPr5i2 PyhsiUNSiZEwElRWccICm8WyyJVZ4wgcDv+0DMrITn3n9riVLu6wIwMJ//srfLSnID3m BDOM57pqYC4wx7lbqWo5Hyz2B61dTosPrnyng= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=6wABVHzsSBKOt9B7CPwWHZ9d2BU1wdfp0ug045CdiHw=; b=6CHlEFFjhd9RvBbARvoR/vEsIN2bBAktbC8A9uo/jMU/zdf+lD/GkZ0M7jxCUAiCsb vfrugqvzg3jPJeN3x8uIirsYLIJBPkiTvzmcLBfCgw6iEr6hS2PyKVCybAw3xNRs+UiJ k6ZibISmxsc+vLtNstKOSGva8RMf+YgyrZ9QOIOXOklUFcOpPcUeuYcxdIG9BApzYxzG FE2qwtU6ewRQ+C9hqz3M+q80dXfUahWHX20f8V4kvaguBpGfrP4SpRsXfxpHJnUebVpD 4gkSRXvpzcnwStbvLjj0WwkPhJeOJXIDIMtN+QkXFML1SGyq5IjZ0R8HduIz+Z41FnZY jaQA== X-Gm-Message-State: AOAM531tI3RuhlFFUjyLex35I/zsXYd187OEJDgGD0BzNZe16OxO6IHB f4iy9APS/RziGUWO+FbdvXH66avViIhHqQ== X-Google-Smtp-Source: ABdhPJxeneVg0TCGhRuUmejyE6smQjzmAIqwz+ULuUtjkTt+G2OfDgjpTt49pGl/QqWSk6EEm5XpQQ== X-Received: by 2002:aa7:8215:0:b0:4f7:125a:c88c with SMTP id k21-20020aa78215000000b004f7125ac88cmr30497733pfi.70.1651068579679; Wed, 27 Apr 2022 07:09:39 -0700 (PDT) Received: from localhost ([2401:fa00:1:17:47e:a9de:8ea1:3876]) by smtp.gmail.com with UTF8SMTPSA id q93-20020a17090a4fe600b001d7e76f0470sm2778914pjh.0.2022.04.27.07.09.38 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 27 Apr 2022 07:09:39 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Wed, 27 Apr 2022 22:09:29 +0800 Message-Id: <20220427140929.429977-3-hanlinchen@chromium.org> X-Mailer: git-send-email 2.36.0.rc2.479.g8af0fa9b8e-goog In-Reply-To: <20220427140929.429977-1-hanlinchen@chromium.org> References: <20220427140929.429977-1-hanlinchen@chromium.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 3/3] android: camera_hal_config: Use YamlParser to parse android HAL config X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Han-Lin Chen via libcamera-devel From: Hanlin Chen Reply-To: Han-Lin Chen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Use YamlParser to parse android HAL config files, instead of handling YAML tokens directly, as a preparation for the further parameter extension. Signed-off-by: Han-Lin Chen Reviewed-by: Laurent Pinchart --- src/android/camera_hal_config.cpp | 334 +++++++----------------------- src/android/meson.build | 1 - 2 files changed, 74 insertions(+), 261 deletions(-) diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp index aa90dac7..29e506ff 100644 --- a/src/android/camera_hal_config.cpp +++ b/src/android/camera_hal_config.cpp @@ -14,15 +14,15 @@ namespace filesystem = std::experimental::filesystem; #else #include #endif -#include #include #include -#include #include #include +#include + using namespace libcamera; LOG_DEFINE_CATEGORY(HALConfig) @@ -37,16 +37,10 @@ public: int parseConfigFile(FILE *fh, std::map *cameras); private: - std::string parseValue(); - std::string parseKey(); - int parseValueBlock(); - int parseCameraLocation(CameraConfigData *cameraConfigData, - const std::string &location); - int parseCameraConfigData(const std::string &cameraId); - int parseCameras(); - int parseEntry(); - - yaml_parser_t parser_; + int parseCameraConfigData(const std::string &cameraId, const YamlObject &); + int parseLocation(const YamlObject &, CameraConfigData &cameraConfigData); + int parseRotation(const YamlObject &, CameraConfigData &cameraConfigData); + std::map *cameras_; }; @@ -54,290 +48,110 @@ CameraHalConfig::Private::Private() { } -std::string CameraHalConfig::Private::parseValue() +int CameraHalConfig::Private::parseConfigFile(FILE *fh, + std::map *cameras) { - yaml_token_t token; - - /* Make sure the token type is a value and get its content. */ - yaml_parser_scan(&parser_, &token); - if (token.type != YAML_VALUE_TOKEN) { - yaml_token_delete(&token); - return ""; - } - yaml_token_delete(&token); - - yaml_parser_scan(&parser_, &token); - if (token.type != YAML_SCALAR_TOKEN) { - yaml_token_delete(&token); - return ""; - } - - std::string value(reinterpret_cast(token.data.scalar.value), - token.data.scalar.length); - yaml_token_delete(&token); - - return value; -} + /* + * Parse the HAL properties. + * + * Each camera properties block is a list of properties associated + * with the ID (as assembled by CameraSensor::generateId()) of the + * camera they refer to. + * + * cameras: + * "camera0 id": + * location: value + * rotation: value + * ... + * + * "camera1 id": + * location: value + * rotation: value + * ... + */ -std::string CameraHalConfig::Private::parseKey() -{ - yaml_token_t token; + cameras_ = cameras; - /* Make sure the token type is a key and get its value. */ - yaml_parser_scan(&parser_, &token); - if (token.type != YAML_SCALAR_TOKEN) { - yaml_token_delete(&token); - return ""; - } + std::unique_ptr root = YamlParser::parse(fh); + if (!root) + return -EINVAL; - std::string value(reinterpret_cast(token.data.scalar.value), - token.data.scalar.length); - yaml_token_delete(&token); + if (!root->isDictionary()) + return -EINVAL; - return value; -} + /* Parse property "cameras" */ + if (!root->contains("cameras")) + return -EINVAL; -int CameraHalConfig::Private::parseValueBlock() -{ - yaml_token_t token; + const YamlObject &yamlObjectCameras = (*root)["cameras"]; - /* Make sure the next token are VALUE and BLOCK_MAPPING_START. */ - yaml_parser_scan(&parser_, &token); - if (token.type != YAML_VALUE_TOKEN) { - yaml_token_delete(&token); + if (!yamlObjectCameras.isDictionary()) return -EINVAL; - } - yaml_token_delete(&token); - yaml_parser_scan(&parser_, &token); - if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) { - yaml_token_delete(&token); - return -EINVAL; + std::vector cameraIds = yamlObjectCameras.memberNames(); + for (const std::string &cameraId : cameraIds) { + if (parseCameraConfigData(cameraId, + yamlObjectCameras[cameraId])) + return -EINVAL; } - yaml_token_delete(&token); return 0; } -int CameraHalConfig::Private::parseCameraLocation(CameraConfigData *cameraConfigData, - const std::string &location) -{ - if (location == "front") - cameraConfigData->facing = CAMERA_FACING_FRONT; - else if (location == "back") - cameraConfigData->facing = CAMERA_FACING_BACK; - else - return -EINVAL; +int CameraHalConfig::Private::parseCameraConfigData(const std::string &cameraId, + const YamlObject &cameraObject) - return 0; -} - -int CameraHalConfig::Private::parseCameraConfigData(const std::string &cameraId) { - int ret = parseValueBlock(); - if (ret) - return ret; - - /* - * Parse the camera properties and store them in a cameraConfigData - * instance. - * - * Add a safety counter to make sure we don't loop indefinitely in case - * the configuration file is malformed. - */ - CameraConfigData cameraConfigData; - unsigned int sentinel = 100; - bool blockEnd = false; - yaml_token_t token; - - do { - yaml_parser_scan(&parser_, &token); - switch (token.type) { - case YAML_KEY_TOKEN: { - yaml_token_delete(&token); - - /* - * Parse the camera property key and make sure it is - * valid. - */ - std::string key = parseKey(); - std::string value = parseValue(); - if (key.empty() || value.empty()) - return -EINVAL; - - if (key == "location") { - ret = parseCameraLocation(&cameraConfigData, value); - if (ret) { - LOG(HALConfig, Error) - << "Unknown location: " << value; - return -EINVAL; - } - } else if (key == "rotation") { - ret = std::stoi(value); - if (ret < 0 || ret >= 360) { - LOG(HALConfig, Error) - << "Unknown rotation: " << value; - return -EINVAL; - } - cameraConfigData.rotation = ret; - } else { - LOG(HALConfig, Error) - << "Unknown key: " << key; - return -EINVAL; - } - break; - } - - case YAML_BLOCK_END_TOKEN: - blockEnd = true; - [[fallthrough]]; - default: - yaml_token_delete(&token); - break; - } - - --sentinel; - } while (!blockEnd && sentinel); - if (!sentinel) + if (!cameraObject.isDictionary()) return -EINVAL; - (*cameras_)[cameraId] = cameraConfigData; - - return 0; -} + CameraConfigData &cameraConfigData = (*cameras_)[cameraId]; -int CameraHalConfig::Private::parseCameras() -{ - int ret = parseValueBlock(); - if (ret) { - LOG(HALConfig, Error) << "Configuration file is not valid"; - return ret; - } + /* Parse property "location" */ + if (parseLocation(cameraObject, cameraConfigData)) + return -EINVAL; - /* - * Parse the camera properties. - * - * Each camera properties block is a list of properties associated - * with the ID (as assembled by CameraSensor::generateId()) of the - * camera they refer to. - * - * cameras: - * "camera0 id": - * key: value - * key: value - * ... - * - * "camera1 id": - * key: value - * key: value - * ... - */ - bool blockEnd = false; - yaml_token_t token; - do { - yaml_parser_scan(&parser_, &token); - switch (token.type) { - case YAML_KEY_TOKEN: { - yaml_token_delete(&token); - - /* Parse the camera ID as key of the property list. */ - std::string cameraId = parseKey(); - if (cameraId.empty()) - return -EINVAL; - - ret = parseCameraConfigData(cameraId); - if (ret) - return -EINVAL; - break; - } - case YAML_BLOCK_END_TOKEN: - blockEnd = true; - [[fallthrough]]; - default: - yaml_token_delete(&token); - break; - } - } while (!blockEnd); + /* Parse property "rotation" */ + if (parseRotation(cameraObject, cameraConfigData)) + return -EINVAL; return 0; } -int CameraHalConfig::Private::parseEntry() +int CameraHalConfig::Private::parseLocation(const YamlObject &cameraObject, + CameraConfigData &cameraConfigData) { - int ret = -EINVAL; + if (!cameraObject.contains("location")) + return -EINVAL; - /* - * Parse each key we find in the file. - * - * The 'cameras' keys maps to a list of (lists) of camera properties. - */ + std::string location = cameraObject["location"].get(""); - std::string key = parseKey(); - if (key.empty()) - return ret; - - if (key == "cameras") - ret = parseCameras(); + if (location == "front") + cameraConfigData.facing = CAMERA_FACING_FRONT; + else if (location == "back") + cameraConfigData.facing = CAMERA_FACING_BACK; else - LOG(HALConfig, Error) << "Unknown key: " << key; + return -EINVAL; - return ret; + return 0; } -int CameraHalConfig::Private::parseConfigFile(FILE *fh, - std::map *cameras) +int CameraHalConfig::Private::parseRotation(const YamlObject &cameraObject, + CameraConfigData &cameraConfigData) { - cameras_ = cameras; - - int ret = yaml_parser_initialize(&parser_); - if (!ret) { - LOG(HALConfig, Error) << "Failed to initialize yaml parser"; - return -EINVAL; - } - yaml_parser_set_input_file(&parser_, fh); - - yaml_token_t token; - yaml_parser_scan(&parser_, &token); - if (token.type != YAML_STREAM_START_TOKEN) { - LOG(HALConfig, Error) << "Configuration file is not valid"; - yaml_token_delete(&token); - yaml_parser_delete(&parser_); + if (!cameraObject.contains("rotation")) return -EINVAL; - } - yaml_token_delete(&token); - yaml_parser_scan(&parser_, &token); - if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) { - LOG(HALConfig, Error) << "Configuration file is not valid"; - yaml_token_delete(&token); - yaml_parser_delete(&parser_); + int32_t rotation = cameraObject["rotation"].get(-1); + + if (rotation < 0 || rotation >= 360) { + LOG(HALConfig, Error) + << "Unknown rotation: " << rotation; return -EINVAL; } - yaml_token_delete(&token); - - /* Parse the file and parse each single key one by one. */ - do { - yaml_parser_scan(&parser_, &token); - switch (token.type) { - case YAML_KEY_TOKEN: - yaml_token_delete(&token); - ret = parseEntry(); - break; - - case YAML_STREAM_END_TOKEN: - ret = -ENOENT; - [[fallthrough]]; - default: - yaml_token_delete(&token); - break; - } - } while (ret >= 0); - yaml_parser_delete(&parser_); - - if (ret && ret != -ENOENT) - LOG(HALConfig, Error) << "Configuration file is not valid"; - - return ret == -ENOENT ? 0 : ret; + + cameraConfigData.rotation = rotation; + return 0; } CameraHalConfig::CameraHalConfig() diff --git a/src/android/meson.build b/src/android/meson.build index 75b4bf20..1bba54de 100644 --- a/src/android/meson.build +++ b/src/android/meson.build @@ -3,7 +3,6 @@ android_deps = [ dependency('libexif', required : get_option('android')), dependency('libjpeg', required : get_option('android')), - dependency('yaml-0.1', required : get_option('android')), libcamera_private, ]