
Advent of Z80 - Day 7, 2022


Day 7 is a challenge primarily about parsing the input, with a little side-trip into a recursive algorithm and a dab of 32-bit arithmetic on an 8-bit processor.
JP DAY07
Regrettably, I contracted COVID this week. As a result the following is light on prose and heavy on code I originally wrote on the 8th of December 2022. You will also notice that the code style is rather different: I’ve settled on a particular style for this series, using lowercase mnemonics and code blocks; a large portion of the code below is a bit more shouty.
The general approach I took was to produce a list of directory sizes. Star 1 requires summing all directory sizes below 100,000 while Star 2 requires finding the smallest directory greater than a number to be determined. The size of a directory is the size of all its files plus all its subdirectories, so the code below scans the lines in the input recursively determining directory sizes when $ cd <dir>
is encountered, returning when $ cd ..
is encountered, and summing up file sizes.
Once the set of sizes is determined, star 1 is simply scanning the list for values under 100,000 and summing them up, while star 2 first requires determining the minimum size acceptable, then scanning for the smallest value larger than that.
DAY07:
LD SP, 8000H
LD IX, INPUT
LD IY, SIZES
CALL READDIR
JP FINISH
; in: IX input lines
; IY dest for size
; out: DE:BC dir size
; IX next input
; IY next size
READDIR:
PUSH AF
PUSH HL
PUSH IY
LD IY, 0
LD HL, 0
; begin by consuming up to a newline
; for the first invocation this consumes '$ cd /'
; for subsequent invocations it consumes the remainder of '$ cd <dir>'
JR NEWLINE
READLINE:
LD A, (IX+0)
OR A
JR Z, LEAVEDIR
CP '$' ; if $ ... parse command
JR Z, CMD
CP 'd' ; if 'dir <name>' ignore
JR Z, NEWLINE
; if '<size> name' add size, ignore name
CALL READ32
ADD IY, BC
ADC HL, DE
; Keep consuming input until end of input reached or a newline is consumed
NEWLINE:
LD A, (IX + 0)
OR A
JR Z, LEAVEDIR
INC IX
CP '\n'
JR Z, READLINE
JR NEWLINE
; Code flow reaches here on end of input or '$ cd ..'
; The directory's size is written into (IY) and READDIR returns
LEAVEDIR:
; Move the size into DE:BC
EX DE, HL
PUSH IY
POP BC
; Store the size
POP IY
LD (IY+0), C
LD (IY+1), B
LD (IY+2), E
LD (IY+3), D
INC IY
INC IY
INC IY
INC IY
; Increment the count of dirs
LD HL, DIRS
INC (HL)
; Return
POP HL
POP AF
RET
; Could be '$ cd /' or '$ cd ..' or '$ cd <name>' or '$ ls'
; By the time this code is reached '$ cd /' is already consumed
CMD:
LD A, (IX+2)
CP 'l' ; if '$ ls' ignore
JR Z, NEWLINE
; must be $ cd <path>
LD A, (IX+5)
CP '.' ; if '$ cd ..' then return to caller
JR Z, LEAVEDIR
; it must be '$ cd <dir>'
EX (SP), IY ; switch high word of count for sizes index
CALL READDIR
EX (SP), IY ; switch them back
ADD IY, BC
ADC HL, DE
JR NEWLINE
SUM: DEFB 0, 0, 0, 0
FINISH:
; sum the directory sizes <= 100000, 0x186a0
LD IX, SIZES
LD A, (DIRS)
LD B, A
SUMSMALL:
; the highest byte must be zero to match
LD A, (IX+3)
OR A
JR NZ, SUMLOOP
; if next byte also zero, it's small enough
LD A, (IX+2)
OR A
JR Z, SMALL
; else it must be exactly 1
CP 1
JR NZ, SUMLOOP
; the low word must be <= 86A0
LD H, (IX+1)
LD L, (IX+0)
LD DE, 086A1H
OR A ; clear carry
SBC HL, DE
JR NC, SUMLOOP
SMALL:
LD HL, (SUM)
LD D, (IX+1)
LD E, (IX+0)
OR A
ADC HL, DE ; low word
LD (SUM), HL
LD HL, (SUM+2)
LD D, (IX+3)
LD E, (IX+2)
ADC HL, DE ; high word
LD (SUM+2), HL
SUMLOOP:
INC IX
INC IX
INC IX
INC IX
DJNZ SUMSMALL
LD IY, (SUM)
LD HL, (SUM+2)
CALL PRINT32
LD A, '\n'
OUT (1), A
; 70,000,000 units of total space
; 30,000,000 units must be free
; used space is the last value in SIZES
; so subtract 40,000,000 from that for the target size
ld a, (DIRS)
ld l, a
ld h, 0
add hl, hl ; * 2
add hl, hl ; * 4
ld de, SIZES - 4
add hl, de
; more useful to have this in IX
push hl
pop ix
; load low word into de
ld e, (ix+0)
ld d, (ix+1)
ld hl, -40000000 & $ffff
add hl, de
ld c, (ix+2)
ld b, (ix+3)
ex de, hl
ld hl, -40000000 shr 16
adc hl, bc
; HL:DE contains the min size
ld b, a
ld ix, SIZES
TARGET:
; compare msb down
ld a, (ix+3)
cp h
jr c, TOOSMALL
jr nz, CHECKMIN
ld a, (ix+2)
cp l
jr c, TOOSMALL
jr nz, CHECKMIN
ld a, (ix+1)
cp d
jr c, TOOSMALL
jr nz, CHECKMIN
ld a, (ix+0)
cp e
jr c, TOOSMALL
CHECKMIN:
ld c, (ix+3)
ld a, (MIN+3)
cp c
jr c, TOOSMALL
jr nz, SMALLEST
ld c, (ix+2)
ld a, (MIN+2)
cp c
jr c, TOOSMALL
jr nz, SMALLEST
ld c, (ix+1)
ld a, (MIN+1)
cp c
jr c, TOOSMALL
jr nz, SMALLEST
ld c, (ix+0)
ld a, (MIN+0)
cp c
jr c, TOOSMALL
SMALLEST:
ld a, (ix+0)
ld (MIN+0), a
ld a, (ix+1)
ld (MIN+1), a
ld a, (ix+2)
ld (MIN+2), a
ld a, (ix+3)
ld (MIN+3), a
TOOSMALL:
inc ix
inc ix
inc ix
inc ix
djnz TARGET
ld iy, (MIN)
ld hl, (MIN+2)
call PRINT32
ld a, '\n'
out (1), a
halt
include "util32.z80"
.
INPUT:
include "day07-input.z80"
.
; sample input, ignored because above is zero terminated
"$ cd /\n"
db "$ ls\n"
db "dir a\n"
db "14848514 b.txt\n"
db "8504156 c.dat\n"
db "dir d\n"
db "$ cd a\n"
db "$ ls\n"
db "dir e\n"
db "29116 f\n"
db "2557 g\n"
db "62596 h.lst\n"
db "$ cd e\n"
db "$ ls\n"
db "584 i\n"
db "$ cd ..\n"
db "$ cd ..\n"
db "$ cd d\n"
db "$ ls\n"
db "4060174 j\n"
db "8033020 d.log\n"
db "5626152 d.ext\n"
db "7214296 k\n"
db 0
db
MIN: db $ff, $ff, $ff, $ff
DIRS: db 0
SIZES:
The series
Day 1 | Day 2 | Day 3 | Day 4 | Day 5 | Day 6 | Day 7 | Day 8 | Day 9 | Day 10 | Day 11 | Day 12 | Day 13 | Day 14 | Day 15 | Day 16 | Day 17 | Day 18 | Day 19 | Day 20 | Day 21 | Day 22 | Day 23 | Day 24 | Day 25 |