packages feed

cparsing-0.1.0.0: SourceCode/ASTNode.hs

{-# LANGUAGE MultiParamTypeClasses, TypeOperators, FlexibleInstances, FlexibleContexts, DefaultSignatures, OverlappingInstances, ImpredicativeTypes #-}

-- | Provides a derivable class that can be used to access information stored in nodes.
module SourceCode.ASTNode where

import Control.Lens (Lens', lens)
import GHC.Generics
import Control.Applicative
import Data.Maybe

-- | Mark data in the AST that is not a node itself
-- No data type should ever be instantiated with both 'ASTData' and 'ASTNode' classes.
class ASTData d where

-- | Mark a datatype that it is an AST node. Provides access to the node information.
-- TODO : replace with a SmartTrav traversal
class ASTNode a inf where
  info :: Lens' (a inf) inf
  info = lens getInfo (flip setInfo)

  getInfo :: a inf -> inf
  
  default getInfo :: (Generic (a inf), GASTNode (Rep (a inf)) inf)
                  => a inf -> inf
  getInfo = fromMaybe (error "getInfo: No info for node") . ggetInfo True . from
  
  setInfo :: inf -> a inf -> a inf
  default setInfo :: (Generic (a inf), GASTNode (Rep (a inf)) inf)
                  => inf -> a inf -> a inf
  setInfo inf = to . gsetInfo True inf . from
  
-- | Generic class to access info members inside nodes
class GASTNode f inf where
  ggetInfo :: Bool -> f a -> Maybe inf
  ggetInfo _ _ = Nothing
  
  gsetInfo :: Bool -> inf -> f a -> f a
  gsetInfo _ _ k = k
  
-- | No info in empty constructors
instance GASTNode U1 inf where
  
-- | Ignore metainformation
instance (GASTNode a inf) => GASTNode (M1 t c a) inf where
  ggetInfo k (M1 x) = ggetInfo k x
  gsetInfo k inf m1@(M1 x) = M1 $ gsetInfo k inf x
  
-- | Add the results from parts of products, 
-- but can only follow a member if there is only one
instance (GASTNode a inf, GASTNode b inf) 
           => GASTNode (a :*: b) inf where
  ggetInfo _ (a :*: b) = ggetInfo False a <|> ggetInfo False b
  gsetInfo _ inf (a :*: b) = gsetInfo False inf a :*: gsetInfo False inf b
  
-- | Ignore other possiblities
instance (GASTNode a inf, GASTNode b inf) 
           => GASTNode (a :+: b) inf where
  ggetInfo k (L1 x) = ggetInfo k x
  ggetInfo k (R1 x) = ggetInfo k x
  gsetInfo k inf (L1 x) = L1 $ gsetInfo k inf x
  gsetInfo k inf (R1 x) = R1 $ gsetInfo k inf x
  
-- | When a field is the info return / replace it
instance GASTNode (K1 i inf) inf where
  ggetInfo _ (K1 x) = Just x
  gsetInfo _ inf (K1 x) = K1 inf
  
-- | On data nodes inside AST, return Nothing / leave it be
instance ASTData a => GASTNode (K1 i a) inf where
  
-- | On children nodes, if there is only one child try to return the info
-- from there or replace there. This helps to keep AST from containing 
-- redundant info data.
instance ASTNode a inf => GASTNode (K1 i (a inf)) inf where      
  ggetInfo True (K1 x) = Just $ getInfo x
  ggetInfo False _ = Nothing
  
  gsetInfo True inf (K1 x) = K1 $ setInfo inf x
  gsetInfo False inf constr = constr