C++

индекс
236,50

Spirit. Спиритические сеансы

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

Недавно вышел Boost 1.41, а с ним и Spirit 2, синтаксический анализатор, почти равный по возможностям оригинальным регулярным выражениям Perl. Я просто обязан о нём написать.

Сегодня мы попытаемся запрограммировать простой интерпретируемый язык.

Состав библиотеки

  • Classic. Старая версия Spirit для слоупоков. Продокументирована очень хорошо, но от некоторых хаков и синтаксиса мозги заворачиваются трубочкой.
  • Qi. Генератор парсеров. Собственно, это и есть новый Spirit, и далее в статье он будет так называться.
  • Karma. Генератор генераторов. Позволяет выводить структуры данных с помощью того же самого синтаксиса, которым они разбираются из текста. Из девелоперского maillist можно сделать вывод, что Karma даже быстрее, чем boost::format.
  • Lex. Генератор лексических анализаторов.

Краткое описание

  • Spirit не требует дополнительных шагов компиляции
  • Spirit устанавливать очень просто и этот процесс хорошо задокументирован, причём у большинства программистов на C++ библиотека Boost уже есть.
  • Spirit не принуждает пользоваться лексическим анализатором. Не знаю как вам, а мне лень выписывать списки лексем и обновлять их вместе в кодом парсера.
  • Код на Spirit медленно компилируется. Действительно медленно. Ни gcc, ни cl (Visual Studio 2008) не справились с компиляцией полной грамматики С++, состоящей из 180 правил, выпав с “out of heap”. Для первого Spirit в FAQ есть рекомендации по ускорению компиляции, а именно предложение разделить грамматику на части. В новой версии пока не пробовал, это не очевидно.
  • Spirit не поддерживает левую рекурсию, поэтому некоторые правила придется переписывать вручную. Это просто.

Отдельным абзацом упомяну, что устанавливать Antlr 3, компилировать к нему рантайм для С++ и редактировать исходные коды, чтобы он перестал лепить extern "C" вокруг хидеров в генерируемых файлах, — это самое бестолковое времяпрепровождение для программиста, которое можно придумать. Не используйте эту библиотеку с С++.

Начнём...

… с форм Бекуса-Наура. Мы хотим сделать стандартные арифметические операторы (+, -, *, /), унарные операторы знака (+, -) и оператор присваивания (=) в стиле Си, т.е. возвращающий значение, а также переменные и числа с плавающей точкой. Получаем:

Строка :: = Идентификатор '=' Строка | Выражение
Выражение ::= Слагаемое (('+' | '-') Слагаемое)*
Слагаемое ::= Множитель (('*' | '/') Множитель)*
Множитель ::= Число | Атом | '+' Атом | '-' Атом
Атом ::= '(' Строка ')' | Идентификатор
Идентификатор ::= (Латиница | '_') (Латиница | Цифра | '_')*


Теперь переведём это в синтаксис Spirit.

row = (id >> '=' >> row) | expr;
expr = term >> *( '+' >> term | '-' >> term );
term = fact >> *( '*' >> fact | '/' >> fact );
fact = number | subf | '+' >> subf | '-' >> subf;
subf = '(' >> row >> ')' | id;
id = lexeme[ (alpha | '_') >> *(alnum | '_') ];
number = double_;


Зачем нужен lexeme? Дело в том, что мы хотим, чтобы Spirit за нам сам пропускал лишние пробелы во входных данных. Однако, если он будет пропускать пробелы внутри идентификатора, строка ab cd может считаться идентификатором abcd, что неверно. Поэтому мы запрещаем пропускать пробелы, применяя к правилу идентификатора lexeme. Встроенные парсеры double_, alpha, alnum тоже ничего сложного из себя не представляют.

Однако Spirit должен начинать с какого-то правила. Мы могли бы выбрать row. Но здесь есть одна особенность. Spirit ищет не только полные совпадения, но и частичные. Например, выражение a = b^c может совпасть с правилом row до символа ^, который не будет распознан. Поэтому мы создадим правило следующего вида:

start = row > eoi;

Оператор > означает “обязательно должно следовать”, а eoi — конец данных. start теперь должно полностью совпадать со всеми данными, иначе будет генерироваться exception.

Давайте теперь добавим немного семантики. Spirit генерирует к каждому выражению некоторый атрибут, связанное значение. Например double_ после совпадения будет иметь в себе значение типа double. Эти значения можно менять.

