brittany/doc/showcases/Parser.md

7.1 KiB

Input

contains some very long lines and strange formatting of the list/monad-comprehension:

parseCalUnits :: forall m . (MonadMultiReader ParserWrapperGuard m, MonadMultiState ParserFileCache m, MonadIO m) => [GenericParser] -> [FilePath] -> m (Either Text [CalUnit])
parseCalUnits parsers inputs = fmap (fmap join . sequence) $ inputs `forM` \path -> do
  readCachedFile path (\raw -> parseChunks $ createChunks $ (Text.pack path, raw))
 where

  createChunks :: (Text, Text) -> [(Text, [(Int, InputLine)])]
  createChunks (file, input) = fmap ((,) file) $ groupBy (\a b -> grouper (snd a) (snd b)) $ dropWhile (lineIsSpace . snd) $ zip [1 ..] $ lineMapper <$> Text.lines input
   where
    headerParser :: Parser (Text, Bool, Text)
    headerParser =
      [ (Text.pack name, isMain, Text.pack typ)
        | _ <- string "node", _ <- many1 $ oneOf " \t",
          isMain <- fmap isJust $ optionMaybe (string "*" *> many1 (oneOf " \t")),
          name <- many1 $ noneOf " \t\r\n:",
          _ <- many $ oneOf " \t",
          _ <- char ':',
          _ <- many $ oneOf " \t",
          typ <- many1 $ satisfy $ not . isSpace,
          _ <- many $ oneOf " \t",
          _ <- eof ]
    lineMapper :: Text -> InputLine
    lineMapper line = case runParser headerParser () "" line of
      Left  _e -> NormalLine line
      Right (n, m, t) -> HeaderLine n m t
    lineIsSpace :: InputLine -> Bool
    lineIsSpace (NormalLine x) = Text.null $ Text.strip x
    lineIsSpace _ = False
    grouper :: InputLine -> InputLine -> Bool
    grouper _ HeaderLine{} = False
    grouper _ _ = True

  parseChunks :: [(Text, [(Int, InputLine)])] -> m (Either Text [CalUnit])
  parseChunks nodes = runEitherT $ sequence (uncurry parseNode <$> nodes)
   where
    findParser :: Text -> Maybe GenericParser
    findParser pid = find (\(GenericParser p) -> parser_ident p == pid) parsers
    parseNode :: Text -> [(Int, InputLine)] -> EitherT Text m CalUnit
    parseNode file ((n, HeaderLine name isMain typ):rlines) = do
      (GenericParser p) <- case findParser typ of
        Nothing -> left $ Text.pack "could not find parser for node \"" <> name <> Text.pack "\" of type \"" <> typ <> Text.pack "\" at " <> file <> Text.pack (": " ++ show n ++ ".")
        Just x -> return x
      let firstLine = fromMaybe 0 $ fst <$> listToMaybe rlines
      ParserDataStore dstore version <- _pfc_store <$> mGet
      let postfix = Text.pack "\n(When parsing node \"" <> name <> Text.pack "\")"
      parsed <- bimapEitherT (<>postfix) id $ hoistEither $ parser_parse p file firstLine $ Text.unlines $ [ fst $ Text.breakOn (Text.pack "--") line | (_, NormalLine line) <- rlines ]
      let oldDat = case M.lookup name dstore of
            Nothing -> parser_zero p $> version
            Just s -> case decodeOrFail s of
              Right (rest, _, decoded) | BSL.null rest -> decoded
              _ -> parser_zero p $> version
      let (newDat, delta) = parser_diff p version oldDat (parsed $> version)
      pfc <- mGet
      mSet $ pfc { _pfc_store = ParserDataStore (M.insert name (encode newDat) dstore) version }
      let calUnit = parser_build p version name (newDat, delta)
      return $ CalUnit isMain calUnit
    parseNode file ((n, _):_) = left $ Text.pack "expected node definition at " <> file <> Text.pack (": " ++ show n ++ ".")
    parseNode file _ = left $ Text.pack "expected node definition at " <> file <> Text.pack "."
~~~~.hs

### Brittany 0.8.0.1 output on default settings

