packages feed

descript-lang-0.2.0.0: src/Descript/Misc/Build/Read/Parse/SrcAnn.hs

module Descript.Misc.Build.Read.Parse.SrcAnn
  ( SrcAnn (..)
  , TaintAnn (..)
  , parsedSrcAnn
  , gendSrcAnn
  , isTainted
  , appGend
  ) where

import Descript.Misc.Loc
import Descript.Misc.Summary
import Descript.Misc.Ann
import Data.Semigroup

-- | The relation between this AST node and the source it came from.
-- Organizes range and tainted status.
data SrcAnn
  = SrcAnn
  { -- | Where in the file the node came from.
    srcRange :: Range
    -- | Whether the node was completely generated or modified. A node
    -- is tainted if it's fully tainted, or one of its children are tainted.
  , isFullyTainted :: Bool
  } deriving (Eq, Ord, Read, Show)

-- | An annotation which explicitly specifies or doesn't specify that a
-- node is tainted.
class (Semigroup an) => TaintAnn an where
  -- | Specifies the node is fully tainted, if the annotation does so.
  -- Otherwise does nothing.
  taint :: an -> an

  -- | An annotation for a node inserted before the given node.
  preInsertAnn :: an -> an

  -- | An annotation for a node inserted after the given node
  postInsertAnn :: an -> an

instance TaintAnn SrcAnn where
  taint ann
    = SrcAnn
    { srcRange = taint $ srcRange ann
    , isFullyTainted = True
    }

  preInsertAnn ann
    = SrcAnn
    { srcRange = preInsertAnn $ srcRange ann
    , isFullyTainted = isFullyTainted ann
    }

  postInsertAnn ann
    = SrcAnn
    { srcRange = postInsertAnn $ srcRange ann
    , isFullyTainted = isFullyTainted ann
    }

instance TaintAnn Range where
  taint = id

  preInsertAnn = singletonRange . start

  postInsertAnn = singletonRange . end

instance TaintAnn () where
  taint () = ()
  preInsertAnn () = ()
  postInsertAnn () = ()

instance Semigroup SrcAnn where
  SrcAnn xSrcRange _ <> SrcAnn ySrcRange _
    = SrcAnn
    { srcRange = xSrcRange <> ySrcRange
    , isFullyTainted = True
    }

instance AnnSummary SrcAnn where
  annSummaryPre = annSummaryPre . srcRange

instance Summary SrcAnn where
  summary (SrcAnn srcRange' isFullyTainted')
     = "{"
    ++ summary srcRange'
    ++ ", "
    ++ taintSummary isFullyTainted'
    ++ "}"

-- | The annotation for a parsed value with the given range (the range
-- is typically the parsed value's annotation). Not tainted.
parsedSrcAnn :: Range -> SrcAnn
parsedSrcAnn srcRange'
  = SrcAnn
  { srcRange = srcRange'
  , isFullyTainted = False
  }

-- | The annotation for a generated (not parsed) value at the given range.
gendSrcAnn :: Range -> SrcAnn
gendSrcAnn srcRange'
  = SrcAnn
  { srcRange = srcRange'
  , isFullyTainted = True
  }

-- | Whether the node wasn't just parsed from a file, it was also
-- modified or completely generated.
--
-- If a node isn't tainted, it can just be "printed" by just taking the
-- text at its range. Otherwise its text needs to be regenerated.
isTainted :: (Ann a) => a SrcAnn -> Bool
isTainted = any isFullyTainted

-- | Appends a generated node to a parsed (and possibly modified) node.
appGend :: (Ann a, TaintAnn an, Semigroup (a an)) => a an -> a () -> a an
x `appGend` y = x <> (ann' <$ y)
  where ann' = taint $ postInsertAnn $ getAnn x

taintSummary :: Bool -> String
taintSummary True = "tainted"
taintSummary False = "untainted"