Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y scons gcc libglib2.0-dev pkg-config
sudo apt-get install -y scons gcc libglib2.0-dev pkg-config swig default-jdk libgtest-dev python3-setuptools

- name: Run tests
run: scons test

- name: Run binding tests
run: scons bindings=all test
Comment thread
melbasiouny-riverside marked this conversation as resolved.

build-deb:
runs-on: ubuntu-latest
needs: [clang-format, scons-test]
Expand Down
45 changes: 44 additions & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ Use `clang-format` to keep C code consistent.
Examples:

- Format all C sources and headers:

```bash
clang-format -i **/*.c **/*.h
```

- Format a single file:

```bash
clang-format -i path/to/file.c
```
Expand All @@ -39,49 +42,87 @@ The repository centralizes the semantic version in the `VERSION` file. Update th
### Test coverage

Install coverage tools:

```bash
sudo apt install lcov xdg-utils
```

Generate coverage and open the HTML report:

```bash
scons -c --variant=debug && scons --coverage --variant=debug test && mkdir -p coverage && lcov --directory build/debug/src --zerocounters && lcov --ignore-errors gcov --capture --initial --directory build/debug/src --output-file coverage/base.info && scons --coverage --variant=debug test && lcov --ignore-errors gcov --capture --directory build/debug/src --output-file coverage/test.info && lcov --add-tracefile coverage/base.info --add-tracefile coverage/test.info --output-file coverage/coverage.info && genhtml coverage/coverage.info --output-directory coverage/html && xdg-open coverage/html/index.html
```

Notes:

- On WSL, replace `xdg-open` with `wslview` (from the `wslu` package).
- Coverage and object files (`.gcov`, `.gcno`, `.gcda`, `.o`) are generated under `build/debug/` or `build/opt/`.
- To generate `.gcov` files manually: `scons --coverage --variant=debug gcov`.

### Building and testing language bindings

Install the required tools:

```bash
sudo apt install swig default-jdk libgtest-dev
pip install setuptools
```

Build and test all language bindings:

```bash
scons bindings=all test
```

To target a specific binding, pass it individually and use its alias (`testpython`, `testjava`, or `testcpp`):

```bash
scons bindings=python testpython
scons bindings=java testjava
scons bindings=cpp testcpp
```

If `JAVA_HOME` is not set, the build locates `javac` via `PATH`. To use a specific JDK:

```bash
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 scons bindings=java
```

### Linting Python and SCons files

Install `ruff`:

```bash
sudo apt install pipx
pipx install ruff
```

Lint all Python and SCons files with:

```bash
ruff check $(find . -name "*.py" -o -name "SConstruct" -o -name "SConscript")
ruff check $(find . -path ./build -prune -o \( -name "*.py" -o -name "SConstruct" -o -name "SConscript" \) -print)
```

Notes:

- `ruff` configuration lives in `ruff.toml`.

### Generating documentation (Doxygen)

Install Doxygen:

```bash
sudo apt install doxygen
```

Build the documentation:

```bash
doxygen Doxyfile
```

Output will be in `docs/html/`. Open the main page:

```bash
xdg-open docs/html/index.html
```
Expand Down Expand Up @@ -133,10 +174,12 @@ In other words, don't let the (memory manager) streams cross.
### Parse Result Behavior

**Regarding parse_result_t:**

- If a parse fails, the parse_result_t will be NULL.
- If a parse is successful but there's nothing there (i.e., if end_p succeeds), then there's a parse_result_t but its ast is NULL.

**Regarding input location:**

- If parse is successful, input is left at beginning of next thing to be read.
- If parse fails, location is UNPREDICTABLE.

Expand Down
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Hammer is a parsing library. Like many modern parsing libraries, it provides a p
Hammer is written in C and provides a packrat parsing backend.

## MicroHammer

