Бабушка, смотри, я сделал двач! Войти !bnw Сегодня Клубы

Пишу опердень на attoparsec. Т.к. парсер у нас одновременно и лексер, и собственно парсер, логику приходится перемежать обработкой пробельных символов:

pVarDecl :: Parser VariableDeclaration
pVarDecl = do
  string "var"
  skipWhitespace1 -- пропускаем 1 или больше пробельных символов
  name <- pVarName
  skipWhitespace -- пропускаем 0 или больше пробельных символов
  value <- optional $ do
    string "="
    skipWhitespace
    pExpression
  skipWhitespace
  string ";"

  return $ VariableDeclaration name value

Это утомляет. Появляется закономерное желание «переопределить точку с запятой» и явно указывать только места, где пробельные символы обязательны:

pVarDecl :: Parser VariableDeclaration
pVarDecl = do
  string "var"
  requiredWhitespace
  name <- pVarName
  value <- optional $ do
    string "="
    pExpression
  string ";"

  return $ VariableDeclaration name value

(и потом ещё для полного счастья keyword k = string k >> requiredWhitespace, да).

Удивительно, но сам attoparsec, похоже, ничего для этого не предлагает. Итак, какие у меня варианты?

Можно определить свою монаду. Это, конечно, круто, но придётся лифтить часть Data.Attoparsec. В принципе, это всё же лучше, чем ещё двести раз набрать «skipWhitespace», но все равно грязновато.

Есть ощущение, что можно обернуть парсер в трансформер, для которого определить инстанс Monad, и будет мне счастье. Но я трансформерами никогда не пользовался даже, не говоря уж о написании собственных; возможно, это бред, а не идея.

Есть у вас какие-то соображения на этот счёт?

Рекомендовали: @l29ah @ndtimofeev
#ENLYJ7 / @minoru / 3194 дня назад

юзать bison и не выебываться.

#ENLYJ7/S5S / @lexszero / 3194 дня назад
Во-первых с такими вопросами тебе в juick. Во-вторых идея перегрузки точки с запятой здесь только кажется интересной, поскольку все элементарные парсеры содержат >>= в том или ином виде. Для комбинаторов парсеров в целом и для attoparsec'а в особенности данная проблема решается путём страдания^Wпропуска white-space'ов в явном виде. Если внимательно посмотреть на parsec, то можно увидеть, что он использует специальный класс-токенизатор. В принципе никто не мешает тебе определить его как полноценный лексер или хотя бы скипать white-space'ы там. Лексер можешь написать на самом парсеке примерно как здесь http://hackage.haskell.org/package/language-lua
#ENLYJ7/UAP / @ndtimofeev / 3194 дня назад
Ну и да, все существующие реализации комбинаторов парсеров на haskell — говно, а parsec — самое мутное из этих говен. Я гарантирую.
#ENLYJ7/WRJ / @ndtimofeev / 3194 дня назад

@ndtimofeev > Во-первых с такими вопросами тебе в juick.

Это потому, что там @qnikst и больше движухи, или это сейчас был посыл нахуй bnw-style?

Во-вторых идея перегрузки точки с запятой здесь только кажется интересной, поскольку все элементарные парсеры содержат >>= в том или ином виде.

Ну и что? Я же не хочу вмешиваться в их работу; мне нужно, чтобы skipWhitespace вставлялись между моими парсерами, только и всего.

За указание в сторону language-lua и Text.Parsec.Token — спасибо.

Ну и да, все существующие реализации комбинаторов парсеров на haskell — говно, а parsec — самое мутное из этих говен. Я гарантирую.

Есть что получше?

