packages feed

yi-0.6.0: Yi/Syntax/Haskell.hs

-- Copyright (c) JP Bernardy 2008

module Yi.Syntax.Haskell where

import Yi.IncrementalParse
import Yi.Lexer.Alex
import Yi.Lexer.Haskell
import Yi.Style (hintStyle, errorStyle, commentStyle, StyleName)
import Yi.Syntax.Layout
import Yi.Syntax.Tree
import Yi.Syntax.Paren (modStroke, tokenToStroke)
import Yi.Syntax
import Yi.Prelude
import Prelude ()
import Data.Monoid
import Data.Maybe
import Data.List (filter, takeWhile)

indentScanner :: Scanner (AlexState lexState) (TT)
              -> Scanner (Yi.Syntax.Layout.State Token lexState) (TT)
indentScanner = layoutHandler startsLayout [(Special '(', Special ')'),
                                            (Special '[', Special ']'),
                                            (Special '{', Special '}')] ignoredToken
                         (fmap Special ['<', '>', '.'])

-- HACK: We insert the Special '<', '>', '.', that don't occur in normal haskell
-- parsing.

ignoredToken :: TT -> Bool
ignoredToken (Tok t _ (Posn _ _ col)) = col == 0 && isComment t || t == CppDirective
    
-- isNoise :: Token -> Bool
isNoise :: (Char -> Bool) -> Token -> Bool
-- isNoise (Special _) = False
isNoise f (Special c) =  f c
isNoise _ (ReservedOp _) = False
-- isNoise _ (ReservedOp Equal) = False
isNoise _ (Reserved _) = False
isNoise _ (Comment _) = False
-- isNoise _ (Operator _) = False
isNoise _ _ = True

data Tree t
    = Paren t (Tree t) t -- A parenthesized expression (maybe with [ ] ...)
    | Block [Tree t]     -- A list of things separated by layout (as in do, where, let, etc.)
    | Atom t
    | Error t
    | Bind (Tree t) t (Tree t)
    | Expr [Tree t]
    | KW t (Tree t) -- opening kw, body
    | Cmnt [t] (Tree t) -- comments before the stuff
    | Bin (Tree t) (Tree t)
    | Empty
      deriving Show

instance Functor Tree where
  fmap = fmapDefault

instance IsTree Tree where
    subtrees (Paren _ (Expr g) _) = g
    subtrees (Block s) = s
    subtrees (Expr a) = a
    subtrees (Bind l _ r) = [l,r]
    subtrees (KW _ b) = [b]
    subtrees (Cmnt _ t) = [t]
    subtrees _ = []

instance Traversable Tree where
    traverse f t = trav1 (traverse f) f t
--    traverse f (Atom t) = Atom <$> f t
--    traverse f (Error t) = Error <$> f t
--    traverse f (Paren l g r) = Paren <$> f l <*> traverse f g <*> f r
--    traverse f (Block s) = Block <$> traverse (traverse f) s
--    traverse f (Expr a) = Expr <$> traverse (traverse f) a
--    traverse f (Bind l eq r) = Bind <$> traverse f l <*> f eq <*> traverse f r
--    traverse f (KW k h b) = KW <$> f k <*> traverse f h <*> traverse f b
--    traverse f (Cmnt cmts t) = Cmnt <$> traverse f cmts <*> traverse f t
--    traverse _ Empty = pure Empty

class XTrav t where
    trav1 :: (Applicative f) => (t a -> f (t b)) -> (a -> f b) -> t a -> f (t b)

instance XTrav Tree where
 trav1 rec f x = help x
  where
    help (Atom t) = Atom <$> f t
    help (Error t) = Error <$> f t
    help (Paren l g r) = Paren <$> f l <*> rec g <*> f r
    help (Bind l eq r) = Bind <$> rec l <*> f eq <*> rec r
    help (KW k b) = KW <$> f k <*> rec b
    help Empty = pure Empty
    help (Block s) = Block <$> traverse rec s
    help (Cmnt cmts t) = Cmnt <$> traverse f cmts <*> rec t

