11
\$\begingroup\$

As nobody has provided input, I have updated the question. (The next one is coming soon)

Coding to this interface:

namespace ThorsAnvil
{
    namespace Serialization
    {

class ParserInterface
{
    public:
        enum class ParserToken {Error, DocStart, DocEnd, MapStart, MapEnd, ArrayStart, ArrayEnd, Key, Value};
        std::istream&   input;
        ParserToken     pushBack;

        ParserInterface(std::istream& input)
            : input(input)
            , pushBack(ParserToken::Error)
        {}
        virtual ~ParserInterface() {}
                ParserToken     getToken();
                void            pushBackToken(ParserToken token);
        virtual ParserToken     getNextToken()          = 0;
        virtual std::string     getKey()                = 0;

        virtual void    getValue(short int&)             = 0;
        virtual void    getValue(int&)                   = 0;
        virtual void    getValue(long int&)              = 0;
        virtual void    getValue(long long int&)         = 0;

        virtual void    getValue(unsigned short int&)    = 0;
        virtual void    getValue(unsigned int&)          = 0;
        virtual void    getValue(unsigned long int&)     = 0;
        virtual void    getValue(unsigned long long int&)= 0;

        virtual void    getValue(float&)                 = 0;
        virtual void    getValue(double&)                = 0;
        virtual void    getValue(long double&)           = 0;

        virtual void    getValue(bool&)                  = 0;

        virtual void    getValue(std::string&)           = 0;
};

    }
}

The Json Implementation is:

JsonParser.h

#ifndef THORS_ANVIL_SERIALIZATION_JSON_PARSER_H
#define THORS_ANVIL_SERIALIZATION_JSON_PARSER_H

#include "Serialize.h"
#include "JsonLexer.h"
#include <istream>
#include <string>
#include <vector>

namespace ThorsAnvil
{
    namespace Serialization
    {

class JsonParser: public ParserInterface
{
    enum State          {Error, Init, OpenM, Key, Colon, ValueM, CommaM, CloseM, OpenA, ValueA, CommaA, CloseA, Done};

    JsonLexerFlexLexer  lexer;
    std::vector<State>  parrentState;
    State               currentEnd;
    State               currentState;
    bool                started;

    std::string getString();

