------------------------------------------------------------------------ -- Tests the performance and correctness of the precedence handling ------------------------------------------------------------------------ {-# LANGUAGE FlexibleContexts #-} -- Summary: The memoising backtracking parser seems to be fast enough. -- -- Note that if this code is not fast enough, then we can apply -- another optimisation: pruning the graph, keeping only those nodes -- which have operator parts occurring in the expression at hand. I -- think that this covers all practical cases. If someone actually -- tries to make things hard for the parser, then it might not, -- though. -- -- One can guarantee polynomial complexity of parsing (for -- context-free grammars) by, among other things, using a dense -- representation of ambiguous results. However, the most ambiguous -- result in the test below has length 2, and I think that we won't -- encounter very much more than that in practice. module Main where import qualified Memoised import qualified ExpressionParser as Expr import Parser import PrecedenceGraph hiding (tests) import Name hiding (tests) import Expression hiding (tests) import Token hiding (tests) import Control.Monad.State hiding (lift) import Data.Char import Data.List import qualified Data.Map as Map import qualified Data.Set as Set import Data.Set (Set) import Data.Function import qualified Control.Applicative as A import Prelude hiding (lex) ------------------------------------------------------------------------ -- Test driver test :: Set Name -> Set Name -> PrecedenceGraph -> String -> [Expr] -> IO Bool test closed names g s es = case Token.lex s of Nothing -> do putStrLn $ "Lex error: " ++ show s return (es == []) Just ts -> do let es' = Expr.parse g (lookupName names) closed ts correct = sort es' == sort es isOK = if correct then "OK" else "Not OK" putStrLn $ pad 40 (show s) ++ pad 8 (isOK ++ ": ") ++ pad 90 (show es') return correct where pad n s = take n s ++ replicate (n - length s) ' ' ++ " " main = do ok <- tests putStrLn $ if ok then "All tests passed." else "Some test failed." ------------------------------------------------------------------------ -- Example precedence graph lift :: (s -> s) -> State s () lift f = State (\x -> ((), f x)) eq = Name [] (Just Infix) ["="] ltgt = Name [] (Just Infix) ["<",">"] plus = Name [] (Just Infix) ["+"] plus' = Name [] (Just Infix) ["+'"] minus = Name [] (Just Infix) ["-"] mul = Name [] (Just Infix) ["*"] div' = Name [] (Just Infix) ["/"] pow = Name [] (Just Infix) ["^"] or' = Name [] (Just Infix) ["||"] not' = Name [] (Just Prefix) ["!"] and' = Name [] (Just Infix) ["&&"] eq' = Name [] (Just Infix) ["=="] ite = Name [] (Just Prefix) ["if", "then", "else"] it = Name [] (Just Prefix) ["if", "then"] ox = Name [] (Just Postfix) ["<[","]>"] oxstar = Name [] (Just Postfix) ["<[","]>*"] oxplus = Name [] (Just Prefix) ["<[","]>+"] foo1 = Name ["1"] (Just Infix) ["foo"] foo2 = Name ["2"] (Just Infix) ["foo"] llgg = Name [] (Just Infix) ["<<",">>"] ggll = Name [] (Just Infix) [">>","<<"] ox' = Name [] Nothing ["[[","]]"] ox'star = Name [] Nothing ["[[","]]*"] ox'plus = Name [] Nothing ["[[","]]+"] -- Note that this graph is not intended to be representative of how I -- want operator precedences to be specified for the given operators. example :: PrecedenceGraph example = flip execState empty $ mapM lift [ unrelated eq Non , unrelated ltgt Non , bindsBetween plus L [eq] [] , bindsAs plus' L plus , bindsAs minus R plus , bindsBetween mul L [plus] [] , bindsAs div' L mul , bindsBetween pow R [mul] [] , bindsBetween or' R [eq] [] , bindsBetween not' Non [or'] [] , bindsBetween and' R [or'] [not', plus] , bindsBetween eq' Non [] [or'] , bindsBetween ite Non [eq] [and', mul] , bindsAs it Non ite , unrelated ox Non , bindsAs oxstar Non ox , bindsAs oxplus Non ox , unrelated foo1 L , unrelated foo2 R , unrelated llgg L , unrelated ggll Non ] exampleClosed = Set.fromList [ox', ox'star, ox'plus] exampleNames = Set.unions $ Map.elems (allOperators example) ++ [ exampleClosed , Set.fromList $ map (Name [] Nothing . (: [])) ["x", "y", "z", "a", "b", "c", "d", "f", "g"] ] ------------------------------------------------------------------------ -- Looking up names -- | A smarter data structure should be used here. lookupName :: Set Name -> Name -> Set Name lookupName names n = Set.filter p names where p n' = n' == n { moduleName = drop (length mn - length mn') mn } where mn = moduleName n mn' = moduleName n' ------------------------------------------------------------------------ -- A demanding graph -- stressTest n yields a graph which is the transitive closure of a -- graph with the following shape (with edges going upwards): -- -- ⋱ ⋮ ⋰ -- ⋱ ⋮ ⋰ -- ⋱⋮⋰ -- n₂ -- ╱│╲ -- ╱ │ ╲ -- ╱ │ ╲ -- a₂ b₂ c₂ -- ╲ │ ╱ -- ╲ │ ╱ -- ╲│╱ -- n₁ -- ╱│╲ -- ╱ │ ╲ -- ╱ │ ╲ -- a₁ b₁ c₁ -- ╲ │ ╱ -- ╲ │ ╱ -- ╲│╱ -- n₀ -- -- The top-most node is n_n. stressTest :: Integer -> ([Name], PrecedenceGraph) stressTest n = if n <= 0 then let n = stressTestName 0 'n' in ([n], unrelated n Non empty) else ( topName : names ++ below , flip execState g $ do mapM_ (\n -> lift $ bindsBetween n Non below []) names lift $ bindsBetween topName Non (names ++ below) []) where (below, g) = stressTest (n - 1) prev = stressTestName (pred n) 'n' names = map (stressTestName n) ['a'..'c'] topName = stressTestName n 'n' stressTestName n c = Name [] (Just Infix) [c : show n] stressTestNames :: Integer -> Set Name stressTestNames n = Set.fromList $ Name [] Nothing ["x"] : stressTestName 0 'n' : [ stressTestName i c | i <- [1 .. n], c <- "abcn" ] ------------------------------------------------------------------------ -- Tests tests :: IO Bool tests = fmap and $ sequence [ test' "x" [fun "x"] , test' "x = x" [Op eq [jF "x", jF "x"]] , test' "x = x = x" [] , test' "x < x > x" [Op ltgt [jF "x", jF "x", jF "x"]] , test' "x < x = x > x" [Op ltgt [jF "x", jOp eq [jF "x", jF "x"], jF "x"]] , test' "x + x" [Op plus [jF "x", jF "x"]] , test' "x + y + z" [Op plus [jOp plus [jF "x", jF "y"], jF "z"]] , test' "x - y" [Op minus [jF "x", jF "y"]] , test' "x + y - z" [] , test' "x * y / z" [Op div' [jOp mul [jF "x", jF "y"], jF "z"]] , test' "x * y = z" [] , test' "x ^ y = z" [] , test' "x + y && z" [Op and' [jOp plus [jF "x", jF "y"], jF "z"]] , test' "x ^ y ^ z" [Op pow [jF "x", jOp pow [jF "y", jF "z"]]] , test' "! x" [Op not' [jF "x"]] , test' "! ! x" [Op not' [jOp not' [jF "x"]]] , test' "! x + y" [] , test' "! x && y" [Op and' [jOp not' [jF "x"], jF "y"]] , test' "x <[ x <[ x ]>* ]>" [Op ox [jF "x", jOp oxstar [jF "x", jF "x"]]] , test' "x <[ x ]> <[ x ]>*" [Op oxstar [jOp ox [jF "x", jF "x"], jF "x"]] , test' "x << x >> x << x >> x" [ Op llgg [jF "x", jOp ggll [jF "x", jF "x", jF "x"], jF "x"] , Op llgg [jOp llgg [jF "x", jF "x", jF "x"], jF "x", jF "x"] ] , test' "if x then a else b" [Op ite [jF "x", jF "a", jF "b"]] , test' "if x then if y then a else b else c" [Op ite [jF "x", jOp ite [jF "y", jF "a", jF "b"], jF "c"]] , test' "if x then if y then a else b" [ Op ite [jF "x", jOp it [jF "y", jF "a"], jF "b"] , Op it [jF "x", jOp ite [jF "y", jF "a", jF "b"]] ] , test' "if x then a + b else c = d" [Op eq [jOp ite [jF "x", jOp plus [jF "a", jF "b"], jF "c"], jF "d"]] , test' "x foo x foo x" [ Op foo2 [jF "x", jOp foo2 [jF "x", jF "x"]] , Op foo1 [jOp foo1 [jF "x", jF "x"], jF "x"] ] , test' "x foo x foo x foo x" [ Op foo2 [jF "x", jOp foo2 [jF "x", jOp foo2 [jF "x", jF "x"]]] , Op foo1 [jOp foo1 [jOp foo1 [jF "x", jF "x"], jF "x"], jF "x"] ] , test' "x 1.foo x 1.foo x" [Op foo1 [jOp foo1 [jF "x", jF "x"], jF "x"]] , test' "x 1.foo x 2.foo x" [] , test' "1._foo_" [Op foo1 [p, p]] , test' "2._foo_" [Op foo2 [p, p]] , test' "x 1.foo_" [Op foo1 [jF "x", p]] , test' "1._foo x" [Op foo1 [p, jF "x"]] , test' "_1.foo x" [] , test' "_" [w] , test' "_+_" [Op plus [p, p]] , test' "_ + _" [Op plus [Just w, Just w]] , test' "if_then a + _ else_ = d" [Op eq [jOp ite [p, jOp plus [jF "a", Just w], p], jF "d"]] , test' "if__then a + _ else_ = d" [] , test' "f (_+_)" [app (fun "f") [Op plus [p, p]]] , test' "(_+_) f" [app (Op plus [p, p]) [fun "f"]] , test' "f _+_" [app (fun "f") [Op plus [p, p]]] , test' "f _ +_" [Op plus [jApp (fun "f") [w], p]] , test' "(((f))) (((x))) (((y)))" [app (fun "f") [fun "x", fun "y"]] , test' "(_)" [w] , test' "_<[_]>" [Op ox [p, p]] , test' "_+ _+'_" [Op plus [p, jOp plus' [p, p]]] , test' "_+_ +'_" [Op plus' [jOp plus [p, p], p]] , test' "f (x <[ y ]>) + z" [Op plus [jApp (fun "f") [Op ox [jF "x", jF "y"]], jF "z"]] , test' "f (_+_ <[ y ]>) + z" [Op plus [jApp (fun "f") [Op ox [jOp plus [p, p], jF "y"]], jF "z"]] , test' "f (x <[ _+_ ]>) + z" [Op plus [jApp (fun "f") [Op ox [jF "x", jOp plus [p, p]]], jF "z"]] , test' "f x <[ _+_ ]> + z" [] , test' "f x <[ _+_ ]>" [Op ox [jApp (fun "f") [fun "x"], jOp plus [p, p]]] , test' "f if_then_else_ * z" [Op mul [jApp (fun "f") [Op ite [p, p, p]], jF "z"]] , test' "f (if_then_else_) * z" [Op mul [jApp (fun "f") [Op ite [p, p, p]], jF "z"]] , test' "[[_]]" [Op ox' [p]] , test' "[[ [[ x ]]* ]]" [Op ox' [jOp ox'star [jF "x"]]] , test' "f [[ g [[ x ]]* ]]" [app (fun "f") [Op ox' [jApp (fun "g") [Op ox'star [jF "x"]]]]] , test' (nested nestingDepth) [nestedResult nestingDepth] , test' (assoc nestingDepth) [assocResult nestingDepth] , test'' 6 2 6 , test'' 6 1 5 , test'' 10 1 8 , test'' 12 2 11 , test'' 17 1 17 ] where -- Some abbreviations. fun s = Fun (Name [] Nothing [s]) w = WildcardE p = Nothing -- Placeholder. jF = Just . fun jOp name args = Just $ Op name args jApp expr args = Just $ app expr args test' = test exampleClosed exampleNames example nestingDepth = 100 iterateN n f x = iterate f x !! n nested d = iterateN d (\s -> "x <[ " ++ s ++ " ]>") "x" nestedResult d = iterateN d (\x -> Op ox [jF "x", Just x]) (fun "x") assoc d = iterateN d ("x + " ++) "x" assocResult d = iterateN d (\x -> Op plus [Just x, jF "x"]) (fun "x") test'' m i k | not (m >= k && k > i && i > 0) = error "test'': Precondition failed." | otherwise = test Set.empty (stressTestNames m) (snd $ stressTest m) (unwords ["x", op i 'a', "x", op k 'b', "x"]) [Op (stressTestName i 'a') [ jF "x" , Just $ Op (stressTestName k 'b') [jF "x", jF "x"] ] ] where op i c = c : show i