Skip to content

Commit 00da33c

Browse files
committed
Implement whitespace_ok option
1 parent 3741fec commit 00da33c

File tree

5 files changed

+100
-41
lines changed

5 files changed

+100
-41
lines changed

haskell_edsl/src-main/Test.hs

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Main ( main ) where
22

33
import Checktestdata
44
import Checktestdata.Script
5+
import Checktestdata.Options
56

67
import Control.Monad
78
import Control.Exception
@@ -19,61 +20,72 @@ main = do
1920
-- Read the tests directory
2021
allfiles <- listDirectory testsdir
2122

22-
-- Go over all regular test programs
23-
let isProg f = "testprog" `isPrefixOf` f && ".in" `isSuffixOf` f
24-
r1 <- forM (filter isProg allfiles) $ \prog -> do
25-
-- Get the test num
26-
let testnum = takeWhile (/='.') $ drop (length "testprog") prog
23+
-- Generic function we can run for both whitespace and non-whitespace
24+
let tests opts progf datf = do
25+
-- Go over all regular test programs
26+
let isProg f = progf `isPrefixOf` f && ".in" `isSuffixOf` f
27+
r1 <- forM (filter isProg allfiles) $ \prog -> do
28+
-- Get the test num
29+
let testnum = takeWhile (/='.') $ drop (length progf) prog
2730

28-
-- Go over the correct testdata files
29-
let isCorrect f = ("testdata"++ testnum ++ ".in") `isPrefixOf` f
30-
r2 <- forM (filter isCorrect allfiles) $ \dataf -> checkSuccess prog dataf
31+
-- Go over the correct testdata files
32+
let isCorrect f = (datf ++ testnum ++ ".in") `isPrefixOf` f
33+
r2 <- forM (filter isCorrect allfiles) $ \dataf -> checkSuccess opts prog dataf
3134

32-
-- Go over the failure testdata files
33-
let isFailure f = ("testdata"++ testnum ++ ".err") `isPrefixOf` f
34-
r3 <- forM (filter isFailure allfiles) $ \dataf -> checkFailure prog dataf
35+
-- Go over the failure testdata files
36+
let isFailure f = (datf ++ testnum ++ ".err") `isPrefixOf` f
37+
r3 <- forM (filter isFailure allfiles) $ \dataf -> checkFailure opts prog dataf
3538

36-
return $ r2 ++ r3
39+
return $ r2 ++ r3
3740

38-
-- Go over all test programs that should fail
39-
let isErrProg f = "testprog" `isPrefixOf` f && ".err" `isSuffixOf` f
40-
r4 <- forM (filter isErrProg allfiles) $ \prog -> do
41-
-- Get the test num
42-
let testnum = takeWhile (/='.') $ drop (length "testprog") prog
41+
-- Go over all test programs that should fail
42+
let isErrProg f = progf `isPrefixOf` f && ".err" `isSuffixOf` f
43+
r4 <- forM (filter isErrProg allfiles) $ \prog -> do
44+
-- Get the test num
45+
let testnum = takeWhile (/='.') $ drop (length "testprog") prog
4346

44-
-- Go over the correct testdata files
45-
let isCorrect f = ("testdata"++ testnum ++ ".in") `isPrefixOf` f
46-
forM (filter isCorrect allfiles) $ \dataf -> checkFailure prog dataf
47+
-- Go over the correct testdata files
48+
let isCorrect f = (datf ++ testnum ++ ".in") `isPrefixOf` f
49+
forM (filter isCorrect allfiles) $ \dataf -> checkFailure opts prog dataf
50+
51+
-- Return all results
52+
return $ concat $ r1 ++ r4
53+
54+
-- Run tests for normal progs
55+
r1 <- tests defaultOptions "testprog" "testdata"
56+
57+
-- Run tests with -w options
58+
r2 <- tests (defaultOptions { whitespace_ok = True }) "testwsprog" "testwsdata"
4759

4860
-- Check that all tests succeeded
49-
when (not $ and $ concat $ r1 ++ r4) $ exitFailure
61+
when (not $ and $ r1 ++ r2) $ exitFailure
5062

