# fmtlib Format Specifier Parsing States: Inside the State Machine Implementation

> Explore the six-state machine used by fmtlib to parse format specifiers: arg_id, flags, width, precision, type, and done. Understand the internal implementation in src/format.cc.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: internals
- Published: 2026-09-08

---

**fmtlib uses a six-state machine (`arg_id`, `flags`, `width`, `precision`, `type`, `done`) to parse format specifiers, implemented in `detail::parse_format_specs` in `src/format.cc`.**

The {fmt} library—one of the fastest and most widely-used C++ formatting libraries—processes format strings through a compact hand-crafted state machine. Understanding these states helps developers write more efficient format strings and debug parsing errors. This article breaks down each parsing state exactly as implemented in the fmtlib source code.

## The Six Parsing States in fmtlib's State Machine

The state machine is defined in `src/format.cc` around line 1040, within the `detail::parse_format_spec` enumeration. Here are the states in their logical order of progression:

| State | Purpose | Typical Input |
|-------|---------|---------------|
| **arg_id** | Parse optional argument index or named identifier | `0`, `name` |
| **flags** | Parse format flags | `+`, `-`, `#`, `0`, space |
| **width** | Parse field width specification | `10`, `{width}` |
| **precision** | Parse precision after decimal point | `2`, `{prec}` |
| **type** | Parse conversion type specifier | `d`, `f`, `x`, `g` |
| **done** | Final state when closing `}` is reached | `}` |

The core enumeration appears in simplified form as:

```cpp
enum class state {
  arg_id,      // reading the argument identifier
  flags,       // +, -, #, 0, space
  width,       // field width
  precision,   // "." followed by precision value
  type,        // conversion type
  done         // '}' reached
};

```

This state machine drives the `detail::parse_format_specs` function, which iterates through characters following the colon in a replacement field.

## State-by-State Breakdown

### arg_id State

The parser enters this state immediately after the opening `{` or after a colon `:` if an argument identifier was present. It handles:

- **Numeric indices**: `{0}`, `{1}`, `{42}`
- **Named arguments**: `{name}`, `{value}`

If no identifier is present, the parser advances to the next argument automatically.

### flags State

Once past the argument identifier, the parser collects **format flags** in any combination:

- `+` — always show sign
- `-` — left-align
- `#` — alternate form (e.g., `0x` prefix for hex)
- `0` — zero-padding
- space — leading space for positive numbers

From `src/format.cc`, the flag parsing logic consumes these characters before transitioning to width detection.

### width State

The **width** state accepts:

- **Static widths**: Decimal numbers like `10` or `5`
- **Dynamic widths**: Nested replacement fields like `{width}` or `{}` (for next argument)

The parser distinguishes these by checking for an opening `{` character.

### precision State

Triggered only when a dot `.` is encountered. Similar to width, this accepts:

- **Static precision**: `.{2}`, `.{6}`
- **Dynamic precision**: `.{prec}`, `.{3}` (argument index)

This state is skipped entirely if no dot appears before the type specifier.

### type State

The **type** state consumes the conversion specifier—essentially mandatory for built-in types. Supported types include:

- Integer: `d`, `i`, `u`, `b`, `B`, `o`, `x`, `X`
- Floating-point: `f`, `F`, `e`, `E`, `g`, `G`, `a`, `A`
- String/char: `s`, `c`
- Pointer: `p`
- Special: `?` (debug), `n` (count), `%` (literal percent)

User-defined formatters may extend this set.

### done State

Reached when the closing `}` is encountered. At this point, `parse_format_specs` returns the fully populated format specification structure.

## Practical Code Examples

Each example demonstrates state transitions through the machine:

```cpp
#include <fmt/core.h>

// Example 1: Integer with flags and width
// States: arg_id (implicit) → flags (+, 0) → width (8) → type (d)
fmt::print("{:+08d}\n", 42);
// Output: +0000042

// Example 2: Float with alignment, width, precision
// States: arg_id → flags (>) → width (10) → precision (3) → type (f)
fmt::print("{:>10.3f}\n", 3.14159);
// Output: "     3.142"

// Example 3: Named arguments with dynamic width/precision
// States: arg_id (name) → flags (>) → width ({w}) → precision ({p}) → type (g)
fmt::print("{name:>{w}.{p}g}\n",
           fmt::arg("name", 12345.6),
           fmt::arg("w", 12),
           fmt::arg("p", 2));
// Output: "      1.2e+04"

```

## Key Implementation Files

The fmtlib format specifier parsing state machine spans these critical files:

- **[`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h)** — Public API and format specification grammar documentation
- **`src/format.cc`** — Core implementation containing `detail::parse_format_specs` and the `state` enumeration (≈ line 1040)
- **`test/format-test.cc`** — Comprehensive tests exercising each state transition

## Summary

- **Six states** form fmtlib's format specifier parser: `arg_id`, `flags`, `width`, `precision`, `type`, and `done`
- **State transitions** are deterministic and follow the syntax `[arg_id]:[flags][width][.precision][type]`
- **Dynamic specifications** (runtime width/precision) use nested `{}` syntax, parsed in the same `width` and `precision` states
- **Source location**: The enumeration lives in `src/format.cc` within `detail::parse_format_specs`

## Frequently Asked Questions

### How does fmtlib handle invalid state transitions?

The parser throws `format_error` with descriptive messages. For example, placing a flag after width (`{5+}`) fails in `src/format.cc` when the state machine encounters an unexpected character for the current state.

### Can user-defined types add custom parsing states?

No—the six-state machine is fixed. However, `parse()` member functions in custom formatters receive the remaining format string after `type` and can implement sub-parsers. The built-in states are intentionally minimal for performance.

### What is the performance impact of dynamic width/precision?

Minimal. According to the `src/format.cc` implementation, dynamic specs (`{:{}}`) simply read another argument during the `width` or `precision` state—no additional virtual calls or heap allocations occur.

### Where is the state machine actually invoked?

`detail::parse_format_specs` is called from `format_handler::on_format_specs` in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h), triggered when the format string parser encounters a colon `:` in a replacement field.