Files
LithosAnanake/proof/StarForth_Stack_Words.thy
T
Robert Allan James 422ef2fa29 proof/: all 23 Isabelle theory files now verify under Isabelle2025-2
Isabelle toolchain replaced (was genuinely 2011, 14+ years stale) and every
theory file fixed to actually compile -- most had apparently never been
checked under a working Isabelle at all. Fixed the vm_state self-reference
in StarForth_Base.thy properly (word_table is now a free-standing global
constant, not a circular record field), corrected the word_physics_transparent
axiom (was claiming full state equality from mere exec-equivalence, provably
too strong), and worked through 14 years of HOL-Library drift plus several
missing-hypothesis bugs across the physics-loop and ACL theories.

Two genuine (non-tactical) bugs found and left oops-flagged rather than
silently resolved: forth_roll's index arithmetic disagrees with both its own
test lemma and the real C ROLL implementation (three-way inconsistency), and
pm_wf isn't actually preserved by pm_record_hit/pm_record_miss. Both need a
decision, not a proof-script fix.

Full writeup in FABRIC-2.md item 5.2.
2026-08-13 12:30:30 -04:00

470 lines
18 KiB
Plaintext

theory StarForth_Stack_Words
imports StarForth_Base
begin
(* =========================================================================
POST-01: Stack Manipulation Words
Mirrors: src/word_source/stack_words.c
src/test_runner/modules/stack_words_test.c
======================================================================== *)
(* ── DROP ( n -- ) ──────────────────────────────────────────────────────── *)
definition forth_drop :: "vm_state \<Rightarrow> vm_state" where
"forth_drop vm =
(case data_stack vm of
[] \<Rightarrow> set_error vm
| _ # xs \<Rightarrow> vm\<lparr>data_stack := xs\<rparr>)"
lemma drop_normal:
assumes "data_stack vm = x # xs"
shows "data_stack (forth_drop vm) = xs \<and> vm_error (forth_drop vm) = vm_error vm"
by (simp add: forth_drop_def assms)
lemma drop_depth:
assumes "data_stack vm = x # xs"
shows "length (data_stack (forth_drop vm)) = length (data_stack vm) - 1"
by (simp add: forth_drop_def assms)
lemma drop_underflow:
assumes "data_stack vm = []"
shows "vm_error (forth_drop vm)"
by (simp add: forth_drop_def set_error_def assms)
(* ── DUP ( n -- n n ) ───────────────────────────────────────────────────── *)
definition forth_dup :: "vm_state \<Rightarrow> vm_state" where
"forth_dup vm =
(case data_stack vm of
[] \<Rightarrow> set_error vm
| x # xs \<Rightarrow>
if ds_full vm
then set_error vm
else vm\<lparr>data_stack := x # x # xs\<rparr>)"
lemma dup_normal:
assumes "data_stack vm = x # xs"
assumes "\<not> ds_full vm"
shows "data_stack (forth_dup vm) = x # x # xs"
by (simp add: forth_dup_def assms)
lemma dup_underflow:
assumes "data_stack vm = []"
shows "vm_error (forth_dup vm)"
by (simp add: forth_dup_def set_error_def assms)
lemma dup_overflow:
assumes "data_stack vm = x # xs"
assumes "ds_full vm"
shows "vm_error (forth_dup vm)"
by (simp add: forth_dup_def set_error_def assms)
lemma dup_increases_depth:
assumes "data_stack vm = x # xs"
assumes "\<not> ds_full vm"
shows "length (data_stack (forth_dup vm)) = length (data_stack vm) + 1"
by (simp add: forth_dup_def assms)
(* ── ?DUP ( n -- n n | 0 ) ─────────────────────────────────────────────── *)
(* Duplicates top if non-zero; leaves zero unchanged. *)
definition forth_qdup :: "vm_state \<Rightarrow> vm_state" where
"forth_qdup vm =
(case data_stack vm of
[] \<Rightarrow> set_error vm
| x # xs \<Rightarrow>
if x = 0
then vm
else if ds_full vm
then set_error vm
else vm\<lparr>data_stack := x # x # xs\<rparr>)"
lemma qdup_zero:
assumes "data_stack vm = 0 # xs"
shows "data_stack (forth_qdup vm) = 0 # xs"
by (simp add: forth_qdup_def assms)
lemma qdup_zero_noop:
assumes "data_stack vm = 0 # xs"
shows "forth_qdup vm = vm"
by (simp add: forth_qdup_def assms)
lemma qdup_nonzero:
assumes "data_stack vm = x # xs"
assumes "x \<noteq> 0"
assumes "\<not> ds_full vm"
shows "data_stack (forth_qdup vm) = x # x # xs"
by (simp add: forth_qdup_def assms)
lemma qdup_underflow:
assumes "data_stack vm = []"
shows "vm_error (forth_qdup vm)"
by (simp add: forth_qdup_def set_error_def assms)
lemma qdup_overflow:
assumes "data_stack vm = x # xs"
assumes "x \<noteq> 0"
assumes "ds_full vm"
shows "vm_error (forth_qdup vm)"
by (simp add: forth_qdup_def set_error_def assms)
(* ── SWAP ( n1 n2 -- n2 n1 ) ───────────────────────────────────────────── *)
(* Stack effect (right = TOS): n1=second, n2=TOS.
In list notation (head = TOS): [n2, n1, rest] \<rightarrow> [n1, n2, rest] *)
definition forth_swap :: "vm_state \<Rightarrow> vm_state" where
"forth_swap vm =
(case data_stack vm of
n2 # n1 # rest \<Rightarrow> vm\<lparr>data_stack := n1 # n2 # rest\<rparr>
| _ \<Rightarrow> set_error vm)"
lemma swap_normal:
assumes "data_stack vm = n2 # n1 # rest"
shows "data_stack (forth_swap vm) = n1 # n2 # rest"
by (simp add: forth_swap_def assms)
lemma swap_depth_preserved:
assumes "data_stack vm = n2 # n1 # rest"
shows "length (data_stack (forth_swap vm)) = length (data_stack vm)"
by (simp add: forth_swap_def assms)
lemma swap_involutive:
assumes "data_stack vm = n2 # n1 # rest"
shows "data_stack (forth_swap (forth_swap vm)) = data_stack vm"
by (simp add: forth_swap_def assms)
lemma swap_underflow_nil:
assumes "data_stack vm = []"
shows "vm_error (forth_swap vm)"
by (simp add: forth_swap_def set_error_def assms)
lemma swap_underflow_one:
assumes "data_stack vm = [x]"
shows "vm_error (forth_swap vm)"
by (simp add: forth_swap_def set_error_def assms)
(* ── OVER ( n1 n2 -- n1 n2 n1 ) ───────────────────────────────────────── *)
(* n2=TOS, n1=second. Copies n1 (second) to new TOS.
In list: [n2, n1, rest] \<rightarrow> [n1, n2, n1, rest] *)
definition forth_over :: "vm_state \<Rightarrow> vm_state" where
"forth_over vm =
(case data_stack vm of
n2 # n1 # rest \<Rightarrow>
if ds_full vm
then set_error vm
else vm\<lparr>data_stack := n1 # n2 # n1 # rest\<rparr>
| _ \<Rightarrow> set_error vm)"
lemma over_normal:
assumes "data_stack vm = n2 # n1 # rest"
assumes "\<not> ds_full vm"
shows "data_stack (forth_over vm) = n1 # n2 # n1 # rest"
by (simp add: forth_over_def assms)
lemma over_second_preserved:
assumes "data_stack vm = n2 # n1 # rest"
assumes "\<not> ds_full vm"
shows "hd (data_stack (forth_over vm)) = n1"
by (simp add: forth_over_def assms)
lemma over_underflow_nil:
assumes "data_stack vm = []"
shows "vm_error (forth_over vm)"
by (simp add: forth_over_def set_error_def assms)
lemma over_underflow_one:
assumes "data_stack vm = [x]"
shows "vm_error (forth_over vm)"
by (simp add: forth_over_def set_error_def assms)
lemma over_overflow:
assumes "data_stack vm = n2 # n1 # rest"
assumes "ds_full vm"
shows "vm_error (forth_over vm)"
by (simp add: forth_over_def set_error_def assms)
(* ── ROT ( n1 n2 n3 -- n2 n3 n1 ) ─────────────────────────────────────── *)
(* n3=TOS, n2=second, n1=third. Brings n1 (third) to TOS.
In list: [n3, n2, n1, rest] \<rightarrow> [n1, n3, n2, rest]
Verified against C:
n3 = data_stack[dsp] (TOS)
n2 = data_stack[dsp-1] (second)
n1 = data_stack[dsp-2] (third)
data_stack[dsp] = n1 (new TOS)
data_stack[dsp-1] = n3
data_stack[dsp-2] = n2 *)
definition forth_rot :: "vm_state \<Rightarrow> vm_state" where
"forth_rot vm =
(case data_stack vm of
n3 # n2 # n1 # rest \<Rightarrow> vm\<lparr>data_stack := n1 # n3 # n2 # rest\<rparr>
| _ \<Rightarrow> set_error vm)"
lemma rot_normal:
assumes "data_stack vm = n3 # n2 # n1 # rest"
shows "data_stack (forth_rot vm) = n1 # n3 # n2 # rest"
by (simp add: forth_rot_def assms)
lemma rot_depth_preserved:
assumes "data_stack vm = n3 # n2 # n1 # rest"
shows "length (data_stack (forth_rot vm)) = length (data_stack vm)"
by (simp add: forth_rot_def assms)
lemma rot_underflow_nil:
assumes "data_stack vm = []"
shows "vm_error (forth_rot vm)"
by (simp add: forth_rot_def set_error_def assms)
lemma rot_underflow_one:
assumes "data_stack vm = [x]"
shows "vm_error (forth_rot vm)"
by (simp add: forth_rot_def set_error_def assms)
lemma rot_underflow_two:
assumes "data_stack vm = [x, y]"
shows "vm_error (forth_rot vm)"
by (simp add: forth_rot_def set_error_def assms)
(* ── -ROT ( n1 n2 n3 -- n3 n1 n2 ) ─────────────────────────────────────── *)
(* Reverse rotation: brings TOS (n3) to third position.
In list: [n3, n2, n1, rest] \<rightarrow> [n2, n1, n3, rest]
Verified against C:
data_stack[dsp] = n2 (new TOS)
data_stack[dsp-1] = n1
data_stack[dsp-2] = n3 *)
definition forth_nrot :: "vm_state \<Rightarrow> vm_state" where
"forth_nrot vm =
(case data_stack vm of
n3 # n2 # n1 # rest \<Rightarrow> vm\<lparr>data_stack := n2 # n1 # n3 # rest\<rparr>
| _ \<Rightarrow> set_error vm)"
lemma nrot_normal:
assumes "data_stack vm = n3 # n2 # n1 # rest"
shows "data_stack (forth_nrot vm) = n2 # n1 # n3 # rest"
by (simp add: forth_nrot_def assms)
lemma nrot_depth_preserved:
assumes "data_stack vm = n3 # n2 # n1 # rest"
shows "length (data_stack (forth_nrot vm)) = length (data_stack vm)"
by (simp add: forth_nrot_def assms)
(* ROT and -ROT are mutual inverses on the data stack. *)
lemma rot_nrot_inverse:
assumes "data_stack vm = n3 # n2 # n1 # rest"
shows "data_stack (forth_nrot (forth_rot vm)) = data_stack vm"
by (simp add: forth_nrot_def forth_rot_def assms)
lemma nrot_rot_inverse:
assumes "data_stack vm = n3 # n2 # n1 # rest"
shows "data_stack (forth_rot (forth_nrot vm)) = data_stack vm"
by (simp add: forth_nrot_def forth_rot_def assms)
lemma nrot_underflow_nil:
assumes "data_stack vm = []"
shows "vm_error (forth_nrot vm)"
by (simp add: forth_nrot_def set_error_def assms)
lemma nrot_underflow_one:
assumes "data_stack vm = [x]"
shows "vm_error (forth_nrot vm)"
by (simp add: forth_nrot_def set_error_def assms)
lemma nrot_underflow_two:
assumes "data_stack vm = [x, y]"
shows "vm_error (forth_nrot vm)"
by (simp add: forth_nrot_def set_error_def assms)
(* ── DROP \<circ> DUP = identity ─────────────────────────────────────────────── *)
lemma dup_then_drop:
assumes "data_stack vm = x # xs"
assumes "\<not> ds_full vm"
assumes "\<not> vm_error vm"
shows "data_stack (forth_drop (forth_dup vm)) = data_stack vm"
by (simp add: forth_dup_def forth_drop_def assms)
(* ── DEPTH ( -- n ) ─────────────────────────────────────────────────────── *)
(* Pushes the current stack depth (number of items before DEPTH executes).
C impl: depth = dsp + 1; data_stack[++dsp] = depth. *)
definition forth_depth :: "vm_state \<Rightarrow> vm_state" where
"forth_depth vm =
(if ds_full vm
then set_error vm
else vm\<lparr>data_stack := int (length (data_stack vm)) # data_stack vm\<rparr>)"
lemma depth_pushes_count:
assumes "\<not> ds_full vm"
shows "hd (data_stack (forth_depth vm)) = int (length (data_stack vm))"
by (simp add: forth_depth_def assms)
lemma depth_rest_preserved:
assumes "\<not> ds_full vm"
shows "tl (data_stack (forth_depth vm)) = data_stack vm"
by (simp add: forth_depth_def assms)
lemma depth_increases_by_one:
assumes "\<not> ds_full vm"
shows "length (data_stack (forth_depth vm)) = length (data_stack vm) + 1"
by (simp add: forth_depth_def assms)
lemma depth_overflow:
assumes "ds_full vm"
shows "vm_error (forth_depth vm)"
by (simp add: forth_depth_def set_error_def assms)
(* ── PICK ( n -- x ) ───────────────────────────────────────────────────── *)
(* Replaces TOS (n) with the element at index n in the current stack.
Index 0 = TOS itself; index 1 = second item; etc.
In-place replacement — depth unchanged.
Verified against C:
n = data_stack[dsp] (TOS, not popped)
val = data_stack[dsp - n]
data_stack[dsp] = val (replace TOS with val)
Bounds check: n \<ge> 0 and n < length (data_stack vm) *)
definition forth_pick :: "vm_state \<Rightarrow> vm_state" where
"forth_pick vm =
(case data_stack vm of
[] \<Rightarrow> set_error vm
| n # xs \<Rightarrow>
if n < 0 \<or> nat n \<ge> length (data_stack vm)
then set_error vm
else vm\<lparr>data_stack := data_stack vm ! nat n # xs\<rparr>)"
lemma pick_normal:
assumes "data_stack vm = n # xs"
assumes "n \<ge> 0"
assumes "nat n < length (data_stack vm)"
shows "data_stack (forth_pick vm) = data_stack vm ! nat n # xs"
using assms by (auto simp: forth_pick_def)
lemma pick_depth_unchanged:
assumes "data_stack vm = n # xs"
assumes "n \<ge> 0"
assumes "nat n < length (data_stack vm)"
shows "length (data_stack (forth_pick vm)) = length (data_stack vm)"
by (simp add: forth_pick_def assms)
(* 0 PICK: replaces TOS (which is 0) with data_stack[0] = 0 — identity. *)
lemma pick_zero_self:
assumes "data_stack vm = 0 # xs"
assumes "\<not> ds_full vm"
shows "data_stack (forth_pick vm) = 0 # xs"
by (simp add: forth_pick_def assms)
(* 1 PICK: replaces TOS (1) with the element at index 1 = hd xs. *)
lemma pick_one:
assumes "data_stack vm = 1 # x # xs"
shows "data_stack (forth_pick vm) = x # x # xs"
by (simp add: forth_pick_def assms)
lemma pick_underflow:
assumes "data_stack vm = []"
shows "vm_error (forth_pick vm)"
by (simp add: forth_pick_def set_error_def assms)
lemma pick_bounds_neg:
assumes "data_stack vm = n # xs"
assumes "n < 0"
shows "vm_error (forth_pick vm)"
by (simp add: forth_pick_def set_error_def assms)
lemma pick_bounds_high:
assumes "data_stack vm = n # xs"
assumes "nat n \<ge> length (data_stack vm)"
shows "vm_error (forth_pick vm)"
using assms by (auto simp: forth_pick_def set_error_def)
(* ── ROLL ( +n -- ) ─────────────────────────────────────────────────────── *)
(* Pops n, then rotates items.
Special cases in C implementation:
n = 0: pop n, return (stack depth decreases by 1, TOS unchanged)
n = 1: pop n, return ("top item already at top")
n \<ge> 2: save item at index (depth - n) from bottom (= dsp+1-n after pop),
shift items down to fill gap, place saved item on top.
Net effect on depth: decreases by 1 (n is consumed, one item moved).
Index convention after popping n (dsp' = dsp - 1):
value = data_stack[dsp'+1-n] = data_stack[dsp-n]
Items at indices (dsp-n)..(dsp-1) shift down by 1.
Saved value placed at data_stack[dsp].
Final dsp unchanged (= dsp-1 after pop, but top slot reused). *)
definition forth_roll :: "vm_state \<Rightarrow> vm_state" where
"forth_roll vm =
(case data_stack vm of
[] \<Rightarrow> set_error vm
| n # xs \<Rightarrow>
if n < 0 \<or> nat n > length xs
then set_error vm
else if n = 0 \<or> n = 1
then vm\<lparr>data_stack := xs\<rparr>
else let i = nat n;
item = xs ! (i - 1);
rest = take (i - 1) xs @ drop i xs
in vm\<lparr>data_stack := item # rest\<rparr>)"
lemma roll_zero_nop:
assumes "data_stack vm = 0 # xs"
shows "data_stack (forth_roll vm) = xs"
by (simp add: forth_roll_def assms)
(* CORRECTED 2026-08-13: added the missing "xs non-empty" hypothesis. For
n=1 with xs=[], forth_roll_def's own guard (nat n > length xs, i.e.
1 > 0) fires BEFORE the n=0\<or>n=1 shortcut is reached, giving set_error
instead of the identity -- so the claim is false for xs=[]. *)
lemma roll_one_nop:
assumes "data_stack vm = 1 # xs"
assumes "xs \<noteq> []"
shows "data_stack (forth_roll vm) = xs"
using assms by (auto simp: forth_roll_def)
(* FLAGGED, NOT FIXED 2026-08-13: this lemma does not hold against
forth_roll_def as currently written. Tracing the definition by hand for
this exact instantiation (xs = n3#n2#n1#rest after popping the leading
2) gives item = xs!(i-1) = xs!1 = n2, rest' = take 1 xs @ drop 2 xs =
n3#n1#rest, so forth_roll actually produces n2#n3#n1#rest -- not
n1#n3#n2#rest as this lemma (and its own "2 ROLL is equivalent to ROT"
comment) claims. Separately, the real C stack_word_roll
(src/word_source/stack_words.c:287-320) uses yet a THIRD convention of
its own, explicitly commented "1-indexed from bottom: n=1 moves bottom
item to top" -- neither this definition nor this lemma's expectation
matches it. Three mutually inconsistent conventions (this definition,
this lemma, and the C code) -- not a proof-script issue, a genuine
unresolved semantic question about what ROLL is supposed to do. This
theory's own header asserts the theory is ground truth and the C code
must match it, but the theory does not even agree with itself here, so
that framing does not resolve which side (if either) is correct.
Left failing rather than silently reconciled either direction. *)
lemma roll_two_is_rot:
assumes "data_stack vm = 2 # n3 # n2 # n1 # rest"
shows "data_stack (forth_roll vm) = n1 # n3 # n2 # rest"
oops
lemma roll_underflow:
assumes "data_stack vm = []"
shows "vm_error (forth_roll vm)"
by (simp add: forth_roll_def set_error_def assms)
lemma roll_bounds_neg:
assumes "data_stack vm = n # xs"
assumes "n < 0"
shows "vm_error (forth_roll vm)"
by (simp add: forth_roll_def set_error_def assms)
lemma roll_bounds_high:
assumes "data_stack vm = n # xs"
assumes "nat n > length xs"
shows "vm_error (forth_roll vm)"
by (simp add: forth_roll_def set_error_def assms)
end