Pull to refresh

Comments 16

Про друзей знаем, статья для тех, кому интересно, как всё работает.
Простите, но статья никак не поможет разработчикам парсеров языков программирования. Как и «разобраться как сделать разбор КС-грамматики».
От простмотра метода parse() в
gitlab.com/2che/markedit/blob/master/parser.cpp
у меня, если честно, холодок по коже.

Ладно вы один раз это написали, а поддерживать как?
А если я вам скажу, что там в одной из строк ошибка, и вызывается не тот метод, вы ее быстро найдете?)

Такой код может и должен автогенерироваться.

А еще, зачем такое количество статик кастов? там прямо напрашивается использование виртуальных методов.

Я писал интерпретатор паскаля сперва ручками, потом с помощью CoCo генератора, потом перешел на bison, если интересно, какой мой опыт.
> зачем такое количество статик кастов?
Виртуальные методы, напомню, требуют дополнительную память и ресурсы процессора. Мы знаем по перечисляемому типу Node_type, что объект принадлежит к классу Quote, и подкастовываем его, чтобы вызвать метод add_bold(), который, мы точно знаем, в нём содержится.
Виртуальные методы, напомню, требуют дополнительную память и ресурсы процессора

Напомню, чтобы утверждать про ресурсы процессора, нужно сделать бенчмарк со сравнением.
У меня нет уверенности что
if (some_cond) obj->direct_call();
сильно быстрее условного obj->indirect_call() (по указателю), т.к. процессору переходы гораздо легче предсказывать без ветвлений (он уже видит куда дальше будет переход).

И по поводу памяти — если честно, смешно. Оверхед идет только на класс, таблица виртуальных методов одна на все объекты.
Да, у объекта появляется один указатель. Но у вас и так есть память под node_type; на 32-битной архитектуре разницы нет вообще. Да, на 64 битах наличие vtable вместо enum даст +4 байта, но вы реально в это упираетесь? Зато насколько будет чище код…

Нашёл, быстро, можете проверить :)
А что если я вам скажу, что там все равно осталась строчка с логической ошибкой?) вы мне поверите?)
Там до сих пор как-то много подозрительной копипасты типа:

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

Прямо глаз цепляется.
Ну что вы, зато мы экономим память на виртуальных методах! Вы ничего не понимаете!
Ладно, вы правы, лучше использовать виртуальные методы. Но замечу, что совсем без геморроя не обойтись: придётся прописывать выбросы исключений в реализации методов у классов, которые с этими методами роднятся меньше, чем никак. Классу Root, например, совершенно не идёт метод add_bold, поскольку его объекты могут содержать указатели лишь на разделы (Section) и линии (Underline). И прописывать придётся у десятков методов, в десятках классов, так что неопытный погромист вроде меня может опять же наделать ошибок.
Можно использовать std::variant и std::visit. Очень давно, когда последний раз делал что-то такое, то использовал их (только из boost).
Можно сразу прописать выбросы исключений абсолютно во всех виртуальных методах некоего корневого класса, от которого далее будут наследоваться остальные (собственно, по умолчанию все, что они будут делать — это выбрасывать исключение), а в наследниках переопределять только те из этих методов, которые реально должны что-то делать именно в этом конкретном типе наследника.
yacc/bison
Выглядят как г-но мамонта. Серьёзно? Глобальные переменные, чтение из stdin/FILE* (а если у меня в компиляторе исходники читаются из zip-потока, мне их в файл пересохранять?). Каждый найденный токен копируется в отдельную область памяти, чтобы сделать из него asciiz-строку (ладно, string_view тогда не было, но хотя бы кортеж [char*,size_t]). Перемешанный си-код с грамматикой (удачи подебажить). Если тупо хочу построить AST, а не сразу выдавать что-то наружу, всё равно надо писать кучу бойлерплейта.

От всего этого в соседстве с C++14 кодом просто кровь из глаз.

Да и в реальных проектах, которые планируется поддерживать годами, не похоже, что кто-то использует. Тупо невозможно выдать юзер-френдли сообщения об ошибках, типа — «не найден END к открытому BEGIN». Или «ключевое слово 'function' обнаружено внутри функции — вложенные функции не поддерживаются». Неудобно вывести позицию (строка/столбец), где реально находится ошибка (например, незакрытый BEGIN), а не текущая позиция чтения.

lex/yacc это очень ограниченный инструмент.
Я написал «и друзья». yacc/bison самые известные. Как минимум postgresql использует bison и поддерживается больше 20 лет. Несмотря на ужасы наследства С кода… я буду рад увидеть yacc/bison (или любой другой кодогенератор) в проекте, куда приду работать, чем тонну недокументированного кода как бывает чаще всего… если у вас все красиво — я рад, вы попадаете в исключения.
буду рад увидеть yacc/bison (или любой другой кодогенератор) в проекте, куда приду работать, чем тонну недокументированного кода
Так там и будет тонна недокументированного кода, в файле с грамматикой. Только его ещё и не подебажишь — на выходе куски юзер-кода, перемешанные с кодом парсера.
использует bison и поддерживается больше 20 лет
Мне интересно, что говорят в коммерческой разработке, когда появляется хотелка, не предусмотренная генератором (как пример выше с вложенными ф-циями). Говорят: «извините, это невозможно, у нас Бизон», или начинают корёжить грамматику, описывая некорректные конструкции как корректные, но транслирующиеся в код выдачи ошибки. Вот уж где граблей и костылей можно насобирать.
Лично мне понравилась идея интеллектуальных ссылок.
===========================
// main.cpp
#include «fgl_glut_t.h»

//=============================
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
auto my_gl = std::make_unique<MyGL::GlEngine>();
}

=================
StaticFun::StaticFun()
{
auto *bispiral = std::make_unique;

if (!bispiral)
std::exit(0);
}
Sign up to leave a comment.

Articles