Нда. Как раз тот случай, когда жизнь — это боль… :( А ведь для своего времени Borland C++ был очень хорошим компилятором. Правда, не таким хорошим, как Watcom… :)
Для того, чтобы эффективно использовать clang в IDE (как парсер) — clang надо правильно готовить и патчить. Как правильно написала Анастасия, кланг — это, в первую очередь, компилятор. И ведёт он себя с кодом как компилятор, а не как IDE — более строго. И работает он, мягко скажем, не быстро. А чтобы заставить работать быстро — надо колдовать. Да и после колдовства (если не лезть глубоко в реализацию парсера) некоторые фишки просто не будут работать. Например, о фичах типа «создать декларацию функции из определения» или «создать декларацию из использования» можно будет забыть — кланг для таких кусков кода просто не построит адекватную AST (код для него, как компилятора, будет невалидный). Про адекватную работу код-коплита внутри шаблонов тоже можно будет забыть, передав привет двухфазному парсингу. В общем, дружить clang и IDE — весело. И больно.
Ну, такой вариант is_class у нас успешно работает на довольно широкой линейке компиляторов — gcc начиная с 3.9, MSVC начиная с 2008, clang и некоторая экзотика. То есть я допускаю, что существуют компиляторы, которые поведут себя в этом случае иначе, но, к счастью, давно ими не приходилось пользоваться.
Дальнейшие исследования показали, что питоновская версия выигрывает только на шаблоне такого характера: "{% for i in range(20)%} {{i}} {%endfor%}"
Но стоит его модифицировать так: "{% for i in range(20)%} {{i}}-{{loop.index}} {%endfor%}" — и всё. Снова проигрывает.
TEST(PerfTests, PlainText)
{
std::string source = "Hello World from Parser!";
Template tpl;
ASSERT_TRUE(tpl.Load(source));
jinja2::ValuesMap params;
std::cout << tpl.RenderAsString(params) << std::endl;
std::string result;
for (int n = 0; n < Iterations * 100; ++ n)
result = tpl.RenderAsString(params);
std::cout << result << std::endl;
}
TEST(PerfTests, SimpleSubstituteText)
{
std::string source = "{{ message }} from Parser!";
Template tpl;
ASSERT_TRUE(tpl.Load(source));
jinja2::ValuesMap params = {{"message", "Hello World!"}};
std::cout << tpl.RenderAsString(params) << std::endl;
std::string result;
for (int n = 0; n < Iterations * 100; ++ n)
result = tpl.RenderAsString(params);
std::cout << result << std::endl;
}
Для питона:
import unittest
from jinja2 import Template
class PerfTestCase(unittest.TestCase):
def _test_plait_text_render(self):
tpl = Template('Hello World from Parser!')
result = tpl.render()
print (result)
for n in range(0, 10000 * 100):
tpl.render()
print (result)
def test_simple_substitute_text(self):
tpl = Template('{{ message }} from Parser!')
result = tpl.render(message='Hello World!')
print (result)
for n in range(0, 10000 * 100):
tpl.render(message='Hello World!')
print (result)
Цифры вышли такие. Для C++: [----------] 2 tests from PerfTests
[ RUN ] PerfTests.PlainText
Hello World from Parser!
Hello World from Parser!
[ OK ] PerfTests.PlainText (2084 ms)
[ RUN ] PerfTests.SimpleSubstituteText
Hello World! from Parser!
Hello World! from Parser!
[ OK ] PerfTests.SimpleSubstituteText (2581 ms)
[----------] 2 tests from PerfTests (4665 ms total)
Для питона: d:\projects\work\Personal\Jinja2Cpp\python_jinja2>python -m unittest perf_test.PerfTestCase._test_plait_text_render
Hello World from Parser!
Hello World from Parser!
.
----------------------------------------------------------------------
Ran 1 test in 5.474s
OK
d:\projects\work\Personal\Jinja2Cpp\python_jinja2>python -m unittest perf_test.PerfTestCase.test_simple_substitute_text
Hello World! from Parser!
Hello World! from Parser!
.
----------------------------------------------------------------------
Ran 1 test in 6.543s
OK
Запускалось на одной машине. C++-версия — сборка MSVC 14.0, Release, x64. Питон — версии 3.5.1
Мне, в общем, тоже не очень нравится boost::variant на границе внешнего интерфейса. В том числе из соображений бинарной совместимости. Но адекватной замены ему нет, разве что собственный велосипед тащить или variant-light от Martin Moene ( github.com/martinmoene/variant-lite ). Возможно, в эту сторону посмотрю, т. к. в случае C++17 он «мягко» переключается на стандартный вариант. А вот из потрохов boost выпилить всё равно не выйдет — достаточно много завязок на него.
На счёт функций — такой стиль вызова будет несколько, гм, выбиваться из общей схемы, которая допускает и позиционные, и именованные параметры.
1. Я храню posFrom и posTo. То есть тот же спан. Отдельно веду учёт разрывов строк, чтобы по позиции начала можно было определить строку и колонку. Всё делается с привязкой с «ширине» символа.
2, 3. Сырой текст не парсится в принципе. Регулярка мне для того и нужна, чтобы отделить одно от другого. Парсится только то, что находится внутри скобок.
4. А чем boost::variant плох? На счёт позволить значению быть функцией — тут интересные аспекты возникают. Пробросить то несложно (reflection по этому принципу здесь и построен). Только авторы шаблонов захотят в эту функцию параметры передавать. А вот тут возникают интересности.
Прошу прощения у комментатора tgregory, который поделился соображениями и опытом. При ответе промахнулся по ссылке и вместо «Подтвердить» нажал «Отклонить». Интерфейс, разумеется, подтверждения не спросил.
Восстанавливаю из почты:
Делал нечто подобное, правда скорее придерживался стиля Go(text/template) (с изменениями вызова функций и индексации элементов). Jinja вроде похож. Вот пара идей, которые вам могут показаться полезными:
1. В токенах имеет смысл указывать span, т.е. от и до, причём позицию удобно отслеживать сразу в виде {byte_pos, line, column} (обратите внимание, что для того чтобы правильно считать column понадобится рудиментарная поддержка юникод). Это пригодится при выводе сообщений об ошибках.
2. В грамматику языка ввести сырой текст как терминал.
3. Дать лексеру два состояния: парсинг выражений; парсинг сырого текста. Читать лексером посимвольно и дать ему понимание что состояние надо переключать в момент порождения токенов начала/конца выражений.
4. Избавиться от boost::variant и позволить Value быть функцией (с С++11 можно на шаблонах заворачивать произвольные функции и лямбды, давая возможность их проброса в движок в виде Value)
Я догадываюсь, что существуют такого рода пакеты. Но конкретно с Jinja2 два момента:
1. Оригинальная (питоновская) jinja, как библиотека — несколько больше, чем просто шаблонизатор. Там довольно богатое API, хотя и всё крутится вокруг рендеринга шаблонов.
2. В любом случае говорить об этом можно не раньше, чем померю перформанс и реализую все возможности языка шаблонов.
1. Ещё нет. Но к концу публикации (должна быть ещё минимум одна часть) сделаю. Я знаю, что эта информация интересна. Да и самому интересно.
2. Честно говоря, пока особого смысла в таком бэкпорте не вижу. У Python уже есть Jinja2. Если у вас есть какие-то аргументы к такому биндингу — внимательно выслушаю.
По поводу первого комментария — в смысле просто сделать шаблон где сразу указывается жестко что «вот этот тип беззнаковый, столько то бит», а «этот тип знаковый, столько то бит»?
Ага.
По поводу второй части с long. Так это по-моему широко известная особенность.
Посмотрел на код. На объяснения. Понял, что, похоже, было проще напедалить шаблон:
template<typename T, int typeKind, bool isSigned, int bits, /… />
struct BuiltinTypeTraits {};
И тупо специализировать этот шаблон для всех встроенных типов, чем городить огород из детекторов. Кода бы получилось меньше.
И с long (как short, signed и unsigned) есть одна малоизвестная особенность: всё это модификаторы интегрального типа, а не часть его имени. :) То есть записи long signed int/long signed/long/signed long/signed long int значат одно и то же. :D
А когда PVS Studio можно будет интегрировать в IDE так же, как это делается с clang-tidy и clang static analyzer в случае с CLion и QtCreator? :) Да и, прямо скажем, лучше пользовать сразу несколько средств, а не какое-то одно. Как мы, например. :)
Полагаю, что это всё из-за того, что далеко не со всеми сущностями в C++ можно работать в объектном стиле. А для функций (шаблонных) легко работает template argument deduction. Да и синтаксис универсален. Тут даже такие размышления есть: A bit of background for the unified call proposal
Буду иметь в виду. Спасибо.
ideone.com/hqevaJ
В данном случае ручное (явное) перечисление целочисленных типов играет сильно на руку — подавляет компиляторную магию.
{% for i in range(20)%} {{i}} {%endfor%}
"Но стоит его модифицировать так: "
{% for i in range(20)%} {{i}}-{{loop.index}} {%endfor%}
" — и всё. Снова проигрывает.Для питона:
Цифры вышли такие. Для C++:
[----------] 2 tests from PerfTests
[ RUN ] PerfTests.PlainText
Hello World from Parser!
Hello World from Parser!
[ OK ] PerfTests.PlainText (2084 ms)
[ RUN ] PerfTests.SimpleSubstituteText
Hello World! from Parser!
Hello World! from Parser!
[ OK ] PerfTests.SimpleSubstituteText (2581 ms)
[----------] 2 tests from PerfTests (4665 ms total)
Для питона:
d:\projects\work\Personal\Jinja2Cpp\python_jinja2>python -m unittest perf_test.PerfTestCase._test_plait_text_render
Hello World from Parser!
Hello World from Parser!
.
----------------------------------------------------------------------
Ran 1 test in 5.474s
OK
d:\projects\work\Personal\Jinja2Cpp\python_jinja2>python -m unittest perf_test.PerfTestCase.test_simple_substitute_text
Hello World! from Parser!
Hello World! from Parser!
.
----------------------------------------------------------------------
Ran 1 test in 6.543s
OK
Запускалось на одной машине. C++-версия — сборка MSVC 14.0, Release, x64. Питон — версии 3.5.1
На счёт функций — такой стиль вызова будет несколько, гм, выбиваться из общей схемы, которая допускает и позиционные, и именованные параметры.
2, 3. Сырой текст не парсится в принципе. Регулярка мне для того и нужна, чтобы отделить одно от другого. Парсится только то, что находится внутри скобок.
4. А чем boost::variant плох? На счёт позволить значению быть функцией — тут интересные аспекты возникают. Пробросить то несложно (reflection по этому принципу здесь и построен). Только авторы шаблонов захотят в эту функцию параметры передавать. А вот тут возникают интересности.
Восстанавливаю из почты:
1. Оригинальная (питоновская) jinja, как библиотека — несколько больше, чем просто шаблонизатор. Там довольно богатое API, хотя и всё крутится вокруг рендеринга шаблонов.
2. В любом случае говорить об этом можно не раньше, чем померю перформанс и реализую все возможности языка шаблонов.
2. Честно говоря, пока особого смысла в таком бэкпорте не вижу. У Python уже есть Jinja2. Если у вас есть какие-то аргументы к такому биндингу — внимательно выслушаю.
Ага.
Ну мало ли, вдруг кто не знает? :)
Посмотрел на код. На объяснения. Понял, что, похоже, было проще напедалить шаблон:
template<typename T, int typeKind, bool isSigned, int bits, /… />
struct BuiltinTypeTraits {};
И тупо специализировать этот шаблон для всех встроенных типов, чем городить огород из детекторов. Кода бы получилось меньше.
И с long (как short, signed и unsigned) есть одна малоизвестная особенность: всё это модификаторы интегрального типа, а не часть его имени. :) То есть записи long signed int/long signed/long/signed long/signed long int значат одно и то же. :D
А вот и сам пропозал от Саттера: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf