Pull to refresh
56
0
Сергей Садовников @FlexFerrum

Пользователь

Send message
Нда. Как раз тот случай, когда жизнь — это боль… :( А ведь для своего времени Borland C++ был очень хорошим компилятором. Правда, не таким хорошим, как Watcom… :)
Для того, чтобы эффективно использовать clang в IDE (как парсер) — clang надо правильно готовить и патчить. Как правильно написала Анастасия, кланг — это, в первую очередь, компилятор. И ведёт он себя с кодом как компилятор, а не как IDE — более строго. И работает он, мягко скажем, не быстро. А чтобы заставить работать быстро — надо колдовать. Да и после колдовства (если не лезть глубоко в реализацию парсера) некоторые фишки просто не будут работать. Например, о фичах типа «создать декларацию функции из определения» или «создать декларацию из использования» можно будет забыть — кланг для таких кусков кода просто не построит адекватную AST (код для него, как компилятора, будет невалидный). Про адекватную работу код-коплита внутри шаблонов тоже можно будет забыть, передав привет двухфазному парсингу. В общем, дружить clang и IDE — весело. И больно.
Ну, такой вариант is_class у нас успешно работает на довольно широкой линейке компиляторов — gcc начиная с 3.9, MSVC начиная с 2008, clang и некоторая экзотика. То есть я допускаю, что существуют компиляторы, которые поведут себя в этом случае иначе, но, к счастью, давно ими не приходилось пользоваться.

Буду иметь в виду. Спасибо.

Внимательный читатель уже говорил, что надо делать. В прошлый раз. :) Вот, собственно, как можно отличить enum от int'а:
#include <iostream>

struct true_type
{
	enum {value = 1};
};

struct false_type
{
	enum {value = 0};	
};

template<typename T>
struct is_integer_impl : false_type {};

#define DECLARE_INTEGER(T) \
template<> \
struct is_integer_impl<T> : true_type {}

DECLARE_INTEGER(char);
DECLARE_INTEGER(unsigned char);
DECLARE_INTEGER(signed char);
DECLARE_INTEGER(unsigned short);
DECLARE_INTEGER(signed short);
DECLARE_INTEGER(unsigned int);
DECLARE_INTEGER(signed int);
DECLARE_INTEGER(unsigned long);
DECLARE_INTEGER(signed long);

template<typename T>
struct is_integer : is_integer_impl<T> {};

template<typename T>
struct is_class
{
	typedef char yes;
	typedef int no;
	
	template<class U>
	static yes doCheck(void (U::*)());
	template<class U>
	static no doCheck(...);
	
	enum {value = (sizeof(doCheck<T>(0)) == sizeof(yes))};
};

template<typename T>
struct is_enum
{
	enum {value = !is_integer<T>::value && !is_class<T>::value};
};

enum TestEnum
{
	Item1,
	Item2
};

struct TestStruct
{
	
};

int main() 
{
	std::cout << is_integer<int>::value << std::endl;
	std::cout << is_enum<int>::value << std::endl;
	std::cout << is_class<int>::value << std::endl;
	std::cout << std::endl;
	std::cout << is_integer<TestStruct>::value << std::endl;
	std::cout << is_enum<TestStruct>::value << std::endl;
	std::cout << is_class<TestStruct>::value << std::endl;
	std::cout << std::endl;
	std::cout << is_integer<TestEnum>::value << std::endl;
	std::cout << is_enum<TestEnum>::value << std::endl;
	std::cout << is_class<TestEnum>::value << std::endl;

	return 0;
}

ideone.com/hqevaJ

В данном случае ручное (явное) перечисление целочисленных типов играет сильно на руку — подавляет компиляторную магию.
Дальнейшие исследования показали, что питоновская версия выигрывает только на шаблоне такого характера: "{% for i in range(20)%} {{i}} {%endfor%}"
Но стоит его модифицировать так: "{% for i in range(20)%} {{i}}-{{loop.index}} {%endfor%}" — и всё. Снова проигрывает.
По-быстрому накидал пару кейсов. Для C++:
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
Ещё про вызовы. Внутри используется структура типа такой:
struct CallParams
{
    std::unordered_map<std::string, Value> kwParams;
    std::vector<Value> posParams;
};
Мне, в общем, тоже не очень нравится 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

А вот и сам пропозал от Саттера: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf

Information

Rating
Does not participate
Location
Москва и Московская обл., Россия
Date of birth
Registered
Activity