Привет, Хабр. Если ты знаешь ответ на вопрос в заголовке, – поздравляю, эта статья тебе не нужна. Она адресуется новичкам в программировании, вроде меня, которые не всегда могут самостоятельно разобраться со всеми тонкостями C++ и других типизированных языков, а если и могут, лучше всё равно учиться на чужих ошибках.

В данной статье я не просто отвечу на вопрос "Зачем нужны виртуальные функции в C++", а приведу пример из своей практики. Для краткого ответа можно обратиться к поисковикам, которые выдают примерно следующее: "Виртуальные функции нужны для обеспечения полиморфизма — одного из трёх китов ООП. Благодаря им машина может сама определить тип объекта по указателю, не загружая этой задачей программиста". О'кей, но вопрос «зачем» остался, хотя теперь он значит немного другое: "Зачем полагаться на машину, тратить лишние время и память, если можно самостоятельно подкастовать указатель, ведь тип объекта, на который он ссылается, почти всегда известен?" Действительно, кастование на первый взгляд оставляет виртуальные функции без работы, и именно оно становится причиной заблуждений и плохого кода. В мелких проектах проигрыш незаметен, но, как вы скоро убедитесь, с ростом программы касты увеличивают листинг в почти геометрической прогрессии.

Для начала вспомним, где вообще могут понадобиться касты и виртуальные функции. Тип теряется, когда объекту, объявленному с типом A, операцией new выделяется память под объект типа B, совместимого с типом A, обычно наследуемого от A. Чаще всего объект не один, а целый массив. Массив указателей одного типа, каждый из которых ждёт присвоение области памяти с объектами совершенно других типов. Вот такой пример мы и рассмотрим.

Долго не буду тянуть, задача была такова: на основе документа, размеченного языком гипертекстовой разметки Markedit (про него можете почитать тут), построить синтаксическое дерево разбора и создать файл, содержащий тот же документ в разметке HTML. Моё решение состоит из трёх последовательных подпрограмм: разбор исходного текста на токены, построение из токенов синтаксического дерева и построение на его основе документа HTML. Нас интересует вторая часть.
Дело в том, что узлы конечного дерева имеют разные типы (раздел, параграф, текстовый узел, ссылка, сноска итд.), но для узлов-родителей указатели на узлы-детей хранятся в массиве, и потому имеют один тип – Node.

Сам парсер в упрощенной форме работает так: создаётся «корень» синтаксического дерева tree с типом Root, объявляется указатель open_node общего типа Node, которому тут же присваивается адрес tree, и переменная type перечислимого типа Node_type, а затем начинается цикл, перебирающий токены от самого первого до последнего. На каждой итерации первым делом в переменную type заносится тип открытой ноды open_node (типы в виде перечисления хранятся в структуре нод), после чего следует оператор выбора switch, проверяющий тип очередного токена (типы токенов уже заботливо предоставлены лексером). В каждой ветке switch-а представлено ещё одно ветвление, проверяющее переменную type, где, как мы помним, содержится тип открытого узла. В зависимости от её значения выполняются разные действия, например: добавить в открытый узел узел-лист определённого типа, открыть в открытом узле другой узел определённого типа и передать его адрес в open_node, закрыть открытый узел, выбросить исключение. Применимо к теме статьи, нас интересует второй пример. Каждый открытый узел (и вообще каждый узел, который можно открыть) уже содержит массив указателей на узлы типа Node. Поэтому, когда мы открываем в открытом узле новый узел (присваиваем очередному указателю массива область памяти для объекта другого типа), для семантического анализатора языка C++ он остаётся экземпляром типа Node, не приобретая новых полей и методов. Указатель на него теперь присваивается переменной open_node, не теряя типа Node. Но как работать с указателем общего типа Node, когда нужно вызвать метод, например, абзаца? Например, open_bold(), открывающий в нём узел полужирного шрифта? Ведь open_bold() объявлен и определён как метод класса Paragraph, и Node совершенно о нём не знает. Вдобавок, open_node тоже объявлена как указатель на Node, а методы должна принимать ото всех типов открывающихся узлов.

Здесь есть два решения: очевидное и правильное. Очевидным для новичка является static_cast, а правильным — виртуальные функции. Давайте сначала рассмотрим одну ветвь switch-а парсера, написанного с помощью первого способа:

        case Lexer::BOLD_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_bold();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_bold();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_bold();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_bold();
        break; }

