packages feed

hat-2.7.0.0: tools/SExp.hs

-- Pretty printing of an ART expression with a sub-expression highlit.
-- First convert a value (basically a FileNode) into an S-expression,
-- then convert the S-expression to a Doc for pretty printing.
module SExp
  ( SExp(..)
  , SFixity(..), transFixity -- used only in module Pattern
  , Label
  , QName(..), showQN
  , fileNode2SExp
  , optParens
  , schemeParens
  , notCondParens
  , sExp2Doc
  , arity, child, label, children, rebuild, parent, prune
  , funId, funLabel
  , prettyExpression
  , prettySExp
  , prettyEquation
  , prettyEquation2
  ) where

import LowLevel hiding    (nil)
import qualified LowLevel (nil)
import HighlightStyle     (Highlight(..),Colour(..))
import PrettyLibHighlight as Pretty
                          (Doc,text,(<>),delimiter,fdelimiter,nil,group,parens
                          ,groupNest,pretty,highlight)
import CommonUI           (Options(..))
import Data.Char          (isAlpha)
import Data.List          (unzip3,isPrefixOf)
import System.IO          (hPutStrLn,stderr)
import System.IO.Unsafe  (unsafePerformIO)
import Numeric            (showHex)

bold = highlight [Bold, Foreground Blue]

errorT  :: String -> a
errorT s = unsafePerformIO (do hPutStrLn stderr s; return (error ""))
debug  :: (Show a) => a -> String -> a
x `debug` s = unsafePerformIO (do hPutStrLn stderr (s++show x); return x)

data QName
  = Plain String
  | Qualified String String
  deriving Show

instance Eq QName where
  Plain v       == Plain v'         =   v==v'
  Qualified q v == Plain v'         =   v==v'
  Plain v       == Qualified q' v'  =   v==v'
  Qualified q v == Qualified q' v'  =   v==v' && q==q'

showQN :: Bool -> QName -> String
showQN _ (Plain n)           = showString n ""
showQN False (Qualified q n) = showString n ""
showQN True  (Qualified q n) = (showString q . showChar '.' . showString n) ""

type Label = (String,FileNode)	-- The label on an SExp contains two components,
				-- a unique string, and the file pointer.
				-- The former enables unique highlighting, and
				-- the latter enables highlighting of sharing.

