1717
1818from cedarscript_ast_parser import Marker , RelativeMarker , RelativePositionType , MarkerType , BodyOrWhole
1919
20- from .indentation_kit import get_line_indent_count
20+ from .indentation_kit import get_line_indent_count_from_lines
2121
2222MATCH_TYPES = ('exact' , 'stripped' , 'normalized' , 'partial' )
2323
@@ -190,25 +190,71 @@ def from_line_marker(
190190 f"must be less than or equal to line count ({ len (lines )} )"
191191 )
192192
193- for i in range (search_start_index , search_end_index ):
194- line = lines [i ]
195- reference_indent = get_line_indent_count (line )
193+ marker_subtype = (search_term .marker_subtype or "" ).casefold ()
194+
195+ match marker_subtype :
196+ case 'number' : # Simple case: a line number
197+ index = int (stripped_search ) - 1
198+ assert 0 <= index < len (lines ), (
199+ f"Line number { stripped_search } out of bounds "
200+ f"(must be in interval [1, { len (lines )} ])"
201+ )
202+ reference_indent = get_line_indent_count_from_lines (lines , index )
203+ index += calc_index_delta_for_relative_position (search_term )
204+ return cls (index , index , reference_indent )
196205
197- # Check for exact match
198- if search_line == line :
199- matches ['exact' ].append ((i , reference_indent ))
206+ case 'regex' :
207+ try :
208+ pattern = re .compile (search_line )
209+ except re .error as e :
210+ raise ValueError (f"Invalid regex pattern '{ search_line } ': { e } " )
200211
201- # Check for stripped match
202- elif stripped_search == line .strip ():
203- matches ['stripped' ].append ((i , reference_indent ))
212+ case _:
213+ pattern = None
204214
205- # Check for normalized match
206- elif normalized_search_line == cls . normalize_line ( line ):
207- matches [ 'normalized' ]. append (( i , reference_indent ) )
215+ # Not a line number, so we need to find all line matches
216+ for i in range ( search_start_index , search_end_index ):
217+ reference_indent = get_line_indent_count_from_lines ( lines , i )
208218
209- # Dangerous! Last resort!
210- elif normalized_search_line .casefold () in cls .normalize_line (line ).casefold ():
211- matches ['partial' ].append ((i , reference_indent ))
219+ line = lines [i ]
220+ match marker_subtype :
221+
222+ case 'regex' :
223+ if pattern .search (line ) or pattern .search (line .strip ()):
224+ matches ['exact' ].append ((i , reference_indent ))
225+
226+ case 'prefix' :
227+ # Check for stripped prefix match
228+ if line .strip ().startswith (stripped_search ):
229+ matches ['exact' ].append ((i , reference_indent ))
230+ # Check for normalized prefix match
231+ elif cls .normalize_line (line ).startswith (normalized_search_line ):
232+ matches ['normalized' ].append ((i , reference_indent ))
233+
234+ case 'suffix' :
235+ # Check for stripped suffix match
236+ if line .strip ().endswith (stripped_search ):
237+ matches ['exact' ].append ((i , reference_indent ))
238+ # Check for normalized suffix match
239+ elif cls .normalize_line (line ).endswith (normalized_search_line ):
240+ matches ['normalized' ].append ((i , reference_indent ))
241+
242+ case _:
243+ # Check for exact match
244+ if search_line == line :
245+ matches ['exact' ].append ((i , reference_indent ))
246+
247+ # Check for stripped match
248+ elif stripped_search == line .strip ():
249+ matches ['stripped' ].append ((i , reference_indent ))
250+
251+ # Check for normalized match
252+ elif normalized_search_line == cls .normalize_line (line ):
253+ matches ['normalized' ].append ((i , reference_indent ))
254+
255+ # Dangerous! Last resort!
256+ elif normalized_search_line .casefold () in cls .normalize_line (line ).casefold ():
257+ matches ['partial' ].append ((i , reference_indent ))
212258
213259 offset = search_term .offset or 0
214260 max_match_count = max ([len (m ) for m in matches .values ()])
@@ -241,16 +287,7 @@ def from_line_marker(
241287 case 'partial' :
242288 print (f"Note: Won't accept { match_type } match at index { index } for { search_term } " )
243289 continue
244- if isinstance (search_term , RelativeMarker ):
245- match search_term .qualifier :
246- case RelativePositionType .BEFORE :
247- index += - 1
248- case RelativePositionType .AFTER :
249- index += 1
250- case RelativePositionType .AT :
251- pass
252- case _ as invalid :
253- raise ValueError (f"Not implemented: { invalid } " )
290+ index += calc_index_delta_for_relative_position (search_term )
254291 return cls (index , index , reference_indent )
255292
256293 return None
@@ -259,12 +296,29 @@ def from_line_marker(
259296RangeSpec .EMPTY = RangeSpec (0 , - 1 , 0 )
260297
261298
299+ def calc_index_delta_for_relative_position (marker : Marker ):
300+ match marker :
301+ case RelativeMarker (qualifier = RelativePositionType .BEFORE ):
302+ return - 1
303+ case RelativeMarker (qualifier = RelativePositionType .AFTER ):
304+ return 1
305+ case RelativeMarker (qualifier = RelativePositionType .AT ):
306+ pass
307+ case RelativeMarker (qualifier = invalid ):
308+ raise ValueError (f"Not implemented: { invalid } " )
309+ case _:
310+ pass
311+ return 0
312+
313+
262314class ParentInfo (NamedTuple ):
263315 parent_name : str
264316 parent_type : str
265317
318+
266319ParentRestriction : TypeAlias = RangeSpec | str | None
267320
321+
268322class IdentifierBoundaries (NamedTuple ):
269323 """
270324 Represents the boundaries of an identifier in code, including its whole range and body range.
0 commit comments