~~~~.hs
parseCalUnits
  :: forall m
   . ( MonadMultiReader ParserWrapperGuard m
     , MonadMultiState ParserFileCache m
     , MonadIO m
     )
  => [GenericParser]
  -> [FilePath]
  -> m (Either Text [CalUnit])
parseCalUnits parsers inputs =
  fmap (fmap join . sequence) $ inputs `forM` \path -> do
    readCachedFile
      path
      (\raw -> parseChunks $ createChunks $ (Text.pack path, raw))
 where

  createChunks :: (Text, Text) -> [(Text, [(Int, InputLine)])]
  createChunks (file, input) =
    fmap ((,) file)
      $   groupBy (\a b -> grouper (snd a) (snd b))
      $   dropWhile (lineIsSpace . snd)
      $   zip [1 ..]
      $   lineMapper
      <$> Text.lines input
   where
    headerParser :: Parser (Text, Bool, Text)
    headerParser =
      [ (Text.pack name, isMain, Text.pack typ)
      | _      <- string "node"
      , _      <- many1 $ oneOf " \t"
      , isMain <- fmap isJust $ optionMaybe (string "*" *> many1 (oneOf " \t"))
      , name   <- many1 $ noneOf " \t\r\n:"
      , _      <- many $ oneOf " \t"
      , _      <- char ':'
      , _      <- many $ oneOf " \t"
      , typ    <- many1 $ satisfy $ not . isSpace
      , _      <- many $ oneOf " \t"
      , _      <- eof
      ]
    lineMapper :: Text -> InputLine
    lineMapper line = case runParser headerParser () "" line of
      Left  _e        -> NormalLine line
      Right (n, m, t) -> HeaderLine n m t
    lineIsSpace :: InputLine -> Bool
    lineIsSpace (NormalLine x) = Text.null $ Text.strip x
    lineIsSpace _              = False
    grouper :: InputLine -> InputLine -> Bool
    grouper _ HeaderLine{} = False
    grouper _ _            = True

  parseChunks :: [(Text, [(Int, InputLine)])] -> m (Either Text [CalUnit])
  parseChunks nodes = runEitherT $ sequence (uncurry parseNode <$> nodes)
   where
    findParser :: Text -> Maybe GenericParser
    findParser pid = find (\(GenericParser p) -> parser_ident p == pid) parsers
    parseNode :: Text -> [(Int, InputLine)] -> EitherT Text m CalUnit
    parseNode file ((n, HeaderLine name isMain typ):rlines) = do
      (GenericParser p) <- case findParser typ of
        Nothing ->
          left
            $  Text.pack "could not find parser for node \""
            <> name
            <> Text.pack "\" of type \""
            <> typ
            <> Text.pack "\" at "
            <> file
            <> Text.pack (": " ++ show n ++ ".")
        Just x -> return x
      let firstLine = fromMaybe 0 $ fst <$> listToMaybe rlines
      ParserDataStore dstore version <- _pfc_store <$> mGet
      let postfix =
            Text.pack "\n(When parsing node \"" <> name <> Text.pack "\")"
      parsed <-
        bimapEitherT (<>postfix) id
        $ hoistEither
        $ parser_parse p file firstLine
        $ Text.unlines
        $ [ fst $ Text.breakOn (Text.pack "--") line
          | (_, NormalLine line) <- rlines
          ]
      let oldDat = case M.lookup name dstore of
            Nothing -> parser_zero p $> version
            Just s  -> case decodeOrFail s of
              Right (rest, _, decoded) | BSL.null rest -> decoded
              _ -> parser_zero p $> version
      let (newDat, delta) = parser_diff p version oldDat (parsed $> version)
      pfc <- mGet
      mSet $ pfc
        { _pfc_store = ParserDataStore (M.insert name (encode newDat) dstore)
                                       version
        }
      let calUnit = parser_build p version name (newDat, delta)
      return $ CalUnit isMain calUnit
    parseNode file ((n, _):_) =
      left $ Text.pack "expected node definition at " <> file <> Text.pack
        (": " ++ show n ++ ".")
    parseNode file _ =
      left $ Text.pack "expected node definition at " <> file <> Text.pack "."