theory StarForth_Double_Words imports StarForth_Base begin (* ========================================================================= POST-XX: Double-Cell Stack Manipulation Words Mirrors: src/word_source/double_words.c (partial -- see SCOPE below) src/test_runner/modules/double_words_test.c SCOPE, decided 2026-08-13: double_words.c has 20 registered words in three categories. Only the first is modeled here: 1. Pure double-cell DATA-STACK shuffles (2DROP, 2DUP, 2SWAP, 2OVER, 2ROT) -- modeled below. These only rearrange data_stack list elements; nothing about their correctness depends on cell's exact representation or on any vm_state field this theory doesn't already track. 2. Double-cell RETURN-STACK transfer (2>R, 2R>, 2R@) -- NOT modeled. The real C (double_words.c:330-382) branches on "vm->ecw_nesting > 0" (execute_colon_word re-entrancy depth), a VM field that does not exist anywhere in StarForth_Base.thy's vm_state record. Modeling these words correctly requires adding ecw_nesting to vm_state first -- a model-extension decision, not something to add as a side effect of this file. 3. Double-precision ARITHMETIC (S>D, D+, D-, DNEGATE, DABS, DMAX, DMIN, D<, D=, D0=, D0<, D2*, D2/) -- NOT modeled. The real C implementations (double_words.c:56-250ish) depend on cell_t being a fixed-width (64-bit, "signed long") wrapping integer type -- D+/D- explicitly do carry/borrow detection via "unsigned long" arithmetic and wraparound, DNEGATE uses bitwise complement (~) with wraparound. This theory's "type_synonym cell = int" (StarForth_Base) is unbounded/arbitrary-precision, not a fixed-width wrapping type, so none of this carry/wraparound reasoning is even expressible as currently modeled. Correctly formalizing these words needs a foundational decision -- change cell's representation to a 64-bit word type (matching StarForth_Q48_16.thy's own "64 word" approach) or add explicit range side-conditions everywhere -- that ripples into all 23 already-verified theories built on today's plain-int cell. Not decided or attempted here; flagged for a dedicated pass. Every word modeled below is a direct structural analog of an already-covered single-cell word in StarForth_Stack_Words.thy (DUP, SWAP, OVER, ROT) or StarForth_Return_Stack_Words.thy, just operating on cell PAIRS instead of single cells. Convention throughout, matching the C comments: a double d is pushed as (dlow dhigh) with dhigh on top -- i.e. in this list's head=TOS convention, a double at the top of stack is "dhigh # dlow # rest". None of the C implementations below check ds_full before pushing (confirmed by reading double_words.c in full) -- not modeled here either, to stay faithful to the real source rather than inventing a guard that doesn't exist. ======================================================================== *) (* ── 2DROP ( d -- ) ─────────────────────────────────────────────────────── *) (* src/word_source/double_words.c:250-258. Guard: dsp \ 1 (at least two cells present). *) definition forth_2drop :: "vm_state \ vm_state" where "forth_2drop vm = (case data_stack vm of _ # _ # rest \ vm\data_stack := rest\ | _ \ set_error vm)" lemma two_drop_normal: assumes "data_stack vm = dhigh # dlow # rest" shows "data_stack (forth_2drop vm) = rest" by (simp add: forth_2drop_def assms) lemma two_drop_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_2drop vm)" by (simp add: forth_2drop_def set_error_def assms) lemma two_drop_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (forth_2drop vm)" by (simp add: forth_2drop_def set_error_def assms) (* ── 2DUP ( d -- d d ) ──────────────────────────────────────────────────── *) (* src/word_source/double_words.c:260-272. Guard: dsp \ 1. *) definition forth_2dup :: "vm_state \ vm_state" where "forth_2dup vm = (case data_stack vm of dhigh # dlow # rest \ vm\data_stack := dhigh # dlow # dhigh # dlow # rest\ | _ \ set_error vm)" lemma two_dup_normal: assumes "data_stack vm = dhigh # dlow # rest" shows "data_stack (forth_2dup vm) = dhigh # dlow # dhigh # dlow # rest" by (simp add: forth_2dup_def assms) lemma two_dup_depth: assumes "data_stack vm = dhigh # dlow # rest" shows "length (data_stack (forth_2dup vm)) = length (data_stack vm) + 2" by (simp add: forth_2dup_def assms) lemma two_dup_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_2dup vm)" by (simp add: forth_2dup_def set_error_def assms) lemma two_dup_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (forth_2dup vm)" by (simp add: forth_2dup_def set_error_def assms) (* ── 2SWAP ( d1 d2 -- d2 d1 ) ───────────────────────────────────────────── *) (* src/word_source/double_words.c:274-290. Guard: dsp \ 3 (four cells). *) definition forth_2swap :: "vm_state \ vm_state" where "forth_2swap vm = (case data_stack vm of d2high # d2low # d1high # d1low # rest \ vm\data_stack := d1high # d1low # d2high # d2low # rest\ | _ \ set_error vm)" lemma two_swap_normal: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" shows "data_stack (forth_2swap vm) = d1high # d1low # d2high # d2low # rest" by (simp add: forth_2swap_def assms) lemma two_swap_depth_preserved: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" shows "length (data_stack (forth_2swap vm)) = length (data_stack vm)" by (simp add: forth_2swap_def assms) (* 2SWAP is its own inverse. *) lemma two_swap_involutive: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "data_stack vm' = data_stack (forth_2swap vm)" shows "data_stack (forth_2swap vm') = data_stack vm" using assms by (simp add: forth_2swap_def) lemma two_swap_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_2swap vm)" by (simp add: forth_2swap_def set_error_def assms) lemma two_swap_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (forth_2swap vm)" by (simp add: forth_2swap_def set_error_def assms) lemma two_swap_underflow_two: assumes "data_stack vm = [x, y]" shows "vm_error (forth_2swap vm)" by (simp add: forth_2swap_def set_error_def assms) lemma two_swap_underflow_three: assumes "data_stack vm = [x, y, z]" shows "vm_error (forth_2swap vm)" by (simp add: forth_2swap_def set_error_def assms) (* ── 2OVER ( d1 d2 -- d1 d2 d1 ) ────────────────────────────────────────── *) (* src/word_source/double_words.c:292-305. Guard: dsp \ 3. *) definition forth_2over :: "vm_state \ vm_state" where "forth_2over vm = (case data_stack vm of d2high # d2low # d1high # d1low # rest \ vm\data_stack := d1high # d1low # d2high # d2low # d1high # d1low # rest\ | _ \ set_error vm)" lemma two_over_normal: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" shows "data_stack (forth_2over vm) = d1high # d1low # d2high # d2low # d1high # d1low # rest" by (simp add: forth_2over_def assms) lemma two_over_depth: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" shows "length (data_stack (forth_2over vm)) = length (data_stack vm) + 2" by (simp add: forth_2over_def assms) (* ── 2ROT ( d1 d2 d3 -- d2 d3 d1 ) ──────────────────────────────────────── *) (* src/word_source/double_words.c:307-327. Guard: dsp \ 5 (six cells). Hand-verified against the doc comment's own semantics: d1 (deepest) moves to the top, d2 and d3 shift down keeping their relative order. *) definition forth_2rot :: "vm_state \ vm_state" where "forth_2rot vm = (case data_stack vm of d3high # d3low # d2high # d2low # d1high # d1low # rest \ vm\data_stack := d1high # d1low # d3high # d3low # d2high # d2low # rest\ | _ \ set_error vm)" lemma two_rot_normal: assumes "data_stack vm = d3high # d3low # d2high # d2low # d1high # d1low # rest" shows "data_stack (forth_2rot vm) = d1high # d1low # d3high # d3low # d2high # d2low # rest" by (simp add: forth_2rot_def assms) lemma two_rot_depth_preserved: assumes "data_stack vm = d3high # d3low # d2high # d2low # d1high # d1low # rest" shows "length (data_stack (forth_2rot vm)) = length (data_stack vm)" by (simp add: forth_2rot_def assms) end