content: import Data.List (isPrefixOf)
  import Data.Maybe (catMaybes)
  import Control.Monad
  import Text.ParserCombinators.Parsec
  import System.IO
  import System.Environment (getArgs)
  
  main = do
     args <- getArgs
     unless (length args == 1) $
         fail "Please provide exactly one source file as an argument."
     let sourcePath = head args
     source <- readFile sourcePath
     input <- getContents
     case parse markovParser sourcePath source of
         Right rules -> putStr $ runMarkov rules input
         Left  err   -> hPutStrLn stderr $ "Parse error at " ++ show err
  
  data Rule = Rule
     {from :: String, terminating :: Bool, to :: String}
  
  markovParser :: Parser [Rule]
  markovParser = liftM catMaybes $
      (comment <|> rule) `sepEndBy` many1 newline
    where comment = char '#' >> skipMany nonnl >> return Nothing
          rule = liftM Just $ liftM3 Rule
              (manyTill (nonnl <?> "pattern character") $ try arrow)
              (succeeds $ char '.')
              (many nonnl)
          arrow = ws >> string "->" >> ws <?> "whitespace-delimited arrow"
          nonnl = noneOf "\n"
          ws = many1 $ oneOf " \t"
          succeeds p = option False $ p >> return True
  
  runMarkov :: [Rule] -> String -> String
  runMarkov rules s = f rules s
    where f []                              s = s
          f (Rule from terminating to : rs) s = g "" s
            where g _      ""    = f rs s
                  g before ahead@(a : as) = if from `isPrefixOf` ahead
                    then let new = reverse before ++ to ++ drop (length from) ahead
                         in if terminating then new else f rules new
                    else g (a : before) as
  