type-of answers a value's kind as a keyword or an instance's class name, and a class named like a kind is refused.
This commit is contained in:
parent
8aaf33589e
commit
e80411d283
5
TODO.org
5
TODO.org
@ -20,11 +20,6 @@ Dyn text stays immutable, with chars and text converting to and from a dyn
|
||||
vector of characters; length and indexing count characters on dyn text and bytes on
|
||||
str. Waits on the dyn-unless-annotated design.
|
||||
|
||||
** NEXT type-of
|
||||
Decided 2026-09-25: (type-of x) answers a keyword — :nil :bool :int :float :text :vec
|
||||
:map :keyword, :char once dyn has one, and a class instance's class name as class-of
|
||||
does; a class named like a built-in kind is refused.
|
||||
|
||||
** NEXT Any typed container crosses into dyn as a view
|
||||
Decided 2026-09-25: every element type (all numbers, chars, structs, nested arrays)
|
||||
and any storage; a dev build checks a view against its frame or allocation and traps
|
||||
|
||||
@ -167,7 +167,7 @@ face says.")
|
||||
;; Map
|
||||
"map-new" "put" "get" "map-remove" "map-next" "has-key?"
|
||||
;; dyn
|
||||
"class-of" "keyword"
|
||||
"class-of" "type-of" "keyword"
|
||||
;; compile time
|
||||
"embed" "embed-dir" "compile-error"
|
||||
;; files
|
||||
|
||||
18
lib/check.ml
18
lib/check.ml
@ -11266,6 +11266,19 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
(rt loc Types.Dyn "flan_dyn_class_of" [ check ctx ~want:Types.Dyn v ])
|
||||
| _ -> assert false)
|
||||
|
||||
(* (type-of v) -> the value's kind as a keyword, or a class instance's
|
||||
class name. A typed argument crosses into dyn first, as it does for
|
||||
class-of and every other dyn builtin, so it answers the kind the crossing
|
||||
makes of it: a typed i32 is :int and an f32 is :float. That keeps one
|
||||
answer per value whichever side of the program holds it. *)
|
||||
| "type-of" ->
|
||||
arity ctx loc name 1 args;
|
||||
(match args with
|
||||
| [ v ] ->
|
||||
expect ctx loc ~want
|
||||
(rt loc Types.Dyn "flan_dyn_type_of" [ check ctx ~want:Types.Dyn v ])
|
||||
| _ -> assert false)
|
||||
|
||||
(* (map-remove m k) -> (Option V): the value that was there, or None when
|
||||
the key was not. The same answer [get] gives, for the same reason — a key
|
||||
that is not in the map is an answer and not a failure — and the value
|
||||
@ -13757,6 +13770,11 @@ let builtins : (string * string * string) list =
|
||||
It is what a defgeneric dispatches on, so a class dispatcher is this \
|
||||
call over the first argument and a defmulti whose body is (class-of x) \
|
||||
is the same generic function written the other way.");
|
||||
("type-of", "type-of [dyn] dyn",
|
||||
"The value's kind as a keyword — :nil :bool :int :float :text :vec \
|
||||
:keyword :map — or, for a value built by a defclass constructor, the \
|
||||
class's name as class-of answers it. A typed value answers the kind it \
|
||||
has as a dyn value: an i32 is :int.");
|
||||
("keyword", "keyword [string|[const u8]] dyn",
|
||||
"The interned dyn keyword named by the bytes, for a name that only \
|
||||
exists at run time — a reader building :texture-path out of a token's \
|
||||
|
||||
@ -4993,6 +4993,7 @@ declare void @flan_dyn_slot_init(i64, i64, i64, ptr, i64)
|
||||
declare void @flan_dyn_ctor_site(ptr, i64)
|
||||
declare void @flan_dyn_map_put(i64, i64, i64, ptr, i64)
|
||||
declare i64 @flan_dyn_class_of(i64)
|
||||
declare i64 @flan_dyn_type_of(i64)
|
||||
declare void @flan_dyn_class_def(i64, ptr, i64)
|
||||
declare void @flan_dyn_class_hook(ptr)
|
||||
declare i64 @flan_dyn_kw(ptr, i64)
|
||||
|
||||
16
lib/parse.ml
16
lib/parse.ml
@ -1682,6 +1682,22 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
[[x y]] two dyn slots, [[pause bool step bool]] two typed ones — and
|
||||
is carried undecided for the same reason. *)
|
||||
| [ n; { v = Vec slots; _ } ] ->
|
||||
(* type-of answers a class instance's class name and any other value's
|
||||
kind, so a class named like a kind would make :map (say) mean two
|
||||
things. Checked before [tname] so bool, int and float get this
|
||||
sentence too. char is reserved for dyn's char kind. *)
|
||||
(match n.v with
|
||||
| Sym s
|
||||
when List.mem s
|
||||
[ "nil"; "bool"; "int"; "float"; "text"; "vec"; "map";
|
||||
"keyword"; "char" ] ->
|
||||
Loc.failk "parse/class-named-kind" n.loc
|
||||
"%s is the name of a kind of dyn value, so (type-of x) would \
|
||||
answer :%s both for an instance of this class and for a plain \
|
||||
%s. Name the class %s-value, or any name that is not one of \
|
||||
nil bool int float text vec map keyword char"
|
||||
s s s s
|
||||
| _ -> ());
|
||||
List.iter
|
||||
(fun (s : Form.t) -> match s.v with Sym _ -> no_sigil s | _ -> ())
|
||||
slots;
|
||||
|
||||
@ -2295,6 +2295,27 @@ flan_dyn flan_dyn_class_of(flan_dyn v) {
|
||||
return dyn_make(BOX_KW, (uint64_t)(uintptr_t)o->u.v.klass);
|
||||
}
|
||||
|
||||
/* The kind of a value as a keyword named by [tag_words], or a class instance's
|
||||
* class name as [flan_dyn_class_of] answers it. A class may not be named like
|
||||
* a kind (the parser refuses it), so :map always means a plain map. Keywords
|
||||
* are immortal, so each kind's keyword is interned once and kept. When dyn
|
||||
* gains a char, its tag gets a word in [tag_words] and :char falls out here
|
||||
* with no change to this function. */
|
||||
flan_dyn flan_dyn_type_of(flan_dyn v) {
|
||||
static flan_dyn kinds[FLAN_DYN_TAG_MAP + 1];
|
||||
static int interned;
|
||||
int32_t t = flan_dyn_tag(v);
|
||||
if (t == FLAN_DYN_TAG_MAP && dyn_obj(v)->u.v.klass != NULL)
|
||||
return flan_dyn_class_of(v);
|
||||
if (!interned) {
|
||||
for (int i = 0; i <= FLAN_DYN_TAG_MAP; i++)
|
||||
kinds[i] = flan_dyn_kw((const uint8_t *)tag_words[i],
|
||||
(int64_t)strlen(tag_words[i]));
|
||||
interned = 1;
|
||||
}
|
||||
return kinds[t];
|
||||
}
|
||||
|
||||
/* ── Keywords ──────────────────────────────────────────────────────────
|
||||
*
|
||||
* One global table, append-only, never freed: a keyword is a *name*, the set
|
||||
|
||||
@ -133,6 +133,11 @@ void flan_dyn_slot_set(flan_dyn m, flan_dyn k, flan_dyn v,
|
||||
* — an ordinary map included. Never traps. */
|
||||
flan_dyn flan_dyn_class_of(flan_dyn v);
|
||||
|
||||
/* The value's kind as a keyword — :nil :bool :int :float :text :vec :keyword
|
||||
* :map — or, for a class instance, its class name as [flan_dyn_class_of]
|
||||
* answers it. Never traps. */
|
||||
flan_dyn flan_dyn_type_of(flan_dyn v);
|
||||
|
||||
/* A class definition, registered or re-registered: [name] is the class's name
|
||||
* as a keyword and [slots]/[n] is its slots packed into one string, a line
|
||||
* each, the slot's name and then — after a space, for a typed slot — its
|
||||
|
||||
36
test/programs/dyn-type-of.flan
Normal file
36
test/programs/dyn-type-of.flan
Normal file
@ -0,0 +1,36 @@
|
||||
;;;; type-of: every value's kind as a keyword, and a class instance's class
|
||||
;;;; name, which is what class-of answers for it.
|
||||
|
||||
(defclass point [x y])
|
||||
|
||||
;; A defmulti over type-of is dispatch on the kind of the first argument.
|
||||
(defmulti describe [v] dyn (type-of v))
|
||||
(defmethod describe :int [v] "a whole number")
|
||||
(defmethod describe :text [v] "some text")
|
||||
(defmethod describe :point [v] "a point")
|
||||
(defmethod describe :else [v] "something else")
|
||||
|
||||
(defn main [] i32
|
||||
(let [n (the i32 7)
|
||||
f (the f32 1.5)
|
||||
b (the bool true)
|
||||
s (the string "typed")]
|
||||
(println (type-of nil))
|
||||
(println (type-of true))
|
||||
(println (type-of 42))
|
||||
(println (type-of 2.5))
|
||||
(println (type-of "hello"))
|
||||
(println (type-of [1 2 3]))
|
||||
(println (type-of {:a 1}))
|
||||
(println (type-of :k))
|
||||
(println (type-of (point 1 2)))
|
||||
;; Typed values cross into dyn first and answer the kind they become.
|
||||
(println (type-of n))
|
||||
(println (type-of f))
|
||||
(println (type-of b))
|
||||
(println (type-of s))
|
||||
(println (describe 3))
|
||||
(println (describe "x"))
|
||||
(println (describe (point 3 4)))
|
||||
(println (describe [1]))
|
||||
(if (= (type-of (point 0 0)) (class-of (point 0 0))) 0 1)))
|
||||
@ -5298,6 +5298,18 @@ level "1"
|
||||
"programs/dyn-class.flan" dyn_class_out;
|
||||
outputs ~x86:true "dyn: classes and dispatch, --x86"
|
||||
"programs/dyn-class.flan" dyn_class_out;
|
||||
(* type-of over each kind, a class instance, four typed values crossing
|
||||
into dyn, and a defmulti dispatching on it. *)
|
||||
let dyn_type_of_out =
|
||||
":nil\n:bool\n:int\n:float\n:text\n:vec\n:map\n:keyword\n:point\n\
|
||||
:int\n:float\n:bool\n:text\n\
|
||||
a whole number\nsome text\na point\nsomething else\n"
|
||||
in
|
||||
outputs "dyn: type-of" "programs/dyn-type-of.flan" dyn_type_of_out;
|
||||
outputs ~opt:"-O0" "dyn: type-of, -O0"
|
||||
"programs/dyn-type-of.flan" dyn_type_of_out;
|
||||
outputs ~x86:true "dyn: type-of, --x86"
|
||||
"programs/dyn-type-of.flan" dyn_type_of_out;
|
||||
(* (watch "name" v) with nothing arming the table: a struct, an array, a
|
||||
slice, a dyn map and a string all compile against flan_dev.c's watch
|
||||
entry points on both backends, write nothing, and evaluate the value
|
||||
|
||||
@ -9215,7 +9215,11 @@ let () =
|
||||
answers what it always did, which is what keeps every method
|
||||
ever written for this class reachable. *)
|
||||
holds "the instance is still an instance of its class"
|
||||
"(if (= (class-of (at instances 1)) :point) 1 0)"
|
||||
"(if (= (class-of (at instances 1)) :point) 1 0)";
|
||||
holds "type-of answers the class name in a session"
|
||||
"(if (= (type-of (at instances 1)) :point) 1 0)";
|
||||
holds "and a kind for anything that is not an instance"
|
||||
"(if (= (type-of (get (at instances 1) :w)) :nil) 1 0)"
|
||||
end;
|
||||
|
||||
(* ── A definition that did not change ──
|
||||
|
||||
@ -2974,6 +2974,20 @@ let () =
|
||||
"(defclass marker [])\n(defn main [] i32 (let [m (marker)] 0))";
|
||||
accepts "class-of answers nil for anything that is not an instance"
|
||||
"(defn main [] i32 (if (= (class-of 1) nil) 0 1))";
|
||||
(* type-of answers a class instance's name, so a class may not take the
|
||||
name of a kind: :map would mean a plain map and an instance at once.
|
||||
bool, int and float are also built-in types; they get this sentence. *)
|
||||
List.iter
|
||||
(fun k ->
|
||||
rejects_check ("a class may not be named " ^ k)
|
||||
(Printf.sprintf "(defclass %s [a])\n(defn main [] i32 0)" k)
|
||||
~needle:(Printf.sprintf "Name the class %s-value" k))
|
||||
[ "nil"; "bool"; "int"; "float"; "text"; "vec"; "map"; "keyword"; "char" ];
|
||||
accepts "the fix a class-named-kind refusal offers compiles"
|
||||
"(defclass map-value [a])\n\
|
||||
(defn main [] i32 (if (= (type-of (map-value 1)) :map-value) 0 1))";
|
||||
rejects_check "type-of takes one argument"
|
||||
"(defn main [] i32 (let [k (type-of 1 2)] 0))" ~needle:"type-of";
|
||||
(* The constructor is an ordinary function, so its arity is the ordinary
|
||||
arity check and a wrong one names the class. *)
|
||||
rejects_check "a constructor takes one argument per slot"
|
||||
|
||||
@ -720,7 +720,11 @@ and a slot may be <code>bool</code>, an integer type, <code>f32</code>,
|
||||
<code>f64</code>, <code>string</code>, a class, or <code>(Option T)</code> of one of
|
||||
those, which also admits <code>nil</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>
|
||||
for anything that is not an instance. <code>type-of</code> answers any value's
|
||||
kind as a keyword — <code>:nil</code>, <code>:bool</code>, <code>:int</code>,
|
||||
<code>:float</code>, <code>:text</code>, <code>:vec</code>, <code>:map</code> or
|
||||
<code>:keyword</code> — and an instance's class name, so a class cannot be named
|
||||
after one of those kinds. 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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user