Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/HarvardPL/AbcDatalog/llms.txt

Use this file to discover all available pages before exploring further.

AbcDatalog’s AST nodes use the visitor pattern to let you process different node types with compile-time safety and without casting. Three independent visitor hierarchies cover every part of a program: heads of clauses, premises (body conjuncts), and terms within atoms. Each hierarchy ships with a fluent builder, two base classes (one that crashes on unhandled nodes, one that returns null), and a matching default implementation you can override selectively.

Visitor hierarchies

HeadVisitor<I, O>

Visits the head of a Clause. Currently the only concrete head type is PositiveAtom, so the interface has one method:
public interface HeadVisitor<I, O> {
    O visit(PositiveAtom atom, I state);
}
I is an arbitrary input state threaded through the visit call; O is the return type.

PremiseVisitor<I, O>

Visits body premises. A clause body can contain five node types:
public interface PremiseVisitor<I, O> {
    O visit(PositiveAtom atom, I state);
    O visit(AnnotatedAtom atom, I state);
    O visit(BinaryUnifier u, I state);
    O visit(BinaryDisunifier u, I state);
    O visit(NegatedAtom atom, I state);
}
AnnotatedAtom is an internal wrapper used by bottom-up evaluation engines; user-facing programs typically contain PositiveAtom, NegatedAtom, BinaryUnifier, and BinaryDisunifier.

TermVisitor<I, O>

Visits the two kinds of term that appear in atom argument lists:
public interface TermVisitor<I, O> {
    O visit(Variable t, I state);
    O visit(Constant t, I state);
}

Builder APIs

Each visitor interface has a corresponding builder that lets you register per-type handlers as lambdas and choose a fallback strategy.

PremiseVisitorBuilder<I, O>

new PremiseVisitorBuilder<I, O>()
    .onPositiveAtom(BiFunction<PositiveAtom, I, O>)
    .onNegatedAtom(BiFunction<NegatedAtom, I, O>)
    .onBinaryUnifier(BiFunction<BinaryUnifier, I, O>)
    .onBinaryDisunifier(BiFunction<BinaryDisunifier, I, O>)
    .onAnnotatedAtom(BiFunction<AnnotatedAtom, I, O>)
    .or(BiFunction<Premise, I, O>)   // fallback for unregistered types
    // .orNull()                      // fallback returns null
    // .orCrash()                     // fallback throws UnsupportedOperationException

HeadVisitorBuilder<I, O>

new HeadVisitorBuilder<I, O>()
    .onPositiveAtom(BiFunction<PositiveAtom, I, O>)
    .or(BiFunction<Head, I, O>)
    // .orNull() / .orCrash()

TermVisitorBuilder<I, O>

new TermVisitorBuilder<I, O>()
    .onVariable(BiFunction<Variable, I, O>)
    .onConstant(BiFunction<Constant, I, O>)
    .or(BiFunction<Term, I, O>)
    // .orNull() / .orCrash()
Every builder method returns this, enabling chaining. Handlers not registered via a builder method fall through to the or/orNull/orCrash fallback.

Example: collecting all PositiveAtom premises from a clause body

import edu.harvard.seas.pl.abcdatalog.ast.Clause;
import edu.harvard.seas.pl.abcdatalog.ast.PositiveAtom;
import edu.harvard.seas.pl.abcdatalog.ast.Premise;
import edu.harvard.seas.pl.abcdatalog.ast.visitors.PremiseVisitor;
import edu.harvard.seas.pl.abcdatalog.ast.visitors.PremiseVisitorBuilder;
import java.util.ArrayList;
import java.util.List;

public static List<PositiveAtom> collectPositiveAtoms(Clause clause) {
    List<PositiveAtom> result = new ArrayList<>();

    PremiseVisitor<Void, Void> collector =
        new PremiseVisitorBuilder<Void, Void>()
            .onPositiveAtom((atom, state) -> {
                result.add(atom);
                return null;
            })
            .orNull(); // ignore NegatedAtom, BinaryUnifier, BinaryDisunifier, AnnotatedAtom

    for (Premise premise : clause.getBody()) {
        premise.accept(collector, null);
    }

    return result;
}

Default and crash base classes

When subclassing is more convenient than a builder, four abstract base classes are available:
ClassImplementsUnhandled case
CrashHeadVisitor<I, O>HeadVisitor<I, O>throws UnsupportedOperationException
CrashPremiseVisitor<I, O>PremiseVisitor<I, O>throws UnsupportedOperationException
DefaultConjunctVisitor<I, O>PremiseVisitor<I, O>returns null
DefaultTermVisitor<I, O>TermVisitor<I, O>returns null

When to use each visitor type

  • HeadVisitor — when iterating over a set of clauses to inspect or rewrite heads; used in normalization passes and rule indexing.
  • PremiseVisitor — when implementing analyses or transformations that must distinguish between positive atoms, negated atoms, and unification constraints in a rule body.
  • TermVisitor — when you need to recurse into atom arguments, for example to collect all variables, check groundness, or apply a substitution.
Extend CrashPremiseVisitor or DefaultConjunctVisitor when your analysis only cares about a subset of premise types. CrashPremiseVisitor will surface unexpected node types as exceptions during development; DefaultConjunctVisitor silently ignores them, which is appropriate when unrecognized premises are legitimately no-ops for your use case.

Build docs developers (and LLMs) love