module Parse.Helpers where
import Prelude hiding (until)
import Control.Applicative ((<$>),(<*>))
import Control.Monad
import Control.Monad.State
import Data.Char (isUpper)
import qualified Data.Map as Map
import qualified Language.GLSL.Parser as GLP
import Language.GLSL.Syntax as GLS
import Text.Parsec hiding (newline,spaces,State)
import Text.Parsec.Indent
import qualified Text.Parsec.Token as T
import AST.Annotation as Annotation
import AST.Declaration (Assoc)
import AST.Expression.General
import qualified AST.Expression.Source as Source
import AST.Helpers as Help
import AST.Literal as Literal
import AST.PrettyPrint
import qualified AST.Variable as Variable
reserveds = [ "if", "then", "else"
, "case", "of"
, "let", "in"
, "data", "type"
, "module", "where"
, "import", "as", "hiding", "open"
, "export", "foreign"
, "deriving", "port"
]
expecting = flip (<?>)
type OpTable = Map.Map String (Int, Assoc)
type SourceM = State SourcePos
type IParser a = ParsecT String OpTable SourceM a
iParse :: IParser a -> String -> Either ParseError a
iParse = iParseWithTable "" Map.empty
iParseWithTable :: SourceName -> OpTable -> IParser a -> String -> Either ParseError a
iParseWithTable sourceName table aParser input =
runIndent sourceName $ runParserT aParser table sourceName input
var :: IParser String
var = makeVar (letter <|> char '_' <?> "variable")
lowVar :: IParser String
lowVar = makeVar (lower <?> "lower case variable")
capVar :: IParser String
capVar = makeVar (upper <?> "upper case variable")
qualifiedVar :: IParser String
qualifiedVar = do
vars <- many ((++) <$> capVar <*> string ".")
(++) (concat vars) <$> lowVar
rLabel :: IParser String
rLabel = lowVar
innerVarChar :: IParser Char
innerVarChar = alphaNum <|> char '_' <|> char '\'' <?> ""
makeVar :: IParser Char -> IParser String
makeVar p = do
v <- (:) <$> p <*> many innerVarChar
if v `elem` reserveds
then fail $ "unexpected keyword '" ++ v ++ "', variables cannot be keywords"
else return v
reserved :: String -> IParser String
reserved word =
try (string word >> notFollowedBy innerVarChar) >> return word
<?> "reserved word '" ++ word ++ "'"
anyOp :: IParser String
anyOp = betwixt '`' '`' qualifiedVar <|> symOp <?> "infix operator (e.g. +, *, ||)"
symOp :: IParser String
symOp = do op <- many1 (satisfy Help.isSymbol)
guard (op `notElem` [ "=", "..", "->", "--", "|", "\8594", ":" ])
case op of
"." -> notFollowedBy lower >> return op
"\8728" -> return "."
_ -> return op
padded :: IParser a -> IParser a
padded p = do whitespace
out <- p
whitespace
return out
equals :: IParser String
equals = string "="
arrow :: IParser String
arrow = string "->" <|> string "\8594" <?> "arrow (->)"
hasType :: IParser String
hasType = string ":" <?> "':' (a type annotation)'"
commitIf check p = commit <|> try p
where commit = do (try $ lookAhead check) >> p
spaceySepBy1 :: IParser b -> IParser a -> IParser [a]
spaceySepBy1 sep p = do
a <- p
(a:) <$> many (commitIf (whitespace >> sep) (padded sep >> p))
commaSep1 :: IParser a -> IParser [a]
commaSep1 = spaceySepBy1 (char ',' <?> "comma ','")
commaSep :: IParser a -> IParser [a]
commaSep = option [] . commaSep1
semiSep1 :: IParser a -> IParser [a]
semiSep1 = spaceySepBy1 (char ';' <?> "semicolon ';'")
pipeSep1 :: IParser a -> IParser [a]
pipeSep1 = spaceySepBy1 (char '|' <?> "type divider '|'")
consSep1 :: IParser a -> IParser [a]
consSep1 = spaceySepBy1 (string "::" <?> "cons operator '::'")
dotSep1 :: IParser a -> IParser [a]
dotSep1 p = (:) <$> p <*> many (try (char '.') >> p)
spaceSep1 :: IParser a -> IParser [a]
spaceSep1 p = (:) <$> p <*> spacePrefix p
spacePrefix p = constrainedSpacePrefix p (\_ -> return ())
constrainedSpacePrefix p constraint =
many $ choice [ try (spacing >> lookAhead (oneOf "[({")) >> p
, try (spacing >> p)
]
where
spacing = do
n <- whitespace
constraint n
indented
failure msg = do
inp <- getInput
setInput ('x':inp)
anyToken
fail msg
followedBy a b = do x <- a ; b ; return x
betwixt a b c = do char a ; out <- c
char b <?> "closing '" ++ [b] ++ "'" ; return out
surround a z name p = do
char a
v <- padded p
char z <?> unwords ["closing", name, show z]
return v
braces :: IParser a -> IParser a
braces = surround '[' ']' "brace"
parens :: IParser a -> IParser a
parens = surround '(' ')' "paren"
brackets :: IParser a -> IParser a
brackets = surround '{' '}' "bracket"
addLocation :: (Pretty a) => IParser a -> IParser (Annotation.Located a)
addLocation expr = do
(start, e, end) <- located expr
return (Annotation.at start end e)
located :: IParser a -> IParser (SourcePos, a, SourcePos)
located p = do
start <- getPosition
e <- p
end <- getPosition
return (start, e, end)
accessible :: IParser Source.Expr -> IParser Source.Expr
accessible expr = do
start <- getPosition
ce@(A _ e) <- expr
let rest f = do
let dot = char '.' >> notFollowedBy (char '.')
access <- optionMaybe (try dot <?> "field access (e.g. List.map)")
case access of
Nothing -> return ce
Just _ -> accessible $ do
v <- var <?> "field access (e.g. List.map)"
end <- getPosition
return (Annotation.at start end (f v))
case e of
Var (Variable.Raw (c:cs))
| isUpper c -> rest (\v -> rawVar (c:cs ++ '.':v))
| otherwise -> rest (Access ce)
_ -> rest (Access ce)
spaces :: IParser String
spaces = concat <$> many1 (multiComment <|> string " ") <?> "spaces"
forcedWS :: IParser String
forcedWS = choice [ try $ (++) <$> spaces <*> (concat <$> many nl_space)
, try $ concat <$> many1 nl_space ]
where nl_space = try ((++) <$> (concat <$> many1 newline) <*> spaces)
dumbWhitespace :: IParser String
dumbWhitespace = concat <$> many (spaces <|> newline)
whitespace :: IParser String
whitespace = option "" forcedWS <?> "whitespace"
freshLine :: IParser [[String]]
freshLine = try (many1 newline >> many space_nl) <|> try (many1 space_nl) <?> ""
where space_nl = try $ spaces >> many1 newline
newline :: IParser String
newline = simpleNewline <|> lineComment <?> "newline"
simpleNewline :: IParser String
simpleNewline = try (string "\r\n") <|> string "\n"
lineComment :: IParser String
lineComment = do
try (string "--")
comment <- anyUntil $ simpleNewline <|> (eof >> return "\n")
return ("--" ++ comment)
multiComment :: IParser String
multiComment = (++) <$> try (string "{-") <*> closeComment
closeComment :: IParser String
closeComment =
anyUntil . choice $
[ try (string "-}") <?> "close comment"
, concat <$> sequence [ try (string "{-"), closeComment, closeComment ]
]
until :: IParser a -> IParser b -> IParser b
until p end = go
where
go = end <|> (p >> go)
anyUntil :: IParser String -> IParser String
anyUntil end = go
where
go = end <|> (:) <$> anyChar <*> go
ignoreUntil :: IParser a -> IParser (Maybe a)
ignoreUntil end = go
where
ignore p = const () <$> p
filler = choice [ try (ignore chr) <|> ignore str
, ignore multiComment
, ignore (markdown (\_ -> mzero))
, ignore anyChar
]
go = choice [ Just <$> end
, filler `until` choice [ const Nothing <$> eof, newline >> go ]
]
onFreshLines :: (a -> b -> b) -> b -> IParser a -> IParser b
onFreshLines insert init thing = go init
where
go values = do
optionValue <- ignoreUntil thing
case optionValue of
Nothing -> return values
Just v -> go (insert v values)
withSource :: IParser a -> IParser (String, a)
withSource p = do
start <- getParserState
result <- p
endPos <- getPosition
setParserState start
raw <- anyUntilPos endPos
return (raw, result)
anyUntilPos :: SourcePos -> IParser String
anyUntilPos pos = go
where
go = do currentPos <- getPosition
case currentPos == pos of
True -> return []
False -> (:) <$> anyChar <*> go
markdown :: ([a] -> IParser (String, [a])) -> IParser (String, [a])
markdown interpolation =
do try (string "[markdown|")
closeMarkdown id []
where
closeMarkdown markdownBuilder stuff =
choice [ do try (string "|]")
return (markdownBuilder "", stuff)
, do (markdown,stuff') <- interpolation stuff
closeMarkdown (markdownBuilder . (markdown++)) stuff'
, do c <- anyChar
closeMarkdown (markdownBuilder . (c:)) stuff
]
shader :: IParser (String, Literal.GLShaderTipe)
shader =
do try (string "[glsl|")
rawSrc <- closeShader id
case glSource rawSrc of
Left err -> parserFail . show $ err
Right tipe -> return (rawSrc, tipe)
where
closeShader builder = choice
[ do try (string "|]")
return (builder "")
, do c <- anyChar
closeShader (builder . (c:))
]
glSource :: String -> Either ParseError Literal.GLShaderTipe
glSource src =
case GLP.parse src of
Right (TranslationUnit decls) ->
Right . foldr addGLinput emptyDecls . join . map extractGLinputs $ decls
Left e -> Left e
where
emptyDecls = Literal.GLShaderTipe Map.empty Map.empty Map.empty
addGLinput (qual,tipe,name) glDecls = case qual of
Attribute -> glDecls { attribute = Map.insert name tipe $ attribute glDecls }
Uniform -> glDecls { uniform = Map.insert name tipe $ uniform glDecls }
Varying -> glDecls { varying = Map.insert name tipe $ varying glDecls }
_ -> error "Should never happen due to below filter"
extractGLinputs decl = case decl of
Declaration (InitDeclaration (TypeDeclarator (FullType (Just (TypeQualSto qual)) (TypeSpec _prec (TypeSpecNoPrecision tipe _mexpr1)))) [InitDecl name _mexpr2 _mexpr3]) ->
if elem qual [Attribute, Varying, Uniform]
then case tipe of
GLS.Int -> return (qual,Literal.Int,name)
GLS.Float -> return (qual,Literal.Float,name)
GLS.Vec2 -> return (qual,V2,name)
GLS.Vec3 -> return (qual,V3,name)
GLS.Vec4 -> return (qual,V4,name)
GLS.Mat4 -> return (qual,M4,name)
GLS.Sampler2D -> return (qual,Texture,name)
_ -> []
else []
_ -> []
str = expecting "String" $ do
s <- choice [ multiStr, singleStr ]
processAs T.stringLiteral . sandwich '\"' $ concat s
where
rawString quote insides =
quote >> manyTill insides quote
multiStr = rawString (try (string "\"\"\"")) multilineStringChar
singleStr = rawString (char '"') stringChar
stringChar :: IParser String
stringChar = choice [ newlineChar, escaped '\"', (:[]) <$> satisfy (/= '\"') ]
multilineStringChar :: IParser String
multilineStringChar =
do noEnd
choice [ newlineChar, escaped '\"', expandQuote <$> anyChar ]
where
noEnd = notFollowedBy (string "\"\"\"")
expandQuote c = if c == '\"' then "\\\"" else [c]
newlineChar :: IParser String
newlineChar =
choice [ char '\n' >> return "\\n"
, char '\r' >> return "\\r" ]
sandwich :: Char -> String -> String
sandwich delim s = delim : s ++ [delim]
escaped :: Char -> IParser String
escaped delim = try $ do
char '\\'
c <- char '\\' <|> char delim
return ['\\', c]
chr :: IParser Char
chr = betwixt '\'' '\'' character <?> "character"
where
nonQuote = satisfy (/='\'')
character = do
c <- choice [ escaped '\''
, (:) <$> char '\\' <*> many1 nonQuote
, (:[]) <$> nonQuote ]
processAs T.charLiteral $ sandwich '\'' c
processAs :: (T.GenTokenParser String u SourceM -> IParser a) -> String -> IParser a
processAs processor s = calloutParser s (processor lexer)
where
calloutParser :: String -> IParser a -> IParser a
calloutParser inp p = either (fail . show) return (iParse p inp)
lexer :: T.GenTokenParser String u SourceM
lexer = T.makeTokenParser elmDef
elmDef :: T.GenLanguageDef String u SourceM
elmDef = T.LanguageDef
{ T.commentStart = "{-"
, T.commentEnd = "-}"
, T.commentLine = "--"
, T.nestedComments = True
, T.identStart = undefined
, T.identLetter = undefined
, T.opStart = undefined
, T.opLetter = undefined
, T.reservedNames = reserveds
, T.reservedOpNames = [":", "->", "<-", "|"]
, T.caseSensitive = True
}