packages feed

marxup-3.0.0: Main3.hs

{-# 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 "<interactive>" pHask completeResults "arst « text @z<-fct[x](y) awft"
testHask2 = parse "<interactive>" pHask completeResults "ars(t) « text @z<-fct[x](y) » awft"
testText2 = parse "<interactive>" pTextArg completeResults "« text @fct(x »"
testText3 = parse "<interactive>" pTextArg completeResults "« 1 @x 2 @y 3 @x 4 »"
testElem = parse "<interactive>" pElement completeResults "@x<-fct(x « yop »)[y]"
testChunk = parse "<interactive>" pHaskChunk completeResults "t"
testArg = parse "<interactive>" (pArg "()") completeResults "()"

terr e  = case e of
    Left x -> handleErr x
    Right [res] -> putStrLn $ render res