diff --git a/lib/check.ml b/lib/check.ml index fb5aa531..7a1199d7 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -7371,6 +7371,60 @@ and named_call ?(qualified = false) ctx ~want loc name args = expect ctx loc ~want (List.fold_left (fun acc arg -> pick acc (check ctx ~want:ty arg)) (pick a b) rest) + (* (max-of T) and (min-of T): the type-limit constants, by type, so a + generic body can name its own type's. Odin's max(T) and min(T), and the + same answer for a float: the largest finite value and its negation, not + the smallest positive one. Given a value rather than a type, the name is + the prelude's reduction of a slice, and the call is an ordinary one — + the same split [vec-new] makes between a type and an allocator. *) + | ("max-of" | "min-of") + when (match args with + | [ a ] -> + type_of_expr a <> None + || (match a.Ast.e with + | Ast.Var n -> + lookup ctx n = None + && (not (Hashtbl.mem ctx.env.globals n)) + && type_named ctx n + | _ -> false) + | _ -> false) -> + let a = List.hd args in + let ty = + match type_of_expr a, a.Ast.e with + | Some t, _ -> resolve ctx.env t + | _, Ast.Var n -> resolve_name ctx.env ~seen:[] a.Ast.loc n + | _ -> fail a.Ast.loc "internal: max-of's type argument is not a type" + in + let max = String.equal name "max-of" in + let v = + match ty with + | Types.Int k -> + let b = Types.bits k in + let n = + if Types.signed k then + let top = Int64.shift_left 1L (b - 1) in + if max then Int64.sub top 1L else Int64.neg top + else if not max then 0L + else if b = 64 then -1L + else Int64.sub (Int64.shift_left 1L b) 1L + in + mk loc ty (Tast.Int (n, k)) + | Types.Float k -> + let m = + match k with + | Types.F32 -> Int32.float_of_bits 0x7f7fffffl + | Types.F64 -> Float.max_float + in + mk loc ty (Tast.Float ((if max then m else -.m), k)) + | Types.Var _ -> + unconstrained ctx.env loc name ~needs:"numeric?" ty; + int_literal loc ~want:(Some ty) ~preds:ctx.env.tvpreds 0L + | _ -> + fail a.Ast.loc + "%s takes a numeric? type, and %s is not one — as in (%s i32)" name + (Types.to_string ty) name + in + expect ctx loc ~want v (* (zeroed) is the all-bytes-zero value of whatever it is being stored into, so it only means anything where a type is expected of it. *) | "zeroed" -> diff --git a/lib/prelude.ml b/lib/prelude.ml index ff520d4b..4cab01aa 100644 --- a/lib/prelude.ml +++ b/lib/prelude.ml @@ -406,6 +406,8 @@ let source = {flan| ;; or more numbers, and a defn cannot shadow a builtin: nothing shadows [+] ;; either. These reduce a slice, which is a different operation with a ;; different arity, so the different name is honest rather than a workaround. +;; Given a type instead of a slice, (min-of i8) and (max-of $t) are the type's +;; limits, and the checker answers those itself. (defn min-of [s [$t]] (Option $t) {:where (ordered? $t)} (if (= (length s) 0) diff --git a/test/programs/max-of.flan b/test/programs/max-of.flan new file mode 100644 index 00000000..2b5f5e76 --- /dev/null +++ b/test/programs/max-of.flan @@ -0,0 +1,54 @@ +;;;; (max-of T) and (min-of T): a numeric type's limits, named by the type, at +;;;; a concrete type and inside a generic whose bound admits numbers. + +;; A selection sort, descending, whose running best starts at the least value +;; of the element type, so any element beats it. +(defn sort-desc [s [$t]] () + {:where (numeric? $t)} + (dotimes [i (length s)] + (let [best (min-of $t) + at-best i] + (dotimes [j (- (length s) i)] + (let [k (+ i j)] + (when (> (at s k) best) + (set best (at s k)) + (set at-best k)))) + (swap s i at-best)))) + +(defn largest [s [$t]] $t + {:where (numeric? $t)} + (let [best (min-of t)] + (dotimes [i (length s)] + (when (> (at s i) best) (set best (at s i)))) + best)) + +(defn show-i32 [s [i32]] () + (dotimes [i (length s)] (print (at s i)) (print " ")) + (println "")) + +(defn show-f64 [s [f64]] () + (dotimes [i (length s)] (print (at s i)) (print " ")) + (println "")) + +(defn main [] i32 + (println (max-of u8)) + (println (min-of u8)) + (println (max-of i8)) + (println (min-of i8)) + (println (max-of i32)) + (println (min-of i64)) + (println (max-of u64)) + (println (= (max-of f32) f32-max)) + (println (= (min-of f64) (- f64-max))) + (println (= (max-of i16) i16-max)) + (let [a [(i32 3) -7 12 0 -2147483648 5] + b [2.5 -1.0 1e300 -1e308] + c [(u8 4) 0 200 9]] + (sort-desc (slice a)) + (show-i32 (slice a)) + (sort-desc (slice b)) + (show-f64 (slice b)) + (println (largest (slice c))) + (println (let [d [(i64 -5) -9]] (largest (slice d)))) + (println (let [e [(f32 -1.0) -3.0]] (= (largest (slice e)) (f32 -1.0))))) + 0) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index c6660228..2dd063da 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -573,6 +573,14 @@ let () = outputs "a prelude function shadowed" "programs/shadow-prelude.flan" sp_out; outputs ~x86:true "a prelude function shadowed, x86" "programs/shadow-prelude.flan" sp_out; + (* (max-of T) and (min-of T), concrete and inside a generic. *) + let maxof_out = + "255\n0\n127\n-128\n2147483647\n-9223372036854775808\n\ + 18446744073709551615\ntrue\ntrue\ntrue\n\ + 12 5 3 0 -7 -2147483648 \n1e+300 2.5 -1 -1e+308 \n200\n-5\ntrue\n" in + outputs "max-of and min-of" "programs/max-of.flan" maxof_out; + outputs ~opt:"-O0" "max-of and min-of, -O0" "programs/max-of.flan" maxof_out; + outputs ~x86:true "max-of and min-of, x86" "programs/max-of.flan" maxof_out; (* (- x) negates, on every numeric type, a type variable and a dyn. *) let neg_out = "-3\n7\n-2.5\n-inf\n-1.5\n255\n-4\n-2.5\n-inf\n-9000000000\n\ diff --git a/test/test_flan.ml b/test/test_flan.ml index 636c1f36..759b7bbd 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -6399,6 +6399,19 @@ let () = "(defn main [] i32 (let [a [None None]] 0))" ~needle:"what None is an Option of"; + (* ── (max-of T) and (min-of T) ──────────────────────────────────── *) + infers "max-of carries its type" "(max-of u16)" "u16"; + infers "min-of at a float" "(min-of f32)" "f32"; + rejects_check "max-of at an unbounded type variable names the bound" + "(defn f [x $t] $t (max-of $t))" ~needle:"{:where (numeric? $t)}"; + accepts "max-of at a type variable the bound admits" + "(defn f [x $t] $t {:where (integer? $t)} (max-of t))"; + rejects_check "max-of at a type that is not a number names the bound" + "(defn f [] string (max-of string))" + ~needle:"max-of takes a numeric? type, and string is not one"; + accepts "max-of of a slice is still the prelude's reduction" + "(defn f [xs [i32]] (Option i32) (max-of xs))"; + (* ── (the T e) ─────────────────────────────────────────────────── *) infers "the gives a literal its type" "(the u8 200)" "u8"; infers "the widens as an annotation does" "(the i64 (the i32 1))" "i64";