-
Notifications
You must be signed in to change notification settings - Fork 1
Reference Examples
This file is meant to back the Quick Reference with copy/paste runnable examples.
How to use:
- Copy the Framework code into a file named
reference_examples.c. - Pick one example section below.
- Copy the example’s PASTE INTO MAIN block into the marked area in
main(). - Compile and run.
- Delete that example and paste the next one.
These examples are intentionally independent. They do not need to work together.
// 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;
}Typical build (See Compiling with Hammer for help):
gcc -I. reference_examples.c -o ref -lhammerRun:
./ref "hello"
./ref --hex "48656c6c6f"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"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)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"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"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\""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"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"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"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"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"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"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"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"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"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"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"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"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"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"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"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"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"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 ""Learn Hammer
Protocol Examples
NTP
- NTP Overview
- Parsing the Header
- Parsing Data Fields
- Extension Fields and MAC
- Assembling the Parser
- Hex Input Preprocessing
- Running and Testing
DNS
TFTP
- TFTP Overview
- RRQ/WRQ Packets
- DATA Packets
- ACK Packets
- ERROR Packets
- Assembling the Parser
- Running and Testing
References
- Hammer Quick Reference
- Parsing Backends
- Unit Testing
- Using RTEMS
- Extending Hammer
- Adding a New Example
- Adding a New Binding
Further Reading