cryptol/docs/FindExercises.hs
Ben Selfridge 861e9e9651
Feature/docs checking (#976)
* Adds a stub executable to cabal file for checking docs

* [WIP] Adds check-exercises executable

A program that checks that the exercises in Programming Cryptol actual work when
executed against an Cryptol REPL. Instead of using \begin{Verbatim}, we use
\begin{REPL} in both the exercise and the answer, which is rendered the same but
gets checked by this program.

This is a WIP -- we still need to do a number of things, including (but not
limited to):

* Move the "REPL" macro out of CrashCourse.tex and into some including latex
  file
* Change many of the "Verbatim"s into "REPL"s to test if this approach works in
  general

* Several updates

* Moves REPL command definitions into main latex file

* Generalizes repl commands

* Several improvements:

* documentation of CheckExercises.hs
* factoring out addReplData, addReplin, addReplout, nextLine functions
* took the IO out of P monad (shouldn't have been there)
* worked on annotating repls for many exercises/examples in crash course

* Adds a README for check-exercises

* Uses cryptol's -e option to detect errors

* updates ProgrammingCryptol.pdf

* Fixes main function

After changing to use the -e option to detect errors, I used cabal v2-exec which
apparently does not rebuild anything, but I thought it did. This just fixes the
code so it builds again.
2020-11-20 16:53:36 -08:00

124 lines
3.6 KiB
Haskell

#!/usr/bin/env runhaskell
{-# Language RecordWildCards #-}
module Main(main) where
import Data.List(isPrefixOf)
main :: IO ()
main = interact (pp . findExercises)
where
pp (es,as) = unlines $ ppMany "Exercises" es ++
ppMany "Answers" as
ppMany h xs = h : "-------------" : map ppEnv xs
ppEnv Env { .. } = envName ++ ":" ++ show envStart ++ "--" ++ show envEnd ++
"(" ++ show (info envLines) ++ ")"
info = length . findVerbatim
--------------------------------------------------------------------------------
data Line = Line { lineNo :: Int, lineText :: String }
data Env = Env
{ envName :: String
, envStart, envEnd :: Int -- location in latex, includes \begin \end
, envLines :: [Line]
}
findExercises :: String -> ([Env],[Env])
findExercises = noEx [] [] () . zipWith toLine [1..] . lines
where toLine lineNo lineText = Line { .. }
type ExS s = [Env] -> [Env] -> s -> [Line] -> ([Env],[Env])
noEx :: ExS ()
noEx es as _ xs =
case xs of
[] -> (es,as)
Line { .. } : more
| Just nm <- startExercise lineText -> inEx es as (lineNo, nm, []) more
| Just nm <- startAnswer lineText -> inAns es as (lineNo, nm, []) more
| otherwise -> noEx es as () more
inEx :: ExS (Int,String,[Line])
inEx es as (envStart,envName,ls) xs =
case xs of
[] -> error $ unwords [ show envStart ++ ":"
, "Unterminated exercise", show envName ]
l : more
| endExercise (lineText l) ->
let ex = Env { envLines = reverse ls, envEnd = lineNo l, .. }
in noEx (ex : es) as () more
| otherwise -> inEx es as (envStart,envName,l:ls) more
inAns :: ExS (Int,String,[Line])
inAns es as (envStart,envName,ls) xs =
case xs of
[] -> error $ unwords [ show envStart ++ ":"
, "Unterminated answer", show envName ]
l : more
| endAnswer (lineText l) ->
let ex = Env { envLines = reverse ls, envEnd = lineNo l, .. }
in noEx es (ex : as) () more
| otherwise -> inAns es as (envStart,envName,l:ls) more
findVerbatim :: [Line] -> [Env]
findVerbatim ls =
case break (startVerbatim . lineText) ls of
(_,l:more) ->
case break (endVerbatim . lineText) more of
(ls,e:rest) ->
Env { envName = "Verbatim"
, envStart = lineNo l
, envEnd = lineNo e
, envLines = ls
} : findVerbatim rest
_ -> error (show (lineNo l) ++ ": Unterminated verbatim")
_ -> []
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
startVerbatim :: String -> Bool
startVerbatim x = pref `isPrefixOf` x
where
pref = "\\begin{Verbatim}"
endVerbatim :: String -> Bool
endVerbatim x = pref `isPrefixOf` x
where
pref = "\\end{Verbatim}"
startExercise :: String -> Maybe String
startExercise x
| pref `isPrefixOf` x = Just name
| otherwise = Nothing
where
pref = "\\begin{Exercise}\\label{ex:"
name = takeWhile (/= '}') (drop (length pref) x)
endExercise :: String -> Bool
endExercise x = pref `isPrefixOf` x
where
pref = "\\end{Exercise}"
startAnswer :: String -> Maybe String
startAnswer x
| pref `isPrefixOf` x = Just name
| otherwise = Nothing
where
pref = "\\begin{Answer}\\ansref{ex:"
name = takeWhile (/= '}') (drop (length pref) x)
endAnswer :: String -> Bool
endAnswer x = pref `isPrefixOf` x
where
pref = "\\end{Answer}"