packages feed

lasercutter-0.1.0.0: src/Lasercutter/Internal.hs

module Lasercutter.Internal where

import Control.Applicative
import Control.Monad (join)
import Data.Maybe (mapMaybe)
import Lasercutter.Types


------------------------------------------------------------------------------
-- | Split a parser into a parser to run on the node's children, and how to
-- reassemble those pieces into a parser for the current node.
--
-- @since 0.1.0.0
split :: bc -> Parser bc t a -> t -> Split bc t a
split _ (Pure a) _         = ignoreChildren $ pure a
split cr GetCrumbs _       = ignoreChildren $ pure cr
split cr (LiftA2 f l r) tt =
  case (split cr l tt, split cr r tt) of
    (Split l' kl, Split r' kr) ->
      Split (LiftA2 (,) l' r') $ \(unzip -> (lcs, rcs)) ->
        LiftA2 f (kl lcs) (kr rcs)
split cr p0@(Target se pa) tt
  | se tt
  = continue (fmap pure) $ split cr pa tt
  | otherwise
  = Split p0 $ pure . join
split cr (Expect pa) tt    = continue expect $ split cr pa tt
split _ (OnChildren pa) _ = Split pa pure
split _ Current tt         = ignoreChildren $ pure tt
split _ Fail _             = Split Fail $ const $ Fail


------------------------------------------------------------------------------
-- | There is no work to do for the children, so ignore them.
--
-- @since 0.1.0.0
ignoreChildren :: Parser bc t b -> Split bc t b
ignoreChildren = Split (pure ()) . const


------------------------------------------------------------------------------
-- | Append a continuation after a 'Split'.
--
-- @since 0.1.0.0
continue :: (Parser bc t a -> Parser bc t b) -> Split bc t a -> Split bc t b
continue f (Split p k) = Split p $ f . k


------------------------------------------------------------------------------
-- | Parse the current node by splitting the parser, accumulating the results
-- of each child, and then running the continuation.
--
-- @since 0.1.0.0
parseNode
    :: (Semigroup bc, IsTree t)
    => (t -> bc)
    -> bc
    -> Parser bc t a
    -> t
    -> Parser bc t a
parseNode summarize cr p tt =
  case split cr' p tt of
    Split (Pure a) k -> k [a]
    Split pa k -> k $ parseChildren summarize cr' pa $ getChildren tt
  where
    cr' = summarize tt <> cr


------------------------------------------------------------------------------
-- | Run a parser on each child, accumulating the results.
--
-- @since 0.1.0.0
parseChildren
    :: (IsTree t, Semigroup bc)
    => (t -> bc)
    -> bc
    -> Parser bc t a
    -> [t]
    -> [a]
parseChildren summarize cr pa =
  mapMaybe $ getResult . parseNode summarize cr pa


------------------------------------------------------------------------------
-- | Extract a value from a parser. The way the applicative evaluates,
-- all "combinator" effects are guaranteed to have been run by the time this
-- function gets called.
--
-- @since 0.1.0.0
getResult :: Parser bc t a -> Maybe a
getResult (Pure a)       = pure a
getResult (LiftA2 f a b) = liftA2 f (getResult a) (getResult b)
getResult (Expect pa)    = join $ getResult pa
getResult Fail           = Nothing
getResult GetCrumbs      = error "getResult: impossible"
getResult (Target _ _)   = error "getResult: impossible"
getResult (OnChildren _) = error "getResult: impossible"
getResult Current        = error "getResult: impossible"


------------------------------------------------------------------------------
-- | Run a parser over a tree in a single pass.
--
-- @since 0.1.0.0
runParser
    :: (Monoid bc, IsTree t)
    => (t -> bc)
       -- ^ A means of summarizing the current node for tracking breadcrumbs.
       -- If you don't need breadcrumbs, use @'const' ()@.
    -> t
       -- ^ The tree to parse.
    -> Parser bc t a
       -- ^ How to parse the tree.
    -> Maybe a
runParser summarize tt =
  getResult . flip (parseNode summarize mempty) tt


------------------------------------------------------------------------------
-- | Transformer the breadcrumbs of a 'Parser'.
--
-- @since 0.1.0.0
mapBreadcrumbs :: (bc' -> bc) -> Parser bc t a -> Parser bc' t a
mapBreadcrumbs _ (Pure a)         = Pure a
mapBreadcrumbs t (LiftA2 f pa pb) = LiftA2 f (mapBreadcrumbs t pa) (mapBreadcrumbs t pb)
mapBreadcrumbs t GetCrumbs        = fmap t GetCrumbs
mapBreadcrumbs t (Target p pa)    = Target p $ mapBreadcrumbs t pa
mapBreadcrumbs t (OnChildren pa)  = OnChildren $ mapBreadcrumbs t pa
mapBreadcrumbs _ Current          = Current
mapBreadcrumbs t (Expect pa)      = Expect $ mapBreadcrumbs t pa
mapBreadcrumbs _ Fail             = Fail