data SExp a
  = SApp a [SExp a]		-- n-ary application of at least 2 expressions
  | SId a QName SFixity		-- an identifier (variable or constructor)
  | SLiteral a String		-- any other kind of basic value
  | SString a String Ellipsis	-- character strings have special sugar
  | SWithin a [SExp a]		-- chains of if/case/guard inside an expression
  | SLambda a			-- a lambda expression
  | SDoLambda a			-- (internal) the lambda binding in a do stmt
  | SDoStmt a a (SExp a) (SExp a) -- do { val <- stmt }
  | SIf a (SExp a) (Maybe (SExp a))	-- possibly contains result
  | SCase a (SExp a) (Maybe (SExp a))
  | SGuard a (SExp a) (Maybe (SExp a))
  | SFieldExpr a (SExp a) [String] [SExp a]
				-- constructor value, or update
  | SCut a			-- cut off subexpression (to limit depth)
  | SUnevaluated a		-- underscore
  | SInterrupted a		--  ^C  (expr entered but never completed)
  | SBottom a			-- _|_  (expr entered but never completed)
  | SCycle a String (SExp a)	-- cyclic expression shown as `id where id = ..'
  | SEquation a (SExp a) (SExp a)
			-- an equation only makes sense as the root of an SExp
  | SParens a (SExp a) Int	-- parenthesised expr
  | SInfinite a
  | SFiniteMap a [([SExp a],SExp a)]
   deriving Show		-- only for testing/debugging

type Ellipsis = Bool		-- is a character string truncated?

data SFixity = 
  SInfix Int | SInfixL Int | SInfixR Int | SAssoc Int String | SInfixDefault
  -- need own type for some hardcoded operators that are known to be
  -- semantically associative
  deriving (Show)

-- translate fixity from the file representation to the structured fixity type
transFixity :: Int -> SFixity
transFixity f = case f `divMod` 4 of
                  (p,0) -> SInfix p
                  (p,1) -> SInfixR p
                  (p,2) -> SInfixL p
                  (p,3) -> SInfixDefault

-- arity of an S-expression
arity :: SExp a -> Int
arity (SApp _ exps)     = length exps
arity (SWithin _ exps)  = length exps
arity (SIf _ _ _)       = 1
arity (SCase _ _ _)     = 1
arity (SGuard _ _ _)    = 1
arity (SEquation _ _ _) = 2
arity (SFieldExpr _ _ _ upds) = 1 + length upds
arity (SDoStmt _ _ _ _) = 3
arity _                 = 0

-- get child of an S-expression
-- precondition: i < arity exp
child :: Int -> SExp a -> SExp a
child i (SApp _ exps)    = skipCaseIfGuard (exps!!i)
child i (SWithin _ exps) = skipCaseIfGuard (exps!!i)
--child 0 (SIf _ exp)      = exp
--child 0 (SCase _ exp)    = exp
--child 0 (SGuard _ exp)   = exp
child 0 (SEquation _ l r) = l
child 1 (SEquation _ l r) = r
child 0 (SFieldExpr _ e _ upds) = e
child i (SFieldExpr _ e _ upds) = upds!!(i-1)
child 0 (SDoStmt _ m _ _) = SLambda m
child 1 (SDoStmt _ _ v _) = v
child 2 (SDoStmt _ _ _ a) = a
child i exp               = errorT ("SExp.child: "++show i++" is too large.")

skipCaseIfGuard (SIf _ exp _) = exp
skipCaseIfGuard (SCase _ exp _) = exp
skipCaseIfGuard (SGuard _ exp _) = exp
skipCaseIfGuard exp = exp

label :: SExp a -> a
label (SApp l _)      = l
label (SId l _ _)     = l
label (SLiteral l _)  = l
label (SString l _ _) = l
label (SLambda l)     = l
label (SWithin l _)   = l
label (SIf l _ _)     = l
label (SCase l _ _)   = l
label (SGuard l _ _)  = l
label (SCut l)        = l
label (SUnevaluated l)= l
label (SInterrupted l)= l
label (SBottom l)     = l
label (SCycle l _ _)  = l
label (SDoStmt l _ _ _) = l
label (SEquation l _ _) = l
label (SFieldExpr l _ _ _) = l

children :: SExp a -> [SExp a]
children (SApp _ es)             = es
children (SWithin l es)          = es
children (SIf _ e _)             = [e]
children (SCase _ e _)           = [e]
children (SGuard _ e _)          = [e]
children (SCycle _ _ e)          = [e]
children (SEquation _ e r)       = [e,r]
children (SFieldExpr _ e _ upds) = e: upds
children (SDoStmt _ m e p)       = [SDoLambda m,e,p]
children _                       = []

rebuild :: SExp a -> [SExp a] -> SExp a
rebuild (SApp l _)       es = SApp l es
rebuild (SWithin l _)    es = SWithin l es
rebuild (SIf l _ r)     [e] = SIf l e r
rebuild (SCase l _ r)   [e] = SCase l e r
rebuild (SGuard l _ r)  [e] = SGuard l e r
rebuild (SCycle l v _)  [e] = SCycle l v e
rebuild (SEquation l _ _) [e,r] = SEquation l e r
rebuild (SFieldExpr l _ labs _) (e:upds) = SFieldExpr l e labs upds
rebuild (SDoStmt l _ _ _) [SDoLambda m,e,p] = SDoStmt l m e p
rebuild sexp             _  = sexp

relabel :: a -> SExp a -> SExp a
relabel l (SApp _ es)       = SApp l es
relabel l (SId _ v f)       = SId l v f
relabel l (SLiteral _ s)    = SLiteral l s
relabel l (SString _ s d)   = SString l s d
relabel l (SLambda _)       = SLambda l
relabel l (SDoLambda _)     = SDoLambda l
relabel l (SWithin _ e)     = SWithin l e
relabel l (SIf _ e r)       = SIf l e r
relabel l (SCase _ e r)     = SCase l e r
relabel l (SGuard _ e r)    = SGuard l e r
relabel l (SCut _)          = SCut l
relabel l (SUnevaluated _)  = SUnevaluated l
relabel l (SInterrupted _)  = SInterrupted l
relabel l (SBottom _)       = SBottom l
relabel l (SCycle _ a b)    = SCycle l a b
relabel l (SDoStmt _ m e p) = SDoStmt l m e p
relabel l (SEquation _ e r) = SEquation l e r
relabel l (SFieldExpr _ e labs upds) = SFieldExpr l e labs upds

parent :: SExp Label -> FileNode
parent (SWithin _ (x:_)) = parent x
parent (SEquation _ x _) = parent x
parent x                 = getParentNode (snd (label x))

prune :: Int -> SExp a -> SExp a
prune 0 s = SCut (label s)
prune n (SApp a es) = SApp a (head es: map (prune (n-1)) (tail es))
prune n (SWithin a es) = SWithin a (head es: map (prune (n-1)) (tail es))
prune n (SEquation a lhs rhs) = SEquation a (prune n lhs) (prune n rhs)
prune n s = s

funId :: SExp a -> QName
funId (SApp _ es) = funId (head es)
funId (SWithin _ es) = funId (head es)
funId (SEquation _ e _) = funId e
funId (SFieldExpr _ e _ _) = funId e
funId (SString _ _ _) = Qualified "Prelude" ":"
funId (SLambda _) = Plain "(\\..)"
funId (SDoStmt _ _ _ _) = Plain "do"
funId (SId _ s _) = s
funId _ = Plain ""

funLabel :: SExp Label -> FileNode
funLabel (SApp _ es) = funLabel (head es)
funLabel (SWithin _ es) = funLabel (head es)
funLabel (SEquation _ e _) = funLabel e
funLabel (SFieldExpr _ e _ _) = funLabel e
funLabel (SString (_,n) _ _) = n
funLabel (SDoStmt _ (_,n) _ _) = n
funLabel (SLambda (_,n)) = n
funLabel (SId (_,n) _ _) = n
funLabel _ = LowLevel.nil

fst3 (x,_,_) = x
snd3 (_,x,_) = x
thd3 (_,_,x) = x

-- conversion function
-- If `uneval' boolean is True, then unevaluated arguments appear in full
-- in the result, otherwise they are represented by SUnevaluated.
fileNode2SExp :: Int -> Bool -> Bool -> Bool -> Label -> SExp Label
fileNode2SExp cutoff uneval strings toplevelLHS labl =
  case go cutoff uneval strings toplevelLHS [] labl of (e,_,_) -> e  
  where
  simple e = (e,[],[])
  go :: Int	            -- cutoff depth
     -> Bool                -- show unevaluated args in full?
     -> Bool                -- sugar character strings?
     -> Bool                -- top-level LHS? (implies uneval to one level)
     -> [(FileNode,String)] -- enclosing nodes w/ variable name for `where'
     -> Label               -- root node of expression
     -> ( SExp Label        -- expression 
        , [FileNode]        -- nodes that start cycle
        , [String] )        -- variable names occurring (except for cycles)
  go 0     uneval strings top nodesAbove labl = simple (SCut labl)
  go depth uneval strings top nodesAbove labl@(lab,node) =
    if      node == LowLevel.nil then simple $ SUnevaluated labl
    else if node == unevaluated then simple $ SUnevaluated labl
    else if node == entered then simple $ SBottom labl
    else if node == interrupted then simple $ SInterrupted labl
    else if node == lambda then simple $ SLambda labl
    else if node == dolambda then simple $ SDoLambda labl
    else
    let r = peekResult node in
    if  r == unevaluated && not (uneval||top) then simple $ SUnevaluated labl
    else if r == entered && not top then simple $ SBottom labl
    else if r == interrupted && top then simple $ SInterrupted labl
    else if r == lambda && not uneval then simple $ SLambda labl
    else if r == dolambda && not uneval then simple $ SDoLambda labl
    else case simpleNodeType node of
      NodeModule -> "got a Module" `errorAt` node
      NodeSrcPos -> "got an SrcPos" `errorAt` node
      NodeAtom -> -- "got an Atom" `errorAt` node
        let i = getAtom node
            m = getAtomMod node
        in simple (SId labl (Qualified m i) (transFixity (getAtomFixity node)))
      NodeApplication -> 
        let partCycles :: [FileNode]
            partCycles = (funCycles ++ concat argsCycles)
            partVars :: [String]
            partVars = funVars ++ concat argsVars
            isCycle = node `elem` partCycles
            var :: String
            var = head . filter (not . (`elem` partVars)) .
                  map (("cyc"++) . show) $ [1..] 
            newNodesAbove :: [(FileNode,String)]
            newNodesAbove = (node,var) : nodesAbove
            subExps :: [FileNode]
            subExps = getSubExprs node
            -- (fun,funCycles,funVars) = ... not accepted by nhc98
            fun = fst3 z
            funCycles = snd3 z
            funVars = thd3 z
            z = let f = head subExps in
                if f==LowLevel.nil then simple (SCut ('f':lab,f))
                else go depth uneval strings False newNodesAbove ('f':lab,f)
            args = fst3 zs
            argsCycles = snd3 zs
            argsVars = thd3 zs
            -- (args,argsCycles,argsVars) = ... not accepted by nhc98
            zs = unzip3 $ map (go (depth-1) uneval strings False newNodesAbove)
                              (zipWith lbl ['1'..] (tail subExps))
            lbl c n = (c:lab, n)
            -- To do strings right, need to peek one level inside a cons.
            z1 = go 1 uneval strings False newNodesAbove
                 ('1':lab, subExps!!1)	-- only used in string cutoff case
            z2 = go 3 uneval strings False newNodesAbove
                 ('2':lab, subExps!!2)	-- only used in string cutoff case

            sexp = case fun of
              -- convert the representation of constructors with fields
              SId n@(_,m) c _ | isConstrFields m ->
                SFieldExpr labl (SId n c SInfixDefault)
                                 (getFieldLabels m) args
              -- convert char-list into string
              SId _ (Qualified _ ":") _ | strings && length args == 2 ->
                case args!!0 of
                  SLiteral _ c | not (null c) && head c == '\'' ->
                    case args!!1 of
                      SId _ (Qualified _ "[]") _
                                    -> SString labl (init (tail c)) False
                      SString _ s d -> SString labl (init (tail c)++s) d
                      _             -> SApp labl (fun:args)
                  SCut _ ->	-- peek beyond the cut
                    case fst3 z1 of
                      SLiteral _ c | not (null c) && head c == '\'' ->
                        case fst3 z2 of
                          SId _ (Qualified _ "[]") _
                             -> SString labl (init (tail c)) False
                          _  -> SString labl (init (tail c)) True
                      _ -> SApp labl (fun:args)
                  _ -> SApp labl (fun:args)
              -- different bracketing of a char-list
              SApp _ args1@[SId _ (Qualified _ ":") _,SLiteral _ c]
                | strings && length args == 1
                && not (null c) && head c == '\'' ->
                    case args!!0 of
                      SId _ (Qualified _ "[]") _
                                    -> SString labl (init (tail c)) False
                      SString _ s d -> SString labl (init (tail c)++s) d
                      _             -> SApp labl (args1++args)
              SApp _ args1@[SId _ (Qualified _ ":") _,SCut _]
                | strings && length args == 1 ->
                    fst3 (go (depth+1) uneval strings False nodesAbove labl)
              -- combine applications
              SApp n args1 -> SApp labl (args1++args)
              -- convert DoLambda to DoStmt
        --    SDoLambda n -> let binding = args!!0 in
              SId n (Qualified _ "do") _ ->
                    let binding = args!!0
                        bindingFN = snd (label binding)
                        nearestEnclosing node =
                            let par = getParentNode node
                                res = getResult par False
                            in if res==bindingFN
                               then nearestEnclosing par else res
                    in
                    SDoStmt labl n binding
                            (fst3 (go depth uneval strings top []
                                      ("d",(nearestEnclosing bindingFN))))
              -- anything else is just a simple application
              _ -> SApp labl (fun:args)
        
        -- Check for cycles in the application and return appropriately.
        -- The case for no cycles is Nothing then the else branch.
        in case lookup node nodesAbove of
             Just var -> (SId labl (Plain var) SInfixDefault,[node],[]) 
                         -- `lower' end of cycle
             Nothing  -> ( if isCycle then SCycle (lab,node) var sexp else sexp
                         , partCycles
                         , partVars)
      NodeBasicValue ->
          let i = getValue node in simple $ SLiteral labl i
      NodeIdentifier ->
          if isLambda node then simple (SLambda labl) else
          let i = getValue node
              m = getValueMod node
          in
          ( SId labl (Qualified m i) (case i of
                           "."  | m == "Prelude" -> SAssoc 9 i
                           "++" | m == "Prelude" -> SAssoc 5 i
                           "&&" | m == "Prelude" -> SAssoc 3 i
                           "||" | m == "Prelude" -> SAssoc 2 i
                           "*"  | m == "Prelude" -> SAssoc 7 i
                           "+"  | m == "Prelude" -> SAssoc 6 i
                           ">>" | m == "Prelude" -> SAssoc 1 i
                           ">>=" | m == "Prelude" -> SAssoc 1 i
                           _ -> transFixity (getFixity node))
          , []
          , if isConstructor node then [] else [i] )
      NodeCAF ->
          let i = getValue node
              m = getValueMod node
          in simple (SId labl (Qualified m i) (transFixity (getFixity node)))
      NodeConditional ->
          let skind = case nodeType node of
                          ExpGuard -> SGuard
                          ExpCase  -> SCase
                          ExpIf    -> SIf
              within :: Label -> (SExp Label->Maybe (SExp Label)->SExp Label)
                        -> SExp Label -> SExp Label -> SExp Label
              within labl kind parent exp =
                case parent of	-- eliminate chains of SWithin
                    SWithin _ ps -> SWithin labl (ps++[kind exp Nothing])
                    _            -> SWithin labl [parent,kind exp Nothing]
          in simple $
             within labl (skind ('c':lab,node))
                          (fileNode2SExp depth uneval strings True
                                         ('w':lab, getParentNode node))
                          (fileNode2SExp depth uneval strings False
                                         ('v':lab, head (getSubExprs node)))
      NodeSugar -> -- simple $ SLiteral labl "{sugar}"
          case nodeType node of
            ExpDoStmt -> simple $ SLiteral labl "{do stmt}"
            ExpFieldUpdate ->
                let (exp:updValues) = getSubExprs node
                    updLabels = getFieldLabels node
                in simple $
                   SFieldExpr labl
                              (fileNode2SExp (depth-1) uneval strings False
                                             ('u':lab, exp))
                              updLabels
                              (map (fileNode2SExp (depth-1) uneval
                                                  strings False)
                                   (zipWith (\i v-> (i:lab, v))
                                            ['1'..] updValues))
      NodeSpecial ->
          case nodeType node of
            ExpProjection ->
                (\(exp,x,y) -> (relabel ('p':lab,node) exp, x, y) ) $
                go depth uneval strings False nodesAbove
                   ('p':lab, getResult (head (getSubExprs node)) top)
            ExpHidden -> 
                simple $ SLiteral labl "{?}"
            ExpForward ->
                go depth uneval strings False nodesAbove
                   (lab, head (getSubExprs node))

  errorAt :: String -> FileNode -> a
  errorAt str node = errorT ("SExp: "++str++" at 0x"++showHex (int node) "")


