flan/test/programs/arith.flan
Joseph Ferano a4c6b996ff def re-runs its initialiser, and defvar is renamed defonce
The trio the author decided on 2026-09-20 is now all built: def is CL's
defparameter — its initialiser runs on every daemon re-run, unguarded, so
an edited initialiser repaints the same storage on C-c C-c plus re-run —
defonce (Clojure's name for CL's defvar, per the author) initialises once
behind the .init~once. flag, and defconst stays the image.

One parse arm reads both forms; the difference is Ast.reinit, carried to
Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and
Check.check_global lifts every def initialiser — zero and literal
included — into global/<n>, so the host's startup reaches it through the
function cell and a re-evaluated def swaps it (Session's def_inits;
Emit.redefinition declares the cell for a non-sibling target). The old
defvar spelling is refused with the rename and both compiling spellings,
and every program, test, doc and editor list is swept — except sand.flan,
the author's live WIP, whose seven defvar lines are flagged in FIX.org
and keep its three dependent tests red on this branch.
2026-09-21 07:12:04 +07:00

82 lines
4.2 KiB
Plaintext

;;;; Arithmetic with no answer, unhandled. One program, one case per argument,
;;;; the same shape bounds.flan has and for the same reason: a death is
;;;; observable only as an exit status and a sentence on stderr, so each case
;;;; needs its own run.
;;;;
;;;; What is asserted is the *reason* — the location, and which operation
;;;; against which operands. Before this change none of these cases had a
;;;; reason to assert on: the first four were a raw SIGFPE, which prints
;;;; nothing at all, and the last was undefined and would have printed whatever
;;;; the optimiser decided the answer was.
;;;;
;;;; Everything comes through a global rather than a literal, which keeps the
;;;; operands dynamic. A literal divisor is exactly the case the guard is
;;;; allowed to elide, and folding these away would leave the test asserting on
;;;; a program that does not contain the check.
(defonce zero i64)
(defonce neg1 i64 -1)
(defonce big i64 9223372036854775807)
(defonce ten i64 10)
(defonce uz u32)
(defonce huge f64 1e300)
;; The narrow versions of the same two failures. They are here because they are
;; the ones the two backends reach by different routes: the overflow test
;; compares against the *narrow* type's most negative value inside a 64-bit
;; register, and the f32 range test is compared in f32 on one backend and in a
;; double on the other. Both routes are supposed to give the same answer and
;; the survey is what says so.
(defonce i32big i32 2147483647)
(defonce m1-32 i32 -1)
(defonce wide f32 1e30)
(defn main [args [string]] i32
(let [n (i32 (bytes->i64 (bytes-view (at args 1))))
;; The most negative i64. No literal spells it — the reader parses the
;; digits and then negates, and the positive half does not fit — so it
;; is built, which also keeps it out of the constant folder's reach.
min (- (- (i64 0) big) 1)]
(cond
;; None of these may die. Division that is fine has to stay fine, and
;; these are the four shapes the guard has an opinion about: an ordinary
;; dynamic divisor, a literal one the guard drops entirely, unsigned
;; division, which has no overflow case because it has no most-negative
;; value, and a float division by zero, which is an infinity and is a
;; defined answer this language is not in the business of refusing.
(= n 0) (do (print (/ ten (+ zero 3))) (print " ")
(print (/ ten 2)) (print " ")
(print (/ (u32 100) (+ uz 7))) (print " ")
(print (/ (f64 1.0) (f64 0.0)))
(println ""))
(= n 1) (print (/ ten zero)) ; divide by zero
(= n 2) (print (% ten zero)) ; remainder by zero
;; The one division that overflows. Nothing is wrong with either
;; operand on its own; it is the pair, and it is the only pair.
(= n 3) (print (/ min neg1))
;; `srem` overflows on exactly the operands `sdiv` does, because the
;; quotient is what does not fit and a remainder computes one too.
(= n 4) (print (% min neg1))
;; A float too large for the destination, and the same value against a
;; narrower destination, which reports its own range.
(= n 5) (print (i64 huge))
(= n 6) (print (i8 (/ huge 1e290)))
;; NaN, which fails the range test at both ends rather than passing it at
;; neither: the comparisons are ordered, deliberately.
(= n 7) (print (i64 (/ (f64 0.0) (f64 0.0))))
;; The same overflow one width down, which is the case the two backends
;; used to disagree about *silently* rather than both dying: this one
;; loaded sign-extended into a 64-bit register, divided there and
;; truncated on the store, answering -2147483648, where the other backend
;; emitted poison. Neither was wrong about anything; they just were not
;; the same program.
(= n 8) (print (/ (- (- (i32 0) i32big) 1) m1-32))
;; And an f32 source, whose range test the two backends reach by
;; different routes on purpose — compared in f32 here and in a widened
;; double there, which agree because every bound is a power of two and is
;; exact in both.
(= n 9) (print (i32 wide))
:else (println "?"))
0))