packages feed

jl-0.1.0: app/Main.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Main where

import           Control.Monad.Writer
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Encode.Pretty as Aeson
import qualified Data.Aeson.Parser as Aeson
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as L8
import           Data.Conduit ( (.|), runConduit)
import           Data.Conduit.Attoparsec
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.List as CL
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Vector as V
import           JL.Functions
import           JL.Inferer
import           JL.Interpreter
import           JL.Parser
import           JL.Printer
import           JL.Serializer
import           JL.Types
import           Options.Applicative.Simple
import           System.IO

main :: IO ()
main = do
  do hSetBuffering stdout LineBuffering
     hSetBuffering stdin LineBuffering
     ((inp, file, inarray, aslines, browse, markdown, pretty), ()) <-
       simpleOptions
         "0.0.0"
         "jl - JSON Lambda calculus"
         "Command-line language for querying and outputting JSON."
         ((,,,,,,) <$>
          strArgument
            (metavar "CODE" <>
             help "JL code; supports completion of function names" <>
             completeWith (map (\(Variable v) -> T.unpack v) (M.keys context))) <*>
          optional (strArgument (metavar "FILE")) <*>
          flag
            False
            True
            (short 'a' <> long "array" <>
             help "Read each line of input as a single array") <*>
          flag
            False
            True
            (short 'l' <> long "lines" <>
             help
               "Output each element of arrays on a newline suitable for UNIX piping") <*>
          flag
            False
            True
            (long "browse" <> help "Prints out all available functions") <*>
          flag
            False
            True
            (long "browse-markdown" <>
             help "Prints out all available functions, in markdown format") <*>
          flag
            False
            True
            (short 'p' <> long "pretty" <>
             help "Outputs JSON in a human-friendly format"))
         empty
     let block xs =
           if markdown
             then "```haskell\n" <> xs <> "\n```"
             else xs
     if browse || markdown
       then mapM_
              (\(groupname, defs) ->
                 T.putStrLn
                   (((if markdown
                        then "## "
                        else "") <>
                     groupname) <>
                    "\n\n" <>
                    T.unlines
                      (map
                         (\def ->
                            (let Variable v = definitionName def
                             in block
                                  (v <> " :: " <>
                                   prettyType (definitionType def)) <>
                                "\n\n" <>
                                (if not markdown
                                   then "  "
                                   else "") <>
                                definitionDoc def <>
                                "\n"))
                         defs)))
              functions
       else case parseText "" (T.pack inp) of
              Left err -> error (show err)
              Right expr0 -> do
                case file of
                  Just fp -> do
                    bytes <- L.readFile fp
                    case Aeson.decode bytes of
                      Nothing -> hPutStr stderr "invalid input JSON"
                      Just j -> handleJson pretty expr0 aslines j
                  Nothing -> process pretty expr0 inarray aslines
  where
    process pretty expr0 inarray aslines = runConduit $
      CB.sourceHandle stdin .| CB.lines .| conduitParserEither Aeson.value .|
      (if inarray
         then do
           es <-
             CL.mapM (either (error . errorMessage) (return . snd)) .|
             CL.consume
           liftIO
             (handleJson pretty expr0 aslines (Aeson.Array (V.fromList es)))
         else CL.mapM_
                (either
                   (hPutStrLn stderr . errorMessage)
                   (handleJson pretty expr0 aslines . snd))) .|
      CL.sinkNull

-- | Handle a JSON input, printing out one to many JSON values.
handleJson :: Bool -> Expression -> Bool -> Aeson.Value -> IO ()
handleJson pretty expr0 aslines j =
  let expr = ApplicationExpression expr0 (valueToExpression j)
  in case infer context expr (map TypeVariable [1 ..]) of
       !_ ->
         case eval
                (foldl
                   (\e (v, f) -> subst v f e)
                   (desugar expr)
                   (M.toList scope)) of
           v ->
             if aslines
               then mapM_
                      L8.putStrLn
                      (map Aeson.encode (V.toList (asArray (coreToValue v))))
               else L8.putStrLn (encode' (coreToValue v))
             where asArray =
                     \case
                       Aeson.Array xs -> xs
                       x -> V.singleton x
                   encode' =
                     if pretty
                       then Aeson.encodePretty
                       else Aeson.encode