about summary refs log tree commit diff stats
path: root/shell/global.mu
blob: f6a779f93389b8ac26e6356f68c5527cc701f722 (plain) (blame)
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
type global-table {
  data: (handle array global)
  final-index: int
  render-list: (handle array int)  # sequence of globals to render on the left side
                                   # no pagination or scrolling
}

type global {
  name: (handle array byte)
  input: (handle gap-buffer)
  value: (handle cell)
  trace: (handle trace)
}

fn initialize-globals _self: (addr global-table) {
  var self/esi: (addr global-table) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "initialize globals"
    return
  }
  var data-ah/eax: (addr handle array global) <- get self, data
  populate data-ah, 0x80
  initialize-primitives self
  var render-ah/eax: (addr handle array int) <- get self, render-list
  populate render-ah, 0x20/render-size
}

fn load-globals in: (addr handle cell), self: (addr global-table) {
  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "loading globals:", 3/fg, 0/bg
  var remaining-ah/esi: (addr handle cell) <- copy in
  {
    var _remaining/eax: (addr cell) <- lookup *remaining-ah
    var remaining/ebx: (addr cell) <- copy _remaining
    var done?/eax: boolean <- nil? remaining
    compare done?, 0/false
    break-if-!=
    var curr-ah/eax: (addr handle cell) <- get remaining, left
    var _curr/eax: (addr cell) <- lookup *curr-ah
    var curr/ecx: (addr cell) <- copy _curr
    remaining-ah <- get remaining, right
    var name-ah/eax: (addr handle cell) <- get curr, left
    var name/eax: (addr cell) <- lookup *name-ah
    var name-data-ah/eax: (addr handle stream byte) <- get name, text-data
    var _name-data/eax: (addr stream byte) <- lookup *name-data-ah
    var name-data/edx: (addr stream byte) <- copy _name-data
    rewind-stream name-data
    draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, " ", 3/fg 0/bg
    draw-stream-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, name-data, 3/fg, 0/bg
    var value-ah/eax: (addr handle cell) <- get curr, right
    var value/eax: (addr cell) <- lookup *value-ah
    var value-data-ah/eax: (addr handle stream byte) <- get value, text-data
    var _value-data/eax: (addr stream byte) <- lookup *value-data-ah
    var value-data/ecx: (addr stream byte) <- copy _value-data
    var value-gap-buffer-storage: (handle gap-buffer)
    var value-gap-buffer-ah/edi: (addr handle gap-buffer) <- address value-gap-buffer-storage
    allocate value-gap-buffer-ah
    var value-gap-buffer/eax: (addr gap-buffer) <- lookup *value-gap-buffer-ah
    initialize-gap-buffer value-gap-buffer, 0x40000/256KB
    load-gap-buffer-from-stream value-gap-buffer, value-data
    load-lexical-scope value-gap-buffer-ah, self
    loop
  }
  move-cursor-to-left-margin-of-next-line 0/screen
}

fn write-globals out: (addr stream byte), _self: (addr global-table) {
  var self/esi: (addr global-table) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "write globals"
    return
  }
  write out, "  (globals . (\n"
  var data-ah/eax: (addr handle array global) <- get self, data
  var data/eax: (addr array global) <- lookup *data-ah
  var final-index/edx: (addr int) <- get self, final-index
  var curr-index/ecx: int <- copy 1/skip-0
  {
    compare curr-index, *final-index
    break-if->
    var curr-offset/ebx: (offset global) <- compute-offset data, curr-index
    var curr/ebx: (addr global) <- index data, curr-offset
    var curr-value-ah/edx: (addr handle cell) <- get curr, value
    var curr-value/eax: (addr cell) <- lookup *curr-value-ah
    var curr-type/eax: (addr int) <- get curr-value, type
    {
      compare *curr-type, 4/primitive-function
      break-if-=
      compare *curr-type, 5/screen
      break-if-=
      compare *curr-type, 6/keyboard
      break-if-=
      write out, "    ("
      var curr-name-ah/eax: (addr handle array byte) <- get curr, name
      var curr-name/eax: (addr array byte) <- lookup *curr-name-ah
      write out, curr-name
      write out, " . ["
      var curr-input-ah/eax: (addr handle gap-buffer) <- get curr, input
      var curr-input/eax: (addr gap-buffer) <- lookup *curr-input-ah
      {
        compare curr-input, 0
        break-if-!=
        abort "null gap buffer"
      }
      append-gap-buffer curr-input, out
      write out, "])\n"
    }
    curr-index <- increment
    loop
  }
  write out, "  ))\n"
}

