{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-| Module : Text.Pretty.Simple.Internal.ExprParser Copyright : (c) Dennis Gosnell, 2016 License : BSD-style (see LICENSE file) Maintainer : cdep.illabout@gmail.com Stability : experimental Portability : POSIX -} module Text.Pretty.Simple.Internal.ExprParser where #if __GLASGOW_HASKELL__ < 710 -- We don't need this import for GHC 7.10 as it exports all required functions -- from Prelude import Control.Applicative #endif import Control.Applicative ((<|>)) import Data.Functor.Identity (Identity) import Text.Parsec (Parsec, ParseError, between, char, many, noneOf, parserFail, runParser) import Text.Parsec.Language (haskellDef) import qualified Text.Parsec.Token as Token import Text.Pretty.Simple.Internal.Expr (CommaSeparated(..), Expr(..)) -- $setup -- >>> import Data.Either (isLeft) -- >>> :{ -- let test :: Parser a -> String -> Either ParseError a -- test parser = runParser parser () "(no source)" -- :} type Parser = Parsec String () ---------------------------- -- Lexer helper functions -- ---------------------------- lexer :: Token.GenTokenParser String u Identity lexer = Token.makeTokenParser haskellDef stringLiteral :: Parser String stringLiteral = Token.stringLiteral lexer brackets :: Parser a -> Parser a brackets = between (char '[') (char ']') braces :: Parser a -> Parser a braces = between (char '{') (char '}') parens :: Parser a -> Parser a parens = between (char '(') (char ')') commaSep :: Parser a -> Parser [a] commaSep = Token.commaSep lexer lexeme :: Parser a -> Parser a lexeme = Token.lexeme lexer ------------ -- Parser -- ------------ expr :: Parser [Expr] expr = many expr' expr' :: Parser Expr expr' = recursiveExpr <|> nonRecursiveExpr -- | Parse brackets around a list of expressions. -- -- >>> test bracketsExpr "[hello\"what\", foo]" -- Right (Brackets (CommaSeparated {unCommaSeparated = [[Other "hello",StringLit "what"],[Other "foo"]]})) -- >>> test bracketsExpr "[[] ]" -- Right (Brackets (CommaSeparated {unCommaSeparated = [[Brackets (CommaSeparated {unCommaSeparated = []}),Other " "]]})) bracketsExpr :: Parser Expr bracketsExpr = Brackets <$> recursiveSurroundingExpr brackets bracesExpr :: Parser Expr bracesExpr = Braces <$> recursiveSurroundingExpr braces parensExpr :: Parser Expr parensExpr = Parens <$> recursiveSurroundingExpr parens recursiveSurroundingExpr :: (forall a. Parser a -> Parser a) -> Parser (CommaSeparated [Expr]) recursiveSurroundingExpr surround = do res <- surround (commaSep expr) case res of [[]] -> pure $ CommaSeparated [] [] -> pure $ CommaSeparated [] _ -> pure $ CommaSeparated res recursiveExpr :: Parser Expr recursiveExpr = do bracketsExpr <|> parensExpr <|> bracesExpr -- | Parse a string literal. -- -- >>> test stringLiteralExpr "\"hello\"" -- Right (StringLit "hello") -- -- >>> isLeft $ test stringLiteralExpr " \"hello\"" -- True stringLiteralExpr :: Parser Expr stringLiteralExpr = StringLit <$> stringLiteral nonRecursiveExpr :: Parser Expr nonRecursiveExpr = do stringLiteralExpr <|> anyOtherText -- | Parse anything that doesn't get parsed by the parsers above. -- -- >>> test anyOtherText " Foo " -- Right (Other " Foo ") -- -- Parse empty strings. -- -- >>> test anyOtherText " " -- Right (Other " ") -- -- Stop parsing if we hit @\[@, @\]@, @\(@, @\)@, @\{@, @\}@, @\"@, or @,@. -- -- >>> test anyOtherText "hello[" -- Right (Other "hello") -- -- Don\'t parse the empty string. -- -- >>> isLeft $ test anyOtherText "" -- True -- >>> isLeft $ test anyOtherText "," -- True anyOtherText :: Parser Expr anyOtherText = do res <- many (Text.Parsec.noneOf "[](){},\"") case res of "" -> parserFail "Trying to apply anyOtherText to an empty string. This doesn't work." _ -> pure $ Other res testString1, testString2 :: String testString1 = "Just [TextInput {textInputClass = Just (Class {unClass = \"class\"}), textInputId = Just (Id {unId = \"id\"}), textInputName = Just (Name {unName = \"name\"}), textInputValue = Just (Value {unValue = \"value\"}), textInputPlaceholder = Just (Placeholder {unPlaceholder = \"placeholder\"})}, TextInput {textInputClass = Just (Class {unClass = \"class\"}), textInputId = Just (Id {unId = \"id\"}), textInputName = Just (Name {unName = \"name\"}), textInputValue = Just (Value {unValue = \"value\"}), textInputPlaceholder = Just (Placeholder {unPlaceholder = \"placeholder\"})}]" testString2 = "some stuff (hello [\"dia\\x40iahello\", why wh, bye] ) (bye)" expressionParse :: String -> Either ParseError [Expr] expressionParse = runParser expr () "(no source)"