@mark1russell7/coeval 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/world.ts ADDED
@@ -0,0 +1,163 @@
1
+ import { type Envelope, type Json, type Key, type TypeRef, field, isRecord, typePath } from "./doc.ts";
2
+
3
+ /** The two sides of evaluation. `derive` is pure. `run` causes effects in order. */
4
+ export type Side = "derive" | "run";
5
+
6
+ /**
7
+ * Where the parts of a value are. A part is a typed document that the evaluator gets sealed.
8
+ *
9
+ * - `none`: the value is data. It has no parts.
10
+ * - `list`: the value is a list, and each item is a part.
11
+ * - `record`: the value is a record. The named fields are parts, and the other fields are data.
12
+ *
13
+ * `of` is the type of a bare item. An item without an envelope gets this type from its container.
14
+ */
15
+ export type PartsSpec =
16
+ | { readonly shape: "none" }
17
+ | { readonly shape: "list"; readonly of?: string }
18
+ | { readonly shape: "record"; readonly fields: readonly string[]; readonly of?: string };
19
+
20
+ /** A kind gives the name of an evaluator for each verb, and the contract of its value. */
21
+ export interface Kind {
22
+ readonly verbs: Readonly<Record<string, string>>;
23
+ /** Where the parts of the value are. The default is `none`. */
24
+ readonly parts?: PartsSpec;
25
+ /** The side of the kind. A kind of the run side causes effects. The default is `derive`. */
26
+ readonly side?: Side;
27
+ /** One sentence that tells what the kind is. */
28
+ readonly summary?: string;
29
+ }
30
+
31
+ /** A namespace: a group of entries under one key of the world. */
32
+ export interface Namespace {
33
+ readonly kinds: Readonly<Record<string, WorldEntry>>;
34
+ }
35
+
36
+ /** An alias: a reference to another entry of the world. The lookup follows it (collapse). */
37
+ export type Alias = { readonly type: "ref"; readonly value: readonly Json[] };
38
+
39
+ /** One entry of the world: a kind, a namespace, or an alias. */
40
+ export type WorldEntry = Kind | Namespace | Alias;
41
+
42
+ /** A world holds the kinds and the defaults that answer the questions. */
43
+ export interface World {
44
+ readonly kinds: Readonly<Record<string, WorldEntry>>;
45
+ readonly defaults: Readonly<Record<string, string>>;
46
+ }
47
+
48
+ /** The result of a lookup of a type in the world. */
49
+ export type Lookup =
50
+ | { readonly ok: true; readonly kind: Kind; readonly path: readonly Key[]; readonly aliases: number }
51
+ | { readonly ok: false; readonly reason: "unknown" | "cycle" | "not-a-kind" | "computed"; readonly path: readonly Key[] };
52
+
53
+ /**
54
+ * This function tells if an entry of the world is a kind.
55
+ *
56
+ * @param entry - The entry.
57
+ * @returns `true` for a kind.
58
+ */
59
+ export function isKind(entry: WorldEntry | undefined): entry is Kind {
60
+ return entry !== undefined && Object.hasOwn(entry, "verbs");
61
+ }
62
+
63
+ /**
64
+ * This function tells if an entry of the world is an alias.
65
+ *
66
+ * @param entry - The entry.
67
+ * @returns `true` for an alias.
68
+ */
69
+ export function isAlias(entry: WorldEntry | undefined): entry is Alias {
70
+ return entry !== undefined && (entry as { type?: unknown }).type === "ref" && Array.isArray((entry as { value?: unknown }).value);
71
+ }
72
+
73
+ function entryAt(entries: Readonly<Record<string, WorldEntry>>, key: Key): WorldEntry | undefined {
74
+ const name = String(key);
75
+ return Object.hasOwn(entries, name) ? entries[name] : undefined;
76
+ }
77
+
78
+ /**
79
+ * This function finds the kind that a type names. It walks the path from the root of the world, one key at a
80
+ * time. An alias on the way is followed until a kind answers (collapse). A cycle of aliases stops the lookup.
81
+ *
82
+ * @param world - The world.
83
+ * @param type - The reference to the type.
84
+ * @returns The kind and the path of the kind after each alias, or the reason of a failure.
85
+ */
86
+ export function lookupKind(world: World, type: TypeRef): Lookup {
87
+ const start = typePath(type);
88
+ if (start === undefined) return { ok: false, reason: "computed", path: [] };
89
+ let path: readonly Key[] = start;
90
+ let aliases = 0;
91
+ for (;;) {
92
+ let entries = world.kinds;
93
+ let entry: WorldEntry | undefined;
94
+ let followed = false;
95
+ for (let index = 0; index < path.length; index += 1) {
96
+ const key = path[index] as Key;
97
+ entry = entryAt(entries, key);
98
+ if (entry === undefined) return { ok: false, reason: "unknown", path };
99
+ if (isAlias(entry)) {
100
+ aliases += 1;
101
+ if (aliases > 32) return { ok: false, reason: "cycle", path };
102
+ const target = entry.value.slice(1);
103
+ if (entry.value[0] !== "world" || !target.every((item) => typeof item === "string" || typeof item === "number")) {
104
+ return { ok: false, reason: "not-a-kind", path };
105
+ }
106
+ path = [...(target as readonly Key[]), ...path.slice(index + 1)];
107
+ followed = true;
108
+ break;
109
+ }
110
+ if (index < path.length - 1) {
111
+ if (isKind(entry)) return { ok: false, reason: "not-a-kind", path };
112
+ entries = (entry as Namespace).kinds;
113
+ }
114
+ }
115
+ if (followed) continue;
116
+ if (!isKind(entry)) return { ok: false, reason: "not-a-kind", path };
117
+ return { ok: true, kind: entry, path, aliases };
118
+ }
119
+ }
120
+
121
+ /**
122
+ * This function gives the entries of a world as a JSON record, so that a reference can walk it. A kind is a
123
+ * record with its verbs, an alias is a reference document, and a namespace is a record of its entries.
124
+ *
125
+ * @param entries - The entries.
126
+ * @returns The JSON form.
127
+ */
128
+ export function entriesAsJson(entries: Readonly<Record<string, WorldEntry>>): Json {
129
+ const out: Record<string, Json> = {};
130
+ for (const [key, entry] of Object.entries(entries)) {
131
+ if (isAlias(entry)) out[key] = { type: "ref", value: entry.value };
132
+ else if (isKind(entry)) out[key] = JSON.parse(JSON.stringify(entry)) as Json;
133
+ else out[key] = entriesAsJson((entry as Namespace).kinds);
134
+ }
135
+ return out;
136
+ }
137
+
138
+ /**
139
+ * This function makes the document of a kind, so that a view can draw it as each other document.
140
+ *
141
+ * @param name - The name of the kind.
142
+ * @param kind - The kind.
143
+ * @returns The document of type `kind`.
144
+ */
145
+ export function kindDocument(name: string, kind: Kind): Envelope {
146
+ const parts: Json =
147
+ kind.parts === undefined || kind.parts.shape === "none"
148
+ ? "none"
149
+ : kind.parts.shape === "list"
150
+ ? `a list${kind.parts.of === undefined ? "" : ` of ${kind.parts.of}`}`
151
+ : `the fields ${kind.parts.fields.join(", ")}`;
152
+ return { type: "kind", value: { name, verbs: { ...kind.verbs }, parts, side: kind.side ?? "derive", summary: kind.summary ?? "" } };
153
+ }
154
+
155
+ /**
156
+ * This function tells if a JSON value is the record of a kind.
157
+ *
158
+ * @param value - The value.
159
+ * @returns `true` when the value has verbs.
160
+ */
161
+ export function looksLikeKind(value: Json | undefined): boolean {
162
+ return isRecord(value) && isRecord(field(value, "verbs"));
163
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.