MicroHammer is a slimmed-down version of Hammer with the goal of providing a lightweight, Linux-focused version of Hammer with a minimal, clean codebase. [Link to public release.](https://github.com/riversideresearch/hammer/releases/)

The main feature of MicroHammer is its significantly smaller codebase, allowing for easier maintenance and onboarding. Key differences from the full Hammer library include:
Expand All @@ -13,8 +14,7 @@ The main feature of MicroHammer is its significantly smaller codebase, allowing
- More thorough and consistent documentation
- Windows / macOS not supported
- Packrat parsing backend only
- No bindings for other languages

- Language bindings for Python, Java, and C++ (see [Python Bindings](src/bindings/python/README.md), [Java Bindings](src/bindings/java/README.md), [C++ Bindings](src/bindings/cpp/README.md))

## Features

Expand Down Expand Up @@ -70,6 +70,43 @@ To learn about hammer, check:
- [Hammer Primer](https://github.com/sergeybratus/HammerPrimer) (outdated in terms of code, but good to get the general thinking)
- [Try Hammer](https://github.com/sboesen/TryHammer)

## Language Bindings

Hammer provides bindings for use from languages other than C.

### Python

Requires [SWIG](https://www.swig.org/) 4.x, Python 3.8+, and `setuptools`.

```bash
sudo apt install swig
scons bindings=python
```

See [src/bindings/python/README.md](src/bindings/python/README.md) for the full API reference and usage guide.

### Java

Requires [SWIG](https://www.swig.org/) 4.x and JDK 11+.

```bash
sudo apt install swig default-jdk
scons bindings=java
```

See [src/bindings/java/README.md](src/bindings/java/README.md) for the full API reference and usage guide.

### C++

Requires `g++` and `libgtest-dev` (for tests).

```bash
sudo apt install libgtest-dev
scons bindings=cpp
```

See [src/bindings/cpp/README.md](src/bindings/cpp/README.md) for the full API reference and usage guide.

## Examples

The `examples/` directory contains some simple examples, currently including:
Comment thread
melbasiouny-riverside marked this conversation as resolved.
Expand All @@ -83,12 +120,12 @@ For information on contributing to Hammer, including development setup, code for

## Contact

Send an email to parsing@riversideresearch.org
Send an email to <parsing@riversideresearch.org>

## Acknowledgment

This material is based upon work supported by the Defense Advanced Research Projects Agency (DARPA) under Prime Contract No. HR001119C0077. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the Defense Advanced Research Projects Agency (DARPA).

This work is a fork of the repository found at: https://gitlab.special-circumstanc.es/hammer/hammer
This work is a fork of the repository found at: <https://gitlab.special-circumstanc.es/hammer/hammer>

Distribution A: Approved for Public Release
Distribution A: Approved for Public Release
45 changes: 44 additions & 1 deletion SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ vars.Add(
vars.Add(
PathVariable("prefix", "Where to install in the FHS", "/usr/local", PathVariable.PathAccept)
)
vars.Add("python", "Python interpreter", "python")
vars.Add(
ListVariable("bindings", "Language bindings to build", "none", ["python", "java", "cpp"])
)
vars.Add("python", "Python interpreter", "python3")
Comment thread
melbasiouny-riverside marked this conversation as resolved.

tools = ["default", "scanreplace"]

Expand Down Expand Up @@ -188,12 +191,16 @@ env["ENV"].update(x for x in os.environ.items() if x[0].startswith("CCC_"))
# env.Append(CPPPATH=os.path.join('#', 'hammer'))

testruns = []
binding_results = [] # [(display_name, results_file_path), ...]
binding_test_stamps = [] # [SCons node of each binding test stamp, ...]

targets = ["$libpath", "$incpath", "$parsersincpath", "$backendsincpath", "$pkgconfigpath"]

Export("env")
Export("testruns")
Export("targets")
Export("binding_results")
Export("binding_test_stamps")

if not GetOption("in_place"):
env["BUILD_BASE"] = "build/$VARIANT"
Expand All @@ -209,6 +216,42 @@ else:
for testrun in testruns:
env.Alias("test", testrun)

if binding_results:
_br = list(binding_results)

def _print_summary(target, source, env, br=_br):
rows = []
for name, rf in br:
try:
parts = open(env.subst(rf)).read().strip().split()
rows.append((name, int(parts[1]), int(parts[2])))
except Exception:
pass
if not rows:
return 0
w = max(len(r[0]) for r in rows)
tp = sum(r[1] for r in rows)
tf = sum(r[2] for r in rows)
print("\nTest Results:")
for name, p, f in rows:
t = p + f
status = "passed" if not f else "FAILED"
print(f" {name:<{w}} : {p}/{t} {status}")
print(" " + "\u2500" * (w + 18))
total = tp + tf
status = "passed" if not tf else "FAILED"
print(f" {'Total':<{w}} : {tp}/{total} {status}\n")
return 0

build_base = env.subst("$BUILD_BASE")
summary = env.Command(
os.path.join(build_base, "src", "binding_tests_summary.stamp"),
binding_test_stamps,
Action(_print_summary, strfunction=lambda *a: ""),
)
AlwaysBuild(summary)
env.Alias("test", summary)

# Add gcov target to generate coverage files in build directory
if GetOption("coverage"):
build_base = env.subst("$BUILD_BASE")
Expand Down
1 change: 1 addition & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# https://docs.astral.sh/ruff/configuration/

line-length = 100
exclude = ["build/"]

[lint]
ignore = ["F821"]
Expand Down
35 changes: 23 additions & 12 deletions src/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ from __future__ import absolute_import, division, print_function
import glob
import os
import re
import sys

Import("env testruns")
Import("env testruns binding_results binding_test_stamps")

dist_headers = [
"hammer.h",
Expand Down Expand Up @@ -211,18 +212,28 @@ if GetOption("with_tests"):
if os.path.basename(_libdir) == "build":
_libdir = os.path.dirname(_libdir)
_exe = ctestexec[0].path
_run_and_summarize = (
"tmp=$$(mktemp); env LD_LIBRARY_PATH="
+ _libdir
+ " "
+ _exe
+ ' > "$$tmp"; status=$$?; cat "$$tmp"; rm "$$tmp"; exit $$status'
reporter = os.path.join(Dir("#").abspath, "tools", "test_reporter.py")
core_results_dir = os.path.join(Dir("#").abspath, env.subst("$BUILD_BASE"), "src")
core_results_file = os.path.join(core_results_dir, "hammer_core.results")
ctestexec_stamp = testenv.Command(
"core_tests.stamp",
[ctestexec],
[
"LD_LIBRARY_PATH=%s %s %s --binding Core --results-file %s -- %s"
% (_libdir, sys.executable, reporter, core_results_file, _exe),
"touch $TARGET",
],
)
ctest = Alias("testc", [ctestexec], _run_and_summarize)
AlwaysBuild(ctestexec_stamp)
ctest = Alias("testc", [ctestexec_stamp], ctestexec_stamp)
AlwaysBuild(ctest)
testruns.append(ctest)
binding_results.append(("Core", core_results_file))
binding_test_stamps.append(ctestexec_stamp[0])

if libhammer_shared is not None:
Export("libhammer_static libhammer_shared")
else:
Export("libhammer_static")
Export("libhammer_static libhammer_shared")

for b in env.get("bindings", []):

Copilot AI Mar 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env.get('bindings', []) will include the default ['none'] from the ListVariable, so this loop will attempt to load src/bindings/none/SConscript and fail. After normalizing the bindings option in SConstruct, this loop should only iterate real binding names (or be guarded to skip none).

Suggested change
for b in env.get("bindings", []):
for b in env.get("bindings", []):
if b == "none":
continue

Copilot uses AI. Check for mistakes.
if b == "none":
continue
env.SConscript(["bindings/%s/SConscript" % b])
Loading
Loading