    template<typename T>
    T scan();
    public:
        JsonParser(std::istream& stream);
        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

JsonParser.cpp

#include "JsonParser.h"
#include "JsonLexemes.h"
#include "UnicodeIterator.h"
#include <map>
#include <cstdlib>

// enum class ParserToken {Error, MapStart, MapEnd, ArrayStart, ArrayEnd, Key, Value};
using namespace ThorsAnvil::Serialization;
using ParserToken = ParserInterface::ParserToken;

JsonParser::JsonParser(std::istream& stream)
    : ParserInterface(stream)
    , lexer(&stream)
    , currentEnd(Done)
    , currentState(Init)
    , started(false)
{}

ParserToken JsonParser::getNextToken()
{
    /* Handle States were we are not going to read any more */
    if (!started)
    {
        started = true;
        return ParserToken::DocStart;
    }
    if (currentState == Done)
    {
        currentState = Error;
        return ParserToken::DocEnd;
    }
    if (currentState == Error)
    {
        return ParserToken::Error;
    }

    // Convert Lexer tokens into smaller range 0-12
    static std::map<int, int>   tokenIndex  =
        {0,                                     0},
        {'{',                                   1},
        {'}',                                   2},
        {'[',                                   3},
        {']',                                   4},
        {',',                                   5},
        {':',                                   6},
        {ThorsAnvil::Serialize::JSON_TRUE,      7},
        {ThorsAnvil::Serialize::JSON_FALSE,     8},
        {ThorsAnvil::Serialize::JSON_NULL,      9},
        {ThorsAnvil::Serialize::JSON_STRING,    10},
        {ThorsAnvil::Serialize::JSON_INTEGER,   11},
        {ThorsAnvil::Serialize::JSON_FLOAT,     12}
    };
    // State transition table;
    static State   stateTable[][13]   =
    {
        /* Token   ->   0,      1,      2,      3,      4,      5,      6,      7,      8,      9,      10,     11,     12 */
        /* Error */ {   Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error   },
        /* Init  */ {   Error,  OpenM,  Error,  OpenA,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error   },
        /* OpenM */ {   Error,  Error,  CloseM, Error,  Error,  Error,  Error,  Error,  Error,  Error,  Key,    Error,  Error   },
        /* Key   */ {   Error,  Error,  Error,  Error,  Error,  Error,  Colon,  Error,  Error,  Error,  Error,  Error,  Error   },
        /* Colon */ {   Error,  OpenM,  Error,  OpenA,  Error,  Error,  Error,  ValueM, ValueM, ValueM, ValueM, ValueM, ValueM  },
        /* ValueM*/ {   Error,  Error,  CloseM, Error,  Error,  CommaM, Error,  Error,  Error,  Error,  Error,  Error,  Error   },
        /* CommaM*/ {   Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Key,    Error,  Error   },
        /* CloseM*/ {   Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error   },
        /* OpenA */ {   Error,  OpenM,  Error,  OpenA,  CloseA, Error,  Error,  ValueA, ValueA, ValueA, ValueA, ValueA, ValueA  },
        /* ValueA*/ {   Error,  Error,  Error,  Error,  CloseA, CommaA, Error,  Error,  Error,  Error,  Error,  Error,  Error   },
        /* CommaA*/ {   Error,  OpenM,  Error,  OpenA,  Error,  Error,  Error,  ValueA, ValueA, ValueA, ValueA, ValueA, ValueA  },
        /* CloseA*/ {   Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error   },
        /* Done  */ {   Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error,  Error   },
    };
    // Read the next token and update the state.
    int token   = lexer.yylex();
    int index   = tokenIndex[token];

