{-# LANGUAGE OverloadedStrings #-} module Language.PureScript.Bridge.Printer where import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Monoid import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T import System.Directory import System.FilePath import Control.Monad import Language.PureScript.Bridge.SumType import Language.PureScript.Bridge.TypeInfo data PSModule = PSModule { psModuleName :: !Text , psImportLines :: !(Map Text ImportLine) , psTypes :: ![SumType] } deriving Show data ImportLine = ImportLine { importModule :: !Text , importTypes :: !(Set Text) } deriving Show type Modules = Map Text PSModule type ImportLines = Map Text ImportLine printModule :: FilePath -> PSModule -> IO () printModule root m = do unlessM (doesDirectoryExist mDir) $ createDirectoryIfMissing True mDir T.writeFile mPath . moduleToText $ m where mFile = (joinPath . map T.unpack . T.splitOn "." $ psModuleName m) <> ".purs" mPath = root mFile mDir = takeDirectory mPath moduleToText :: PSModule -> Text moduleToText m = T.unlines $ "module " <> psModuleName m <> " where\n" : map importLineToText (M.elems (psImportLines m)) ++ [ "\nimport Data.Generic (class Generic)\n\n" ] ++ map sumTypeToText (psTypes m) importLineToText :: ImportLine -> Text importLineToText l = "import " <> importModule l <> " (" <> typeList <> ")" where typeList = T.intercalate ", " (S.toList (importTypes l)) sumTypeToText :: SumType -> Text sumTypeToText (SumType t cs) = T.unlines $ "data " <> typeName t <> " =" : [ " " <> T.intercalate "\n | " (map (constructorToText 4) cs) ] ++ [ "\nderive instance generic" <> typeName t <> " :: Generic " <> typeName t <> "\n" ] constructorToText :: Int -> DataConstructor -> Text constructorToText _ (DataConstructor n (Left ts)) = n <> " " <> T.intercalate " " (map typeInfoToText ts) constructorToText indentation (DataConstructor n (Right rs)) = T.unlines $ n <> " {" : [ spaces (indentation + 2) <> T.intercalate intercalation (map recordEntryToText rs) ] ++ [ spaces indentation <> "}" ] where intercalation = "\n" <> spaces indentation <> "," <> " " spaces c = T.replicate c " " recordEntryToText :: RecordEntry -> Text recordEntryToText e = recLabel e <> " :: " <> typeInfoToText (recValue e) typeInfoToText :: TypeInfo -> Text typeInfoToText t = if length textParameters > 1 then "(" <> inner <> ")" else inner where inner = typeName t <> T.intercalate " " textParameters textParameters = map typeInfoToText (typeParameters t) sumTypesToModules :: Modules -> [SumType] -> Modules sumTypesToModules = foldr sumTypeToModule sumTypeToModule :: SumType -> Modules -> Modules sumTypeToModule st@(SumType t _) = M.alter (Just. updateModule) (typeModule t) where updateModule Nothing = PSModule { psModuleName = typeModule t , psImportLines = dropSelf $ typesToImportLines M.empty (getUsedTypes st) , psTypes = [st] } updateModule (Just m) = m { psImportLines = dropSelf $ typesToImportLines (psImportLines m) (getUsedTypes st) , psTypes = st : psTypes m } dropSelf = M.delete (typeModule t) typesToImportLines :: ImportLines -> [TypeInfo] -> ImportLines typesToImportLines = foldr typeToImportLines typeToImportLines :: TypeInfo -> ImportLines -> ImportLines typeToImportLines t = M.alter (Just . updateLine) (typeModule t) where updateLine Nothing = ImportLine (typeModule t) (S.singleton (typeName t)) updateLine (Just (ImportLine m types)) = ImportLine m $ S.insert (typeName t) types unlessM :: Monad m => m Bool -> m () -> m () unlessM mbool action = mbool >>= flip unless action