{-# OPTIONS_GHC -Wno-unused-do-bind #-} {-# LANGUAGE OverloadedStrings #-} module Parsers where import Common (Comparison (..), Evaluatable (..), Focuser (..), IfExpr (..), Mapping, Oper (..), Parser, Quantor (..), Range (..), composeFocusers, focusTo, foldFocusers, foldMappings, integer, lexeme, scient, symbol, mappingTo) import Data.Char (isAlphaNum) import Data.Functor (($>)) import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T import Focusers (focusAverage, focusCollect, focusCols, focusContains, focusEach, focusEndsWith, focusFilter, focusId, focusIf, focusIndex, focusIsAlpha, focusIsAlphaNum, focusIsDigit, focusIsLower, focusIsSpace, focusIsUpper, focusLength, focusLines, focusMaxBy, focusMaxLexBy, focusMinBy, focusMinLexBy, focusProduct, focusRegex, focusSlice, focusSortedBy, focusSortedLexBy, focusSpace, focusStartsWith, focusSum, focusWords) import Mappings (mappingAbs, mappingAdd, mappingAppend, mappingDiv, mappingId, mappingLength, mappingLower, mappingMap, mappingMult, mappingPow, mappingPrepend, mappingReverse, mappingSign, mappingSlice, mappingSortBy, mappingSortLexBy, mappingSub, mappingUpper) import Text.Megaparsec (MonadParsec (try), anySingle, between, choice, empty, label, many, noneOf, notFollowedBy, optional, satisfy, sepBy, sepBy1, takeWhile1P, (<|>)) import Text.Megaparsec.Char (char, string) -- Focuser parsers parseFocuser :: Parser Focuser parseFocuser = label "valid focuser" $ choice [ symbol "id" $> focusId , symbol "each" $> focusEach , parseFocusCollect , symbol "words" $> focusWords , symbol "lines" $> focusLines , symbol "ws" $> focusSpace , symbol "cols" $> focusCols , parseFocusSlice , parseFocusSortedLexBy , symbol "sortedLex" $> focusSortedLexBy focusId , parseFocusMinLexBy , parseFocusMaxLexBy , symbol "minLex" $> focusMinLexBy focusId , symbol "maxLex" $> focusMaxLexBy focusId , parseFocusSortedBy , parseFocusIndex , symbol "sorted" $> focusSortedBy focusId , parseFocusTo , symbol "len" $> focusLength , parseFocusMinBy , parseFocusMaxBy , symbol "min" $> focusMinBy focusId , symbol "max" $> focusMaxBy focusId , between (symbol "(") (symbol ")") $ foldFocusers <$> parseFocusers , symbol "sum" $> focusSum , symbol "product" $> focusProduct , parseFocusAverage , parseFocusAdd , parseFocusSub , parseFocusMult , parseFocusDiv , parseFocusPow , symbol "abs" $> focusTo mappingAbs , symbol "sign" $> focusTo mappingSign , parseFocusIf , symbol "isUpper" $> focusIsUpper , symbol "isLower" $> focusIsLower , symbol "isDigit" $> focusIsDigit , symbol "isAlphaNum" $> focusIsAlphaNum , symbol "isAlpha" $> focusIsAlpha , symbol "isSpace" $> focusIsSpace , parseFocusRegex , parseFocusFilter , parseFocusContains , parseFocusStartsWith , parseFocusEndsWith ] parseFocusers :: Parser [Focuser] parseFocusers = label "valid focuser stack" $ parseFocuser `sepBy1` symbol "." parseFocusCollect :: Parser Focuser parseFocusCollect = do symbol "<" focusers <- parseFocusers symbol ">" let focuser = foldFocusers focusers return $ focusCollect focuser parseFocusSlice :: Parser Focuser parseFocusSlice = do ranges <- between (symbol "{") (symbol "}") (range `sepBy` symbol ",") return $ focusSlice ranges range :: Parser Range range = try rangeRange <|> rangeSingle rangeSingle :: Parser Range rangeSingle = RangeSingle <$> integer rangeRange :: Parser Range rangeRange = label "range" $ do mstart <- lexeme $ optional integer symbol ":" mend <- lexeme $ optional integer return $ RangeRange mstart mend parseFocusSortedBy :: Parser Focuser parseFocusSortedBy = do lexeme $ string "sortedBy" >> notFollowedBy (satisfy isAlphaNum) focusSortedBy <$> parseFocuser parseFocusIndex :: Parser Focuser parseFocusIndex = do symbol "[" n <- integer symbol "]" return $ focusIndex n parseFocusTo :: Parser Focuser parseFocusTo = do lexeme $ string "to" >> notFollowedBy (satisfy isAlphaNum) mapping <- foldMappings <$> parseMappings return $ focusTo mapping parseFocusMinBy :: Parser Focuser parseFocusMinBy = do lexeme $ string "minBy" >> notFollowedBy (satisfy isAlphaNum) focusMinBy <$> parseFocuser parseFocusMaxBy :: Parser Focuser parseFocusMaxBy = do lexeme $ string "maxBy" >> notFollowedBy (satisfy isAlphaNum) focusMaxBy <$> parseFocuser parseFocusSortedLexBy :: Parser Focuser parseFocusSortedLexBy = do lexeme $ string "sortedLexBy" >> notFollowedBy (satisfy isAlphaNum) focusSortedLexBy <$> parseFocuser parseFocusMinLexBy :: Parser Focuser parseFocusMinLexBy = do lexeme $ string "minLexBy" >> notFollowedBy (satisfy isAlphaNum) focusMinLexBy <$> parseFocuser parseFocusMaxLexBy :: Parser Focuser parseFocusMaxLexBy = do lexeme $ string "maxLexBy" >> notFollowedBy (satisfy isAlphaNum) focusMaxLexBy <$> parseFocuser parseFocusAdd :: Parser Focuser parseFocusAdd = do symbol "add " focusTo . mappingAdd <$> scient parseFocusSub :: Parser Focuser parseFocusSub = do symbol "sub " focusTo . mappingSub <$> scient parseFocusMult :: Parser Focuser parseFocusMult = do symbol "mult " focusTo . mappingMult <$> scient parseFocusDiv :: Parser Focuser parseFocusDiv = do symbol "div " focusTo . mappingDiv <$> scient parseFocusPow :: Parser Focuser parseFocusPow = do symbol "pow " focusTo . mappingPow <$> integer parseFocusIf :: Parser Focuser parseFocusIf = do lexeme $ string "if" >> notFollowedBy (satisfy isAlphaNum) ifExpr <- parseIfExpr return $ focusIf ifExpr parseIfExpr :: Parser IfExpr parseIfExpr = label "one or more blocks separated by '||'" $ do andBlocks <- parseAndBlock `sepBy1` symbol "||" case andBlocks of [] -> empty [block] -> return block _ -> return $ IfOr andBlocks parseAndBlock :: Parser IfExpr parseAndBlock = label "one or more blocks separated by '&&'" $ do atoms <- parseAtom `sepBy1` symbol "&&" case atoms of [] -> empty [atom] -> return atom _ -> return $ IfAnd atoms parseAtom :: Parser IfExpr parseAtom = between (symbol "(") (symbol ")") parseIfExpr <|> try parseComp <|> parseIfExprShort parseComp :: Parser IfExpr parseComp = do q1 <- fromMaybe QAll <$> optional parseQuantor lhs <- fromMaybe (EFocuser focusId) <$> optional parseEvaluatableLong comp <- parseCompOp q2 <- fromMaybe QAll <$> optional parseQuantor rhs <- parseEvaluatableLong return $ IfSingle $ Comparison (q1, lhs) comp (q2, rhs) parseQuantor :: Parser Quantor parseQuantor = symbol "all " $> QAll <|> symbol "any " $> QAny parseCompOp :: Parser Oper parseCompOp = choice [ symbol "=" $> OpEq , symbol "!=" $> OpNe , symbol "<=" $> OpLe , symbol "<" $> OpLt , symbol ">=" $> OpGe , symbol ">" $> OpGt ] parseIfExprShort :: Parser IfExpr parseIfExprShort = do q <- fromMaybe QAll <$> optional parseQuantor e <- EFocuser <$> parseFocuser return $ IfSingle $ Comparison (q, e) OpEq (QAny, EText "1") parseFocusRegex :: Parser Focuser parseFocusRegex = do symbol "regex" focusRegex <$> stringLiteral parseFocusFilter :: Parser Focuser parseFocusFilter = do lexeme $ string "filter" >> notFollowedBy (satisfy isAlphaNum) focusFilter <$> parseIfExpr parseFocusContains :: Parser Focuser parseFocusContains = do symbol "contains" focusContains <$> stringLiteral parseFocusStartsWith :: Parser Focuser parseFocusStartsWith = do symbol "startsWith" focusStartsWith <$> stringLiteral parseFocusEndsWith :: Parser Focuser parseFocusEndsWith = do symbol "endsWith" focusEndsWith <$> stringLiteral parseFocusAverage :: Parser Focuser parseFocusAverage = do symbol "average" def <- fromMaybe 0 <$> optional scient return $ focusAverage def -- mapping parsers parseMapping :: Parser Mapping parseMapping = label "valid mapping" $ choice [ symbol "reverse" $> mappingReverse , symbol "len" $> mappingLength , parseMappingMap , parseMappingAppend , parseMappingPrepend , symbol "upper" $> mappingUpper , symbol "lower" $> mappingLower , between (symbol "(") (symbol ")") $ foldMappings <$> parseMappings , parseMappingAdd , parseMappingSub , parseMappingMult , parseMappingDiv , parseMappingPow , symbol "abs" $> mappingAbs , symbol "sign" $> mappingSign , parseMappingSlice , parseMappingSortLexBy , symbol "sortLex" $> mappingSortLexBy focusId , parseMappingSortBy , symbol "sort" $> mappingSortBy focusId , symbol "id" $> mappingId , parseMappingTo ] parseMappings :: Parser [Mapping] parseMappings = label "valid mapping stack" $ parseMapping `sepBy1` symbol ":" parseMappingMap :: Parser Mapping parseMappingMap = do lexeme $ string "map" >> notFollowedBy (satisfy isAlphaNum) mappingMap <$> parseMapping parseEvaluatable :: Parser Evaluatable parseEvaluatable = EText <$> stringLiteral <|> ENumber <$> scient <|> EFocuser <$> parseFocuser parseEvaluatableLong :: Parser Evaluatable parseEvaluatableLong = EText <$> stringLiteral <|> ENumber <$> scient <|> EFocuser . foldFocusers <$> parseFocusers stringLiteral :: Parser Text stringLiteral = label "string literal" $ lexeme $ do char '"' inner <- T.concat <$> many (choice [ takeWhile1P Nothing (\c -> c /= '/' && c /= '"') , try (string "\\\"" $> "\"") , string "\\" ]) char '"' return inner parseMappingAppend :: Parser Mapping parseMappingAppend = do lexeme $ string "append" >> notFollowedBy (satisfy isAlphaNum) mappingAppend <$> parseEvaluatableLong parseMappingPrepend :: Parser Mapping parseMappingPrepend = do lexeme $ string "prepend" >> notFollowedBy (satisfy isAlphaNum) mappingPrepend <$> parseEvaluatableLong parseMappingAdd :: Parser Mapping parseMappingAdd = do symbol "add " mappingAdd <$> scient parseMappingSub :: Parser Mapping parseMappingSub = do symbol "sub " mappingSub <$> scient parseMappingMult :: Parser Mapping parseMappingMult = do symbol "mult " mappingMult <$> scient parseMappingDiv :: Parser Mapping parseMappingDiv = do symbol "div " mappingDiv <$> scient parseMappingPow :: Parser Mapping parseMappingPow = do symbol "pow " mappingPow <$> integer parseMappingSlice :: Parser Mapping parseMappingSlice = do ranges <- between (symbol "{") (symbol "}") (range `sepBy` symbol ",") return $ mappingSlice ranges parseMappingSortBy :: Parser Mapping parseMappingSortBy = do lexeme $ string "sortBy" >> notFollowedBy (satisfy isAlphaNum) mappingSortBy <$> parseFocuser parseMappingSortLexBy :: Parser Mapping parseMappingSortLexBy = do lexeme $ string "sortLexBy" >> notFollowedBy (satisfy isAlphaNum) mappingSortLexBy <$> parseFocuser parseMappingTo :: Parser Mapping parseMappingTo = do lexeme $ string "to" >> notFollowedBy (satisfy isAlphaNum) focuser <- foldFocusers <$> parseFocusers return $ mappingTo focuser