eyeling 1.17.2 → 1.18.1
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/HANDBOOK.md +300 -18
- package/README.md +5 -207
- package/examples/bmi.n3 +219 -0
- package/examples/output/bmi.n3 +20 -0
- package/examples/output/pn-junction-tunneling.n3 +23 -0
- package/examples/output/sudoku.n3 +42 -0
- package/examples/output/transistor-switch.n3 +24 -0
- package/examples/pn-junction-tunneling.n3 +227 -0
- package/examples/sudoku.n3 +257 -0
- package/examples/transistor-switch.n3 +299 -0
- package/eyeling.js +678 -2
- package/index.d.ts +21 -0
- package/index.js +13 -0
- package/lib/builtin-sudoku.js +465 -0
- package/lib/builtins.js +156 -0
- package/lib/cli.js +33 -2
- package/lib/engine.js +21 -0
- package/package.json +1 -1
- package/test/api.test.js +29 -0
package/lib/builtins.js
CHANGED
|
@@ -61,6 +61,152 @@ function __useNumericCacheKey(key) {
|
|
|
61
61
|
return typeof key === 'string' && key.length <= MAX_NUMERIC_CACHE_KEY_LEN;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Custom builtin registry
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
const __customBuiltinHandlers = new Map(); // predicate IRI -> evaluator(ctx) => deltas[]
|
|
68
|
+
const __loadedBuiltinModuleIds = new Set();
|
|
69
|
+
|
|
70
|
+
function __validateBuiltinIri(iri) {
|
|
71
|
+
if (typeof iri !== 'string' || !iri) {
|
|
72
|
+
throw new TypeError('Custom builtin IRI must be a non-empty string');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function registerBuiltin(iri, handler) {
|
|
77
|
+
__validateBuiltinIri(iri);
|
|
78
|
+
if (typeof handler !== 'function') {
|
|
79
|
+
throw new TypeError(`Custom builtin ${iri} must be registered with a function handler`);
|
|
80
|
+
}
|
|
81
|
+
__customBuiltinHandlers.set(iri, handler);
|
|
82
|
+
return handler;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function unregisterBuiltin(iri) {
|
|
86
|
+
return __customBuiltinHandlers.delete(iri);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function listBuiltinIris() {
|
|
90
|
+
return Array.from(__customBuiltinHandlers.keys()).sort();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function __buildBuiltinRegistrationApi() {
|
|
94
|
+
return {
|
|
95
|
+
registerBuiltin,
|
|
96
|
+
unregisterBuiltin,
|
|
97
|
+
listBuiltinIris,
|
|
98
|
+
internIri,
|
|
99
|
+
internLiteral,
|
|
100
|
+
literalParts,
|
|
101
|
+
termToJsString,
|
|
102
|
+
termToJsStringDecoded,
|
|
103
|
+
termToN3,
|
|
104
|
+
iriValue,
|
|
105
|
+
unifyTerm,
|
|
106
|
+
applySubstTerm,
|
|
107
|
+
applySubstTriple,
|
|
108
|
+
proveGoals,
|
|
109
|
+
isGroundTerm,
|
|
110
|
+
computeConclusionFromFormula,
|
|
111
|
+
skolemIriFromGroundTerm,
|
|
112
|
+
parseBooleanLiteralInfo,
|
|
113
|
+
parseNumericLiteralInfo,
|
|
114
|
+
parseXsdDecimalToBigIntScale,
|
|
115
|
+
pow10n,
|
|
116
|
+
normalizeLiteralForFastKey,
|
|
117
|
+
literalsEquivalentAsXsdString,
|
|
118
|
+
materializeRdfLists,
|
|
119
|
+
terms: { Literal, Iri, Var, Blank, ListTerm, OpenListTerm, GraphTerm, Triple, Rule },
|
|
120
|
+
ns: { RDF_NS, XSD_NS, CRYPTO_NS, MATH_NS, TIME_NS, LIST_NS, LOG_NS, STRING_NS },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function registerBuiltinModule(mod, origin = '<builtin-module>') {
|
|
125
|
+
if (!mod) throw new TypeError(`Builtin module ${origin} did not export anything`);
|
|
126
|
+
|
|
127
|
+
const api = __buildBuiltinRegistrationApi();
|
|
128
|
+
|
|
129
|
+
if (typeof mod === 'function') {
|
|
130
|
+
mod(api);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (typeof mod.register === 'function') {
|
|
135
|
+
mod.register(api);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const candidates = [];
|
|
140
|
+
if (mod && typeof mod.builtins === 'object' && mod.builtins) candidates.push(mod.builtins);
|
|
141
|
+
if (mod && typeof mod.default === 'object' && mod.default) candidates.push(mod.default);
|
|
142
|
+
candidates.push(mod);
|
|
143
|
+
|
|
144
|
+
let registeredAny = false;
|
|
145
|
+
for (const obj of candidates) {
|
|
146
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) continue;
|
|
147
|
+
for (const [iri, handler] of Object.entries(obj)) {
|
|
148
|
+
if (typeof handler !== 'function') continue;
|
|
149
|
+
registerBuiltin(iri, handler);
|
|
150
|
+
registeredAny = true;
|
|
151
|
+
}
|
|
152
|
+
if (registeredAny) return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
throw new TypeError(
|
|
156
|
+
`Builtin module ${origin} must export a function, a { register() } object, or an object mapping predicate IRIs to handlers`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function loadBuiltinModule(specifier, options = {}) {
|
|
161
|
+
if (typeof require !== 'function') {
|
|
162
|
+
throw new Error('Custom builtin modules can only be loaded when require() is available');
|
|
163
|
+
}
|
|
164
|
+
if (typeof specifier !== 'string' || !specifier) {
|
|
165
|
+
throw new TypeError('Builtin module specifier must be a non-empty string');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const path = require('node:path');
|
|
169
|
+
const resolved = options && options.resolveFrom ? path.resolve(options.resolveFrom, specifier) : specifier;
|
|
170
|
+
const moduleId = String(resolved);
|
|
171
|
+
if (__loadedBuiltinModuleIds.has(moduleId)) return moduleId;
|
|
172
|
+
|
|
173
|
+
const loaded = require(resolved);
|
|
174
|
+
registerBuiltinModule(loaded, moduleId);
|
|
175
|
+
__loadedBuiltinModuleIds.add(moduleId);
|
|
176
|
+
return moduleId;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
180
|
+
const handler = __customBuiltinHandlers.get(pv);
|
|
181
|
+
if (typeof handler !== 'function') return null;
|
|
182
|
+
|
|
183
|
+
const ctx = {
|
|
184
|
+
iri: pv,
|
|
185
|
+
goal,
|
|
186
|
+
subst,
|
|
187
|
+
facts,
|
|
188
|
+
backRules,
|
|
189
|
+
depth,
|
|
190
|
+
varGen,
|
|
191
|
+
maxResults,
|
|
192
|
+
api: __buildBuiltinRegistrationApi(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const out = handler(ctx);
|
|
197
|
+
if (out == null) return [];
|
|
198
|
+
if (!Array.isArray(out)) {
|
|
199
|
+
throw new TypeError(`Custom builtin ${pv} must return an array of substitution deltas`);
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
} catch (err) {
|
|
203
|
+
if (err && typeof err === 'object' && typeof err.message === 'string') {
|
|
204
|
+
err.message = `Error in custom builtin ${pv}: ${err.message}`;
|
|
205
|
+
}
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
64
210
|
//
|
|
65
211
|
// Engine hooks (injected once by makeBuiltins)
|
|
66
212
|
//
|
|
@@ -1466,6 +1612,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
1466
1612
|
if (pv !== allow1 && pv !== allow2) return [];
|
|
1467
1613
|
}
|
|
1468
1614
|
|
|
1615
|
+
const registeredBuiltinResult = __evalRegisteredBuiltin(pv, g, subst, facts, backRules, depth, varGen, maxResults);
|
|
1616
|
+
if (registeredBuiltinResult !== null) return registeredBuiltinResult;
|
|
1617
|
+
|
|
1469
1618
|
// -----------------------------------------------------------------
|
|
1470
1619
|
// 4.1 crypto: builtins
|
|
1471
1620
|
// -----------------------------------------------------------------
|
|
@@ -3677,6 +3826,8 @@ function isBuiltinPred(p) {
|
|
|
3677
3826
|
return true;
|
|
3678
3827
|
}
|
|
3679
3828
|
|
|
3829
|
+
if (__customBuiltinHandlers.has(v)) return true;
|
|
3830
|
+
|
|
3680
3831
|
return (
|
|
3681
3832
|
v.startsWith(CRYPTO_NS) ||
|
|
3682
3833
|
v.startsWith(MATH_NS) ||
|
|
@@ -3834,6 +3985,11 @@ function listHasTriple(list, tr) {
|
|
|
3834
3985
|
|
|
3835
3986
|
module.exports = {
|
|
3836
3987
|
makeBuiltins,
|
|
3988
|
+
registerBuiltin,
|
|
3989
|
+
unregisterBuiltin,
|
|
3990
|
+
registerBuiltinModule,
|
|
3991
|
+
loadBuiltinModule,
|
|
3992
|
+
listBuiltinIris,
|
|
3837
3993
|
// shared helpers used by engine/explain
|
|
3838
3994
|
parseBooleanLiteralInfo,
|
|
3839
3995
|
parseNumericLiteralInfo,
|
package/lib/cli.js
CHANGED
|
@@ -55,7 +55,7 @@ function main() {
|
|
|
55
55
|
argv.push(a);
|
|
56
56
|
continue;
|
|
57
57
|
}
|
|
58
|
-
// Combined short flags (
|
|
58
|
+
// Combined short flags (the long --builtin option takes a value)
|
|
59
59
|
for (const ch of a.slice(1)) argv.push('-' + ch);
|
|
60
60
|
}
|
|
61
61
|
const prog = String(process.argv[1] || 'eyeling')
|
|
@@ -67,6 +67,7 @@ function main() {
|
|
|
67
67
|
`Usage: ${prog} [options] <file.n3>\n\n` +
|
|
68
68
|
`Options:\n` +
|
|
69
69
|
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
70
|
+
` --builtin <module.js> Load a custom builtin module (repeatable).\n` +
|
|
70
71
|
` -d, --deterministic-skolem Make log:skolem stable across reasoning runs.\n` +
|
|
71
72
|
` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
|
|
72
73
|
` -h, --help Show this help and exit.\n` +
|
|
@@ -89,6 +90,27 @@ function main() {
|
|
|
89
90
|
process.exit(0);
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
const builtinModules = [];
|
|
94
|
+
const positional = [];
|
|
95
|
+
for (let i = 0; i < argv.length; i++) {
|
|
96
|
+
const a = argv[i];
|
|
97
|
+
if (a === '--builtin') {
|
|
98
|
+
const next = argv[i + 1];
|
|
99
|
+
if (!next || next.startsWith('-')) {
|
|
100
|
+
console.error('Error: --builtin expects a module path.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
builtinModules.push(next);
|
|
104
|
+
i += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (typeof a === 'string' && a.startsWith('--builtin=')) {
|
|
108
|
+
builtinModules.push(a.slice('--builtin='.length));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (!a.startsWith('-')) positional.push(a);
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
const showAst = argv.includes('--ast') || argv.includes('-a');
|
|
93
115
|
const streamMode = argv.includes('--stream') || argv.includes('-t');
|
|
94
116
|
|
|
@@ -113,7 +135,6 @@ function main() {
|
|
|
113
135
|
}
|
|
114
136
|
|
|
115
137
|
// Positional args (the N3 file)
|
|
116
|
-
const positional = argv.filter((a) => !a.startsWith('-'));
|
|
117
138
|
if (positional.length === 0) {
|
|
118
139
|
printHelp(false);
|
|
119
140
|
process.exit(0);
|
|
@@ -124,6 +145,16 @@ function main() {
|
|
|
124
145
|
process.exit(1);
|
|
125
146
|
}
|
|
126
147
|
|
|
148
|
+
for (const spec of builtinModules) {
|
|
149
|
+
try {
|
|
150
|
+
if (typeof engine.loadBuiltinModule === 'function')
|
|
151
|
+
engine.loadBuiltinModule(spec, { resolveFrom: process.cwd() });
|
|
152
|
+
} catch (e) {
|
|
153
|
+
console.error(`Error loading builtin module ${JSON.stringify(spec)}: ${e && e.message ? e.message : String(e)}`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
127
158
|
const filePath = positional[0];
|
|
128
159
|
let text;
|
|
129
160
|
try {
|
package/lib/engine.js
CHANGED
|
@@ -40,6 +40,11 @@ const { liftBlankRuleVars } = require('./rules');
|
|
|
40
40
|
|
|
41
41
|
const {
|
|
42
42
|
makeBuiltins,
|
|
43
|
+
registerBuiltin,
|
|
44
|
+
unregisterBuiltin,
|
|
45
|
+
registerBuiltinModule,
|
|
46
|
+
loadBuiltinModule,
|
|
47
|
+
listBuiltinIris,
|
|
43
48
|
// helpers used by engine core
|
|
44
49
|
parseBooleanLiteralInfo,
|
|
45
50
|
parseNumericLiteralInfo,
|
|
@@ -526,6 +531,10 @@ const { evalBuiltin, isBuiltinPred } = makeBuiltins({
|
|
|
526
531
|
termsEqualNoIntDecimal,
|
|
527
532
|
});
|
|
528
533
|
|
|
534
|
+
try {
|
|
535
|
+
registerBuiltinModule(require('./builtin-sudoku'), './builtin-sudoku');
|
|
536
|
+
} catch (_) {}
|
|
537
|
+
|
|
529
538
|
// Initialize proof/output helpers (implemented in lib/explain.js).
|
|
530
539
|
const { printExplanation, collectOutputStringsFromFacts } = makeExplain({
|
|
531
540
|
applySubstTerm,
|
|
@@ -3194,6 +3203,7 @@ function reasonStream(input, opts = {}) {
|
|
|
3194
3203
|
enforceHttps = false,
|
|
3195
3204
|
rdfjs = false,
|
|
3196
3205
|
dataFactory = null,
|
|
3206
|
+
builtinModules = null,
|
|
3197
3207
|
} = opts;
|
|
3198
3208
|
|
|
3199
3209
|
const parsedInput = normalizeParsedReasonerInputSync(input);
|
|
@@ -3203,6 +3213,12 @@ function reasonStream(input, opts = {}) {
|
|
|
3203
3213
|
deref.setEnforceHttpsEnabled(!!enforceHttps);
|
|
3204
3214
|
proofCommentsEnabled = !!proof;
|
|
3205
3215
|
|
|
3216
|
+
if (Array.isArray(builtinModules)) {
|
|
3217
|
+
for (const spec of builtinModules) loadBuiltinModule(spec);
|
|
3218
|
+
} else if (typeof builtinModules === 'string' && builtinModules) {
|
|
3219
|
+
loadBuiltinModule(builtinModules);
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3206
3222
|
let prefixes, triples, frules, brules, logQueryRules;
|
|
3207
3223
|
|
|
3208
3224
|
if (parsedInput) {
|
|
@@ -3427,4 +3443,9 @@ module.exports = {
|
|
|
3427
3443
|
setTracePrefixes,
|
|
3428
3444
|
getDeterministicSkolemEnabled,
|
|
3429
3445
|
setDeterministicSkolemEnabled,
|
|
3446
|
+
registerBuiltin,
|
|
3447
|
+
unregisterBuiltin,
|
|
3448
|
+
registerBuiltinModule,
|
|
3449
|
+
loadBuiltinModule,
|
|
3450
|
+
listBuiltinIris,
|
|
3430
3451
|
};
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -1762,6 +1762,35 @@ _:x :hates { _:foo :making :mess }.
|
|
|
1762
1762
|
},
|
|
1763
1763
|
expect: [/http:\/\/example\.org\/ancestor/m],
|
|
1764
1764
|
},
|
|
1765
|
+
{
|
|
1766
|
+
name: '240 custom builtin module can be loaded via --builtin',
|
|
1767
|
+
run() {
|
|
1768
|
+
const tmp = require('node:fs').mkdtempSync(
|
|
1769
|
+
require('node:path').join(require('node:os').tmpdir(), 'eyeling-builtin-'),
|
|
1770
|
+
);
|
|
1771
|
+
const modPath = require('node:path').join(tmp, 'hello-builtin.js');
|
|
1772
|
+
require('node:fs').writeFileSync(
|
|
1773
|
+
modPath,
|
|
1774
|
+
`module.exports = ({ registerBuiltin, internLiteral, unifyTerm, terms }) => {\n` +
|
|
1775
|
+
` const { Var } = terms;\n` +
|
|
1776
|
+
` registerBuiltin("http://example.org/custom#hello", ({ goal, subst }) => {\n` +
|
|
1777
|
+
` const lit = internLiteral("\\"world\\"");\n` +
|
|
1778
|
+
` if (goal.o instanceof Var) { const s2 = { ...subst }; s2[goal.o.name] = lit; return [s2]; }\n` +
|
|
1779
|
+
` const s2 = unifyTerm(goal.o, lit, subst);\n` +
|
|
1780
|
+
` return s2 !== null ? [s2] : [];\n` +
|
|
1781
|
+
` });\n` +
|
|
1782
|
+
`};\n`,
|
|
1783
|
+
'utf8',
|
|
1784
|
+
);
|
|
1785
|
+
const out = reasonQuiet(
|
|
1786
|
+
{ builtinModules: [modPath] },
|
|
1787
|
+
`@prefix : <http://example.org/> .\n@prefix cb: <http://example.org/custom#> .\n{ :x cb:hello ?o . } => { :x :value ?o . } .\n:x cb:hello ?o .\n`,
|
|
1788
|
+
);
|
|
1789
|
+
require('node:fs').rmSync(tmp, { recursive: true, force: true });
|
|
1790
|
+
return out;
|
|
1791
|
+
},
|
|
1792
|
+
expect: [/:x :value "world" \./m],
|
|
1793
|
+
},
|
|
1765
1794
|
];
|
|
1766
1795
|
|
|
1767
1796
|
let passed = 0;
|