about summary refs log tree commit diff stats
path: root/linux/apps/advent2020
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2021-07-16 08:09:42 -0700
committerKartik K. Agaram <vc@akkartik.com>2021-07-16 08:28:56 -0700
commit44d26b77c45668c9b0c99894a4294cec004361fe (patch)
tree68a5dcd4971873efd4ce184e9bf9a531c2161813 /linux/apps/advent2020
parentac45f097153afd3a89f43886e4124c5b2c26b98a (diff)
downloadmu-44d26b77c45668c9b0c99894a4294cec004361fe.tar.gz
.
Diffstat (limited to 'linux/apps/advent2020')
-rw-r--r--linux/apps/advent2020/1a.mu97
-rw-r--r--linux/apps/advent2020/1b.mu113
-rw-r--r--linux/apps/advent2020/2a.mu92
-rw-r--r--linux/apps/advent2020/2b.mu122
-rw-r--r--linux/apps/advent2020/3a.mu107
-rw-r--r--linux/apps/advent2020/3b.mu137
-rw-r--r--linux/apps/advent2020/4a.mu75
-rw-r--r--linux/apps/advent2020/4b.mu314
-rw-r--r--linux/apps/advent2020/5a.mu79
-rw-r--r--linux/apps/advent2020/5b.mu81
-rw-r--r--linux/apps/advent2020/vimrc.vim2
11 files changed, 1219 insertions, 0 deletions
diff --git a/linux/apps/advent2020/1a.mu b/linux/apps/advent2020/1a.mu
new file mode 100644
index 00000000..da71340a
--- /dev/null
+++ b/linux/apps/advent2020/1a.mu
@@ -0,0 +1,97 @@
+# https://adventofcode.com/2020/day/1
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/1a.mu
+#   $ ./a.elf < input
+#   found
+#   1353 667
+#   902451
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  # data structure
+  var numbers-storage: (array int 0x100)  # 256 ints
+  var numbers/esi: (addr array int) <- address numbers-storage
+  var numbers-index/ecx: int <- copy 0
+  # phase 1: parse each line from stdin and add it to numbers
+  {
+    var line-storage: (stream byte 0x100)  # 256 bytes
+    var line/edx: (addr stream byte) <- address line-storage
+    {
+#?       print-string 0, "== iter\n"
+      # read line from stdin
+      clear-stream line
+      read-line-from-real-keyboard line
+      # if line is empty (not even a newline), quit
+      var done?/eax: boolean <- stream-empty? line
+      compare done?, 0/false
+      break-if-!=
+#?       print-stream-to-real-screen line
+      # convert line to int and append it to numbers
+      var n/eax: int <- parse-decimal-int-from-stream line
+#?       print-int32-decimal 0, n
+#?       print-string 0, "\n"
+      var dest/ebx: (addr int) <- index numbers, numbers-index
+      copy-to *dest, n
+      numbers-index <- increment
+#?       print-string 0, "== "
+#?       print-int32-decimal 0, numbers-index
+#?       print-string 0, "\n"
+      loop
+    }
+  }
+  # phase 2: for each number in the array, check if 2020-it is in the rest of
+  # the array
+  var i/eax: int <- copy 0
+  {
+    compare i, numbers-index
+    break-if->=
+    var src/ebx: (addr int) <- index numbers, i
+#?     print-int32-decimal 0, *src
+#?     print-string 0, "\n"
+    var target/ecx: int <- copy 0x7e4  # 2020
+    target <- subtract *src
+    {
+      var found?/eax: boolean <- find-after numbers, i, target
+      compare found?, 0/false
+      break-if-=
+      print-string 0, "found\n"
+      print-int32-decimal 0, *src
+      print-string 0, " "
+      print-int32-decimal 0, target
+      print-string 0, "\n"
+      target <- multiply *src
+      print-int32-decimal 0, target
+      print-string 0, "\n"
+      return 0/success
+    }
+    i <- increment
+    loop
+  }
+  return 1/not-found
+}
+
+fn find-after _numbers: (addr array int), start: int, _target: int -> _/eax: boolean {
+  var numbers/esi: (addr array int) <- copy _numbers
+  var target/edi: int <- copy _target
+  var len/ecx: int <- length numbers
+  var i/eax: int <- copy start
+  i <- increment
+  {
+    compare i, len
+    break-if->=
+    var src/edx: (addr int) <- index numbers, i
+    # if *src == target, return true
+    compare *src, target
+    {
+      break-if-!=
+      return 1/true
+    }
+    i <- increment
+    loop
+  }
+  return 0/false
+}
diff --git a/linux/apps/advent2020/1b.mu b/linux/apps/advent2020/1b.mu
new file mode 100644
index 00000000..a99709b8
--- /dev/null
+++ b/linux/apps/advent2020/1b.mu
@@ -0,0 +1,113 @@
+# https://adventofcode.com/2020/day/1
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/1b.mu
+#   $ ./a.elf < input
+#   found
+#   143 407 1470
+#   85555470
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  # data structure
+  var numbers-storage: (array int 0x100)  # 256 ints
+  var numbers/esi: (addr array int) <- address numbers-storage
+  var numbers-index/ecx: int <- copy 0
+  # phase 1: parse each line from stdin and add it to numbers
+  {
+    var line-storage: (stream byte 0x100)  # 256 bytes
+    var line/edx: (addr stream byte) <- address line-storage
+    {
+#?       print-string 0, "== iter\n"
+      # read line from stdin
+      clear-stream line
+      read-line-from-real-keyboard line
+      # if line is empty (not even a newline), quit
+      var done?/eax: boolean <- stream-empty? line
+      compare done?, 0/false
+      break-if-!=
+#?       print-stream-to-real-screen line
+      # convert line to int and append it to numbers
+      var n/eax: int <- parse-decimal-int-from-stream line
+#?       print-int32-decimal 0, n
+#?       print-string 0, "\n"
+      var dest/ebx: (addr int) <- index numbers, numbers-index
+      copy-to *dest, n
+      numbers-index <- increment
+#?       print-string 0, "== "
+#?       print-int32-decimal 0, numbers-index
+#?       print-string 0, "\n"
+      loop
+    }
+  }
+  # phase 2: for every pair of distinct numbers, check if the rest of the
+  # array has 2020-it
+  var i/edi: int <- copy 0
+  {
+    compare i, numbers-index
+    break-if->=
+    # for j from i+1 to end
+    var j/edx: int <- copy i
+    j <- increment
+    {
+      compare j, numbers-index
+      break-if->=
+      {
+        compare i, j
+        break-if-=
+        var target/ebx: int <- copy 0x7e4  # 2020
+        var src/edi: (addr int) <- index numbers, i
+        target <- subtract *src
+        var src2/ecx: (addr int) <- index numbers, j
+        target <- subtract *src2
+        {
+          var found?/eax: boolean <- find-after numbers, j, target
+          compare found?, 0/false
+          break-if-=
+          print-string 0, "found\n"
+          print-int32-decimal 0, *src
+          print-string 0, " "
+          print-int32-decimal 0, *src2
+          print-string 0, " "
+          print-int32-decimal 0, target
+          print-string 0, "\n"
+          target <- multiply *src
+          target <- multiply *src2
+          print-int32-decimal 0, target
+          print-string 0, "\n"
+          return 0/success
+        }
+      }
+      j <- increment
+      loop
+    }
+    i <- increment
+    loop
+  }
+  return 1/not-found
+}
+
+fn find-after _numbers: (addr array int), start: int, _target: int -> _/eax: boolean {
+  var numbers/esi: (addr array int) <- copy _numbers
+  var target/edi: int <- copy _target
+  var len/ecx: int <- length numbers
+  var i/eax: int <- copy start
+  i <- increment
+  {
+    compare i, len
+    break-if->=
+    var src/edx: (addr int) <- index numbers, i
+    # if *src == target, return true
+    compare *src, target
+    {
+      break-if-!=
+      return 1/true
+    }
+    i <- increment
+    loop
+  }
+  return 0/false
+}
diff --git a/linux/apps/advent2020/2a.mu b/linux/apps/advent2020/2a.mu
new file mode 100644
index 00000000..44d50d46
--- /dev/null
+++ b/linux/apps/advent2020/2a.mu
@@ -0,0 +1,92 @@
+# https://adventofcode.com/2020/day/2
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/2a.mu
+#   $ ./a.elf < input
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  var valid-password-count/edi: int <- copy 0
+  var line-storage: (stream byte 0x100)  # 256 bytes
+  var line/edx: (addr stream byte) <- address line-storage
+  var slice-storage: slice
+  var slice/ecx: (addr slice) <- address slice-storage
+  {
+    # read line from stdin
+    clear-stream line
+    read-line-from-real-keyboard line
+    # if line is empty (not even a newline), quit
+    var done?/eax: boolean <- stream-empty? line
+    compare done?, 0/false
+    break-if-!=
+    print-stream-to-real-screen line
+    # slice = next-token(line, '-')
+    next-token line, 0x2d, slice
+    # start = parse-int(slice)
+    var _start/eax: int <- parse-decimal-int-from-slice slice
+    var start/ebx: int <- copy _start
+    var dash/eax: byte <- read-byte line  # skip '-'
+    # slice = next-token(line, ' ')
+    next-token line, 0x20, slice
+    var _end/eax: int <- parse-decimal-int-from-slice slice
+    var end/esi: int <- copy _end
+    print-int32-decimal 0, start
+    print-string 0, " "
+    print-int32-decimal 0, end
+    print-string 0, "\n"
+    # letter = next non-space
+    skip-chars-matching-whitespace line
+    var letter/eax: byte <- read-byte line
+    # skip some stuff
+    {
+      var colon/eax: byte <- read-byte line  # skip ':'
+    }
+    skip-chars-matching-whitespace line
+    # now check the rest of the line
+    var valid?/eax: boolean <- valid? start, end, letter, line
+    compare valid?, 0/false
+    {
+      break-if-=
+      print-string 0, "valid!\n"
+      valid-password-count <- increment
+    }
+    loop
+  }
+  print-int32-decimal 0, valid-password-count
+  print-string 0, "\n"
+  return 0
+}
+
+fn valid? start: int, end: int, letter: byte, password: (addr stream byte) -> _/eax: boolean {
+  var letter-count/edi: int <- copy 0
+  # for every c in password
+  #   if (c == letter)
+  #     ++letter-count
+  {
+    var done?/eax: boolean <- stream-empty? password
+    compare done?, 0/false
+    break-if-!=
+    var c/eax: byte <- read-byte password
+    compare c, letter
+    {
+      break-if-!=
+      letter-count <- increment
+    }
+    loop
+  }
+  # return (start <= letter-count <= end)
+  compare letter-count, start
+  {
+    break-if->=
+    return 0/false
+  }
+  compare letter-count, end
+  {
+    break-if-<=
+    return 0/false
+  }
+  return 1/true
+}
diff --git a/linux/apps/advent2020/2b.mu b/linux/apps/advent2020/2b.mu
new file mode 100644
index 00000000..d0d23998
--- /dev/null
+++ b/linux/apps/advent2020/2b.mu
@@ -0,0 +1,122 @@
+# https://adventofcode.com/2020/day/2
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/2b.mu
+#   $ ./a.elf < input
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  var valid-password-count/edi: int <- copy 0
+  var line-storage: (stream byte 0x100)  # 256 bytes
+  var line/edx: (addr stream byte) <- address line-storage
+  var slice-storage: slice
+  var slice/ecx: (addr slice) <- address slice-storage
+  {
+    # read line from stdin
+    clear-stream line
+    read-line-from-real-keyboard line
+    # if line is empty (not even a newline), quit
+    var done?/eax: boolean <- stream-empty? line
+    compare done?, 0/false
+    break-if-!=
+    print-stream-to-real-screen line
+    # slice = next-token(line, '-')
+    next-token line, 0x2d, slice
+    # pos1 = parse-int(slice)
+    var _pos1/eax: int <- parse-decimal-int-from-slice slice
+    var pos1/ebx: int <- copy _pos1
+    var dash/eax: byte <- read-byte line  # skip '-'
+    # slice = next-token(line, ' ')
+    next-token line, 0x20, slice
+    var _pos2/eax: int <- parse-decimal-int-from-slice slice
+    var pos2/esi: int <- copy _pos2
+    print-int32-decimal 0, pos1
+    print-string 0, " "
+    print-int32-decimal 0, pos2
+    print-string 0, "\n"
+    compare pos1, pos2
+    {
+      break-if-<=
+      print-string 0, "out of order!\n"
+      return 1
+    }
+    # letter = next non-space
+    skip-chars-matching-whitespace line
+    var letter/eax: byte <- read-byte line
+    # skip some stuff
+    {
+      var colon/eax: byte <- read-byte line  # skip ':'
+    }
+    skip-chars-matching-whitespace line
+    # now check the rest of the line
+    var valid?/eax: boolean <- valid? pos1, pos2, letter, line
+    compare valid?, 0/false
+    {
+      break-if-=
+      print-string 0, "valid!\n"
+      valid-password-count <- increment
+    }
+    loop
+  }
+  print-int32-decimal 0, valid-password-count
+  print-string 0, "\n"
+  return 0
+}
+
+# ideally password would be a random-access array
+# we'll just track an index
+# one benefit: we can easily start at 1
+fn valid? pos1: int, pos2: int, letter: byte, password: (addr stream byte) -> _/eax: boolean {
+  var i/esi: int <- copy 1
+  var letter-count/edi: int <- copy 0
+  # while password stream isn't empty
+  #   c = read byte from password
+  #   if (c == letter)
+  #     if (i == pos1)
+  #       ++letter-count
+  #     if (i == pos2)
+  #       ++letter-count
+  #     ++i
+  {
+#?     print-string 0, "  "
+#?     print-int32-decimal 0, i
+#?     print-string 0, "\n"
+    var done?/eax: boolean <- stream-empty? password
+    compare done?, 0/false
+    break-if-!=
+    var c/eax: byte <- read-byte password
+#?     {
+#?       var c2/eax: int <- copy c
+#?       print-int32-decimal 0, c2
+#?       print-string 0, "\n"
+#?     }
+    compare c, letter
+    {
+      break-if-!=
+      compare i, pos1
+      {
+        break-if-!=
+        letter-count <- increment
+#?         print-string 0, "  hit\n"
+      }
+      compare i, pos2
+      {
+        break-if-!=
+        letter-count <- increment
+#?         print-string 0, "  hit\n"
+      }
+    }
+    i <- increment
+    loop
+  }
+  # return (letter-count == 1)
+  compare letter-count, 1
+  {
+    break-if-!=
+    return 1/true
+  }
+  return 0/false
+}
diff --git a/linux/apps/advent2020/3a.mu b/linux/apps/advent2020/3a.mu
new file mode 100644
index 00000000..6f95ddf1
--- /dev/null
+++ b/linux/apps/advent2020/3a.mu
@@ -0,0 +1,107 @@
+# https://adventofcode.com/2020/day/3
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/3a.mu
+#   $ ./a.elf < input
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  # represent trees in a 2D array of ints
+  # wasteful since each tree is just one bit
+  var trees-storage: (array int 0x2800)  # 10k ints
+  var trees/esi: (addr array int) <- address trees-storage
+  var trees-length/ecx: int <- copy 0
+  var num-rows: int
+  var width: int
+  # phase 1: parse each row of trees from stdin
+  {
+    var line-storage: (stream byte 0x40)  # 64 bytes
+    var line/edx: (addr stream byte) <- address line-storage
+    {
+      # read line from stdin
+      clear-stream line
+      read-line-from-real-keyboard line
+      # if line is empty (not even a newline), quit
+      var done?/eax: boolean <- stream-empty? line
+      compare done?, 0/false
+      break-if-!=
+      # wastefully recompute width on every line
+      # zero error-checking; we assume input lines are all equally long
+      copy-to width, 0
+      # turn each byte into a tree and append it
+      $main:line-loop: {
+        var done?/eax: boolean <- stream-empty? line
+        compare done?, 0/false
+        break-if-!=
+#?         print-int32-decimal 0, num-rows
+#?         print-string 0, " "
+#?         print-int32-decimal 0, width
+#?         print-string 0, "\n"
+        var dest/ebx: (addr int) <- index trees, trees-length
+        var c/eax: byte <- read-byte line
+        # newline comes only at end of line
+        compare c, 0xa/newline
+        break-if-=
+        # '#' = tree
+        compare c, 0x23/hash
+        {
+          break-if-!=
+          copy-to *dest, 1
+        }
+        # anything else = no tree
+        {
+          break-if-=
+          copy-to *dest, 0
+        }
+        increment width
+        trees-length <- increment
+        loop
+      }
+      increment num-rows
+      loop
+    }
+  }
+  # phase 2: compute
+  print-int32-decimal 0, num-rows
+  print-string 0, "x"
+  print-int32-decimal 0, width
+  print-string 0, "\n"
+  var row/ecx: int <- copy 0
+  var col/edx: int <- copy 0
+  var num-trees-hit/edi: int <- copy 0
+  {
+    compare row, num-rows
+    break-if->=
+    var curr/eax: int <- index2d trees, row, col, width
+    compare curr, 0
+    {
+      break-if-=
+      num-trees-hit <- increment
+    }
+    # right 3, down 1
+    col <- add 3
+    row <- add 1
+    loop
+  }
+  print-int32-decimal 0, num-trees-hit
+  print-string 0, "\n"
+  return 0
+}
+
+fn index2d _arr: (addr array int), _row: int, _col: int, width: int -> _/eax: int {
+  # handle repeating columns of trees
+  var dummy/eax: int <- copy 0
+  var col/edx: int <- copy 0
+  dummy, col <- integer-divide _col, width
+  # compute index
+  var index/eax: int <- copy _row
+  index <- multiply width
+  index <- add col
+  # look up array
+  var arr/esi: (addr array int) <- copy _arr
+  var src/eax: (addr int) <- index arr, index
+  return *src
+}
diff --git a/linux/apps/advent2020/3b.mu b/linux/apps/advent2020/3b.mu
new file mode 100644
index 00000000..8a4f442d
--- /dev/null
+++ b/linux/apps/advent2020/3b.mu
@@ -0,0 +1,137 @@
+# https://adventofcode.com/2020/day/3
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/3a.mu
+#   $ ./a.elf < input
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  # represent trees in a 2D array of ints
+  # wasteful since each tree is just one bit
+  var trees-storage: (array int 0x2800)  # 10k ints
+  var trees/esi: (addr array int) <- address trees-storage
+  var trees-length/ecx: int <- copy 0
+  var num-rows: int
+  var width: int
+  # phase 1: parse each row of trees from stdin
+  {
+    var line-storage: (stream byte 0x40)  # 64 bytes
+    var line/edx: (addr stream byte) <- address line-storage
+    {
+      # read line from stdin
+      clear-stream line
+      read-line-from-real-keyboard line
+      # if line is empty (not even a newline), quit
+      var done?/eax: boolean <- stream-empty? line
+      compare done?, 0/false
+      break-if-!=
+      # wastefully recompute width on every line
+      # zero error-checking; we assume input lines are all equally long
+      copy-to width, 0
+      # turn each byte into a tree and append it
+      $main:line-loop: {
+        var done?/eax: boolean <- stream-empty? line
+        compare done?, 0/false
+        break-if-!=
+#?         print-int32-decimal 0, num-rows
+#?         print-string 0, " "
+#?         print-int32-decimal 0, width
+#?         print-string 0, "\n"
+        var dest/ebx: (addr int) <- index trees, trees-length
+        var c/eax: byte <- read-byte line
+        # newline comes only at end of line
+        compare c, 0xa/newline
+        break-if-=
+        # '#' = tree
+        compare c, 0x23/hash
+        {
+          break-if-!=
+          copy-to *dest, 1
+        }
+        # anything else = no tree
+        {
+          break-if-=
+          copy-to *dest, 0
+        }
+        increment width
+        trees-length <- increment
+        loop
+      }
+      increment num-rows
+      loop
+    }
+  }
+  # phase 2: compute
+  var product/edi: int <- copy 1
+  var result/eax: int <- num-trees-hit trees, width, num-rows, 1, 1
+  print-int32-decimal 0, result
+  print-string 0, " x "
+  product <- multiply result
+  var result/eax: int <- num-trees-hit trees, width, num-rows, 3, 1
+  print-int32-decimal 0, result
+  print-string 0, " x "
+  product <- multiply result
+  var result/eax: int <- num-trees-hit trees, width, num-rows, 5, 1
+  print-int32-decimal 0, result
+  print-string 0, " x "
+  product <- multiply result
+  var result/eax: int <- num-trees-hit trees, width, num-rows, 7, 1
+  print-int32-decimal 0, result
+  print-string 0, " x "
+  product <- multiply result
+  var result/eax: int <- num-trees-hit trees, width, num-rows, 1, 2
+  print-int32-decimal 0, result
+  print-string 0, " = "
+  product <- multiply result
+  print-int32-hex 0, product
+  print-string 0, "\n"
+  return 0
+}
+
+fn num-trees-hit trees: (addr array int), width: int, num-rows: int, right: int, down: int -> _/eax: int {
+#?   print-string 0, "== "
+#?   print-int32-decimal 0, right
+#?   print-string 0, " "
+#?   print-int32-decimal 0, down
+#?   print-string 0, "\n"
+  var row/ecx: int <- copy 0
+  var col/edx: int <- copy 0
+  var num-trees-hit/edi: int <- copy 0
+  {
+    compare row, num-rows
+    break-if->=
+#?     print-int32-decimal 0, col
+#?     print-string 0, "\n"
+    var curr/eax: int <- index2d trees, row, col, width
+    compare curr, 0
+    {
+      break-if-=
+      num-trees-hit <- increment
+    }
+    col <- add right
+    row <- add down
+    loop
+  }
+  return num-trees-hit
+}
+
+fn index2d _arr: (addr array int), _row: int, _col: int, width: int -> _/eax: int {
+  # handle repeating columns of trees
+  var dummy/eax: int <- copy 0
+  var col/edx: int <- copy 0
+  dummy, col <- integer-divide _col, width
+#?   print-string 0, "  "
+#?   print-int32-decimal 0, col
+#?   print-string 0, "\n"
+  # compute index
+  var index/eax: int <- copy _row
+  index <- multiply width
+  index <- add col
+  # look up array
+  var arr/esi: (addr array int) <- copy _arr
+  var src/eax: (addr int) <- index arr, index
+  return *src
+}
diff --git a/linux/apps/advent2020/4a.mu b/linux/apps/advent2020/4a.mu
new file mode 100644
index 00000000..2efe475a
--- /dev/null
+++ b/linux/apps/advent2020/4a.mu
@@ -0,0 +1,75 @@
+# https://adventofcode.com/2020/day/4
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/4a.mu
+#   $ ./a.elf < input
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  var curr-passport-field-count/esi: int <- copy 0
+  var valid-passport-count/edi: int <- copy 0
+  var line-storage: (stream byte 0x100)  # 256 bytes
+  var line/ecx: (addr stream byte) <- address line-storage
+  var slice-storage: slice
+  var slice/edx: (addr slice) <- address slice-storage
+  $main:line-loop: {
+    # read line from stdin
+    clear-stream line
+    read-line-from-real-keyboard line
+    # if line is empty (not even a newline), quit
+    var done?/eax: boolean <- stream-empty? line
+    compare done?, 0/false
+    break-if-!=
+    print-stream-to-real-screen line
+    # if line has just a newline, process passport
+    skip-chars-matching-whitespace line
+    var new-passport?/eax: boolean <- stream-empty? line
+    {
+      compare new-passport?, 0/false
+      break-if-=
+      compare curr-passport-field-count, 7
+      {
+        break-if-!=
+        valid-passport-count <- increment
+        print-string 0, "=> "
+        print-int32-decimal 0, valid-passport-count
+        print-string 0, "\n"
+      }
+      curr-passport-field-count <- copy 0
+      loop $main:line-loop
+    }
+    $main:word-loop: {
+      next-word line, slice
+      var done?/eax: boolean <- slice-empty? slice
+      compare done?, 0/false
+      break-if-!=
+      print-string 0, "  "
+      print-slice-to-real-screen slice
+      # treat cid as optional
+      var optional?/eax: boolean <- slice-starts-with? slice, "cid:"
+      compare optional?, 0/false
+      {
+        break-if-!=
+        # otherwise assume there are no invalid fields and no duplicate fields
+        curr-passport-field-count <- increment
+        print-string 0, " => "
+        print-int32-decimal 0, curr-passport-field-count
+      }
+      print-string 0, "\n"
+      loop
+    }
+    loop
+  }
+  # process final passport
+  compare curr-passport-field-count, 7
+  {
+    break-if-!=
+    valid-passport-count <- increment
+  }
+  print-int32-decimal 0, valid-passport-count
+  print-string 0, "\n"
+  return 0
+}
diff --git a/linux/apps/advent2020/4b.mu b/linux/apps/advent2020/4b.mu
new file mode 100644
index 00000000..e92e96e4
--- /dev/null
+++ b/linux/apps/advent2020/4b.mu
@@ -0,0 +1,314 @@
+# https://adventofcode.com/2020/day/4
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/4b.mu
+#   $ ./a.elf < input
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  var curr-passport-field-count/esi: int <- copy 0
+  var valid-passport-count/edi: int <- copy 0
+  var line-storage: (stream byte 0x100)  # 256 bytes
+  var line/ecx: (addr stream byte) <- address line-storage
+  var key-slice-storage: slice
+  var key-slice/edx: (addr slice) <- address key-slice-storage
+  var val-slice-storage: slice
+  var val-slice/ebx: (addr slice) <- address val-slice-storage
+  $main:line-loop: {
+    # read line from stdin
+    clear-stream line
+    read-line-from-real-keyboard line
+    # if line is empty (not even a newline), quit
+    var done?/eax: boolean <- stream-empty? line
+    compare done?, 0/false
+    break-if-!=
+    print-stream-to-real-screen line
+    # if line has just a newline, process passport
+    skip-chars-matching-whitespace line
+    var new-passport?/eax: boolean <- stream-empty? line
+    {
+      compare new-passport?, 0/false
+      break-if-=
+      compare curr-passport-field-count, 7
+      {
+        break-if-!=
+        valid-passport-count <- increment
+        print-string 0, "=> "
+        print-int32-decimal 0, valid-passport-count
+        print-string 0, "\n"
+      }
+      curr-passport-field-count <- copy 0
+      loop $main:line-loop
+    }
+    $main:word-loop: {
+      skip-chars-matching-whitespace line
+      var done?/eax: boolean <- stream-empty? line
+      compare done?, 0/false
+      break-if-!=
+      next-token line, 0x3a, key-slice  # ':'
+      var dummy/eax: byte <- read-byte line  # skip ':'
+      next-raw-word line, val-slice
+      print-slice-to-real-screen key-slice
+      print-string 0, " : "
+      print-slice-to-real-screen val-slice
+      print-string 0, "\n"
+      # treat cid as optional
+      var cid?/eax: boolean <- slice-equal? key-slice, "cid"
+      compare cid?, 0/false
+      loop-if-!=
+      # increment field count
+      curr-passport-field-count <- increment
+      # - validate fields one by one, setting curr-passport-field-count to impossibly high value to signal invalid
+      # byr
+      {
+        var byr?/eax: boolean <- slice-equal? key-slice, "byr"
+        compare byr?, 0/false
+        break-if-=
+        # 1920 <= byr <= 2002
+        var byr/eax: int <- parse-decimal-int-from-slice val-slice
+        compare byr, 0x780  # 1920
+        {
+          break-if->=
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+        }
+        compare byr, 0x7d2  # 2002
+        {
+          break-if-<=
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+        }
+      }
+      # iyr
+      {
+        var iyr?/eax: boolean <- slice-equal? key-slice, "iyr"
+        compare iyr?, 0/false
+        break-if-=
+        # 2010 <= iyr <= 2020
+        var iyr/eax: int <- parse-decimal-int-from-slice val-slice
+        compare iyr, 0x7da  # 2010
+        {
+          break-if->=
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+        }
+        compare iyr, 0x7e4  # 2020
+        {
+          break-if-<=
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+        }
+      }
+      # eyr
+      {
+        var eyr?/eax: boolean <- slice-equal? key-slice, "eyr"
+        compare eyr?, 0/false
+        break-if-=
+        # 2020 <= eyr <= 2030
+        var eyr/eax: int <- parse-decimal-int-from-slice val-slice
+        compare eyr, 0x7e4  # 2020
+        {
+          break-if->=
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+        }
+        compare eyr, 0x7ee  # 2030
+        {
+          break-if-<=
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+        }
+      }
+      # hgt
+      {
+        var hgt?/eax: boolean <- slice-equal? key-slice, "hgt"
+        compare hgt?, 0/false
+        break-if-=
+        # convert val
+        var s: (handle array byte)
+        var s2/eax: (addr handle array byte) <- address s
+        _slice-to-string val-slice, s2
+        var s3/eax: (addr array byte) <- lookup *s2
+        var s4/ebx: (addr array byte) <- copy s3
+        # check suffix
+        var start/edx: int <- length s4
+        start <- subtract 2  # luckily both 'in' and 'cm' have the same length
+        {
+          var suffix-h: (handle array byte)
+          var suffix-ah/ecx: (addr handle array byte) <- address suffix-h
+          substring s4, start, 2, suffix-ah
+          var suffix/eax: (addr array byte) <- lookup *suffix-ah
+          {
+            var match?/eax: boolean <- string-equal? suffix, "in"
+            compare match?, 0/false
+            break-if-=
+            # if suffix is "in", 59 <= val <= 96
+            var num-h: (handle array byte)
+            var num-ah/ecx: (addr handle array byte) <- address num-h
+            substring s4, 0, start, num-ah
+            var num/eax: (addr array byte) <- lookup *num-ah
+            var val/eax: int <- parse-decimal-int num
+            compare val, 0x3b  # 59
+            {
+              break-if->=
+          print-string 0, "invalid\n"
+              curr-passport-field-count <- copy 8
+            }
+            compare val, 0x60  # 96
+            {
+              break-if-<=
+          print-string 0, "invalid\n"
+              curr-passport-field-count <- copy 8
+            }
+            loop $main:word-loop
+          }
+          {
+            var match?/eax: boolean <- string-equal? suffix, "cm"
+            compare match?, 0/false
+            break-if-=
+            # if suffix is "cm", 150 <= val <= 193
+            var num-h: (handle array byte)
+            var num-ah/ecx: (addr handle array byte) <- address num-h
+            substring s4, 0, start, num-ah
+            var num/eax: (addr array byte) <- lookup *num-ah
+            var val/eax: int <- parse-decimal-int num
+            compare val, 0x96  # 150
+            {
+              break-if->=
+          print-string 0, "invalid\n"
+              curr-passport-field-count <- copy 8
+            }
+            compare val, 0xc1  # 193
+            {
+              break-if-<=
+          print-string 0, "invalid\n"
+              curr-passport-field-count <- copy 8
+            }
+            loop $main:word-loop
+          }
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+          loop $main:word-loop
+        }
+      }
+      # hcl
+      {
+        var hcl?/eax: boolean <- slice-equal? key-slice, "hcl"
+        compare hcl?, 0/false
+        break-if-=
+        # convert val
+        var s: (handle array byte)
+        var s2/eax: (addr handle array byte) <- address s
+        _slice-to-string val-slice, s2
+        var s3/eax: (addr array byte) <- lookup *s2
+        # check length
+        var len/ebx: int <- length s3
+        compare len, 7
+        {
+          break-if-=
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+          loop $main:word-loop
+        }
+        # check first byte
+        {
+          var c/eax: (addr byte) <- index s3, 0
+          var c2/eax: byte <- copy-byte *c
+          compare c2, 0x23/hash
+          break-if-=
+          print-string 0, "invalid2\n"
+          curr-passport-field-count <- copy 8
+          loop $main:word-loop
+        }
+        # check remaining bytes
+        var i/ebx: int <- copy 1  # skip 0
+        {
+          compare i, 7
+          break-if->=
+          var c/eax: (addr byte) <- index s3, i
+          {
+            var c2/eax: byte <- copy-byte *c
+            var valid?/eax: boolean <- hex-digit? c2
+            compare valid?, 0
+            loop-if-= $main:word-loop
+          }
+          i <- increment
+          loop
+        }
+      }
+      # ecl
+      {
+        var ecl?/eax: boolean <- slice-equal? key-slice, "ecl"
+        compare ecl?, 0/false
+        break-if-=
+        var amb?/eax: boolean <- slice-equal? val-slice, "amb"
+        compare amb?, 0/false
+        loop-if-!= $main:word-loop
+        var blu?/eax: boolean <- slice-equal? val-slice, "blu"
+        compare blu?, 0/false
+        loop-if-!= $main:word-loop
+        var brn?/eax: boolean <- slice-equal? val-slice, "brn"
+        compare brn?, 0/false
+        loop-if-!= $main:word-loop
+        var gry?/eax: boolean <- slice-equal? val-slice, "gry"
+        compare gry?, 0/false
+        loop-if-!= $main:word-loop
+        var grn?/eax: boolean <- slice-equal? val-slice, "grn"
+        compare grn?, 0/false
+        loop-if-!= $main:word-loop
+        var hzl?/eax: boolean <- slice-equal? val-slice, "hzl"
+        compare hzl?, 0/false
+        loop-if-!= $main:word-loop
+        var oth?/eax: boolean <- slice-equal? val-slice, "oth"
+        compare oth?, 0/false
+        loop-if-!= $main:word-loop
+        print-string 0, "invalid\n"
+        curr-passport-field-count <- copy 8
+      }
+      # pid
+      {
+        var pid?/eax: boolean <- slice-equal? key-slice, "pid"
+        compare pid?, 0/false
+        break-if-=
+        # convert val
+        var s: (handle array byte)
+        var s2/eax: (addr handle array byte) <- address s
+        _slice-to-string val-slice, s2
+        var s3/eax: (addr array byte) <- lookup *s2
+        # check length
+        var len/eax: int <- length s3
+        compare len, 9
+        {
+          break-if-=
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+          loop $main:word-loop
+        }
+        # check valid decimal int
+        # parse-decimal-int-from-slice currently returns 0 on invalid parse,
+        # which isn't ideal but suffices for our purposes
+        var val/eax: int <- parse-decimal-int-from-slice val-slice
+        compare val, 0
+        {
+          break-if->
+          print-string 0, "invalid\n"
+          curr-passport-field-count <- copy 8
+        }
+      }
+      loop
+    }
+    loop
+  }
+  # process final passport
+  compare curr-passport-field-count, 7
+  {
+    break-if-!=
+    valid-passport-count <- increment
+  }
+  print-int32-decimal 0, valid-passport-count
+  print-string 0, "\n"
+  return 0
+}
diff --git a/linux/apps/advent2020/5a.mu b/linux/apps/advent2020/5a.mu
new file mode 100644
index 00000000..34e2e113
--- /dev/null
+++ b/linux/apps/advent2020/5a.mu
@@ -0,0 +1,79 @@
+# https://adventofcode.com/2020/day/5
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/5a.mu
+#   $ ./a.elf < input
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  var line-storage: (stream byte 0x10)  # 16 bytes is enough
+  var line/edx: (addr stream byte) <- address line-storage
+  var max-seat-id/edi: int <- copy 0
+  {
+    # read line from stdin
+    clear-stream line
+    read-line-from-real-keyboard line
+    print-stream-to-real-screen line
+    # if line is empty (not even a newline), quit
+    var done?/eax: boolean <- stream-empty? line
+    compare done?, 0/false
+    break-if-!=
+    # process line
+    var seat-id/eax: int <- convert-from-binary line
+    compare seat-id, max-seat-id
+    {
+      break-if-<=
+      max-seat-id <- copy seat-id
+    }
+    loop
+  }
+  print-int32-decimal 0, max-seat-id
+  print-string 0, "\n"
+  return 0
+}
+
+fn convert-from-binary in: (addr stream byte) -> _/eax: int {
+  var result/edi: int <- copy 0
+  var i/ecx: int <- copy 9  # loop counter and also exponent
+  {
+    compare i, 0
+    break-if-<
+    var c/eax: byte <- read-byte in
+#?     print-string 0, "char: "
+#?     {
+#?       var c2/eax: int <- copy c
+#?       print-int32-hex 0, c2
+#?     }
+#?     print-string 0, "\n"
+    var bit/edx: int <- copy 0
+    {
+      compare c, 0x42/B
+      break-if-!=
+      bit <- copy 1
+    }
+    {
+      compare c, 0x52/R
+      break-if-!=
+      bit <- copy 1
+    }
+#?     print-string 0, "bit: "
+#?     print-int32-decimal 0, bit
+#?     print-string 0, "\n"
+    var bit-value/eax: int <- repeated-shift-left bit, i
+#?     print-string 0, "bit value: "
+#?     print-int32-decimal 0, bit-value
+#?     print-string 0, "\n"
+    result <- add bit-value
+#?     print-string 0, "result: "
+#?     print-int32-decimal 0, result
+#?     print-string 0, "\n"
+    i <- decrement
+    loop
+  }
+  print-int32-decimal 0, result
+  print-string 0, "\n"
+  return result
+}
diff --git a/linux/apps/advent2020/5b.mu b/linux/apps/advent2020/5b.mu
new file mode 100644
index 00000000..197ea885
--- /dev/null
+++ b/linux/apps/advent2020/5b.mu
@@ -0,0 +1,81 @@
+# https://adventofcode.com/2020/day/5
+#
+# To run (on Linux):
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+#   $ ./translate apps/advent2020/5b.mu
+#   $ ./a.elf < input
+#
+# You'll need to register to download the 'input' file for yourself.
+
+fn main -> _/ebx: int {
+  var pass-storage: (array int 0x400)  # 1k ints
+  var pass/esi: (addr array int) <- address pass-storage
+  # phase 1: populate pass array
+  var line-storage: (stream byte 0x10)  # 16 bytes is enough
+  var line/edx: (addr stream byte) <- address line-storage
+  {
+    # read line from stdin
+    clear-stream line
+    read-line-from-real-keyboard line
+    # if line is empty (not even a newline), quit
+    var done?/eax: boolean <- stream-empty? line
+    compare done?, 0/false
+    break-if-!=
+    # process line
+    var seat-id/eax: int <- convert-from-binary line
+    var dest/eax: (addr int) <- index pass, seat-id
+    copy-to *dest, 1
+    loop
+  }
+  # phase 2: skip empty seats
+  var i/eax: int <- copy 0
+  {
+    compare i, 0x400
+    break-if->=
+    var src/ecx: (addr int) <- index pass, i
+    compare *src, 0
+    break-if-!=
+    i <- increment
+    loop
+  }
+  # phase 3: skip non-empty seats
+  {
+    compare i, 0x400
+    break-if->=
+    var src/ecx: (addr int) <- index pass, i
+    compare *src, 0
+    break-if-=
+    i <- increment
+    loop
+  }
+  print-int32-decimal 0, i
+  print-string 0, "\n"
+  return 0
+}
+
+fn convert-from-binary in: (addr stream byte) -> _/eax: int {
+  var result/edi: int <- copy 0
+  var i/ecx: int <- copy 9  # loop counter and also exponent
+  {
+    compare i, 0
+    break-if-<
+    var c/eax: byte <- read-byte in
+    var bit/edx: int <- copy 0
+    {
+      compare c, 0x42/B
+      break-if-!=
+      bit <- copy 1
+    }
+    {
+      compare c, 0x52/R
+      break-if-!=
+      bit <- copy 1
+    }
+    var bit-value/eax: int <- repeated-shift-left bit, i
+    result <- add bit-value
+    i <- decrement
+    loop
+  }
+  return result
+}
diff --git a/linux/apps/advent2020/vimrc.vim b/linux/apps/advent2020/vimrc.vim
new file mode 100644
index 00000000..348fe364
--- /dev/null
+++ b/linux/apps/advent2020/vimrc.vim
@@ -0,0 +1,2 @@
+" when opening files in this directory, load vimrc from cwd (top-level)
+source vimrc.vim