4
\$\begingroup\$

After this question, the next part.

Coding to the same interface as the previous question:

Note: Since not every system has libyaml installed, the config script tries to detect its existance and sets HAVE_YAML if it can be found (see autotools detect yaml library).

YamlParser.h

#ifndef THORS_ANIVL_SERIALIZE_YAML_PARSER_H
#define THORS_ANIVL_SERIALIZE_YAML_PARSER_H

#include "../../config.h"
#ifdef HAVE_YAML

#include "Serialize.h"
#include <yaml.h>
#include <istream>
#include <vector>

namespace ThorsAnvil
{
    namespace Serialize
    {

class YamlParser: public ParserInterface
{
    enum class State {Open, Map, Array};
    std::vector<std::pair<State, int>>  state;
    yaml_parser_t       parser;
    yaml_event_t        event;
    bool                first;
    bool                error;

    std::string getString();
    void generateParsingException(std::function<bool ()> test, std::string const& msg);
    void generateParsingException(std::string const& msg);
    ParserToken parsingError();

    template<typename T>
    T getValue(char* buffer, char** end);

    template<typename T>
    T scan();
    public:
        YamlParser(std::istream& input);
        virtual ~YamlParser();

        virtual ParserToken     getNextToken()                  override;
        virtual std::string     getKey()                        override;

        virtual void    getValue(short int& value)              override;
        virtual void    getValue(int& value)                    override;
        virtual void    getValue(long int& value)               override;
        virtual void    getValue(long long int& value)          override;

        virtual void    getValue(unsigned short int& value)     override;
        virtual void    getValue(unsigned int& value)           override;
        virtual void    getValue(unsigned long int& value)      override;
        virtual void    getValue(unsigned long long int& value) override;

        virtual void    getValue(float& value)                  override;
        virtual void    getValue(double& value)                 override;
        virtual void    getValue(long double& value)            override;

        virtual void    getValue(bool& value)                   override;

        virtual void    getValue(std::string& value)            override;
};

    }
}

#endif
#endif

YamlParser.cpp

#include "../../config.h"
#ifdef HAVE_YAML
#include "YamlParser.h"

using namespace ThorsAnvil::Serialize;

extern "C"
{
    int thorsanvilYamlStreamReader(void* data, unsigned char* buffer, size_t size, size_t* size_read);
}

int thorsanvilYamlStreamReader(void* data, unsigned char* buffer, size_t size, size_t* size_read)
{
    YamlParser*     owner = reinterpret_cast<YamlParser*>(data);
    bool            result  = false;

    owner->input.read(reinterpret_cast<char*>(buffer), size);
    *size_read      = owner->input.gcount();
    result          = ((*size_read) != size_t(-1));

    return result;
}

YamlParser::YamlParser(std::istream& input)
    : ParserInterface(input)
    , first(true)
    , error(false)
{
    yaml_parser_initialize(&parser);
    yaml_parser_set_input(&parser, thorsanvilYamlStreamReader, this);
}

YamlParser::~YamlParser()
{
    if (!first)
    {
        yaml_event_delete(&event);
    }
    yaml_parser_delete(&parser);
}

ParserInterface::ParserToken YamlParser::getNextToken()
{
    // enum class ParserToken {Error, DocStart, DocEnd, MapStart, MapEnd, ArrayStart, ArrayEnd, Key, Value};
    if (first)
    {
        first = false;
        if (!yaml_parser_parse(&parser, &event) || event.type != YAML_STREAM_START_EVENT)
        {
            return parsingError();
        }
        state.emplace_back(State::Open, 0);
    }

    if (error)
    {
        return ParserToken::Error;
    }
    yaml_event_delete(&event);


    if (!yaml_parser_parse(&parser, &event))
    {
        return parsingError();
    }

    switch(event.type)
    {
        case YAML_STREAM_START_EVENT:   generateParsingException("ThorsAnvil::Serialize::YamlParser: Start should only happen as first event");
        case YAML_ALIAS_EVENT:          generateParsingException("ThorsAnvil::Serialize::YamlParser: Alias not supported");
        case YAML_NO_EVENT:             generateParsingException("ThorsAnvil::Serialize::YamlParser: No Event not supported");

        case YAML_STREAM_END_EVENT:
        {
            return parsingError();
        }

        case YAML_DOCUMENT_START_EVENT:
        {
            generateParsingException([&](){return (state.size() != 1 || state.back().first != State::Open || state.back().second != 0);},
                    "ThorsAnvil::Serialize::YamlParser: Invalid document start event");
            ++state.back().second;
            return ParserToken::DocStart;
        }
        case YAML_DOCUMENT_END_EVENT:
        {
            generateParsingException([&](){return (state.size() != 1 || state.back().first != State::Open);},
                    "ThorsAnvil::Serialize::YamlParser: Invalid document end event");
            return ParserToken::DocEnd;
        }

        case YAML_MAPPING_START_EVENT:
        {
            ++state.back().second;
            generateParsingException([&](){return ((state.back().first == State::Map) && ((state.back().second % 2) == 1));},
                    "ThorsAnvil::Serialize::YamlParser: Map is not a valid Key");
            state.emplace_back(State::Map, 0);
            return ParserToken::MapStart;
        }
        case YAML_MAPPING_END_EVENT:
        {
            generateParsingException([&](){return ((state.back().second % 2) != 0);},
                    "ThorsAnvil::Serialize::YamlParser: Maps must have key value pairs");
            state.pop_back();
            return ParserToken::MapEnd;
        }

        case YAML_SEQUENCE_START_EVENT:
        {
            ++state.back().second;
            generateParsingException([&](){return ((state.back().first == State::Map) && ((state.back().second % 2) == 1));},
                    "ThorsAnvil::Serialize::YamlParser: Array is not a valid Key");
            state.emplace_back(State::Array, 0);
            return ParserToken::ArrayStart;
        }
        case YAML_SEQUENCE_END_EVENT:
        {
            state.pop_back();
            return ParserToken::ArrayEnd;
        }

        case YAML_SCALAR_EVENT:
        {
            ++state.back().second;
            return ((state.back().first == State::Map) && ((state.back().second % 2) == 1))
                        ? ParserToken::Key
                        : ParserToken::Value;
        }
        default:
            break;
    }
    return ParserToken::Error;
}