instance Foldable Tree where
    foldMap = foldMapDefault

-- | Search the given list, and return the 1st tree after the given
-- point on the given line.  This is the tree that will be moved if
-- something is inserted at the point.  Precondition: point is in the
-- given line.  

-- TODO: this should be optimized by just giving the point of the end
-- of the line
getIndentingSubtree :: [Tree TT] -> Point -> Int -> Maybe (Tree TT)
getIndentingSubtree roots offset line =
    listToMaybe $ [t | (t,posn) <- takeWhile ((<= line) . posnLine . snd) $ allSubTreesPosn,
                   -- it's very important that we do a linear search
                   -- here (takeWhile), so that the tree is evaluated
                   -- lazily and therefore parsing it can be lazy.
                   posnOfs posn > offset, posnLine posn == line]
    where allSubTreesPosn = [(t',posn) | root <- roots, t'@(Block (Expr (t:_):_)) <- getAllSubTrees root, 
                               let Just tok = getFirstElement t, let posn = tokPosn tok]    

-- dropWhile' f = foldMap (\x -> if f x then mempty else Endo (x :))
-- 
-- isBefore l (Atom t) = isBefore' l t
-- isBefore l (Error t) = isBefore l t
-- isBefore l (Paren l g r) = isBefore l r
-- isBefore l (Block s) = False
-- 
-- isBefore' l (Tok {tokPosn = Posn {posnLn = l'}}) = 

isEmpty :: Tree t -> Bool
isEmpty Empty = True
isEmpty _ = False

parse :: P TT (Tree TT)
parse = parse' tokT tokFromT

parse' :: (TT -> Token) -> (Token -> TT) -> P TT (Tree TT)
parse' toTok fromT = pc <* eof -- pBlockOf pDecl <* eof
    where 
      -- | parse a special symbol
      sym f = symbol (f . toTok)
      sym' d = sym (`elem` d)
      exact s = sym (== s)
      spec '|' = exact (ReservedOp Pipe)
      spec '=' = exact (ReservedOp Equal)
      spec c = sym (isSpecial [c])

      -- | Create a special character symbol
      newT c = fromT (Special c)

      pleaseSym c = (recoverWith (pure $ newT '!')) <|> spec c
      pleaseB b = (recoverWith (pure $ newT '!')) <|> b


      pFun = Bin <$> pExpr' <*> ((Block <$> some pGuard) <|> pRhs) -- add support for do
      pGuard = KW <$> spec '|' <*> (Bin <$> pExpr <*> (pRhs <|> pEmpty)) -- same here, add support for do

      pRhs, pEmpty :: P TT (Tree TT)
      pRhs = KW <$> spec '=' <*> (pExpr <|> pDo)

      pDo = KW <$> sym' [Reserved OtherLayout] <*> pExpr -- do let in and so on, should maybe match more exact char..
      pImport = KW <$> sym' [Reserved Other] <*> (pAs <|> pEmpty) -- (Atom <$> sym (`elem` [ConsIdent])) <|> pEmpty) -- catch imports, import qualified, import A as B, import A ()

      pAs = Bin <$> (Atom <$> sym' [ConsIdent]) <*> 
              (Bin <$> ((Atom <$> sym' [Reserved Other]) <|> pExpr) <*> ((Atom <$> sym' [ConsIdent]) <|> pExpr)) -- can be more sensitive

      pModule = KW <$> sym' [Reserved Module] <*> pExpr
      pType = KW <$> sym' (fmap Reserved [NewType, Data, Type]) <*> pExpr

      -- Comments
      pCommentLine = Atom <$> sym' [Comment Line]
      pComment = (pCommentLine <|> pCommentBrack)
      pCommentBrack = Paren <$> sym' [Comment Open] <*> 
                      pLine <*> (pleaseB $  sym' [Comment Close])
      pLine = Expr <$> many (Atom <$> sym' [Comment Text]) -- some comment lines
      pCommentBlock = KW <$> (sym' [Comment Open]) <*> (Block <$> (spec '<' *> (filter (not . isEmpty) <$> ((Atom <$> sym' [Comment Close]) `sepBy` spec '.')) <* spec '>'))
      -- blocks dont work yet..
      

      -- Begin the parsing
      pc = (Bin <$> (Expr <$> many (pComment <|> pCppDir)) <*> (pBlockOf pBegin <|> pEmpty)) <|> pBlockOf pBegin

      pCppDir = Atom <$> sym' [CppDirective]

      pTuple = Paren  <$>  spec '(' <*> pExpr  <*> pleaseSym ')'
      pBlockOf p = Block <$> (spec '<' *> (filter (not . isEmpty) <$> (p `sepBy` spec '.')) <* spec '>')  -- see HACK above 
      pBlock = pBlockOf pExpr
      pWhereCl = KW <$> sym' [Reserved Where] <*> ((Bin <$> (Expr <$> many pComment) <*> pd) <|> pd)

      pd = pBlockOf pDecl <|> pEmpty

      pEmpty = pure Empty

      pAny = pComment <|> pType <|> pEmpty

      pBegin = pModule <|> pDecl -- small hack to avoid allowing several module declarations

      pDecl = pImport <|> pFun <|> pExpr' <|> pEmpty <|> pType

      pExpr' = Expr <$> some pElem
      pExpr = pExpr' <|> pEmpty
      -- also, we discard the empty statements

      pElem :: P TT (Tree TT)
      pElem = pTuple
          <|> (Paren  <$>  spec '[' <*> pExpr  <*> pleaseSym ']')
          <|> (Paren  <$>  spec '{' <*> pExpr  <*> pleaseSym '}') -- records should be able to contain '='
          <|> pWhereCl
          <|> pBlock
          <|> pComment
          <|> (Atom <$> sym (isNoise (\x -> elem x ";,`")))
          <|> (Error <$> recoverWith (sym (not . isNoise (\x -> not $ elem x "})]"))))

      -- note that, by construction in Layout module, '<' and '>' will always be
      -- matched, so we don't try to recover errors with them.

