packages feed

KiCS-0.8.9: src/lib/Curry/Module/PrettyFlat.curry

module PrettyFlat where

import Char
import Maybe
import Pretty
import FlatCurry
import FlatCurryGoodies
import StyledText
import System

prelude = "Prelude"

arrow = operator (text "->")
bar = operator (char '|')
dcolon = operator (text "::")

type Precs = [(QName,Int)]

precFillEncloseSep :: Int -> Int -> Doc -> Doc -> Doc -> [Doc] -> Doc
precFillEncloseSep p1 p2 l r s ds =
  fillEncloseSep (pre p1 p2 l) (pre p1 p2 r) s ds
 where
  pre pO pI br
    | pO<pI = empty
    | True  = br

isInfixName :: QName -> Bool
isInfixName (_,n) = all (`elem` infixIDs) n

infixIDs :: String
infixIDs =  "~!@#$%^&*+-=<>?./|\\:"

isTupleName :: QName -> Bool
isTupleName (mod,name) = mod == prelude && elem (take 2 name) ["()","(,"]

showStyledProg :: Prog -> String
showStyledProg = prettyProg 78

prettyProg :: Int -> Prog -> String
prettyProg n = pretty n . progDoc . updProgExps elimApp

prettyTypeExpr :: String -> TypeExpr -> String
prettyTypeExpr mod = pretty 78 . typeExprDoc mod False

prettyTypes :: String -> [TypeDecl] -> String
prettyTypes mod = pretty 78 . typesDoc mod

prettyOps :: [OpDecl] -> String
prettyOps = pretty 78 . opsDoc

showProg :: Prog -> String
showProg = plainText . showStyledProg

printStyledProg :: String -> IO ()
printStyledProg f = readFlatCurry f >>= printStyledText . showStyledProg

mainPrint :: IO ()
mainPrint = getArgs >>= mapIO_ printProg

printProg :: String -> IO ()
printProg f = readFlatCurryFile f >>= putStrLn . showProg

-- viewStyledProg :: WidgetRef -> Prog -> GuiPort -> IO ()
-- viewStyledProg ref prog = setStyledText ref (showStyledProg prog)

--viewProg :: WidgetRef -> Prog -> GuiPort -> IO ()
--viewProg ref prog = setValue ref (showProg prog)

keyword, consname :: String -> Doc
keyword = magentaDoc . text
consname = greenDoc . text

operator, literal, marked :: Doc -> Doc
operator = blueDoc
literal = cyanDoc
marked = bgYellowDoc . boldDoc

block :: Doc -> Doc
block = group . hang 1

def :: Doc -> [Int] -> Doc -> Doc
def name params body = block (name <> paramDoc <$> body)
 where
  paramDoc = if null params then empty 
              else space <> align (fillSep (map varDoc params))
{-
fillMulti :: (Doc -> Doc) -> (Doc -> Doc) -> [Doc] -> Doc
fillMulti _ _ [] = empty
fillMulti x y (d:ds)
  = align (fillSep ((x d) : map (group . (linebreak<>) . y) ds))
-}
app :: Doc -> [Doc] -> Doc
app d ds = if null ds then d
            else block (fillEncloseSep empty empty space (d:ds))
            --else block (d <$> fillEncloseSep empty empty space ds)
            --else block (d <$> fillMulti (empty<>) (empty<>) ds)

layout :: [Doc] -> Doc
layout = align . compose (combine (linesep "; "))

qname :: String -> QName -> Doc
qname prog mn@(mod,name)
  | mn == (prelude,"[]") || isTupleName mn = operator (text name)
  | isInfixName mn = 
      if mod == prog -- True
       then parens (operator (text name))
       else parens (operator (consname mod <> dot <> (txt name)))
  | otherwise -- do not show module names for Haskell generation
    = if -- True 
         -- mod `elem` [prelude,prog]
         mod == prog
       then txt (correctName name) 
       else consname mod <> dot <> (txt name)
 where
  txt (n:ame) = if isUpper n then consname (n:ame) else text (n:ame)

correctName :: String -> String
correctName name = 
  let name' = filter (not . flip elem ['#','.']) name
  in
  case name' of 
       ('_':xs) -> xs
       _        -> name'

d1 <$>> d2 = d1 <$> line <> d2

progDoc :: Prog -> Doc
progDoc prog@(Prog name imps types funcs ops)
  = moduleHeaderDoc name (exportedNames name prog) <$>>
    impsDoc imps <$>> opsDoc ops <$>>
    typesDoc name types <$>>
    funcsDoc (precs ops) name funcs

precs :: [OpDecl] -> Precs
precs = map (\(Op name _ i) -> (name,i))

exportedNames :: String -> Prog -> [Doc]
exportedNames mod (Prog _ _ types funcs _)
  = map typeExpDoc (filter ((Public==) . typeVisibility) types)
 ++ map (qname mod . funcName) (filter ((Public==) . funcVisibility) funcs)
 where
  typeExpDoc tdecl =
    let ecs = filter ((Public==) . consVisibility)
                     (trType (\_ _ _ cs -> cs) (\_ _ _ _ -> []) tdecl)
     in qname mod (typeName tdecl) <> if null ecs then empty else text "(..)"

