eyeling 1.33.2 → 1.33.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/examples/eyelang/README.md +11 -76
- package/examples/eyelang/SPEC.md +1 -2
- package/eyelang.d.ts +0 -12
- package/index.d.ts +0 -4
- package/index.js +1 -5
- package/lib/eyelang/builtins/registry.js +1 -2
- package/lib/eyelang/builtins/search.js +3 -3
- package/lib/eyelang/cli.js +1 -13
- package/lib/eyelang/hash.js +2 -2
- package/lib/eyelang/index.js +0 -1
- package/lib/eyelang/program.js +0 -7
- package/package.json +1 -1
- package/test/eyelang/run-examples.mjs +1 -2
- package/test/eyelang/run-regression.mjs +0 -61
- package/examples/eyelang/annotation-rdf12.ttl +0 -12
- package/examples/eyelang/directional-language.ttl +0 -9
- package/examples/eyelang/eyeling-ackermann.n3 +0 -41
- package/examples/eyelang/eyeling-age-threshold.n3 +0 -12
- package/examples/eyelang/eyeling-alignment-demo.n3 +0 -11
- package/examples/eyelang/eyeling-allen-interval-calculus-small.n3 +0 -13
- package/examples/eyelang/eyeling-backward-recursion.n3 +0 -11
- package/examples/eyelang/eyeling-backward.n3 +0 -10
- package/examples/eyelang/eyeling-basic-monadic-small.n3 +0 -11
- package/examples/eyelang/eyeling-bmi.n3 +0 -10
- package/examples/eyelang/eyeling-cat-koko.n3 +0 -15
- package/examples/eyelang/eyeling-collatz-small.n3 +0 -11
- package/examples/eyelang/eyeling-collection.n3 +0 -11
- package/examples/eyelang/eyeling-complex-arithmetic.n3 +0 -10
- package/examples/eyelang/eyeling-context-association.n3 +0 -11
- package/examples/eyelang/eyeling-control-system-small.n3 +0 -11
- package/examples/eyelang/eyeling-crypto-builtins-extra.n3 +0 -10
- package/examples/eyelang/eyeling-crypto-builtins.n3 +0 -8
- package/examples/eyelang/eyeling-deep-taxonomy-10.n3 +0 -18
- package/examples/eyelang/eyeling-derived-backward-rule-flat.n3 +0 -10
- package/examples/eyelang/eyeling-derived-rule-flat.n3 +0 -9
- package/examples/eyelang/eyeling-digital-product-passport-small.n3 +0 -11
- package/examples/eyelang/eyeling-dijkstra-tiny.n3 +0 -14
- package/examples/eyelang/eyeling-dog-license.n3 +0 -13
- package/examples/eyelang/eyeling-drone-corridor-planner-small.n3 +0 -13
- package/examples/eyelang/eyeling-equals.n3 +0 -8
- package/examples/eyelang/eyeling-equivalence-classes.n3 +0 -11
- package/examples/eyelang/eyeling-euler-identity.n3 +0 -9
- package/examples/eyelang/eyeling-existential-rule.n3 +0 -9
- package/examples/eyelang/eyeling-expression-eval.n3 +0 -11
- package/examples/eyelang/eyeling-family-cousins-extended.n3 +0 -12
- package/examples/eyelang/eyeling-fastpow.n3 +0 -10
- package/examples/eyelang/eyeling-fibonacci.n3 +0 -44
- package/examples/eyelang/eyeling-french-cities-reachability.n3 +0 -22
- package/examples/eyelang/eyeling-goldbach-small.n3 +0 -22
- package/examples/eyelang/eyeling-good-cobbler.n3 +0 -9
- package/examples/eyelang/eyeling-list-builtins.n3 +0 -10
- package/examples/eyelang/eyeling-list-collection-extra.n3 +0 -9
- package/examples/eyelang/eyeling-math-builtins.n3 +0 -9
- package/examples/eyelang/eyeling-string-builtins-extra.n3 +0 -9
- package/examples/eyelang/eyeling-string-builtins.n3 +0 -10
- package/examples/eyelang/eyeling-time-builtins.n3 +0 -11
- package/examples/eyelang/eyeling-time-components-extra.n3 +0 -10
- package/examples/eyelang/eyeling-witch.n3 +0 -10
- package/examples/eyelang/family-cousins.n3 +0 -17
- package/examples/eyelang/n3-builtins.n3 +0 -28
- package/examples/eyelang/output/annotation-rdf12.ttl +0 -1
- package/examples/eyelang/output/directional-language.ttl +0 -1
- package/examples/eyelang/output/eyeling-ackermann.n3 +0 -12
- package/examples/eyelang/output/eyeling-age-threshold.n3 +0 -4
- package/examples/eyelang/output/eyeling-alignment-demo.n3 +0 -1
- package/examples/eyelang/output/eyeling-allen-interval-calculus-small.n3 +0 -3
- package/examples/eyelang/output/eyeling-backward-recursion.n3 +0 -9
- package/examples/eyelang/output/eyeling-backward.n3 +0 -1
- package/examples/eyelang/output/eyeling-basic-monadic-small.n3 +0 -8
- package/examples/eyelang/output/eyeling-bmi.n3 +0 -2
- package/examples/eyelang/output/eyeling-cat-koko.n3 +0 -3
- package/examples/eyelang/output/eyeling-collatz-small.n3 +0 -3
- package/examples/eyelang/output/eyeling-collection.n3 +0 -1
- package/examples/eyelang/output/eyeling-complex-arithmetic.n3 +0 -5
- package/examples/eyelang/output/eyeling-context-association.n3 +0 -4
- package/examples/eyelang/output/eyeling-control-system-small.n3 +0 -4
- package/examples/eyelang/output/eyeling-crypto-builtins-extra.n3 +0 -3
- package/examples/eyelang/output/eyeling-crypto-builtins.n3 +0 -2
- package/examples/eyelang/output/eyeling-deep-taxonomy-10.n3 +0 -32
- package/examples/eyelang/output/eyeling-derived-backward-rule-flat.n3 +0 -4
- package/examples/eyelang/output/eyeling-derived-rule-flat.n3 +0 -2
- package/examples/eyelang/output/eyeling-digital-product-passport-small.n3 +0 -3
- package/examples/eyelang/output/eyeling-dijkstra-tiny.n3 +0 -9
- package/examples/eyelang/output/eyeling-dog-license.n3 +0 -1
- package/examples/eyelang/output/eyeling-drone-corridor-planner-small.n3 +0 -5
- package/examples/eyelang/output/eyeling-equals.n3 +0 -1
- package/examples/eyelang/output/eyeling-equivalence-classes.n3 +0 -2
- package/examples/eyelang/output/eyeling-euler-identity.n3 +0 -3
- package/examples/eyelang/output/eyeling-existential-rule.n3 +0 -4
- package/examples/eyelang/output/eyeling-expression-eval.n3 +0 -3
- package/examples/eyelang/output/eyeling-family-cousins-extended.n3 +0 -6
- package/examples/eyelang/output/eyeling-fastpow.n3 +0 -4
- package/examples/eyelang/output/eyeling-fibonacci.n3 +0 -6
- package/examples/eyelang/output/eyeling-french-cities-reachability.n3 +0 -25
- package/examples/eyelang/output/eyeling-goldbach-small.n3 +0 -2
- package/examples/eyelang/output/eyeling-good-cobbler.n3 +0 -2
- package/examples/eyelang/output/eyeling-list-builtins.n3 +0 -6
- package/examples/eyelang/output/eyeling-list-collection-extra.n3 +0 -5
- package/examples/eyelang/output/eyeling-math-builtins.n3 +0 -5
- package/examples/eyelang/output/eyeling-string-builtins-extra.n3 +0 -3
- package/examples/eyelang/output/eyeling-string-builtins.n3 +0 -4
- package/examples/eyelang/output/eyeling-time-builtins.n3 +0 -4
- package/examples/eyelang/output/eyeling-time-components-extra.n3 +0 -5
- package/examples/eyelang/output/eyeling-witch.n3 +0 -2
- package/examples/eyelang/output/family-cousins.n3 +0 -8
- package/examples/eyelang/output/n3-builtins.n3 +0 -6
- package/examples/eyelang/output/socrates.n3 +0 -1
- package/examples/eyelang/output/triple-term.n3 +0 -2
- package/examples/eyelang/socrates.n3 +0 -11
- package/examples/eyelang/triple-term.n3 +0 -9
- package/lib/eyelang/builtins/n3.js +0 -483
- package/lib/eyelang/rdf.js +0 -747
package/lib/eyelang/rdf.js
DELETED
|
@@ -1,747 +0,0 @@
|
|
|
1
|
-
// RDF 1.2 / Notation3 compatibility layer.
|
|
2
|
-
//
|
|
3
|
-
// The core eyelang parser intentionally stays small and Prolog-like. This file
|
|
4
|
-
// accepts a practical Turtle/N-Triples/N3 subset and lowers it to ordinary
|
|
5
|
-
// eyelang clauses over rdf/3:
|
|
6
|
-
//
|
|
7
|
-
// rdf(Subject, Predicate, Object).
|
|
8
|
-
//
|
|
9
|
-
// RDF terms are represented explicitly so they cannot collide with ordinary
|
|
10
|
-
// eyelang atoms:
|
|
11
|
-
//
|
|
12
|
-
// iri("https://example.org/s")
|
|
13
|
-
// bnode("b0")
|
|
14
|
-
// literal("Alice", iri("http://www.w3.org/2001/XMLSchema#string"), "", "")
|
|
15
|
-
// triple(Subject, Predicate, Object) % RDF 1.2 triple term
|
|
16
|
-
//
|
|
17
|
-
// N3 rules of the form `{ ... } => { ... } .` become Horn clauses whose heads
|
|
18
|
-
// and bodies are rdf/3 goals. RDF 1.2 reified triples and annotation syntax are
|
|
19
|
-
// expanded to rdf:reifies triples.
|
|
20
|
-
import { atom, compound, listFromItems, numberTerm, stringTerm, termToString, variable } from './term.js';
|
|
21
|
-
|
|
22
|
-
const TOKEN = {
|
|
23
|
-
EOF: 'eof', IRI: 'iri', STRING: 'string', NUMBER: 'number', NAME: 'name', BNODE: 'bnode', VAR: 'var', LANG: 'lang', ATWORD: 'atword',
|
|
24
|
-
DOT: '.', COMMA: ',', SEMI: ';', LBRACKET: '[', RBRACKET: ']', LPAREN: '(', RPAREN: ')', LBRACE: '{', RBRACE: '}',
|
|
25
|
-
TT_START: '<<(', TT_END: ')>>', REIF_START: '<<', REIF_END: '>>', ANNO_START: '{|', ANNO_END: '|}', HATHAT: '^^', TILDE: '~', IMPLIES: '=>', IMPLIED_BY: '<=',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
|
|
29
|
-
export const RDFS_NS = 'http://www.w3.org/2000/01/rdf-schema#';
|
|
30
|
-
export const XSD_NS = 'http://www.w3.org/2001/XMLSchema#';
|
|
31
|
-
export const OWL_NS = 'http://www.w3.org/2002/07/owl#';
|
|
32
|
-
export const CRYPTO_NS = 'http://www.w3.org/2000/10/swap/crypto#';
|
|
33
|
-
export const LIST_NS = 'http://www.w3.org/2000/10/swap/list#';
|
|
34
|
-
export const LOG_NS = 'http://www.w3.org/2000/10/swap/log#';
|
|
35
|
-
export const MATH_NS = 'http://www.w3.org/2000/10/swap/math#';
|
|
36
|
-
export const STRING_NS = 'http://www.w3.org/2000/10/swap/string#';
|
|
37
|
-
export const TIME_NS = 'http://www.w3.org/2000/10/swap/time#';
|
|
38
|
-
|
|
39
|
-
export const RDF_TYPE = `${RDF_NS}type`;
|
|
40
|
-
export const RDF_REIFIES = `${RDF_NS}reifies`;
|
|
41
|
-
export const RDF_FIRST = `${RDF_NS}first`;
|
|
42
|
-
export const RDF_REST = `${RDF_NS}rest`;
|
|
43
|
-
export const RDF_NIL = `${RDF_NS}nil`;
|
|
44
|
-
export const RDF_LANG_STRING = `${RDF_NS}langString`;
|
|
45
|
-
export const RDF_DIR_LANG_STRING = `${RDF_NS}dirLangString`;
|
|
46
|
-
export const XSD_STRING = `${XSD_NS}string`;
|
|
47
|
-
export const XSD_INTEGER = `${XSD_NS}integer`;
|
|
48
|
-
export const XSD_DECIMAL = `${XSD_NS}decimal`;
|
|
49
|
-
export const XSD_DOUBLE = `${XSD_NS}double`;
|
|
50
|
-
export const XSD_BOOLEAN = `${XSD_NS}boolean`;
|
|
51
|
-
|
|
52
|
-
const DEFAULT_PREFIXES = new Map([
|
|
53
|
-
['rdf', RDF_NS],
|
|
54
|
-
['rdfs', RDFS_NS],
|
|
55
|
-
['xsd', XSD_NS],
|
|
56
|
-
['owl', OWL_NS],
|
|
57
|
-
['crypto', CRYPTO_NS],
|
|
58
|
-
['list', LIST_NS],
|
|
59
|
-
['log', LOG_NS],
|
|
60
|
-
['math', MATH_NS],
|
|
61
|
-
['string', STRING_NS],
|
|
62
|
-
['time', TIME_NS],
|
|
63
|
-
]);
|
|
64
|
-
|
|
65
|
-
export function rdfIri(iri) {
|
|
66
|
-
return compound('iri', [stringTerm(String(iri ?? ''))]);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function rdfBlank(label) {
|
|
70
|
-
return compound('bnode', [stringTerm(String(label ?? ''))]);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function rdfLiteral(value, datatype = XSD_STRING, lang = '', direction = '') {
|
|
74
|
-
return compound('literal', [stringTerm(String(value ?? '')), rdfIri(datatype), stringTerm(lang ?? ''), stringTerm(direction ?? '')]);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function rdfTripleTerm(subject, predicate, object) {
|
|
78
|
-
return compound('triple', [subject, predicate, object]);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function rdfGoal(subject, predicate, object) {
|
|
82
|
-
return compound('rdf', [subject, predicate, object]);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function filenameLooksRdf(filename = '') {
|
|
86
|
-
return /\.(?:ttl|nt|n3)$/i.test(String(filename));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function parseRdfClauses(source, options = {}) {
|
|
90
|
-
return new RdfParser(source, options).parseDocument();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function rdfToEyelang(source, options = {}) {
|
|
94
|
-
return clausesToEyelang(parseRdfClauses(source, options));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function clausesToEyelang(clauses) {
|
|
98
|
-
return clauses.map((clause) => {
|
|
99
|
-
const head = termToString(clause.head, undefined, true);
|
|
100
|
-
if (!clause.body?.length) return `${head}.\n`;
|
|
101
|
-
return `${head} :- ${clause.body.map((goal) => termToString(goal, undefined, true)).join(', ')}.\n`;
|
|
102
|
-
}).join('');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
class RdfTokenizer {
|
|
106
|
-
constructor(source, filename = '<rdf>') {
|
|
107
|
-
this.source = String(source ?? '');
|
|
108
|
-
this.filename = filename;
|
|
109
|
-
this.pos = 0;
|
|
110
|
-
this.line = 1;
|
|
111
|
-
}
|
|
112
|
-
peek(offset = 0) {
|
|
113
|
-
return this.source[this.pos + offset] ?? '';
|
|
114
|
-
}
|
|
115
|
-
starts(text) {
|
|
116
|
-
return this.source.startsWith(text, this.pos);
|
|
117
|
-
}
|
|
118
|
-
take() {
|
|
119
|
-
const ch = this.peek();
|
|
120
|
-
if (ch) {
|
|
121
|
-
this.pos++;
|
|
122
|
-
if (ch === '\n') this.line++;
|
|
123
|
-
}
|
|
124
|
-
return ch;
|
|
125
|
-
}
|
|
126
|
-
token(type, text, line = this.line, value = text) {
|
|
127
|
-
return { type, text, value, line };
|
|
128
|
-
}
|
|
129
|
-
skipSpaceAndComments() {
|
|
130
|
-
while (true) {
|
|
131
|
-
while (/\s/.test(this.peek())) this.take();
|
|
132
|
-
if (this.peek() === '#') {
|
|
133
|
-
while (this.peek() && this.peek() !== '\n') this.take();
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
nextToken() {
|
|
140
|
-
this.skipSpaceAndComments();
|
|
141
|
-
const line = this.line;
|
|
142
|
-
const ch = this.peek();
|
|
143
|
-
if (!ch) return this.token(TOKEN.EOF, '', line);
|
|
144
|
-
|
|
145
|
-
if (this.starts('<<(')) { this.pos += 3; return this.token(TOKEN.TT_START, '<<(', line); }
|
|
146
|
-
if (this.starts(')>>')) { this.pos += 3; return this.token(TOKEN.TT_END, ')>>', line); }
|
|
147
|
-
if (this.starts('<<')) { this.pos += 2; return this.token(TOKEN.REIF_START, '<<', line); }
|
|
148
|
-
if (this.starts('>>')) { this.pos += 2; return this.token(TOKEN.REIF_END, '>>', line); }
|
|
149
|
-
if (this.starts('{|')) { this.pos += 2; return this.token(TOKEN.ANNO_START, '{|', line); }
|
|
150
|
-
if (this.starts('|}')) { this.pos += 2; return this.token(TOKEN.ANNO_END, '|}', line); }
|
|
151
|
-
if (this.starts('^^')) { this.pos += 2; return this.token(TOKEN.HATHAT, '^^', line); }
|
|
152
|
-
if (this.starts('=>')) { this.pos += 2; return this.token(TOKEN.IMPLIES, '=>', line); }
|
|
153
|
-
if (this.starts('<=')) { this.pos += 2; return this.token(TOKEN.IMPLIED_BY, '<=', line); }
|
|
154
|
-
|
|
155
|
-
if (ch === '<') return this.readIri(line);
|
|
156
|
-
if (ch === '"' || ch === "'") return this.readString(line);
|
|
157
|
-
if (ch === '@') return this.readAtToken(line);
|
|
158
|
-
if (ch === '?' || (ch === '$' && this.peek(1) && !/\s/.test(this.peek(1)))) return this.readVariable(line);
|
|
159
|
-
if (ch === '_' && this.peek(1) === ':') return this.readBlankNode(line);
|
|
160
|
-
|
|
161
|
-
const punct = {
|
|
162
|
-
'.': TOKEN.DOT, ',': TOKEN.COMMA, ';': TOKEN.SEMI, '[': TOKEN.LBRACKET, ']': TOKEN.RBRACKET,
|
|
163
|
-
'(': TOKEN.LPAREN, ')': TOKEN.RPAREN, '{': TOKEN.LBRACE, '}': TOKEN.RBRACE, '~': TOKEN.TILDE,
|
|
164
|
-
};
|
|
165
|
-
if (punct[ch]) {
|
|
166
|
-
this.take();
|
|
167
|
-
return this.token(punct[ch], ch, line);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (isNumberStart(this.source, this.pos)) return this.readNumber(line);
|
|
171
|
-
return this.readName(line);
|
|
172
|
-
}
|
|
173
|
-
readIri(line) {
|
|
174
|
-
this.take();
|
|
175
|
-
let value = '';
|
|
176
|
-
while (true) {
|
|
177
|
-
if (!this.peek()) this.fail(line, 'unterminated IRI reference');
|
|
178
|
-
let ch = this.take();
|
|
179
|
-
if (ch === '>') break;
|
|
180
|
-
if (ch === '\\') ch = this.readEscape(line, true);
|
|
181
|
-
value += ch;
|
|
182
|
-
}
|
|
183
|
-
return this.token(TOKEN.IRI, `<${value}>`, line, value);
|
|
184
|
-
}
|
|
185
|
-
readString(line) {
|
|
186
|
-
const quote = this.take();
|
|
187
|
-
const long = this.peek() === quote && this.peek(1) === quote;
|
|
188
|
-
if (long) { this.take(); this.take(); }
|
|
189
|
-
let value = '';
|
|
190
|
-
while (true) {
|
|
191
|
-
if (!this.peek()) this.fail(line, 'unterminated string literal');
|
|
192
|
-
if (long && this.peek() === quote && this.peek(1) === quote && this.peek(2) === quote) {
|
|
193
|
-
this.take(); this.take(); this.take();
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
let ch = this.take();
|
|
197
|
-
if (!long && ch === quote) break;
|
|
198
|
-
if (ch === '\\') ch = this.readEscape(line, false);
|
|
199
|
-
value += ch;
|
|
200
|
-
}
|
|
201
|
-
return this.token(TOKEN.STRING, value, line, value);
|
|
202
|
-
}
|
|
203
|
-
readEscape(line, iriMode) {
|
|
204
|
-
const ch = this.take();
|
|
205
|
-
if (!ch) this.fail(line, 'unterminated escape');
|
|
206
|
-
if (ch === 'u' || ch === 'U') {
|
|
207
|
-
const width = ch === 'u' ? 4 : 8;
|
|
208
|
-
let hex = '';
|
|
209
|
-
for (let i = 0; i < width; i++) {
|
|
210
|
-
const h = this.take();
|
|
211
|
-
if (!/[0-9A-Fa-f]/.test(h)) this.fail(line, `bad Unicode escape`);
|
|
212
|
-
hex += h;
|
|
213
|
-
}
|
|
214
|
-
return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
215
|
-
}
|
|
216
|
-
if (iriMode) return ch;
|
|
217
|
-
const escapes = { t: '\t', b: '\b', n: '\n', r: '\r', f: '\f', '"': '"', "'": "'", '\\': '\\' };
|
|
218
|
-
return escapes[ch] ?? ch;
|
|
219
|
-
}
|
|
220
|
-
readAtToken(line) {
|
|
221
|
-
this.take();
|
|
222
|
-
let text = '@';
|
|
223
|
-
while (this.peek() && /[A-Za-z0-9_-]/.test(this.peek())) text += this.take();
|
|
224
|
-
if (['@prefix', '@base', '@version', '@forAll', '@forSome', '@keywords'].includes(text)) return this.token(TOKEN.ATWORD, text, line, text.slice(1));
|
|
225
|
-
return this.token(TOKEN.LANG, text, line, text.slice(1));
|
|
226
|
-
}
|
|
227
|
-
readVariable(line) {
|
|
228
|
-
const sigil = this.take();
|
|
229
|
-
let name = '';
|
|
230
|
-
while (this.peek() && /[A-Za-z0-9_\-]/.test(this.peek())) name += this.take();
|
|
231
|
-
if (!name) this.fail(line, 'empty variable name');
|
|
232
|
-
return this.token(TOKEN.VAR, `${sigil}${name}`, line, name);
|
|
233
|
-
}
|
|
234
|
-
readBlankNode(line) {
|
|
235
|
-
this.pos += 2;
|
|
236
|
-
let name = '';
|
|
237
|
-
while (this.peek() && /[^\s\[\]\(\)\{\};,<>"'`]/.test(this.peek())) {
|
|
238
|
-
const ch = this.peek();
|
|
239
|
-
if (ch === '.') break;
|
|
240
|
-
name += this.take();
|
|
241
|
-
}
|
|
242
|
-
if (!name) this.fail(line, 'empty blank node label');
|
|
243
|
-
return this.token(TOKEN.BNODE, `_:${name}`, line, name);
|
|
244
|
-
}
|
|
245
|
-
readNumber(line) {
|
|
246
|
-
const start = this.pos;
|
|
247
|
-
if (this.peek() === '+' || this.peek() === '-') this.take();
|
|
248
|
-
while (/[0-9]/.test(this.peek())) this.take();
|
|
249
|
-
if (this.peek() === '.' && /[0-9]/.test(this.source[this.pos + 1] ?? '')) {
|
|
250
|
-
this.take();
|
|
251
|
-
while (/[0-9]/.test(this.peek())) this.take();
|
|
252
|
-
}
|
|
253
|
-
if (this.peek() === 'e' || this.peek() === 'E') {
|
|
254
|
-
const save = this.pos;
|
|
255
|
-
this.take();
|
|
256
|
-
if (this.peek() === '+' || this.peek() === '-') this.take();
|
|
257
|
-
if (!/[0-9]/.test(this.peek())) this.pos = save;
|
|
258
|
-
else while (/[0-9]/.test(this.peek())) this.take();
|
|
259
|
-
}
|
|
260
|
-
const text = this.source.slice(start, this.pos);
|
|
261
|
-
return this.token(TOKEN.NUMBER, text, line, text);
|
|
262
|
-
}
|
|
263
|
-
readName(line) {
|
|
264
|
-
const start = this.pos;
|
|
265
|
-
while (this.peek() && !isNameStop(this.peek())) this.take();
|
|
266
|
-
if (this.pos === start) this.fail(line, `bad character ${JSON.stringify(this.peek())}`);
|
|
267
|
-
const text = this.source.slice(start, this.pos);
|
|
268
|
-
return this.token(TOKEN.NAME, text, line, text);
|
|
269
|
-
}
|
|
270
|
-
fail(line, message) {
|
|
271
|
-
throw new Error(`${this.filename}:${line}: ${message}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function isNameStop(ch) {
|
|
276
|
-
return /\s/.test(ch) || '.;,[](){}<>^"\'`|'.includes(ch) || ch === '#';
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function isNumberStart(source, pos) {
|
|
280
|
-
const ch = source[pos] ?? '';
|
|
281
|
-
const next = source[pos + 1] ?? '';
|
|
282
|
-
if (/[0-9]/.test(ch)) return true;
|
|
283
|
-
if ((ch === '+' || ch === '-') && /[0-9.]/.test(next)) return true;
|
|
284
|
-
if (ch === '.' && /[0-9]/.test(next)) return true;
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
class RdfParser {
|
|
289
|
-
constructor(source, options = {}) {
|
|
290
|
-
this.filename = options.filename ?? '<rdf>';
|
|
291
|
-
this.tokens = new RdfTokenizer(source, this.filename);
|
|
292
|
-
this.token = this.tokens.nextToken();
|
|
293
|
-
this.prefixes = new Map(DEFAULT_PREFIXES);
|
|
294
|
-
for (const [prefix, iri] of Object.entries(options.prefixes ?? {})) this.prefixes.set(prefix, String(iri));
|
|
295
|
-
this.base = options.baseIRI ?? options.base ?? '';
|
|
296
|
-
this.blankCounter = 0;
|
|
297
|
-
this.variableNames = new Map();
|
|
298
|
-
this.collectionTerms = new Map();
|
|
299
|
-
this.clauses = [];
|
|
300
|
-
this.sourceMetadata = options.sourceMetadata !== false;
|
|
301
|
-
this.addMaterialize = options.materializeRdf !== false;
|
|
302
|
-
}
|
|
303
|
-
parseDocument() {
|
|
304
|
-
if (this.addMaterialize) this.pushClause(compound('materialize', [atom('rdf'), numberTerm('3')]), [], this.token.line);
|
|
305
|
-
while (this.token.type !== TOKEN.EOF) {
|
|
306
|
-
if (this.consumeOptionalDot()) continue;
|
|
307
|
-
if (this.parseDirective()) continue;
|
|
308
|
-
if (this.token.type === TOKEN.LBRACE) this.parseN3Rule();
|
|
309
|
-
else {
|
|
310
|
-
this.parseTriples(null);
|
|
311
|
-
this.expect(TOKEN.DOT, '.');
|
|
312
|
-
this.advance();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return this.clauses;
|
|
316
|
-
}
|
|
317
|
-
parseDirective() {
|
|
318
|
-
if (this.token.type === TOKEN.ATWORD) {
|
|
319
|
-
const name = this.token.value;
|
|
320
|
-
const oldStyle = true;
|
|
321
|
-
this.advance();
|
|
322
|
-
if (name === 'prefix') this.parsePrefixDirective(oldStyle);
|
|
323
|
-
else if (name === 'base') this.parseBaseDirective(oldStyle);
|
|
324
|
-
else if (name === 'version') this.parseVersionDirective(oldStyle);
|
|
325
|
-
else if (name === 'forAll' || name === 'forSome' || name === 'keywords') this.skipDirectiveLikeStatement();
|
|
326
|
-
else this.fail(`unsupported @ directive @${name}`);
|
|
327
|
-
return true;
|
|
328
|
-
}
|
|
329
|
-
if (this.token.type !== TOKEN.NAME) return false;
|
|
330
|
-
const upper = this.token.text.toUpperCase();
|
|
331
|
-
if (upper === 'PREFIX' || upper === 'BASE' || upper === 'VERSION') {
|
|
332
|
-
this.advance();
|
|
333
|
-
if (upper === 'PREFIX') this.parsePrefixDirective(false);
|
|
334
|
-
else if (upper === 'BASE') this.parseBaseDirective(false);
|
|
335
|
-
else this.parseVersionDirective(false);
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
parsePrefixDirective(oldStyle) {
|
|
341
|
-
this.expect(TOKEN.NAME, 'prefix name');
|
|
342
|
-
const prefixToken = this.token.text;
|
|
343
|
-
if (!prefixToken.endsWith(':')) this.fail('prefix name must end with :');
|
|
344
|
-
const prefix = prefixToken.slice(0, -1);
|
|
345
|
-
this.advance();
|
|
346
|
-
this.expect(TOKEN.IRI, 'prefix IRI');
|
|
347
|
-
this.prefixes.set(prefix, this.resolveIri(this.token.value));
|
|
348
|
-
this.advance();
|
|
349
|
-
if (oldStyle) { this.expect(TOKEN.DOT, '.'); this.advance(); }
|
|
350
|
-
}
|
|
351
|
-
parseBaseDirective(oldStyle) {
|
|
352
|
-
this.expect(TOKEN.IRI, 'base IRI');
|
|
353
|
-
this.base = this.resolveIri(this.token.value);
|
|
354
|
-
this.advance();
|
|
355
|
-
if (oldStyle) { this.expect(TOKEN.DOT, '.'); this.advance(); }
|
|
356
|
-
}
|
|
357
|
-
parseVersionDirective(oldStyle) {
|
|
358
|
-
this.expect(TOKEN.STRING, 'version string');
|
|
359
|
-
this.advance();
|
|
360
|
-
if (oldStyle) { this.expect(TOKEN.DOT, '.'); this.advance(); }
|
|
361
|
-
}
|
|
362
|
-
skipDirectiveLikeStatement() {
|
|
363
|
-
while (this.token.type !== TOKEN.EOF && this.token.type !== TOKEN.DOT) this.advance();
|
|
364
|
-
if (this.token.type === TOKEN.DOT) this.advance();
|
|
365
|
-
}
|
|
366
|
-
parseN3Rule() {
|
|
367
|
-
const line = this.token.line;
|
|
368
|
-
const first = [];
|
|
369
|
-
this.parseFormula(first, { rawTriples: true });
|
|
370
|
-
if (this.token.type === TOKEN.IMPLIES) {
|
|
371
|
-
this.advance();
|
|
372
|
-
const second = this.parseRuleFormula({ rawTriples: true });
|
|
373
|
-
this.expect(TOKEN.DOT, '.');
|
|
374
|
-
this.advance();
|
|
375
|
-
const body = this.lowerFormula(first, 'body');
|
|
376
|
-
const heads = this.lowerFormula(second, 'head');
|
|
377
|
-
for (const head of heads) this.pushClause(head, body, line);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (this.token.type === TOKEN.IMPLIED_BY) {
|
|
381
|
-
this.advance();
|
|
382
|
-
const second = this.parseRuleFormula({ rawTriples: true });
|
|
383
|
-
this.expect(TOKEN.DOT, '.');
|
|
384
|
-
this.advance();
|
|
385
|
-
const heads = this.lowerFormula(first, 'head');
|
|
386
|
-
const body = this.lowerFormula(second, 'body');
|
|
387
|
-
for (const head of heads) this.pushClause(head, body, line);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
this.fail('expected => or <= after N3 formula');
|
|
391
|
-
}
|
|
392
|
-
parseRuleFormula(options = {}) {
|
|
393
|
-
if (this.token.type === TOKEN.NAME && this.token.text === 'true') {
|
|
394
|
-
this.advance();
|
|
395
|
-
return [];
|
|
396
|
-
}
|
|
397
|
-
const triples = [];
|
|
398
|
-
this.parseFormula(triples, options);
|
|
399
|
-
return triples;
|
|
400
|
-
}
|
|
401
|
-
lowerFormula(triples, role) {
|
|
402
|
-
return triples.map(({ subject, predicate, object }) => {
|
|
403
|
-
const s = this.collectionOrSelf(subject);
|
|
404
|
-
const p = this.collectionOrSelf(predicate);
|
|
405
|
-
const o = this.collectionOrSelf(object);
|
|
406
|
-
const builtin = role === 'body' ? this.n3BuiltinGoal(s, p, o) : null;
|
|
407
|
-
return builtin ?? rdfGoal(s, p, o);
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
parseFormula(sink, options = {}) {
|
|
411
|
-
this.expect(TOKEN.LBRACE, '{');
|
|
412
|
-
this.advance();
|
|
413
|
-
while (this.token.type !== TOKEN.RBRACE && this.token.type !== TOKEN.EOF) {
|
|
414
|
-
if (this.consumeOptionalDot()) continue;
|
|
415
|
-
if (this.parseDirective()) continue;
|
|
416
|
-
this.parseTriples(sink, options);
|
|
417
|
-
if (this.token.type === TOKEN.DOT) this.advance();
|
|
418
|
-
else if (this.token.type !== TOKEN.RBRACE) this.expect(TOKEN.DOT, '.');
|
|
419
|
-
}
|
|
420
|
-
this.expect(TOKEN.RBRACE, '}');
|
|
421
|
-
this.advance();
|
|
422
|
-
}
|
|
423
|
-
parseTriples(sink, options = {}) {
|
|
424
|
-
const subject = this.parseSubject(sink);
|
|
425
|
-
this.parsePredicateObjectList(subject, sink, options);
|
|
426
|
-
}
|
|
427
|
-
parsePredicateObjectList(subject, sink, options = {}) {
|
|
428
|
-
while (true) {
|
|
429
|
-
if (this.token.type === TOKEN.RBRACKET || this.token.type === TOKEN.ANNO_END || this.token.type === TOKEN.RBRACE || this.token.type === TOKEN.DOT) break;
|
|
430
|
-
const predicate = this.parseVerb();
|
|
431
|
-
this.parseObjectList(subject, predicate, sink, options);
|
|
432
|
-
if (this.token.type !== TOKEN.SEMI) break;
|
|
433
|
-
while (this.token.type === TOKEN.SEMI) this.advance();
|
|
434
|
-
if (this.token.type === TOKEN.RBRACKET || this.token.type === TOKEN.ANNO_END || this.token.type === TOKEN.RBRACE || this.token.type === TOKEN.DOT) break;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
parseObjectList(subject, predicate, sink, options = {}) {
|
|
438
|
-
while (true) {
|
|
439
|
-
const object = this.parseObject(sink);
|
|
440
|
-
const tripleTerm = rdfTripleTerm(subject, predicate, object);
|
|
441
|
-
this.emitTriple(subject, predicate, object, sink, options);
|
|
442
|
-
this.parseAnnotations(tripleTerm, sink, options);
|
|
443
|
-
if (this.token.type !== TOKEN.COMMA) break;
|
|
444
|
-
this.advance();
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
parseAnnotations(tripleTerm, sink, options = {}) {
|
|
448
|
-
let lastReifier = null;
|
|
449
|
-
while (this.token.type === TOKEN.TILDE || this.token.type === TOKEN.ANNO_START) {
|
|
450
|
-
if (this.token.type === TOKEN.TILDE) {
|
|
451
|
-
this.advance();
|
|
452
|
-
const reifier = this.canStartReifierTerm() ? this.parseReifierTerm(sink) : this.freshBlank();
|
|
453
|
-
this.emitTriple(reifier, rdfIri(RDF_REIFIES), tripleTerm, sink, options);
|
|
454
|
-
lastReifier = reifier;
|
|
455
|
-
if (this.token.type === TOKEN.ANNO_START) {
|
|
456
|
-
this.parseAnnotationBlock(lastReifier, sink, options);
|
|
457
|
-
lastReifier = null;
|
|
458
|
-
}
|
|
459
|
-
} else {
|
|
460
|
-
const reifier = lastReifier ?? this.freshBlank();
|
|
461
|
-
this.emitTriple(reifier, rdfIri(RDF_REIFIES), tripleTerm, sink, options);
|
|
462
|
-
this.parseAnnotationBlock(reifier, sink, options);
|
|
463
|
-
lastReifier = null;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
parseAnnotationBlock(reifier, sink, options = {}) {
|
|
468
|
-
this.expect(TOKEN.ANNO_START, '{|');
|
|
469
|
-
this.advance();
|
|
470
|
-
if (this.token.type !== TOKEN.ANNO_END) this.parsePredicateObjectList(reifier, sink, options);
|
|
471
|
-
this.expect(TOKEN.ANNO_END, '|}');
|
|
472
|
-
this.advance();
|
|
473
|
-
}
|
|
474
|
-
parseSubject(sink) {
|
|
475
|
-
if (this.token.type === TOKEN.REIF_START) return this.parseReifiedTriple(sink);
|
|
476
|
-
return this.parseTerm(sink, { role: 'subject' });
|
|
477
|
-
}
|
|
478
|
-
parseObject(sink) {
|
|
479
|
-
return this.parseTerm(sink, { role: 'object' });
|
|
480
|
-
}
|
|
481
|
-
parseReifierTerm(sink) {
|
|
482
|
-
if (this.token.type === TOKEN.REIF_START) return this.parseReifiedTriple(sink);
|
|
483
|
-
return this.parseTerm(sink, { role: 'reifier' });
|
|
484
|
-
}
|
|
485
|
-
parseVerb() {
|
|
486
|
-
if (this.token.type === TOKEN.NAME && this.token.text === 'a') {
|
|
487
|
-
this.advance();
|
|
488
|
-
return rdfIri(RDF_TYPE);
|
|
489
|
-
}
|
|
490
|
-
if (this.token.type === TOKEN.NAME && this.token.text === '=') {
|
|
491
|
-
this.advance();
|
|
492
|
-
return rdfIri(`${OWL_NS}sameAs`);
|
|
493
|
-
}
|
|
494
|
-
if (this.token.type === TOKEN.VAR) return this.parseVariable();
|
|
495
|
-
return this.parseIri();
|
|
496
|
-
}
|
|
497
|
-
parseTerm(sink, { role }) {
|
|
498
|
-
switch (this.token.type) {
|
|
499
|
-
case TOKEN.IRI: return this.parseIri();
|
|
500
|
-
case TOKEN.NAME: {
|
|
501
|
-
if (role === 'object' && (this.token.text === 'true' || this.token.text === 'false')) return this.parseBooleanLiteral();
|
|
502
|
-
return this.parseIri();
|
|
503
|
-
}
|
|
504
|
-
case TOKEN.BNODE: return this.parseBlankNode();
|
|
505
|
-
case TOKEN.VAR: return this.parseVariable();
|
|
506
|
-
case TOKEN.STRING: return this.parseStringLiteral();
|
|
507
|
-
case TOKEN.NUMBER: return this.parseNumericLiteral();
|
|
508
|
-
case TOKEN.LBRACKET: return this.parseBlankNodePropertyList(sink);
|
|
509
|
-
case TOKEN.LPAREN: return this.parseCollection(sink);
|
|
510
|
-
case TOKEN.TT_START: return this.parseTripleTerm(sink);
|
|
511
|
-
case TOKEN.REIF_START: return this.parseReifiedTriple(sink);
|
|
512
|
-
default: this.fail(`expected ${role} term, got ${this.token.text || this.token.type}`);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
parseIri() {
|
|
516
|
-
if (this.token.type === TOKEN.IRI) {
|
|
517
|
-
const iri = this.resolveIri(this.token.value);
|
|
518
|
-
this.advance();
|
|
519
|
-
return rdfIri(iri);
|
|
520
|
-
}
|
|
521
|
-
if (this.token.type !== TOKEN.NAME) this.expect(TOKEN.IRI, 'IRI or prefixed name');
|
|
522
|
-
const text = this.token.text;
|
|
523
|
-
if (!text.includes(':')) this.fail(`expected prefixed name or IRI, got ${text}`);
|
|
524
|
-
const iri = this.expandPrefixedName(text);
|
|
525
|
-
this.advance();
|
|
526
|
-
return rdfIri(iri);
|
|
527
|
-
}
|
|
528
|
-
parseBlankNode() {
|
|
529
|
-
const label = this.token.value;
|
|
530
|
-
this.advance();
|
|
531
|
-
return rdfBlank(label);
|
|
532
|
-
}
|
|
533
|
-
parseVariable() {
|
|
534
|
-
const sourceName = this.token.value;
|
|
535
|
-
this.advance();
|
|
536
|
-
if (!this.variableNames.has(sourceName)) this.variableNames.set(sourceName, variable(toEyelangVariableName(sourceName)));
|
|
537
|
-
return this.variableNames.get(sourceName);
|
|
538
|
-
}
|
|
539
|
-
parseStringLiteral() {
|
|
540
|
-
const value = this.token.value;
|
|
541
|
-
this.advance();
|
|
542
|
-
if (this.token.type === TOKEN.LANG) {
|
|
543
|
-
const { lang, direction } = parseLangDir(this.token.value, this.token.line, this.filename);
|
|
544
|
-
this.advance();
|
|
545
|
-
return rdfLiteral(value, direction ? RDF_DIR_LANG_STRING : RDF_LANG_STRING, lang, direction);
|
|
546
|
-
}
|
|
547
|
-
if (this.token.type === TOKEN.HATHAT) {
|
|
548
|
-
this.advance();
|
|
549
|
-
const datatype = iriStringValue(this.parseIri());
|
|
550
|
-
return rdfLiteral(value, datatype, '', '');
|
|
551
|
-
}
|
|
552
|
-
return rdfLiteral(value, XSD_STRING, '', '');
|
|
553
|
-
}
|
|
554
|
-
parseNumericLiteral() {
|
|
555
|
-
const text = this.token.text;
|
|
556
|
-
this.advance();
|
|
557
|
-
const datatype = /[eE]/.test(text) ? XSD_DOUBLE : text.includes('.') ? XSD_DECIMAL : XSD_INTEGER;
|
|
558
|
-
return rdfLiteral(text, datatype, '', '');
|
|
559
|
-
}
|
|
560
|
-
parseBooleanLiteral() {
|
|
561
|
-
const text = this.token.text;
|
|
562
|
-
this.advance();
|
|
563
|
-
return rdfLiteral(text, XSD_BOOLEAN, '', '');
|
|
564
|
-
}
|
|
565
|
-
parseBlankNodePropertyList(sink) {
|
|
566
|
-
this.expect(TOKEN.LBRACKET, '[');
|
|
567
|
-
this.advance();
|
|
568
|
-
const node = this.freshBlank();
|
|
569
|
-
if (this.token.type !== TOKEN.RBRACKET) this.parsePredicateObjectList(node, sink);
|
|
570
|
-
this.expect(TOKEN.RBRACKET, ']');
|
|
571
|
-
this.advance();
|
|
572
|
-
return node;
|
|
573
|
-
}
|
|
574
|
-
parseCollection(sink) {
|
|
575
|
-
this.expect(TOKEN.LPAREN, '(');
|
|
576
|
-
this.advance();
|
|
577
|
-
if (this.token.type === TOKEN.RPAREN) {
|
|
578
|
-
this.advance();
|
|
579
|
-
return rdfIri(RDF_NIL);
|
|
580
|
-
}
|
|
581
|
-
const items = [];
|
|
582
|
-
while (this.token.type !== TOKEN.RPAREN && this.token.type !== TOKEN.EOF) items.push(this.parseObject(sink));
|
|
583
|
-
this.expect(TOKEN.RPAREN, ')');
|
|
584
|
-
this.advance();
|
|
585
|
-
|
|
586
|
-
const nodes = items.map(() => this.freshBlank());
|
|
587
|
-
if (!sink) {
|
|
588
|
-
for (let i = 0; i < items.length; i++) {
|
|
589
|
-
this.emitTriple(nodes[i], rdfIri(RDF_FIRST), items[i], sink);
|
|
590
|
-
this.emitTriple(nodes[i], rdfIri(RDF_REST), i + 1 < nodes.length ? nodes[i + 1] : rdfIri(RDF_NIL), sink);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
const node = nodes[0] ?? rdfIri(RDF_NIL);
|
|
594
|
-
this.collectionTerms.set(collectionKey(node), listFromItems(items));
|
|
595
|
-
return node;
|
|
596
|
-
}
|
|
597
|
-
parseTripleTerm(sink) {
|
|
598
|
-
this.expect(TOKEN.TT_START, '<<(');
|
|
599
|
-
this.advance();
|
|
600
|
-
const subject = this.parseTerm(sink, { role: 'triple-term subject' });
|
|
601
|
-
const predicate = this.parseVerb();
|
|
602
|
-
const object = this.parseTerm(sink, { role: 'triple-term object' });
|
|
603
|
-
this.expect(TOKEN.TT_END, ')>>');
|
|
604
|
-
this.advance();
|
|
605
|
-
return rdfTripleTerm(subject, predicate, object);
|
|
606
|
-
}
|
|
607
|
-
parseReifiedTriple(sink) {
|
|
608
|
-
this.expect(TOKEN.REIF_START, '<<');
|
|
609
|
-
this.advance();
|
|
610
|
-
const subject = this.token.type === TOKEN.REIF_START ? this.parseReifiedTriple(sink) : this.parseTerm(sink, { role: 'reified-triple subject' });
|
|
611
|
-
const predicate = this.parseVerb();
|
|
612
|
-
const object = this.parseTerm(sink, { role: 'reified-triple object' });
|
|
613
|
-
let reifier = null;
|
|
614
|
-
if (this.token.type === TOKEN.TILDE) {
|
|
615
|
-
this.advance();
|
|
616
|
-
reifier = this.canStartReifierTerm() ? this.parseReifierTerm(sink) : this.freshBlank();
|
|
617
|
-
}
|
|
618
|
-
this.expect(TOKEN.REIF_END, '>>');
|
|
619
|
-
this.advance();
|
|
620
|
-
reifier ??= this.freshBlank();
|
|
621
|
-
this.emitTriple(reifier, rdfIri(RDF_REIFIES), rdfTripleTerm(subject, predicate, object), sink);
|
|
622
|
-
return reifier;
|
|
623
|
-
}
|
|
624
|
-
canStartReifierTerm() {
|
|
625
|
-
return this.token.type === TOKEN.IRI || this.token.type === TOKEN.NAME || this.token.type === TOKEN.BNODE || this.token.type === TOKEN.VAR || this.token.type === TOKEN.LBRACKET || this.token.type === TOKEN.REIF_START;
|
|
626
|
-
}
|
|
627
|
-
emitTriple(subject, predicate, object, sink, options = {}) {
|
|
628
|
-
if (options.rawTriples && sink) {
|
|
629
|
-
sink.push({ subject, predicate, object });
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
const s = this.collectionOrSelf(subject);
|
|
633
|
-
const p = this.collectionOrSelf(predicate);
|
|
634
|
-
const o = this.collectionOrSelf(object);
|
|
635
|
-
const builtinGoal = options.role === 'body' ? this.n3BuiltinGoal(s, p, o) : null;
|
|
636
|
-
const goal = builtinGoal ?? rdfGoal(s, p, o);
|
|
637
|
-
if (sink) sink.push(goal);
|
|
638
|
-
else this.pushClause(goal, [], this.token.line);
|
|
639
|
-
}
|
|
640
|
-
n3BuiltinGoal(subject, predicate, object) {
|
|
641
|
-
const iri = iriStringValue(predicate);
|
|
642
|
-
const name = n3BuiltinName(iri);
|
|
643
|
-
if (!name) return null;
|
|
644
|
-
return compound(name, [this.collectionOrSelf(subject), this.collectionOrSelf(object)]);
|
|
645
|
-
}
|
|
646
|
-
collectionOrSelf(term, seen = new Set()) {
|
|
647
|
-
const key = collectionKey(term);
|
|
648
|
-
if (!seen.has(key) && this.collectionTerms.has(key)) {
|
|
649
|
-
seen.add(key);
|
|
650
|
-
return this.collectionOrSelf(this.collectionTerms.get(key), seen);
|
|
651
|
-
}
|
|
652
|
-
if (term?.type === 'compound' && term.name === '.' && term.arity === 2) {
|
|
653
|
-
return compound('.', [this.collectionOrSelf(term.args[0], seen), this.collectionOrSelf(term.args[1], seen)]);
|
|
654
|
-
}
|
|
655
|
-
return term;
|
|
656
|
-
}
|
|
657
|
-
pushClause(head, body = [], line = this.token.line) {
|
|
658
|
-
const clause = { head, body: [...body] };
|
|
659
|
-
if (this.sourceMetadata) clause.source = { filename: this.filename, line, clause: this.clauses.length + 1 };
|
|
660
|
-
this.clauses.push(clause);
|
|
661
|
-
}
|
|
662
|
-
freshBlank() {
|
|
663
|
-
return rdfBlank(`b${this.blankCounter++}`);
|
|
664
|
-
}
|
|
665
|
-
expandPrefixedName(text) {
|
|
666
|
-
const colon = text.indexOf(':');
|
|
667
|
-
const prefix = text.slice(0, colon);
|
|
668
|
-
const local = text.slice(colon + 1);
|
|
669
|
-
if (!this.prefixes.has(prefix)) this.fail(`unknown prefix ${prefix}:`);
|
|
670
|
-
return `${this.prefixes.get(prefix)}${unescapeLocalName(local)}`;
|
|
671
|
-
}
|
|
672
|
-
resolveIri(value) {
|
|
673
|
-
const iri = String(value ?? '');
|
|
674
|
-
if (!this.base || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(iri)) return iri;
|
|
675
|
-
try {
|
|
676
|
-
return new URL(iri, this.base).href;
|
|
677
|
-
} catch (_) {
|
|
678
|
-
return iri;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
consumeOptionalDot() {
|
|
682
|
-
if (this.token.type !== TOKEN.DOT) return false;
|
|
683
|
-
this.advance();
|
|
684
|
-
return true;
|
|
685
|
-
}
|
|
686
|
-
advance() {
|
|
687
|
-
this.token = this.tokens.nextToken();
|
|
688
|
-
}
|
|
689
|
-
expect(type, desc = type) {
|
|
690
|
-
if (this.token.type !== type) this.fail(`expected ${desc}, got ${this.token.text || this.token.type}`);
|
|
691
|
-
}
|
|
692
|
-
fail(message) {
|
|
693
|
-
throw new Error(`${this.filename}:${this.token.line}: ${message}`);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
function collectionKey(term) {
|
|
699
|
-
if (term?.type === 'compound' && term.name === 'bnode' && term.args.length === 1) return `bnode:${term.args[0].name}`;
|
|
700
|
-
if (term?.type === 'compound' && term.name === 'iri' && term.args.length === 1 && term.args[0].name === RDF_NIL) return `iri:${RDF_NIL}`;
|
|
701
|
-
return termToString(term, undefined, true);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
const N3_BUILTIN_PREFIXES = [
|
|
705
|
-
[MATH_NS, 'n3_math_'],
|
|
706
|
-
[STRING_NS, 'n3_string_'],
|
|
707
|
-
[LIST_NS, 'n3_list_'],
|
|
708
|
-
[TIME_NS, 'n3_time_'],
|
|
709
|
-
[CRYPTO_NS, 'n3_crypto_'],
|
|
710
|
-
[LOG_NS, 'n3_log_'],
|
|
711
|
-
];
|
|
712
|
-
|
|
713
|
-
function n3BuiltinName(iri) {
|
|
714
|
-
if (!iri || typeof iri !== 'string') return null;
|
|
715
|
-
for (const [ns, prefix] of N3_BUILTIN_PREFIXES) {
|
|
716
|
-
if (iri.startsWith(ns)) {
|
|
717
|
-
const local = iri.slice(ns.length).replace(/[^A-Za-z0-9_]/g, '_');
|
|
718
|
-
return local ? `${prefix}${local}` : null;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
return null;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
function iriStringValue(term) {
|
|
725
|
-
if (term?.type === 'compound' && term.name === 'iri' && term.args.length === 1) return term.args[0].name;
|
|
726
|
-
return termToString(term, undefined, true);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function parseLangDir(value, line, filename) {
|
|
730
|
-
const parts = String(value).split('--');
|
|
731
|
-
const lang = parts[0];
|
|
732
|
-
const direction = parts[1] ?? '';
|
|
733
|
-
if (!lang || !/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) throw new Error(`${filename}:${line}: malformed language tag @${value}`);
|
|
734
|
-
if (direction && direction !== 'ltr' && direction !== 'rtl') throw new Error(`${filename}:${line}: RDF 1.2 literal direction must be ltr or rtl`);
|
|
735
|
-
return { lang: lang.toLowerCase(), direction };
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
function unescapeLocalName(local) {
|
|
739
|
-
return String(local).replace(/\\([_~.\-!$&'()*+,;=/?#@%])/g, '$1');
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
function toEyelangVariableName(name) {
|
|
743
|
-
const clean = String(name ?? '').replace(/[^A-Za-z0-9_]/g, '_') || 'V';
|
|
744
|
-
const first = clean[0];
|
|
745
|
-
if (/[A-Z_]/.test(first)) return clean;
|
|
746
|
-
return `${first.toUpperCase()}${clean.slice(1)}`;
|
|
747
|
-
}
|