https://github.com/akkartik/mu/blob/main/browse-slack/main.mu
  1 type channel {
  2   name: (handle array byte)
  3   posts: (handle array int)  # item indices
  4   posts-first-free: int
  5 }
  6 
  7 type user {
  8   id: (handle array byte)
  9   name: (handle array byte)
 10   real-name: (handle array byte)
 11   avatar: (handle image)
 12 }
 13 
 14 type item {
 15   id: (handle array byte)
 16   channel: (handle array byte)
 17   by: int  # user index
 18   text: (handle array byte)
 19   parent: int  # item index
 20   comments: (handle array int)
 21   comments-first-free: int
 22 }
 23 
 24 type item-list {
 25   data: (handle array item)
 26   data-first-free: int
 27 }
 28 
 29 # globals:
 30 #   users: (handle array user)
 31 #   channels: (handle array channel)
 32 #   items: (handle array item)
 33 #
 34 # flows:
 35 #   channel -> posts
 36 #   user -> posts|comments
 37 #   post -> comments
 38 #   comment -> post|comments
 39 #   keywords -> posts|comments
 40 
 41 # static buffer sizes in this program:
 42 #   data-size
 43 #   data-size-in-sectors
 44 #   num-channels
 45 #   num-users
 46 #   num-items
 47 #   num-comments
 48 #   message-text-limit
 49 #   channel-capacity
 50 
 51 fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
 52   # load entire disk contents to a single enormous stream
 53   var s-h: (handle stream byte)  # the stream is too large to put on the stack
 54   var s-ah/eax: (addr handle stream byte) <- address s-h
 55   populate-stream s-ah, 0x4000000/data-size
 56   var _s/eax: (addr stream byte) <- lookup *s-ah
 57   var s/ebx: (addr stream byte) <- copy _s
 58   var sector-count/eax: int <- copy 0x400  # test_data
 59 #?   var sector-count/eax: int <- copy 0x20000  # largest size tested; slow
 60   set-cursor-position 0/screen, 0x20/x 0/y  # aborts clobber the screen starting x=0
 61   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "loading ", 3/fg 0/bg
 62   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, sector-count, 3/fg 0/bg
 63   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " sectors from data disk..", 3/fg 0/bg
 64   load-sectors data-disk, 0/lba, sector-count, s
 65   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "done", 3/fg 0/bg
 66   # parse global data structures out of the stream
 67   var users-h: (handle array user)
 68   var users-ah/eax: (addr handle array user) <- address users-h
 69   populate users-ah, 0x800/num-users
 70   var _users/eax: (addr array user) <- lookup *users-ah
 71   var users/edi: (addr array user) <- copy _users
 72   var channels-h: (handle array channel)
 73   var channels-ah/eax: (addr handle array channel) <- address channels-h
 74   populate channels-ah, 0x20/num-channels
 75   var _channels/eax: (addr array channel) <- lookup *channels-ah
 76   var channels/esi: (addr array channel) <- copy _channels
 77   var items-storage: item-list
 78   var items/edx: (addr item-list) <- address items-storage
 79   var items-data-ah/eax: (addr handle array item) <- get items, data
 80   populate items-data-ah, 0x10000/num-items
 81   parse s, users, channels, items
 82   # render
 83   var env-storage: environment
 84   var env/ebx: (addr environment) <- address env-storage
 85   initialize-environment env, items
 86   {
 87     render-environment screen, env, users, channels, items
 88     {
 89       var key/eax: byte <- read-key keyboard
 90       compare key, 0
 91       loop-if-=
 92       update-environment env, key, users, channels, items
 93     }
 94     loop
 95   }
 96 }
 97 
 98 fn parse in: (addr stream byte), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
 99   var items/esi: (addr item-list) <- copy _items
