From patchwork Mon Apr 25 14:46:15 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15705 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 7872BC3256 for ; Mon, 25 Apr 2022 14:46:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 297DD6564B; Mon, 25 Apr 2022 16:46:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1650897989; bh=KtbKn3u8HQLKJMWT5quiykYi3GhI4pmNNXQ/H8Pv5Sc=; 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=mXrOHkAy4TPxcgSL02++HEf8XN0OzQQKppubhf4svQPc8grGxj/FewGR7zrCTk1t7 BYgRucqvrmQY5ULyiWl/6uyH7gvERbJ/JYxZVvVZ6LaUGNP4JllP4guSu/7wJjrTr4 dDjBsntP9lax6+MoEwbXZhj16wTxdkgbb7VvmFibW78Yu3piX5qb8G0hR4JwF/01sn oN+n+5Np+ZL9GPoGf5UjorX0qzr9hHMMclWpOrVGRF7z//cTNekVr9T7IjADgcB/Yd EawgOhA3006LSe/YGDmdkaQqPfGehyj0BHFkf3IAnVj5qASUwoYP+UuuTgUHhsvTH+ 3JqUpiJzESffg== Received: from mail-pj1-x1032.google.com (mail-pj1-x1032.google.com [IPv6:2607:f8b0:4864:20::1032]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7A6876042F for ; Mon, 25 Apr 2022 16:46:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="lgiHvO23"; dkim-atps=neutral Received: by mail-pj1-x1032.google.com with SMTP id p6so125612pjm.1 for ; Mon, 25 Apr 2022 07:46:26 -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=LLtWqTGmztrFnijO8aZky7tJrg38aT3QTOa7+y710rs=; b=lgiHvO23OQv/xmlk4e/fcldjnvxnqU+YCn5nz5EUSEFA1xphOBGnomsmkfoQt2DdBx hB177U3i9l6lCRah3SvFLHcByMem5tddoKKsW1u4s/YAZJ5y/cm6z8ysEu3hj95X9eYX gTxpoofq7RyoixvXhRo6dn9ou4mT/qFDEL2ds= 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=LLtWqTGmztrFnijO8aZky7tJrg38aT3QTOa7+y710rs=; b=W+jO0ZBF/2NS9/LZO8u4+0+v4sGBKIfB5/5vSZb9lTgFoZZp2bFnkq0khK1NE1Ti0Y NyPDS+YTJMtYHllYevmbfRBPheD5k7ugtbtRbjCaezMZr4EBWE6uowO3hCBEVwlJJYhR 3ZPwYq2Vg+5VSrZ7vM96EAtBLysDY57DAySXXpm6sVZmnM67puagvz91KX8n8pb+0e+a kfA0qHCxMmRvkn0PQohYFwBSMoN6kkTv/OGFtZW8KLHkho+EjhyBuLGlFFbczKiOu/L4 Tz6x/8GUCyz6R+Ja8QZj7Iqltsly41i7B+vAPISvU5LiLMiNJFurKo2LyVehuAXoBDBb g+4w== X-Gm-Message-State: AOAM530KFvrULRfH4iOKvpqJ3Tv5oM74HSkuClgvk2whCVuTYj1nPzvu 3w2ySH9wjSEGBf22X5Q8EMN6hafn2pf1Dw== X-Google-Smtp-Source: ABdhPJxh4YXJDLr6tzLezHbYiciaiIuW3yqhCqkKjOBRlTvhnRm9jiFV0KmbXLlJatiooxesWsJiAw== X-Received: by 2002:a17:903:22d2:b0:15a:805d:3b16 with SMTP id y18-20020a17090322d200b0015a805d3b16mr18287708plg.173.1650897984429; Mon, 25 Apr 2022 07:46:24 -0700 (PDT) Received: from localhost ([2401:fa00:1:17:38a2:b0a:67fa:9b05]) by smtp.gmail.com with UTF8SMTPSA id f17-20020a17090a4a9100b001cd4989fed3sm15721468pjh.31.2022.04.25.07.46.23 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 25 Apr 2022 07:46:24 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Apr 2022 22:46:15 +0800 Message-Id: <20220425144617.2549778-2-hanlinchen@chromium.org> X-Mailer: git-send-email 2.36.0.rc2.479.g8af0fa9b8e-goog In-Reply-To: <20220425144617.2549778-1-hanlinchen@chromium.org> References: <20220425144617.2549778-1-hanlinchen@chromium.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 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 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 --- 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..99f9eb17 --- /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 getMemberNames() 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..43b83b8f --- /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 + +/** + * \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) +{ +} + +/** + * \class YamlObject + * \brief Destructor of YamlObject + */ +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; + + /* + * strtoul accept a leading minus sign, and the calculated digits + * is negated as if by unary minus. Rule out the case. + */ + 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 of 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::getMemberNames() + * \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::getMemberNames() 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 getEvent(); + + void readValue(std::string &value, EventPtr event); + int parseDictionaryOrList(bool isDictionary, + 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::getEvent() + * \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 EventPtr on success or nullptr otherwise + */ +YamlParserContext::EventPtr YamlParserContext::getEvent() +{ + static yaml_event_t event; + + /* yaml_parser_parse returns when it succeeds */ + if (1 != yaml_parser_parse(&parser_, &event)) { + return nullptr; + } + + return EventPtr(new yaml_event_t(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 is failed to validate end of a YAML file + */ +int YamlParserContext::parseContent(YamlObject &yamlObject) +{ + /* Check start of the YAML file */ + EventPtr event = getEvent(); + if (!event || event->type != YAML_STREAM_START_EVENT) + return -EINVAL; + + event = getEvent(); + if (!event || event->type != YAML_DOCUMENT_START_EVENT) + return -EINVAL; + + /* Parse the root object */ + event = getEvent(); + if (parseNextYamlObject(yamlObject, std::move(event))) + return -EINVAL; + + /* Check end of the YAML file */ + event = getEvent(); + if (!event || event->type != YAML_DOCUMENT_END_EVENT) + return -EINVAL; + + event = getEvent(); + 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 need 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 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 a item from a dictionary or a list. + * The differences of them in a YAML event stream are: + * + * 1. The start and end event type 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(bool isDictionary, + const std::function &parseItem) +{ + yaml_event_type_t endEventType = YAML_SEQUENCE_END_EVENT; + if (isDictionary) + endEventType = YAML_MAPPING_END_EVENT; + + /* + * Add a safety counter to make sure we don't loop indefinitely in case + * the configuration file is malformed. + */ + unsigned int sentinel = 1000; + do { + auto evt = getEvent(); + if (!evt) + return -EINVAL; + + if (evt->type == endEventType) + return 0; + + int ret = parseItem(std::move(evt)); + if (ret) + return ret; + + --sentinel; + } while (sentinel); + + if (!sentinel) + return -EINVAL; + + return 0; +} + +/** + * \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(false, 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 = getEvent(); + if (!evtValue) + return -EINVAL; + + dictionary[key].reset(new YamlObject()); + return parseNextYamlObject(*dictionary[key], std::move(evtValue)); + }; + return parseDictionaryOrList(true, handler); + } + default: + LOG(YamlParser, Error) << "Invalid YAML file"; + } + + return -EINVAL; +} + +#endif /* __DOXYGEN__ */ + +/** + * \class YamlParser + * \brief A helper class for parsing YAML file + * + * The YamlParser class provides an easy interface to parse the contents of a + * YAML file int 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(""); + * + * 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); + * + * \endcode + * + * The YamlParser::parse() function takes 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 open FILE, parses its contents, and + * returns a pointer to a YamlObject corresponding to the root node of the YAML + * document. + * + * \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)) + return nullptr; + + return root; +} + +} /* namespace libcamera */ From patchwork Mon Apr 25 14:46:16 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15706 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 6ED2FC3260 for ; Mon, 25 Apr 2022 14:46:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A2E6E65648; Mon, 25 Apr 2022 16:46:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1650897989; bh=y+6V4O/0su9kVaHW6PFoGQdl7/IFfzHY4YnAqPaov8A=; 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=worlXHsRcsFWwuNHaUhgmEeWjp1lHGe6C77is67eHEgszfl2HzvQRmBzh61fsRMiu K4EMr6boJ3sFzaQfeBLSI5pBekHo337Z9ziS8KLgGXsl6tK6Z00eHYHC5pyek+7iBF /lYBSCEWSNaWrgqPRTDxIEnDmOfH3E4pUalHiilx7YC02Fy33JDDz1s26rwssDXfpp bwjMK6kLetDn3SA1B+0XpE+fEyBl4rftm0Nlg+XWyfM57WiVVdsmfX1mwvFC2rUQ5/ 5cpqZ4DuOC5ifKhiAkOQS4QseOOQnFPHIYXFJEHYM18kGzQDNSFqvTAlTb6pOO8Hca 56LUpXyM/nG2Q== Received: from mail-pg1-x534.google.com (mail-pg1-x534.google.com [IPv6:2607:f8b0:4864:20::534]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2C750604A9 for ; Mon, 25 Apr 2022 16:46:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="OYcoorok"; dkim-atps=neutral Received: by mail-pg1-x534.google.com with SMTP id z21so5961818pgj.1 for ; Mon, 25 Apr 2022 07:46:28 -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=doSB8OplibwubXrB7WWu5izCTnk9bKZaDlqwCHPNx8c=; b=OYcoorokiZtEnihLpl8u5++xF2SxvpvNjqAt2t503/9D0wo1Ls0mwdpxoYCgwAOPVg mkBJX2KPqR3s+ETvJV+YHForAX5LA/2tuwPAw5w/osaQl78vcRzFiKn13iu/cREu3z2q TPJm2+i1uJ17IPtNObAcvruqLKqaMNeFPmk5g= 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=doSB8OplibwubXrB7WWu5izCTnk9bKZaDlqwCHPNx8c=; b=kkg8QvbETWj6JMyeaeTaTN4lyaIXOyipkfWKn+XHEIfIXwO2R9Yfo6qjbm4OWxpS2g UPYd3ERpaWBFl10FMPNUqmn4B0OolaCF7NlXC4QFhM2mzKIV0/qxOfAyu26aSotsNpUl lvyA8Np3ISxdM4ztb8ukd1RQaCjxthdOl8FbGgPJVVOVelRaSMu5RfU7oEu6w7SMx1xH 8XzevUA5yoTD3hvB2tVlZt53GcVyzHm+un3EV6jws+Oyt45ijUYhzbsEA4NH6KBJiIY7 0KrV9WXFEJFlcg/RbRE6mcib/VUSWOj0JOtCuS2s8mFgBAKM58tgK5rOw7nokPS0j9JQ XUmw== X-Gm-Message-State: AOAM531ve9ZQpjxcEJhn/6hoZ1iVq6RohIQpsNeHb39+sB6oKGWPR8Er H61oCNnK5vnhELJD/oPNO+nR4IVzFq0LlQ== X-Google-Smtp-Source: ABdhPJzmEFHP3te/m7uCP4gOuqeWpVpQW/k8ylSR06FyPtpb4K5NWJ/ja6Vxr4DVD2i189b3O70nYA== X-Received: by 2002:a05:6a00:24cb:b0:50a:8151:9abc with SMTP id d11-20020a056a0024cb00b0050a81519abcmr19128222pfv.57.1650897986479; Mon, 25 Apr 2022 07:46:26 -0700 (PDT) Received: from localhost ([2401:fa00:1:17:38a2:b0a:67fa:9b05]) by smtp.gmail.com with UTF8SMTPSA id 189-20020a6217c6000000b0050aca6473e0sm11851025pfx.192.2022.04.25.07.46.25 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 25 Apr 2022 07:46:26 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Apr 2022 22:46:16 +0800 Message-Id: <20220425144617.2549778-3-hanlinchen@chromium.org> X-Mailer: git-send-email 2.36.0.rc2.479.g8af0fa9b8e-goog In-Reply-To: <20220425144617.2549778-1-hanlinchen@chromium.org> References: <20220425144617.2549778-1-hanlinchen@chromium.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 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 | 496 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 497 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..b9122287 --- /dev/null +++ b/test/yaml-parser.cpp @@ -0,0 +1,496 @@ +/* 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"; + return TestFail; + } + + std::unique_ptr root = YamlParser::parse(fh); + + if (root) { + cerr << "Invalid YAML file parse successfully"; + return TestFail; + } + + fclose(fh); + + /* Test YAML file */ + fh = fopen(testYamlFile_.c_str(), "r"); + if (!fh) { + cerr << "Fail to open test YAML file"; + return TestFail; + } + + root = YamlParser::parse(fh); + + if (!root) { + cerr << "Fail to parse test YAML file: "; + return TestFail; + } + + fclose(fh); + + if (!(*root).isDictionary()) { + cerr << "YAML root is not dictionary"; + return TestFail; + } + + if (!(*root).contains("string")) { + cerr << "Missing string object in YAML root"; + return TestFail; + } + + if (!(*root).contains("double")) { + cerr << "Missing double object in YAML root"; + return TestFail; + } + + if (!(*root).contains("int32_t")) { + cerr << "Missing int32_t object in YAML root"; + return TestFail; + } + + if (!(*root).contains("uint32_t")) { + cerr << "Missing uint32_t object in YAML root"; + return TestFail; + } + + if (!(*root).contains("size")) { + cerr << "Missing Size object in YAML root"; + return TestFail; + } + + if (!(*root).contains("list")) { + cerr << "Missing list object in YAML root"; + return TestFail; + } + + if (!(*root).contains("dictionary")) { + cerr << "Missing dictionary object in YAML root"; + return TestFail; + } + + if (!(*root).contains("level1")) { + cerr << "Missing leveled object in YAML root"; + return TestFail; + } + + /* Test string object */ + bool ok; + auto &strObj = (*root)["string"]; + + if (strObj.isDictionary()) { + cerr << "String object parse as Dictionary"; + return TestFail; + } + + if (strObj.isList()) { + cerr << "String object parse as List"; + return TestFail; + } + + if (strObj.get("", &ok) != "libcamera" || !ok) { + cerr << "String object parse as wrong content"; + return TestFail; + } + + if (strObj.get(-1, &ok) != -1 || ok) { + cerr << "String object parse as integer"; + return TestFail; + } + + if (strObj.get(1, &ok) != 1 || ok) { + cerr << "String object parse as unsigned integer"; + return TestFail; + } + + if (strObj.get(1.0, &ok) != 1.0 || ok) { + cerr << "String object parse as double"; + return TestFail; + } + + if (strObj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "String object parse as Size"; + return TestFail; + } + + /* Test int32_t object */ + auto &int32Obj = (*root)["int32_t"]; + + if (int32Obj.isDictionary()) { + cerr << "Integer object parse as Dictionary"; + return TestFail; + } + + if (int32Obj.isList()) { + cerr << "Integer object parse as Integer"; + return TestFail; + } + + if (int32Obj.get(-100, &ok) != -100 || !ok) { + cerr << "Integer object parse as wrong value"; + return TestFail; + } + + if (int32Obj.get("", &ok) != "-100" || !ok) { + cerr << "Integer object fail to parse as string"; + return TestFail; + } + + if (int32Obj.get(1.0, &ok) != -100.0 || !ok) { + cerr << "Integer object fail to parse as double"; + return TestFail; + } + + if (int32Obj.get(1, &ok) != 1 || ok) { + cerr << "Negative integer object parse as unsigned integer"; + return TestFail; + } + + if (int32Obj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "Integer object parse as Size"; + return TestFail; + } + + /* Test uint32_t object */ + auto &uint32Obj = (*root)["uint32_t"]; + + if (uint32Obj.isDictionary()) { + cerr << "Unsigned integer object parse as Dictionary"; + return TestFail; + } + + if (uint32Obj.isList()) { + cerr << "Unsigned integer object parse as List"; + return TestFail; + } + + if (uint32Obj.get(-1, &ok) != 100 || !ok) { + cerr << "Unsigned integer object fail to parse as integer"; + return TestFail; + } + + if (uint32Obj.get("", &ok) != "100" || !ok) { + cerr << "Unsigned integer object fail to parse as string"; + return TestFail; + } + + if (uint32Obj.get(1.0, &ok) != 100.0 || !ok) { + cerr << "Unsigned integer object fail to parse as double"; + return TestFail; + } + + if (uint32Obj.get(100, &ok) != 100 || !ok) { + cerr << "Unsigned integer object parsed as wrong value"; + return TestFail; + } + + if (uint32Obj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "Unsigned integer object parsed as Size"; + return TestFail; + } + + /* Test double value */ + auto &doubleObj = (*root)["double"]; + + if (doubleObj.isDictionary()) { + cerr << "Double object parse as Dictionary"; + return TestFail; + } + + if (doubleObj.isList()) { + cerr << "Double object parse as List"; + return TestFail; + } + + if (doubleObj.get("", &ok) != "3.14159" || !ok) { + cerr << "Double object fail to parse as string"; + return TestFail; + } + + if (doubleObj.get(1.0, &ok) != 3.14159 || !ok) { + cerr << "Double object parse as wrong value"; + return TestFail; + } + + if (doubleObj.get(-1, &ok) != -1 || ok) { + cerr << "Double object parse as integer"; + return TestFail; + } + + if (doubleObj.get(1, &ok) != 1 || ok) { + cerr << "Double object parse as unsigned integer"; + return TestFail; + } + + if (doubleObj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "Double object parse as Size"; + return TestFail; + } + + /* Test Size value */ + auto &sizeObj = (*root)["size"]; + + if (sizeObj.isDictionary()) { + cerr << "Size object parse as Dictionary"; + return TestFail; + } + + if (!sizeObj.isList()) { + cerr << "Size object parse as List"; + return TestFail; + } + + if (sizeObj.get("", &ok) != "" || ok) { + cerr << "Size object parse as string"; + return TestFail; + } + + if (sizeObj.get(1.0, &ok) != 1.0 || ok) { + cerr << "Size object parse as double"; + return TestFail; + } + + if (sizeObj.get(-1, &ok) != -1 || ok) { + cerr << "Size object parse as integer"; + return TestFail; + } + + if (sizeObj.get(1, &ok) != 1 || ok) { + cerr << "Size object parse as unsigned integer"; + return TestFail; + } + + if (sizeObj.get(Size(0, 0), &ok) != Size(1920, 1080) || !ok) { + cerr << "Size object parse as wrong value"; + return TestFail; + } + + /* Test list object */ + auto &listObj = (*root)["list"]; + + if (listObj.isDictionary()) { + cerr << "List object parse as Dictionary"; + return TestFail; + } + + if (!listObj.isList()) { + cerr << "List object fail to parse as List"; + return TestFail; + } + + if (listObj.get("", &ok) != "" || ok) { + cerr << "List object parse as string"; + return TestFail; + } + + if (listObj.get(1.0, &ok) != 1.0 || ok) { + cerr << "List object parse as double"; + return TestFail; + } + + if (listObj.get(-1, &ok) != -1 || ok) { + cerr << "List object parse as integer"; + return TestFail; + } + + if (listObj.get(1, &ok) != 1 || ok) { + cerr << "List object parse as unsigne integer"; + return TestFail; + } + + if (listObj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "String list object parse as Size"; + return TestFail; + } + + if (listObj.size() > 2) { + cerr << "List object parse with wrong size"; + return TestFail; + } + + if (listObj[0].get("") != "James" || + listObj[1].get("") != "Mary") { + cerr << "List object parse as wrong value"; + return TestFail; + } + + /* Test dictionary object */ + auto &dictObj = (*root)["dictionary"]; + + if (!dictObj.isDictionary()) { + cerr << "Dictionary object fail to parse as Dictionary"; + return TestFail; + } + + if (dictObj.isList()) { + cerr << "Dictionary object parse as List"; + return TestFail; + } + + if (dictObj.get("", &ok) != "" || ok) { + cerr << "Dictionary object parse as string"; + return TestFail; + } + + if (dictObj.get(1.0, &ok) != 1.0 || ok) { + cerr << "Dictionary object parse as double"; + return TestFail; + } + + if (dictObj.get(-1, &ok) != -1 || ok) { + cerr << "Dictionary object parse as integer"; + return TestFail; + } + + if (dictObj.get(1, &ok) != 1 || ok) { + cerr << "Dictionary object parse as unsigned integer"; + return TestFail; + } + + if (dictObj.get(Size(0, 0), &ok) != Size(0, 0) || ok) { + cerr << "Dictionary object parse as Size"; + return TestFail; + } + + auto memeberNames = dictObj.getMemberNames(); + sort(memeberNames.begin(), memeberNames.end()); + + if (memeberNames.size() != 3) { + cerr << "Dictionary object fail to extra member names"; + return TestFail; + } + + if (memeberNames[0] != "a" || + memeberNames[1] != "b" || + memeberNames[2] != "c") { + cerr << "Dictionary object fail to parse member names"; + 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"; + return TestFail; + } + + /* Test leveled objects */ + auto &level1Obj = (*root)["level1"]; + + if (!level1Obj.isDictionary()) { + cerr << "level1 object fail to parse as Dictionary"; + return TestFail; + } + + auto &level2Obj = level1Obj["level2"]; + + if (!level2Obj.isList() || level2Obj.size() != 2) { + cerr << "level2 object should be 2 element list"; + 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 pars as integer list"; + 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 pases as dictionary"; + 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 Mon Apr 25 14:46:17 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15707 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 EF554C3256 for ; Mon, 25 Apr 2022 14:46:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A20F665650; Mon, 25 Apr 2022 16:46:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1650897992; bh=fsKyd4+IjSGybWpAR7NIi8bqJ3d4pZmfstg1MSQiR4U=; 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=wK2+n9wup7JQKLKlCUhiI/v1ZR/fN3FJDZxGzhsRg+O7DpMYzujVmSiTwl7GIUH5T KpqT5siZfFJgKPou3z//v0YuwF59Vq2ddixUEjLPc16xnKeqHpGxiTaljceDr4IEE4 2OuKKGhr4dizBY8Xeuu/YDM4GGO6Zy458TB4gpDnRgKONjfbsQ9d7zsfGRWyAKoiua lSFowzbVc5EL7ZUTVPF3crjH4qVNPipUf9SL/K04lNqe8J56a9GkaNEIhlK8YFcW18 c+Ct8mlHtK3P/zGzgZdEHk5E7GhR6NCP2eD1fen8+aLf5ftNhiftraGsf4fWfWdLpP viGRThpf36NbA== Received: from mail-pl1-x632.google.com (mail-pl1-x632.google.com [IPv6:2607:f8b0:4864:20::632]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8A381604A9 for ; Mon, 25 Apr 2022 16:46:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="dLR3JPQC"; dkim-atps=neutral Received: by mail-pl1-x632.google.com with SMTP id h12so23669386plf.12 for ; Mon, 25 Apr 2022 07:46:30 -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=UK/N9QOQ5ZUoMMU+80Rv9jNpnZBZy3QfzA53edv39NI=; b=dLR3JPQCtigIdNrrLrLF9Y4rvs183J5aYREZHMHlOC39gNYp7u25Q8EpXOJGHDfBig JbwJx39R1LqSUJCql8jTfjzLkYiMfnggSqgkGcD/qWJvuDsnu/6c0GjHDU9Oi6flyqc0 I2YJfwaVTOaOFT+12ava1PKz/lYiJ5NGnUzqk= 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=UK/N9QOQ5ZUoMMU+80Rv9jNpnZBZy3QfzA53edv39NI=; b=UEL6EXwv3AoDtTXLWrpHLgnuDLjge/n8ymWgiGS+QqBtUhm1JqQvITSS9gOcYjm3xB ZKGgm0d7I7uNKefhWw73i1ym6qmKh19vDCJl8jaS49tmKDyc3LJTeWXePct1XqR3My+3 jR5pusmkTm+2URJ01YuHNLUooYszdNYHFmx3WJFdKmn7mRCaFbhfp5JAo8eRwOQ53nsx qetp8ozAyWWEHgcWjzIIjfdCDwXcT7d85RxyPf9K8nhA4RO6GrDh8IXcoF7zaQUMRz+7 SbROA+5tHRGDRzE+hqc3TBpkRJZsxCMcquefxrzB9rqh29fcyTb83SQLRdti2mZyw2xB JaxA== X-Gm-Message-State: AOAM530c/wFXqKnrJ5lFka8DG3XUqIdMF/84CveYwtgFtqJm7c2z/xBs tEQQl0N573GktUPU3ZoBpCMmounm6sx5Aw== X-Google-Smtp-Source: ABdhPJyL/fZSSI0q9knuyk2B1CF3sh59uTAAwIp4r8Vp7t8uIdUdPT9O+3+YEc/N/9GHwgYt9pwH9w== X-Received: by 2002:a17:90b:1646:b0:1d9:7e9b:60aa with SMTP id il6-20020a17090b164600b001d97e9b60aamr4873536pjb.28.1650897988768; Mon, 25 Apr 2022 07:46:28 -0700 (PDT) Received: from localhost ([2401:fa00:1:17:38a2:b0a:67fa:9b05]) by smtp.gmail.com with UTF8SMTPSA id t7-20020a17090a5d8700b001cd4989ff64sm14809790pji.43.2022.04.25.07.46.27 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 25 Apr 2022 07:46:28 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Apr 2022 22:46:17 +0800 Message-Id: <20220425144617.2549778-4-hanlinchen@chromium.org> X-Mailer: git-send-email 2.36.0.rc2.479.g8af0fa9b8e-goog In-Reply-To: <20220425144617.2549778-1-hanlinchen@chromium.org> References: <20220425144617.2549778-1-hanlinchen@chromium.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 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..d269ab25 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.getMemberNames(); + 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, ]