summaryrefslogtreecommitdiff
path: root/src/Language/Fiddle/Compiler/Backend/Internal/FormattedWriter.hs
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-10-16 00:03:09 -0600
committerJosh Rahm <joshuarahm@gmail.com>2024-10-16 00:03:09 -0600
commitc31a34382d6fe1307a0c6fe1710c42f27fe833ca (patch)
treef74810d73aeda78e85f63f7c023769791c6afea2 /src/Language/Fiddle/Compiler/Backend/Internal/FormattedWriter.hs
parent5924b745fbaf52000981c298ec8f18b3c0c4a1be (diff)
downloadfiddle-c31a34382d6fe1307a0c6fe1710c42f27fe833ca.tar.gz
fiddle-c31a34382d6fe1307a0c6fe1710c42f27fe833ca.tar.bz2
fiddle-c31a34382d6fe1307a0c6fe1710c42f27fe833ca.zip
Add framework for more easily editing files.
This introduces the FilesM monad, which allows for monadic and fragmented writing to files in a filesystem. This provides an abstraction over writing to different "fragments" of files so implementation, headers and declarations can all be written using just one pass of the compiler.
Diffstat (limited to 'src/Language/Fiddle/Compiler/Backend/Internal/FormattedWriter.hs')
-rw-r--r--src/Language/Fiddle/Compiler/Backend/Internal/FormattedWriter.hs113
1 files changed, 113 insertions, 0 deletions
diff --git a/src/Language/Fiddle/Compiler/Backend/Internal/FormattedWriter.hs b/src/Language/Fiddle/Compiler/Backend/Internal/FormattedWriter.hs
new file mode 100644
index 0000000..bde9ebe
--- /dev/null
+++ b/src/Language/Fiddle/Compiler/Backend/Internal/FormattedWriter.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | This module provides the 'FormattedWriter' monad, which extends a basic
+-- "Writer" over 'Text' by allowing formatting features like indentation, line
+-- breaks, and ensuring the output is well-structured for readability.
+--
+-- The 'FormattedWriter' is useful for generating pretty-printed output, such as
+-- source code generation, where indentation and newline management are essential.
+module Language.Fiddle.Compiler.Backend.Internal.FormattedWriter
+ ( -- * Types
+ FormattedWriter, -- | The main writer monad with formatting features.
+ -- * Core Operations
+ ensureNL, -- | Ensures that there is a newline at the end of the current line.
+ flush, -- | Flushes any pending text (writes the pending line).
+ incIndent, -- | Increases the indentation level.
+ decIndent, -- | Decreases the indentation level.
+ indented, -- | Performs an action with increased indentation.
+ execFormattedWriter, -- | Runs the 'FormattedWriter' and produces the final formatted 'Text'.
+ )
+where
+
+import Control.Monad (forM_)
+import Control.Monad.RWS.Strict
+import Data.Text (Text)
+import qualified Data.Text as Text
+
+-- | Internal state for the 'FormattedWriter' monad.
+--
+-- * 'indentLevel': The current level of indentation.
+-- * 'pendingLine': The current line that is being built but not yet written.
+data Fmt = Fmt
+ { indentLevel :: Int, -- ^ Current indentation level.
+ pendingLine :: Text -- ^ Text that has been added but not yet written to the output.
+ }
+
+-- | The 'FormattedWriter' is a monad that provides functionality for writing
+-- formatted text, with control over indentation and newlines.
+newtype FormattedWriter a = FormattedWriter (RWS () Text Fmt a)
+ deriving newtype (Functor, Applicative, Monad, MonadState Fmt)
+
+-- | 'MonadWriter' instance for 'FormattedWriter', which allows writing text
+-- while maintaining proper indentation and ensuring that newlines are handled
+-- correctly. Each line is prefixed with the correct indentation level.
+instance MonadWriter Text FormattedWriter where
+ tell txt = FormattedWriter $ do
+ -- Get the current indentation level and prepare the indentation prefix.
+ indent <- (`Text.replicate` " ") <$> gets indentLevel
+ -- Split the text into lines.
+ let lines = Text.splitOn "\n" txt
+ -- For each line, write the indent, the pending line, and then the line itself.
+ forM_ (Prelude.init lines) $ \line -> do
+ pending <- gets pendingLine
+ modify $ \s -> s {pendingLine = ""} -- Reset pending line
+ tell indent -- Add indentation
+ tell pending -- Write any pending text
+ tell line -- Write the actual line
+ tell "\n" -- Add a newline after the line
+ -- The last fragment is kept as the new pending line, to be written later.
+ modify $ \s -> s {pendingLine = pendingLine s <> Prelude.last lines}
+
+ -- Allow listening to the written text within the 'FormattedWriter' context.
+ listen (FormattedWriter fn) = FormattedWriter $ listen fn
+
+ -- Allow transformation of the written text via a pass operation.
+ pass (FormattedWriter fn) = FormattedWriter $ pass fn
+
+-- | Ensures that a newline is written if there is any pending text. This is
+-- useful for ensuring that the output ends on a clean line.
+ensureNL :: FormattedWriter ()
+ensureNL = do
+ p <- gets pendingLine
+ if Text.null p
+ then return ()
+ else do
+ modify $ \s -> s {pendingLine = ""}
+ tell p
+ tell "\n"
+
+-- | Flushes the pending line by writing it to the output and clearing the
+-- pending state. This is useful when you want to force the current line to
+-- be written immediately.
+flush :: FormattedWriter ()
+flush = do
+ p <- gets pendingLine
+ modify $ \s -> s {pendingLine = ""}
+ tell p
+
+-- | Increases the indentation level by one. This will affect all subsequent
+-- lines written within the 'FormattedWriter' monad.
+incIndent :: FormattedWriter ()
+incIndent = modify (\(Fmt id p) -> Fmt (id + 1) p)
+
+-- | Decreases the indentation level by one. This will affect all subsequent
+-- lines written within the 'FormattedWriter' monad.
+decIndent :: FormattedWriter ()
+decIndent = modify (\(Fmt id p) -> Fmt (id - 1) p)
+
+-- | Runs the given 'FormattedWriter' action with an additional indentation level.
+-- Once the action completes, the indentation level is decreased.
+indented :: FormattedWriter () -> FormattedWriter ()
+indented fn = do
+ incIndent -- Increase indentation level
+ fn -- Run the action with the increased indentation
+ decIndent -- Restore the original indentation level
+
+-- | Runs a 'FormattedWriter' and returns the final formatted 'Text' output.
+-- It ensures that any pending lines are flushed before returning the result.
+execFormattedWriter :: FormattedWriter a -> Text
+execFormattedWriter ((>> flush) -> FormattedWriter rws) =
+ snd $ execRWS rws () (Fmt 0 "") -- Execute the RWS monad, starting with no indentation and no pending line.
+