module Language.Clafer.Front.LayoutResolver where
import Control.Monad.State
import Data.Functor.Identity (Identity)
import Language.ClaferT
import Language.Clafer.Front.LexClafer
import Data.Maybe
data LayEnv = LayEnv {
level :: Int,
levels :: [Int],
input :: String,
output :: String,
brCtr :: Int
} deriving Show
type LastNl = (Int, Int)
type Position = Posn
data ExToken = NewLine LastNl | ExToken Token deriving Show
data LEnv = LEnv [Int] (Maybe LastNl)
getToken :: (Monad m) => ExToken -> ClaferT m Token
getToken (ExToken t) = return t
getToken (NewLine (x, y)) = throwErr $ ParseErr (ErrPos 0 fPos fPos) $ "LayoutResolver.getToken: Cannot get ExToken NewLine"
where
fPos = Pos (fromIntegral x) (fromIntegral y)
layoutOpen :: String
layoutOpen = "{"
layoutClose :: String
layoutClose = "}"
resolveLayout :: (Monad m) => [Token] -> ClaferT m [Token]
resolveLayout xs = addNewLines xs >>= (resolve (LEnv [1] Nothing)) >>= adjust
resolve :: (Monad m) => LEnv -> [ExToken] -> ClaferT m [Token]
resolve (LEnv st _) [] = return $ replicate (length st 1) dedent
resolve (LEnv st _) ((NewLine lastNl):ts) = resolve (LEnv st (Just lastNl)) ts
resolve env@(LEnv st lastNl) (t:ts)
| isJust lastNl && parLev > 0 = do
r <- resolve env ts
t' >>= return . (:r)
| isJust lastNl && newLev > head st = do
r <- resolve (LEnv (newLev : st) Nothing) ts
t' >>= return . (indent:) . (:r)
| isJust lastNl && newLev == head st = do
r <- resolve (LEnv st Nothing) ts
t' >>= return . (:r)
| isJust lastNl = do
r <- resolve (LEnv st' Nothing) ts
t' >>= return . (replicate (length st length st') dedent ++) . (:r)
| otherwise = do
r <- resolve env ts
t' >>= return . (:r)
where
t' = getToken t
(newLev, parLev) = fromJust lastNl
st' = dropWhile (newLev <) st
indent :: Token
indent = PT (Pn 0 0 0) (TS "{" $ fromJust $ tokenLookup "{")
dedent :: Token
dedent = PT (Pn 0 0 0) (TS "}" $ fromJust $ tokenLookup "}")
toToken :: ExToken -> [Token]
toToken (NewLine _) = []
toToken (ExToken t) = [t]
isExTokenIn :: [String] -> ExToken -> Bool
isExTokenIn l (ExToken t) = isTokenIn l t
isExTokenIn _ _ = False
isNewLine :: Token -> Token -> Bool
isNewLine t1 t2 = line t1 < line t2
incrGlobal :: (Monad m) => Position
-> Int
-> Token -> ClaferT m Token
incrGlobal (Pn _ l0 _) i (PT (Pn g l c) t) =
return $ if l /= l0 then PT (Pn (g + i) l c) t
else PT (Pn (g + i) l (c + i)) t
incrGlobal _ _ (Err (Pn z x y)) = do
env <- getEnv
let claferModel = lines $ unlines $ modelFrags env
throwErr $ ParseErr (ErrPos z fPos fPos) $ "Cannot add token at '" ++ (take y $ claferModel !! (x1)) ++ "'"
where
fPos = Pos (fromIntegral x) (fromIntegral y)
tokenLookup :: String -> Maybe Int
tokenLookup s = treeFind resWords
where
treeFind N = Nothing
treeFind (B a t left right) | s < a = treeFind left
| s > a = treeFind right
| s == a = tokenCode t
treeFind (B _ _ _ _) = error "LayoutResolver.treeFind should never happen"
tokenCode :: Tok -> Maybe Int
tokenCode (TS _ c) = Just c
tokenCode _ = Nothing
position :: Token -> Position
position t = case t of
PT p _ -> p
Err p -> p
line :: Token -> Int
line t = case position t of Pn _ l _ -> l
column :: Token -> Int
column t = case position t of Pn _ _ c -> c
isTokenIn :: [String] -> Token -> Bool
isTokenIn ts t = case t of
PT _ (TS r _) | elem r ts -> True
_ -> False
isLayoutOpen :: Token -> Bool
isLayoutOpen = isTokenIn [layoutOpen]
isBracketOpen :: Token -> Bool
isBracketOpen = isTokenIn ["["]
isLayoutClose :: Token -> Bool
isLayoutClose = isTokenIn [layoutClose]
isBracketClose :: Token -> Bool
isBracketClose = isTokenIn ["]"]
tokenLength :: Token -> Int
tokenLength t = length $ prToken t
addNewLines :: (Monad m) => [Token] -> ClaferT m [ExToken]
addNewLines [] = return []
addNewLines ts@(t:_) = addNewLines' (if isBracketOpen t then 1 else 0) ts
addNewLines' :: (Monad m) => Int -> [Token] -> ClaferT m [ExToken]
addNewLines' _ [] = return []
addNewLines' 0 (t:[]) = return [ExToken t]
addNewLines' 1 (t:[]) = return [ExToken t]
addNewLines' _ ((PT (Pn z x y) t):[]) = throwErr $ ParseErr (ErrPos z fPos fPos) $ "']' bracket missing for (" ++ show t ++ ")"
where
fPos = (Pos (fromIntegral x) (fromIntegral y))
addNewLines' n (t0:t1:ts)
| isNewLine t0 t1 && isBracketOpen t1 =
addNewLines' (n + 1) (t1:ts) >>= (return . (ExToken t0:) . (NewLine (column t1, n):))
| isNewLine t0 t1 && isBracketClose t1 =
addNewLines' (n 1) (t1:ts) >>= (return . (ExToken t0:) . (NewLine (column t1, n):))
| isLayoutOpen t1 || isBracketOpen t1 =
addNewLines' (n + 1) (t1:ts) >>= (return . (ExToken t0:))
| isLayoutClose t1 || isBracketClose t1 =
addNewLines' (n 1) (t1:ts) >>= (return . (ExToken t0:))
| isNewLine t0 t1 = addNewLines' n (t1:ts) >>= (return . (ExToken t0:) . (NewLine (column t1, n):))
| otherwise = addNewLines' n (t1:ts) >>= (return . (ExToken t0:))
addNewLines' _ tokens' = throwErr (ClaferErr ("[bug] LayoutResolver.addNewLines': invalid argument:" ++ show tokens') :: CErr Span)
adjust :: (Monad m) => [Token] -> ClaferT m [Token]
adjust [] = return []
adjust (t:[]) = return [t]
adjust (t:ts) = ((updToken (t:ts)) >>= adjust) >>= (return . (t:))
updToken :: (Monad m) => [Token] -> ClaferT m [Token]
updToken (t0:t1:ts)
| isLayoutOpen t1 || isLayoutClose t1 = addToken (nextPos t0) sym ts
| otherwise = return (t1:ts)
where
sym = if isLayoutOpen t1 then "{" else "}"
nextPos :: Token -> Position
nextPos t = Pn (g + s) l (c + s + 1)
where Pn g l c = position t
s = tokenLength t
updToken [] = return []
updToken (t:ts) = return (t:ts)
addToken :: (Monad m) => Position
-> String
-> [Token]
-> ClaferT m [Token]
addToken p@(Pn z x y) s ts = do
when (not $ validToken t) $ throwErr $ ParseErr (ErrPos z fPos fPos) $ "not a reserved word: " ++ show s
(>>= (return . (PT p (TS s (fromJust t)):))) $ mapM (incrGlobal p (length s)) ts
where
fPos = Pos (fromIntegral x) (fromIntegral y)
validToken Nothing = False
validToken (Just _) = True
t = tokenLookup s
resLayout :: String -> String
resLayout input' =
reverse $ output $ execState resolveLayout' $ LayEnv 0 [] input'' [] 0
where
input'' = unlines $ filter (/= "") $ lines input'
resolveLayout' :: StateT LayEnv Identity ()
resolveLayout' = do
stop <- isEof
when (not stop) $ do
c <- getc
c' <- handleIndent c
emit c'
resolveLayout'
handleIndent :: Char -> StateT LayEnv Identity Char
handleIndent c = case c of
'\n' -> do
emit c
n <- eatSpaces
c' <- readC n
emitIndent n
emitDedent n
when (c' `elem` ['[', ']','{', '}']) $ void $ handleIndent c'
return c'
'[' -> do
modify (\e -> e {brCtr = brCtr e + 1})
return c
'{' -> do
modify (\e -> e {brCtr = brCtr e + 1})
return c
']' -> do
modify (\e -> e {brCtr = brCtr e 1})
return c
'}' -> do
modify (\e -> e {brCtr = brCtr e 1})
return c
_ -> return c
emit :: MonadState LayEnv m => Char -> m ()
emit c = modify (\e -> e {output = c : output e})
readC :: (Num a, Ord a) => a -> StateT LayEnv Identity Char
readC n = if n > 0 then getc else return '\n'
eatSpaces :: StateT LayEnv Identity Int
eatSpaces = do
cs <- gets input
let (sp, rest) = break (/= ' ') cs
modify (\e -> e {input = rest, output = sp ++ output e})
ctr <- gets brCtr
if ctr > 0 then gets level else return $ length sp
emitIndent :: MonadState LayEnv m => Int -> m ()
emitIndent n = do
lev <- gets level
when (n > lev) $ do
ctr <- gets brCtr
when (ctr < 1) $ do
emit '{'
modify (\e -> e {level = n, levels = lev : levels e})
emitDedent :: MonadState LayEnv m => Int -> m ()
emitDedent n = do
lev <- gets level
when (n < lev) $ do
ctr <- gets brCtr
when (ctr < 1) $ emit '}'
modify (\e -> e {level = head $ levels e, levels = tail $ levels e})
emitDedent n
isEof :: StateT LayEnv Identity Bool
isEof = null `liftM` (gets input)
getc :: StateT LayEnv Identity Char
getc = do
c <- gets (head.input)
modify (\e -> e {input = tail $ input e})
return c
revertLayout :: String -> String
revertLayout input' = unlines $ revertLayout' (lines input') 0
revertLayout' :: [String] -> Int -> [String]
revertLayout' [] _ = []
revertLayout' ([]:xss) i = revertLayout' xss i
revertLayout' (('{':xs):xss) i = (replicate i' ' ' ++ xs):revertLayout' xss i'
where i' = i + 2
revertLayout' (('}':xs):xss) i = (replicate i' ' ' ++ xs):revertLayout' xss i'
where i' = i 2
revertLayout' (xs:xss) i = (replicate i ' ' ++ xs):revertLayout' xss i