a = (double_ >> '+' >> double_)[_val = _1 + _2];

Квадратные скобки окружают семантические действия. По сути это функции, но благодаря библиотеке Phoenix можно их объявлять в самой строке. Любой человек, хотя бы поверхностно знакомый с функциональным программированием уже признал в них лямбда-функции. Да, это они. Для тех, кто не признал, поясню. _val = _1 + _2 эквивалентно определению функции

double no_name(double _1, double _2) {
    double _val;
    _val = _1 + _2;
    return _val;
}


И эта функция передается в качестве параметра. Смысл выражения для a прост: получить два числа, разделенные знаком +, сложить их атрибуты и вернуть сумму в качестве атрибута выражения.

Если мы не собираемся проводить никаких действий, писать для правила _val = _1 глупо. Для этого есть оператор %=.

a %= b; // то же, что и a = b[_val = _1]

Теперь разберемся с переменными. Воспользуемся встроенным с Spirit средством под названием symbol. Определим таблицу переменных

// char - тип, из которого созданы ключи, double - тип значения
symbols<char, double> names;


В правиле subf мы можем написать теперь names вместо id. names будет совпадать теперь только с идентификаторами, которые были определены. Чтобы определять новые переменные, поступим следующим образом.

row = (id >> '=' >> row)[_val = set_tbl(_1, _2, ref(names))]
    | expr[_val = _1];


Так как names вычислится сразу, как только С++ на него наткнется, мы передаем ссылку, полученную с помощью ref, определенного в Phoenix. Функцию set_tbl нам предстоит написать. К сожалению, С++ воспринимает вызов функции непосредственно, поэтому нам придется схитрить и сделать ленивую функцию.

// Метафункция для Phoenix
struct set_tbl_impl {
    // Устанавливаем type тип возвращаемого значения
    // В нашем случае он всегда такой же, как и у Value
    template <typename Key, typename Value, typename Table>
    struct result {
        typedef Value type;
    };
    // Добавляем в таблицу запись (или обновляем её)
    // Возвращаем значение, чтобы разрешить a = b = 1
    // (и не морочить голову с optional<double>)
    template <typename Key, typename Value, typename Table>
    Value operator()(Key s, Value d, Table table) const {
        table.add(s, d);
        return d;
    }
};
phx::function<set_tbl_impl> set_tbl;


Добавляем обработчик ошибок и ввод из консоли и получаем готовую программу.

Как видите, всё не так уж и сложно. Зачем это нужно? Выбирайте сами. Разбирать INI-файлы c настройками PHP, писать языки, заточенные под вашу конкретную задачу.

Для людей, которые особо тяготеют к регулярным выражениям Perl, я специально делаю перло-спиритовый словарь. Выложу его вскоре в апдейт или отдельным постом. Атомарная группировка, применение модификаторов к части выражения, нечувствительность к регистру, рекурсия, развертка, минимальные квантификаторы и обратные ссылки — всё имеет аналог в Spirit.

P.S. Почти первый пост.
+51
18 января 2010, 23:49
46

комментарии (69)

+3
ki11obyte #
Простите, я не понял. А в чем заключается истинная сфера применения Spirit?
+10
sse #
Построение парсеров + сканеров + лексеров с записью правил прямо в коде в формате, очень близком к BNF
0
odiszapc #
У меня есть некий язык, например PHP. Могу ли я с помощью Spirit достаточно точно распарсить его с целью подсветки?
+2
Rafael_Delon #
Поправьте
… особо тяготеют к регулЯрным выражениям…

