nomen-lang 0.4.0 → 0.6.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/core/System/Arena.nm +6 -6
- package/core/System/Awaitable.nm +21 -0
- package/core/System/Buffer.nm +31 -15
- package/core/System/Channel.nm +82 -29
- package/core/System/ClassBuffer.nm +23 -9
- package/core/System/Controls/Button.nm +2 -2
- package/core/System/Controls/CheckBox.nm +2 -2
- package/core/System/Controls/Container.nm +3 -3
- package/core/System/Controls/TextBox.nm +2 -2
- package/core/System/Fiber.nm +201 -0
- package/core/System/Graph.nm +1 -1
- package/core/System/LinkedList.nm +1 -1
- package/core/System/List.nm +3 -3
- package/core/System/Map.nm +2 -2
- package/core/System/Mutex.nm +50 -54
- package/core/System/Nursery.nm +10 -8
- package/core/System/Option.nm +2 -2
- package/core/System/Random.nm +67 -0
- package/core/System/Result.nm +2 -2
- package/core/System/Set.nm +1 -1
- package/core/System/Spawnable.nm +18 -0
- package/core/System/Stream/File.nm +3 -3
- package/core/System/Stream/Http.nm +85 -436
- package/core/System/Stream/Tcp.nm +441 -0
- package/core/System/String.nm +7 -7
- package/core/System/StringBuilder.nm +1 -1
- package/core/System/Task.nm +145 -12
- package/core/System/Test.nm +20 -11
- package/core/System/Text/CharIndex.nm +8 -8
- package/core/System/Text/Chars.nm +1 -1
- package/core/System/Text/JsonTree.nm +1 -1
- package/core/System/Text/Regex.nm +122 -32
- package/core/System/Text/Utf8.nm +6 -6
- package/core/System/Thread.nm +199 -0
- package/core/System/Tree.nm +1 -1
- package/dist/index.mjs +19473 -13661
- package/package.json +1 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Fiber — a stackful coroutine (ASYNC.md Phase 1). Fibers multiplex
|
|
2
|
+
// over the worker pool's threads: a parked fiber frees its worker instead of
|
|
3
|
+
// blocking it, so thousands of waiting tasks cost stacks (~64 KB each), not
|
|
4
|
+
// threads (~MB each).
|
|
5
|
+
//
|
|
6
|
+
// `Fiber(fn(args)).start()` mirrors `Thread(fn(args)).start()` — same eager
|
|
7
|
+
// arg packing into a task closure, same future, same Task<T> handle — but
|
|
8
|
+
// the wrapped call runs on a fiber stack under the fiber scheduler. While
|
|
9
|
+
// it waits (Task.result/wait, Channel.receive), the fiber parks and the
|
|
10
|
+
// worker moves on; `Fiber.yield()` cooperatively hands the worker to the
|
|
11
|
+
// next runnable fiber.
|
|
12
|
+
//
|
|
13
|
+
// Like Thread, a Fiber construction is a real value: it can be stored and
|
|
14
|
+
// started later (`var f = Fiber(work(n))`, then `f.start()` or
|
|
15
|
+
// `f.start_on(buf)`), and destroying an unstarted Fiber is a programming
|
|
16
|
+
// error that #destroy reports and aborts on (must-start — the library
|
|
17
|
+
// pattern from ASYNC.md).
|
|
18
|
+
//
|
|
19
|
+
// `Fiber(fn(args)).start_on(buf)` runs the fiber on a caller-provided
|
|
20
|
+
// fixed-size array stack (>= 16 KB) — the embedded/no-heap path.
|
|
21
|
+
//
|
|
22
|
+
// `Fiber.set_cooperative(true)` (before the first spawn) runs fibers on the
|
|
23
|
+
// calling thread and never starts worker threads — a cooperative,
|
|
24
|
+
// single-threaded runtime for bare-metal targets.
|
|
25
|
+
//
|
|
26
|
+
// The constructor and `.start_on(buf)` are compiler-special-cased (they
|
|
27
|
+
// need the per-site spawn/trampoline machinery and the buffer's compile-time
|
|
28
|
+
// byte size); `.start()` is a regular method over the runtime seam — it
|
|
29
|
+
// reads the packed handles from the instance's fields and drives the
|
|
30
|
+
// scheduler. The runtime (scheduler, context switch) lives in the build's
|
|
31
|
+
// emitted companion C — see build_fiber_spawn.
|
|
32
|
+
|
|
33
|
+
// The launch surface's return type (ASYNC.md: the dependency is
|
|
34
|
+
// declared by the library file, not inferred from user-source tokens).
|
|
35
|
+
import Task
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A stackful coroutine: lightweight, cooperatively scheduled, parks instead of blocking
|
|
39
|
+
**/
|
|
40
|
+
pub class Fiber<T> : Sendable, Spawnable<T> {
|
|
41
|
+
// The construction hook (see Thread.nm): the deferred-call special form.
|
|
42
|
+
#spawn = () {
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The packed task closure (a `struct nomen_closure *`); owned by this
|
|
46
|
+
// value until start/start_on/nursery.start transfers it to the runtime.
|
|
47
|
+
pub var uint64 task = 0
|
|
48
|
+
// Result-slot / cancel-flag / future handles for the (single) launch.
|
|
49
|
+
pub var uint64 result_slot = 0
|
|
50
|
+
pub var uint64 cancel_flag = 0
|
|
51
|
+
pub var uint64 future = 0
|
|
52
|
+
// Must-start flag: set by .start() / .start_on(buf) / nursery.start.
|
|
53
|
+
pub var started = false
|
|
54
|
+
// LEXICAL NURSERY CAPTURE (ASYNC.md): the tracking slots of the
|
|
55
|
+
// `async` block that lexically encloses the CONSTRUCTION, or 0 when the
|
|
56
|
+
// construction is outside any block. `.start()` registers the future
|
|
57
|
+
// through these stored pointers — registration follows the scope that
|
|
58
|
+
// created the deferred call, not the one that happens to start it. See
|
|
59
|
+
// Thread.nm's matching fields.
|
|
60
|
+
pub var uint64 nursery_futures = 0
|
|
61
|
+
pub var uint64 nursery_count = 0
|
|
62
|
+
pub var uint64 nursery_cap = 0
|
|
63
|
+
|
|
64
|
+
// Start the deferred call on the fiber scheduler and yield the Task<T>
|
|
65
|
+
// handle. Same shape as Thread.start: consume the machinery, register
|
|
66
|
+
// with the construction-time nursery (if captured), transfer the handles
|
|
67
|
+
// out of the instance (started; #destroy no-ops), return the handle.
|
|
68
|
+
// While the fiber waits, it parks and frees its worker instead of
|
|
69
|
+
// blocking it.
|
|
70
|
+
pub func start = (ref self, move out Task<T>) {
|
|
71
|
+
```
|
|
72
|
+
#arch: c
|
|
73
|
+
struct nomen_future *_f = (struct nomen_future *)self->future;
|
|
74
|
+
_f->refs = self->nursery_futures ? 3 : 2;
|
|
75
|
+
__nomen_fiber_spawn((struct nomen_closure *)self->task, _f);
|
|
76
|
+
if (self->nursery_futures) {
|
|
77
|
+
__nomen_nursery_track((void **)self->nursery_futures, (int *)self->nursery_count, (int *)self->nursery_cap, _f);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
```
|
|
81
|
+
#arch: aarch64
|
|
82
|
+
// x19 = self. f = self->future (#32); nursery_futures at #48.
|
|
83
|
+
ldr x0, [x19, #32]
|
|
84
|
+
ldr x1, [x19, #48]
|
|
85
|
+
mov w2, #2
|
|
86
|
+
cbz x1, .Lnomen_fstart_refs
|
|
87
|
+
mov w2, #3
|
|
88
|
+
.Lnomen_fstart_refs:
|
|
89
|
+
str w2, [x0, #116]
|
|
90
|
+
// __nomen_fiber_spawn(self->task, f)
|
|
91
|
+
ldr x0, [x19, #8]
|
|
92
|
+
ldr x1, [x19, #32]
|
|
93
|
+
bl ___nomen_fiber_spawn
|
|
94
|
+
// if (self->nursery_futures) __nomen_nursery_track(futures, count, cap, f)
|
|
95
|
+
ldr x0, [x19, #48]
|
|
96
|
+
cbz x0, .Lnomen_fstart_notrack
|
|
97
|
+
ldr x1, [x19, #56]
|
|
98
|
+
ldr x2, [x19, #64]
|
|
99
|
+
ldr x3, [x19, #32]
|
|
100
|
+
bl ___nomen_nursery_track
|
|
101
|
+
.Lnomen_fstart_notrack:
|
|
102
|
+
```
|
|
103
|
+
var t = Task<T>()
|
|
104
|
+
t.result_slot = self.result_slot
|
|
105
|
+
t.cancel_flag = self.cancel_flag
|
|
106
|
+
t.future = self.future
|
|
107
|
+
// Transfer the handles out of the instance: the runtime owns
|
|
108
|
+
// everything now, and #destroy must not touch (or must-start-abort).
|
|
109
|
+
self.task = 0
|
|
110
|
+
self.future = 0
|
|
111
|
+
self.result_slot = 0
|
|
112
|
+
self.cancel_flag = 0
|
|
113
|
+
self.started = true
|
|
114
|
+
return t
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Run the deferred call fire-and-forget (the Spawnable daemon role).
|
|
118
|
+
// The fiber is launched exactly like start, but the handle is dropped
|
|
119
|
+
// immediately: the future's refs are released as the discarded Task is
|
|
120
|
+
// reclaimed, and the fiber completes into the void — nothing joins it.
|
|
121
|
+
pub func detach = (ref self) {
|
|
122
|
+
self.start()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Destroying an unstarted Fiber is a programming error (the wrapped
|
|
126
|
+
// call would silently never run) — report it and abort. A started
|
|
127
|
+
// Fiber owns nothing: the launch transferred the task closure, result
|
|
128
|
+
// slot, cancel flag, and future to the runtime, and cleared the fields.
|
|
129
|
+
pub func #destroy = () {
|
|
130
|
+
```
|
|
131
|
+
#arch: c
|
|
132
|
+
if (!self->started) {
|
|
133
|
+
__nomen_spawn_must_start_abort();
|
|
134
|
+
}
|
|
135
|
+
if (self->future) {
|
|
136
|
+
__nomen_future_release((struct nomen_future *)self->future);
|
|
137
|
+
self->future = 0;
|
|
138
|
+
self->result_slot = 0;
|
|
139
|
+
self->cancel_flag = 0;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
```
|
|
143
|
+
#arch: aarch64
|
|
144
|
+
// if (!self->started) __nomen_spawn_must_start_abort()
|
|
145
|
+
ldrb w0, [x19, #40]
|
|
146
|
+
cbnz w0, .Lfb_started
|
|
147
|
+
bl ___nomen_spawn_must_start_abort
|
|
148
|
+
.Lfb_started:
|
|
149
|
+
// if (self->future) { release it (an unstarted task's future is
|
|
150
|
+
// unshared — refs == 1 — so this tears down slot + flag + future);
|
|
151
|
+
// zero the fields }
|
|
152
|
+
ldr x0, [x19, #32]
|
|
153
|
+
cbz x0, .Lfb_done
|
|
154
|
+
bl ___nomen_future_release
|
|
155
|
+
str xzr, [x19, #32]
|
|
156
|
+
str xzr, [x19, #16]
|
|
157
|
+
str xzr, [x19, #24]
|
|
158
|
+
.Lfb_done:
|
|
159
|
+
```
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Hand the worker to the next runnable fiber. No-op outside a fiber.
|
|
163
|
+
pub func yield = () {
|
|
164
|
+
```
|
|
165
|
+
#arch: c
|
|
166
|
+
__nomen_fiber_yield();
|
|
167
|
+
```
|
|
168
|
+
```
|
|
169
|
+
#arch: aarch64
|
|
170
|
+
bl ___nomen_fiber_yield
|
|
171
|
+
```
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Is the calling code running inside a fiber? True when Fiber.yield or
|
|
175
|
+
// fiber parking would take effect.
|
|
176
|
+
pub func is_fiber = (out bool) {
|
|
177
|
+
```
|
|
178
|
+
#arch: c
|
|
179
|
+
return __nomen_fiber_is_active();
|
|
180
|
+
```
|
|
181
|
+
```
|
|
182
|
+
#arch: aarch64
|
|
183
|
+
bl ___nomen_fiber_is_active
|
|
184
|
+
```
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Run fibers on the calling thread and never start worker threads. Must
|
|
188
|
+
// be called before the first fiber spawn; intended for single-threaded
|
|
189
|
+
// (bare-metal) targets. In cooperative mode a fiber that parks on an
|
|
190
|
+
// event only another thread could fire will never resume.
|
|
191
|
+
pub func set_cooperative = (bool on) {
|
|
192
|
+
```
|
|
193
|
+
#arch: c
|
|
194
|
+
__nomen_fiber_set_cooperative(on ? 1 : 0);
|
|
195
|
+
```
|
|
196
|
+
```
|
|
197
|
+
#arch: aarch64
|
|
198
|
+
bl ___nomen_fiber_set_cooperative
|
|
199
|
+
```
|
|
200
|
+
}
|
|
201
|
+
}
|
package/core/System/Graph.nm
CHANGED
package/core/System/List.nm
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**/
|
|
4
4
|
pub struct List<T>: Viewable {
|
|
5
5
|
var length = 0
|
|
6
|
-
var
|
|
6
|
+
var items = Buffer<T>()
|
|
7
7
|
|
|
8
8
|
pub func push = (ref self, move T value) {
|
|
9
9
|
if self.length >= self.items.cap {
|
|
@@ -70,8 +70,8 @@ pub struct List<T>: Viewable {
|
|
|
70
70
|
// never re-checked, so it can't rely on check-time call-arg hoisting for
|
|
71
71
|
// struct element types.
|
|
72
72
|
pub func copy = (self, out List<T>) {
|
|
73
|
-
var
|
|
74
|
-
var
|
|
73
|
+
var dst = List<T>()
|
|
74
|
+
var i = 0
|
|
75
75
|
while i < self.length {
|
|
76
76
|
var T v = self.at(i)
|
|
77
77
|
dst.push(v)
|
package/core/System/Map.nm
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
**/
|
|
11
11
|
pub struct Map<TK: Hashable + Equatable, TV> {
|
|
12
12
|
var length = 0
|
|
13
|
-
var
|
|
14
|
-
var
|
|
13
|
+
var keys = Buffer<TK>()
|
|
14
|
+
var values = Buffer<TV>()
|
|
15
15
|
var used = Buffer<int>()
|
|
16
16
|
|
|
17
17
|
// Variadic-tuple constructor: Map<string, int>(["a", 1], ["b", 2]) or
|
package/core/System/Mutex.nm
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
// Default stance in Nomen is "no shared mutable state" — communicate by moving
|
|
4
4
|
// Sendable values (or via Channel<T>). Mutex exists for when you genuinely
|
|
5
5
|
// need shared mutability. See ASYNC.md.
|
|
6
|
+
//
|
|
7
|
+
// A fiber that would block on a contended lock parks instead of blocking its
|
|
8
|
+
// worker (both models), and unlock wakes the parked fibers. Cancellation
|
|
9
|
+
// while waiting never returns without the lock — a waiter keeps waiting and
|
|
10
|
+
// observes the cancel flag at its next checkpoint after acquiring — so a
|
|
11
|
+
// matching unlock is always sound.
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* A pthread-backed mutual-exclusion lock for protecting shared mutable state
|
|
@@ -11,68 +17,58 @@ pub class Mutex: Sendable {
|
|
|
11
17
|
pub var uint64 handle = 0
|
|
12
18
|
|
|
13
19
|
pub func #init = (self) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
```
|
|
25
|
-
#arch: aarch64
|
|
26
|
-
mov x0, #64
|
|
27
|
-
bl _malloc
|
|
28
|
-
str x0, [x19, #8]
|
|
29
|
-
mov x1, #0
|
|
30
|
-
bl _pthread_mutex_init
|
|
31
|
-
```
|
|
20
|
+
```
|
|
21
|
+
#arch: c
|
|
22
|
+
self->handle = (unsigned long long)__nomen_mutex_create();
|
|
23
|
+
```
|
|
24
|
+
```
|
|
25
|
+
#arch: aarch64
|
|
26
|
+
bl ___nomen_mutex_create
|
|
27
|
+
str x0, [x19, #8]
|
|
28
|
+
```
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
pub func lock = (self) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
```
|
|
33
|
+
#arch: c
|
|
34
|
+
// Fibers park instead of blocking the worker (both models); other
|
|
35
|
+
// contexts take the pthread lock directly.
|
|
36
|
+
__nomen_mutex_lock((void *)self->handle);
|
|
37
|
+
```
|
|
38
|
+
```
|
|
39
|
+
#arch: aarch64
|
|
40
|
+
ldr x0, [x19, #8]
|
|
41
|
+
bl ___nomen_mutex_lock
|
|
42
|
+
```
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
pub func unlock = (self) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
```
|
|
47
|
+
#arch: c
|
|
48
|
+
__nomen_mutex_unlock_wake((void *)self->handle);
|
|
49
|
+
```
|
|
50
|
+
```
|
|
51
|
+
#arch: aarch64
|
|
52
|
+
ldr x0, [x19, #8]
|
|
53
|
+
bl ___nomen_mutex_unlock_wake
|
|
54
|
+
```
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
pub func #destroy = () {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
str xzr, [x19, #8]
|
|
75
|
-
.Lmutex_destroy_end:
|
|
76
|
-
```
|
|
58
|
+
```
|
|
59
|
+
#arch: c
|
|
60
|
+
if (self->handle) {
|
|
61
|
+
__nomen_mutex_dispose((void *)self->handle);
|
|
62
|
+
self->handle = 0;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
```
|
|
66
|
+
#arch: aarch64
|
|
67
|
+
ldr x0, [x19, #8]
|
|
68
|
+
cbz x0, .Lmutex_destroy_end
|
|
69
|
+
bl ___nomen_mutex_dispose
|
|
70
|
+
str xzr, [x19, #8]
|
|
71
|
+
.Lmutex_destroy_end:
|
|
72
|
+
```
|
|
77
73
|
}
|
|
78
74
|
}
|
package/core/System/Nursery.nm
CHANGED
|
@@ -5,18 +5,19 @@
|
|
|
5
5
|
// An `async` block names its nursery (`async pool { }`), binding a Nursery
|
|
6
6
|
// variable in scope. That variable is passed to helper functions with `ref`
|
|
7
7
|
// and is the receiver of `pool.spawn(fn(args))`. The Nursery wraps the
|
|
8
|
-
// per-invocation tracking state of the `async { }` block —
|
|
9
|
-
// futures
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
8
|
+
// per-invocation tracking state of the `async { }` block — pointers to the
|
|
9
|
+
// futures list's storage slot, the registered count, and the capacity (the
|
|
10
|
+
// list starts empty and grows by realloc; the compiler's registration helper
|
|
11
|
+
// writes through these pointers) — so it is only valid for the lifetime of
|
|
12
|
+
// its enclosing block (which is exactly the structured-concurrency
|
|
13
|
+
// invariant: the block cannot exit until every spawned task, including those
|
|
14
|
+
// spawned through a passed Nursery, has finished).
|
|
14
15
|
//
|
|
15
16
|
// Config rides on the declaration: `async pool = Nursery(timeout: 2000) { }`.
|
|
16
17
|
//
|
|
17
18
|
// `name.spawn(fn(args))` is special-cased by the compiler (it needs the spawn
|
|
18
|
-
// trampoline machinery), so it is NOT a regular method declared here. The
|
|
19
|
-
// fields are the full runtime contract.
|
|
19
|
+
// trampoline machinery), so it is NOT a regular method declared here. The
|
|
20
|
+
// three fields are the full runtime contract.
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* The capability for spawning into a caller's `async` block; owns and tracks its tasks
|
|
@@ -24,4 +25,5 @@
|
|
|
24
25
|
pub struct Nursery: Sendable {
|
|
25
26
|
pub var uint64 futures_ptr = 0
|
|
26
27
|
pub var uint64 count_ptr = 0
|
|
28
|
+
pub var uint64 cap_ptr = 0
|
|
27
29
|
}
|
package/core/System/Option.nm
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Random — a small, fast pseudo-random generator (splitmix64).
|
|
2
|
+
//
|
|
3
|
+
// Splitmix64 steps a 64-bit state by the golden ratio, then runs two
|
|
4
|
+
// multiply-xor rounds. Streams from nearby seeds are fully decorrelated
|
|
5
|
+
// (each seed is its own stream), so `Random()` — seeded from the wall
|
|
6
|
+
// clock — is safe to construct repeatedly in quick succession (e.g. one
|
|
7
|
+
// per spawned task, all starting within the same microsecond).
|
|
8
|
+
//
|
|
9
|
+
// Not cryptographically secure; `below`/`range` have slight modulo bias
|
|
10
|
+
// for very large bounds.
|
|
11
|
+
//
|
|
12
|
+
// var Random rng = Random()
|
|
13
|
+
// var uint64 roll = rng.below(6) // 0..5
|
|
14
|
+
// var uint64 ms = rng.range(1000, 10000) // 1000..10000, inclusive
|
|
15
|
+
//
|
|
16
|
+
import Time
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A fast splitmix64 pseudo-random number generator
|
|
20
|
+
**/
|
|
21
|
+
pub class Random {
|
|
22
|
+
// The raw 64-bit generator state (seeded from the wall clock by the
|
|
23
|
+
// no-arg constructor, or an explicit seed for reproducible sequences).
|
|
24
|
+
pub var uint64 state = 0
|
|
25
|
+
|
|
26
|
+
// Seed from the wall clock (nanoseconds since the epoch).
|
|
27
|
+
pub func #init = (self) {
|
|
28
|
+
self.state = Time.now_ns()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Seed from an explicit value — reproducible sequences for tests.
|
|
32
|
+
pub func #init = (self, uint64 seed) {
|
|
33
|
+
self.state = seed
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The next 64-bit value (splitmix64: golden-ratio step, then two
|
|
37
|
+
// multiply-xor rounds). Pure Nomen — the 64-bit arithmetic, bitwise
|
|
38
|
+
// operators, and hex literals all lower directly. The aarch64 codegen is
|
|
39
|
+
// a little looser than a hand-written `#arch` body would be (constant
|
|
40
|
+
// materialization, field reloads, non-immediate shift counts); see
|
|
41
|
+
// FOLLOWUP.md if this ever needs to be hot.
|
|
42
|
+
pub func next = (ref self, out uint64) {
|
|
43
|
+
self.state = self.state + 0x9E3779B97F4A7C15
|
|
44
|
+
var uint64 z = self.state
|
|
45
|
+
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9
|
|
46
|
+
z = (z ^ (z >> 27)) * 0x94D049BB133111EB
|
|
47
|
+
return z ^ (z >> 31)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The next value in [0, bound). A bound of 0 returns 0. Slight modulo
|
|
51
|
+
// bias for very large bounds — irrelevant at demo scale.
|
|
52
|
+
pub func below = (ref self, uint64 bound, out uint64) {
|
|
53
|
+
if bound == 0 {
|
|
54
|
+
return 0
|
|
55
|
+
}
|
|
56
|
+
return self.next() % bound
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The next value in [lo, hi], both ends inclusive. A hi <= lo
|
|
60
|
+
// returns lo.
|
|
61
|
+
pub func range = (ref self, uint64 lo, uint64 hi, out uint64) {
|
|
62
|
+
if hi <= lo {
|
|
63
|
+
return lo
|
|
64
|
+
}
|
|
65
|
+
return lo + self.below(hi - lo + 1)
|
|
66
|
+
}
|
|
67
|
+
}
|
package/core/System/Result.nm
CHANGED
package/core/System/Set.nm
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Spawnable — the trait role of a deferred-work LAUNCHER (the C# shape;
|
|
2
|
+
// see docs/ASYNC.md, "Design decisions"). A Spawnable class packs a deferred unit of work
|
|
3
|
+
// (the construction special form) and its `start` submits it, yielding the
|
|
4
|
+
// library-owned `Task<T>` handle — so the await half is guaranteed by the
|
|
5
|
+
// return type and each type carries exactly one role: `Thread<T>`/`Fiber<T>`
|
|
6
|
+
// are spawnable, `Task<T>` is awaitable. `Awaitable` stays non-generic (its
|
|
7
|
+
// `wait` does not mention T); `Spawnable` is generic because `start`'s
|
|
8
|
+
// return type does.
|
|
9
|
+
//
|
|
10
|
+
// `detach` is the daemon role: run the work unjoinable, statement only.
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A class that can launch a deferred call and hand back its Task<T> handle
|
|
14
|
+
**/
|
|
15
|
+
pub trait Spawnable<T> {
|
|
16
|
+
func start = (ref self, move out Task<T>)
|
|
17
|
+
func detach = (ref self)
|
|
18
|
+
}
|
|
@@ -39,7 +39,7 @@ pub struct File {
|
|
|
39
39
|
pub var eof = false
|
|
40
40
|
// errno of the last operation; 0 = success. Raw #arch bodies set it,
|
|
41
41
|
// the Result-returning wrappers map it to FileError.
|
|
42
|
-
var
|
|
42
|
+
var error = 0
|
|
43
43
|
|
|
44
44
|
// Maps an errno to its FileError case.
|
|
45
45
|
func error_of = (int err, out FileError) {
|
|
@@ -569,7 +569,7 @@ pub struct File {
|
|
|
569
569
|
// (static) Read the entire contents of `path` in one open/read/close.
|
|
570
570
|
pub func read_all = (string path, out Result<string, FileError>) {
|
|
571
571
|
var Result<string, FileError> result = .error(FileError.other)
|
|
572
|
-
var
|
|
572
|
+
var f = File()
|
|
573
573
|
match f.open(path, "r") {
|
|
574
574
|
case .ok(did) {
|
|
575
575
|
match f.readAll() {
|
|
@@ -594,7 +594,7 @@ pub struct File {
|
|
|
594
594
|
// be deliberate).
|
|
595
595
|
pub func write_all = (string path, string data, out Result<bool, FileError>) {
|
|
596
596
|
var Result<bool, FileError> result = .error(FileError.other)
|
|
597
|
-
var
|
|
597
|
+
var f = File()
|
|
598
598
|
match f.open(path, "w") {
|
|
599
599
|
case .ok(did) {
|
|
600
600
|
match f.writeAll(data) {
|