A single-header, zero-dependency C11 library implementing Common Lisp-style format directives.
#define FORMAT_IMPLEMENTATION
#include "format.h"
char buf[256];
format(buf, sizeof(buf), "Hello ~A! You have ~:D message~:P.", "user", 5, 5);
// → "Hello user! You have 5 messages."| Directive | Description | Example |
|---|---|---|
~A |
Aesthetic output | ~A → hello |
~S |
Standard (quoted strings) | ~S → "hello" |
~D |
Decimal | ~D → 42 |
~:D |
With separators | ~:D → 1,234,567 |
~B |
Binary | ~B → 101010 |
~O |
Octal | ~O → 52 |
~X |
Hexadecimal | ~X → FF |
~nR |
Arbitrary radix (2-36) | ~3R → 1120 |
~R |
English words | ~R → forty-two |
~:R |
Ordinal | ~:R → forty-second |
~@R |
Roman numerals | ~@R → XLII |
~F |
Fixed float | ~,2F → 3.14 |
~E |
Exponential | ~E → 3.14e+00 |
~G |
General float | ~G → 3.14 |
~$ |
Monetary | ~$ → 3.14 |
~C |
Character | ~C → A |
~P |
Plural | ~P → s or nothing |
~% |
Newline | ~3% = 3 newlines |
~& |
Fresh line | |
~T |
Tab | |
| `~n | ` | Page breaks |
~~ |
Literal tilde | |
~[~;~] |
Conditional | ~[zero~;one~] |
~:[~;~] |
Boolean conditional | |
~@[~] |
Optional | |
~:@[~] |
Optional (no consume if nil) | |
~{~} |
Iteration | ~{~A~^, ~} |
~^ |
Escape iteration | |
~* |
Skip forward | |
~:* |
Skip backward | |
~n@* |
Goto position | |
~(~) |
Case conversion | |
~<~> |
Justification | ~10:<~A~> |
~? |
Indirect format | |
~/func/ |
Call registered function | here |
~W |
Write (custom types) | |
~v |
Param from arg | ~vD |
~# |
Remaining arg count |
format(buf, sz, "|~10A|", "hi"); // |hi |
format(buf, sz, "|~10@A|", "hi"); // | hi|
format(buf, sz, "~,2F", 3.14159); // 3.14// Numeric selector - picks branch by index
format(buf, sz, "~[zero~;one~;two~]", 1); // "one"
// Boolean - ~:[false~;true~]
format(buf, sz, "~:[no~;yes~]", (bool)true); // "yes"
// Optional - ~@[content~] if truthy, uses arg inside
format(buf, sz, "~@[Value: ~A~]", "hi"); // "Value: hi"
format(buf, sz, "~@[Value: ~A~]", ""); // ""FormatArg items[] = {FORMAT_ARG("a"), FORMAT_ARG("b"), FORMAT_ARG("c")};
FormatArg arr = FORMAT_ARRAY(items, 3);
format_impl(buf, sizeof(buf), "~{~A~^, ~}", &arr, 1); // "a, b, c"
// ~^ escapes iteration before last separatorformat0(buf, sz, "|~10<hi~>|"); // "|hi |" (left)
format0(buf, sz, "|~10:<hi~>|"); // "| hi|" (right)
format0(buf, sz, "|~10@<hi~>|"); // "| hi |" (center)// ~? takes format string and args array
FormatArg sub[] = {FORMAT_ARG(42)};
FormatArg args[] = {FORMAT_ARG("N=~D"), FORMAT_ARRAY(sub, 1)};
format_impl(buf, sizeof(buf), "~?", args, 2); // "N=42"
// ~@? uses remaining args
format(buf, sz, "~@?", "~A ~A", "hello", "world"); // "hello world"void my_upper(FormatState *state, FormatArg *arg) {
if (arg->type == FORMAT_TYPE_STRING && arg->value.as_string) {
for (const char *s = arg->value.as_string; *s; s++)
format_append_char(state, toupper(*s));
}
}
format_register_func("upper", my_upper);
format(buf, sz, "~/upper/", "hello"); // "HELLO"
format_clear_funcs();FormatError err;
format_impl_ex(buf, sizeof(buf), fmt, args, n, &err);
if (err != FORMAT_OK) {
printf("Error: %s\n", format_error_string(err));
}
// Or check thread-local: format_get_error()- UTF-8 only: Padding counts codepoints, not grapheme clusters (combining chars count separately).
- Character literals: C treats
'A'asint. Use(char)'A'for~C. - Bool literals: Use
(bool)truefor proper type detection. - Iteration: Requires explicit
FORMAT_ARRAY()wrapper. - Custom types: Use
FORMAT_CUSTOM()with a formatter function.
- Pretty-printing (~_, ~I) - requires full printer infrastructure
- Floating-point scale factors - rarely needed
- Full grapheme cluster counting - would need Unicode librar
$ clang -std=c11 -Wall -Wextra -pedantic -o test test.c && ./test && rm ./test
> ...
> Function Registry:
> func_directive PASS
> Extended Conditionals:
> conditional_colon_at_truthy PASS
> conditional_colon_at_nil PASS
> ========================================
> Results: 53/53 tests passedformat.h Copyright (C) 2025 George Watson
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.