/// <summary> /// Parse a string field /// </summary> static bool ParseString(Action <object> add, TextVisiter input, FormatSpecifier spec) { // Skip any whitespace input.MovePastWhitespace(); // Parse string characters var start = input.Position; while (!input.EndOfText && !char.IsWhiteSpace(input.Peek())) { input.MoveAhead(); } // Don't exceed field width if (spec.Width > 0) { var count = input.Position - start; if (spec.Width < count) { input.MoveAhead(spec.Width - count); } } // Extract token if (input.Position > start) { if (!spec.NoResult) { add(input.Extract(start, input.Position)); } return(true); } return(false); }
/// <summary> /// Parse an octal field /// </summary> static bool ParseOctal(Action <object> add, TextVisiter input, FormatSpecifier spec) { // Skip any whitespace input.MovePastWhitespace(); // Parse digits var start = input.Position; while (IsValidDigit(input.Peek(), 8)) { input.MoveAhead(); } // Don't exceed field width if (spec.Width > 0) { var count = input.Position - start; if (spec.Width < count) { input.MoveAhead(spec.Width - count); } } // Extract token if (input.Position > start) { if (!spec.NoResult) { add(Unsigned(input.Extract(start, input.Position), spec.Modifier, 8)); } return(true); } return(false); }
/// <summary> /// Parse integer field /// </summary> static bool ParseDecimal(Action <object> add, TextVisiter input, FormatSpecifier spec) { // Skip any whitespace input.MovePastWhitespace(); // Parse leading sign var radix = 10; var start = input.Position; if (input.Peek() == '+' || input.Peek() == '-') { input.MoveAhead(); } else if (input.Peek() == '0') { if (char.ToLower(input.Peek(1)) == 'x') { radix = 16; input.MoveAhead(2); } else { radix = 8; input.MoveAhead(); } } // Parse digits while (IsValidDigit(input.Peek(), radix)) { input.MoveAhead(); } // Don't exceed field width if (spec.Width > 0) { var count = input.Position - start; if (spec.Width < count) { input.MoveAhead(spec.Width - count); } } // Extract token if (input.Position > start) { if (!spec.NoResult) { add(spec.Type == Types.Decimal ? Signed(input.Extract(start, input.Position), spec.Modifier, radix) : Unsigned(input.Extract(start, input.Position), spec.Modifier, radix)); } return(true); } return(false); }
/// <summary> /// Parses the input string according to the rules in the /// format string. Similar to the standard C library's /// sscanf() function. Parsed fields are placed in the /// class' Results member. /// </summary> /// <param name="input">String to parse</param> /// <param name="format">Specifies rules for parsing input</param> public static int Parse(string input, string format, out IList <object> values) { var inp = new TextVisiter(input); var fmt = new TextVisiter(format); var results = new List <object>(); var spec = new FormatSpecifier(); var count = 0; // Process input string as indicated in format string while (!fmt.EndOfText && !inp.EndOfText) { if (ParseFormatSpecifier(fmt, spec)) { // Found a format specifier var parser = _typeParsers.First(tp => tp.Type == spec.Type); if (parser.Parser(results.Add, inp, spec)) { count++; } else { break; } } else if (char.IsWhiteSpace(fmt.Peek())) { // Whitespace inp.MovePastWhitespace(); fmt.MoveAhead(); } else if (fmt.Peek() == inp.Peek()) { // Matching character inp.MoveAhead(); fmt.MoveAhead(); } else { break; // Break at mismatch } } // Return number of fields successfully parsed values = results; return(count); }
/// <summary> /// Parse a scan-set field /// </summary> static bool ParseScanSet(Action <object> add, TextVisiter input, FormatSpecifier spec) { // Parse characters var start = input.Position; if (!spec.ScanSetExclude) { while (spec.ScanSet.Contains(input.Peek())) { input.MoveAhead(); } } else { while (!input.EndOfText && !spec.ScanSet.Contains(input.Peek())) { input.MoveAhead(); } } // Don't exceed field width if (spec.Width > 0) { var count = input.Position - start; if (spec.Width < count) { input.MoveAhead(spec.Width - count); } } // Extract token if (input.Position > start) { if (!spec.NoResult) { add(input.Extract(start, input.Position)); } return(true); } return(false); }
/// <summary> /// Parse a character field /// </summary> static bool ParseCharacter(Action <object> add, TextVisiter input, FormatSpecifier spec) { // Parse character(s) var start = input.Position; var count = (spec.Width > 1) ? spec.Width : 1; while (!input.EndOfText && count-- > 0) { input.MoveAhead(); } // Extract token if (count <= 0 && input.Position > start) { if (!spec.NoResult) { var token = input.Extract(start, input.Position); add(token.Length > 1 ? token.ToCharArray() : token[0]); } return(true); } return(false); }
/// <summary> /// Parse a floating-point field /// </summary> static bool ParseFloat(Action <object> add, TextVisiter input, FormatSpecifier spec) { // Skip any whitespace input.MovePastWhitespace(); // Parse leading sign var start = input.Position; if (input.Peek() == '+' || input.Peek() == '-') { input.MoveAhead(); } // Parse digits var hasPoint = false; while (char.IsDigit(input.Peek()) || input.Peek() == '.') { if (input.Peek() == '.') { if (hasPoint) { break; } hasPoint = true; } input.MoveAhead(); } // Parse exponential notation if (char.ToLower(input.Peek()) == 'e') { input.MoveAhead(); if (input.Peek() == '+' || input.Peek() == '-') { input.MoveAhead(); } while (char.IsDigit(input.Peek())) { input.MoveAhead(); } } // Don't exceed field width if (spec.Width > 0) { var count = input.Position - start; if (spec.Width < count) { input.MoveAhead(spec.Width - count); } } // Because we parse the exponential notation before we apply any field-width constraint, it becomes awkward to verify // we have a valid floating point token. To prevent an exception, we use TryParse() here instead of Parse(). // Extract token if (input.Position > start && double.TryParse(input.Extract(start, input.Position), out var result)) { if (!spec.NoResult) { add(spec.Modifier == Modifiers.Long || spec.Modifier == Modifiers.LongLong ? result : result); } return(true); } return(false); }
/// <summary> /// Attempts to parse a field format specifier from the format string. /// </summary> static bool ParseFormatSpecifier(TextVisiter format, FormatSpecifier spec) { // Return if not a field format specifier if (format.Peek() != '%') { return(false); } format.MoveAhead(); // Return if "%%" (treat as '%' literal) if (format.Peek() == '%') { return(false); } // Test for asterisk, which indicates result is not stored if (format.Peek() == '*') { spec.NoResult = true; format.MoveAhead(); } else { spec.NoResult = false; } // Parse width var start = format.Position; while (char.IsDigit(format.Peek())) { format.MoveAhead(); } if (format.Position > start) { spec.Width = int.Parse(format.Extract(start, format.Position)); } else { spec.Width = 0; } // Parse modifier if (format.Peek() == 'h') { format.MoveAhead(); if (format.Peek() == 'h') { format.MoveAhead(); spec.Modifier = Modifiers.ShortShort; } else { spec.Modifier = Modifiers.Short; } } else if (char.ToLower(format.Peek()) == 'l') { format.MoveAhead(); if (format.Peek() == 'l') { format.MoveAhead(); spec.Modifier = Modifiers.LongLong; } else { spec.Modifier = Modifiers.Long; } } else { spec.Modifier = Modifiers.None; } // Parse type switch (format.Peek()) { case 'c': spec.Type = Types.Character; break; case 'd': case 'i': spec.Type = Types.Decimal; break; case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': spec.Type = Types.Float; break; case 'o': spec.Type = Types.Octal; break; case 's': spec.Type = Types.String; break; case 'u': spec.Type = Types.Unsigned; break; case 'x': case 'X': spec.Type = Types.Hexadecimal; break; case '[': spec.Type = Types.ScanSet; format.MoveAhead(); // Parse scan set characters if (format.Peek() == '^') { spec.ScanSetExclude = true; format.MoveAhead(); } else { spec.ScanSetExclude = false; } start = format.Position; // Treat immediate ']' as literal if (format.Peek() == ']') { format.MoveAhead(); } format.MoveTo(']'); if (format.EndOfText) { throw new Exception("Type specifier expected character : ']'"); } spec.ScanSet = format.Extract(start, format.Position); break; default: throw new Exception($"Unknown format type specified : '{format.Peek()}'"); } format.MoveAhead(); return(true); }