{-# LANGUAGE TupleSections, FlexibleInstances, TransformListComp #-} import Text.ParserCombinators.Parsek.Position import Data.Char import Data.List import System.IO import System.Environment import Control.Monad import Data.DList hiding (map,foldr) import Data.Monoid import GHC.Exts (the,groupWith) import Config -- todo: parse haskell comments ------------------ -- Simple printing combinators, which do not add nor remove line breaks type Doc = DList Char text = fromList x <+> y = x <> text " " <> y oChar = singleton parens s = oChar '(' <> s <> oChar ')' braces s = oChar '{' <> s <> oChar '}' brackets s = oChar '[' <> s <> oChar ']' doubleQuotes s = oChar '"' <> s <> oChar '"' int x = text $ show x hcat :: [Doc] -> Doc hcat = foldr (<>) mempty punctuate t = map (<> t) render :: Doc -> String render = toList ------------------------------------------ -- Output combinators oPos :: SourcePos -> Doc oPos EOF = mempty oPos p = text "{-# LINE" <+> int (sourceLine p) <+> text (show (sourceName p)) <+> text "#-}" oText :: String -> Doc oText x = text "textual" <+> text (show x) oMappend :: [Doc] -> Doc oMappend [] = text "(return ())" oMappend [x] = parens x oMappend l = text "do" <+> braces (text "rec" <+> braces (hcat (punctuate (text ";") binds)) <> text ";" <> ret) where binds = init l ret = last l ---------------------------------------------- -- Parsing helpers satisfy' :: (String -> Bool) -> Parser Char satisfy' p = do l <- look unless (p l) $ fail "Unexpected leading string" anySymbol munch',munch1' :: (String -> Bool) -> Parser String munch' p = scan =<< look where scan (c:cs) | p (c:cs) = (:) <$> anySymbol <*> scan cs scan _ = pure [] munch1' p = (:) <$> satisfy' p <*> munch' p -- | A chunck not containing some strings pChunk' :: [String] -> Parser String pChunk' stops = munch1' (\l -> not $ any (`isPrefixOf` l) stops) -- | A chunk not containing some chars. pChunk :: [Char] -> Parser String pChunk stops = munch1 (not . (`elem` stops)) ---------------------------------------------- -- Parsing combinators anyQuoteStrings :: [String] anyQuoteStrings = concatMap (\(x,y) -> [x,y]) quoteStrings pTextChunk = oText <$> pChunk' ("\n" : commentString : antiQuoteStrings ++ anyQuoteStrings) "Text chunk" pHaskChunk = text <$> pChunk' (map box "\n\"[]()" ++ map fst quoteStrings) "Haskell chunk" -- we keep track of balancing pWPos :: Parser Doc pWPos = do char '\n' pos <- getPosition return $ oPos pos <> text "\n" pHaskLn = pWPos -- before each newline, tell GHC where we are. pTextLn = (oText "\n" <>) <$> pWPos -- add code to output a newline; and insert a newline in the code. box = (:[]) pString :: Parser Doc pString = do char '"' result <- many (string "\\\"" <|> pChunk ['"']) char '"' return $ doubleQuotes . hcat . map text $ result -- | Parse some Haskell code with markup inside. pHask :: Parser Doc pHask = (hcat <$> many ((brackets <$> pArg "[]") <|> (parens <$> pArg "()") <|> (parens <$> pTextArg ) <|> pString <|> pHaskChunk <|> pHaskLn)) -- | Parse a text argument to an element pTextArg' :: String -> String -> Parser Doc pTextArg' open close = label "quoted text" $ string open *> (oMappend <$> many (pElement <|> pTextChunk <|> pTextLn <|> pComment)) <* string close pTextArg :: Parser Doc pTextArg = choice $ map (uncurry pTextArg') quoteStrings pArg :: String -> Parser Doc pArg [open,close] = char open *> pHask <* char close isIdentChar :: Char -> Bool isIdentChar x = isAlphaNum x || (x `elem` "\'_") pIdent :: Parser Doc pIdent = text <$> munch1 isIdentChar "identifier" pArgument :: Parser Doc pArgument = (pArg "()" <|> (brackets <$> pArg "[]") <|> pTextArg <|> pString) "argument" pElement :: Parser Doc pElement = label "Haskell element" $ do choice $ map string $ antiQuoteStrings var <- ((<+> text "<-") <$> (pIdent <* string "<-")) <<|> pure mempty val <- ((:) <$> pIdent <*> manyGreedy pArgument) <|> (box <$> pArg "()") return $ var <> text "element" <+> parens (hcat $ fmap parens val) commentString :: String commentString = "%%" pComment :: Parser Doc pComment = label "Comment" $ do string commentString munch (/= '\n') string "\n" return mempty main :: IO () main = do x : y : z : _ <- getArgs putStrLn x putStrLn y putStrLn z p <- parseFromFile (pHask <* endOfFile) completeResults y case p of Left e -> handleErr e Right [res] -> writeFile z $ render res Right _ -> hPutStrLn stderr "Amibiguous input!" handleErr e = sequence_ [ hPutStrLn stderr (show $ maybePosToPos $ the pos) >> hPutStrLn stderr (" Expected:" ++ (intercalate " or " $ nub what)) | (exps,_why) <- e, (what,pos) <- exps, then group by pos using groupWith, then reverse ] instance Show (DList Char) where show x = show $ render x -- Tests testHask = parse "" pHask completeResults "arst « text @z<-fct[x](y) awft" testHask2 = parse "" pHask completeResults "ars(t) « text @z<-fct[x](y) » awft" testText2 = parse "" pTextArg completeResults "« text @fct(x »" testText3 = parse "" pTextArg completeResults "« 1 @x 2 @y 3 @x 4 »" testElem = parse "" pElement completeResults "@x<-fct(x « yop »)[y]" testChunk = parse "" pHaskChunk completeResults "t" testArg = parse "" (pArg "()") completeResults "()" terr e = case e of Left x -> handleErr x Right [res] -> putStrLn $ render res