{-# LANGUAGE DeriveFunctor #-} module Plot ( Plot, Line (..), Point (..), emptyPlot, addLine, parsePlot, plotLines, plotPoints, ToXML (..), ) where import Data.Char (ord) class ToXML a where toXML :: a -> String data Plot = MkPlot { plotLines :: [Line], plotPoints :: [Point] } deriving (Show, Eq) instance Semigroup Plot where a <> b = MkPlot { plotLines = plotLines a <> plotLines b, plotPoints = plotPoints a <> plotPoints b } instance Monoid Plot where mempty = MkPlot [] [] mappend = (<>) instance ToXML Plot where toXML (MkPlot lines points) = let xmlLines = map toXML lines xmlPoints = map toXML points in unlines $ [ "", "", "", "" ] ++ xmlLines ++ [ "", "" ] ++ xmlPoints ++ [ "", "" ] emptyPlot :: Plot emptyPlot = MkPlot [] [] addLine :: Line -> Plot -> Plot addLine l p = p {plotLines = l : plotLines p} data Point = Point { x :: Double, y :: Double } deriving (Show, Eq) instance ToXML Point where toXML (Point x y) = "" data Line = Line { start :: Point, end :: Point } deriving (Show, Eq) instance ToXML Line where toXML (Line (Point x1 y1) (Point x2 y2)) = concat [ "" ] -- Parsing newtype Parser a = Parser {runParser :: String -> Either ParseError (String, a)} deriving (Functor) executeParser :: Parser a -> String -> Either ParseError a executeParser p = fmap snd . runParser p instance Applicative Parser where pure x = Parser $ \s -> return (s, x) ab <*> a = Parser $ \s -> do (s', ab') <- runParser ab s (s'', a') <- runParser a s' return (s'', ab' a') instance Monad Parser where return = pure pa >>= apb = Parser $ \s -> do runParser pa s >>= uncurry (flip (runParser . apb)) data ParseError = InvalidInput deriving (Show, Eq) (<|>) :: Parser a -> Parser a -> Parser a a <|> b = Parser $ \s -> either (const $ runParser b s) return $ runParser a s eof :: Parser () eof = Parser $ \s -> if null s then return (s, ()) else Left InvalidInput -- Parsing parsePlot :: [String] -> Either ParseError Plot parsePlot = foldl ( \acc cur -> do p <- executeParser parseInputLine cur (<> p) <$> acc ) (Right emptyPlot) parseInputLine :: Parser Plot parseInputLine = ( (lineParser >>= \l -> return $ emptyPlot {plotLines = [l]}) <|> (pointParser >>= \p -> return $ emptyPlot {plotPoints = [p]}) ) <* eof lineParser :: Parser Line lineParser = Line <$> (pointParser <* commaParser) <*> pointParser pointParser :: Parser Point pointParser = Point <$> (fromIntegral <$> (intParser <* commaParser)) <*> (fromIntegral <$> intParser) intParser :: Parser Integer intParser = Parser $ \s -> if null s then Left InvalidInput else go 0 s where go acc "" = return ("", acc) go acc s@(x : xs) = maybe (return (s, acc)) (\n -> go (acc * 10 + fromIntegral n) xs) $ digitToInt x -- see challenge 162 ch-1.hs digitToInt :: Char -> Maybe Int digitToInt c = let digit = ord c - ord '0' in if 0 <= digit && digit <= 9 then return digit else Nothing commaParser :: Parser () commaParser = Parser $ \s -> if not (null s) && head s == ',' then return (tail s, ()) else Left InvalidInput