packages feed

clac-0.5.0: src/Clac/IO.hs

{-# OPTIONS_GHC -fno-warn-type-defaults #-}

{- |
Module     : $Header$
Description: IO Operations for clac.
Copyright  : (c) Alexander Berntsen 2015
License    : GPL-3

Maintainer : alexander@plaimi.net
-} module Clac.IO where

import Control.Applicative
  (
  (<$>),
  )
import Control.Monad
  (
  when,
  )
import Data.List.Split
  (
  splitOn,
  )
import Data.Maybe
  (
  mapMaybe,
  )
import System.IO
  (
  hFlush,
  stdout,
  )

import Plailude

import Clac.CliParser
  (
  Opt,
  e,
  h,
  r,
  v,
  )
import Clac.REPL
  (
  inlet,
  repAns,
  repLets,
  unlet,
  )
import Clac.Stack
  (
  os,
  sa,
  )

f :: Show a => Opt -> [(a, String)] -> IO ()
-- | Formats an answer and optionally (if the verbose option is set) its
-- answer tree for printing. Puts '='s under the answer.
f o = mapM_ (\(solution, tree) -> do
  when (v o) $ putStrLn $ "\n\n" ++ tree
  print solution
  putStrLn $ replicate (length $ show solution) '=')

repl :: (Floating a, Real a, Read a, Show a)
     => Opt -> [(String, String)] -> [a] -> IO b
-- | Run a REPL (Read-Evaluate-Print-Loop).
--
-- Prompts the user for input, solves it with 'sa' after checking if there
-- were multiple equations per the 'Neq' operator, and prints it using 'f'.
-- Prints an answer tree if the verbose option is activated.
--
-- > 1 1 + , 2 2 + -- this is turned into [["1","1","+"],["2","2","+"]]
--
-- There is a let stack. The user may add things to the let stack with #let.
-- The stack may be printed with #lets. Variables may also be unlet with
-- #unlet and #unletall.
--
-- > #let a = 1 -- the let stack is now [("a", "1")], i.e. a bound to 1
-- > #let b = 2 -- the let stack is now [("b","2"),("a","1")]
-- > #let c = 3 -- the let stack is now [("c","3"),("b","2"),("a","1")]
-- > #lets      -- prints [("c","3"),("b","2"),("a","1")]
-- > #unlet a   -- the let stack is now [("c","3"),("b","2")]
-- > #unletall  -- the stack is now empty
--
-- 'repl' runs 'repLets' to reduce an application of a variable bound by let
-- with its value.
--
-- > #let a = 1
-- > a a + -- this is turned into [["1","1","+"]]
--
-- There is an ans stack. Every answer is added to this automatically. The ans
-- function lets the user use these answers. #ans prints the stack.
--
-- > 1     -- ans stack is now [1.0]
-- > 0 ans -- this is turned into [["1"]]
-- > #ans  -- prints [1.0]
repl o lets ans = do
  putStr ">"
  hFlush stdout
  l <- words <$> getLine
  case l of
    ("#ans":_)          -> print ans  >> repl o lets ans
    ("#lets":_)         -> print lets >> repl o lets ans
    ["#let", x, "=", y] -> repl o (inlet x y lets) ans
    ["#unlet", x]       -> repl o (unlet x lets) ans
    ("#unletall":_)     -> repl o [] ans
    _                   -> do
      let ss = sa . splitOn [","] . repAns ans . repLets lets $ l
      f o ss
      repl o lets (mapMaybe fst ss ++ ans)

clac :: Opt -> [[String]] -> IO ()
-- | Run the calculator.
--
-- If an equation is given as an argument to the program, it is solved with
-- 'sa', and formatted and printed by 'f'.
--
-- > $ clac 1 1 +
-- > Just 2.0
-- > ========
--
-- If there is STDIN waiting, 'clac' calls itself with the equation in STDIN
-- as an argument.
--
-- > $ echo 1 1 + | clac
-- > Just 2.0
-- > ========
--
-- If there is there is nothing to solve, it starts the REPL with 'repl'.
--
-- > $ clac
-- > >
--
-- The REPL should be started with GNU rlwrap or similar software for an
-- optimal experience.
--
-- > $ rlwrap clac
-- > >
clac o [[]] = repl o [] []
          ~+~ (getContents >>= \cs -> clac o (splitOn [","] . words $ cs))
clac o es = f o . sa $ es

calc :: Opt -> IO ()
-- | Check what flags the user has given, if any, and run the program
-- accordingly. Prints various help output, or runs the calculator with
-- 'clac'.
calc o | h o = mapM_ putStrLn $ "OPERATORS":"=========":map snd os
       | r o = mapM_ putStrLn ["REPL OPERATIONS","==============="
                              ,"#lets:\tprint the let stack"
                              ,"#unletall:\tempty the let stack"
                              ,"#let:\t#'let a = b', bind a to b"
                              ,"#unlet:\t'#unlet v', remove the let binding v"
                              ,"#ans:\tprint the answer stack"
                              ,"ans:\tanswer operator, use the n-th last ans"
                              ]
       | otherwise = clac o $ splitOn [","] $ case e o of
                                                [a] -> words a
                                                _   -> e o