@cavelang/canonical 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/prelude.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The shared standard prelude (spec §5.5).
3
+ *
4
+ * Standard verbs SHOULD carry inverse declarations; emitters MAY prepend
5
+ * them to a document or keep them in a shared prelude. This module is that
6
+ * shared prelude: the spec's declaration block as CAVE text, plus a
7
+ * pre-built registry for callers that want inverse-aware reads without
8
+ * ingesting the text first.
9
+ */
10
+
11
+ import * as Registry from './registry.ts'
12
+
13
+ /** The spec §5.5 declaration block, verbatim. */
14
+ export const standardPrelude = `REVERSE IS verb ; declares that two verbs name the same edge read in opposite directions
15
+ REVERSE HAS arity: 2
16
+ CONTAINS REVERSE PART-OF
17
+ CAUSE REVERSE CAUSED-BY
18
+ PRECEDES REVERSE FOLLOWS
19
+ USES REVERSE USED-BY
20
+ NEEDS REVERSE NEEDED-BY
21
+ ENABLES REVERSE ENABLED-BY
22
+ BLOCKS REVERSE BLOCKED-BY
23
+ EXTENDS REVERSE EXTENDED-BY
24
+ `
25
+
26
+ const standardPairs: readonly [string, string][] = [
27
+ ['CONTAINS', 'PART-OF'],
28
+ ['CAUSE', 'CAUSED-BY'],
29
+ ['PRECEDES', 'FOLLOWS'],
30
+ ['USES', 'USED-BY'],
31
+ ['NEEDS', 'NEEDED-BY'],
32
+ ['ENABLES', 'ENABLED-BY'],
33
+ ['BLOCKS', 'BLOCKED-BY'],
34
+ ['EXTENDS', 'EXTENDED-BY']
35
+ ]
36
+
37
+ /** Registry with the standard §5.5 inverse pairs declared. */
38
+ export const standardRegistry: Registry.t =
39
+ standardPairs.reduce<Registry.t>((registry, [primary, inverse]) => {
40
+ const declared = Registry.declareReverse(registry, primary, inverse)
41
+ return declared.registry
42
+ }, Registry.declareVerb(Registry.empty, 'REVERSE'))
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Verb registry (spec §5.4, §5.5).
3
+ *
4
+ * Tracks in-band verb knowledge: extension verb declarations (`MIGRATES IS
5
+ * verb`) and inverse pairs (`CONTAINS REVERSE PART-OF`). No verb is born
6
+ * with an inverse — a relation without a `REVERSE` declaration simply has
7
+ * no reverse name.
8
+ *
9
+ * The registry is an immutable value; declaring returns a new registry, so
10
+ * the canonicalization pipeline can thread it through a document and
11
+ * declarations take effect for subsequent lines only.
12
+ */
13
+
14
+ import { Verb } from '@cavelang/core'
15
+
16
+ /** An inverse pair. The primary is the left side of the first declaration (spec §5.5). */
17
+ export type Pair = {
18
+ readonly primary: string
19
+ readonly inverse: string
20
+ }
21
+
22
+ export type Registry = {
23
+ /** Every verb of every pair → its pair. */
24
+ readonly pairs: ReadonlyMap<string, Pair>
25
+ /** Extension verbs declared via `X IS verb`. */
26
+ readonly declared: ReadonlySet<string>
27
+ }
28
+
29
+ export type t = Registry
30
+
31
+ /** Registry with no declarations. */
32
+ export const empty: Registry = {
33
+ pairs: new Map(),
34
+ declared: new Set()
35
+ }
36
+
37
+ export type Declared =
38
+ | { readonly ok: true, readonly registry: Registry }
39
+ | { readonly ok: false, readonly registry: Registry, readonly problem: string }
40
+
41
+ /**
42
+ * Declares `a REVERSE b`: `a` is primary, `b` its inverse (spec §5.5).
43
+ * Redeclaring an existing pair in either direction is a no-op; a
44
+ * declaration that conflicts with an existing pair is rejected — the first
45
+ * declaration wins.
46
+ */
47
+ export const declareReverse = (registry: Registry, a: string, b: string): Declared => {
48
+ if (!Verb.isVerbToken(a) || !Verb.isVerbToken(b)) {
49
+ return { ok: false, registry, problem: `REVERSE operands must be UPPERCASE verbs, got ${a} REVERSE ${b}` }
50
+ }
51
+ const existingA = registry.pairs.get(a)
52
+ const existingB = registry.pairs.get(b)
53
+ const existing = existingA ?? existingB
54
+ if (existing !== undefined) {
55
+ const same =
56
+ (existing.primary === a && existing.inverse === b) ||
57
+ (existing.primary === b && existing.inverse === a)
58
+ if (same && existingA === existingB) {
59
+ return { ok: true, registry }
60
+ }
61
+ return {
62
+ ok: false,
63
+ registry,
64
+ problem: `${a} REVERSE ${b} conflicts with existing ${existing.primary} REVERSE ${existing.inverse} — first declaration wins (spec §5.5)`
65
+ }
66
+ }
67
+ const pair: Pair = { primary: a, inverse: b }
68
+ const pairs = new Map(registry.pairs)
69
+ pairs.set(a, pair)
70
+ pairs.set(b, pair)
71
+ return { ok: true, registry: { pairs, declared: registry.declared } }
72
+ }
73
+
74
+ /** Declares an extension verb (`X IS verb`, spec §5.4). */
75
+ export const declareVerb = (registry: Registry, verb: string): Registry => {
76
+ if (registry.declared.has(verb)) {
77
+ return registry
78
+ }
79
+ const declared = new Set(registry.declared)
80
+ declared.add(verb)
81
+ return { pairs: registry.pairs, declared }
82
+ }
83
+
84
+ /**
85
+ * @returns the canonical primary of `verb` and whether `verb` is the
86
+ * inverse side. An unpaired verb is its own primary.
87
+ */
88
+ export const primaryOf = (registry: Registry, verb: string): { primary: string, isInverse: boolean } => {
89
+ const pair = registry.pairs.get(verb)
90
+ if (pair === undefined || pair.primary === verb) {
91
+ return { primary: verb, isInverse: false }
92
+ }
93
+ return { primary: pair.primary, isInverse: true }
94
+ }
95
+
96
+ /**
97
+ * @returns the opposite name of `verb` — `CONTAINS` → `PART-OF`,
98
+ * `PART-OF` → `CONTAINS` — or `undefined` when no inverse is declared
99
+ * (reverse reads then fall back to an un-named object-side scan, spec §5.5).
100
+ */
101
+ export const inverseOf = (registry: Registry, verb: string): undefined | string => {
102
+ const pair = registry.pairs.get(verb)
103
+ if (pair === undefined) {
104
+ return undefined
105
+ }
106
+ return pair.primary === verb ? pair.inverse : pair.primary
107
+ }
108
+
109
+ /** @returns `true` if `verb` was declared as an extension verb. */
110
+ export const isDeclared = (registry: Registry, verb: string): boolean =>
111
+ registry.declared.has(verb)
112
+
113
+ /** All pairs, deduplicated, in insertion order. */
114
+ export const allPairs = (registry: Registry): Pair[] =>
115
+ [...new Set(registry.pairs.values())]