# globals layout: 1 char padding, 41 code, 1 padding, 41 code, 1 padding = 85 chars
fn render-globals screen: (addr screen), _self: (addr global-table), show-cursor?: boolean {
  clear-rect screen, 0/xmin, 0/ymin, 0x55/xmax, 0x2f/ymax=screen-height-without-menu, 0xdc/bg=green-bg
  var self/esi: (addr global-table) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "render globals"
    return
  }
  var data-ah/eax: (addr handle array global) <- get self, data
  var _data/eax: (addr array global) <- lookup *data-ah
  var data/ebx: (addr array global) <- copy _data
  var curr-index/edx: int <- copy 0
  var y1: int
  copy-to y1, 1/padding-top
  var y2: int
  copy-to y2, 1/padding-top
  $render-globals:loop: {
    compare curr-index, 0x20/render-size
    break-if->=
    {
      compare y1, 0x2f/ymax
      break-if-<
      compare y2, 0x2f/ymax
      break-if-<
      break $render-globals:loop
    }
    $render-globals:iter: {
      var cursor-in-current-line?: boolean
      {
        compare show-cursor?, 0/false
        break-if-=
        compare curr-index, 0
        break-if-!=
        copy-to cursor-in-current-line?, 1/true
      }
      var render-list-ah/eax: (addr handle array int) <- get self, render-list
      var render-list/eax: (addr array int) <- lookup *render-list-ah
      var global-id-a/eax: (addr int) <- index render-list, curr-index
      var global-id/eax: int <- copy *global-id-a
      compare global-id, 0
      break-if-=
      var global-offset/edx: (offset global) <- compute-offset data, global-id
      var curr/edx: (addr global) <- index data, global-offset
      {
        var render-image?/eax: boolean <- render-image? curr, cursor-in-current-line?
        compare render-image?, 0/false
        break-if-=
        var y/ecx: int <- render-image-definition screen, curr, 0/x y1
        y <- increment  # padding
        copy-to y1, y
        break $render-globals:iter
      }
      var curr-input-ah/eax: (addr handle gap-buffer) <- get curr, input
      var _curr-input/eax: (addr gap-buffer) <- lookup *curr-input-ah
      var curr-input/ebx: (addr gap-buffer) <- copy _curr-input
      compare curr-input, 0
      break-if-=
      var curr-trace-ah/eax: (addr handle trace) <- get curr, trace
      var _curr-trace/eax: (addr trace) <- lookup *curr-trace-ah
      var curr-trace/edx: (addr trace) <- copy _curr-trace
      $render-globals:render-global: {
        var x/eax: int <- copy 0
        var y/ecx: int <- copy y1
        compare y, y2
        {
          break-if->=
          x, y <- render-gap-buffer-wrapping-right-then-down screen, curr-input, 1/padding-left, y1, 0x2a/xmax, 0x2f/ymax, cursor-in-current-line?, 7/fg=definition, 0xc5/bg=blue-bg
          y <- increment
          y <- render-trace screen, curr-trace, 1/padding-left, y, 0x2a/xmax, 0x2f/ymax, 0/no-cursor
          y <- increment
          copy-to y1, y
          break $render-globals:render-global
        }
        x, y <- render-gap-buffer-wrapping-right-then-down screen, curr-input, 0x2b/xmin, y2, 0x54/xmax, 0x2f/ymax, cursor-in-current-line?, 7/fg=definition, 0xc5/bg=blue-bg
        y <- increment
        y <- render-trace screen, curr-trace, 0x2b/xmin, y, 0x54/xmax, 0x2f/ymax, 0/no-cursor
        y <- increment
        copy-to y2, y
      }
    }
    curr-index <- increment
    loop
  }
  # render primitives on top
  render-primitives screen, 1/xmin=padding-left, 0x55/xmax, 0x2f/ymax
}