P.S. С первым постом!)
+4
udpn #
Спасибо, поправил. Но лучше в ЛС.
+2
michurin #
"синтаксический анализатор, почти равный по возможностям оригинальным регулярным выражениям Perl." — это шутка такой? Регулярные выражения — это конечные автоматы; они используется для лексического разбора; это самый примитивный автомат, использующий конечную память и не способный на лексический анализ (в последних версиях Perl появилась возможность рекурсии в регекспах, но она всё ещё не очень хорошо работает и уж никак не может служить основой для полноценного синтаксического анализатора). Для лексического анализа используются более сложные автоматы или (классически) рекурсивные алгоритмы. Синтаксический анализатор — по определению мощнее (способен анализировать большее число грамматик), чем лексический. Поэтому ваша фраза выглядит так… как буд-то вы не совсем в курсе, про что пишете.
0
udpn #
1. Весьма оригинальный ответ на просьбу писать об ошибках в ЛС. Не смогли долистать до низа?
2. Возможность рекурсии в регекспах очень даже подходит для синтаксического анализа. Вы _действительно_ знакомы с Perl?
3. Если бы вы действительно хотели оспорить моё утверждение, вы бы привели некоторое формальное опровержение. Сказать «о, да этого быть не может» умеет каждый.
0
michurin #
1. Это не ответ на просьбу писать в ЛС, а реплика по теме ветки обсуждения.
2. Своего знакомства с Perl я не скрываю. У меня есть профайл на хабре, есть блог (в котором, кстати, есть прост про регекспы), есть home page, а там пяток стареньких статей на перловые темы. Вы можете сами составить мнение о том знаю ли я Perl или нет.
3. Я вам сказал, что конечный автомат оперирует с конечной памятью. Разборщик же даже элементарной грамматики класса LL(1) оперирует с бесконечной (потенциально) памятью. Достаточно формально? Конечно и бесконечно — заметная для вас разница? Кстати, «если бы вы действительно» © хорошо знали про рекурсию в перловых регекспах, то различали бы старый и новый механизмы организации рекурсии и знали бы, что ни один из этих механизмов так и не включен в PCRE.
+1
bigbes #
Спасибо за хорошую статью.
+1
xiWera #
сравнить спирит с регекспом… гыгы…
0
udpn #
Не с регекспом, а с перловым регескпом. Там средствами интеполяции и встроенного кода можно делать всё то же самое.

А всякие PCRE на это не способны. Так-то.
–1
xiWera #
даже с перловым регекспом не надо сравнивать, совершенно разные вещи. На перловом регекспе можно попытаться «догнать» спирит используя вложенные регеспы внутри основного используя встроенный код, но при этом будет ужас какой по производительности. На спирите же мы получаем конечный автомат.

Кроме того как вы правильно заметили, второй спирит это не только парсеры. Тут перловые регекспы вообще не к месту были упомянуты.

Вобщем не надо сравнивать регекспы, хоть и перловые, созданные для решения совсем другой задачи с полноценным движком парсеров/генераторов.
–1
xiWera #
артем кулябин ниже правильно все написал
0
udpn #
На перловом регекспе можно попытаться «догнать» спирит используя вложенные регеспы внутри основного используя встроенный код

А я вам чуть выше не об этом ли написал?

но при этом будет ужас какой по производительности.

А разница в скорости исполнения здесь ни при чём, я о ней и не говорил.

На спирите же мы получаем конечный автомат

Вы мне не поверите, но и в Perl мы тоже получаем конечный автомат, точнее — НКА.

Кроме того как вы правильно заметили, второй спирит это не только парсеры

А ещё я заметил, что буду называть в статье Spirit'ом именно Spirit::Qi.

созданные для решения совсем другой задачи

Позвольте, для какой другой задачи? =)
–1
udpn #
Извиняюсь за столь подробный отчет. Можете минуснуть.
–1
xiWera #
нет, в перле со вложенными инструкциями мы не получаем конечный автомат.
0
cybery4k #
А ведь довольно интересная темка. Возможно даже при правильном использовании в некоторых случаях избавит от создания велосипедов. Спасибо, убежал тестить!
0
udpn #
Всегда пожалуйста. Будет интересно узнать, чем ваши тесты закончатся. Я ведь сам пока только учусь.
–1
Mad_Fish #
Всё это доказывает, насколько крут C++, но это ещё не повод такую штуку использовать.
Если Вам такое понадобилось в коде, скорее всего you are doing it wrong. Оно очень сильно идёт вразрез с принципом keep it simple.

Если нужно парсить конфиги — это можно сделать гораздо проще, или руками, или использовать xml (или любой другой язык разметки с готовыми библиотеками). Писать на таком компилятор тоже глупо — время компиляции (и количество памяти, необходимой для этого) такого кода стремительно увеличивается при увеличении количества правил. Да, собственно, в статье же пример — компиляция анализатора С++ на такой штуке просто загибается.

В общем… Интересно, но не нужно. Увы.
+1
Volfram #
Использовать Spirit для простых грамматик вроде конфигов — очень удобно. Проще чем использовать xml-библиотеки, про руки, надеюсь, это была шутка. Крупные вещи, пожалуй, да, из-за времени компиляции особо не попишешь.
Разбор на регулярках тоже менее гибок и понятен для чтения.

