-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.Trace
-- Copyright   :  (C) 2016-2026 David M. Johnson
-- License     :  BSD3-style (see the file LICENSE)
-- Maintainer  :  David M. Johnson <code@dmj.io>
-- Stability   :  experimental
-- Portability :  non-portable
--
-- = Overview
--
-- "Miso.Trace" provides functions for tracing values to the browser's
-- developer console, in the spirit of "Debug.Trace" from @base@. Where
-- "Debug.Trace" writes to @stderr@, these functions write to the browser
-- console using
-- <https://developer.mozilla.org/en-US/docs/Web/API/console/log_static console.log>,
-- <https://developer.mozilla.org/en-US/docs/Web/API/console/warn_static console.warn>
-- and
-- <https://developer.mozilla.org/en-US/docs/Web/API/console/error_static console.error>,
-- gaining the browser's affordances such as severity filtering and stack
-- traces.
--
-- The motivation is debugging /pure/ code: places where 'IO' is
-- unavailable or inconvenient, such as a miso application's @view@
-- function or pure helpers called from @update@.
--
-- Like "Debug.Trace", these functions are implemented with
-- 'unsafePerformIO' and are not referentially transparent: they are meant
-- only as a debugging aid and should not be used in production code.
-- Since Haskell is lazily evaluated, a trace fires when (and only when)
-- the traced expression is forced, so messages can appear out of order,
-- once, or not at all.
--
-- = Naming conventions
--
-- The functions follow the naming conventions of "Debug.Trace":
--
-- * @trace*@ functions log with @console.log@, @traceWarn*@ with
--   @console.warn@, and @traceError*@ with @console.error@.
-- * @*Show@ variants accept any 'Show'-able value instead of a string.
-- * @*Id@ variants return the traced value itself.
-- * @*With@ variants trace the result of applying a function to the value.
-- * @*M@ variants trace inside an 'Applicative' (e.g. miso's
--   'Miso.Effect.Effect' monad, or 'IO').
--
-- = See also
--
-- * "Debug.Trace" — the @base@ equivalent, on which this API is modeled
-- * "Miso.FFI" — 'consoleLog', 'consoleWarn', 'consoleError'
----------------------------------------------------------------------------
module Miso.Trace
  ( -- ** Logging (@console.log@)
    trace
  , traceId
  , traceWith
  , traceShow
  , traceShowId
  , traceShowWith
  , traceM
  , traceShowM
    -- ** Errors (@console.error@)
  , traceError
  , traceErrorId
  , traceErrorWith
  , traceErrorShow
  , traceErrorShowId
  , traceErrorShowWith
  , traceErrorM
  , traceErrorShowM
    -- ** Warnings (@console.warn@)
  , traceWarn
  , traceWarnId
  , traceWarnWith
  , traceWarnShow
  , traceWarnShowId
  , traceWarnShowWith
  , traceWarnM
  , traceWarnShowM
    -- ** Generalized tracing
  , traceTo
  ) where
-----------------------------------------------------------------------------
import           System.IO.Unsafe (unsafePerformIO)
import           Prelude
-----------------------------------------------------------------------------
import           Miso.FFI
import           Miso.String
-----------------------------------------------------------------------------
-- | Outputs a message to the browser console with @console.log@ when the
-- result is forced, then returns the second argument. The browser
-- analogue of 'Debug.Trace.trace'.
trace
  :: ToMisoString s
  => s
  -- ^ Message to log
  -> a
  -- ^ Value to return
  -> a
trace :: forall s a. ToMisoString s => s -> a -> a
trace = (MisoString -> IO ()) -> s -> a -> a
forall s a. ToMisoString s => (MisoString -> IO ()) -> s -> a -> a
traceTo MisoString -> IO ()
consoleLog
-----------------------------------------------------------------------------
-- | Like 'trace', but returns the message itself:
-- @'traceId' x = 'trace' x x@.
traceId :: ToMisoString s => s -> s
traceId :: forall s. ToMisoString s => s -> s
traceId = (s -> s) -> s -> s
forall s a. ToMisoString s => (a -> s) -> a -> a
traceWith s -> s
forall a. a -> a
id
-----------------------------------------------------------------------------
-- | Traces the result of applying a function to a value, then returns the
-- original value. Useful for logging a projection of a larger structure
-- while leaving the structure untouched.
traceWith
  :: ToMisoString s
  => (a -> s)
  -- ^ Function producing the message from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceWith :: forall s a. ToMisoString s => (a -> s) -> a -> a
traceWith a -> s
f a
a = s -> a -> a
forall s a. ToMisoString s => s -> a -> a
trace (a -> s
f a
a) a
a
-----------------------------------------------------------------------------
-- | Like 'trace', but accepts any 'Show'-able value as the message. The
-- browser analogue of 'Debug.Trace.traceShow'.
traceShow
  :: Show a
  => a
  -- ^ Value to log
  -> b
  -- ^ Value to return
  -> b
