Информация об изменениях

Сообщение Re[70]: Исповедь C++ника от 05.01.2021 19:24

Изменено 06.01.2021 6:49 netch80

Re[70]: Исповедь C++ника
Здравствуйте, Тёмчик, Вы писали:

Тё>>>Текст в статье подтвердил моё первоначальное предположение- вы тупой сплит строки переусложнили в кромешный шаблонный ад.

S>>Писать для каждого из таких заголовков тупой сплит строки нерационально.
Тё>Если бы вы открыли букварь учебник Ахо, вы бы не изобретали это убожище.
Тё>Сканер генерит поток токенов, из которого парсер строит AST. Оба являются конечными автоматами.
Тё>Я хз нахрена у вас в сканере шаблон "probably_with_comma", когда всего-то нужно бить входной поток на токены. Там нужен простейший автомат.

Видите ли, Тёмчик... вы не пробовали и даже не пытались. Если бы попытались, то поняли бы, в чём проблема.

Ахо с компанией — "книга дракона" — это, конечно, стильно (толстый том полный странных букв... нет, я его читал). Проблема в том, что это не имеет никакого отношения к действительности — в частности, к той, которую формирует IETF.

Я буду дальше объяснять на примере SIP, а не HTTP — хотя они с точки зрения общего стиля применённого извращения близнецы-братья, но детали разные. Так вот — парсинг в принципе не укладывается в концепцию "бить входной поток на токены" "простейшим автоматом".

Для начала возьмём адресный поле (header field) (From, To, Contact, Route и ещё несколько) — хотя сплошь и рядом говорят только header. Вот варианты такого заголовка:

(1) From: sip:foo.bar:5000;tag=123
(2) From: <sip:foo.bar:5000>;tag=123
(3) From: <sip:foo.bar:5000@baz.buka:6000;zyx=456>;tag=123
(4) From: sip sip.sip<sip:foo>;tag=123
(5) From: "sip sip \\sip"<sip:foo>;tag=123
(6) From: <sip:foo.bar:5000;rn=bar.baz;npdi@baz.buka:6000>;tag=123
(7) From: <sip:a;x1=a%5C%25b@foo.bar;x2=a%5C%25b>;tag=123;x3="a\\%b"
(8) From: <sip:a@[::4]?foo=[::5]>;tag=123

Вот мы начинаем разбирать то, что после "From:", пропустили пробелы, можем дальше искать или token (это такой грамматический элемент — да, именно так и зовётся, хотя бывают и другие токены), или quoted-string (как в (5)). OK, нашли token. Теперь после него если ':', то это короткая форма (как в (1) или (2), элемент addr-spec), но мы уже начали разбирать URL, найдя его схему. Если после него шёл ещё token, то это на самом деле display-name длинной формы (addr-spec). Следовательно, нам уже нужен в стандартных понятиях предпросмотр минимум на 2 токена вперёд, чтобы выбрать вариант разбора.

Теперь в угловых скобках. В (2) foo.bar:5000 это хост foo.bar и порт 5000. В (3) foo.bar:5000 это юзер foo.bar и пароль 5000. Чтобы их различить, надо было добраться до '@' — если она есть, то всё перед ней было юзером и паролем. То есть это предпросмотр на 3 токена? Как бы не так. В доменной части может быть hostname, которое может быть в варианте IPv4 и IPv6. В варианте IPv4 его длина не ограничена, и если считать токеном один элемент между точками, то предпросмотр будет на неограниченную длину, а если всё за один, и определите его как то, что допустимо в DNS, вы в этом месте не отличите его от того же token. В варианте IPv6, например, у него есть символы [:], которые допустимы только тут, и ещё в элементе hvalue (см. случай (8)), но не в других местах.

Или вот например, в user допустимы кроме базового набора символов "&" / "=" / "+" / "$" / "," / ";" / "?" / "/", а в пароле только "&" / "=" / "+" / "$" / "," (на три меньше). Вы не сведёте это под один тип токена, если не будете токеном считать один символ.

А вот в примере (7) одинаковые с виду конструкции и с реально одинаковым значением параметра. Но address parameters имеют совсем другие правила квотинга, чем URI parameters или user parameters. Мало того, в address parameters (x3 в примере) грамматика неоднозначна: если будет x3=a.b.c.d, это соответствует правилу как token, так и host. Как отличать? А никак — пока вы по имени параметра это не определите.