Неплохо. А теперь, не буду долго тянуть, покажу тот же участок кода, написанный при помощи виртуальных функций:

        case Lexer::BOLD_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_bold();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_bold();

            } else if (type == Node::UNORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_bold();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_bold();
        break; }

Выигрыш очевиден, но действительно ли он нам нужен? Ведь тогда придётся объявлять в классе Node все методы всех производных классов как виртуальные и в каждом производном классе их как-то реализовывать. Ответ — да, действительно. Методов конкретно в этой программе не так уж и много (29), а их реализация в производных классах, не относящихся к ним, состоит всего из одной строчки: throw string(«error!»);. Можно включить творческий режим и придумать для каждого выброса исключения уникальную строку. Но самое главное — из-за сокращения кода уменьшилось число ошибок в нём. Кастование — одна из самых главных причин ошибок в коде. Потому что после применения static_cast компилятор перестаёт ругаться, если в приведённом классе содержится вызываемый метод. А между тем, в разных классах могут содержаться разные методы с одним названием. В моём случае в коде запряталось 6!!! ошибок, при том одна из них дублировалась в нескольких ветвях switch. Вот она:

else if (type == Node::
    open_node = static_cast<Title*>(open_node)->open_italic();

Далее под спойлерами привожу полные листинги первой и второй версии парсера.

Парсер с кастованием
Root * Parser::parse (const Lexer &lexer) {
    Node * open_node(tree);
    Node::Node_type type;

    for (unsigned long i(0), len(lexer.count()); i < len; i++) {
        type = open_node->get_type();
        if (type == Node::CITE || type == Node::TEXT || type == Node::NEWLINE || type == Node::NOTIFICATION || type == Node::IMAGE)
            throw string("error!");

        switch (lexer[i].type) {
        case Lexer::NEWLINE: {
            if (type == Node::ROOT || type == Node::SECTION)
                ;

            else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->add_text("\n");

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->add_text("\n");

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->add_text("\n");

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }

            } else if (type == Node::LINK) {
                open_node = static_cast<Link*>(open_node)->add_text(lexer[i].lexeme);

            } else // INLINE
                open_node = static_cast<Inline*>(open_node)->add_text(lexer[i].lexeme);
        break; }
        case Lexer::DOUBLE_NEWLINE: {
            if (type == Node::ROOT || type == Node::SECTION)
                ;

            else if (type == Node::PARAGRAPH) {
                open_node = static_cast<Paragraph*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }

            } else if (type == Node::QUOTE) {
                open_node = static_cast<Quote*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }

            } else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }

            } else
                throw string("unexpected double newline!");
        break; }
        case Lexer::UNDERLINE: {
            if (type == Node::ROOT)
                open_node = tree->add_line();

            else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->add_line();

            } else if (type == Node::PARAGRAPH) {
                open_node = static_cast<Paragraph*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->add_line();

            } else if (type == Node::TITLE)
                throw string("unexpected underline inside title!");

            else if (type == Node::QUOTE) {
                open_node = static_cast<Quote*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->add_line();

            } else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->add_line();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->add_line();

            } else // INLINE
                throw string("unexpected underline inside inline span!");
        break; }
        case Lexer::TITLE_START: {
            if (lexer[i].lexeme.size() > 7)
                throw string("invalid title: \"" + lexer[i].lexeme + "\"!");

            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_title(lexer[i].lexeme.size()-1);

            } else if (type == Node::SECTION)
                open_node = static_cast<Section*>(open_node)->open_title(lexer[i].lexeme.size()-1);

            else if (type == Node::PARAGRAPH) {
                open_node = static_cast<Paragraph*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_title(lexer[i].lexeme.size()-1);

            } else if (type == Node::TITLE)
                throw string("title can't contain another title!");

            else if (type == Node::QUOTE) {
                open_node = static_cast<Quote*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_title(lexer[i].lexeme.size()-1);

            } else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_title(lexer[i].lexeme.size()-1);

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_title(lexer[i].lexeme.size()-1);

            } else if (type == Node::LINK)
                throw string("link can't contain a title!");

            else // INLINE
                throw string("inline span can't contain a title!");
        break; }
        case Lexer::BOLD_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_bold();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_bold();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_bold();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_bold();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_bold();
        break; }
        case Lexer::ITALIC_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_italic();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_italic();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_italic();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_italic();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_italic();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_italic();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_italic();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_italic();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_italic();
        break; }
        case Lexer::UNDERLINED_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_underlined();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_underlined();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_underlined();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_underlined();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_underlined();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_underlined();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_underlined();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_underlined();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_underlined();
        break; }
        case Lexer::OVERLINED_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_overlined();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_overlined();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_overlined();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_overlined();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_overlined();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_overlined();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_overlined();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_overlined();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_overlined();
        break; }
        case Lexer::THROWLINED_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_throwlined();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_throwlined();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_throwlined();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_throwlined();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_throwlined();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_throwlined();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_throwlined();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_throwlined();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_throwlined();
        break; }
        case Lexer::SUBSCRIPT_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_subscript();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_subscript();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_subscript();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_subscript();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_subscript();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_subscript();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_subscript();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_subscript();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_subscript();
        break; }
        case Lexer::SUPERSCRIPT_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_superscript();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_superscript();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_superscript();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_superscript();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_superscript();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_superscript();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_superscript();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_superscript();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_superscript();
        break; }
        case Lexer::MARKED_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_marked();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_marked();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_marked();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_marked();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_marked();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_marked();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_marked();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_marked();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_marked();
        break; }
        case Lexer::MONOSPACE_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_monospace();

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_monospace();

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_monospace();

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_monospace();

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_monospace();

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_monospace();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_monospace();

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_monospace();

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_monospace();
        break; }
        case Lexer::SPAN_OR_IMAGE_FINISH: {
            if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->close();

            else if (type == Node::BOLD || type == Node::ITALIC || type == Node::UNDERLINED || type == Node::OVERLINED || type == Node::THROWLINED || type == Node::SUBSCRIPT || type == Node::SUPERSCRIPT || type == Node::MARKED || type == Node::MONOSPACE)
                open_node = static_cast<Inline*>(open_node)->close();

            else if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text("]");

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text("]");

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->add_text("]");

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->add_text("]");

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->add_text("]");

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text("]");

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text("]");

            } else // INLINE
                open_node = static_cast<Inline*>(open_node)->add_text(">");
        break; }
        case Lexer::LINK_START: {
            if (i > len-3 || lexer[++i].type != Lexer::TEXT || lexer[++i].type != Lexer::LINK_FINISH)
                throw string("unclosed link!");

            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_link(lexer[i-1].lexeme);

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_link(lexer[i-1].lexeme);

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->open_link(lexer[i-1].lexeme);

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->open_link(lexer[i-1].lexeme);

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->open_link(lexer[i-1].lexeme);

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_link(lexer[i-1].lexeme);

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->open_link(lexer[i-1].lexeme);

            } else if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->open_link(lexer[i-1].lexeme);

            else // INLINE
                open_node = static_cast<Inline*>(open_node)->open_link(lexer[i-1].lexeme);
        break; }
        case Lexer::LINK_FINISH: {
            if (type == Node::LINK)
                open_node = static_cast<Link*>(open_node)->close();

            else if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text(">");

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section>(open_node).open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text(">");

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->add_text(">");

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->add_text(">");

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->add_text(">");

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text(">");

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text(">");

            } else // INLINE
                open_node = static_cast<Inline*>(open_node)->add_text(">");

        break; }
        case Lexer::IMAGE_START: {
            if (i > len-5 || lexer[++i].type != Lexer::TEXT || lexer[++i].type != Lexer::LINK_FINISH || (lexer[++i].type != Lexer::TEXT && lexer[i].type != Lexer::SPAN_OR_IMAGE_FINISH) || (lexer[i].type == Lexer::TEXT && lexer[i+1].type != Lexer::SPAN_OR_IMAGE_FINISH))
                throw string("unclosed image defintion!");

            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::PARAGRAPH) {
                open_node = static_cast<Paragraph*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::TITLE) {
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::QUOTE) {
                open_node = static_cast<Quote*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::LINK) {
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");

            } else { // INLINE
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = static_cast<Paragraph*>(open_node)->add_image(lexer[i-2].lexeme, "");
            }
        break; }
        case Lexer::CITE: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->add_cite(atoi(lexer[i].lexeme.c_str()));

            } else if (type == Node::SECTION)
                open_node = static_cast<Section*>(open_node)->add_cite(atoi(lexer[i].lexeme.c_str()));

            else if (type == Node::PARAGRAPH) {
                open_node = static_cast<Paragraph*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->add_cite(atoi(lexer[i].lexeme.c_str()));

            } else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->add_image(lexer[i-3].lexeme, lexer[i-1].lexeme);

            else if (type == Node::QUOTE) {
                open_node = static_cast<Quote*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->add_cite(atoi(lexer[i].lexeme.c_str()));

            } else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->add_cite(atoi(lexer[i].lexeme.c_str()));

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->add_cite(atoi(lexer[i].lexeme.c_str()));

            } else if (type == Node::LINK)
                throw string("link can't contain a cite!");

            else // INLINE
                throw string("inline span can't contain a cite!");
        break; }
        case Lexer::QUOTE_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_quote();

            } else if (type == Node::SECTION)
                open_node = static_cast<Section*>(open_node)->open_quote();

            else if (type == Node::PARAGRAPH) {
                open_node = static_cast<Paragraph*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_quote();

            } else if (type == Node::TITLE) {
                open_node = static_cast<Title*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_quote();

            } else if (type == Node::QUOTE) {
                open_node = static_cast<Quote*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_quote();

            } else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_quote();

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_quote();

            } else if (type == Node::LINK) {
                open_node = static_cast<Link*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_quote();

            } else { // INLINE
                open_node = static_cast<Inline*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_quote();
            }
        break; }
        case Lexer::NOTIFICATION: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);

            } else if (type == Node::PARAGRAPH) {
                open_node = static_cast<Paragraph*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);

            } else if (type == Node::TITLE) {
                open_node = static_cast<Title*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);

            } else if (type == Node::QUOTE) {
                open_node = static_cast<Quote*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);

            } else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);

            } else if (type == Node::LINK) {
                open_node = static_cast<Link*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);

            } else { // INLINE
                open_node = static_cast<Inline*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_notification(lexer[i].lexeme);
            }
        break; }
        case Lexer::TEXT: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text(lexer[i].lexeme);

            } else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text(lexer[i].lexeme);

            } else if (type == Node::PARAGRAPH)
                open_node = static_cast<Paragraph*>(open_node)->add_text(lexer[i].lexeme);

            else if (type == Node::TITLE)
                open_node = static_cast<Title*>(open_node)->add_text(lexer[i].lexeme);

            else if (type == Node::QUOTE)
                open_node = static_cast<Quote*>(open_node)->add_text(lexer[i].lexeme);

            else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text(lexer[i].lexeme);

            } else if (type == Node::ORDERED_LIST) {
                open_node = static_cast<Ordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->open_paragraph();
                open_node = static_cast<Paragraph*>(open_node)->add_text(lexer[i].lexeme);

            } else if (type == Node::LINK) {
                open_node = static_cast<Link*>(open_node)->add_text(lexer[i].lexeme);

            } else // INLINE
                open_node = static_cast<Inline*>(open_node)->add_text(lexer[i].lexeme);
        break; }
        case Lexer::UNORDERED_LIST_ITEM_MARKER: {

        break; }
        case Lexer::ORDERED_LIST_ITEM_MARKER: {

        break; }
        case Lexer::END: {
            if (type == Node::ROOT)
                open_node = tree->close();

            else if (type == Node::SECTION) {
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->close();

            } else if (type == Node::PARAGRAPH) {
                open_node = static_cast<Paragraph*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->close();

            } else if (type == Node::QUOTE) {
                open_node = static_cast<Quote*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->close();

            } else if (type == Node::UNORDERED_LIST) {
                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->close();

            } else if (type == Node::ORDERED_LIST) {

                open_node = static_cast<Unordered_list*>(open_node)->close();
                while (open_node->get_type() != Node::SECTION) {
                    if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::UNORDERED_LIST)
                        open_node = static_cast<Unordered_list*>(open_node)->close();
                    else if (open_node->get_type() == Node::PARAGRAPH)
                        open_node = static_cast<Paragraph*>(open_node)->close();
                }
                open_node = static_cast<Section*>(open_node)->close();
                open_node = tree->close();

            } else // LINK || INLINE
                throw string("unexpected ending!");
            ///    ROOT,
            ///    SECTION,
            ///    PARAGRAPH, TITLE, QUOTE, UNORDERED_LIST, ORDERED_LIST,
            ///    BOLD, ITALIC, UNDERLINED, OVERLINED, THROWLINED, SUBSCRIPT, SUPERSCRIPT, MARKED, MONOSPACE,
            ///    LINK
        break; }
        }
    }
    concatenate();
    return tree;
}