traceShow :: forall a b. Show a => a -> b -> b
traceShow = String -> b -> b
forall s a. ToMisoString s => s -> a -> a
trace (String -> b -> b) -> (a -> String) -> a -> b -> b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | Shows and traces a value, then returns it. Convenient to wrap around
-- any sub-expression you want to inspect without restructuring the code.
traceShowId :: Show a => a -> a
traceShowId :: forall a. Show a => a -> a
traceShowId = (a -> String) -> a -> a
forall s a. ToMisoString s => (a -> s) -> a -> a
traceWith a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | Traces the 'show'-n result of applying a function to a value, then
-- returns the original value.
traceShowWith
  :: Show b
  => (a -> b)
  -- ^ Function producing the value to show from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceShowWith :: forall b a. Show b => (a -> b) -> a -> a
traceShowWith a -> b
f = (a -> String) -> a -> a
forall s a. ToMisoString s => (a -> s) -> a -> a
traceWith (b -> String
forall a. Show a => a -> String
show (b -> String) -> (a -> b) -> a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
f)
-----------------------------------------------------------------------------
-- | Traces a message in an 'Applicative' context, such as miso's
-- 'Miso.Effect.Effect' monad or 'IO'. The browser analogue of
-- 'Debug.Trace.traceM'.
traceM :: (ToMisoString s, Applicative f) => s -> f ()
traceM :: forall s (f :: * -> *).
(ToMisoString s, Applicative f) =>
s -> f ()
traceM s
s = s -> f () -> f ()
forall s a. ToMisoString s => s -> a -> a
trace s
s (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ () -> f ()
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
-----------------------------------------------------------------------------
-- | Like 'traceM', but accepts any 'Show'-able value. Useful for logging
-- every action that flows through an update function.
traceShowM :: (Show a, Applicative f) => a -> f ()
traceShowM :: forall a (f :: * -> *). (Show a, Applicative f) => a -> f ()
traceShowM = String -> f ()
forall s (f :: * -> *).
(ToMisoString s, Applicative f) =>
s -> f ()
traceM (String -> f ()) -> (a -> String) -> a -> f ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | Like 'trace', but logs with @console.error@, which browsers render
-- prominently (typically in red, with an expandable stack trace).
traceError
  :: ToMisoString s
  => s
  -- ^ Message to log
  -> a
  -- ^ Value to return
  -> a
traceError :: forall s a. ToMisoString s => s -> a -> a
traceError = (MisoString -> IO ()) -> s -> a -> a
forall s a. ToMisoString s => (MisoString -> IO ()) -> s -> a -> a
traceTo MisoString -> IO ()
consoleError
-----------------------------------------------------------------------------
-- | Like 'traceId', but logs with @console.error@.
traceErrorId :: ToMisoString s => s -> s
traceErrorId :: forall s. ToMisoString s => s -> s
traceErrorId = (s -> s) -> s -> s
forall s a. ToMisoString s => (a -> s) -> a -> a
traceErrorWith s -> s
forall a. a -> a
id
-----------------------------------------------------------------------------
-- | Like 'traceWith', but logs with @console.error@.
traceErrorWith
  :: ToMisoString s
  => (a -> s)
  -- ^ Function producing the message from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceErrorWith :: forall s a. ToMisoString s => (a -> s) -> a -> a
traceErrorWith a -> s
f a
a = s -> a -> a
forall s a. ToMisoString s => s -> a -> a
traceError (a -> s
f a
a) a
a
-----------------------------------------------------------------------------
-- | Like 'traceShow', but logs with @console.error@.
traceErrorShow
  :: Show a
  => a
  -- ^ Value to log
  -> b
  -- ^ Value to return
  -> b
traceErrorShow :: forall a b. Show a => a -> b -> b
traceErrorShow = String -> b -> b
forall s a. ToMisoString s => s -> a -> a
traceError (String -> b -> b) -> (a -> String) -> a -> b -> b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | Like 'traceShowId', but logs with @console.error@.
traceErrorShowId :: Show a => a -> a
traceErrorShowId :: forall a. Show a => a -> a
traceErrorShowId = (a -> String) -> a -> a
forall s a. ToMisoString s => (a -> s) -> a -> a
traceErrorWith a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | Like 'traceShowWith', but logs with @console.error@.
traceErrorShowWith
  :: Show b
  => (a -> b)
  -- ^ Function producing the value to show from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceErrorShowWith :: forall b a. Show b => (a -> b) -> a -> a
traceErrorShowWith a -> b
f = (a -> String) -> a -> a
forall s a. ToMisoString s => (a -> s) -> a -> a
traceErrorWith (b -> String
forall a. Show a => a -> String
show (b -> String) -> (a -> b) -> a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
f)
-----------------------------------------------------------------------------
-- | Like 'traceM', but logs with @console.error@.
traceErrorM :: (ToMisoString s, Applicative f) => s -> f ()
traceErrorM :: forall s (f :: * -> *).
(ToMisoString s, Applicative f) =>
s -> f ()
traceErrorM s
s = s -> f () -> f ()
forall s a. ToMisoString s => s -> a -> a
traceError s
s (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ () -> f ()
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
-----------------------------------------------------------------------------
-- | Like 'traceShowM', but logs with @console.error@.
traceErrorShowM :: (Show a, Applicative f) => a -> f ()
traceErrorShowM :: forall a (f :: * -> *). (Show a, Applicative f) => a -> f ()
traceErrorShowM = String -> f ()
forall s (f :: * -> *).
(ToMisoString s, Applicative f) =>
s -> f ()
traceErrorM (String -> f ()) -> (a -> String) -> a -> f ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | Like 'trace', but logs with @console.warn@, which browsers render as
-- a warning (typically in yellow) and can be filtered by severity.
traceWarn
  :: ToMisoString s
  => s
  -- ^ Message to log
  -> a
  -- ^ Value to return
  -> a
traceWarn :: forall s a. ToMisoString s => s -> a -> a
traceWarn = (MisoString -> IO ()) -> s -> a -> a
forall s a. ToMisoString s => (MisoString -> IO ()) -> s -> a -> a
traceTo MisoString -> IO ()
consoleWarn
-----------------------------------------------------------------------------
-- | Like 'traceId', but logs with @console.warn@.
traceWarnId :: ToMisoString s => s -> s
traceWarnId :: forall s. ToMisoString s => s -> s
traceWarnId = (s -> s) -> s -> s
forall s a. ToMisoString s => (a -> s) -> a -> a
traceWarnWith s -> s
forall a. a -> a
id
-----------------------------------------------------------------------------
-- | Like 'traceWith', but logs with @console.warn@.
traceWarnWith
  :: ToMisoString s
  => (a -> s)
  -- ^ Function producing the message from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceWarnWith :: forall s a. ToMisoString s => (a -> s) -> a -> a
traceWarnWith a -> s
f a
a = s -> a -> a
forall s a. ToMisoString s => s -> a -> a
traceWarn (a -> s
f a
a) a
a
-----------------------------------------------------------------------------
-- | Like 'traceShow', but logs with @console.warn@.
traceWarnShow
  :: Show a
  => a
  -- ^ Value to log
  -> b
  -- ^ Value to return
  -> b
traceWarnShow :: forall a b. Show a => a -> b -> b
traceWarnShow = String -> b -> b
forall s a. ToMisoString s => s -> a -> a
traceWarn (String -> b -> b) -> (a -> String) -> a -> b -> b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | Like 'traceShowId', but logs with @console.warn@.
traceWarnShowId :: Show a => a -> a
traceWarnShowId :: forall a. Show a => a -> a
traceWarnShowId = (a -> String) -> a -> a
forall s a. ToMisoString s => (a -> s) -> a -> a
traceWarnWith a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | Like 'traceShowWith', but logs with @console.warn@.
traceWarnShowWith
  :: Show b
  => (a -> b)
  -- ^ Function producing the value to show from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceWarnShowWith :: forall b a. Show b => (a -> b) -> a -> a
traceWarnShowWith a -> b
f = (a -> String) -> a -> a
forall s a. ToMisoString s => (a -> s) -> a -> a
traceWarnWith (b -> String
forall a. Show a => a -> String
show (b -> String) -> (a -> b) -> a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
f)
-----------------------------------------------------------------------------
-- | Like 'traceM', but logs with @console.warn@.
traceWarnM :: (ToMisoString s, Applicative f) => s -> f ()
traceWarnM :: forall s (f :: * -> *).
(ToMisoString s, Applicative f) =>
s -> f ()
traceWarnM s
s = s -> f () -> f ()
forall s a. ToMisoString s => s -> a -> a
traceWarn s
s (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ () -> f ()
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
-----------------------------------------------------------------------------
-- | Like 'traceShowM', but logs with @console.warn@.
traceWarnShowM :: (Show a, Applicative f) => a -> f ()
traceWarnShowM :: forall a (f :: * -> *). (Show a, Applicative f) => a -> f ()
traceWarnShowM = String -> f ()
forall s (f :: * -> *).
(ToMisoString s, Applicative f) =>
s -> f ()
traceWarnM (String -> f ()) -> (a -> String) -> a -> f ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show
-----------------------------------------------------------------------------
-- | The generalized tracing combinator underlying this module: traces via
-- the given console function from "Miso.FFI". Every other function here
-- is defined in terms of it.
traceTo
  :: ToMisoString s
  => (MisoString -> IO ())
  -- ^ Console function to log with, e.g. 'consoleLog'
  -> s
  -- ^ Message to log
  -> a
  -- ^ Value to return
  -> a
{-# NOINLINE traceTo #-}
traceTo :: forall s a. ToMisoString s => (MisoString -> IO ()) -> s -> a -> a
traceTo MisoString -> IO ()
f s
s a
a = IO a -> a
forall a. IO a -> a
unsafePerformIO (IO a -> a) -> IO a -> a
forall a b. (a -> b) -> a -> b
$ do
  MisoString -> IO ()
f (s -> MisoString
forall str. ToMisoString str => str -> MisoString
toMisoString s
s)
  a -> IO a
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
a
-----------------------------------------------------------------------------