summaryrefslogtreecommitdiff
path: root/src/compiler/GF/Grammar/PatternMatch.hs
blob: dc0a5d3a52650f3bc471eef527c3a8c83ec4be40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
----------------------------------------------------------------------
-- |
-- Module      : PatternMatch
-- Maintainer  : AR
-- Stability   : (stable)
-- Portability : (portable)
--
-- > CVS $Date: 2005/10/12 12:38:29 $
-- > CVS $Author: aarne $
-- > CVS $Revision: 1.7 $
--
-- pattern matching for both concrete and abstract syntax. AR -- 16\/6\/2003
-----------------------------------------------------------------------------

module GF.Grammar.PatternMatch (
                                matchPattern,
                                testOvershadow,
                                findMatch,
                                measurePatt
                               ) where

import GF.Data.Operations
import GF.Grammar.Grammar
import GF.Infra.Ident
import GF.Grammar.Macros
--import GF.Grammar.Printer

--import Data.List
import Control.Monad
import GF.Text.Pretty
--import Debug.Trace

matchPattern :: ErrorMonad m => [(Patt,rhs)] -> Term -> m (rhs, Substitution)
matchPattern pts term =
  if not (isInConstantForm term)
    then raise (render ("variables occur in" <+> pp term))
  else do
    term' <- mkK term
    errIn (render ("trying patterns" <+> hsep (punctuate ',' (map fst pts)))) $
      findMatch [([p],t) | (p,t) <- pts] [term']
 where
  -- to capture all Str with string pattern matching
  mkK s = case s of
    C _ _ -> do
      s' <- getS s
      return (K (unwords s'))
    _ -> return s

  getS s = case s of
    K w -> return [w]
    C v w -> liftM2 (++) (getS v) (getS w)
    Empty -> return []
    _ -> raise (render ("cannot get string from" <+> s))

testOvershadow :: ErrorMonad m => [Patt] -> [Term] -> m [Patt]
testOvershadow pts vs = do
  let numpts = zip pts [0..]
  let cases  = [(p,EInt i) | (p,i) <- numpts]
  ts <- mapM (liftM fst . matchPattern cases) vs
  return [p | (p,i) <- numpts, notElem i [i | EInt i <- ts] ]

findMatch :: ErrorMonad m => [([Patt],rhs)] -> [Term] -> m (rhs, Substitution)
findMatch cases terms = case cases of
   [] -> raise (render ("no applicable case for" <+> hsep (punctuate ',' terms)))
   (patts,_):_ | length patts /= length terms ->
       raise (render ("wrong number of args for patterns :" <+> hsep patts <+>
                    "cannot take" <+> hsep terms))
   (patts,val):cc -> case mapM tryMatch (zip patts terms) of
       Ok substs -> return (val, concat substs)
       _         -> findMatch cc terms

tryMatch :: (Patt, Term) -> Err [(Ident, Term)]
tryMatch (p,t) = do
  t' <- termForm t
  trym p t'
 where
  trym p t' =
    case (p,t') of
--    (_,(x,Typed e ty,y)) -> trym p (x,e,y) -- Add this? /TH 2013-09-05
      (_,(x,Empty,y)) -> trym p (x,K [],y)   -- because "" = [""] = []
      (PW, _) -> return [] -- optimization with wildcard
      (PV x,([],K s,[])) -> return [(x,words2term (words s))]
      (PV x,  _) -> return [(x,t)]
      (PString s, ([],K i,[])) | s==i -> return []
      (PInt s, ([],EInt i,[])) | s==i -> return []
      (PFloat s,([],EFloat i,[])) | s==i -> return [] --- rounding?
      (PC p pp, ([], Con f, tt)) |
            p `eqStrIdent` f && length pp == length tt ->
         do matches <- mapM tryMatch (zip pp tt)
            return (concat matches)

      (PP (q,p) pp, ([], QC (r,f), tt)) |
            -- q `eqStrIdent` r &&  --- not for inherited AR 10/10/2005
            p `eqStrIdent` f && length pp == length tt ->
         do matches <- mapM tryMatch (zip pp tt)
            return (concat matches)
      ---- hack for AppPredef bug
      (PP (q,p) pp, ([], Q (r,f), tt)) |
            -- q `eqStrIdent` r && ---
            p `eqStrIdent` f && length pp == length tt ->
         do matches <- mapM tryMatch (zip pp tt)
            return (concat matches)

      (PR r, ([],R r',[])) |
            all (`elem` map fst r') (map fst r) ->
         do matches <- mapM tryMatch
                            [(p,snd a) | (l,p) <- r, let Just a = lookup l r']
            return (concat matches)
      (PT _ p',_) -> trym p' t'

      (PAs x p',([],K s,[])) -> do
         subst <- trym p' t'
         return $ (x,words2term (words s)) : subst

      (PAs x p',_) -> do
         subst <- trym p' t'
         return $ (x,t) : subst

      (PAlt p1 p2,_) -> checks [trym p1 t', trym p2 t']

      (PNeg p',_) -> case tryMatch (p',t) of
        Bad _ -> return []
        _ -> raise (render ("no match with negative pattern" <+> p))

      (PSeq p1 p2, ([],K s, [])) -> matchPSeq p1 p2 s
      (PMSeq mp1 mp2, ([],K s, [])) -> matchPMSeq mp1 mp2 s

      (PRep p1, ([],K s, [])) -> checks [
         trym (foldr (const (PSeq p1)) (PString "")
           [1..n]) t' | n <- [0 .. length s]
        ] >>
        return []

      (PChar,  ([],K [_], [])) -> return []
      (PChars cs, ([],K [c], [])) | elem c cs -> return []

      _ -> raise (render ("no match in case expr for" <+> t))

  words2term []     = Empty
  words2term [w]    = K w
  words2term (w:ws) = C (K w) (words2term ws)


matchPMSeq (m1,p1) (m2,p2) s = matchPSeq' m1 p1 m2 p2 s
--matchPSeq p1 p2 s = matchPSeq' (0,maxBound::Int) p1 (0,maxBound::Int) p2 s
matchPSeq p1 p2 s = matchPSeq' (lengthBounds p1) p1 (lengthBounds p2) p2 s

matchPSeq' b1@(min1,max1) p1 b2@(min2,max2) p2 s =
  do let n = length s
         lo = min1 `max` (n-max2)
         hi = (n-min2) `min` max1
         cuts = [splitAt i s | i <- [lo..hi]]
     matches <- checks [mapM tryMatch [(p1,K s1),(p2,K s2)] | (s1,s2) <- cuts]
     return (concat matches)

-- | Estimate the minimal length of the string that a pattern will match
minLength = matchLength 0 id (+) min -- safe underestimate

-- | Estimate the maximal length of the string that a pattern will match
maxLength =
    maybe maxBound id . matchLength Nothing Just (liftM2 (+)) (liftM2 max)
        -- safe overestimate

matchLength unknown known seq alt = len
  where
    len p =
      case p of
        PString s  -> known (length s)
        PSeq p1 p2 -> seq (len p1) (len p2)
        PAlt p1 p2 -> alt (len p1) (len p2)
        PChar      -> known 1
        PChars _   -> known 1
        PAs x p'   -> len p'
        PT t p'    -> len p'
        _          -> unknown

lengthBounds p = (minLength p,maxLength p)

mPatt p = (lengthBounds p,measurePatt p)

measurePatt p =
  case p of
    PSeq p1 p2 -> PMSeq (mPatt p1) (mPatt p2)
    _ -> composSafePattOp measurePatt p


isInConstantForm :: Term -> Bool
isInConstantForm trm = case trm of
    Cn _     -> True
    Con _    -> True
    Q _      -> True
    QC _     -> True
    Abs _ _ _ -> True
    C c a    -> isInConstantForm c && isInConstantForm a
    App c a  -> isInConstantForm c && isInConstantForm a
    R r      -> all (isInConstantForm . snd . snd) r
    K _      -> True
    Empty    -> True
    EInt _   -> True
    V ty ts  -> isInConstantForm ty && all isInConstantForm ts -- TH 2013-09-05
--  Typed e t-> isInConstantForm e && isInConstantForm t -- Add this? TH 2013-09-05

    _       -> False ---- isInArgVarForm trm
{- -- unused and suspicuous, see contP in GF.Compile.Compute.Concrete instead
varsOfPatt :: Patt -> [Ident]
varsOfPatt p = case p of
  PV x -> [x]
  PC _ ps -> concat $ map varsOfPatt ps
  PP _ ps -> concat $ map varsOfPatt ps
  PR r    -> concat $ map (varsOfPatt . snd) r
  PT _ q -> varsOfPatt q
  _ -> []

-- | to search matching parameter combinations in tables
isMatchingForms :: [Patt] -> [Term] -> Bool
isMatchingForms ps ts = all match (zip ps ts') where
  match (PC c cs, (Cn d, ds)) = c == d && isMatchingForms cs ds
  match _ = True
  ts' = map appForm ts

-}