{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module FlatBuffers.Internal.Compiler.ParserIO where
import Control.Monad ( when )
import Control.Monad.Except ( MonadError, MonadIO, liftIO, throwError )
import Control.Monad.State ( MonadState, execStateT, get, put )
import Data.Coerce ( coerce )
import Data.Foldable ( traverse_ )
import Data.Map.Strict ( Map )
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import FlatBuffers.Internal.Compiler.Display ( display )
import FlatBuffers.Internal.Compiler.Parser ( schema )
import FlatBuffers.Internal.Compiler.SyntaxTree ( FileTree(..), Include(..), Schema, StringLiteral(..), includes )
import qualified System.Directory as Dir
import qualified System.FilePath as FP
import Text.Megaparsec ( errorBundlePretty, parse )
parseSchemas ::
MonadIO m
=> MonadError String m
=> FilePath
-> [FilePath]
-> m (FileTree Schema)
parseSchemas rootFilePath includeDirs = do
fileContent <- liftIO $ readFile rootFilePath
case parse schema rootFilePath fileContent of
Left err -> throwError $ errorBundlePretty err
Right rootSchema -> do
rootFilePathCanon <- liftIO $ Dir.canonicalizePath rootFilePath
let importedFilePaths = T.unpack . coerce <$> includes rootSchema
importedSchemas <- flip execStateT Map.empty $
traverse_
(parseImportedSchema includeDirs rootFilePathCanon)
importedFilePaths
pure FileTree
{ fileTreeFilePath = rootFilePathCanon
, fileTreeRoot = rootSchema
, fileTreeForest = importedSchemas
}
parseImportedSchema ::
MonadState (Map FilePath Schema) m
=> MonadIO m
=> MonadError String m
=> [FilePath]
-> FilePath
-> FilePath
-> m ()
parseImportedSchema includeDirs rootFilePathCanon filePath =
go rootFilePathCanon filePath
where
go parentSchemaPath filePath = do
let parentSchemaDir = FP.takeDirectory parentSchemaPath
let dirCandidates = parentSchemaDir : includeDirs
actualFilePathCanonMaybe <- liftIO $ Dir.findFile dirCandidates filePath >>= traverse Dir.canonicalizePath
case actualFilePathCanonMaybe of
Nothing -> throwError $
"File '"
<> filePath
<> "' (imported from '"
<> parentSchemaPath
<> "') not found.\nSearched in these directories: "
<> display dirCandidates
Just actualFilePathCanon -> do
importedSchemas <- get
when (actualFilePathCanon /= rootFilePathCanon && actualFilePathCanon `Map.notMember` importedSchemas) $ do
fileContent <- liftIO $ readFile actualFilePathCanon
case parse schema actualFilePathCanon fileContent of
Left err -> throwError $ errorBundlePretty err
Right importedSchema -> do
put (Map.insert actualFilePathCanon importedSchema importedSchemas)
traverse_ (go actualFilePathCanon . T.unpack . coerce) (includes importedSchema)