Впрочем, каюсь, с новым Spirit не успел совсем познакомиться — мне и старый люто запал в душу: )
0
udpn #
Повторюсь: если разбить грамматику на части и каждую часть засунуть в свой собственный юнит (cpp файл), время компиляции падает в разы. Основная проблема в том, что ни в одном современном компиляторе нет вменяемого garbage collector и все промежуточные структуры данных Spirit'а забивают память напрочь. А когда памяти использовано порядочно, она начинает течь в своп.

Ещё можно написать на Spirit'е же программку, которая сама будет делить грамматику на части. Но это дополнительный шаг компиляции, настройка custom build rule в студии, что некошерно.
0
Volfram #
Да, но изящество сразу теряется. Хотя, эстетствование здесь не уместно: )
+1
Mad_Fish #
Про руки — не шутка. Для простых конфигов я уже кучу времени обхожусь одной самописной функцией из 38 строк, которая разбивает строку вида
"arg1 arg2 "complex arg" 'another complex arg' arg3"
и т.д. на std::vector, в который войдут строки "arg1", "arg2", "complex arg", "another complex arg", "arg3".
Достаточно универсально, удобно, и крайне просто. И никаких лишних зависимостей.
А для сложных конфигов, наверное, лучше всего Lua.
0
udpn #
Не стану жертвой неконструктивизма, осуждая функцию, исходного кода которой я не видел, но… Вы действительно уверены, что следующему за вами maintainer'у будет просто разобраться в вашей функции? Она так же хорошо продокументирована и известна?
0
Mad_Fish #
Пока что я использую её в своих личных проектах. Для меня оно решает задачу, и всё на том. И кстати да, даже для себя оно документировано. Никаких проблем.

Вообще я тоже не люблю велосипеды, но для такого простого случая тащить лишнюю зависимость — нет уж, увольте.
+1
Volfram #
Само собой, тащить Boost ради одного Spirit — тяжело. Но я, скажем, и так стараюсь во всех проектах использовать готовенькое из boost по максимуму — лениво же.
0
Volfram #
Был вполне живой рабочий пример, когда формат конфигурационных файлов менялся за время разарботки не один и не два раза — как и просто обрастал понемножку новыми фичами. Где-то на второй раз ушёл с самописного парсера на спирит — количество времени, убитого на рутину, сразу резко уменьшилось.
0
chiaroscuro #
Все верно говорите. Это одна из причин предпочитать декларативное описание вместо императивного, причем не только в разборе текста.
+1
Qnan #
А если нужно парсить что-то более содержательно, например DSL?
0
Qnan #
*содержательное
–2
akzhan #
А что, встроить интерпретатор того же Ruby религия не позволяет?

Для многих C-приложений использование интерпретаторов для DSL — самое обычное дело.
НЛО прилетело и опубликовало эту надпись здесь
–1
ALev #
Тоже не понял этой фразы. Ждём-с разъяснений.
НЛО прилетело и опубликовало эту надпись здесь
+1
udpn #
Объясните.
0
udpn #
Суть была в том, что примитивы обоих языков очень похожи и их легко преобразовывать друг в друга. Посмотрите, пожалуйста, в книгу Дж.Фридла «Регулярные выражения» с. 357, раздел про динамические регулярные выражения. Регекспы Перла очень сильно отличаются от других реализаций, и Spirit похож на них.

Кстати говоря, синтаксис атомарной группировки в Perl (?>...), а в Spirit >. Есть множество других сходств. Скорее, это не случайность, а как раз Spirit был разработан по мотивам Perl.

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

Это позволяет делать вещи, которые невозможно сделать с регулярными выражениями, например, матчить соответствующие скобки.
+1
ALev #
Регулярные выражения — прочно устоявшийся математический и он не может быть расширен. Если в Perl «синтаксис регулярных выражений» расширен, то это уже не регулярные выражения.

Так что не всё так просто, как Вы говорите.
0
udpn #
Безусловно. Это де-факто не регулярные выражения, но устоявшаяся терминология.

К сожалению, большинство «знатоков» в этом топике не обратили внимания на благоразумно дописанное мной уточнение.
0
allter #
Да, но движки регулярных выражений в информатике предназначены для практических целей, а не для экспериментов математиков. Если вы не согласны, то приведите здесь регулярную грамматику для языка, состоящего из строк a… a^1000000.

