Skip to content

Reference Examples

Justin Jones edited this page Mar 5, 2026 · 1 revision

Reference Examples for Hammer Quick Reference

This file is meant to back the Quick Reference with copy/paste runnable examples.

How to use:

  1. Copy the Framework code into a file named reference_examples.c.
  2. Pick one example section below.
  3. Copy the example’s PASTE INTO MAIN block into the marked area in main().
  4. Compile and run.
  5. Delete that example and paste the next one.

These examples are intentionally independent. They do not need to work together.


Framework (copy once)

// reference_examples.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

// Hammer headers
#include <hammer/hammer.h>
#include <hammer/glue.h>

// -------------------- small helpers --------------------

static void die(const char *msg) {
    perror(msg);
    exit(1);
}

// Read either a literal string argument or a hex string (pairs) into a byte buffer.
// Usage:
//   ./ref "hello"
//   ./ref --hex "48656c6c6f"
static size_t read_input(int argc, char **argv, uint8_t *out, size_t out_cap) {
    if (argc < 2) {
        fprintf(stderr, "Usage:\n");
        fprintf(stderr, "  %s \"string\"\n", argv[0]);
        fprintf(stderr, "  %s --hex \"48656c6c6f\"\n", argv[0]);
        exit(2);
    }

    if (argc >= 3 && strcmp(argv[1], "--hex") == 0) {
        const char *hex = argv[2];
        size_t n = strlen(hex);
        if (n % 2 != 0) {
            fprintf(stderr, "hex string must have even length\n");
            exit(2);
        }
        size_t bytes = n / 2;
        if (bytes > out_cap) {
            fprintf(stderr, "input too large\n");
            exit(2);
        }
        for (size_t i = 0; i < bytes; i++) {
            unsigned v = 0;
            if (sscanf(hex + 2*i, "%2x", &v) != 1) {
                fprintf(stderr, "bad hex at position %zu\n", 2*i);
                exit(2);
            }
            out[i] = (uint8_t)v;
        }
        return bytes;
    }

    // default: treat argv[1] as bytes of a C string
    const char *s = argv[1];
    size_t n = strlen(s);
    if (n > out_cap) {
        fprintf(stderr, "input too large\n");
        exit(2);
    }
    memcpy(out, s, n);
    return n;
}

static void print_result(const HParseResult *r) {
    if (!r) {
        puts("Parse FAILED (result == NULL)");
        return;
    }
    if (!r->ast) {
        puts("Parse OK (ast == NULL)");
        return;
    }

    puts("Parse OK. Pretty print:");
    h_pprintln(stdout, r->ast);

    char *u = h_write_result_unamb(r->ast);
    if (u) {
        printf("Unambiguous: %s\n", u);
        free(u);
    }
}

static void run_and_free(HParser *p, const uint8_t *buf, size_t len) {
    HParseResult *r = h_parse(p, buf, len);
    print_result(r);
    if (r) h_parse_result_free(r);
}

// -------------------- main --------------------

int main(int argc, char **argv) {
    uint8_t input[2048];
    size_t len = read_input(argc, argv, input, sizeof(input));

    // ======================
    // PASTE INTO MAIN HERE
    // ======================

    (void)len;
    (void)input;
    puts("No example pasted into main yet.");
    return 0;
}

Build

Typical build (See Compiling with Hammer for help):

gcc -I. reference_examples.c -o ref -lhammer

Run:

./ref "hello"
./ref --hex "48656c6c6f"

Examples

Example: h_bytes(len)

What it shows: fixed-length byte blob parsing.

PASTE INTO MAIN:

HParser *p = h_bytes(4);               // 4 bytes
run_and_free(p, input, len);
return 0;

Try:

./ref --hex "DEADBEEF"

Example: h_sepBy and h_sepBy1

What it shows: parsing a comma-separated list of digits, where separators are discarded.

PASTE INTO MAIN:

HParser *digit = h_ch_range('0', '9');
HParser *comma = h_ch(',');

// zero-or-more list
puts("sepBy:");
run_and_free(h_sepBy(digit, comma), input, len);

// one-or-more list
puts("\nsepBy1:");
run_and_free(h_sepBy1(digit, comma), input, len);
return 0;

Try:

./ref "1,2,3"
./ref ""          # sepBy: succeeds with empty sequence
./ref ","         # sepBy: succeeds with empty sequence (first element missing)

Example: h_optional(p)

What it shows: optional prefix.

PASTE INTO MAIN:

HParser *opt_x = h_optional(h_ch('x'));
HParser *p = h_sequence(opt_x, h_literal("hello"), h_end_p(), NULL);
run_and_free(p, input, len);
return 0;

