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
1 Answer 1
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 toYamlParser
. Since you've made every method plus the destructorvirtual
, 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 theextern "C"
block.The boolean
result
insidethorsanvilYamlStreamReader
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 aconst std::string
would be the way to go!The lambdas you are passing to
generateParsingException
inYamlParser::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");