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 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.7.7",
3
+ "version": "1.7.9",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [