theory StarForth_Arithmetic_Words imports StarForth_Base begin (* ========================================================================= POST-02: Arithmetic Words Mirrors: src/word_source/arithmetic_words.c src/test_runner/modules/arithmetic_words_test.c All operations use HOL int (arbitrary precision). Proofs hold in C provided no intermediate value overflows 64-bit signed range. ======================================================================== *) (* ── Helper: binary op lifting ─────────────────────────────────────────── *) (* Most binary arithmetic words: pop two, push result. *) definition binop :: "(cell \ cell \ cell) \ vm_state \ vm_state" where "binop f vm = (case data_stack vm of n2 # n1 # rest \ vm\data_stack := f n1 n2 # rest\ | _ \ set_error vm)" lemma binop_normal: assumes "data_stack vm = n2 # n1 # rest" shows "data_stack (binop f vm) = f n1 n2 # rest" by (simp add: binop_def assms) lemma binop_depth: assumes "data_stack vm = n2 # n1 # rest" shows "length (data_stack (binop f vm)) = length (data_stack vm) - 1" by (simp add: binop_def assms) lemma binop_underflow_nil: assumes "data_stack vm = []" shows "vm_error (binop f vm)" by (simp add: binop_def set_error_def assms) lemma binop_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (binop f vm)" by (simp add: binop_def set_error_def assms) (* ── Helper: unary op lifting ───────────────────────────────────────────── *) definition unop :: "(cell \ cell) \ vm_state \ vm_state" where "unop f vm = (case data_stack vm of [] \ set_error vm | x # xs \ vm\data_stack := f x # xs\)" lemma unop_normal: assumes "data_stack vm = x # xs" shows "data_stack (unop f vm) = f x # xs" by (simp add: unop_def assms) lemma unop_depth_preserved: assumes "data_stack vm = x # xs" shows "length (data_stack (unop f vm)) = length (data_stack vm)" by (simp add: unop_def assms) lemma unop_underflow: assumes "data_stack vm = []" shows "vm_error (unop f vm)" by (simp add: unop_def set_error_def assms) (* ── + ( n1 n2 -- n3 ) ─────────────────────────────────────────────────── *) definition forth_add :: "vm_state \ vm_state" where "forth_add = binop (+)" lemma add_normal: assumes "data_stack vm = n2 # n1 # rest" shows "data_stack (forth_add vm) = (n1 + n2) # rest" by (simp add: forth_add_def binop_def assms) lemma add_commutative: assumes "data_stack vm = n2 # n1 # rest" assumes "data_stack vm' = n1 # n2 # rest" shows "data_stack (forth_add vm) = data_stack (forth_add vm')" by (simp add: forth_add_def binop_def assms add.commute) lemma add_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_add vm)" by (simp add: forth_add_def binop_def set_error_def assms) lemma add_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (forth_add vm)" by (simp add: forth_add_def binop_def set_error_def assms) (* ── - ( n1 n2 -- n3 ) ─────────────────────────────────────────────────── *) (* Stack effect: n3 = n1 - n2 (n2 = TOS subtracted from n1 = second) *) definition forth_sub :: "vm_state \ vm_state" where "forth_sub = binop (-)" lemma sub_normal: assumes "data_stack vm = n2 # n1 # rest" shows "data_stack (forth_sub vm) = (n1 - n2) # rest" by (simp add: forth_sub_def binop_def assms) lemma sub_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_sub vm)" by (simp add: forth_sub_def binop_def set_error_def assms) lemma sub_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (forth_sub vm)" by (simp add: forth_sub_def binop_def set_error_def assms) (* ── * ( n1 n2 -- n3 ) ─────────────────────────────────────────────────── *) definition forth_mul :: "vm_state \ vm_state" where "forth_mul = binop (*)" lemma mul_normal: assumes "data_stack vm = n2 # n1 # rest" shows "data_stack (forth_mul vm) = (n1 * n2) # rest" by (simp add: forth_mul_def binop_def assms) lemma mul_commutative: assumes "data_stack vm = n2 # n1 # rest" assumes "data_stack vm' = n1 # n2 # rest" shows "data_stack (forth_mul vm) = data_stack (forth_mul vm')" by (simp add: forth_mul_def binop_def assms mult.commute) lemma mul_by_zero_tos: assumes "data_stack vm = 0 # n1 # rest" shows "data_stack (forth_mul vm) = 0 # rest" by (simp add: forth_mul_def binop_def assms) lemma mul_by_zero_second: assumes "data_stack vm = n2 # 0 # rest" shows "data_stack (forth_mul vm) = 0 # rest" by (simp add: forth_mul_def binop_def assms) lemma mul_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_mul vm)" by (simp add: forth_mul_def binop_def set_error_def assms) (* ── / ( n1 n2 -- n3 ) ─────────────────────────────────────────────────── *) (* Division by zero sets vm_error (matching C behaviour). *) definition forth_div :: "vm_state \ vm_state" where "forth_div vm = (case data_stack vm of n2 # n1 # rest \ if n2 = 0 then set_error vm else vm\data_stack := (cell_sdiv n1 n2) # rest\ | _ \ set_error vm)" lemma div_normal: assumes "data_stack vm = n2 # n1 # rest" assumes "n2 \ 0" shows "data_stack (forth_div vm) = (cell_sdiv n1 n2) # rest" by (simp add: forth_div_def assms) lemma div_by_zero: assumes "data_stack vm = 0 # n1 # rest" shows "vm_error (forth_div vm)" by (simp add: forth_div_def set_error_def assms) lemma div_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_div vm)" by (simp add: forth_div_def set_error_def assms) lemma div_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (forth_div vm)" by (simp add: forth_div_def set_error_def assms) (* ── MOD ( n1 n2 -- n3 ) ───────────────────────────────────────────────── *) definition forth_mod :: "vm_state \ vm_state" where "forth_mod vm = (case data_stack vm of n2 # n1 # rest \ if n2 = 0 then set_error vm else vm\data_stack := (cell_smod n1 n2) # rest\ | _ \ set_error vm)" lemma mod_normal: assumes "data_stack vm = n2 # n1 # rest" assumes "n2 \ 0" shows "data_stack (forth_mod vm) = (cell_smod n1 n2) # rest" by (simp add: forth_mod_def assms) lemma mod_by_zero: assumes "data_stack vm = 0 # n1 # rest" shows "vm_error (forth_mod vm)" by (simp add: forth_mod_def set_error_def assms) lemma mod_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_mod vm)" by (simp add: forth_mod_def set_error_def assms) (* ── /MOD ( n1 n2 -- n3 n4 ) ───────────────────────────────────────────── *) (* Pushes remainder (n3) then quotient (n4); quotient is TOS. *) definition forth_divmod :: "vm_state \ vm_state" where "forth_divmod vm = (case data_stack vm of n2 # n1 # rest \ if n2 = 0 then set_error vm else if ds_full vm then set_error vm else vm\data_stack := (cell_sdiv n1 n2) # (cell_smod n1 n2) # rest\ | _ \ set_error vm)" lemma divmod_normal: assumes "data_stack vm = n2 # n1 # rest" assumes "n2 \ 0" assumes "\ ds_full vm" shows "data_stack (forth_divmod vm) = (cell_sdiv n1 n2) # (cell_smod n1 n2) # rest" by (simp add: forth_divmod_def assms) lemma divmod_quotient: assumes "data_stack vm = n2 # n1 # rest" assumes "n2 \ 0" assumes "\ ds_full vm" shows "hd (data_stack (forth_divmod vm)) = cell_sdiv n1 n2" by (simp add: forth_divmod_def assms) lemma divmod_remainder: assumes "data_stack vm = n2 # n1 # rest" assumes "n2 \ 0" assumes "\ ds_full vm" shows "hd (tl (data_stack (forth_divmod vm))) = cell_smod n1 n2" by (simp add: forth_divmod_def assms) (* ── 1+ ( n -- n+1 ) ───────────────────────────────────────────────────── *) definition forth_one_plus :: "vm_state \ vm_state" where "forth_one_plus = unop (\n. n + 1)" lemma one_plus_normal: assumes "data_stack vm = n # xs" shows "data_stack (forth_one_plus vm) = (n + 1) # xs" by (simp add: forth_one_plus_def unop_def assms) lemma one_plus_underflow: assumes "data_stack vm = []" shows "vm_error (forth_one_plus vm)" by (simp add: forth_one_plus_def unop_def set_error_def assms) (* ── 1- ( n -- n-1 ) ───────────────────────────────────────────────────── *) definition forth_one_minus :: "vm_state \ vm_state" where "forth_one_minus = unop (\n. n - 1)" lemma one_minus_normal: assumes "data_stack vm = n # xs" shows "data_stack (forth_one_minus vm) = (n - 1) # xs" by (simp add: forth_one_minus_def unop_def assms) lemma one_minus_underflow: assumes "data_stack vm = []" shows "vm_error (forth_one_minus vm)" by (simp add: forth_one_minus_def unop_def set_error_def assms) (* ── 2+ ( n -- n+2 ) ───────────────────────────────────────────────────── *) definition forth_two_plus :: "vm_state \ vm_state" where "forth_two_plus = unop (\n. n + 2)" lemma two_plus_normal: assumes "data_stack vm = n # xs" shows "data_stack (forth_two_plus vm) = (n + 2) # xs" by (simp add: forth_two_plus_def unop_def assms) (* ── 2- ( n -- n-2 ) ───────────────────────────────────────────────────── *) definition forth_two_minus :: "vm_state \ vm_state" where "forth_two_minus = unop (\n. n - 2)" lemma two_minus_normal: assumes "data_stack vm = n # xs" shows "data_stack (forth_two_minus vm) = (n - 2) # xs" by (simp add: forth_two_minus_def unop_def assms) (* ── 2* ( n -- n*2 ) ───────────────────────────────────────────────────── *) (* Left shift by 1 in C; multiplication by 2 in HOL. *) definition forth_two_mul :: "vm_state \ vm_state" where "forth_two_mul = unop (\n. n * 2)" lemma two_mul_normal: assumes "data_stack vm = n # xs" shows "data_stack (forth_two_mul vm) = (n * 2) # xs" by (simp add: forth_two_mul_def unop_def assms) (* ── 2/ ( n -- n/2 ) ───────────────────────────────────────────────────── *) (* CODE-MUST-MATCH: arithmetic_words.c does "n >> 1" on signed cell_t -- an arithmetic (sign-extending) right shift, i.e. floor division by 2, NOT truncating division. signed_drop_bit is Word's arithmetic shift and matches this exactly (unlike plain word div/mod, which are unsigned-magnitude and wrong here). *) definition forth_two_div :: "vm_state \ vm_state" where "forth_two_div = unop (signed_drop_bit 1)" lemma two_div_normal: assumes "data_stack vm = n # xs" shows "data_stack (forth_two_div vm) = (signed_drop_bit 1 n) # xs" by (simp add: forth_two_div_def unop_def assms) (* ── ABS ( n -- |n| ) ──────────────────────────────────────────────────── *) (* word has no signed abs of its own (that error is what forced this whole ABS/MIN/MAX section to be revisited) -- cell_abs in StarForth_Base.thy supplies it via the signed order vm_state" where "forth_abs = unop cell_abs" lemma abs_normal: assumes "data_stack vm = n # xs" shows "data_stack (forth_abs vm) = cell_abs n # xs" by (simp add: forth_abs_def unop_def assms) (* ⚠ GENUINE FINDING, not an artifact of the proof: ABS(INT64_MIN) is not \ 0 -- two's-complement negation of the minimum signed 64-bit value overflows and wraps back to itself (identical to C's ABS(LONG_MIN) hazard, since cell_t is a plain signed long with no overflow trap). cell_safe n excludes exactly this single value (and the rest of the far-from-boundary range no real FORTH program should be relying on the overflow behaviour of anyway) so the property holds for the values arithmetic proofs elsewhere in this suite actually use. *) lemma abs_nonneg: assumes "data_stack vm = n # xs" assumes "\ vm_error vm" assumes "cell_safe n" shows "0 \s hd (data_stack (forth_abs vm))" proof (cases "n s (-n)" using True by (simp add: word_sle_eq word_sless_alt sint_negn) have "hd (data_stack (forth_abs vm)) = -n" using assms(1) True by (simp add: forth_abs_def unop_def cell_abs_def) with negn_nonneg show ?thesis by simp next case False then show ?thesis using assms(1) by (simp add: forth_abs_def unop_def cell_abs_def word_sle_eq word_sless_alt) qed (* cell_safe n again excludes the INT64_MIN self-wrap case: without it, idempotence still happens to hold there too (cell_abs wraps INT64_MIN to itself both times), but proving that needs the extra double-negation self-wrap fact rather than a plain case split, and nothing downstream needs that generality. *) lemma abs_idempotent: assumes "data_stack vm = n # xs" assumes "\ vm_error vm" assumes "cell_safe n" shows "hd (data_stack (forth_abs (forth_abs vm))) = hd (data_stack (forth_abs vm))" proof (cases "n s (-n)" using True by (simp add: word_sle_eq word_sless_alt sint_negn) have step1: "hd (data_stack (forth_abs vm)) = -n" using assms(1) True by (simp add: forth_abs_def unop_def cell_abs_def) have step2: "hd (data_stack (forth_abs (forth_abs vm))) = -n" using assms(1) True negn_nonneg by (simp add: forth_abs_def unop_def cell_abs_def word_sless_alt word_sle_eq) from step1 step2 show ?thesis by simp next case False then show ?thesis using assms(1) by (simp add: forth_abs_def unop_def cell_abs_def) qed lemma abs_underflow: assumes "data_stack vm = []" shows "vm_error (forth_abs vm)" by (simp add: forth_abs_def unop_def set_error_def assms) (* ── NEGATE ( n -- -n ) ─────────────────────────────────────────────────── *) definition forth_negate :: "vm_state \ vm_state" where "forth_negate = unop uminus" lemma negate_normal: assumes "data_stack vm = n # xs" shows "data_stack (forth_negate vm) = (-n) # xs" by (simp add: forth_negate_def unop_def assms) lemma negate_involutive: assumes "data_stack vm = n # xs" shows "hd (data_stack (forth_negate (forth_negate vm))) = n" by (simp add: forth_negate_def unop_def assms) lemma negate_underflow: assumes "data_stack vm = []" shows "vm_error (forth_negate vm)" by (simp add: forth_negate_def unop_def set_error_def assms) (* ── MIN ( n1 n2 -- n3 ) ───────────────────────────────────────────────── *) (* word's inherited min/max (via the default, unsigned <=) is wrong for FORTH's signed MIN/MAX -- must use the "signed" locale interpretation (Word.thy:1771, linorder over word_sle/word_sless) instead. *) definition forth_min :: "vm_state \ vm_state" where "forth_min = binop signed.min" lemma min_normal: assumes "data_stack vm = n2 # n1 # rest" shows "data_stack (forth_min vm) = signed.min n1 n2 # rest" by (simp add: forth_min_def binop_def assms) lemma min_commutative: assumes "data_stack vm = n2 # n1 # rest" assumes "data_stack vm' = n1 # n2 # rest" shows "data_stack (forth_min vm) = data_stack (forth_min vm')" by (simp add: forth_min_def binop_def assms signed.min.commute) lemma min_idempotent: assumes "data_stack vm = n # n # rest" shows "data_stack (forth_min vm) = n # rest" by (simp add: forth_min_def binop_def assms) lemma min_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_min vm)" by (simp add: forth_min_def binop_def set_error_def assms) (* ── MAX ( n1 n2 -- n3 ) ───────────────────────────────────────────────── *) definition forth_max :: "vm_state \ vm_state" where "forth_max = binop signed.max" lemma max_normal: assumes "data_stack vm = n2 # n1 # rest" shows "data_stack (forth_max vm) = signed.max n1 n2 # rest" by (simp add: forth_max_def binop_def assms) lemma max_commutative: assumes "data_stack vm = n2 # n1 # rest" assumes "data_stack vm' = n1 # n2 # rest" shows "data_stack (forth_max vm) = data_stack (forth_max vm')" by (simp add: forth_max_def binop_def assms signed.max.commute) lemma max_idempotent: assumes "data_stack vm = n # n # rest" shows "data_stack (forth_max vm) = n # rest" by (simp add: forth_max_def binop_def assms) lemma max_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_max vm)" by (simp add: forth_max_def binop_def set_error_def assms) (* ── min/max algebraic relationship ────────────────────────────────────── *) lemma min_le_max: "signed.min (a::cell) b \s signed.max a b" by (cases "a \s b") (simp_all add: signed.min_def signed.max_def signed.linear) end