/// <summary> /// Parse a string with any number of interpolation tokens (%1, %2, ...). /// '%' characters may be self-escaped (%%). /// </summary> /// <param name="message">Text containing interpolation tokens.</param> /// <returns>Array of strings and numbers.</returns> public static JsArray <Union <string, int> > tokenizeInterpolation(string message) { var tokens = new JsArray <Union <string, int> >(); var chars = new JsArray <char>(message.ToCharArray()); // End marker. chars.Push('\0'); // Parse the message with a finite state machine. // 0 - Base case. // 1 - % found. // 2 - Digit found. var state = 0; var buffer = new JsArray <char>(); var number = ""; for (var i = 0; i < chars.Length; i++) { var c = chars[i]; if (state == 0) { if (c == '%') { state = 1; // Start escape. } else { buffer.Push(c); // Regular char. } } else if (state == 1) { if (c == '%') { buffer.Push(c); // Escaped %: %% state = 0; } else if ('0' <= c && c <= '9') { state = 2; number = c.ToString(); var text2 = new String(buffer.ToArray()); if (!String.IsNullOrEmpty(text2)) { tokens.Push(text2); } buffer = new JsArray <char>(); } else { buffer.Push('%', c); // Not an escape: %a state = 0; } } else if (state == 2) { if ('0' <= c && c <= '9') { number += c; // Multi-digit number. } else { tokens.Push(Int32.Parse(number)); i--; // Parse this char again. state = 0; } } } var text = new String(buffer.ToArray()); if (!String.IsNullOrEmpty(text)) { tokens.Push(text); } return(tokens); }