From patchwork Wed Feb 9 07:19:11 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hanlin Chen X-Patchwork-Id: 15347 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 C69A3C3261 for ; Wed, 9 Feb 2022 07:19:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E2C85610E7; Wed, 9 Feb 2022 08:19:33 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="RhwLRR3g"; dkim-atps=neutral Received: from mail-pj1-x102d.google.com (mail-pj1-x102d.google.com [IPv6:2607:f8b0:4864:20::102d]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 846CD60E66 for ; Wed, 9 Feb 2022 08:19:32 +0100 (CET) Received: by mail-pj1-x102d.google.com with SMTP id c5-20020a17090a1d0500b001b904a7046dso2743072pjd.1 for ; Tue, 08 Feb 2022 23:19:32 -0800 (PST) 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=vSxjdnVBCGYsMUphGRb24V2CxxmnLtecgOxg8GmfZZY=; b=RhwLRR3gn3AQ3bl4iyHSFE+qRzHUi8G80lOxF9H892aUe92uvNqPhoTjOTKjAapDQO 1bNZz1glpgAoN7bZhW2fSmxppJxZqFSmwMM8IL1VrZoeMkuBZ1EvkolhkihM/Bo6+AJt c16eKA7q8wsHgo7MFZg50IRYtlb01lpC9R1fk= 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=vSxjdnVBCGYsMUphGRb24V2CxxmnLtecgOxg8GmfZZY=; b=HKgKSb88XytY0YjiFMVbrJHnWgzyN0N0biGzTzqunIji+nWOyZyJUhJGb7RepKZIy9 QiDMUdQ5Z+MCDDkg0DACYU2ciBUH2xHGOr39lHL4SizGQHjDFR+gsWaNairVKGj4ghLP htPGbu+c2gUOoJJKX1YDwwVuA7Ng/HBaZn4Pme7PEYv3GNY9pwxhHRmQAW7o7e16761N +Xrb7RFlZOlYUowkrePXZXz/oHVpqi7ndJ7n2gc4gehn9KMfsEd5OIOtPOHzxVLKZrqd uzDvptQa7fEG6+w6z3t91YfWSCg/8qE+FzqK1U8UaC7iTATbQXKw8hjtj4GDEvlVUbfl haUA== X-Gm-Message-State: AOAM5331ARp+LKqAVDPjKk38fTWMfExDVX5ja6ONQs+57u2Anl2Jp1QI xwsfVaBCyM47A++ySSI8BglaCqOxfsMk4Q== X-Google-Smtp-Source: ABdhPJy191/5Cj3Z98Hd1pUCFUGte7aVicEEUsQ9Ov2T3o7JfPcXCcQMMMnVdvEnlkfYO3h9rKPYrg== X-Received: by 2002:a17:902:a708:: with SMTP id w8mr838510plq.101.1644391170462; Tue, 08 Feb 2022 23:19:30 -0800 (PST) Received: from localhost ([2401:fa00:1:10:a5cf:9a43:1c6d:824]) by smtp.gmail.com with UTF8SMTPSA id ml19sm5266237pjb.52.2022.02.08.23.19.29 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 08 Feb 2022 23:19:30 -0800 (PST) From: Han-Lin Chen To: libcamera-devel@lists.libcamera.org Date: Wed, 9 Feb 2022 15:19:11 +0800 Message-Id: <20220209071917.559993-4-hanlinchen@chromium.org> X-Mailer: git-send-email 2.35.0.263.gb82422642f-goog In-Reply-To: <20220209071917.559993-1-hanlinchen@chromium.org> References: <20220209071917.559993-1-hanlinchen@chromium.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 3/9] 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: , 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 | 2 +- include/libcamera/internal/meson.build | 1 + include/libcamera/internal/yaml_parser.h | 82 +++ src/libcamera/meson.build | 3 + src/libcamera/yaml_parser.cpp | 796 +++++++++++++++++++++++ 5 files changed, 883 insertions(+), 1 deletion(-) 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 ca8a97cb..4a2a451a 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 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..7ba7c52c --- /dev/null +++ b/include/libcamera/internal/yaml_parser.h @@ -0,0 +1,82 @@ +/* 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 YamlObject +{ +public: + YamlObject() = default; + ~YamlObject() = default; + + YamlObject(const YamlObject &) = delete; + YamlObject &operator=(const YamlObject &) = delete; + + bool isValue() const + { + return type_ == VALUE; + } + bool isList() const + { + return type_ == LIST; + } + bool isDictionary() const + { + return type_ == DICTIONARY; + } + + bool asBool(bool defaultValue = false, bool *ok = nullptr) const; + double asDouble(double defaultValue = 0.0, bool *ok = nullptr) const; + int32_t asInt32(int32_t defaultValue = 0, bool *ok = nullptr) const; + uint32_t asUint32(uint32_t defaultValue = 0, bool *ok = nullptr) const; + std::string asString(std::string defaultValue = "", bool *ok = nullptr) const; + Size asSize(Size defaultValue = Size(), bool *ok = nullptr) const; + + int length() const; + const YamlObject &operator[](int index) const; + + bool isMember(const std::string &key) const; + const YamlObject &get(const std::string &key) const; + std::vector getMemberNames() const; + +private: + friend class YamlParser; + + enum PropertyType { + DICTIONARY, + LIST, + VALUE, + } type_; + + std::string value_; + std::vector> list_; + std::map> dictionary_; +}; + +class YamlParser final : public Extensible +{ + LIBCAMERA_DECLARE_PRIVATE() + +public: + YamlParser(); + int ParseAsYamlObject(FILE *fh, YamlObject &yamlObject); + +private: + int ParseNextYamlObject(YamlObject &yamlObject); +}; + +} /* 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..203d9cf4 --- /dev/null +++ b/src/libcamera/yaml_parser.cpp @@ -0,0 +1,796 @@ +/* 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. + */ + +/** + * \fn YamlObject::isValue() + * \brief Return whether the YamlObject is a value + * + * \return Whether the YamlObject is a value + */ + +/** + * \fn YamlObject::isList() + * \brief Return whether the YamlObject is a list + * + * \return Whether the YamlObject is a list + */ + +/** + * \fn YamlObject::isDictionary() + * \brief Return whether the YamlObject is a dictionary + * + * \return Whether the YamlObject is a dictionary + */ + +/** + * \fn YamlObject::asBool() + * \brief Helper function to parse the YamlObject as a bool value + * \param[in] defaultValue The default value when fail to parse + * \param[in] ok The result of whether the parse success + * + * Helper function to parse the YamlObject as a bool value. + * + * \return Value as a bool type + */ +bool YamlObject::asBool(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; +} + +/** + * \fn YamlObject::asInt32() + * \brief Helper function to parse the YamlObject as a int32_t value + * \param[in] defaultValue The default value when fail to parse + * \param[in] ok The result of whether the parse success + * + * Helper function to parse the YamlObject as a int32_t value. + * + * \return Value as a int32_t type + */ +int32_t YamlObject::asInt32(int32_t defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != VALUE) + return defaultValue; + + int32_t value = std::strtol(value_.c_str(), nullptr, 10); + if (errno == ERANGE) + return defaultValue; + + setOk(ok, true); + return value; +} + +/** + * \fn YamlObject::asUint32() + * \brief Helper function to parse the YamlObject as a uint32_t value + * \param[in] defaultValue The default value when fail to parse + * \param[in] ok The result of whether the parse success + * + * Helper function to parse the YamlObject as a uint32_t value. + * + * \return Value as a uint32_t type + */ +uint32_t YamlObject::asUint32(uint32_t defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != VALUE) { + return defaultValue; + } + + uint32_t value = std::strtoul(value_.c_str(), nullptr, 10); + if (errno == ERANGE) + return defaultValue; + + setOk(ok, true); + return value; +} + +/** + * \fn YamlObject::asDouble() + * \brief Helper function to parse the YamlObject as a double value + * \param[in] defaultValue The default value when fail to parse + * \param[in] ok The result of whether the parse success + * + * Helper function to parse the YamlObject as a double value. + * + * \return Value as a double type + */ +double YamlObject::asDouble(double defaultValue, bool *ok) const +{ + setOk(ok, false); + if (type_ != VALUE) + return defaultValue; + + double value = std::strtod(value_.c_str(), nullptr); + if (errno == ERANGE) + return defaultValue; + + setOk(ok, true); + return value; +} + +/** + * \fn YamlObject::asString() + * \brief Helper function to parse the YamlObject as a string value + * \param[in] defaultValue The default value when fail to parse + * \param[in] ok The result of whether the parse success + * + * Helper function to parse the YamlObject as a string value. + * + * \return Value as a string type + */ +std::string YamlObject::asString(std::string defaultValue, bool *ok) const +{ + if (type_ != VALUE) { + setOk(ok, false); + return defaultValue; + } + + return value_; +} + +/** + * \fn YamlObject::asSize() + * \brief Helper function to parse the YamlObject as a Size value + * \param[in] defaultValue The default value when fail to parse + * \param[in] ok The result of whether the parse success + * + * Helper function to parse the YamlObject as a Size value. The Size structure + * is represented as a list of two non-negative numbers. + * + * \return Value as a Size type + */ +Size YamlObject::asSize(Size defaultValue, bool *ok) const +{ + setOk(ok, false); + + if (type_ != LIST) + return defaultValue; + + if (list_.size() != 2) + return defaultValue; + + bool isInt = false; + int width = list_[0]->asUint32(0, &isInt); + if (!isInt) + return Size(0, 0); + + int height = list_[1]->asUint32(0, &isInt); + if (!isInt) + return Size(0, 0); + + setOk(ok, true); + return Size(width, height); +} + +/** + * \fn YamlObject::length() + * \brief Helper function to return length of the list + * + * Helper function to parse the YamlObject as a List and return the length of + * the List. + * + * \return The length as a list + */ +int YamlObject::length() const +{ + assert(type_ == LIST); + + return list_.size(); +} + +/** + * \fn YamlObject::operator[](int index) + * \brief Helper function to return an element of a list YamlObject by index + * + * Helper function to return an element of a list YamlObject by index. + * + * \return The YamlObject as an element of the list + */ +const YamlObject &YamlObject::operator[](int index) const +{ + assert(type_ == LIST); + + return *list_[index]; +} + +/** + * \fn YamlObject::isMember() + * \brief Helper function to check if an element of a dictionary exists + * + * Helper function to return an element of a list YamlObject by index. + * + * \return Whether an element exists + */ +bool YamlObject::isMember(const std::string &key) const +{ + assert(type_ == DICTIONARY); + + if (dictionary_.find(key) == dictionary_.end()) + return false; + + return true; +} + +/** + * \fn YamlObject::getMemberNames() + * \brief Helper function to get all member names of the dictionary + * + * Helper function to get all member names of the dictionary. + * + * \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::get() + * \brief Helper function to get a member by name from the dictionary + * + * Helper function to get a member by name from the dictionary + * + * \return A YamlObject as a member + */ +const YamlObject &YamlObject::get(const std::string &key) const +{ + assert(type_ == DICTIONARY); + assert(isMember(key)); + + auto iter = dictionary_.find(key); + return *iter->second; +} + +class YamlParser::Private : public Extensible::Private +{ + LIBCAMERA_DECLARE_PUBLIC(YamlParser) + +public: + Private(); + ~Private(); + + 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); + +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 YamlParser::Private + * \brief A Private class helps control event based parsing for yaml files. + * + * The YamlParser::Private class stores the internal yaml_parser_t and provides + * helper functions to do event based parsing for yaml files. + */ +YamlParser::Private::Private() + : nextEventLoaded_(false), parserValid_(false) +{ +} + +/** + * \class YamlParser::Private + * \brief Destructor of YamlParser::Private. + */ +YamlParser::Private::~Private() +{ + release(); +} + +/** + * \fn YamlParser::Private::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() should be called after use. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL The parser is failed to initialize + */ +int YamlParser::Private::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 YamlParser::Private::release() + * \brief Release the internal parser + * + * After parsing the yaml content, The YamlParser::release() should be called + * to release the internal parser. + */ +void YamlParser::Private::release() +{ + if (nextEventLoaded_) { + yaml_event_delete(&nextEvent_); + nextEventLoaded_ = false; + } + + if (parserValid_) { + parserValid_ = false; + yaml_parser_delete(&parser_); + } +} + +/** + * \fn YamlParser::Private::consume() + * \brief Cosume 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 is failed to initialize + */ +int YamlParser::Private::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 YamlParser::Private::ItemParser + * \brief The functor to handle an item in a yaml list + */ + +/** + * \typedef YamlParser::Private::RecordParser + * \brief The functor to handle an item in a yaml dictionary + */ + +/** + * \fn YamlParser::Private::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 YamlParser::Private::consumeDocumentStart() +{ + if (consume(YAML_STREAM_START_EVENT)) + return -EINVAL; + + if (consume(YAML_DOCUMENT_START_EVENT)) + return -EINVAL; + + return 0; +} + +/** + * \fn YamlParser::Private::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 YamlParser::Private::consumeDocumentEnd() +{ + if (consume(YAML_DOCUMENT_END_EVENT)) + return -EINVAL; + + if (consume(YAML_STREAM_END_EVENT)) + return -EINVAL; + + return 0; +} + +/** + * \fn YamlParser::Private::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 YamlParser::Private::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 YamlParser::Private::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 YamlParser::Private::parseList(ItemParser parseItem) +{ + return parseDictionaryOrList(false, parseItem); +} + +/** + * \fn YamlParser::Private::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 YamlParser::Private::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 YamlParser::Private::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 YamlParser::Private::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 YamlParser::Private::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 YamlParser::Private::nextEvent(yaml_event_t &event) +{ + if (nextEventLoaded_) { + event = nextEvent_; + return 0; + } + + /* yaml_parser_parse returns 1 when succeeded */ + if (1 != yaml_parser_parse(&parser_, &nextEvent_)) { + return -EINVAL; + } + + nextEventLoaded_ = true; + event = nextEvent_; + + return 0; +} + +/** + * \class YamlParser + * \brief A helper class for parsing yaml files. + * + * The YamlParser class eases handling of parsing a yaml file by providing + * helper function to extract content yaml files into a tree base yaml object. + * + * Example usage 1: + * The following code illustrates how to parse the following yaml file: + * + * name: + * "John" + * numbers: + * - 1 + * - 2 + * + * @code + * + * YamlObject root; + * YamlParser yamlParser; + * if (yamlParser.ParseAsYamlObject(fh, root)); + * return; + * + * if (!root.isDictionary()) + * return; + * + * std::string name = root.get("name"); + * cout << name.asString(); + * + * const YamlObject &numbers = root.get("numbers"); + * if (!numbers.isList()) + * return; + * + * for (int i = 0; i < numbers.size; i++) + * cout << numbers[i].asInt(); + * + * @endcode + * + * Function ParseAsYamlObject(FILE *, YamlObject &) accept an opened FILE and + * initialize an internal parser. The FILE need to stay valid during function + * call. + */ + +/** + * \brief Construct a YamlParser + */ +YamlParser::YamlParser() + : Extensible(std::make_unique()) +{ +} + +/** + * \fn YamlParser::ParseAsYamlObject() + * \brief Parse a yaml file as a YamlObject + * \param[in] fh The yaml file to parse + * \param[in] yamlObject The result fo YamlObject + * + * Prior to parsing the yaml content, the function accepts an opened FILE to + * create an internal parser. The FILE need to stay valid during the function + * call. When fails, the contect of the YamlObject is undefined. + * + * \return 0 on success or a negative error code otherwise + * \retval -EINVAL Fail to parse the yaml file. + */ +int YamlParser::ParseAsYamlObject(FILE *fh, YamlObject &yamlObject) +{ + if (_d()->initParser(fh)) + return -EINVAL; + + if (_d()->consumeDocumentStart()) + goto error; + + if (ParseNextYamlObject(yamlObject)) + goto error; + + if (_d()->consumeDocumentEnd()) + goto error; + + _d()->release(); + return 0; + +error: + _d()->release(); + return -EINVAL; +} + +/** + * \fn YamlParser::ParseNextEvent() + * \brief Helper function to parse next yaml event and read it as a YamlObject + * \param[in] yamlObject The result of YamlObject + * + * A helper function to 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 YamlParser::ParseNextYamlObject(YamlObject &yamlObject) +{ + yaml_event_t event; + + if (_d()->nextEvent(event)) + return -EINVAL; + + if (event.type == YAML_SCALAR_EVENT) { + yamlObject.type_ = YamlObject::VALUE; + _d()->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 _d()->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 _d()->parseDictionary(handler); + } + + LOG(YamlParser, Error) << "Invalid yaml file"; + return -EINVAL; +} + +} /* namespace libcamera */