-- useful document combinators:
-- non-breaking space
(<->) :: Doc -> Doc -> Doc
d1 <-> d2 = d1 <> delimiter " " <> d2 

-- breakable space
(<+>) :: Doc -> Doc -> Doc
d1 <+> d2 = d1 <> fdelimiter " " <> d2

-- breakable non-space
(<|>) :: Doc -> Doc -> Doc
d1 <|> d2 = d1 <> fdelimiter "" <> d2

-- breakable before a comma
(<*>) :: Doc -> Doc -> Doc
d1 <*> d2 = d1 <|> text "," <> d2

-- breakable before or after a cons
(<:>) :: Doc -> Doc -> Doc
d1 <:> d2 = d1 <|> text ":" <|> d2

indentation :: Int
indentation = 2


isOpSym :: String -> Bool
isOpSym ""  = True	-- representation of the unit value ()
isOpSym sym = let c = head sym in
              not (isAlpha c || c `elem` "[_{" || sym == "(\\..)") 

funDoc :: Bool -> QName -> Doc
funDoc q qn = (if isOpSym var then parens else id) $ text (showQN q qn)
  where var = case qn of Plain v -> v; Qualified _ v -> v

opDoc :: Bool -> QName -> Doc
opDoc q qn =  text (if isAlpha (head var) then ('`' :showQN q qn++ "`")
                    else showQN q qn)
  where var = case qn of Plain v -> v; Qualified _ v -> v


