{-# LANGUAGE FlexibleInstances#-} {-| Module : FZSolutionParser Description : FlatZinc solutions parser Copyright : (c) Some Guy, 2013 Someone Else, 2014 License : GPL-3 Maintainer : Klara Marntirosian Stability : experimental This module parses the solutions outputed by the specified FlatZinc solver. It supports multiple solutions. The parser might fail if there is a show item in the represented MiniZinc model which alters the default format of the solutions' output. -} module Interfaces.FZSolutionParser ( getSolution, printSolution, getSolutionFromFile, printSolutionFromFile, Solution, MValue(..) ) where import Data.Char import Control.Applicative import qualified Data.Set as S import qualified Text.Parsec as P import qualified Text.Parsec.Char as C import Text.Parsec.String (Parser) -- Next two modules for testing only --import GHC.Generics --import Control.DeepSeq -- | A Solution consists of a list of pairs. Each pair represents an assignment of a value to -- a decision variable of the constraint model. type Solution = [(String, MValue)] -- | Representation of returned values. data MValue = MError String | MInt Int | MFloat Float | MBool Bool | MString String | MArray [MValue] | MSet (S.Set MValue) deriving Show --deriving (Show, Generic, NFData) -- | Returns either a parse error or a list of solutions of the constraint model, parsed from -- the file where they are printed. The length of the list is specified by the second argument -- of the function. getSolutionFromFile :: FilePath -> Int -> IO (Either P.ParseError [Solution]) getSolutionFromFile path n = do output <- readFile path return $ runParser (trySolutions n) output -- | Prints either a parse error or a list of solutions of the constraint model parsed from -- a file where they are printed. The length of the list is sepcified by the second argument -- of the function. printSolutionFromFile :: Int -> FilePath -> IO () printSolutionFromFile n path = do output <- readFile path print $ runParser (trySolutions n) output -- | Same as @getSolutionFromFile@ but parses the string argument of the function instead -- of the contents of a file. getSolution :: Int -> String -> Either P.ParseError [Solution] getSolution n = runParser (trySolutions n) -- | Same as @printSolutionFromFile@ but parses the string argument of the function instead -- of the contents of a file. printSolution :: Int -> String -> IO () printSolution n = print . runParser (trySolutions n) -- Auxiliary definitions digit :: Parser Char digit = C.digit anyChar :: Parser Char anyChar = C.anyChar char :: Char -> Parser Char char = C.char sepBy :: Parser a -> Parser b -> Parser [a] sepBy = P.sepBy between :: Parser a -> Parser b -> Parser c -> Parser c between = P.between manyTill :: Parser a -> Parser b -> Parser [a] manyTill = P.manyTill many1 :: Parser a -> Parser [a] many1 = P.many1 skipMany :: Parser a -> Parser () skipMany = P.skipMany anyToken = P.anyToken eof :: Parser () eof = P.eof endOfLine :: Parser Char endOfLine = C.endOfLine string :: String -> Parser String string = C.string spaces :: Parser () spaces = C.spaces parse :: Parser a -> P.SourceName -> String -> Either P.ParseError a parse = P.parse try :: Parser a -> Parser a try = P.try ----------------------- runParser :: Parser a -> String -> Either P.ParseError a runParser p = parse (p <* eof) "" trySolutions :: Int -> Parser [Solution] trySolutions n = (try (takeSolutions n) <|> (unsat >> return [[]])) unsat :: Parser String unsat = skipMany comment *> (string "=====UNSATISFIABLE=====") <* endOfLine <* many comment takeSolutions :: Int -> Parser [Solution] takeSolutions n = take n <$> (solutions) solutions :: Parser [Solution] solutions = manyTill solution (string "==========" >> endOfLine) solution :: Parser [(String, MValue)] solution = (many $ (skipMany comment) *> (assigned >>= return)) <* string "----------" <* endOfLine comment :: Parser String comment = char '%' *> (manyTill anyToken endOfLine) *> return "" assigned :: Parser (String, MValue) assigned = do name <- varName value <- valueParser char ';' endOfLine return (name, value) varName :: Parser String varName = manyTill (C.noneOf "-=%") ((C.space >> char '=' >> C.space) <|> char '=') valueParser :: Parser MValue valueParser = try floatM <|> intM <|> boolM <|> (setM scalar) <|> (array scalar) <|> stringM intM :: Parser MValue intM = MInt <$> int boolM :: Parser MValue boolM = MBool <$> bool floatM :: Parser MValue floatM = MFloat <$> float stringM :: Parser MValue stringM = MString <$> (many anyChar) setM :: Parser MValue -> Parser MValue setM p = (MSet <$> S.fromDistinctAscList <$> (set p)) <|> setRange int :: Parser Int int = (char '-' >> opposite ) <|> natural bool :: Parser Bool bool = string "true" >> return True <|> (string "false" >> return False) float :: Parser Float float = do ipart <- many1 digit char '.' dpart <- many1 digit let a = read (ipart ++ "." ++ dpart) :: Float in return a set :: Parser a -> Parser [a] set p = between (char '{') (char '}') (sepBy p (string "," >> spaces)) setRange :: Parser MValue setRange = MSet <$> S.fromDistinctAscList <$> do v1 <- int string ".." v2 <- int return (map MInt (take (v2 - v1 + 1) (iterate ((+) 1) v1))) array :: Parser MValue -> Parser MValue array p = do string "array" manyTill anyChar (char '(') ls <- arraySizes es <- extract p string ")" return (fixDims ls es) natural :: Parser Int natural = P.chainl1 digitValue ascendDecimal opposite :: Parser Int opposite = (0 - ) <$> natural digitValue :: Parser Int digitValue = do d <- digit return $ ord(d) - ord('0') ascendDecimal :: Parser (Int -> Int -> Int) ascendDecimal = do return $ \x y -> x*10 + y indexRange :: Parser Int indexRange = do a <- int string ".." b <- int return (b - a + 1) arraySizes :: Parser [Int] arraySizes = P.sepEndBy1 indexRange (string "," >> spaces) extract :: Parser MValue -> Parser [MValue] extract p = between (char '[') (char ']') (sepBy p (string "," >> spaces)) fixDims :: [Int] -> [MValue] -> MValue fixDims [] _ = MError "Array dimensions error: fixDims applied on empty list" fixDims [d] ms = MArray $ ms fixDims ds ms = fixDims (init ds) (fix1Dim (last ds) ms) fix1Dim :: Int -> [MValue] -> [MValue] fix1Dim _ [] = [] fix1Dim d ms = MArray (take d ms) : (fix1Dim d (drop d ms)) scalar :: Parser MValue scalar = try floatM <|> intM <|> boolM <|> stringM -- for testing purposes parseWithLeftOver :: Parser a -> String -> Either P.ParseError (a,String) parseWithLeftOver p = parse ((,) <$> p <*> leftOver) "" where leftOver = manyTill anyToken eof