-
Notifications
You must be signed in to change notification settings - Fork 1
Frequently Asked Questions
Here are answers to some frequently asked questions about Hammer.
Yes. Hammer is written in C99 and does not require a 64-bit pointer architecture. It uses fixed-width integer types such as uint8_t, uint64_t, and int64_t where a specific data width is required, while sizes, lengths, and indexes are generally represented using size_t.
On a 32-bit system, size_t will typically also be 32 bits, so the maximum practical size of inputs, allocations, and indexes is limited by the platform's address space. This does not prevent Hammer from representing parsed 64-bit integer values, since those values use explicit 64-bit integer types.
As with any platform, a compatible C99 compiler and Hammer's required build dependencies must be available.
Packrat is Hammer's default backend.
Packrat is the general-purpose backend and is intended to work with general Hammer parser-combinator grammars without requiring the grammar to first satisfy the restrictions of one of the more specialized backends.
Hammer also provides the following backends:
- Regular / RVM
- LL(k)
- LALR(k)
- GLR
A different backend can be selected by compiling the parser with h_compile().
For example:
h_compile(parser, PB_LALR, NULL);If h_compile() is not used to select another backend, Hammer uses Packrat.
Different parsing backends are useful for different kinds of grammars and can have different performance characteristics.
For example, Hammer provides:
- Packrat for general parser-combinator grammars.
- Regular / RVM for regular grammars.
- LL(k) for suitable predictive context-free grammars.
- LALR(k) for suitable context-free grammars that can be represented by LALR parsing tables.
- GLR for context-free grammars where conflicts or ambiguity may need to be handled.
One advantage of Hammer's backend model is that the grammar is described using the same parser-combinator interface. When a grammar is compatible with another backend, you can compile it for that backend rather than rewriting the grammar in an entirely different parser-generator language.
Not every grammar is compatible with every backend, so Packrat remains the safest general-purpose choice.
Yes. Hammer's normal optimized build uses the compiler's -O3 optimization level.
The optimized build is selected with:
scons --variant=optopt is currently the default build variant, so running:
sconsalso produces an optimized build unless another variant is selected.
For debugging, Hammer can instead be built with:
scons --variant=debugThe opt setting is a C compiler optimization setting. It does not change the grammar or the expected parsing result; it allows the compiler to optimize Hammer's generated machine code for runtime performance.
Hammer's parsing backends are a separate concept. Using h_compile() to select Regular/RVM, LL(k), LALR(k), or GLR may also change the way a compatible grammar is executed, but backend compilation is distinct from building the Hammer library with -O3.
Hammer can be built and installed on Windows, but Windows is not officially supported.
The project primarily targets Unix-like development environments and its documented build process uses tools such as SCons and GCC. It may be possible to build Hammer on Windows using an environment that provides the required toolchain, but Windows-specific builds are not currently part of Hammer's officially supported platforms.
If you encounter a Windows-specific build issue, it may require additional configuration or platform-specific changes.
Hammer provides two useful ways to parse input without including every parsed value in the resulting AST: h_drop_from() and h_ignore().
h_drop_from() takes an h_sequence() parser and one or more zero-based parser indices to omit from the resulting sequence.
m
For example, suppose we want to parse a value surrounded by parentheses but only keep the value:
HParser *parser = h_drop_from(
h_sequence(
h_ch('('), // index 0
h_uint8(), // index 1
h_ch(')'), // index 2
NULL
),
0, 2
);Given the input:
(A)
where A is the byte parsed by h_uint8(), all three parsers must still succeed. However, the AST contains only the result from index 1:
TT_SEQUENCE
└── TT_UINT: 65
The opening and closing parentheses are consumed from the input but omitted from the AST.
h_drop_from() is most useful when you already have a sequence and want to remove specific fixed elements from its result.
Avoid using it where the sequence layout is affected by optional parsers. In those cases, explicitly wrapping the unwanted parser with h_ignore() is clearer and safer.
h_ignore() wraps a parser so that the parser still has to succeed and still consumes its input, but its result is not included in the surrounding sequence's AST.
The same example can be written as:
HParser *parser = h_sequence(
h_ignore(h_ch('(')),
h_uint8(),
h_ignore(h_ch(')')),
NULL
);Again, the parentheses must be present for the parse to succeed, but only the value parsed by h_uint8() appears in the resulting AST:
TT_SEQUENCE
└── TT_UINT: 65
h_ignore() is generally preferable when you know at grammar construction time that a particular parser's result is not useful. It also makes the intent of the grammar obvious when reading the code.
A common use is ignoring structural bytes or delimiters:
HParser *header = h_sequence(
h_ignore(h_ch('[')),
h_uint8(),
h_ignore(h_ch(',')),
h_uint8(),
h_ignore(h_ch(']')),
NULL
);This parser accepts an input such as:
[A,B]
but the resulting sequence contains only the two values:
TT_SEQUENCE
├── TT_UINT: 65
└── TT_UINT: 66
The [, ,, and ] bytes are still validated and consumed; they simply do not appear in the AST.
If I have a Hammer action that accepts a parsed result argument p, do I need to check whether p is NULL?
Normally, no. An action registered with h_action() is only called after the parser wrapped by the action has successfully produced an HParseResult, so the HParseResult *p passed to the action should be non-NULL.
However, p->ast may be NULL, even when the parse itself succeeded. Some parsers can succeed without producing an AST value, and an action may also intentionally return NULL. Therefore, an action should check p->ast before dereferencing it unless the grammar guarantees that the wrapped parser always produces a token.
For example:
HParsedToken *my_action(const HParseResult *p, void *user_data)
{
if (p->ast == NULL) {
return NULL;
}
/* Safely use p->ast here. */
return (HParsedToken *)p->ast;
}Whether additional checks are necessary depends on the guarantees made by the parser being wrapped.
Use h_pprint_ast_indexed(). It prints an easily readable representation of the abstract syntax tree while also showing token indexes.
For example:
HParseResult *result = h_parse(parser, input, input_length);
if (result != NULL) {
h_pprint_ast_indexed(stdout, result->ast, 0);
}Its signature is:
void h_pprint_ast_indexed(
FILE *stream,
const HParsedToken *token,
size_t indent
);Passing stdout prints the AST to standard output, and an initial indentation of 0 is usually appropriate for a complete parse result.
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