Парсер с обращением к виртуальным методам
Root * Parser::parse (const Lexer &lexer) {
    Node * open_node(tree);
    Node::Node_type type;

    for (unsigned long i(0), len(lexer.count()); i < len; i++) {
        type = open_node->get_type();
        if (type == Node::CITE || type == Node::TEXT || type == Node::NEWLINE || type == Node::NOTIFICATION || type == Node::IMAGE)
            throw string("error!");

        switch (lexer[i].type) {
        case Lexer::NEWLINE: {
            if (type == Node::ROOT || type == Node::SECTION)
                ;

            else if (type == Node::PARAGRAPH || type == Node::TITLE || type == Node::QUOTE || type == Node::TITLE || type == Node::QUOTE)
                open_node = open_node->add_text("\n");

            else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();

            } else // LINK, INLINE
                open_node = open_node->add_text(lexer[i].lexeme);
        break; }
        case Lexer::DOUBLE_NEWLINE: {
            if (type == Node::ROOT || type == Node::SECTION)
                ;

            else if (type == Node::PARAGRAPH || type == Node::QUOTE || type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();

            } else
                throw string("unexpected double newline!");
        break; }
        case Lexer::UNDERLINE: {
            if (type == Node::ROOT)
                open_node = tree->add_line();

            else if (type == Node::SECTION) {
                open_node = open_node->close();
                open_node = tree->add_line();

            } else if (type == Node::PARAGRAPH || type == Node::QUOTE || type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->close();
                open_node = tree->add_line();

            } else if (type == Node::TITLE)
                throw string("unexpected underline inside title!");

            else if (type == Node::LINK)
                throw string("unexpected underline inside link!");

            else // INLINE
                throw string("unexpected underline inside inline span!");
        break; }
        case Lexer::TITLE_START: {
            if (lexer[i].lexeme.size() > 7)
                throw string("invalid title: \"" + lexer[i].lexeme + "\"!");

            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_title(lexer[i].lexeme.size()-1);

            } else if (type == Node::SECTION)
                open_node = open_node->open_title(lexer[i].lexeme.size()-1);

            else if (type == Node::PARAGRAPH || type == Node::QUOTE || type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_title(lexer[i].lexeme.size()-1);

            } else if (type == Node::TITLE)
                throw string("title can't contain another title!");

            else if (type == Node::LINK)
                throw string("link can't contain a title!");

            else // INLINE
                throw string("inline span can't contain a title!");
        break; }
        case Lexer::BOLD_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_bold();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_bold();

            } else if (type == Node::UNORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_bold();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_bold();
        break; }
        case Lexer::ITALIC_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_italic();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_italic();

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_italic();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_italic();
        break; }
        case Lexer::UNDERLINED_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_underlined();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_underlined();

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_underlined();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_underlined();
        break; }
        case Lexer::OVERLINED_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_overlined();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_overlined();

            } else if (type == Node::PARAGRAPH)
                open_node = open_node->open_overlined();

            else if (type == Node::TITLE)
                open_node = open_node->open_overlined();

            else if (type == Node::QUOTE)
                open_node = open_node->open_overlined();

            else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_overlined();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_overlined();
        break; }
        case Lexer::THROWLINED_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_throwlined();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_throwlined();

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_throwlined();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_throwlined();
        break; }
        case Lexer::SUBSCRIPT_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_subscript();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_subscript();

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_subscript();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_subscript();
        break; }
        case Lexer::SUPERSCRIPT_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_superscript();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_superscript();

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_superscript();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_superscript();
        break; }
        case Lexer::MARKED_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_marked();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_marked();

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_marked();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_marked();
        break; }
        case Lexer::MONOSPACE_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_monospace();

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_monospace();

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_monospace();

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_monospace();
        break; }
        case Lexer::SPAN_OR_IMAGE_FINISH: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text("]");

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text("]");

            } else if (type == Node::PARAGRAPH || type == Node::QUOTE || type == Node::LINK)
                open_node = open_node->add_text("]");

            else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text("]");

            } else // TITLE, INLINE
                open_node = open_node->close();
        break; }
        case Lexer::LINK_START: {
            if (i > len-3 || lexer[++i].type != Lexer::TEXT || lexer[++i].type != Lexer::LINK_FINISH)
                throw string("unclosed link!");

            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_link(lexer[i-1].lexeme);

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->open_link(lexer[i-1].lexeme);

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->open_link(lexer[i-1].lexeme);

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->open_link(lexer[i-1].lexeme);
        break; }
        case Lexer::LINK_FINISH: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text(">");

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text(">");

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text(">");

            } else if (type == Node::LINK)
                open_node = open_node->close();

            else // PARAGRAPH, TITLE, QUOTE, INLINE
                open_node = open_node->add_text(">");

        break; }
        case Lexer::IMAGE_START: {
            if (i > len-5 || lexer[++i].type != Lexer::TEXT || lexer[++i].type != Lexer::LINK_FINISH || (lexer[++i].type != Lexer::TEXT && lexer[i].type != Lexer::SPAN_OR_IMAGE_FINISH) || (lexer[i].type == Lexer::TEXT && lexer[i+1].type != Lexer::SPAN_OR_IMAGE_FINISH))
                throw string("unclosed image defintion!");

            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = open_node->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = open_node->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = open_node->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = open_node->add_image(lexer[i-2].lexeme, "");

            } else if (type == Node::PARAGRAPH || type == Node::QUOTE || type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = open_node->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = open_node->add_image(lexer[i-2].lexeme, "");

            } else { // TITLE, LINK, INLINE
                if (lexer[i].type == Lexer::TEXT) {
                    open_node = open_node->add_image(lexer[i-2].lexeme, lexer[i].lexeme);
                    i++;
                } else
                    open_node = open_node->add_image(lexer[i-2].lexeme, "");
            }
        break; }
        case Lexer::CITE: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->add_cite(atoi(lexer[i].lexeme.c_str()));

            } else if (type == Node::SECTION)
                open_node = open_node->add_cite(atoi(lexer[i].lexeme.c_str()));

            else if (type == Node::PARAGRAPH || type == Node::QUOTE || type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->add_cite(atoi(lexer[i].lexeme.c_str()));

            } else if (type == Node::TITLE)
                throw string("title cant't contain a cite!");

            else if (type == Node::LINK)
                throw string("link can't contain a cite!");

            else // INLINE
                throw string("inline span can't contain a cite!");
        break; }
        case Lexer::QUOTE_START: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_quote();

            } else if (type == Node::SECTION)
                open_node = open_node->open_quote();

            else {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_quote();
            }
        break; }
        case Lexer::NOTIFICATION: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->add_notification(lexer[i].lexeme);

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->add_notification(lexer[i].lexeme);

            } else {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->add_notification(lexer[i].lexeme);
            }
        break; }
        case Lexer::TEXT: {
            if (type == Node::ROOT) {
                open_node = tree->open_section();
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text(lexer[i].lexeme);

            } else if (type == Node::SECTION) {
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text(lexer[i].lexeme);

            } else if (type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->open_paragraph();
                open_node = open_node->add_text(lexer[i].lexeme);

            } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                open_node = open_node->add_text(lexer[i].lexeme);
        break; }
        case Lexer::UNORDERED_LIST_ITEM_MARKER: {

        break; }
        case Lexer::ORDERED_LIST_ITEM_MARKER: {

        break; }
        case Lexer::END: {
            if (type == Node::ROOT)
                open_node = tree->close();

            else if (type == Node::SECTION) {
                open_node = open_node->close();
                open_node = tree->close();

            } else if (type == Node::PARAGRAPH || type == Node::QUOTE || type == Node::UNORDERED_LIST || type == Node::ORDERED_LIST) {
                open_node = open_node->close();
                while (open_node->get_type() != Node::SECTION)
                    open_node = open_node->close();
                open_node = open_node->close();
                open_node = tree->close();

            } else // LINK || INLINE
                throw string("unexpected ending!");
        break; }
        }
    }
    concatenate();
    return tree;
}