Если вы это попытаетесь перевести на сканер, выдающий токены, вы не сможете это сделать без контекстов (как бы они ни назывались — conditions в flex и re2c, например): например, в начале адреса контекст будет выбирать quoted-string или single token, после второго надо выбрать — если ':', то это была схема и надо войти в соответствующий контекст (ой, а какой? для схемы sip и схемы tel они разные!) Но если станете переключать контекст на каждом прочтении, то у вас лексер быстро превратится в парсер, потому что будет знать все эти контексты сам. Ну и писать это на языке описания лексера — практически сразу повеситься.

И ещё: если во From: написано sip:foo.bar:5000;tag=123, то tag=123 это параметр адреса. А если в первой строке запроса, то это уже параметр URI. Чтобы сделать параметр URI, надо было писать: From: <sip:foo.bar:5000;tag=123> (всё в угловых скобках). Что, для этого надо было двоить контексты в лексере?

А если ещё учесть, что в авторизационных заголовках есть URI, который в кавычках, но внутри него полная грамматика URI... мне уже самому страшно, хоть у меня это всё и работает.

А вот на PEG такие вещи пишутся влёгкую — по крайней мере на >90% их, потому что 1) эти парсеры включают в себя грамматику лексем и контекст лексера сразу оказывается нужным в явном виде, 2) у них есть откат на произвольную длину назад.

Мало того, всё это влёгкую парсится на регэкспах! Это смешно звучит, но эти грамматики при всех их странностях нерекурсивны (максимум — есть списки), стандартных проблем регэкспов с грамматикой (в рекурсивном случае) нет, и на каком-нибудь Питоне на регэкспах (сишным движком) получается на порядки быстрее, чем на явной грамматике. А вот уже на Java лучше всего писать на циклах по классам символов (да, вручную, но быстро).

И никакая книга дракона вам такое не опишет: там авторы заняты совсем другими классами задач. Впрочем, она не поможет и для C++, где видя >> надо определить, это оператор сдвига вправо или закрытия двух шаблонных скобок. Кстати, PEG у них нет — для них он, видимо, некошерен?

Тё> И далее простой в доску парсер- генератор AST. Совсем по феншую, если сделать StAX интерфейс с visitor-м.


Ну сделайте. Только полный разбор, а не выбранных 5% стандарта... а мы посмотрим... а я, если потребуется написать что-то такое на C++, таки посмотрю на тот RESTinio (или на Spirit, если подойдёт по условиям).
А то разобрать стандартный Паскаль, где нет никакой зависимости лексера от контекста — все дартаньяны в белом, а как только что-то реальное — почему-то быстро переходят на более практические средства...
Re[70]: Исповедь C++ника
Здравствуйте, Тёмчик, Вы писали:

Тё>>>Текст в статье подтвердил моё первоначальное предположение- вы тупой сплит строки переусложнили в кромешный шаблонный ад.

S>>Писать для каждого из таких заголовков тупой сплит строки нерационально.
Тё>Если бы вы открыли букварь учебник Ахо, вы бы не изобретали это убожище.
Тё>Сканер генерит поток токенов, из которого парсер строит AST. Оба являются конечными автоматами.
Тё>Я хз нахрена у вас в сканере шаблон "probably_with_comma", когда всего-то нужно бить входной поток на токены. Там нужен простейший автомат.

Видите ли, Тёмчик... вы не пробовали и даже не пытались. Если бы попытались, то поняли бы, в чём проблема.

Ахо с компанией — "книга дракона" — это, конечно, стильно (толстый том полный странных букв... нет, я его читал). Проблема в том, что это не имеет никакого отношения к действительности — в частности, к той, которую формирует IETF.

Я буду дальше объяснять на примере SIP, а не HTTP — хотя они с точки зрения общего стиля применённого извращения близнецы-братья, но детали разные. Так вот — парсинг в принципе не укладывается в концепцию "бить входной поток на токены" "простейшим автоматом".

Для начала возьмём адресный поле (header field) (From, To, Contact, Route и ещё несколько) — хотя сплошь и рядом говорят только header. Вот варианты такого заголовка:

(1) From: sip:foo.bar:5000;tag=123
(2) From: <sip:foo.bar:5000>;tag=123
(3) From: <sip:foo.bar:5000@baz.buka:6000;zyx=456>;tag=123
(4) From: sip sip.sip<sip:foo>;tag=123
(5) From: "sip sip \\sip"<sip:foo>;tag=123
(6) From: <sip:foo.bar:5000;rn=bar.baz;npdi@baz.buka:6000>;tag=123
(7) From: <sip:a;x1=a%5C%25b@foo.bar;x2=a%5C%25b>;tag=123;x3="a\\%b"
(8) From: <sip:a@[::4]?foo=[::5]>;tag=123

