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