123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- (import
- (except (rnrs base) let-values map error)
- (only (guile) lambda* λ command-line string-null?)
- (ice-9 peg)
- (srfi srfi-1) ; list procs
- (srfi srfi-69) ; hash-tables
- (fileio))
- ;; GRAMMAR
- (define-peg-pattern DIGIT body
- (or (range #\0 #\9)))
- (define-peg-pattern ALPHA-NUMERIC body
- (or (range #\0 #\9) (range #\a #\z) (range #\A #\Z)))
- (define-peg-pattern HEXADECIMAL body
- (or (range #\0 #\9) (range #\a #\f)))
- (define-peg-pattern KEY-VALUE-SEP none ":")
- (define-peg-pattern PAIR-SEP body " ")
- (define-peg-pattern LENGTH-UNIT all (or "in" "cm"))
- (define-peg-pattern LENGTH-NUMBER all (+ DIGIT))
- (define-peg-pattern EYE-COLOR body (or "amb" "blu" "brn" "gry" "grn" "hzl" "oth"))
- (define-peg-pattern VALUE-CHARS body
- (or (range #\0 #\9) (range #\a #\z) (range #\A #\Z) "#"))
- (define-peg-pattern BIRTH-YEAR-VALUE all (and DIGIT DIGIT DIGIT DIGIT))
- (define-peg-pattern ISSUE-YEAR-VALUE all (and DIGIT DIGIT DIGIT DIGIT))
- (define-peg-pattern EXPIRATION-YEAR-VALUE all (and DIGIT DIGIT DIGIT DIGIT))
- (define-peg-pattern HEIGHT-VALUE all (and LENGTH-NUMBER LENGTH-UNIT))
- (define-peg-pattern HAIR-COLOR-VALUE all
- (and "#" HEXADECIMAL HEXADECIMAL HEXADECIMAL HEXADECIMAL HEXADECIMAL HEXADECIMAL))
- (define-peg-pattern EYE-COLOR-VALUE all EYE-COLOR)
- (define-peg-pattern PASSPORT-ID-VALUE all
- (and DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT))
- (define-peg-pattern COUNTRY-ID-VALUE all (+ VALUE-CHARS))
- (define-peg-pattern BIRTH-YEAR-KEY body "byr")
- (define-peg-pattern ISSUE-YEAR-KEY body "iyr")
- (define-peg-pattern EXPIRATION-YEAR-KEY body "eyr")
- (define-peg-pattern HEIGHT-KEY body "hgt")
- (define-peg-pattern HAIR-COLOR-KEY body "hcl")
- (define-peg-pattern EYE-COLOR-KEY body "ecl")
- (define-peg-pattern PASSPORT-ID-KEY body "pid")
- (define-peg-pattern COUNTRY-ID-KEY body "cid")
- (define-peg-pattern KNOWN-KEY-VALUE-PAIRS body
- (or (and BIRTH-YEAR-KEY KEY-VALUE-SEP BIRTH-YEAR-VALUE)
- (and ISSUE-YEAR-KEY KEY-VALUE-SEP ISSUE-YEAR-VALUE)
- (and EXPIRATION-YEAR-KEY KEY-VALUE-SEP EXPIRATION-YEAR-VALUE)
- (and HEIGHT-KEY KEY-VALUE-SEP HEIGHT-VALUE)
- (and HAIR-COLOR-KEY KEY-VALUE-SEP HAIR-COLOR-VALUE)
- (and EYE-COLOR-KEY KEY-VALUE-SEP EYE-COLOR-VALUE)
- (and PASSPORT-ID-KEY KEY-VALUE-SEP PASSPORT-ID-VALUE)
- (and COUNTRY-ID-KEY KEY-VALUE-SEP COUNTRY-ID-VALUE)))
- (define-peg-pattern PASSPORT body
- (and (* (and KNOWN-KEY-VALUE-PAIRS PAIR-SEP))
- KNOWN-KEY-VALUE-PAIRS))
- (define get-value-from-peg-tree
- (λ (peg-tree label)
- ;; (simple-format (current-output-port) "looking for ~a in ~a\n" label peg-tree)
- (cond
- [(null? peg-tree) #f]
- [(pair? (car peg-tree))
- (or (get-value-from-peg-tree (car peg-tree) label)
- (get-value-from-peg-tree (cdr peg-tree) label))]
- [(string? (car peg-tree))
- (cond
- [(string=? (car peg-tree) label)
- (cdr peg-tree)]
- [else
- (get-value-from-peg-tree (cdr peg-tree) label)])]
- [else
- (get-value-from-peg-tree (cdr peg-tree) label)])))
- (define println
- (λ (thing)
- (simple-format (current-output-port) "~a\n" thing)))
- (define lines->passport-data
- (lambda* (lines #:key (passport-separator-test string-null?) (key-separator " "))
- (let next-line ([remaining-lines lines] [single-passport-data ""])
- (cond
- [(null? remaining-lines)
- (list (string-trim-both single-passport-data))]
- [else
- (let ([cur-line (car remaining-lines)])
- (cond
- ;; The passport-separator finishes an entry in
- ;; the lines. We cons the single passport data
- ;; onto the recursion and start collecting data
- ;; for the next single passport.
- [(passport-separator-test cur-line)
- (cons (string-trim-both single-passport-data)
- (next-line (cdr remaining-lines) ""))]
- ;; If more data for a single passport follows, we
- ;; append it onto the single passport data and
- ;; look at the next line.
- [else
- (next-line (cdr remaining-lines)
- (string-append single-passport-data
- key-separator
- cur-line))]))]))))
- (define valid-birth-year?
- (λ (byr)
- ;; (simple-format (current-output-port) "validating birth year: ~s\n" byr)
- (let* ([year (second (first byr))]
- [num-byr (string->number year)])
- ;; (simple-format (current-output-port) "validating num-byr: ~s\n" num-byr)
- (and (>= num-byr 1920)
- (<= num-byr 2002)))))
- (define valid-issue-year?
- (λ (iyr)
- ;; (simple-format (current-output-port) "validating issue year: ~s\n" iyr)
- (let* ([year (second (first iyr))]
- [num-iyr (string->number year)])
- ;; (simple-format (current-output-port) "validating num-iyr: ~s\n" num-iyr)
- (and (>= num-iyr 2010)
- (<= num-iyr 2020)))))
- (define valid-expiration-year?
- (λ (eyr)
- ;; (simple-format (current-output-port) "validating expiration year: ~s\n" eyr)
- (let* ([year (second (first eyr))]
- [num-eyr (string->number year)])
- ;; (simple-format (current-output-port) "validating num-eyr: ~s\n" num-eyr)
- (and (>= num-eyr 2020)
- (<= num-eyr 2030)))))
- (define valid-height?
- (λ (hgt)
- ;; (simple-format (current-output-port) "validating height: ~s\n" hgt)
- ;; (HEIGHT-VALUE (LENGTH-NUMBER 174) (LENGTH-UNIT cm))
- (let ([unit (second (third (car hgt)))]
- [num (string->number (second (second (car hgt))))])
- ;; (simple-format (current-output-port) "validating num: ~s\n" num)
- ;; (simple-format (current-output-port) "validating unit: ~s\n" unit)
- (or (and (string=? unit "in")
- (>= num 59)
- (<= num 76))
- (and (string=? unit "cm")
- (>= num 150)
- (<= num 193))))))
- (define valid-hair-color?
- (λ (hcl)
- ;; is already validated through parsing
- #t))
- (define valid-eye-color?
- (λ (ecl)
- ;; is already validated through parsing
- #t))
- (define valid-passport-id?
- (λ (pid)
- ;; is already validated through parsing
- #t))
- (define valid-country-id?
- (λ (cid)
- ;; is already validated through parsing
- #t))
- (define valid-passport?
- (λ (passport)
- (let ([tree (peg:tree passport)]
- [mandatory-keys-to-pred
- (alist->hash-table `(("byr" . ,valid-birth-year?)
- ("iyr" . ,valid-issue-year?)
- ("eyr" . ,valid-expiration-year?)
- ("hgt" . ,valid-height?)
- ("hcl" . ,valid-hair-color?)
- ("ecl" . ,valid-eye-color?)
- ("pid" . ,valid-passport-id?)))]
- [optional-keys '("cid")])
- (hash-table-fold mandatory-keys-to-pred
- (λ (key valid? prior-result)
- (and prior-result
- (if tree
- (let ([val (get-value-from-peg-tree tree key)])
- (if val (valid? val) #f))
- #f)))
- #t))))
- (define main
- (λ (cmd-line-args)
- (let* ([lines (get-lines-from-file (second cmd-line-args))]
- [passport-data (lines->passport-data lines)]
- [passports (map (λ (datum) (match-pattern PASSPORT datum))
- passport-data)])
- (length (filter (λ (pp) (valid-passport? pp))
- passports)))))
- (simple-format (current-output-port)
- "~a\n"
- (main (command-line)))
|