fn render-globals-menu screen: (addr screen), _self: (addr global-table) {
  var _width/eax: int <- copy 0
  var height/ecx: int <- copy 0
  _width, height <- screen-size screen
  var width/edx: int <- copy _width
  var y/ecx: int <- copy height
  y <- decrement
  var height/ebx: int <- copy y
  height <- increment
  clear-rect screen, 0/x, y, width, height, 0xc5/bg=blue-bg
  set-cursor-position screen, 0/x, y
  draw-text-rightward-from-cursor screen, " ^r ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " run main  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^s ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " run sandbox  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^g ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " go to  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^a ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " <<  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^b ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " <word  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^f ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " word>  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^e ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " >>  ", width, 7/fg, 0xc5/bg=blue-bg
}

fn edit-globals _self: (addr global-table), key: grapheme {
  var self/esi: (addr global-table) <- copy _self
  # ctrl-s
  {
    compare key, 0x13/ctrl-s
    break-if-!=
    #
    refresh-cursor-definition self
    return
  }
  var cursor-index/ecx: int <- cursor-global self
  compare cursor-index, 0
  {
    break-if-!=
    return
  }
  var data-ah/eax: (addr handle array global) <- get self, data
  var data/eax: (addr array global) <- lookup *data-ah
  var cursor-offset/ecx: (offset global) <- compute-offset data, cursor-index
  var curr-global/eax: (addr global) <- index data, cursor-offset
  var curr-editor-ah/eax: (addr handle gap-buffer) <- get curr-global, input
  var curr-editor/eax: (addr gap-buffer) <- lookup *curr-editor-ah
  edit-gap-buffer curr-editor, key
}

fn create-empty-global _self: (addr global-table), name-stream: (addr stream byte), capacity: int {
  var self/esi: (addr global-table) <- copy _self
  var final-index-addr/ecx: (addr int) <- get self, final-index
  increment *final-index-addr
  var render-list-ah/eax: (addr handle array int) <- get self, render-list
  var render-list/eax: (addr array int) <- lookup *render-list-ah
  slide-down render-list, 0/start 0x1f/penultimate, 1/target
  var curr-index/ecx: int <- copy *final-index-addr
  var dest/eax: (addr int) <- index render-list, 0
  copy-to *dest, curr-index
  var data-ah/eax: (addr handle array global) <- get self, data
  var data/eax: (addr array global) <- lookup *data-ah
  var curr-offset/ecx: (offset global) <- compute-offset data, curr-index
  var curr/esi: (addr global) <- index data, curr-offset
  var curr-name-ah/eax: (addr handle array byte) <- get curr, name
  stream-to-array name-stream, curr-name-ah
  var curr-input-ah/eax: (addr handle gap-buffer) <- get curr, input
  allocate curr-input-ah
  var curr-input/eax: (addr gap-buffer) <- lookup *curr-input-ah
  initialize-gap-buffer curr-input, capacity
  var trace-ah/eax: (addr handle trace) <- get curr, trace
  allocate trace-ah
  var trace/eax: (addr trace) <- lookup *trace-ah
  initialize-trace trace, 1/only-errors, 0x10/capacity, 0/visible
}

fn refresh-cursor-definition _self: (addr global-table) {
  var self/esi: (addr global-table) <- copy _self
  var render-list-ah/eax: (addr handle array int) <- get self, render-list
  var render-list/eax: (addr array int) <- lookup *render-list-ah
  var cursor-index/edx: (addr int) <- index render-list, 0
  refresh-definition self, *cursor-index
}