(<||>) :: (t -> Bool) -> (t -> Bool) -> t -> Bool
(<||>) f g x = f x || g x


-- TODO: (optimization) make sure we take in account the begin, so we don't return useless strokes
getStrokes :: Point -> Point -> Point -> (Tree TT) -> [Stroke]
getStrokes point _begin _end t0 = result
    where getStrokes' (Atom t) = (ts t :)
          getStrokes' (Error t) = err t
          getStrokes' (Block s) = getStrokesL s
          getStrokes' (Paren l g r)
              | isErrorTok $ tokT r = err l . getStrokes' g
              -- left paren wasn't matched: paint it in red.
              -- note that testing this on the "Paren" node actually forces the parsing of the
              -- right paren, undermining online behaviour.
              | (posnOfs $ tokPosn $ l) == point || (posnOfs $ tokPosn $ r) == point - 1
               = (modStroke hintStyle (ts l) :) . getStrokes' g . (modStroke hintStyle (ts r) :)
              | otherwise  = tk l . getStrokes' g . tk r
          getStrokes' (Expr g) = getStrokesL g
          getStrokes' (Bind l eq r) = getStrokes' l . tk eq . getStrokes' r
          getStrokes' (Bin l r) = getStrokes' l . getStrokes' r
          getStrokes' (KW k b) = tk k . getStrokes' b
          getStrokes' Empty = id
          getStrokes' (Cmnt t t') = ((++) (fmap ((modStroke commentStyle) . ts) t)) . getStrokes' t'
          getStrokesL g = compose (fmap getStrokes' g)
          tk t | isErrorTok $ tokT t = id
               | otherwise = (ts t :)
          err t = (modStroke errorStyle (ts t) :)
          ts = tokenToStroke
          compose = foldr (.) id
          result = getStrokes' t0 []
          -- result = getStrokesL t0 []