packages feed

automata-0.1.0.0: test/Main.hs

{-# language DerivingStrategies #-}
{-# language LambdaCase #-}
{-# language ScopedTypeVariables #-}

import Automata.Dfsa (Dfsa)
import Automata.Dfst (Dfst)
import Automata.Nfsa (Nfsa)
import Automata.Nfst (Nfst)
import Control.Monad (forM_,replicateM)
import Data.Enum.Types (B(..),D(..))
import Data.Monoid (All(..))
import Data.Primitive (Array)
import Data.Proxy (Proxy(..))
import Data.Set (Set)
import Test.HUnit ((@?=),assertBool)
import Test.LeanCheck (Listable,(\/),cons0)
import Test.LeanCheck.Instances.Enum ()
import Test.QuickCheck (Arbitrary)
import Test.QuickCheck.Instances.Enum ()
import Test.Tasty (TestTree,defaultMain,testGroup,adjustOption)
import Test.Tasty.HUnit (testCase)

import qualified Automata.Nfsa as Nfsa
import qualified Automata.Nfst as Nfst
import qualified Automata.Dfsa as Dfsa
import qualified Automata.Dfst as Dfst
import qualified Automata.Nfsa.Builder as B
import qualified Data.Set as S
import qualified Data.List as L
import qualified GHC.Exts as E
import qualified Test.Tasty.LeanCheck as TL
import qualified Test.QuickCheck as QC
import qualified Test.Tasty.QuickCheck as TQC
import qualified Test.QuickCheck.Classes as QCC

main :: IO ()
main = defaultMain
  $ adjustOption (\_ -> TL.LeanCheckTests 5000)
  $ tests 

tests :: TestTree
tests = testGroup "Automata"
  [ testGroup "Nfsa"
    [ testGroup "evaluate"
      [ testCase "A" (Nfsa.evaluate ex1 [D3,D1] @?= False)
      , testCase "B" (Nfsa.evaluate ex1 [D0,D1,D3] @?= True)
      , testCase "C" (Nfsa.evaluate ex1 [D1,D3,D3] @?= True)
      , testCase "D" (Nfsa.evaluate ex1 [D0,D0,D0] @?= False)
      , testCase "E" (Nfsa.evaluate ex1 [D0,D0] @?= False)
      , testCase "F" (Nfsa.evaluate ex1 [D1] @?= True)
      , testCase "G" (Nfsa.evaluate ex1 [D1,D3] @?= True)
      , testCase "H" (Nfsa.evaluate ex2 [D3,D3,D0] @?= False)
      , testCase "I" (Nfsa.evaluate ex2 [D3,D3,D2] @?= True)
      , testCase "J" (Nfsa.evaluate ex3 [D1] @?= False)
      , testCase "K" (Nfsa.evaluate ex3 [D1,D3] @?= True)
      , testCase "L" (Nfsa.evaluate ex3 [D1,D3,D0] @?= False)
      , testCase "M" (Nfsa.evaluate ex3 [D1,D3,D0,D2,D3] @?= True)
      , testCase "N" (Nfsa.evaluate ex3 [D1,D3,D3] @?= True)
      ]
    , testGroup "append"
      [ testCase "A" (Nfsa.evaluate (Nfsa.append ex1 ex2) [D0,D1,D3,D3,D3,D2] @?= True)
      , testCase "B" (Nfsa.evaluate (Nfsa.append ex1 ex2) [D0,D0,D3,D0] @?= False)
      , testCase "C" (Nfsa.evaluate (Nfsa.append ex1 ex2) [D1,D3] @?= True)
      , testCase "D" (Nfsa.evaluate (Nfsa.append ex2 ex3) [D3,D3,D2,D1,D3,D3] @?= True)
      , testCase "E" (Nfsa.evaluate (Nfsa.append ex2 ex3) [D3,D3,D2] @?= False)
      ]
    , testGroup "union"
      [ testGroup "unit"
        [ testCase "A" (Nfsa.evaluate (Nfsa.union ex1 ex2) [D3,D1] @?= True)
        , testCase "B" (Nfsa.evaluate (Nfsa.union ex1 ex2) [D3,D3,D2] @?= True)
        , testCase "C" (Nfsa.evaluate (Nfsa.union ex1 ex2) [D0,D0,D0] @?= False)
        ]
      ]
    , testGroup "toDfsa"
      [ testGroup "unit"
        [ testCase "A" (Dfsa.evaluate (Nfsa.toDfsa ex1) [D0,D1,D3] @?= True)
        , testCase "B" (Dfsa.evaluate (Nfsa.toDfsa ex1) [D3,D1] @?= False)
        , testCase "C" (Dfsa.evaluate (Nfsa.toDfsa (Nfsa.append ex1 ex2)) [D0,D1,D3,D3,D3,D2] @?= True)
        , testCase "D" (Dfsa.evaluate (Nfsa.toDfsa (Nfsa.append ex2 ex3)) [D3,D3,D2,D1,D3,D3] @?= True)
        , testCase "E" (Dfsa.evaluate (Nfsa.toDfsa (Nfsa.append ex1 ex2)) [D0,D0,D3,D0] @?= False)
        , testCase "F" (Nfsa.toDfsa ex1 == Nfsa.toDfsa ex4 @?= True)
        , testCase "G" (Nfsa.toDfsa ex1 == Nfsa.toDfsa ex2 @?= False)
        , testCase "H" (Nfsa.toDfsa ex5 == Nfsa.toDfsa ex6 @?= True)
        ]
      , testGroup "evaluation"
        [ TL.testProperty "1" $ \(a,b,c,d) -> Dfsa.evaluate (Nfsa.toDfsa ex1) [a,b,c,d] == Nfsa.evaluate ex1 [a,b,c,d]
        , TL.testProperty "2" $ \(a,b,c,d) -> Dfsa.evaluate (Nfsa.toDfsa ex2) [a,b,c,d] == Nfsa.evaluate ex2 [a,b,c,d]
        , TL.testProperty "3" $ \(a,b,c,d) -> Dfsa.evaluate (Nfsa.toDfsa ex3) [a,b,c,d] == Nfsa.evaluate ex3 [a,b,c,d]
        , TL.testProperty "4" $ \(a,b,c,d) -> Dfsa.evaluate (Nfsa.toDfsa ex4) [a,b,c,d] == Nfsa.evaluate ex4 [a,b,c,d]
        , TL.testProperty "5" $ \(a,b,c,d) -> Dfsa.evaluate (Nfsa.toDfsa ex5) [a,b,c,d] == Nfsa.evaluate ex5 [a,b,c,d]
        , TL.testProperty "6" $ \(a,b,c,d) -> Dfsa.evaluate (Nfsa.toDfsa ex6) [a,b,c,d] == Nfsa.evaluate ex6 [a,b,c,d]
        , TL.testProperty "7" $ \(a,b,c,d) -> Dfsa.evaluate (Nfsa.toDfsa ex7) [a,b,c,d] == Nfsa.evaluate ex7 [a,b,c,d]
        ]
      , lawsToTest (QCC.semiringLaws (Proxy :: Proxy (Nfsa D)))
      ]
    ]
  , testGroup "Dfsa"
    [ testGroup "evaluate"
      [ testCase "A" (Dfsa.evaluate exDfsa1 [D1] @?= True)
      , testCase "B" (Dfsa.evaluate exDfsa1 [D3,D2,D1,D2,D0] @?= True)
      , testCase "C" (Dfsa.evaluate exDfsa2 [D3,D3] @?= False)
      , testCase "D" (Dfsa.evaluate exDfsa2 [D1] @?= True)
      , testCase "E" (Dfsa.evaluate exDfsa2 [D0,D2] @?= True)
      ]
    , testGroup "union"
      [ testGroup "unit"
        [ testCase "A" (Dfsa.evaluate (Dfsa.union (Nfsa.toDfsa ex1) (Nfsa.toDfsa ex3)) [D0,D1,D3] @?= True)
        , testCase "B" (Dfsa.evaluate (Dfsa.union (Nfsa.toDfsa ex1) (Nfsa.toDfsa ex3)) [D2,D3] @?= True)
        , testCase "C" (Dfsa.evaluate (Dfsa.union (Nfsa.toDfsa ex1) (Nfsa.toDfsa ex3)) [D1,D3] @?= True)
        , testCase "D" (Dfsa.evaluate (Dfsa.union (Nfsa.toDfsa ex1) (Nfsa.toDfsa ex3)) [D1,D3,D0] @?= False)
        , testCase "E" (Dfsa.evaluate (Dfsa.union (Nfsa.toDfsa ex1) (Nfsa.toDfsa ex3)) [D1] @?= True)
        , testCase "F" (Dfsa.evaluate (Dfsa.union (Nfsa.toDfsa ex1) (Nfsa.toDfsa ex3)) [D3] @?= False)
        , testCase "G" (Dfsa.union (Nfsa.toDfsa ex1) (Nfsa.toDfsa ex3) @?= Dfsa.union (Nfsa.toDfsa ex3) (Nfsa.toDfsa ex1))
        , testCase "H" (Dfsa.union (Nfsa.toDfsa ex1) (Nfsa.toDfsa ex1) @?= (Nfsa.toDfsa ex1))
        , testCase "I" (Dfsa.union (Nfsa.toDfsa ex3) (Nfsa.toDfsa ex3) @?= (Nfsa.toDfsa ex3))
        ]
      , TL.testProperty "idempotent" $ \x -> let y = mkBinDfsa x in y == Dfsa.union y y
      , testGroup "identity"
        [ TL.testProperty "left" $ \x -> let y = mkBinDfsa x in y == Dfsa.union Dfsa.rejection y
        , TL.testProperty "right" $ \x -> let y = mkBinDfsa x in y == Dfsa.union y Dfsa.rejection
        ]
      ]
    , testGroup "intersection"
      [ TL.testProperty "idempotent" $ \x -> let y = mkBinDfsa x in y == Dfsa.intersection y y
      , testGroup "identity"
        [ TL.testProperty "left" $ \x -> let y = mkBinDfsa x in y == Dfsa.intersection Dfsa.acceptance y
        , TL.testProperty "right" $ \x -> let y = mkBinDfsa x in y == Dfsa.intersection y Dfsa.acceptance
        ]
      ]
    , lawsToTest (QCC.semiringLaws (Proxy :: Proxy (Dfsa D)))
    ]
  , testGroup "Nfst"
    [ testGroup "evaluate"
      [ testCase "A" (Nfst.evaluate exNfst1 [D0,D1] @?= S.singleton [B1,B0])
      , testCase "B" (Nfst.evaluate exNfst1 [D2,D1,D3] @?= S.singleton [B1,B1,B1])
      , testCase "C" (Nfst.evaluate exNfst2 [D0,D0] @?= S.singleton [B0,B0])
      , testCase "D" (Nfst.evaluate exNfst2 [D1,D0] @?= S.fromList [[B0,B0],[B0,B1]])
      , testCase "E" (Nfst.evaluate exNfst3 [D0,D2] @?= S.singleton [B1,B0])
      , testCase "F" (Nfst.evaluate exNfst3 [D0,D1] @?= S.singleton [B0,B1])
      , testCase "G" (Nfst.evaluate (Nfst.union Nfst.rejection exNfst3) [D0,D1] @?= S.singleton [B0,B1])
      , testCase "H" (Nfst.evaluate (Nfst.union exNfst1 exNfst3) [D0,D1] @?= S.fromList [[B1,B0],[B0,B1]])
      , testCase "I" (Nfst.evaluate (Nfst.union exNfst3 exNfst1) [D0,D1] @?= S.fromList [[B1,B0],[B0,B1]])
      ]
    , testGroup "toDfst"
      [ testGroup "unit"
        [ testCase "A" (let x = Dfst.evaluate (Nfst.toDfst exNfst4) [D0,D1] in assertBool (show x) (setSubresult [B1, B0] x))
        , testCase "B" (let x = Dfst.evaluate (Nfst.toDfst exNfst5) [D1,D2] in assertBool (show x) (setSubresult [B0, B1] x))
        ]
      , testGroup "evaluation"
        [ TL.testProperty "4" $ \(input :: [D]) -> getAll (foldMap (\x -> All (subresult (L.reverse x) (Dfst.evaluate (Nfst.toDfst exNfst4) input))) (Nfst.evaluate exNfst4 input))
        , TL.testProperty "5" $ \(input :: [D]) -> getAll (foldMap (\x -> All (subresult (L.reverse x) (Dfst.evaluate (Nfst.toDfst exNfst5) input))) (Nfst.evaluate exNfst5 input))
        , TL.testProperty "6" $ \(input :: [D]) -> getAll (foldMap (\x -> All (subresult (L.reverse x) (Dfst.evaluate (Nfst.toDfst exNfst6) input))) (Nfst.evaluate exNfst6 input))
        ]
      ]
    ]
  , testGroup "Dfst"
    [ testGroup "evaluate"
      [ testCase "A" (Dfst.evaluate exDfst1 [D0,D2] @?= Nothing)
      , testCase "B" (Dfst.evaluate exDfst1 [D0,D1] @?= Just (E.fromList [B1,B0]))
      ]
    , testGroup "union"
      [ testGroup "unit"
        [ testCase "A" (let x = Dfst.evaluate (Dfst.union (Dfst.map S.singleton exDfst1) (Dfst.map S.singleton exDfst2)) [D0,D1] in assertBool (show x) (setSubresult [B0, B1] x))
        , testCase "B" (let x = Dfst.evaluate (Dfst.union (Dfst.map S.singleton exDfst1) (Dfst.map S.singleton exDfst2)) [D0,D3] in assertBool (show x) (setSubresult [B0, B0] x))
        ]
      ]
    ]
  ]

subresult :: Ord a => [Set a] -> Maybe (Array (Set a)) -> Bool
subresult xs = \case
  Nothing -> False
  Just ys -> length xs == length ys && all (uncurry S.isSubsetOf) (zip xs (E.toList ys))

setSubresult :: Ord a => [a] -> Maybe (Array (Set a)) -> Bool
setSubresult xs = \case
  Nothing -> False
  Just ys -> length xs == length ys && all (uncurry S.member) (zip xs (E.toList ys))

lawsToTest :: QCC.Laws -> TestTree
lawsToTest (QCC.Laws name pairs) = testGroup name (map (uncurry TQC.testProperty) pairs)

instance Semigroup B where
  (<>) = max

instance Monoid B where
  mempty = minBound

instance (Arbitrary t, Bounded t, Enum t, Ord t) => Arbitrary (Dfsa t) where
  arbitrary = do
    let states = 6
    n <- QC.choose (0,30)
    (ts :: [(Int,Int,t,t)]) <- QC.vectorOf n $ (,,,)
      <$> QC.choose (0,states)
      <*> QC.choose (0,states)
      <*> QC.arbitrary
      <*> QC.arbitrary
    return $ Dfsa.build $ \s0 -> do
      states <- fmap (s0:) (replicateM states Dfsa.state)
      Dfsa.accept (states L.!! 3)
      forM_ ts $ \(source,dest,a,b) -> do
        let lo = min a b
            hi = max a b
        Dfsa.transition lo hi (states L.!! source) (states L.!! dest)

instance (Arbitrary t, Bounded t, Enum t, Ord t) => Arbitrary (Nfsa t) where
  arbitrary = do
    let states = 3
    n <- QC.choose (0,20)
    (ts :: [(Int,Int,t,t,Bool)]) <- QC.vectorOf n $ (,,,,)
      <$> QC.choose (0,states)
      <*> QC.choose (0,states)
      <*> QC.arbitrary
      <*> QC.arbitrary
      <*> QC.frequency [(975,pure False),(25,pure True)]
    return $ B.run $ \s0 -> do
      states <- fmap (s0:) (replicateM states B.state)
      B.accept (states L.!! 1)
      forM_ ts $ \(source,dest,a,b,epsilon) -> do
        let lo = min a b
            hi = max a b
        if epsilon
          then B.epsilon (states L.!! source) (states L.!! dest)
          else B.transition lo hi (states L.!! source) (states L.!! dest)

-- This instance is provided for testing. The library does not provide
-- an Eq instance for Nfsa since there is no efficent algorithm to do this
-- in general.
instance (Ord t, Bounded t, Enum t) => Eq (Nfsa t) where
  a == b = Nfsa.toDfsa a == Nfsa.toDfsa b

ex1 :: Nfsa D
ex1 = B.run $ \s0 -> do
  s1 <- B.state
  B.accept s1
  B.transition D1 D2 s0 s1
  B.transition D0 D0 s0 s0
  B.transition D3 D3 s1 s1

ex2 :: Nfsa D
ex2 = B.run $ \s0 -> do
  s1 <- B.state
  B.accept s1
  B.transition D1 D2 s0 s1
  B.transition D0 D0 s0 s0
  B.transition D3 D3 s0 s0
  B.transition D3 D3 s0 s1
  B.transition D3 D3 s1 s1

ex3 :: Nfsa D
ex3 = B.run $ \s0 -> do
  s1 <- B.state
  s2 <- B.state
  B.accept s2
  B.transition D1 D2 s0 s1
  B.transition D3 D3 s1 s2
  B.transition D2 D3 s1 s0
  B.transition D0 D0 s2 s0
  B.epsilon s2 s1

ex4 :: Nfsa D
ex4 = B.run $ \s0 -> do
  s1 <- B.state
  s2 <- B.state
  B.accept s1
  B.accept s2
  B.transition D1 D2 s0 s1
  B.transition D0 D0 s0 s0
  B.transition D3 D3 s1 s1
  B.transition D1 D2 s0 s2
  B.transition D3 D3 s2 s2

ex5 :: Nfsa D
ex5 = B.run $ \s0 -> do
  s1 <- B.state
  s2 <- B.state
  B.accept s2
  B.transition D0 D1 s0 s1
  B.transition D1 D2 s1 s2

-- Note: ex5 and ex6 accept the same inputs.
ex6 :: Nfsa D
ex6 = B.run $ \s0 -> do
  -- s3, s4, and s5 are unreachable
  s3 <- B.state
  s4 <- B.state
  s5 <- B.state
  s2 <- B.state
  s1 <- B.state
  B.accept s2
  B.transition D0 D1 s0 s1
  B.transition D1 D2 s1 s2
  B.epsilon s3 s4
  B.transition D0 D2 s4 s5
  B.transition D2 D2 s5 s3
  B.transition D1 D2 s5 s3

ex7 :: Nfsa D
ex7 = B.run $ \s0 -> do
  s1 <- B.state
  s2 <- B.state
  s3 <- B.state
  s4 <- B.state
  s5 <- B.state
  B.accept s3
  B.accept s4
  B.transition D0 D0 s0 s1
  B.transition D0 D0 s0 s2
  B.transition D2 D2 s1 s3
  B.transition D0 D0 s1 s4
  B.transition D1 D1 s2 s4
  B.transition D3 D3 s1 s3
  B.transition D3 D3 s2 s5
  B.transition D2 D3 s4 s4
  B.epsilon s4 s5
  B.epsilon s5 s4


exDfsa1 :: Dfsa D
exDfsa1 = Dfsa.build $ \s0 -> do
  s1 <- Dfsa.state
  Dfsa.accept s1
  Dfsa.transition D0 D1 s0 s1

exDfsa2 :: Dfsa D
exDfsa2 = Dfsa.build $ \s0 -> do
  s1 <- Dfsa.state
  s2 <- Dfsa.state
  Dfsa.accept s2
  Dfsa.transition D0 D3 s0 s1
  Dfsa.transition D1 D2 s0 s2
  Dfsa.transition D2 D3 s1 s1
  Dfsa.transition D2 D2 s1 s2
  Dfsa.transition D3 D3 s2 s2

exNfst1 :: Nfst D B
exNfst1 = Nfst.build $ \s0 -> do
  s1 <- Nfst.state
  Nfst.accept s1
  Nfst.transition D0 D1 B0 s0 s1 
  Nfst.transition D2 D3 B1 s0 s1 
  Nfst.transition D0 D3 B1 s1 s1 

exNfst2 :: Nfst D B
exNfst2 = Nfst.build $ \s0 -> do
  s1 <- Nfst.state
  s2 <- Nfst.state
  Nfst.epsilon s0 s1
  Nfst.accept s2
  Nfst.transition D0 D1 B0 s0 s1 
  Nfst.transition D2 D3 B1 s0 s1 
  Nfst.transition D0 D0 B0 s1 s2 
  Nfst.transition D1 D3 B1 s1 s1
  Nfst.transition D0 D0 B0 s2 s2
  Nfst.transition D1 D3 B1 s2 s0

exNfst3 :: Nfst D B
exNfst3 = Nfst.build $ \s0 -> do
  s1 <- Nfst.state
  s2 <- Nfst.state
  s3 <- Nfst.state
  s4 <- Nfst.state
  Nfst.accept s3
  Nfst.accept s4
  Nfst.transition D0 D0 B0 s0 s1
  Nfst.transition D0 D0 B1 s0 s2
  Nfst.transition D2 D2 B1 s1 s3
  Nfst.transition D1 D1 B0 s2 s4
  Nfst.transition D3 D3 B0 s1 s3
  Nfst.transition D3 D3 B0 s2 s4

exNfst4 :: Nfst D (Set B)
exNfst4 = Nfst.build $ \s0 -> do
  s1 <- Nfst.state
  s2 <- Nfst.state
  s3 <- Nfst.state
  s4 <- Nfst.state
  s5 <- Nfst.state
  Nfst.accept s3
  Nfst.accept s4
  Nfst.transition D0 D0 (S.singleton B0) s0 s1
  Nfst.transition D0 D0 (S.singleton B1) s0 s2
  Nfst.transition D2 D2 (S.singleton B1) s1 s3
  Nfst.transition D0 D0 (S.singleton B0) s1 s4
  Nfst.transition D1 D1 (S.singleton B0) s2 s4
  Nfst.transition D3 D3 (S.singleton B0) s1 s3
  Nfst.transition D3 D3 (S.singleton B0) s2 s5
  Nfst.transition D2 D3 (S.singleton B1) s4 s4
  Nfst.epsilon s4 s5
  Nfst.epsilon s5 s4

exNfst5 :: Nfst D (Set B)
exNfst5 = Nfst.build $ \s0 -> do
  s1 <- Nfst.state
  s2 <- Nfst.state
  Nfst.accept s2
  Nfst.transition D1 D1 (S.singleton B0) s0 s1
  Nfst.transition D2 D2 (S.singleton B1) s1 s2

exNfst6 :: Nfst D (Set B)
exNfst6 = Nfst.build $ \s0 -> do
  s1 <- Nfst.state
  s2 <- Nfst.state
  s3 <- Nfst.state
  s4 <- Nfst.state
  s5 <- Nfst.state
  s6 <- Nfst.state
  Nfst.epsilon s0 s4
  Nfst.accept s2
  Nfst.accept s6
  Nfst.transition D1 D1 (S.singleton B1) s0 s1
  Nfst.transition D3 D3 (S.singleton B0) s0 s4
  Nfst.transition D2 D2 (S.singleton B1) s1 s2
  Nfst.transition D3 D3 (S.singleton B0) s1 s4
  Nfst.transition D2 D2 (S.singleton B1) s1 s2
  Nfst.transition D0 D1 (S.singleton B0) s4 s6
  Nfst.transition D1 D1 (S.singleton B1) s6 s4
  Nfst.transition D0 D0 (S.singleton B0) s4 s1

exDfst1 :: Dfst D B
exDfst1 = Dfst.build $ \s0 -> do
  s1 <- Dfst.state
  s2 <- Dfst.state
  s3 <- Dfst.state
  s4 <- Dfst.state
  Dfst.accept s3
  Dfst.accept s4
  Dfst.transition D0 D0 B0 s0 s1
  Dfst.transition D0 D0 B1 s0 s2
  Dfst.transition D2 D2 B1 s1 s3
  Dfst.transition D1 D1 B0 s2 s4
  Dfst.transition D3 D3 B0 s1 s3
  Dfst.transition D3 D3 B0 s2 s4

exDfst2 :: Dfst D B
exDfst2 = Dfst.build $ \s0 -> do
  s1 <- Dfst.state
  s2 <- Dfst.state
  Dfst.accept s2
  Dfst.transition D0 D0 B0 s0 s1
  Dfst.transition D1 D1 B1 s1 s2
  Dfst.transition D2 D2 B0 s2 s0

-- This uses s3 as a dead state. So, we are roughly testing
-- all DFA with three nodes, a binary transition function,
-- and a single fixed end state.
mkBinDfsa :: ((D,D),(D,D),(D,D)) -> Dfsa B
mkBinDfsa (ws,xs,ys) = Dfsa.build $ \s0 -> do
  s1 <- Dfsa.state
  s2 <- Dfsa.state
  s3 <- Dfsa.state
  Dfsa.accept s1
  let resolve = \case
        D0 -> s0
        D1 -> s1
        D2 -> s2
        D3 -> s3
      binTransitions (a,b) s = do
        Dfsa.transition B0 B0 s (resolve a)
        Dfsa.transition B1 B1 s (resolve b)
  binTransitions ws s0
  binTransitions xs s1
  binTransitions ys s2
  Dfsa.transition B0 B1 s3 s3