eyeprolog 1.5.19 → 1.5.21
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 -1
- package/package.json +1 -1
- package/src/io.js +32 -2
- package/src/iso.js +29 -13
- package/src/lib/sockets.pl +48 -0
- package/src/library-autoload-index.js +6 -0
- package/src/sockets-host.js +298 -0
- package/src/sockets-worker.js +235 -0
- package/src/standard-library.js +6 -1
- package/test/run-regression.mjs +57 -11
- package/test/scryer-library-exports.json +7 -0
- package/the-art-of-eyeprolog.md +485 -391
- package/tools/generate-predicate-reference.mjs +1 -1
- package/tools/predicate-reference.json +46 -1
- package/why-eyeprolog.md +2 -3
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
4
|
+
|
|
5
|
+
const HEADER_WORDS = 4;
|
|
6
|
+
const header = new Int32Array(workerData.shared, 0, HEADER_WORDS);
|
|
7
|
+
const bytes = new Uint8Array(workerData.shared, HEADER_WORDS * Int32Array.BYTES_PER_ELEMENT);
|
|
8
|
+
const decoder = new TextDecoder();
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
|
|
11
|
+
let nextServerId = 1;
|
|
12
|
+
let nextConnectionId = 1;
|
|
13
|
+
const servers = new Map();
|
|
14
|
+
const connections = new Map();
|
|
15
|
+
|
|
16
|
+
function errorRecord(error) {
|
|
17
|
+
return {
|
|
18
|
+
code: String(error?.code ?? 'EUNKNOWN'),
|
|
19
|
+
message: String(error?.message ?? error ?? 'socket error'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeResponse(response) {
|
|
24
|
+
const encoded = encoder.encode(JSON.stringify(response));
|
|
25
|
+
if (encoded.length > bytes.length) {
|
|
26
|
+
const fallback = encoder.encode(JSON.stringify({ ok: false, error: { code: 'EMSGSIZE', message: 'socket response too large' } }));
|
|
27
|
+
bytes.set(fallback.subarray(0, bytes.length), 0);
|
|
28
|
+
Atomics.store(header, 2, Math.min(fallback.length, bytes.length));
|
|
29
|
+
} else {
|
|
30
|
+
bytes.set(encoded, 0);
|
|
31
|
+
Atomics.store(header, 2, encoded.length);
|
|
32
|
+
}
|
|
33
|
+
Atomics.store(header, 0, 2);
|
|
34
|
+
Atomics.notify(header, 0, 1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function takeBuffered(state, maxBytes) {
|
|
38
|
+
if (state.chunks.length === 0) return null;
|
|
39
|
+
const first = state.chunks[0];
|
|
40
|
+
if (first.length <= maxBytes) {
|
|
41
|
+
state.chunks.shift();
|
|
42
|
+
return first;
|
|
43
|
+
}
|
|
44
|
+
const result = first.subarray(0, maxBytes);
|
|
45
|
+
state.chunks[0] = first.subarray(maxBytes);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function satisfyReaders(state) {
|
|
50
|
+
while (state.readers.length > 0) {
|
|
51
|
+
const waiter = state.readers[0];
|
|
52
|
+
if (state.error) {
|
|
53
|
+
state.readers.shift();
|
|
54
|
+
waiter.reject(state.error);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const chunk = takeBuffered(state, waiter.maxBytes);
|
|
58
|
+
if (chunk != null) {
|
|
59
|
+
state.readers.shift();
|
|
60
|
+
waiter.resolve(chunk);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (state.ended) {
|
|
64
|
+
state.readers.shift();
|
|
65
|
+
waiter.resolve(null);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function registerConnection(socket) {
|
|
73
|
+
const id = nextConnectionId++;
|
|
74
|
+
const state = { id, socket, chunks: [], readers: [], ended: false, error: null };
|
|
75
|
+
connections.set(id, state);
|
|
76
|
+
socket.on('data', (chunk) => {
|
|
77
|
+
state.chunks.push(Buffer.from(chunk));
|
|
78
|
+
satisfyReaders(state);
|
|
79
|
+
});
|
|
80
|
+
socket.on('end', () => {
|
|
81
|
+
state.ended = true;
|
|
82
|
+
satisfyReaders(state);
|
|
83
|
+
});
|
|
84
|
+
socket.on('close', () => {
|
|
85
|
+
state.ended = true;
|
|
86
|
+
satisfyReaders(state);
|
|
87
|
+
});
|
|
88
|
+
socket.on('error', (error) => {
|
|
89
|
+
state.error = error;
|
|
90
|
+
satisfyReaders(state);
|
|
91
|
+
});
|
|
92
|
+
return state;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function connectionClient(socket) {
|
|
96
|
+
const address = socket.remoteAddress ?? '';
|
|
97
|
+
const port = socket.remotePort ?? 0;
|
|
98
|
+
return address.includes(':') && !address.startsWith('[')
|
|
99
|
+
? `[${address}]:${port}`
|
|
100
|
+
: `${address}:${port}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function queueAccepted(serverState, socket) {
|
|
104
|
+
const connection = registerConnection(socket);
|
|
105
|
+
const accepted = { connectionId: connection.id, client: connectionClient(socket) };
|
|
106
|
+
const waiter = serverState.acceptors.shift();
|
|
107
|
+
if (waiter) waiter.resolve(accepted);
|
|
108
|
+
else serverState.accepted.push(accepted);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function serverOpen(host, port) {
|
|
112
|
+
const server = net.createServer();
|
|
113
|
+
const state = { id: nextServerId++, server, accepted: [], acceptors: [], closed: false };
|
|
114
|
+
server.on('connection', (socket) => queueAccepted(state, socket));
|
|
115
|
+
await new Promise((resolve, reject) => {
|
|
116
|
+
const onError = (error) => { server.off('listening', onListen); reject(error); };
|
|
117
|
+
const onListen = () => { server.off('error', onError); resolve(); };
|
|
118
|
+
server.once('error', onError);
|
|
119
|
+
server.once('listening', onListen);
|
|
120
|
+
server.listen({ host, port });
|
|
121
|
+
});
|
|
122
|
+
const address = server.address();
|
|
123
|
+
servers.set(state.id, state);
|
|
124
|
+
return { serverId: state.id, port: typeof address === 'object' && address ? address.port : port };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function clientOpen(host, port) {
|
|
128
|
+
const socket = new net.Socket();
|
|
129
|
+
const state = registerConnection(socket);
|
|
130
|
+
try {
|
|
131
|
+
await new Promise((resolve, reject) => {
|
|
132
|
+
const onConnect = () => { socket.off('error', onError); resolve(); };
|
|
133
|
+
const onError = (error) => { socket.off('connect', onConnect); reject(error); };
|
|
134
|
+
socket.once('connect', onConnect);
|
|
135
|
+
socket.once('error', onError);
|
|
136
|
+
socket.connect({ host, port });
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
connections.delete(state.id);
|
|
140
|
+
socket.destroy();
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
return { connectionId: state.id };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function serverAccept(serverId) {
|
|
147
|
+
const state = servers.get(serverId);
|
|
148
|
+
if (!state || state.closed) throw Object.assign(new Error('server socket is closed'), { code: 'EBADF' });
|
|
149
|
+
if (state.accepted.length > 0) return state.accepted.shift();
|
|
150
|
+
return new Promise((resolve, reject) => state.acceptors.push({ resolve, reject }));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function serverClose(serverId) {
|
|
154
|
+
const state = servers.get(serverId);
|
|
155
|
+
if (!state || state.closed) throw Object.assign(new Error('server socket does not exist'), { code: 'EBADF' });
|
|
156
|
+
state.closed = true;
|
|
157
|
+
for (const waiter of state.acceptors.splice(0)) {
|
|
158
|
+
waiter.reject(Object.assign(new Error('server socket is closed'), { code: 'EBADF' }));
|
|
159
|
+
}
|
|
160
|
+
try { state.server.close(); } catch (_) { /* already closed */ }
|
|
161
|
+
servers.delete(serverId);
|
|
162
|
+
return {};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function connectionRead(connectionId, maxBytes) {
|
|
166
|
+
const state = connections.get(connectionId);
|
|
167
|
+
if (!state) throw Object.assign(new Error('socket stream does not exist'), { code: 'EBADF' });
|
|
168
|
+
if (state.error) throw state.error;
|
|
169
|
+
const chunk = takeBuffered(state, maxBytes);
|
|
170
|
+
if (chunk != null) return { eof: false, data: chunk.toString('base64') };
|
|
171
|
+
if (state.ended) return { eof: true, data: '' };
|
|
172
|
+
const result = await new Promise((resolve, reject) => state.readers.push({ maxBytes, resolve, reject }));
|
|
173
|
+
return result == null ? { eof: true, data: '' } : { eof: false, data: result.toString('base64') };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function connectionWrite(connectionId, base64) {
|
|
177
|
+
const state = connections.get(connectionId);
|
|
178
|
+
if (!state || state.ended) throw Object.assign(new Error('socket stream is closed'), { code: 'EBADF' });
|
|
179
|
+
const data = Buffer.from(base64, 'base64');
|
|
180
|
+
await new Promise((resolve, reject) => {
|
|
181
|
+
let settled = false;
|
|
182
|
+
const onError = (error) => {
|
|
183
|
+
if (settled) return;
|
|
184
|
+
settled = true;
|
|
185
|
+
state.socket.off('error', onError);
|
|
186
|
+
reject(error);
|
|
187
|
+
};
|
|
188
|
+
state.socket.once('error', onError);
|
|
189
|
+
state.socket.write(data, () => {
|
|
190
|
+
if (settled) return;
|
|
191
|
+
settled = true;
|
|
192
|
+
state.socket.off('error', onError);
|
|
193
|
+
resolve();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function connectionClose(connectionId) {
|
|
200
|
+
const state = connections.get(connectionId);
|
|
201
|
+
if (!state) return {};
|
|
202
|
+
state.ended = true;
|
|
203
|
+
satisfyReaders(state);
|
|
204
|
+
state.socket.destroy();
|
|
205
|
+
connections.delete(connectionId);
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function handle(request) {
|
|
210
|
+
switch (request.op) {
|
|
211
|
+
case 'hostname': return { hostname: os.hostname() };
|
|
212
|
+
case 'server_open': return serverOpen(request.host, request.port);
|
|
213
|
+
case 'server_accept': return serverAccept(request.serverId);
|
|
214
|
+
case 'server_close': return serverClose(request.serverId);
|
|
215
|
+
case 'client_open': return clientOpen(request.host, request.port);
|
|
216
|
+
case 'read': return connectionRead(request.connectionId, request.maxBytes);
|
|
217
|
+
case 'write': return connectionWrite(request.connectionId, request.data);
|
|
218
|
+
case 'close': return connectionClose(request.connectionId);
|
|
219
|
+
default: throw Object.assign(new Error(`unknown socket operation: ${request.op}`), { code: 'EINVAL' });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
parentPort.on('message', async () => {
|
|
224
|
+
if (Atomics.load(header, 0) !== 1) return;
|
|
225
|
+
try {
|
|
226
|
+
const length = Atomics.load(header, 1);
|
|
227
|
+
const request = JSON.parse(decoder.decode(bytes.subarray(0, length)));
|
|
228
|
+
writeResponse({ ok: true, result: await handle(request) });
|
|
229
|
+
} catch (error) {
|
|
230
|
+
writeResponse({ ok: false, error: errorRecord(error) });
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
Atomics.store(header, 0, -1);
|
|
235
|
+
Atomics.notify(header, 0, 1);
|
package/src/standard-library.js
CHANGED
|
@@ -20,6 +20,7 @@ import { pioHostBuiltins } from './pio-host.js';
|
|
|
20
20
|
import { randomHostBuiltins } from './random-host.js';
|
|
21
21
|
import { tablingHostBuiltins } from './tabling-host.js';
|
|
22
22
|
import { timeHostBuiltins } from './time-host.js';
|
|
23
|
+
import { socketsHostBuiltins } from './sockets-host.js';
|
|
23
24
|
import { fs, isNode, memoryStatistics } from './platform.js';
|
|
24
25
|
import { ATOM, VAR, atom, deref, numberTerm, unify } from './term.js';
|
|
25
26
|
import { eyePrologAmbiguousLibraryAutoload, eyePrologLibraryAutoload, eyePrologLibraryAutoloadModules } from './library-autoload-index.js';
|
|
@@ -59,6 +60,7 @@ const moduleFiles = Object.freeze({
|
|
|
59
60
|
random: 'random.pl',
|
|
60
61
|
reif: 'reif.pl',
|
|
61
62
|
si: 'si.pl',
|
|
63
|
+
sockets: 'sockets.pl',
|
|
62
64
|
strings: 'strings.pl',
|
|
63
65
|
terms: 'terms.pl',
|
|
64
66
|
tabling: 'tabling.pl',
|
|
@@ -98,6 +100,7 @@ export const eyePrologNativeLibraryIndicators = Object.freeze([
|
|
|
98
100
|
'number_to_rational/2', 'number_to_rational/3', 'rational_numerator_denominator/3',
|
|
99
101
|
'read_from_chars/2', 'read_term_from_chars/3', 'write_term_to_chars/3', 'chars_base64/3',
|
|
100
102
|
'sleep/1',
|
|
103
|
+
'socket_client_open/3', 'socket_server_open/2', 'socket_server_accept/4', 'socket_server_close/1', 'current_hostname/1',
|
|
101
104
|
'directory_files/2', 'file_size/2', 'file_exists/1', 'directory_exists/1',
|
|
102
105
|
'delete_file/1', 'rename_file/2', 'file_copy/2', 'delete_directory/1',
|
|
103
106
|
'make_directory/1', 'make_directory_path/1', 'working_directory/2', 'path_canonical/2',
|
|
@@ -274,6 +277,7 @@ const eyePrologSharedLibraryIndicators = [
|
|
|
274
277
|
'ord_memberchk/2', 'ord_selectchk/3', 'ord_seteq/2', 'ord_subset/2',
|
|
275
278
|
'ord_subtract/3', 'ord_symdiff/3', 'ord_union/2', 'ord_union/3', 'ord_union/4',
|
|
276
279
|
'directory_files/2', 'delete_file/1', 'rename_file/2', 'make_directory/1', 'make_directory_path/1', 'working_directory/2',
|
|
280
|
+
'socket_client_open/3', 'socket_server_accept/4', 'socket_server_close/1',
|
|
277
281
|
'phrase_from_file/2', 'phrase_from_file/3', 'phrase_to_file/2',
|
|
278
282
|
'phrase_to_file/3', 'phrase_to_stream/2',
|
|
279
283
|
'maybe/0', 'random/1', 'random_integer/3', 'set_random/1',
|
|
@@ -299,7 +303,7 @@ export const eyePrologInteropLibraryIndicators = Object.freeze([
|
|
|
299
303
|
export const eyePrologInteropLibraryModules = Object.freeze([
|
|
300
304
|
'arithmetic', 'assoc', 'atts', 'charsio', 'clpb', 'clpz', 'dcgs', 'debug', 'dif',
|
|
301
305
|
'files', 'format', 'freeze', 'gensym', 'iso_ext', 'lambda', 'lists', 'ordsets', 'os', 'pio',
|
|
302
|
-
'random', 'reif', 'tabling', 'time', 'ugraphs', 'uuid', 'when',
|
|
306
|
+
'random', 'reif', 'sockets', 'tabling', 'time', 'ugraphs', 'uuid', 'when',
|
|
303
307
|
]);
|
|
304
308
|
|
|
305
309
|
function* tabledNegationBuiltin({ solver, goal, env }) {
|
|
@@ -372,6 +376,7 @@ export function createEyePrologRegistry() {
|
|
|
372
376
|
randomHostBuiltins.register(registry);
|
|
373
377
|
tablingHostBuiltins.register(registry);
|
|
374
378
|
timeHostBuiltins.register(registry);
|
|
379
|
+
socketsHostBuiltins.register(registry);
|
|
375
380
|
cryptoHostBuiltins.register(registry);
|
|
376
381
|
dcgsHostBuiltins.register(registry);
|
|
377
382
|
registry.eyePrologLibrary = true;
|
package/test/run-regression.mjs
CHANGED
|
@@ -4792,15 +4792,15 @@ function documentationSyncCases() {
|
|
|
4792
4792
|
encoding: 'utf8',
|
|
4793
4793
|
});
|
|
4794
4794
|
assertEqual(result.status, 0, 'exit status');
|
|
4795
|
-
assertIncludes(result.stdout, 'predicate reference is up to date (
|
|
4795
|
+
assertIncludes(result.stdout, 'predicate reference is up to date (523 predicates)', 'stdout');
|
|
4796
4796
|
assertEqual(result.stderr, '', 'stderr');
|
|
4797
4797
|
|
|
4798
4798
|
const book = fs.readFileSync(path.join(packageRoot, 'the-art-of-eyeprolog.md'), 'utf8');
|
|
4799
4799
|
const section = between(book, '<!-- eyeprolog-predicate-reference:start -->', '<!-- eyeprolog-predicate-reference:end -->');
|
|
4800
4800
|
assertEqual(section.split('\n').some((line) => line.trimStart().startsWith('|')), false, 'predicate reference avoids wide table markup');
|
|
4801
|
-
assertEqual((section.match(/^- \*\*`/gm) ?? []).length,
|
|
4802
|
-
assertEqual((section.match(/<a id="predicate-reference-\d{4}"><\/a>/g) ?? []).length,
|
|
4803
|
-
assertEqual((section.match(/\]\(#predicate-reference-\d{4}\)/g) ?? []).length,
|
|
4801
|
+
assertEqual((section.match(/^- \*\*`/gm) ?? []).length, 523, 'one reference entry per predicate');
|
|
4802
|
+
assertEqual((section.match(/<a id="predicate-reference-\d{4}"><\/a>/g) ?? []).length, 523, 'one explicit anchor per predicate');
|
|
4803
|
+
assertEqual((section.match(/\]\(#predicate-reference-\d{4}\)/g) ?? []).length, 523, 'one direct index link per predicate');
|
|
4804
4804
|
assertNotIncludes(section, '[Symbols](#predicate-reference-symbols)', 'predicate index does not rely on renderer-generated group anchors');
|
|
4805
4805
|
|
|
4806
4806
|
const chapter = between(book, '## 39. Predicate reference', '## 40. Running EyeProlog: command line and corpus');
|
|
@@ -5827,6 +5827,50 @@ answer(Status,Pid) :-
|
|
|
5827
5827
|
assertEqual(fs.existsSync(nested), true, 'make_directory_path/1 created nested directories');
|
|
5828
5828
|
},
|
|
5829
5829
|
},
|
|
5830
|
+
{
|
|
5831
|
+
name: 'library(sockets) provides Scryer-compatible bidirectional TCP text streams',
|
|
5832
|
+
run: () => {
|
|
5833
|
+
const source = `:- use_module(library(sockets)).
|
|
5834
|
+
answer(Port,Client,Term,Mode,Alias,Host) :-
|
|
5835
|
+
current_hostname(Host), atom(Host),
|
|
5836
|
+
socket_server_open('127.0.0.1':Port,Server), Port > 0,
|
|
5837
|
+
socket_client_open('127.0.0.1':Port,Out,[type(text),alias(client_socket),eof_action(eof_code)]),
|
|
5838
|
+
socket_server_accept(Server,Client,In,[type(text)]),
|
|
5839
|
+
stream_property(Out,input), stream_property(Out,output),
|
|
5840
|
+
stream_property(Out,mode(Mode)), stream_property(Out,alias(Alias)),
|
|
5841
|
+
stream_property(Out,position(0)), stream_property(Out,file_name(FileName)), atom(FileName),
|
|
5842
|
+
socket_server_close(Server),
|
|
5843
|
+
write(Out,ping), put_char(Out,'.'), nl(Out), flush_output(client_socket),
|
|
5844
|
+
read(In,Term),
|
|
5845
|
+
close(Out), close(In).
|
|
5846
|
+
`;
|
|
5847
|
+
const result = run(source, { goal: 'answer(Port,Client,Term,Mode,Alias,Host)' });
|
|
5848
|
+
assertIncludes(result.stdout, ', ping, read_append, client_socket, ', 'socket text stream result');
|
|
5849
|
+
assertIncludes(result.stdout, "'127.0.0.1:", 'accepted peer address');
|
|
5850
|
+
},
|
|
5851
|
+
},
|
|
5852
|
+
{
|
|
5853
|
+
name: 'library(sockets) supports binary streams and rejects repositioning',
|
|
5854
|
+
run: () => {
|
|
5855
|
+
const source = `:- use_module(library(sockets)).
|
|
5856
|
+
answer(A,B) :-
|
|
5857
|
+
socket_server_open(Port,Server),
|
|
5858
|
+
socket_client_open('127.0.0.1':Port,Out,[type(binary)]),
|
|
5859
|
+
socket_server_accept(Server,_,In,[type(binary)]),
|
|
5860
|
+
put_byte(Out,65), put_byte(Out,255), flush_output(Out),
|
|
5861
|
+
get_byte(In,A), get_byte(In,B),
|
|
5862
|
+
close(Out), close(In), socket_server_close(Server).
|
|
5863
|
+
reject_reposition :-
|
|
5864
|
+
socket_server_open(Port,Server),
|
|
5865
|
+
catch(socket_client_open('127.0.0.1':Port,_,[reposition(true)]),
|
|
5866
|
+
error(permission_error(reposition,stream,_),_),
|
|
5867
|
+
true),
|
|
5868
|
+
socket_server_close(Server).
|
|
5869
|
+
`;
|
|
5870
|
+
assertEqual(run(source, { goal: 'answer(A,B)' }).stdout, 'answer(65, 255).\n', 'binary socket stream');
|
|
5871
|
+
assertEqual(run(source, { goal: 'reject_reposition' }).stdout, 'reject_reposition.\n', 'socket reposition error');
|
|
5872
|
+
},
|
|
5873
|
+
},
|
|
5830
5874
|
{
|
|
5831
5875
|
name: 'completed Scryer arithmetic charsio dcgs and lists predicates compose',
|
|
5832
5876
|
run: () => {
|
|
@@ -6573,13 +6617,15 @@ answer(ok) :-
|
|
|
6573
6617
|
assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
|
|
6574
6618
|
assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
|
|
6575
6619
|
assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
|
|
6576
|
-
assertEqual(library.defs.size,
|
|
6620
|
+
assertEqual(library.defs.size, 221, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS tnot/1, and generic library adapters');
|
|
6577
6621
|
assertEqual(registry.get('eyeprolog__dynify', 1), null, 'Eyelet dynify adapter is absent from the ISO registry');
|
|
6578
6622
|
assertEqual(Boolean(library.get('eyeprolog__dynify', 1)), true, 'Eyelet dynify adapter is an internal EyeProlog library primitive');
|
|
6579
6623
|
assertEqual(registry.get('eyeprolog__eyelet_emit', 2), null, 'Eyelet event adapter is absent from the ISO registry');
|
|
6580
6624
|
assertEqual(Boolean(library.get('eyeprolog__eyelet_emit', 2)), true, 'Eyelet event adapter is an internal EyeProlog library primitive');
|
|
6581
6625
|
assertEqual(registry.get('eyeprolog__random_value', 1), null, 'native random helper is absent from the ISO registry');
|
|
6582
6626
|
assertEqual(Boolean(library.get('eyeprolog__random_value', 1)), true, 'native random helper is an internal EyeProlog library adapter');
|
|
6627
|
+
assertEqual(registry.get('eyeprolog__socket_client_open', 4), null, 'socket adapter is absent from the ISO registry');
|
|
6628
|
+
assertEqual(Boolean(library.get('eyeprolog__socket_client_open', 4)), true, 'socket client adapter is an internal EyeProlog library primitive');
|
|
6583
6629
|
assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
|
|
6584
6630
|
assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
|
|
6585
6631
|
assertEqual(registry.get('statistics', 0), null, 'statistics/0 is absent from the ISO registry');
|
|
@@ -6596,10 +6642,10 @@ answer(ok) :-
|
|
|
6596
6642
|
assertEqual(Boolean(library.get('call_cleanup', 2)), true, 'call_cleanup/2 is an EyeProlog cleanup control');
|
|
6597
6643
|
assertEqual(registry.get('setup_call_cleanup', 3), null, 'setup_call_cleanup/3 is absent from the ISO registry');
|
|
6598
6644
|
assertEqual(Boolean(library.get('setup_call_cleanup', 3)), true, 'setup_call_cleanup/3 is an EyeProlog cleanup control');
|
|
6599
|
-
assertEqual(registeredNativeEyePrologLibraryNames().length,
|
|
6645
|
+
assertEqual(registeredNativeEyePrologLibraryNames().length, 114, 'public host-supported EyeProlog library count');
|
|
6600
6646
|
assertEqual(eyePrologPortableLibraryIndicators.length, 280, 'portable Prolog library count');
|
|
6601
|
-
assertEqual(eyePrologInteropLibraryIndicators.length,
|
|
6602
|
-
assertEqual(eyePrologInteropLibraryModules.length,
|
|
6647
|
+
assertEqual(eyePrologInteropLibraryIndicators.length, 221, 'cross-implementation interop profile count');
|
|
6648
|
+
assertEqual(eyePrologInteropLibraryModules.length, 27, 'common explicit library module profile count');
|
|
6603
6649
|
assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
|
|
6604
6650
|
assertEqual(eyePrologInteropAutoload['between/3'], 'between', 'between/3 canonical internal autoload');
|
|
6605
6651
|
assertEqual(eyePrologInteropAutoload['call_nth/2'], 'iso_ext', 'call_nth/2 canonical interop autoload');
|
|
@@ -6611,11 +6657,11 @@ answer(ok) :-
|
|
|
6611
6657
|
assertEqual(eyePrologLibraryAutoload['pairs_keys_values/3'], 'pairs', 'complete library autoload includes library(pairs)');
|
|
6612
6658
|
assertEqual(eyePrologLibraryAutoload['stable/1'], 'eyelet', 'complete library autoload includes Eyelet stable/1');
|
|
6613
6659
|
assertEqual(eyePrologLibraryAutoload['becomes/2'], 'eyelet', 'complete library autoload includes Eyelet becomes/2');
|
|
6614
|
-
assertEqual(eyePrologLibraryAutoloadModules.length,
|
|
6615
|
-
assertEqual(eyePrologNativeLibraryIndicators.length,
|
|
6660
|
+
assertEqual(eyePrologLibraryAutoloadModules.length, 40, 'all bundled src/lib modules are indexed for autoload');
|
|
6661
|
+
assertEqual(eyePrologNativeLibraryIndicators.length, 114, 'host-supported library count');
|
|
6616
6662
|
assertEqual(eyePrologNativeLibraryIndicators.includes('call_nth/2'), true, 'call_nth/2 remains classified as host-supported');
|
|
6617
6663
|
assertEqual(eyePrologNativeLibraryIndicators.includes('random/1'), true, 'stateful random/1 is classified as host-supported');
|
|
6618
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
6664
|
+
assertEqual(eyePrologLibraryIndicators.length, 394, 'complete non-ISO EyeProlog library surface');
|
|
6619
6665
|
assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
|
|
6620
6666
|
assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
|
|
6621
6667
|
assertEqual(Boolean(library.get('eyeprolog__call_residue_vars', 2)), true, 'private call_residue_vars adapter is registered for EyeProlog');
|
|
@@ -345,6 +345,13 @@
|
|
|
345
345
|
"not_si/1",
|
|
346
346
|
"when_si/2"
|
|
347
347
|
],
|
|
348
|
+
"sockets": [
|
|
349
|
+
"socket_client_open/3",
|
|
350
|
+
"socket_server_open/2",
|
|
351
|
+
"socket_server_accept/4",
|
|
352
|
+
"socket_server_close/1",
|
|
353
|
+
"current_hostname/1"
|
|
354
|
+
],
|
|
348
355
|
"tabling": [
|
|
349
356
|
"start_tabling/2",
|
|
350
357
|
"abolish_all_tables/0"
|