module RainParseTest (tests) where import qualified RainParse as RP import qualified AST as A import Text.ParserCombinators.Parsec (runParser,eof) import Test.HUnit import Metadata (Meta,emptyMeta) import Prelude hiding (fail) import TestUtil import CompState data ParseTest a = Show a => ExpPass (String, RP.RainParser a , (a -> Assertion)) | ExpFail (String, RP.RainParser a) pass :: Show a => (String, RP.RainParser a , (a -> Assertion)) -> ParseTest a pass x = ExpPass x fail :: Show a => (String, RP.RainParser a) -> ParseTest a fail x = ExpFail x --Runs a parse test, given a tuple of: (source text, parser function, assert) testParsePass :: Show a => (String, RP.RainParser a , (a -> Assertion)) -> Assertion testParsePass (text,prod,test) = case (runParser parser emptyState "" text) of Left error -> assertString (show error) Right result -> ((return result) >>= test) where parser = do { p <- prod ; eof ; return p} --Adding the eof parser above ensures that all the input is consumed from a test. Otherwise --tests such as "seq {}}" would succeed, because the final character simply wouldn't be parsed - --which would ruin the point of the test testParseFail :: Show a => (String, RP.RainParser a) -> Assertion testParseFail (text,prod) = case (runParser parser emptyState "" text) of Left error -> return () Right result -> assertFailure ("Test was expected to fail:\n***BEGIN CODE***\n" ++ text ++ "\n*** END CODE ***\n") where parser = do { p <- prod ; eof ; return p} testExp0 = pass ("b",RP.expression, assertEqual "Variable Expression Test" (exprVariable "b") ) testExp1 = pass ("b == c",RP.expression, assertEqual "Operator Expression Test" $ A.Dyadic emptyMeta A.Eq (exprVariable "b") (exprVariable "c") ) --Helper function for ifs: makeIf :: [(A.Expression,A.Process)] -> A.Process makeIf list = A.If m $ A.Several m (map makeChoice list) where makeChoice :: (A.Expression,A.Process) -> A.Structured makeChoice (exp,proc) = A.OnlyC m $ A.Choice m exp proc dyExp :: A.DyadicOp -> A.Variable -> A.Variable -> A.Expression dyExp op v0 v1 = A.Dyadic m op (A.ExprVariable m v0) (A.ExprVariable m v1) makeAssign :: A.Variable -> A.Expression -> A.Process makeAssign v e = A.Assign m [v] $ A.ExpressionList m [e] makeLiteralString :: String -> A.Expression makeLiteralString str = A.Literal m (A.Array [A.Dimension 1] A.Byte) (A.ArrayLiteral m (map makeLiteralChar str)) where makeLiteralChar :: Char -> A.ArrayElem makeLiteralChar c = A.ArrayElemExpr $ A.Literal m A.Byte (A.ByteLiteral m (show (fromEnum c))) data EachType = Seq | Par makeEach :: EachType -> String -> A.Type -> A.Expression -> A.Process -> A.Process makeEach ty loopVar listType listVar body = case listSpec of Nothing -> A.Seq m builtRep Just lspec -> A.Seq m $ A.Spec m lspec builtRep where -- Possibly put list into temporary: (actualListVar,listSpec) = calcListVar listVar listType -- Produce the loop using a replicator: rep = A.For m (simpleName (loopVar ++ ".index")) (A.Literal m A.Int (A.IntLiteral m "0")) (A.SizeExpr m (A.ExprVariable m actualListVar)) -- Add a specification for abbreviating the array item: spec = A.Specification m (simpleName loopVar) (A.Is m A.Abbrev A.Byte (A.SubscriptedVariable m (A.Subscript m (A.ExprVariable m (variable (loopVar ++ ".index")))) actualListVar) ) --TODO workout where the SEQ/PAR distinction goes builtRep = A.Rep m rep (A.Spec m spec (A.OnlyP m body)) calcListVar :: A.Expression -> A.Type -> (A.Variable,Maybe A.Specification) calcListVar (A.ExprVariable _ v) _ = (v,Nothing) --HACK! need proper nonce calcListVar v ty = (variable var,Just $ A.Specification m (simpleName var) (A.IsExpr m A.ValAbbrev ty v)) where var = "listvar" testIf :: [ParseTest A.Process] testIf = [ pass ("if (a) ;",RP.statement, assertEqual "If Test 0" $ makeIf [(exprVariable "a",A.Skip m),(A.True m,A.Skip m)]) ,pass ("if (a) ; else ;",RP.statement, assertEqual "If Test 1" $ makeIf [(exprVariable "a",A.Skip m),(A.True m,A.Skip m)]) ,pass ("if (a) ; else a = b;",RP.statement, assertEqual "If Test 2" $ makeIf [(exprVariable "a",A.Skip m),(A.True m,makeSimpleAssign "a" "b")]) ,pass ("if (a) ; else if (b) ; ",RP.statement, assertEqual "If Test 3" $ makeIf [(exprVariable "a",A.Skip m),(A.True m,makeIf [(exprVariable "b",A.Skip m),(A.True m,A.Skip m)])]) ,pass ("if (a) ; else if (b) ; else ; ",RP.statement, assertEqual "If Test 4" $ makeIf [(exprVariable "a",A.Skip m),(A.True m,makeIf [(exprVariable "b",A.Skip m),(A.True m,A.Skip m)])]) ,pass ("if (a) c = d; else if (b) e = f; else g = h;",RP.statement, assertEqual "If Test 5" $ makeIf [(exprVariable "a",makeSimpleAssign "c" "d"),(A.True m,makeIf [(exprVariable "b",makeSimpleAssign "e" "f"),(A.True m,makeSimpleAssign "g" "h")])]) --TODO add fail tests, maybe {} brackets ] testAssign :: [ParseTest A.Process] testAssign = [ pass ("a = b;",RP.statement, assertEqual "Assign Test 0" $ makeSimpleAssign "a" "b") ,fail ("a != b;",RP.statement) ,pass ("a += b;",RP.statement, assertEqual "Assign Test 1" $ makeAssign (variable "a") (dyExp A.Plus (variable ("a")) (variable ("b")) ) ) ,fail ("a + = b;",RP.statement) ] testWhile :: [ParseTest A.Process] testWhile = [ pass ("while (a) ;",RP.statement, assertEqual "While Test" $ A.While emptyMeta (exprVariable "a") (A.Skip emptyMeta) ) ,fail ("while (a)",RP.statement) ,fail ("while () ;",RP.statement) ,fail ("while () {}",RP.statement) ,fail ("while ;",RP.statement) ,fail ("while {}",RP.statement) ,fail ("while ",RP.statement) ] testSeq :: [ParseTest A.Process] testSeq = [ pass ("seq { }",RP.statement, assertEqual "Empty Seq Test" $ A.Seq m $ A.Several m [] ) ,pass ("seq { ; ; }",RP.statement, assertEqual "Seq Skip Test" $ A.Seq m $ A.Several m [(A.OnlyP m (A.Skip m)),(A.OnlyP m (A.Skip m))] ) ,pass ("{ }",RP.statement, assertEqual "Empty Unlabelled-Seq Test" $ A.Seq m $ A.Several m [] ) ,pass ("{ ; ; }",RP.statement, assertEqual "Unlabelled-Seq Skip Test" $ A.Seq m $ A.Several m [(A.OnlyP m (A.Skip m)),(A.OnlyP m (A.Skip m))] ) ,pass ("{ { } }",RP.statement, assertEqual "Unlabelled-Seq Nest Test 0" $ A.Seq m $ A.Several m [A.OnlyP m $ A.Seq m (A.Several m [])] ) ,pass ("seq { { } }",RP.statement, assertEqual "Unlabelled-Seq Nest Test 1" $ A.Seq m $ A.Several m [A.OnlyP m $ A.Seq m (A.Several m [])] ) ,pass ("{ seq { } }",RP.statement, assertEqual "Unlabelled-Seq Nest Test 2" $ A.Seq m $ A.Several m [A.OnlyP m $ A.Seq m (A.Several m [])] ) ,pass ("{ ; {} }",RP.statement, assertEqual "Unlabelled-Seq Nest Test 3" $ A.Seq m $ A.Several m [(A.OnlyP m (A.Skip m)),(A.OnlyP m $ A.Seq m (A.Several m []))] ) ,pass ("seq { ; {} }",RP.statement, assertEqual "Unlabelled-Seq Nest Test 4" $ A.Seq m $ A.Several m [(A.OnlyP m (A.Skip m)),(A.OnlyP m $ A.Seq m (A.Several m []))] ) ,pass ("{ ; seq {} }",RP.statement, assertEqual "Unlabelled-Seq Nest Test 5" $ A.Seq m $ A.Several m [(A.OnlyP m (A.Skip m)),(A.OnlyP m $ A.Seq m (A.Several m []))] ) ,fail ("seq",RP.statement) ,fail ("seq ;",RP.statement) ,fail ("seq {",RP.statement) ,fail ("seq }",RP.statement) ,fail ("{",RP.statement) ,fail ("}",RP.statement) ,fail ("seq seq {}",RP.statement) ,fail ("seq seq",RP.statement) ,fail ("seq {}}",RP.statement) ,fail ("seq {{}",RP.statement) --should fail, because it is two statements, not one: ,fail ("seq {};",RP.statement) ,fail ("{};",RP.statement) ] testPar :: [ParseTest A.Process] testPar = [ pass ("par { }",RP.statement, assertEqual "Empty Par Test" $ A.Par m A.PlainPar $ A.Several m [] ) ,pass ("par { ; ; }",RP.statement, assertEqual "Par Skip Test" $ A.Par m A.PlainPar $ A.Several m [(A.OnlyP m (A.Skip m)),(A.OnlyP m (A.Skip m))] ) ] --Returns the list of tests: tests :: Test tests = TestList [ parseTest testExp0,parseTest testExp1, parseTests testWhile, parseTests testSeq, parseTests testPar, parseTests testIf, parseTests testAssign ] --TODO test: -- input (incl. ext input) -- output -- alting --TODO later on: -- types (lists, tuples, maps) -- functions -- typedefs where parseTest :: Show a => ParseTest a -> Test parseTest (ExpPass test) = TestCase (testParsePass test) parseTest (ExpFail test) = TestCase (testParseFail test) parseTests :: Show a => [ParseTest a] -> Test parseTests tests = TestList (map parseTest tests)