ParserInterface::ParserToken YamlParser::parsingError()
{
    error = true;
    return ParserToken::Error;
}

void YamlParser::generateParsingException(std::function<bool ()> test, std::string const& msg)
{
    if (test())
    {
        generateParsingException(msg);
    }
}

void YamlParser::generateParsingException(std::string const& msg)
{
    error = true;
    throw std::runtime_error(msg);
}

std::string YamlParser::getString()
{    
    char const* buffer  = reinterpret_cast<char const*>(event.data.scalar.value);
    std::size_t length  = event.data.scalar.length;    

    return std::string(buffer, buffer + length);
}
std::string YamlParser::getKey()
{
    return getString();
}

template<typename T>
T YamlParser::scan()
{
    char const* buffer  = reinterpret_cast<char const*>(event.data.scalar.value);
    std::size_t length  = event.data.scalar.length;
    char*       end;

    T   value = scanValue<T>(buffer, &end);
    if (buffer + length != end)
    {
        throw std::runtime_error("ThorsAnvil::Serialize::YamlParser: Not an integer");
    }
    return value;
}


void YamlParser::getValue(short& value)                 {value   = scan<short>();}
void YamlParser::getValue(int& value)                   {value   = scan<int>();}
void YamlParser::getValue(long& value)                  {value   = scan<long>();}
void YamlParser::getValue(long long& value)             {value   = scan<long long>();}

void YamlParser::getValue(unsigned short& value)        {value   = scan<unsigned short>();}
void YamlParser::getValue(unsigned int& value)          {value   = scan<unsigned int>();}
void YamlParser::getValue(unsigned long& value)         {value   = scan<unsigned long>();}
void YamlParser::getValue(unsigned long long& value)    {value   = scan<unsigned long long>();}

void YamlParser::getValue(float& value)                 {value   = scan<float>();}
void YamlParser::getValue(double& value)                {value   = scan<double>();}
void YamlParser::getValue(long double& value)           {value   = scan<long double>();}

void YamlParser::getValue(bool& value)
{
    char const* buffer  = reinterpret_cast<char const*>(event.data.scalar.value);
    std::size_t length  = event.data.scalar.length;

    if (length == 4 && strncmp(buffer, "true", 4) == 0)
    {
        value = true;
    }
    else if (length == 5 && strncmp(buffer, "false", 5) == 0)
    {
        value = false;
    }
    else
    {
        throw std::runtime_error("ThorsAnvil::Serialize::YamlParser: Not a bool");
    }
}

void YamlParser::getValue(std::string& value)
{
    value = getString();
}

#endif
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Overall, it seems quite good, I have only a few comments:

  • This seems weird:

        namespace ThorsAnvil
        {
            namespace Serialize
            {
    
        class YamlParser: public ParserInterface
        {
    

    I think it would be better to either properly nest the class under the second namespace or don't indent for namespace members at all. Personally, I prefer the latter.

  • C++ 11 provides the final keyword, which in theory could aid the compiler when optimizing virtual method calls. Not sure if it can be applied to your use case, but if so, it wouldn't harm adding it to YamlParser. Since you've made every method plus the destructor virtual, I'm guessing there is a need to expand this class?

  • Personally, I like to always add the private tag to the respective section of a class, to make things as explicit/clear as possible.

  • The prototype for function thorsanvilYamlStreamReader seems superfluous. You could have just wrapped the whole function declaration inside the extern "C" block.

  • The boolean result inside thorsanvilYamlStreamReader is unnecessary. You could just return the expression instead:

    return (*size_read) != size_t(-1);
    
  • The textual message "ThorsAnvil::Serialize::YamlParser: <something>" appears several times through the code. It seems to me that this would be a legitimate case for a macro. Just #undef it that the end, of course.

    #define ERR_MSG_PREFIX "ThorsAnvil::Serialize::YamlParser: "
    

    Then

    throw std::runtime_error(ERR_MSG_PREFIX "Not a bool");
    

    Or if you're into concatenating strings with the + operator, then a const std::string would be the way to go!

  • The lambdas you are passing to generateParsingException in YamlParser::getNextToken are resulting in quite long lines. I'd advise rearranging them with some extra line breaks. E.g.:

    generateParsingException(
        [&]() {
            return ((state.back().first == State::Map) && 
                   ((state.back().second % 2) == 1));
        },
        "ThorsAnvil::Serialize::YamlParser: Map is not a valid Key");
    
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.