eyeling 1.7.7 → 1.7.9
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/README.md +1 -0
- package/examples/log-pan-rt-soe.n3 +29 -0
- package/examples/output/log-pan-rt-soe.n3 +9 -0
- package/eyeling.js +238 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,6 +108,7 @@ Options:
|
|
|
108
108
|
-n, --no-proof-comments Disable proof explanations (default).
|
|
109
109
|
-s, --super-restricted Disable all builtins except => and <=.
|
|
110
110
|
-a, --ast Print parsed AST as JSON and exit.
|
|
111
|
+
--strings Print log:outputString strings (ordered by key) instead of N3 output.
|
|
111
112
|
```
|
|
112
113
|
|
|
113
114
|
By default, `eyeling`:
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# =======================================================
|
|
2
|
+
# log:parsedAsN3 log:rawType log:semanticsOrError example
|
|
3
|
+
# =======================================================
|
|
4
|
+
|
|
5
|
+
@prefix : <http://example.org/> .
|
|
6
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
7
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
8
|
+
|
|
9
|
+
:Let :param """
|
|
10
|
+
@prefix : <http://example.org/> .
|
|
11
|
+
:s :p :o .
|
|
12
|
+
""" .
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
:Let :param ?txt .
|
|
16
|
+
?txt log:parsedAsN3 ?f .
|
|
17
|
+
?f log:rawType ?tParsed .
|
|
18
|
+
|
|
19
|
+
<https://www.w3.org/2000/10/swap/test/s2.n3> log:semanticsOrError ?x .
|
|
20
|
+
?x log:rawType ?tRemote .
|
|
21
|
+
}
|
|
22
|
+
=>
|
|
23
|
+
{
|
|
24
|
+
:parsedFormula :is ?f .
|
|
25
|
+
:parsedType :is ?tParsed .
|
|
26
|
+
:remoteValue :is ?x .
|
|
27
|
+
:remoteType :is ?tRemote .
|
|
28
|
+
} .
|
|
29
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@prefix : <http://example.org/> .
|
|
2
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
3
|
+
|
|
4
|
+
:parsedFormula :is {
|
|
5
|
+
:s :p :o .
|
|
6
|
+
} .
|
|
7
|
+
:parsedType :is log:Formula .
|
|
8
|
+
:remoteValue :is "error(dereference_failed,https://www.w3.org/2000/10/swap/test/s2.n3)" .
|
|
9
|
+
:remoteType :is log:Literal .
|
package/eyeling.js
CHANGED
|
@@ -89,6 +89,7 @@ const jsonPointerCache = new Map();
|
|
|
89
89
|
// Key is the dereferenced document IRI *without* fragment.
|
|
90
90
|
const __logContentCache = new Map(); // iri -> string | null (null means fetch/read failed)
|
|
91
91
|
const __logSemanticsCache = new Map(); // iri -> GraphTerm | null (null means parse failed)
|
|
92
|
+
const __logSemanticsOrErrorCache = new Map(); // iri -> Term (GraphTerm | Literal) for log:semanticsOrError
|
|
92
93
|
const __logConclusionCache = new WeakMap(); // GraphTerm -> GraphTerm (deductive closure)
|
|
93
94
|
|
|
94
95
|
// Environment detection (Node vs Browser/Worker).
|
|
@@ -2971,6 +2972,23 @@ function stripQuotes(lex) {
|
|
|
2971
2972
|
return lex;
|
|
2972
2973
|
}
|
|
2973
2974
|
|
|
2975
|
+
function termToJsXsdStringNoLang(t) {
|
|
2976
|
+
// Strict xsd:string extraction *without* language tags.
|
|
2977
|
+
// Accept:
|
|
2978
|
+
// - plain string literals ("...")
|
|
2979
|
+
// - "..."^^xsd:string
|
|
2980
|
+
// Reject:
|
|
2981
|
+
// - language-tagged strings ("..."@en)
|
|
2982
|
+
// - any other datatype
|
|
2983
|
+
if (!(t instanceof Literal)) return null;
|
|
2984
|
+
if (literalHasLangTag(t.value)) return null;
|
|
2985
|
+
|
|
2986
|
+
const [lex, dt] = literalParts(t.value);
|
|
2987
|
+
if (!isQuotedLexical(lex)) return null;
|
|
2988
|
+
if (dt !== null && dt !== XSD_NS + 'string' && dt !== 'xsd:string') return null;
|
|
2989
|
+
return decodeN3StringEscapes(stripQuotes(lex));
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2974
2992
|
function termToJsString(t) {
|
|
2975
2993
|
// Strict string extraction for SWAP/N3 string builtins:
|
|
2976
2994
|
// - accept plain string literals ("...") and language-tagged ones ("..."@en)
|
|
@@ -5074,6 +5092,110 @@ if (pv === LOG_NS + 'conclusion') {
|
|
|
5074
5092
|
return s2 !== null ? [s2] : [];
|
|
5075
5093
|
}
|
|
5076
5094
|
|
|
5095
|
+
// log:semanticsOrError
|
|
5096
|
+
// Schema: $s+ log:semanticsOrError $o?
|
|
5097
|
+
// Like log:semantics, but yields an xsd:string error message on failure.
|
|
5098
|
+
if (pv === LOG_NS + 'semanticsOrError') {
|
|
5099
|
+
const iri = iriValue(g.s);
|
|
5100
|
+
if (iri === null) return [];
|
|
5101
|
+
|
|
5102
|
+
const docIri = __stripFragment(iri);
|
|
5103
|
+
const norm = __normalizeDerefIri(docIri);
|
|
5104
|
+
const key = typeof norm === 'string' && norm ? norm : docIri;
|
|
5105
|
+
|
|
5106
|
+
let term = null;
|
|
5107
|
+
|
|
5108
|
+
if (__logSemanticsOrErrorCache.has(key)) {
|
|
5109
|
+
term = __logSemanticsOrErrorCache.get(key);
|
|
5110
|
+
} else {
|
|
5111
|
+
// If we already successfully computed log:semantics, reuse it.
|
|
5112
|
+
const formula = __derefSemanticsSync(docIri);
|
|
5113
|
+
|
|
5114
|
+
if (formula instanceof GraphTerm) {
|
|
5115
|
+
term = formula;
|
|
5116
|
+
} else {
|
|
5117
|
+
// Try to get an informative error.
|
|
5118
|
+
const txt = __derefTextSync(docIri);
|
|
5119
|
+
if (typeof txt !== 'string') {
|
|
5120
|
+
term = makeStringLiteral(`error(dereference_failed,${docIri})`);
|
|
5121
|
+
} else {
|
|
5122
|
+
try {
|
|
5123
|
+
const baseIri = typeof key === 'string' && key ? key : docIri;
|
|
5124
|
+
term = __parseSemanticsToFormula(txt, baseIri);
|
|
5125
|
+
// Keep the semantics cache consistent.
|
|
5126
|
+
__logSemanticsCache.set(key, term);
|
|
5127
|
+
} catch (e) {
|
|
5128
|
+
const msg = e && e.message ? e.message : String(e);
|
|
5129
|
+
term = makeStringLiteral(`error(parse_error,${msg})`);
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
|
|
5134
|
+
__logSemanticsOrErrorCache.set(key, term);
|
|
5135
|
+
}
|
|
5136
|
+
|
|
5137
|
+
if (g.o instanceof Var) {
|
|
5138
|
+
const s2 = { ...subst };
|
|
5139
|
+
s2[g.o.name] = term;
|
|
5140
|
+
return [s2];
|
|
5141
|
+
}
|
|
5142
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
5143
|
+
|
|
5144
|
+
const s2 = unifyTerm(g.o, term, subst);
|
|
5145
|
+
return s2 !== null ? [s2] : [];
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
// log:parsedAsN3
|
|
5149
|
+
// Schema: $s+ log:parsedAsN3 $o-
|
|
5150
|
+
// Parses the subject xsd:string as N3 and returns it as a formula.
|
|
5151
|
+
if (pv === LOG_NS + 'parsedAsN3') {
|
|
5152
|
+
const txt = termToJsXsdStringNoLang(g.s);
|
|
5153
|
+
if (txt === null) return [];
|
|
5154
|
+
|
|
5155
|
+
let formula;
|
|
5156
|
+
try {
|
|
5157
|
+
// No external base is specified in the builtin definition; the parsed
|
|
5158
|
+
// string may contain its own @base / @prefix directives.
|
|
5159
|
+
formula = __parseSemanticsToFormula(txt, '');
|
|
5160
|
+
} catch {
|
|
5161
|
+
return [];
|
|
5162
|
+
}
|
|
5163
|
+
|
|
5164
|
+
if (g.o instanceof Var) {
|
|
5165
|
+
const s2 = { ...subst };
|
|
5166
|
+
s2[g.o.name] = formula;
|
|
5167
|
+
return [s2];
|
|
5168
|
+
}
|
|
5169
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
5170
|
+
|
|
5171
|
+
const s2 = unifyTerm(g.o, formula, subst);
|
|
5172
|
+
return s2 !== null ? [s2] : [];
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
// log:rawType
|
|
5176
|
+
// Schema: $s+ log:rawType $o-
|
|
5177
|
+
// Returns one of log:Formula, log:Literal, rdf:List, or log:Other.
|
|
5178
|
+
if (pv === LOG_NS + 'rawType') {
|
|
5179
|
+
if (g.s instanceof Var) return [];
|
|
5180
|
+
|
|
5181
|
+
let ty;
|
|
5182
|
+
if (g.s instanceof GraphTerm) ty = internIri(LOG_NS + 'Formula');
|
|
5183
|
+
else if (g.s instanceof Literal) ty = internIri(LOG_NS + 'Literal');
|
|
5184
|
+
else if (g.s instanceof ListTerm || g.s instanceof OpenListTerm) ty = internIri(RDF_NS + 'List');
|
|
5185
|
+
else ty = internIri(LOG_NS + 'Other');
|
|
5186
|
+
|
|
5187
|
+
if (g.o instanceof Var) {
|
|
5188
|
+
const s2 = { ...subst };
|
|
5189
|
+
s2[g.o.name] = ty;
|
|
5190
|
+
return [s2];
|
|
5191
|
+
}
|
|
5192
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
5193
|
+
|
|
5194
|
+
const s2 = unifyTerm(g.o, ty, subst);
|
|
5195
|
+
return s2 !== null ? [s2] : [];
|
|
5196
|
+
}
|
|
5197
|
+
|
|
5198
|
+
|
|
5077
5199
|
|
|
5078
5200
|
// log:dtlit
|
|
5079
5201
|
// Schema: ( $s.1? $s.2? )? log:dtlit $o?
|
|
@@ -5300,6 +5422,21 @@ if (pv === LOG_NS + 'conclusion') {
|
|
|
5300
5422
|
return sols.length ? [] : [{ ...subst }];
|
|
5301
5423
|
}
|
|
5302
5424
|
|
|
5425
|
+
// log:outputString
|
|
5426
|
+
// Schema: $s+ log:outputString $o+
|
|
5427
|
+
// Side-effecting output directive. As a builtin goal, we simply succeed
|
|
5428
|
+
// when both sides are bound and the object is a string literal.
|
|
5429
|
+
// Actual printing is handled at the end of a reasoning run (see --strings).
|
|
5430
|
+
if (pv === LOG_NS + 'outputString') {
|
|
5431
|
+
// Require subject to be bound (not a variable) and object to be a concrete string literal.
|
|
5432
|
+
if (g.s instanceof Var) return [];
|
|
5433
|
+
if (g.o instanceof Var) return [];
|
|
5434
|
+
const s = termToJsString(g.o);
|
|
5435
|
+
if (s === null) return [];
|
|
5436
|
+
return [{ ...subst }];
|
|
5437
|
+
}
|
|
5438
|
+
|
|
5439
|
+
|
|
5303
5440
|
// log:collectAllIn (scoped)
|
|
5304
5441
|
if (pv === LOG_NS + 'collectAllIn') {
|
|
5305
5442
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
|
|
@@ -6348,6 +6485,97 @@ function formatN3SyntaxError(err, text, path) {
|
|
|
6348
6485
|
// ===========================================================================
|
|
6349
6486
|
// CLI entry point
|
|
6350
6487
|
// ===========================================================================
|
|
6488
|
+
// ===========================================================================
|
|
6489
|
+
// log:outputString support
|
|
6490
|
+
// ===========================================================================
|
|
6491
|
+
|
|
6492
|
+
function __compareOutputStringKeys(a, b, prefixes) {
|
|
6493
|
+
// Deterministic ordering of keys. The spec only requires "order of the subject keys"
|
|
6494
|
+
// and leaves concrete term ordering reasoner-dependent. We implement:
|
|
6495
|
+
// 1) numeric literals (numeric value)
|
|
6496
|
+
// 2) plain literals (lexical form)
|
|
6497
|
+
// 3) IRIs
|
|
6498
|
+
// 4) blank nodes (label)
|
|
6499
|
+
// 5) fallback: skolemKeyFromTerm
|
|
6500
|
+
const aNum = parseNumericLiteralInfo(a);
|
|
6501
|
+
const bNum = parseNumericLiteralInfo(b);
|
|
6502
|
+
if (aNum && bNum) {
|
|
6503
|
+
// bigint or number
|
|
6504
|
+
if (aNum.kind === 'bigint' && bNum.kind === 'bigint') {
|
|
6505
|
+
if (aNum.value < bNum.value) return -1;
|
|
6506
|
+
if (aNum.value > bNum.value) return 1;
|
|
6507
|
+
return 0;
|
|
6508
|
+
}
|
|
6509
|
+
const av = Number(aNum.value);
|
|
6510
|
+
const bv = Number(bNum.value);
|
|
6511
|
+
if (av < bv) return -1;
|
|
6512
|
+
if (av > bv) return 1;
|
|
6513
|
+
return 0;
|
|
6514
|
+
}
|
|
6515
|
+
if (aNum && !bNum) return -1;
|
|
6516
|
+
if (!aNum && bNum) return 1;
|
|
6517
|
+
|
|
6518
|
+
// Plain literal ordering (lexical)
|
|
6519
|
+
if (a instanceof Literal && b instanceof Literal) {
|
|
6520
|
+
const [alex] = literalParts(a.value);
|
|
6521
|
+
const [blex] = literalParts(b.value);
|
|
6522
|
+
if (alex < blex) return -1;
|
|
6523
|
+
if (alex > blex) return 1;
|
|
6524
|
+
return 0;
|
|
6525
|
+
}
|
|
6526
|
+
if (a instanceof Literal && !(b instanceof Literal)) return -1;
|
|
6527
|
+
if (!(a instanceof Literal) && b instanceof Literal) return 1;
|
|
6528
|
+
|
|
6529
|
+
// IRIs
|
|
6530
|
+
if (a instanceof Iri && b instanceof Iri) {
|
|
6531
|
+
if (a.value < b.value) return -1;
|
|
6532
|
+
if (a.value > b.value) return 1;
|
|
6533
|
+
return 0;
|
|
6534
|
+
}
|
|
6535
|
+
if (a instanceof Iri && !(b instanceof Iri)) return -1;
|
|
6536
|
+
if (!(a instanceof Iri) && b instanceof Iri) return 1;
|
|
6537
|
+
|
|
6538
|
+
// Blank nodes
|
|
6539
|
+
if (a instanceof Blank && b instanceof Blank) {
|
|
6540
|
+
if (a.label < b.label) return -1;
|
|
6541
|
+
if (a.label > b.label) return 1;
|
|
6542
|
+
return 0;
|
|
6543
|
+
}
|
|
6544
|
+
if (a instanceof Blank && !(b instanceof Blank)) return -1;
|
|
6545
|
+
if (!(a instanceof Blank) && b instanceof Blank) return 1;
|
|
6546
|
+
|
|
6547
|
+
// Fallback
|
|
6548
|
+
const ak = skolemKeyFromTerm(a);
|
|
6549
|
+
const bk = skolemKeyFromTerm(b);
|
|
6550
|
+
if (ak < bk) return -1;
|
|
6551
|
+
if (ak > bk) return 1;
|
|
6552
|
+
return 0;
|
|
6553
|
+
}
|
|
6554
|
+
|
|
6555
|
+
function __collectOutputStringsFromFacts(facts, prefixes) {
|
|
6556
|
+
// Gather all (key, string) pairs from the saturated fact store.
|
|
6557
|
+
const pairs = [];
|
|
6558
|
+
for (const tr of facts) {
|
|
6559
|
+
if (!(tr && tr.p instanceof Iri)) continue;
|
|
6560
|
+
if (tr.p.value !== LOG_NS + 'outputString') continue;
|
|
6561
|
+
if (!(tr.o instanceof Literal)) continue;
|
|
6562
|
+
|
|
6563
|
+
const s = termToJsString(tr.o);
|
|
6564
|
+
if (s === null) continue;
|
|
6565
|
+
|
|
6566
|
+
pairs.push({ key: tr.s, text: s, idx: pairs.length });
|
|
6567
|
+
}
|
|
6568
|
+
|
|
6569
|
+
pairs.sort((a, b) => {
|
|
6570
|
+
const c = __compareOutputStringKeys(a.key, b.key, prefixes);
|
|
6571
|
+
if (c !== 0) return c;
|
|
6572
|
+
return a.idx - b.idx; // stable tie-breaker
|
|
6573
|
+
});
|
|
6574
|
+
|
|
6575
|
+
return pairs.map((p) => p.text).join('');
|
|
6576
|
+
}
|
|
6577
|
+
|
|
6578
|
+
|
|
6351
6579
|
function main() {
|
|
6352
6580
|
// Drop "node" and script name; keep only user-provided args
|
|
6353
6581
|
const argv = process.argv.slice(2);
|
|
@@ -6364,7 +6592,8 @@ function main() {
|
|
|
6364
6592
|
` -p, --proof-comments Enable proof explanations.\n` +
|
|
6365
6593
|
` -n, --no-proof-comments Disable proof explanations (default).\n` +
|
|
6366
6594
|
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
6367
|
-
` -a, --ast Print parsed AST as JSON and exit.\n
|
|
6595
|
+
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
6596
|
+
` --strings Print log:outputString strings (ordered by key) instead of N3 output.\n`;
|
|
6368
6597
|
(toStderr ? console.error : console.log)(msg);
|
|
6369
6598
|
}
|
|
6370
6599
|
|
|
@@ -6385,6 +6614,8 @@ function main() {
|
|
|
6385
6614
|
|
|
6386
6615
|
const showAst = argv.includes('--ast') || argv.includes('-a');
|
|
6387
6616
|
|
|
6617
|
+
const outputStringsMode = argv.includes('--strings');
|
|
6618
|
+
|
|
6388
6619
|
// --proof-comments / -p: enable proof explanations
|
|
6389
6620
|
if (argv.includes('--proof-comments') || argv.includes('-p')) {
|
|
6390
6621
|
proofCommentsEnabled = true;
|
|
@@ -6460,6 +6691,12 @@ function main() {
|
|
|
6460
6691
|
|
|
6461
6692
|
const facts = triples.filter((tr) => isGroundTriple(tr));
|
|
6462
6693
|
const derived = forwardChain(facts, frules, brules);
|
|
6694
|
+
// If requested, print log:outputString values (ordered by subject key) and exit.
|
|
6695
|
+
if (outputStringsMode) {
|
|
6696
|
+
const out = __collectOutputStringsFromFacts(facts, prefixes);
|
|
6697
|
+
if (out) process.stdout.write(out);
|
|
6698
|
+
process.exit(0);
|
|
6699
|
+
}
|
|
6463
6700
|
const derivedTriples = derived.map((df) => df.fact);
|
|
6464
6701
|
const usedPrefixes = prefixes.prefixesUsedForOutput(derivedTriples);
|
|
6465
6702
|
|