From patchwork Mon Apr 18 12:09:22 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15681 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 3F7D2C3260 for ; Mon, 18 Apr 2022 12:09:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E282565641; Mon, 18 Apr 2022 14:09:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1650283772; bh=9SumQPraztHwJBtMzpLYdRMoyk9ehK/y/zuJU7jih3w=; 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=xzjabpB6Fcpadlay6+59w/9z2lwthfdAkSSs2fLsyv4NvXSDRWTjsPk0834bM11O9 PfCKLAp5/eYCNkiBJ71mavrRZhVrDgwWgzVdhhjtT5bR6JlW0loNbdKQr7MBpilTdS i+U2cI7yxXxE0AGKoZdBTIDd35B8Q+QNUBi/dar8U1V+DVf59njf6YcIRzyRJ/xSDI Wd0OjfgQxR+nimSbEOjY8ik8JKSJlE7mdnsndZC8p4OWsOrt8z/5FdoTIkqCMIf1SE ipcnqTOGDo1kRfVS/4LBq6+rguqEqBQ1DRNnSkiGSHkoqAw+aEIksza0lZs37LJWUz FC0bPRP81/fbA== Received: from mail-pg1-x52b.google.com (mail-pg1-x52b.google.com [IPv6:2607:f8b0:4864:20::52b]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AA9646563F for ; Mon, 18 Apr 2022 14:09:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="hS/1hM4a"; dkim-atps=neutral Received: by mail-pg1-x52b.google.com with SMTP id k62so12459951pgd.2 for ; Mon, 18 Apr 2022 05:09: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=kpfqAAVfvb1Zvp92zy4nJ+BdYuF6NZBRz/jeIKHRUTs=; b=hS/1hM4ahGpO/aoFI/VZ/ZwjRJ0AgNKKJXZL3iMTsnIA74NLB9A5QR6W2cs4SqhLVK tVYvUhJVZvfXpsUDul5e+yoodrEybdFpB4xA88Kw8iJGAAZcs/zgoDp1ric1jqKP1MJN b/BDuBW1EG1HxgX4V9bqiLqCGrUd2yCfg/+wM= 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=kpfqAAVfvb1Zvp92zy4nJ+BdYuF6NZBRz/jeIKHRUTs=; b=znVkMxs8pSimwxDo7MnBlapAaTFyvbXoRJXagkbnkn0g1TWXPHWp2IlbBkSI2zxXdl nMqP4fa9m9hy4ow25bwodnD25D5jo+as09u+GB/qNDTBBxMM2t5eGfdfPkqoJL+867Fc yLGjFrm7p/8TJ/34IXcBo5cA62twLFygFBIFaZ4DevEL2eJNAu8zy8yV9T7ZmFYyrLCv xAM6dGLHxVpTMni9ruv8BSpl/BZS0hPSbQ5hjgItGlNzd1OuDhYj5ey0caLS9aUsDcOo Np4EMZ/3W/pE3N03CBZUBXgfJBXTl0xxIm1xUy9rXe+J41bcIWpK3Ax4ud89pNFjFvMe qTEg== X-Gm-Message-State: AOAM530R3RUJL6zCSqw42CR6heUAv/nHAFjhuqlrdOjGUq8Es2hXGPx3 4WoF53b2iVUR1Djmk4oHFZbEdinMIK0+Ow== X-Google-Smtp-Source: ABdhPJzL2jO13cCk8+A9xJR7tglupv11H+iAJ81w+YhXXIqas+hjprgcOM2evDvURO3acblZECPUUA== X-Received: by 2002:a65:5b4b:0:b0:3a3:d8fb:6926 with SMTP id y11-20020a655b4b000000b003a3d8fb6926mr9699349pgr.76.1650283768584; Mon, 18 Apr 2022 05:09:28 -0700 (PDT) Received: from localhost ([2401:fa00:1:17:9775:a6e9:7655:6686]) by smtp.gmail.com with UTF8SMTPSA id e10-20020a17090a630a00b001c685cfd9d1sm12694221pjj.20.2022.04.18.05.09.27 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 18 Apr 2022 05:09:28 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Mon, 18 Apr 2022 20:09:22 +0800 Message-Id: <20220418120923.453131-2-hanlinchen@chromium.org> X-Mailer: git-send-email 2.36.0.rc0.470.gd361397f0d-goog In-Reply-To: <20220418120923.453131-1-hanlinchen@chromium.org> References: <20220418120923.453131-1-hanlinchen@chromium.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 1/2] 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 | 86 +++ src/libcamera/meson.build | 3 + src/libcamera/yaml_parser.cpp | 802 +++++++++++++++++++++++ 5 files changed, 894 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..e00ac25d --- /dev/null +++ b/include/libcamera/internal/yaml_parser.h @@ -0,0 +1,86 @@ +/* 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 + +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(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..f4072bb0 --- /dev/null +++ b/src/libcamera/yaml_parser.cpp @@ -0,0 +1,802 @@ +/* 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 + +/** + * \file libcamera/internal/yaml_parser.h + * \brief A YAML parser helper + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(YamlParser) + +namespace { + +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 a YAML content. A + * YamlObject can be a dictionary or list of YamlObjects or a value if a tree + * leaf. + */ +YamlObject::YamlObject() = default; + +/** + * \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 boolean 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; + + 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 diemension 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 +{ + assert(type_ == List); + + 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 associates 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 +{ + assert(type_ == List); + + 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 associates 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 +{ + assert(type_ == Dictionary); + + 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 associates elements with names, calling this + * function on other types of instances is invalid and results in undefined + * behaviour. + * + * \return A vector of string as the member names + */ +std::vector YamlObject::getMemberNames() const +{ + assert(type_ == Dictionary); + + 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 memberof a YamlObject by name. Only YamlObject + * instances of dictionary type associates elements with names, calling this + * function on other types of instances is invalid and results in undefined + * behaviour. + * + * \return A YamlObject as a member + */ +const YamlObject &YamlObject::operator[](const std::string &key) const +{ + assert(type_ == Dictionary); + assert(contains(key)); + + auto iter = dictionary_.find(key); + return *iter->second; +} + +class YamlParserContext +{ +public: + YamlParserContext(); + ~YamlParserContext(); + + using ItemParser = const std::function &; + using RecordParser = const std::function &; + + int initParser(FILE *fh); + void release(); + + int consumeDocumentStart(); + int consumeDocumentEnd(); + + int parseString(std::string &value); + int parseList(ItemParser parseItem); + int parseDictionary(RecordParser parseRecord); + + int parseNextYamlObject(YamlObject &yamlObject); + +private: + int nextEvent(yaml_event_t &event); + int consume(yaml_event_type_t); + + int parseDictionaryOrList(bool isDictionary, + const std::function &parseItem); + + bool nextEventLoaded_; + yaml_event_t nextEvent_; + + 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() + : nextEventLoaded_(false), parserValid_(false) +{ +} + +/** + * \class YamlParserContext + * \brief Destructor of YamlParserContext. + */ +YamlParserContext::~YamlParserContext() +{ + release(); +} + +/** + * \fn YamlParserContext::initParser() + * \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 YamlParser must be initialized with + * an opened FILE to create an internal parser. The FILE need to stay valid + * during the process. The release() must be called after use. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser is failed to initialize + */ +int YamlParserContext::initParser(FILE *fh) +{ + /* yaml_parser_initialize returns 1 when succeeded */ + 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::release() + * \brief Release the internal parser + * + * After parsing the YAML content, The YamlParser::release() should be called + * to release the internal parser. + */ +void YamlParserContext::release() +{ + if (nextEventLoaded_) { + yaml_event_delete(&nextEvent_); + nextEventLoaded_ = false; + } + + if (parserValid_) { + parserValid_ = false; + yaml_parser_delete(&parser_); + } +} + +/** + * \fn YamlParserContext::consume() + * \brief Consume the next event with an expected event type + * + * \param[in] type The expected event type to consume + * + * Consume next event and check whether next event has an expected type. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser failed to initialize + */ +int YamlParserContext::consume(yaml_event_type_t type) +{ + yaml_event_t event; + int ret = nextEvent(event); + if (ret) + return ret; + + if (event.type != type) { + LOG(YamlParser, Error) + << "Expect event: " << type + << " but get " << event.type + << " at line: " << event.start_mark.line + << " column: " << event.start_mark.column; + return -EINVAL; + } + + yaml_event_delete(&event); + nextEventLoaded_ = false; + + return 0; +} + +/** + * \typedef YamlParserContext::ItemParser + * \brief The functor to handle an item in a YAML list + */ + +/** + * \typedef YamlParserContext::RecordParser + * \brief The functor to handle an item in a YAML dictionary + */ + +/** + * \fn YamlParserContext::consumeDocumentStart() + * \brief Consume start of a YAML document + * + * Check YAML start of a YAML document. The function should be called and + * checked before parsing any content of the YAML file. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser is failed to validate start of a YAML file + */ +int YamlParserContext::consumeDocumentStart() +{ + if (consume(YAML_STREAM_START_EVENT)) + return -EINVAL; + + if (consume(YAML_DOCUMENT_START_EVENT)) + return -EINVAL; + + return 0; +} + +/** + * \fn YamlParserContext::consumeDocumentEnd() + * \brief Consume end of a YAML document + * + * Check YAML end of a YAML document. The function should be called and + * checked after parsing any content of the YAML file. + * + * \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::consumeDocumentEnd() +{ + if (consume(YAML_DOCUMENT_END_EVENT)) + return -EINVAL; + + if (consume(YAML_STREAM_END_EVENT)) + return -EINVAL; + + return 0; +} + +/** + * \fn YamlParserContext::parseString() + * \brief Parse scalar and read its content as a string + * \param[in] value The string reference to fill value + * + * A helper function to peek the next event, check whether it's scalar type + * and read its content as a string. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser is failed to initialize + */ +int YamlParserContext::parseString(std::string &value) +{ + yaml_event_t event; + int ret = nextEvent(event); + if (ret) + return ret; + + if (event.type != YAML_SCALAR_EVENT) { + LOG(YamlParser, Error) << "Expect scalar at line: " + << event.start_mark.line + << " column: " + << event.start_mark.column; + return -EINVAL; + } + + value.assign(reinterpret_cast(event.data.scalar.value), + event.data.scalar.length); + + consume(YAML_SCALAR_EVENT); + + return 0; +} + +/** + * \fn YamlParserContext::parseList() + * \brief Parse a list with an callback function to parse each item in the list + * \param[in] parseItem The functor to parse a single item in the list + * + * Start to parse a list from the current position and call back to parseItem + * for parsing each item in the list. The parseItem should return 0 on success + * or a negative error code otherwise. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser is failed to parse the list + */ +int YamlParserContext::parseList(ItemParser parseItem) +{ + return parseDictionaryOrList(false, parseItem); +} + +/** + * \fn YamlParserContext::parseDictionary() + * \brief Parse a dictionary with an callback function to parse each item + * \param[in] parseRecord The functor to parse a single item in the dictionary + * + * Start to parse a dictionary from the current position and call back to + * parseRecord to parse value by a string argument as the key to the value. + * The parseRecord should return 0 on success or a negative error code + * otherwise. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser is failed to parse the dictionary + */ +int YamlParserContext::parseDictionary(RecordParser parseRecord) +{ + auto parseItem = [this, &parseRecord]() { + std::string key; + int ret = parseString(key); + if (ret) + return -EINVAL; + + return parseRecord(key); + }; + + return parseDictionaryOrList(true, parseItem); +} + +/** + * \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 startEventType = YAML_SEQUENCE_START_EVENT; + yaml_event_type_t endEventType = YAML_SEQUENCE_END_EVENT; + + if (isDictionary) { + startEventType = YAML_MAPPING_START_EVENT; + endEventType = YAML_MAPPING_END_EVENT; + } + + if (consume(startEventType)) + return -EINVAL; + + /* + * Add a safety counter to make sure we don't loop indefinitely in case + * the configuration file is malformed. + */ + unsigned int sentinel = 1000; + yaml_event_t event; + do { + int ret = nextEvent(event); + if (ret) + return ret; + + if (event.type == endEventType) + return consume(endEventType); + + ret = parseItem(); + if (ret) + return ret; + + --sentinel; + } while (sentinel); + + if (!sentinel) + return -EINVAL; + + return 0; +} + +/** + * \fn YamlParserContext::nextEvent() + * \brief Peek the next event + * + * \param[in] event The event reference to fill information + * + * Peek the next event in the current YAML event stream, and return -EINVAL when + * there is no more event. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser is failed to initialize + */ +int YamlParserContext::nextEvent(yaml_event_t &event) +{ + if (nextEventLoaded_) { + event = nextEvent_; + return 0; + } + + /* yaml_parser_parse returns when it succeeds */ + if (1 != yaml_parser_parse(&parser_, &nextEvent_)) { + return -EINVAL; + } + + nextEventLoaded_ = true; + event = nextEvent_; + + return 0; +} + +/** + * \fn YamlParserContext::parseNextYamlObject() + * \brief Parse next YAML event and read it as a YamlObject + * \param[in] yamlObject The result of YamlObject + * + * Parse next YAML event by peeking next event and parse + * them 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) +{ + yaml_event_t event; + + if (nextEvent(event)) + return -EINVAL; + + if (event.type == YAML_SCALAR_EVENT) { + yamlObject.type_ = YamlObject::Value; + parseString(yamlObject.value_); + return 0; + } + + if (event.type == YAML_SEQUENCE_START_EVENT) { + yamlObject.type_ = YamlObject::List; + auto &list = yamlObject.list_; + auto handler = [this, &list]() { + list.emplace_back(new YamlObject()); + return parseNextYamlObject(*list.back()); + }; + return parseList(handler); + } + + if (event.type == YAML_MAPPING_START_EVENT) { + yamlObject.type_ = YamlObject::Dictionary; + auto &dictionary = yamlObject.dictionary_; + auto handler = [this, &dictionary](const std::string &key) { + dictionary[key].reset(new YamlObject()); + return parseNextYamlObject(*dictionary[key]); + }; + return parseDictionary(handler); + } + + LOG(YamlParser, Error) << "Invalid YAML file"; + return -EINVAL; +} + +/** + * \class YamlParser + * \brief A helper class for parsing YAML files. + * + * The YamlParser class provides an easy interface to parse the contents of a + * YAML file int a tree fo YamlObject instances. + * + * Example usage 1: + * The following code illustrates how to parse the following YAML file: + * + * name: + * "John" + * numbers: + * - 1 + * - 2 + * + * \code + * + * YamlObject root; + * std::unique_ptr root = YamlParser::parse(fh); + * if (!root) + * return; + * + * if (!root->isDictionary()) + * return; + * + * std::string name = (*root)["name"]; + * std::cout << name.get(""); + * + * const YamlObject &numbers = (*root)["numbers"]; + * if (!numbers.isList()) + * return; + * + * for (int i = 0; i < numbers.size; i++) + * std::cout << numbers[i].get(0); + * + * \endcode + * + * The YamlParser::parse() function accepts an open FILE and initializes an + * internal parse. + */ + +/** + * \fn YamlParser::parse() + * \brief Parse a YAML file as a YamlObject + * \param[in] fh The YAML file to parse + * + * Prior to parsing the YAML content, the function accepts an opened FILE to + * create an internal parser. + * + * \return Pointer to result YamlObject on success or nullptr otherwise + */ +std::unique_ptr YamlParser::parse(FILE *fh) +{ + YamlParserContext context; + + if (context.initParser(fh)) + return nullptr; + + std::unique_ptr root(new YamlObject()); + + if (context.consumeDocumentStart()) + goto error; + + if (context.parseNextYamlObject(*root)) + goto error; + + if (context.consumeDocumentEnd()) + goto error; + + context.release(); + return root; + +error: + root.release(); + context.release(); + return nullptr; +} + +} /* namespace libcamera */ From patchwork Mon Apr 18 12:09:23 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15682 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 30C2FC0F1B for ; Mon, 18 Apr 2022 12:09:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DF17E65647; Mon, 18 Apr 2022 14:09:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1650283774; bh=J6txzjWeCAiHMi7PmlrLW+sYUOs5geXSWcPIXms8Bs4=; 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=pVCLAXbgpuELZKD7WurNE4nzlOnP9cJxtsfx1QHMDyn0clzpeEWy2kTpFj3dd7wae nPGHkMAyOcuD/ur1gzv9RZemmA/+lo4KHnMJXggygqbB0NVUATiRNDSI5G7omymAAe gBvmO3pGLN0J6kzV+aRSrVa3piu4zs5vGUOmqxAteH557IsjwKd2PNK1mg0iPrXaDh dqLdH/2G/cpgr5vBR0yXjEcEJ+cOceAABevWY/svz7KBFsqgQJw/Lql7oTryjX8ZnB NEY+/FIwZ0nykS+hjBDdIN2nqxpMzh8Hbv7Wt5gXDEqbTBB0N1b/8z+2SgrM6Fe09M 7N9l46rm+xx7Q== Received: from mail-pg1-x52a.google.com (mail-pg1-x52a.google.com [IPv6:2607:f8b0:4864:20::52a]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 44BD565640 for ; Mon, 18 Apr 2022 14:09:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="KQvewgdq"; dkim-atps=neutral Received: by mail-pg1-x52a.google.com with SMTP id k29so18500098pgm.12 for ; Mon, 18 Apr 2022 05:09:32 -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=wQ7kabCFShC5JW0bIzE4d2rAhrU+VGXBkHHFDjrsPk4=; b=KQvewgdqWL4SSYapT+GHDGZ0ijzcULo1dkj1Lj6ydZc//xvm6csc0LpYLFXMDPucSP JTyducOB9L0a+3GbZg8OHMk0VyslaYaetqXjd/CPyqOXnUs8htBsJhDggsnJTWt6CCR8 rGnlS2qTPiZhr/YlaROzfDGJRy2NhLNFH+Fco= 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=wQ7kabCFShC5JW0bIzE4d2rAhrU+VGXBkHHFDjrsPk4=; b=srqcI+esR0Foe58Oyx0I1jRrppmu182L0nsExRcAvIT+/sBPHDBKzqyLXDOYpRyFY+ F6DnyqAhgwGL9moVgDnUH71AQYQkcWanL3+m1QByJ7b3ZtiTvkva5yamLNQl2NFy5Jpi q1MzEKJhJwOWNDTd0OUtZwXd3qcT5L05E0jQx5vdrLQfAKVYg5uQD8g2GS/LjWdyCwZM DqrFK45ucRFB1L65SGQWbyXR1yQtOu0aJcOD8bjWcwbvq7JcysYB9wF4dnmgH55HVUms n24d8jBmeQ/KZ1O5pGRCTqHA5Pi4vXuewug0zhu+Ee5/t5Jpf0Im5cWIaSvH5+1+LHlT 4XxA== X-Gm-Message-State: AOAM532pm/DM95s/N7Q/XYjTclnq1G5718ho17JUbApVLsB1e/tYfqXn LoHTtZTVj3KShJwbSrKg1Zr4182jfl84Yw== X-Google-Smtp-Source: ABdhPJxYEgde+CBwkEa86sET74rdNVUM/01EEb9gRQv729KkuFiMoZruTIQzvZSCAHRh3aPt71y2UA== X-Received: by 2002:a63:5747:0:b0:381:54b9:b083 with SMTP id h7-20020a635747000000b0038154b9b083mr9812024pgm.178.1650283770585; Mon, 18 Apr 2022 05:09:30 -0700 (PDT) Received: from localhost ([2401:fa00:1:17:9775:a6e9:7655:6686]) by smtp.gmail.com with UTF8SMTPSA id d16-20020a056a00245000b004f771b48736sm12991665pfj.194.2022.04.18.05.09.29 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 18 Apr 2022 05:09:30 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Mon, 18 Apr 2022 20:09:23 +0800 Message-Id: <20220418120923.453131-3-hanlinchen@chromium.org> X-Mailer: git-send-email 2.36.0.rc0.470.gd361397f0d-goog In-Reply-To: <20220418120923.453131-1-hanlinchen@chromium.org> References: <20220418120923.453131-1-hanlinchen@chromium.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 2/2] 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, ]