The demo below encodes a simple counting loop as Horn clauses and runs a forward-chaining solver that derives the strongest reachable fact at each step.
<!-- Horn-clause verification demo layout -->
<div class="panel">
<div class="section-label">Program</div>
<pre class="code-block" id="program-code"></pre>
</div>
<div class="panel">
<div class="section-label">Horn Clauses</div>
<div id="clause-list" class="clause-list"></div>
</div>
<div class="panel">
<div class="section-label">Derived Facts (Invariant)</div>
<div id="deriv-table" class="deriv-table"></div>
</div>
<div class="status" id="status"></div>
<div class="btns">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run all steps</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Styles for the Horn-clause verification demo */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; font-size: 14px; color: #222; padding: 4px; }
.panel { margin-bottom: 8px; }
.section-label { font-size: 11px; font-weight: 700; text-transform: uppercase;
letter-spacing: .06em; color: #5a7088; margin-bottom: 4px; }
.code-block { background: #f4f6f8; border: 1px solid #d0d7de; border-radius: 6px;
padding: 8px 10px; font-size: 12px; line-height: 1.6; white-space: pre; overflow-x: auto; }
.clause-list { display: flex; flex-direction: column; gap: 4px; }
.clause { font-family: ui-monospace, monospace; font-size: 12px; padding: 5px 8px;
border-radius: 5px; border: 1px solid #d0d7de; background: #f9fbfc; }
.clause .lbl { font-weight: 700; color: #1d3557; margin-right: 6px; }
.clause.fired { background: #e6f4ea; border-color: #7dc99e; }
.deriv-table { display: flex; flex-direction: column; gap: 3px; max-height: 140px; overflow-y: auto; }
.deriv-row { display: flex; gap: 8px; align-items: baseline; font-size: 12px;
padding: 3px 6px; border-radius: 4px; }
.deriv-row.new { background: #e8f0fe; animation: fadein .35s; }
.deriv-row .step-num { font-weight: 700; color: #5a7088; min-width: 22px; }
.deriv-row .fact { font-family: ui-monospace, monospace; }
.deriv-row .source { color: #888; font-size: 11px; }
@keyframes fadein { from { opacity:0; transform: translateY(-4px); } to { opacity:1; transform:none; } }
.status { font-size: 13px; font-weight: 600; min-height: 1.4em; margin: 6px 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: 6px; flex-wrap: wrap; }
button { font: 600 13px system-ui; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* The program being verified — a simple counting loop from 0 to N */
const PROGRAM_TEXT = [
'int x = 0, n = 5;',
'while (x < n) {',
' x = x + 1;',
'}',
'// assertion: x == n'
].join('\n');
/* Horn clauses encoding the loop: init, body transition, query (post-condition) */
/* init: the invariant holds before the loop (x=0 is reachable) */
/* body: if Inv(x) and x<N then Inv(x+1) (the loop step preserves the invariant) */
/* query: if Inv(x) and x>=N then x=N (the post-condition) */
const CLAUSES = [
{ id: 'init', label: 'init', text: 'Inv(0)', rule: null },
{ id: 'body', label: 'body', text: 'Inv(x) ∧ x<5 ⇒ Inv(x+1)', rule: 'body' },
{ id: 'query', label: 'query', text: 'Inv(x) ∧ x≥5 ⇒ x=5', rule: 'query' }
];
/* Forward-chaining solver state */
let derived = [];
let stepIndex = 0;
let done = false;
const N = 5;
/* Pre-compute all derivation steps from init through N body firings */
function computeAllSteps() {
const steps = [];
steps.push({ val: 0, clause: 'init', stepNum: 0 });
for (let x = 0; x < N; x++) {
steps.push({ val: x + 1, clause: 'body', stepNum: x + 1 });
}
return steps;
}
const ALL_STEPS = computeAllSteps();
function renderProgram() {
document.getElementById('program-code').textContent = PROGRAM_TEXT;
}
function renderClauses(firedId) {
const el = document.getElementById('clause-list');
el.innerHTML = '';
for (const c of CLAUSES) {
const d = document.createElement('div');
d.className = 'clause' + (firedId === c.id ? ' fired' : '');
d.innerHTML = '<span class="lbl">[' + c.label + ']</span>' + c.text;
el.appendChild(d);
}
}
function renderDeriv() {
const el = document.getElementById('deriv-table');
el.innerHTML = '';
for (let i = 0; i < derived.length; i++) {
const r = document.createElement('div');
r.className = 'deriv-row' + (i === derived.length - 1 ? ' new' : '');
const { val, clause } = derived[i];
r.innerHTML =
'<span class="step-num">' + i + '</span>' +
'<span class="fact">Inv(' + val + ')</span>' +
'<span class="source">← [' + clause + ']</span>';
el.appendChild(r);
}
el.scrollTop = el.scrollHeight;
}
function setStatus(msg, cls) {
const el = document.getElementById('status');
el.textContent = msg;
el.className = 'status ' + (cls || '');
}
function applyStep() {
if (done) return false;
if (stepIndex < ALL_STEPS.length) {
derived.push(ALL_STEPS[stepIndex]);
stepIndex++;
return true;
}
return false;
}
function checkQuery() {
/* Check that the last derived fact satisfies the query clause */
const last = derived[derived.length - 1];
if (last && last.val === N) {
return true;
}
return false;
}
function updateButtons() {
document.getElementById('btn-step').disabled = done;
document.getElementById('btn-run').disabled = done;
}
function step() {
if (done) return;
const progressed = applyStep();
const lastStep = ALL_STEPS[stepIndex - 1];
renderClauses(lastStep ? lastStep.clause : null);
renderDeriv();
if (!progressed || stepIndex >= ALL_STEPS.length) {
/* Fixpoint reached — check the query clause */
const valid = checkQuery();
done = true;
updateButtons();
if (valid) {
setStatus(i18nFmt('Proved: after %n% iterations the invariant guarantees x = %n%.', { n: N }), 'ok');
} else {
setStatus('Query clause not satisfied — verification failed.', 'bad');
}
renderClauses('query');
} else {
setStatus(i18nFmt('Derived: Inv(%x%) via [body].', { x: lastStep.val }), 'info');
}
}
function runAll() {
if (done) return;
while (!done) { step(); }
}
function reset() {
derived = [];
stepIndex = 0;
done = false;
renderProgram();
renderClauses(null);
renderDeriv();
setStatus('Press Step or Run to watch the solver derive the loop invariant.', 'info');
updateButtons();
}
document.getElementById('btn-step').addEventListener('click', step);
document.getElementById('btn-run').addEventListener('click', runAll);
document.getElementById('btn-reset').addEventListener('click', reset);
reset();
<!-- Horn-clause verification demo layout -->
<div class="panel">
<div class="section-label">Program</div>
<pre class="code-block" id="program-code"></pre>
</div>
<div class="panel">
<div class="section-label">Horn Clauses</div>
<div id="clause-list" class="clause-list"></div>
</div>
<div class="panel">
<div class="section-label">Derived Facts (Invariant)</div>
<div id="deriv-table" class="deriv-table"></div>
</div>
<div class="status" id="status"></div>
<div class="btns">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run all steps</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Styles for the Horn-clause verification demo */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; font-size: 14px; color: #222; padding: 4px; }
.panel { margin-bottom: 8px; }
.section-label { font-size: 11px; font-weight: 700; text-transform: uppercase;
letter-spacing: .06em; color: #5a7088; margin-bottom: 4px; }
.code-block { background: #f4f6f8; border: 1px solid #d0d7de; border-radius: 6px;
padding: 8px 10px; font-size: 12px; line-height: 1.6; white-space: pre; overflow-x: auto; }
.clause-list { display: flex; flex-direction: column; gap: 4px; }
.clause { font-family: ui-monospace, monospace; font-size: 12px; padding: 5px 8px;
border-radius: 5px; border: 1px solid #d0d7de; background: #f9fbfc; }
.clause .lbl { font-weight: 700; color: #1d3557; margin-right: 6px; }
.clause.fired { background: #e6f4ea; border-color: #7dc99e; }
.deriv-table { display: flex; flex-direction: column; gap: 3px; max-height: 140px; overflow-y: auto; }
.deriv-row { display: flex; gap: 8px; align-items: baseline; font-size: 12px;
padding: 3px 6px; border-radius: 4px; }
.deriv-row.new { background: #e8f0fe; animation: fadein .35s; }
.deriv-row .step-num { font-weight: 700; color: #5a7088; min-width: 22px; }
.deriv-row .fact { font-family: ui-monospace, monospace; }
.deriv-row .source { color: #888; font-size: 11px; }
@keyframes fadein { from { opacity:0; transform: translateY(-4px); } to { opacity:1; transform:none; } }
.status { font-size: 13px; font-weight: 600; min-height: 1.4em; margin: 6px 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: 6px; flex-wrap: wrap; }
button { font: 600 13px system-ui; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* The program being verified — a simple counting loop from 0 to N */
const PROGRAM_TEXT = [
'int x = 0, n = 5;',
'while (x < n) {',
' x = x + 1;',
'}',
'// assertion: x == n'
].join('\n');
/* Horn clauses encoding the loop: init, body transition, query (post-condition) */
/* init: the invariant holds before the loop (x=0 is reachable) */
/* body: if Inv(x) and x<N then Inv(x+1) (the loop step preserves the invariant) */
/* query: if Inv(x) and x>=N then x=N (the post-condition) */
const CLAUSES = [
{ id: 'init', label: 'init', text: 'Inv(0)', rule: null },
{ id: 'body', label: 'body', text: 'Inv(x) ∧ x<5 ⇒ Inv(x+1)', rule: 'body' },
{ id: 'query', label: 'query', text: 'Inv(x) ∧ x≥5 ⇒ x=5', rule: 'query' }
];
/* Forward-chaining solver state */
let derived = [];
let stepIndex = 0;
let done = false;
const N = 5;
/* Pre-compute all derivation steps from init through N body firings */
function computeAllSteps() {
const steps = [];
steps.push({ val: 0, clause: 'init', stepNum: 0 });
for (let x = 0; x < N; x++) {
steps.push({ val: x + 1, clause: 'body', stepNum: x + 1 });
}
return steps;
}
const ALL_STEPS = computeAllSteps();
function renderProgram() {
document.getElementById('program-code').textContent = PROGRAM_TEXT;
}
function renderClauses(firedId) {
const el = document.getElementById('clause-list');
el.innerHTML = '';
for (const c of CLAUSES) {
const d = document.createElement('div');
d.className = 'clause' + (firedId === c.id ? ' fired' : '');
d.innerHTML = '<span class="lbl">[' + c.label + ']</span>' + c.text;
el.appendChild(d);
}
}
function renderDeriv() {
const el = document.getElementById('deriv-table');
el.innerHTML = '';
for (let i = 0; i < derived.length; i++) {
const r = document.createElement('div');
r.className = 'deriv-row' + (i === derived.length - 1 ? ' new' : '');
const { val, clause } = derived[i];
r.innerHTML =
'<span class="step-num">' + i + '</span>' +
'<span class="fact">Inv(' + val + ')</span>' +
'<span class="source">← [' + clause + ']</span>';
el.appendChild(r);
}
el.scrollTop = el.scrollHeight;
}
function setStatus(msg, cls) {
const el = document.getElementById('status');
el.textContent = msg;
el.className = 'status ' + (cls || '');
}
function applyStep() {
if (done) return false;
if (stepIndex < ALL_STEPS.length) {
derived.push(ALL_STEPS[stepIndex]);
stepIndex++;
return true;
}
return false;
}
function checkQuery() {
/* Check that the last derived fact satisfies the query clause */
const last = derived[derived.length - 1];
if (last && last.val === N) {
return true;
}
return false;
}
function updateButtons() {
document.getElementById('btn-step').disabled = done;
document.getElementById('btn-run').disabled = done;
}
function step() {
if (done) return;
const progressed = applyStep();
const lastStep = ALL_STEPS[stepIndex - 1];
renderClauses(lastStep ? lastStep.clause : null);
renderDeriv();
if (!progressed || stepIndex >= ALL_STEPS.length) {
/* Fixpoint reached — check the query clause */
const valid = checkQuery();
done = true;
updateButtons();
if (valid) {
setStatus(i18nFmt('Proved: after %n% iterations the invariant guarantees x = %n%.', { n: N }), 'ok');
} else {
setStatus('Query clause not satisfied — verification failed.', 'bad');
}
renderClauses('query');
} else {
setStatus(i18nFmt('Derived: Inv(%x%) via [body].', { x: lastStep.val }), 'info');
}
}
function runAll() {
if (done) return;
while (!done) { step(); }
}
function reset() {
derived = [];
stepIndex = 0;
done = false;
renderProgram();
renderClauses(null);
renderDeriv();
setStatus('Press Step or Run to watch the solver derive the loop invariant.', 'info');
updateButtons();
}
document.getElementById('btn-step').addEventListener('click', step);
document.getElementById('btn-run').addEventListener('click', runAll);
document.getElementById('btn-reset').addEventListener('click', reset);
reset();
Derived Facts (Invariant)
Step
Run all steps
Reset