@@ -13,7 +13,7 @@ public static partial class SymbolExtractor
1313 @"^\s*use\s+constant\s+\{" ,
1414 RegexOptions . Compiled | RegexOptions . CultureInvariant ) ;
1515 private static readonly Regex PerlHashConstantKeyRegex = new (
16- @"(?:^|,)\s*(?:""(?<quoted>(?:\\x[0-9A-Fa-f]{2 }|\\u [0-9A-Fa-f]{4 }|\\.|[^""])*)""|'(?<quoted>(?:\\x[0-9A-Fa-f]{2 }|\\u [0-9A-Fa-f]{4 }|\\.|[^'])*)'|(?<bare>[\p{L}_][\p{L}\p{Nd}_]*))\s*=>" ,
16+ @"(?:^|,)\s*(?:""(?<quoted>(?:\\x\{ [0-9A-Fa-f]+\ }|\\x [0-9A-Fa-f]{2 }|\\.|[^""])*)""|'(?<quoted>(?:\\x\{ [0-9A-Fa-f]+\ }|\\x [0-9A-Fa-f]{2 }|\\.|[^'])*)'|(?<bare>[\p{L}_][\p{L}\p{Nd}_]*))\s*=>" ,
1717 RegexOptions . Compiled | RegexOptions . CultureInvariant ) ;
1818
1919 private static void ExtractPerlHashConstantSymbols ( long fileId , string [ ] lines , List < SymbolRecord > symbols )
@@ -75,17 +75,22 @@ private static string DecodePerlQuotedConstantEscapes(string value)
7575 }
7676
7777 var marker = value [ i + 1 ] ;
78- if ( marker == 'x' && i + 3 < value . Length && TryParseHexScalar ( value . AsSpan ( i + 2 , 2 ) , out var hexByte ) )
78+ if ( marker == 'x'
79+ && i + 3 < value . Length
80+ && value [ i + 2 ] == '{'
81+ && TryFindPerlBracedHexEscapeEnd ( value , i + 3 , out var escapeEnd )
82+ && TryParseHexScalar ( value . AsSpan ( i + 3 , escapeEnd - ( i + 3 ) ) , out var scalar )
83+ && scalar <= 0x10FFFF )
7984 {
80- builder . Append ( ( char ) hexByte ) ;
81- i += 3 ;
85+ builder . Append ( char . ConvertFromUtf32 ( scalar ) ) ;
86+ i = escapeEnd ;
8287 continue ;
8388 }
8489
85- if ( marker == 'u ' && i + 5 < value . Length && TryParseHexScalar ( value . AsSpan ( i + 2 , 4 ) , out var unicodeScalar ) )
90+ if ( marker == 'x ' && i + 3 < value . Length && TryParseHexScalar ( value . AsSpan ( i + 2 , 2 ) , out var hexByte ) )
8691 {
87- builder . Append ( char . ConvertFromUtf32 ( unicodeScalar ) ) ;
88- i += 5 ;
92+ builder . Append ( ( char ) hexByte ) ;
93+ i += 3 ;
8994 continue ;
9095 }
9196
@@ -99,6 +104,21 @@ private static string DecodePerlQuotedConstantEscapes(string value)
99104 private static bool TryParseHexScalar ( ReadOnlySpan < char > value , out int scalar )
100105 => int . TryParse ( value , NumberStyles . HexNumber , CultureInfo . InvariantCulture , out scalar ) ;
101106
107+ private static bool TryFindPerlBracedHexEscapeEnd ( string value , int start , out int end )
108+ {
109+ for ( var i = start ; i < value . Length ; i ++ )
110+ {
111+ if ( value [ i ] == '}' )
112+ {
113+ end = i ;
114+ return i > start ;
115+ }
116+ }
117+
118+ end = - 1 ;
119+ return false ;
120+ }
121+
102122 private readonly record struct PerlBodyLineSegment ( int BodyStartIndex , int LineIndex , int ColumnOffset ) ;
103123
104124 private static bool TryCollectPerlHashConstantBody (
@@ -124,11 +144,13 @@ private static bool TryCollectPerlHashConstantBody(
124144 return false ;
125145
126146 var builder = new System . Text . StringBuilder ( ) ;
147+ var inQuotedKey = false ;
148+ var quotedKeyDelimiter = '\0 ' ;
127149 for ( var lineIndex = startLineIndex ; lineIndex < lines . Length ; lineIndex ++ )
128150 {
129151 var line = lines [ lineIndex ] ;
130152 var segmentStart = lineIndex == startLineIndex ? openBraceIndex + 1 : 0 ;
131- var closeBraceIndex = line . IndexOf ( '}' , segmentStart ) ;
153+ var closeBraceIndex = FindPerlHashConstantBlockCloseBrace ( line , segmentStart , ref inQuotedKey , ref quotedKeyDelimiter ) ;
132154 var segmentEnd = closeBraceIndex >= 0 ? closeBraceIndex : line . Length ;
133155 if ( segmentEnd > segmentStart )
134156 {
@@ -149,6 +171,45 @@ private static bool TryCollectPerlHashConstantBody(
149171 return false ;
150172 }
151173
174+ private static int FindPerlHashConstantBlockCloseBrace (
175+ string line ,
176+ int start ,
177+ ref bool inQuotedKey ,
178+ ref char quotedKeyDelimiter )
179+ {
180+ for ( var i = start ; i < line . Length ; i ++ )
181+ {
182+ if ( inQuotedKey )
183+ {
184+ if ( line [ i ] == '\\ ' && i + 1 < line . Length )
185+ {
186+ i ++ ;
187+ continue ;
188+ }
189+
190+ if ( line [ i ] == quotedKeyDelimiter )
191+ {
192+ inQuotedKey = false ;
193+ quotedKeyDelimiter = '\0 ' ;
194+ }
195+
196+ continue ;
197+ }
198+
199+ if ( line [ i ] == '"' || line [ i ] == '\' ' )
200+ {
201+ inQuotedKey = true ;
202+ quotedKeyDelimiter = line [ i ] ;
203+ continue ;
204+ }
205+
206+ if ( line [ i ] == '}' )
207+ return i ;
208+ }
209+
210+ return - 1 ;
211+ }
212+
152213 private static ( int LineIndex , int Column ) ResolvePerlHashConstantBodyPosition (
153214 IReadOnlyList < PerlBodyLineSegment > lineSegments ,
154215 int bodyIndex )
0 commit comments