Try:

./ref "hello"
./ref "xhello"

Example: h_right(a, b) and h_left(a, b)

What it shows: keep one side of a sequence.

PASTE INTO MAIN:

HParser *tag = h_literal("tag:");
HParser *num = h_many1(h_ch_range('0','9'));

HParser *keep_num = h_right(tag, num);  // discard "tag:", keep digits
HParser *keep_tag = h_left(tag, num);   // keep "tag:", discard digits

puts("h_right(tag, num):");
run_and_free(h_sequence(keep_num, h_end_p(), NULL), input, len);

puts("\nh_left(tag, num):");
run_and_free(h_sequence(keep_tag, h_end_p(), NULL), input, len);

return 0;

Try:

./ref "tag:42"

Example: h_middle(a, b, c) BROKEN

What it shows: discarding delimiters.

PASTE INTO MAIN:

HParser *quoted = h_middle(h_ch('"'), h_many1(h_ch_range(0x20, 0x7E)), h_ch('"'));
run_and_free(h_sequence(quoted, h_end_p(), NULL), input, len);
return 0;

Try:

./ref "\"hello\""

Example: h_ignore(p) (and how it elides inside sequences)

What it shows: ignored tokens disappear from surrounding sequence AST.

PASTE INTO MAIN:

HParser *p = h_sequence(
    h_ch('A'),
    h_ignore(h_ch(':')),   // elided
    h_ch('B'),
    h_end_p(),
    NULL
);
run_and_free(p, input, len);
return 0;

Try:

./ref "A:B"

Example: h_whitespace(p)

What it shows: skip leading whitespace before parsing.

PASTE INTO MAIN:

HParser *p = h_sequence(h_whitespace(h_literal("hello")), h_end_p(), NULL);
run_and_free(p, input, len);
return 0;

Try:

./ref "   hello"

Example: h_in(charset, len) and h_not_in(charset, len) BROKEN

What it shows: character class inclusion / exclusion.

PASTE INTO MAIN:

const uint8_t vowels[] = { 'a','e','i','o','u','A','E','I','O','U' };
HParser *is_vowel = h_in(vowels, sizeof(vowels));
HParser *not_vowel = h_not_in(vowels, sizeof(vowels));

puts("First char is a vowel:");
run_and_free(h_sequence(is_vowel, h_end_p(), NULL), input, len);

puts("\nFirst char is NOT a vowel:");
run_and_free(h_sequence(not_vowel, h_end_p(), NULL), input, len);
return 0;

Try:

./ref "abcXYZ"

Example: Lookahead h_and(p) and h_not(p)

What it shows: zero-width assertions to steer parsing.

PASTE INTO MAIN:

HParser *prefix = h_literal("0x");
HParser *hex_digit = h_choice(
    h_ch_range('0','9'),
    h_ch_range('a','f'),
    h_ch_range('A','F'),
    NULL
);

// Require 0x prefix, but don't consume it before reading it again.
HParser *p1 = h_sequence(
    h_and(prefix),         // assert
    prefix,                // now consume
    h_many1(hex_digit),
    h_end_p(),
    NULL
);

puts("Requires 0x prefix (uses h_and):");
run_and_free(p1, input, len);

// Reject "0x" prefix:
HParser *p2 = h_sequence(
    h_not(prefix),         // assert NOT present
    h_many1(hex_digit),
    h_end_p(),
    NULL
);

puts("\nRejects 0x prefix (uses h_not):");
run_and_free(p2, input, len);

return 0;

Try:

./ref "0x2a"
./ref "2a"

Example: h_epsilon_p() and h_nothing_p()

What it shows: always-succeed / always-fail.

PASTE INTO MAIN:

puts("epsilon + end_p (accepts empty only):");
run_and_free(h_sequence(h_epsilon_p(), h_end_p(), NULL), input, len);

puts("\nnothing_p (always fails):");
run_and_free(h_nothing_p(), input, len);

return 0;

Try:

./ref ""        # epsilon succeeds
./ref "x"

Example: h_permutation(a, b, c, ...) LOOK INTO INTERESTING BEHAVIOR

What it shows: matching items in any order, returning results in argument order.

PASTE INTO MAIN:

HParser *A = h_ch('A');
HParser *B = h_ch('B');
HParser *C = h_ch('C');

HParser *p = h_sequence(h_permutation(A, B, C, NULL), h_end_p(), NULL);
run_and_free(p, input, len);
return 0;

Try:

./ref "ABC"
./ref "CAB"