Вот мы начинаем разбирать то, что после "From:", пропустили пробелы, можем дальше искать или token (это такой грамматический элемент — да, именно так и зовётся, хотя бывают и другие токены), или quoted-string (как в (5)). OK, нашли token. Теперь после него если ':', то это короткая форма (как в (1) или (2), элемент addr-spec), но мы уже начали разбирать URL, найдя его схему. Если после него шёл ещё token, то это на самом деле display-name длинной формы (name-addr). Следовательно, нам уже нужен в стандартных понятиях предпросмотр минимум на 2 токена вперёд, чтобы выбрать вариант разбора.

Теперь в угловых скобках. В (2) foo.bar:5000 это хост foo.bar и порт 5000. В (3) foo.bar:5000 это юзер foo.bar и пароль 5000. Чтобы их различить, надо было добраться до '@' — если она есть, то всё перед ней было юзером и паролем. То есть это предпросмотр на 3 токена? Как бы не так. В доменной части может быть hostname, которое может быть в варианте IPv4 и IPv6. В варианте IPv4 его длина не ограничена, и если считать токеном один элемент между точками, то предпросмотр будет на неограниченную длину, а если всё за один, и определите его как то, что допустимо в DNS, вы в этом месте не отличите его от того же token. В варианте IPv6, например, у него есть символы [:], которые допустимы только тут, и ещё в элементе hvalue (см. случай (8)), но не в других местах.

Или вот например, в user допустимы кроме базового набора символов "&" / "=" / "+" / "$" / "," / ";" / "?" / "/", а в пароле только "&" / "=" / "+" / "$" / "," (на три меньше). Вы не сведёте это под один тип токена, если не будете токеном считать один символ.

А вот в примере (7) одинаковые с виду конструкции и с реально одинаковым значением параметра. Но address parameters имеют совсем другие правила квотинга, чем URI parameters или user parameters. Мало того, в address parameters (x3 в примере) грамматика неоднозначна: если будет x3=a.b.c.d, это соответствует правилу как token, так и host. Как отличать? А никак — пока вы по имени параметра это не определите.

Если вы это попытаетесь перевести на сканер, выдающий токены, вы не сможете это сделать без контекстов (как бы они ни назывались — conditions в flex и re2c, например): например, в начале адреса контекст будет выбирать quoted-string или single token, после второго надо выбрать — если ':', то это была схема и надо войти в соответствующий контекст (ой, а какой? для схемы sip и схемы tel они разные!) Но если станете переключать контекст на каждом прочтении, то у вас лексер быстро превратится в парсер, потому что будет знать все эти контексты сам. Ну и писать это на языке описания лексера — практически сразу повеситься.

И ещё: если во From: написано sip:foo.bar:5000;tag=123, то tag=123 это параметр адреса. А если в первой строке запроса, то это уже параметр URI. Чтобы сделать параметр URI, надо было писать: From: <sip:foo.bar:5000;tag=123> (всё в угловых скобках). Что, для этого надо было двоить контексты в лексере?

А если ещё учесть, что в авторизационных заголовках есть URI, который в кавычках, но внутри него полная грамматика URI... мне уже самому страшно, хоть у меня это всё и работает.

А вот на PEG такие вещи пишутся влёгкую — по крайней мере на >90% их, потому что 1) эти парсеры включают в себя грамматику лексем и контекст лексера сразу оказывается нужным в явном виде, 2) у них есть откат на произвольную длину назад.

Мало того, всё это влёгкую парсится на регэкспах! Это смешно звучит, но эти грамматики при всех их странностях нерекурсивны (максимум — есть списки), стандартных проблем регэкспов с грамматикой (в рекурсивном случае) нет, и на каком-нибудь Питоне на регэкспах (сишным движком) получается на порядки быстрее, чем на явной грамматике. А вот уже на Java лучше всего писать на циклах по классам символов (да, вручную, но быстро).

И никакая книга дракона вам такое не опишет: там авторы заняты совсем другими классами задач. Впрочем, она не поможет и для C++, где видя >> надо определить, это оператор сдвига вправо или закрытия двух шаблонных скобок. Кстати, PEG у них нет — для них он, видимо, некошерен?

Тё> И далее простой в доску парсер- генератор AST. Совсем по феншую, если сделать StAX интерфейс с visitor-м.


Ну сделайте. Только полный разбор, а не выбранных 5% стандарта... а мы посмотрим... а я, если потребуется написать что-то такое на C++, таки посмотрю на тот RESTinio (или на Spirit, если подойдёт по условиям).
А то разобрать стандартный Паскаль, где нет никакой зависимости лексера от контекста — все дартаньяны в белом, а как только что-то реальное — почему-то быстро переходят на более практические средства...