data ArgPos = ALeft | ARight
isRight ARight = True
isRight ALeft = False
isLeft = not . isRight

-- surround by parentheses if necessary
-- first fixity of surrounding expression, then if left or right argument,
-- then fixity of expression itself
optParens :: SFixity -> ArgPos -> SFixity -> Doc -> Doc
optParens surFixity aPos ownFixity =
  case (priority surFixity) `compare` (priority ownFixity) of
    LT -> if priority surFixity == (-1) then groupNest indentation else id
    GT -> groupNest indentation . parens
    EQ -> if (isInfixR surFixity && isInfixR ownFixity && isRight aPos)
            || (isInfixL surFixity && isInfixL ownFixity && isLeft aPos)
            || sameAssoc surFixity ownFixity 
            then id
            else groupNest indentation . parens

{-
minimalParens :: Parenthesiser
minimalParens (SApp va (id@(SId vt (Qualified _ (',':xs)) _):args))
  = if length xs + 2 != length args
      then SParens va (SApp va (minParens id):(map minParens args))
      else SApp va (minParens id):(map minParens args)
-}
schemeParens :: SExp a -> SExp a
schemeParens = par 0
  where
    par :: Int -> SExp a -> SExp a
    par d (SApp a xs)         = SParens a (SApp a (map (par (d+1)) xs)) d
    par d (SWithin a xs)      = SWithin a (map (par (d+1)) xs)
    par d (SDoStmt a b xa xb) = SParens a (SDoStmt a b (par (d+1) xa)
                                                       (par (d+1) xb)) d
    par d (SIf a c r)         = SParens a (SIf a (par (d+1) c) r) d
    par d (SCase a c r)       = SParens a (SCase a (par (d+1) c) r) d
    par d (SGuard a c r)      = SParens a (SGuard a (par (d+1) c) r) d
    par d (SFieldExpr a x ms bs) = SParens a (SFieldExpr a (par (d+1) x) ms
                                                         (map (par (d+1)) bs)) d
    par d (SCycle a s x)      = SCycle a s (par d x)
    par d (SEquation a x y)   = SParens a (SEquation a (par (d+1) x)
                                                       (par (d+1) y)) d
    par _ x = x

