Logic programming was built for this. Unification searches the space; backtracking is free.
01
Represent a clause as a list of literals — positive literals are atoms, negative literals are wrapped in not/1. The whole formula is a list of clauses.
% (a ∨ ¬b) ∧ (b ∨ c) ∧ (¬a ∨ ¬c)
formula([[a, not(b)], [b, c], [not(a), not(c)]]).
02
A literal is satisfied if its variable is bound to the right value in the assignment list. Member/2 walks the assignment until it finds a match — or fails, triggering backtracking.
sat_lit(not(V), A) :- member(V=false, A), !.
sat_lit(V, A) :- atom(V), member(V=true, A), !.
clause_sat(C, A) :- member(L, C), sat_lit(L, A).
03
DPLL — pick an unbound variable, try true, then false. The disjunction (;)/2 hands the second branch to Prolog's backtracker for free; we only describe the relation, not the procedure.
dpll([], _).
dpll(Cs, A) :-
pick_var(Cs, A, V),
( solve(Cs, [V=true |A])
; solve(Cs, [V=false|A]) ).
solve(Cs, A) :- maplist([C]>>clause_sat(C,A), Cs).
04
Run it. SWI-Prolog returns the first satisfying assignment, or false if the formula is UNSAT. The whole solver is twenty lines because the language is a backtracking search engine — you write the spec; the runtime does the searching.
?- formula(F), dpll(F, A).
A = [a=true, b=false, c=true].