A defclass slot may declare a type that every store into it checks, and set writes a declared slot
This commit is contained in:
parent
8b4c6f81df
commit
2682214499
19
lib/ast.ml
19
lib/ast.ml
@ -199,6 +199,11 @@ and place =
|
|||||||
| Pfield of expr * string (* (set (.hp e) v) *)
|
| Pfield of expr * string (* (set (.hp e) v) *)
|
||||||
| Pindex of expr * expr list (* (set (at grid r c) v) *)
|
| Pindex of expr * expr list (* (set (at grid r c) v) *)
|
||||||
| Pderef of expr (* (set (deref p) v) *)
|
| Pderef of expr (* (set (deref p) v) *)
|
||||||
|
(* (set (get inst :slot) v) — a class instance's declared slot. A map has
|
||||||
|
no such place: an absent key has no location, and [put] is how one is
|
||||||
|
written. Which of the two a value is, is known only at run time, so
|
||||||
|
this is a runtime store that refuses a plain map. *)
|
||||||
|
| Pslot of expr * expr
|
||||||
|
|
||||||
and arm = { pat : pattern; body : expr list; aloc : Loc.t }
|
and arm = { pat : pattern; body : expr list; aloc : Loc.t }
|
||||||
|
|
||||||
@ -282,8 +287,10 @@ and decl_kind =
|
|||||||
| Defvar of string * texpr option * init * reinit
|
| Defvar of string * texpr option * init * reinit
|
||||||
| Defconst of string * texpr option * expr
|
| Defconst of string * texpr option * expr
|
||||||
(* ── The dyn side's classes and generic functions ──────────────────
|
(* ── The dyn side's classes and generic functions ──────────────────
|
||||||
None of these four reaches [Check]. [Classes.expand] turns the whole set
|
None of these four reaches [Check]'s signature pass. [Classes.expand]
|
||||||
into ordinary [Defn]s before pass one collects anything, the way [Shim]
|
turns the generic forms into ordinary [Defn]s before pass one collects
|
||||||
|
anything, and [Check.pair_decls] turns a [Defclass] into its constructor
|
||||||
|
once its slot vector can be paired, the way [Shim]
|
||||||
already turns a [DeclareC] into a [Declare] plus a [Defn]: a class is a
|
already turns a [DeclareC] into a [Declare] plus a [Defn]: a class is a
|
||||||
constructor, and a generic function is one function whose body is a
|
constructor, and a generic function is one function whose body is a
|
||||||
dispatch over the methods written for it.
|
dispatch over the methods written for it.
|
||||||
@ -293,8 +300,11 @@ and decl_kind =
|
|||||||
anywhere in the file, or arrive at a reload long after the generic did,
|
anywhere in the file, or arrive at a reload long after the generic did,
|
||||||
and a macro sees one form. *)
|
and a macro sees one form. *)
|
||||||
|
|
||||||
(* (defclass point [x y]) — the slot names, in constructor order. *)
|
(* (defclass point [x y]) or (defclass state [pause bool step bool]) — the
|
||||||
| Defclass of string * (string * Loc.t) list
|
slot vector, in constructor order, left unpaired for the reason a
|
||||||
|
[defn]'s is: [[x y]] is two slots or one slot [x] of type [y] depending
|
||||||
|
on whether [y] names a type. [Check.pair_decls] pairs it. *)
|
||||||
|
| Defclass of string * pitem list
|
||||||
(* (defgeneric area [self] dyn) — CLOS's class dispatch: the dispatch value
|
(* (defgeneric area [self] dyn) — CLOS's class dispatch: the dispatch value
|
||||||
is the shape tag of the first argument. The parameter vector and the
|
is the shape tag of the first argument. The parameter vector and the
|
||||||
return slot are a [defn]'s, and there is no body. *)
|
return slot are a [defn]'s, and there is no body. *)
|
||||||
@ -413,6 +423,7 @@ let map_children f (e : expr) : expr =
|
|||||||
| Pfield (x, n) -> Pfield (ex x, n)
|
| Pfield (x, n) -> Pfield (ex x, n)
|
||||||
| Pindex (x, is) -> Pindex (ex x, List.map ex is)
|
| Pindex (x, is) -> Pindex (ex x, List.map ex is)
|
||||||
| Pderef x -> Pderef (ex x)
|
| Pderef x -> Pderef (ex x)
|
||||||
|
| Pslot (x, k) -> Pslot (ex x, ex k)
|
||||||
in
|
in
|
||||||
let kind =
|
let kind =
|
||||||
match e.e with
|
match e.e with
|
||||||
|
|||||||
111
lib/check.ml
111
lib/check.ml
@ -181,6 +181,13 @@ type env = {
|
|||||||
flag is what lets [resolve_name] say the honest thing in each place
|
flag is what lets [resolve_name] say the honest thing in each place
|
||||||
instead of a suggestion that cannot be followed. *)
|
instead of a suggestion that cannot be followed. *)
|
||||||
mutable in_field : bool;
|
mutable in_field : bool;
|
||||||
|
(* Every [defclass], by name: its slots in constructor order, each with the
|
||||||
|
type a value stored in it must have — [Types.Dyn] for a slot written
|
||||||
|
with no type. Filled by [pair_decls], which is where a slot vector is
|
||||||
|
first readable. The type is a declaration about the values and not a
|
||||||
|
layout: an instance is a dyn map whatever this says, and what reads it
|
||||||
|
is [class_spec], which is what the runtime checks a store against. *)
|
||||||
|
classes : (string, (string * Types.t) list) Hashtbl.t;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_env () = {
|
let new_env () = {
|
||||||
@ -210,6 +217,7 @@ let new_env () = {
|
|||||||
tvpreds = [];
|
tvpreds = [];
|
||||||
chain = [];
|
chain = [];
|
||||||
in_field = false;
|
in_field = false;
|
||||||
|
classes = Hashtbl.create 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
(* Where a named type was declared, and what it has, as a note.
|
(* Where a named type was declared, and what it has, as a note.
|
||||||
@ -1491,6 +1499,40 @@ let pair_params env (items : Ast.pitem list) : Ast.field list =
|
|||||||
in
|
in
|
||||||
go items
|
go items
|
||||||
|
|
||||||
|
(* A class slot's type, resolved and held to the set a stored dyn value can
|
||||||
|
be checked against: its tag says bool, int, float or text and nothing
|
||||||
|
finer, so those are the types there are. A narrower integer is a range on
|
||||||
|
top of the int tag. Everything else a type can be — a struct, a Vec, a
|
||||||
|
pointer — does not cross into dyn at all, so a slot of one could never be
|
||||||
|
written. *)
|
||||||
|
let slot_type env cls (f : Ast.field) : Types.t =
|
||||||
|
let t = resolve env f.Ast.fty in
|
||||||
|
match t with
|
||||||
|
| Types.Dyn | Types.Bool | Types.Int _ | Types.Float _ | Types.String -> t
|
||||||
|
| other ->
|
||||||
|
Loc.failk "check/slot-type" f.Ast.fty.Ast.tloc
|
||||||
|
"the slot %s of %s is declared %s, and a class slot holds a dyn value, \
|
||||||
|
which can be checked as bool, an integer type, f32, f64 or string. \
|
||||||
|
Write one of those, or leave the type out and the slot holds any dyn \
|
||||||
|
value: [%s]"
|
||||||
|
f.Ast.fname cls (Types.to_string other) f.Ast.fname
|
||||||
|
|
||||||
|
(* What the runtime is told a class is: one line per slot, in constructor
|
||||||
|
order, the slot's name and then its type's name after a space — no type
|
||||||
|
for a dyn slot. The same string goes to [flan_dyn_map_new_class] from the
|
||||||
|
constructor and to [flan_dyn_class_def] from a reload, so the two cannot
|
||||||
|
describe one class differently. *)
|
||||||
|
let class_spec_of (slots : (string * Types.t) list) =
|
||||||
|
String.concat "\n"
|
||||||
|
(List.map
|
||||||
|
(fun (n, t) ->
|
||||||
|
match t with
|
||||||
|
| Types.Dyn -> n
|
||||||
|
| t -> n ^ " " ^ Types.to_string t)
|
||||||
|
slots)
|
||||||
|
|
||||||
|
let class_slots env n = Hashtbl.find_opt env.classes n
|
||||||
|
|
||||||
(* Every [defn] in the program, with its parameter vector paired. Run as a pass
|
(* Every [defn] in the program, with its parameter vector paired. Run as a pass
|
||||||
of its own, after the type names are registered and before any signature is
|
of its own, after the type names are registered and before any signature is
|
||||||
resolved, so that nothing downstream ever sees an unpaired one. *)
|
resolved, so that nothing downstream ever sees an unpaired one. *)
|
||||||
@ -1503,6 +1545,18 @@ let pair_decls env (decls : Ast.decl list) : Ast.decl list =
|
|||||||
List.map
|
List.map
|
||||||
(fun (d : Ast.decl) ->
|
(fun (d : Ast.decl) ->
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
|
(* A class's slot vector is paired here and nowhere earlier, for the
|
||||||
|
reason a [defn]'s is, and its constructor is written from the
|
||||||
|
pairs — [Classes.expand] left the declaration as it was for exactly
|
||||||
|
this. *)
|
||||||
|
| Ast.Defclass (n, items) ->
|
||||||
|
let slots = pair_params env items in
|
||||||
|
Hashtbl.replace env.classes n
|
||||||
|
(List.map
|
||||||
|
(fun (f : Ast.field) ->
|
||||||
|
(f.Ast.fname, slot_type env n f))
|
||||||
|
slots);
|
||||||
|
Classes.constructor n slots d.Ast.dloc
|
||||||
| Ast.Defn f -> { d with Ast.d = Ast.Defn (fn f) }
|
| Ast.Defn f -> { d with Ast.d = Ast.Defn (fn f) }
|
||||||
| Ast.Declare (f, c) -> { d with Ast.d = Ast.Declare (fn f, c) }
|
| Ast.Declare (f, c) -> { d with Ast.d = Ast.Declare (fn f, c) }
|
||||||
| Ast.DeclareC (f, c) -> { d with Ast.d = Ast.DeclareC (fn f, c) }
|
| Ast.DeclareC (f, c) -> { d with Ast.d = Ast.DeclareC (fn f, c) }
|
||||||
@ -3450,10 +3504,14 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
|||||||
| Ast.MapLit (tag, kvs) ->
|
| Ast.MapLit (tag, kvs) ->
|
||||||
let m = fresh_slot ctx Types.Dyn in
|
let m = fresh_slot ctx Types.Dyn in
|
||||||
let mval = mk loc Types.Dyn (Tast.Local m) in
|
let mval = mk loc Types.Dyn (Tast.Local m) in
|
||||||
|
(* A class's constructor stores through [flan_dyn_slot_init], which is
|
||||||
|
the plain store plus the slot's type check, worded for the
|
||||||
|
constructor rather than for a [put] nobody wrote. *)
|
||||||
|
let store = if tag = None then "flan_dyn_map_set" else "flan_dyn_slot_init" in
|
||||||
let sets =
|
let sets =
|
||||||
List.map
|
List.map
|
||||||
(fun (k, v) ->
|
(fun (k, v) ->
|
||||||
rt loc Types.Unit "flan_dyn_map_set"
|
rt loc Types.Unit store
|
||||||
[ mval; check ctx ~want:Types.Dyn k;
|
[ mval; check ctx ~want:Types.Dyn k;
|
||||||
check ctx ~want:Types.Dyn v ])
|
check ctx ~want:Types.Dyn v ])
|
||||||
kvs
|
kvs
|
||||||
@ -3466,8 +3524,19 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
|||||||
match tag with
|
match tag with
|
||||||
| None -> rt loc Types.Dyn "flan_dyn_map_new" []
|
| None -> rt loc Types.Dyn "flan_dyn_map_new" []
|
||||||
| Some cls ->
|
| Some cls ->
|
||||||
|
(* The class's slots and their types ride along, so the first
|
||||||
|
instance built registers the class with the runtime and every
|
||||||
|
store after it — this literal's own included — is checked. A
|
||||||
|
class registered already, by an earlier instance or by a reload,
|
||||||
|
keeps what it has: redefining one is a reload's business. *)
|
||||||
|
let spec =
|
||||||
|
match Hashtbl.find_opt ctx.env.classes cls with
|
||||||
|
| Some slots -> class_spec_of slots
|
||||||
|
| None -> ""
|
||||||
|
in
|
||||||
rt loc Types.Dyn "flan_dyn_map_new_class"
|
rt loc Types.Dyn "flan_dyn_map_new_class"
|
||||||
[ rt loc Types.Dyn "flan_dyn_kw" [ mk loc Types.String (Tast.Str cls) ] ]
|
[ rt loc Types.Dyn "flan_dyn_kw" [ mk loc Types.String (Tast.Str cls) ];
|
||||||
|
mk loc Types.String (Tast.Str spec) ]
|
||||||
in
|
in
|
||||||
expect ctx loc ~want
|
expect ctx loc ~want
|
||||||
(mk loc Types.Dyn (Tast.Let ([ (m, empty) ], sets @ [ mval ])))
|
(mk loc Types.Dyn (Tast.Let ([ (m, empty) ], sets @ [ mval ])))
|
||||||
@ -3586,6 +3655,23 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
|||||||
let v = check ctx ~want:pty v in
|
let v = check ctx ~want:pty v in
|
||||||
expect ctx loc ~want (mk loc Types.Unit (Tast.Set (p, v)))
|
expect ctx loc ~want (mk loc Types.Unit (Tast.Set (p, v)))
|
||||||
end
|
end
|
||||||
|
(* (set (get inst :slot) x) — a class instance's declared slot. A call and
|
||||||
|
not a place for [flan_dyn_set_at]'s reason: the runtime has to look at
|
||||||
|
the value to know it is an instance, which of its slots the key names,
|
||||||
|
and whether [x] fits the type that slot was declared with, and it traps
|
||||||
|
on each with a sentence of its own. A typed map has no such place; its
|
||||||
|
entries are written with [put]. *)
|
||||||
|
| Ast.Set (Ast.Pslot (target, k), v) ->
|
||||||
|
let target = check ctx target in
|
||||||
|
if target.Tast.ty <> Types.Dyn then
|
||||||
|
fail loc
|
||||||
|
"(get m k) is a place only on a class instance, and this is %s. A \
|
||||||
|
map's entries are written with (put m k v)"
|
||||||
|
(Types.to_string target.Tast.ty);
|
||||||
|
let k = check ctx ~want:Types.Dyn k in
|
||||||
|
let v = check ctx ~want:Types.Dyn v in
|
||||||
|
expect ctx loc ~want
|
||||||
|
(rt loc Types.Unit "flan_dyn_slot_set" [ target; k; v; here loc ])
|
||||||
| Ast.Set (p, v) ->
|
| Ast.Set (p, v) ->
|
||||||
let p, pty = check_place ctx loc p in
|
let p, pty = check_place ctx loc p in
|
||||||
let v = check ctx ~want:pty v in
|
let v = check ctx ~want:pty v in
|
||||||
@ -6187,6 +6273,13 @@ and check_place ctx loc (p : Ast.place) : Tast.place * Types.t =
|
|||||||
| Types.Ptr t -> Tast.Pderef target, t
|
| Types.Ptr t -> Tast.Pderef target, t
|
||||||
| other ->
|
| other ->
|
||||||
fail loc "deref takes a (Ptr T), found %s" (Types.to_string other))
|
fail loc "deref takes a (Ptr T), found %s" (Types.to_string other))
|
||||||
|
(* Only [set] writes a class slot, and it has its own arm above. A slot
|
||||||
|
lives in a map the collector may move entries of, so it has no address
|
||||||
|
to hand out. *)
|
||||||
|
| Ast.Pslot _ ->
|
||||||
|
fail loc
|
||||||
|
"a class slot (get inst :slot) is written with set and has no address. \
|
||||||
|
Read it into a local with let"
|
||||||
|
|
||||||
(* An index or a slice bound that is a literal is known now, so it is an error
|
(* An index or a slice bound that is a literal is known now, so it is an error
|
||||||
now rather than a trap later. Only literals: a [defconst] is a global in the
|
now rather than a trap later. Only literals: a [defconst] is a global in the
|
||||||
@ -7829,12 +7922,14 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
let target = check ctx target in
|
let target = check ctx target in
|
||||||
(* A put into a dyn map is a call and nothing else, the way a push into
|
(* A put into a dyn map is a call and nothing else, the way a push into
|
||||||
a dyn vec is: the runtime owns the storage, so there is no guard, no
|
a dyn vec is: the runtime owns the storage, so there is no guard, no
|
||||||
restart and no region check. An equal key's value is replaced. *)
|
restart and no region check. An equal key's value is replaced. The
|
||||||
|
site rides along for the one refusal a put can meet, a class
|
||||||
|
instance's typed slot. *)
|
||||||
if target.Tast.ty = Types.Dyn then
|
if target.Tast.ty = Types.Dyn then
|
||||||
expect ctx loc ~want
|
expect ctx loc ~want
|
||||||
(rt loc Types.Unit "flan_dyn_map_set"
|
(rt loc Types.Unit "flan_dyn_map_put"
|
||||||
[ target; check ctx ~want:Types.Dyn k;
|
[ target; check ctx ~want:Types.Dyn k;
|
||||||
check ctx ~want:Types.Dyn v ])
|
check ctx ~want:Types.Dyn v; here loc ])
|
||||||
else begin
|
else begin
|
||||||
let kt, vt = map_kv loc "put" target.Tast.ty in
|
let kt, vt = map_kv loc "put" target.Tast.ty in
|
||||||
let k = check ctx ~want:kt k in
|
let k = check ctx ~want:kt k in
|
||||||
@ -10845,7 +10940,11 @@ let collect env (decls : Ast.decl list) =
|
|||||||
driver that assembled a declaration list and skipped that pass would
|
driver that assembled a declaration list and skipped that pass would
|
||||||
otherwise get a missing name from wherever the constructor was
|
otherwise get a missing name from wherever the constructor was
|
||||||
called, with nothing pointing here. *)
|
called, with nothing pointing here. *)
|
||||||
| Ast.Defclass (n, _) | Ast.Defgeneric { Ast.name = n; _ }
|
| Ast.Defclass (n, _) ->
|
||||||
|
fail loc
|
||||||
|
"internal: the class %s reached the checker unpaired — \
|
||||||
|
pair_decls writes its constructor, and did not run" n
|
||||||
|
| Ast.Defgeneric { Ast.name = n; _ }
|
||||||
| Ast.Defmulti { Ast.name = n; _ } ->
|
| Ast.Defmulti { Ast.name = n; _ } ->
|
||||||
fail loc
|
fail loc
|
||||||
"internal: %s reached the checker unexpanded — Classes.expand did \
|
"internal: %s reached the checker unexpanded — Classes.expand did \
|
||||||
|
|||||||
@ -4,9 +4,11 @@
|
|||||||
[(defclass point [x y])] is a constructor. [(defgeneric area [self] dyn)]
|
[(defclass point [x y])] is a constructor. [(defgeneric area [self] dyn)]
|
||||||
and [(defmulti describe [x] dyn (get x :kind))] are each one function whose
|
and [(defmulti describe [x] dyn (get x :kind))] are each one function whose
|
||||||
body is a dispatch, and [(defmethod area point [p] ...)] is a branch of
|
body is a dispatch, and [(defmethod area point [p] ...)] is a branch of
|
||||||
one. Nothing below this pass knows any of the four forms exists: what it
|
one. What it writes is [defn]s, and they are checked, emitted, rooted,
|
||||||
writes is [defn]s, and they are checked, emitted, rooted, redefined and
|
redefined and inspected as any other function is. The one exception is
|
||||||
inspected as any other function is.
|
[defclass], which passes through untouched: its slot vector reads like a
|
||||||
|
[defn]'s and cannot be paired until every type name is known, so
|
||||||
|
[Check.pair_decls] pairs it and calls [constructor] below.
|
||||||
|
|
||||||
**Why a pass and not a macro.** A macro sees one form. This needs the
|
**Why a pass and not a macro.** A macro sees one form. This needs the
|
||||||
whole declaration list, because a method may be written anywhere — above
|
whole declaration list, because a method may be written anywhere — above
|
||||||
@ -65,23 +67,7 @@ let collect (decls : Ast.decl list) =
|
|||||||
List.iter
|
List.iter
|
||||||
(fun (d : Ast.decl) ->
|
(fun (d : Ast.decl) ->
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
| Ast.Defclass (n, slots) ->
|
| Ast.Defclass (n, _) -> Hashtbl.replace classes n d.Ast.dloc
|
||||||
(* Two slots of one name would write one entry and read one value,
|
|
||||||
and the constructor would take two arguments for it. The duplicate
|
|
||||||
parameter that falls out of it is refused by the checker anyway;
|
|
||||||
this says which declaration it came from. *)
|
|
||||||
let seen = Hashtbl.create 8 in
|
|
||||||
List.iter
|
|
||||||
(fun (s, sloc) ->
|
|
||||||
if Hashtbl.mem seen s then
|
|
||||||
Loc.failk "check/duplicate-slot" sloc
|
|
||||||
"%s names the slot %s twice. A slot is a key in the \
|
|
||||||
instance's map, so the second would replace the first and \
|
|
||||||
the constructor would take an argument that goes nowhere"
|
|
||||||
n s;
|
|
||||||
Hashtbl.replace seen s ())
|
|
||||||
slots;
|
|
||||||
Hashtbl.replace classes n d.Ast.dloc
|
|
||||||
| Ast.Defgeneric fn ->
|
| Ast.Defgeneric fn ->
|
||||||
Hashtbl.replace generics fn.Ast.name
|
Hashtbl.replace generics fn.Ast.name
|
||||||
{ gkind = `Class; gfn = fn; gloc = d.Ast.dloc; gms = [] }
|
{ gkind = `Class; gfn = fn; gloc = d.Ast.dloc; gms = [] }
|
||||||
@ -162,14 +148,37 @@ let collect (decls : Ast.decl list) =
|
|||||||
omitted slot meaning nil — is deferred, and so is refusing an unknown slot
|
omitted slot meaning nil — is deferred, and so is refusing an unknown slot
|
||||||
at [(get p :z)]. Both are recorded in TODO.org, "Class features deferred,
|
at [(get p :z)]. Both are recorded in TODO.org, "Class features deferred,
|
||||||
each with its reason". *)
|
each with its reason". *)
|
||||||
let constructor n slots loc : Ast.decl =
|
let constructor n (slots : Ast.field list) loc : Ast.decl =
|
||||||
|
(* Two slots of one name would write one entry and read one value, and the
|
||||||
|
constructor would take two arguments for it. The duplicate parameter
|
||||||
|
that falls out of it is refused by the checker anyway; this says which
|
||||||
|
declaration it came from. *)
|
||||||
|
let seen = Hashtbl.create 8 in
|
||||||
|
List.iter
|
||||||
|
(fun (f : Ast.field) ->
|
||||||
|
if Hashtbl.mem seen f.Ast.fname then
|
||||||
|
Loc.failk "check/duplicate-slot" f.Ast.floc
|
||||||
|
"%s names the slot %s twice. A slot is a key in the instance's \
|
||||||
|
map, so the second would replace the first and the constructor \
|
||||||
|
would take an argument that goes nowhere"
|
||||||
|
n f.Ast.fname;
|
||||||
|
Hashtbl.replace seen f.Ast.fname ())
|
||||||
|
slots;
|
||||||
|
(* Every parameter is dyn whatever its slot's type. The type is checked
|
||||||
|
where the value is stored — the constructor's own stores included — so
|
||||||
|
a caller holding a dyn passes it as it is, and a caller holding an i32
|
||||||
|
boxes it; neither has to convert to the slot's type first. *)
|
||||||
let params =
|
let params =
|
||||||
List.map
|
List.map
|
||||||
(fun (s, sloc) -> { Ast.fname = s; fty = dyn_at sloc; floc = sloc })
|
(fun (f : Ast.field) ->
|
||||||
|
{ Ast.fname = f.Ast.fname; fty = dyn_at f.Ast.floc; floc = f.Ast.floc })
|
||||||
slots
|
slots
|
||||||
in
|
in
|
||||||
let pairs =
|
let pairs =
|
||||||
List.map (fun (s, sloc) -> (ex sloc (Ast.Kw s), ex sloc (Ast.Var s))) slots
|
List.map
|
||||||
|
(fun (f : Ast.field) ->
|
||||||
|
(ex f.Ast.floc (Ast.Kw f.Ast.fname), ex f.Ast.floc (Ast.Var f.Ast.fname)))
|
||||||
|
slots
|
||||||
in
|
in
|
||||||
{ Ast.d =
|
{ Ast.d =
|
||||||
Ast.Defn
|
Ast.Defn
|
||||||
@ -343,7 +352,9 @@ let expand (decls : Ast.decl list) : Ast.decl list =
|
|||||||
List.filter_map
|
List.filter_map
|
||||||
(fun (d : Ast.decl) ->
|
(fun (d : Ast.decl) ->
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
| Ast.Defclass (n, slots) -> Some (constructor n slots d.Ast.dloc)
|
(* Kept: its slot vector cannot be paired until every type name is
|
||||||
|
known, so [Check.pair_decls] writes the constructor. *)
|
||||||
|
| Ast.Defclass _ -> Some d
|
||||||
| Ast.Defgeneric fn | Ast.Defmulti fn ->
|
| Ast.Defgeneric fn | Ast.Defmulti fn ->
|
||||||
Some (dispatcher (Hashtbl.find generics fn.Ast.name))
|
Some (dispatcher (Hashtbl.find generics fn.Ast.name))
|
||||||
(* Gone: its body is inside its generic's dispatch. *)
|
(* Gone: its body is inside its generic's dispatch. *)
|
||||||
|
|||||||
26
lib/dev.ml
26
lib/dev.ml
@ -1422,17 +1422,29 @@ let defs t =
|
|||||||
[M-.] on a prelude macro from "the prelude is not a file on disk" into a
|
[M-.] on a prelude macro from "the prelude is not a file on disk" into a
|
||||||
shrug about the daemon having no location. *)
|
shrug about the daemon having no location. *)
|
||||||
let macro_locs = Hashtbl.create 16 in
|
let macro_locs = Hashtbl.create 16 in
|
||||||
(* The classes, off the session's declarations: [Classes.expand] turns a
|
(* The classes: where each was written, off the session's declarations,
|
||||||
[defclass] into its constructor [defn] before the checker runs, so the
|
and its slots as the checker paired them, off its environment — the
|
||||||
class is not in [Tast.program] or the checker's environment, and the
|
slot vector is unreadable before pairing. The constructor the checker
|
||||||
declarations are the one place that still has it. Its constructor is
|
wrote is dropped from the [fn] rows for the macro rows' reason: one name,
|
||||||
dropped from the [fn] rows for the macro rows' reason: one name, one row,
|
one row, and [class] is what was written. *)
|
||||||
and [class] is what was written. *)
|
|
||||||
let classes =
|
let classes =
|
||||||
List.filter_map
|
List.filter_map
|
||||||
(fun (d : Ast.decl) ->
|
(fun (d : Ast.decl) ->
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
| Ast.Defclass (n, slots) -> Some (n, List.map fst slots, d.Ast.dloc)
|
| Ast.Defclass (n, _) ->
|
||||||
|
let slots =
|
||||||
|
Option.value ~default:[]
|
||||||
|
(Check.class_slots t.session.Session.env n)
|
||||||
|
in
|
||||||
|
Some
|
||||||
|
(n,
|
||||||
|
List.map
|
||||||
|
(fun (s, ty) ->
|
||||||
|
match ty with
|
||||||
|
| Types.Dyn -> s
|
||||||
|
| ty -> s ^ " " ^ Types.to_string ty)
|
||||||
|
slots,
|
||||||
|
d.Ast.dloc)
|
||||||
| _ -> None)
|
| _ -> None)
|
||||||
t.session.Session.decls
|
t.session.Session.decls
|
||||||
in
|
in
|
||||||
|
|||||||
@ -4370,7 +4370,10 @@ declare i64 @flan_dyn_from_bool(i32)
|
|||||||
declare i64 @flan_dyn_from_bytes(ptr, i64)
|
declare i64 @flan_dyn_from_bytes(ptr, i64)
|
||||||
declare i64 @flan_dyn_vec_new()
|
declare i64 @flan_dyn_vec_new()
|
||||||
declare i64 @flan_dyn_map_new()
|
declare i64 @flan_dyn_map_new()
|
||||||
declare i64 @flan_dyn_map_new_class(i64)
|
declare i64 @flan_dyn_map_new_class(i64, ptr, i64)
|
||||||
|
declare void @flan_dyn_slot_set(i64, i64, i64, ptr, i64)
|
||||||
|
declare void @flan_dyn_slot_init(i64, i64, i64)
|
||||||
|
declare void @flan_dyn_map_put(i64, i64, i64, ptr, i64)
|
||||||
declare i64 @flan_dyn_class_of(i64)
|
declare i64 @flan_dyn_class_of(i64)
|
||||||
declare void @flan_dyn_class_def(i64, ptr, i64)
|
declare void @flan_dyn_class_def(i64, ptr, i64)
|
||||||
declare i64 @flan_dyn_kw(ptr, i64)
|
declare i64 @flan_dyn_kw(ptr, i64)
|
||||||
|
|||||||
28
lib/load.ml
28
lib/load.ml
@ -390,6 +390,7 @@ and rename_place owned alias bound (p : Ast.place) : Ast.place =
|
|||||||
| Ast.Pfield (t, f) -> Ast.Pfield (go t, f)
|
| Ast.Pfield (t, f) -> Ast.Pfield (go t, f)
|
||||||
| Ast.Pindex (t, idx) -> Ast.Pindex (go t, List.map go idx)
|
| Ast.Pindex (t, idx) -> Ast.Pindex (go t, List.map go idx)
|
||||||
| Ast.Pderef t -> Ast.Pderef (go t)
|
| Ast.Pderef t -> Ast.Pderef (go t)
|
||||||
|
| Ast.Pslot (t, k) -> Ast.Pslot (go t, go k)
|
||||||
|
|
||||||
let rename_field owned alias (f : Ast.field) : Ast.field =
|
let rename_field owned alias (f : Ast.field) : Ast.field =
|
||||||
{ f with Ast.fty = rename_texpr owned alias f.Ast.fty }
|
{ f with Ast.fty = rename_texpr owned alias f.Ast.fty }
|
||||||
@ -501,12 +502,17 @@ let qualify_decl owned alias (d : Ast.decl) : Ast.decl =
|
|||||||
and the rename is the ordinary one: the declared name, plus whatever
|
and the rename is the ordinary one: the declared name, plus whatever
|
||||||
inside them is a name of this package.
|
inside them is a name of this package.
|
||||||
|
|
||||||
A class's slots are not renamed. They are keywords in the map the
|
A class's slot names are not renamed. They are keywords in the map the
|
||||||
constructor builds, and a keyword belongs to nobody — the same line the
|
constructor builds, and a keyword belongs to nobody — the same line the
|
||||||
[MapLit] arm above takes about a map literal's keys. The *class's* name
|
[MapLit] arm above takes about a map literal's keys. A slot's *type* is
|
||||||
is qualified, so [pkg/point] is what an instance's shape tag reads and
|
a type like any other, and the vector is unpaired, so it goes through
|
||||||
two packages' [point] classes are two classes. *)
|
[rename_pitem] as a [defn]'s does: a bare symbol the package owns is a
|
||||||
| Ast.Defclass (n, slots) -> Ast.Defclass (qualify alias n, slots)
|
type of this package, since no slot name is ever an owned name that
|
||||||
|
matters. The *class's* name is qualified, so [pkg/point] is what an
|
||||||
|
instance's shape tag reads and two packages' [point] classes are two
|
||||||
|
classes. *)
|
||||||
|
| Ast.Defclass (n, slots) ->
|
||||||
|
Ast.Defclass (qualify alias n, List.map (rename_pitem owned alias) slots)
|
||||||
(* A generic's parameters are dyn and were written out by the parser, so
|
(* A generic's parameters are dyn and were written out by the parser, so
|
||||||
there is no unpaired vector here and [bound] is exactly the parameter
|
there is no unpaired vector here and [bound] is exactly the parameter
|
||||||
names. *)
|
names. *)
|
||||||
@ -832,6 +838,7 @@ and place_uses acc loc (p : Ast.place) =
|
|||||||
| Ast.Pfield (t, _) -> expr_uses acc t
|
| Ast.Pfield (t, _) -> expr_uses acc t
|
||||||
| Ast.Pindex (t, idx) -> expr_uses acc t; List.iter (expr_uses acc) idx
|
| Ast.Pindex (t, idx) -> expr_uses acc t; List.iter (expr_uses acc) idx
|
||||||
| Ast.Pderef t -> expr_uses acc t
|
| Ast.Pderef t -> expr_uses acc t
|
||||||
|
| Ast.Pslot (t, k) -> expr_uses acc t; expr_uses acc k
|
||||||
|
|
||||||
let decl_uses acc (d : Ast.decl) =
|
let decl_uses acc (d : Ast.decl) =
|
||||||
let field (f : Ast.field) = texpr_uses acc f.Ast.fty in
|
let field (f : Ast.field) = texpr_uses acc f.Ast.fty in
|
||||||
@ -873,9 +880,14 @@ let decl_uses acc (d : Ast.decl) =
|
|||||||
| Ast.Zeroed | Ast.Uninit -> ())
|
| Ast.Zeroed | Ast.Uninit -> ())
|
||||||
| Ast.Defconst (_, t, v) ->
|
| Ast.Defconst (_, t, v) ->
|
||||||
Option.iter (texpr_uses acc) t; expr_uses acc v
|
Option.iter (texpr_uses acc) t; expr_uses acc v
|
||||||
(* A class's slots are keywords and name nothing. Its constructor's body is
|
(* A class's slot names are keywords and name nothing; its slot types are
|
||||||
written by [Classes.expand], long after this, out of the slots alone. *)
|
uses, recorded the way an unpaired [defn] vector's are. *)
|
||||||
| Ast.Defclass _ -> ()
|
| Ast.Defclass (_, slots) ->
|
||||||
|
List.iter
|
||||||
|
(function
|
||||||
|
| Ast.Pname (n, loc) -> acc := (n, loc) :: !acc
|
||||||
|
| Ast.Ptype t -> texpr_uses acc t)
|
||||||
|
slots
|
||||||
| Ast.Defgeneric f | Ast.Defmulti f -> fn f
|
| Ast.Defgeneric f | Ast.Defmulti f -> fn f
|
||||||
(* The generic is a use — a method in one package extending another's has to
|
(* The generic is a use — a method in one package extending another's has to
|
||||||
pull that package in — and so is the class in the dispatch slot, for the
|
pull that package in — and so is the class in the dispatch slot, for the
|
||||||
|
|||||||
29
lib/parse.ml
29
lib/parse.ml
@ -1205,13 +1205,19 @@ and place (f : Form.t) : Ast.place =
|
|||||||
either inserts or replaces — so there is no store into a lookup, and an
|
either inserts or replaces — so there is no store into a lookup, and an
|
||||||
entry that is absent has no location to store into. Refused here rather
|
entry that is absent has no location to store into. Refused here rather
|
||||||
than parsed into a place form the language does not have. *)
|
than parsed into a place form the language does not have. *)
|
||||||
|
(* A class instance's slot: declared by its defclass, so it always exists
|
||||||
|
and is a place, where a map's absent key is not. Whether the value is an
|
||||||
|
instance or a map is a run-time fact, so the store is a run-time call
|
||||||
|
and a map there is refused by it. *)
|
||||||
|
| List [ { v = Sym "get"; _ }; target; key ] ->
|
||||||
|
Ast.Pslot (expr target, expr key)
|
||||||
| List ({ v = Sym "get"; _ } :: _) ->
|
| List ({ v = Sym "get"; _ } :: _) ->
|
||||||
fail f "(get m k) is not a place — a map is written with (put m k v)"
|
fail f "(get m k) is not a place — a map is written with (put m k v)"
|
||||||
| List [ { v = Sym "deref"; _ }; p ] -> Ast.Pderef (expr p)
|
| List [ { v = Sym "deref"; _ }; p ] -> Ast.Pderef (expr p)
|
||||||
| _ ->
|
| _ ->
|
||||||
fail f
|
fail f
|
||||||
"%s is not assignable. set takes a name, (.field x), (at a i ...), \
|
"%s is not assignable. set takes a name, (.field x), (at a i ...), \
|
||||||
or (deref p)"
|
(deref p), or a class slot (get inst :slot)"
|
||||||
(Form.to_string f)
|
(Form.to_string f)
|
||||||
|
|
||||||
and arms f (items : Form.t list) : Ast.arm list =
|
and arms f (items : Form.t list) : Ast.arm list =
|
||||||
@ -1484,20 +1490,15 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
only names can be read here. *)
|
only names can be read here. *)
|
||||||
| List ({ v = Sym "defclass"; _ } :: args) ->
|
| List ({ v = Sym "defclass"; _ } :: args) ->
|
||||||
(match args with
|
(match args with
|
||||||
|
(* The slot vector is a [defn]'s parameter vector in every respect —
|
||||||
|
[[x y]] two dyn slots, [[pause bool step bool]] two typed ones — and
|
||||||
|
is carried undecided for the same reason. *)
|
||||||
| [ n; { v = Vec slots; _ } ] ->
|
| [ n; { v = Vec slots; _ } ] ->
|
||||||
mk (Ast.Defclass
|
List.iter
|
||||||
(dname n,
|
(fun (s : Form.t) -> match s.v with Sym _ -> no_sigil s | _ -> ())
|
||||||
List.map
|
slots;
|
||||||
(fun (s : Form.t) ->
|
mk (Ast.Defclass (dname n, pitems slots))
|
||||||
match s.v with
|
| _ -> fail f "defclass is (defclass Name [slot Type ...])")
|
||||||
| Sym name -> no_sigil s; (name, s.loc)
|
|
||||||
| _ ->
|
|
||||||
fail s
|
|
||||||
"a class slot is a name — its value is dyn, so there \
|
|
||||||
is no type to write. Read one with (get p :%s)"
|
|
||||||
(Form.to_string s))
|
|
||||||
slots))
|
|
||||||
| _ -> fail f "defclass is (defclass Name [slot ...])")
|
|
||||||
|
|
||||||
| List ({ v = Sym ("defgeneric" | "defmulti" as which); _ } :: args) ->
|
| List ({ v = Sym ("defgeneric" | "defmulti" as which); _ } :: args) ->
|
||||||
let generic = String.equal which "defgeneric" in
|
let generic = String.equal which "defgeneric" in
|
||||||
|
|||||||
@ -747,27 +747,27 @@ let eval ?(origin = "<eval>") ?pause t src : change =
|
|||||||
A class whose slot list did not change is not in here at all: its
|
A class whose slot list did not change is not in here at all: its
|
||||||
constructor has the same signature and goes through [compatible]
|
constructor has the same signature and goes through [compatible]
|
||||||
untouched. *)
|
untouched. *)
|
||||||
let class_slots (ds : Ast.decl list) n =
|
(* The slots as the checker paired them, off each side's environment: the
|
||||||
List.fold_left
|
vector is not readable before pairing, and [t.env] is the one the
|
||||||
(fun acc (d : Ast.decl) ->
|
running program was checked against. Only the names decide the
|
||||||
match d.Ast.d with
|
constructor's arity; the types are the registration's business below. *)
|
||||||
| Ast.Defclass (m, slots) when String.equal m n ->
|
let slot_names env n =
|
||||||
Some (List.map fst slots)
|
Option.map (List.map fst) (Check.class_slots env n)
|
||||||
| _ -> acc)
|
|
||||||
None ds
|
|
||||||
in
|
in
|
||||||
let incoming_classes =
|
let incoming_classes =
|
||||||
List.filter_map
|
List.filter_map
|
||||||
(fun (d : Ast.decl) ->
|
(fun (d : Ast.decl) ->
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
| Ast.Defclass (n, slots) -> Some (n, List.map fst slots)
|
| Ast.Defclass (n, _) ->
|
||||||
|
Some (n, Option.value ~default:[] (Check.class_slots env n))
|
||||||
| _ -> None)
|
| _ -> None)
|
||||||
incoming
|
incoming
|
||||||
in
|
in
|
||||||
let relaxed =
|
let relaxed =
|
||||||
List.filter_map
|
List.filter_map
|
||||||
(fun (n, slots) ->
|
(fun (n, slots) ->
|
||||||
match class_slots t.decls n with
|
let slots = List.map fst slots in
|
||||||
|
match slot_names t.env n with
|
||||||
| Some old when old <> slots ->
|
| Some old when old <> slots ->
|
||||||
(* Every function of the running program that calls the
|
(* Every function of the running program that calls the
|
||||||
constructor or takes its address, minus the ones this
|
constructor or takes its address, minus the ones this
|
||||||
@ -971,15 +971,16 @@ let eval ?(origin = "<eval>") ?pause t src : change =
|
|||||||
{ Tast.e = Tast.Prim (Tast.Rt "flan_dyn_kw", [ str n ]);
|
{ Tast.e = Tast.Prim (Tast.Rt "flan_dyn_kw", [ str n ]);
|
||||||
ty = Types.Dyn; loc }
|
ty = Types.Dyn; loc }
|
||||||
in
|
in
|
||||||
(* The slot names in one string, newline between: the runtime
|
(* The slots in one string, a line each with the slot's type
|
||||||
splits them. A dyn vector would have been the obvious shape
|
after its name: the runtime splits them. A dyn vector would
|
||||||
and is the wrong one — it is a collector object, so the
|
have been the obvious shape and is the wrong one — it is a
|
||||||
registry would hold something the marker has to reach, where
|
collector object, so the registry would hold something the
|
||||||
a packed string reaches interned keywords that are immortal
|
marker has to reach, where a packed string reaches interned
|
||||||
already. *)
|
keywords that are immortal already. The constructor carries
|
||||||
|
the same string, from the same function. *)
|
||||||
{ Tast.e =
|
{ Tast.e =
|
||||||
Tast.Prim (Tast.Rt "flan_dyn_class_def",
|
Tast.Prim (Tast.Rt "flan_dyn_class_def",
|
||||||
[ kw; str (String.concat "\n" slots) ]);
|
[ kw; str (Check.class_spec_of slots) ]);
|
||||||
ty = Types.Unit; loc })
|
ty = Types.Unit; loc })
|
||||||
incoming_classes
|
incoming_classes
|
||||||
in
|
in
|
||||||
|
|||||||
@ -1172,12 +1172,14 @@ flan_dyn flan_dyn_map_new(void) {
|
|||||||
* of dyn vectors would have needed both, and would have needed them to
|
* of dyn vectors would have needed both, and would have needed them to
|
||||||
* survive a collection triggered from inside a migration.
|
* survive a collection triggered from inside a migration.
|
||||||
*
|
*
|
||||||
* **What the registry does not do.** It does not constrain [put]. A class
|
* **What the registry constrains.** A store into a slot the class declares
|
||||||
|
* — the constructor's, [put]'s, [set]'s — is checked against the slot's
|
||||||
|
* type. A key the class does not declare is not refused by [put]: a class
|
||||||
* instance is an open map — TODO.org, "Class features deferred, each with its
|
* instance is an open map — TODO.org, "Class features deferred, each with its
|
||||||
* reason", already defers unknown-slot checking — so a key nobody declared
|
* reason", defers unknown-slot checking — so a key nobody declared can be
|
||||||
* can be written to one, and the migration below will *drop* it at the next
|
* written to one, and the migration below will *drop* it at the next
|
||||||
* redefinition, because its rule is that an instance's keys are the class's
|
* redefinition, because its rule is that an instance's keys are the class's
|
||||||
* slots. That is real data loss and it is written down as such in TODO.org,
|
* slots. [set] does refuse one, because a slot it writes has to exist. That is real data loss and it is written down as such in TODO.org,
|
||||||
* "A redefined defclass migrates its instances lazily", rather than dressed
|
* "A redefined defclass migrates its instances lazily", rather than dressed
|
||||||
* up as enforcement.
|
* up as enforcement.
|
||||||
*
|
*
|
||||||
@ -1219,9 +1221,26 @@ flan_dyn flan_dyn_map_new(void) {
|
|||||||
* runs ahead of its definition declares itself. */
|
* runs ahead of its definition declares itself. */
|
||||||
flan_dyn flan_dyn_kw(const uint8_t *p, int64_t n);
|
flan_dyn flan_dyn_kw(const uint8_t *p, int64_t n);
|
||||||
|
|
||||||
|
/* What a slot may hold. A dyn value's tag is the whole of what can be asked
|
||||||
|
* of it, so these are the tags, plus a range on top of the int tag for a
|
||||||
|
* slot declared with a narrower integer type. [word] is the type as the
|
||||||
|
* defclass wrote it, for the sentence a refusal prints. */
|
||||||
|
enum { ST_ANY, ST_BOOL, ST_INT, ST_FLOAT, ST_TEXT };
|
||||||
|
|
||||||
|
typedef struct slot_type {
|
||||||
|
uint8_t kind;
|
||||||
|
int64_t lo, hi; /* ST_INT only */
|
||||||
|
const char *word; /* static; NULL for ST_ANY */
|
||||||
|
} slot_type;
|
||||||
|
|
||||||
typedef struct class_entry {
|
typedef struct class_entry {
|
||||||
kw_entry *name;
|
kw_entry *name;
|
||||||
kw_entry **slots; /* interned, immortal, in declaration order */
|
kw_entry **slots; /* interned, immortal, in declaration order */
|
||||||
|
slot_type *types; /* one per slot, same order */
|
||||||
|
/* Per slot, the generation a migration last warned about a value that no
|
||||||
|
* longer fits the slot's type. One warning per slot per redefinition,
|
||||||
|
* however many instances carry such a value. */
|
||||||
|
uint32_t *warned;
|
||||||
int64_t nslots;
|
int64_t nslots;
|
||||||
uint32_t gen;
|
uint32_t gen;
|
||||||
} class_entry;
|
} class_entry;
|
||||||
@ -1237,74 +1256,99 @@ static class_entry *class_find(kw_entry *name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The generation a new instance of [name] is stamped with. Zero for a class
|
/* The generation a new instance of [name] is stamped with. Zero for a class
|
||||||
* no definition has been registered for, which is every class in a program
|
* no definition has been registered for — which, now that the constructor
|
||||||
* that was built and never reloaded: nothing has changed shape, so nothing
|
* registers its class, is only an instance built by something other than a
|
||||||
* needs to migrate, and the registry earns its keep only once an editor has
|
* constructor: test/dyn_ops.c, calling the runtime directly. */
|
||||||
* sent a new definition. */
|
|
||||||
static uint32_t class_gen(kw_entry *name) {
|
static uint32_t class_gen(kw_entry *name) {
|
||||||
class_entry *e = class_find(name);
|
class_entry *e = class_find(name);
|
||||||
return e == NULL ? 0u : e->gen;
|
return e == NULL ? 0u : e->gen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* One class's current slot list, as the compiler's per-reload thunk hands it
|
static slot_type slot_type_of(const uint8_t *w, int64_t n) {
|
||||||
* over: the class's name as a keyword, and the slot names packed into one
|
static const struct { const char *w; uint8_t kind; int64_t lo, hi; } known[] = {
|
||||||
* string, newline between and no leading colons — the shape a string literal
|
{ "bool", ST_BOOL, 0, 0 },
|
||||||
* already crosses in, rather than a dyn vector this would have to root.
|
{ "string", ST_TEXT, 0, 0 },
|
||||||
*
|
{ "f32", ST_FLOAT, 0, 0 },
|
||||||
* The generation is bumped only when the list actually differs. That is what
|
{ "f64", ST_FLOAT, 0, 0 },
|
||||||
* makes C-c C-k idempotent: reloading a file re-runs every one of its class
|
{ "i8", ST_INT, INT8_MIN, INT8_MAX },
|
||||||
* definitions, and a bump per reload would migrate every instance in the
|
{ "i16", ST_INT, INT16_MIN, INT16_MAX },
|
||||||
* program every time anybody saved, for no change. */
|
{ "i32", ST_INT, INT32_MIN, INT32_MAX },
|
||||||
void flan_dyn_class_def(flan_dyn name, const uint8_t *slots, int64_t n) {
|
{ "i64", ST_INT, INT64_MIN, INT64_MAX },
|
||||||
kw_entry *k;
|
{ "u8", ST_INT, 0, UINT8_MAX },
|
||||||
kw_entry **list = NULL;
|
{ "u16", ST_INT, 0, UINT16_MAX },
|
||||||
int64_t count = 0, i, start;
|
{ "u32", ST_INT, 0, UINT32_MAX },
|
||||||
class_entry *e;
|
{ "u64", ST_INT, 0, INT64_MAX },
|
||||||
if (flan_dyn_tag(name) != FLAN_DYN_TAG_KEYWORD)
|
};
|
||||||
/* No location: the caller is the thunk a reload runs, which has no
|
slot_type t = { ST_ANY, 0, 0, NULL };
|
||||||
source position of its own — the class's own [defclass] is where a
|
size_t i;
|
||||||
reader would look, and it is not on any stack by the time this runs.
|
for (i = 0; i < sizeof known / sizeof known[0]; i++)
|
||||||
Unreachable from written Flan in any case; only the compiler emits
|
if ((int64_t)strlen(known[i].w) == n && memcmp(known[i].w, w, (size_t)n) == 0) {
|
||||||
this call, and it emits a keyword. */
|
t.kind = known[i].kind;
|
||||||
trap1(NULL, 0, TYPE_TRAP, "class definition",
|
t.lo = known[i].lo;
|
||||||
"a class name is a keyword", name);
|
t.hi = known[i].hi;
|
||||||
k = dyn_kw(name);
|
t.word = known[i].w;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
/* A word this table does not know is a compiler newer than this runtime.
|
||||||
|
Holding anything is the answer that loses no data. */
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int slot_fits(const slot_type *t, flan_dyn v) {
|
||||||
|
int tag = flan_dyn_tag(v);
|
||||||
|
switch (t->kind) {
|
||||||
|
case ST_BOOL: return tag == FLAN_DYN_TAG_BOOL;
|
||||||
|
case ST_TEXT: return tag == FLAN_DYN_TAG_TEXT;
|
||||||
|
case ST_FLOAT: return tag == FLAN_DYN_TAG_FLOAT;
|
||||||
|
case ST_INT: {
|
||||||
|
int64_t x;
|
||||||
|
if (tag != FLAN_DYN_TAG_INT) return 0;
|
||||||
|
x = dyn_int_value(v);
|
||||||
|
return x >= t->lo && x <= t->hi;
|
||||||
|
}
|
||||||
|
default: return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A class's slots as the compiler hands them over: one line per slot, the
|
||||||
|
* slot's name and then, after a space, its type's name — nothing for a slot
|
||||||
|
* written with no type. The same string comes from a constructor and from a
|
||||||
|
* reload, so it is read in one place. The count is returned; both arrays are
|
||||||
|
* NULL for a class with no slots, which allocates nothing. */
|
||||||
|
static int64_t class_spec(const uint8_t *spec, int64_t n, kw_entry ***names,
|
||||||
|
slot_type **types) {
|
||||||
|
int64_t count = 0, i, start;
|
||||||
if (n < 0) n = 0;
|
if (n < 0) n = 0;
|
||||||
/* Count first, then fill: one allocation of the right size, and an empty
|
*names = NULL;
|
||||||
* class — (defclass marker []) is in the corpus — allocates nothing. */
|
*types = NULL;
|
||||||
for (i = 0, start = 0; i <= n; i++)
|
for (i = 0, start = 0; i <= n; i++)
|
||||||
if (i == n ? i > start : slots[i] == '\n') {
|
if (i == n ? i > start : spec[i] == '\n') {
|
||||||
if (i > start) count++;
|
if (i > start) count++;
|
||||||
start = i + 1;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
if (count > 0) {
|
if (count == 0) return 0;
|
||||||
list = (kw_entry **)malloc((size_t)count * sizeof *list);
|
*names = (kw_entry **)malloc((size_t)count * sizeof **names);
|
||||||
if (list == NULL) trap_oom(NULL, 0, count * (int64_t)sizeof *list);
|
*types = (slot_type *)malloc((size_t)count * sizeof **types);
|
||||||
count = 0;
|
if (*names == NULL || *types == NULL)
|
||||||
for (i = 0, start = 0; i <= n; i++)
|
trap_oom(NULL, 0, count * (int64_t)(sizeof **names + sizeof **types));
|
||||||
if (i == n ? i > start : slots[i] == '\n') {
|
count = 0;
|
||||||
if (i > start)
|
for (i = 0, start = 0; i <= n; i++)
|
||||||
list[count++] = dyn_kw(flan_dyn_kw(slots + start, i - start));
|
if (i == n ? i > start : spec[i] == '\n') {
|
||||||
start = i + 1;
|
if (i > start) {
|
||||||
|
int64_t sp = start;
|
||||||
|
while (sp < i && spec[sp] != ' ') sp++;
|
||||||
|
(*names)[count] = dyn_kw(flan_dyn_kw(spec + start, sp - start));
|
||||||
|
(*types)[count] = sp < i ? slot_type_of(spec + sp + 1, i - sp - 1)
|
||||||
|
: slot_type_of(NULL, 0);
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
}
|
start = i + 1;
|
||||||
e = class_find(k);
|
}
|
||||||
if (e != NULL) {
|
return count;
|
||||||
int same = e->nslots == count;
|
}
|
||||||
if (same)
|
|
||||||
for (i = 0; i < count; i++)
|
static void class_add(kw_entry *k, kw_entry **list, slot_type *types,
|
||||||
if (e->slots[i] != list[i]) { same = 0; break; }
|
int64_t count) {
|
||||||
if (same) { free(list); return; }
|
|
||||||
free(e->slots);
|
|
||||||
e->slots = list;
|
|
||||||
e->nslots = count;
|
|
||||||
/* Wrapping is not a correctness question — what matters is that the new
|
|
||||||
* generation differs from the one the live instances carry — but zero is
|
|
||||||
* reserved for "no definition registered", so it is stepped over. */
|
|
||||||
e->gen = e->gen + 1u;
|
|
||||||
if (e->gen == 0u) e->gen = 1u;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (classes_n == classes_cap) {
|
if (classes_n == classes_cap) {
|
||||||
int64_t cap = classes_cap ? classes_cap * 2 : 8;
|
int64_t cap = classes_cap ? classes_cap * 2 : 8;
|
||||||
class_entry *t =
|
class_entry *t =
|
||||||
@ -1315,6 +1359,11 @@ void flan_dyn_class_def(flan_dyn name, const uint8_t *slots, int64_t n) {
|
|||||||
}
|
}
|
||||||
classes[classes_n].name = k;
|
classes[classes_n].name = k;
|
||||||
classes[classes_n].slots = list;
|
classes[classes_n].slots = list;
|
||||||
|
classes[classes_n].types = types;
|
||||||
|
classes[classes_n].warned =
|
||||||
|
count > 0 ? (uint32_t *)calloc((size_t)count, sizeof(uint32_t)) : NULL;
|
||||||
|
if (count > 0 && classes[classes_n].warned == NULL)
|
||||||
|
trap_oom(NULL, 0, count * (int64_t)sizeof(uint32_t));
|
||||||
classes[classes_n].nslots = count;
|
classes[classes_n].nslots = count;
|
||||||
/* One, never zero: an instance built before this registration carries zero
|
/* One, never zero: an instance built before this registration carries zero
|
||||||
* and has to be seen as stale, because the definition it was built from is
|
* and has to be seen as stale, because the definition it was built from is
|
||||||
@ -1323,6 +1372,61 @@ void flan_dyn_class_def(flan_dyn name, const uint8_t *slots, int64_t n) {
|
|||||||
classes_n++;
|
classes_n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* One class's current definition, as the compiler's per-reload thunk hands
|
||||||
|
* it over: the class's name as a keyword, and [class_spec]'s string.
|
||||||
|
*
|
||||||
|
* The generation is bumped only when the definition actually differs — a
|
||||||
|
* slot's name or its type. That is what makes C-c C-k idempotent: reloading
|
||||||
|
* a file re-runs every one of its class definitions, and a bump per reload
|
||||||
|
* would migrate every instance in the program every time anybody saved, for
|
||||||
|
* no change. */
|
||||||
|
void flan_dyn_class_def(flan_dyn name, const uint8_t *slots, int64_t n) {
|
||||||
|
kw_entry *k;
|
||||||
|
kw_entry **list;
|
||||||
|
slot_type *types;
|
||||||
|
int64_t count, i;
|
||||||
|
class_entry *e;
|
||||||
|
if (flan_dyn_tag(name) != FLAN_DYN_TAG_KEYWORD)
|
||||||
|
/* No location: the caller is the thunk a reload runs, which has no
|
||||||
|
source position of its own — the class's own [defclass] is where a
|
||||||
|
reader would look, and it is not on any stack by the time this runs.
|
||||||
|
Unreachable from written Flan in any case; only the compiler emits
|
||||||
|
this call, and it emits a keyword. */
|
||||||
|
trap1(NULL, 0, TYPE_TRAP, "class definition",
|
||||||
|
"a class name is a keyword", name);
|
||||||
|
k = dyn_kw(name);
|
||||||
|
count = class_spec(slots, n, &list, &types);
|
||||||
|
e = class_find(k);
|
||||||
|
if (e != NULL) {
|
||||||
|
int same = e->nslots == count;
|
||||||
|
if (same)
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
if (e->slots[i] != list[i] || e->types[i].kind != types[i].kind
|
||||||
|
|| e->types[i].lo != types[i].lo || e->types[i].hi != types[i].hi) {
|
||||||
|
same = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (same) { free(list); free(types); return; }
|
||||||
|
free(e->slots);
|
||||||
|
free(e->types);
|
||||||
|
free(e->warned);
|
||||||
|
e->slots = list;
|
||||||
|
e->types = types;
|
||||||
|
e->warned =
|
||||||
|
count > 0 ? (uint32_t *)calloc((size_t)count, sizeof(uint32_t)) : NULL;
|
||||||
|
if (count > 0 && e->warned == NULL)
|
||||||
|
trap_oom(NULL, 0, count * (int64_t)sizeof(uint32_t));
|
||||||
|
e->nslots = count;
|
||||||
|
/* Wrapping is not a correctness question — what matters is that the new
|
||||||
|
* generation differs from the one the live instances carry — but zero is
|
||||||
|
* reserved for "no definition registered", so it is stepped over. */
|
||||||
|
e->gen = e->gen + 1u;
|
||||||
|
if (e->gen == 0u) e->gen = 1u;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
class_add(k, list, types, count);
|
||||||
|
}
|
||||||
|
|
||||||
/* The migration. [o] is left holding exactly the class's current slots, in
|
/* The migration. [o] is left holding exactly the class's current slots, in
|
||||||
* the class's order, with the values it already had for the ones it still
|
* the class's order, with the values it already had for the ones it still
|
||||||
* has and nil for the ones it has just gained — which is precisely the
|
* has and nil for the ones it has just gained — which is precisely the
|
||||||
@ -1367,6 +1471,27 @@ static void class_sync(flan_obj *o) {
|
|||||||
if (flan_dyn_tag(key) == FLAN_DYN_TAG_KEYWORD
|
if (flan_dyn_tag(key) == FLAN_DYN_TAG_KEYWORD
|
||||||
&& dyn_kw(key) == e->slots[j]) {
|
&& dyn_kw(key) == e->slots[j]) {
|
||||||
v = o->u.v.items[i * 2 + 1];
|
v = o->u.v.items[i * 2 + 1];
|
||||||
|
/* A kept value that the slot's new type does not admit is kept
|
||||||
|
anyway: throwing it away would be the data loss a redefinition
|
||||||
|
exists to avoid, and there is nothing to convert it to. What it
|
||||||
|
gets is a warning, once per slot per redefinition, and the next
|
||||||
|
write to the slot is checked like any other. A slot the class
|
||||||
|
has only just gained holds nil without a word: it holds nothing,
|
||||||
|
rather than something of the wrong type. */
|
||||||
|
if (!slot_fits(&e->types[j], v) && e->warned[j] != e->gen) {
|
||||||
|
char sv[SAY_MAX];
|
||||||
|
kw_entry *c = o->u.v.klass, *sl = e->slots[j];
|
||||||
|
e->warned[j] = e->gen;
|
||||||
|
say(sv, SAY_MAX, v);
|
||||||
|
fflush(stdout);
|
||||||
|
fprintf(stderr,
|
||||||
|
"warning: %.*s was redefined, and its slot :%.*s is now "
|
||||||
|
"declared %s. An instance holds %s there, which is %s; it "
|
||||||
|
"keeps that value, and the next write to :%.*s is checked\n",
|
||||||
|
(int)c->len, (const char *)(c + 1),
|
||||||
|
(int)sl->len, (const char *)(sl + 1), e->types[j].word, sv,
|
||||||
|
tag_of(v), (int)sl->len, (const char *)(sl + 1));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1386,11 +1511,25 @@ static void class_sync(flan_obj *o) {
|
|||||||
|
|
||||||
/* The same map with a shape tag on it: what a (defclass ...) constructor
|
/* The same map with a shape tag on it: what a (defclass ...) constructor
|
||||||
* calls. [k] is a keyword and anything else traps by name — the compiler
|
* calls. [k] is a keyword and anything else traps by name — the compiler
|
||||||
* hands it the class's own name and nothing else can reach this. */
|
* hands it the class's own name and nothing else can reach this.
|
||||||
flan_dyn flan_dyn_map_new_class(flan_dyn k) {
|
*
|
||||||
|
* [spec] is the class's definition, [class_spec]'s string, and it registers
|
||||||
|
* the class the first time any instance of it is built. That is what makes
|
||||||
|
* a slot's type checked in a program that is never reloaded — the registry
|
||||||
|
* used to be filled only by a reload. A class already registered keeps
|
||||||
|
* what it has, and has to: a constructor compiled before a redefinition may
|
||||||
|
* still be on some stack, and letting its definition win would put the
|
||||||
|
* class back the way it was. Redefining is [flan_dyn_class_def]'s alone. */
|
||||||
|
flan_dyn flan_dyn_map_new_class(flan_dyn k, const uint8_t *spec, int64_t n) {
|
||||||
flan_obj *o;
|
flan_obj *o;
|
||||||
if (flan_dyn_tag(k) != FLAN_DYN_TAG_KEYWORD)
|
if (flan_dyn_tag(k) != FLAN_DYN_TAG_KEYWORD)
|
||||||
trap1(NULL, 0, TYPE_TRAP, "class instance", "a class tag is a keyword", k);
|
trap1(NULL, 0, TYPE_TRAP, "class instance", "a class tag is a keyword", k);
|
||||||
|
if (class_find(dyn_kw(k)) == NULL) {
|
||||||
|
kw_entry **list;
|
||||||
|
slot_type *types;
|
||||||
|
int64_t count = class_spec(spec, n, &list, &types);
|
||||||
|
class_add(dyn_kw(k), list, types, count);
|
||||||
|
}
|
||||||
o = gc_alloc(OBJ_MAP, 0);
|
o = gc_alloc(OBJ_MAP, 0);
|
||||||
o->len = 0;
|
o->len = 0;
|
||||||
o->u.v.items = NULL;
|
o->u.v.items = NULL;
|
||||||
@ -2265,8 +2404,141 @@ flan_dyn flan_dyn_map_contains(flan_dyn m, flan_dyn k) {
|
|||||||
return flan_dyn_from_bool(map_find(o, k) >= 0);
|
return flan_dyn_from_bool(map_find(o, k) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void flan_dyn_map_set(flan_dyn m, flan_dyn k, flan_dyn v) {
|
/* A class slot's type, checked at the store — SBCL's place for it
|
||||||
|
* (src/pcl/slots.lisp, [set-slot-value]'s typecheck before the write),
|
||||||
|
* because the store is where the wrong value is. -1 when [k] is not a slot
|
||||||
|
* the class declares. */
|
||||||
|
static int64_t class_slot(class_entry *e, flan_dyn k) {
|
||||||
|
int64_t j;
|
||||||
|
if (e == NULL || flan_dyn_tag(k) != FLAN_DYN_TAG_KEYWORD) return -1;
|
||||||
|
for (j = 0; j < e->nslots; j++)
|
||||||
|
if (e->slots[j] == dyn_kw(k)) return j;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The three stores that reach a declared slot, for the sentence a refusal
|
||||||
|
* prints: the call as it would have been written. */
|
||||||
|
enum { BY_PUT, BY_SET, BY_NEW };
|
||||||
|
|
||||||
|
static _Noreturn void trap_slot_type(const uint8_t *loc, int64_t loclen,
|
||||||
|
int by, flan_obj *o, class_entry *e,
|
||||||
|
int64_t j, flan_dyn m, flan_dyn v) {
|
||||||
|
char sm[SAY_MAX], sv[SAY_MAX];
|
||||||
|
kw_entry *sl = e->slots[j], *c = o->u.v.klass;
|
||||||
|
int sn = (int)sl->len, cn = (int)c->len;
|
||||||
|
const char *ss = (const char *)(sl + 1), *cs = (const char *)(c + 1);
|
||||||
|
say(sm, SAY_MAX, m);
|
||||||
|
say(sv, SAY_MAX, v);
|
||||||
|
fflush(stdout);
|
||||||
|
trap_where(loc, loclen);
|
||||||
|
fprintf(stderr, "dyn %s: the slot :%.*s of %.*s is declared %s, and ",
|
||||||
|
by == BY_PUT ? "put" : by == BY_SET ? "set" : "construct", sn, ss,
|
||||||
|
cn, cs, e->types[j].word);
|
||||||
|
/* An int of the wrong size is the right tag, so the tag is not the news. */
|
||||||
|
if (e->types[j].kind == ST_INT && flan_dyn_tag(v) == FLAN_DYN_TAG_INT)
|
||||||
|
fprintf(stderr, "%s is outside its range — ", sv);
|
||||||
|
else
|
||||||
|
fprintf(stderr, "this is %s — ", tag_of(v));
|
||||||
|
if (by == BY_PUT)
|
||||||
|
fprintf(stderr, "(put %s :%.*s %s)\n", sm, sn, ss, sv);
|
||||||
|
else if (by == BY_SET)
|
||||||
|
fprintf(stderr, "(set (get %s :%.*s) %s)\n", sm, sn, ss, sv);
|
||||||
|
else
|
||||||
|
fprintf(stderr, "(%.*s ...) with :%.*s %s\n", cn, cs, sn, ss, sv);
|
||||||
|
flan_trap((const uint8_t *)"DynType", 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_slot(const uint8_t *loc, int64_t loclen, int by,
|
||||||
|
flan_obj *o, flan_dyn m, flan_dyn k, flan_dyn v) {
|
||||||
|
class_entry *e;
|
||||||
|
int64_t j;
|
||||||
|
if (o->u.v.klass == NULL) return;
|
||||||
|
e = class_find(o->u.v.klass);
|
||||||
|
j = class_slot(e, k);
|
||||||
|
if (j >= 0 && !slot_fits(&e->types[j], v))
|
||||||
|
trap_slot_type(loc, loclen, by, o, e, j, m, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void map_store(flan_obj *o, flan_dyn k, flan_dyn v);
|
||||||
|
|
||||||
|
/* A constructor's stores: [flan_dyn_map_set]'s, with the refusal worded for
|
||||||
|
* the constructor call it happened inside rather than for a [put] nobody
|
||||||
|
* wrote. */
|
||||||
|
void flan_dyn_slot_init(flan_dyn m, flan_dyn k, flan_dyn v) {
|
||||||
|
flan_obj *o = want_map("construct", m, k);
|
||||||
|
check_slot(NULL, 0, BY_NEW, o, m, k, v);
|
||||||
|
map_store(o, k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (set (get inst :slot) v). Three refusals, each its own sentence, because
|
||||||
|
* they are three different mistakes: the value is not a class instance at
|
||||||
|
* all (a map's entries are written with [put], which is where inserting a
|
||||||
|
* key is real); the key is not a slot the class declares; the value does not
|
||||||
|
* fit the slot's type. The first two are why this is not [put]: a declared
|
||||||
|
* slot always exists, so writing one is a store and never an insertion. */
|
||||||
|
void flan_dyn_slot_set(flan_dyn m, flan_dyn k, flan_dyn v,
|
||||||
|
const uint8_t *loc, int64_t loclen) {
|
||||||
|
flan_obj *o;
|
||||||
|
class_entry *e;
|
||||||
|
int64_t j;
|
||||||
|
if (!is_map(m) || dyn_obj(m)->u.v.klass == NULL) {
|
||||||
|
char sm[SAY_MAX];
|
||||||
|
say(sm, SAY_MAX, m);
|
||||||
|
fflush(stdout);
|
||||||
|
trap_where(loc, loclen);
|
||||||
|
fprintf(stderr,
|
||||||
|
"dyn set: (get m k) is a place only on a class instance, and "
|
||||||
|
"this is %s%s — %s. A map's entries are written with "
|
||||||
|
"(put m k v)\n",
|
||||||
|
is_map(m) ? "a map with no class" : "a ",
|
||||||
|
is_map(m) ? "" : tag_of(m), sm);
|
||||||
|
flan_trap((const uint8_t *)"DynType", 7);
|
||||||
|
}
|
||||||
|
o = dyn_obj(m);
|
||||||
|
class_sync(o);
|
||||||
|
e = class_find(o->u.v.klass);
|
||||||
|
j = class_slot(e, k);
|
||||||
|
if (j < 0) {
|
||||||
|
char sk[SAY_MAX];
|
||||||
|
kw_entry *c = o->u.v.klass;
|
||||||
|
int64_t i;
|
||||||
|
say(sk, SAY_MAX, k);
|
||||||
|
fflush(stdout);
|
||||||
|
trap_where(loc, loclen);
|
||||||
|
fprintf(stderr, "dyn set: %.*s has no slot %s. Its slots are",
|
||||||
|
(int)c->len, (const char *)(c + 1), sk);
|
||||||
|
if (e == NULL || e->nslots == 0) fprintf(stderr, " none");
|
||||||
|
else
|
||||||
|
for (i = 0; i < e->nslots; i++)
|
||||||
|
fprintf(stderr, " :%.*s", (int)e->slots[i]->len,
|
||||||
|
(const char *)(e->slots[i] + 1));
|
||||||
|
fprintf(stderr, "; a key the class does not declare is written with "
|
||||||
|
"(put inst k v)\n");
|
||||||
|
flan_trap((const uint8_t *)"DynType", 7);
|
||||||
|
}
|
||||||
|
if (!slot_fits(&e->types[j], v))
|
||||||
|
trap_slot_type(loc, loclen, BY_SET, o, e, j, m, v);
|
||||||
|
map_store(o, k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* [put]: a key the class declares is checked against its type, and the
|
||||||
|
* refusal names [loc]. A key it does not declare is let through: an
|
||||||
|
* instance is an open map to [put], and the next redefinition drops such a
|
||||||
|
* key — see "Classes" above. */
|
||||||
|
void flan_dyn_map_put(flan_dyn m, flan_dyn k, flan_dyn v, const uint8_t *loc,
|
||||||
|
int64_t loclen) {
|
||||||
flan_obj *o = want_map("put", m, k);
|
flan_obj *o = want_map("put", m, k);
|
||||||
|
check_slot(loc, loclen, BY_PUT, o, m, k, v);
|
||||||
|
map_store(o, k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The same with no site: a map literal's stores, and test/dyn_ops.c. */
|
||||||
|
void flan_dyn_map_set(flan_dyn m, flan_dyn k, flan_dyn v) {
|
||||||
|
flan_dyn_map_put(m, k, v, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The store under all three, with the instance already brought up to date. */
|
||||||
|
static void map_store(flan_obj *o, flan_dyn k, flan_dyn v) {
|
||||||
int64_t i = map_find(o, k);
|
int64_t i = map_find(o, k);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
o->u.v.items[i * 2 + 1] = v;
|
o->u.v.items[i * 2 + 1] = v;
|
||||||
|
|||||||
@ -88,15 +88,28 @@ flan_dyn flan_dyn_map_new(void);
|
|||||||
*
|
*
|
||||||
* The tag is not traced and does not have to be: an interned keyword entry is
|
* The tag is not traced and does not have to be: an interned keyword entry is
|
||||||
* immortal and is not a collector object. */
|
* immortal and is not a collector object. */
|
||||||
flan_dyn flan_dyn_map_new_class(flan_dyn k);
|
flan_dyn flan_dyn_map_new_class(flan_dyn k, const uint8_t *spec, int64_t n);
|
||||||
|
|
||||||
|
/* A constructor's store into a slot, checked against the slot's declared
|
||||||
|
* type — [flan_dyn_map_set] with a refusal worded for the constructor. */
|
||||||
|
void flan_dyn_slot_init(flan_dyn m, flan_dyn k, flan_dyn v);
|
||||||
|
|
||||||
|
/* (set (get inst :slot) v): [m] must be a class instance and [k] a slot its
|
||||||
|
* class declares, and [v] must fit the slot's type; each is a trap with its
|
||||||
|
* own sentence, at [loc]. A declared slot always exists, so this stores and
|
||||||
|
* never inserts. */
|
||||||
|
void flan_dyn_slot_set(flan_dyn m, flan_dyn k, flan_dyn v,
|
||||||
|
const uint8_t *loc, int64_t loclen);
|
||||||
|
|
||||||
/* The class's name as a keyword, or nil for anything that is not an instance
|
/* The class's name as a keyword, or nil for anything that is not an instance
|
||||||
* — an ordinary map included. Never traps. */
|
* — an ordinary map included. Never traps. */
|
||||||
flan_dyn flan_dyn_class_of(flan_dyn v);
|
flan_dyn flan_dyn_class_of(flan_dyn v);
|
||||||
|
|
||||||
/* A class definition, registered or re-registered: [name] is the class's name
|
/* A class definition, registered or re-registered: [name] is the class's name
|
||||||
* as a keyword and [slots]/[n] is its slot names packed into one string,
|
* as a keyword and [slots]/[n] is its slots packed into one string, a line
|
||||||
* newline between and no leading colons. The compiler emits one call per
|
* each, the slot's name and then — after a space, for a typed slot — its
|
||||||
|
* type's name: "x i64\ny" is an i64 slot :x and a slot :y of any value.
|
||||||
|
* [flan_dyn_map_new_class] takes the same string. The compiler emits one call per
|
||||||
* (defclass ...) into the thunk a reload runs, so a definition that changed
|
* (defclass ...) into the thunk a reload runs, so a definition that changed
|
||||||
* lands here before anything touches an instance.
|
* lands here before anything touches an instance.
|
||||||
*
|
*
|
||||||
@ -185,6 +198,10 @@ void flan_dyn_push(flan_dyn v, flan_dyn x, const uint8_t *loc, int64_t loclen);
|
|||||||
* key in place, so a key occurs once and insertion order is print order. */
|
* key in place, so a key occurs once and insertion order is print order. */
|
||||||
flan_dyn flan_dyn_map_get(flan_dyn m, flan_dyn k);
|
flan_dyn flan_dyn_map_get(flan_dyn m, flan_dyn k);
|
||||||
void flan_dyn_map_set(flan_dyn m, flan_dyn k, flan_dyn v);
|
void flan_dyn_map_set(flan_dyn m, flan_dyn k, flan_dyn v);
|
||||||
|
/* [put]'s: [flan_dyn_map_set], with the site a typed class slot's refusal
|
||||||
|
* prints. */
|
||||||
|
void flan_dyn_map_put(flan_dyn m, flan_dyn k, flan_dyn v,
|
||||||
|
const uint8_t *loc, int64_t loclen);
|
||||||
flan_dyn flan_dyn_map_contains(flan_dyn m, flan_dyn k);
|
flan_dyn flan_dyn_map_contains(flan_dyn m, flan_dyn k);
|
||||||
|
|
||||||
/* Structural, and per type it renders what typed [print] renders. Never
|
/* Structural, and per type it renders what typed [print] renders. Never
|
||||||
|
|||||||
@ -1061,7 +1061,8 @@ static void define(const char *name, const char *slots) {
|
|||||||
* Built through the same entry point a constructor uses, so it is stamped
|
* Built through the same entry point a constructor uses, so it is stamped
|
||||||
* exactly as compiled code would stamp it. */
|
* exactly as compiled code would stamp it. */
|
||||||
static flan_dyn a_point(int64_t x, int64_t y) {
|
static flan_dyn a_point(int64_t x, int64_t y) {
|
||||||
flan_dyn p = flan_dyn_map_new_class(flan_dyn_kw((const uint8_t *)"point", 5));
|
flan_dyn p = flan_dyn_map_new_class(flan_dyn_kw((const uint8_t *)"point", 5),
|
||||||
|
(const uint8_t *)"x\ny", 3);
|
||||||
flan_dyn_map_set(p, flan_dyn_kw((const uint8_t *)"x", 1),
|
flan_dyn_map_set(p, flan_dyn_kw((const uint8_t *)"x", 1),
|
||||||
flan_dyn_from_i64(x));
|
flan_dyn_from_i64(x));
|
||||||
flan_dyn_map_set(p, flan_dyn_kw((const uint8_t *)"y", 1),
|
flan_dyn_map_set(p, flan_dyn_kw((const uint8_t *)"y", 1),
|
||||||
@ -1176,7 +1177,8 @@ static void classes(void) {
|
|||||||
define("point", "x\ny\nn");
|
define("point", "x\ny\nn");
|
||||||
/* Built by hand rather than through [a_point], because it is the instance
|
/* Built by hand rather than through [a_point], because it is the instance
|
||||||
the *new* constructor would build: three slots, stamped current. */
|
the *new* constructor would build: three slots, stamped current. */
|
||||||
q = flan_dyn_map_new_class(flan_dyn_kw((const uint8_t *)"point", 5));
|
q = flan_dyn_map_new_class(flan_dyn_kw((const uint8_t *)"point", 5),
|
||||||
|
(const uint8_t *)"x\ny\nn", 5);
|
||||||
flan_dyn_map_set(q, flan_dyn_kw((const uint8_t *)"x", 1),
|
flan_dyn_map_set(q, flan_dyn_kw((const uint8_t *)"x", 1),
|
||||||
flan_dyn_from_i64(1));
|
flan_dyn_from_i64(1));
|
||||||
flan_dyn_map_set(q, flan_dyn_kw((const uint8_t *)"y", 1),
|
flan_dyn_map_set(q, flan_dyn_kw((const uint8_t *)"y", 1),
|
||||||
|
|||||||
38
test/programs/dyn-class-slots.flan
Normal file
38
test/programs/dyn-class-slots.flan
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
;;;; Typed class slots, and set on a slot.
|
||||||
|
;;;;
|
||||||
|
;;;; A slot vector reads as a defn's parameter vector: [pause bool] is a slot
|
||||||
|
;;;; of type bool, and a name followed by another name is a slot with no type,
|
||||||
|
;;;; which holds any dyn value. The type is checked when a value is stored --
|
||||||
|
;;;; by the constructor, by put and by set -- and not when one is read: an
|
||||||
|
;;;; instance is a dyn map whatever its slots say.
|
||||||
|
;;;;
|
||||||
|
;;;; set writes a declared slot. A class declares its slots, so one always
|
||||||
|
;;;; exists and (get s :pause) is a place, where a plain map's absent key is
|
||||||
|
;;;; not. dyn-slot-trap.flan has the refusals.
|
||||||
|
|
||||||
|
(defclass state [pause bool step i32 speed f64 name string tag])
|
||||||
|
|
||||||
|
(defn twelve [] i64 12)
|
||||||
|
|
||||||
|
(defn main [] i32
|
||||||
|
(let [s (state false 3 1.5 "sand" :x)]
|
||||||
|
(println s)
|
||||||
|
(set (get s :pause) true)
|
||||||
|
(println (get s :pause))
|
||||||
|
;; An i32 slot takes any int in i32's range; a dyn caller passes the
|
||||||
|
;; value as it is, with nothing converted first.
|
||||||
|
(set (get s :step) -7)
|
||||||
|
(println (get s :step))
|
||||||
|
(set (get s :tag) [1 2])
|
||||||
|
(println (get s :tag))
|
||||||
|
;; put reaches the same check for a declared slot, and still inserts a
|
||||||
|
;; key the class does not declare -- an instance is an open map to put.
|
||||||
|
(put s :speed 2.5)
|
||||||
|
(put s :scratch 9)
|
||||||
|
(println (get s :speed))
|
||||||
|
(println (get s :scratch))
|
||||||
|
(println (length s))
|
||||||
|
;; A typed caller boxes into the dyn parameter as any call does.
|
||||||
|
(set (get s :step) (twelve))
|
||||||
|
(println (get s :step)))
|
||||||
|
0)
|
||||||
18
test/programs/dyn-slot-trap.flan
Normal file
18
test/programs/dyn-slot-trap.flan
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
;;;; The refusals of a typed class slot, one per run because each ends the
|
||||||
|
;;;; process. The argument chooses which. The line numbers are asserted by
|
||||||
|
;;;; the test, so an edit above them moves them.
|
||||||
|
(defclass state [pause bool step i32 tag])
|
||||||
|
|
||||||
|
(defn as-dyn [d dyn] dyn d)
|
||||||
|
|
||||||
|
(defn main [args [string]] i32
|
||||||
|
(let [which (if (> (length args) 1) (i32 (bytes->i64 (bytes-view (at args 1)))) 0)
|
||||||
|
s (state false 3 nil)]
|
||||||
|
(println "before")
|
||||||
|
(cond
|
||||||
|
(= which 0) (println (state 1 2 3))
|
||||||
|
(= which 1) (put s :pause 1)
|
||||||
|
(= which 2) (set (get s :step) 5000000000)
|
||||||
|
(= which 3) (set (get s :paws) true)
|
||||||
|
:else (set (get (as-dyn {:pause 1}) :pause) true)))
|
||||||
|
0)
|
||||||
@ -4935,6 +4935,47 @@ level "1"
|
|||||||
index_site ();
|
index_site ();
|
||||||
index_site ~x86:true ();
|
index_site ~x86:true ();
|
||||||
|
|
||||||
|
(* Typed class slots and set on a slot: the stores that fit, then one
|
||||||
|
run per refusal. The constructor, put and set each check a declared
|
||||||
|
slot's type, set refuses a slot the class does not declare and a
|
||||||
|
value that is not an instance, and put still inserts an undeclared
|
||||||
|
key. On both backends, because every one of these is a runtime call
|
||||||
|
whose arguments the two emit separately. *)
|
||||||
|
let slots_out =
|
||||||
|
"#state{ :pause false :step 3 :speed 1.5 :name \"sand\" :tag :x}\n\
|
||||||
|
true\n-7\n[ 1 2]\n2.5\n9\n6\n12\n"
|
||||||
|
in
|
||||||
|
outputs "dyn: typed class slots" "programs/dyn-class-slots.flan" slots_out;
|
||||||
|
outputs ~x86:true "dyn: typed class slots, --x86"
|
||||||
|
"programs/dyn-class-slots.flan" slots_out;
|
||||||
|
let slot_trap ?x86 () =
|
||||||
|
let exe = compile ?x86 "programs/dyn-slot-trap.flan" in
|
||||||
|
List.iter
|
||||||
|
(fun (arg, want) ->
|
||||||
|
let code, text = run exe (Some arg) in
|
||||||
|
if code <> 134 || not (contains text want) then begin
|
||||||
|
incr failures;
|
||||||
|
Printf.printf
|
||||||
|
"FAIL dyn: a class slot's refusal%s\n got: %S \
|
||||||
|
(exit %d)\n wanted: %S (exit 134)\n"
|
||||||
|
(match x86 with Some true -> ", --x86" | _ -> "")
|
||||||
|
text code want
|
||||||
|
end)
|
||||||
|
[ ("0", "dyn construct: the slot :pause of state is declared bool, \
|
||||||
|
and this is int — (state ...) with :pause 1");
|
||||||
|
("1", "dyn-slot-trap.flan:14:19: dyn put: the slot :pause of state \
|
||||||
|
is declared bool, and this is int");
|
||||||
|
("2", "dyn-slot-trap.flan:15:19: dyn set: the slot :step of state \
|
||||||
|
is declared i32, and 5000000000 is outside its range");
|
||||||
|
("3", "dyn-slot-trap.flan:16:19: dyn set: state has no slot :paws. \
|
||||||
|
Its slots are :pause :step :tag");
|
||||||
|
("4", "dyn-slot-trap.flan:17:13: dyn set: (get m k) is a place only \
|
||||||
|
on a class instance, and this is a map with no class") ];
|
||||||
|
(try Sys.remove exe with Sys_error _ -> ())
|
||||||
|
in
|
||||||
|
slot_trap ();
|
||||||
|
slot_trap ~x86:true ();
|
||||||
|
|
||||||
(* A numeric cast opening a dyn box — TODO.org, "A numeric cast opens a
|
(* A numeric cast opening a dyn box — TODO.org, "A numeric cast opens a
|
||||||
dyn box". programs/dyn-cast.flan is one program because the three
|
dyn box". programs/dyn-cast.flan is one program because the three
|
||||||
behaviours are one story told in order: the same-kind casts print, the
|
behaviours are one story told in order: the same-kind casts print, the
|
||||||
|
|||||||
@ -2611,14 +2611,31 @@ let () =
|
|||||||
rejects_check "a constructor takes one argument per slot"
|
rejects_check "a constructor takes one argument per slot"
|
||||||
"(defclass point [x y])\n(defn main [] i32 (let [p (point 1)] 0))"
|
"(defclass point [x y])\n(defn main [] i32 (let [p (point 1)] 0))"
|
||||||
~needle:"point";
|
~needle:"point";
|
||||||
(* A slot vector holds names and nothing else. [(defclass point [x i64])]
|
(* A slot vector is a defn's parameter vector: a name followed by a type
|
||||||
is therefore two slots, one of them unfortunately named — the parser
|
is a typed slot, a name followed by another name is an untyped one. The
|
||||||
cannot tell a type's name from a slot's and does not have to, since a
|
type is what a stored dyn value is checked against, so it is one a dyn
|
||||||
slot has no type to write. What it can tell is a form that is not a name
|
value can be checked as, and nothing else. *)
|
||||||
at all. *)
|
accepts "typed slots, and untyped ones beside them"
|
||||||
rejects_check "a slot is a name, not a type expression"
|
"(defclass state [pause bool step bool n i32 tag])\n\
|
||||||
|
(defn main [] i32 (let [s (state false true 3 :x)] (if (= (get s :n) 3) 0 1)))";
|
||||||
|
rejects_check "a slot's type is one a dyn value can be checked as"
|
||||||
"(defclass point [x (Ptr i64)])\n(defn main [] i32 0)"
|
"(defclass point [x (Ptr i64)])\n(defn main [] i32 0)"
|
||||||
~needle:"a class slot is a name";
|
~needle:"the slot x of point is declared (Ptr i64)";
|
||||||
|
rejects_check "a capitalised name in a slot vector is an unknown type"
|
||||||
|
"(defclass point [x Widget])\n(defn main [] i32 0)"
|
||||||
|
~needle:"unknown type Widget";
|
||||||
|
rejects_check "a slot's type is resolved like any other"
|
||||||
|
"(defclass point [x f65])\n(defn main [] i32 0)"
|
||||||
|
~needle:"did you mean f64";
|
||||||
|
(* The slot is a place: its class declares it, so it always exists. *)
|
||||||
|
accepts "set writes a class slot"
|
||||||
|
"(defclass state [pause bool])\n\
|
||||||
|
(defn main [] i32 (let [s (state false)] (set (get s :pause) true) \
|
||||||
|
(if (get s :pause) 0 1)))";
|
||||||
|
rejects_check "a class slot has no address"
|
||||||
|
"(defclass state [pause bool])\n\
|
||||||
|
(defn main [] i32 (let [s (state false)] (addr (get s :pause)) 0))"
|
||||||
|
~needle:"addr takes the address of a place";
|
||||||
rejects_check "a class does not name a slot twice"
|
rejects_check "a class does not name a slot twice"
|
||||||
"(defclass point [x x])\n(defn main [] i32 0)"
|
"(defclass point [x x])\n(defn main [] i32 0)"
|
||||||
~needle:"names the slot x twice";
|
~needle:"names the slot x twice";
|
||||||
@ -3710,7 +3727,10 @@ let () =
|
|||||||
into a lookup and no place form for one. Refused with that reason rather
|
into a lookup and no place form for one. Refused with that reason rather
|
||||||
than as a milestone that will never arrive. *)
|
than as a milestone that will never arrive. *)
|
||||||
rejects_check "a map entry as a place"
|
rejects_check "a map entry as a place"
|
||||||
"(defn f [] () (set (get m 1) 2))"
|
"(defn f [m (Map i64 i64)] () (set (get m 1) 2))"
|
||||||
|
~needle:"entries are written with (put m k v)";
|
||||||
|
rejects_check "get with three arguments is not a place"
|
||||||
|
"(defn f [m dyn] () (set (get m 1 2) 2))"
|
||||||
~needle:"a map is written with (put m k v)";
|
~needle:"a map is written with (put m k v)";
|
||||||
|
|
||||||
(* ── restart-case and invoke-restart, §3 to §6 ─────────────────── *)
|
(* ── restart-case and invoke-restart, §3 to §6 ─────────────────── *)
|
||||||
|
|||||||
@ -1384,6 +1384,20 @@ let () =
|
|||||||
(String.concat " " c.Session.fns)
|
(String.concat " " c.Session.fns)
|
||||||
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
||||||
fail "a class and its caller evaluated together: %s" m);
|
fail "a class and its caller evaluated together: %s" m);
|
||||||
|
(* A slot's type changed and nothing else. Every constructor parameter is
|
||||||
|
dyn whatever the slot says, so the signature is the one it was and a
|
||||||
|
compiled caller is no reason to refuse — the type is checked where a
|
||||||
|
value is stored, at run time. What has to reach the program is the new
|
||||||
|
definition, and the registration carries it with the type after the
|
||||||
|
name, which is what makes the runtime see a change and migrate. *)
|
||||||
|
(let t, _ = Session.create ~file:"programs/dev-class.flan" () in
|
||||||
|
ignore (Session.eval t "(defn origin [] dyn (point 0 0))");
|
||||||
|
match Session.eval t "(defclass point [x i64 y])" with
|
||||||
|
| c ->
|
||||||
|
if not (has c.Session.ir "c\"x i64\\0Ay\"") then
|
||||||
|
fail "a slot's new type did not reach the registration"
|
||||||
|
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
||||||
|
fail "a slot's type changed under a compiled caller was refused: %s" m);
|
||||||
|
|
||||||
(* ── What a slot is shown as ──────────────────────────────────────────
|
(* ── What a slot is shown as ──────────────────────────────────────────
|
||||||
[strip_rebind] takes only a trailing ~N — [~] is the reader's delimiter
|
[strip_rebind] takes only a trailing ~N — [~] is the reader's delimiter
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
;; A class is a named dyn map with a shape tag. Its slots are names and
|
;; A class is a named dyn map with a shape tag. A slot with no type holds
|
||||||
;; carry no types, and its constructor is the class's own name, positional.
|
;; any value, and its constructor is the class's own name, positional.
|
||||||
(defclass point [x y])
|
(defclass point [x y])
|
||||||
(defclass circle [r])
|
(defclass circle [r])
|
||||||
|
|
||||||
|
|||||||
@ -710,10 +710,16 @@ unit carries nothing for a dyn word to hold, and boxing it is refused — and
|
|||||||
<h3>Classes and generic functions</h3>
|
<h3>Classes and generic functions</h3>
|
||||||
|
|
||||||
<p>A class is a named dyn map with a shape tag. <code>defclass</code> names its
|
<p>A class is a named dyn map with a shape tag. <code>defclass</code> names its
|
||||||
slots, which carry no types; the constructor is the class's own name and is
|
slots, and a slot may be followed by a type, the way a parameter is:
|
||||||
positional; and <code>class-of</code> answers the tag, or <code>nil</code> for
|
<code>[x y]</code> is two slots that hold any value, and <code>[pause bool]</code>
|
||||||
anything that is not an instance. The slots are map keys, so nothing was added to
|
is one that holds only a bool. The type is checked whenever a value is stored,
|
||||||
read or write one.</p>
|
and a slot may be <code>bool</code>, an integer type, <code>f32</code>,
|
||||||
|
<code>f64</code> or <code>string</code>. The constructor is the class's own name
|
||||||
|
and is positional, and <code>class-of</code> answers the tag, or <code>nil</code>
|
||||||
|
for anything that is not an instance. The slots are map keys: <code>get</code>
|
||||||
|
reads one, and <code>set</code> writes one, as in
|
||||||
|
<code>(set (get s :pause) true)</code>. <code>put</code> writes one too, and is
|
||||||
|
also how a key the class does not declare is added.</p>
|
||||||
|
|
||||||
<p>Dispatch comes in the two styles and they are one mechanism.
|
<p>Dispatch comes in the two styles and they are one mechanism.
|
||||||
<code>defgeneric</code> dispatches on the class of the first argument, which is
|
<code>defgeneric</code> dispatches on the class of the first argument, which is
|
||||||
@ -725,8 +731,8 @@ which is last whatever order it was written in. The generic states the return ty
|
|||||||
once, for every method; a method has no return slot; and every parameter of both is
|
once, for every method; a method has no return slot; and every parameter of both is
|
||||||
<code>dyn</code>, written or not.</p>
|
<code>dyn</code>, written or not.</p>
|
||||||
|
|
||||||
<pre><code>;; A class is a named dyn map with a shape tag. Its slots are names and
|
<pre><code>;; A class is a named dyn map with a shape tag. A slot with no type holds
|
||||||
;; carry no types, and its constructor is the class's own name, positional.
|
;; any value, and its constructor is the class's own name, positional.
|
||||||
(defclass point [x y])
|
(defclass point [x y])
|
||||||
(defclass circle [r])
|
(defclass circle [r])
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user