This was actually much more work than I thought it would be. Tests pass, but I'm sure there's some regressions in here.