100   var items-data-ah/eax: (addr handle array item) <- get items, data
101   var _items-data/eax: (addr array item) <- lookup *items-data-ah
102   var items-data/edi: (addr array item) <- copy _items-data
103   # 'in' consists of a long, flat sequence of records surrounded by parens
104   var record-storage: (stream byte 0x18000)
105   var record/ecx: (addr stream byte) <- address record-storage
106   var user-idx/edx: int <- copy 0
107   var item-idx/ebx: int <- copy 0
108   {
109     var done?/eax: boolean <- stream-empty? in
110     compare done?, 0/false
111     break-if-!=
112     var c/eax: byte <- peek-byte in
113     compare c, 0
114     break-if-=
115     set-cursor-position 0/screen, 0x20/x 1/y
116     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "parsed " 3/fg 0/bg
117     draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, user-idx, 3/fg 0/bg
118     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, " users, " 3/fg 0/bg
119     draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, item-idx, 3/fg 0/bg
120     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, " posts/comments" 3/fg 0/bg
121     clear-stream record
122     parse-record in, record
123     var user?/eax: boolean <- user-record? record
124     {
125       compare user?, 0/false
126       break-if-=
127       parse-user record, users, user-idx
128       user-idx <- increment
129     }
130     {
131       compare user?, 0/false
132       break-if-!=
133       parse-item record, channels, items-data, item-idx
134       item-idx <- increment
135     }
136     loop
137   }
138   var dest/eax: (addr int) <- get items, data-first-free
139   copy-to *dest, item-idx
140 }
141 
142 fn parse-record in: (addr stream byte), out: (addr stream byte) {
143   var paren/eax: byte <- read-byte in
144   compare paren, 0x28/open-paren
145   {
146     break-if-=
147     set-cursor-position 0/screen, 0x20 0x10
148     var c/eax: int <- copy paren
149     draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen c, 5/fg 0/bg
150     abort "parse-record: ("
151   }
152   var paren-int/eax: int <- copy paren
153   append-byte out, paren-int
154   {
155     {
156       var eof?/eax: boolean <- stream-empty? in
157       compare eof?, 0/false
158       break-if-=
159       abort "parse-record: truncated; increase the sector-count to load from disk"
160     }
161     var c/eax: byte <- read-byte in
162     {
163       var c-int/eax: int <- copy c
164       append-byte out, c-int
165     }
166     compare c, 0x29/close-paren
167     break-if-=
168     compare c, 0x22/double-quote
169     {
170       break-if-!=
171       slurp-json-string in, out
172     }
173     loop
174   }
175   skip-chars-matching-whitespace in
176 }
177 
178 fn user-record? record: (addr stream byte) -> _/eax: boolean {
179   rewind-stream record
180   var c/eax: byte <- read-byte record  # skip paren
181   var c/eax: byte <- read-byte record  # skip double quote
182   var c/eax: byte <- read-byte record
183   compare c, 0x55/U
184   {
185     break-if-!=
186     return 1/true
187   }
188   rewind-stream record
189   return 0/false
190 }
191 
192 fn parse-user record: (addr stream byte), _users: (addr array user), user-idx: int {
193   var users/esi: (addr array user) <- copy _users
194   var offset/eax: (offset user) <- compute-offset users, user-idx
195   var user/esi: (addr user) <- index users, offset
196   #
197   var s-storage: (stream byte 0x100)
198   var s/ecx: (addr stream byte) <- address s-storage
199   #
200   rewind-stream record
201   var paren/eax: byte <- read-byte record
202   compare paren, 0x28/open-paren
203   {
204     break-if-=
205     abort "parse-user: ("
206   }
207   # user id
208   skip-chars-matching-whitespace record
209   var double-quote/eax: byte <- read-byte record
210   compare double-quote, 0x22/double-quote
211   {
212     break-if-=
213     abort "parse-user: id"
214   }
215   next-json-string record, s
216   var dest/eax: (addr handle array byte) <- get user, id
217   stream-to-array s, dest
218   # user name
219   skip-chars-matching-whitespace record
220   var double-quote/eax: byte <- read-byte record
221   compare double-quote, 0x22/double-quote
222   {
223     break-if-=
224     abort "parse-user: name"
225   }
226   clear-stream s
227   next-json-string record, s
228   var dest/eax: (addr handle array byte) <- get user, name
229   stream-to-array s, dest
230   # real name
231   skip-chars-matching-whitespace record
232   var double-quote/eax: byte <- read-byte record
233   compare double-quote, 0x22/double-quote
234   {
235     break-if-=
236     abort "parse-user: real-name"
237   }
238   clear-stream s
239   next-json-string record, s
240   var dest/eax: (addr handle array byte) <- get user, real-name
241   stream-to-array s, dest
242   # avatar
243   skip-chars-matching-whitespace record
244   var open-bracket/eax: byte <- read-byte record
245   compare open-bracket, 0x5b/open-bracket
246   {
247     break-if-=
248     abort "parse-user: avatar"
249   }
250   skip-chars-matching-whitespace record
251   var c/eax: byte <- peek-byte record
252   {
253     compare c, 0x5d/close-bracket
254     break-if-=
255     var dest-ah/eax: (addr handle image) <- get user, avatar
256     allocate dest-ah
257     var dest/eax: (addr image) <- lookup *dest-ah
258     initialize-image dest, record
259   }
260 }
261 
262 fn parse-item record: (addr stream byte), _channels: (addr array channel), _items: (addr array item), item-idx: int {
263   var items/esi: (addr array item) <- copy _items
264   var offset/eax: (offset item) <- compute-offset items, item-idx
265   var item/edi: (addr item) <- index items, offset
266   #
267   var s-storage: (stream byte 0x40)
268   var s/ecx: (addr stream byte) <- address s-storage
269   #
270   rewind-stream record
271   var paren/eax: byte <- read-byte record
272   compare paren, 0x28/open-paren
273   {
274     break-if-=
275     abort "parse-item: ("
276   }
277   # item id
278   skip-chars-matching-whitespace record
279   var double-quote/eax: byte <- read-byte record
280   compare double-quote, 0x22/double-quote
281   {
282     break-if-=
283     abort "parse-item: id"
284   }
285   next-json-string record, s
286   var dest/eax: (addr handle array byte) <- get item, id
287   stream-to-array s, dest
288   # parent index
289   {
290     var word-slice-storage: slice
291     var word-slice/ecx: (addr slice) <- address word-slice-storage
292     next-word record, word-slice
293     var src/eax: int <- parse-decimal-int-from-slice word-slice
294     compare src, -1
295     break-if-=
296     var dest/edx: (addr int) <- get item, parent
297     copy-to *dest, src
298     # cross-link to parent
299     var parent-offset/eax: (offset item) <- compute-offset items, src
300     var parent-item/esi: (addr item) <- index items, parent-offset
301     var parent-comments-ah/ebx: (addr handle array int) <- get parent-item, comments
302     var parent-comments/eax: (addr array int) <- lookup *parent-comments-ah
303     compare parent-comments, 0
304     {
305       break-if-!=
306       populate parent-comments-ah, 0x200/num-comments
307       parent-comments <- lookup *parent-comments-ah
308     }
309     var parent-comments-first-free-addr/edi: (addr int) <- get parent-item, comments-first-free
310     var parent-comments-first-free/edx: int <- copy *parent-comments-first-free-addr
311     var dest/eax: (addr int) <- index parent-comments, parent-comments-first-free
312     var src/ecx: int <- copy item-idx
313     copy-to *dest, src
314     increment *parent-comments-first-free-addr
315   }
316   # channel name
317   skip-chars-matching-whitespace record
318   var double-quote/eax: byte <- read-byte record
319   compare double-quote, 0x22/double-quote
320   {
321     break-if-=
322     abort "parse-item: channel"
323   }
324   clear-stream s
325   next-json-string record, s
326   var dest/eax: (addr handle array byte) <- get item, channel
327   stream-to-array s, dest
328 #?   set-cursor-position 0/screen, 0x70 item-idx
329   # cross-link to channels
330   {
331     var channels/esi: (addr array channel) <- copy _channels
332     var channel-index/eax: int <- find-or-insert channels, s
333 #?     draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, channel-index, 5/fg 0/bg
334     var channel-offset/eax: (offset channel) <- compute-offset channels, channel-index
335     var channel/eax: (addr channel) <- index channels, channel-offset
336 #?     {
337 #?       var foo/eax: int <- copy channel
338 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 4/fg 0/bg
339 #?     }
340     var channel-posts-ah/ecx: (addr handle array int) <- get channel, posts
341     var channel-posts-first-free-addr/edx: (addr int) <- get channel, posts-first-free
342 #?     {
343 #?       var foo/eax: int <- copy channel-posts-first-free-addr
344 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 4/fg 0/bg
345 #?     }
346     var channel-posts-first-free/ebx: int <- copy *channel-posts-first-free-addr
347 #?     draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, channel-posts-first-free, 3/fg 0/bg
348     var channel-posts/eax: (addr array int) <- lookup *channel-posts-ah
349     var dest/eax: (addr int) <- index channel-posts, channel-posts-first-free
350     var src/ecx: int <- copy item-idx
351     copy-to *dest, src
352     increment *channel-posts-first-free-addr
353   }
354   # user index
355   {
356     var word-slice-storage: slice
357     var word-slice/ecx: (addr slice) <- address word-slice-storage
358     next-word record, word-slice
359     var src/eax: int <- parse-decimal-int-from-slice word-slice
360     var dest/edx: (addr int) <- get item, by
361     copy-to *dest, src
362   }
363   # text
364   var s-storage: (stream byte 0x4000)  # message-text-limit
365   var s/ecx: (addr stream byte) <- address s-storage
366   skip-chars-matching-whitespace record
367   var double-quote/eax: byte <- read-byte record
368   compare double-quote, 0x22/double-quote
369   {
370     break-if-=
371     abort "parse-item: text"
372   }
373   next-json-string record, s
374   var dest/eax: (addr handle array byte) <- get item, text
375   stream-to-array s, dest
376 }
377 
378 fn find-or-insert _channels: (addr array channel), name: (addr stream byte) -> _/eax: int {
379   var channels/esi: (addr array channel) <- copy _channels
380   var i/ecx: int <- copy 0
381   var max/edx: int <- length channels
382   {
383     compare i, max
384     break-if->=
385     var offset/eax: (offset channel) <- compute-offset channels, i
386     var curr/ebx: (addr channel) <- index channels, offset
387     var curr-name-ah/edi: (addr handle array byte) <- get curr, name
388     var curr-name/eax: (addr array byte) <- lookup *curr-name-ah
389     {
390       compare curr-name, 0
391       break-if-!=
392       rewind-stream name
393       stream-to-array name, curr-name-ah
394       var posts-ah/eax: (addr handle array int) <- get curr, posts
395       populate posts-ah, 0x8000/channel-capacity
396       return i
397     }
398     var found?/eax: boolean <- stream-data-equal? name, curr-name
399     {
400       compare found?, 0/false
401       break-if-=
402       return i
403     }
404     i <- increment
405     loop
406   }
407   abort "out of channels"
408   return -1
409 }
410 
411 # includes trailing double quote
412 fn slurp-json-string in: (addr stream byte), out: (addr stream byte) {
413   # open quote is already slurped
414   {
415     {
416       var eof?/eax: boolean <- stream-empty? in
417       compare eof?, 0/false
418       break-if-=
419       abort "slurp-json-string: truncated"
420     }
421     var c/eax: byte <- read-byte in
422     {
423       var c-int/eax: int <- copy c
424       append-byte out, c-int
425     }
426     compare c, 0x22/double-quote
427     break-if-=
428     compare c, 0x5c/backslash
429     {
430       break-if-!=
431       # read next byte raw
432       c <- read-byte in
433       var c-int/eax: int <- copy c
434       append-byte out, c-int
435     }
436     loop
437   }
438 }
439 
440 # drops trailing double quote
441 fn next-json-string in: (addr stream byte), out: (addr stream byte) {
442   # open quote is already read
443   {
444     {
445       var eof?/eax: boolean <- stream-empty? in
446       compare eof?, 0/false
447       break-if-=
448       abort "next-json-string: truncated"
449     }
450     var c/eax: byte <- read-byte in
451     compare c, 0x22/double-quote
452     break-if-=
453     {
454       var c-int/eax: int <- copy c
455       append-byte out, c-int
456     }
457     compare c, 0x5c/backslash
458     {
459       break-if-!=
460       # read next byte raw
461       c <- read-byte in
462       var c-int/eax: int <- copy c
463       append-byte out, c-int
464     }
465     loop
466   }
467 }