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
asked Feb 19, 2015 at 9:11
\$\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");
    
answered Feb 20, 2015 at 22:50
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.