notCondParens :: SExp a -> SExp a
notCondParens = par 0
  where
    par d (SApp a xs)         = SApp a (map (allow d) xs)
    par d (SWithin a xs)      = SWithin a (map (allow d) xs)
    par d (SDoStmt a b xa xb) = SDoStmt a b (allow d xa) (allow d xb)
    par d (SIf a c r)         = SIf a (par d c) r
    par d (SCase a c r)       = SCase a (par d c) r
    par d (SGuard a c r)      = SGuard a (par d c) r
    par d (SFieldExpr a x ms bs) = SFieldExpr a (allow d x) ms
                                              (map (allow d) bs)
    par d (SCycle a s x)      = SCycle a s (allow d x)
    par d (SEquation a x y)   = SEquation a (allow d x) (allow d y)
    par _ x = x

    allow d (SApp a xs)         = SParens a (SApp a (map (allow (d+1)) xs)) d
    allow d (SWithin a xs)      = SWithin a (map (allow d) xs)
    allow d (SDoStmt a b xa xb) = SDoStmt a b (allow d xa) (allow d xb)
    allow d (SIf a c r)         = SParens a (SIf a (par (d+1) c) r) d
    allow d (SCase a c r)       = SParens a (SCase a (par (d+1) c) r) d
    allow d (SGuard a c r)      = SParens a (SGuard a (par (d+1) c) r) d
    allow d (SFieldExpr a x ms bs) = SFieldExpr a (allow d x) ms
                                                (map (allow d) bs)
    allow d (SCycle a s x)      = SCycle a s (allow d x)
    allow d (SEquation a x y)   = SParens a (SEquation a (allow (d+1) x)
                                                         (allow (d+1) y)) d
    allow _ x = x


sameAssoc :: SFixity -> SFixity -> Bool
sameAssoc (SAssoc _ var1) (SAssoc _ var2) = True
sameAssoc _ _ = False

foldr0 :: (a -> a -> a) -> a -> [a] -> a
foldr0 f c [] = c
foldr0 f c xs = foldr1 f xs

listDoc :: (Show a, Eq a) => Bool -> (a->Doc->Doc) -> SFixity -> ArgPos -> SExp a -> Doc
listDoc qual high surFixity aPos e =
  group $ text "[" <> commas e
  where
  commas = sExpFold (sExp2Doc False True qual high) (text ",") (text "]")

  sExpFold head cons nil (SApp v [SId c (Qualified _ ":") _, hd
                                 , SId n (Qualified _ "[]") _]) =
      high v (head hd <|> high c (high n nil))
  sExpFold head cons nil (SApp v [SId c (Qualified _ ":") _, hd
                                 , SId n (Plain cyc) _])
      | "cyc" `isPrefixOf` cyc =
      high v (head hd <|> high c cons <|> high n (text (cyc++"..."))
      <|> high c (high n nil))
  sExpFold head cons nil (SApp v [SId c (Qualified _ ":") _
                                 , hd@(SCut t), SCut n]) =
      high v (head hd <> high c (high t (text "...")) <|> high n nil)
  sExpFold head cons nil (SApp v [SId c (Qualified _ ":") _, hd, tl]) =
      high v (head hd <|> high c cons <|> sExpFold head cons nil tl)
  sExpFold head cons nil (SId n (Qualified _ "[]") _) =
      high n nil
  sExpFold head cons nil e@(SUnevaluated _) =
      sExp2Doc False True qual high e <|> nil
  sExpFold head cons nil e@(SLiteral _ "{?}") =
      sExp2Doc False True qual high e<|>nil
  sExpFold head cons nil e =
      sExp2Doc False True qual high e