5163
-- | Run the prog on the given data file and ensure that it succeeded.
52-
checkSuccess :: FilePath -> FilePath -> IO Bool
53-
checkSuccess prog dataf = do
54-
res <- checkRun prog dataf
64+
checkSuccess :: Options -> FilePath -> FilePath -> IO Bool
65+
checkSuccess opts prog dataf = do
66+
res <- checkRun opts prog dataf
5567
case res of
5668
Right () -> return True
5769
Left _ -> do
5870
putStrLn $ "Running " ++ prog ++ " on " ++ dataf ++ " did not succeed"
5971
return False
6072

6173
-- | Run the prog on the given data file and ensure that it failed
62-
checkFailure :: FilePath -> FilePath -> IO Bool
63-
checkFailure prog dataf = do
64-
res <- checkRun prog dataf
74+
checkFailure :: Options -> FilePath -> FilePath -> IO Bool
75+
checkFailure opts prog dataf = do
76+
res <- checkRun opts prog dataf
6577
case res of
6678
Left _ -> return True
6779
Right () -> do
6880
putStrLn $ "Running " ++ prog ++ " on " ++ dataf ++ " did not fail"
6981
return False
7082

7183
-- | Run the prog on the given data file and return it's success or failure
72-
checkRun :: FilePath -> FilePath -> IO (Either String ())
73-
checkRun prog dataf = do
84+
checkRun :: Options -> FilePath -> FilePath -> IO (Either String ())
85+
checkRun opts prog dataf = do
7486
res <- try $ do
7587
ctd <- parseScript $ testsdir ++ prog
76-
runCTDFile (interpret ctd) $ testsdir ++ dataf
88+
runCTDFile opts (interpret ctd) $ testsdir ++ dataf
7789
case res of
7890
Left e -> return $ Left $ show (e :: SomeException)
7991
Right (Left e) -> return $ Left e

