module Lex (eqProp) where
import Control.Applicative
((<$>), (<*>), pure, (*>), (<*), (<|>), empty, many, some)
import Control.Monad (void)
import Control.Monad.Trans.State (StateT (..), evalStateT, get, put)
import Control.Monad.Trans.Class (lift)
import Data.Maybe (listToMaybe, fromMaybe)
import Data.Map (Map)
import qualified Data.Map as Map
import Distribution.TestSuite (Test)
import Distribution.TestSuite.Compat (prop')
type P = StateT String Maybe
satisfy' :: (Char -> Bool) -> P Char
satisfy' p = do
s <- get
case s of
c:cs -> if p c
then put cs *> pure c
else empty
[] -> empty
char' :: Char -> P Char
char' x = satisfy' (== x)
look' :: P String
look' = get
eof' :: P ()
eof' = do
s <- get
case s of
[] -> pure ()
_:_ -> empty
type Var = Int
data Token
= Qualifier Var
| Table Var
| Symbol String
| Op String
| String String
| LParen
| RParen
| Comma
| PlaceHolder
deriving (Eq, Show)
type VarName = String
data QState =
QState
{ nextVar :: Var
, varMap :: Map VarName Var
} deriving Eq
type Parser = StateT QState P
run' :: Parser a -> String -> Maybe (a, String)
run' p = runStateT (evalStateT p (QState { nextVar = 0, varMap = Map.empty }))
char :: Char -> Parser Char
char = lift . char'
satisfy :: (Char -> Bool) -> Parser Char
satisfy = lift . satisfy'
quote :: Parser Char
quote = char '\''
symbolCharset :: [Char]
symbolCharset = '_' : ['0'..'9'] ++ ['A'..'Z'] ++ ['a'..'z']
symbol' :: Parser String
symbol' = some $ satisfy (`elem` symbolCharset)
symbol :: Parser Token
symbol = Symbol <$> symbol'
opCharset :: [Char]
opCharset = "=<>+-*/|"
op :: Parser Token
op = Op <$> some (satisfy (`elem` opCharset))
stringChar :: Parser Char
stringChar = quote *> quote <|> satisfy (`notElem` ("'().\""))
string :: Parser Token
string = String <$> (quote *> many stringChar <* quote)
queryVar :: VarName -> Parser Var
queryVar n = do
s <- get
let m = varMap s
v' = nextVar s
maybe
(do put $ QState { nextVar = v' + 1, varMap = Map.insert n v' m }
return v')
return
$ n `Map.lookup` m
qualified :: Parser Token
qualified = do
s <- symbol'
void $ char '.'
Qualifier <$> queryVar s
table :: Parser Token
table = do
t <- (:) <$> char 'T' <*> some (satisfy (`elem` ['0'..'9']))
Table <$> queryVar t
space :: Parser Char
space = satisfy (`elem` " \t")
someSpaces :: Parser ()
someSpaces = some space *> pure ()
spaces :: Parser ()
spaces = many space *> pure ()
peekChar :: Parser (Maybe Char)
peekChar = listToMaybe <$> lift look'
peekSatisfy :: (Char -> Bool) -> Parser Char
peekSatisfy pre = do
mc <- peekChar
case mc of
Just c | pre c -> pure c
| otherwise -> empty
Nothing -> empty
symbolSep :: Parser ()
symbolSep = peekSatisfy (`elem` ("()," ++ opCharset)) *> return () <|> someSpaces <|> eof
opSep :: Parser ()
opSep = peekSatisfy (`elem` symbolCharset) *> return () <|> someSpaces <|> eof
lParen :: Parser Token
lParen = char '(' *> pure LParen
rParen :: Parser Token
rParen = char ')' *> pure RParen
comma :: Parser Token
comma = char ',' *> pure Comma
placeholder :: Parser Token
placeholder = char '?' *> pure PlaceHolder
eof :: Parser ()
eof = lift eof'
token :: Parser Token
token =
qualified <|>
table <* symbolSep <|>
symbol <* symbolSep <|>
op <* opSep <|>
string <|>
lParen <|>
rParen <|>
comma <|>
placeholder
tokens :: Parser [Token]
tokens = (many $ token <* spaces) <* eof
run :: String -> Maybe [Token]
run = (fst <$>) . run' tokens
eq :: String -> String -> Bool
eq a b = fromMaybe False $ do
x <- run a
y <- run b
return $ x == y
eqProp' :: String -> (a -> String) -> a -> String -> Test
eqProp' name t x est = prop' name (Just em) (t x `eq` est)
where em = unlines [show $ run $ t x, " -- compares --", show $ run est]
eqProp :: Show a => String -> a -> String -> Test
eqProp name = eqProp' name show