fn refresh-definition _self: (addr global-table), _index: int {
  var self/esi: (addr global-table) <- copy _self
  var data-ah/eax: (addr handle array global) <- get self, data
  var data/eax: (addr array global) <- lookup *data-ah
  var index/ebx: int <- copy _index
  var offset/ebx: (offset global) <- compute-offset data, index
  var curr-global/ebx: (addr global) <- index data, offset
  var curr-input-ah/edx: (addr handle gap-buffer) <- get curr-global, input
  var curr-trace-ah/eax: (addr handle trace) <- get curr-global, trace
  var curr-trace/eax: (addr trace) <- lookup *curr-trace-ah
  clear-trace curr-trace
  var curr-value-ah/edi: (addr handle cell) <- get curr-global, value
  var definitions-created-storage: (stream int 0x10)
  var definitions-created/ecx: (addr stream int) <- address definitions-created-storage
  read-and-evaluate-and-save-gap-buffer-to-globals curr-input-ah, curr-value-ah, self, definitions-created, curr-trace, 0/no-screen, 0/no-keyboard
}

fn assign-or-create-global _self: (addr global-table), name: (addr array byte), value: (handle cell), index-updated: (addr int), trace: (addr trace) {
  var self/esi: (addr global-table) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "assign global"
  }
  var curr-index/ecx: int <- find-symbol-name-in-globals self, name
  {
    compare curr-index, -1/not-found
    break-if-!=
    var final-index-addr/eax: (addr int) <- get self, final-index
    increment *final-index-addr
    curr-index <- copy *final-index-addr
    var arr-ah/eax: (addr handle array int) <- get self, render-list
    var arr/eax: (addr array int) <- lookup *arr-ah
    slide-down arr, 0/start 0x1e/penultimate, 1/target
    var dest/eax: (addr int) <- index arr, 0
    copy-to *dest, curr-index
  }
  var data-ah/eax: (addr handle array global) <- get self, data
  var data/eax: (addr array global) <- lookup *data-ah
  var curr-offset/esi: (offset global) <- compute-offset data, curr-index
  var curr/esi: (addr global) <- index data, curr-offset
  var curr-name-ah/eax: (addr handle array byte) <- get curr, name
  copy-array-object name, curr-name-ah
  var curr-value-ah/eax: (addr handle cell) <- get curr, value
  copy-handle value, curr-value-ah
  var index-updated/edi: (addr int) <- copy index-updated
  copy-to *index-updated, curr-index
  var trace-ah/eax: (addr handle trace) <- get curr, trace
  allocate trace-ah
  var trace/eax: (addr trace) <- lookup *trace-ah
  initialize-trace trace, 1/only-errors, 0x10/capacity, 0/visible
}

fn bump-global _globals: (addr global-table), global-id: int {
  var globals/esi: (addr global-table) <- copy _globals
  var render-list-ah/eax: (addr handle array int) <- get globals, render-list
  var render-list/eax: (addr array int) <- lookup *render-list-ah
  var idx/ecx: int <- find-slide-down-slot-in-array render-list, global-id
  {
    compare idx, 0
    break-if-!=
    return
  }
  slide-down render-list, 0/start idx, 1/target
  var dest/eax: (addr int) <- index render-list, 0
  var val/ecx: int <- copy global-id
  copy-to *dest, val
}

fn cursor-global _globals: (addr global-table) -> _/ecx: int {
  var globals/esi: (addr global-table) <- copy _globals
  var render-list-ah/eax: (addr handle array int) <- get globals, render-list
  var render-list/eax: (addr array int) <- lookup *render-list-ah
  var dest/eax: (addr int) <- index render-list, 0
  return *dest
}

