packages feed

hq-json-0.1.1: src/HQ/Optic/Parser.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE NoImplicitPrelude #-}

module HQ.Optic.Parser where

import Control.Monad.Combinators.Expr (Operator (..), makeExprParser)
import HQ.Optic
import HQ.Parser (Parser, keyword, lexeme, parens, parseTop, symbol)
import HQ.Transformation.Parser (atomParser)
import Relude hiding (Compose, filter, id, many, some)
import Text.Megaparsec
import Text.Megaparsec.Char (alphaNumChar, char)
import Text.Megaparsec.Char.Lexer (decimal)

parseOptic :: Text -> Either (ParseErrorBundle Text Void) Optic
parseOptic = parseTop "optic" opticParser

opticParser :: Parser Optic
opticParser = makeExprParser opticAtomParser [[InfixL (Compose <$ symbol ".")]]

opticAtomParser :: Parser Optic
opticAtomParser =
  choice
    [ fieldParser,
      eachParser,
      keysParser,
      valuesParser,
      filterParser,
      idParser,
      prismParser,
      ixParser,
      groupedOptic
    ]

-- | Redundant parentheses are tolerated around any optic atom.
groupedOptic :: Parser Optic
groupedOptic = parens opticParser

fieldParser :: Parser Optic
fieldParser = char '@' >> Field <$> identifier

identifier :: Parser Text
identifier = lexeme $ fromString <$> some (alphaNumChar <|> char '_' <|> char '-')

eachParser :: Parser Optic
eachParser = keyword "each" Each

keysParser :: Parser Optic
keysParser = keyword "keys" Keys

valuesParser :: Parser Optic
valuesParser = keyword "values" Values

-- | @filter OPTIC TRANSFORMATION@: two bare atoms, each optionally
-- parenthesized. Non-atoms take their own paren group
-- (@filter (each . @age) (== 30)@), which keeps the split
-- deterministic: a @.@ after a bare optic starts an outer
-- composition, and one shared group would force two parsers to
-- greedily share a token stream.
filterParser :: Parser Optic
filterParser = keyword "filter" () >> bare
  where
    bare = Filter <$> opticAtomParser <*> transArg
    transArg = atomParser

idParser :: Parser Optic
idParser = keyword "id" Id

prismParser :: Parser Optic
prismParser = char '_' *> prismNameParser

ixParser :: Parser Optic
ixParser = keyword "ix" () >> Ix <$> lexeme decimal

prismNameParser :: Parser Optic
prismNameParser =
  choice
    [ keyword "String" (Prism PString),
      keyword "Number" (Prism PNumber),
      keyword "Bool" (Prism PBool),
      keyword "Null" (Prism PNull),
      keyword "Array" (Prism PArray),
      keyword "Object" (Prism PObject),
      keyword "Just" PrismJust
    ]