@dxos/datalog 0.0.1 → 0.13.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/LICENSE +105 -0
- package/README.md +58 -1
- package/dist/lib/Ast.mjs +72 -0
- package/dist/lib/Ast.mjs.map +1 -0
- package/dist/lib/Builtin.mjs +37 -0
- package/dist/lib/Builtin.mjs.map +1 -0
- package/dist/lib/Checker.mjs +3 -0
- package/dist/lib/Engine.mjs +3 -0
- package/dist/lib/Parser.mjs +2 -0
- package/dist/lib/chunk-Checker.mjs +414 -0
- package/dist/lib/chunk-Checker.mjs.map +1 -0
- package/dist/lib/chunk-Engine.mjs +488 -0
- package/dist/lib/chunk-Engine.mjs.map +1 -0
- package/dist/lib/chunk-Parser.mjs +366 -0
- package/dist/lib/chunk-Parser.mjs.map +1 -0
- package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
- package/dist/lib/index.mjs +6 -0
- package/dist/types/src/Ast.d.ts +81 -0
- package/dist/types/src/Ast.d.ts.map +1 -0
- package/dist/types/src/Builtin.d.ts +35 -0
- package/dist/types/src/Builtin.d.ts.map +1 -0
- package/dist/types/src/Checker.d.ts +61 -0
- package/dist/types/src/Checker.d.ts.map +1 -0
- package/dist/types/src/Checker.test.d.ts +2 -0
- package/dist/types/src/Checker.test.d.ts.map +1 -0
- package/dist/types/src/Engine.d.ts +55 -0
- package/dist/types/src/Engine.d.ts.map +1 -0
- package/dist/types/src/Engine.test.d.ts +2 -0
- package/dist/types/src/Engine.test.d.ts.map +1 -0
- package/dist/types/src/Engine.workerd.test.d.ts +2 -0
- package/dist/types/src/Engine.workerd.test.d.ts.map +1 -0
- package/dist/types/src/Parser.d.ts +43 -0
- package/dist/types/src/Parser.d.ts.map +1 -0
- package/dist/types/src/Parser.test.d.ts +2 -0
- package/dist/types/src/Parser.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +6 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/internal/lexer.d.ts +46 -0
- package/dist/types/src/internal/lexer.d.ts.map +1 -0
- package/dist/types/src/internal/plan.d.ts +96 -0
- package/dist/types/src/internal/plan.d.ts.map +1 -0
- package/dist/types/src/internal/relation.d.ts +20 -0
- package/dist/types/src/internal/relation.d.ts.map +1 -0
- package/dist/types/src/internal/stratify.d.ts +28 -0
- package/dist/types/src/internal/stratify.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +57 -5
- package/src/Ast.ts +136 -0
- package/src/Builtin.ts +73 -0
- package/src/Checker.test.ts +99 -0
- package/src/Checker.ts +156 -0
- package/src/Engine.test.ts +239 -0
- package/src/Engine.ts +614 -0
- package/src/Engine.workerd.test.ts +33 -0
- package/src/Parser.test.ts +84 -0
- package/src/Parser.ts +180 -0
- package/src/index.ts +9 -0
- package/src/internal/lexer.ts +154 -0
- package/src/internal/plan.ts +346 -0
- package/src/internal/relation.ts +95 -0
- package/src/internal/stratify.ts +114 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-Apache-2.0
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 DXOS
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,60 @@
|
|
|
1
1
|
# @dxos/datalog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small Datalog engine with no third-party dependencies: a parser for a Soufflé-like dialect, static checks, and an
|
|
4
|
+
incremental stratified semi-naive evaluator with provenance and pluggable built-ins. It is plain
|
|
5
|
+
TypeScript with no Node built-ins, `eval` or threads, so it runs unchanged in the browser, in a
|
|
6
|
+
workerd Durable Object and in Node.
|
|
7
|
+
|
|
8
|
+
## Dialect
|
|
9
|
+
|
|
10
|
+
```prolog
|
|
11
|
+
% Facts and rules; lowercase words and "strings" are the same symbol, numbers are numbers.
|
|
12
|
+
edge(a, b).
|
|
13
|
+
path(X, Y) :- edge(X, Y).
|
|
14
|
+
path(X, Z) :- path(X, Y), edge(Y, Z).
|
|
15
|
+
|
|
16
|
+
% Stratified negation (`not p(…)`, `!p(…)`), anonymous variables and comparisons.
|
|
17
|
+
isolated(X) :- node(X), not path(X, _), X != root.
|
|
18
|
+
|
|
19
|
+
% Aggregates: count, min, max and sum over a body; variables shared with the rule correlate it.
|
|
20
|
+
degree(X, N) :- node(X), N = count : { edge(X, _) }.
|
|
21
|
+
|
|
22
|
+
% Built-ins are called like relations; `2d` is a symbol, so durations need no quotes.
|
|
23
|
+
followup :- elapsed(goal, 2d), not achieved(goal).
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
No disjunction, functions or existential heads: every program terminates.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { Builtin, Checker, Engine, Parser } from '@dxos/datalog';
|
|
32
|
+
|
|
33
|
+
const program = Parser.parse(source); // throws Parser.ParseError with line and column
|
|
34
|
+
const diagnostics = Checker.check(program, { builtins, relations: { edge: 2 } });
|
|
35
|
+
|
|
36
|
+
const engine = Engine.make({ program, builtins });
|
|
37
|
+
const changes = engine.insert('edge', ['a', 'b']); // only the tuples this insert added or removed
|
|
38
|
+
engine.update({ refresh: true }); // re-evaluate volatile built-ins (a clock tick)
|
|
39
|
+
engine.provenance('path', ['a', 'b']); // the base tuples one derivation rests on
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **Checks** (`Checker.check`) return structured diagnostics: `arity`, `undefined` (when base
|
|
43
|
+
relations are declared), `builtin-head`, `unsafe` (every head variable and every variable under
|
|
44
|
+
`not` or in a comparison must be bound by a positive literal or a built-in mode that binds it) and
|
|
45
|
+
`unstratifiable` (negation or aggregation through recursion).
|
|
46
|
+
- **Built-ins** (`Builtin.make`) declare binding modes: `"fb"` means the first argument may be free and
|
|
47
|
+
is bound by the call. A `volatile` built-in (one that reads a clock) is re-evaluated on `refresh`.
|
|
48
|
+
- **Incremental evaluation**: insertions propagate semi-naively; a stratum is recomputed and diffed
|
|
49
|
+
only when a relation it reads under negation or aggregation changed, or a relation it reads lost
|
|
50
|
+
tuples. `Changes.added` is the "new binding" signal callers wake on.
|
|
51
|
+
- **Provenance**: one derivation is kept per derived tuple (the rule and the tuples its positive
|
|
52
|
+
literals matched). Aggregates and negation contribute no premises.
|
|
53
|
+
- **Rewrites**: the AST (`Ast`) is plain data, so a caller rewrites a parsed program (for example,
|
|
54
|
+
expanding predicate shorthand) before checking it.
|
|
55
|
+
|
|
56
|
+
## Why synchronous
|
|
57
|
+
|
|
58
|
+
The engine is a pure function of its inputs with no I/O, so it exposes plain synchronous calls rather
|
|
59
|
+
than Effects; callers that need Effect wrap a call where it is used. This keeps the hot path (one
|
|
60
|
+
evaluation per appended fact) free of fiber overhead and the package free of third-party runtime dependencies.
|
package/dist/lib/Ast.mjs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
|
+
//#region src/Ast.ts
|
|
3
|
+
var Ast_exports = /* @__PURE__ */ __exportAll({
|
|
4
|
+
atom: () => atom,
|
|
5
|
+
constant: () => constant,
|
|
6
|
+
format: () => format,
|
|
7
|
+
formatAtom: () => formatAtom,
|
|
8
|
+
formatLiteral: () => formatLiteral,
|
|
9
|
+
formatRule: () => formatRule,
|
|
10
|
+
formatTerm: () => formatTerm,
|
|
11
|
+
formatValue: () => formatValue,
|
|
12
|
+
literalVariables: () => literalVariables,
|
|
13
|
+
variable: () => variable,
|
|
14
|
+
variablesOf: () => variablesOf
|
|
15
|
+
});
|
|
16
|
+
/** Creates a variable term. */
|
|
17
|
+
var variable = (name) => ({
|
|
18
|
+
type: "variable",
|
|
19
|
+
name
|
|
20
|
+
});
|
|
21
|
+
/** Creates a constant term. */
|
|
22
|
+
var constant = (value) => ({
|
|
23
|
+
type: "constant",
|
|
24
|
+
value
|
|
25
|
+
});
|
|
26
|
+
/** Creates an atom. */
|
|
27
|
+
var atom = (predicate, terms) => ({
|
|
28
|
+
predicate,
|
|
29
|
+
terms
|
|
30
|
+
});
|
|
31
|
+
/** Named (non-anonymous) variables of a term list, in order of first occurrence. */
|
|
32
|
+
var variablesOf = (terms) => {
|
|
33
|
+
const names = [];
|
|
34
|
+
for (const term of terms) if (term.type === "variable" && !term.anonymous && !names.includes(term.name)) names.push(term.name);
|
|
35
|
+
return names;
|
|
36
|
+
};
|
|
37
|
+
/** Named variables a literal mentions, including those inside an aggregate body. */
|
|
38
|
+
var literalVariables = (literal) => {
|
|
39
|
+
switch (literal.type) {
|
|
40
|
+
case "atom": return variablesOf(literal.atom.terms);
|
|
41
|
+
case "comparison": return variablesOf([literal.left, literal.right]);
|
|
42
|
+
case "aggregate": {
|
|
43
|
+
const terms = [literal.result, ...literal.target ? [literal.target] : []];
|
|
44
|
+
return [.../* @__PURE__ */ new Set([...variablesOf(terms), ...literal.body.flatMap(literalVariables)])];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/** Formats a value as dialect source. */
|
|
49
|
+
var formatValue = (value) => typeof value === "number" ? String(value) : /^[a-z][A-Za-z0-9_]*$/.test(value) ? value : JSON.stringify(value);
|
|
50
|
+
/** Formats a term as dialect source. */
|
|
51
|
+
var formatTerm = (term) => term.type === "constant" ? formatValue(term.value) : term.anonymous ? "_" : term.name;
|
|
52
|
+
/** Formats an atom as dialect source. */
|
|
53
|
+
var formatAtom = (atom) => atom.terms.length === 0 ? atom.predicate : `${atom.predicate}(${atom.terms.map(formatTerm).join(", ")})`;
|
|
54
|
+
/** Formats a literal as dialect source. */
|
|
55
|
+
var formatLiteral = (literal) => {
|
|
56
|
+
switch (literal.type) {
|
|
57
|
+
case "atom": return `${literal.negated ? "not " : ""}${formatAtom(literal.atom)}`;
|
|
58
|
+
case "comparison": return `${formatTerm(literal.left)} ${literal.operator} ${formatTerm(literal.right)}`;
|
|
59
|
+
case "aggregate": {
|
|
60
|
+
const target = literal.target ? ` ${formatTerm(literal.target)}` : "";
|
|
61
|
+
return `${formatTerm(literal.result)} = ${literal.function}${target} : { ${literal.body.map(formatLiteral).join(", ")} }`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/** Formats a rule as dialect source. */
|
|
66
|
+
var formatRule = (rule) => rule.body.length === 0 ? `${formatAtom(rule.head)}.` : `${formatAtom(rule.head)} :- ${rule.body.map(formatLiteral).join(", ")}.`;
|
|
67
|
+
/** Formats a program as dialect source, one rule per line. */
|
|
68
|
+
var format = (program) => program.rules.map(formatRule).join("\n");
|
|
69
|
+
//#endregion
|
|
70
|
+
export { atom, constant, format, formatAtom, formatLiteral, formatRule, formatTerm, formatValue, literalVariables, Ast_exports as t, variable, variablesOf };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=Ast.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Ast.mjs","names":[],"sources":["../../src/Ast.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\n/** A ground value; symbols (`dima`) and strings (`\"dima\"`) are the same value. */\nexport type Value = string | number;\n\n/** A ground row of a relation. */\nexport type Tuple = ReadonlyArray<Value>;\n\n/** Source location (1-based line and column). */\nexport type Position = { readonly line: number; readonly column: number };\n\nexport type Variable = {\n readonly type: 'variable';\n readonly name: string;\n /** Set for `_`; each occurrence is a distinct, never-joined variable. */\n readonly anonymous?: boolean;\n};\n\nexport type Constant = { readonly type: 'constant'; readonly value: Value };\n\nexport type Term = Variable | Constant;\n\nexport type Atom = { readonly predicate: string; readonly terms: ReadonlyArray<Term> };\n\nexport type ComparisonOperator = '=' | '!=' | '<' | '<=' | '>' | '>=';\n\nexport type AggregateFunction = 'count' | 'min' | 'max' | 'sum';\n\nexport type AtomLiteral = {\n readonly type: 'atom';\n readonly atom: Atom;\n readonly negated: boolean;\n readonly position?: Position;\n};\n\nexport type ComparisonLiteral = {\n readonly type: 'comparison';\n readonly operator: ComparisonOperator;\n readonly left: Term;\n readonly right: Term;\n readonly position?: Position;\n};\n\n/** `Result = fn Target : { body }`; `Target` is absent for `count`. */\nexport type AggregateLiteral = {\n readonly type: 'aggregate';\n readonly function: AggregateFunction;\n readonly result: Term;\n readonly target?: Term;\n readonly body: ReadonlyArray<Literal>;\n readonly position?: Position;\n};\n\nexport type Literal = AtomLiteral | ComparisonLiteral | AggregateLiteral;\n\n/** A rule; a fact is a rule with an empty body. */\nexport type Rule = {\n readonly head: Atom;\n readonly body: ReadonlyArray<Literal>;\n readonly position?: Position;\n};\n\nexport type Program = { readonly rules: ReadonlyArray<Rule> };\n\n/** Creates a variable term. */\nexport const variable = (name: string): Variable => ({ type: 'variable', name });\n\n/** Creates a constant term. */\nexport const constant = (value: Value): Constant => ({ type: 'constant', value });\n\n/** Creates an atom. */\nexport const atom = (predicate: string, terms: ReadonlyArray<Term>): Atom => ({ predicate, terms });\n\n/** Named (non-anonymous) variables of a term list, in order of first occurrence. */\nexport const variablesOf = (terms: ReadonlyArray<Term>): string[] => {\n const names: string[] = [];\n for (const term of terms) {\n if (term.type === 'variable' && !term.anonymous && !names.includes(term.name)) {\n names.push(term.name);\n }\n }\n return names;\n};\n\n/** Named variables a literal mentions, including those inside an aggregate body. */\nexport const literalVariables = (literal: Literal): string[] => {\n switch (literal.type) {\n case 'atom':\n return variablesOf(literal.atom.terms);\n case 'comparison':\n return variablesOf([literal.left, literal.right]);\n case 'aggregate': {\n const terms = [literal.result, ...(literal.target ? [literal.target] : [])];\n return [...new Set([...variablesOf(terms), ...literal.body.flatMap(literalVariables)])];\n }\n }\n};\n\n/** Formats a value as dialect source. */\nexport const formatValue = (value: Value): string =>\n typeof value === 'number' ? String(value) : /^[a-z][A-Za-z0-9_]*$/.test(value) ? value : JSON.stringify(value);\n\n/** Formats a term as dialect source. */\nexport const formatTerm = (term: Term): string =>\n term.type === 'constant' ? formatValue(term.value) : term.anonymous ? '_' : term.name;\n\n/** Formats an atom as dialect source. */\nexport const formatAtom = (atom: Atom): string =>\n atom.terms.length === 0 ? atom.predicate : `${atom.predicate}(${atom.terms.map(formatTerm).join(', ')})`;\n\n/** Formats a literal as dialect source. */\nexport const formatLiteral = (literal: Literal): string => {\n switch (literal.type) {\n case 'atom':\n return `${literal.negated ? 'not ' : ''}${formatAtom(literal.atom)}`;\n case 'comparison':\n return `${formatTerm(literal.left)} ${literal.operator} ${formatTerm(literal.right)}`;\n case 'aggregate': {\n const target = literal.target ? ` ${formatTerm(literal.target)}` : '';\n return `${formatTerm(literal.result)} = ${literal.function}${target} : { ${literal.body.map(formatLiteral).join(', ')} }`;\n }\n }\n};\n\n/** Formats a rule as dialect source. */\nexport const formatRule = (rule: Rule): string =>\n rule.body.length === 0\n ? `${formatAtom(rule.head)}.`\n : `${formatAtom(rule.head)} :- ${rule.body.map(formatLiteral).join(', ')}.`;\n\n/** Formats a program as dialect source, one rule per line. */\nexport const format = (program: Program): string => program.rules.map(formatRule).join('\\n');\n"],"mappings":";;;;;;;;;;;;;;;;AAqEA,IAAa,YAAY,UAA4B;CAAE,MAAM;CAAY;AAAK;;AAG9E,IAAa,YAAY,WAA4B;CAAE,MAAM;CAAY;AAAM;;AAG/E,IAAa,QAAQ,WAAmB,WAAsC;CAAE;CAAW;AAAM;;AAGjG,IAAa,eAAe,UAAyC;CACnE,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,cAAc,CAAC,KAAK,aAAa,CAAC,MAAM,SAAS,KAAK,IAAI,GAC1E,MAAM,KAAK,KAAK,IAAI;CAGxB,OAAO;AACT;;AAGA,IAAa,oBAAoB,YAA+B;CAC9D,QAAQ,QAAQ,MAAhB;EACE,KAAK,QACH,OAAO,YAAY,QAAQ,KAAK,KAAK;EACvC,KAAK,cACH,OAAO,YAAY,CAAC,QAAQ,MAAM,QAAQ,KAAK,CAAC;EAClD,KAAK,aAAa;GAChB,MAAM,QAAQ,CAAC,QAAQ,QAAQ,GAAI,QAAQ,SAAS,CAAC,QAAQ,MAAM,IAAI,CAAC,CAAE;GAC1E,OAAO,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,YAAY,KAAK,GAAG,GAAG,QAAQ,KAAK,QAAQ,gBAAgB,CAAC,CAAC,CAAC;EACxF;CACF;AACF;;AAGA,IAAa,eAAe,UAC1B,OAAO,UAAU,WAAW,OAAO,KAAK,IAAI,uBAAuB,KAAK,KAAK,IAAI,QAAQ,KAAK,UAAU,KAAK;;AAG/G,IAAa,cAAc,SACzB,KAAK,SAAS,aAAa,YAAY,KAAK,KAAK,IAAI,KAAK,YAAY,MAAM,KAAK;;AAGnF,IAAa,cAAc,SACzB,KAAK,MAAM,WAAW,IAAI,KAAK,YAAY,GAAG,KAAK,UAAU,GAAG,KAAK,MAAM,IAAI,UAAU,CAAC,CAAC,KAAK,IAAI,EAAE;;AAGxG,IAAa,iBAAiB,YAA6B;CACzD,QAAQ,QAAQ,MAAhB;EACE,KAAK,QACH,OAAO,GAAG,QAAQ,UAAU,SAAS,KAAK,WAAW,QAAQ,IAAI;EACnE,KAAK,cACH,OAAO,GAAG,WAAW,QAAQ,IAAI,EAAE,GAAG,QAAQ,SAAS,GAAG,WAAW,QAAQ,KAAK;EACpF,KAAK,aAAa;GAChB,MAAM,SAAS,QAAQ,SAAS,IAAI,WAAW,QAAQ,MAAM,MAAM;GACnE,OAAO,GAAG,WAAW,QAAQ,MAAM,EAAE,KAAK,QAAQ,WAAW,OAAO,OAAO,QAAQ,KAAK,IAAI,aAAa,CAAC,CAAC,KAAK,IAAI,EAAE;EACxH;CACF;AACF;;AAGA,IAAa,cAAc,SACzB,KAAK,KAAK,WAAW,IACjB,GAAG,WAAW,KAAK,IAAI,EAAE,KACzB,GAAG,WAAW,KAAK,IAAI,EAAE,MAAM,KAAK,KAAK,IAAI,aAAa,CAAC,CAAC,KAAK,IAAI,EAAE;;AAG7E,IAAa,UAAU,YAA6B,QAAQ,MAAM,IAAI,UAAU,CAAC,CAAC,KAAK,IAAI"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
|
+
//#region src/Builtin.ts
|
|
3
|
+
var Builtin_exports = /* @__PURE__ */ __exportAll({
|
|
4
|
+
acceptsBinding: () => acceptsBinding,
|
|
5
|
+
make: () => make,
|
|
6
|
+
predicate: () => predicate,
|
|
7
|
+
registry: () => registry
|
|
8
|
+
});
|
|
9
|
+
/** Defines a built-in. */
|
|
10
|
+
var make = (builtin) => builtin;
|
|
11
|
+
/** Defines a test-only built-in: holds iff `test` returns true for its fully bound arguments. */
|
|
12
|
+
var predicate = (name, arity, test, options = {}) => ({
|
|
13
|
+
name,
|
|
14
|
+
arity,
|
|
15
|
+
modes: ["b".repeat(arity)],
|
|
16
|
+
volatile: options.volatile,
|
|
17
|
+
evaluate: (args) => {
|
|
18
|
+
const bound = args.filter((arg) => arg !== void 0);
|
|
19
|
+
return bound.length === arity && test(bound) ? [bound] : [];
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
/** Builds a registry, rejecting duplicate names. */
|
|
23
|
+
var registry = (...builtins) => {
|
|
24
|
+
const entries = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const builtin of builtins) {
|
|
26
|
+
if (entries.has(builtin.name)) throw new Error(`Duplicate built-in: ${builtin.name}`);
|
|
27
|
+
if (builtin.modes.some((mode) => mode.length !== builtin.arity || /[^bf]/.test(mode))) throw new Error(`Invalid mode for built-in ${builtin.name}/${builtin.arity}`);
|
|
28
|
+
entries.set(builtin.name, builtin);
|
|
29
|
+
}
|
|
30
|
+
return entries;
|
|
31
|
+
};
|
|
32
|
+
/** True if `builtin` can be called with exactly the `bound` argument positions bound. */
|
|
33
|
+
var acceptsBinding = (builtin, bound) => bound.every(Boolean) || builtin.modes.some((mode) => [...mode].every((flag, index) => flag === "f" || bound[index]));
|
|
34
|
+
//#endregion
|
|
35
|
+
export { acceptsBinding, make, predicate, registry, Builtin_exports as t };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=Builtin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Builtin.mjs","names":[],"sources":["../../src/Builtin.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport type * as Ast from './Ast.ts';\n\n/**\n * A binding pattern, one character per argument: `b` must be bound before the call, `f` may be\n * free and is bound by the call. `\"fb\"` lets the first argument be generated from the second.\n */\nexport type Mode = string;\n\n/** Arguments as seen by a built-in: `undefined` marks a free position of the selected mode. */\nexport type Arguments = ReadonlyArray<Ast.Value | undefined>;\n\n/** A predicate implemented in code rather than by rules. */\nexport type Builtin = {\n readonly name: string;\n readonly arity: number;\n /** Accepted binding patterns; the all-bound pattern is always accepted. */\n readonly modes: ReadonlyArray<Mode>;\n /**\n * Set when the result can change with no change to the facts (a clock), so the engine\n * re-evaluates the rules that use it on `refresh`.\n */\n readonly volatile?: boolean;\n /** Returns every full argument tuple that holds and agrees with the bound arguments. */\n readonly evaluate: (args: Arguments) => Iterable<Ast.Tuple>;\n};\n\n/** Built-ins by name. */\nexport type Registry = ReadonlyMap<string, Builtin>;\n\n/** Defines a built-in. */\nexport const make = (builtin: Builtin): Builtin => builtin;\n\n/** Defines a test-only built-in: holds iff `test` returns true for its fully bound arguments. */\nexport const predicate = (\n name: string,\n arity: number,\n test: (args: Ast.Tuple) => boolean,\n options: { volatile?: boolean } = {},\n): Builtin => ({\n name,\n arity,\n modes: ['b'.repeat(arity)],\n volatile: options.volatile,\n evaluate: (args) => {\n const bound = args.filter((arg): arg is Ast.Value => arg !== undefined);\n return bound.length === arity && test(bound) ? [bound] : [];\n },\n});\n\n/** Builds a registry, rejecting duplicate names. */\nexport const registry = (...builtins: ReadonlyArray<Builtin>): Registry => {\n const entries = new Map<string, Builtin>();\n for (const builtin of builtins) {\n if (entries.has(builtin.name)) {\n throw new Error(`Duplicate built-in: ${builtin.name}`);\n }\n if (builtin.modes.some((mode) => mode.length !== builtin.arity || /[^bf]/.test(mode))) {\n throw new Error(`Invalid mode for built-in ${builtin.name}/${builtin.arity}`);\n }\n entries.set(builtin.name, builtin);\n }\n return entries;\n};\n\n/** True if `builtin` can be called with exactly the `bound` argument positions bound. */\nexport const acceptsBinding = (builtin: Builtin, bound: ReadonlyArray<boolean>): boolean =>\n bound.every(Boolean) || builtin.modes.some((mode) => [...mode].every((flag, index) => flag === 'f' || bound[index]));\n"],"mappings":";;;;;;;;;AAoCA,IAAa,QAAQ,YAA8B;;AAGnD,IAAa,aACX,MACA,OACA,MACA,UAAkC,CAAC,OACtB;CACb;CACA;CACA,OAAO,CAAC,IAAI,OAAO,KAAK,CAAC;CACzB,UAAU,QAAQ;CAClB,WAAW,SAAS;EAClB,MAAM,QAAQ,KAAK,QAAQ,QAA0B,QAAQ,KAAA,CAAS;EACtE,OAAO,MAAM,WAAW,SAAS,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC;CAC5D;AACF;;AAGA,IAAa,YAAY,GAAG,aAA+C;CACzE,MAAM,0BAAU,IAAI,IAAqB;CACzC,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,IAAI,QAAQ,IAAI,GAC1B,MAAM,IAAI,MAAM,uBAAuB,QAAQ,MAAM;EAEvD,IAAI,QAAQ,MAAM,MAAM,SAAS,KAAK,WAAW,QAAQ,SAAS,QAAQ,KAAK,IAAI,CAAC,GAClF,MAAM,IAAI,MAAM,6BAA6B,QAAQ,KAAK,GAAG,QAAQ,OAAO;EAE9E,QAAQ,IAAI,QAAQ,MAAM,OAAO;CACnC;CACA,OAAO;AACT;;AAGA,IAAa,kBAAkB,SAAkB,UAC/C,MAAM,MAAM,OAAO,KAAK,QAAQ,MAAM,MAAM,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,MAAM,UAAU,SAAS,OAAO,MAAM,MAAM,CAAC"}
|