packages feed

moonlight-core-0.1.0.0: test/syntax/PatternSpec.hs

{-# LANGUAGE DerivingStrategies #-}

module PatternSpec (tests) where

import Data.Kind (Type)
import Data.Foldable (toList)
import Data.List (sort)
import Moonlight.Core (Pattern (..), patternVariables)
import Moonlight.Core qualified as EGraph
import Moonlight.Core (IsLawName (..), constructorLawName)
import LawProperty (lawProperty)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit ((@?=), testCase)
import Test.Tasty.QuickCheck (Arbitrary (arbitrary), Gen, frequency, listOf, resize, sized)

data PatternLaw
  = PatternCataAfterAnaIdentity
  | PatternInterpreterCoherence
  deriving stock (Bounded, Enum, Eq, Ord, Show)

instance IsLawName PatternLaw where
  lawNameText =
    constructorLawName . show

cataAfterAnaIdentity :: Eq seed => (seed -> recursive) -> (recursive -> seed) -> seed -> Bool
cataAfterAnaIdentity anamorphism catamorphism seed = catamorphism (anamorphism seed) == seed

interpreterCoherence :: Eq value => (seed -> recursive) -> (recursive -> value) -> (seed -> value) -> seed -> Bool
interpreterCoherence anamorphism interpretation seedInterpreter seed = seedInterpreter seed == interpretation (anamorphism seed)

type PatternSeed :: Type
data PatternSeed
  = PatternSeedVar EGraph.PatternVar
  | PatternSeedNode [PatternSeed]
  deriving stock (Eq, Show)

patternFromSeed :: PatternSeed -> Pattern []
patternFromSeed seed =
  case seed of
    PatternSeedVar patternVar ->
      PatternVar patternVar
    PatternSeedNode children ->
      PatternNode (map patternFromSeed children)

patternSeedFromPattern :: Pattern [] -> PatternSeed
patternSeedFromPattern patternValue =
  case patternValue of
    PatternVar patternVar ->
      PatternSeedVar patternVar
    PatternNode children ->
      PatternSeedNode (map patternSeedFromPattern children)

canonicalPatternVars :: [EGraph.PatternVar] -> [EGraph.PatternVar]
canonicalPatternVars =
  foldr
    ( \patternVar accumulatedPatternVars ->
        case accumulatedPatternVars of
          [] ->
            [patternVar]
          accumulatedHead : _ ->
            if patternVar == accumulatedHead
              then accumulatedPatternVars
              else patternVar : accumulatedPatternVars
    )
    []
    . sort

patternSeedVariables :: PatternSeed -> [EGraph.PatternVar]
patternSeedVariables seed =
  case seed of
    PatternSeedVar patternVar ->
      [patternVar]
    PatternSeedNode children ->
      canonicalPatternVars (foldMap patternSeedVariables children)

patternVariablesCanonical :: Pattern [] -> [EGraph.PatternVar]
patternVariablesCanonical =
  toList . patternVariables

samplePatternSeeds :: [PatternSeed]
samplePatternSeeds =
  [ PatternSeedVar (EGraph.mkPatternVar 1),
    PatternSeedNode [],
    PatternSeedNode [PatternSeedVar (EGraph.mkPatternVar 2), PatternSeedVar (EGraph.mkPatternVar 3)],
    PatternSeedNode
      [ PatternSeedVar (EGraph.mkPatternVar 4),
        PatternSeedNode [PatternSeedVar (EGraph.mkPatternVar 4), PatternSeedVar (EGraph.mkPatternVar 5)]
      ]
  ]

patternSeedAtomGen :: Gen PatternSeed
patternSeedAtomGen =
  PatternSeedVar . EGraph.mkPatternVar <$> arbitrary

patternSeedGenSized :: Int -> Gen PatternSeed
patternSeedGenSized size =
  case size of
    0 ->
      patternSeedAtomGen
    _ ->
      frequency
        [ (2, patternSeedAtomGen),
          (1, PatternSeedNode <$> childSeedListGen)
        ]
  where
    childSeedSize = size `div` 2
    childSeedListGen =
      resize childSeedSize (listOf (patternSeedGenSized childSeedSize))

instance Arbitrary PatternSeed where
  arbitrary =
    sized patternSeedGenSized

tests :: TestTree
tests =
  testGroup
    "Pattern"
    [ testCase "patternVariables collects variables from sample recursive patterns" $
        patternVariablesCanonical
          ( patternFromSeed
              ( PatternSeedNode
                  [ PatternSeedVar (EGraph.mkPatternVar 1),
                    PatternSeedNode [PatternSeedVar (EGraph.mkPatternVar 2), PatternSeedVar (EGraph.mkPatternVar 1)]
                  ]
              )
          )
          @?= [EGraph.mkPatternVar 1, EGraph.mkPatternVar 2],
      testCase "pattern ordering follows constructor declaration order" $
        compare (PatternVar (EGraph.mkPatternVar 0) :: Pattern []) (PatternNode [])
          @?= LT,
      testCase "pattern seed reconstruction holds across sample recursive patterns" $
        map (cataAfterAnaIdentity patternFromSeed patternSeedFromPattern) samplePatternSeeds
          @?= [True, True, True, True],
      testCase "pattern variable interpretation is coherent across sample recursive patterns" $
        map
          (interpreterCoherence patternFromSeed patternVariablesCanonical patternSeedVariables)
          samplePatternSeeds
          @?= [True, True, True, True],
      lawProperty PatternCataAfterAnaIdentity $
        cataAfterAnaIdentity patternFromSeed patternSeedFromPattern,
      lawProperty PatternInterpreterCoherence $
        interpreterCoherence patternFromSeed patternVariablesCanonical patternSeedVariables
    ]