fn lookup-symbol-in-globals _sym: (addr cell), out: (addr handle cell), _globals: (addr global-table), trace: (addr trace), inner-screen-var: (addr handle cell), inner-keyboard-var: (addr handle cell) {
  var sym/eax: (addr cell) <- copy _sym
  var sym-name-ah/eax: (addr handle stream byte) <- get sym, text-data
  var _sym-name/eax: (addr stream byte) <- lookup *sym-name-ah
  var sym-name/edx: (addr stream byte) <- copy _sym-name
  var globals/esi: (addr global-table) <- copy _globals
  {
    compare globals, 0
    break-if-=
    var curr-index/ecx: int <- find-symbol-in-globals globals, sym-name
    compare curr-index, -1/not-found
    break-if-=
    var global-data-ah/eax: (addr handle array global) <- get globals, data
    var global-data/eax: (addr array global) <- lookup *global-data-ah
    var curr-offset/ebx: (offset global) <- compute-offset global-data, curr-index
    var curr/ebx: (addr global) <- index global-data, curr-offset
    var curr-value/eax: (addr handle cell) <- get curr, value
    copy-object curr-value, out
    return
  }
  # if sym is "screen" and inner-screen-var exists, return it
  {
    var sym-is-screen?/eax: boolean <- stream-data-equal? sym-name, "screen"
    compare sym-is-screen?, 0/false
    break-if-=
    compare inner-screen-var, 0
    break-if-=
    copy-object inner-screen-var, out
    return
  }
  # if sym is "keyboard" and inner-keyboard-var exists, return it
  {
    var sym-is-keyboard?/eax: boolean <- stream-data-equal? sym-name, "keyboard"
    compare sym-is-keyboard?, 0/false
    break-if-=
    compare inner-keyboard-var, 0
    break-if-=
    copy-object inner-keyboard-var, out
    return
  }
  # otherwise error "unbound symbol: ", sym
  var stream-storage: (stream byte 0x40)
  var stream/ecx: (addr stream byte) <- address stream-storage
  write stream, "unbound symbol: "
  rewind-stream sym-name
  write-stream stream, sym-name
  error-stream trace, stream
}

fn maybe-lookup-symbol-in-globals _sym: (addr cell), out: (addr handle cell), _globals: (addr global-table), trace: (addr trace) {
  var sym/eax: (addr cell) <- copy _sym
  var sym-name-ah/eax: (addr handle stream byte) <- get sym, text-data
  var _sym-name/eax: (addr stream byte) <- lookup *sym-name-ah
  var sym-name/edx: (addr stream byte) <- copy _sym-name
  var globals/esi: (addr global-table) <- copy _globals
  {
    compare globals, 0
    break-if-=
    var curr-index/ecx: int <- find-symbol-in-globals globals, sym-name
    compare curr-index, -1/not-found
    break-if-=
    var global-data-ah/eax: (addr handle array global) <- get globals, data
    var global-data/eax: (addr array global) <- lookup *global-data-ah
    var curr-offset/ebx: (offset global) <- compute-offset global-data, curr-index
    var curr/ebx: (addr global) <- index global-data, curr-offset
    var curr-value/eax: (addr handle cell) <- get curr, value
    copy-object curr-value, out
    return
  }
}

# return the index in globals containing 'sym'
# or -1 if not found
fn find-symbol-in-globals _globals: (addr global-table), sym-name: (addr stream byte) -> _/ecx: int {
  var globals/esi: (addr global-table) <- copy _globals
  compare globals, 0
  {
    break-if-!=
    return -1/not-found
  }
  var global-data-ah/eax: (addr handle array global) <- get globals, data
  var global-data/eax: (addr array global) <- lookup *global-data-ah
  var final-index/ecx: (addr int) <- get globals, final-index
  var curr-index/ecx: int <- copy *final-index
  {
    compare curr-index, 0
    break-if-<
    var curr-offset/ebx: (offset global) <- compute-offset global-data, curr-index
    var curr/ebx: (addr global) <- index global-data, curr-offset
    var curr-name-ah/eax: (addr handle array byte) <- get curr, name
    var curr-name/eax: (addr array byte) <- lookup *curr-name-ah
    var found?/eax: boolean <- stream-data-equal? sym-name, curr-name
    compare found?, 0/false
    {
      break-if-=
      return curr-index
    }
    curr-index <- decrement
    loop
  }
  return -1/not-found
}

