vsprintf method

String vsprintf(
  1. String format,
  2. va_list arg
)

Formats arguments into a String using a va_list.

This is the core formatting engine. Supports the full C99 format specifier syntax: %[flags][width][.precision][length]specifier.

  • Flags: -, +, , 0, #
  • Width / Precision: integer or * (next arg)
  • Length modifiers: h, l, ll, L, z, t, j — consumed and ignored (Dart integers are always 64-bit)
  • Specifiers: d i u o x X f F e E g G s c p n %

Implementation

String vsprintf(String format, va_list arg) {
  final buffer = StringBuffer();
  int i = 0;

  while (i < format.length) {
    if (format[i] != '%') {
      buffer.write(format[i++]);
      continue;
    }
    i++; // skip '%'
    if (i >= format.length) break;
    if (format[i] == '%') {
      buffer.write('%');
      i++;
      continue;
    }

    // --- Flags ---
    bool flagMinus = false, flagPlus = false, flagSpace = false;
    bool flagZero = false, flagHash = false;
    bool parsingFlags = true;
    while (parsingFlags && i < format.length) {
      switch (format[i]) {
        case '-':
          flagMinus = true;
          i++;
        case '+':
          flagPlus = true;
          i++;
        case ' ':
          flagSpace = true;
          i++;
        case '0':
          flagZero = true;
          i++;
        case '#':
          flagHash = true;
          i++;
        default:
          parsingFlags = false;
      }
    }

    // --- Width ---
    int width = 0;
    if (i < format.length && format[i] == '*') {
      dynamic wVal;
      try {
        wVal = va_arg<dynamic>(arg);
      } on StateError {
        break;
      }
      width = (wVal as num).toInt();
      if (width < 0) {
        flagMinus = true;
        width = -width;
      }
      i++;
    } else {
      while (i < format.length && _fmtIsDigit(format[i])) {
        width = width * 10 + (format.codeUnitAt(i) - 48);
        i++;
      }
    }

    // --- Precision ---
    int? precision;
    if (i < format.length && format[i] == '.') {
      i++;
      if (i < format.length && format[i] == '*') {
        dynamic pVal;
        try {
          pVal = va_arg<dynamic>(arg);
        } on StateError {
          break;
        }
        final p = (pVal as num).toInt();
        precision = p < 0 ? null : p;
        i++;
      } else {
        precision = 0;
        while (i < format.length && _fmtIsDigit(format[i])) {
          precision = precision! * 10 + (format.codeUnitAt(i) - 48);
          i++;
        }
      }
    }

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

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

    // --- Fetch argument ---
    dynamic argVal;
    if (spec != 'n') {
      try {
        argVal = va_arg<dynamic>(arg);
      } on StateError {
        buffer.write('%$spec');
        continue;
      }
    }

    // --- Format ---
    switch (spec) {
      case 'd':
      case 'i':
        buffer.write(
          _fmtInt(
            (argVal as num).toInt(),
            10,
            false,
            flagMinus,
            flagPlus,
            flagSpace,
            flagZero,
            false,
            width,
            precision,
          ),
        );
      case 'u':
        buffer.write(
          _fmtUInt(
            (argVal as num).toInt(),
            10,
            false,
            flagMinus,
            flagZero,
            false,
            width,
            precision,
          ),
        );
      case 'o':
        buffer.write(
          _fmtUInt(
            (argVal as num).toInt(),
            8,
            false,
            flagMinus,
            flagZero,
            flagHash,
            width,
            precision,
          ),
        );
      case 'x':
        buffer.write(
          _fmtUInt(
            (argVal as num).toInt(),
            16,
            false,
            flagMinus,
            flagZero,
            flagHash,
            width,
            precision,
          ),
        );
      case 'X':
        buffer.write(
          _fmtUInt(
            (argVal as num).toInt(),
            16,
            true,
            flagMinus,
            flagZero,
            flagHash,
            width,
            precision,
          ),
        );
      case 'f':
      case 'F':
        buffer.write(
          _fmtFloat(
            (argVal as num).toDouble(),
            precision ?? 6,
            flagMinus,
            flagPlus,
            flagSpace,
            flagZero,
            flagHash,
            width,
          ),
        );
      case 'e':
        buffer.write(
          _fmtSci(
            (argVal as num).toDouble(),
            precision ?? 6,
            false,
            flagMinus,
            flagPlus,
            flagSpace,
            flagZero,
            width,
          ),
        );
      case 'E':
        buffer.write(
          _fmtSci(
            (argVal as num).toDouble(),
            precision ?? 6,
            true,
            flagMinus,
            flagPlus,
            flagSpace,
            flagZero,
            width,
          ),
        );
      case 'g':
        buffer.write(
          _fmtG(
            (argVal as num).toDouble(),
            precision == 0 ? 1 : (precision ?? 6),
            false,
            flagMinus,
            flagPlus,
            flagSpace,
            flagZero,
            flagHash,
            width,
          ),
        );
      case 'G':
        buffer.write(
          _fmtG(
            (argVal as num).toDouble(),
            precision == 0 ? 1 : (precision ?? 6),
            true,
            flagMinus,
            flagPlus,
            flagSpace,
            flagZero,
            flagHash,
            width,
          ),
        );
      case 's':
        String s = argVal.toString();
        if (precision != null && precision < s.length)
          s = s.substring(0, precision);
        buffer.write(_fmtPad(s, width, flagMinus, false, ''));
      case 'c':
        final ch = argVal is int
            ? String.fromCharCode(argVal)
            : (argVal is String && argVal.isNotEmpty ? argVal[0] : '');
        buffer.write(_fmtPad(ch, width, flagMinus, false, ''));
      case 'p':
        final hex = '0x${identityHashCode(argVal).toRadixString(16)}';
        buffer.write(_fmtPad(hex, width, flagMinus, false, ''));
      case 'n':
        // C writes chars-written count to a pointer; no-op in Dart.
        break;
      default:
        buffer.write('%$spec');
    }
  }

  return buffer.toString();
}