setDoc :: (Show a, Eq a) => Bool -> (a->Doc->Doc) -> SFixity -> ArgPos -> SExp a -> Doc
setDoc qual high surFixity aPos e =
  group $ text "{" <> (addCommas $ elems e) <> text "}"
  where
  addCommas [] = nil
  addCommas [x] = sExp2Doc False True qual high x
  addCommas (x:xs) =    (sExp2Doc False True qual high x)
                     <> text ","
                     <> addCommas xs
  
  elems (SId v (Qualified "Data.FiniteMap" "EmptyFM") _) = []
  elems (SApp v [SId c (Qualified "Data.FiniteMap" "Branch") _, k, _, _, l, r])
    = k : elems l ++ elems r

fmDoc :: (Show a, Eq a) => Bool
                       -> (a->Doc->Doc)
                       -> SFixity
                       -> ArgPos
                       -> SExp a
                       -> Doc
fmDoc qual high surFixity aPos e =
  group $ text "{" <> (addCommas $ elems e) <> text "}"
  where
  addCommas [] = nil
  addCommas [(k,e)] =
        sExp2Doc False True qual high k
    <+> text "|->"
    <+> sExp2Doc False True qual high e
  addCommas ((k,e):xs) =
        sExp2Doc False True qual high k
    <+> text "|->"
    <+> sExp2Doc False True qual high e
    <>  text ","
    <>  addCommas xs
  
  elems (SId v (Qualified "Data.FiniteMap" "EmptyFM") _) = []
  elems (SApp v [SId c (Qualified "Data.FiniteMap" "Branch") _, k, e, _, l, r])
    = (k,e) : elems l ++ elems r

priority (SInfix p) = p
priority (SInfixL p) = p
priority (SInfixR p) = p
priority (SAssoc p _) = p
priority SInfixDefault = 9

isInfixL (SInfixL _) = True
isInfixL SInfixDefault = True
isInfixL _ = False

isInfixR (SInfixR _) = True
isInfixR _ = False

isNotInfixDefault SInfixDefault = False
isNotInfixDefault _ = False

considerAsOperator :: QName -> SFixity -> Bool
considerAsOperator qname fixity = isOpSym var || isNotInfixDefault fixity 
  where var = case qname of Plain v -> v; Qualified _ v -> v

highlightForDepth :: Int -> [Highlight]
highlightForDepth x = [head (drop ((x `mod` (length highlights))) highlights)]
  where
    highlights :: [Highlight]
    highlights = [ Foreground Red, Foreground Green, Foreground Cyan
                 , Foreground Magenta, Foreground Yellow ]

-- A central function.  Convert an SExpression to a Document using
-- the pretty-printing combinators.  In sugar mode, lists are shown
-- with [,,,] sugar, rather than in full with cons applications.
-- Colour mode = use coloured parentheses to highlight expr depth
sExp2Doc :: (Show a, Eq a) => Bool -> Bool -> Bool -> (a->Doc->Doc) -> SExp a -> Doc
sExp2Doc colour sugar qual high = goDoc (SInfix (-1)) ARight 
  where
  -- fixity of surrounding expression and which sort of argument