# return the index in globals containing 'sym'
# or -1 if not found
fn find-symbol-name-in-globals _globals: (addr global-table), sym-name: (addr array byte) -> _/ecx: int {
  var globals/esi: (addr global-table) <- copy _globals
  compare globals, 0
  {
    break-if-!=
    return -1/not-found
  }
  var global-data-ah/eax: (addr handle array global) <- get globals, data
  var global-data/eax: (addr array global) <- lookup *global-data-ah
  var final-index/ecx: (addr int) <- get globals, final-index
  var curr-index/ecx: int <- copy *final-index
  {
    compare curr-index, 0
    break-if-<
    var curr-offset/ebx: (offset global) <- compute-offset global-data, curr-index
    var curr/ebx: (addr global) <- index global-data, curr-offset
    var curr-name-ah/eax: (addr handle array byte) <- get curr, name
    var curr-name/eax: (addr array byte) <- lookup *curr-name-ah
    var found?/eax: boolean <- string-equal? sym-name, curr-name
    compare found?, 0/false
    {
      break-if-=
      return curr-index
    }
    curr-index <- decrement
    loop
  }
  return -1/not-found
}

fn mutate-binding-in-globals name: (addr stream byte), val: (addr handle cell), _globals: (addr global-table), trace: (addr trace) {
  var globals/esi: (addr global-table) <- copy _globals
  {
    compare globals, 0
    break-if-=
    var curr-index/ecx: int <- find-symbol-in-globals globals, name
    compare curr-index, -1/not-found
    break-if-=
    var global-data-ah/eax: (addr handle array global) <- get globals, data
    var global-data/eax: (addr array global) <- lookup *global-data-ah
    var curr-offset/ebx: (offset global) <- compute-offset global-data, curr-index
    var curr/ebx: (addr global) <- index global-data, curr-offset
    var dest/eax: (addr handle cell) <- get curr, value
    copy-object val, dest
    return
  }
  # otherwise error "unbound symbol: ", sym
  var stream-storage: (stream byte 0x40)
  var stream/ecx: (addr stream byte) <- address stream-storage
  write stream, "unbound symbol: "
  rewind-stream name
  write-stream stream, name
  error-stream trace, stream
}

fn stash-gap-buffer-to-globals _globals: (addr global-table), definitions: (addr stream int), gap: (addr handle gap-buffer) {
  var globals/eax: (addr global-table) <- copy _globals
  compare globals, 0
  {
    break-if-!=
    return
  }
  var global-data-ah/eax: (addr handle array global) <- get globals, data
  var global-data/eax: (addr array global) <- lookup *global-data-ah
  rewind-stream definitions
  {
    {
      var done?/eax: boolean <- stream-empty? definitions
      compare done?, 0/false
    }
    break-if-!=
    var index: int
    var index-addr/ecx: (addr int) <- address index
    read-from-stream definitions, index-addr
    var index/ecx: int <- copy *index-addr
    var offset/ebx: (offset global) <- compute-offset global-data, index
    var dest-global/eax: (addr global) <- index global-data, offset
    var dest-ah/eax: (addr handle gap-buffer) <- get dest-global, input
    copy-object gap, dest-ah
    loop
  }
}

