An integer written at or above 2^63 is a u64 and nothing else, and vec-new reads a type from its argument only when the builtin is what was called
This commit is contained in:
parent
7b843f6753
commit
cb0be62087
25
TODO.org
25
TODO.org
@ -145,12 +145,16 @@ Closing it needs a reader literal or a float-capable folding pass.
|
|||||||
|
|
||||||
** DONE A u64 constant above 2^63 cannot be written in decimal
|
** DONE A u64 constant above 2^63 cannot be written in decimal
|
||||||
CLOSED: [2026-09-25]
|
CLOSED: [2026-09-25]
|
||||||
A decimal between 2^63 and 2^64 is read as its bit pattern, as hex is. A cast's
|
An integer written at or above 2^63 — a decimal up to 2^64 - 1, or hex with the
|
||||||
integer literal that does not fit =i32= is checked at the cast's type; one that
|
top bit set — reads as =Form.UInt=, its pattern and its spelling. It is accepted
|
||||||
fits keeps the =i32= default, so =(u32 -1)= still means what it did. It inherits
|
where the type is =u64=, a =(u64 ...)= cast included, and refused everywhere else
|
||||||
hex's hole: a decimal above 2^63 at =i64= is accepted as a negative, and a refusal
|
in the spelling it was written in. Hex with the top bit set was accepted as a
|
||||||
at a narrower type prints the negative number. Rules out a separate unsigned
|
negative at any integer type before this; it is refused now too. A negative
|
||||||
literal in =Form=, whose layout the prelude's =Form= mirrors.
|
decimal is still a =u64= bit pattern. A cast's integer literal that does not fit
|
||||||
|
=i32= is checked at the cast's type; one that fits keeps the =i32= default, so
|
||||||
|
=(u32 -1)= still means what it did. A wide literal that passes through a macro
|
||||||
|
comes back as an ordinary =Int=, because the macro side's =Form= has one integer
|
||||||
|
case. Rules out a second integer case in the prelude's =Form=.
|
||||||
|
|
||||||
** DONE {.row .col} binds same-named locals
|
** DONE {.row .col} binds same-named locals
|
||||||
CLOSED: [2026-09-20]
|
CLOSED: [2026-09-20]
|
||||||
@ -846,10 +850,11 @@ lines at the call site, where =K= is known.
|
|||||||
** DONE (vec-new [u8]) is refused
|
** DONE (vec-new [u8]) is refused
|
||||||
CLOSED: [2026-09-25]
|
CLOSED: [2026-09-25]
|
||||||
The type positions of =vec-new= and =map-new= take a type expression: brackets, or
|
The type positions of =vec-new= and =map-new= take a type expression: brackets, or
|
||||||
a parenthesised =Ptr=, =Option=, =Vec=, =Map=, =Fn= or =CFn=. Parse reads it with
|
a parenthesised =Ptr=, =Option=, =Vec=, =Map=, =Fn= or =CFn=. The arguments stay
|
||||||
=texpr= into =Ast.TypeArg=; a bare name is still left for the checker to tell a
|
ordinary expressions and the builtin reads the type back out of one
|
||||||
type from an allocator. Rules out a type expression anywhere else in expression
|
(=Check.type_of_expr=), so a program's own =vec-new= still gets values; only a
|
||||||
position.
|
type an expression cannot hold, such as =(Fn [i32] ())=, is parsed as
|
||||||
|
=Ast.TypeArg=. Rules out a type expression anywhere else in expression position.
|
||||||
|
|
||||||
** TODO An array literal cannot say it is [f32]
|
** TODO An array literal cannot say it is [f32]
|
||||||
A float literal defaults to =f64=, an array literal has no context, and a =let=
|
A float literal defaults to =f64=, an array literal has no context, and a =let=
|
||||||
|
|||||||
@ -36,6 +36,7 @@ type expr = { e : expr_kind; loc : Loc.t }
|
|||||||
|
|
||||||
and expr_kind =
|
and expr_kind =
|
||||||
| Int of int64
|
| Int of int64
|
||||||
|
| UInt of int64 * string (* 18446744073709551615 — u64 only *)
|
||||||
| Float of float
|
| Float of float
|
||||||
| Byte of int
|
| Byte of int
|
||||||
| Str of string
|
| Str of string
|
||||||
@ -408,7 +409,7 @@ let map_children f (e : expr) : expr =
|
|||||||
in
|
in
|
||||||
let kind =
|
let kind =
|
||||||
match e.e with
|
match e.e with
|
||||||
| Int _ | Float _ | Byte _ | Str _ | Kw _ | Quote _ | Var _ | ArrayOf _
|
| Int _ | UInt _ | Float _ | Byte _ | Str _ | Kw _ | Quote _ | Var _ | ArrayOf _
|
||||||
| TypeArg _ | Break _ | Continue _ -> e.e
|
| TypeArg _ | Break _ | Continue _ -> e.e
|
||||||
| Do es -> Do (List.map ex es)
|
| Do es -> Do (List.map ex es)
|
||||||
| Let (bs, es) -> Let (List.map bind bs, List.map ex es)
|
| Let (bs, es) -> Let (List.map bind bs, List.map ex es)
|
||||||
|
|||||||
98
lib/check.ml
98
lib/check.ml
@ -395,6 +395,7 @@ let spell_arg stand_for (a : Ast.expr) =
|
|||||||
match a.Ast.e with
|
match a.Ast.e with
|
||||||
| Ast.Var v -> v
|
| Ast.Var v -> v
|
||||||
| Ast.Int n -> Int64.to_string n
|
| Ast.Int n -> Int64.to_string n
|
||||||
|
| Ast.UInt (_, s) -> s
|
||||||
| _ -> stand_for
|
| _ -> stand_for
|
||||||
|
|
||||||
(* What a [break] or a [continue] may be talking about, innermost first.
|
(* What a [break] or a [continue] may be talking about, innermost first.
|
||||||
@ -1938,7 +1939,9 @@ let check_fn_ref : (env -> Ast.fn -> Tast.fn) ref =
|
|||||||
(* Untyped literals: their machine type comes from context, so when one is an
|
(* Untyped literals: their machine type comes from context, so when one is an
|
||||||
operand of a binary operator we look at the *other* operand first. *)
|
operand of a binary operator we look at the *other* operand first. *)
|
||||||
let is_literal (e : Ast.expr) =
|
let is_literal (e : Ast.expr) =
|
||||||
match e.Ast.e with Ast.Int _ | Ast.Float _ | Ast.Byte _ -> true | _ -> false
|
match e.Ast.e with
|
||||||
|
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ -> true
|
||||||
|
| _ -> false
|
||||||
|
|
||||||
(* [addr] takes the address of a place, but the parser only builds places for
|
(* [addr] takes the address of a place, but the parser only builds places for
|
||||||
[set]. Recover one from the expression it parsed instead. *)
|
[set]. Recover one from the expression it parsed instead. *)
|
||||||
@ -3297,6 +3300,7 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
|||||||
ctx.tail <- false;
|
ctx.tail <- false;
|
||||||
match e.Ast.e with
|
match e.Ast.e with
|
||||||
| Ast.Int n -> int_literal loc ~want ~preds:ctx.env.tvpreds n
|
| Ast.Int n -> int_literal loc ~want ~preds:ctx.env.tvpreds n
|
||||||
|
| Ast.UInt (n, s) -> wide_literal loc ~want n s
|
||||||
| Ast.Byte b ->
|
| Ast.Byte b ->
|
||||||
int_literal loc ~want ~preds:ctx.env.tvpreds ~default:Types.U8
|
int_literal loc ~want ~preds:ctx.env.tvpreds ~default:Types.U8
|
||||||
(Int64.of_int b)
|
(Int64.of_int b)
|
||||||
@ -3547,11 +3551,11 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
|||||||
| Ast.ArrayOf t ->
|
| Ast.ArrayOf t ->
|
||||||
let ty = resolve ctx.env t in
|
let ty = resolve ctx.env t in
|
||||||
expect ctx loc ~want (mk loc ty (Tast.Zero ty))
|
expect ctx loc ~want (mk loc ty (Tast.Zero ty))
|
||||||
(* Parse writes one only into a type position of vec-new or map-new, and
|
(* Parse writes one only into a type position of a call named vec-new or
|
||||||
those read it before it could get here. *)
|
map-new, and the builtins read it before it could get here. A program's
|
||||||
|
own function of that name does not. *)
|
||||||
| Ast.TypeArg _ ->
|
| Ast.TypeArg _ ->
|
||||||
fail loc "internal: a type argument reached the checker outside vec-new or \
|
fail loc "this is a type, and a value is wanted here"
|
||||||
map-new — this is a compiler bug"
|
|
||||||
| Ast.ArrayFill (dims, v) -> check_array_fill ctx ~want loc dims v
|
| Ast.ArrayFill (dims, v) -> check_array_fill ctx ~want loc dims v
|
||||||
| Ast.ArrayGen (dims, f) -> check_array_gen ctx ~want loc dims f
|
| Ast.ArrayGen (dims, f) -> check_array_gen ctx ~want loc dims f
|
||||||
| Ast.Match (scrutinee, arms) -> check_match ctx ~tail ?want loc scrutinee arms
|
| Ast.Match (scrutinee, arms) -> check_match ctx ~tail ?want loc scrutinee arms
|
||||||
@ -3732,6 +3736,29 @@ and int_literal loc ~want ?(preds = []) ?(default = Types.I32) n =
|
|||||||
(Types.to_string other) n
|
(Types.to_string other) n
|
||||||
| _ -> mk loc (Types.Int default) (Tast.Int (in_range loc default n, default))
|
| _ -> mk loc (Types.Int default) (Tast.Int (in_range loc default n, default))
|
||||||
|
|
||||||
|
(* An integer written at or above 2^63, in decimal or in hex. Only a u64 holds
|
||||||
|
one, so it is accepted there and refused everywhere else, in the spelling it
|
||||||
|
was written in — its pattern read as an i64 is a different number. *)
|
||||||
|
and wide_literal loc ~want n s =
|
||||||
|
match want with
|
||||||
|
| Some (Types.Int Types.U64) -> mk loc (Types.Int Types.U64) (Tast.Int (n, Types.U64))
|
||||||
|
| Some (Types.Int k) ->
|
||||||
|
Loc.failk literal_at_want loc "%s does not fit in %s" s (Types.ikind_name k)
|
||||||
|
| Some (Types.Float _ as t) ->
|
||||||
|
Loc.failk literal_at_want loc
|
||||||
|
"%s is too large for any integer type but u64, and an integer literal \
|
||||||
|
where %s is wanted is read as one — write (%s (u64 %s))"
|
||||||
|
s (Types.to_string t) (Types.to_string t) s
|
||||||
|
| Some Types.Never | None ->
|
||||||
|
Loc.failk literal_at_want loc
|
||||||
|
"%s does not fit in i32, the type an integer literal takes when nothing \
|
||||||
|
says otherwise — write (u64 %s) for a u64"
|
||||||
|
s s
|
||||||
|
| Some other ->
|
||||||
|
Loc.failk literal_at_want loc
|
||||||
|
"expected %s, found the integer literal %s, which only a u64 holds"
|
||||||
|
(Types.to_string other) s
|
||||||
|
|
||||||
(* Arithmetic wraps, but a literal that does not fit its type is a typo, not a
|
(* Arithmetic wraps, but a literal that does not fit its type is a typo, not a
|
||||||
wrap — 300 is never what someone meant by a u8. *)
|
wrap — 300 is never what someone meant by a u8. *)
|
||||||
and in_range loc k n =
|
and in_range loc k n =
|
||||||
@ -3742,14 +3769,10 @@ and in_range loc k n =
|
|||||||
|| (Int64.compare n (Int64.neg (Int64.shift_left 1L (bits - 1))) >= 0
|
|| (Int64.compare n (Int64.neg (Int64.shift_left 1L (bits - 1))) >= 0
|
||||||
&& Int64.compare n (Int64.shift_left 1L (bits - 1)) < 0)
|
&& Int64.compare n (Int64.shift_left 1L (bits - 1)) < 0)
|
||||||
else if bits = 64 then
|
else if bits = 64 then
|
||||||
(* A u64 literal is its 64-bit pattern, so anything at or above 2^63
|
(* A literal at or above 2^63 is a [UInt] and never reaches here; see
|
||||||
arrives here as a negative [int64] and is still in range —
|
[wide_literal]. A negative decimal is accepted as a u64's bit pattern,
|
||||||
0xcbf29ce484222325 is a real u64 and not an error, and so is its
|
which is a settled rule. Narrower unsigned types keep the strict
|
||||||
decimal, which the reader reads as the same pattern. The cost is that a
|
check, which is where a typo like 300 for a u8 actually shows up. *)
|
||||||
negative *decimal* literal is accepted as a u64 too, because the
|
|
||||||
reader records only the value and not how it was written. Narrower
|
|
||||||
unsigned types keep the strict check, which is where a typo like 300
|
|
||||||
for a u8 actually shows up. *)
|
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
Int64.compare n 0L >= 0
|
Int64.compare n 0L >= 0
|
||||||
@ -6578,7 +6601,8 @@ and type_named ctx n =
|
|||||||
and vec_new_elem ctx ~want loc args =
|
and vec_new_elem ctx ~want loc args =
|
||||||
let named =
|
let named =
|
||||||
match args with
|
match args with
|
||||||
| { Ast.e = Ast.TypeArg t; _ } :: rest -> Some (resolve ctx.env t, rest)
|
| a :: rest when type_of_expr a <> None ->
|
||||||
|
Some (resolve ctx.env (Option.get (type_of_expr a)), rest)
|
||||||
| { Ast.e = Ast.Var n; _ } :: rest
|
| { Ast.e = Ast.Var n; _ } :: rest
|
||||||
when lookup ctx n = None
|
when lookup ctx n = None
|
||||||
&& (not (Hashtbl.mem ctx.env.globals n))
|
&& (not (Hashtbl.mem ctx.env.globals n))
|
||||||
@ -6596,6 +6620,39 @@ and vec_new_elem ctx ~want loc args =
|
|||||||
"nothing here says what (vec-new) is a Vec of — write the element \
|
"nothing here says what (vec-new) is a Vec of — write the element \
|
||||||
type, as (vec-new i32), or give the binding a type")
|
type, as (vec-new i32), or give the binding a type")
|
||||||
|
|
||||||
|
(* A type written as an argument to vec-new or map-new, read back out of the
|
||||||
|
expression Parse made of it. Only the shapes that cannot be a value there:
|
||||||
|
brackets — an allocator is never an array — or a parenthesised Ptr,
|
||||||
|
Option, Vec, Map, Fn or CFn. A bare name is not one of them, because there
|
||||||
|
it may be an allocator's name; the callers ask about that themselves. *)
|
||||||
|
and type_of_expr (e : Ast.expr) : Ast.texpr option =
|
||||||
|
let mk t = { Ast.t; tloc = e.Ast.loc } in
|
||||||
|
let inner (e : Ast.expr) =
|
||||||
|
match e.Ast.e with
|
||||||
|
| Ast.Var s -> Some { Ast.t = Ast.Tname s; tloc = e.Ast.loc }
|
||||||
|
| _ -> type_of_expr e
|
||||||
|
in
|
||||||
|
let all es =
|
||||||
|
let ts = List.filter_map inner es in
|
||||||
|
if List.length ts = List.length es then Some ts else None
|
||||||
|
in
|
||||||
|
match e.Ast.e with
|
||||||
|
| Ast.TypeArg t -> Some t
|
||||||
|
| Ast.Arr [ x ] -> Option.map (fun t -> mk (Ast.Tslice t)) (inner x)
|
||||||
|
| Ast.Arr [ { Ast.e = Ast.Int n; _ }; x ] ->
|
||||||
|
Option.map (fun t -> mk (Ast.Tarray (Ast.Lint n, t))) (inner x)
|
||||||
|
| Ast.Arr [ { Ast.e = Ast.Var n; _ }; x ] ->
|
||||||
|
Option.map (fun t -> mk (Ast.Tarray (Ast.Lname n, t))) (inner x)
|
||||||
|
| Ast.Call ({ Ast.e = Ast.Var (("Fn" | "CFn") as which); _ },
|
||||||
|
[ { Ast.e = Ast.Arr ps; _ }; r ]) ->
|
||||||
|
(match all ps, inner r with
|
||||||
|
| Some ps, Some r -> Some (mk (Ast.Tfn (which = "Fn", ps, r)))
|
||||||
|
| _ -> None)
|
||||||
|
| Ast.Call ({ Ast.e = Ast.Var (("Ptr" | "Option" | "Vec" | "Map") as c); _ },
|
||||||
|
(_ :: _ as args)) ->
|
||||||
|
Option.map (fun ts -> mk (Ast.Tapp (c, ts))) (all args)
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
(* The key and value types, or the reason this is not a Map. *)
|
(* The key and value types, or the reason this is not a Map. *)
|
||||||
and map_kv loc what (t : Types.t) =
|
and map_kv loc what (t : Types.t) =
|
||||||
match t with
|
match t with
|
||||||
@ -6616,15 +6673,15 @@ and map_new_types ctx ~want loc args =
|
|||||||
(* A type position holds a bare name or a type expression Parse has read
|
(* A type position holds a bare name or a type expression Parse has read
|
||||||
as one, as [vec-new]'s does. *)
|
as one, as [vec-new]'s does. *)
|
||||||
let as_type (a : Ast.expr) =
|
let as_type (a : Ast.expr) =
|
||||||
match a.Ast.e with
|
match a.Ast.e, type_of_expr a with
|
||||||
| Ast.TypeArg t -> Some (resolve ctx.env t)
|
| _, Some t -> Some (resolve ctx.env t)
|
||||||
| Ast.Var n when is_type n -> Some (resolve_name ctx.env ~seen:[] loc n)
|
| Ast.Var n, None when is_type n -> Some (resolve_name ctx.env ~seen:[] loc n)
|
||||||
| _ -> None
|
| _ -> None
|
||||||
in
|
in
|
||||||
match args with
|
match args with
|
||||||
| k :: v :: rest when as_type k <> None && as_type v <> None ->
|
| k :: v :: rest when as_type k <> None && as_type v <> None ->
|
||||||
Option.get (as_type k), Option.get (as_type v), rest
|
Option.get (as_type k), Option.get (as_type v), rest
|
||||||
| { Ast.e = Ast.TypeArg _; _ } :: _ ->
|
| a :: _ when type_of_expr a <> None ->
|
||||||
fail loc
|
fail loc
|
||||||
"(map-new) names a key and no value — write both, as (map-new string \
|
"(map-new) names a key and no value — write both, as (map-new string \
|
||||||
i32), or give the binding a type"
|
i32), or give the binding a type"
|
||||||
@ -8806,6 +8863,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
| Ast.Int n, (Types.Int _ | Types.Float _)
|
| Ast.Int n, (Types.Int _ | Types.Float _)
|
||||||
when Int64.compare n (-2147483648L) < 0
|
when Int64.compare n (-2147483648L) < 0
|
||||||
|| Int64.compare n 2147483647L > 0 -> Some target
|
|| Int64.compare n 2147483647L > 0 -> Some target
|
||||||
|
| Ast.UInt _, (Types.Int _ | Types.Float _) -> Some target
|
||||||
| _ -> None
|
| _ -> None
|
||||||
in
|
in
|
||||||
let a = check ctx ?want (List.hd args) in
|
let a = check ctx ?want (List.hd args) in
|
||||||
@ -9131,7 +9189,7 @@ and generic_call ctx ~want loc name vars pats pret args =
|
|||||||
else is checked on its own terms. *)
|
else is checked on its own terms. *)
|
||||||
let untyped_literal =
|
let untyped_literal =
|
||||||
match a.Ast.e with
|
match a.Ast.e with
|
||||||
| Ast.Int _ | Ast.Float _ | Ast.Byte _ -> true
|
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ -> true
|
||||||
| _ -> false
|
| _ -> false
|
||||||
in
|
in
|
||||||
let a =
|
let a =
|
||||||
@ -9603,7 +9661,7 @@ and binary ctx ?(dyn_ok = false) ?(join = true) name loc ~want args =
|
|||||||
let y_decides =
|
let y_decides =
|
||||||
(is_literal x && not (is_literal y))
|
(is_literal x && not (is_literal y))
|
||||||
|| (match x.Ast.e, y.Ast.e with
|
|| (match x.Ast.e, y.Ast.e with
|
||||||
| (Ast.Int _ | Ast.Byte _), Ast.Float _ -> true
|
| (Ast.Int _ | Ast.UInt _ | Ast.Byte _), Ast.Float _ -> true
|
||||||
| _ -> false)
|
| _ -> false)
|
||||||
in
|
in
|
||||||
(* A form that cannot be checked without being told what is wanted. A
|
(* A form that cannot be checked without being told what is wanted. A
|
||||||
|
|||||||
@ -114,6 +114,9 @@ let rec write (sites : sites) p (f : Form.t) =
|
|||||||
| Form.Kw s -> str TKw s
|
| Form.Kw s -> str TKw s
|
||||||
| Form.Str s -> str TStr s
|
| Form.Str s -> str TStr s
|
||||||
| Form.Int i -> tag TInt; Dynload.poke_i64 p payload i
|
| Form.Int i -> tag TInt; Dynload.poke_i64 p payload i
|
||||||
|
(* A macro's Form has one integer case, so a wide literal crosses as its
|
||||||
|
pattern and comes back as an ordinary [Int]. *)
|
||||||
|
| Form.UInt (i, _) -> tag TInt; Dynload.poke_i64 p payload i
|
||||||
| Form.Float x -> tag TFloat; Dynload.poke_f64 p payload x
|
| Form.Float x -> tag TFloat; Dynload.poke_f64 p payload x
|
||||||
| Form.Byte b -> tag TByte; Dynload.poke_i32 p payload (Int32.of_int b)
|
| Form.Byte b -> tag TByte; Dynload.poke_i32 p payload (Int32.of_int b)
|
||||||
| Form.List xs -> seq TList xs
|
| Form.List xs -> seq TList xs
|
||||||
@ -251,7 +254,7 @@ let rec quote (f : Form.t) : Form.t =
|
|||||||
inner form with form-cons"
|
inner form with form-cons"
|
||||||
| Form.Sym s -> node loc "Sym" "s" (Form.Str s)
|
| Form.Sym s -> node loc "Sym" "s" (Form.Str s)
|
||||||
| Form.Kw s -> node loc "Kw" "s" (Form.Str s)
|
| Form.Kw s -> node loc "Kw" "s" (Form.Str s)
|
||||||
| Form.Int i -> node loc "Int" "i" (Form.Int i)
|
| Form.Int i | Form.UInt (i, _) -> node loc "Int" "i" (Form.Int i)
|
||||||
| Form.Float x -> node loc "Float" "x" (Form.Float x)
|
| Form.Float x -> node loc "Float" "x" (Form.Float x)
|
||||||
| Form.Str s -> node loc "Str" "s" (Form.Str s)
|
| Form.Str s -> node loc "Str" "s" (Form.Str s)
|
||||||
| Form.Byte b -> node loc "Byte" "b" (Form.Int (Int64.of_int b))
|
| Form.Byte b -> node loc "Byte" "b" (Form.Int (Int64.of_int b))
|
||||||
|
|||||||
@ -12,6 +12,10 @@ and value =
|
|||||||
| Sym of string (* foo rl/draw-fps .pos + *)
|
| Sym of string (* foo rl/draw-fps .pos + *)
|
||||||
| Kw of string (* :space :else (leading : dropped) *)
|
| Kw of string (* :space :else (leading : dropped) *)
|
||||||
| Int of int64 (* 42 -1 0xE6B800FF *)
|
| Int of int64 (* 42 -1 0xE6B800FF *)
|
||||||
|
(* An integer written at or above 2^63 — 18446744073709551615, or a hex
|
||||||
|
literal with its top bit set. Its 64-bit pattern and its spelling: only a
|
||||||
|
u64 holds it, and a refusal anywhere else prints the number as written. *)
|
||||||
|
| UInt of int64 * string
|
||||||
| Float of float (* 0.05 *)
|
| Float of float (* 0.05 *)
|
||||||
| Str of string (* "SAND" *)
|
| Str of string (* "SAND" *)
|
||||||
| Byte of int (* \space \0 \( (0..255) *)
|
| Byte of int (* \space \0 \( (0..255) *)
|
||||||
@ -30,6 +34,7 @@ let rec to_string f =
|
|||||||
| Sym s -> s
|
| Sym s -> s
|
||||||
| Kw s -> ":" ^ s
|
| Kw s -> ":" ^ s
|
||||||
| Int i -> Int64.to_string i
|
| Int i -> Int64.to_string i
|
||||||
|
| UInt (_, s) -> s
|
||||||
| Float x -> Printf.sprintf "%g" x
|
| Float x -> Printf.sprintf "%g" x
|
||||||
| Str s -> Printf.sprintf "%S" s
|
| Str s -> Printf.sprintf "%S" s
|
||||||
| Byte b ->
|
| Byte b ->
|
||||||
@ -126,6 +131,7 @@ let rec to_source f =
|
|||||||
| Sym s -> s
|
| Sym s -> s
|
||||||
| Kw s -> ":" ^ s
|
| Kw s -> ":" ^ s
|
||||||
| Int i -> Int64.to_string i
|
| Int i -> Int64.to_string i
|
||||||
|
| UInt (_, s) -> s
|
||||||
| Float x -> float_repr x
|
| Float x -> float_repr x
|
||||||
| Str s -> "\"" ^ escape s ^ "\""
|
| Str s -> "\"" ^ escape s ^ "\""
|
||||||
| Byte b -> byte_repr b
|
| Byte b -> byte_repr b
|
||||||
|
|||||||
@ -223,7 +223,7 @@ let rec rename_expr owned alias bound (e : Ast.expr) : Ast.expr =
|
|||||||
let name n = qualify_name owned alias bound n in
|
let name n = qualify_name owned alias bound n in
|
||||||
let k =
|
let k =
|
||||||
match e.Ast.e with
|
match e.Ast.e with
|
||||||
| Ast.Int _ | Ast.Float _ | Ast.Byte _ | Ast.Str _ | Ast.Kw _
|
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ | Ast.Str _ | Ast.Kw _
|
||||||
| Ast.Quote _ -> e.Ast.e
|
| Ast.Quote _ -> e.Ast.e
|
||||||
| Ast.Var n -> Ast.Var (name n)
|
| Ast.Var n -> Ast.Var (name n)
|
||||||
| Ast.Do body -> Ast.Do (gos body)
|
| Ast.Do body -> Ast.Do (gos body)
|
||||||
@ -754,7 +754,8 @@ let rec expr_uses acc (e : Ast.expr) =
|
|||||||
let go = expr_uses acc in
|
let go = expr_uses acc in
|
||||||
let gos = List.iter go in
|
let gos = List.iter go in
|
||||||
match e.Ast.e with
|
match e.Ast.e with
|
||||||
| Ast.Int _ | Ast.Float _ | Ast.Byte _ | Ast.Str _ | Ast.Kw _ | Ast.Quote _ ->
|
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ | Ast.Str _ | Ast.Kw _
|
||||||
|
| Ast.Quote _ ->
|
||||||
()
|
()
|
||||||
(* The name is not one an import can supply, but the arguments are ordinary
|
(* The name is not one an import can supply, but the arguments are ordinary
|
||||||
expressions and may well use one. *)
|
expressions and may well use one. *)
|
||||||
|
|||||||
31
lib/parse.ml
31
lib/parse.ml
@ -310,6 +310,7 @@ let rec expr (f : Form.t) : Ast.expr =
|
|||||||
let mk e = { Ast.e; loc = f.loc } in
|
let mk e = { Ast.e; loc = f.loc } in
|
||||||
match f.v with
|
match f.v with
|
||||||
| Int i -> mk (Ast.Int i)
|
| Int i -> mk (Ast.Int i)
|
||||||
|
| UInt (i, s) -> mk (Ast.UInt (i, s))
|
||||||
| Float x -> mk (Ast.Float x)
|
| Float x -> mk (Ast.Float x)
|
||||||
| Byte b -> mk (Ast.Byte b)
|
| Byte b -> mk (Ast.Byte b)
|
||||||
| Str s -> mk (Ast.Str s)
|
| Str s -> mk (Ast.Str s)
|
||||||
@ -426,28 +427,26 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
|
|||||||
| _ -> fail f "set is (set place value)")
|
| _ -> fail f "set is (set place value)")
|
||||||
|
|
||||||
(* ── (vec-new [u8]) and (map-new string [u8]) ───────────────────────
|
(* ── (vec-new [u8]) and (map-new string [u8]) ───────────────────────
|
||||||
The type positions of these two take a type expression as well as a bare
|
The type positions of these two take a type expression. Whether this call
|
||||||
name. A bare name is left for the checker, which knows whether it names a
|
is the builtin at all is the checker's to know — a program may define its
|
||||||
type or an allocator; a bracket or a parenthesised type constructor can
|
own [vec-new] — so the arguments are read as ordinary expressions and
|
||||||
only be a type there, so it is read as one now, with [texpr], the reader
|
[Check.type_of_expr] reads a type back out of one when the builtin is
|
||||||
a parameter list's types go through. *)
|
what was called. The one type an expression cannot carry is [()], as in
|
||||||
|
[(Fn [i32] ())], so an argument that is not an expression but is a type
|
||||||
|
is kept as a [TypeArg]. *)
|
||||||
| Sym (("vec-new" | "builtin/vec-new" | "map-new" | "builtin/map-new") as n) ->
|
| Sym (("vec-new" | "builtin/vec-new" | "map-new" | "builtin/map-new") as n) ->
|
||||||
let slots =
|
let slots =
|
||||||
if n = "vec-new" || n = "builtin/vec-new" then 1 else 2
|
if n = "vec-new" || n = "builtin/vec-new" then 1 else 2
|
||||||
in
|
in
|
||||||
let is_type (a : Form.t) =
|
|
||||||
match a.v with
|
|
||||||
| Vec _ -> true
|
|
||||||
| List ({ v = Sym ("Ptr" | "Option" | "Vec" | "Map" | "Fn" | "CFn"); _ }
|
|
||||||
:: _ :: _) -> true
|
|
||||||
| _ -> false
|
|
||||||
in
|
|
||||||
let args =
|
let args =
|
||||||
List.mapi
|
List.mapi
|
||||||
(fun i a ->
|
(fun i (a : Form.t) ->
|
||||||
if i < slots && is_type a then
|
match expr a with
|
||||||
{ Ast.e = Ast.TypeArg (texpr a); loc = a.loc }
|
| e -> e
|
||||||
else expr a)
|
| exception (Loc.Error _ as not_expr) when i < slots ->
|
||||||
|
(match texpr a with
|
||||||
|
| t -> { Ast.e = Ast.TypeArg t; loc = a.loc }
|
||||||
|
| exception Loc.Error _ -> raise not_expr))
|
||||||
args
|
args
|
||||||
in
|
in
|
||||||
mk (Ast.Call (expr head, args))
|
mk (Ast.Call (expr head, args))
|
||||||
|
|||||||
@ -234,10 +234,9 @@ let source = {flan|
|
|||||||
;; LLVM; at this width there is no such case to guard.
|
;; LLVM; at this width there is no such case to guard.
|
||||||
;;
|
;;
|
||||||
;; The multiplier is written in hex, which is how it is written everywhere
|
;; The multiplier is written in hex, which is how it is written everywhere
|
||||||
;; it appears: in decimal it is 12605985483714917081, and a decimal
|
;; it appears: in decimal it is 12605985483714917081. Either spelling is
|
||||||
;; literal that large is refused here because the reader reads one as a
|
;; a u64 literal and nothing else. It is written here rather than given a
|
||||||
;; signed 64-bit number. Hex is read as a bit pattern, and this is a bit
|
;; name of its own: a
|
||||||
;; pattern. It is written here rather than given a name of its own: a
|
|
||||||
;; prelude constant is a name in every program, and this is an
|
;; prelude constant is a name in every program, and this is an
|
||||||
;; implementation number that nothing outside these four lines wants.
|
;; implementation number that nothing outside these four lines wants.
|
||||||
(let [w (* (bit-xor (>> s (+ (>> s 59) 5)) s) 0xAEF17502108EF2D9)]
|
(let [w (* (bit-xor (>> s (+ (>> s 59) 5)) s) 0xAEF17502108EF2D9)]
|
||||||
|
|||||||
@ -144,6 +144,8 @@ let read_number st =
|
|||||||
in
|
in
|
||||||
if is_hex then
|
if is_hex then
|
||||||
match Int64.of_string_opt text with
|
match Int64.of_string_opt text with
|
||||||
|
(* A top bit set is a value at or above 2^63, which only a u64 holds. *)
|
||||||
|
| Some i when Int64.compare i 0L < 0 -> spanned st loc (Form.UInt (i, text))
|
||||||
| Some i -> spanned st loc (Form.Int i)
|
| Some i -> spanned st loc (Form.Int i)
|
||||||
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
|
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
|
||||||
"malformed hex literal %s" text
|
"malformed hex literal %s" text
|
||||||
@ -153,10 +155,8 @@ let read_number st =
|
|||||||
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
|
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
|
||||||
"malformed float literal %s" text
|
"malformed float literal %s" text
|
||||||
else
|
else
|
||||||
(* A decimal above the largest i64 and below 2^64 is read as its 64-bit
|
(* A decimal above the largest i64 and below 2^64 is a [UInt], as a hex
|
||||||
pattern, as a hex literal is, so a u64 constant can be written in
|
literal with its top bit set is: only a u64 holds it. *)
|
||||||
decimal. It therefore arrives negative, and [Check.in_range] makes the
|
|
||||||
same allowance for it that it makes for hex. *)
|
|
||||||
let unsigned () =
|
let unsigned () =
|
||||||
if String.for_all (fun c -> c >= '0' && c <= '9') text then
|
if String.for_all (fun c -> c >= '0' && c <= '9') text then
|
||||||
Int64.of_string_opt ("0u" ^ text)
|
Int64.of_string_opt ("0u" ^ text)
|
||||||
@ -166,7 +166,7 @@ let read_number st =
|
|||||||
| Some i -> spanned st loc (Form.Int i)
|
| Some i -> spanned st loc (Form.Int i)
|
||||||
| None ->
|
| None ->
|
||||||
match unsigned () with
|
match unsigned () with
|
||||||
| Some i -> spanned st loc (Form.Int i)
|
| Some i -> spanned st loc (Form.UInt (i, text))
|
||||||
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
|
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
|
||||||
"malformed integer literal %s" text
|
"malformed integer literal %s" text
|
||||||
|
|
||||||
|
|||||||
14
test/programs/vec-new-shadow.flan
Normal file
14
test/programs/vec-new-shadow.flan
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
;;;; A program's own vec-new is an ordinary function: a bracket passed to it
|
||||||
|
;;;; is an array value, not an element type. builtin/vec-new is still the
|
||||||
|
;;;; builtin and still reads one as a type.
|
||||||
|
|
||||||
|
(defn vec-new [xs [1 i32]] i32 (at xs 0))
|
||||||
|
|
||||||
|
(defn main [] i32
|
||||||
|
(let [x 7
|
||||||
|
v (builtin/vec-new [u8])]
|
||||||
|
(println (vec-new [x]))
|
||||||
|
(push v (bytes-view "ab"))
|
||||||
|
(println (length (at v 0)))
|
||||||
|
(free v))
|
||||||
|
0)
|
||||||
@ -508,6 +508,10 @@ let () =
|
|||||||
"2 3 4 7 9 3\n";
|
"2 3 4 7 9 3\n";
|
||||||
outputs ~x86:true "vec-new takes a type expression, x86"
|
outputs ~x86:true "vec-new takes a type expression, x86"
|
||||||
"programs/vec-new-type.flan" "2 3 4 7 9 3\n";
|
"programs/vec-new-type.flan" "2 3 4 7 9 3\n";
|
||||||
|
(* A program's own vec-new takes its arguments as values; the builtin,
|
||||||
|
reached as builtin/vec-new, still reads a bracket as a type. *)
|
||||||
|
outputs "a vec-new of the program's own" "programs/vec-new-shadow.flan"
|
||||||
|
"7\n2\n";
|
||||||
(* A u64 above 2^63 in decimal, and a cast of a literal too wide for i32. *)
|
(* A u64 above 2^63 in decimal, and a cast of a literal too wide for i32. *)
|
||||||
let u64_out =
|
let u64_out =
|
||||||
"18446744073709551615\n14695981039346656037\n2935910691\n\
|
"18446744073709551615\n14695981039346656037\n2935910691\n\
|
||||||
|
|||||||
@ -94,7 +94,8 @@ let () =
|
|||||||
reads "hex" "0xE6B800FF" "3870818559";
|
reads "hex" "0xE6B800FF" "3870818559";
|
||||||
(* A decimal between 2^63 and 2^64 is its bit pattern, as hex is; one past
|
(* A decimal between 2^63 and 2^64 is its bit pattern, as hex is; one past
|
||||||
2^64, or a negative one past the smallest i64, is still malformed. *)
|
2^64, or a negative one past the smallest i64, is still malformed. *)
|
||||||
reads "u64 decimal" "18446744073709551615" "-1";
|
reads "u64 decimal" "18446744073709551615" "18446744073709551615";
|
||||||
|
reads "hex top bit" "0xFFFFFFFFFFFFFFFF" "0xFFFFFFFFFFFFFFFF";
|
||||||
rejects "decimal past 2^64" "18446744073709551616"
|
rejects "decimal past 2^64" "18446744073709551616"
|
||||||
~needle:"malformed integer literal";
|
~needle:"malformed integer literal";
|
||||||
rejects "negative decimal past i64" "-9223372036854775809"
|
rejects "negative decimal past i64" "-9223372036854775809"
|
||||||
@ -6045,6 +6046,51 @@ let () =
|
|||||||
rejects_check "map-new with a key type expression and no value type"
|
rejects_check "map-new with a key type expression and no value type"
|
||||||
~needle:"(map-new) names a key and no value"
|
~needle:"(map-new) names a key and no value"
|
||||||
"(defn f [] i32 (let [m (map-new [u8])] (free m) 0))";
|
"(defn f [] i32 (let [m (map-new [u8])] (free m) 0))";
|
||||||
|
accepts "vec-new over a function type returning unit"
|
||||||
|
"(defn f [] i32 (let [v (vec-new (Fn [i32] ()))] (free v) 0))";
|
||||||
|
accepts "a program's own vec-new takes an array literal"
|
||||||
|
"(defn vec-new [xs [3 i32]] i32 (at xs 2)) \
|
||||||
|
(defn f [] i32 (vec-new [1 2 3]))";
|
||||||
|
accepts "builtin/vec-new over a type expression"
|
||||||
|
"(defn f [] i32 (let [v (builtin/vec-new [u8])] (free v) 0))";
|
||||||
|
|
||||||
|
(* An integer written at or above 2^63 — decimal, or hex with the top bit
|
||||||
|
set — is a u64 and nothing else, and a refusal prints it as written. *)
|
||||||
|
accepts "a wide decimal at u64"
|
||||||
|
"(defconst a u64 18446744073709551615) (defonce b u64 0xFFFFFFFFFFFFFFFF) \
|
||||||
|
(defn f [x u64] u64 (+ x 9223372036854775808)) \
|
||||||
|
(defn g [] f64 (f64 (u64 12345678901234567890)))";
|
||||||
|
accepts "a negative decimal is still a u64 bit pattern"
|
||||||
|
"(defconst a u64 -1)";
|
||||||
|
rejects_check "a wide decimal with nothing to say u64"
|
||||||
|
~needle:"18446744073709551615 does not fit in i32, the type an integer \
|
||||||
|
literal takes when nothing says otherwise — write (u64 \
|
||||||
|
18446744073709551615)"
|
||||||
|
"(defn f [] () (println 18446744073709551615))";
|
||||||
|
rejects_check "a wide decimal cast to i64"
|
||||||
|
~needle:"18446744073709551615 does not fit in i64"
|
||||||
|
"(defn f [] i64 (i64 18446744073709551615))";
|
||||||
|
rejects_check "a wide decimal cast to f64"
|
||||||
|
~needle:"write (f64 (u64 18446744073709551615))"
|
||||||
|
"(defn f [] f64 (f64 18446744073709551615))";
|
||||||
|
rejects_check "a wide decimal constant at i32"
|
||||||
|
~needle:"18446744073709551615 does not fit in i32"
|
||||||
|
"(defconst x i32 18446744073709551615)";
|
||||||
|
rejects_check "a wide decimal constant at f64"
|
||||||
|
~needle:"write (f64 (u64 12345678901234567890))"
|
||||||
|
"(defconst x f64 12345678901234567890)";
|
||||||
|
rejects_check "a wide decimal argument to an i8 parameter"
|
||||||
|
~needle:"18446744073709551600 does not fit in i8"
|
||||||
|
"(defn g [a i8] i8 a) (defn f [] i8 (g 18446744073709551600))";
|
||||||
|
rejects_check "a wide decimal operand prints as written"
|
||||||
|
~needle:"9223372036854775808 does not fit in i32"
|
||||||
|
"(defn f [] () (println (+ 1 9223372036854775808)))";
|
||||||
|
rejects_check "a hex literal with the top bit set at i32"
|
||||||
|
~needle:"0xFFFFFFFFFFFFFFFF does not fit in i32"
|
||||||
|
"(defconst x i32 0xFFFFFFFFFFFFFFFF)";
|
||||||
|
rejects_check "a hex literal with the top bit set at i64"
|
||||||
|
~needle:"0xFFFFFFFFFFFFFFFF does not fit in i64"
|
||||||
|
"(defconst x i64 0xFFFFFFFFFFFFFFFF)";
|
||||||
(* And a sigil on a name nothing binds is answered as the unbound variable
|
(* And a sigil on a name nothing binds is answered as the unbound variable
|
||||||
it is, rather than as a missing element type — with the names that *are*
|
it is, rather than as a missing element type — with the names that *are*
|
||||||
bound, because inside a signature that introduces one the mistake is
|
bound, because inside a signature that introduces one the mistake is
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user