С 1357 строк код сократился до 487 – почти в три раза, не считая длину строк!

Остался один вопрос: а что же со временем выполнения? Сколько миллисекунд мы должны заплатить за то, чтобы компьютер сам определял тип открытого узла? Я провёл эксперимент – зафиксировал время работы парсера в миллисекундах в первом и втором случаях для одного и того же документа на своём домашнем компьютере. Вот результат:

Кастование – 538 мс.
Виртуальные функции – 1174 мс.

Итого, 636 мс – плата за компактность кода и отсутствие ошибок. Много это? Возможно. Но если нам нужна программа, работающая с максимально возможной скоростью и требующая минимально возможное количество памяти, мы бы не стали обращаться к ООП и вообще написали бы её на языке ассемблера, потратив неделю времени и рискуя допустить огромное число ошибок. Так что мой выбор – везде, где в программе встретится static_cast и dynamic_cast, заменить их виртуальными функциями. А какое ваше мнение?

Комментарии (25)


  1. AntonSazonov
    30.06.2019 15:37

    • if (type == Node::ROOT || type == Node::SECTION) ;

    Что это у вас за конструкция такая интересная? Расскажете для чего она нужна?


    1. 2che Автор
      30.06.2019 15:46

      Если у нас открыт корень документа или раздел, но не параграф, тогда токен с типом NEWLINE просто игнорируем. Точка с запятой — пустой оператор.


      1. AntonSazonov
        30.06.2019 15:59

        Тогда почему бы вам просто не удалить это условие?
        От него нет абсолютно никакого толка.


        1. 2che Автор
          30.06.2019 16:06

          Если его удалить здесь, оно попадёт под else в конце вместе с LINK и INLINE-типами.


          1. AntonSazonov
            30.06.2019 16:17

            Ок. Не обратил на это внимание изначально.
            Тогда почему бы не задать явное условие для LINK и INLINE? Таким образом вы сократите по 3 строки для каждого условия.


            1. 2che Автор
              30.06.2019 16:21

              Инлайнов слишком много, код получится длиннее.


              1. AntonSazonov
                30.06.2019 16:47

                В смысле "инлайнов много"? INLINE_A, INLINE_B и т.п.?
                Если так, то просто добавьте в последний else:


                • if (type != Node::ROOT && type != Node::SECTION)


                1. 2che Автор
                  30.06.2019 18:02

                  "инлайны" — это BOLD, ITALIC, UNDERLINED, OVERLINED, THROWLINED, SUBSCRIPT, SUPERSCRIPT, MARKED, MONOSPACE.
                  Неплохое предложение. Добавлю в следующий коммит.


  1. WNeZRoS
    30.06.2019 15:43

    Тег "чистый код" явно лишний. Разница с кастами сейчас не такая заметная. Избавьтесь от метода Node::get_type() и сравнений if (type == *), тогда виртуальные функции будут более правильно применяться.
    Заодно подумайте как работают такие if:
    if (i > len-3 || lexer[++i].type != Lexer::TEXT || lexer[++i].type != Lexer::LINK_FINISH)


  1. ittakir
    30.06.2019 15:45
    +1

    Статью не читал, но считаю что сегодня в 2019 году нужно измерять не время исполнения программы в тактах CPU, а время на написание программы и повторное её чтение другим программистом.
    И static_cast и dynamic_cast и виртуальные функции можно и нужно применять по потребностям. Ориентироваться прежде всего на простоту и понятность программы.


    1. 2che Автор
      30.06.2019 16:12

      время на написание программы и повторное её чтение другим программистом

      И то и другое также сокращается при применении виртуальных методов.


    1. Playa
      30.06.2019 22:10

      А пользоваться программой кто будет? Ваш коллега?


      1. 2che Автор
        01.07.2019 00:13

        Если Вы про транслитератор Markedit, он в открытом доступе под лицензией MIT. Эту статью я им состряпал, чуть изменив построение HTML-документа.


  1. u_235
    30.06.2019 16:50

    А почему нельзя вместо

            case Lexer::BOLD_START:
                if (type == Node::ROOT) {
                    open_node = tree->open_section();
                    open_node = open_node->open_paragraph();
                    open_node = open_node->open_bold();
    
                } else if (type == Node::SECTION) {
                    open_node = open_node->open_paragraph();
                    open_node = open_node->open_bold();
    
                } else if (type == Node::UNORDERED_LIST) {
                    open_node = open_node->close();
                    while (open_node->get_type() != Node::SECTION)
                        open_node = open_node->close();
                    open_node = open_node->open_paragraph();
                    open_node = open_node->open_bold();
    
                } else // PARAGRAPH, TITLE, QUOTE, LINK, INLINE
                    open_node = open_node->open_bold();
    

    Сделать примерно так
    Node *Root::open_bold(){
        return open_section().open_bold();
    }
    
    ....
    
    Node *Section::open_bold(){
        return open_paragraph().open_bold();
    }
    
    ...
    
            case Lexer::BOLD_START:
                    open_node = open_node->open_bold();
                    break;
    


    1. 2che Автор
      30.06.2019 18:13

      Можно, но я предпочёл все детали синтаксического разбора оставить в парсере, чтобы при изменении грамматики, допустим, редактировать только его и не трогать протестированные классы узлов дерева.


      1. mayorovp
        30.06.2019 21:52
        +3

        Самое время вспомнить про паттерн Visitor...


      1. Deosis
        01.07.2019 06:58

        Именно поэтому у вас код для разных lexer::*_START получен копипастой?
        ПС. В одной из этих копий вы забыли обработать Node::ORDERED_LIST.


  1. berez
    30.06.2019 17:47
    +5

    Я провёл эксперимент – зафиксировал время работы парсера в миллисекундах в первом и втором случаях для одного и того же документа на своём домашнем компьютере. Вот результат:

    Кастование – 538 мс.
    Виртуальные функции – 1174 мс.

    Какие-то очень странные цифры. Не должны виртуальные функции настолько тормозить.
    Что за компилятор? Что за операционная система? С какими опциями собирали тестовые примеры, сколько раз запускали?


    1. 2che Автор
      30.06.2019 18:07
      -1

      Компилятор GCC самой новой версии, ОС — Linux Mint 19, опции расставил Qt creator на сборке qmake, запускал 1 раз.


      1. berez
        01.07.2019 00:23
        +4

        Компилятор GCC самой новой версии,

        «Самой новой» — это какой?
        У меня вот на дебиане «самая новая» — это 6.3.0 выпуска 2017 года.

        опции расставил Qt creator на сборке qmake,

        Их можно посмотреть в makefile, который генерится qmake'ом.

        запускал 1 раз.

        И что вам один запуск покажет? Тем более, у вас тест, как я понял, читает многомегабайтный файл с диска. На первом тесте файл попал в буфер ОСи, на втором — вполне вероятно, что вместо чтения с диска было чтение из памяти (а это гораздо быстрее). Или наоборот: один тест протарахтел нормально, а при запуске второго теста какая-нибудь утилита запустилась по крону и начала активно юзать диск — в результате операции чтения в тесте стали занимать больше времени.

        По-хорошему, нужно запускать тесты несколько раз, причем в разном порядке. И замерять время, за которое тесты отработали. Потом — опционально — для каждого теста отсекаем результаты, которые слишком отличаются от остальных (подразумевается, что на них повлияли какие-нибудь случайные факторы типа запустившегося невовремя тяжелого процесса).
        Вот когда будет хотя бы с десяток результатов по каждому тесту — тогда и можно их как-то сравнивать. А сейчас у вас «я кинул гранату левой рукой — попал в канаву, кинул правой — забросил к соседу за забор. Значит, правая рука на бросает дальше на 636 см».


  1. Sdima1357
    30.06.2019 20:15

    А чем Вас Yacc или Bison не устраивают? Во всяком случае разбор в них явно нагляднее.


    1. 2che Автор
      01.07.2019 00:05

      Это отдельная тема, касающаяся другой статьи. Алгоритм придуман мной ещё до знакомства с бизонами.


  1. WinPooh73
    30.06.2019 23:03

    Преимущество виртуальных функций над явной проверкой типов проявляется ещё, например, при добавлении в иерархию новых производных классов. Клиентский код при этом не нуждается в добавлении новых веток switch и даже в перекомпиляции.


  1. zawodskoj
    01.07.2019 10:27
    +4

    Зачем нужны виртуальные функции?

    Для реализации возможности переопределения поведения функций базового класса, а не для того, про что написано в статье.
    Код в статье заявлен как более типобезопасный, чем код на кастах, и при этом содержит как минимум три нарушения принципов SOLID.
    1. Принцип единой ответственности — Node самостоятельно открывает другие ноды и кладет их куда-то
    2. Принцип подстановки — Что сделает виртуальный метод open_paragraph в ноде Link? Упадет с исключением «Not Implemented/Supported»? Отличное нарушение контракта базового класса
    3. Принцип разделения интерфейсов — туда же, куда и первый пункт, когда у нас Node «поддерживает» сразу ВСЕ типы дочерних нод и «умеет работать» с ними
    Ни код на кастах, ни на виртуальных функциях не является приемлемым, однако первый, хотя бы, логически правильный.

    P.S. Отличный прием сократить второй пример в два раза, чтобы показать разительную разницу в объеме кода, который на деле не изменился практически ничем


  1. ncr
    01.07.2019 11:50

    Чаще всего объект не один, а целый массив. Массив указателей одного типа, каждый из которых ждёт присвоение области памяти с объектами совершенно других типов

    «О'кей, но вопрос «зачем» остался».

    Зачем у вас какие-то массивы указателей одного типа, указывающих на какие-то совершенно другие типы? Кто все эти люди?

    Если вы пишете для новичков, имеет смысл начать с концепций интерфейса и реализации, зачем это вообще нужно и какие задачи решает.