Example: h_drop_from_(seq, ...) (and macro h_drop_from(seq, ...))

What it shows: parse a sequence but remove elements from the resulting sequence token.

PASTE INTO MAIN:

HParser *seq = h_sequence(h_ch('A'), h_ch_range('0','9'), h_ch('B'), h_ch_range('0','9'), NULL);

// Drop indices 0 and 2 (the letters), keep just digits in the output sequence.
HParser *p = h_sequence(h_drop_from_(seq, 0, 2, -1), h_end_p(), NULL);
run_and_free(p, input, len);
return 0;

Try:

./ref "A1B2"

Example: h_put_value, h_get_value, h_free_value

What it shows: storing a parsed value and retrieving it later.

PASTE INTO MAIN:

// Format: [len: uint8][payload bytes...]
// We'll parse len, stash it, then use it to read payload.

HParser *len_p = h_put_value(h_uint8(), "n");
HParser *payload = h_length_value(h_free_value("n"), h_uint8());

HParser *p = h_sequence(len_p, payload, h_end_p(), NULL);
run_and_free(p, input, len);
return 0;

Try:

./ref --hex "03414243"

Example: h_bind(p, k, env) (data-dependent parser construction) BROKEN

What it shows: building the next parser based on a prior parsed value.

PASTE INTO MAIN:

static HParser *k_len_then_bytes(HAllocator *mm__, const HParsedToken *x, void *env) {
    (void)env;
    if (!x || x->token_type != TT_UINT) return NULL;
    size_t n = (size_t)x->uint;
    return h_bytes__m(mm__, n);
}

HParser *p = h_sequence(
    h_bind(h_uint8(), k_len_then_bytes, NULL),
    h_end_p(),
    NULL
);

run_and_free(p, input, len);
return 0;

Try:

./ref --hex "03414243"

Example: h_skip(nbits)

What it shows: skipping bits (here: 8 bits == 1 byte) and returning NULL AST.

PASTE INTO MAIN:

HParser *p = h_sequence(
    h_skip(8),
    h_bytes(2),
    h_end_p(),
    NULL
);
run_and_free(p, input, len);
return 0;

Try:

./ref --hex "FF4142"

Example: h_seek(offset_bits, whence) and h_tell() BROKEN

What it shows: random access and reporting current position (in bits).

PASTE INTO MAIN:

HParser *p = h_sequence(
    h_tell(),                // should be 0
    h_seek(16, SEEK_SET),    // jump to byte 2 (bit offset 16)
    h_tell(),                // should be 16
    h_uint8(),               // read 0x33
    h_end_p(),
    NULL
);
run_and_free(p, input, len);
return 0;

Try:

./ref --hex "11223344"

Example: h_with_endianness(flags, p)

What it shows: parsing the same bytes under different byte endianness.

PASTE INTO MAIN:

HParser *be = h_with_endianness(BYTE_BIG_ENDIAN | BIT_BIG_ENDIAN, h_uint32());
HParser *le = h_with_endianness(BYTE_LITTLE_ENDIAN | BIT_BIG_ENDIAN, h_uint32());

puts("Big-endian uint32:");
run_and_free(h_sequence(be, h_end_p(), NULL), input, len);

puts("\nLittle-endian uint32:");
run_and_free(h_sequence(le, h_end_p(), NULL), input, len);

return 0;

Try:

./ref --hex "01020304"

Example: Incremental parsing (h_parse_start, h_parse_chunk, h_parse_finish)

What it shows: feeding input in chunks.

PASTE INTO MAIN:

HParser *p = h_sequence(h_literal("hello"), h_end_p(), NULL);

HSuspendedParser *s = h_parse_start(p);
if (!s) {
    puts("This backend does not support incremental parsing (h_parse_start returned NULL).");
    return 0;
}

bool done = false;
size_t mid = len / 2;
done = h_parse_chunk(s, input, mid);
printf("After chunk1 (len=%zu), done=%s\n", mid, done ? "true" : "false");

done = h_parse_chunk(s, input + mid, len - mid);
printf("After chunk2 (len=%zu), done=%s\n", len - mid, done ? "true" : "false");

HParseResult *r = h_parse_finish(s);
print_result(r);
if (r) h_parse_result_free(r);

return 0;

Try:

./ref "hello"

Example: Glue helpers h_seq_* (manual sequence building) BROKEN

What it shows: creating a sequence token in an action and appending items.

PASTE INTO MAIN:

