https://github.com/akkartik/mu/blob/main/linux/apps/advent2020/4b.mu
  1 # https://adventofcode.com/2020/day/4
  2 #
  3 # To run (on Linux):
  4 #   $ git clone https://github.com/akkartik/mu
  5 #   $ cd mu
  6 #   $ ./translate apps/advent2020/4b.mu
  7 #   $ ./a.elf < input
  8 #
  9 # You'll need to register to download the 'input' file for yourself.
 10 
 11 fn main -> _/ebx: int {
 12   var curr-passport-field-count/esi: int <- copy 0
 13   var valid-passport-count/edi: int <- copy 0
 14   var line-storage: (stream byte 0x100)  # 256 bytes
 15   var line/ecx: (addr stream byte) <- address line-storage
 16   var key-slice-storage: slice
 17   var key-slice/edx: (addr slice) <- address key-slice-storage
 18   var val-slice-storage: slice
 19   var val-slice/ebx: (addr slice) <- address val-slice-storage
 20   $main:line-loop: {
 21     # read line from stdin
 22     clear-stream line
 23     read-line-from-real-keyboard line
 24     # if line is empty (not even a newline), quit
 25     var done?/eax: boolean <- stream-empty? line
 26     compare done?, 0/false
 27     break-if-!=
 28     print-stream-to-real-screen line
 29     # if line has just a newline, process passport
 30     skip-chars-matching-whitespace line
 31     var new-passport?/eax: boolean <- stream-empty? line
 32     {
 33       compare new-passport?, 0/false
 34       break-if-=
 35       compare curr-passport-field-count, 7
 36       {
 37         break-if-!=
 38         valid-passport-count <- increment
 39         print-string 0, "=> "
 40         print-int32-decimal 0, valid-passport-count
 41         print-string 0, "\n"
 42       }
 43       curr-passport-field-count <- copy 0
 44       loop $main:line-loop
 45     }
 46     $main:word-loop: {
 47       skip-chars-matching-whitespace line
 48       var done?/eax: boolean <- stream-empty? line
 49       compare done?, 0/false
 50       break-if-!=
 51       next-token line, 0x3a, key-slice  # ':'
 52       var dummy/eax: byte <- read-byte line  # skip ':'
 53       next-raw-word line, val-slice
 54       print-slice-to-real-screen key-slice
 55       print-string 0, " : "
 56       print-slice-to-real-screen val-slice
 57       print-string 0, "\n"
 58       # treat cid as optional
 59       var cid?/eax: boolean <- slice-equal? key-slice, "cid"
 60       compare cid?, 0/false
 61       loop-if-!=
 62       # increment field count
 63       curr-passport-field-count <- increment
 64       # - validate fields one by one, setting curr-passport-field-count to impossibly high value to signal invalid
 65       # byr
 66       {
 67         var byr?/eax: boolean <- slice-equal? key-slice, "byr"
 68         compare byr?, 0/false
 69         break-if-=
 70         # 1920 <= byr <= 2002
 71         var byr/eax: int <- parse-decimal-int-from-slice val-slice
 72         compare byr, 0x780  # 1920
 73         {
 74           break-if->=
 75           print-string 0, "invalid\n"
 76           curr-passport-field-count <- copy 8
 77         }
 78         compare byr, 0x7d2  # 2002
 79         {
 80           break-if-<=
 81           print-string 0, "invalid\n"
 82           curr-passport-field-count <- copy 8
 83         }
 84       }
 85       # iyr
 86       {
 87         var iyr?/eax: boolean <- slice-equal? key-slice, "iyr"
 88         compare iyr?, 0/false
 89         break-if-=
 90         # 2010 <= iyr <= 2020
 91         var iyr/eax: int <- parse-decimal-int-from-slice val-slice
 92         compare iyr, 0x7da  # 2010
 93         {
 94           break-if->=
 95           print-string 0, "invalid\n"
 96           curr-passport-field-count <- copy 8
 97         }
 98         compare iyr, 0x7e4  # 2020
 99         {
100           break-if-<=
101           print-string 0, "invalid\n"
102           curr-passport-field-count <- copy 8
103         }
104       }
105       # eyr
106       {
107         var eyr?/eax: boolean <- slice-equal? key-slice, "eyr"
108         compare eyr?, 0/false
109         break-if-=
110         # 2020 <= eyr <= 2030
111         var eyr/eax: int <- parse-decimal-int-from-slice val-slice
112         compare eyr, 0x7e4  # 2020
113         {
114           break-if->=
115           print-string 0, "invalid\n"
116           curr-passport-field-count <- copy 8
117         }
118         compare eyr, 0x7ee  # 2030
119         {
120           break-if-<=
121           print-string 0, "invalid\n"
122           curr-passport-field-count <- copy 8
123         }
124       }
125       # hgt
126       {
127         var hgt?/eax: boolean <- slice-equal? key-slice, "hgt"
128         compare hgt?, 0/false
129         break-if-=
130         # convert val
131         var s: (handle array byte)
132         var s2/eax: (addr handle array byte) <- address s
133         _slice-to-string val-slice, s2
134         var s3/eax: (addr array byte) <- lookup *s2
135         var s4/ebx: (addr array byte) <- copy s3
136         # check suffix
137         var start/edx: int <- length s4
138         start <- subtract 2  # luckily both 'in' and 'cm' have the same length
139         {
140           var suffix-h: (handle array byte)
141           var suffix-ah/ecx: (addr handle array byte) <- address suffix-h
142           substring s4, start, 2, suffix-ah
143           var suffix/eax: (addr array byte) <- lookup *suffix-ah
144           {
145             var match?/eax: boolean <- string-equal? suffix, "in"
146             compare match?, 0/false
147             break-if-=
148             # if suffix is "in", 59 <= val <= 96
149             var num-h: (handle array byte)
150             var num-ah/ecx: (addr handle array byte) <- address num-h
151             substring s4, 0, start, num-ah
152             var num/eax: (addr array byte) <- lookup *num-ah
153             var val/eax: int <- parse-decimal-int num
154             compare val, 0x3b  # 59
155             {
156               break-if->=
157           print-string 0, "invalid\n"
158               curr-passport-field-count <- copy 8
159             }
160             compare val, 0x60  # 96
161             {
162               break-if-<=
163           print-string 0, "invalid\n"
164               curr-passport-field-count <- copy 8
165             }
166             loop $main:word-loop
167           }
168           {
169             var match?/eax: boolean <- string-equal? suffix, "cm"
170             compare match?, 0/false
171             break-if-=
172             # if suffix is "cm", 150 <= val <= 193
173             var num-h: (handle array byte)
174             var num-ah/ecx: (addr handle array byte) <- address num-h
175             substring s4, 0, start, num-ah
176             var num/eax: (addr array byte) <- lookup *num-ah
177             var val/eax: int <- parse-decimal-int num
178             compare val, 0x96  # 150
179             {
180               break-if->=
181           print-string 0, "invalid\n"
182               curr-passport-field-count <- copy 8
183             }
184             compare val, 0xc1  # 193
185             {
186               break-if-<=
187           print-string 0, "invalid\n"
188               curr-passport-field-count <- copy 8
189             }
190             loop $main:word-loop
191           }
192           print-string 0, "invalid\n"
193           curr-passport-field-count <- copy 8
194           loop $main:word-loop
195         }
196       }
197       # hcl
198       {
199         var hcl?/eax: boolean <- slice-equal? key-slice, "hcl"
200         compare hcl?, 0/false
201         break-if-=
202         # convert val
203         var s: (handle array byte)
204         var s2/eax: (addr handle array byte) <- address s
205         _slice-to-string val-slice, s2
206         var s3/eax: (addr array byte) <- lookup *s2
207         # check length
208         var len/ebx: int <- length s3
209         compare len, 7
210         {
211           break-if-=
212           print-string 0, "invalid\n"
213           curr-passport-field-count <- copy 8
214           loop $main:word-loop
215         }
216         # check first byte
217         {
218           var c/eax: (addr byte) <- index s3, 0
219           var c2/eax: byte <- copy-byte *c
220           compare c2, 0x23/hash
221           break-if-=
222           print-string 0, "invalid2\n"
223           curr-passport-field-count <- copy 8
224           loop $main:word-loop
225         }
226         # check remaining bytes
227         var i/ebx: int <- copy 1  # skip 0
228         {
229           compare i, 7
230           break-if->=
231           var c/eax: (addr byte) <- index s3, i
232           {
233             var c2/eax: byte <- copy-byte *c
234             var valid?/eax: boolean <- hex-digit? c2
235             compare valid?, 0
236             loop-if-= $main:word-loop
237           }
238           i <- increment
239           loop
240         }
241       }
242       # ecl
243       {
244         var ecl?/eax: boolean <- slice-equal? key-slice, "ecl"
245         compare ecl?, 0/false
246         break-if-=
247         var amb?/eax: boolean <- slice-equal? val-slice, "amb"
248         compare amb?, 0/false
249         loop-if-!= $main:word-loop
250         var blu?/eax: boolean <- slice-equal? val-slice, "blu"
251         compare blu?, 0/false
252         loop-if-!= $main:word-loop
253         var brn?/eax: boolean <- slice-equal? val-slice, "brn"
254         compare brn?, 0/false
255         loop-if-!= $main:word-loop
256         var gry?/eax: boolean <- slice-equal? val-slice, "gry"
257         compare gry?, 0/false
258         loop-if-!= $main:word-loop
259         var grn?/eax: boolean <- slice-equal? val-slice, "grn"
260         compare grn?, 0/false
261         loop-if-!= $main:word-loop
262         var hzl?/eax: boolean <- slice-equal? val-slice, "hzl"
263         compare hzl?, 0/false
264         loop-if-!= $main:word-loop
265         var oth?/eax: boolean <- slice-equal? val-slice, "oth"
266         compare oth?, 0/false
267         loop-if-!= $main:word-loop
268         print-string 0, "invalid\n"
269         curr-passport-field-count <- copy 8
270       }
271       # pid
272       {
273         var pid?/eax: boolean <- slice-equal? key-slice, "pid"
274         compare pid?, 0/false
275         break-if-=
276         # convert val
277         var s: (handle array byte)
278         var s2/eax: (addr handle array byte) <- address s
279         _slice-to-string val-slice, s2
280         var s3/eax: (addr array byte) <- lookup *s2
281         # check length
282         var len/eax: int <- length s3
283         compare len, 9
284         {
285           break-if-=
286           print-string 0, "invalid\n"
287           curr-passport-field-count <- copy 8
288           loop $main:word-loop
289         }
290         # check valid decimal int
291         # parse-decimal-int-from-slice currently returns 0 on invalid parse,
292         # which isn't ideal but suffices for our purposes
293         var val/eax: int <- parse-decimal-int-from-slice val-slice
294         compare val, 0
295         {
296           break-if->
297           print-string 0, "invalid\n"
298           curr-passport-field-count <- copy 8
299         }
300       }
301       loop
302     }
303     loop
304   }
305   # process final passport
306   compare curr-passport-field-count, 7
307   {
308     break-if-!=
309     valid-passport-count <- increment
310   }
311   print-int32-decimal 0, valid-passport-count
312   print-string 0, "\n"
313   return 0
314 }