@@ -42,8 +42,8 @@ def __eq__(self, other: object) -> bool:
4242 return self .value == other .value
4343
4444
45- class ShellExpansion (Node ):
46- """Node representing shell expansion, e.g. _%(whoami)_ ."""
45+ class Expansion (Node ):
46+ """Abstract base class for expansion nodes ."""
4747
4848 def __init__ (self , body : str ) -> None :
4949 self .body = body
@@ -54,16 +54,20 @@ def __repr__(self) -> str:
5454 # don't have to reimplement __repr__()
5555 return f"{ self .__class__ .__name__ } ({ self .body !r} )"
5656
57- def __str__ (self ) -> str :
58- return f"%({ self .body } )"
59-
6057 def __eq__ (self , other : object ) -> bool :
6158 if not isinstance (other , self .__class__ ):
6259 return NotImplemented
6360 return self .body == other .body
6461
6562
66- class ExpressionExpansion (ShellExpansion ):
63+ class ShellExpansion (Expansion ):
64+ """Node representing shell expansion, e.g. _%(whoami)_."""
65+
66+ def __str__ (self ) -> str :
67+ return f"%({ self .body } )"
68+
69+
70+ class ExpressionExpansion (Expansion ):
6771 """Node representing expression expansion, e.g. _%[1+1]_."""
6872
6973 def __str__ (self ) -> str :
@@ -125,6 +129,29 @@ def __eq__(self, other: object) -> bool:
125129 )
126130
127131
132+ class SingleArgEnclosedMacroSubstitution (Node ):
133+ """
134+ Node representing single-argument bracket-enclosed macro substitution,
135+ e.g. _%{quote:Ancient Greek}_.
136+ """
137+
138+ def __init__ (self , name : str , arg : str ) -> None :
139+ self .name = name
140+ self .arg = arg
141+
142+ @formatted
143+ def __repr__ (self ) -> str :
144+ return f"SingleArgEnclosedMacroSubstitution({ self .name !r} , { self .arg !r} )"
145+
146+ def __str__ (self ) -> str :
147+ return f"%{{{ self .name } :{ self .arg } }}"
148+
149+ def __eq__ (self , other : object ) -> bool :
150+ if not isinstance (other , self .__class__ ):
151+ return NotImplemented
152+ return self .name == other .name and self .arg == other .arg
153+
154+
128155class ConditionalMacroExpansion (Node ):
129156 """Node representing conditional macro expansion, e.g. _%{?prerel:0.}_."""
130157
@@ -156,26 +183,6 @@ def __eq__(self, other: object) -> bool:
156183 )
157184
158185
159- class BuiltinMacro (Node ):
160- """Node representing built-in macro, e.g. _%{quote:Ancient Greek}_."""
161-
162- def __init__ (self , name : str , body : str ) -> None :
163- self .name = name
164- self .body = body
165-
166- @formatted
167- def __repr__ (self ) -> str :
168- return f"BuiltinMacro({ self .name !r} , { self .body !r} )"
169-
170- def __str__ (self ) -> str :
171- return f"%{{{ self .name } :{ self .body } }}"
172-
173- def __eq__ (self , other : object ) -> bool :
174- if not isinstance (other , self .__class__ ):
175- return NotImplemented
176- return self .name == other .name and self .body == other .body
177-
178-
179186class ValueParser :
180187 @classmethod
181188 def flatten (cls , nodes : List [Node ]) -> Generator [Node , None , None ]:
@@ -243,7 +250,7 @@ def find_macro_end(index):
243250 return i
244251
245252 result : List [Node ] = []
246- start = 0
253+ start = start0 = 0
247254 offset = 0
248255 while start < len (value ):
249256 try :
@@ -258,8 +265,6 @@ def find_macro_end(index):
258265 end = None
259266 if end is None :
260267 break
261- if end > start :
262- result .append (StringLiteral (value [start :end ]))
263268 start = end
264269 end = find_macro_end (start + 1 )
265270 if end is None :
@@ -269,24 +274,46 @@ def find_macro_end(index):
269274 elif value [start + 1 ] == "[" :
270275 result .append (ExpressionExpansion (value [start + 2 : end - 1 ]))
271276 elif value [start + 1 ] == "{" :
272- if ":" in value [start :end ]:
273- condition , body = value [start + 2 : end - 1 ].split (":" , maxsplit = 1 )
277+ index , delimiter = next (
278+ (
279+ (i , c )
280+ for i , c in enumerate (value [start + 2 : end - 1 ])
281+ if c in " :}"
282+ ),
283+ (- 1 , None ),
284+ )
285+ if delimiter == ":" :
286+ condition = value [start + 2 : start + index + 2 ]
287+ body = value [start + index + 3 : end - 1 ]
274288 tokens = re .split (r"^([?!]+)" , condition , maxsplit = 1 )
275289 prefix = "" if len (tokens ) == 1 else tokens [1 ]
276290 if "?" in prefix :
277291 result .append (
278292 ConditionalMacroExpansion (condition , cls .parse (body ))
279293 )
280294 else :
281- result .append (BuiltinMacro (condition , body ))
295+ result .append (
296+ SingleArgEnclosedMacroSubstitution (condition , body )
297+ )
282298 else :
283- result .append (EnclosedMacroSubstitution (value [start + 2 : end - 1 ]))
299+ body = value [start + 2 : end - 1 ]
300+ name = (
301+ value [start + 2 : start + index + 2 ]
302+ if delimiter is not None
303+ else body
304+ )
305+ if not re .match (r"[A-Za-z_]\w*$" , name .lstrip ("?!" ), re .ASCII ):
306+ start += 2
307+ continue
308+ result .append (EnclosedMacroSubstitution (body ))
284309 else :
285310 result .append (MacroSubstitution (value [start + 1 : end ]))
286- start = end
311+ if start > start0 :
312+ result .insert (- 1 , StringLiteral (value [start0 :start ]))
313+ start = start0 = end
287314 offset = 0
288- if value [start :]:
289- result .append (StringLiteral (value [start :]))
315+ if value [start0 :]:
316+ result .append (StringLiteral (value [start0 :]))
290317 return result
291318
292319 @classmethod
@@ -387,7 +414,14 @@ def evaluate(node):
387414 tokens .append (("c" , "" , node ))
388415 elif isinstance (node , StringLiteral ):
389416 tokens .append (("v" , node .value , "" ))
390- elif isinstance (node , (ShellExpansion , ExpressionExpansion , BuiltinMacro )):
417+ elif isinstance (
418+ node ,
419+ (
420+ ShellExpansion ,
421+ ExpressionExpansion ,
422+ SingleArgEnclosedMacroSubstitution ,
423+ ),
424+ ):
391425 const = expand (str (node ))
392426 tokens .append (("c" , const , str (node )))
393427 elif isinstance (node , MacroSubstitution ):
0 commit comments