module Language.Bash.Parse.Internal
( skipSpace
, word
, arith
, name
, assign
, operator
, unquote
) where
import Control.Applicative
import Data.Monoid
import Text.Parsec.Char
import Text.Parsec.Combinator hiding (optional)
import Text.Parsec.Prim hiding ((<|>), many)
import Text.Parsec.String ()
import Language.Bash.Parse.Builder (Builder, (<+>))
import qualified Language.Bash.Parse.Builder as B
import Language.Bash.Syntax
surroundBy
:: Stream s m t
=> ParsecT s u m a
-> ParsecT s u m sep
-> ParsecT s u m [a]
surroundBy p sep = sep *> endBy p sep
skipSpace :: Stream s m Char => ParsecT s u m ()
skipSpace = skipMany spaceChar <* optional comment <?> "whitespace"
where
spaceChar = try (B.string "\\\n")
<|> B.oneOf " \t"
comment = char '#' *> many (satisfy (/= '\n'))
escape :: Stream s m Char => ParsecT s u m Builder
escape = B.char '\\' <+> B.anyChar
singleQuote :: Stream s m Char => ParsecT s u m Builder
singleQuote = B.matchedPair '\'' '\'' empty
doubleQuote :: Stream s m Char => ParsecT s u m Builder
doubleQuote = B.matchedPair '"' '"' $ escape <|> backquote <|> dollar
ansiQuote :: Stream s m Char => ParsecT s u m Builder
ansiQuote = B.char '$' <+> B.matchedPair '\'' '\'' escape
localeQuote :: Stream s m Char => ParsecT s u m Builder
localeQuote = B.char '$' <+> doubleQuote
backquote :: Stream s m Char => ParsecT s u m Builder
backquote = B.matchedPair '`' '`' escape
dollar :: Stream s m Char => ParsecT s u m Builder
dollar = B.char '$' <+> rest
where
rest = braceParameter
<|> try arithSubst
<|> commandSubst
<|> return mempty
braceParameter = B.matchedPair '{' '}' $
escape
<|> singleQuote
<|> doubleQuote
<|> backquote
<|> dollar
arithSubst = B.string "((" <+> parens <+> B.string "))"
commandSubst = subst
processSubst :: Stream s m Char => ParsecT s u m Builder
processSubst = B.oneOf "<>" <+> subst
subst :: Stream s m Char => ParsecT s u m Builder
subst = B.matchedPair '(' ')' $
subst
<|> B.char '#' <+> B.many (B.satisfy (/= '\n')) <+> B.char '\n'
<|> escape
<|> singleQuote
<|> doubleQuote
<|> backquote
<|> dollar
parens :: Stream s m Char => ParsecT s u m Builder
parens = B.many inner
where
inner = B.matchedPair '(' ')' parens
wordSpan :: Stream s m Char => ParsecT s u m Builder
wordSpan = mempty <$ try (string "\\\n")
<|> escape
<|> singleQuote
<|> doubleQuote
<|> try ansiQuote
<|> try localeQuote
<|> backquote
<|> dollar
<|> try processSubst
word :: Stream s m Char => ParsecT s u m String
word = B.toString <$> B.many wordPart <?> "word"
where
wordPart = wordSpan
<|> B.noneOf " \t\n|&;()<>"
arith :: Stream s m Char => ParsecT s u m String
arith = B.toString <$> parens <?> "arithmetic expression"
name :: Stream s m Char => ParsecT s u m String
name = (:) <$> nameStart <*> many nameLetter
where
nameStart = letter <|> char '_'
nameLetter = alphaNum <|> char '_'
assign :: Stream s m Char => ParsecT s u m Assign
assign = Assign <$> lvalue <*> assignOp <*> rvalue <?> "assignment"
where
lvalue = LValue <$> name <*> optional subscript
subscript = B.toString <$> B.span '[' ']' wordSpan
assignOp = Equals <$ string "="
<|> PlusEquals <$ string "+="
rvalue = RArray <$ char '(' <*> arrayElems <* char ')'
<|> RValue <$> word
arrayElems = arrayElem `surroundBy` skipArraySpace
arrayElem = do
s <- optional (subscript <* char '=')
w <- word
case (s, w) of
(Nothing, "") -> empty
_ -> return (s, w)
skipArraySpace = char '\n' `surroundBy` skipSpace
operator :: Stream s m Char => [String] -> ParsecT s u m String
operator ops = go ops <?> "operator"
where
go xs
| null xs = empty
| "" `elem` xs = try (continue xs) <|> pure ""
| otherwise = continue xs
continue xs = do
c <- anyChar
(c :) <$> go (prefix c xs)
prefix c = map tail . filter (\x -> not (null x) && head x == c)
unquote :: String -> String
unquote s = case parse unquoteBare s s of
Left _ -> s
Right s' -> B.toString s'
where
unquoteBare = B.many $
try unquoteEscape
<|> try unquoteSingle
<|> try unquoteDouble
<|> try unquoteAnsi
<|> try unquoteLocale
<|> B.anyChar
unquoteEscape = char '\\' *> B.anyChar
unquoteSingle = B.span '\'' '\'' empty
unquoteDouble = B.span '\"' '\"' unquoteEscape
unquoteAnsi = char '$' *> B.span '\'' '\'' unquoteEscape
unquoteLocale = char '$' *> unquoteDouble