@@ -11,6 +11,13 @@ class Match:
11
11
end : int
12
12
bindings : dict [Symbol , Any ]
13
13
14
+ def __len__ (self ):
15
+ return self .end - self .start
16
+
17
+ def __or__ (self , other : "Match" ):
18
+ return (Match (self .pattern , self .start , other .end , self .bindings | other .bindings )
19
+ if isinstance (other , Match ) else NotImplemented )
20
+
14
21
15
22
def pat (
16
23
* pattern ,
@@ -34,90 +41,132 @@ def partition_and_sort(patterns: list[Pattern]) -> tuple[list[Pattern], list[Pat
34
41
return sorted (macros ), sorted (non_macros )
35
42
36
43
44
+ def _match_alternate (line : list , source_pat : Pattern , options : list , start : int ) -> Match | None :
45
+ for option in options :
46
+ if result := match_one (line , source_pat , option , start ):
47
+ return result
48
+ # No options matched, so the entire pattern doesn't match
49
+ return None
50
+
51
+
52
+ def _match_space (line : list , start : int ):
53
+ return isinstance (line [start ], Space )
54
+
55
+
56
+ def _match_var (line : list , source_pat : Pattern , what : Var , start : int ):
57
+ elem = line [start ]
58
+ if what .use_cls :
59
+ success = isinstance (elem , what .cls )
60
+ else :
61
+ success = elem == what .cls
62
+ if success :
63
+ return Match (source_pat , start , start + 1 , {what .var : elem })
64
+ return None
65
+
66
+
67
+ def _match_repeat_fixed (line : list , source_pat : Pattern , what : list , start : int , num_repeats : int ):
68
+ result = None
69
+ i = start
70
+ for _ in range (num_repeats ):
71
+ result = match_one (line , source_pat , what , i )
72
+ if result is None :
73
+ break
74
+ i = result .end
75
+ return result
76
+
77
+
78
+ def _match_repeat_greedy (line : list , source_pat : Pattern , what : list , start : int , max_rep : int ):
79
+ # NOTE HERE: it is NOT 'max_rep > 0' because max_rep == -1 if infinite repeats allowed
80
+ i = start
81
+ result = None
82
+ while max_rep != 0 and i < len (line ):
83
+ result = match_one (line , source_pat , what , i )
84
+ if result is None :
85
+ # hit max number of repeats
86
+ break
87
+ max_rep -= 1
88
+ assert result .start == i
89
+ i = result .end
90
+ return result
91
+
92
+
93
+ def _match_repeat_lazy (
94
+ line : list ,
95
+ source_pat : Pattern ,
96
+ what : list ,
97
+ rest_of_pat : list ,
98
+ start : int ,
99
+ max_rep : int ):
100
+ # NOTE HERE: it is NOT 'max_rep > 0' because max_rep == -1 if infinite repeats allowed
101
+ i = start
102
+ result = None
103
+ while max_rep != 0 and i < len (line ):
104
+ # try to match remainder without more
105
+ # if it works, stop (not greedy)
106
+ if without := match_one (line , source_pat , rest_of_pat , i ):
107
+ assert result .start == i
108
+ i = without .end
109
+ result |= without
110
+ break
111
+ if result := match_one (line , source_pat , what , i ):
112
+ assert result .start == i
113
+ i = result .end
114
+ else :
115
+ return None
116
+ max_rep -= 1
117
+ return result
118
+
119
+
37
120
def match_one (line : list , source_pat : Pattern , pattern : list , start : int ) -> Match | None :
38
121
"""Try to match the mattern at the specified index in the line,
39
122
or return None if it doesn't match."""
40
- # pylint:disable=assignment-from-none
41
123
patt_i = 0
42
124
line_i = start
43
125
bindings = {}
44
- while patt_i < len ( pattern ) and line_i < len ( line ):
126
+ for patt_i , item in enumerate ( pattern ):
45
127
elem = line [line_i ]
46
- match entry := pattern [patt_i ]:
47
- case Alternate ():
48
- for option in entry .options :
49
- if result := match_one (line , source_pat , option , line_i ):
50
- bindings |= result .bindings
51
- line_i = result .end
52
- break
53
- else :
54
- # No options matched, so the entire pattern doesn't match
55
- return None
56
- case Repeat ():
128
+ result = None
129
+ match item :
130
+ case Alternate (options = opts ):
131
+ result = _match_alternate (line , source_pat , opts , line_i )
132
+ case Repeat (min = min_rep , max = max_rep , what = what , greedy = greedy ):
57
133
# try to match min repeats first;
58
- # if there isn't enough for that even then bail
59
- for _ in range (entry .min ):
60
- result = match_one (line , source_pat , entry .what , line_i )
61
- if result is None :
62
- return None
63
- bindings |= result .bindings
64
- line_i = result .end
65
- if entry .max is not None :
66
- more = entry .max - entry .min
67
- else :
68
- more = - 1
69
- if entry .greedy :
70
- # NOTE HERE: it is NOT 'more > 0' because more == -1 if infinite repeats allowed
71
- while more != 0 and line_i < len (line ):
72
- result = match_one (
73
- line , source_pat , entry .what , line_i )
74
- if result is None :
75
- # hit max number of repeats
76
- break
77
- bindings |= result .bindings
78
- line_i = result .end
79
- more -= 1
134
+ # if there isn't enough for that, then bail
135
+ result = _match_repeat_fixed (
136
+ line , source_pat , what , line_i , min_rep )
137
+ if result is None :
138
+ return None
139
+ more = max_rep - min_rep if max_rep else - 1
140
+ if greedy :
141
+ more = _match_repeat_greedy (
142
+ line , source_pat , what , line_i , more )
80
143
else :
81
- # NOTE HERE: it is NOT 'more > 0' because more == -1 if infinite repeats allowed
82
- while more != 0 and line_i < len (line ):
83
- # try to match remainder without more
84
- # if it works, stop (not greedy)
85
- if without := match_one (line , source_pat , pattern [patt_i + 1 :], line_i ):
86
- bindings |= without .bindings
87
- line_i = without .end
88
- patt_i = len (pattern )
89
- break
90
- if result := match_one (line , source_pat , entry .what , line_i ):
91
- bindings |= result .bindings
92
- line_i = result .end
93
- else :
94
- return None
95
- more -= 1
144
+ more = _match_repeat_lazy (
145
+ line , source_pat , what , pattern [patt_i + 1 :], line_i , more )
146
+ if more is not None :
147
+ result |= more
96
148
case Space ():
97
- if isinstance (elem , Space ):
98
- # greedy
99
- line_i += 1
100
- case Var ():
101
- if entry .use_cls :
102
- success = isinstance (elem , entry .cls )
103
- else :
104
- success = elem == entry .cls
105
- if success :
106
- line_i += 1
107
- bindings [entry .var ] = elem
108
- else :
109
- return None
110
- case _:
111
- if entry != elem :
112
- return None
113
- line_i += 1
114
- patt_i += 1
149
+ result = _match_space (line , line_i )
150
+ case Var () as entry :
151
+ result = _match_var (line , source_pat , entry , line_i )
152
+ case _ as entry :
153
+ result = entry != elem
154
+ if not result :
155
+ return None
156
+ if isinstance (result , Match ):
157
+ bindings |= result .bindings
158
+ assert result .start == line_i
159
+ line_i = result .end
160
+ else :
161
+ line_i += 1
162
+ if line_i >= len (line ):
163
+ break
115
164
if patt_i >= len (pattern ):
116
165
return Match (source_pat , start , line_i + 1 , bindings )
117
166
return None
118
167
119
168
120
- def match (line : list , patterns : list [Pattern ]) -> Match :
169
+ def match_patterns (line : list , patterns : list [Pattern ]) -> Match | None :
121
170
"""Finds the best match using the highest-precedence pattern that matches."""
122
171
for pattern in sorted (patterns ):
123
172
matches : list [Match ] = []
@@ -128,12 +177,15 @@ def match(line: list, patterns: list[Pattern]) -> Match:
128
177
return matches [- 1 if pattern .right else 0 ]
129
178
return None
130
179
180
+
131
181
if __name__ == "__main__" :
132
182
test_line = [0 , 1 , 2 , 1 , 2 , 1 , 2 , 77 ]
183
+ # """
133
184
patts = [
134
- Pattern (1 , [Repeat ([1 , 2 ], 1 , None , False ), Var ("foo" , int )], None ),
185
+ Pattern (1 , [Repeat ([1 , 2 ], 1 , None ), Var ("foo" , int )], None ),
135
186
]
136
- m = match (test_line , patts )
137
- test_line [m .start :m .end ] = ["foo " ]
187
+ m = match_patterns (test_line , patts )
188
+ test_line [m .start :m .end ] = ["replaced " ]
138
189
print (m )
139
190
print (test_line )
191
+ # """
0 commit comments