Skip to content

takeiteasy/format.h

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 

Repository files navigation

format.h

A single-header, zero-dependency C11 library implementing Common Lisp-style format directives.

Usage

#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."

Supported Directives

Directive Description Example
~A Aesthetic output ~Ahello
~S Standard (quoted strings) ~S"hello"
~D Decimal ~D42
~:D With separators ~:D1,234,567
~B Binary ~B101010
~O Octal ~O52
~X Hexadecimal ~XFF
~nR Arbitrary radix (2-36) ~3R1120
~R English words ~Rforty-two
~:R Ordinal ~:Rforty-second
~@R Roman numerals ~@RXLII
~F Fixed float ~,2F3.14
~E Exponential ~E3.14e+00
~G General float ~G3.14
~$ Monetary ~$3.14
~C Character ~CA
~P Plural ~Ps 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

Padding & Alignment

format(buf, sz, "|~10A|", "hi");     // |hi        |
format(buf, sz, "|~10@A|", "hi");    // |        hi|
format(buf, sz, "~,2F", 3.14159);    // 3.14

Conditionals

// 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~]", "");       // ""

Iteration

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 separator

Justification

format0(buf, sz, "|~10<hi~>|");      // "|hi        |" (left)
format0(buf, sz, "|~10:<hi~>|");     // "|        hi|" (right)  
format0(buf, sz, "|~10@<hi~>|");     // "|    hi    |" (center)

Indirect Formatting

// ~? 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"

Call Registered Function

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();

Error Handling

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()

Limitations

  • UTF-8 only: Padding counts codepoints, not grapheme clusters (combining chars count separately).
  • Character literals: C treats 'A' as int. Use (char)'A' for ~C.
  • Bool literals: Use (bool)true for proper type detection.
  • Iteration: Requires explicit FORMAT_ARRAY() wrapper.
  • Custom types: Use FORMAT_CUSTOM() with a formatter function.

NOT implemented

  • Pretty-printing (~_, ~I) - requires full printer infrastructure
  • Floating-point scale factors - rarely needed
  • Full grapheme cluster counting - would need Unicode librar

Testing

$ 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 passed

LICENSE

format.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/>.

About

Single-header implementation of Common Lisp's format directives in C

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages