packages feed

reorder-expression-0.1.0.0: test/Expression/ReorderSpec.hs

{-# LANGUAGE OverloadedLists #-}

module Expression.ReorderSpec
    ( spec
    ) where

import Data.Bifunctor (first, second)
import Data.List.NonEmpty (NonEmpty)
import Test.Hspec
import Test.Expr (parenthesize, parenthesizeVerbose)
import Test.Parser (expr)
import Expression.Reorder (Validation(..), Ambiguity(..), reorder)

{- Input:

* @(...)@ is the association for the parser (default priority is postfix, prefix, then left infix).
* @{...}@ is grouping.
* @<n.@ is an @infixl n@ operator.
* @>n.@ is an @infixr n@ operator.
* @=n.@ is an @infix n@ operator.
* @~n.@ is a @prefix n@ operator.
* @!n.@ is a @postfix n@ operator.
* @\@{x, y, ...}@ is a @postfix 99@ operator that can contain many expressions.

Output:

* @(...)@ is the actual tree structure of the expression.
* @[i:s-e]@ means the operator node has index @i@ and spans @s@ to @e@ in the source.
* Everything else is the same.
-}

-- Include source position and operator index in output.
(~=) :: String -> String -> Expectation
l ~= r = parenthesizeVerbose l <$> reorder (expr l) `shouldBe` Success r

-- Only include parentheses.
(~~) :: String -> String -> Expectation
l ~~ r = parenthesize <$> reorder (expr l) `shouldBe` Success r

-- Test that the offending expression is correct.
(~!) :: String -> NonEmpty (Ambiguity, String) -> Expectation
l ~! r = f (reorder (expr l)) `shouldBe` Failure r
    where
        f = (first . fmap . second) parenthesize

spec :: Spec
spec = do
    describe "reorder" $ do
        it "keeps closed both sides (atoms)" $ do
            "a <1. b"
            ~~ "(a <1. b)"

        it "keeps closed both sides (postfix, prefix)" $ do
            "a !1. <1. ~1. b"
            ~~ "((a !1.) <1. (~1. b))"

        it "keeps right associative" $ do
            "a >4. (b >4. c)"
            ~~ "(a >4. (b >4. c))"

        it "reorders left associativity out" $ do
            "(a <1. b) <2. c"
            ~~ "(a <1. (b <2. c))"

        it "keeps stronger left associativity" $ do
            "(a <2. b) <1. c"
            ~~ "((a <2. b) <1. c)"

        it "reorders stronger left, weaker right" $ do
            "(a <7. b) <5. (c <3. d)"
            ~~ "(((a <7. b) <5. c) <3. d)"

        it "reorders weaker left, stronger right" $ do
            "(a <3. b) <5. (c <7. d)"
            ~~ "(a <3. (b <5. (c <7. d)))"

        it "reorders weaker both sides" $ do
            "(a <4. b) <5. (c <3. d)"
            ~~ "((a <4. (b <5. c)) <3. d)"

        it "reorders weaker (equal left assoc) both sides" $ do
            "(a <3. b) <5. (c <3. d)"
            ~~ "((a <3. (b <5. c)) <3. d)"

        it "reorders weaker (equal right assoc) both sides" $ do
            "(a >3. b) <5. (c >3. d)"
            ~~ "(a >3. ((b <5. c) >3. d))"

        it "reorders weaker left, equal (assoc left) right" $ do
            "(a <3. b) <5. (c <5. d)"
            ~~ "(a <3. ((b <5. c) <5. d))"

        it "reorders weaker left, equal (assoc right) right" $ do
            "(a <3. b) >5. (c >5. d)"
            ~~ "(a <3. (b >5. (c >5. d)))"

        it "reorders equal (assoc left) left, weaker right" $ do
            "(a <5. b) <5. (c <3. d)"
            ~~ "(((a <5. b) <5. c) <3. d)"

        it "reorders equal (assoc right) left, weaker right" $ do
            "(a >5. b) >5. (c <3. d)"
            ~~ "((a >5. (b >5. c)) <3. d)"

        it "is ambiguous when equal (different assoc) left, weaker right" $ do
            "(a <5. b) >5. (c <3. d)"
            ~! [(AmbiguityMismatchAssoc, "((a <5. b) >5. c)")]

        it "is ambiguous when weaker (equal no assoc) both sides" $ do
            "(a =3. b) <5. (c =3. d)"
            ~! [(AmbiguityAssocNone, "((a =3. b) <5. (c =3. d))")]

        it "is ambiguous when weaker (different assoc) both sides" $ do
            "(a <3. b) =5. (c >3. d)"
            ~! [(AmbiguityMismatchAssoc, "((a <3. b) =5. (c >3. d))")]

        it "is ambiguous when equal (different assoc) both sides" $ do
            "(a <5. b) =5. (c >5. d)"
            ~! [(AmbiguityMismatchAssoc, "((a <5. b) =5. (c >5. d))")]

        it "is ambiguous when stronger left, equal (different assoc) right" $ do
            "(a <7. b) <5. (c >5. d)"
            ~! [(AmbiguityMismatchAssoc, "(b <5. (c >5. d))")]

        it "is ambiguous when equal (different assoc) left, stronger right" $ do
            "(a >5. b) <5. (c =7. d)"
            ~! [(AmbiguityMismatchAssoc, "((a >5. b) <5. c)")]

        it "reorders higher fixity in right-hand side" $ do
            "a <3. b <4. c"
            ~= "(a <3.[0:0-13] (b <4.[1:6-13] c))"

        it "keeps correct left associativity" $ do
            "a <3. b <3. c"
            ~= "((a <3.[0:0-7] b) <3.[1:0-13] c)"

        it "reorders a postfix into a binary" $ do
            "(a <1. b) !2."
            ~~ "(a <1. (b !2.))"

        it "reorders a prefix into a binary" $ do
            "~2. (a <1. b)"
            ~~ "((~2. a) <1. b)"

        it "reorders postfix over prefix" $ do
            "~2. (a !1.)"
            ~= "((~2.[0:0-6] a) !1.[1:0-10])"

        it "reorders prefix over postfix" $ do
            "(~1. a) !2."
            ~= "(~1.[0:1-11] (a !2.[1:5-11]))"

        it "reorders alternating prefix and postfix" $ do
            "~2. ~4. a !5. !3."
            ~= "(~2.[0:0-17] ((~4.[1:4-13] (a !5.[2:8-13])) !3.[3:4-17]))"

        it "reorders right associative operator" $ do
            "a >4. b >4. c"
            ~= "(a >4.[0:0-13] (b >4.[1:6-13] c))"

        it "reorders a prefix operator outside" $ do
            "~1. a <2. b"
            ~= "(~1.[0:0-11] (a <2.[1:4-11] b))"

        it "keeps a prefix operator inside" $ do
            "~2. a <1. b"
            ~= "((~2.[0:0-5] a) <1.[1:0-11] b)"

        it "reorders a postfix operator outside" $ do
            "a <2. b !1."
            ~= "((a <2.[0:0-7] b) !1.[1:0-11])"

        it "reorders higher fixity on both sides" $ do
            "a <2. b <1. c <2. d"
            ~= "((a <2.[0:0-7] b) <1.[1:0-19] (c <2.[2:12-19] d))"

        it "reorders inside leaves" $ do
            "{a <3. b <4. c}"
            ~="{(a <3.[0:1-14] (b <4.[1:7-14] c))}"

        it "reorders inside many leaves" $ do
            "{a <3. b <4. c} @{a <3. b <4. c, a <3. b <4. c}"
            ~= "({(a <3.[0:1-14] (b <4.[1:7-14] c))} @[2:0-47]{(a <3.[3:18-31] (b <4.[4:24-31] c)), (a <3.[5:33-46] (b <4.[6:39-46] c))})"

        it "reorders something complicated" $ do
            "~2. ~4. {a >3. {b >3. c} <4. d <4. e =1. f} !5. !3."
            ~= "(~2.[0:0-51] ((~4.[1:4-47] ({((a >3.[2:9-36] (({(b >3.[3:16-23] c)} <4.[4:15-30] d) <4.[5:15-36] e)) =1.[6:9-42] f)} !5.[7:8-47])) !3.[8:4-51]))"

        it "is ambiguous when chaining non-associative" $ do
            "a =1. b =1. c"
            ~! [(AmbiguityAssocNone, "((a =1. b) =1. c)")]

        it "is ambiguous when same precedence prefix and postfix" $ do
            "~1. a !1."
            ~! [(AmbiguityMismatchAssoc, "((~1. a) !1.)")]

        it "is ambiguous when same precedence left and right associative" $ do
            "a <3. b >3. c"
            ~! [(AmbiguityMismatchAssoc, "((a <3. b) >3. c)")]

        it "finds multiple ambiguities in closed children" $ do
            "a @{a =1. b =1. c, a =2. b =2. c}"
            ~! [ (AmbiguityAssocNone, "((a =1. b) =1. c)")
               , (AmbiguityAssocNone, "((a =2. b) =2. c)")
               ]

        it "finds multiple ambiguities in open children" $ do
            "{a =1. b =1. c} <1. {a =2. b =2. c}"
            ~! [ (AmbiguityAssocNone, "((a =1. b) =1. c)")
               , (AmbiguityAssocNone, "((a =2. b) =2. c)")
               ]

        it "finds multiple ambiguities in open and closed children" $ do
            "{a =1. b =1. c} @{a =2. b =2. c}"
            ~! [ (AmbiguityAssocNone, "((a =1. b) =1. c)")
               , (AmbiguityAssocNone, "((a =2. b) =2. c)")
               ]

        it "reorders something really complicated" $ do
            "~2. a <4. (b <4. c) >5. (d >5. (e =3. f) !7.) =1. g"
            ~~ "((~2. (((a <4. b) <4. (c >5. (d >5. e))) =3. (f !7.))) =1. g)"

        it "reorders binary containing binary and postfix" $ do
            "(a <2. b) <2. (c !1.)" ~~ "(((a <2. b) <2. c) !1.)"

        it "reorders binary containing prefix and binary" $ do
            "(~1. a) <2. (b <2. c)" ~~ "(~1. ((a <2. b) <2. c))"

        it "reorders postfix and prefix in a binary" $ do
            "(~1. a) <3. (c !2.)" ~~ "(~1. ((a <3. c) !2.))"

        it "is ambiguous when all same precedence, all different associativity" $ do
            "(a <3. b) =3. (c >3. d)"
            ~! [(AmbiguityMismatchAssoc, "((a <3. b) =3. (c >3. d))")]

        it "is ambiguous when all same precedence, left different associativity" $ do
            "(a <3. b) >3. (c >3. d)"
            ~! [(AmbiguityMismatchAssoc, "((a <3. b) >3. c)")]

        it "is ambiguous when all same precedence, right different associativity" $ do
            "(a <3. b) <3. (c >3. d)"
            ~! [(AmbiguityMismatchAssoc, "(b <3. (c >3. d))")]

        it "is ambiguous when all same precedence, no associativity" $ do
            "(a =3. b) =3. (c =3. d)"
            ~! [(AmbiguityAssocNone, "((a =3. b) =3. (c =3. d))")]

        it "reorders all same precedence, left associative" $ do
            "(a <3. b) <3. (c <3. d)"
            ~~ "(((a <3. b) <3. c) <3. d)"

        it "reorders all same precedence, right associative" $ do
            "(a >3. b) >3. (c >3. d)"
            ~~ "(a >3. (b >3. (c >3. d)))"