packages feed

she-0.0: src/HaLay.lhs

> {-# LANGUAGE TypeSynonymInstances #-}

> module HaLay where

> import Control.Arrow
> import Control.Applicative
> import Data.Char
> import Data.List
> import Data.Traversable
> import Control.Monad
> import Control.Monad.State
> import Debug.Trace

> import Parsley

------------------------------------------------------------------------------
Glom a file like this
------------------------------------------------------------------------------

> ready :: String -> [[Tok]]
> ready = map (munge exTyMu) . fst . getLines (Seek NoLay "") [] .
>         tokenize . (,) 0

------------------------------------------------------------------------------
Stage 1 : lexing
------------------------------------------------------------------------------

> tokenize :: (Int, String) -> [(Int, Tok)]
> tokenize = unfoldr (runStateT ((,) <$> gets fst <*> tokIn))

> data Tok
>   = Lit String
>   | Ope BK
>   | Clo BK
>   | Uid String
>   | Lid String
>   | KW String
>   | Sym String
>   | Semi
>   | Spc String
>   | Com String
>   | Urk Char
>   | NL
>   | B BK [Tok]          -- a bracket
>   | L String [[Tok]]    -- some layout
>   | T Tag [Tok]         -- a tagged region
>   deriving (Show, Eq)

> tokOut :: Tok -> String
> tokOut t = case t of
>   Lit s -> s
>   Ope b -> ope b
>   Clo b -> clo b
>   Uid s -> s
>   Lid s -> s
>   KW  s -> s
>   Sym s -> s
>   Semi  -> ";"
>   Spc s -> s
>   Com s -> s
>   Urk c -> [c]
>   NL    -> "\n"
>   B b ts -> ope b ++ toksOut ts ++ clo b
>   L s tss -> s ++ tokssOut tss
>   T _ ts -> toksOut ts

> toksOut :: [Tok] -> String
> toksOut ts = ts >>= tokOut
> tokssOut :: [[Tok]] -> String
> tokssOut tss = tss >>= toksOut

> isSpcT :: Tok -> Bool
> isSpcT (Spc _) = True
> isSpcT NL      = True
> isSpcT (Com _) = True
> isSpcT _       = False

> tokIn :: L Tok
> tokIn = Lit <$> ((:) <$> ch '\"' <*> slit)
>     <|> Com <$ stol <*> ((++) <$> traverse ch "#" <*>  spa (not . isNL))
>     <|> Sym <$>  sym
>     <|> Com <$> ((++) <$> traverse ch "--" <*>  spa (not . isNL))
>     <|> Com <$> ((++) <$> traverse ch "{-" <*>  comment 1)
>     <|> Ope Rnd <$ ch '('
>     <|> Clo Rnd <$ ch ')'
>     <|> Ope Sqr <$ ch '['
>     <|> Clo Sqr <$ ch ']'
>     <|> Ope Crl <$ ch '{'
>     <|> Clo Crl <$ ch '}'
>     <|> Semi <$ ch ';'
>     <|> Lit <$> ((:) <$> chk isDigit cha <*> spa isDigit)  -- sod FP now
>     <|> Uid <$> ((:) <$> chk isUpper cha <*> spa isIddy)
>     <|> klid <$> ((:) <$> chk isLower cha <*> spa isIddy)
>     <|> Spc  <$> ((:) <$> chk isHSpace cha <*> spa isHSpace)
>     <|> NL <$ chk isNL cha
>     <|> Urk <$> cha
>  where
>    slit = (:) <$> ch '\\' <*> ((:) <$> cha <*> slit)
>      <|> return <$> ch '\"'
>      <|> (:) <$> cha <*> slit
>    klid s = if elem s keywords then KW s else Lid s
>    comment 0 = pure ""
>    comment i = (++) <$> traverse ch "{-" <*> comment (i + 1)
>            <|> (++) <$> traverse ch "-}" <*> comment (i - 1)
>            <|> (++) <$> ((:) <$> ch '\"' <*> slit) <*> comment i
>            <|> (:) <$> cha <*> comment i

------------------------------------------------------------------------
Stage 2 : Group according to brackets and layout rules
------------------------------------------------------------------------

> data ChunkMode
>   = Lay String Int
>   | Bra BK
>   | NoLay
>   deriving (Show, Eq)

> getChunks :: ChunkMode -> [Tok] -> [(Int, Tok)] -> ([Tok], [(Int, Tok)])
> getChunks _ acc [] = (reverse acc, [])
> getChunks m acc its@((i, t) : its') = case (m, t) of
>   _ | isSpcT t -> getChunks m (t : acc) its'
>   (Lay _ j, _) | not (null acc) && i <= j -> (reverse acc, its)
>   (Lay _ _, Semi) -> (reverse (t : acc), its')
>   (Lay k _, KW e) | elem (k, e) layDKillaz -> (reverse acc, its)
>   (Lay _ _, Clo _) -> (reverse acc, its)
>   (Bra b, Clo b') | b == b' -> (reverse acc, its')
>   (m, Ope b) -> case getChunks (Bra b) [] its' of
>     (cs, its) -> getChunks m (B b cs : acc) its
>   (m, KW e) | elem e lakeys -> case getLines (Seek m e) [] its' of
>     (css, its) -> getChunks m ((L e css) : acc) its
>   _ -> getChunks m (t : acc) its'

> data LineMode
>   = Bracing
>   | Seek ChunkMode String
>   | Edge String Int
>   deriving (Show, Eq)

> getLines :: LineMode -> [[Tok]] -> [(Int, Tok)] -> ([[Tok]], [(Int, Tok)])
> getLines _ acc [] = (reverse acc, [])
> getLines m acc ((_, s) : its) | isSpcT s = eat [s] its where
>   eat sacc ((_, s) : its) | isSpcT s = eat (s : sacc) its
>   eat sacc its = getLines m (reverse (splendid (reverse sacc)) ++ acc) its
> getLines m acc its@((i, t) : its') = case (m, t) of
>   (Edge k j, _)
>     | i == j -> case getChunks (Lay k i) [] its of
>       ([], its) -> (reverse acc, its)
>       (cs, its) -> getLines (Edge k i) (reverse (splendid cs) ++ acc) its
>     | otherwise -> (reverse acc, its)
>   (Seek m s, Ope Crl) -> getLines Bracing ([Ope Crl] : acc) its'
>   (Seek (Lay k j) s, _)
>     | j < i -> getLines (Edge s i) acc its
>     | otherwise -> (reverse acc, its)
>   (Seek NoLay s, _) -> getLines (Edge s i) acc its
>   (Bracing, Clo Crl) -> (reverse ([Clo Crl] : acc), its')
>   (Bracing, _) -> case getChunks NoLay [] its of
>       ([], its) -> (reverse acc, its)
>       (cs, its) -> getLines Bracing (cs : acc) its

> layDKillaz :: [(String, String)]
> layDKillaz = [("of", "where"), ("do", "where"), ("let", "in")]

> splendid :: [Tok] -> [[Tok]]
> splendid [] = [[]]
> splendid (NL : ts) = case splendid ts of
>   (ss : sss) | all isSpcT ss  -> [] : (NL : ss) : sss
>              | otherwise      -> (NL : ss) : sss
> splendid (t : ts) = case splendid ts of
>   (us : sss) -> (t : us) : sss

------------------------------------------------------------------------------
Stage 3 : tag regions of interest
------------------------------------------------------------------------------

> data Tag = Ty | Ki | Ex deriving (Show, Eq)

> tender :: Tok -> Bool
> tender (L _ _ ) = True
> tender t = elem t [Semi, Sym "=", Sym ",", Sym "|", KW "in"]

> exTyMu :: [Tok] -> Maybe [Tok]
> exTyMu (t : ts)
>   | elem t [Sym "::", KW "class", KW "instance"]
>   = Just $ t : u : munge exTyMu vs
>   | elem t [KW "data", KW "newtype"]
>   = Just $ case vs of
>       Sym "=" : vs -> t : u : Sym "=" : [T Ty vs]  -- dodgy or what?
>       _ -> t : u : munge exTyMu vs
>   | elem t [KW "type"]
>   = Just $ case vs of
>       Sym "=" : vs -> t : u : Sym "=" : [T Ty (munge tyMu vs)]
>       _ -> t : u : munge exTyMu vs
>   where
>     (u, vs) = first (T Ty . munge tyMu) (span (not . tender) ts)
> exTyMu _ = Nothing

> tyMu :: [Tok] -> Maybe [Tok]
> tyMu (Sym "::" : ts) = Just $ Sym "::" : u : munge tyMu vs
>   where
>     (u, vs) = first (T Ki . munge kiMu) (span (not . tender) ts)
> tyMu (B Crl us : ts) = Just $
>   B Crl [T Ex (munge exTyMu us)] : munge tyMu ts
> tyMu _ = Nothing

> kiMu :: [Tok] -> Maybe [Tok]
> kiMu (B Crl us : ts) = Just $
>   B Crl [T Ty (munge tyMu us)] : munge kiMu ts
> kiMu (KW "forall" : ts) = case span (/= Sym ".") ts of
>   (ts, us) -> Just $ KW "forall" : T Ty (munge tyMu ts) : munge kiMu us
> kiMu _ = Nothing


------------------------------------------------------------------------------
Parsley for layout
------------------------------------------------------------------------------

> spc :: P Tok ()
> spc = () <$ many (tok isSpcT)

> uid :: P Tok String
> uid = grok h next where
>   h (Uid s) = Just s
>   h _ = Nothing

> lid :: P Tok String
> lid = grok h next where
>   h (Lid s) = Just s
>   h _ = Nothing

> infC :: P Tok String
> infC = grok h next where
>   h (Sym (':' : s)) = Just (':' : s)
>   h _ = Nothing

> pBr :: BK -> P Tok x -> P Tok x
> pBr k p = grok pb next where
>   pb (B j cs) | k == j = parse p cs
>   pb _ = Nothing

> pLay :: String -> P [Tok] x -> P Tok x
> pLay k p = grok pb next where
>   pb (L j tss) | k == j = parse p tss
>   pb _ = Nothing

> pTag :: Tag -> P Tok x -> P Tok x
> pTag t p = grok pb next where
>   pb (T u ts) | t == u = parse p ts
>   pb _ = Nothing

------------------------------------------------------------------------------
Mungers
------------------------------------------------------------------------------

> munge :: ([Tok] -> Maybe [Tok]) -> [Tok] -> [Tok]
> munge m ts = case m ts of
>   Just us -> us
>   Nothing -> case ts of
>     [] -> []
>     (B b ss : ts) -> B b (munge m ss) : munge m ts
>     (L k sss : ts) -> L k (map (munge m) sss) : munge m ts
>     (T t ss : ts) -> T t (munge m ss) : munge m ts
>     (t : ts) -> t : munge m ts

> mungeLines :: ([[Tok]] -> Maybe [[Tok]]) -> ([Tok] -> Maybe [Tok]) ->
>               [[Tok]] -> [[Tok]]
> mungeLines ms m tss = case ms tss of
>     Just uss -> uss
>     Nothing -> case tss of
>       [] -> []
>       (ts : tss) -> munge help ts : mungeLines ms m tss
>   where
>     help ts = m ts <|>
>       case ts of
>         (L k sss : ts) -> Just (L k (mungeLines ms m sss) : munge help ts)
>         _ -> Nothing

> dashOut :: [Tok] -> [Tok]
> dashOut ts = [Com ("-- " ++ easy ts)] where
>   easy [] = []
>   easy (B _ _ : _) = []
>   easy (L _ _ : _) = []
>   easy (t : ts) = tokOut t ++ easy ts

> dental :: [[Tok]] -> [Tok]
> dental [] = [NL]
> dental (l@(NL : _) : (c : _) : _) | not (isSpcT c) = l
> dental (l : ls) = dental ls

> redent :: [Tok] -> [[Tok]] -> [[Tok]]
> redent nl ((NL : _) : tss) = redent nl tss
> redent nl (ts : tss) = nl : ts : redent nl tss
> redent nl [] = []

> preamble :: [[Tok]] -> [[Tok]] -> [[Tok]]
> preamble ls [] = ls
> preamble ls ms@(l@(NL : _) : (c : _) : _) | not (isSpcT c) =
>   redent l ls ++ ms
> preamble ls (m : ms) = m : preamble ls ms


------------------------------------------------------------------------------
Classifiers, odds and ends
------------------------------------------------------------------------------

> isNL :: Char -> Bool
> isNL b = elem b "\r\n"

> isHSpace :: Char -> Bool
> isHSpace c = isSpace c && not (isNL c)

> isIddy :: Char -> Bool
> isIddy b = isAlphaNum b || elem b "_'"

> isInfy :: Char -> Bool
> isInfy b = elem b "!#$%&*+-.:/<=>?@\\^|~"

> data BK = Rnd | Sqr | Crl deriving (Show, Eq)

> ope :: BK -> String
> ope Rnd = "("
> ope Sqr = "["
> ope Crl = "{"

> clo :: BK -> String
> clo Rnd = ")"
> clo Sqr = "]"
> clo Crl = "}"

> keywords :: [String]
> keywords = ["module", "import", "type", "data", "newtype", "pattern", "kind",
>             "let", "in", "case", "of", "do", "forall", "class", "instance",
>             "family", "where", "if", "then", "else"]

> lakeys :: [String]
> lakeys = ["let", "of", "do", "where"]

> width :: [Tok] -> Int
> width ts = case span (/= NL) ts of
>   (ts, []) -> length (ts >>= tokOut)
>   (_, NL : ts) -> width ts

------------------------------------------------------------------------
The lexer monad
------------------------------------------------------------------------

> type L = StateT (Int, String) Maybe

> instance Alternative L where
>   empty = StateT $ \ is -> empty
>   p <|> q = StateT $ \ is -> runStateT p is <|> runStateT q is

> instance Applicative L where
>   pure = return
>   (<*>) = ap

> cha :: L Char
> cha = StateT moo where
>   moo (i, []) = Nothing
>   moo (i, c : s) | isNL c = Just (c, (0, s))
>                  | c == '\t'
>                  = if mod i 8 == 7 then Just (' ', (i + 1, s))
>                                    else Just (' ', (i + 1, c : s))
>                  | otherwise = Just (c, (i + 1, s))

> stol :: L ()
> stol = do
>   i <- gets fst
>   guard (i == 0)

> chk :: (t -> Bool) -> L t -> L t
> chk p l = do t <- l ; if p t then return t else empty

> ch :: Char -> L Char
> ch c = chk (== c) cha

> spa :: (Char -> Bool) -> L String
> spa p = (:) <$> chk p cha <*> spa p  <|> pure []

> sym :: L String
> sym = StateT $ \ (i, s) -> case h s of
>   ("", _) -> Nothing
>   ("--", _) -> Nothing
>   (s, t) -> Just (s, (i + length s, t))
>  where
>   h (s@('-':'}':_)) = ("", s)
>   h (c : s) | isInfy c = first (c :) (h s)
>   h s = ([], s)