static HParsedToken *act_letters_to_seq(const HParseResult *p, void *user_data) {
    (void)user_data;

    const HParsedToken *in = p->ast;

    HParsedToken *out = H_MAKE_SEQN(3);
    for (size_t i = 0; i < h_seq_len(in); i++) {
        const HParsedToken *t = h_seq_index(in, i);
        h_seq_snoc(out, H_MAKE_UINT(t->uint));
    }
    return out;
}

HParser *letters = h_repeat_n(h_ch_range('A','Z'), 3);
HParser *p = h_sequence(h_action(letters, act_letters_to_seq, NULL), h_end_p(), NULL);
run_and_free(p, input, len);
return 0;

Try:

./ref "ABC"

Example: H_ACT_APPLY + h_act_index (parameterized action) BROKEN

What it shows: picking a specific element from a parsed sequence.

PASTE INTO MAIN:

H_ACT_APPLY(act_pick_last, h_act_index, 3)

HParser *seq = h_sequence(h_ch('A'), h_ch_range('0','9'), h_ch('B'), h_ch_range('0','9'), NULL);
HParser *p = h_sequence(h_action(seq, act_pick_last, NULL), h_end_p(), NULL);
run_and_free(p, input, len);
return 0;

Try:

./ref "A1B2"

Example: Token type registry (h_allocate_token_new) + custom printing hooks BROKEN

What it shows: registering a new TT_* and giving it unambiguous and pprint printers.

PASTE INTO MAIN:

typedef struct {
    uint64_t x;
    uint64_t y;
} point_t;

static void point_unamb(const HParsedToken *tok, struct result_buf *buf) {
    point_t *pt = (point_t*)tok->user;
    h_append_buf_formatted(buf, "Point(%llu,%llu)",
        (unsigned long long)pt->x, (unsigned long long)pt->y);
}

static void point_pprint(FILE *stream, const HParsedToken *tok, int indent, int delta) {
    (void)delta;
    for (int i = 0; i < indent; i++) fputc(' ', stream);
    point_t *pt = (point_t*)tok->user;
    fprintf(stream, "Point { x=%llu, y=%llu }",
        (unsigned long long)pt->x, (unsigned long long)pt->y);
}

static HTokenType TT_point_t = 0;

static HParsedToken *act_make_point(const HParseResult *p, void *user_data) {
    (void)user_data;
    const HParsedToken *seq = p->ast; // [uint8,uint8]
    point_t *pt = H_ALLOC(point_t);
    pt->x = h_seq_index(seq, 0)->uint;
    pt->y = h_seq_index(seq, 1)->uint;
    return H_MAKE(point_t, pt);
}

if (TT_point_t == 0) {
    TT_point_t = h_allocate_token_new("point_t", point_unamb, point_pprint);
    printf("Allocated token type for point_t: %d\n", (int)TT_point_t);
}

HParser *p = h_sequence(
    h_action(h_sequence(h_uint8(), h_uint8(), NULL), act_make_point, NULL),
    h_end_p(),
    NULL
);

run_and_free(p, input, len);
return 0;

Try:

./ref --hex "0A14"

Example: h_sloballoc(mem, size) (allocator backed by a fixed buffer) BROKEN

What it shows: using a fixed memory region as the allocator.

PASTE INTO MAIN:

uint8_t arena_mem[4096];
HAllocator *a = h_sloballoc(arena_mem, sizeof(arena_mem));
if (!a) {
    puts("h_sloballoc failed");
    return 1;
}

HParser *p = h_sequence__m(a, h_literal("hello"), h_end_p(), NULL);
HParseResult *r = h_parse__m(a, p, input, len);
print_result(r);
if (r) h_parse_result_free__m(a, r);

return 0;

Try:

./ref "hello"

Example: Bit writer (h_bit_writer_*) + parsing the produced bytes BROKEN. TBD why.

What it shows: writing bits to a buffer, then parsing that buffer back.

PASTE INTO MAIN:

HBitWriter *w = h_bit_writer_new(NULL);
if (!w) die("h_bit_writer_new");

h_bit_writer_put(w, 0b101, 3);
h_bit_writer_put(w, 0b00110, 5);

size_t out_len = 0;
const uint8_t *buf = h_bit_writer_get_buffer(w, &out_len);
if (!buf) {
    puts("writer not at whole-byte boundary?");
    h_bit_writer_free(w);
    return 0;
}

printf("bitwriter produced %zu byte(s): ", out_len);
for (size_t i = 0; i < out_len; i++) printf("%02X", buf[i]);
puts("");

HParser *p = h_sequence(h_bits(3, false), h_bits(5, false), h_end_p(), NULL);
run_and_free(p, buf, out_len);

h_bit_writer_free(w);
return 0;

Try:

./ref ""

Clone this wiki locally