flan/spike/js/p1-int-semantics.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

107 lines
4.3 KiB
Plaintext

;;;; The evidence for lib/js.ml's integer decisions, run by the survey.
;;;;
;;;; JavaScript has one number type and it is a double. Flan has eight integer
;;;; types and they wrap. Every line below is a place where the two disagree
;;;; unless the backend normalises, and the survey diffs this program's output
;;;; against the LLVM build's byte for byte — so a wrong answer here is a
;;;; DIFFER and not a discussion.
;;;;
;;;; Everything goes through a global rather than a literal, for the reason
;;;; arith.flan gives: a literal operand is exactly what a constant folder
;;;; removes, and a folded program does not contain the code being tested.
(defonce i8hi i8 127)
(defonce i16hi i16 32767)
(defonce i32hi i32 2147483647)
(defonce u8hi u8 255)
(defonce u16hi u16 65535)
(defonce u32hi u32 4294967295)
(defonce i64hi i64 9223372036854775807)
(defonce u64zero u64 0)
(defonce one8 i8 1)
(defonce one16 i16 1)
(defonce one32 i32 1)
(defonce oneu8 u8 1)
(defonce oneu16 u16 1)
(defonce oneu32 u32 1)
(defonce one64 i64 1)
(defonce oneu64 u64 1)
(defonce big32 i32 123456789)
(defonce big64 i64 1234567890123)
(defonce three i32 3)
(defonce three64 i64 3)
(defonce seven i32 7)
(defonce negsev i32 -7)
(defonce shift i32 33)
(defonce shift8 i8 9)
(defonce f32one f32 1.0)
(defonce f32big f32 16777217.0)
(defn main [] i32
;; ── Wrapping at every width ──────────────────────────────────────
;; The top of the type plus one. A JS number would answer 128, 32768,
;; 2147483648, 256, 65536, 4294967296 and never wrap at all.
(println (+ i8hi one8))
(println (+ i16hi one16))
(println (+ i32hi one32))
(println (+ u8hi oneu8))
(println (+ u16hi oneu16))
(println (+ u32hi oneu32))
(println (+ i64hi one64))
(println (- u64zero oneu64)) ; the top of u64, which no literal spells
;; A multiply whose exact product passes 2^53, where a * b in JS is not
;; exact and Math.imul is. 123456789 * 123456789 = 15241578750190521, which
;; is above 2^53, so a double rounds it before the truncation can happen.
(println (* big32 big32))
(println (* big64 big64))
;; ── Truncating division, and the sign of a remainder ─────────────
;; C truncates toward zero and so does JS's %, but (a / b) | 0 is only the
;; same answer below 2^31, which is why the backend uses Math.trunc.
(println (/ seven three))
(println (/ negsev three))
(println (% seven three))
(println (% negsev three))
(println (/ big64 three64))
;; ── Shifts, with the count masked to the operand's width ─────────
;; 33 is not a legal shift count for a 32-bit type, and the checker only
;; rejects a *literal* out of range, so this is the computed case emit.ml
;; masks and this backend has to mask too. JS masks a 32-bit shift itself
;; and has no 8- or 16-bit shift at all, which is why the mask is written
;; out rather than relied on.
(println (<< one32 shift))
(println (<< one8 shift8))
(println (>> i32hi one32))
(println (>> (- (i32 0) i32hi) one32)) ; arithmetic: signed stays signed
(println (>> u32hi oneu32)) ; logical: unsigned stays unsigned
(println (<< one64 (i64 65)))
;; ── Bitwise over an unsigned 32-bit value ────────────────────────
;; JS bitwise operators produce a *signed* 32-bit result, so every one of
;; these needs the >>> 0 back.
(println (bit-and u32hi u32hi))
(println (bit-or u32hi oneu32))
(println (bit-xor u32hi oneu32))
;; ── f32 is not a double ──────────────────────────────────────────
;; 16777217 is the first integer a float cannot hold, so this prints
;; 16777216 under a real f32 and 16777218 under a double.
(println (+ f32big f32one))
(println (/ (f32 1.0) (f32 3.0)))
(println (/ (f64 1.0) (f64 3.0)))
;; ── The conversions, both ways ───────────────────────────────────
(println (i8 i32hi))
(println (u8 (- (i32 0) one32)))
(println (i32 i64hi))
(println (i64 big32))
(println (u64 i64hi))
(println (f64 i64hi))
(println (i32 (f64 2.9)))
(println (i32 (f64 -2.9)))
0)