С помощью «регулярных выражений perl» очень просто, соответствующий regexp == qr/^a{1,100000}$/. А теперь вы приводите грамматику, только хабр не сломайте.
0
allter #
Ошибся с количеством нулей, надо: qr/^a{1,1000000}$/ Впрочем, это лишь для демонстрации.
+1
mraleph #
строго говоря большинство regex движков поддерживают backreferences (и Perl в том числе), а backreferences позволяют распознать грамматики, которые не являются ни регулярными, ни КС.
0
Volfram #
До сих пор не покидает ощущение, что Spirit — это самое безумное и самое гениальное, что есть в boost. До сих по гложет любопытство, _как_ кому-то в голову вообще пришла мысль попытаться сделать что-то подобное на С++.
0
xiWera #
fusion безумнее :)
+2
chiaroscuro #
Объясняю:
— человек нудно, долго и терпеливо писал парсеры, которые, несмотря на все его усилия, содержали баги, были медленными, не поддавались поддержке и изменениям, и т.п. (вероятно, все это одновременно)
— и вдруг в голову ему пришла идея: «елы-палы, чож я парюсь, посмотреть нада, а другие-то как делают»?
— он сел за умные книжки, почитал хороших, грамотных статей по формальным языкам, генеративным грамматикам и прочему сопутствующему
— после чего на него снизошло озарение (когда кирпичики наконец сложились в его голове), что такое формальные языки и проблема разбора
— а потом он наткнулся на комбинаторные парсеры (иными словами, рекурсивный спуск без лишнего boilerplate)…

Как-то так зародилась идея Spirit, но я могу и ошибаться. :)
0
energycsdx #
есть пару нюансов, вопервых, заметно увиличивается время компиляции, вовторых, таким образом строится рекурсивный парсер и если внем ошибка плюс особые входные данные то можно легко получить безконечный цикл
–1
udpn #
Я упомянул и про время компиляции, и про необходимость преобразования левой рекурсии. В чём дело-то?
+1
energycsdx #
просмотрел
0
impwx #
Мне все-таки кажется, что необходимо хоть раз попробовать написать парсер самому :) Это как Hello World на ассемблере — может толком никуда не везти, но чисто из любопытства, чтобы примерно понимать как это работает.

А статья замечательная. Мне понравилось. И Spirit, конечно, очень впечатляющая штука. Такую вещь реализовать на перегрузке операторов — это нечто.

Кстати, вопрос. В БНФ везде использовались односимвольные идентификаторы — =, +, — в одинарных кавычках. В двойных-то можно?
0
chiaroscuro #
> Мне все-таки кажется, что необходимо хоть раз попробовать написать парсер самому :) Это как Hello World на ассемблере — может толком никуда не везти, но чисто из любопытства, чтобы примерно понимать как это работает.

Вы что, как можно, это же непрактично! :) /irony

> Кстати, вопрос. В БНФ везде использовались односимвольные идентификаторы — =, +, — в одинарных кавычках. В двойных-то можно?

Можно, главное, чтобы Вы и те, кто будет читать такую запись БНФ, понимали, о чем речь.
–1
impwx #
я имею в виду, что например == или ++ в одинарные кавычки не засунешь
0
chiaroscuro #
> я имею в виду, что например == или ++ в одинарные кавычки не засунешь

Хм, Вы про БНФ как оно есть, или же про код на Spirit?

Если про Spirit, то там есть комбинатор, который описывает строковый литерал (а не символьный), его и надо использовать для == или ++.
0
udpn #
Да, можно и в двойных. Spirit предпочтений не имеет, просто я не хотел тратить место на нуль-терминированную строку. По привычке.
0
Lazin #
Инструмент не должен быть сложнее той задачи, которую он решает.
Писать код, использующий EDSL имитирующий EBNF синтаксис и описывающий контекстно свободную грамматику парсера конфигов, который будет выполняться во время компиляции и генерировать код этого самого парсера… что-то тут лишнее, не находите?
0
dshalkhakov #
>Инструмент не должен быть сложнее той задачи, которую он решает.

Это аксиома?

>Писать код, использующий EDSL имитирующий EBNF синтаксис и описывающий контекстно свободную грамматику парсера конфигов, который будет выполняться во время компиляции и генерировать код этого самого парсера… что-то тут лишнее, не находите?

