sscanf method
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;
}