sscanf method

List sscanf(
  1. String str,
  2. String format
)

Parses the string str according to format and returns matched values.

This is the string-scanning counterpart to sprintf. Each % specifier in format consumes one token from str and appends the parsed value to the returned List. The list length equals C's integer return value (number of successfully matched items).

Supported specifiers: d, i (auto-base), u, o, x/X, f/e/g (all yield double), s (whitespace-delimited), c (single char as String), n (chars consumed so far), %%.

Width modifier: %5d reads at most 5 characters for that field.

Suppress-assignment: %*d discards the matched value without adding it to the result list.

Deviation from C: values are returned in a List instead of being written through out-pointer arguments.

Example:

final r = stdc.sscanf("42 3.14 hello", "%d %f %s");
// r == [42, 3.14, "hello"]

final r2 = stdc.sscanf("0xff 010 99", "%i %i %i");
// r2 == [255, 8, 99]  — auto-base detection

Implementation

List<dynamic> sscanf(String str, String format) {
  final results = <dynamic>[];
  int si = 0; // position in input string
  int fi = 0; // position in format string

  while (fi < format.length) {
    if (format[fi] == '%') {
      fi++;
      if (fi >= format.length) break;

      // Suppress-assignment flag
      bool suppress = false;
      if (format[fi] == '*') {
        suppress = true;
        fi++;
      }

      // Width
      int width = 0;
      while (fi < format.length && _fmtIsDigit(format[fi])) {
        width = width * 10 + (format.codeUnitAt(fi) - 48);
        fi++;
      }

      // Length modifier (consume, ignore)
      if (fi < format.length) {
        final c = format[fi];
        if (c == 'h' ||
            c == 'l' ||
            c == 'L' ||
            c == 'z' ||
            c == 't' ||
            c == 'j') {
          fi++;
          if (fi < format.length && (format[fi] == 'l' || format[fi] == 'h'))
            fi++;
        }
      }

      if (fi >= format.length) break;
      final spec = format[fi++];

      if (spec == '%') {
        // Match literal '%'
        if (si < str.length && str[si] == '%') si++;
        continue;
      }

      // Skip leading whitespace in input for all specifiers except %c and %n
      if (spec != 'c' && spec != 'n') {
        while (si < str.length && _fmtIsWhitespace(str[si])) si++;
      }

      if (si >= str.length && spec != 'n') break;

      switch (spec) {
        case 'd':
          final m = _scanInt(str, si, width, 10, true);
          if (m == null) return results;
          si = m.$1;
          if (!suppress) results.add(m.$2);
        case 'u':
          final m = _scanInt(str, si, width, 10, false);
          if (m == null) return results;
          si = m.$1;
          if (!suppress) results.add(m.$2);
        case 'i':
          final m = _scanIntAuto(str, si, width);
          if (m == null) return results;
          si = m.$1;
          if (!suppress) results.add(m.$2);
        case 'o':
          final m = _scanInt(str, si, width, 8, false);
          if (m == null) return results;
          si = m.$1;
          if (!suppress) results.add(m.$2);
        case 'x':
        case 'X':
          // Skip optional 0x/0X prefix
          final prefixStart = si;
          if (si + 1 < str.length &&
              str[si] == '0' &&
              (str[si + 1] == 'x' || str[si + 1] == 'X')) {
            si += 2;
          }
          final effectiveWidth = width > 0
              ? (width - (si - prefixStart)).clamp(0, str.length)
              : 0;
          final m = _scanInt(str, si, effectiveWidth, 16, false);
          if (m == null) return results;
          si = m.$1;
          if (!suppress) results.add(m.$2);
        case 'f':
        case 'e':
        case 'E':
        case 'g':
        case 'G':
          final m = _scanDouble(str, si, width);
          if (m == null) return results;
          si = m.$1;
          if (!suppress) results.add(m.$2);
        case 's':
          final start = si;
          int count = 0;
          while (si < str.length &&
              !_fmtIsWhitespace(str[si]) &&
              (width == 0 || count < width)) {
            si++;
            count++;
          }
          if (si == start) return results;
          if (!suppress) results.add(str.substring(start, si));
        case 'c':
          final n = width == 0 ? 1 : width;
          if (si + n > str.length) return results;
          if (!suppress) results.add(str[si]); // single char as String
          si += n;
        case 'n':
          if (!suppress) results.add(si);
        default:
          return results; // unknown specifier — stop
      }
    } else if (_fmtIsWhitespace(format[fi])) {
      // Whitespace in format matches zero or more whitespace in input
      fi++;
      while (si < str.length && _fmtIsWhitespace(str[si])) si++;
    } else {
      // Literal character must match exactly
      if (si < str.length && str[si] == format[fi]) {
        si++;
        fi++;
      } else {
        break;
      }
    }
  }

  return results;
}