(library (lib) (export solve-part1 solve-part2) (import (chezscheme)) ; Global state... a sin, but very convenient in this case! (define chars '()) ; Characters still to be parsed (define part1-total 0) ; Total score of groups in input (define part2-total 0) ; Total non-escaped garbage chars ; Is the next character in the stream equal to `c'? (define (peek? c) (char=? c (car chars))) ; Blindly accept character and pop it. We do all detection ; via `peek?', so we don't care what gets discarded here. (define (next) (set! chars (cdr chars))) ; Parse a "group". We can assume all input is well-formed, ; which really helps keep this code as simple as possible. (define (parse-group depth) (next) ; Accept initial #\{ (let loop () ; Expect start of "group" or "garbage" (cond ((peek? #\{) (parse-group (+ depth 1))) ((peek? #\<) (parse-garbage))) ; Expect comma or end of current "group" (cond ((peek? #\,) (next) ; Accept #\, (loop)) ; Parse next "group"/"garbage" ((peek? #\}) ; Add current group's nesting depth to total score (set! part1-total (+ part1-total depth)) (next))))) ; Accept #\} and return ; Parse "garbage": everything until next non-escaped #\>. (define (parse-garbage) (let loop () (next) ; Accept char (#\< at first) (cond ((peek? #\>) (next)) ; Accept #\> and return ((peek? #\!) (next) ; Accept #\! (loop)) ; Continue to escaped char (else ; Add this character to the total count (set! part2-total (+ part2-total 1)) (loop))))) ; Reset global parser state and parse the input string (define (solve-puzzle str) (set! chars (string->list str)) (set! part1-total 0) (set! part2-total 0) (parse-group 1)) (define (solve-part1 str) (solve-puzzle str) part1-total) (define (solve-part2 str) (solve-puzzle str) part2-total) )