#ENLYJ7/AIG / @minoru --> #ENLYJ7/UAP / 3194 дня назад
@minoru > Это потому, что там @qnikst и больше движухи, или это сейчас был посыл нахуй bnw-style? Это потому что juick - неофициальная платформа общения русскоязычного haskell-комьюнити. > Ну и что? Я же не хочу вмешиваться в их работу; мне нужно, чтобы skipWhitespace вставлялись между моими парсерами, только и всего. Для трансформера это невозможно. Ты либо перегружаешь все >>=, либо перегружаешь все, но явно лифтишь свои парсеры в лексер. То есть будет примерно как применение к каждому парсеру функции skipWhitespace только сложнее. > Есть что получше? Смотря для каких целей. То есть если ты не оказался в области применения генераторов парсеров, то скорее нет чем да. Но это не отменяет того, что при работе с комбинаторами парсеров будет вылезать масса бойлерплейта и косяков, которые обусловлены исключительно спецификой реализации. parsec например поощряет написание лапши^Wконечного автомата руками.
#ENLYJ7/DIN / @ndtimofeev --> #ENLYJ7/AIG / 3194 дня назад
@ndtimofeev А где реализации комбинаторов парсеров не говно?
#ENLYJ7/HFS / @l29ah --> #ENLYJ7/WRJ / 3194 дня назад
@l29ah Понятия не имею.
#ENLYJ7/7KA / @ndtimofeev --> #ENLYJ7/HFS / 3194 дня назад

тупой штоле

pKeyword :: String -> Parser ()
pKeyword k = string k *> many1 pWhitespace *> pure ()

pLexeme :: Parser a -> Parser a
pLexeme p = p <* many0 pWhitespace

pVarDecl :: Parser VarDecl
pVarDecl = pKeyword "var" *> (
              BareVarDecl <$> pIdent
          <|> InitVar <$> pIdent <*> (pLexeme "=" *> pExpr)
         )

data VarDecl = BareVarDecl VarName | InitVar VarName Expr
...

КОМПОЗИЦИЯ мазафака блядь, выкинь нахуй эти ебаные монадки, они в парсинге не нужны -- и осиль Applicative

#ENLYJ7/ZOX / @ulidtko / 3193 дня назад

@ndtimofeev хуй пососи, говно ему блядь

#ENLYJ7/YH2 / @ulidtko --> #ENLYJ7/WRJ / 3193 дня назад

@ulidtko Насколько я понял, ты предлагаешь просто определить пару функций, с помощью которых абстрагировать пожирание пробельных символов? Вобщем-то вариант, спасибо!

#ENLYJ7/49R / @minoru --> #ENLYJ7/ZOX / 3193 дня назад

@minoru да. в яблочко! ты был близок с keyword k = string k >> requiredWhitespace (таких функций для твоих вайтспейс-потребностей понадобится аж две.)

и — я не могу сделать достаточное ударение: applicative parsing FTW

#ENLYJ7/ZWT / @ulidtko --> #ENLYJ7/49R / 3193 дня назад

@minoru смотри, вот ты пишешь

do
  name <- foo
  value <- bar
  ...
  return $ VariableDeclaration name value

— для этого не нужна аж монада. Эквивалентный код (всему do-блоку): VariableDeclaration <$> foo <*> bar. Всё. Монада не нужна, Applicative достаточно.

#ENLYJ7/263 / @ulidtko --> #ENLYJ7/49R / 3193 дня назад

@ulidtko Ок, убедил. Спасибо ещё раз :)

#ENLYJ7/1RX / @minoru --> #ENLYJ7/263 / 3193 дня назад

@minoru Хотя мне не сильно нравится, что мы больше никак не называем аргументы конструктора.

#ENLYJ7/0YD / @minoru --> #ENLYJ7/1RX / 3193 дня назад

@minoru ты заебешься всё именовать в стиле name, value. Особенно на большой грамматике — попробуй написать в стиле одна строка парсера на одну строку грамматики (EBNF десу), поймёшь как охуенно

#ENLYJ7/5YR / @ulidtko --> #ENLYJ7/0YD / 3193 дня назад