moduleHeaderDoc :: String -> [Doc] -> Doc
moduleHeaderDoc name exports
  = keyword "module" <+> consname name <+> exportsDoc exports <+>
    keyword "where"

exportsDoc :: [Doc] -> Doc
exportsDoc xs
  = group (nest 1 (lparen <$> align (fillSep (punctuate comma xs)) <$> rparen))

impsDoc :: [String] -> Doc
impsDoc imps = vcat (map ((keyword "import" <+>) . consname) imps)

opsDoc :: [OpDecl] -> Doc
opsDoc ops = vcat (map opDoc ops)

opDoc :: OpDecl -> Doc
opDoc (Op n@(_,name) fix prec)
  = keyword "infix" <> fixDoc fix <+> int prec <+> operator (text infname)
 where
  infname = if isInfixName n then name else '`':name++"`"
  fixDoc InfixOp = empty
  fixDoc InfixlOp = keyword "l"
  fixDoc InfixrOp = keyword "r"

typesDoc :: String -> [TypeDecl] -> Doc
typesDoc mod = vcat . map (typeDoc mod)

typeDoc :: String -> TypeDecl -> Doc
typeDoc mod (Type name _ params cs)
  = def (keyword "data" <+> qname mod name) params (consDeclsDoc mod cs)

typeDoc mod (TypeSyn name _ params syn)
  = def (keyword "type" <+> qname mod name) params
        (operator equals <+> typeExprDoc mod False syn)

varDoc :: Int -> Doc
varDoc = text . ('x':) . show

tvarDoc :: Int -> Doc
tvarDoc i
  | i>25      = text ("x" ++ show i)
  | otherwise = text [chr (97+i)]

consDeclsDoc :: String -> [ConsDecl] -> Doc
consDeclsDoc mod
  = fillEncloseSep (operator equals<>space) empty (bar<>space)
  . map ((<>space) . consDeclDoc mod)
  --= fillMulti (equals<+>) (bar<+>) . map (consDeclDoc mod)

consDeclDoc :: String -> ConsDecl -> Doc
consDeclDoc mod (Cons name _ _ args)
  = app (qname mod name) (map (typeExprDoc mod True) args)

typeExprDoc :: String -> Bool -> TypeExpr -> Doc
typeExprDoc _ _ (TVar n) = tvarDoc n

typeExprDoc mod br (TCons name args)
  | null args = qname mod name
  | name == (prelude,"[]") = brackets (typeExprDoc mod False (head args))
  | isTupleName name = tupled (map (typeExprDoc mod False) args)
  | otherwise 
    = par br $ app (qname mod name) (map (typeExprDoc mod True) args)

typeExprDoc mod br typ@(FuncType _ _)
  = par br $ fillEncloseSep empty empty (space<>arrow<>space)
              --fillMulti (empty<>) (arrow<+>)
     (map (typeExprDoc mod True) (argTypes typ) ++ 
      [typeExprDoc mod False (resultType typ)])

par br = if br then parens else id

funcsDoc :: Precs -> String -> [FuncDecl] -> Doc
funcsDoc pr mod funcs = vcat (punctuate line (map (funcDoc pr mod) funcs))

funcDoc :: Precs -> String -> FuncDecl -> Doc
funcDoc pr mod (Func name _ _ typ rule)
  = funcTypeDeclDoc mod name typ <$>
    ruleDoc pr mod name rule

funcTypeDeclDoc :: String -> QName -> TypeExpr -> Doc
funcTypeDeclDoc mod name typ
  = def (qname mod name) [] (funcTypeDoc mod (argTypes typ) (resultType typ))

funcTypeDoc :: String -> [TypeExpr] -> TypeExpr -> Doc
funcTypeDoc mod args res
  = fillEncloseSep (dcolon<>space) empty (arrow<>space)
  --= fillMulti (dcolon<+>) (arrow<+>)
     (map ((<>space) . typeExprDoc mod True) (args++[res]))

ruleDoc :: Precs -> String -> QName -> Rule -> Doc
ruleDoc pr mod name (Rule args body)
  = def (qname mod name) args (equals <+> align (expDoc pr 0 mod False body))

ruleDoc _ mod name (External _)
  = qname mod name <+> keyword "external" -- <+> string decl

expDoc,expDoc2 :: Precs -> Int -> String -> Bool -> Expr -> Doc
expDoc pr _ mod br exp =
  maybe (maybe (expDoc2 pr 0 mod br exp)
          (\l -> list (map (expDoc pr 0 mod False) l))
          (toList exp))
    (\s -> if null s then consname "[]" else literal (dquotes (text s)))
      (toString exp)

expDoc2 _ _ _ _ (Var n) = varDoc n
expDoc2 _ _ _ _ (Lit l) = litDoc l