Ничего лишнего не вижу. Наоборот, я считаю, очень удобно описывать парсер в близком к спецификации виде.
Программы должны быть понятны прежде всего людям, а только потом машинам.
0
dshalkhakov #
Ох черт, напарил с тэгами :\
0
Lazin #
Ничего лишнего не вижу. Наоборот, я считаю, очень удобно описывать парсер в близком к спецификации виде.
Программы должны быть понятны прежде всего людям, а только потом машинам.

Это если есть спецификация, я писал о парсере конфигов, для простых задач это overkill, не находите? К тому-же порой бывает очень сложно понять, что делает та или иная строчка кода, код выглядит просто, но делает очень много чего.
+1
dshalkhakov #
>Это если есть спецификация, я писал о парсере конфигов

В этом случае код *является* спецификацией, в данном случае неких конфигов.

>для простых задач это overkill, не находите?

Для совсем простых — да. Однако, простые программы всегда растут (либо заменяются теми программами, которые растут) и становятся большими и сложными. В итоге начальная *излишняя* простота может выйти боком.

>К тому-же порой бывает очень сложно понять, что делает та или иная строчка кода, код выглядит просто, но делает очень много чего.

В случае со Spirit (вы ведь про него говорите?) нужно понимать как работают комбинаторные парсеры, и все. Не так уж и сложно, на самом деле.
0
Lazin #
По поводу времени компиляции, мне уже давно хочется избавиться от boost::fusion в одном из проектов, так как его сборка требует большого количества памяти. Каждое инстанциирование шаблона, это дополнительные накладные расходы времени компиляции, поэтому я бы хорошо подумал, прежде чем использовать spirit в чем-то серьезном. И вообще, в последнее время, мне все чаще кажется, что мета-программирование на срр, это что-то неправильное. Зачастую отладка такого кода — весьма непростое занятие. Оптимизация времени компиляции или чтение сообщений об ошибках в общем то тоже.
0
udpn #
Мне зачастую кажется, что современные языки программирования это вообще что-то неправильное. (Не для холивора, конечно.)
0
dshalkhakov #
>Мне зачастую кажется, что современные языки программирования это вообще что-то неправильное.

[жесточайший оффтоп]
Например, какие? (Не для холивора, просто интересно мнение).
И как вы определяете «правильность»? Это что-то неуловимое, интуитивное, или возможно какое-то более-менее строгое определение?
[/жесточайший оффтоп]
+2
udpn #
Видел в английской википедии список характеристик «хороших» языков программирования согласно какому-то известному лингивисту, но вот сходу найти ссылку не смог.

Проблема кроется в том, что внутренняя логика языков программирования довольно сложна. Исходники компиляторов настолько неудобоваримы, что даже при всём желании нельзя быстро добавить новые оптимизации или возможности. Это сдерживает их эволюцию. Вывод типов, статический анализ, суперкомпиляция и прочие фичи уже давно должны стать стандартом для всех языков.

Нужны языки нового поколения. Проблема только в bootstrapping'е.
0
mraleph #
вот кстати вопрос, который меня очень интересует: почему производители С++ компиляторов не озаботятся и не разгонят таки свои компиляторы…
0
afaber #
Потому что сама по себе задача написать соответствующий стандарту компилятор C++ дьявольски сложна, и даже на нее разработчикам не хватает времени. А на оптимизацию компилятора — тем более не хватает.
Да и в самом стандарте есть вещи, которые негативно сказываются на времени компиляции, и поменять их уже нельзя — груз совместимости давит…
+1
mraleph #
уточню свою позицию: уже есть компиляторы компилирующие приложения, использующие скажем boost::fusion, могли бы и инвестировать часть ресурсов в его разгон.

у меня конечно, есть гипотеза, что всему виной качество кода этих компиляторов — ничего нормально не разгонишь, пока не отрефакторишь весь компилятор…
0
Lazin #
что-то подобное обещают в clang, там компиляция сложного шаблонного кода будет очень быстрой
0
voidlizard #
А где можно ознакомиться с полной формальной грамматикой С++? Идеально, если в виде исходников для спирита.
0
udpn #
Стандарт языка С++. Драфт вы запросто найдете в первой строке Google, а официальную платную (пиратскую) версию можно найти в торрентах.

Приложение А содержит грамматику целиком. Только в драфте забыли определить правило, совпадающее с любым оператором, но оно легко восстанавливается по списку операторов в Википедии.

В виде исходников на Spirit я пока дать не готов, как раз потому что не могу их даже скомпилировать по объективным причинам. Об отладке в рантайме речи не идет.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.