module Language.Sh.Parser.Parsec where
import Text.ParserCombinators.Parsec ( GenParser, getState, setState,
tokenPrim, count, (<|>), (<?>),
skipMany, many, eof,
getInput, setInput )
import Text.ParserCombinators.Parsec.Pos ( updatePosChar )
import Text.ParserCombinators.Parsec.Error ( ParseError, Message(..),
errorMessages, errorPos,
newErrorMessage )
import Data.Char ( isUpper, isLower, isAlpha, isAlphaNum,
isDigit, isHexDigit, isOctDigit )
import Data.Monoid ( Monoid, mappend )
import Data.Maybe ( listToMaybe )
import Control.Monad ( unless, when )
import Debug.Trace ( trace )
import Language.Sh.Syntax ( Word )
data MChar = Ctl Control | Chr Char
instance Show MChar where
show (Ctl (AliasOn b)) = "AliasOn "++show b
show (Ctl (Aliases s)) = "Aliases "++show s
show (Ctl (IncPos b)) = "IncPos "++show b
show (Chr c) = show c
data Control = AliasOn Bool
| Aliases [(String,String)]
| IncPos Bool
data ParserState = PS { aliasOK :: Bool
, aliases :: [(String,String)]
, incPos :: Bool
, parenDepth :: Int
, hereDocs :: [String]
, readHereDocs :: [(Word,Bool)] }
type P = GenParser MChar ParserState
startState :: [(String,String)] -> ParserState
startState as = PS True as True 0 [] []
modify :: (ParserState -> ParserState) -> P ()
modify f = setState =<< fmap f getState
getAliasInfo :: P (Bool, [(String,String)], Bool)
getAliasInfo = fmap (\(PS a b c _ _ _) -> (a,b,c)) getState
setAliasInfo :: (Bool, [(String,String)], Bool) -> P ()
setAliasInfo (a,b,c) = modify $ \(PS _ _ _ d h h') -> PS a b c d h h'
insideParens :: P Bool
insideParens = fmap (\s -> parenDepth s > 0) getState
openParen :: P ()
openParen = modify $ \s -> s { parenDepth = parenDepth s+1 }
closeParen :: P ()
closeParen = modify $ \s -> s { parenDepth = parenDepth s1 }
getParenDepth :: P Int
getParenDepth = fmap parenDepth getState
addHereDoc :: String -> P ()
addHereDoc d = modify $ \s -> s { hereDocs = hereDocs s ++ [d] }
nextHereDoc :: P (Maybe String)
nextHereDoc = fmap (listToMaybe . hereDocs) getState
popHereDoc :: (Word,Bool) -> P ()
popHereDoc (w,b) = modify $ \s -> s { hereDocs = drop 1 $ hereDocs s
, readHereDocs = readHereDocs s ++ [(w,b)] }
nextHDReplacement :: P (Maybe (Word,Bool))
nextHDReplacement = do rhd <- readHereDocs `fmap` getState
case rhd of
(next:rest) -> do modify $
\s -> s { readHereDocs = rest }
return $ Just next
[] -> return Nothing
fatal :: String -> P a
fatal = fail . ('!':)
getFatal :: ParseError -> Maybe String
getFatal e = listToMaybe $ filter (not . null) $ map isFatal $ errorMessages e
where isFatal (Message ('!':s)) = s
isFatal _ = ""
unFatal :: ParseError -> ParseError
unFatal e = case getFatal e of
Just s -> newErrorMessage (Message s) (errorPos e)
Nothing -> e
infixl 3 <++>, <:>
(<++>) :: Monoid w => GenParser i s w -> GenParser i s w -> GenParser i s w
a <++> b = do w <- a
w' <- b
return $ w `mappend` w'
(<:>) :: GenParser i s a -> GenParser i s [a] -> GenParser i s [a]
a <:> b = do w <- a
w' <- b
return $ w:w'
tr :: Show a => String -> P a -> P a
tr s p = do a <- p
return $ trace (s++": "++show a) a
oneOf :: [Char] -> P Char
oneOf cs = satisfy' ("oneOf: "++show cs) (\c -> elem c cs)
noneOf :: [Char] -> P Char
noneOf cs = satisfy' ("noneOf: "++show cs) (\c -> not (elem c cs))
spaces :: P ()
spaces = skipMany space <?> "white space"
space :: P Char
space = satisfy' ("space") isBlank <?> "space"
space_ :: P ()
space_ = space >> return ()
isBlank :: Char -> Bool
isBlank = (`elem` " \t")
one :: P a -> P [a]
one = sequence . replicate 1
zeroOne :: P a -> P [a]
zeroOne p = one p <|> return []
newline :: P ()
newline = (count 1 (char '\n') >> zeroOne (char '\r') >> return ()) <|>
(count 1 (char '\r') >> zeroOne (char '\n') >> return ())
<?> "newline"
tab :: P Char
tab = char '\t' <?> "tab"
upper :: P Char
upper = satisfy isUpper <?> "uppercase letter"
lower :: P Char
lower = satisfy isLower <?> "lowercase letter"
alphaNum :: P Char
alphaNum = satisfy' "alphaNum" isAlphaNum <?> "letter or digit"
alphaUnder :: P Char
alphaUnder = satisfy' "alphaUnder" (\c -> isAlpha c || c=='_') <?> "letter or underscore"
alphaUnderNum :: P Char
alphaUnderNum = satisfy' "alphaUnderNum" (\c -> isAlphaNum c || c=='_')
<?> "letter, number, or underscore"
letter :: P Char
letter = satisfy' "alpha" isAlpha <?> "letter"
digit :: P Char
digit = satisfy' "digit" isDigit <?> "digit"
hexDigit :: P Char
hexDigit = satisfy' "hexDigit" isHexDigit <?> "hexadecimal digit"
octDigit :: P Char
octDigit = satisfy' "octDigit" isOctDigit <?> "octal digit"
char :: Char -> P Char
char c = satisfy' ("char: "++show c) (==c) <?> show [c]
anyChar :: P Char
anyChar = satisfy' "anyChar" (const True)
satisfy' :: String -> (Char -> Bool) -> P Char
satisfy' _ = satisfy'' False
satisfy = satisfy'' False
satisfy'' :: Bool -> (Char -> Bool) -> P Char
satisfy'' v f = do ip <- incPos `fmap` getState
let update = if ip then updatePosChar else const
c <- tokenPrim showToken (nextpos update) test
unless (isBlank c) $ modify $ \s -> s { aliasOK = False }
runCtls v
return c
where showToken (Chr c) = show c
nextpos u p (Chr c) _ = u p c
test (Chr c) = if f c then Just c else Nothing
runCtls :: Bool -> P ()
runCtls v = getInput >>= run >>= setInput
where run [] = return []
run (Ctl a:xs) = act a >> run xs
run xs = return xs
act (AliasOn b) = modify $ t "AliasOn" b $ \s -> s { aliasOK = b }
act (Aliases as) = modify $ t "Aliases" as $ \s -> s { aliases = as }
act (IncPos b) = modify $ t "IncPos" b $ \s -> s { incPos = b }
t s x = if v then trace (s++": "++show x) else id
aliasOn :: P ()
aliasOn = modify $ \s -> s { aliasOK = True }
string :: String -> P String
string [] = return []
string (c:cs) = do c <- char c
fmap (c:) $ string cs
schar :: Char -> P Char
schar c = do x <- char c
spaces
return x
assocL :: P a -> P (b -> a -> b) -> (a -> b) -> P b
assocL p op single = do x <- p
rest $ single x
where rest x = do f <- op
y <- p
rest (f x y)
<|> return x
getInput' :: P String
getInput' = do ts <- getInput
return $ concatMap f ts
where f (Chr c) = [c]
f _ = []
tok :: Char -> String
tok c | c `elem` "\n\r" = "newline"
| otherwise = [c]
token :: P a -> P a
token p = do p' <- p
spaces
return p'
unexpectedToken :: P a
unexpectedToken = do s <- getInput'
when (null s) $ err '\n'
err (head s)
where err c = fatal $ "syntax error near unexpected token `"++tok c++"'"
putBack :: Char -> P ()
putBack c = do i <- getInput
setInput $ Chr c:trace ("putting back a "++[c]++": "++show i) i
unexpected :: P a
unexpected = (anyChar >>= putBack >> unexpectedToken) <|> fail ""
unexpectedNoEOF :: P a
unexpectedNoEOF = unexpected