inventory-0.1.0.0: src/MatchSigs/Matching.hs
module MatchSigs.Matching
( MatchedSigs(..)
) where
import Control.Monad.State.Strict
import Data.List
import Name
import MatchSigs.Matching.Env
import MatchSigs.Sig
type SigMatches = ( [Sig FreeVarIdx] -- Sig shared by these 'Name's
, String -- rendered sig
, [Name] -- Names that share this signature
)
newtype MatchedSigs =
MatchedSigs { getMatchedSigs :: [SigMatches] }
instance Semigroup MatchedSigs where
(<>) = unionMatchedSigs
instance Monoid MatchedSigs where
mempty = MatchedSigs mempty
-- | Create the union of two 'MatchedSigs' by checking if there a match in one
-- group for each sig in the other.
-- This is O(n^2) since there is no suitable ordering for sigs due to different
-- potential ordering of free vars.
unionMatchedSigs :: MatchedSigs -> MatchedSigs -> MatchedSigs
unionMatchedSigs (MatchedSigs a) (MatchedSigs b)
= MatchedSigs
. uncurry (++)
-- fold compatible sigs from b in a, append the ones that are not compatible
$ foldl' go (a, []) b
where
go (aSigs, nonMatches) bSig
= let check (ss, False) aSig
= case compatibleSigs aSig bSig of
Just s' -> (s' : ss, True)
Nothing -> (aSig : ss, False)
check (ss, True) aSig = (aSig : ss, True)
in case foldl' check ([], False) aSigs of
(res, False) -> (res, bSig : nonMatches)
(res, True) -> (res, nonMatches)
-- | Combines the names in two 'SigMatches' if the sigs match
compatibleSigs :: SigMatches -> SigMatches -> Maybe SigMatches
compatibleSigs (sigA, str, namesA) (sigB, _, namesB) =
if evalState (checkMatch sigA sigB) initEnv
then Just (sigA, str, namesA ++ namesB)
else Nothing
-- | Check that two sigs are isomorphic
-- First step is to check that the contexts match.
checkMatch :: [Sig FreeVarIdx]
-> [Sig FreeVarIdx]
-> State Env Bool
-- VarCtx and Qual are both expected to occur at the front of the list
checkMatch (VarCtx va : restA) (VarCtx vb : restB)
= introVars va vb
/\ checkMatch restA restB
checkMatch (VarCtx _ : _) _ = pure False
checkMatch _ (VarCtx _ : _) = pure False
-- Appearance order of quals not significant
checkMatch (Qual qa : restA) bs@(Qual _ : _) =
let (qualsB, restB) = span isQual bs
splits = zip (inits qualsB) (tails qualsB)
go (i, Qual f : rest)
= checkMatch qa f
/\ checkMatch restA (i ++ rest ++ restB)
go _ = pure False
in checkOr $ go <$> splits
checkMatch (Qual _ : _) _ = pure False
checkMatch _ (Qual _ : _) = pure False
checkMatch sa sb = checkResult sa sb
-- | Extract the result types and make sure they match before going any further.
checkResult :: [Sig FreeVarIdx]
-> [Sig FreeVarIdx]
-> State Env Bool
checkResult sa sb
| ra : restA <- reverse sa
, rb : restB <- reverse sb
= checkArguments [ra] [rb]
/\ checkArguments restA restB
checkResult _ _ = pure True
-- | After the result type has been removed, check the argument types.
checkArguments :: [Sig FreeVarIdx]
-> [Sig FreeVarIdx]
-> State Env Bool
checkArguments [] [] = pure True
checkArguments (FreeVar ai : restA) (FreeVar bi : restB)
= tryAssignVar ai bi
/\ checkArguments restA restB
checkArguments (TyDescriptor sa na : restA) (TyDescriptor sb nb : restB)
| sa == sb
, na == nb
= checkArguments restA restB
| otherwise = pure False
-- this is where we need to check for a failure and rotate the list
checkArguments (Arg aa : restA) sb =
let splits = zip (inits sb) (tails sb)
go (i, Arg ab : rest)
= checkMatch aa ab
/\ checkArguments restA (i ++ rest)
go _ = pure False
in checkOr $ go <$> splits
checkArguments (Apply ca aa : restA) (Apply cb ab : restB)
| length aa == length ab
= checkMatch ca cb
/\ checkAnd (zipWith checkMatch aa ab)
/\ checkArguments restA restB
| otherwise = pure False
checkArguments (Tuple [] : restA) (Tuple [] : restB)
= checkArguments restA restB
checkArguments (Tuple (a : as) : restA) (Tuple bs : restB)
| length as + 1 == length bs
, let splits = zip (inits bs) (tails bs)
go (i, b : rest)
= checkMatch a b
/\ checkArguments [Tuple as] [Tuple $ i ++ rest]
/\ checkArguments restA restB
go _ = pure False
= checkOr $ go <$> splits
| otherwise = pure False
checkArguments (KindSig ta ka : restA) (KindSig tb kb : restB)
= checkMatch ta tb
/\ checkMatch ka kb
/\ checkArguments restA restB
checkArguments _ _ = pure False