@minoru Поясняю суть Applicative на пальцах.

  1. Нужно знать Functor. Это пререквизит. Его инстансы дают нам fmap :: (a -> b) -> f a -> f b. Это как map для списка, только для любого функтора вместо ссаного всех доебавшего уже списка. Позволяет промапить любую функцию «внутри» некого контекста. Если у меня есть foobar :: Parser Int, то (*2) <$> foobar будет парсером, парсящим число — но возвращающим удвоенное. Значок (<$>) = fmap.

  2. Applicative — подкласс Functor. Значит, в Applicative у нас всегда есть fmap, а также: pure и (*) (apply). Сигнатура pure :: Applicative f => a -> f a — значит кладет «чистое» значение a (без эффектов этц) тупо в наш контекст f. Из Int получается Parser Int — который ничего не парсит, ни единого символа, а просто возвращает переданный инт. Добавим монаду — будет return = pure, то есть это просто аппликативный ретурн.

Вторая и последняя примочка Applicative — это способ апплаить функции тама, в контексте, с эффектами, над эффектными аргументами.

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

Частично применяя, можно двумя способами расставить скобки: либо так f (a -> b) -> (f a -> f b) — тогда мы «грязную» функцию как бы выдергиваем этим оператором оттуда на уровень обычной хаскельной функции; либо как бинарный оператор, принимающий грязную функцию, грязный аргумент, и отдающий грязный результат. Интуитивно?

(*>) и (<*) — это частные случаи для удобства, они выбрасывают результат (но не эффекты) со стороны где не хватает треугольной скобки. по сигнатурам всё видно

Дальше, один из законов Applicative такой, что f <$> x = pure f <*> x. Если не шаришь функтор, то так легче понять (<$>); берёт чистую фукнцию, лифтит её туда и применяет к эффектной правой части. Вот там где VarDecl <$> foo <*> bar именно это и происходит: чистая функция-конструктор применяется к результатам парсеров foo и bar, эффекты автоматически делаются. У VarDecl два аргумента, но по цепочке можно делать сколько хочешь — потому что тип VarName -> Expr -> VarDecl матчит a -> b если положить a = VarName, b = Expr -> VarDecl.

<|> — это из Alternative, и это просто композиция по or. Либо то, либо следующее, проще простого.

#ENLYJ7/Z8K / @ulidtko --> #ENLYJ7/49R / 3193 дня назад

@ulidtko Да ты с ума сошёл, такие портянки без предварительного допроса писать! Я в курсе, что такое Applicative.

#ENLYJ7/JJD / @minoru --> #ENLYJ7/Z8K / 3193 дня назад

@minoru @anonymous не знает
// знаешь но не юзаешь? при парсинге? ЛОЛ

#ENLYJ7/3MV / @ulidtko --> #ENLYJ7/JJD / 3193 дня назад

@ulidtko > @anonymous не знает
Но мы с тобой одни в этом треде ._.

// знаешь но не юзаешь? при парсинге? ЛОЛ

Сорь. Хотел сейчас сослаться на то, что меня испортила документация к парсеку, глядь — а там везде тоже все эти <*>, <$> и <|>. Сфигали я всегда думал, что «парсер-комбинаторы» обязательно подразумевают монадки — хз.

Спасибо, что просветил!

#ENLYJ7/OK9 / @minoru --> #ENLYJ7/3MV / 3193 дня назад

@ulidtko Олсо в аппликативном стиле из-за его сжатости как-то стыдно писать <?>, а без этого дебаг парсера превращается в ад.

#ENLYJ7/COP / @minoru --> #ENLYJ7/263 / 3193 дня назад

@ulidtko Переписал часть кода, сделал вывод, что для простых кусочков аппликативный стиль действительно получше, но как только появляются сложные опциональные элементы, наступает жопа. При этом эти же сложные части в монадном стиле пишутся легко и непринуждённо.

#ENLYJ7/TN8 / @minoru --> #ENLYJ7/263 / 3193 дня назад

@minoru хули тебе там стыдно // один <?> на нетерминал, аккуратно в конце выражения, последней строкой

