# Mu - browse-slack/main.mu

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 }