expDoc2 pr p mod br (Comb ct name args)
  | ct == FuncCall && name == (prelude,"apply")
    = par br $ app (expDoc pr 0 mod True (args!!0)) 
                           [expDoc pr 0 mod True (args!!1)]
  | ct == ConsCall && isTupleName name
    = tupled (map (expDoc pr 0 mod False) args)
  | isInfixName name && length args == 2
    = align $ precFillEncloseSep pOp p lbr rbr empty
               [expDoc pr pOp mod True (args!!0) 
               ,space <> operator (text (snd name)) <> space
               ,expDoc pr pOp mod True (args!!1)]
--     = par br $ app (expDoc mod True (args!!0))
--                    [text (snd name), expDoc mod True (args!!1)]
  | otherwise
    = par (not (null args) && br) $
       app (qname mod name) (map (expDoc pr 0 mod True) args)
 where
  (lbr,rbr) = if br then (lparen,rparen) else (empty,empty)
  pOp = case lookup name pr of
             Just pr -> pr
             Nothing -> 0 

expDoc2 pr _ mod br (Let bs e)
  = par br $ hang 1 $ 
     keyword "let" <+> letBindsDoc pr mod bs <$> 
     keyword "in" <+> expDoc pr 0 mod False e

expDoc2 pr _ mod br (Free vs e)
--   | null vs = marked (expDoc mod br e)
--   | any (<0) vs = expDoc mod br e
--   | otherwise
    = par br $ hang 1 $ 
       keyword "let" <+> align (fillSep (punctuate comma (map varDoc vs))) <+>
       keyword "free" <$> keyword "in" <+> expDoc pr 0 mod False e

expDoc2 pr _ mod br (FlatCurry.Or e1 e2)
  = expDoc pr 0 mod br (Comb FuncCall (prelude,"?") [e1,e2])

expDoc2 pr _ mod br (Case ct e bs)
  = par br $ hang 1 $ 
     caseTypeDoc ct <> keyword "case" <+> align (expDoc pr 0 mod False e) 
       <+> keyword "of" <$> layout (map (branchDoc pr mod) bs)

branchDoc :: Precs -> String -> BranchExpr -> Doc
branchDoc pr mod (Branch pat e)
  = def (patternDoc mod pat <+> arrow) [] (align (expDoc pr 0 mod False e))

caseTypeDoc :: CaseType -> Doc
caseTypeDoc Rigid = empty
caseTypeDoc Flex  = empty -- literal $ text "{-f-}"

patternDoc :: String -> Pattern -> Doc
patternDoc mod (Pattern name args)
  | null args = qname mod name
  | isTupleName name = tupled (map varDoc args)
  | isInfixName name && length args == 2
    = varDoc (args!!0) <> operator (text (snd name)) <> varDoc (args!!1)
  {-| name == (prelude,":")
    = varDoc (args!!0) <> operator colon <> varDoc (args!!1)-}
  | otherwise = qname mod name <+> hsep (map varDoc args)

patternDoc _ (LPattern l) = litDoc l

letBindsDoc :: Precs -> String -> [(Int,Expr)] -> Doc
letBindsDoc pr mod = layout . map (letBindDoc pr mod)

letBindDoc :: Precs -> String -> (Int,Expr) -> Doc
letBindDoc pr mod (n,e) = 
  varDoc n <+> operator equals <+> expDoc pr 0 mod False e

litDoc :: Literal -> Doc
litDoc (Intc n) = literal (int n)
litDoc (Floatc x) = literal (float x)
litDoc (Charc c) = literal (squotes (text (quoteChar c)))

quoteChar c = maybe [c] id (lookup c specialChars)

-- more?
specialChars = [('\\',"\\\\"),('\n',"\\n"),('\r',"\\r"),('\t',"\\t")]

toString :: Expr -> Maybe String
toString exp
  = case exp of
      Comb ConsCall ("Prelude","[]") [] -> Just ""
      Comb ConsCall ("Prelude",":") [Lit (Charc c),cs] ->
        toString cs >>- Just . (quoteChar c++)
      _ -> Nothing

toList :: Expr -> Maybe [Expr]
toList exp
  = case exp of
      Comb ConsCall ("Prelude","[]") [] -> Just []
      Comb ConsCall ("Prelude",":") [x,xs] -> toList xs >>- Just . (x:)
      _ -> Nothing

-- introduces over-applications on purpose!
elimApp :: Expr -> Expr
elimApp = updCombs elim
 where
  elim ct name args
    | ct == FuncCall && name == (prelude,"apply") && isComb (head args) &&
      combName (head args) /= (prelude,"apply")
      = extend (head args) (args!!1)
    | otherwise = Comb ct name args
  extend (Comb ct name args) arg = Comb ct name (args++[arg])


-- testHtml name
--   = readFlatCurry name >>=
--     writeFile (name++".html") . showHtmlPage . HtmlPage name [] . (:[]) . 
--     styledHtml . showStyledProg

-- Apply x y = Comb FuncCall ("Prelude","apply") [x,y]

-- test e = pretty 78 $ expDoc "" False $ e