quox/lib/Quox/Log.idr

241 lines
6 KiB
Idris
Raw Normal View History

2024-04-04 12:11:26 -04:00
module Quox.Log
import Quox.Loc
import Quox.Pretty
import Data.So
import Data.DPair
import Data.Maybe
import Data.List1
import Control.Eff
import Control.Monad.ST.Extra
import Data.IORef
import System.File
import Derive.Prelude
%default total
%language ElabReflection
public export
maxLogLevel : Nat
maxLogLevel = 100
public export
logCategories : List String
logCategories = ["whnf", "equal", "check"]
public export %inline
isLogLevel : Nat -> Bool
isLogLevel l = l <= maxLogLevel
public export
IsLogLevel : Nat -> Type
IsLogLevel l = So $ isLogLevel l
public export %inline
isLogCategory : String -> Bool
isLogCategory cat = cat `elem` logCategories
public export
IsLogCategory : String -> Type
IsLogCategory cat = So $ isLogCategory cat
public export
LogLevel : Type
LogLevel = Subset Nat IsLogLevel
public export
LogCategory : Type
LogCategory = Subset String IsLogCategory
public export %inline
toLogLevel : Nat -> Maybe LogLevel
toLogLevel l =
case choose $ isLogLevel l of
Left y => Just $ Element l y
Right _ => Nothing
public export %inline
toLogCategory : String -> Maybe LogCategory
toLogCategory c =
case choose $ isLogCategory c of
Left y => Just $ Element c y
Right _ => Nothing
public export
LevelMap : Type
LevelMap = List (LogCategory, LogLevel)
-- i tried SortedMap first, but it is too much overhead for LevelMaps
public export
record LogLevels where
constructor MkLogLevels
defLevel : LogLevel
levels : LevelMap
%name LogLevels lvls
%runElab derive "LogLevels" [Eq, Show]
public export
LevelStack : Type
LevelStack = List1 LogLevels
export %inline
defaultLogLevels : LogLevels
defaultLogLevels = MkLogLevels (Element 0 Oh) []
export %inline
initStack : LevelStack
initStack = singleton defaultLogLevels
||| right biased for the default and for overlapping elements
public export %inline
mergeLevels : LogLevels -> LogLevels -> LogLevels
mergeLevels (MkLogLevels _ map1) (MkLogLevels def map2) =
MkLogLevels def $ map1 ++ map2
export %inline
getLevel : LogCategory -> LogLevels -> LogLevel
getLevel cat lvls = fromMaybe lvls.defLevel $ lookup cat lvls.levels
public export
LogDoc : Type
LogDoc = Doc (Opts {lineLength = 80})
public export
data PushArg = SetDefault LogLevel | SetCats LevelMap
%name PushArg push
export %inline
mergePush : PushArg -> LogLevels -> LogLevels
mergePush (SetDefault def) = {defLevel := def}
mergePush (SetCats map) = {levels $= (map ++)}
public export
record LogMsg where
constructor (:>)
level : Nat
{auto 0 levelOk : IsLogLevel level}
message : Lazy LogDoc
infix 0 :>
%name Log.LogMsg msg
public export
data LogL : (lbl : tag) -> Type -> Type where
SayMany : (cat : LogCategory) -> (loc : Loc) ->
(msgs : List LogMsg) -> LogL lbl ()
Push : (push : PushArg) -> LogL lbl ()
Pop : LogL lbl ()
CurLevels : LogL lbl LogLevels
public export
Log : Type -> Type
Log = LogL ()
parameters (0 lbl : tag) {auto _ : Has (LogL lbl) fs}
public export %inline
sayManyAt : (cat : String) -> (0 catOk : IsLogCategory cat) =>
Loc -> List LogMsg -> Eff fs ()
sayManyAt cat loc msgs {catOk} =
send $ SayMany {lbl} (Element cat catOk) loc msgs
public export %inline
sayAt : (cat : String) -> (0 catOk : IsLogCategory cat) =>
(lvl : Nat) -> (0 lvlOk : IsLogLevel lvl) =>
Loc -> Lazy LogDoc -> Eff fs ()
sayAt cat lvl loc msg = sayManyAt cat loc [lvl :> msg]
public export %inline
pushAt : PushArg -> Eff fs ()
pushAt lvls = send $ Push {lbl} lvls
public export %inline
popAt : Eff fs ()
popAt = send $ Pop {lbl}
public export %inline
curLevelsAt : Eff fs LogLevels
curLevelsAt = send $ CurLevels {lbl}
parameters {auto _ : Has Log fs}
public export %inline
sayMany : (cat : String) -> (0 catOk : IsLogCategory cat) =>
Loc -> List LogMsg -> Eff fs ()
sayMany = sayManyAt ()
public export %inline
say : (cat : String) -> (0 _ : IsLogCategory cat) =>
(lvl : Nat) -> (0 _ : IsLogLevel lvl) =>
Loc -> Lazy LogDoc -> Eff fs ()
say = sayAt ()
public export %inline
push : PushArg -> Eff fs ()
push = pushAt ()
public export %inline
pop : Eff fs ()
pop = popAt ()
public export %inline
curLevels : Eff fs LogLevels
curLevels = curLevelsAt ()
export %inline
doPush : PushArg -> LevelStack -> LevelStack
doPush push list = mergePush push (head list) `cons` list
export %inline
doPop : List1 a -> List1 a
doPop (_ ::: x :: xs) = x ::: xs
doPop (x ::: []) = x ::: []
export %inline
doSayMany : Applicative m =>
LevelStack -> (LogDoc -> m ()) ->
LogCategory -> Loc -> List LogMsg -> m ()
doSayMany (lvls ::: _) act cat loc msgs = do
let Element catLvl _ = getLevel cat lvls
loc = runPretty $ prettyLoc loc
for_ msgs $ \msg => when (msg.level <= catLvl) $
act $ hcat [loc, text cat.fst, "@", pshow msg.level, ":"] <++>
msg.message
export %inline
handleLogIO : HasIO m => (FileError -> m ()) ->
IORef LevelStack -> File -> LogL tag a -> m a
handleLogIO th lvls h = \case
Push push => modifyIORef lvls $ doPush push
Pop => modifyIORef lvls doPop
SayMany cat loc msgs => doSayMany !(readIORef lvls) printMsg cat loc msgs
CurLevels => head <$> readIORef lvls
where printMsg : LogDoc -> m ()
printMsg msg = fPutStr h (render _ msg) >>= either th pure
export %inline
handleLogST : (HasST m, Monad (m s)) =>
STRef s (SnocList LogDoc) -> STRef s LevelStack ->
LogL tag a -> m s a
handleLogST docs lvls = \case
Push push => modifySTRef' lvls $ doPush push
Pop => modifySTRef' lvls doPop
SayMany cat loc msgs => doSayMany !(readSTRef' lvls) printMsg cat loc msgs
CurLevels => head <$> readSTRef' lvls
where printMsg : LogDoc -> m s ()
printMsg msg = modifySTRef' docs (:< msg)
export %inline
handleLogDiscard : Applicative m => LogL tag a -> m a
handleLogDiscard = \case
SayMany {} => pure ()
Push {} => pure ()
Pop => pure ()
CurLevels => pure defaultLogLevels