# load all bindings in a single lexical scope, aka gap buffer of the environment, aka file of the file system
fn load-lexical-scope in-ah: (addr handle gap-buffer), _globals: (addr global-table) {
  var globals/esi: (addr global-table) <- copy _globals
  var definitions-created-storage: (stream int 0x10)
  var definitions-created/ebx: (addr stream int) <- address definitions-created-storage
  var trace-h: (handle trace)
  var trace-ah/edx: (addr handle trace) <- address trace-h
  allocate trace-ah
  var trace/eax: (addr trace) <- lookup *trace-ah
  initialize-trace trace, 1/only-errors, 0x10/capacity, 0/visible
  var dummy-result-h: (handle cell)
  var dummy-result-ah/ecx: (addr handle cell) <- address dummy-result-h
  read-and-evaluate-and-save-gap-buffer-to-globals in-ah, dummy-result-ah, globals, definitions-created, trace, 0/no-inner-screen-var, 0/no-inner-keyboard-var
  #
  # save trace to all needed globals as well
  rewind-stream definitions-created
  var globals-data-ah/eax: (addr handle array global) <- get globals, data
  var _globals-data/eax: (addr array global) <- lookup *globals-data-ah
  var globals-data/edi: (addr array global) <- copy _globals-data
  {
    var no-definitions?/eax: boolean <- stream-empty? definitions-created
    compare no-definitions?, 0/false
    break-if-!=
    var curr-index: int
    var curr-index-a/eax: (addr int) <- address curr-index
    read-from-stream definitions-created, curr-index-a
    var curr-offset/eax: (offset global) <- compute-offset globals-data, curr-index
    var curr-global/ecx: (addr global) <- index globals-data, curr-offset
    var curr-trace-ah/eax: (addr handle trace) <- get curr-global, trace
    copy-object trace-ah, curr-trace-ah
    loop
  }
}

fn render-image? _self: (addr global), cursor-in-definition?: boolean -> _/eax: boolean {
  {
    compare cursor-in-definition?, 0/false
    break-if-=
    # if the cursor is in this definition we need to be able to edit raw data
    return 0/false
  }
  var self/esi: (addr global) <- copy _self
  var value-ah/eax: (addr handle cell) <- get self, value
  var value/eax: (addr cell) <- lookup *value-ah
  compare value, 0
  {
    break-if-!=
    # unparsed buffers can't be rendered; either they're uninitialized or they have errors
    return 0/false
  }
  {
    var pair?/eax: boolean <- pair? value
    compare pair?, 0/false
    break-if-!=
    # not a pair? not an image
    return 0/false
  }
  var first-ah/eax: (addr handle cell) <- get value, left
  var first/eax: (addr cell) <- lookup *first-ah
  var litimg?/eax: boolean <- litimg? first
  return litimg?
}

fn render-image-definition screen: (addr screen), _self: (addr global), x: int, _y: int -> _/ecx: int {
  var self/esi: (addr global) <- copy _self
  var y/ecx: int <- copy _y
  set-cursor-position 0/screen, 0x20 0x20
  draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 4/fg 0/bg
  {
    var name-ah/eax: (addr handle array byte) <- get self, name
    var _name/eax: (addr array byte) <- lookup *name-ah
    var name/edx: (addr array byte) <- copy _name
    var x/eax: int <- draw-text-rightward-over-full-screen screen, "define ", x y, 7/fg=definition, 0xc5/bg=blue-bg
    x <- draw-text-rightward-over-full-screen screen, name, x y, 7/fg=definition, 0xc5/bg=blue-bg
  }
  y <- increment  # skip definition line
  # y is now in pixels
  y <- shift-left 4/log2fontheight
  add-to x, 4
  shift-left x, 3/log2fontwidth
  var value-ah/eax: (addr handle cell) <- get self, value
  var value/eax: (addr cell) <- lookup *value-ah
  var rest-ah/eax: (addr handle cell) <- get value, right
  var rest/eax: (addr cell) <- lookup *rest-ah
  var second-ah/eax: (addr handle cell) <- get rest, left
  var second/eax: (addr cell) <- lookup *second-ah
  var img-data-ah/eax: (addr handle stream byte) <- get second, text-data
  var img-data/eax: (addr stream byte) <- lookup *img-data-ah
  var img-h: (handle cell)
  var img-cell-ah/edx: (addr handle cell) <- address img-h
  new-image img-cell-ah, img-data
  var img-cell/eax: (addr cell) <- lookup *img-cell-ah
  var img-ah/eax: (addr handle image) <- get img-cell, image-data
  var img/eax: (addr image) <- lookup *img-ah
  var height/ebx: int <- scale-image-height img, 0x80/width
  render-image screen, img, x y, 0x80/width height
  y <- add height
  # switch y back to characters
  y <- scale-down-and-round-up y, 0x10/font-height
  return y
}