theory StarForth_Double_Words imports StarForth_Base begin unbundle bit_operations_syntax (* ========================================================================= POST-XX: Double-Cell Words Mirrors: src/word_source/double_words.c (all 20 registered words) src/test_runner/modules/double_words_test.c SCOPE, decided 2026-08-13, REVISED 2026-08-13 (later same day, resumed session): double_words.c has 20 registered words in three categories. All three are now modeled: 1. Pure double-cell DATA-STACK shuffles (2DROP, 2DUP, 2SWAP, 2OVER, 2ROT) -- unchanged from the original pass. 2. Double-cell RETURN-STACK transfer (2>R, 2R>, 2R@) -- NOW MODELED. The blocker recorded earlier (`vm->ecw_nesting` missing from vm_state) is resolved: `ecw_nesting :: nat` added to the vm_state record in StarForth_Base.thy, immediately after vm_halted. 3. Double-precision ARITHMETIC (S>D, D+, D-, DNEGATE, DABS, DMAX, DMIN, D<, D=, D0=, D0<, D2*, D2/) -- NOW MODELED. The blocker recorded earlier ("cell is unbounded int, not a fixed-width wrapping type") was STALE when re-checked this session: `cell` was migrated from `int` to `"64 word"` in commit fe6e705, well before this file was first touched -- the earlier note simply wasn't re-verified against current StarForth_Base.thy before being written down. Flagging this as a process lesson, not just a code fix: carried-forward reasoning about *why* something is out of scope needs re-checking against the current file, not just trusted because a previous session wrote it down (same lesson the control_words.c "vm_ip as raw pointer" note taught one file earlier in this same sweep). With cell now a proper wrapping 64-bit word, HOL word `+`/`-` already compute mod 2^64 exactly matching C's unsigned-long carry/borrow arithmetic and two's-complement wraparound bit-for-bit -- no foundational change needed, just transcription. 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) (* ── S>D ( n -- d ) : single to double ─────────────────────────────────── *) (* C: dsp<0 -> error; dhigh = (n<0) ? -1 : 0; push n (low), push dhigh. *) definition forth_s_to_d :: "vm_state \ vm_state" where "forth_s_to_d vm = (case data_stack vm of [] \ set_error vm | n # rest \ vm\data_stack := (if n )" lemma s_to_d_underflow: assumes "data_stack vm = []" shows "vm_error (forth_s_to_d vm)" by (simp add: forth_s_to_d_def set_error_def assms) lemma s_to_d_negative: assumes "data_stack vm = n # rest" assumes "n n error. Pops d2high,d2low,d1high,d1low. Carry detected via unsigned comparison; word `+` already computes mod 2^64, so it's the direct transcription of the C unsigned-long addition with no reinterpretation needed. *) definition forth_d_plus :: "vm_state \ vm_state" where "forth_d_plus vm = (case data_stack vm of d2high # d2low # d1high # d1low # rest \ let result_low = d1low + d2low; carry = (if result_low < d1low then 1 else 0); result_high = d1high + d2high + carry in vm\data_stack := result_high # result_low # rest\ | _ \ set_error vm)" lemma d_plus_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_d_plus vm)" by (simp add: forth_d_plus_def set_error_def assms) lemma d_plus_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (forth_d_plus vm)" by (simp add: forth_d_plus_def set_error_def assms) lemma d_plus_underflow_two: assumes "data_stack vm = [x, y]" shows "vm_error (forth_d_plus vm)" by (simp add: forth_d_plus_def set_error_def assms) lemma d_plus_underflow_three: assumes "data_stack vm = [x, y, z]" shows "vm_error (forth_d_plus vm)" by (simp add: forth_d_plus_def set_error_def assms) lemma d_plus_normal_no_carry: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "\ (d1low + d2low) < d1low" shows "data_stack (forth_d_plus vm) = (d1high + d2high) # (d1low + d2low) # rest" using assms by (simp add: forth_d_plus_def) lemma d_plus_normal_with_carry: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "(d1low + d2low) < d1low" shows "data_stack (forth_d_plus vm) = (d1high + d2high + 1) # (d1low + d2low) # rest" using assms by (simp add: forth_d_plus_def) (* ── D- ( d1 d2 -- d3 ) ────────────────────────────────────────────────── *) definition forth_d_minus :: "vm_state \ vm_state" where "forth_d_minus vm = (case data_stack vm of d2high # d2low # d1high # d1low # rest \ let result_low = d1low - d2low; borrow = (if d1low < d2low then 1 else 0); result_high = d1high - d2high - borrow in vm\data_stack := result_high # result_low # rest\ | _ \ set_error vm)" lemma d_minus_normal_no_borrow: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "\ d1low < d2low" shows "data_stack (forth_d_minus vm) = (d1high - d2high) # (d1low - d2low) # rest" using assms by (simp add: forth_d_minus_def) lemma d_minus_normal_with_borrow: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "d1low < d2low" shows "data_stack (forth_d_minus vm) = (d1high - d2high - 1) # (d1low - d2low) # rest" using assms by (simp add: forth_d_minus_def) (* ── DNEGATE ( d1 -- d2 ) ──────────────────────────────────────────────── *) (* C: ~dlow + 1, ~dhigh + (new_low==0 ? 1 : 0) -- textbook 128-bit two's complement negation, transcribed literally rather than via HOL's `-` (which would be equal but less faithful to the C bit-operations). *) definition forth_dnegate :: "vm_state \ vm_state" where "forth_dnegate vm = (case data_stack vm of dhigh # dlow # rest \ let new_low = NOT dlow + 1; new_high = NOT dhigh + (if new_low = 0 then 1 else 0) in vm\data_stack := new_high # new_low # rest\ | _ \ set_error vm)" lemma dnegate_underflow: assumes "data_stack vm = [] \ (\x. data_stack vm = [x])" shows "vm_error (forth_dnegate vm)" using assms by (auto simp: forth_dnegate_def set_error_def) lemma dnegate_normal: assumes "data_stack vm = dhigh # dlow # rest" shows "data_stack (forth_dnegate vm) = (NOT dhigh + (if NOT dlow + 1 = 0 then 1 else 0)) # (NOT dlow + 1) # rest" by (simp add: forth_dnegate_def assms) (* Double negation (DNEGATE twice = identity) is a true property of 128-bit two's complement, but proving it here needs the same carry/borrow-across- the-pair algebra as D+/D-'s case-split lemmas, not just simp -- left unproved as a nice-to-have rather than forcing it, consistent with this sweep's "plumbing lemmas only" scope. *) (* ── DABS ( d1 -- d2 ) ─────────────────────────────────────────────────── *) (* C peeks dhigh (TOS) WITHOUT popping; if negative, calls DNEGATE (which does its own pop/negate/push using the still-untouched stack); else the word is a complete no-op. Reuses forth_dnegate directly. *) definition forth_dabs :: "vm_state \ vm_state" where "forth_dabs vm = (case data_stack vm of dhigh # dlow # rest \ (if dhigh set_error vm)" lemma dabs_underflow: assumes "data_stack vm = [] \ (\x. data_stack vm = [x])" shows "vm_error (forth_dabs vm)" using assms by (auto simp: forth_dabs_def set_error_def) lemma dabs_already_nonneg_is_noop: assumes "data_stack vm = dhigh # dlow # rest" assumes "\ dhigh cell \ cell \ cell \ int" where "d_compare d1h d1l d2h d2l = (if d1h vm_state" where "forth_dmax vm = (case data_stack vm of d2high # d2low # d1high # d1low # rest \ vm\data_stack := (if d_compare d1high d1low d2high d2low \ 0 then d1high # d1low # rest else d2high # d2low # rest)\ | _ \ set_error vm)" lemma dmax_picks_d1: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "d_compare d1high d1low d2high d2low \ 0" shows "data_stack (forth_dmax vm) = d1high # d1low # rest" using assms by (simp add: forth_dmax_def) lemma dmax_picks_d2: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "\ d_compare d1high d1low d2high d2low \ 0" shows "data_stack (forth_dmax vm) = d2high # d2low # rest" using assms by (simp add: forth_dmax_def) definition forth_dmin :: "vm_state \ vm_state" where "forth_dmin vm = (case data_stack vm of d2high # d2low # d1high # d1low # rest \ vm\data_stack := (if d_compare d1high d1low d2high d2low \ 0 then d1high # d1low # rest else d2high # d2low # rest)\ | _ \ set_error vm)" lemma dmin_picks_d1: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "d_compare d1high d1low d2high d2low \ 0" shows "data_stack (forth_dmin vm) = d1high # d1low # rest" using assms by (simp add: forth_dmin_def) lemma dmin_picks_d2: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "\ d_compare d1high d1low d2high d2low \ 0" shows "data_stack (forth_dmin vm) = d2high # d2low # rest" using assms by (simp add: forth_dmin_def) (* ── D< ( d1 d2 -- flag ) / D= ( d1 d2 -- flag ) ────────────────────────── *) definition forth_d_less :: "vm_state \ vm_state" where "forth_d_less vm = (case data_stack vm of d2high # d2low # d1high # d1low # rest \ vm\data_stack := to_forth_bool (d_compare d1high d1low d2high d2low < 0) # rest\ | _ \ set_error vm)" lemma d_less_true: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "d_compare d1high d1low d2high d2low < 0" shows "data_stack (forth_d_less vm) = forth_true # rest" using assms by (simp add: forth_d_less_def to_forth_bool_def) lemma d_less_false: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "\ d_compare d1high d1low d2high d2low < 0" shows "data_stack (forth_d_less vm) = forth_false # rest" using assms by (simp add: forth_d_less_def to_forth_bool_def) definition forth_d_equals :: "vm_state \ vm_state" where "forth_d_equals vm = (case data_stack vm of d2high # d2low # d1high # d1low # rest \ vm\data_stack := to_forth_bool (d_compare d1high d1low d2high d2low = 0) # rest\ | _ \ set_error vm)" lemma d_equals_true: assumes "data_stack vm = d2high # d2low # d1high # d1low # rest" assumes "d_compare d1high d1low d2high d2low = 0" shows "data_stack (forth_d_equals vm) = forth_true # rest" using assms by (simp add: forth_d_equals_def to_forth_bool_def) lemma d_equals_reflexive: assumes "data_stack vm = h # l # h # l # rest" shows "data_stack (forth_d_equals vm) = forth_true # rest" using assms by (simp add: forth_d_equals_def to_forth_bool_def d_compare_def) (* ── D0= ( d -- flag ) / D0< ( d -- flag ) ────────────────────────────── *) definition forth_d_zero_equals :: "vm_state \ vm_state" where "forth_d_zero_equals vm = (case data_stack vm of dhigh # dlow # rest \ vm\data_stack := to_forth_bool (dhigh = 0 \ dlow = 0) # rest\ | _ \ set_error vm)" lemma d_zero_equals_true: assumes "data_stack vm = 0 # 0 # rest" shows "data_stack (forth_d_zero_equals vm) = forth_true # rest" by (simp add: forth_d_zero_equals_def to_forth_bool_def assms) lemma d_zero_equals_false: assumes "data_stack vm = dhigh # dlow # rest" assumes "dhigh \ 0 \ dlow \ 0" shows "data_stack (forth_d_zero_equals vm) = forth_false # rest" using assms by (auto simp: forth_d_zero_equals_def to_forth_bool_def) definition forth_d_zero_less :: "vm_state \ vm_state" where "forth_d_zero_less vm = (case data_stack vm of dhigh # dlow # rest \ vm\data_stack := to_forth_bool (dhigh | _ \ set_error vm)" lemma d_zero_less_true: assumes "data_stack vm = dhigh # dlow # rest" assumes "dhigh dhigh vm_state" where "forth_d_two_star vm = (case data_stack vm of dhigh # dlow # rest \ let new_dlow = push_bit 1 dlow; carry = of_bool (bit dlow 63); new_dhigh = push_bit 1 dhigh OR carry in vm\data_stack := new_dhigh # new_dlow # rest\ | _ \ set_error vm)" lemma d_two_star_normal: assumes "data_stack vm = dhigh # dlow # rest" shows "data_stack (forth_d_two_star vm) = (push_bit 1 dhigh OR of_bool (bit dlow 63)) # push_bit 1 dlow # rest" by (simp add: forth_d_two_star_def assms) (* ── D2/ ( d1 -- d2 ) : double-cell arithmetic right shift by 1 ────────── C: new_dhigh = dhigh >> 1 (arithmetic, sign-preserving); new_dlow = (dlow >> 1 unsigned) with dhigh's bit 0 carried into dlow's bit 63. Arithmetic shift right by 1 on two's complement = floor division by 2, which is exactly HOL int `div` (rounds toward -infinity, NOT C99 truncating division) -- reusing the sint round-trip already established for signed reasoning elsewhere in this suite, deliberately NOT cell_sdiv (which is C99 truncating division and would be wrong here for negative dhigh). *) definition forth_d_two_slash :: "vm_state \ vm_state" where "forth_d_two_slash vm = (case data_stack vm of dhigh # dlow # rest \ let new_dhigh = word_of_int (sint dhigh div 2); new_dlow = drop_bit 1 dlow OR (if bit dhigh 0 then push_bit 63 1 else 0) in vm\data_stack := new_dhigh # new_dlow # rest\ | _ \ set_error vm)" lemma d_two_slash_normal: assumes "data_stack vm = dhigh # dlow # rest" shows "data_stack (forth_d_two_slash vm) = word_of_int (sint dhigh div 2) # (drop_bit 1 dlow OR (if bit dhigh 0 then push_bit 63 1 else 0)) # rest" by (simp add: forth_d_two_slash_def assms) (* ── 2>R ( d -- ) ( R: -- d ) ──────────────────────────────────────────── *) (* C: dsp<1 -> error (checked, then popped, BEFORE the ecw_nesting branch -- so on a subsequent rsp overflow error, data_stack is already popped, matching ALLOT's pop-then-check pattern). Inside a colon word (ecw_nesting>0): insert dlow,dhigh BELOW the resume-ip that's already on top of the return stack, keeping resume-ip on top. Outside (ecw_nesting = 0, e.g. interactively at the REPL): plain push of dlow then dhigh. ecw_nesting>0 with an EMPTY return_stack is an invariant violation the real C never defensively checks (it would read return_stack[-1], i.e. real UB) -- modeled here as set_error as a defensive default, which is NOT what the C does (the C has no such check at all); flagged as a finding, not a faithful transcription of that one unreachable case. *) definition forth_2to_r :: "vm_state \ vm_state" where "forth_2to_r vm = (case data_stack vm of [] \ set_error vm | dhigh # dlow # drest \ let vm1 = vm\data_stack := drest\ in if ecw_nesting vm > 0 then (case return_stack vm of [] \ set_error vm1 \ \invariant violation, see above\ | resume_ip # rst \ if length rst + 2 \ STACK_SIZE then set_error vm1 else vm1\return_stack := resume_ip # dhigh # dlow # rst\) else if length (return_stack vm) + 2 \ STACK_SIZE then set_error vm1 else vm1\return_stack := dhigh # dlow # return_stack vm\ | _ \ set_error vm)" lemma two_to_r_underflow: assumes "data_stack vm = [] \ (\x. data_stack vm = [x])" shows "vm_error (forth_2to_r vm)" using assms by (auto simp: forth_2to_r_def set_error_def) lemma two_to_r_outside_colon: assumes "data_stack vm = dhigh # dlow # drest" assumes "ecw_nesting vm = 0" assumes "length (return_stack vm) + 2 < STACK_SIZE" shows "return_stack (forth_2to_r vm) = dhigh # dlow # return_stack vm" and "data_stack (forth_2to_r vm) = drest" using assms by (simp_all add: forth_2to_r_def) lemma two_to_r_inside_colon: assumes "data_stack vm = dhigh # dlow # drest" assumes "ecw_nesting vm > 0" assumes "return_stack vm = resume_ip # rst" assumes "length rst + 2 < STACK_SIZE" shows "return_stack (forth_2to_r vm) = resume_ip # dhigh # dlow # rst" and "data_stack (forth_2to_r vm) = drest" using assms by (simp_all add: forth_2to_r_def) (* ── 2R> ( -- d ) ( R: d -- ) ──────────────────────────────────────────── *) (* Inside a colon word: pops (dlow,dhigh) from just below the resume-ip, keeping resume-ip on top. Outside: plain pop of dhigh then dlow. Both branches push the same result to the data stack: dhigh # dlow # ... *) definition forth_2r_from :: "vm_state \ vm_state" where "forth_2r_from vm = (if ecw_nesting vm > 0 then (case return_stack vm of resume_ip # dhigh # dlow # rst \ vm\data_stack := dhigh # dlow # data_stack vm, return_stack := resume_ip # rst\ | _ \ set_error vm) else (case return_stack vm of dhigh # dlow # rst \ vm\data_stack := dhigh # dlow # data_stack vm, return_stack := rst\ | _ \ set_error vm))" lemma two_r_from_inside_colon: assumes "ecw_nesting vm > 0" assumes "return_stack vm = resume_ip # dhigh # dlow # rst" shows "data_stack (forth_2r_from vm) = dhigh # dlow # data_stack vm" and "return_stack (forth_2r_from vm) = resume_ip # rst" using assms by (simp_all add: forth_2r_from_def) lemma two_r_from_outside_colon: assumes "ecw_nesting vm = 0" assumes "return_stack vm = dhigh # dlow # rst" shows "data_stack (forth_2r_from vm) = dhigh # dlow # data_stack vm" and "return_stack (forth_2r_from vm) = rst" using assms by (simp_all add: forth_2r_from_def) lemma two_r_from_inside_colon_underflow_nil: assumes "ecw_nesting vm > 0" assumes "return_stack vm = []" shows "vm_error (forth_2r_from vm)" by (simp add: forth_2r_from_def set_error_def assms) lemma two_r_from_inside_colon_underflow_one: assumes "ecw_nesting vm > 0" assumes "return_stack vm = [x]" shows "vm_error (forth_2r_from vm)" by (simp add: forth_2r_from_def set_error_def assms) lemma two_r_from_inside_colon_underflow_two: assumes "ecw_nesting vm > 0" assumes "return_stack vm = [x, y]" shows "vm_error (forth_2r_from vm)" by (simp add: forth_2r_from_def set_error_def assms) (* ── 2R@ ( -- d ) ( R: d -- d ) ────────────────────────────────────────── *) (* Same source cells as 2R>, but the return stack is left untouched (fetch, not pop) -- both branches produce the identical data_stack result as 2R>. *) definition forth_2r_fetch :: "vm_state \ vm_state" where "forth_2r_fetch vm = (if ecw_nesting vm > 0 then (case return_stack vm of resume_ip # dhigh # dlow # rst \ vm\data_stack := dhigh # dlow # data_stack vm\ | _ \ set_error vm) else (case return_stack vm of dhigh # dlow # rst \ vm\data_stack := dhigh # dlow # data_stack vm\ | _ \ set_error vm))" lemma two_r_fetch_inside_colon: assumes "ecw_nesting vm > 0" assumes "return_stack vm = resume_ip # dhigh # dlow # rst" shows "data_stack (forth_2r_fetch vm) = dhigh # dlow # data_stack vm" and "return_stack (forth_2r_fetch vm) = return_stack vm" using assms by (simp_all add: forth_2r_fetch_def) lemma two_r_fetch_outside_colon: assumes "ecw_nesting vm = 0" assumes "return_stack vm = dhigh # dlow # rst" shows "data_stack (forth_2r_fetch vm) = dhigh # dlow # data_stack vm" and "return_stack (forth_2r_fetch vm) = return_stack vm" using assms by (simp_all add: forth_2r_fetch_def) end