Skip to content

Commit

Permalink
Refactor tests into separate modules
Browse files Browse the repository at this point in the history
  • Loading branch information
ollef committed Mar 7, 2016
1 parent 8dbf366 commit be85130
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 334 deletions.
2 changes: 1 addition & 1 deletion Earley.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ benchmark bench

test-suite tests
type: exitcode-stdio-1.0
main-is: Tests.hs
main-is: Main.hs
ghc-options: -Wall
hs-source-dirs: tests
default-language: Haskell2010
Expand Down
25 changes: 25 additions & 0 deletions tests/Empty.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{-# LANGUAGE ScopedTypeVariables #-}
module Empty where
import Control.Applicative
import Test.Tasty
import Test.Tasty.QuickCheck as QC

import Text.Earley

tests :: TestTree
tests = testGroup "Empty productions"
[ QC.testProperty "The empty production doesn't parse anything" $
\(input :: String) ->
allParses (parser (return empty :: forall r. Grammar r (Prod r () Char ()))) input
== (,) [] Report { position = 0
, expected = []
, unconsumed = input
}
, QC.testProperty "Many empty productions parse very little" $
\(input :: String) ->
allParses (parser (return $ many empty <* pure "blah" :: forall r. Grammar r (Prod r () Char [()]))) input
== (,) [([], 0)] Report { position = 0
, expected = []
, unconsumed = input
}
]
92 changes: 92 additions & 0 deletions tests/Expr.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{-# LANGUAGE RecursiveDo #-}
module Expr where
import Control.Applicative
import Data.Char
import Test.Tasty
import Test.Tasty.QuickCheck as QC

import Text.Earley

tests :: TestTree
tests = testGroup "Expr"
[ QC.testProperty "Expr: parse . pretty = id" $
\e -> [e] === parseExpr (prettyExpr 0 e)
, QC.testProperty "Ambiguous Expr: parse . pretty ≈ id" $
\e -> e `elem` parseAmbiguousExpr (prettyExpr 0 e)
]

parseExpr :: String -> [Expr]
parseExpr input = fst (fullParses (parser expr) (lexExpr input)) -- We need to annotate types for point-free version

parseAmbiguousExpr :: String -> [Expr]
parseAmbiguousExpr input = fst (fullParses (parser ambiguousExpr) (lexExpr input))

data Expr
= Add Expr Expr
| Mul Expr Expr
| Var String
deriving (Eq, Ord, Show)

instance Arbitrary Expr where
arbitrary = sized arbExpr
where arbIdent = Var <$> elements ["a", "b", "c", "x", "y", "z"]
arbExpr n | n > 0 = oneof [ arbIdent
, Add <$> arbExpr1 <*> arbExpr1
, Mul <$> arbExpr1 <*> arbExpr1
]
where arbExpr1 = arbExpr (n `div` 2)
arbExpr _ = arbIdent

shrink (Var _) = []
shrink (Add a b) = a : b : [ Add a' b | a' <- shrink a ] ++ [ Add a b' | b' <- shrink b ]
shrink (Mul a b) = a : b : [ Mul a' b | a' <- shrink a ] ++ [ Mul a b' | b' <- shrink b ]

expr :: Grammar r (Prod r String String Expr)
expr = mdo
x1 <- rule $ Add <$> x1 <* namedSymbol "+" <*> x2
<|> x2
<?> "sum"
x2 <- rule $ Mul <$> x2 <* namedSymbol "*" <*> x3
<|> x3
<?> "product"
x3 <- rule $ Var <$> (satisfy ident <?> "identifier")
<|> namedSymbol "(" *> x1 <* namedSymbol ")"
return x1
where
ident (x:_) = isAlpha x
ident _ = False

ambiguousExpr :: Grammar r (Prod r String String Expr)
ambiguousExpr = mdo
x1 <- rule $ Add <$> x1 <* namedSymbol "+" <*> x1
<|> x2
<?> "sum"
x2 <- rule $ Mul <$> x2 <* namedSymbol "*" <*> x2
<|> x3
<?> "product"
x3 <- rule $ Var <$> (satisfy ident <?> "identifier")
<|> namedSymbol "(" *> x1 <* namedSymbol ")"
return x1
where
ident (x:_) = isAlpha x
ident _ = False

prettyParens :: Bool -> String -> String
prettyParens True s = "(" ++ s ++ ")"
prettyParens False s = s

prettyExpr :: Int -> Expr -> String
prettyExpr _ (Var s) = s
prettyExpr d (Add a b) = prettyParens (d > 0) $ prettyExpr 0 a ++ " + " ++ prettyExpr 1 b
prettyExpr d (Mul a b) = prettyParens (d > 1) $ prettyExpr 1 a ++ " * " ++ prettyExpr 2 b

-- @words@ like lexer, but consider parentheses as separate tokens
lexExpr :: String -> [String]
lexExpr "" = []
lexExpr ('(' : s) = "(" : lexExpr s
lexExpr (')' : s) = ")" : lexExpr s
lexExpr (c : s)
| isSpace c = lexExpr s
| otherwise = let (tok, rest) = span p (c : s)
in tok : lexExpr rest
where p x = not (x == '(' || x == ')' || isSpace x)
26 changes: 26 additions & 0 deletions tests/InlineAlts.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{-# LANGUAGE RecursiveDo, ScopedTypeVariables #-}
module InlineAlts where
import Control.Applicative
import Test.Tasty
import Test.Tasty.HUnit as HU

import Text.Earley

tests :: TestTree
tests = testGroup "Inline alternatives"
[ HU.testCase "They work" $
let input = "ababbbaaabaa" in
allParses (parser inlineAlts) input @?= allParses (parser nonInlineAlts) input
]

inlineAlts :: Grammar r (Prod r Char Char String)
inlineAlts = mdo
p <- rule $ pure []
<|> (:) <$> (namedSymbol 'a' <|> namedSymbol 'b') <*> p
return p

nonInlineAlts :: Grammar r (Prod r Char Char String)
nonInlineAlts = mdo
ab <- rule $ namedSymbol 'a' <|> namedSymbol 'b'
p <- rule $ pure [] <|> (:) <$> ab <*> p
return p
73 changes: 73 additions & 0 deletions tests/Issue11.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{-# LANGUAGE RecursiveDo #-}
module Issue11 where
import Control.Applicative
import Test.Tasty
import Test.Tasty.HUnit as HU

import Text.Earley
import Text.Earley.Mixfix

tests :: TestTree
tests = testGroup "Issue 11"
[ let x = words "+ + 5 6 7" in
HU.testCase "1" $
fullParses (parser $ grammar LeftAssoc) x
@?= (,) [] Report {position = 1, expected = [], unconsumed = drop 1 x}
, let x = words "+ 5 + 6 7" in
HU.testCase "2" $
fullParses (parser $ grammar LeftAssoc) x
@?= (,) [] Report {position = 2, expected = [], unconsumed = drop 2 x}
, let x = words "+ 5 6" in
HU.testCase "3" $
fullParses (parser $ grammar LeftAssoc) x
@?= (,) [Plus (Var "5") (Var "6")]
Report {position = 3, expected = [], unconsumed = []}
, let x = words "+ + 5 6 7" in
HU.testCase "4" $
fullParses (parser $ grammar RightAssoc) x
@?= (,) [Plus (Plus (Var "5") (Var "6")) (Var "7")]
Report {position = 5, expected = [], unconsumed = []}
, let x = words "+ 5 + 6 7" in
HU.testCase "5" $
fullParses (parser $ grammar RightAssoc) x
@?= (,) [Plus (Var "5") (Plus (Var "6") (Var "7"))]
Report {position = 5, expected = [], unconsumed = []}
, let x = words "+ 5 6" in
HU.testCase "6" $
fullParses (parser $ grammar RightAssoc) x
@?= (,) [Plus (Var "5") (Var "6")]
Report {position = 3, expected = [], unconsumed = []}
, let x = words "+ + 5 6 7" in
HU.testCase "7" $
fullParses (parser $ grammar NonAssoc) x
@?= (,) [Plus (Plus (Var "5") (Var "6")) (Var "7")]
Report {position = 5, expected = [], unconsumed = []}
, let x = words "+ 5 + 6 7" in
HU.testCase "8" $
fullParses (parser $ grammar NonAssoc) x
@?= (,) [Plus (Var "5") (Plus (Var "6") (Var "7"))]
Report {position = 5, expected = [], unconsumed = []}
, let x = words "+ 5 6" in
HU.testCase "9" $
fullParses (parser $ grammar NonAssoc) x
@?= (,) [Plus (Var "5") (Var "6")]
Report {position = 3, expected = [], unconsumed = []}
]

data AST
= Var String
| Plus AST AST
deriving (Eq, Ord, Show)

grammar :: Associativity -> Grammar r (Prod r String String AST)
grammar a = mdo
atomicExpr <- rule $ Var <$> satisfy (/= "+")

expr <- mixfixExpression
[[([Just (symbol "+"), Nothing, Nothing], a)]]
atomicExpr
(\x y -> case (x,y) of
([Just "+", Nothing, Nothing], [e1,e2]) -> Plus e1 e2
_ -> undefined)

return expr
49 changes: 49 additions & 0 deletions tests/Issue14.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module Issue14 where
import Control.Applicative
import Test.Tasty
import Test.Tasty.QuickCheck as QC

import Text.Earley

tests :: TestTree
tests = testGroup "Issue 14"
[ QC.testProperty "The same rule in alternatives gives many results" $
\x -> fullParses (parser (issue14 x)) ""
== (,) (replicate (issue14Length x) ())
Report { position = 0, expected = [], unconsumed = [] }
]

data Issue14 a
= Pure a
| Alt (Issue14 a) (Issue14 a)
| Ap (Issue14 a) (Issue14 a)
deriving (Eq, Ord, Show)

instance Arbitrary a => Arbitrary (Issue14 a) where
arbitrary = sized arbTree
where arbTree n | n > 0 = oneof [ Pure <$> arbitrary
, Alt <$> arbTree1 <*> arbTree1
, Ap <$> arbTree1 <*> arbTree1
]
where arbTree1 = arbTree (n `div` 2)
arbTree _ = Pure <$> arbitrary

shrink (Pure a) = Pure <$> shrink a
shrink (Alt a b) = a : b : [Alt a' b | a' <- shrink a] ++ [Alt a b' | b' <- shrink b]
shrink (Ap a b) = a : b : [Ap a' b | a' <- shrink a] ++ [Ap a b' | b' <- shrink b]

issue14Length :: Issue14 () -> Int
issue14Length (Pure ()) = 1
issue14Length (Alt a b) = issue14Length a + issue14Length b
issue14Length (Ap a b) = issue14Length a * issue14Length b

issue14 :: Issue14 () -> Grammar r (Prod r () Char ())
issue14 tree = do
emptyRule <- rule $ pure ()
let x = go emptyRule tree
return x
where
go x (Pure ()) = x
go x (Alt b1 b2) = go x b1 <|> go x b2
go x (Ap b1 b2) = go x b1 <* go x b2

25 changes: 25 additions & 0 deletions tests/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Main where
import Test.Tasty

import qualified Empty
import qualified Expr
import qualified InlineAlts
import qualified Issue11
import qualified Issue14
import qualified Mixfix
import qualified Optional
import qualified ReversedWords
import qualified VeryAmbiguous

main :: IO ()
main = defaultMain $ testGroup "Tests"
[ Empty.tests
, Expr.tests
, InlineAlts.tests
, Issue11.tests
, Issue14.tests
, Mixfix.tests
, Optional.tests
, ReversedWords.tests
, VeryAmbiguous.tests
]
53 changes: 53 additions & 0 deletions tests/Mixfix.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module Mixfix where
import Control.Applicative
import Test.Tasty
import Test.Tasty.HUnit as HU

import Text.Earley
import Text.Earley.Mixfix

tests :: TestTree
tests = testGroup "Mixfix"
[ HU.testCase "1" $
let x = Ident [Just "x"] in
fullParses (parser mixfixGrammar) (words "if x then x else x")
@?= (,) [App ifthenelse [x, x, x]] Report {position = 6, expected = [], unconsumed = []}
, HU.testCase "2" $
let x = Ident [Just "x"] in
fullParses (parser mixfixGrammar) (words "prefix x postfix")
@?= (,) [App prefix [App postfix [x]]] Report {position = 3, expected = [], unconsumed = []}
, HU.testCase "3" $
let x = Ident [Just "x"] in
fullParses (parser mixfixGrammar) (words "x infix1 x infix2 x")
@?= (,) [App infix1 [x, App infix2 [x, x]]] Report {position = 5, expected = [], unconsumed = []}
, HU.testCase "4" $
let x = Ident [Just "x"] in
fullParses (parser mixfixGrammar) (words "[ x ]")
@?= (,) [App closed [x]] Report {position = 3, expected = [], unconsumed = []}
]

data MixfixExpr = Ident (Holey String) | App (Holey String) [MixfixExpr]
deriving (Eq, Show)

mixfixGrammar :: Grammar r (Prod r String String MixfixExpr)
mixfixGrammar = mixfixExpression table
(Ident . pure . Just <$> namedSymbol "x")
App
where
hident = map (fmap symbol)
table =
[ [(hident ifthenelse, RightAssoc)]
, [(hident prefix, RightAssoc)]
, [(hident postfix, LeftAssoc)]
, [(hident infix1, LeftAssoc)]
, [(hident infix2, RightAssoc)]
, [(hident closed, NonAssoc)]
]

ifthenelse, prefix, postfix, infix1, infix2, closed :: Holey String
ifthenelse = [Just "if", Nothing, Just "then", Nothing, Just "else", Nothing]
prefix = [Just "prefix", Nothing]
postfix = [Nothing, Just "postfix"]
infix1 = [Nothing, Just "infix1", Nothing]
infix2 = [Nothing, Just "infix2", Nothing]
closed = [Just "[", Nothing, Just "]"]
Loading

0 comments on commit be85130

Please sign in to comment.