Replace the single-pass body run with table-2-slg-iter / table-3-slg-iter:
each iteration stores the current vals in cache and re-runs the body;
loop until vals length stops growing. The cache thus grows
monotonically until no new answers appear.
For simple cycles (single tabled relation) this is sound and
terminating — len comparison is O(1) and the cache only grows.
Limitation: mutually-recursive tabled relations have INDEPENDENT
iteration loops. Each runs to its own fixed point in isolation; the
two don't coordinate. True SLG uses a worklist that cross-fires
re-iteration when any subgoal's cache grows. Left as a future
refinement.
All 5 SLG tests still pass (Fibonacci unchanged, 3 cyclic-patho
cases unchanged).
Solves the canonical cyclic-graph divergence problem from the deferred
plan. Naive memoization (table-1/2/3 in tabling.sx) drains the body's
answer stream eagerly; cyclic recursive calls with the same ground key
diverge before populating the cache.
table-2-slg / table-3-slg add an in-progress sentinel: before
evaluating the body, mark the cache entry :in-progress. Any recursive
call to the same key sees the sentinel and returns mzero (no answers
yet). Outer recursion thus terminates on cycles. After the body
finishes, the sentinel is replaced with the actual answer-value list.
Demo: tab-patho with a 3-edge graph (a -> b, b -> a, b -> c).
(run* q (tab-patho :a :c q)) -> ((:a :b :c)) ; finite
(run* q (tab-patho :a :a q)) -> ((:a :b :a)) ; one cycle visit
(run* q (tab-patho :a :b q)) -> ((:a :b)) ; direct edge
Without SLG, all three diverge.
Limitation: single-pass — answers found by cycle-dependent recursive
calls are not iteratively re-discovered. Full SLG with fixed-point
iteration (re-running until no new answers) is left for follow-up.
5 new tests including SLG-fib for sanity (matches naive table-2),
3 cyclic patho cases.