annah-1.0.0: exec/Main.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Annah.Core (Data(..), Expr(..), Type(..))
import Control.Applicative ((<|>))
import Control.Exception (Exception, throwIO)
import Control.Monad (forM_)
import Data.Monoid (mempty)
import Data.Text.Lazy (fromStrict)
import Filesystem.Path (FilePath, (</>))
import Morte.Core (Path(..), Var(..))
import Options.Applicative
import Prelude hiding (FilePath)
import System.IO (stderr)
import qualified Annah.Core as Annah
import qualified Annah.Parser as Annah
import qualified Data.Text.Lazy as Text
import qualified Data.Text.Lazy.IO as Text
import qualified Filesystem
import qualified Filesystem.Path.CurrentOS as Filesystem
import qualified Morte.Core as Morte
import qualified Morte.Import as Morte
throws :: Exception e => Either e a -> IO a
throws (Left e) = throwIO e
throws (Right r) = return r
data Mode = Default | Compile FilePath | Desugar | Types
parser :: Parser Mode
parser
= subparser
( command "compile"
(info (helper <*> (Compile <$> parseFilePath))
( fullDesc
<> header "annah compile - Compile Annah code"
<> progDesc "Compile an Annah program located at the given \
\file path. Prefer this subcommand over reading \
\from standard input when you want remote \
\references to be resolved relative to that \
\file's path"
)
)
<> metavar "compile"
)
<|> subparser
( command "desugar"
(info (helper <*> pure Desugar)
( fullDesc
<> header "annah desugar - Desugar Annah code"
<> progDesc "Desugar an Annah program to the equivalent Morte \
\program, reading the Annah program from standard \
\input and writing the Morte program to standard \
\output."
)
)
<> metavar "desugar"
)
<|> subparser
( command "types"
( info (helper <*> pure Types)
( fullDesc
<> header "annah types - Compile an Annah datatype definition"
<> progDesc "Translate an Annah datatype definition to the \
\equivalent set of files, reading the datatype \
\definition from standard input. This creates \
\one output directory for each type with one file \
\underneath each directory per data constructor \
\associated with that type."
)
)
<> metavar "types"
)
<|> pure Default
where
parseFilePath =
fmap Filesystem.decodeString
(strArgument (metavar "FILEPATH" <> help "Path to file to compile"))
main :: IO ()
main = do
mode <- execParser $ info (helper <*> parser)
( fullDesc
<> header "annah - A strongly typed, purely functional language"
<> progDesc "Annah is a medium-level language that is a superset of \
\Morte. Use this compiler to desugar Annah code to Morte \
\code."
)
case mode of
Default -> do
txt <- Text.getContents
ae <- throws (Annah.exprFromText txt)
let me = Annah.desugar ae
-- Only statically link the Morte expression for type-checking
me' <- Morte.load Nothing me
mt <- throws (Morte.typeOf me')
-- Return the dynamically linked Morte expression
Text.putStrLn (Morte.pretty (Morte.normalize me))
Compile file -> do
txt <- Text.readFile (Filesystem.encodeString file)
ae <- throws (Annah.exprFromText txt)
let me = Annah.desugar ae
-- Only statically link the Morte expression for type-checking
me' <- Morte.load (Just (File file)) me
mt <- throws (Morte.typeOf me')
-- Return the dynamically linked Morte expression
Text.putStrLn (Morte.pretty (Morte.normalize me))
Desugar -> do
txt <- Text.getContents
ae <- throws (Annah.exprFromText txt)
Text.putStrLn (Morte.pretty (Annah.desugar ae))
Types -> do
-- TODO: Handle duplicate type and data constructor names
txt <- Text.getContents
ts <- throws (Annah.typesFromText txt)
let write file txt =
Filesystem.writeTextFile file (Text.toStrict txt <> "\n")
let named = Filesystem.fromText . Text.toStrict
forM_ ts (\t -> do
let typeDir = named (typeName t)
let typeAnnahFile = named (typeName t <> ".annah")
let typeMorteFile = typeDir </> "@"
let foldAnnahFile = typeDir </> named (typeFold t <> ".annah")
let foldMorteFile = typeDir </> named (typeFold t)
Filesystem.createDirectory True typeDir
write typeAnnahFile (txt <> "in " <> typeName t)
let e0 = Family ts (Var (V (typeName t) 0))
let typeTxt = Morte.pretty (Morte.normalize (Annah.desugar e0))
write typeMorteFile typeTxt
if typeFold t /= "_"
then do
write foldAnnahFile (txt <> "in " <> typeFold t)
let e1 = Family ts (Var (V (typeFold t) 0))
let foldTxt =
Morte.pretty (Morte.normalize (Annah.desugar e1))
write foldMorteFile foldTxt
else return ()
forM_ (typeDatas t) (\d -> do
let dataAnnahName = named (dataName d <> ".annah")
let dataMorteName = named (dataName d)
let dataAnnahFile = typeDir </> dataAnnahName
let dataMorteFile = typeDir </> dataMorteName
write dataAnnahFile (txt <> "in " <> dataName d)
let e2 = Family ts (Var (V (dataName d) 0))
let dataTxt =
Morte.pretty (Morte.normalize (Annah.desugar e2))
write dataMorteFile dataTxt ) )