These goals are what we are working towards in this project:
- A complete, widely compatible Prolog implementation
- Robust: It should handle all kinds of error conditions gracefully
- Fast: It should run prolog programs fast
- Scalable: It should be able to run large prolog programs
- Follow PEP 8 for Python code
- All imports must be at the top of the file (after module docstring, before other code)
- Never use
importstatements inside functions or methods - Group imports: standard library, third-party, local (separated by blank lines)
- Run all tests before pushing a PR
- Use type hints where helpful
- Add docstrings to all public methods
- Keep functions focused and testable
- Use descriptive variable names
This document provides an overview of the project structure and guidelines for development and testing.
See @docs/ARCHITECTURE.md
See @docs/FEATURES.md
You must keep ./docs/FEATURES.md up to date when you add or change these
All new markdown documentation should go in @docs/
Standard documentation files:
@docs/ARCHITECTURE.md- System architecture and design@docs/FEATURES.md- ISO Prolog conformance matrix and feature checklist- Any additional documentation, guides, or specs should be added as new files within the
@docs/directory.
Handle Prolog errors like this:
except PrologThrow:
# Re-raise Prolog errors
raise
except Exception: # Or use a more specific Exception if possible
# Unexpected errors - convert to evaluation error
raise PrologThrow(PrologError.evaluation_error("error", f"{op}/2"))All test files must go in the tests/ directory.
- Unit tests:
test_<module>.py- Tests for specific modules - Integration tests:
test_<feature>.py- Tests for features spanning multiple modules - Fixtures:
*.plfiles - Prolog code used by tests
- Python test files:
test_*.py - Prolog fixtures:
test_*.plor descriptive names likeminimal_mi_test.pl - Follow pytest conventions
-
Import the interpreter:
from vibeprolog import PrologInterpreter
-
Create a new interpreter for each test:
def test_something(): prolog = PrologInterpreter() # Your test code
-
Use pytest test classes to organize related tests:
class TestAppend: """Tests for append/3 predicate.""" def test_append_two_lists(self): prolog = PrologInterpreter() result = prolog.query_once("append([1, 2], [3, 4], X).") assert result['X'] == [1, 2, 3, 4]
-
Test both success and failure cases:
def test_unification_success(self): prolog = PrologInterpreter() assert prolog.has_solution("X = 5") def test_unification_failure(self): prolog = PrologInterpreter() assert not prolog.has_solution("1 = 2")
# Run all tests
uv run pytest
# Run all tests including performance (skipped by default). This is slow so run only when required.
uv run pytest --run-performance
# Run all slow tests (tests empirically longer than ~4 seconds). Skip unless flag is present.
uv run pytest --run-slow-tests
# Run specific test file
uv run pytest tests/test_builtins.py
# Run specific test class
uv run pytest tests/test_builtins.py::TestAppend
# Run specific test
uv run pytest tests/test_builtins.py::TestAppend::test_append_two_lists
# Run with verbose output
uv run pytest -v
# Run with output capture disabled (see print statements)
uv run pytest -s- When adding a new test that consistently takes longer than ~4 seconds, mark it with
@pytest.mark.performance(or setpytestmark = pytest.mark.performancefor whole classes/files) so it only runs when--run-performanceis used. - Document why the test is performance-marked in its docstring when the reason is not obvious.
This is standard ISO prolog implementation. The parser should parse standard prolog syntax.
- Choose a module in
vibeprolog/builtins/that fits (e.g.,arithmetic.py,list_ops.py,type_tests.py). - Implement a static handler with the signature
(args, subst, engine)and annotate return types. - Register the predicate in the module's
register(registry, engine_ref=None)usingregister_builtin. - Write tests in
tests/covering success and failure cases. - Update documentation (
CLAUDE.md/FEATURES.md) to reflect the new predicate. - Run tests:
uv run pytest tests/test_new_builtins.py -v
Syntax errors: When a built-in parses text (e.g., via
PrologParserorread_from_chars/2) and the input might be malformed, wrap the parser call intry/except (ValueError, LarkError)and create an error term withPrologError.syntax_error(str(exc), "predicate/arity")then raisePrologThrow(error_term). This ensures callers can intercept the ISO-styleerror(syntax_error(_), Context)term withcatch/3.
-
Use print statements in built-ins:
def _builtin_something(self, arg, subst): print(f"Debug: arg={arg}, subst={subst}") # ...
-
Create minimal test cases:
- Start with the simplest possible query
- Gradually add complexity
- Put test files in
tests/directory
-
Check parser output:
from vibeprolog.parser import PrologParser parser = PrologParser() result = parser.parse("your_query.") print(result)
The tools/ directory contains utilities for analyzing Prolog code and identifying missing functionality.
Analyzes which operators are used in a Prolog file and compares them against what Vibe-Prolog supports.
Usage:
uv run python tools/check_operators.py <prolog_file>Example:
uv run python tools/check_operators.py library/clpb.plWhat it does:
- Finds all operators used in the code
- Identifies operators declared in
:- op(...)directives or module exports - Categorizes operators as:
- Supported (defined in
operator_defaults.py) - Unsupported ISO operators (required by standard)
- Unsupported non-ISO operators (extensions)
- Supported (defined in
- Outputs a markdown report
When to use:
- Before implementing support for a new library
- To prioritize which operators to implement
- To understand operator dependencies
- To verify ISO compliance
Exit codes:
- 0: All ISO operators are supported
- 1: Some ISO operators are missing
Scans a directory of Prolog files to identify which predicates are called but not defined, indicating they're likely built-in predicates.
Usage:
uv run python tools/find_builtins.py <directory>Example:
uv run python tools/find_builtins.py examples/What it does:
- Parses all
.plfiles in the directory - Identifies predicates that are called but not defined
- Categorizes predicates as:
- Actual built-ins (registered in Vibe-Prolog)
- Dynamic predicates (declared
:- dynamicbut asserted at runtime) - Undefined predicates (not built-in, not dynamic, not defined - might be missing)
- Shows which files use each predicate
When to use:
- To discover what built-ins are needed for a set of examples
- To identify missing implementations
- To prioritize built-in development
- To verify completeness of built-in coverage
Note: This tool identifies predicates, not operators. Operators are syntactic and checked by check_operators.py.
Tests Vibe-Prolog against the official ISO Prolog conformity test suite (355 tests).
Usage:
# Run all tests and generate report
uv run python tools/conformity_test.py
# Run specific tests
uv run python tools/conformity_test.py --tests 1-50
# Verbose output
uv run python tools/conformity_test.py --verboseWhen to use:
- Before major releases to verify ISO compliance
- After parser/syntax changes to catch regressions
- When investigating ISO compatibility issues
- To track conformity improvements over time
Output: Generates docs/CONFORMITY_TESTING.md with detailed results.
- SWI-Prolog Documentation - Reference for built-in predicates
- pytest Documentation - Testing framework
- Lark Parser - Parser generator used for Prolog syntax
- Write tests first (TDD approach recommended)
- Ensure all tests pass before committing
- Update this documentation when adding features
- Keep the codebase clean and well-organized