eyeling 1.25.1 → 1.25.3
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/dist/browser/eyeling.browser.js +252 -99
- package/eyeling.js +252 -99
- package/lib/cli.js +3 -1
- package/lib/engine.js +99 -25
- package/lib/lexer.js +123 -44
- package/lib/parser.js +27 -38
- package/package.json +1 -1
package/lib/engine.js
CHANGED
|
@@ -362,6 +362,14 @@ function __prepareForwardRule(r) {
|
|
|
362
362
|
configurable: true,
|
|
363
363
|
});
|
|
364
364
|
}
|
|
365
|
+
if (!hasOwn.call(r, '__needsForwardSkipCheck')) {
|
|
366
|
+
Object.defineProperty(r, '__needsForwardSkipCheck', {
|
|
367
|
+
value: !!(r.__headIsStrictGround || (r.__scopedSkipInfo && r.__scopedSkipInfo.needsSnap)),
|
|
368
|
+
enumerable: false,
|
|
369
|
+
writable: false,
|
|
370
|
+
configurable: true,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
365
373
|
}
|
|
366
374
|
|
|
367
375
|
function __graphTriplesOrTrue(term) {
|
|
@@ -680,6 +688,11 @@ function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter, firi
|
|
|
680
688
|
}
|
|
681
689
|
|
|
682
690
|
function skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter, firingKey, globalMap) {
|
|
691
|
+
// Fast path: the common case has no explicit head blanks. Do not allocate a
|
|
692
|
+
// replacement Triple or compute a firing key when skolemization cannot change
|
|
693
|
+
// anything. This matters for long single-premise chains such as
|
|
694
|
+
// deep-taxonomy-100000, where every derived head triple is otherwise copied.
|
|
695
|
+
if (!headBlankLabels || headBlankLabels.size === 0) return tr;
|
|
683
696
|
return new Triple(
|
|
684
697
|
skolemizeTermForHeadBlanks(tr.s, headBlankLabels, mapping, skCounter, firingKey, globalMap),
|
|
685
698
|
skolemizeTermForHeadBlanks(tr.p, headBlankLabels, mapping, skCounter, firingKey, globalMap),
|
|
@@ -1155,6 +1168,35 @@ function ensureFactIndexes(facts) {
|
|
|
1155
1168
|
for (let i = 0; i < facts.length; i++) indexFact(facts, facts[i], i, false);
|
|
1156
1169
|
}
|
|
1157
1170
|
|
|
1171
|
+
function cloneFactIndexesForSnapshot(src, dest) {
|
|
1172
|
+
ensureFactIndexes(src);
|
|
1173
|
+
|
|
1174
|
+
function cloneArrayMap(map) {
|
|
1175
|
+
const out = new Map();
|
|
1176
|
+
for (const [k, arr] of map) out.set(k, arr.slice());
|
|
1177
|
+
return out;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function cloneNestedArrayMap(map) {
|
|
1181
|
+
const out = new Map();
|
|
1182
|
+
for (const [k, inner] of map) {
|
|
1183
|
+
const innerOut = new Map();
|
|
1184
|
+
for (const [k2, arr] of inner) innerOut.set(k2, arr.slice());
|
|
1185
|
+
out.set(k, innerOut);
|
|
1186
|
+
}
|
|
1187
|
+
return out;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
Object.defineProperty(dest, '__byPred', { value: cloneArrayMap(src.__byPred), enumerable: false, writable: true });
|
|
1191
|
+
Object.defineProperty(dest, '__byPS', { value: cloneNestedArrayMap(src.__byPS), enumerable: false, writable: true });
|
|
1192
|
+
Object.defineProperty(dest, '__byPO', { value: cloneNestedArrayMap(src.__byPO), enumerable: false, writable: true });
|
|
1193
|
+
Object.defineProperty(dest, '__wildPred', { value: src.__wildPred.slice(), enumerable: false, writable: true });
|
|
1194
|
+
Object.defineProperty(dest, '__wildPS', { value: cloneArrayMap(src.__wildPS), enumerable: false, writable: true });
|
|
1195
|
+
Object.defineProperty(dest, '__wildPO', { value: cloneArrayMap(src.__wildPO), enumerable: false, writable: true });
|
|
1196
|
+
Object.defineProperty(dest, '__keySet', { value: new Set(src.__keySet), enumerable: false, writable: true });
|
|
1197
|
+
Object.defineProperty(dest, '__keySetComplete', { value: !!src.__keySetComplete, enumerable: false, writable: true });
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1158
1200
|
function indexFact(facts, tr, idx, addKeySet = true) {
|
|
1159
1201
|
const sk = termFastKey(tr.s);
|
|
1160
1202
|
const ok = termFastKey(tr.o);
|
|
@@ -1441,13 +1483,20 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1441
1483
|
if (!isSinglePremiseAgendaRuleSafe(r, backRules)) continue;
|
|
1442
1484
|
|
|
1443
1485
|
const goal = r.premise[0];
|
|
1486
|
+
const goalSKey = termFastKey(goal.s);
|
|
1487
|
+
const goalOKey = termFastKey(goal.o);
|
|
1488
|
+
const fastSubjectVar = goal.p instanceof Iri && goal.s instanceof Var && goalOKey !== null ? goal.s.name : null;
|
|
1489
|
+
const fastObjectVar = goal.p instanceof Iri && goal.o instanceof Var && goalSKey !== null ? goal.o.name : null;
|
|
1444
1490
|
const entry = {
|
|
1445
1491
|
rule: r,
|
|
1446
1492
|
ruleIndex: i,
|
|
1447
1493
|
goal,
|
|
1448
1494
|
goalPredTid: goal.p instanceof Iri ? goal.p.__tid : null,
|
|
1449
|
-
goalSKey
|
|
1450
|
-
goalOKey
|
|
1495
|
+
goalSKey,
|
|
1496
|
+
goalOKey,
|
|
1497
|
+
needsSkipCheck: !!r.__needsForwardSkipCheck,
|
|
1498
|
+
fastSubjectVar,
|
|
1499
|
+
fastObjectVar,
|
|
1451
1500
|
};
|
|
1452
1501
|
|
|
1453
1502
|
index.indexed.add(r);
|
|
@@ -2852,6 +2901,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2852
2901
|
__attachGoalTable(backRules, goalTable);
|
|
2853
2902
|
|
|
2854
2903
|
const captureExplanations = !(opts && opts.captureExplanations === false);
|
|
2904
|
+
const collectDerived = !(opts && opts.collectDerived === false);
|
|
2905
|
+
const hasDerivedCallback = typeof onDerived === 'function';
|
|
2855
2906
|
const derivedForward = [];
|
|
2856
2907
|
const varGen = [0];
|
|
2857
2908
|
const skCounter = [0];
|
|
@@ -2908,7 +2959,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2908
2959
|
|
|
2909
2960
|
function makeScopedSnapshot() {
|
|
2910
2961
|
const snap = facts.slice();
|
|
2911
|
-
|
|
2962
|
+
cloneFactIndexesForSnapshot(facts, snap);
|
|
2912
2963
|
Object.defineProperty(snap, '__scopedSnapshot', {
|
|
2913
2964
|
value: snap,
|
|
2914
2965
|
enumerable: false,
|
|
@@ -2962,10 +3013,21 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2962
3013
|
let changedHere = false;
|
|
2963
3014
|
let rulesChanged = false;
|
|
2964
3015
|
|
|
2965
|
-
// IMPORTANT: one skolem map per *rule firing
|
|
3016
|
+
// IMPORTANT: one skolem map per *rule firing*. Instantiate premise
|
|
3017
|
+
// triples and build the firing key lazily: normal CLI runs do not capture
|
|
3018
|
+
// proof records, and most rules have no explicit head blanks, so the eager
|
|
3019
|
+
// work was pure allocation on large forward chains.
|
|
2966
3020
|
const skMap = {};
|
|
2967
|
-
|
|
2968
|
-
|
|
3021
|
+
let instantiatedPremises = null;
|
|
3022
|
+
let fireKey = null;
|
|
3023
|
+
function getInstantiatedPremises() {
|
|
3024
|
+
if (instantiatedPremises === null) instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
|
|
3025
|
+
return instantiatedPremises;
|
|
3026
|
+
}
|
|
3027
|
+
function getFireKey() {
|
|
3028
|
+
if (fireKey === null) fireKey = __firingKey(ruleIndex, getInstantiatedPremises());
|
|
3029
|
+
return fireKey;
|
|
3030
|
+
}
|
|
2969
3031
|
|
|
2970
3032
|
// Support "dynamic" rule heads where the consequent is a term that
|
|
2971
3033
|
// (after substitution) evaluates to a quoted formula.
|
|
@@ -3018,9 +3080,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3018
3080
|
if (isFwRuleTriple || isBwRuleTriple) {
|
|
3019
3081
|
if (!hasFactIndexed(facts, instantiated)) {
|
|
3020
3082
|
pushFactIndexed(facts, instantiated);
|
|
3021
|
-
const df = makeDerivedRecord(instantiated, r,
|
|
3022
|
-
derivedForward.push(df);
|
|
3023
|
-
if (
|
|
3083
|
+
const df = makeDerivedRecord(instantiated, r, getInstantiatedPremises(), s, captureExplanations);
|
|
3084
|
+
if (collectDerived) derivedForward.push(df);
|
|
3085
|
+
if (hasDerivedCallback) onDerived(df);
|
|
3024
3086
|
changedHere = true;
|
|
3025
3087
|
}
|
|
3026
3088
|
|
|
@@ -3066,22 +3128,25 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3066
3128
|
}
|
|
3067
3129
|
|
|
3068
3130
|
// Only skolemize blank nodes that occur explicitly in the rule head
|
|
3069
|
-
const inst =
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3131
|
+
const inst =
|
|
3132
|
+
headBlankLabelsHere && headBlankLabelsHere.size
|
|
3133
|
+
? skolemizeTripleForHeadBlanks(
|
|
3134
|
+
instantiated,
|
|
3135
|
+
headBlankLabelsHere,
|
|
3136
|
+
skMap,
|
|
3137
|
+
skCounter,
|
|
3138
|
+
getFireKey(),
|
|
3139
|
+
headSkolemCache,
|
|
3140
|
+
)
|
|
3141
|
+
: instantiated;
|
|
3077
3142
|
|
|
3078
3143
|
if (!isGroundTriple(inst)) continue;
|
|
3079
3144
|
if (hasFactIndexed(facts, inst)) continue;
|
|
3080
3145
|
|
|
3081
3146
|
pushFactIndexed(facts, inst);
|
|
3082
|
-
const df = makeDerivedRecord(inst, r,
|
|
3083
|
-
derivedForward.push(df);
|
|
3084
|
-
if (
|
|
3147
|
+
const df = makeDerivedRecord(inst, r, getInstantiatedPremises(), s, captureExplanations);
|
|
3148
|
+
if (collectDerived) derivedForward.push(df);
|
|
3149
|
+
if (hasDerivedCallback) onDerived(df);
|
|
3085
3150
|
|
|
3086
3151
|
changedHere = true;
|
|
3087
3152
|
}
|
|
@@ -3106,10 +3171,19 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3106
3171
|
for (let ci = 0; ci < total; ci++) {
|
|
3107
3172
|
const entry = ci < candidates.exactLen ? candidates.exact[ci] : candidates.wild[ci - candidates.exactLen];
|
|
3108
3173
|
const r = entry.rule;
|
|
3109
|
-
if (__skipForwardRuleNow(r)) continue;
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
if (
|
|
3174
|
+
if (entry.needsSkipCheck && __skipForwardRuleNow(r)) continue;
|
|
3175
|
+
|
|
3176
|
+
let s;
|
|
3177
|
+
if (entry.fastSubjectVar !== null) {
|
|
3178
|
+
s = __emptySubst();
|
|
3179
|
+
s[entry.fastSubjectVar] = fact.s;
|
|
3180
|
+
} else if (entry.fastObjectVar !== null) {
|
|
3181
|
+
s = __emptySubst();
|
|
3182
|
+
s[entry.fastObjectVar] = fact.o;
|
|
3183
|
+
} else {
|
|
3184
|
+
s = unifyTriple(entry.goal, fact, __emptySubst());
|
|
3185
|
+
if (s === null) continue;
|
|
3186
|
+
}
|
|
3113
3187
|
|
|
3114
3188
|
const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
|
|
3115
3189
|
if (outcome.rulesChanged) {
|
|
@@ -3126,7 +3200,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3126
3200
|
for (let i = 0; i < forwardRules.length; i++) {
|
|
3127
3201
|
const r = forwardRules[i];
|
|
3128
3202
|
if (agendaIndex.indexed.has(r)) continue;
|
|
3129
|
-
if (__skipForwardRuleNow(r)) continue;
|
|
3203
|
+
if (r.__needsForwardSkipCheck && __skipForwardRuleNow(r)) continue;
|
|
3130
3204
|
|
|
3131
3205
|
const headIsStrictGround = r.__headIsStrictGround;
|
|
3132
3206
|
const maxSols = r.isFuse || headIsStrictGround ? 1 : undefined;
|
package/lib/lexer.js
CHANGED
|
@@ -121,15 +121,6 @@ function isIdentChar(c) {
|
|
|
121
121
|
return c === ':' || isPnChars(c);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function canContinueAfterDot(next) {
|
|
125
|
-
// PN_LOCAL allows '.' but it cannot appear at the end.
|
|
126
|
-
// We include '.' only if it is followed by something that could continue a name.
|
|
127
|
-
if (next === null) return false;
|
|
128
|
-
if (isIdentChar(next)) return true;
|
|
129
|
-
if (next === '%' || next === '\\') return true;
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
124
|
function isForbiddenNoncharacterCodePoint(cp) {
|
|
134
125
|
return (cp & 0xffff) === 0xfffe || (cp & 0xffff) === 0xffff;
|
|
135
126
|
}
|
|
@@ -1189,13 +1180,25 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
1189
1180
|
return text;
|
|
1190
1181
|
}
|
|
1191
1182
|
|
|
1183
|
+
|
|
1184
|
+
function isNumericLikeIdentifier(word) {
|
|
1185
|
+
if (typeof word !== 'string' || word.length === 0) return false;
|
|
1186
|
+
for (let j = 0; j < word.length; j++) {
|
|
1187
|
+
const code = word.charCodeAt(j);
|
|
1188
|
+
if (!((code >= 48 && code <= 57) || code === 46 || code === 45)) return false;
|
|
1189
|
+
}
|
|
1190
|
+
return true;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1192
1193
|
function lex(inputText, opts = {}) {
|
|
1193
1194
|
const rdf = !!(opts && opts.rdf);
|
|
1194
1195
|
if (rdf) inputText = normalizeRdfCompatibility(inputText);
|
|
1195
1196
|
// Avoid copying large ASCII/BMP inputs into an Array. Array.from() is
|
|
1196
1197
|
// only needed when the text contains surrogate pairs and we want the old
|
|
1197
1198
|
// code-point iteration behavior for non-BMP characters.
|
|
1198
|
-
const
|
|
1199
|
+
const hasSurrogates = /[\uD800-\uDFFF]/.test(inputText);
|
|
1200
|
+
const inputMayContainInvalidStringChar = hasSurrogates || /[\u0000\uFFFE\uFFFF]/.test(inputText);
|
|
1201
|
+
const chars = hasSurrogates ? Array.from(inputText) : inputText;
|
|
1199
1202
|
const n = chars.length;
|
|
1200
1203
|
let i = 0;
|
|
1201
1204
|
const tokens = [];
|
|
@@ -1230,14 +1233,47 @@ function lex(inputText, opts = {}) {
|
|
|
1230
1233
|
// Hard stops: delimiters cannot appear unescaped inside PNAME tokens.
|
|
1231
1234
|
if (cc === '{' || cc === '}' || cc === '(' || cc === ')' || cc === '[' || cc === ']' || cc === ';' || cc === ',') break;
|
|
1232
1235
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1236
|
+
const code = cc.charCodeAt(0);
|
|
1237
|
+
|
|
1238
|
+
// Common ASCII QName/identifier characters. Keep this branch inline so
|
|
1239
|
+
// ordinary N3 files do not call through the full Unicode PN_CHARS predicate
|
|
1240
|
+
// for every character.
|
|
1241
|
+
if (
|
|
1242
|
+
code === 58 || // ':'
|
|
1243
|
+
code === 95 || // '_'
|
|
1244
|
+
code === 45 || // '-'
|
|
1245
|
+
(code >= 48 && code <= 57) ||
|
|
1246
|
+
(code >= 65 && code <= 90) ||
|
|
1247
|
+
(code >= 97 && code <= 122)
|
|
1248
|
+
) {
|
|
1249
|
+
if (out !== null) out.push(cc);
|
|
1237
1250
|
i++;
|
|
1238
1251
|
continue;
|
|
1239
1252
|
}
|
|
1240
1253
|
|
|
1254
|
+
// Dot is allowed inside PN_LOCAL, but not at the end.
|
|
1255
|
+
if (cc === '.') {
|
|
1256
|
+
const next = peek(1);
|
|
1257
|
+
if (next === null) break;
|
|
1258
|
+
const ncode = next.charCodeAt(0);
|
|
1259
|
+
if (
|
|
1260
|
+
next === '%' ||
|
|
1261
|
+
next === '\\' ||
|
|
1262
|
+
ncode === 58 ||
|
|
1263
|
+
ncode === 95 ||
|
|
1264
|
+
ncode === 45 ||
|
|
1265
|
+
(ncode >= 48 && ncode <= 57) ||
|
|
1266
|
+
(ncode >= 65 && ncode <= 90) ||
|
|
1267
|
+
(ncode >= 97 && ncode <= 122) ||
|
|
1268
|
+
isIdentChar(next)
|
|
1269
|
+
) {
|
|
1270
|
+
if (out !== null) out.push('.');
|
|
1271
|
+
i++;
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1241
1277
|
// Percent escape: %HH
|
|
1242
1278
|
if (cc === '%') {
|
|
1243
1279
|
const h1 = peek(1);
|
|
@@ -1356,22 +1392,47 @@ function lex(inputText, opts = {}) {
|
|
|
1356
1392
|
continue;
|
|
1357
1393
|
}
|
|
1358
1394
|
|
|
1359
|
-
// 5) Single-character punctuation
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1395
|
+
// 5) Single-character punctuation. Use a switch rather than allocating a
|
|
1396
|
+
// mapping object for every punctuation token in large inputs.
|
|
1397
|
+
switch (c) {
|
|
1398
|
+
case '{':
|
|
1399
|
+
tokens.push(new Token('LBrace', null, i));
|
|
1400
|
+
i++;
|
|
1401
|
+
continue;
|
|
1402
|
+
case '}':
|
|
1403
|
+
tokens.push(new Token('RBrace', null, i));
|
|
1404
|
+
i++;
|
|
1405
|
+
continue;
|
|
1406
|
+
case '(':
|
|
1407
|
+
tokens.push(new Token('LParen', null, i));
|
|
1408
|
+
i++;
|
|
1409
|
+
continue;
|
|
1410
|
+
case ')':
|
|
1411
|
+
tokens.push(new Token('RParen', null, i));
|
|
1412
|
+
i++;
|
|
1413
|
+
continue;
|
|
1414
|
+
case '[':
|
|
1415
|
+
tokens.push(new Token('LBracket', null, i));
|
|
1416
|
+
i++;
|
|
1417
|
+
continue;
|
|
1418
|
+
case ']':
|
|
1419
|
+
tokens.push(new Token('RBracket', null, i));
|
|
1420
|
+
i++;
|
|
1421
|
+
continue;
|
|
1422
|
+
case ';':
|
|
1423
|
+
tokens.push(new Token('Semicolon', null, i));
|
|
1424
|
+
i++;
|
|
1425
|
+
continue;
|
|
1426
|
+
case ',':
|
|
1427
|
+
tokens.push(new Token('Comma', null, i));
|
|
1428
|
+
i++;
|
|
1429
|
+
continue;
|
|
1430
|
+
case '.':
|
|
1431
|
+
tokens.push(new Token('Dot', null, i));
|
|
1432
|
+
i++;
|
|
1433
|
+
continue;
|
|
1434
|
+
default:
|
|
1435
|
+
break;
|
|
1375
1436
|
}
|
|
1376
1437
|
|
|
1377
1438
|
// String literal: short "..." or long """..."""
|
|
@@ -1430,27 +1491,37 @@ function lex(inputText, opts = {}) {
|
|
|
1430
1491
|
continue;
|
|
1431
1492
|
}
|
|
1432
1493
|
|
|
1433
|
-
// Short string literal " ... "
|
|
1494
|
+
// Short string literal " ... ". Most data files contain plain
|
|
1495
|
+
// unescaped labels; keep that path slice-based and avoid building an
|
|
1496
|
+
// intermediate character array + raw quoted string.
|
|
1434
1497
|
i++; // consume opening "
|
|
1435
|
-
const
|
|
1498
|
+
const contentStart = i;
|
|
1499
|
+
let sChars = null;
|
|
1500
|
+
let closed = false;
|
|
1436
1501
|
while (i < n) {
|
|
1437
1502
|
const cc = chars[i];
|
|
1438
1503
|
i++;
|
|
1439
1504
|
if (cc === '\\') {
|
|
1505
|
+
if (sChars === null) sChars = [sliceChars(contentStart, i - 1)];
|
|
1440
1506
|
if (i < n) {
|
|
1441
1507
|
const esc = chars[i];
|
|
1442
1508
|
i++;
|
|
1443
1509
|
sChars.push('\\');
|
|
1444
1510
|
sChars.push(esc);
|
|
1511
|
+
} else {
|
|
1512
|
+
sChars.push('\\');
|
|
1445
1513
|
}
|
|
1446
1514
|
continue;
|
|
1447
1515
|
}
|
|
1448
|
-
if (cc === '"')
|
|
1449
|
-
|
|
1516
|
+
if (cc === '"') {
|
|
1517
|
+
closed = true;
|
|
1518
|
+
break;
|
|
1519
|
+
}
|
|
1520
|
+
if (sChars !== null) sChars.push(cc);
|
|
1450
1521
|
}
|
|
1451
|
-
const
|
|
1452
|
-
const decoded = decodeN3StringEscapes(
|
|
1453
|
-
assertValidStringLiteralValue(decoded, start);
|
|
1522
|
+
const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
|
|
1523
|
+
const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
|
|
1524
|
+
if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
|
|
1454
1525
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
1455
1526
|
tokens.push(new Token('Literal', s, start));
|
|
1456
1527
|
continue;
|
|
@@ -1514,25 +1585,33 @@ function lex(inputText, opts = {}) {
|
|
|
1514
1585
|
|
|
1515
1586
|
// Short string literal ' ... '
|
|
1516
1587
|
i++; // consume opening '
|
|
1517
|
-
const
|
|
1588
|
+
const contentStart = i;
|
|
1589
|
+
let sChars = null;
|
|
1590
|
+
let closed = false;
|
|
1518
1591
|
while (i < n) {
|
|
1519
1592
|
const cc = chars[i];
|
|
1520
1593
|
i++;
|
|
1521
1594
|
if (cc === '\\') {
|
|
1595
|
+
if (sChars === null) sChars = [sliceChars(contentStart, i - 1)];
|
|
1522
1596
|
if (i < n) {
|
|
1523
1597
|
const esc = chars[i];
|
|
1524
1598
|
i++;
|
|
1525
1599
|
sChars.push('\\');
|
|
1526
1600
|
sChars.push(esc);
|
|
1601
|
+
} else {
|
|
1602
|
+
sChars.push('\\');
|
|
1527
1603
|
}
|
|
1528
1604
|
continue;
|
|
1529
1605
|
}
|
|
1530
|
-
if (cc === "'")
|
|
1531
|
-
|
|
1606
|
+
if (cc === "'") {
|
|
1607
|
+
closed = true;
|
|
1608
|
+
break;
|
|
1609
|
+
}
|
|
1610
|
+
if (sChars !== null) sChars.push(cc);
|
|
1532
1611
|
}
|
|
1533
|
-
const
|
|
1534
|
-
const decoded = decodeN3StringEscapes(
|
|
1535
|
-
assertValidStringLiteralValue(decoded, start);
|
|
1612
|
+
const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
|
|
1613
|
+
const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
|
|
1614
|
+
if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
|
|
1536
1615
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
1537
1616
|
tokens.push(new Token('Literal', s, start));
|
|
1538
1617
|
continue;
|
|
@@ -1656,7 +1735,7 @@ function lex(inputText, opts = {}) {
|
|
|
1656
1735
|
}
|
|
1657
1736
|
if (word === 'true' || word === 'false') {
|
|
1658
1737
|
tokens.push(new Token('Literal', word, start));
|
|
1659
|
-
} else if (
|
|
1738
|
+
} else if (isNumericLikeIdentifier(word)) {
|
|
1660
1739
|
tokens.push(new Token('Literal', word, start));
|
|
1661
1740
|
} else {
|
|
1662
1741
|
tokens.push(new Token('Ident', word, start));
|
package/lib/parser.js
CHANGED
|
@@ -241,7 +241,7 @@ class Parser {
|
|
|
241
241
|
} else if (tok2.typ === 'Ident') {
|
|
242
242
|
const qn = tok2.value || '';
|
|
243
243
|
if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), tok2, qn);
|
|
244
|
-
assertValidQNamePrefix(qn.
|
|
244
|
+
assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), tok2, '@prefix directive IRI');
|
|
245
245
|
iri = this.prefixes.expandQName(qn);
|
|
246
246
|
} else {
|
|
247
247
|
this.fail(`Expected IRI after @prefix, got ${tok2.toString()}`, tok2);
|
|
@@ -258,7 +258,7 @@ class Parser {
|
|
|
258
258
|
} else if (tok.typ === 'Ident') {
|
|
259
259
|
const qn = tok.value || '';
|
|
260
260
|
if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), tok, qn);
|
|
261
|
-
assertValidQNamePrefix(qn.
|
|
261
|
+
assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), tok, '@base directive IRI');
|
|
262
262
|
iri = this.prefixes.expandQName(qn);
|
|
263
263
|
} else {
|
|
264
264
|
this.fail(`Expected IRI after @base, got ${tok.toString()}`, tok);
|
|
@@ -287,7 +287,7 @@ class Parser {
|
|
|
287
287
|
} else if (tok2.typ === 'Ident') {
|
|
288
288
|
const qn = tok2.value || '';
|
|
289
289
|
if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), tok2, qn);
|
|
290
|
-
assertValidQNamePrefix(qn.
|
|
290
|
+
assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), tok2, '@prefix directive IRI');
|
|
291
291
|
iri = this.prefixes.expandQName(qn);
|
|
292
292
|
} else {
|
|
293
293
|
this.fail(`Expected IRI after PREFIX, got ${tok2.toString()}`, tok2);
|
|
@@ -308,7 +308,7 @@ class Parser {
|
|
|
308
308
|
} else if (tok.typ === 'Ident') {
|
|
309
309
|
const qn = tok.value || '';
|
|
310
310
|
if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), tok, qn);
|
|
311
|
-
assertValidQNamePrefix(qn.
|
|
311
|
+
assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), tok, 'BASE directive IRI');
|
|
312
312
|
iri = this.prefixes.expandQName(qn);
|
|
313
313
|
} else {
|
|
314
314
|
this.fail(`Expected IRI after BASE, got ${tok.toString()}`, tok);
|
|
@@ -355,14 +355,18 @@ class Parser {
|
|
|
355
355
|
const name = val || '';
|
|
356
356
|
if (name === 'a') {
|
|
357
357
|
return internIri(RDF_NS + 'type');
|
|
358
|
-
}
|
|
358
|
+
}
|
|
359
|
+
const sep = name.indexOf(':');
|
|
360
|
+
if (sep === 1 && name.charCodeAt(0) === 95) {
|
|
359
361
|
return new Blank(name);
|
|
360
|
-
} else if (name.includes(':')) {
|
|
361
|
-
assertValidQNamePrefix(name.split(':', 1)[0], this.fail.bind(this), tok);
|
|
362
|
-
return internIri(this.prefixes.expandQName(name));
|
|
363
|
-
} else {
|
|
364
|
-
failInvalidKeywordLikeIdent(this.fail.bind(this), tok, name);
|
|
365
362
|
}
|
|
363
|
+
if (sep >= 0) {
|
|
364
|
+
const prefixName = name.slice(0, sep);
|
|
365
|
+
assertValidQNamePrefix(prefixName, this.fail.bind(this), tok);
|
|
366
|
+
const base = this.prefixes.map[prefixName] || '';
|
|
367
|
+
return internIri(base ? base + name.slice(sep + 1) : name);
|
|
368
|
+
}
|
|
369
|
+
failInvalidKeywordLikeIdent(this.fail.bind(this), tok, name);
|
|
366
370
|
}
|
|
367
371
|
|
|
368
372
|
if (typ === 'Literal') {
|
|
@@ -393,7 +397,7 @@ class Parser {
|
|
|
393
397
|
} else if (dtTok.typ === 'Ident') {
|
|
394
398
|
const qn = dtTok.value || '';
|
|
395
399
|
if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), dtTok, qn);
|
|
396
|
-
assertValidQNamePrefix(qn.
|
|
400
|
+
assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), dtTok, 'datatype prefixed name');
|
|
397
401
|
dtIri = this.prefixes.expandQName(qn);
|
|
398
402
|
} else {
|
|
399
403
|
this.fail(`Expected datatype after ^^, got ${dtTok.toString()}`, dtTok);
|
|
@@ -638,15 +642,21 @@ class Parser {
|
|
|
638
642
|
|
|
639
643
|
while (true) {
|
|
640
644
|
const { verb, invert } = this.parseStatementVerb();
|
|
641
|
-
const objects = this.parseObjectList();
|
|
642
645
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
+
while (true) {
|
|
647
|
+
const o = this.parseTerm();
|
|
648
|
+
const postTriples = this.pendingTriplesAfter;
|
|
649
|
+
this.pendingTriplesAfter = [];
|
|
650
|
+
|
|
651
|
+
// If VERB or OBJECT contained paths, their helper triples must come
|
|
652
|
+
// before the triple that consumes the path result (Easter depends on this).
|
|
653
|
+
this.flushPendingTriples(out);
|
|
646
654
|
|
|
647
|
-
for (const { term: o, postTriples } of objects) {
|
|
648
655
|
out.push(new Triple(invert ? o : subject, verb, invert ? subject : o));
|
|
649
|
-
if (postTriples
|
|
656
|
+
if (postTriples.length) out.push(...postTriples);
|
|
657
|
+
|
|
658
|
+
if (this.peek().typ !== 'Comma') break;
|
|
659
|
+
this.next();
|
|
650
660
|
}
|
|
651
661
|
|
|
652
662
|
if (this.peek().typ === 'Semicolon') {
|
|
@@ -660,27 +670,6 @@ class Parser {
|
|
|
660
670
|
return out;
|
|
661
671
|
}
|
|
662
672
|
|
|
663
|
-
parseObjectList() {
|
|
664
|
-
// Capture any trailing property-list triples produced while parsing each
|
|
665
|
-
// object term so we can emit them *after* the triple that references the
|
|
666
|
-
// term. (See pendingTriplesAfter in the constructor.)
|
|
667
|
-
|
|
668
|
-
const objs = [];
|
|
669
|
-
const readObj = () => {
|
|
670
|
-
const o = this.parseTerm();
|
|
671
|
-
const post = this.pendingTriplesAfter;
|
|
672
|
-
this.pendingTriplesAfter = [];
|
|
673
|
-
objs.push({ term: o, postTriples: post });
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
readObj();
|
|
677
|
-
while (this.peek().typ === 'Comma') {
|
|
678
|
-
this.next();
|
|
679
|
-
readObj();
|
|
680
|
-
}
|
|
681
|
-
return objs;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
673
|
makeRule(left, right, isForward) {
|
|
685
674
|
let premiseTerm, conclTerm;
|
|
686
675
|
|