{-# language DataKinds #-} {-# language GeneralizedNewtypeDeriving, MultiParamTypeClasses, BangPatterns #-} {-# language TypeFamilies #-} {-# language DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric #-} {-# language TemplateHaskell #-} {-# language InstanceSigs, ScopedTypeVariables, TypeApplications #-} {-| Module : Language.Python.Syntax.Whitespace Copyright : (C) CSIRO 2017-2019 License : BSD3 Maintainer : Isaac Elliott Stability : experimental Portability : non-portable -} module Language.Python.Syntax.Whitespace ( -- * Whitespace Newline(..) , Whitespace(..) , Blank(..) , HasTrailingWhitespace(..) , HasTrailingNewline(..) -- * Indentation , IndentLevel, getIndentLevel, indentLevel, absoluteIndentLevel , Indent(..), indentWhitespaces, indentIt, dedentIt , Indents(..), indentsValue, indentsAnn, subtractStart ) where import Control.Lens.Iso (Iso', iso, from) import Control.Lens.Getter ((^.), view) import Control.Lens.Lens (Lens', lens) import Control.Lens.Setter ((.~)) import Control.Lens.TH (makeLenses) import Control.Lens.Traversal (Traversal') import Data.Deriving (deriveEq1, deriveOrd1) import Data.Generics.Product.Typed (typed) import Data.Foldable (toList) import Data.Function ((&)) import Data.FingerTree (FingerTree, Measured(..), fromList) import Data.List (stripPrefix) import Data.List.NonEmpty (NonEmpty(..)) import Data.Monoid (Monoid, Endo(..), Dual(..)) import Data.Semigroup (Semigroup, (<>)) import GHC.Exts (IsList(..)) import GHC.Generics (Generic) import qualified Data.List.NonEmpty as NonEmpty import Language.Python.Syntax.Ann import Language.Python.Syntax.Comment -- | A newline is either a carriage return, a line feed, or a carriage return -- followed by a line feed. data Newline = CR | LF | CRLF deriving (Eq, Ord, Show) -- | Whitespace is either a space, a tab, a newline that continues the -- logical line ('Continued'), a newline that ends the logical line ('Newline'), -- or a 'Comment'. -- -- Despite not literally being whitespace, comments inside enclosed forms -- are treated as whitespace. See -- -- Example and counterexample of comments as whitespace -- -- @ --( 1 + -- # here's a comment -- 2 + -- 3 # another comment --) -- @ -- -- @ -- x = 5 + 5 -- # this line is not considered whitespace -- y = x * 2 -- @ -- -- @ -- [ 1 -- , 2 # I'm whitespace -- , 3 -- # also whitespace -- ] -- @ data Whitespace = Space | Tab | Continued Newline [Whitespace] | Newline Newline | Comment (Comment ()) deriving (Eq, Ord, Show) -- | Every syntactic element contains the whitespace that immediately follows it. -- -- This type class lets us access this trailing whitespace in many different -- types throughout hpython. class HasTrailingWhitespace s where trailingWhitespace :: Lens' s [Whitespace] instance HasTrailingWhitespace a => HasTrailingWhitespace (NonEmpty a) where trailingWhitespace = lens (view trailingWhitespace . NonEmpty.last) (\(x :| xs) ws -> case xs of [] -> (x & trailingWhitespace .~ ws) :| xs x' : xs' -> NonEmpty.cons x $ (x' :| xs') & trailingWhitespace .~ ws) -- | A statement-containing thing may have a trailing newline -- -- Some forms /always/ have a trailing newline, which is why this class isn't just -- @trailingNewline :: 'Lens'' (s v a) ('Maybe' 'Newline')@ class HasTrailingNewline (s :: [*] -> * -> *) where trailingNewline :: Traversal' (s v a) Newline setTrailingNewline :: s v a -> Newline -> s v a -- | Lines which are "blank", meaning that they contain, if anything, only -- whitespace and/or a comment. data Blank a = Blank { _blankAnn :: Ann a , _blankWhitespaces :: [Whitespace] , _blankComment :: Maybe (Comment a) } deriving (Eq, Show, Functor, Foldable, Traversable, Generic) instance HasAnn Blank where annot :: forall a. Lens' (Blank a) (Ann a) annot = typed @(Ann a) -- | Python has rules regarding the expansion of tabs into spaces and how to -- go about computing indentation after this is done. -- -- See -- -- This data structure implements those rules as a monoid. newtype IndentLevel = IndentLevel { appIndentLevel :: Maybe Int -> Dual (Endo (Bool, Int)) } deriving (Semigroup, Monoid) indentLevel :: Indent -> Int indentLevel = getIndentLevel . measure . unIndent getIndentLevel :: IndentLevel -> Int getIndentLevel il = snd $ appEndo (getDual (appIndentLevel il Nothing)) (False, 0) absoluteIndentLevel :: Int -> Indent -> Int absoluteIndentLevel n il = snd $ appEndo (getDual (appIndentLevel (measure $ unIndent il) $ Just n)) (False, 0) instance Measured IndentLevel Whitespace where measure e = IndentLevel $ \absolute -> Dual . Endo $ \(b, !i) -> case e of Space -> (b, if b then i else i+1) Tab -> (b, if b then i else maybe (i + 8 - rem i 8) (+i) absolute) Continued{} -> (True, i) Newline{} -> error "Newline does not have an IndentLevel" Comment{} -> error "Comment does not have an IndentLevel" newtype Indent = MkIndent { unIndent :: FingerTree IndentLevel Whitespace } deriving (Eq, Ord, Show, Semigroup, Monoid) instance IsList Indent where type Item Indent = Whitespace toList = view indentWhitespaces fromList = view $ from indentWhitespaces -- | Indent some indentation by a chunk indentIt :: [Whitespace] -> Indents a -> Indents a indentIt ws (Indents a b) = Indents (ws ^. from indentWhitespaces : a) b -- | Deent some indentation by a chunk dedentIt :: Indents a -> Indents a dedentIt i@(Indents [] _) = i dedentIt (Indents (_:b) c) = Indents b c -- | An 'Indent' is isomorphic to a list of 'Whitespace' indentWhitespaces :: Iso' Indent [Whitespace] indentWhitespaces = iso (Data.Foldable.toList . unIndent) (MkIndent . Data.FingerTree.fromList) -- | Subtract the first argument from the beginning of the second -- -- Returns 'Nothing' if the first list is not a prefix of the second. subtractStart :: Indents a -> Indents a -> Maybe (Indents a) subtractStart (Indents a _) (Indents b c) = Indents <$> stripPrefix a b <*> pure c -- | A possibly annotated list of 'Indent's. data Indents a = Indents { _indentsValue :: [Indent] , _indentsAnn :: Ann a } deriving (Eq, Show, Functor, Foldable, Traversable, Generic) instance Semigroup a => Semigroup (Indents a) where Indents a b <> Indents c d = Indents (a <> c) (b <> d) instance HasAnn Indents where annot :: forall a. Lens' (Indents a) (Ann a) annot = typed @(Ann a) makeLenses ''Indents deriveEq1 ''Indents deriveOrd1 ''Indents