Evaluate over the structure

One rule per kind of node, each evaluating a node from its children:

# An assignment A is an N-bit number
assignment(A) :- num_vars(N), A in [0 .. (1 << N) - 1].

# Under assignment A, node N has value V
eval(N, A, V) :- node(N, "var"),  node_var(N, I), assignment(A), V = (A >> I) & 1.
eval(N, A, V) :- node(N, "not"),  node_child(N, 0, C), eval(C, A, VC), V = 1 - VC.
eval(N, A, V) :- node(N, "and"),  eval_children(N, A, L, R), V = L & R.
eval(N, A, V) :- node(N, "or"),   eval_children(N, A, L, R), V = L | R.
eval(N, A, V) :- node(N, "impl"), eval_children(N, A, L, R), V = (1 - L) | R.

# Under assignment A, node N's left child has value LV, and its right child has value RV
eval_children(N, A, LV, RV) :- node_child(N, 0, L), node_child(N, 1, R), eval(L, A, LV), eval(R, A, RV).
eval and eval_children are mutually recursive, just like even and odd.

On a single bit, & is AND, | is OR, 1 - V is NOT, and p → q is ¬p ∨ q.