    currentState    = stateTable[currentState][index];
    switch(currentState)
    {
        // These states should be impossible to get too
        case Init:      throw std::runtime_error("ThorsAnvil::Serialize::JsonParser: Got into Init State");
        case Done:      throw std::runtime_error("ThorsAnvil::Serialize::JsonParser: Got into Done State");
        // The states that we actually want to return
        case Error:     return ParserToken::Error;
        case Key:       return ParserToken::Key;
        case ValueM:    return ParserToken::Value;
        case ValueA:    return ParserToken::Value;
        // Punctuation.
        // Parse it but it is not the actual result
        // So try and get the next token.
        case Colon:     return getNextToken();
        case CommaM:    return getNextToken();
        case CommaA:    return getNextToken();
        // We are going into a containing object.

        // Push the state we want when the containing
        // object is complete then set the state we will
        // need if we open another container.
        case OpenM:
            parrentState.push_back(currentEnd);
            currentEnd  = ValueM;
            return ParserToken::MapStart;
        case OpenA:
            parrentState.push_back(currentEnd);
            currentEnd  = ValueA;
            return ParserToken::ArrayStart;

        // We are leaving the containing object.
        // Pop the state we previously saved.
        case CloseM:
            currentEnd  = currentState    = parrentState.back();
            parrentState.pop_back();
            return ParserToken::MapEnd;
        case CloseA:
            currentEnd  = currentState    = parrentState.back();
            parrentState.pop_back();
            return ParserToken::ArrayEnd;

        // Anything else just break.
        default:
            break;
    }
    // If we hit anything else there was a serious problem in the
    // parser itself.
    throw std::runtime_error("ThorsAnvil::Serialize::JsonParser: Reached an Unnamed State");
};

std::string JsonParser::getString()
{
    if (lexer.YYLeng() < 2 || lexer.YYText()[0] != '"' || lexer.YYText()[lexer.YYLeng()-1] != '"')
    {
        throw std::runtime_error("ThorsAnvil::Serialize::JsonParser: Not a String value");
    }
    // Remember to drop the quotes
    return std::string(make_UnicodeWrapperIterator(lexer.YYText() + 1),
                       make_UnicodeWrapperIterator(lexer.YYText() + lexer.YYLeng() - 1));
}

std::string JsonParser::getKey()
{
    return getString();
}

template<typename T>
inline T JsonParser::scan()
{
    char*   end;
    T value = scanValue<T>(lexer.YYText(), &end);
    if (lexer.YYText() + lexer.YYLeng() != end)
    {
        throw std::runtime_error("ThorsAnvil::Serialize::JsonParser: Not an integer");
    }
    return value;
}

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

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


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

void JsonParser::getValue(bool& value)
{
    if (lexer.YYLeng() == 4 && strncmp(lexer.YYText(), "true", 4) == 0)
    {
        value = true;
    }
    else if (lexer.YYLeng() == 5 && strncmp(lexer.YYText(), "false", 5) == 0)
    {
        value = false;
    }
    else
    {
        throw std::runtime_error("ThorsAnvil::Serialize::JsonParser: Not a bool");
    }
}

void JsonParser::getValue(std::string& value)
{
    value = getString();
}
\$\endgroup\$
3
  • \$\begingroup\$ The only improvement I can spot here is making the tables tokenIndex and stateTable constants. \$\endgroup\$
    – glampert
    Commented Feb 20, 2015 at 23:24
  • \$\begingroup\$ @glampert: Not going to any points for a comment. \$\endgroup\$ Commented Feb 20, 2015 at 23:32
  • \$\begingroup\$ It's been three years. Maybe you want to review your own code? \$\endgroup\$
    – Zeta
    Commented Oct 26, 2018 at 19:53

1 Answer 1

2
\$\begingroup\$

As an attempt to kill cure (too bad they are still far from being homophones) zombies, let me try write something here. Here's some suggestions:

  • The indentation looks absurd to me:

    namespace ThorsAnvil
    {
        namespace Serialization
        {
    
    class ParserInterface
    {
        // ...
    };
    
        }
    }
    

    I would typically do

    namespace ThorsAnvil::Serialization {
        class ParserInterface {
            // ...
        };
    }
    

    If you really want to get rid of the indentation, you can flush the namespace declarations to the left altogether.

  • There has to be a better way of writing the 17 getValue functions.

  • Are you sure these belong in a general interface? I don't think so.

    public:
        std::istream&   input;
        ParserToken     pushBack;
    
  • Since scan is a template, you should implement it in the header.

  • Is this necessary? At least the comment should be removed. Personally, I would remove the default branch altogether, but that's pretty much a matter of state.

    // Anything else just break.
    default:
        break;
    
  • The tables used in the implementation should probably be const.

\$\endgroup\$
7
  • \$\begingroup\$ Namespace: I like my format. But I like the C++17 multiple namespace in one go better. But I'll wait until 2021 when everybody is using C++17 (to many people still on C++11). \$\endgroup\$ Commented Aug 26, 2019 at 15:36
  • \$\begingroup\$ getValue() compiler does not differentiate on return value! \$\endgroup\$ Commented Aug 26, 2019 at 15:36
  • \$\begingroup\$ Yes. Those look like they should be private. I'll look at moving them see if any unit tests break. \$\endgroup\$ Commented Aug 26, 2019 at 15:37
  • \$\begingroup\$ Implementing scan() in the source file seems more logical as all the other functions are implemented there. Since it is private it can' be used anywhere else in the code. \$\endgroup\$ Commented Aug 26, 2019 at 15:38
  • 1
    \$\begingroup\$ That default case is actively harmful - it prevents a warning message that can otherwise be emitted when a new state is added to the enum and then not mentioned in the switch. \$\endgroup\$ Commented Aug 27, 2019 at 10:41

Your Answer

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

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