packages feed

graft-0.1.0.0: src/Graft/Build.hs

{-# LANGUAGE BangPatterns #-}

-- | Building green trees in a monad, as a parser does. 'node' brackets a
-- node, so a body that opens and closes its own nodes in pairs leaves none
-- open, and the first error stops the build.
--
-- This module is meant to be imported qualified; its types are also
-- exported by "Graft":
--
-- > import Graft
-- > import qualified Graft.Build as Build
-- >
-- > -- 1 + 2: the operand is emitted before the parser knows it is wrapped.
-- > expression :: Build Kind ()
-- > expression = do
-- >   start <- Build.checkpoint
-- >   Build.token Number "1"
-- >   Build.nodeAt start Binary $ do
-- >     Build.token Whitespace " "
-- >     Build.token Plus "+"
-- >     Build.token Whitespace " "
-- >     Build.token Number "2"
-- >
-- > parsed :: Either BuilderError (GreenNode Kind)
-- > parsed = Build.exec (Build.node Root expression)
--
-- t'Build' is a thin layer over the pure t'Builder' value from "Graft.Builder".
module Graft.Build
  ( Build
  , run
  , exec
  , node
  , nodeAt
  , token
  , element
  , checkpoint
  , startNode
  , startNodeAt
  , finishNode
  , save
  , restore
  ) where

import Data.Text (Text)
import Graft.Builder (Builder, BuilderError, Checkpoint)
import qualified Graft.Builder as Builder
import Graft.Green.Internal (GreenElement, GreenNode)

-- | A construction in progress: a state monad over t'Builder' that stops at
-- the first 'BuilderError'.
newtype Build kind a = Build (Builder kind -> Either BuilderError (a, Builder kind))

instance Functor (Build kind) where
  fmap f (Build step) = Build $ \builder -> do
    (value, next) <- step builder
    pure (f value, next)

instance Applicative (Build kind) where
  pure value = Build $ \builder -> Right (value, builder)
  Build stepFunction <*> Build stepValue = Build $ \builder -> do
    (f, next) <- stepFunction builder
    (value, final) <- stepValue next
    pure (f value, final)

instance Monad (Build kind) where
  Build step >>= continue = Build $ \builder -> do
    (value, next) <- step builder
    let Build stepNext = continue value
    stepNext next

-- | Run a construction from 'Builder.empty' and finish it with
-- 'Builder.finish', returning its result and the tree.
run :: Build kind a -> Either BuilderError (a, GreenNode kind)
run (Build step) = do
  (value, builder) <- step Builder.empty
  built <- Builder.finish builder
  pure (value, built)

-- | Run a construction and return only the tree.
exec :: Build kind a -> Either BuilderError (GreenNode kind)
exec = fmap snd . run

-- | A node around the elements that the body emits. The node is closed when
-- the body returns. If the body itself closes more nodes than it opens, with
-- 'finishNode' or 'restore', this closes some other node instead, so keep
-- the body balanced.
node :: kind -> Build kind a -> Build kind a
node nodeKind body = startNode nodeKind *> body <* finishNode

-- | A node that starts at an earlier checkpoint, wrapping the elements
-- emitted since then together with those the body emits. Fails as
-- 'Builder.startNodeAt' does.
nodeAt :: Checkpoint -> kind -> Build kind a -> Build kind a
nodeAt marker nodeKind body = startNodeAt marker nodeKind *> body <* finishNode

-- | Emit a token.
token :: kind -> Text -> Build kind ()
token tokenKind contents = modifyBuilder (Builder.token tokenKind contents)

-- | Emit an existing node or token, such as an unchanged subtree.
element :: GreenElement kind -> Build kind ()
element emitted = modifyBuilder (Builder.element emitted)

-- | The current position, for a later 'nodeAt'.
checkpoint :: Build kind Checkpoint
checkpoint = Build $ \builder -> Right (Builder.checkpoint builder, builder)

-- | Open a node that a later 'finishNode' closes, for parsers that open and
-- close nodes in different places. Prefer 'node'.
startNode :: kind -> Build kind ()
startNode nodeKind = modifyBuilder (Builder.startNode nodeKind)

-- | Open a node at an earlier checkpoint. Prefer 'nodeAt'.
startNodeAt :: Checkpoint -> kind -> Build kind ()
startNodeAt marker nodeKind = Build $ \builder -> (,) () <$> Builder.startNodeAt marker nodeKind builder

-- | Close the innermost open node.
finishNode :: Build kind ()
finishNode = Build $ \builder -> (,) () <$> Builder.finishNode builder

-- | The current builder. Together with 'restore' this lets a backtracking
-- parser return to an earlier state after a failed alternative.
save :: Build kind (Builder kind)
save = Build $ \builder -> Right (builder, builder)

-- | Replace the current builder with a saved one.
restore :: Builder kind -> Build kind ()
restore saved = Build $ \_ -> Right ((), saved)

modifyBuilder :: (Builder kind -> Builder kind) -> Build kind ()
modifyBuilder step = Build $ \builder -> let !next = step builder in Right ((), next)