haskell_edsl/src/Checktestdata.hs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module Checktestdata (
2929

3030
import Checktestdata.Core
3131
import Checktestdata.Derived
32+
import Checktestdata.Options
3233

3334
import System.Environment ( getArgs, getProgName )
3435
import System.Exit ( exitFailure, exitSuccess)
@@ -37,16 +38,17 @@ import System.IO ( hPutStrLn, stderr)
3738
import qualified Data.ByteString.Char8 as BS
3839

3940
-- | Run a checktestdata script on a file
40-
runCTDFile :: CTD a -> FilePath -> IO (Either String a)
41-
runCTDFile sc fp = do
41+
runCTDFile :: Options -> CTD a -> FilePath -> IO (Either String a)
42+
runCTDFile opts sc fp = do
4243
f <- BS.readFile fp
43-
return $ runCTD sc f
44+
return $ runCTD opts sc f
4445

4546
-- | Main function that reads the commandline arguments
4647
-- and takes either a filename or reads from stdin.
4748
ctdMain :: CTD a -> IO ()
4849
ctdMain sc = do
4950
args <- getArgs
51+
-- todo: add -w options to commandline arguments
5052
bs <- case args of
5153
[] -> BS.getContents
5254
["-"] -> BS.getContents
@@ -57,7 +59,7 @@ ctdMain sc = do
5759
putStrLn $ " " ++ nm ++ " data.in"
5860
putStrLn $ " " ++ nm ++ " < data.in"
5961
exitFailure
60-
case runCTD sc bs of
62+
case runCTD defaultOptions sc bs of
6163
Left err -> do
6264
hPutStrLn stderr err
6365
exitFailure

haskell_edsl/src/Checktestdata/Core.hs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module Checktestdata.Core (
44
-- * Core representation
55
CTD,
66
runCTD,
7+
getOptions,
78

89
-- * Primitives
910
peekChar,
@@ -17,11 +18,15 @@ module Checktestdata.Core (
1718
regex,
1819
eof,
1920
isEOF,
21+
greedyWhitespace,
22+
23+
-- * Helpers
24+
isSpaceNoNewline
2025
) where
2126

22-
import Checktestdata.Options ( FloatOption (..) )
27+
import Checktestdata.Options ( FloatOption (..), Options (..) )
2328

24-
import Data.Char ( isDigit, toUpper )
29+
import Data.Char ( isDigit, toUpper, isSpace )
2530
import Data.Maybe ( fromMaybe, isJust )
2631
import Data.ByteString.Char8 ( ByteString )
2732
import qualified Data.ByteString.Char8 as BS
@@ -40,6 +45,8 @@ import Control.Monad.Trans.Either
4045

4146
-- | Fields in the internal state
4247
data InternalState = InternalState {
48+
-- | The options that were set
49+
options :: Options,
4350
-- | The full input
4451
full_input :: ByteString,
4552
-- | The remaining part of the input
@@ -78,16 +85,22 @@ instance Monad CTD where
7885
-- | Run a checktestdata script on the given input and return either
7986
-- an error or the result of the script. The 'eof' combinator is appended
8087
-- to this script to ensure that the full input is consumed.
81-
runCTD :: CTD a -> ByteString -> Either String a
82-
runCTD sc inp = flip evalState initst $ runEitherT $ f $ sc <* eof where
88+
runCTD :: Options -> CTD a -> ByteString -> Either String a
89+
runCTD opts sc inp = flip evalState initst $ runEitherT $ f $ pr where
90+
pr | whitespace_ok opts = greedyWhitespace *> sc <* greedyWhitespace <* eof
91+
| otherwise = sc <* eof
8392
initst :: InternalState
84-
initst = InternalState inp inp 0
93+
initst = InternalState opts inp inp 0
8594
f :: CTD a -> InternalMonad a
8695
f (Pure a) = return a
8796
f (Apply a b) = f a <*> f b
8897
f (Bind a b) = f a >>= f . b
8998
f (PrimOp g) = g
9099

100+
-- | Get the options that were set
101+
getOptions :: CTD Options
102+
getOptions = PrimOp $ get >>= return . options
103+
91104
--------------------------------------------------------------------------------
92105
-- Error handling
93106
--------------------------------------------------------------------------------
@@ -302,3 +315,13 @@ getRemaining = do
302315
-- Update last_start, which we use for error messages
303316
put $ st { last_start = BS.length $ remaining st }
304317
return $ remaining st
318+
319+
-- | Helper, check that the character is a space but not a newline
320+
isSpaceNoNewline :: Char -> Bool
321+
isSpaceNoNewline c = isSpace c && c /= '\n'
322+
323+
-- | Greedily match all whitespace except newline characters
324+
greedyWhitespace :: CTD ()
325+
greedyWhitespace = PrimOp $ do
326+
cs <- getRemaining
327+
putRemaining $ BS.dropWhile isSpaceNoNewline cs

haskell_edsl/src/Checktestdata/Derived.hs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module Checktestdata.Derived (
1414
unique ) where
1515

1616
import Checktestdata.Core
17-
import Checktestdata.Options ( FloatOption (..) )
17+
import Checktestdata.Options ( FloatOption (..), Options (..) )
1818

1919
import Control.Monad ( when )
2020
import qualified Data.Set as Set
@@ -73,16 +73,22 @@ showF x = show (fromRat x :: Double)
7373
-- | Check that the next character is a space.
7474
space :: CTD ()
7575
space = do
76+
opts <- getOptions
7677
c <- nextChar
77-
when (c /= ' ') $
78+
when (c /= ' ' && (not (whitespace_ok opts && isSpaceNoNewline c))) $
7879
fail $ "Space expected"
80+
when (whitespace_ok opts) $ greedyWhitespace
7981

8082
-- | Check that the next character is a newline.
8183
newline :: CTD ()
8284
newline = do
85+
opts <- getOptions
86+
when (whitespace_ok opts) $ greedyWhitespace
8387
c <- nextChar
8488
when (c /= '\n') $
8589
fail $ "Newline expected"
90+
when (whitespace_ok opts) $ greedyWhitespace
91+
8692

8793
-- | Match any of the given characters
8894
match :: String -> CTD Bool

haskell_edsl/src/Checktestdata/Options.hs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module Checktestdata.Options (
22
FloatOption (..),
3+
Options (..),
4+
defaultOptions
35
) where
46

57
--------------------------------------------------------------------------------
@@ -13,3 +15,17 @@ data FloatOption
1315
| Scientific
1416
| Fixed
1517
deriving (Show)
18+
19+
-- | The options the user can set via the commandline or programatically.
20+
data Options = Options {
21+
-- | When set to 'True', whitespace changes are accepted, including heading
22+
-- and training whitespace, but not newlines. Be careful: extra whitespace
23+
-- matches greedily!
24+
whitespace_ok :: Bool
25+
}
26+
27+
-- | The default values for 'Options'
28+
defaultOptions :: Options
29+
defaultOptions = Options {
30+
whitespace_ok = False
31+
}

0 commit comments

Comments
 (0)