-
Notifications
You must be signed in to change notification settings - Fork 1
Adding a New Binding
This page is a step-by-step guide for contributors who want to add a language binding to Hammer. It covers both the SWIG-based approach (reusing the shared interface file) and a handwritten approach (writing directly against the C API, as the C++ binding does).
Prerequisites: A working Hammer build. See Getting Started and DEVELOPMENT.md.
| Approach | When to use | Examples |
|---|---|---|
| SWIG | The target language has a SWIG backend and you can express all necessary type conversions as SWIG typemaps | Python, Java |
| Handwritten | The target language has no SWIG backend, or the type system requires custom marshalling logic that SWIG can't express cleanly | C++ |
For most languages, SWIG is the faster path. The shared interface file src/bindings/swig/hammer.i already covers all public Hammer combinators and handles several tricky type pairs; you only need to add language-specific typemaps.
Open SConstruct and find the ListVariable near the top:
vars.Add(
ListVariable("bindings", "Language bindings to build", "none", ["python", "java", "cpp"])
)Add your language's name (lowercase, no spaces) to the list:
vars.Add(
ListVariable("bindings", "Language bindings to build", "none", ["python", "java", "cpp", "ruby"])
)This name becomes the bindings= flag value (scons bindings=ruby) and the directory name under src/bindings/.
Create src/bindings/<lang>/ with at minimum:
src/bindings/ruby/
SConscript # build, test, and install rules
README.md # user-facing API reference
hammer_tests.rb # (or equivalent) test suite
The SConscript is the only file required for the build; the rest are conventions shared by existing bindings.
Every binding SConscript follows the same skeleton. The sections below show the pattern; see src/bindings/python/SConscript and src/bindings/java/SConscript for full working examples.
from __future__ import absolute_import, division, print_function
import os, shutil, sys
Import("env libhammer_shared testruns targets binding_results binding_test_stamps")
# Bindings require the shared library; static-only builds skip them.
if libhammer_shared is None:
print("Warning: <Lang> bindings require the shared library. Skipping.")
Return()
# Guard for missing toolchain dependencies.
if not shutil.which("swig"): # or javac, rustc, etc.
print("Warning: swig not found. Skipping <Lang> bindings.")
Return()The libhammer_shared guard is important: coverage and gprof builds produce only a static library and set libhammer_shared = None.
Clone the environment so your changes don't affect other parts of the build:
langenv = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES=0)Copy the interface file to the build directory, run SWIG, then compile the wrapper:
project_root = Dir("#").abspath
src_build_dir = os.path.join(project_root, env["BUILD_BASE"], "src")
hammer_lib_dir = os.path.dirname(str(libhammer_shared[0]))
swig_iface = langenv.Command(
"hammer.i", "#src/bindings/swig/hammer.i", Copy("$TARGET", "$SOURCE")
)
# SWIG generates a C wrapper and language-side source files.
swig_out = langenv.Command(
"hammer_wrap.c",
swig_iface,
"swig -<lang> -I" + src_build_dir + " -o $TARGET $SOURCE",
)
langenv.Append(CPPPATH=[src_build_dir])
langenv.Append(CCFLAGS=["-fPIC", "-DSWIG", "-std=gnu99", "-Wno-strict-aliasing"])
jni_lib = langenv.SharedLibrary(
"hammer_<lang>",
swig_out,
LIBS=["hammer"],
LIBPATH=[hammer_lib_dir],
)
Default(jni_lib)Replace -<lang> with the SWIG language flag (e.g., -ruby, -go, -csharp).
Compile directly against libhammer headers:
src_dir = os.path.join(project_root, "src")
lang_src_dir = os.path.join(project_root, "src/bindings/<lang>")
hammer_lib_dir = os.path.dirname(str(libhammer_shared[0]))
langenv.Append(CPPPATH=[lang_src_dir, src_dir])
langenv.Append(LIBS=["hammer"])
langenv.Append(LIBPATH=[hammer_lib_dir])
lang_lib = langenv.SharedLibrary("hammer_<lang>", ["hammer_<lang>.c"])
Default(lang_lib)Run the test suite through tools/test_reporter.py, which writes a results file and feeds the summary printer in SConstruct:
reporter = os.path.join(Dir("#").abspath, "tools", "test_reporter.py")
langdir = os.path.join(Dir("#").abspath, env["BUILD_BASE"], "src/bindings/<lang>")
results_file = os.path.join(langdir, "hammer_<lang>.results")
langtestexec = langenv.Command(
"hammer_tests.stamp",
["hammer_tests.<ext>"] + list(lang_lib),
[
"LD_LIBRARY_PATH=%s %s %s --binding <Lang> --results-file %s -- <test-runner-command>"
% (hammer_lib_dir, sys.executable, reporter, results_file),
"touch $TARGET",
],
)
langtest = Alias("test<lang>", [langtestexec], langtestexec)
AlwaysBuild(langtestexec)
testruns.append(langtest)
binding_results.append(("<Lang>", results_file))
binding_test_stamps.append(langtestexec[0])The binding_results and binding_test_stamps lists are what SConstruct reads to print the per-binding pass/fail summary at the end of scons test. Always append to both.
AlwaysBuild ensures scons test re-runs the tests even when the stamp file is up to date.
langinstallexec = langenv.Command(
None, lang_lib, "<install command>"
)
langinstall = Alias("install<lang>", [langinstallexec], langinstallexec)
targets.append(langinstall)Use the language's native install tool when one exists (e.g., gem install, pip install, cargo install) for cross-platform compatibility.
src/bindings/swig/hammer.i uses #if defined(SWIG<LANG>) guards to isolate per-language code. Add a new block for your language:
#if defined(SWIG<LANG>)
// Map byte[] ↔ (const uint8_t* input, size_t length)
%typemap(...) (const uint8_t* input, size_t length) { ... }
%apply (const uint8_t* input, size_t length) {
(uint8_t* str, size_t len),
(const uint8_t* str, const size_t len),
(const uint8_t* charset, size_t length)
}
// uint8_t — map to the language's closest unsigned-byte type.
%typemap(in) uint8_t { ... }
// void*[] (NULL-terminated parser array)
%typemap(...) void*[] { ... }
%typemap(freearg) void*[] { free($1); }
#endifThe four conversions listed above are the minimum needed to expose the public API. Look at the Python and Java blocks in hammer.i for concrete examples of how each typemap is structured.
If the SWIG language flag is SWIG<LANG> (e.g., SWIGPYTHON, SWIGJAVA), check the SWIG documentation for the exact macro name for your target language.
Each binding should include tests that cover:
- Basic combinator round-trip: construct a parser, run
parse()on known input, assert the result. - Sequence and choice combinators.
-
action/attr_boolcallbacks (for languages that support passing functions as arguments). - A parse failure case (input that should not match).
Look at src/bindings/python/hammer_tests.py and src/bindings/java/HammerTests.java for the scope and style expected.
After the binding is working:
-
Add a
README.mdtosrc/bindings/<lang>/following the structure of the Python or Java binding README. -
Add an entry to
_Sidebar.mdunder "References":(It is already there; add a link to your binding's README if you publish it separately.)- [Adding a New Binding](Adding-a-New-Binding)
-
Update
DEVELOPMENT.mdto list the newbindings=<lang>option and its test alias.
- Language name added to
ListVariableinSConstruct -
src/bindings/<lang>/SConscriptcreated with build, test, and install sections -
Importline includes all six shared names:env libhammer_shared testruns targets binding_results binding_test_stamps -
libhammer_shared is Noneguard present - Test command appends to both
binding_resultsandbinding_test_stamps -
AlwaysBuildcalled on the test target - (SWIG) Language-specific typemaps added to
hammer.iunder#if defined(SWIG<LANG>) - Test suite covers basic parse, failure, and combinators
-
src/bindings/<lang>/README.mdwritten -
DEVELOPMENT.mdupdated
See also: Extending Hammer · Hammer Fundamentals · Home
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