builder.js
1 import * as API from './api.js' 2 import * as Syntax from './syntax.js' 3 import * as Task from './task.js' 4 import { $, _ } from './$.js' 5 import * as Variable from './variable.js' 6 import { Callable } from './syntax/callable.js' 7 import * as Selector from './selector.js' 8 import * as Link from './data/link.js' 9 import { toDebugString } from './debug.js' 10 import * as JSON from './json.js' 11 import { base58btc } from 'multiformats/bases/base58' 12 13 /** 14 * @param {unknown} descriptor 15 * @returns {API.Type|undefined} 16 */ 17 const fromConstructor = (descriptor) => { 18 switch (descriptor) { 19 case globalThis.Boolean: 20 return { Boolean: {} } 21 case globalThis.String: 22 return { String: {} } 23 case globalThis.Symbol: 24 return { Name: {} } 25 case globalThis.Number: 26 return { Integer: {} } 27 case globalThis.BigInt: 28 return { Integer: {} } 29 case globalThis.Uint8Array: 30 return { Bytes: {} } 31 case globalThis.Object: 32 return { Entity: {} } 33 case null: 34 return { Null: {} } 35 } 36 } 37 38 /** 39 * @param {Record<string, unknown>|null|undefined} descriptor 40 * @returns {API.Type|undefined} 41 */ 42 const fromDescriptor = (descriptor) => { 43 if (descriptor?.Null) { 44 return { Null: {} } 45 } else if (descriptor?.Boolean) { 46 return { Boolean: {} } 47 } else if (descriptor?.String) { 48 return { String: {} } 49 } else if (descriptor?.Integer) { 50 return { Integer: {} } 51 } else if (descriptor?.Float) { 52 return { Float: {} } 53 } else if (descriptor?.Bytes) { 54 return { Bytes: {} } 55 } else if (descriptor?.Entity) { 56 return { Entity: {} } 57 } else if (descriptor?.Name) { 58 return { Name: {} } 59 } else if (descriptor?.Position) { 60 return { Position: {} } 61 } else if (descriptor?.Reference) { 62 return { Reference: {} } 63 } else if (descriptor?.Unknown) { 64 return { Unknown: {} } 65 } 66 } 67 68 /** 69 * 70 * @param {unknown} source 71 * @returns {API.Scalar|undefined} 72 */ 73 const fromScalar = (source) => { 74 switch (typeof source) { 75 case 'string': 76 case 'number': 77 case 'bigint': 78 case 'boolean': 79 return source 80 case 'object': 81 return source == null || source instanceof Uint8Array ? source : undefined 82 default: 83 return undefined 84 } 85 } 86 87 /** 88 * 89 * @param {object} schema 90 */ 91 const deriveThe = (schema) => 92 base58btc.baseEncode(Link.of(schema)['/'].subarray(-32)) 93 94 /** 95 * @template {string} The 96 * @template {API.RuleDescriptor} Schema 97 * @param {Schema & {the?: The, this?: ObjectConstructor | { Entity: {} }, _?: never}} source 98 * @returns {API.Claim<API.FactView<The, Omit<Schema, 'the'> & { this: ObjectConstructor }>, The, Omit<Schema, 'the'> & { this: ObjectConstructor }, {}>} 99 */ 100 export const fact = ({ the, ...source }) => { 101 const members = [] 102 for (const [name, member] of Object.entries({ this: Object, ...source })) { 103 const descriptor = 104 fromConstructor(member) ?? 105 fromDescriptor(/** @type {{}|null|undefined} */ (member)) ?? 106 fromScalar(member) 107 108 if (name === '_') { 109 throw new TypeError(`Schema may no have reserved "_" property`) 110 } 111 112 if (descriptor === undefined) { 113 throw new TypeError( 114 `Unsupported schema member ${toDebugString(/** @type {{}} */ (member))}` 115 ) 116 } else { 117 members.push([name, descriptor]) 118 } 119 } 120 121 if (members.length === 1 && typeof the !== 'string') { 122 throw new TypeError( 123 `Schema must contain at least one property. To tag entities you could use "the" discriminant` 124 ) 125 } 126 127 const schema = Object.fromEntries(members) 128 the = typeof the === 'string' ? the : /** @type {The} */ (deriveThe(schema)) 129 130 if (!schema.this.Entity) { 131 throw new TypeError( 132 `Schema may not have "this" property that is not an entity` 133 ) 134 } 135 136 /** @type {API.Premise<The, Omit<Schema, 'the'> & { this: ObjectConstructor }>} */ 137 const premise = { 138 the, 139 schema, 140 attributes: deriveAttributes(schema), 141 } 142 143 const conclusion = Fact.for(premise) 144 const claim = new Claim(premise, conclusion, {}) 145 146 return claim 147 } 148 149 export const claim = fact 150 151 /** 152 * @template Fact 153 * @template {string} The 154 * @template {API.FactSchema} Schema 155 * @template {API.RuleDescriptor} Context 156 * @implements {API.Claim<Fact, The, Schema, Context>} 157 * @extends {Callable<(terms?: API.InferFactTerms<Schema>) => FactMatch<Fact, The, Schema>>} 158 */ 159 class Claim extends Callable { 160 /** 161 * @param {API.Premise<The, Schema>} premise 162 * @param {API.Conclusion<Fact, The, Schema>} conclusion 163 * @param {Context} context 164 */ 165 constructor(premise, conclusion, context) { 166 super((terms) => this.match(terms)) 167 168 this.premise = premise 169 this.conclusion = conclusion 170 this.context = context 171 this.#cells = 172 /** @type {API.InferSchemaAttributes<Schema & Context> & {_: API.Variable, this: API.Variable<API.Entity>}} */ 173 (deriveCells({ ...context, ...premise.schema })) 174 } 175 176 get the() { 177 return this.premise.the 178 } 179 /** 180 * Map of variables corresponding to the fact members. 181 * 182 * @type {API.InferSchemaAttributes<Schema>} 183 */ 184 get attributes() { 185 return this.premise.attributes 186 } 187 188 get schema() { 189 return this.premise.schema 190 } 191 192 /** @type {API.InferSchemaAttributes<Schema & Context> & {_: API.Variable, this: API.Variable<API.Entity>}} */ 193 #cells 194 195 get cells() { 196 return this.#cells 197 } 198 199 /** @type {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>|undefined} */ 200 #build 201 202 /** 203 * Builds a deduction form for the this fact. 204 */ 205 build() { 206 if (!this.#build) { 207 const { the, schema, attributes } = this.premise 208 const where = [] 209 for (const name of Object.keys(schema)) { 210 if (name !== 'this') { 211 where.push({ 212 match: { 213 the: `${the}/${name}`, 214 of: attributes.this, 215 is: attributes[name], 216 }, 217 fact: {}, 218 }) 219 } 220 } 221 222 // If we have no other fields we should still be able to use fact as a 223 // tag which is why we add a predicate for that case. 224 if (where.length === 0) { 225 where.push({ 226 match: { 227 the: `the/${the}`, 228 of: attributes.this, 229 is: the, 230 }, 231 }) 232 } 233 234 this.#build = Syntax.rule({ match: attributes, when: { where } }) 235 } 236 return this.#build 237 } 238 239 /** 240 * @param {API.InferSchemaTerms<Schema>} terms 241 * @returns {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>} 242 */ 243 apply(terms) { 244 return this.build().apply(terms) 245 } 246 247 /** 248 * @param {API.InferSchemaTerms<Schema>} terms 249 * @returns {API.MatchView} 250 */ 251 recur(terms) { 252 return [this.apply(terms)] 253 } 254 255 /** 256 * Creates predicate for this fact that matches given terms. 257 * 258 * @param {Partial<API.InferFactTerms<Schema>>} [terms] 259 * @returns {FactMatch<Fact, The, Schema>} 260 */ 261 match(terms = {}) { 262 return new FactMatch( 263 this.premise, 264 this.conclusion, 265 this, 266 completeTerms(this.premise.schema, terms) 267 ) 268 } 269 270 /** 271 * Creates a predicate for this fact that excludes ones that match given 272 * terms. 273 * 274 * @param {Partial<API.InferSchemaTerms<Schema>>} terms 275 * @returns {Negation<Fact, The, Schema>} 276 */ 277 not(terms) { 278 return new Negation(this.premise, this.conclusion, this, terms) 279 } 280 281 /** 282 * Asserts this fact with a given data. If data does not conforms this fact 283 * throws an error. 284 * 285 * @param {API.InferAssert<Schema>} fact 286 * @returns {Fact} 287 */ 288 assert(fact) { 289 return this.conclusion.assert(fact) 290 } 291 292 /** 293 * Defines local variables so they could be used in the `.when` and `.where` 294 * methods without makeing those part of the fact. 295 * 296 * @template {Exclude<API.RuleDescriptor, Schema & Context>} Extension 297 * @param {Extension} extension 298 * @returns {API.Claim<Fact, The, Schema, Context & Extension>} 299 */ 300 with(extension) { 301 return new Claim(this.premise, this.conclusion, { 302 ...extension, 303 ...this.context, 304 }) 305 } 306 307 /** 308 * Defines a rule that deduces this fact whenever any of the branches are true. 309 * Takes a `build` function that will be given set of variables corresponding 310 * to the fact members which must return object where keys represent disjuncts 311 * and values are arrays representing conjuncts for those disjuncts. In other 312 * works each member of the returned object represent OR branches where each 313 * branch is an AND joined predicates by passed variables. 314 * 315 * @param {API.SomeBuilder<Schema & Context>} compile 316 * @returns {API.Deduction<Fact, The, Schema, Context>} 317 */ 318 when(compile) { 319 return new Deduction(this.premise, this.conclusion, this.context, compile) 320 } 321 322 /** 323 * Defines a rule that dudces this fact whenever all of the predicates are 324 * true. This is a shortuct of `.when` which is convinient in cases where 325 * only one branch is needed. 326 * 327 * @param {API.EveryBuilder<Schema & Context>} compile 328 * @returns {API.Deduction<Fact, The, Schema, Context>} 329 */ 330 where(compile) { 331 return new Deduction(this.premise, this.conclusion, this.context, compile) 332 } 333 334 /** 335 * This method can be used to project different layout of the selected facts, 336 * it is maraked deprecated, but it is not deprecated but rather an 337 * experimental and may be removed at any point. This method can be used as an 338 * aggregator to group results, however this leads to nested structures which 339 * are at odds with the facts that are flat records. 340 * 341 * @deprecated 342 * 343 * @template {API.Selector} Terms 344 * @param {API.ProjectionBuilder<Schema & Context, Terms>} compile 345 * @returns {Select<The, Schema, Context, Terms>} 346 */ 347 select(compile = (variables) => /** @type {Terms} */ (variables)) { 348 return new Select(this.premise, this.build(), this.cells, compile) 349 } 350 351 /** 352 * @template View 353 * @param {(fact: Fact) => View} mapper 354 * @returns {API.Claim<View, The, Schema, Context>} 355 */ 356 map(mapper) { 357 return new Claim( 358 this.premise, 359 { 360 assert: (fact) => { 361 return mapper(this.conclusion.assert(fact)) 362 }, 363 }, 364 this.context 365 ) 366 } 367 368 /** 369 * @template View, State 370 * @param {API.Aggregator<View, Fact, State>} aggregator 371 */ 372 aggregate(aggregator) { 373 return new Aggregation(this, aggregator) 374 } 375 } 376 377 /** 378 * @template Fact 379 * @template {string} The 380 * @template {API.FactSchema} Schema 381 * @template {API.RuleDescriptor} Context 382 * @implements {API.Deduction<Fact, The, Schema, Context>} 383 * @extends {Claim<Fact, The, Schema, Context>} 384 */ 385 class Deduction extends Claim { 386 /** 387 * @param {API.Premise<The, Schema>} premise 388 * @param {API.Conclusion<Fact, The, Schema>} conclusion 389 * @param {Context} context 390 * @param {API.WhenBuilder<Schema & Context>} compile 391 */ 392 constructor(premise, conclusion, context, compile) { 393 super(premise, conclusion, context) 394 this.compile = compile 395 } 396 397 /** 398 * If rule is applied recursively we want to return `Recur` as opposed to 399 * plain `FactMatch` to accomplish this we set this property to `this` 400 * during `source` compilation. This works because when/where `build` function 401 * is called during compilation which in turn ends up calling `match` method 402 * that looks at `this.#circuit` to decide which one to construct. Furthermore 403 * we do pass this `#circuit` to it during construction. 404 * 405 * @type {Circuit<Schema, Fact>|null} 406 */ 407 self = null 408 409 /** @type {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>|undefined} */ 410 #build 411 build() { 412 if (!this.#build) { 413 this.self = this 414 415 const when = /** @type {Record<string, API.Every>} */ ({}) 416 for (const [name, disjunct] of iterateDisjuncts( 417 this.compile(this.cells) 418 )) { 419 when[name] = /** @type {[API.Conjunct, ...API.Conjunct[]]} */ ([ 420 ...iterateConjuncts(disjunct), 421 ]) 422 } 423 424 this.#build = Syntax.rule({ 425 match: this.premise.attributes, 426 when: /** @type {API.Some} */ (when), 427 }) 428 429 this.self = null 430 } 431 432 return this.#build 433 } 434 435 /** 436 * @param {API.InferSchemaTerms<Schema>} terms 437 * @returns {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>} 438 */ 439 apply(terms) { 440 return this.build().apply(terms) 441 } 442 443 /** 444 * @param {API.InferSchemaTerms<Schema>} terms 445 * @returns {API.MatchView} 446 */ 447 recur(terms) { 448 return [ 449 /** @type {API.Recur} */ ({ 450 recur: terms, 451 }), 452 ] 453 } 454 455 /** 456 * @param {API.InferClaimTerms<Schema>} terms 457 */ 458 induce(terms) { 459 const { premise, cells } = this 460 const { schema, the } = premise 461 const predicates = [] 462 for (const [name, member] of Object.entries(schema)) { 463 const [term, cell] = [terms[name], cells[name]] 464 // If `this` was not provided we derive one from the data itself. 465 if (name === 'this' && !term) { 466 predicates.push( 467 Data.Fact({ 468 .../** @type {Record<string, API.Term>} */ (terms), 469 the, 470 this: cells.this, 471 }) 472 ) 473 } else { 474 predicates.push( 475 ...same({ this: /** @type {API.Scalar} */ (term), as: cell }) 476 ) 477 } 478 } 479 480 return new Constraint(predicates) 481 } 482 483 /** 484 * @param {Partial<API.InferFactTerms<Schema>>} [terms] 485 * @returns {FactMatch<Fact, The, Schema>} 486 */ 487 match(terms) { 488 // 🤔 If it is recursion we can't just generate new variables 489 // we need to reuse ones from the current context. 490 const { premise, conclusion, self } = this 491 const match = 492 self ? 493 new Recur(premise, conclusion, self ?? this, { 494 ...this.attributes, 495 ...terms, 496 }) 497 : new FactMatch( 498 premise, 499 conclusion, 500 self ?? this, 501 completeTerms(premise.schema, terms) 502 ) 503 return match 504 } 505 506 /** 507 * @param {API.InferClaimTerms<Schema>} fact 508 */ 509 claim(fact) { 510 return this.induce(fact) 511 } 512 513 /** @type {Induction<Fact, The, Schema, Context>|undefined} */ 514 #induction 515 get inductive() { 516 if (!this.#induction) { 517 const { self } = this 518 const induction = new Induction( 519 this.premise, 520 this.conclusion, 521 this.context, 522 this.compile 523 ) 524 this.self = induction 525 // Force induction compilaction so that it will be the self in the given 526 // context. 527 induction.build() 528 // Then we reset the self so that it continues to behave as intended. 529 this.self = self 530 // cache the instance 531 this.#induction = induction 532 } 533 534 return this.#induction 535 } 536 537 /** 538 * @template View 539 * @param {(fact: Fact) => View} mapper 540 * @returns {API.Deduction<View, The, Schema, Context>} 541 */ 542 map(mapper) { 543 return new Deduction( 544 this.premise, 545 { 546 assert: (fact) => { 547 return mapper(this.conclusion.assert(fact)) 548 }, 549 }, 550 this.context, 551 this.compile 552 ) 553 } 554 } 555 556 /** 557 * @template Fact 558 * @template {string} The 559 * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema 560 * @template {API.RuleDescriptor} Locals 561 * @extends {Deduction<Fact, The, Schema, Locals>} 562 */ 563 class Induction extends Deduction { 564 /** 565 * @param {API.InferSchemaTerms<Schema>} terms 566 * @returns {API.MatchView} 567 */ 568 recur(terms) { 569 return this.induce(terms) 570 } 571 } 572 573 /** 574 * @template View 575 * @template Fact 576 * @template State 577 * @template {string} The 578 * @template {API.FactSchema} Schema 579 * @template {API.RuleDescriptor} Context 580 * @implements {API.Aggregation<View, Fact, The, Schema>} 581 * @extends {Callable<(terms?: API.InferFactTerms<Schema>) => API.Aggregate<View>>} 582 */ 583 class Aggregation extends Callable { 584 /** 585 * @param {Claim<Fact, The, Schema, Context>} rule 586 * @param {API.Aggregator<View, Fact, State>} aggregator 587 */ 588 constructor(rule, aggregator) { 589 super((terms) => this.match(terms)) 590 this.rule = rule 591 this.aggregator = aggregator 592 } 593 594 get the() { 595 return this.rule.the 596 } 597 get attributes() { 598 return this.rule.attributes 599 } 600 get schema() { 601 return this.rule.schema 602 } 603 get cells() { 604 return this.rule.cells 605 } 606 607 /** 608 * @param {API.InferSchemaTerms<Schema>} terms 609 */ 610 apply(terms) { 611 return this.rule.apply(terms) 612 } 613 614 /** 615 * @param {API.InferSchemaTerms<Schema>} terms 616 */ 617 recur(terms) { 618 return this.rule.recur(terms) 619 } 620 /** 621 * Creates a predicate for this fact that excludes ones that match given 622 * terms. 623 * 624 * @param {Partial<API.InferSchemaTerms<Schema>>} terms 625 * @returns {Negation<Fact, The, Schema>} 626 */ 627 not(terms) { 628 return this.rule.not(terms) 629 } 630 631 /** 632 * Asserts this fact with a given data. If data does not conforms this fact 633 * throws an error. 634 * 635 * @param {API.InferAssert<Schema>} fact 636 * @returns {Fact} 637 */ 638 assert(fact) { 639 return this.rule.assert(fact) 640 } 641 /** 642 * Creates predicate for this fact that matches given terms. 643 * 644 * @param {Partial<API.InferFactTerms<Schema>>} [terms] 645 * @returns {Aggregate<View, Fact, State, The, Schema>} 646 */ 647 match(terms = {}) { 648 return new Aggregate( 649 this.rule.premise, 650 this.rule.conclusion, 651 this, 652 this.aggregator, 653 completeTerms(this.schema, terms) 654 ) 655 } 656 } 657 /** 658 * 659 * @template {API.FactSchema} Schema 660 * @template Fact 661 * @typedef {object} Circuit 662 * @property {API.InferSchemaAttributes<Schema>} cells 663 * @property {(terms: API.InferSchemaTerms<Schema>) => API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>} apply 664 * @property {(terms: API.InferSchemaTerms<Schema>) => API.MatchView} recur 665 * @property {(claim: API.InferAssert<Schema>) => Fact} assert 666 */ 667 668 /** 669 * @template Fact 670 * @template {string} The 671 * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema 672 * @implements {API.MatchView<unknown>} 673 */ 674 class Negation { 675 /** 676 * @param {API.Premise<The, Schema>} premise 677 * @param {API.Conclusion<Fact, The, Schema>} conclusion 678 * @param {Circuit<Schema, Fact>} rule 679 * @param {Partial<API.InferSchemaTerms<Schema>>} terms 680 */ 681 constructor(premise, conclusion, rule, terms) { 682 this.premise = premise 683 this.conclusion = conclusion 684 this.rule = rule 685 this.terms = terms 686 } 687 /** @type {API.NegationSyntax|undefined} */ 688 #build 689 build() { 690 if (!this.#build) { 691 this.#build = this.rule 692 .apply( 693 // This not true, but apply does not actually need all terms we only 694 // type it this way to make lower level API less error prone. Perhaps 695 // we need to revise it to having to lie to it. 696 /** @type {API.InferSchemaTerms<Schema>} */ (this.terms) 697 ) 698 .negate() 699 } 700 return this.#build 701 } 702 703 toJSON() { 704 return this.build().toJSON() 705 } 706 707 *[Symbol.iterator]() { 708 yield this.build() 709 } 710 } 711 712 /** 713 * @template {API.RuleDescriptor} Schema 714 */ 715 class Match { 716 /** 717 * @param {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>} rule 718 * @param {API.InferSchemaTerms<Schema>} terms 719 */ 720 constructor(rule, terms) { 721 this.rule = rule 722 this.terms = terms 723 } 724 725 /** @type {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>|undefined} */ 726 #form 727 get form() { 728 if (!this.#form) { 729 this.#form = this.rule.apply(this.terms) 730 } 731 return this.#form 732 } 733 734 *[Symbol.iterator]() { 735 yield this.form 736 } 737 738 /** @type {API.RuleApplicationPlan<API.InferSchemaAttributes<Schema>>|undefined} */ 739 #plan 740 get plan() { 741 if (!this.#plan) { 742 this.#plan = this.form.prepare() 743 } 744 745 return this.#plan 746 } 747 748 toJSON() { 749 return this.form.toJSON() 750 } 751 /** 752 * @param {{ from: API.Querier }} source 753 */ 754 *execute(source) { 755 const selection = yield* this.plan.query(source) 756 return Selector.select(this.terms, selection) 757 } 758 759 /** 760 * @param {{ from: API.Querier }} source 761 */ 762 query(source) { 763 // 😵💫 Here we force plan compilation because we want to get planning error 764 // before we get a 765 this.plan 766 return Task.perform(this.execute(source)) 767 } 768 } 769 770 /** 771 * @template Fact 772 * @template {string} The 773 * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema 774 * @implements {API.MatchView<unknown>} 775 */ 776 class FactMatch { 777 /** 778 * @param {API.Premise<The, Schema>} premise 779 * @param {API.Conclusion<Fact, The, Schema>} conclusion 780 * @param {Circuit<Schema, Fact>} rule 781 * @param {API.InferSchemaTerms<Schema>} terms 782 */ 783 constructor(premise, conclusion, rule, terms) { 784 this.premise = premise 785 this.conclusion = conclusion 786 this.rule = rule 787 this.terms = terms 788 } 789 790 /** 791 * @returns {Negation<Fact, The, Schema>} 792 */ 793 negate() { 794 return new Negation(this.premise, this.conclusion, this.rule, this.terms) 795 } 796 797 /** @type {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>|undefined} */ 798 #build 799 build() { 800 if (!this.#build) { 801 this.#build = this.rule.apply(this.terms) 802 } 803 return this.#build 804 } 805 /** @type {API.RuleApplicationPlan<API.InferSchemaAttributes<Schema>>|undefined} */ 806 #plan 807 plan() { 808 if (!this.#plan) { 809 this.#plan = this.rule.apply(this.terms).prepare() 810 } 811 return this.#plan 812 } 813 814 /** @returns {Iterator<API.Conjunct|API.Recur>} */ 815 *[Symbol.iterator]() { 816 yield this.build() 817 } 818 819 toJSON() { 820 return this.build().toJSON() 821 } 822 /** 823 * @param {API.Task<API.MatchFrame[], Error>} query 824 */ 825 *execute(query) { 826 const { terms } = this 827 const selection = yield* query 828 829 const facts = [] 830 for (const match of selection) { 831 /** @type {Record<string, API.Scalar>} */ 832 const model = {} 833 for (const [key, term] of Object.entries(terms)) { 834 model[key] = /** @type {API.Scalar} */ ( 835 Variable.is(term) ? match.get(term) : term 836 ) 837 } 838 839 model.the = this.premise.the 840 const fact = this.conclusion.assert( 841 /** @type {API.InferFact<Schema> & { this: API.Entity, the: The }} */ ( 842 model 843 ) 844 ) 845 846 facts.push(fact) 847 } 848 849 return facts 850 } 851 852 /** 853 * @param {{ from: API.Querier }} source 854 */ 855 query(source) { 856 return Task.perform(this.execute(this.plan().query(source))) 857 } 858 } 859 860 /** 861 * @template View 862 * @template Fact 863 * @template State 864 * @template {string} The 865 * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema 866 */ 867 class Aggregate { 868 /** 869 * @param {API.Premise<The, Schema>} premise 870 * @param {API.Conclusion<Fact, The, Schema>} conclusion 871 * @param {Circuit<Schema, Fact>} rule 872 * @param {API.Aggregator<View, Fact, State>} aggregator 873 * @param {API.InferSchemaTerms<Schema>} terms 874 */ 875 constructor(premise, conclusion, rule, aggregator, terms) { 876 this.premise = premise 877 this.conclusion = conclusion 878 this.rule = rule 879 this.aggregator = aggregator 880 this.terms = terms 881 } 882 /** 883 * @returns {Negation<Fact, The, Schema>} 884 */ 885 negate() { 886 return new Negation(this.premise, this.conclusion, this.rule, this.terms) 887 } 888 /** @type {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>|undefined} */ 889 #build 890 build() { 891 if (!this.#build) { 892 this.#build = this.rule.apply(this.terms) 893 } 894 return this.#build 895 } 896 /** @type {API.RuleApplicationPlan<API.InferSchemaAttributes<Schema>>|undefined} */ 897 #plan 898 plan() { 899 if (!this.#plan) { 900 this.#plan = this.rule.apply(this.terms).prepare() 901 } 902 return this.#plan 903 } 904 905 /** @returns {Iterator<API.Conjunct|API.Recur>} */ 906 *[Symbol.iterator]() { 907 yield this.build() 908 } 909 910 toJSON() { 911 return this.build().toJSON() 912 } 913 /** 914 * @param {API.Task<API.MatchFrame[], Error>} query 915 * @returns {API.Task<View, Error>} 916 */ 917 *execute(query) { 918 const { terms, aggregator } = this 919 const selection = yield* query 920 921 let aggregate = aggregator.open() 922 for (const match of selection) { 923 /** @type {Record<string, API.Scalar>} */ 924 const model = {} 925 for (const [key, term] of Object.entries(terms)) { 926 model[key] = /** @type {API.Scalar} */ ( 927 Variable.is(term) ? match.get(term) : term 928 ) 929 } 930 931 model.the = this.premise.the 932 const fact = this.conclusion.assert( 933 /** @type {API.InferFact<Schema> & { this: API.Entity, the: The }} */ ( 934 model 935 ) 936 ) 937 938 aggregate = aggregator.merge(aggregate, fact) 939 } 940 941 return aggregator.close(aggregate) 942 } 943 944 /** 945 * @param {{ from: API.Querier }} source 946 */ 947 query(source) { 948 return Task.perform(this.execute(this.plan().query(source))) 949 } 950 } 951 /** 952 * Subclass of {@link FactMatch} that represents a recursive rule application. 953 954 * @template {string} The 955 * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema 956 * @template Fact 957 * @extends {FactMatch<Fact, The, Schema>} 958 */ 959 class Recur extends FactMatch { 960 /** 961 * We override the itertor to yield recursion form as opposed to application 962 * from. 963 */ 964 *[Symbol.iterator]() { 965 yield* this.rule.recur(this.terms) 966 } 967 } 968 969 /** 970 * @template {string} The 971 * @template {API.FactSchema} Schema 972 */ 973 class Fact { 974 /** 975 * @template {string} The 976 * @template {API.FactSchema} Schema 977 * @param {API.Premise<The, Schema>} premise 978 * @returns {API.Conclusion<API.FactView<The, Schema>, The, Schema>} 979 */ 980 static for({ the, schema }) { 981 const This = this 982 983 /** 984 * @extends {This<The, Schema>} 985 */ 986 class Fact extends this { 987 static the = /** @type {The} */ (the) 988 static schema = schema 989 } 990 991 return Fact 992 } 993 /** 994 * @template {string} The 995 * @template {API.FactSchema} Schema 996 * @this {{the: The, schema: Schema} & typeof Fact} this 997 * @param {Record<string, API.Scalar>} claim 998 * @returns {API.FactView<The, Schema>} 999 */ 1000 static assert({ the, this: entity, ...attributes }) { 1001 if (the != null && the !== this.the) { 1002 throw new TypeError( 1003 `Optional attribute "the", if set must match the schema vaule "${this.the}"` 1004 ) 1005 } 1006 1007 // Validate that all attributes have being provided 1008 // TODO: Do actual schema validation to ensure tha attributes do 1009 // conform to the schema 1010 for (const name of Object.keys(this.schema)) { 1011 const value = attributes[name] 1012 if (value === undefined && name !== 'this') { 1013 throw new TypeError(`Required attribute "${name}" is missing`) 1014 } 1015 } 1016 1017 const fact = new this( 1018 this.the, 1019 /** @type {API.Entity} */ ( 1020 entity ?? Link.of({ ...attributes, the: this.the }) 1021 ), 1022 /** @type {Omit<API.InferAssert<Schema>, 'this'|'the'>} */ (attributes) 1023 ) 1024 1025 return /** @type {API.FactView<The, Schema>} */ (fact) 1026 } 1027 1028 /** 1029 * @param {The} the 1030 * @param {API.Entity} self 1031 * @param {Omit<API.InferAssert<Schema>, 'this'|'the'>} attributes 1032 */ 1033 constructor(the, self, attributes) { 1034 this.#the = the 1035 this.#attributes = attributes 1036 this.this = self 1037 1038 Object.assign(this, attributes) 1039 } 1040 #attributes 1041 get attributes() { 1042 return this.#attributes 1043 } 1044 1045 #the 1046 get the() { 1047 return this.#the 1048 } 1049 1050 *[Symbol.iterator]() { 1051 const { the, this: of } = this 1052 1053 yield { 1054 assert: { the: `the/${the}`, of, is: the }, 1055 } 1056 1057 for (const [name, is] of Object.entries(this.#attributes)) { 1058 yield { 1059 assert: { 1060 the: `${the}/${name}`, 1061 of, 1062 is, 1063 }, 1064 } 1065 } 1066 } 1067 1068 /** 1069 * @returns {IterableIterator<{retract: API.Fact}>} 1070 */ 1071 *retract() { 1072 const { the, this: of } = this 1073 yield { 1074 retract: { the: `the/${the}`, of: of, is: the }, 1075 } 1076 1077 for (const [name, value] of Object.entries(this.#attributes)) { 1078 if (name !== 'this' && name !== 'the') { 1079 yield { 1080 retract: { the: `${the}/${name}`, of, is: value }, 1081 } 1082 } 1083 } 1084 } 1085 1086 toJSON() { 1087 return { ...this, the: this.the, this: JSON.from(this.this) } 1088 } 1089 } 1090 1091 /** 1092 * @implements {API.MatchView<unknown>} 1093 */ 1094 class Constraint { 1095 /** 1096 * @param {API.Conjunct[]} predicates 1097 */ 1098 constructor(predicates) { 1099 this.predicates = predicates 1100 } 1101 *[Symbol.iterator]() { 1102 yield* this.predicates 1103 } 1104 } 1105 1106 /** 1107 * @template {string} The 1108 * @template {API.FactSchema} Schema 1109 * @template {API.RuleDescriptor} Context 1110 * @template {API.Selector} Selector 1111 * @implements {API.Projection<Schema, Selector>} 1112 * @extends {Callable<(terms?: API.InferFactTerms<Schema>) => GroupedSelection<The, Schema, Selector>>} 1113 */ 1114 class Select extends Callable { 1115 /** 1116 * @param {API.Premise<The, Schema>} premise 1117 * @param {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>} rule 1118 * @param {API.InferSchemaAttributes<Schema & Context> & {_: API.Variable; this: API.Variable<API.Entity>}} cells 1119 }} 1120 * @param {API.ProjectionBuilder<Schema & Context, Selector>} compile 1121 */ 1122 constructor(premise, rule, cells, compile) { 1123 super((terms) => this.match(terms)) 1124 this.premise = premise 1125 this.cells = cells 1126 this.rule = rule 1127 this.compile = compile 1128 } 1129 1130 /** @type {Selector|undefined} */ 1131 #build 1132 build() { 1133 if (!this.#build) { 1134 this.#build = this.compile(this.cells) 1135 } 1136 return this.#build 1137 } 1138 1139 /** 1140 * @param {Partial<API.InferFactTerms<Schema>>} [terms] 1141 * @returns {GroupedSelection<The, Schema, Selector>} 1142 */ 1143 match(terms) { 1144 return new GroupedSelection(this.premise, this.build(), this.rule, { 1145 ...this.cells, 1146 ...terms, 1147 }) 1148 } 1149 } 1150 1151 /** 1152 * @template {string} The 1153 * @template {API.RuleDescriptor & {this: ObjectConstructor}} Schema 1154 * @template {API.Selector} Selector 1155 */ 1156 class GroupedSelection { 1157 /** 1158 * @param {API.Premise<The, Schema>} premise 1159 * @param {Selector} selector 1160 * @param {API.DeductiveRuleSyntax<API.InferSchemaAttributes<Schema>>} rule 1161 * @param {API.InferSchemaTerms<Schema>} terms 1162 */ 1163 constructor(premise, selector, rule, terms) { 1164 this.premise = premise 1165 this.selector = selector 1166 this.rule = rule 1167 this.terms = terms 1168 } 1169 1170 /** @type {API.RuleApplicationSyntax<API.InferSchemaAttributes<Schema>>|undefined} */ 1171 #form 1172 get form() { 1173 if (!this.#form) { 1174 this.#form = this.rule.apply( 1175 /** @type {API.InferSchemaTerms<Schema>} */ (this.terms) 1176 ) 1177 } 1178 return this.#form 1179 } 1180 1181 *[Symbol.iterator]() { 1182 yield this.form 1183 } 1184 1185 /** @type {API.RuleApplicationPlan<API.InferSchemaAttributes<Schema>>|undefined} */ 1186 #plan 1187 plan() { 1188 if (!this.#plan) { 1189 this.#plan = this.rule.apply(this.terms).prepare() 1190 } 1191 1192 return this.#plan 1193 } 1194 1195 toJSON() { 1196 return this.form.toJSON() 1197 } 1198 /** 1199 * @param {API.Task<API.MatchFrame[], Error>} query 1200 */ 1201 *execute(query) { 1202 const selection = yield* query 1203 return Selector.select(this.selector, selection) 1204 } 1205 1206 /** 1207 * @param {{ from: API.Querier }} source 1208 */ 1209 query(source) { 1210 return Task.perform(this.execute(this.plan().query(source))) 1211 } 1212 } 1213 1214 export { $, _ } 1215 /** 1216 * @template Terms 1217 * @template {(terms: any) => API.Constraint} F 1218 * @extends {Callable<F>} 1219 */ 1220 export class Operator extends Callable { 1221 /** 1222 * @template Terms 1223 * @template {(terms: Terms) => API.Constraint} Formula 1224 * @param {Formula} match 1225 * @returns {Operator<Terms, Formula>} 1226 */ 1227 static for(match) { 1228 return new this(match) 1229 } 1230 /** 1231 * @param {F} match 1232 */ 1233 constructor(match) { 1234 super(match) 1235 this.match = match 1236 } 1237 1238 /** 1239 * @param {Terms} terms 1240 * @returns {API.Negation} 1241 */ 1242 not(terms) { 1243 return { not: this.match(terms) } 1244 } 1245 } 1246 1247 export const Collection = Operator.for( 1248 /** 1249 * @template {API.Scalar} Member 1250 * @param {object} terms 1251 * @param {API.Term<API.Entity>} terms.this 1252 * @param {API.Term<Member>} terms.of 1253 * @param {API.Term<string>} [terms.at] 1254 */ 1255 (terms) => ({ 1256 match: { the: terms.at, of: terms.this, is: terms.of }, 1257 fact: {}, 1258 }) 1259 ) 1260 1261 /** 1262 * @param {API.Term<string>} term 1263 */ 1264 export const text = (term) => new TextVariable(term) 1265 1266 class TextVariable { 1267 #this 1268 /** 1269 * @param {API.Term<string>} term 1270 */ 1271 constructor(term) { 1272 this.#this = term 1273 } 1274 1275 /** 1276 * @param {API.Term<string>} pattern 1277 */ 1278 like(pattern) { 1279 return Text.match({ this: this.#this, pattern: pattern }) 1280 } 1281 1282 /** 1283 * @param {API.Term<string>} slice 1284 */ 1285 includes(slice) { 1286 return Text.includes({ this: this.#this, slice }) 1287 } 1288 1289 /** 1290 * @param {object} terms 1291 * @param {API.Term<string>} terms.with 1292 * @param {API.Term<string>} terms.is 1293 */ 1294 concat(terms) { 1295 return Text.Concat({ of: [this.#this, terms.with], is: terms.is }) 1296 } 1297 1298 words() { 1299 const of = this.#this 1300 return { 1301 /** 1302 * @param {API.Term<string>} is 1303 */ 1304 is(is) { 1305 return Text.Words({ of, is }) 1306 }, 1307 } 1308 } 1309 1310 lines() { 1311 const of = this.#this 1312 return { 1313 /** 1314 * @param {API.Term<string>} is 1315 */ 1316 is(is) { 1317 return Text.Lines({ of, is }) 1318 }, 1319 } 1320 } 1321 1322 toUpperCase() { 1323 const of = this.#this 1324 return { 1325 /** 1326 * @param {API.Term<string>} is 1327 */ 1328 is(is) { 1329 return Text.UpperCase({ of, is }) 1330 }, 1331 /** 1332 * @param {API.Term<string>} is 1333 */ 1334 not(is) { 1335 return { not: Text.UpperCase({ of, is }) } 1336 }, 1337 } 1338 } 1339 1340 /** 1341 */ 1342 toLowerCase() { 1343 const of = this.#this 1344 return { 1345 /** 1346 * @param {API.Term<string>} is 1347 */ 1348 is(is) { 1349 return Text.LowerCase({ of, is }) 1350 }, 1351 /** 1352 * @param {API.Term<string>} is 1353 */ 1354 not(is) { 1355 return { not: Text.LowerCase({ of, is }) } 1356 }, 1357 } 1358 } 1359 1360 /** 1361 * @param {API.Term<string>} is 1362 */ 1363 trim(is) { 1364 return Text.Trim({ of: this.#this, is }) 1365 } 1366 } 1367 1368 export class Text { 1369 #this 1370 /** 1371 * @param {API.Term<string>} source 1372 */ 1373 constructor(source) { 1374 this.#this = source 1375 } 1376 1377 static match = Operator.for( 1378 /** 1379 * @param {object} terms 1380 * @param {API.Term<string>} terms.this 1381 * @param {API.Term<string>} terms.pattern 1382 */ 1383 ({ this: text, pattern: like }) => ({ 1384 match: { text, pattern: like }, 1385 operator: /** @type {const} */ ('text/like'), 1386 }) 1387 ) 1388 1389 /** 1390 * @param {object} terms 1391 * @param {API.Term<string>} terms.this 1392 * @param {API.Term<string>} terms.pattern 1393 */ 1394 static not(terms) { 1395 return { not: this.match(terms) } 1396 } 1397 1398 static includes = Operator.for( 1399 /** 1400 * @param {object} terms 1401 * @param {API.Term<string>} terms.this 1402 * @param {API.Term<string>} terms.slice 1403 */ 1404 ({ this: source, slice }) => { 1405 return { 1406 match: { this: source, slice }, 1407 operator: /** @type {const} */ ('text/includes'), 1408 } 1409 } 1410 ) 1411 1412 static Concat = Operator.for( 1413 /** 1414 * @param {object} terms 1415 * @param {[left:API.Term<string>, right: API.Term<string>]} terms.of 1416 * @param {API.Term<string>} [terms.is] 1417 * @returns {API.SystemOperator} 1418 */ 1419 ({ of: [left, right], is }) => { 1420 return { 1421 match: { of: left, with: right, is }, 1422 operator: /** @type {const} */ ('text/concat'), 1423 } 1424 } 1425 ) 1426 1427 static Words = Operator.for( 1428 /** 1429 * @param {object} terms 1430 * @param {API.Term<string>} terms.of 1431 * @param {API.Term<string>} [terms.is] 1432 */ 1433 ({ of, is }) => { 1434 return { 1435 match: { of, is }, 1436 operator: /** @type {const} */ ('text/words'), 1437 } 1438 } 1439 ) 1440 1441 static Lines = Operator.for( 1442 /** 1443 * @param {object} terms 1444 * @param {API.Term<string>} terms.of 1445 * @param {API.Term<string>} [terms.is] 1446 */ 1447 ({ of, is }) => { 1448 return { 1449 match: { of, is }, 1450 operator: /** @type {const} */ ('text/lines'), 1451 } 1452 } 1453 ) 1454 1455 static UpperCase = Operator.for( 1456 /** 1457 * @param {object} terms 1458 * @param {API.Term<string>} terms.of 1459 * @param {API.Term<string>} [terms.is] 1460 */ 1461 ({ of, is }) => { 1462 return { 1463 match: { of, is }, 1464 operator: /** @type {const} */ ('text/case/upper'), 1465 } 1466 } 1467 ) 1468 1469 static LowerCase = Operator.for( 1470 /** 1471 * @param {object} terms 1472 * @param {API.Term<string>} terms.of 1473 * @param {API.Term<string>} [terms.is] 1474 * 1475 */ 1476 ({ of, is }) => { 1477 return { 1478 match: { of, is }, 1479 operator: /** @type {const} */ ('text/case/lower'), 1480 } 1481 } 1482 ) 1483 1484 static Trim = Operator.for( 1485 /** 1486 * @param {object} terms 1487 * @param {API.Term<string>} terms.of 1488 * @param {API.Term<string>} [terms.is] 1489 */ 1490 ({ of, is }) => { 1491 return { 1492 match: { of, is }, 1493 operator: /** @type {const} */ ('text/trim'), 1494 } 1495 } 1496 ) 1497 1498 static TrimStart = Operator.for( 1499 /** 1500 * @param {object} terms 1501 * @param {API.Term<string>} terms.of 1502 * @param {API.Term<string>} [terms.is] 1503 */ 1504 ({ of, is }) => { 1505 return { 1506 match: { of, is }, 1507 operator: /** @type {const} */ ('text/trim/start'), 1508 } 1509 } 1510 ) 1511 1512 static TrimEnd = Operator.for( 1513 /** 1514 * @param {object} terms 1515 * @param {API.Term<string>} terms.of 1516 * @param {API.Term<string>} [terms.is] 1517 */ 1518 ({ of, is }) => { 1519 return { 1520 match: { of, is }, 1521 operator: /** @type {const} */ ('text/trim/end'), 1522 } 1523 } 1524 ) 1525 1526 static Length = Operator.for( 1527 /** 1528 * @param {object} terms 1529 * @param {API.Term<string>} terms.of 1530 * @param {API.Term<number>} [terms.is] 1531 */ 1532 ({ of, is }) => { 1533 return { 1534 match: { of, is }, 1535 operator: /** @type {const} */ ('text/length'), 1536 } 1537 } 1538 ) 1539 } 1540 1541 export class UTF8 { 1542 static ToText = Operator.for( 1543 /** 1544 * @param {object} terms 1545 * @param {API.Term<Uint8Array>} terms.of 1546 * @param {API.Term<string>} [terms.is] 1547 * @returns {API.SystemOperator} 1548 */ 1549 ({ of, is }) => { 1550 return { 1551 match: { of, is }, 1552 operator: /** @type {const} */ ('utf8/to/text'), 1553 } 1554 } 1555 ) 1556 1557 static FromText = Operator.for( 1558 /** 1559 * @param {object} terms 1560 * @param {API.Term<string>} terms.of 1561 * @param {API.Term<Uint8Array>} [terms.is] 1562 * @returns {API.SystemOperator} 1563 */ 1564 ({ of, is }) => { 1565 return { 1566 match: { of, is }, 1567 operator: /** @type {const} */ ('text/to/utf8'), 1568 } 1569 } 1570 ) 1571 } 1572 1573 export class Data { 1574 static same = Object.assign( 1575 /** 1576 * @template {API.Scalar} This 1577 * @template {API.Scalar} As 1578 * @param {object} terms 1579 * @param {API.Term<This>} terms.this 1580 * @param {API.Term<As>} [terms.as] 1581 * @returns {API.SystemOperator} 1582 */ 1583 ({ this: of, as }) => { 1584 return /** @type {API.SystemOperator} */ ({ 1585 match: { of, is: as }, 1586 operator: /** @type {const} */ ('=='), 1587 }) 1588 }, 1589 { 1590 /** 1591 * @template {API.Scalar} This 1592 * @template {API.Scalar} As 1593 * @param {object} terms 1594 * @param {API.Term<This>} terms.this 1595 * @param {API.Term<As>} [terms.as] 1596 * @returns {API.Negation} 1597 */ 1598 not: (terms) => ({ not: Data.same(terms) }), 1599 } 1600 ) 1601 1602 static not = Operator.for( 1603 /** 1604 * @param {object} terms 1605 * @param {API.Term<boolean>} terms.of 1606 * @param {API.Term<boolean>} terms.is 1607 * @returns {API.SystemOperator} 1608 */ 1609 (terms) => { 1610 return /** @type {API.SystemOperator} */ ({ 1611 match: terms, 1612 operator: /** @type {const} */ ('!'), 1613 }) 1614 } 1615 ) 1616 static ['!'] = this.not 1617 1618 static greater = Operator.for( 1619 /** 1620 * @template {number|string} T 1621 * @param {object} terms 1622 * @param {API.Term<T>} terms.this 1623 * @param {API.Term<T>} terms.than 1624 * @returns {API.SystemOperator} 1625 */ 1626 (terms) => { 1627 return { 1628 match: terms, 1629 operator: /** @type {const} */ ('>'), 1630 } 1631 } 1632 ) 1633 static ['>'] = this.greater 1634 1635 static greaterOrEqual = Operator.for( 1636 /** 1637 * @template {number|string} T 1638 * @param {object} terms 1639 * @param {API.Term<T>} terms.this 1640 * @param {API.Term<T>} terms.than 1641 * @returns {API.SystemOperator} 1642 */ 1643 (terms) => { 1644 return { 1645 match: terms, 1646 operator: /** @type {const} */ ('>='), 1647 } 1648 } 1649 ) 1650 static ['>='] = this.greaterOrEqual 1651 1652 static less = Operator.for( 1653 /** 1654 * @template {number|string} T 1655 * @param {object} terms 1656 * @param {API.Term<T>} terms.this 1657 * @param {API.Term<T>} terms.than 1658 * @returns {API.SystemOperator} 1659 */ 1660 (terms) => { 1661 return { 1662 match: terms, 1663 operator: /** @type {const} */ ('<'), 1664 } 1665 } 1666 ) 1667 static ['<'] = this.less 1668 1669 static lessOrEqual = Operator.for( 1670 /** 1671 * @template {number|string} T 1672 * @param {object} terms 1673 * @param {API.Term<T>} terms.this 1674 * @param {API.Term<T>} terms.than 1675 * @returns {API.SystemOperator} 1676 */ 1677 (terms) => { 1678 return { 1679 match: terms, 1680 operator: /** @type {const} */ ('<='), 1681 } 1682 } 1683 ) 1684 1685 static ['<='] = this.lessOrEqual 1686 1687 static Type = Operator.for( 1688 /** 1689 * @param {object} terms 1690 * @param {API.Term<API.Scalar>} terms.of 1691 * @param {API.Term<API.TypeName>|API.Term<string>} [terms.is] 1692 * @returns {API.SystemOperator} 1693 */ 1694 ({ of, is }) => { 1695 return /** @type {API.SystemOperator} */ ({ 1696 match: { of, is }, 1697 operator: /** @type {const} */ ('data/type'), 1698 }) 1699 } 1700 ) 1701 1702 static Reference = Operator.for( 1703 /** 1704 * @param {object} terms 1705 * @param {API.Term<any>} terms.of 1706 * @param {API.Term<API.Entity>} [terms.is] 1707 * @returns {{match: { of: API.Term, is?: API.Term<API.Entity> }, operator: 'data/refer' }} 1708 */ 1709 ({ of, is }) => { 1710 return { 1711 match: { of, is }, 1712 operator: /** @type {const} */ ('data/refer'), 1713 } 1714 } 1715 ) 1716 1717 static Fact = Operator.for( 1718 /** 1719 * @template {Record<string, API.Term> & {this?: API.Term<API.Entity>}} Terms 1720 * @param {Terms} terms 1721 * @returns {{match: Omit<Terms, 'this'> & { is?: API.Term<API.Entity> }, operator: 'data/refer' }} 1722 */ 1723 ({ this: is, ...of }) => { 1724 return { 1725 match: { ...of, is }, 1726 operator: 'data/refer', 1727 } 1728 } 1729 ) 1730 } 1731 1732 export class Math { 1733 static Sum = Operator.for( 1734 /** 1735 * @param {object} terms 1736 * @param {API.Term<API.Numeric>} terms.of 1737 * @param {API.Term<API.Numeric>} terms.with 1738 * @param {API.Term<API.Numeric>} [terms.is] 1739 * @returns {API.SystemOperator} 1740 */ 1741 ({ of, with: by, is }) => { 1742 return /** @type {API.SystemOperator} */ ({ 1743 match: { of, with: by, is }, 1744 operator: /** @type {const} */ ('+'), 1745 }) 1746 } 1747 ) 1748 static ['+'] = this.Sum 1749 1750 static Subtraction = Operator.for( 1751 /** 1752 * @param {object} terms 1753 * @param {API.Term<API.Numeric>} terms.of 1754 * @param {API.Term<API.Numeric>} terms.by 1755 * @param {API.Term<API.Numeric>} [terms.is] 1756 * @returns {API.SystemOperator} 1757 */ 1758 (terms) => { 1759 return /** @type {API.SystemOperator} */ ({ 1760 match: terms, 1761 operator: /** @type {const} */ ('-'), 1762 }) 1763 } 1764 ) 1765 static ['-'] = this.Subtraction 1766 1767 static Multiplication = Operator.for( 1768 /** 1769 * @param {object} terms 1770 * @param {API.Term<API.Numeric>} terms.of 1771 * @param {API.Term<API.Numeric>} terms.by 1772 * @param {API.Term<API.Numeric>} [terms.is] 1773 * @returns {API.SystemOperator} 1774 */ 1775 (terms) => { 1776 return /** @type {API.SystemOperator} */ ({ 1777 match: terms, 1778 operator: /** @type {const} */ ('*'), 1779 }) 1780 } 1781 ) 1782 static ['*'] = this.Multiplication 1783 1784 static Division = Operator.for( 1785 /** 1786 * @param {object} terms 1787 * @param {API.Term<API.Numeric>} terms.of 1788 * @param {API.Term<API.Numeric>} terms.by 1789 * @param {API.Term<API.Numeric>} [terms.is] 1790 * @returns {API.SystemOperator} 1791 */ 1792 (terms) => { 1793 return /** @type {API.SystemOperator} */ ({ 1794 match: terms, 1795 operator: /** @type {const} */ ('/'), 1796 }) 1797 } 1798 ) 1799 static ['/'] = this.Division 1800 1801 static Modulo = Operator.for( 1802 /** 1803 * @param {object} terms 1804 * @param {API.Term<API.Numeric>} terms.of 1805 * @param {API.Term<API.Numeric>} terms.by 1806 * @param {API.Term<API.Numeric>} [terms.is] 1807 * @returns {API.SystemOperator} 1808 */ 1809 (terms) => { 1810 return /** @type {API.SystemOperator} */ ({ 1811 match: terms, 1812 operator: /** @type {const} */ ('%'), 1813 }) 1814 } 1815 ) 1816 static ['%'] = this.Modulo 1817 1818 static Power = Operator.for( 1819 /** 1820 * @param {object} terms 1821 * @param {API.Term<API.Numeric>} terms.of 1822 * @param {API.Term<API.Numeric>} terms.exponent 1823 * @param {API.Term<API.Numeric>} [terms.is] 1824 * @returns {API.SystemOperator} 1825 */ 1826 ({ of, exponent, is }) => { 1827 return /** @type {API.SystemOperator} */ ({ 1828 match: { of, by: exponent, is }, 1829 operator: /** @type {const} */ ('**'), 1830 }) 1831 } 1832 ) 1833 static ['**'] = this.Power 1834 1835 static Absolute = Operator.for( 1836 /** 1837 * @param {object} terms 1838 * @param {API.Term<API.Numeric>} terms.of 1839 * @param {API.Term<API.Numeric>} [terms.is] 1840 * @returns {API.SystemOperator} 1841 */ 1842 ({ of, is }) => { 1843 return /** @type {API.SystemOperator} */ ({ 1844 match: { of, is }, 1845 operator: /** @type {const} */ ('math/absolute'), 1846 }) 1847 } 1848 ) 1849 } 1850 1851 const Same = Syntax.rule({ match: { this: $.this, as: $.this } }) 1852 const NotSame = Syntax.rule({ 1853 match: { this: $.this, as: $.as }, 1854 when: { 1855 where: [{ not: Same.apply({ this: $.this, as: $.as }) }], 1856 }, 1857 }) 1858 1859 export const same = Object.assign( 1860 /** 1861 * @template {API.Scalar} This 1862 * @template {API.Scalar} As 1863 * @param {{this: API.Term<This>, as: API.Term<As>}} terms 1864 */ 1865 (terms) => new Match(Same, terms), 1866 { 1867 /** 1868 * @template {API.Scalar} This 1869 * @template {API.Scalar} As 1870 * @param {{this: API.Term<This>, as: API.Term<As>}} terms 1871 */ 1872 not(terms) { 1873 return new Match(NotSame, terms) 1874 }, 1875 } 1876 ) 1877 1878 /** 1879 * @template {API.RuleDescriptor} Schema 1880 * @param {Schema} schema 1881 * @returns {API.InferAttributes<Schema>} 1882 */ 1883 function deriveAttributes(schema) { 1884 const match = /** @type {Record<string, API.Variable>} */ ({}) 1885 for (const [key, type] of Object.entries(schema)) { 1886 match[key] = $[key] 1887 } 1888 match.this = $.this 1889 1890 return /** @type {API.InferSchemaAttributes<Schema>} */ (match) 1891 } 1892 1893 /** 1894 * @template {API.RuleDescriptor} Schema 1895 * @param {Schema} schema 1896 * @returns {API.InferSchemaAttributes<Schema> & {_: API.Variable}} 1897 */ 1898 function deriveCells(schema) { 1899 return Object.assign(deriveAttributes(schema), { _: $._ }) 1900 } 1901 1902 /** 1903 * @template {API.FactSchema} Schema 1904 * @param {Schema} schema 1905 * @param {Partial<API.InferFactTerms<Schema>>} [input] 1906 * @returns {API.InferSchemaTerms<Schema> & { this: API.Term<API.Entity> }} 1907 */ 1908 const completeTerms = (schema, input = {}) => { 1909 const terms = /** @type {Record<String, API.Term>} */ ({}) 1910 for (const key of Object.keys(schema)) { 1911 const value = input[key] 1912 terms[key] = value === undefined ? $[Symbol(key)] : value 1913 } 1914 1915 if (terms.this === undefined) { 1916 terms.this = $[Symbol('this')] 1917 } 1918 1919 return /** @type {API.InferSchemaTerms<Schema> & { this: API.Term<API.Entity> }} */ ( 1920 terms 1921 ) 1922 } 1923 /** 1924 * @param {API.EveryView} source 1925 * @returns {Iterable<API.Conjunct|API.Recur>} 1926 */ 1927 function* iterateConjuncts(source) { 1928 for (const member of source) { 1929 if (member === undefined) { 1930 continue 1931 } else if (Symbol.iterator in member) { 1932 for (const conjunct of member) { 1933 yield conjunct 1934 } 1935 } else { 1936 yield member 1937 } 1938 } 1939 } 1940 1941 /** 1942 * @param {API.WhenView} source 1943 * @returns {Iterable<[string, API.EveryView]>} 1944 */ 1945 function* iterateDisjuncts(source) { 1946 if (Array.isArray(source)) { 1947 yield ['where', source] 1948 } else { 1949 yield* Object.entries(source) 1950 } 1951 }