Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Ikemefula, Вы писали:
I>>Compile-time парсер на дженериках: https://github.com/codemix/ts-sql/blob/master/README.md
VD>Пожалуй даже по круче. Это ближе к макрам. Но вот только пример не работает. Перешел по ссылке. Там выдаётся 5 ошибок и ничего не работает.
VD>Ты это все смог запустить?
Запускать там нечего, это чисто типы, а потому дебажить и запускать там ровно нечего. Ошибки — это скорее варнинги, что значение пишется, но не читается. На компиляцию не влияет.
Кликаешь по ссылке "See the full demo on the TypeScript playground!" и смотришь в левой панели как выводится тип для вот такого объявления:
type EX2 = Query<
"SELECT firstName, lastName FROM people WHERE isChild = true",
DBv1
>;
Наводишь мышом и всё показывается что там на самом деле нашлось.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, VladD2, Вы писали:
I>>>Compile-time парсер на дженериках: https://github.com/codemix/ts-sql/blob/master/README.md VD>>Пожалуй даже по круче. Это ближе к макрам.
EP>Чем именно круче и ближе к макросам? Строчки можно парсить в compile-time и в C++
Здравствуйте, bnk, Вы писали:
EP>>Чем именно круче и ближе к макросам? Строчки можно парсить в compile-time и в C++ bnk>И автокомплит по ним будет? Вот как в примере
Только в этом конкретном пример запрос не виде строчек, что и к лучшему, поэтому будет работать родной автокомплит и для самого запроса, а не только результата:
(в Nemerle вроде был автокомплит и для DSL'ей в строчках? или только подсветка7)
Но если хочется, то запрос можно и виде строчки сделать, так как их можно обрабатывать
Здравствуйте, Ikemefula, Вы писали:
I>Наводишь мышом и всё показывается что там на самом деле нашлось.
До этого я не допер. А без этого результат не виден.
Честно говоря не думал, что в ТС типы могут содержать значения.
И совершенно не понятен смысл всего этого. Зачем нужен тип с результатом? Вот если бы это давало возможность типизированной работы с БД — это было бы очень полезно и удобно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Чем именно круче и ближе к макросам? Строчки можно парсить в compile-time и в C++
Тут в компайлтайме парсится и типизируется строка.
В плюсах строку в качестве параметра типа разве можно передать и обработать?
Правда данный пример несколько похож на сферического коня в вакууме, так как он не предоставляет типизированный доступ к данным в виде макроса, а обрабатывает данные прямо во время компиляции и выдает результат в виде типа. Что на практике не нужно. Но, наверно, подход можно и к обработке данных из БД приспособить, как это сделано в Nemerle: https://github.com/rsdn/nemerle/wiki/SQL-macros
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
EP>>Чем именно круче и ближе к макросам? Строчки можно парсить в compile-time и в C++ VD>Тут в компайлтайме парсится и типизируется строка. VD>В плюсах строку в качестве параметра типа разве можно передать и обработать?
Да, начиная с C++11 можно. В последующих версиях это очеловечивалось, и теперь можно напрямую type<"param">.
Есть даже библиотека генератор парсеров:
Metaparse is a parser generator library for template metaprograms. The purpose of this library is to support the creation of parsers that parse at compile time. This library is intended to be used for embedded domain specific language creation for C++. The input of the generated parser is a compile time string
VD>Правда данный пример несколько похож на сферического коня в вакууме, так как он не предоставляет типизированный доступ к данным в виде макроса, а обрабатывает данные прямо во время компиляции и выдает результат в виде типа. Что на практике не нужно.
Ну пример действительно сферический, но сам подход вполне жизнеспособный: например задать регулярное выражение в виде compile-time строки, и получить тип представляющий оптимизированный парсер для него.
VD>Но, наверно, подход можно и к обработке данных из БД приспособить, как это сделано в Nemerle: VD>https://github.com/rsdn/nemerle/wiki/SQL-macros
Ну в примере TS выше, как я понял — вся база данных, включая все данные, задаётся в compile-time, что действительно не очень-то и нужно — так как если все данные уже есть в compile-time, к ним доступ через sql, да ещё и в compile-time не особо-то нужен.
А вот приспособить это для типизированного доступа к БД а-ля SQL-macros — должно быть вполне реализуемо.
Здравствуйте, Evgeny.Panasyuk, Вы писали: EP>Да, начиная с C++11 можно. В последующих версиях это очеловечивалось, и теперь можно напрямую type<"param">.
Не знал. Интересно. EP>Есть даже библиотека генератор парсеров: EP>
EP>Metaparse is a parser generator library for template metaprograms. The purpose of this library is to support the creation of parsers that parse at compile time. This library is intended to be used for embedded domain specific language creation for C++. The input of the generated parser is a compile time string
Вот это не надо. Это цитата из буста. Он не строки парсит, а имеет говногодный DSL и к тому же порождает кучу промжуточного кода, так что банальный парсер выражений дает гигабайты выхлопа.
Ты мне покажи как в шаблоне, во внемя компиляции, можно эту строку разобрать, выдрать из нее SQL-выражение, и превратить его в код работающий с ним.
using Nitra;
using Nitra.Runtime;
using Nemerle;
using Nemerle.Collections;
using System.Collections.Generic;
language SimpleCalc
{
syntax module SimpleCalcSyntax start rule Start;
}
syntax module SimpleCalcSyntax
{
using Nitra.Core;
[StartRule]
syntax Start = Expr !Any { [Cached] Value() : double = Expr.Value(); }
regex Digits = ['0'..'9']+;
regex Id = ['a' .. 'z', 'A' .. 'Z']+;
[StartRule]
syntax Expr
{
Value() : double;
missing Value = double.NaN;
| [SpanClass(Number)]
Num = Digits { override Value = double.Parse(GetText(this.Digits)); }
| Call = Id '(' Id Id ')' { override Value = 42.0; }
| Rounds = '(' Expr ')' { override Value = Expr.Value(); }
precedence Additive:
| Add = Expr sm '+' sm Expr { override Value = Expr1.Value() + Expr2.Value(); }
| Sub = Expr sm '-' sm Expr { override Value = Expr1.Value() - Expr2.Value(); }
precedence Multiplicative:
| Mul = Expr sm '*' sm Expr { override Value = Expr1.Value() * Expr2.Value(); }
| Div = Expr sm '/' sm Expr { override Value = Expr1.Value() / Expr2.Value(); }
| Mod = Expr sm '%' sm Expr { override Value = Expr1.Value() % Expr2.Value(); }
precedence Power:
| Pow = Expr sm '^' sm Expr right-associative
{ override Value = System.Math.Pow(Expr1.Value(), Expr2.Value()); }
precedence Unary:
| Neg = '-' Expr { override Value = -Expr.Value(); }
}
}
А не таким говнокодом:
Грамматика на Бусте
#include <boost/metaparse/repeated.hpp>
#include <boost/metaparse/sequence.hpp>
#include <boost/metaparse/lit_c.hpp>
#include <boost/metaparse/last_of.hpp>
#include <boost/metaparse/first_of.hpp>
#include <boost/metaparse/space.hpp>
#include <boost/metaparse/int_.hpp>
#include <boost/metaparse/foldl_reject_incomplete_start_with_parser.hpp>
#include <boost/metaparse/one_of.hpp>
#include <boost/metaparse/get_result.hpp>
#include <boost/metaparse/token.hpp>
#include <boost/metaparse/entire_input.hpp>
#include <boost/metaparse/string.hpp>
#include <boost/metaparse/build_parser.hpp>
#include <boost/mpl/apply_wrap.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/back.hpp>
#include <boost/mpl/plus.hpp>
#include <boost/mpl/minus.hpp>
#include <boost/mpl/times.hpp>
#include <boost/mpl/divides.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/bool.hpp>
using boost::metaparse::sequence;
using boost::metaparse::lit_c;
using boost::metaparse::last_of;
using boost::metaparse::first_of;
using boost::metaparse::space;
using boost::metaparse::repeated;
using boost::metaparse::build_parser;
using boost::metaparse::int_;
using boost::metaparse::foldl_reject_incomplete_start_with_parser;
using boost::metaparse::get_result;
using boost::metaparse::one_of;
using boost::metaparse::token;
using boost::metaparse::entire_input;
using boost::mpl::apply_wrap1;
using boost::mpl::fold;
using boost::mpl::front;
using boost::mpl::back;
using boost::mpl::plus;
using boost::mpl::minus;
using boost::mpl::times;
using boost::mpl::divides;
using boost::mpl::eval_if;
using boost::mpl::bool_;
using boost::mpl::equal_to;
using boost::mpl::bool_;
/*
* The grammar
*
* expression ::= plus_exp
* plus_exp ::= prod_exp ((plus_token | minus_token) prod_exp)*
* prod_exp ::= int_token ((mult_token | div_token) int_token)*
*/typedef token<lit_c<'+'> > plus_token;
typedef token<lit_c<'-'> > minus_token;
typedef token<lit_c<'*'> > mult_token;
typedef token<lit_c<'/'> > div_token;
typedef token<int_> int_token;
template <class T, char C>
struct is_c : bool_<T::type::value == C> {};
struct eval_plus
{
template <class State, class C>
struct apply :
eval_if<
is_c<front<C>, '+'>,
plus<typename State::type, typename back<C>::type>,
minus<typename State::type, typename back<C>::type>
>
{};
};
struct eval_mult
{
template <class State, class C>
struct apply :
eval_if<
is_c<front<C>, '*'>,
times<typename State::type, typename back<C>::type>,
divides<typename State::type, typename back<C>::type>
>
{};
};
typedef
foldl_reject_incomplete_start_with_parser<
sequence<one_of<mult_token, div_token>, int_token>,
int_token,
eval_mult
>
prod_exp;
typedef
foldl_reject_incomplete_start_with_parser<
sequence<one_of<plus_token, minus_token>, prod_exp>,
prod_exp,
eval_plus
>
plus_exp;
typedef last_of<repeated<space>, plus_exp> expression;
typedef build_parser<entire_input<expression> > calculator_parser;
#ifdef _STR
# error _STR already defined
#endif
#define _STR BOOST_METAPARSE_STRING
#if BOOST_METAPARSE_STD < 2011
int main()
{
using std::cout;
using std::endl;
using boost::metaparse::string;
cout
<< apply_wrap1<calculator_parser, string<'1','3'> >::type::value << endl
<<
apply_wrap1<
calculator_parser, string<' ','1','+',' ','2','*','4','-','6','/','2'>
>::type::value
<< endl
;
}
#else
int main()
{
using std::cout;
using std::endl;
cout
<< apply_wrap1<calculator_parser, _STR("13")>::type::value << endl
<< apply_wrap1<calculator_parser, _STR(" 1+ 2*4-6/2")>::type::value << endl
;
}
#endif
EP>Ну пример действительно сферический, но сам подход вполне жизнеспособный: например задать регулярное выражение в виде compile-time строки, и получить тип представляющий оптимизированный парсер для него.
Вот и хочется посмотреть на парсер полученный в компайлтайме, а не сфероконя на типах. EP>Ну в примере TS выше, как я понял — вся база данных, включая все данные, задаётся в compile-time, что действительно не очень-то и нужно — так как если все данные уже есть в compile-time, к ним доступ через sql, да ещё и в compile-time не особо-то нужен.
А наг это кому-то нужно? Нужно как раз сходить в реальную БД, прочесть там метаданные, по ним сгенерить типобезопасный код доступа к данынм и позволить его удобно вызвать в коде. Вот пример на макрах Немерла именно это и делает. И я видел как он в продакшене применяется внутри антивируса.
А это сфероконь на практике не применимый. Типа показали силу системы типов. Ну, да круто. Но бесполезно. EP>А вот приспособить это для типизированного доступа к БД а-ля SQL-macros — должно быть вполне реализуемо.
Вот обычно на этом и начинаются трудности, так как системы типов не особо приспособлены для доступа к внешним источникам данных. Для макры это не проблема, так она не не более чем программа запускаемая во время компиляции. А для приседания на типах — проблема.
По этому хочется таки видеть хотя бы игрушечный, но практически полезный пример. А не сфероконика.
Для тех же плюсов мне интересно как разобрать строку из параметра типов? И можно ли сходить за метаданными скажем в Sqlight? Почти уверен, что второе невозможно. На счет первого тоже есть сомнения, но это более реалистично.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>До этого я не допер. А без этого результат не виден.
VD>Честно говоря не думал, что в ТС типы могут содержать значения.
Тип может быть задан перечислением значений, частный случай — одно значение.
VD>И совершенно не понятен смысл всего этого. Зачем нужен тип с результатом? Вот если бы это давало возможность типизированной работы с БД — это было бы очень полезно и удобно.
Поддержка средств разработки, например. Ты пишешь селектор, а получаешь реальный тип.
Здравствуйте, Ikemefula, Вы писали:
I>Тип может быть задан перечислением значений, частный случай — одно значение.
Для простых типов это имеет смысл. А какой смысл это для сложных списков? Я правильно понимаю, что в такой тип нельзя будет поместить значения отличные от перечисленных?
I>Поддержка средств разработки, например. Ты пишешь селектор, а получаешь реальный тип.
Вот и интересно получить реальный пример, в котором ты пишешь селект в строке, и уже дизайнтаме видишь типы параметров и возвращаемых значений, а не этот вакуумный сфероконь.
А это на фиг не нужная игрушка для ценителей навороченных систем типов.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
EP>>Есть даже библиотека генератор парсеров: EP>>
EP>>Metaparse is a parser generator library for template metaprograms. The purpose of this library is to support the creation of parsers that parse at compile time. This library is intended to be used for embedded domain specific language creation for C++. The input of the generated parser is a compile time string
VD>Вот это не надо. Это цитата из буста. Он не строки парсит, а имеет говногодный DSL и к тому же порождает кучу промжуточного кода, так что банальный парсер выражений дает гигабайты выхлопа.
Какие гигабайты выхлопа? Он порождает парсеры compile-time, результат которых — это тип.
VD>Ты мне покажи как в шаблоне, во внемя компиляции, можно эту строку разобрать, выдрать из нее SQL-выражение, и превратить его в код работающий с ним.
Ну я уже выше привёл пример — там for — он по строке, и в compile-time.
VD>Если мы говорим о парсере, то он должен в этой строке иметь грамматику, а на выходе порождать функции парсинга.
В какой строке? Ты о чём? Metaparse пораждает парсеры compile-time строк, а не его граматика задаётся в compile-time строке, хотя если нужно, можно и граматику в строке задать
VD>То есть грамматика того же калькулятора должна выглядеть так:
Кому должна?
VD>А не таким говнокодом:
Молодец, скопипастил кучу include'ов, using'ов, и мёртвый код для pre-C++11 — возьми с полки пирожок.
EP>>Ну пример действительно сферический, но сам подход вполне жизнеспособный: например задать регулярное выражение в виде compile-time строки, и получить тип представляющий оптимизированный парсер для него. VD>Вот и хочется посмотреть на парсер полученный в компайлтайме, а не сфероконя на типах.
Ну этот "сфероконь на типах", таки пораждает compile-time парсер, нравится тебе это или нет
EP>>А вот приспособить это для типизированного доступа к БД а-ля SQL-macros — должно быть вполне реализуемо. VD>Вот обычно на этом и начинаются трудности, так как системы типов не особо приспособлены для доступа к внешним источникам данных. Для макры это не проблема, так она не не более чем программа запускаемая во время компиляции. А для приседания на типах — проблема.
Речь не идёт о хождении в базу compile-time, речь идёт о генерации о проверки запросов compile-time, которые ходят в базу в runtime. Как linq2db, только, только без runtime-оверхеда.
VD>Для тех же плюсов мне интересно как разобрать строку из параметра типов? И можно ли сходить за метаданными скажем в Sqlight? Почти уверен, что второе невозможно. На счет первого тоже есть сомнения, но это более реалистично.
Первое — давно возможно. Доступ к строкам в compile-time есть, полный по-тьюрингу язык для их обработки тоже есть, причём не один.
Второе — нет, так как нет API для доступа к DB, чтению файлов и т.п. Но для таких редких use-case'ов внешнюю кодогенерацию не в лом использовать. Ну то есть например нормальный макросы — да, хотелось бы. Доступ к DB в compile-time — не особо
Здравствуйте, VladD2, Вы писали:
I>>Тип может быть задан перечислением значений, частный случай — одно значение.
VD>Для простых типов это имеет смысл. А какой смысл это для сложных списков? Я правильно понимаю, что в такой тип нельзя будет поместить значения отличные от перечисленных?
Для сложных точно так же, разные апи устроены по разному. Например, параметр функции может принимать пять видов структур. Вот и тип у него будет соответственный.
I>>Поддержка средств разработки, например. Ты пишешь селектор, а получаешь реальный тип.
VD>А это на фиг не нужная игрушка для ценителей навороченных систем типов.
Это именно игрушка, показывает возможности системы типов.
VD>Вот и интересно получить реальный пример, в котором ты пишешь селект в строке, и уже дизайнтаме видишь типы параметров и возвращаемых значений, а не этот вакуумный сфероконь.
Буквально селектами никто не пользуется. Зато есть всякие модификаторы, которые даже прочитать трудно. Тем не менее компилер и IDE выдают нормальный перечень полей буде в этом необходимость.
export type Delta<T> = {
[P in keyof T]?:
T[P] extends Array<infer U> ? Array<ValueOrDelta<U>> : ValueOrDelta<T[P]>;
};
type AllowedPrimitives =
boolean
| string
| number
| Date
| dateTime.TimeOfDay
| duration.Duration
| dateTime.JustDate
| dateTime.DateTimeOffset
| guid.Guid
| binary.Binary;
type ValueOrDelta<T> = T extends AllowedPrimitives ? T : Delta<T>;
const x = Order.from({id = 5});
Вот здесь инициализатор имеет тип Delta<Order>, соответственно в случае чего иде подскажет не мешанину в три абзаца текста, а нужные филды, даже если понакладывать на это все Pick, Readonly, Exclude, Extract, Omit и тд.
Вот эта вещь позволяет мне менять IOperationMetadata и не бояться, что это повлияет на некоторые функции. Можно объявить отдельный интерфейс, но тогда придется думать про синхронизацию деклараций, что есть отстой. Соответсвенно у меня тривиальный маппинг, который контролируется компилятором.
В случае чего ИДЕ дает полный перечень с реальными именами и типами.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Какие гигабайты выхлопа?
Обычные. В выхлопных каталогах. Там формирвется море промежуточного говна. Мы в этот вопрос еще лет 10 назад разбирали.
EP>Он порождает парсеры compile-time, результат которых — это тип.
Это делается страшными побочным эффектами. А в промежутке там формируются тонны шаблонных классов, которые порождают гигабайты временных файлов даже на примитвной грамматике, а компиляция идет неприлично долго.
EP>Ну я уже выше привёл пример — там for — он по строке, и в compile-time.
Это слишком простой пример. Он не показывает достижим ли результат. Нужно чтобы на вход было нечто вроде:
parser<"rule1 = 'test';">();
а в результате формировался, например, вот такой код:
Ну, то есть, чтобы код содержал разбор грамматики и построение по ней некоторого, заранее не определенного, кода зависящего от этой грамматики.
Просто статическая проверка не интересна. Причем хотелось бы, чтобы при возникновении ошибок можно было сообщить о них пользователям в виде понятного сообщения компилятора.
EP>В какой строке? Ты о чём? Metaparse пораждает парсеры compile-time строк, а не его граматика задаётся в compile-time строке, хотя если нужно, можно и граматику в строке задать
В строке переданной в качестве параметра. Пример я выше привел.
EP>Кому должна?
Программисту. Вот макры так могут за милую душу. При этом код их реализации простой и эффективный. В результате порождается только код парсера, без гигабайт промежуточных классов, которые еще должен переварить оптимизатор.
EP>Молодец, скопипастил кучу include'ов, using'ов, и мёртвый код для pre-C++11 — возьми с полки пирожок.
Какой есть в бусте. На инглюды я бы глаза еще закрыл. Их можно и в один свести. А вот на говнокод вместо грамматики я глаза закрывать не хочу. Я хочу иметь грамматические правила вроде:
EP>Ну этот "сфероконь на типах", таки пораждает compile-time парсер, нравится тебе это или нет
Не порождает. Приведенный в теме пример порождает типы, т.е. сфероконя.
EP>Речь не идёт о хождении в базу compile-time, речь идёт о генерации о проверки запросов compile-time, которые ходят в базу в runtime. Как linq2db, только, только без runtime-оверхеда.
Так нет никаких проверок в компалйтайм. И базы нет. Есть самопальная БД на типах и хождение к ней. Ты хотя бы в коде разобрался бы. Нам на входе списки описанные на типах, а на выходе типы описывающие списки. Чистый сфероконо, который не применим на практике. Там в типах обрабатываются данные.
EP>Первое — давно возможно. Доступ к строкам в compile-time есть, полный по-тьюрингу язык для их обработки тоже есть, причём не один.
Ну, значит покажи примитивный пример (я не прошу полный парсер-генератор), который бы разобрал строку и построил по ней функцию разбирающую грамматику в этой строке. Вон выше я пример привел. Его будет достаточно.
EP>Второе — нет, так как нет API для доступа к DB, чтению файлов и т.п. Но для таких редких use-case'ов внешнюю кодогенерацию не в лом использовать. Ну то есть например нормальный макросы — да, хотелось бы. Доступ к DB в compile-time — не особо
Ага. Это как в классическом: Не дает, ну и не очень то и хотелось! (ц)
Нормальные макры тупо не имеют таких ограничений. Это просто код — плагин к компилятору, который может читать любые данные (включая код проекта) и порождать новый. При этом у него нет проблем прочитать и метаданные из внешней БД, например.
Вот представь себе, что вместо:
#define(x) строка#x
Ты можешь написать нечто вроде
Expression #define(CppStringLiteral expr)
{
здесь разбираем выражение и генерируем код парсера, которые компилируется в набор классов или функций
}
expr — это твой литерал. Вот это и есть макрос о котором я говорю. В другом макросе в строке может быть передан URI БД. И мы просто сходим туда во время компиляции и прочтем от туда метаданные, по которым скомпилируем функции доступа к ней.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Ikemefula, Вы писали:
I>Это именно игрушка, показывает возможности системы типов.
Ну, вот оно конечно прикольно, но практического смысла не имеет.
Интересно было бы узнать можно ли сделать таким образом именно типизированный доступ к БД? Ну, чтобы я мог описать метаданные БД, а потом в строках писать запросы, а они не только бы проверялисть на корректнось, но еще и генерился бы код выполняющий запросы и возвращющий результаты в типизированном виде.
Ну, типа (гипотетический код):
const db = {
things: [ id: int, name: string, active: bool ],
} as const;
let query = Query<"SELECT id, name AS nom FROM things WHERE active = :active">;
let result = query.Exec({active: true});
for (let x of result)
{
if (x.nom == "строка" && x.id == 42)
делаем что-то
}
Ну, и естественно, чтобы был контроль имен и типов. При выполнении чтобы получались данные.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.