{-# LANGUAGE RelaxedPolyRec, PatternGuards, ViewPatterns #-}
module Parse
( Doc (..)
, BBlock (..)
, Prompt
, mainParse
, getCommand
, printName
, parseQuickCheck
) where
import Text.Pandoc
import Language.Haskell.Exts.Parser
import Language.Haskell.Exts.Syntax
{- Agda support (unfinished)
import qualified Agda.Syntax.Common as Agda
import qualified Agda.Syntax.Concrete as Agda
import qualified Agda.Syntax.Parser as Agda
-}
import Data.List.Split (splitOn)
import Data.List (tails, partition, groupBy)
import Data.Function (on)
import Data.Char (isAlpha, isSpace, toUpper, isUpper)
import Control.Monad (zipWithM)
--------------------------------- data structures
data Doc
= Doc
Meta{-title, author, date-}
Module{-module directives, module name, imports-}
[BBlock]
deriving (Show)
data BBlock
= Text Block{-pandoc block-}
| OneLineExercise
Prompt
Bool{-intentional error-}
String
| Exercise
[String]{-lines-}
[String]{-visible lines-}
[String]{-hidden lines-}
[Name]{-defined names-}
[String]{-test expressions-}
deriving (Show)
type Prompt = Char -- see the separate documentation
-----------------------------------
mainParse :: Bool -> FilePath -> IO Doc
mainParse agda s = do
c <- readFile s
case readMarkdown pState . unlines . concatMap preprocess . lines $ c of
Pandoc meta (CodeBlock ("",["sourceCode","literate","haskell"],[]) h: blocks) -> do
header <- liftError . parseModule' $ h
fmap (Doc meta header) $ collectTests agda $ map (interpreter . Text) blocks
Pandoc meta blocks -> do
header <- liftError . parseModule' $ "module Unknown where"
fmap (Doc meta header) $ collectTests agda $ map (interpreter . Text) blocks
where
parseModule' = parseModuleWithMode (defaultParseMode {fixities = []})
preprocess (c:'>':' ':l) | c `elem` commandList
= ["~~~~~ {." ++ [c] ++ "}", dropWhile (==' ') l, "~~~~~", ""]
preprocess ('|':l)
= []
preprocess l
= [l]
pState = defaultParserState
{ stateSmart = True
, stateStandalone = True
, stateLiterateHaskell = True
}
liftError :: (Monad m, Show a) => ParseResult a -> m a
liftError (ParseOk m) = return m
liftError x = fail $ "parseHeader: " ++ show x
interpreter :: BBlock -> BBlock
interpreter (Text (CodeBlock ("",[[x]],[]) e)) | x `elem` commandList
= OneLineExercise (toUpper x) (isUpper x) e
interpreter a = a
commandList, testCommandList :: String
commandList = "AaRr" ++ testCommandList
testCommandList = "EeFfH"
------------------------------
collectTests :: Bool -> [BBlock] -> IO [BBlock]
collectTests agda l = zipWithM f l $ tail $ tails l where
f (Text (CodeBlock ("",["sourceCode","literate","haskell"],[]) h)) l = do
let
isExercise = True -- not $ null $ concatMap fst exps
(visible, hidden, funnames) <- processLines agda isExercise h
let
exps = [snd $ getCommand e | (OneLineExercise _ _ e) <- takeWhile p l]
p (OneLineExercise x _ e) = x `elem` testCommandList && fst (getCommand e) == ""
p _ = False
return $ Exercise (lines h) visible hidden funnames exps
f x _ = return x
processLines :: Bool -> Bool -> String -> IO ([String], [String], [Name])
--processLines True = processAgdaLines
processLines _ = processHaskellLines
{- Agda support (unfinished)
processAgdaLines :: Bool -> String -> IO ([String], [String], [Name])
processAgdaLines isExercise l_ = do
let
l = parts l_
x <- fmap (zip l) $ mapM (Agda.parse Agda.moduleParser . ("module X where\n"++) . unlines) l
let
names = map toName $ concatMap (getFName . snd . snd) x
-- getFName [Agda.Module _ _ [Agda.TypedBindings _ (Agda.Arg _ _ [Agda.TBind _ a _])] declarations]
-- = map Agda.boundName a
getFName [Agda.Module _ _ _ [Agda.TypeSig _ n _]]
= [n]
getFName _ = []
-- isVisible [Agda.Module _ _ [Agda.TypedBindings _ (Agda.Arg _ _ [Agda.TBind _ a _])] declarations]
-- = True
isVisible [Agda.Module _ _ _ [Agda.TypeSig _ n _]] = True
isVisible _ = not isExercise
(visible, hidden) = partition (isVisible . snd . snd) x
toName n = Ident $ show n
return (concatMap fst visible, concatMap fst hidden, names)
-}
processHaskellLines :: Bool -> String -> IO ([String], [String], [Name])
processHaskellLines isExercise l_ = return (concatMap fst visible, concatMap fst hidden, names)
where
x = zip l $ map (parseDeclWithMode (defaultParseMode {fixities = []}) . unlines) l
l = parts l_
names = concatMap (getFName . snd) x
getFName (ParseOk x) = case x of
TypeSig _ a _ -> a
PatBind _ (PVar a) _ _ _ -> [a]
FunBind (Match _ a _ _ _ _ :_) -> [a]
TypeDecl _ a _ _ -> [a]
DataDecl _ _ _ a _ x _ -> a: [n | QualConDecl _ _ _ y<-x, n <- getN y]
_ -> []
getFName _ = []
getN (ConDecl n _) = [n]
getN (InfixConDecl _ n _) = [n]
getN (RecDecl n l) = n: concatMap fst l
isVisible (ParseOk (TypeSig _ _ _)) = True
isVisible (ParseOk (InfixDecl _ _ _ _)) = True
isVisible _ = not isExercise
(visible, hidden) = partition (isVisible . snd) x
parts :: String -> [[String]]
parts = groupBy (const id `on` isIndented) . lines where
isIndented s | all isSpace s = True
isIndented (' ':_) = True
isIndented _ = False
------------------------------
getCommand :: String -> (String, String)
getCommand (':':'?': (dropSpace -> Just x))
= ("?", x)
getCommand (':': (span isAlpha -> (c@(_:_), dropSpace -> Just x)))
= (c, x)
getCommand s
= ("", s)
dropSpace :: String -> Maybe String
dropSpace (' ':y) = Just $ dropWhile (==' ') y
dropSpace "" = Just ""
dropSpace _ = Nothing
parseQuickCheck :: String -> ([String], String)
parseQuickCheck s = case splitOn ";;" s of
l -> (init l, last l)
printName :: Name -> String
printName (Ident x) = x
printName (Symbol x) = x