@aleph-ai/tinyaleph 1.0.2 → 1.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/core/types.js ADDED
@@ -0,0 +1,913 @@
1
+ /**
2
+ * Formal Type System for Prime-Based Compositional Languages
3
+ *
4
+ * Implements a typed term calculus for prime-indexed semantics:
5
+ * - N(p): noun/subject term indexed by prime p
6
+ * - A(p): adjective/operator term indexed by prime p
7
+ * - S: sentence-level proposition
8
+ *
9
+ * Key features:
10
+ * - Ordered operator application with p < q constraint
11
+ * - Triadic fusion FUSE(p, q, r) where p+q+r is prime
12
+ * - Sentence composition (◦) and implication (⇒)
13
+ * - Full typing judgments Γ ⊢ e : T
14
+ */
15
+
16
+ const { isPrime, firstNPrimes } = require('./prime');
17
+
18
+ // ============================================================================
19
+ // TYPE DEFINITIONS
20
+ // ============================================================================
21
+
22
+ /**
23
+ * Type constants for the type system
24
+ */
25
+ const Types = {
26
+ NOUN: 'N',
27
+ ADJECTIVE: 'A',
28
+ SENTENCE: 'S'
29
+ };
30
+
31
+ /**
32
+ * Base class for all typed terms
33
+ */
34
+ class Term {
35
+ constructor(type) {
36
+ this.type = type;
37
+ }
38
+
39
+ /**
40
+ * Check if term is well-formed
41
+ */
42
+ isWellFormed() {
43
+ throw new Error('Must be implemented by subclass');
44
+ }
45
+
46
+ /**
47
+ * Get the semantic signature
48
+ */
49
+ signature() {
50
+ throw new Error('Must be implemented by subclass');
51
+ }
52
+
53
+ /**
54
+ * Clone the term
55
+ */
56
+ clone() {
57
+ throw new Error('Must be implemented by subclass');
58
+ }
59
+
60
+ /**
61
+ * Convert to string representation
62
+ */
63
+ toString() {
64
+ throw new Error('Must be implemented by subclass');
65
+ }
66
+ }
67
+
68
+ // ============================================================================
69
+ // NOUN TERMS N(p)
70
+ // ============================================================================
71
+
72
+ /**
73
+ * NounTerm - N(p)
74
+ * Represents a noun/subject indexed by prime p
75
+ * Semantically denotes the prime itself: ⟦N(p)⟧ = p
76
+ */
77
+ class NounTerm extends Term {
78
+ /**
79
+ * @param {number} prime - The prime number indexing this noun
80
+ */
81
+ constructor(prime) {
82
+ super(Types.NOUN);
83
+
84
+ if (!isPrime(prime)) {
85
+ throw new TypeError(`NounTerm requires prime number, got ${prime}`);
86
+ }
87
+
88
+ this.prime = prime;
89
+ }
90
+
91
+ isWellFormed() {
92
+ return isPrime(this.prime);
93
+ }
94
+
95
+ signature() {
96
+ return `N(${this.prime})`;
97
+ }
98
+
99
+ clone() {
100
+ return new NounTerm(this.prime);
101
+ }
102
+
103
+ toString() {
104
+ return `N(${this.prime})`;
105
+ }
106
+
107
+ /**
108
+ * Semantic interpretation: ⟦N(p)⟧ = p
109
+ */
110
+ interpret() {
111
+ return this.prime;
112
+ }
113
+
114
+ /**
115
+ * Check equality with another noun term
116
+ */
117
+ equals(other) {
118
+ return other instanceof NounTerm && this.prime === other.prime;
119
+ }
120
+
121
+ toJSON() {
122
+ return { type: 'N', prime: this.prime };
123
+ }
124
+
125
+ static fromJSON(json) {
126
+ return new NounTerm(json.prime);
127
+ }
128
+ }
129
+
130
+ // ============================================================================
131
+ // ADJECTIVE TERMS A(p)
132
+ // ============================================================================
133
+
134
+ /**
135
+ * AdjTerm - A(p)
136
+ * Represents an adjective/operator indexed by prime p
137
+ * Semantically denotes a partial function: ⟦A(p)⟧ = f_p : D ⇀ D
138
+ * where dom(f_p) ⊆ {q ∈ D : p < q}
139
+ */
140
+ class AdjTerm extends Term {
141
+ /**
142
+ * @param {number} prime - The prime number indexing this adjective
143
+ */
144
+ constructor(prime) {
145
+ super(Types.ADJECTIVE);
146
+
147
+ if (!isPrime(prime)) {
148
+ throw new TypeError(`AdjTerm requires prime number, got ${prime}`);
149
+ }
150
+
151
+ this.prime = prime;
152
+ }
153
+
154
+ isWellFormed() {
155
+ return isPrime(this.prime);
156
+ }
157
+
158
+ signature() {
159
+ return `A(${this.prime})`;
160
+ }
161
+
162
+ clone() {
163
+ return new AdjTerm(this.prime);
164
+ }
165
+
166
+ toString() {
167
+ return `A(${this.prime})`;
168
+ }
169
+
170
+ /**
171
+ * Check if this adjective can apply to a noun (p < q constraint)
172
+ * @param {NounTerm} noun - The noun to apply to
173
+ */
174
+ canApplyTo(noun) {
175
+ if (!(noun instanceof NounTerm)) {
176
+ throw new TypeError('Can only apply to NounTerm');
177
+ }
178
+ return this.prime < noun.prime;
179
+ }
180
+
181
+ /**
182
+ * Apply this adjective to a noun term
183
+ * Returns a ChainTerm for type safety
184
+ * @param {NounTerm} noun - The noun to apply to
185
+ */
186
+ apply(noun) {
187
+ if (!this.canApplyTo(noun)) {
188
+ throw new TypeError(`Application constraint violated: ${this.prime} must be < ${noun.prime}`);
189
+ }
190
+ return new ChainTerm([this], noun);
191
+ }
192
+
193
+ /**
194
+ * Check equality with another adjective term
195
+ */
196
+ equals(other) {
197
+ return other instanceof AdjTerm && this.prime === other.prime;
198
+ }
199
+
200
+ toJSON() {
201
+ return { type: 'A', prime: this.prime };
202
+ }
203
+
204
+ static fromJSON(json) {
205
+ return new AdjTerm(json.prime);
206
+ }
207
+ }
208
+
209
+ // ============================================================================
210
+ // CHAIN TERMS A(p₁)...A(pₖ)N(q)
211
+ // ============================================================================
212
+
213
+ /**
214
+ * ChainTerm - A(p₁), A(p₂), ..., A(pₖ), N(q)
215
+ * Represents an operator chain applied to a noun
216
+ * Semantically: ⟦chain⟧ = f_p₁(f_p₂(...f_pₖ(q)...))
217
+ */
218
+ class ChainTerm extends Term {
219
+ /**
220
+ * @param {Array<AdjTerm>} operators - Sequence of adjective operators
221
+ * @param {NounTerm} noun - The noun being modified
222
+ */
223
+ constructor(operators, noun) {
224
+ super(Types.NOUN);
225
+
226
+ if (!Array.isArray(operators)) {
227
+ throw new TypeError('Operators must be an array');
228
+ }
229
+
230
+ if (!(noun instanceof NounTerm)) {
231
+ throw new TypeError('Noun must be a NounTerm');
232
+ }
233
+
234
+ this.operators = operators;
235
+ this.noun = noun;
236
+ }
237
+
238
+ /**
239
+ * Check well-formedness: all operators must satisfy p < q constraint
240
+ */
241
+ isWellFormed() {
242
+ if (this.operators.length === 0) {
243
+ return this.noun.isWellFormed();
244
+ }
245
+
246
+ // Check innermost constraint: last operator's prime < noun's prime
247
+ const last = this.operators[this.operators.length - 1];
248
+ if (last.prime >= this.noun.prime) {
249
+ return false;
250
+ }
251
+
252
+ // Check chain constraints: each p_i < p_{i+1} for proper ordering
253
+ // This follows from the composition semantics
254
+ for (let i = 0; i < this.operators.length - 1; i++) {
255
+ // In a well-formed chain, each operator must be able to apply
256
+ // to the result of the inner operators
257
+ // This is validated during reduction
258
+ }
259
+
260
+ return true;
261
+ }
262
+
263
+ signature() {
264
+ const ops = this.operators.map(o => o.signature()).join(', ');
265
+ return ops ? `${ops}, ${this.noun.signature()}` : this.noun.signature();
266
+ }
267
+
268
+ clone() {
269
+ return new ChainTerm(
270
+ this.operators.map(o => o.clone()),
271
+ this.noun.clone()
272
+ );
273
+ }
274
+
275
+ toString() {
276
+ const ops = this.operators.map(o => o.toString()).join(' ');
277
+ return ops ? `${ops} ${this.noun}` : this.noun.toString();
278
+ }
279
+
280
+ /**
281
+ * Prepend an operator to this chain
282
+ * @param {AdjTerm} operator - The operator to prepend
283
+ */
284
+ prepend(operator) {
285
+ return new ChainTerm([operator, ...this.operators], this.noun);
286
+ }
287
+
288
+ /**
289
+ * Get the chain length (number of operators)
290
+ */
291
+ get length() {
292
+ return this.operators.length;
293
+ }
294
+
295
+ /**
296
+ * Get all primes in the chain (operators + noun)
297
+ */
298
+ getAllPrimes() {
299
+ return [...this.operators.map(o => o.prime), this.noun.prime];
300
+ }
301
+
302
+ toJSON() {
303
+ return {
304
+ type: 'chain',
305
+ operators: this.operators.map(o => o.toJSON()),
306
+ noun: this.noun.toJSON()
307
+ };
308
+ }
309
+
310
+ static fromJSON(json) {
311
+ return new ChainTerm(
312
+ json.operators.map(o => AdjTerm.fromJSON(o)),
313
+ NounTerm.fromJSON(json.noun)
314
+ );
315
+ }
316
+ }
317
+
318
+ // ============================================================================
319
+ // FUSION TERMS FUSE(p, q, r)
320
+ // ============================================================================
321
+
322
+ /**
323
+ * FusionTerm - FUSE(p, q, r)
324
+ * Represents triadic prime fusion
325
+ * Well-formed when: p, q, r are distinct odd primes and p+q+r is prime
326
+ * Semantically: ⟦FUSE(p,q,r)⟧ = p + q + r
327
+ */
328
+ class FusionTerm extends Term {
329
+ /**
330
+ * @param {number} p - First prime
331
+ * @param {number} q - Second prime
332
+ * @param {number} r - Third prime
333
+ */
334
+ constructor(p, q, r) {
335
+ super(Types.NOUN);
336
+
337
+ this.p = p;
338
+ this.q = q;
339
+ this.r = r;
340
+ }
341
+
342
+ /**
343
+ * Check well-formedness:
344
+ * 1. p, q, r are distinct
345
+ * 2. p, q, r are odd primes (> 2)
346
+ * 3. p + q + r is prime
347
+ */
348
+ isWellFormed() {
349
+ // Check distinctness
350
+ if (this.p === this.q || this.q === this.r || this.p === this.r) {
351
+ return false;
352
+ }
353
+
354
+ // Check all are odd primes (> 2)
355
+ if (this.p === 2 || this.q === 2 || this.r === 2) {
356
+ return false;
357
+ }
358
+
359
+ if (!isPrime(this.p) || !isPrime(this.q) || !isPrime(this.r)) {
360
+ return false;
361
+ }
362
+
363
+ // Check sum is prime
364
+ return isPrime(this.p + this.q + this.r);
365
+ }
366
+
367
+ /**
368
+ * Get the fused prime value
369
+ */
370
+ getFusedPrime() {
371
+ if (!this.isWellFormed()) {
372
+ throw new Error('Cannot get fused prime from ill-formed fusion');
373
+ }
374
+ return this.p + this.q + this.r;
375
+ }
376
+
377
+ signature() {
378
+ return `FUSE(${this.p}, ${this.q}, ${this.r})`;
379
+ }
380
+
381
+ clone() {
382
+ return new FusionTerm(this.p, this.q, this.r);
383
+ }
384
+
385
+ toString() {
386
+ return `FUSE(${this.p}, ${this.q}, ${this.r})`;
387
+ }
388
+
389
+ /**
390
+ * Convert to equivalent NounTerm (after reduction)
391
+ */
392
+ toNounTerm() {
393
+ return new NounTerm(this.getFusedPrime());
394
+ }
395
+
396
+ /**
397
+ * Get canonical form (sorted primes)
398
+ */
399
+ canonical() {
400
+ const sorted = [this.p, this.q, this.r].sort((a, b) => a - b);
401
+ return new FusionTerm(sorted[0], sorted[1], sorted[2]);
402
+ }
403
+
404
+ toJSON() {
405
+ return { type: 'FUSE', p: this.p, q: this.q, r: this.r };
406
+ }
407
+
408
+ static fromJSON(json) {
409
+ return new FusionTerm(json.p, json.q, json.r);
410
+ }
411
+
412
+ /**
413
+ * Find valid fusion triads for a target prime
414
+ * @param {number} target - Target prime (sum of three primes)
415
+ * @param {number} limit - Maximum prime to consider
416
+ */
417
+ static findTriads(target, limit = 100) {
418
+ if (!isPrime(target)) return [];
419
+
420
+ const triads = [];
421
+ const primes = firstNPrimes(Math.min(limit, 100)).filter(p => p > 2 && p < target);
422
+
423
+ for (let i = 0; i < primes.length; i++) {
424
+ for (let j = i + 1; j < primes.length; j++) {
425
+ const p = primes[i];
426
+ const q = primes[j];
427
+ const r = target - p - q;
428
+
429
+ if (r > q && isPrime(r) && r !== 2) {
430
+ triads.push(new FusionTerm(p, q, r));
431
+ }
432
+ }
433
+ }
434
+
435
+ return triads;
436
+ }
437
+ }
438
+
439
+ // ============================================================================
440
+ // SENTENCE TERMS
441
+ // ============================================================================
442
+
443
+ /**
444
+ * SentenceTerm - S
445
+ * Base class for sentence-level expressions
446
+ */
447
+ class SentenceTerm extends Term {
448
+ constructor() {
449
+ super(Types.SENTENCE);
450
+ }
451
+ }
452
+
453
+ /**
454
+ * NounSentence - Sentence from noun term
455
+ * A noun-denoting expression treated as a one-token discourse state
456
+ * ⟦e : N⟧_S = [⟦e⟧] ∈ D*
457
+ */
458
+ class NounSentence extends SentenceTerm {
459
+ /**
460
+ * @param {NounTerm|ChainTerm|FusionTerm} nounExpr - Noun expression
461
+ */
462
+ constructor(nounExpr) {
463
+ super();
464
+
465
+ if (!(nounExpr instanceof NounTerm ||
466
+ nounExpr instanceof ChainTerm ||
467
+ nounExpr instanceof FusionTerm)) {
468
+ throw new TypeError('NounSentence requires noun-typed expression');
469
+ }
470
+
471
+ this.expr = nounExpr;
472
+ }
473
+
474
+ isWellFormed() {
475
+ return this.expr.isWellFormed();
476
+ }
477
+
478
+ signature() {
479
+ return `S(${this.expr.signature()})`;
480
+ }
481
+
482
+ clone() {
483
+ return new NounSentence(this.expr.clone());
484
+ }
485
+
486
+ toString() {
487
+ return `[${this.expr}]`;
488
+ }
489
+
490
+ /**
491
+ * Get discourse state (sequence of primes)
492
+ */
493
+ getDiscourseState() {
494
+ if (this.expr instanceof NounTerm) {
495
+ return [this.expr.prime];
496
+ } else if (this.expr instanceof ChainTerm) {
497
+ // Return the primes involved in the chain
498
+ return this.expr.getAllPrimes();
499
+ } else if (this.expr instanceof FusionTerm) {
500
+ return [this.expr.getFusedPrime()];
501
+ }
502
+ return [];
503
+ }
504
+
505
+ toJSON() {
506
+ return { type: 'NounSentence', expr: this.expr.toJSON() };
507
+ }
508
+ }
509
+
510
+ /**
511
+ * SeqSentence - s₁ ◦ s₂
512
+ * Sequential composition of sentences
513
+ * Semantically: ⟦s₁ ◦ s₂⟧ = ⟦s₁⟧ · ⟦s₂⟧ (concatenation in D*)
514
+ */
515
+ class SeqSentence extends SentenceTerm {
516
+ /**
517
+ * @param {SentenceTerm} left - Left sentence
518
+ * @param {SentenceTerm} right - Right sentence
519
+ */
520
+ constructor(left, right) {
521
+ super();
522
+
523
+ if (!(left instanceof SentenceTerm) || !(right instanceof SentenceTerm)) {
524
+ throw new TypeError('SeqSentence requires two SentenceTerms');
525
+ }
526
+
527
+ this.left = left;
528
+ this.right = right;
529
+ }
530
+
531
+ isWellFormed() {
532
+ return this.left.isWellFormed() && this.right.isWellFormed();
533
+ }
534
+
535
+ signature() {
536
+ return `(${this.left.signature()} ◦ ${this.right.signature()})`;
537
+ }
538
+
539
+ clone() {
540
+ return new SeqSentence(this.left.clone(), this.right.clone());
541
+ }
542
+
543
+ toString() {
544
+ return `(${this.left} ◦ ${this.right})`;
545
+ }
546
+
547
+ /**
548
+ * Get combined discourse state (concatenation)
549
+ */
550
+ getDiscourseState() {
551
+ return [...this.left.getDiscourseState(), ...this.right.getDiscourseState()];
552
+ }
553
+
554
+ toJSON() {
555
+ return { type: 'SeqSentence', left: this.left.toJSON(), right: this.right.toJSON() };
556
+ }
557
+ }
558
+
559
+ /**
560
+ * ImplSentence - s₁ ⇒ s₂
561
+ * Implication/entailment between sentences
562
+ * Semantically: M ⊨ (s₁ ⇒ s₂) iff ⟦s₁⟧ ⪯ ⟦s₂⟧ (prefix entailment)
563
+ */
564
+ class ImplSentence extends SentenceTerm {
565
+ /**
566
+ * @param {SentenceTerm} antecedent - Antecedent sentence
567
+ * @param {SentenceTerm} consequent - Consequent sentence
568
+ */
569
+ constructor(antecedent, consequent) {
570
+ super();
571
+
572
+ if (!(antecedent instanceof SentenceTerm) || !(consequent instanceof SentenceTerm)) {
573
+ throw new TypeError('ImplSentence requires two SentenceTerms');
574
+ }
575
+
576
+ this.antecedent = antecedent;
577
+ this.consequent = consequent;
578
+ }
579
+
580
+ isWellFormed() {
581
+ return this.antecedent.isWellFormed() && this.consequent.isWellFormed();
582
+ }
583
+
584
+ signature() {
585
+ return `(${this.antecedent.signature()} ⇒ ${this.consequent.signature()})`;
586
+ }
587
+
588
+ clone() {
589
+ return new ImplSentence(this.antecedent.clone(), this.consequent.clone());
590
+ }
591
+
592
+ toString() {
593
+ return `(${this.antecedent} ⇒ ${this.consequent})`;
594
+ }
595
+
596
+ /**
597
+ * Check if implication holds (prefix entailment)
598
+ */
599
+ holds() {
600
+ const ante = this.antecedent.getDiscourseState();
601
+ const cons = this.consequent.getDiscourseState();
602
+
603
+ // Prefix entailment: antecedent is prefix of consequent
604
+ if (ante.length > cons.length) return false;
605
+
606
+ for (let i = 0; i < ante.length; i++) {
607
+ if (ante[i] !== cons[i]) return false;
608
+ }
609
+
610
+ return true;
611
+ }
612
+
613
+ toJSON() {
614
+ return {
615
+ type: 'ImplSentence',
616
+ antecedent: this.antecedent.toJSON(),
617
+ consequent: this.consequent.toJSON()
618
+ };
619
+ }
620
+ }
621
+
622
+ // ============================================================================
623
+ // TYPING CONTEXT AND JUDGMENTS
624
+ // ============================================================================
625
+
626
+ /**
627
+ * TypingContext - Γ
628
+ * A context for typing judgments
629
+ */
630
+ class TypingContext {
631
+ constructor() {
632
+ this.bindings = new Map();
633
+ }
634
+
635
+ /**
636
+ * Bind a variable name to a type
637
+ */
638
+ bind(name, type, term = null) {
639
+ this.bindings.set(name, { type, term });
640
+ return this;
641
+ }
642
+
643
+ /**
644
+ * Get type of a variable
645
+ */
646
+ getType(name) {
647
+ const binding = this.bindings.get(name);
648
+ return binding ? binding.type : null;
649
+ }
650
+
651
+ /**
652
+ * Get term for a variable
653
+ */
654
+ getTerm(name) {
655
+ const binding = this.bindings.get(name);
656
+ return binding ? binding.term : null;
657
+ }
658
+
659
+ /**
660
+ * Check if variable is bound
661
+ */
662
+ has(name) {
663
+ return this.bindings.has(name);
664
+ }
665
+
666
+ /**
667
+ * Clone the context
668
+ */
669
+ clone() {
670
+ const ctx = new TypingContext();
671
+ for (const [name, binding] of this.bindings) {
672
+ ctx.bindings.set(name, { ...binding });
673
+ }
674
+ return ctx;
675
+ }
676
+
677
+ toString() {
678
+ const entries = [];
679
+ for (const [name, { type }] of this.bindings) {
680
+ entries.push(`${name}: ${type}`);
681
+ }
682
+ return `Γ = {${entries.join(', ')}}`;
683
+ }
684
+ }
685
+
686
+ /**
687
+ * TypingJudgment - Γ ⊢ e : T
688
+ * Represents a typing judgment
689
+ */
690
+ class TypingJudgment {
691
+ /**
692
+ * @param {TypingContext} context - The typing context Γ
693
+ * @param {Term} term - The term e
694
+ * @param {string} type - The type T
695
+ */
696
+ constructor(context, term, type) {
697
+ this.context = context;
698
+ this.term = term;
699
+ this.type = type;
700
+ }
701
+
702
+ /**
703
+ * Check if this judgment is valid
704
+ */
705
+ isValid() {
706
+ if (!this.term.isWellFormed()) {
707
+ return false;
708
+ }
709
+ return this.term.type === this.type;
710
+ }
711
+
712
+ toString() {
713
+ return `${this.context} ⊢ ${this.term} : ${this.type}`;
714
+ }
715
+ }
716
+
717
+ // ============================================================================
718
+ // TYPE CHECKER
719
+ // ============================================================================
720
+
721
+ /**
722
+ * TypeChecker - Implements typing rules from the paper
723
+ */
724
+ class TypeChecker {
725
+ constructor() {
726
+ this.context = new TypingContext();
727
+ }
728
+
729
+ /**
730
+ * Infer type of a term
731
+ * @param {Term} term - The term to type
732
+ * @returns {string|null} The inferred type, or null if ill-typed
733
+ */
734
+ inferType(term) {
735
+ if (term instanceof NounTerm) {
736
+ return term.isWellFormed() ? Types.NOUN : null;
737
+ }
738
+
739
+ if (term instanceof AdjTerm) {
740
+ return term.isWellFormed() ? Types.ADJECTIVE : null;
741
+ }
742
+
743
+ if (term instanceof ChainTerm) {
744
+ return term.isWellFormed() ? Types.NOUN : null;
745
+ }
746
+
747
+ if (term instanceof FusionTerm) {
748
+ return term.isWellFormed() ? Types.NOUN : null;
749
+ }
750
+
751
+ if (term instanceof SentenceTerm) {
752
+ return term.isWellFormed() ? Types.SENTENCE : null;
753
+ }
754
+
755
+ return null;
756
+ }
757
+
758
+ /**
759
+ * Check if a term has a specific type
760
+ * @param {Term} term - The term to check
761
+ * @param {string} expectedType - The expected type
762
+ */
763
+ checkType(term, expectedType) {
764
+ const inferred = this.inferType(term);
765
+ return inferred === expectedType;
766
+ }
767
+
768
+ /**
769
+ * Derive typing judgment
770
+ * @param {Term} term - The term to type
771
+ * @returns {TypingJudgment|null} The judgment, or null if ill-typed
772
+ */
773
+ derive(term) {
774
+ const type = this.inferType(term);
775
+ if (type === null) return null;
776
+ return new TypingJudgment(this.context.clone(), term, type);
777
+ }
778
+
779
+ /**
780
+ * Check application well-formedness
781
+ * @param {AdjTerm} adj - Adjective term
782
+ * @param {NounTerm} noun - Noun term
783
+ */
784
+ checkApplication(adj, noun) {
785
+ if (!this.checkType(adj, Types.ADJECTIVE)) {
786
+ return { valid: false, reason: 'Adjective ill-typed' };
787
+ }
788
+
789
+ if (!this.checkType(noun, Types.NOUN)) {
790
+ return { valid: false, reason: 'Noun ill-typed' };
791
+ }
792
+
793
+ if (!adj.canApplyTo(noun)) {
794
+ return { valid: false, reason: `Ordering constraint violated: ${adj.prime} ≮ ${noun.prime}` };
795
+ }
796
+
797
+ return { valid: true };
798
+ }
799
+
800
+ /**
801
+ * Check fusion well-formedness
802
+ * @param {FusionTerm} fusion - Fusion term
803
+ */
804
+ checkFusion(fusion) {
805
+ if (!fusion.isWellFormed()) {
806
+ return {
807
+ valid: false,
808
+ reason: `Fusion ill-formed: primes ${fusion.p}, ${fusion.q}, ${fusion.r} don't satisfy constraints`
809
+ };
810
+ }
811
+ return { valid: true, result: fusion.getFusedPrime() };
812
+ }
813
+ }
814
+
815
+ // ============================================================================
816
+ // TERM BUILDERS
817
+ // ============================================================================
818
+
819
+ /**
820
+ * Build a noun term from prime
821
+ */
822
+ function N(prime) {
823
+ return new NounTerm(prime);
824
+ }
825
+
826
+ /**
827
+ * Build an adjective term from prime
828
+ */
829
+ function A(prime) {
830
+ return new AdjTerm(prime);
831
+ }
832
+
833
+ /**
834
+ * Build a fusion term
835
+ */
836
+ function FUSE(p, q, r) {
837
+ return new FusionTerm(p, q, r);
838
+ }
839
+
840
+ /**
841
+ * Build a chain term
842
+ */
843
+ function CHAIN(operators, noun) {
844
+ const ops = operators.map(p => typeof p === 'number' ? A(p) : p);
845
+ const n = typeof noun === 'number' ? N(noun) : noun;
846
+ return new ChainTerm(ops, n);
847
+ }
848
+
849
+ /**
850
+ * Build a sentence from noun expression
851
+ */
852
+ function SENTENCE(expr) {
853
+ if (typeof expr === 'number') {
854
+ expr = N(expr);
855
+ }
856
+ return new NounSentence(expr);
857
+ }
858
+
859
+ /**
860
+ * Sequential composition of sentences
861
+ */
862
+ function SEQ(s1, s2) {
863
+ const left = s1 instanceof SentenceTerm ? s1 : SENTENCE(s1);
864
+ const right = s2 instanceof SentenceTerm ? s2 : SENTENCE(s2);
865
+ return new SeqSentence(left, right);
866
+ }
867
+
868
+ /**
869
+ * Implication of sentences
870
+ */
871
+ function IMPL(s1, s2) {
872
+ const ante = s1 instanceof SentenceTerm ? s1 : SENTENCE(s1);
873
+ const cons = s2 instanceof SentenceTerm ? s2 : SENTENCE(s2);
874
+ return new ImplSentence(ante, cons);
875
+ }
876
+
877
+ // ============================================================================
878
+ // EXPORTS
879
+ // ============================================================================
880
+
881
+ module.exports = {
882
+ // Type constants
883
+ Types,
884
+
885
+ // Base class
886
+ Term,
887
+
888
+ // Term classes
889
+ NounTerm,
890
+ AdjTerm,
891
+ ChainTerm,
892
+ FusionTerm,
893
+
894
+ // Sentence classes
895
+ SentenceTerm,
896
+ NounSentence,
897
+ SeqSentence,
898
+ ImplSentence,
899
+
900
+ // Typing system
901
+ TypingContext,
902
+ TypingJudgment,
903
+ TypeChecker,
904
+
905
+ // Builders
906
+ N,
907
+ A,
908
+ FUSE,
909
+ CHAIN,
910
+ SENTENCE,
911
+ SEQ,
912
+ IMPL
913
+ };