nomen-lang 0.3.0 → 0.5.0
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/NOMEN_AGENTS.md +7 -1
- package/core/System/Arena.nm +7 -7
- package/core/System/Awaitable.nm +21 -0
- package/core/System/BigInt.nm +5 -15
- package/core/System/Buffer.nm +140 -33
- package/core/System/Channel.nm +80 -29
- package/core/System/ClassBuffer.nm +89 -32
- package/core/System/Console.nm +4 -2
- package/core/System/Controls/Button.nm +4 -4
- package/core/System/Controls/CheckBox.nm +1 -1
- package/core/System/Controls/Text.nm +2 -2
- package/core/System/Controls/TextBox.nm +6 -6
- package/core/System/Controls/Window.nm +2 -2
- package/core/System/Fiber.nm +121 -0
- package/core/System/Graph.nm +4 -4
- package/core/System/LinkedList.nm +4 -4
- package/core/System/List.nm +6 -6
- package/core/System/Map.nm +30 -30
- package/core/System/Mutex.nm +57 -61
- package/core/System/Nursery.nm +10 -8
- package/core/System/Set.nm +24 -24
- package/core/System/Stream/Directory.nm +4 -4
- package/core/System/Stream/File.nm +19 -12
- package/core/System/Stream/Http.nm +83 -434
- package/core/System/Stream/Tcp.nm +441 -0
- package/core/System/String.nm +47 -18
- package/core/System/Task.nm +103 -4
- package/core/System/Text/JsonTree.nm +15 -15
- package/core/System/Thread.nm +83 -0
- package/core/System/bool.nm +2 -1
- package/core/System/char.nm +2 -1
- package/core/System/float.nm +2 -1
- package/core/System/float32.nm +2 -1
- package/core/System/float64.nm +2 -1
- package/core/System/int.nm +4 -2
- package/core/System/int16.nm +2 -1
- package/core/System/int32.nm +2 -1
- package/core/System/int64.nm +2 -1
- package/core/System/int8.nm +2 -1
- package/core/System/ufloat.nm +2 -1
- package/core/System/ufloat32.nm +2 -1
- package/core/System/ufloat64.nm +2 -1
- package/core/System/uint.nm +2 -1
- package/core/System/uint16.nm +2 -1
- package/core/System/uint32.nm +2 -1
- package/core/System/uint64.nm +2 -1
- package/core/System/uint8.nm +2 -1
- package/core/docs/System/BigInt.md +0 -1
- package/dist/index.mjs +20401 -13796
- package/package.json +1 -1
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
// Tcp — non-blocking sockets for Nomen tasks.
|
|
2
|
+
//
|
|
3
|
+
// Sockets are put in non-blocking mode and every wait goes through the
|
|
4
|
+
// runtime's netpoller hook (`__nomen_io_wait`): inside a Fiber the task parks
|
|
5
|
+
// (freeing its worker) until the socket is readable/writable; on a plain
|
|
6
|
+
// thread the hook blocks in poll(). So the same code scales from a handful of
|
|
7
|
+
// connections to thousands, and no call blocks a shared worker while it
|
|
8
|
+
// waits. See ASYNC.md Phase 3.
|
|
9
|
+
//
|
|
10
|
+
// Raw helpers return file descriptors / error codes (int-shaped), so the
|
|
11
|
+
// class wrappers construct the Tcp instances in Nomen. `error` mirrors
|
|
12
|
+
// Http's convention: 0 = ok, 1 = DNS, 2 = socket, 3 = connect, 4 = listen,
|
|
13
|
+
// 5 = closed.
|
|
14
|
+
//
|
|
15
|
+
// var Tcp server = Tcp.listen(8080, 128)
|
|
16
|
+
// async {
|
|
17
|
+
// var Tcp conn = server.accept()
|
|
18
|
+
// var bool ok = conn.send("hello")
|
|
19
|
+
// conn.close()
|
|
20
|
+
// }
|
|
21
|
+
//
|
|
22
|
+
// The method bodies are C (compiled directly on the C backend, and via the
|
|
23
|
+
// linked companion on aarch64) — OS socket/DNS interop, where hand-written
|
|
24
|
+
// per-target assembly is not practical.
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A non-blocking TCP socket whose waits park the calling fiber
|
|
28
|
+
**/
|
|
29
|
+
pub class Tcp : Sendable {
|
|
30
|
+
// The underlying file descriptor, -1 when closed/failed.
|
|
31
|
+
pub var int fd = -1
|
|
32
|
+
// 0 = ok; 1 = DNS, 2 = socket, 3 = connect, 4 = listen, 5 = closed.
|
|
33
|
+
pub var int error = 0
|
|
34
|
+
|
|
35
|
+
pub func #init = (self, int handle) {
|
|
36
|
+
self.fd = handle
|
|
37
|
+
self.error = 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub func #destroy = () {
|
|
41
|
+
```
|
|
42
|
+
#arch: c
|
|
43
|
+
if (self->fd >= 0) {
|
|
44
|
+
close(self->fd);
|
|
45
|
+
self->fd = -1;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
```
|
|
49
|
+
#arch: aarch64_use_c
|
|
50
|
+
if (self->fd >= 0) {
|
|
51
|
+
close(self->fd);
|
|
52
|
+
self->fd = -1;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub func close = (ref self) {
|
|
58
|
+
Tcp.close_fd(self.fd)
|
|
59
|
+
self.fd = -1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub func is_open = (self, out bool) => self.fd >= 0
|
|
63
|
+
|
|
64
|
+
// ---- listening ----
|
|
65
|
+
|
|
66
|
+
// Create a listening socket bound to `port` on all interfaces.
|
|
67
|
+
pub func listen = (int port, int backlog, move out Tcp) {
|
|
68
|
+
var int handle = Tcp.listen_fd(port, backlog)
|
|
69
|
+
var Tcp t = Tcp(handle)
|
|
70
|
+
if handle < 0 {
|
|
71
|
+
t.error = 4
|
|
72
|
+
}
|
|
73
|
+
return t
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Accept the next connection, parking the fiber while the backlog is
|
|
77
|
+
// empty. The returned socket is -1 if the listener was cancelled or
|
|
78
|
+
// failed.
|
|
79
|
+
pub func accept = (ref self, move out Tcp) {
|
|
80
|
+
var int handle = Tcp.accept_fd(self.fd)
|
|
81
|
+
var Tcp t = Tcp(handle)
|
|
82
|
+
if handle < 0 {
|
|
83
|
+
t.error = 5
|
|
84
|
+
}
|
|
85
|
+
return t
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---- connecting ----
|
|
89
|
+
|
|
90
|
+
// Connect to `host`:`port`. Check `fd >= 0` (or `error == 0`) for success.
|
|
91
|
+
pub func connect = (string host, int port, move out Tcp) {
|
|
92
|
+
var int handle = Tcp.socket_fd()
|
|
93
|
+
if handle < 0 {
|
|
94
|
+
var Tcp bad = Tcp(-1)
|
|
95
|
+
bad.error = 2
|
|
96
|
+
return bad
|
|
97
|
+
}
|
|
98
|
+
var int err = Tcp.connect_to(handle, host, port)
|
|
99
|
+
if err != 0 {
|
|
100
|
+
Tcp.close_fd(handle)
|
|
101
|
+
var Tcp bad = Tcp(-1)
|
|
102
|
+
bad.error = err
|
|
103
|
+
return bad
|
|
104
|
+
}
|
|
105
|
+
return Tcp(handle)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---- transferring ----
|
|
109
|
+
|
|
110
|
+
// Send every byte of `data`. Parks the fiber while the send buffer is
|
|
111
|
+
// full. Returns false on error or cancellation.
|
|
112
|
+
pub func send = (ref self, string data, out bool) {
|
|
113
|
+
return Tcp.send_all(self.fd, data)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Receive up to `max` bytes. Parks the fiber until data arrives; returns
|
|
117
|
+
// the empty string at end-of-stream, on error, or when cancelled.
|
|
118
|
+
pub func recv = (ref self, int max, move out string) {
|
|
119
|
+
return Tcp.recv_some(self.fd, max)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Receive until the peer closes (how Connection: close HTTP works).
|
|
123
|
+
pub func recv_all = (ref self, move out string) {
|
|
124
|
+
return Tcp.recv_until_close(self.fd)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---- raw helpers (int-shaped, so the wrappers above build the Tcp) ----
|
|
128
|
+
|
|
129
|
+
func socket_fd = (out int) {
|
|
130
|
+
```
|
|
131
|
+
#arch: c
|
|
132
|
+
#scope: file
|
|
133
|
+
#include <sys/socket.h>
|
|
134
|
+
#include <netdb.h>
|
|
135
|
+
#include <netinet/in.h>
|
|
136
|
+
#include <arpa/inet.h>
|
|
137
|
+
#include <fcntl.h>
|
|
138
|
+
#include <unistd.h>
|
|
139
|
+
#include <string.h>
|
|
140
|
+
#include <stdlib.h>
|
|
141
|
+
#include <errno.h>
|
|
142
|
+
#include <stdbool.h>
|
|
143
|
+
```
|
|
144
|
+
```
|
|
145
|
+
#arch: c
|
|
146
|
+
int handle = socket(AF_INET, SOCK_STREAM, 0);
|
|
147
|
+
if (handle < 0) return -1;
|
|
148
|
+
fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
|
|
149
|
+
return handle;
|
|
150
|
+
```
|
|
151
|
+
```
|
|
152
|
+
#arch: aarch64_use_c
|
|
153
|
+
#scope: file
|
|
154
|
+
#include <sys/socket.h>
|
|
155
|
+
#include <netdb.h>
|
|
156
|
+
#include <netinet/in.h>
|
|
157
|
+
#include <arpa/inet.h>
|
|
158
|
+
#include <fcntl.h>
|
|
159
|
+
#include <unistd.h>
|
|
160
|
+
#include <string.h>
|
|
161
|
+
#include <stdlib.h>
|
|
162
|
+
#include <errno.h>
|
|
163
|
+
#include <stdbool.h>
|
|
164
|
+
```
|
|
165
|
+
```
|
|
166
|
+
#arch: aarch64_use_c
|
|
167
|
+
int handle = socket(AF_INET, SOCK_STREAM, 0);
|
|
168
|
+
if (handle < 0) return -1;
|
|
169
|
+
fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
|
|
170
|
+
return handle;
|
|
171
|
+
```
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
func listen_fd = (int port, int backlog, out int) {
|
|
175
|
+
```
|
|
176
|
+
#arch: c
|
|
177
|
+
int handle = socket(AF_INET, SOCK_STREAM, 0);
|
|
178
|
+
if (handle < 0) return -1;
|
|
179
|
+
int one = 1;
|
|
180
|
+
setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
181
|
+
struct sockaddr_in addr;
|
|
182
|
+
memset(&addr, 0, sizeof(addr));
|
|
183
|
+
addr.sin_family = AF_INET;
|
|
184
|
+
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
185
|
+
addr.sin_port = htons((unsigned short)port);
|
|
186
|
+
if (bind(handle, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
187
|
+
close(handle);
|
|
188
|
+
return -1;
|
|
189
|
+
}
|
|
190
|
+
if (listen(handle, backlog) < 0) {
|
|
191
|
+
close(handle);
|
|
192
|
+
return -1;
|
|
193
|
+
}
|
|
194
|
+
fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
|
|
195
|
+
return handle;
|
|
196
|
+
```
|
|
197
|
+
```
|
|
198
|
+
#arch: aarch64_use_c
|
|
199
|
+
int handle = socket(AF_INET, SOCK_STREAM, 0);
|
|
200
|
+
if (handle < 0) return -1;
|
|
201
|
+
int one = 1;
|
|
202
|
+
setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
203
|
+
struct sockaddr_in addr;
|
|
204
|
+
memset(&addr, 0, sizeof(addr));
|
|
205
|
+
addr.sin_family = AF_INET;
|
|
206
|
+
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
207
|
+
addr.sin_port = htons((unsigned short)port);
|
|
208
|
+
if (bind(handle, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
209
|
+
close(handle);
|
|
210
|
+
return -1;
|
|
211
|
+
}
|
|
212
|
+
if (listen(handle, backlog) < 0) {
|
|
213
|
+
close(handle);
|
|
214
|
+
return -1;
|
|
215
|
+
}
|
|
216
|
+
fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
|
|
217
|
+
return handle;
|
|
218
|
+
```
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
func accept_fd = (int listener, out int) {
|
|
222
|
+
```
|
|
223
|
+
#arch: c
|
|
224
|
+
for (;;) {
|
|
225
|
+
int handle = accept(listener, NULL, NULL);
|
|
226
|
+
if (handle >= 0) {
|
|
227
|
+
fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
|
|
228
|
+
return handle;
|
|
229
|
+
}
|
|
230
|
+
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) return -1;
|
|
231
|
+
if (!__nomen_io_wait(listener, 0)) return -1;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
```
|
|
235
|
+
#arch: aarch64_use_c
|
|
236
|
+
for (;;) {
|
|
237
|
+
int handle = accept(listener, NULL, NULL);
|
|
238
|
+
if (handle >= 0) {
|
|
239
|
+
fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
|
|
240
|
+
return handle;
|
|
241
|
+
}
|
|
242
|
+
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) return -1;
|
|
243
|
+
if (!__nomen_io_wait(listener, 0)) return -1;
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Returns 0 on success, or an error code (1 = DNS, 3 = connect).
|
|
249
|
+
func connect_to = (int handle, string host, int port, out int) {
|
|
250
|
+
```
|
|
251
|
+
#arch: c
|
|
252
|
+
char portstr[16];
|
|
253
|
+
snprintf(portstr, sizeof(portstr), "%d", port);
|
|
254
|
+
struct addrinfo hints, *res = NULL;
|
|
255
|
+
memset(&hints, 0, sizeof(hints));
|
|
256
|
+
hints.ai_family = AF_INET;
|
|
257
|
+
hints.ai_socktype = SOCK_STREAM;
|
|
258
|
+
if (getaddrinfo(host.ptr, portstr, &hints, &res) != 0) return 1;
|
|
259
|
+
int rc = connect(handle, res->ai_addr, res->ai_addrlen);
|
|
260
|
+
freeaddrinfo(res);
|
|
261
|
+
if (rc == 0) return 0;
|
|
262
|
+
if (errno != EINPROGRESS) return 3;
|
|
263
|
+
if (!__nomen_io_wait(handle, 1)) return 3;
|
|
264
|
+
int so_err = 0;
|
|
265
|
+
socklen_t so_len = sizeof(so_err);
|
|
266
|
+
if (getsockopt(handle, SOL_SOCKET, SO_ERROR, &so_err, &so_len) < 0) return 3;
|
|
267
|
+
return so_err == 0 ? 0 : 3;
|
|
268
|
+
```
|
|
269
|
+
```
|
|
270
|
+
#arch: aarch64_use_c
|
|
271
|
+
char portstr[16];
|
|
272
|
+
snprintf(portstr, sizeof(portstr), "%d", port);
|
|
273
|
+
struct addrinfo hints, *res = NULL;
|
|
274
|
+
memset(&hints, 0, sizeof(hints));
|
|
275
|
+
hints.ai_family = AF_INET;
|
|
276
|
+
hints.ai_socktype = SOCK_STREAM;
|
|
277
|
+
if (getaddrinfo(host.ptr, portstr, &hints, &res) != 0) return 1;
|
|
278
|
+
int rc = connect(handle, res->ai_addr, res->ai_addrlen);
|
|
279
|
+
freeaddrinfo(res);
|
|
280
|
+
if (rc == 0) return 0;
|
|
281
|
+
if (errno != EINPROGRESS) return 3;
|
|
282
|
+
if (!__nomen_io_wait(handle, 1)) return 3;
|
|
283
|
+
int so_err = 0;
|
|
284
|
+
socklen_t so_len = sizeof(so_err);
|
|
285
|
+
if (getsockopt(handle, SOL_SOCKET, SO_ERROR, &so_err, &so_len) < 0) return 3;
|
|
286
|
+
return so_err == 0 ? 0 : 3;
|
|
287
|
+
```
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
func send_all = (int handle, string data, out bool) {
|
|
291
|
+
```
|
|
292
|
+
#arch: c
|
|
293
|
+
long off = 0;
|
|
294
|
+
while (off < data.len) {
|
|
295
|
+
ssize_t n = send(handle, data.ptr + off, (size_t)(data.len - off), 0);
|
|
296
|
+
if (n > 0) {
|
|
297
|
+
off += (long)n;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
301
|
+
if (!__nomen_io_wait(handle, 1)) return false;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (errno == EINTR) continue;
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
return true;
|
|
308
|
+
```
|
|
309
|
+
```
|
|
310
|
+
#arch: aarch64_use_c
|
|
311
|
+
long off = 0;
|
|
312
|
+
while (off < data.len) {
|
|
313
|
+
ssize_t n = send(handle, data.ptr + off, (size_t)(data.len - off), 0);
|
|
314
|
+
if (n > 0) {
|
|
315
|
+
off += (long)n;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
319
|
+
if (!__nomen_io_wait(handle, 1)) return false;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (errno == EINTR) continue;
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
return true;
|
|
326
|
+
```
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
func recv_some = (int handle, int max, move out string) {
|
|
330
|
+
```
|
|
331
|
+
#arch: c
|
|
332
|
+
long cap = max > 0 ? max : 1;
|
|
333
|
+
char *buf = (char *)malloc((size_t)cap);
|
|
334
|
+
for (;;) {
|
|
335
|
+
ssize_t n = recv(handle, buf, (size_t)cap, 0);
|
|
336
|
+
if (n > 0) return (nomen_string){ buf, (long)n };
|
|
337
|
+
if (n == 0) {
|
|
338
|
+
free(buf);
|
|
339
|
+
return (nomen_string){ strdup(""), 0 };
|
|
340
|
+
}
|
|
341
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
342
|
+
if (!__nomen_io_wait(handle, 0)) {
|
|
343
|
+
free(buf);
|
|
344
|
+
return (nomen_string){ strdup(""), 0 };
|
|
345
|
+
}
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (errno == EINTR) continue;
|
|
349
|
+
free(buf);
|
|
350
|
+
return (nomen_string){ strdup(""), 0 };
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
```
|
|
354
|
+
#arch: aarch64_use_c
|
|
355
|
+
long cap = max > 0 ? max : 1;
|
|
356
|
+
char *buf = (char *)malloc((size_t)cap);
|
|
357
|
+
for (;;) {
|
|
358
|
+
ssize_t n = recv(handle, buf, (size_t)cap, 0);
|
|
359
|
+
if (n > 0) return (nomen_string){ buf, (long)n };
|
|
360
|
+
if (n == 0) {
|
|
361
|
+
free(buf);
|
|
362
|
+
return (nomen_string){ strdup(""), 0 };
|
|
363
|
+
}
|
|
364
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
365
|
+
if (!__nomen_io_wait(handle, 0)) {
|
|
366
|
+
free(buf);
|
|
367
|
+
return (nomen_string){ strdup(""), 0 };
|
|
368
|
+
}
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (errno == EINTR) continue;
|
|
372
|
+
free(buf);
|
|
373
|
+
return (nomen_string){ strdup(""), 0 };
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
func recv_until_close = (int handle, move out string) {
|
|
379
|
+
```
|
|
380
|
+
#arch: c
|
|
381
|
+
long cap = 4096, len = 0;
|
|
382
|
+
char *buf = (char *)malloc((size_t)cap);
|
|
383
|
+
for (;;) {
|
|
384
|
+
if (len + 1 >= cap) {
|
|
385
|
+
cap *= 2;
|
|
386
|
+
buf = (char *)realloc(buf, (size_t)cap);
|
|
387
|
+
}
|
|
388
|
+
ssize_t n = recv(handle, buf + len, (size_t)(cap - len - 1), 0);
|
|
389
|
+
if (n > 0) {
|
|
390
|
+
len += (long)n;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (n == 0) break;
|
|
394
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
395
|
+
if (!__nomen_io_wait(handle, 0)) break;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (errno == EINTR) continue;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
buf[len] = 0;
|
|
402
|
+
return (nomen_string){ buf, len };
|
|
403
|
+
```
|
|
404
|
+
```
|
|
405
|
+
#arch: aarch64_use_c
|
|
406
|
+
long cap = 4096, len = 0;
|
|
407
|
+
char *buf = (char *)malloc((size_t)cap);
|
|
408
|
+
for (;;) {
|
|
409
|
+
if (len + 1 >= cap) {
|
|
410
|
+
cap *= 2;
|
|
411
|
+
buf = (char *)realloc(buf, (size_t)cap);
|
|
412
|
+
}
|
|
413
|
+
ssize_t n = recv(handle, buf + len, (size_t)(cap - len - 1), 0);
|
|
414
|
+
if (n > 0) {
|
|
415
|
+
len += (long)n;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (n == 0) break;
|
|
419
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
420
|
+
if (!__nomen_io_wait(handle, 0)) break;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (errno == EINTR) continue;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
buf[len] = 0;
|
|
427
|
+
return (nomen_string){ buf, len };
|
|
428
|
+
```
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
func close_fd = (int handle) {
|
|
432
|
+
```
|
|
433
|
+
#arch: c
|
|
434
|
+
if (handle >= 0) close(handle);
|
|
435
|
+
```
|
|
436
|
+
```
|
|
437
|
+
#arch: aarch64_use_c
|
|
438
|
+
if (handle >= 0) close(handle);
|
|
439
|
+
```
|
|
440
|
+
}
|
|
441
|
+
}
|
package/core/System/String.nm
CHANGED
|
@@ -31,14 +31,36 @@ pub struct string: Stringable, Hashable, Equatable {
|
|
|
31
31
|
return h
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Returns an OWNED copy of self (`move out string`).
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// with its length.
|
|
34
|
+
// Returns an OWNED byte-exact copy of self (`move out string`). Fresh
|
|
35
|
+
// allocation, never an alias of self's storage — the result can outlive
|
|
36
|
+
// the receiver (e.g. escape a match branch that reclaims the scrutinee
|
|
37
|
+
// temp). Copies by tracked length, so embedded NULs survive (an extern
|
|
38
|
+
// strdup boundary would re-synthesize length via strlen and truncate).
|
|
40
39
|
pub func to_string = (self, move out string) {
|
|
41
|
-
|
|
40
|
+
```
|
|
41
|
+
#arch: c
|
|
42
|
+
// Fat-string ABI: copy self.len bytes; no strlen anywhere.
|
|
43
|
+
char* buf = (char*)malloc(self.len + 1);
|
|
44
|
+
memcpy(buf, self.ptr, self.len);
|
|
45
|
+
buf[self.len] = 0;
|
|
46
|
+
return (nomen_string){ buf, self.len };
|
|
47
|
+
```
|
|
48
|
+
```
|
|
49
|
+
#arch: aarch64
|
|
50
|
+
// x19/x20 = self (ptr, len). malloc + memcpy of known length;
|
|
51
|
+
// memcpy returns the dst in x0, which becomes the return ptr half.
|
|
52
|
+
sub sp, sp, #16
|
|
53
|
+
str x20, [sp, #0]
|
|
54
|
+
add x0, x20, #1
|
|
55
|
+
bl _malloc
|
|
56
|
+
mov x1, x19
|
|
57
|
+
mov x2, x20
|
|
58
|
+
bl _memcpy
|
|
59
|
+
ldr x1, [sp, #0]
|
|
60
|
+
add x2, x0, x1
|
|
61
|
+
strb wzr, [x2] // NUL-terminate for libc consumers
|
|
62
|
+
add sp, sp, #16
|
|
63
|
+
```
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
// The indexing primitive everything string code builds on — `unsafe`
|
|
@@ -80,8 +102,9 @@ pub struct string: Stringable, Hashable, Equatable {
|
|
|
80
102
|
) {
|
|
81
103
|
```
|
|
82
104
|
#arch: c
|
|
105
|
+
// Fat-string ABI: slice out of the tracked buffer — no NUL scan.
|
|
83
106
|
nomen_view _r;
|
|
84
|
-
_r.ptr = (void*)(self + start);
|
|
107
|
+
_r.ptr = (void*)(self.ptr + start);
|
|
85
108
|
_r.len = (long)(end - start);
|
|
86
109
|
return _r;
|
|
87
110
|
```
|
|
@@ -104,7 +127,9 @@ pub struct string: Stringable, Hashable, Equatable {
|
|
|
104
127
|
pub func #op_eq = (self, string other, out bool) {
|
|
105
128
|
```
|
|
106
129
|
#arch: c
|
|
107
|
-
|
|
130
|
+
// Length-aware compare: equal lengths + equal bytes. Embedded
|
|
131
|
+
// NULs are data, not terminators (strcmp truncated at the first).
|
|
132
|
+
return self.len == other.len && memcmp(self.ptr, other.ptr, self.len) == 0;
|
|
108
133
|
```
|
|
109
134
|
```
|
|
110
135
|
#arch: aarch64
|
|
@@ -130,12 +155,15 @@ pub struct string: Stringable, Hashable, Equatable {
|
|
|
130
155
|
pub func #op_add = (self, string other, out string) {
|
|
131
156
|
```
|
|
132
157
|
#arch: c
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
158
|
+
// Fat-string ABI: one malloc sized by the tracked lens, two
|
|
159
|
+
// memcpys, NUL-terminate for libc consumers. No strlen/strcpy.
|
|
160
|
+
long left_len = self.len;
|
|
161
|
+
long right_len = other.len;
|
|
162
|
+
char* result = (char*)malloc(left_len + right_len + 1);
|
|
163
|
+
memcpy(result, self.ptr, left_len);
|
|
164
|
+
memcpy(result + left_len, other.ptr, right_len);
|
|
165
|
+
result[left_len + right_len] = 0;
|
|
166
|
+
return (nomen_string){ result, left_len + right_len };
|
|
139
167
|
```
|
|
140
168
|
```
|
|
141
169
|
#arch: aarch64
|
|
@@ -168,16 +196,17 @@ pub struct string: Stringable, Hashable, Equatable {
|
|
|
168
196
|
pub func #op_mul = (self, int count, out string) {
|
|
169
197
|
```
|
|
170
198
|
#arch: c
|
|
171
|
-
|
|
199
|
+
// Fat-string ABI: memcpy loop of the tracked stride. No strlen.
|
|
200
|
+
size_t str_len = self.len;
|
|
172
201
|
size_t total = str_len * (size_t)count + 1;
|
|
173
202
|
char* buf = malloc(total);
|
|
174
203
|
char* p = buf;
|
|
175
204
|
for (int i = 0; i < count; i++) {
|
|
176
|
-
memcpy(p, self, str_len);
|
|
205
|
+
memcpy(p, self.ptr, str_len);
|
|
177
206
|
p += str_len;
|
|
178
207
|
}
|
|
179
208
|
*p = 0;
|
|
180
|
-
return buf;
|
|
209
|
+
return (nomen_string){ buf, total - 1 };
|
|
181
210
|
```
|
|
182
211
|
```
|
|
183
212
|
#arch: aarch64
|
package/core/System/Task.nm
CHANGED
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
// See ASYNC.md for the design.
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* A handle to an asynchronously-running computation, backed by a shared thread pool
|
|
17
|
+
* A handle to an asynchronously-running computation, backed by a shared thread pool.
|
|
18
|
+
* Conforms to `Awaitable`: `.wait()` may park the current execution context.
|
|
18
19
|
**/
|
|
19
|
-
pub class Task<T> : Sendable {
|
|
20
|
+
pub class Task<T> : Sendable, Awaitable {
|
|
20
21
|
pub var uint64 handle = 0
|
|
21
22
|
pub var done = false
|
|
22
23
|
pub var uint64 result_slot = 0
|
|
@@ -164,13 +165,23 @@ pub class Task<T> : Sendable {
|
|
|
164
165
|
pub func cancel = (ref self) {
|
|
165
166
|
```
|
|
166
167
|
#arch: c
|
|
167
|
-
|
|
168
|
+
// Sets the flag and wakes any fiber parked on this task's future, so
|
|
169
|
+
// a parked task comes back to observe the cancellation.
|
|
170
|
+
if (self->future) {
|
|
171
|
+
__nomen_future_cancel((struct nomen_future *)self->future);
|
|
172
|
+
} else if (self->cancel_flag) {
|
|
168
173
|
*(unsigned long long *)self->cancel_flag = 1;
|
|
169
174
|
}
|
|
170
175
|
```
|
|
171
176
|
```
|
|
172
177
|
#arch: aarch64
|
|
173
|
-
// if (self->
|
|
178
|
+
// if (self->future) __nomen_future_cancel(self->future);
|
|
179
|
+
// else if (self->cancel_flag) *(self->cancel_flag) = 1
|
|
180
|
+
ldr x0, [x19, #40]
|
|
181
|
+
cbz x0, .Lcancel_flag_path
|
|
182
|
+
bl ___nomen_future_cancel
|
|
183
|
+
b .Lcancel_done
|
|
184
|
+
.Lcancel_flag_path:
|
|
174
185
|
ldr x0, [x19, #32]
|
|
175
186
|
cbz x0, .Lcancel_done
|
|
176
187
|
mov x1, #1
|
|
@@ -244,6 +255,94 @@ pub class Task<T> : Sendable {
|
|
|
244
255
|
```
|
|
245
256
|
}
|
|
246
257
|
|
|
258
|
+
// ---- User-defined async primitive seam (ASYNC.md, "User-defined async
|
|
259
|
+
// primitives") ----
|
|
260
|
+
// The construction sugar stores the packed-task machinery in a user
|
|
261
|
+
// class's plain uint64 fields (task / result_slot / cancel_flag /
|
|
262
|
+
// future); these statics are the library boundary its own launch
|
|
263
|
+
// methods drive the runtime through — the same calls the generated
|
|
264
|
+
// Thread/Fiber launch code makes, so a user primitive parks fibers,
|
|
265
|
+
// deadlocks correctly, and frees exactly like a library spawn. The
|
|
266
|
+
// handles are opaque: the only contract is the field NAMES the sugar
|
|
267
|
+
// writes and these calls read.
|
|
268
|
+
|
|
269
|
+
// Submit a packed task closure (the `task` field's value) to the shared
|
|
270
|
+
// worker pool. The closure must come from a construction of an
|
|
271
|
+
// Awaitable-conforming class — it is not callable.
|
|
272
|
+
pub func pool_submit = (uint64 closure) {
|
|
273
|
+
```
|
|
274
|
+
#arch: c
|
|
275
|
+
__nomen_pool_submit((struct nomen_closure *)(void *)closure);
|
|
276
|
+
```
|
|
277
|
+
```
|
|
278
|
+
#arch: aarch64
|
|
279
|
+
// x0 = closure
|
|
280
|
+
bl ___nomen_pool_submit
|
|
281
|
+
```
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Park until the task completes (in a fiber this parks the fiber and
|
|
285
|
+
// frees its worker; on a raw thread it blocks). Idempotent once done.
|
|
286
|
+
pub func future_wait = (uint64 future) {
|
|
287
|
+
```
|
|
288
|
+
#arch: c
|
|
289
|
+
__nomen_future_wait((struct nomen_future *)(void *)future);
|
|
290
|
+
```
|
|
291
|
+
```
|
|
292
|
+
#arch: aarch64
|
|
293
|
+
// x0 = future
|
|
294
|
+
bl ___nomen_future_wait
|
|
295
|
+
```
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Read the uint64 result the task stored. The launch's ref accounting
|
|
299
|
+
// must still hold a reference (see future_set_refs) — read BEFORE the
|
|
300
|
+
// final release, which frees the slot.
|
|
301
|
+
pub func future_result_uint64 = (uint64 future, out uint64) {
|
|
302
|
+
```
|
|
303
|
+
#arch: c
|
|
304
|
+
return *(unsigned long long *)(void *)((struct nomen_future *)(void *)future)->result_slot;
|
|
305
|
+
```
|
|
306
|
+
```
|
|
307
|
+
#arch: aarch64
|
|
308
|
+
// x0 = future; result_slot at #128
|
|
309
|
+
ldr x0, [x0, #128]
|
|
310
|
+
ldr x0, [x0]
|
|
311
|
+
```
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Set the future's reference count before submitting. The construction
|
|
315
|
+
// leaves one reference (the value's own); a launch that parks on the
|
|
316
|
+
// future holds the wait-side reference: set_refs(f, 2), submit, and the
|
|
317
|
+
// running task's completion drops one; the join drops the last (the
|
|
318
|
+
// future frees its slot, flag, and packed closure at zero).
|
|
319
|
+
pub func future_set_refs = (uint64 future, int refs) {
|
|
320
|
+
```
|
|
321
|
+
#arch: c
|
|
322
|
+
((struct nomen_future *)(void *)future)->refs = refs;
|
|
323
|
+
```
|
|
324
|
+
```
|
|
325
|
+
#arch: aarch64
|
|
326
|
+
// x0 = future, x1 = refs (refs is at #116)
|
|
327
|
+
str w1, [x0, #116]
|
|
328
|
+
```
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Drop one reference to the future. At the last reference the future is
|
|
332
|
+
// freed along with the result slot, cancel flag, and packed task closure
|
|
333
|
+
// it owns. After a release the handle's fields are gone: zero them.
|
|
334
|
+
pub func future_release = (uint64 future) {
|
|
335
|
+
```
|
|
336
|
+
#arch: c
|
|
337
|
+
__nomen_future_release((struct nomen_future *)(void *)future);
|
|
338
|
+
```
|
|
339
|
+
```
|
|
340
|
+
#arch: aarch64
|
|
341
|
+
// x0 = future
|
|
342
|
+
bl ___nomen_future_release
|
|
343
|
+
```
|
|
344
|
+
}
|
|
345
|
+
|
|
247
346
|
// Release this handle's reference to the future. The future owns the
|
|
248
347
|
// result slot and cancel flag; it (and they) are freed only when the
|
|
249
348
|
// last reference — this handle, the running trampoline, or a tracking
|