@@ -176,95 +176,160 @@ public StatParseResult TryParse(ReadOnlySpan<char> message, Span<int> result)
176176
177177 private StatParseResult TryParseIsLeft ( ReadOnlySpan < char > message , Span < int > result , char separator , ReadOnlySpan < char > valueGap )
178178 {
179+ // Parse left-to-right by splitting on separator, then identifying which stat each segment contains.
180+ // Format: "StatName Value / StatName Value / ..."
179181 var rec = new StatParseResult ( ) ;
180182
181- for ( int i = 0 ; i < Names . Length ; i ++ )
183+ while ( message . Length ! = 0 )
182184 {
183- if ( message . Length == 0 )
184- break ;
185+ // Get the next segment
186+ ReadOnlySpan < char > segment ;
187+ var indexSeparator = message . IndexOf ( separator ) ;
188+ if ( indexSeparator != - 1 )
189+ {
190+ segment = message [ ..indexSeparator ] . Trim ( ) ;
191+ message = message [ ( indexSeparator + 1 ) ..] . TrimStart ( ) ;
192+ }
193+ else
194+ {
195+ segment = message . Trim ( ) ;
196+ message = default ;
197+ }
185198
186- var statName = Names [ i ] ;
187- var index = message . IndexOf ( statName , StringComparison . OrdinalIgnoreCase ) ;
188- if ( index == - 1 )
199+ if ( segment . Length == 0 )
200+ {
201+ rec . MarkDirty ( ) ; // empty segment
189202 continue ;
203+ }
190204
191- if ( index != 0 )
192- rec . MarkDirty ( ) ; // We have something before our stat name, so it isn't clean.
193-
194- message = message [ statName . Length ..] . TrimStart ( ) ;
195- if ( valueGap . Length > 0 && message . StartsWith ( valueGap ) )
196- message = message [ valueGap . Length ..] . TrimStart ( ) ;
197-
198- var value = message ;
205+ // Find which stat name this segment contains (should be at the start for IsLeft)
206+ var statIndex = TryFindStatNameAtStart ( segment , out var statNameLength ) ;
207+ if ( statIndex == - 1 )
208+ {
209+ rec . MarkDirty ( ) ; // unrecognized stat
210+ continue ;
211+ }
199212
200- var indexSeparator = value . IndexOf ( separator ) ;
201- if ( indexSeparator != - 1 )
202- value = value [ ..indexSeparator ] . Trim ( ) ;
203- else
204- message = default ; // everything remaining belongs in the value we are going to parse.
213+ // Extract the value after the stat name
214+ var value = segment [ statNameLength ..] . TrimStart ( ) ;
215+ if ( valueGap . Length > 0 && value . StartsWith ( valueGap ) )
216+ value = value [ valueGap . Length ..] . TrimStart ( ) ;
205217
206218 if ( value . Length != 0 )
207219 {
208- var amped = TryPeekAmp ( ref value , ref rec , i ) ;
220+ var amped = TryPeekAmp ( ref value , ref rec , statIndex ) ;
209221 if ( amped && value . Length == 0 )
210- rec . MarkParsed ( index ) ;
222+ rec . MarkParsed ( statIndex ) ;
211223 else
212- TryParse ( result , ref rec , value , i ) ;
224+ TryParse ( result , ref rec , value , statIndex ) ;
225+ }
226+ else if ( rec . WasParsed ( statIndex ) )
227+ {
228+ rec . MarkDirty ( ) ; // duplicate stat
213229 }
214-
215- if ( indexSeparator != - 1 )
216- message = message [ ( indexSeparator + 1 ) ..] . TrimStart ( ) ;
217- else
218- break ;
219230 }
220231
221- if ( ! message . IsWhiteSpace ( ) ) // shouldn't be anything left in the message to parse
222- rec . MarkDirty ( ) ;
223232 rec . FinishParse ( Names . Length ) ;
224233 return rec ;
225234 }
226235
236+ /// <summary>
237+ /// Tries to find a stat name at the start of the segment.
238+ /// </summary>
239+ /// <param name="segment">Segment to search</param>
240+ /// <param name="length">Length of the matched stat name</param>
241+ /// <returns>Stat index if found, -1 otherwise</returns>
242+ private int TryFindStatNameAtStart ( ReadOnlySpan < char > segment , out int length )
243+ {
244+ for ( int i = 0 ; i < Names . Length ; i ++ )
245+ {
246+ var name = Names [ i ] ;
247+ if ( segment . StartsWith ( name , StringComparison . OrdinalIgnoreCase ) )
248+ {
249+ length = name . Length ;
250+ return i ;
251+ }
252+ }
253+ length = 0 ;
254+ return - 1 ;
255+ }
256+
257+ /// <summary>
258+ /// Tries to find a stat name at the end of the segment.
259+ /// </summary>
260+ /// <param name="segment">Segment to search</param>
261+ /// <param name="length">Length of the matched stat name</param>
262+ /// <returns>Stat index if found, -1 otherwise</returns>
263+ private int TryFindStatNameAtEnd ( ReadOnlySpan < char > segment , out int length )
264+ {
265+ for ( int i = 0 ; i < Names . Length ; i ++ )
266+ {
267+ var name = Names [ i ] ;
268+ if ( segment . EndsWith ( name , StringComparison . OrdinalIgnoreCase ) )
269+ {
270+ length = name . Length ;
271+ return i ;
272+ }
273+ }
274+ length = 0 ;
275+ return - 1 ;
276+ }
277+
227278 private StatParseResult TryParseRight ( ReadOnlySpan < char > message , Span < int > result , char separator , ReadOnlySpan < char > valueGap )
228279 {
280+ // Parse left-to-right by splitting on separator, then identifying which stat each segment contains.
281+ // Format: "Value StatName / Value StatName / ..."
229282 var rec = new StatParseResult ( ) ;
230283
231- for ( int i = 0 ; i < Names . Length ; i ++ )
284+ while ( message . Length ! = 0 )
232285 {
233- if ( message . Length == 0 )
234- break ;
286+ // Get the next segment
287+ ReadOnlySpan < char > segment ;
288+ var indexSeparator = message . IndexOf ( separator ) ;
289+ if ( indexSeparator != - 1 )
290+ {
291+ segment = message [ ..indexSeparator ] . Trim ( ) ;
292+ message = message [ ( indexSeparator + 1 ) ..] . TrimStart ( ) ;
293+ }
294+ else
295+ {
296+ segment = message . Trim ( ) ;
297+ message = default ;
298+ }
235299
236- var statName = Names [ i ] ;
237- var index = message . IndexOf ( statName , StringComparison . OrdinalIgnoreCase ) ;
238- if ( index == - 1 )
300+ if ( segment . Length == 0 )
301+ {
302+ rec . MarkDirty ( ) ; // empty segment
239303 continue ;
304+ }
240305
241- var value = message [ .. index ] . Trim ( ) ;
242- var indexSeparator = value . LastIndexOf ( separator ) ;
243- if ( indexSeparator ! = - 1 )
306+ // Find which stat name this segment contains (should be at the end for Right/English style)
307+ var statIndex = TryFindStatNameAtEnd ( segment , out var statNameLength ) ;
308+ if ( statIndex = = - 1 )
244309 {
245- rec . MarkDirty ( ) ; // We have something before our stat name, so it isn't clean.
246- value = value [ ( indexSeparator + 1 ) .. ] . TrimStart ( ) ;
310+ rec . MarkDirty ( ) ; // unrecognized stat
311+ continue ;
247312 }
248313
314+ // Extract the value before the stat name
315+ var value = segment [ ..^ statNameLength ] . TrimEnd ( ) ;
249316 if ( valueGap . Length > 0 && value . EndsWith ( valueGap ) )
250- value = value [ ..^ valueGap . Length ] ;
317+ value = value [ ..^ valueGap . Length ] . TrimEnd ( ) ;
251318
252319 if ( value . Length != 0 )
253320 {
254- var amped = TryPeekAmp ( ref value , ref rec , i ) ;
321+ var amped = TryPeekAmp ( ref value , ref rec , statIndex ) ;
255322 if ( amped && value . Length == 0 )
256- rec . MarkParsed ( index ) ;
323+ rec . MarkParsed ( statIndex ) ;
257324 else
258- TryParse ( result , ref rec , value , i ) ;
325+ TryParse ( result , ref rec , value , statIndex ) ;
326+ }
327+ else if ( rec . WasParsed ( statIndex ) )
328+ {
329+ rec . MarkDirty ( ) ; // duplicate stat
259330 }
260-
261- message = message [ ( index + statName . Length ) ..] . TrimStart ( ) ;
262- if ( message . StartsWith ( separator ) )
263- message = message [ 1 ..] . TrimStart ( ) ;
264331 }
265332
266- if ( ! message . IsWhiteSpace ( ) ) // shouldn't be anything left in the message to parse
267- rec . MarkDirty ( ) ;
268333 rec . FinishParse ( Names . Length ) ;
269334 return rec ;
270335 }
0 commit comments