blob: 1c5db5ddda52b3cdd9d6014341559aabb545113b (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
|
# Effects System in Neovere
## What Are Effects?
When Arvo processes an event via poke, it returns:
```
[effects new_core]
```
Where `effects` is a list of effects to execute in the outside world.
## Effect Structure
Each effect is a pair: `[wire card]`
- **Wire**: Path identifying the request (for response routing)
- **Card**: The actual effect to execute
Example effects:
```
[/d/term/1 [%blit [%lin [%& "hello"]]]] // Print "hello" to terminal
[/d/term/1 [%blit [%mor ...]]] // Multiple blits
[/g/http/0v123 [%http-response ...]] // HTTP response
[/a/peer/~zod [%send ...]] // Network packet
```
## Effect Types (Cards)
### Terminal (Dill)
- `%blit` - Print to terminal
- `%lin` - Line of styled text
- `%klr` - Colored/styled text
- `%mor` - Multiple blits
- `%hop` - Move cursor
- `%clr` - Clear screen
- `%logo` - Show logo/sigil
### HTTP (Eyre)
- `%http-response` - Send HTTP response
- `%response` - Generic response
### Network (Ames)
- `%send` - Send packet to another ship
- `%turf` - DNS/networking config
### Filesystem (Clay)
- `%ergo` - File change notification
- `%hill` - Mount/unmount
### Timer (Behn)
- `%doze` - Set timer
- `%wake` - Timer fired
## Our Approach: Eio-Based Effects Processing
### Architecture
```
┌─────────────────────────────────────────┐
│ Event Loop (Eio) │
│ - Read terminal input │
│ - Handle timers │
│ - Network I/O │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Poke Arvo with Event │
│ result = State.poke state event │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Parse Effects from Result │
│ match result with │
│ | Cell (effects, new_core) -> ... │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Route Effects to Drivers │
│ - Dill: terminal I/O │
│ - Eyre: HTTP server (future) │
│ - Ames: networking (future) │
└─────────────────────────────────────────┘
```
### Phase 1: Terminal (Dill) Driver
**Goal:** Boot a ship and see output in the terminal
**What we need:**
1. Parse `%blit` effects
2. Render styled text to ANSI terminal
3. Read keyboard input (belt events)
4. Poke Arvo with belt events
5. Display blit effects
**Implementation:**
```ocaml
(* lib/effects.ml *)
module Effect = struct
type blit =
| Lin of string (* Simple line *)
| Klr of styled_text (* Colored text *)
| Mor of blit list (* Multiple *)
| Hop of int (* Cursor move *)
| Clr (* Clear screen *)
type card =
| Blit of blit
| Logo
| Unknown of Noun.noun
type t = {
wire: Noun.noun;
card: card;
}
let parse_effect noun = ...
let parse_effects effects_noun = ...
end
(* lib/dill.ml *)
module Dill = struct
let render_blit = function
| Effect.Lin text ->
Printf.printf "%s\n%!" text
| Effect.Klr styled ->
(* Convert to ANSI codes *)
render_styled styled
| Effect.Mor blits ->
List.iter render_blit blits
| Effect.Hop n ->
(* ANSI cursor move *)
...
| Effect.Clr ->
(* ANSI clear screen *)
Printf.printf "\x1b[2J\x1b[H%!"
let handle_effects effects =
effects
|> Effect.parse_effects
|> List.filter (fun e -> is_dill_wire e.wire)
|> List.iter (fun e ->
match e.card with
| Effect.Blit b -> render_blit b
| _ -> ())
end
```
### Phase 2: Event Loop with Eio
```ocaml
(* bin/neovere_live.ml *)
open Eio.Std
let run_ship env state =
let stdin = Eio.Stdenv.stdin env in
let clock = Eio.Stdenv.clock env in
(* Start terminal reader fiber *)
Fiber.both
(fun () ->
(* Read keyboard input *)
while true do
let line = Eio.Flow.read_line stdin in
let belt_event = Dill.make_belt_event line in
let effects = State.poke state belt_event in
Dill.handle_effects effects
done)
(fun () ->
(* Timer fiber *)
while true do
Eio.Time.sleep clock 1.0;
(* Handle doze/wake *)
done)
```
### Phase 3: Structured Concurrency
Eio's strength is **structured concurrency** - no callback hell!
```ocaml
let handle_http_request request =
(* Run in fiber, naturally concurrent *)
Switch.run @@ fun sw ->
(* Create ovum for HTTP request *)
let ovum = make_http_ovum request in
(* Poke Arvo *)
let effects = State.poke state ovum in
(* Parse effects, find HTTP response *)
match find_http_response effects with
| Some response -> send_response response
| None -> send_404 ()
let main env =
(* All concurrent activities in one place *)
Switch.run @@ fun sw ->
Fiber.all [
terminal_input_loop env sw;
timer_loop env sw;
http_server env sw;
ames_network env sw;
]
```
## Implementation Plan
### Sprint 1: Basic Effects ✓ (Next!)
- [ ] Create `lib/effects.ml` - effect parsing
- [ ] Parse `[effects new_core]` from poke result
- [ ] Parse individual effects `[wire card]`
- [ ] Parse blit cards
- [ ] Test by logging parsed effects
### Sprint 2: Dill Driver
- [ ] Create `lib/dill.ml` - terminal I/O
- [ ] Render %blit effects to terminal
- [ ] Handle styled text (ANSI codes)
- [ ] Read keyboard input
- [ ] Create belt events from input
- [ ] Round-trip: input → poke → effects → output
### Sprint 3: Event Loop
- [ ] Create `bin/neovere_live.ml` - interactive runtime
- [ ] Eio-based event loop
- [ ] Terminal input fiber
- [ ] Effects processing fiber
- [ ] Graceful shutdown
### Sprint 4: Full Terminal Experience
- [ ] Handle all blit types (hop, clr, etc.)
- [ ] Proper cursor control
- [ ] Line editing support
- [ ] History/readline integration
- [ ] Status line
## Testing Strategy
### Test 1: Parse Effects
```ocaml
(* After boot, send a test event and parse effects *)
let test_event = make_test_event "hello" in
let result = State.poke state test_event in
match result with
| Cell (effects, _) ->
let parsed = Effect.parse_effects effects in
List.iter (fun e ->
Printf.printf "Effect: wire=%s card=%s\n"
(show_wire e.wire)
(show_card e.card)
) parsed
```
### Test 2: Dill Roundtrip
```ocaml
(* Send belt, receive blit *)
let belt = Dill.make_belt_event "~zod/try/1" in
let result = State.poke state belt in
Dill.handle_effects result
(* Should print something to terminal! *)
```
### Test 3: Interactive Session
```bash
$ dune exec bin/neovere_live.exe zod
~zod:dojo> (add 2 2)
4
~zod:dojo> "hello world"
"hello world"
~zod:dojo>
```
## Eio Advantages for Urbit
1. **Structured Concurrency** - All fibers have clear lifetime
2. **Backpressure** - Natural flow control
3. **Cancellation** - Clean shutdown of all I/O
4. **Multicore** - Can use multiple cores for I/O
5. **Type Safety** - Eio ensures proper resource management
## Effect Wire Routing
Wires tell us where effects came from:
```
/d/term/1 -> Dill (terminal 1)
/g/http/... -> Eyre (HTTP)
/a/peer/~zod -> Ames (network to ~zod)
/c/sync/... -> Clay (filesystem)
/b/wait/... -> Behn (timers)
```
We route by examining the wire structure.
## Next Steps
1. **Now:** Create basic effect parsing
2. **Next:** Dill blit rendering
3. **Then:** Keyboard input + belt events
4. **Finally:** Full event loop with Eio
Let's start with `lib/effects.ml`!
|