Класс Token для лексического парсера

В более крупном проекте мне нужен (довольно простой) анализатор выражений, способный принимать числовые значения, операторы и строковые идентификаторы. Хорошо, лексический синтаксический анализатор с вводом, который дает жетоны по одному синтаксическому синтаксическому анализатору. Поскольку токен может содержать только один тип значения за раз, я решил использовать объединение. Проблемы возникли вскоре, когда я понял, что идентификатор — это произвольная строка неизвестной длины, и что std::string внутри профсоюзов было не самое простое дело …

Общий план состоит в том, чтобы инициализировать лексический синтаксический анализатор с некоторыми входными данными, и синтаксический анализатор выражений неоднократно вызывает его следующий участник, чтобы получать по одному токену за раз. Две мои первые попытки разработать класс Token закончились ужасно сложным кодом для первого и кода, вызывающего UB для второго.

Прежде чем идти дальше, я хотел бы убедиться, что мой текущий Token class может быть использован для создания над ним полной техники. В своих тестах я могу успешно создавать токены всех типов, копировать их или хранить в стеках или векторах и получать доступ к их типам и значениям, но я также знаю, что оно работает ни то, ни другое это правильно по стандарту и портативному, ни не содержит антипаттернов… Что касается версий C ++, я ожидаю, что буду следовать стандартам C ++ 14 и выше.

/**
* Token represents a token extracted by a lexical parser.
* 
* It has a type among integer, double, operator (single char) or string and
* contains an appropriate value, except for the special type Eof which has
* no value and represents the end of the input data
* 
* It is a copyable and default constructible type (default constructor gives
* an Eof token) or can be constructed from a value of an acceptable type to
* produce a token of that type.
*/
class Token {
public:
    enum class Type { Int, Double, Operator, Identifier, Eof } type;
protected:
    // only 1! member at at time => union
    union  Foo {
        // group trivial member to be able to process them as a whole
        // because Bar is a trival union
        union Bar {
            int val;
            double fval;
            char op;
        } y;
        // one non trivial member: shall define all special methods
        Foo() { y.val = 0; }
        Foo(int i) { y.val = i; }
        Foo(double d) { y.fval = d; }
        Foo(char c) { y.op = c; }
        Foo(const std::string& str) : str(str) {};
        ~Foo() {}

        std::string str;
    } x;
public:
    // Simple ctors from nothing (Eof) or an acceptable type
    Token() : type(Type::Eof) {}
    Token(int i) : type(Type::Int), x(i) {};
    Token(double d) : type(Type::Double), x(d) {};
    Token(char c) : type(Type::Operator), x(c) {};
    Token(const std::string& str) : type(Type::Identifier), x(str) {};

    //Copy ctor handles specifically the string member
    Token(const Token& other): type(other.type) {
        if (type == Type::Identifier) {
            // in place construction for the string
            new (&x.str) std::string(other.x.str);
        }
        else {
            x.y = other.x.y;  // magic of the trivial member y
        }
    }

    // Explicit dtor destroys a possible string member
    ~Token() {
        if (type == Type::Identifier) {
            x.str.~basic_string();
        }
    }

    // assignment operator again handles the string member
    Token& operator = (const Token& other) {
        if (type == Type::Identifier) {
            if (other.type == Type::Identifier) {
                x.str = other.x.str;
            }
            else {
                // different types: we can safely destroy the destination
                x.str.~basic_string(); 
                x.y = other.x.y;
            }
        }
        else {
            if (other.type == Type::Identifier) {
                // we shall construct a new string member
                new (&x.str) std::string(other.x.str);
            }
            else {
                x.y = other.x.y;
            }
        }
        type = other.type;
        return *this;
    }

    // const accessors...
    int getVal() const { return x.y.val; }
    double getFval() const { return x.y.fval; }
    char getOp() const { return x.y.op; }
    std::string getStr() const { return x.str; }
    Type getType() const { return type; }
};

// and a stream injector to ease debugging traces
std::ostream& operator << (std::ostream& out, const Token& tok) {
    switch (tok.getType()) {
    case Token::Type::Int:
        out << tok.getVal();
        break;
    case Token::Type::Double:
        out << tok.getFval();
        break;
    case Token::Type::Operator:
        out << tok.getOp();
        break;
    case Token::Type::Identifier:
        out << tok.getStr();
        break;
    case Token::Type::Eof:
        out << "__EOF__";
    }
    return out;
}

1 ответ
1

Для того, чтобы делать то, что вы хотите, вы можете либо иметь (не объединенные) члены разных типов, и только один будет заполнен, что и является вашим подходом;
или вы можете использовать буфер массива байтов, достаточно большой для хранения любого из различных типов, и на месте создавать и уничтожать там фактический объект. Вот как Boost’s variant был реализован до того, как вы могли помещать такие типы в примитив union.

Я предлагаю найти готовую зрелую версию variant для включения в свой проект, даже если вы не используете Boost или не включаете все библиотеки Boost. Делать это хорошо сложно, и это уже было сделано другими.

Кроме того, рассматривали ли вы возможность использования string_view вместо string? Вам не нужно копировать токен, если вы можете ссылаться на него в исходном вводе. Это позволит избежать проблем, с которыми вы сталкиваетесь.

(Опять же, если у вас нет std::string_view, получите автономную реализацию для включения в свой проект. Помните, что все эти причудливые новые типы библиотек были проверены и хорошо изношены, прежде чем быть включены в стандарт ISO!)

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *