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.
@@ -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);
@@ -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;
@@ -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 (518 predicates)', 'stdout');
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, 518, 'one reference entry per predicate');
4802
- assertEqual((section.match(/<a id="predicate-reference-\d{4}"><\/a>/g) ?? []).length, 518, 'one explicit anchor per predicate');
4803
- assertEqual((section.match(/\]\(#predicate-reference-\d{4}\)/g) ?? []).length, 518, 'one direct index link per predicate');
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, 216, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS tnot/1, and generic library adapters');
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, 109, 'public host-supported EyeProlog library count');
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, 218, 'cross-implementation interop profile count');
6602
- assertEqual(eyePrologInteropLibraryModules.length, 26, 'common explicit library module profile count');
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, 39, 'all bundled src/lib modules are indexed for autoload');
6615
- assertEqual(eyePrologNativeLibraryIndicators.length, 109, 'host-supported library count');
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, 389, 'complete non-ISO EyeProlog library surface');
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"