--goDoc :: SFixity -> ArgPos -> SExp a -> Doc
  goDoc surFixity aPos (SParens va item d) =
    if colour then
      (highlight (highlightForDepth d) (text "(")) <>
        (sExp2Doc colour sugar qual high item) <>
        (highlight (highlightForDepth d) (text ")"))
    else
      text "(" <>
      (sExp2Doc colour sugar qual high item) <>
      text ")"
  goDoc surFixity aPos (SInfinite va) =
    text "..."
  
  -- Tuples
  goDoc surFixity aPos (SApp va ((SId vt (Qualified _ (',':xs)) _):args)) =
    if length xs + 2 == length args 
    then high va $
         group (text "("
                 <> foldr1 comma (map (sExp2Doc colour sugar qual high) args)
               ) <> text ")"
           -- print tuple properly
    else high va $
         optParens surFixity aPos ownFixity
         . (high vt (text ("(,"++xs++")")) <+>)
         . foldr1 (<+>) . map (goDoc ownFixity ARight) $ args
           -- partial application of tuple constructor
    where
    ownFixity = SInfix 10
    comma l r = l <|> high vt (text ",") <> r
  -- Lists
  goDoc surFixity
             aPos
             (e@(SApp va [SId vf (Qualified _ ":") ownFixity,e1,e2]))
    | sugar && not (ambiguous e2)
      = high va $ listDoc qual high surFixity aPos e
    | otherwise
      = high va $ optParens surFixity aPos ownFixity
                            (goDoc ownFixity ALeft e1
                            <> high vf (text ":")
                            <> goDoc ownFixity ARight e2)
        where -- check whether the final tail of the list is _ or {?} or {^C}.
          ambiguous :: SExp a -> Bool
          ambiguous (SApp _ [SId _ (Qualified _ ":") _, e1, e2]) = ambiguous e2
          ambiguous (SCycle _ _ e) = ambiguous e
          ambiguous (SId _ (Qualified _ "{IO}") _) = True
          ambiguous (SId _ _ _) = False
          ambiguous (SCut _)            = False
          ambiguous (SLiteral _ "{?}")  = True
          ambiguous (SUnevaluated _)    = True
          ambiguous (SInterrupted _)    = True
          ambiguous (SInfinite _)       = True
          ambiguous (SBottom _)         = True
          ambiguous _                   = True	-- shouldn't happen!
  -- Sets
  goDoc surFixity aPos ((SApp va [SId vf (Qualified "Data.Set" "MkSet") ownFixity,s]))
    = high va $ setDoc qual high surFixity aPos s
  -- Finite Maps
  goDoc surFixity aPos e@(SId va (Qualified "Data.FiniteMap" "EmptyFM") ownFixity)
    = high va $ text "{}"
  goDoc surFixity aPos e@((SApp va [SId vf (Qualified "Data.FiniteMap" "Branch") ownFixity,k,el,s,l,r]))
    = high va $ fmDoc qual high surFixity aPos e
  
  goDoc surFixity aPos (SApp va [SId vf var ownFixity,e1,e2]) 
    | considerAsOperator var ownFixity = 
      high va $
      optParens surFixity aPos ownFixity
        (goDoc ownFixity ALeft e1
        <+> high vf (opDoc qual var)
        <+> goDoc ownFixity ARight e2)
  goDoc surFixity aPos (SApp va [SId vf var ownFixity,e]) 
    | considerAsOperator var ownFixity =
      -- show infix operator with single argument as section
      groupNest indentation . high va . parens $
        goDoc ownFixity ALeft e <-> high vf (opDoc qual var)
  goDoc surFixity aPos (SApp va (fun:args)) =
    high va
    . optParens surFixity aPos ownFixity
    . (goDoc ownFixity ALeft fun <+>)
    .  foldr1 (<+>) . map (goDoc ownFixity ARight) $ args
    where
    ownFixity = SInfix 10
  goDoc surFixity aPos (SDoStmt va m var par) =
    high va $
    bold (high m (text "do")) <+> goDoc surFixity ARight var
    <+> bold (text "<-") <+> goDoc surFixity ARight par
  goDoc _ _ (SLambda v) = high v $ text "(\\..)"
  goDoc _ _ (SId v var fixity) = high v $ funDoc qual var
  goDoc _ _ (SString v s d) = high v $ text "\"" <> text s <>
                              (if d
                               then highlight [Foreground Blue] (text "...")
                               else nil) <>
                              text "\""
  goDoc _ _ (SLiteral v lit) = high v $ text lit
  goDoc _ _ (SWithin v es) = 
    high v $ foldr1 (\a b-> a <+> group (bold (text "|") <+> b))
                    (map (sExp2Doc colour sugar qual high) es)
  goDoc _ _ (SIf v exp@(SId vl (Qualified "Prelude" "False") fix) (Just res)) =
    (groupNest indentation . high v) (bold (text "if")
                                     <+> sExp2Doc colour sugar qual high exp
                                     <+> bold (text "else")
                                     <+> sExp2Doc colour sugar qual high res)
  goDoc _ _ (SIf v exp@(SId vl (Qualified "Prelude" "True") fix) (Just res)) =
    (groupNest indentation . high v) (bold (text "if")
                                     <+> sExp2Doc colour sugar qual high exp
                                     <+> bold (text "then")
                                     <+> sExp2Doc colour sugar qual high res)
  goDoc _ _ (SIf v exp Nothing) = 
    (groupNest indentation . high v) (bold (text "if")
                                     <+> sExp2Doc colour sugar qual high exp)
  goDoc _ _ (SCase v exp@(SId vl (Qualified "Prelude" "False") fix)
                     (Just res@(SCase _ _ _))) =
    (groupNest indentation . high v) (bold (text "case")
                                     <+> sExp2Doc colour sugar qual high exp
                                     <+> bold (text "->")
                                     <+> sExp2Doc colour sugar qual high res)
  goDoc _ _ (SCase v exp@(SId vl (Qualified "Prelude" "False") fix)
                     (Just res)) =
    (groupNest indentation . high v) (bold (text "case")
                                     <+> sExp2Doc colour sugar qual high exp
                                     <+> bold (text "otherwise")
                                     <+> sExp2Doc colour sugar qual high res)
  goDoc _ _ (SCase v exp@(SId vl (Qualified "Prelude" "True") fix)
                     (Just res)) =
    (groupNest indentation . high v) (bold (text "case")
                                     <+> sExp2Doc colour sugar qual high exp
                                     <+> bold (text "then")
                                     <+> sExp2Doc colour sugar qual high res)
  goDoc _ _ (SCase v exp Nothing) = 
    (groupNest indentation . high v) (bold (text "case")
                                     <+> sExp2Doc colour sugar qual high exp)
  goDoc _ _ (SGuard v exp@(SId vl (Qualified "Prelude" "False") fix)
                      (Just res)) =
    (groupNest indentation . high v) (bold (text "|")
                                     <+> sExp2Doc colour sugar qual high exp
                                     <+> bold (text "->")
                                     <+> sExp2Doc colour sugar qual high res)
  goDoc _ _ (SGuard v exp@(SId vl (Qualified "Prelude" "True") fix)
                      (Just res)) =
    (groupNest indentation . high v) (bold (text "|")
                                     <+> sExp2Doc colour sugar qual high exp
                                     <+> bold (text "=")
                                     <+> sExp2Doc colour sugar qual high res)
  goDoc _ _ (SGuard v exp Nothing) = 
    (groupNest indentation . high v) (bold (text "|")
                                     <+> sExp2Doc colour sugar qual high exp)
  goDoc _ _ (SCut v) = high v $ highlight [ReverseVideo] (text " ")
  goDoc _ _ (SUnevaluated v) = high v $ text "_"
  goDoc _ _ (SBottom v) = high v $ text "_|_"
  goDoc _ _ (SInterrupted v) =
      highlight [Foreground Blue] $ high v $ text "{^C}"
  goDoc _ _ (SCycle v var exp) = 
    groupNest indentation . high v . parens $
      text var <+> group (bold (text "where" <+> text var <+> text "=")
                          <+> sExp2Doc colour sugar qual high exp)
  goDoc _ _ (SEquation v lhs rhs) =
    group (high v (sExp2Doc colour sugar qual high lhs
                  <+> text "=" <+> sExp2Doc colour sugar qual high rhs))
  goDoc _ _ (SFieldExpr v e labs upds) =
    high v $ group (sExp2Doc colour sugar qual high e
                    <> text "{" <> commas (zipWith field labs upds)
                    <> text "}")
    where
      field name value = text name <> text "="
                         <> sExp2Doc colour sugar qual high value
      commas [] = Pretty.nil
      commas [doc] = doc
      commas (doc:docs) = doc <> text "," <+> commas docs
  goDoc _ _ (SFiniteMap v maps) =
    group (text "{" <> commas (reZipWith mapping maps) <> text "}")
    where 
      mapping args res = foldr1 (<+>)
                                (map (sExp2Doc colour sugar qual high) args)
                         <> text "->"
                         <> sExp2Doc colour sugar qual high res
      commas [] = Pretty.nil
      commas [doc] = doc
      commas (doc:docs) = doc <> text "," <+> commas docs
      reZipWith f [] = []
      reZipWith f ((x,y):rs) = f x y:reZipWith f rs
  goDoc _ _ x = text (show x)