#ENLYJ7/JSN / @ulidtko --> #ENLYJ7/COP / 3193 дня назад
@ulidtko Загибай пальцы: parsec не умеет в бэктрекинг и пытается использовать свои собственные комбинаторы вместо Alternative, attoparsec мутный кусок дерьма прикрученный болтами к ByteString, polyparse вообще не библиотека а полудохлый набор мало связанных между собой набросков. Что там ещё осталось?
#ENLYJ7/A8Y / @ndtimofeev --> #ENLYJ7/YH2 / 3193 дня назад

@ulidtko Стыдно потому, что был однострочник, а стал многострочник. Твой совет касательно <?> плохо работает со штуками типа (keyword "BEGIN" *> parseBlock <* keyword "END"), где хотелось бы отдельно пометить кейворды (у меня парсер на байтстроках работает, я включил OverloadedStrings, а <?> принимает String — я думал, но пока что не осилил, добавить <?> в само определение keyword; это облегчает, но не убирает проблему).

#ENLYJ7/MN1 / @minoru --> #ENLYJ7/JSN / 3193 дня назад

@minoru

сложные опциональные элементы

жопой чую, что у тебя там ещё и AST кривое. Вопрос! Чему вот здесь

  value <- optional $ do
    string "="
    pExpression

будет равно value, если в тексте инициализация пропущена? как факт неинициализированности сохраняется в AST? умеет ли AST отличать var foo; от var foo = (); и var foo = Nothing; ?


Я к чему веду, даже с фиксированной грамматикой есть уйма способов написать разные, но эквивалентные парсеры к ней. Также можно эквивалентно преобразовывать саму грамматику (не меняя её языка). То, что грамматика цитирует [ = Expr]_Opt, не значит что её парсер надо всенепременно записывать через optional; для твоего AST наверняка будет лучше развернуть обе альтернативы через <|> (получив эквивалентный парсер), и уложить их в разные конструкторы. Хотя бы просто чтобы сделать невалидные состояния непредставимыми.

Так-то я бы optional не задумываясь использовал только на Parser (), типа optional whitespace. Везде где внутренний парсер возвращает какие-то данные — вместо optional разворачивать альтернативы через <|>. При появлении дубликации выносить парсеры в функции.

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

#ENLYJ7/6D4 / @ulidtko --> #ENLYJ7/TN8 / 3193 дня назад

@minoru
kwBegin :: Parser ()
kwBegin = keyword "BEGIN" <?> "BEGIN keyword"

kwEnd :: Parser ()
kwEnd = keyword "END" <?> "END keyword"

бля, ну серьёзно, неужели всё так плохо с абстракцией?

#ENLYJ7/ZNQ / @ulidtko --> #ENLYJ7/MN1 / 3193 дня назад

@ulidtko С абстракцией всё збс, но то, что ты написал выше — бойлерплейт. Я хотел keyword k = string k *> skipWhitespace *> pure () <?> k, но обломился из-за вышеописанного расхождения в типах строки.

#ENLYJ7/PLI / @minoru --> #ENLYJ7/ZNQ / 3193 дня назад

@minoru бля лол, а <?> unpack k уже всё, слишком сложно штоле?

#ENLYJ7/L7R / @ulidtko --> #ENLYJ7/PLI / 3193 дня назад

@ulidtko AST у меня действительно кривенький, потому что тупо слизан с грамматики (с минимальными правками). Если инициализация пропущена, то в value будет Nothing, фигли тебе не ясно? Да, все три показанных тобой случая отличаются в AST-е (будет Nothing, Just () и Just Nothing).

#ENLYJ7/VX3 / @minoru --> #ENLYJ7/6D4 / 3193 дня назад

@ulidtko Ага. unpack внезапно выдал [Word8]. Та-да-а!

#ENLYJ7/HEY / @minoru --> #ENLYJ7/L7R / 3193 дня назад

@minoru

Just Nothing

понятно.

#ENLYJ7/GGT / @ulidtko --> #ENLYJ7/VX3 / 3193 дня назад

@ulidtko Не-не-не, это не тот Nothing, который Data.Maybe.Nothing, это другой, из AST. Честно ._.

#ENLYJ7/K60 / @minoru --> #ENLYJ7/GGT / 3193 дня назад

