Your rev/2
is invoking append/3
with two unbound variables. On backtracking, it's generating unbound lists of ever-increasing length:

That's a feature of append/3
.
The "classic" reversal of a list is this:
rev( [] , [] ) .
rev( [X|Xs] , Ys ) :- rev(Xs,X1), append(X1,[X],Ys) .
It runs on O(N2) time. And it will blow the stack given a list of sufficient length. And if the 1st argument to rev/2
is unbound, you'll go into a recursive loop (until the stack overflows).
Instead, use a helper predicate with an accumulator. It's a common Prolog idiom to have a "public" predicate that does little more than invoke a "private" helper predicate of the same name, but with different arity, where the extra arguments carry the necessary state to accomplish the task at hand.
rev( [] , Ys , Ys ) .
rev( [X|Xs] , Ts , Ys ) :- rev(Xs,[X|Ts],Ys).
Now you have something that is (1) tail recursive, and (2) runs in O(n) time and space.
Once you have that, the "public" rev/2
is just this:
rev( Xs, Ys ) :- rev(Xs,[],Ys) .
where we seed the accumulator with the empty list.
Note however, that this rev/2
is not bi-directional. Invoking rev(Xs,[a,b,c])
will work successfully, but backtracking into it will result in an infinite recursive loop.
To fix that, simply add some type checking into rev/2
. To successfully reverse a list, at least one of Xs
or Ys
must be bound. That gives us this:
rev( Xs , Ys ) :- nonvar(Xs), !, rev( Xs, [] , Ys ) .
rev( Xs , Ys ) :- nonvar(Ys), rev( Ys, [] , Xs ) .
Putting it all together, you get:
https://swish.swi-prolog.org/p/RdhuxIlF.pl
rev( Xs , Ys ) :- nonvar(Xs), !, rev( Xs, [] , Ys ) .
rev( Xs , Ys ) :- nonvar(Ys), rev( Ys, [] , Xs ) .
rev( [] , Ys , Ys ) .
rev( [X|Xs] , Ts , Ys ) :- rev(Xs,[X|Ts],Ys).