{- Pretty-print an expression with no interior highlighting -}
prettyExpression :: String -> Int -> Options -> FileNode -> String
prettyExpression initial width
                 Options { cutoffDepth=cutoff, unevalMode=uneval
                         , stringSugar=strSugar, listSugar=listSugar
                         , showQual=qual }
                 node =
  pretty width
         (highlight [Foreground Blue] (text initial)
         <> groupNest (length initial)
                      (sExp2Doc False listSugar qual nohigh
                                (fileNode2SExp cutoff uneval
                                               strSugar False ("",node))))
  where nohigh _ doc = doc

prettySExp :: (Show a, Eq a) => String -> Int -> Options -> SExp a -> String
prettySExp initial width
           Options { cutoffDepth=cutoff, unevalMode=uneval
                   , stringSugar=strSugar, listSugar=listSugar
                   , showQual=qual, colourBracks=colouringOn}
           exp =
  pretty width
         (highlight [Foreground Blue] (text initial)
         <> groupNest (length initial)
                      (sExp2Doc colouringOn listSugar qual nohigh exp))
  where nohigh _ doc = doc

{- Pretty-print an equation with no interior highlighting -}
prettyEquation :: String -> String -> Int -> Options -> FileNode -> String
prettyEquation initial final width
               Options { cutoffDepth=cutoff, unevalMode=uneval
                       , stringSugar=strSugar, listSugar=listSugar
                       , showQual=qual }
               node =
  pretty width
         ( highlight [Foreground Blue] (text initial)
         <> groupNest (length initial)
                      ( sExp2Doc False listSugar qual nohigh
                                 (fileNode2SExp cutoff uneval strSugar True
                                                ("",node))
                        <-> text "=" <->
                        sExp2Doc False listSugar qual nohigh
                                 (fileNode2SExp cutoff uneval strSugar False
                                                ("",getResult node True)))
         <> (if null final then nil
             else delimiter "  " <> highlight [Foreground Blue] (text final)))
  where nohigh _ doc = doc

{- Pretty-print an equation with no interior highlighting -}
-- a bit of a quick hack for hat-source
-- all in one line and allows highlighting of whole equation
prettyEquation2 :: Options -> FileNode -> String
prettyEquation2 
               Options { cutoffDepth=cutoff, unevalMode=uneval
                       , stringSugar=strSugar, listSugar=listSugar
                       , showQual=qual }
               node = 
  pretty 10000
           (group
                      ( sExp2Doc False listSugar qual nohigh
                                 (fileNode2SExp cutoff uneval strSugar True
                                                ("",node))
                        <-> text "=" <->
                        sExp2Doc False listSugar qual nohigh
                                 (fileNode2SExp cutoff uneval strSugar False
                                                ("",getResult node True))))
  where nohigh _ doc = doc


{-
-- only for testing:
test1 =
  SApp (mkHatNode 0)
    [ SId (mkHatNode 1) "fun" SInfixDefault
    , SLiteral (mkHatNode 2) "24"
    , SLiteral (mkHatNode 3) "True"
    , SApp (mkHatNode 4)
        [ SId (mkHatNode 5) "+" (SInfixL 5)
        , SLiteral (mkHatNode 6) "3"
        , SLiteral (mkHatNode 7) "4"
        ]
    ]

test2 =
  SApp (mkHatNode 10)
    [ SId (mkHatNode 11) "*" (SInfixL 6)
    , SApp (mkHatNode 12)
        [ SId (mkHatNode 13) "+" (SInfixL 5)
        , SApp (mkHatNode 14)
            [ SId (mkHatNode 15) "-" (SInfixL 5)
            , SLiteral (mkHatNode 16) "3"
            , SLiteral (mkHatNode 17) "6"
            ]
        , SApp (mkHatNode 18)
            [ SId (mkHatNode 19) "-" (SInfixL 5)
            , SLiteral (mkHatNode 20) "3"
            , SLiteral (mkHatNode 21) "6"
            ]
        ]
    , test1
    ]
-}