@minoru сука блядь, ненавижу эту Lazy/Strict хуйню, пидарасы блядь

$ hoogle 'Word8 -> Char'
Data.Text.Internal.Unsafe.Char unsafeChr8 :: Word8 -> Char
#ENLYJ7/79M / @ulidtko --> #ENLYJ7/HEY / 3193 дня назад

@ulidtko Спасибо, я погляжу попозже. Меня настораживает, что оно unsafe. Ты меня куда ведёшь, дьявол?!

#ENLYJ7/718 / @minoru --> #ENLYJ7/79M / 3193 дня назад

@minoru юникод в keyword не суй — и всё будет хорошо ;)

#ENLYJ7/OHS / @ulidtko --> #ENLYJ7/718 / 3193 дня назад

@ulidtko или так

λ➔ import Data.Text
λ➔ import Data.Text.Encoding
λ➔ :t unpack . decodeUtf8
unpack . decodeUtf8
  :: Data.ByteString.Internal.ByteString -> String
#ENLYJ7/9YB / @ulidtko --> #ENLYJ7/79M / 3193 дня назад

Другой широко известный бонус аппликативного парсинга — парсеры можно анализировать, компилировать (как регекспы), оптимизировать и иначе трансформить перед началом парсинга. uu-parsinglib, например, подсчитывает сам глубину look-ahead-а, опираясь на структуру парсера. Подобные трюки невозможны с монадическими парсерами — об их структуре очень сложно что-то полезное сказать не запуская сам парсер. (потому что он легко может использовать промежуточные данные парсинга и/или инспектировать входной текст, решая каким комбинатором парсить дальше).

кароч если уж парсить — то аппликативно

#ENLYJ7/IK5 / @ulidtko / 3193 дня назад
@ulidtko Аппликативно парсить можно не везде. Для всего остального в следующем GHC есть ApplicativeDo.
#ENLYJ7/I64 / @ndtimofeev --> #ENLYJ7/IK5 / 3193 дня назад

@ndtimofeev пруф или везде

#ENLYJ7/Y6M / @ulidtko --> #ENLYJ7/I64 / 3193 дня назад
@ulidtko Как ты собираешься парсить аппликативно что-нибудь типа otr'а где сначала идёт заголовок определяющий структуру пакета?
#ENLYJ7/N9Q / @ndtimofeev --> #ENLYJ7/Y6M / 3193 дня назад

@ndtimofeev тупой штоле,

pOTR :: Parser OTRPacket
pOTR =  string "?OTR?"    *> pOTRv1Packet
    <|> string "?OTRv2?"  *> pOTRv2Packet
    <|> string "?OTRv23?" *> (pOTRv2Packet <|> pOTRv3Packet)
    ...

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


сорь, я прост криво сформулировал, отличие монадных парсеров от аппликативных немного глубже. справа от >>= у нас уже в скоупе промежуточный результат r парсера слева — и любой код, подставляющий произвольный парсер зависимо от r, допусти́м справа. в аппликативе такая хуйня не прокатит, структура парсера фиксирована и не может динамически варьировать в рантайме. типа (может быть) не получится сделать аппликативно парсер протокола, который внутри себя может на лету описывать свои же расширения (и тут же их использовать в следующем меседже). но я хз, никогда не пробовал такое.

но все(?) практичные парсеры имеют статичную грамматику, так что на деле это «ограничение» аппликатива никого не ебёт. ЗАТО оно покупает возможность анализа и оптимизации парсеров без их запуска.

#ENLYJ7/DN5 / @ulidtko --> #ENLYJ7/N9Q / 3193 дня назад
@ulidtko Автору спасибо за полезный комментарий. Только вот не понятно, как это поможет мне изменить цвет формочки на странице, но плюс всё равно поставлю как украинцу ;) // своим следует отвечать взаимно ;)
#ENLYJ7/F1E / @anonymous --> #ENLYJ7/Z8K / 3193 дня назад
ipv6 ready BnW для ведрофона BnW на Реформале Викивач Котятки

Цоперайт © 2010-2016 @stiletto.