d2878c38 |
(in-package #:tempores.parser)
|
9ba1ff14 |
(defun .digit ()
(.is #'digit-char-p))
(defun .first-hour-char ()
(.is (lambda (x)
(member x '(#\0 #\1 #\2)))))
(defun .first-minute-char ()
(.is (lambda (x)
(member x '(#\0 #\1 #\2 #\3 #\4 #\5)))))
(defun .time-separator ()
(.char= #\:))
|
34aff00c |
; TODO: consider adding one-digit hours
|
9ba1ff14 |
(defun .hour ()
(.let* ((f (.first-hour-char))
(s (.digit)))
(if (or (char/= f #\2) (member s '(#\0 #\1 #\2 #\3))) ;; make sure we don't get 24
|
34aff00c |
(.identity (coerce (vector f s) 'string))
|
9ba1ff14 |
(.fail))))
(defun .minute-or-second ()
(.let* ((f (.first-minute-char))
(s (.digit)))
|
34aff00c |
(.identity (coerce (vector f s) 'string))))
|
9ba1ff14 |
(defun .time-range-separator ()
(.string= "--"))
|
34aff00c |
(define-condition invalid-time (parsing-error) ()
(:report (lambda (condition stream)
(format stream "Not a valid time part ~s"
(failed-chunk condition)))))
(defun .valid-time ()
|
9ba1ff14 |
(.let* ((hour (.hour))
(_ (.time-separator))
(minute (.minute-or-second))
(second (.optional
(.progn (.time-separator)
(.minute-or-second)))))
|
34aff00c |
(.identity (list hour minute (if second second "0")))))
(defun .invalid-time ()
(flet ((.non-time-character ()
(.map 'string
(.and (.not (.or (.time-separator)
(.char= #\newline)
(.char= #\space)))
(.item)))))
(.let* ((hour (.or (.hour)
(.non-time-character)))
(_ (.time-separator))
(minute (.or (.minute-or-second)
(.non-time-character)))
(second (.optional
(.progn (.time-separator)
(.or (.minute-or-second)
(.non-time-character))))))
(error 'invalid-time
:failed-chunk (concatenate 'string
(string hour) ":"
(string minute)
(if second
(concatenate 'string ":" (string second))
""))))))
(defun .time ()
(.let* ((time (.or (.valid-time) (.invalid-time))))
(.identity
(apply #'make-time-obj
(mapcar #'parse-integer
time)))))
|
9ba1ff14 |
(defun .time-unit ()
(.or
|
51faefd0 |
(.string= "mins")
|
9ba1ff14 |
(.string= "hrs")
(.string= "min")
(.string= "hr")))
(defun .time-mod ()
(.let* ((sign (.or (.char= #\+) (.char= #\-)))
(num (.first (.map 'string (.digit))))
(unit (.time-unit)))
(.identity
(make-time-mod
(parse-integer
(with-output-to-string (s)
(princ sign s)
(princ num s)))
unit))))
(defun .peek (parser)
(lambda (input)
(if (run parser input)
(list (cons t input))
(run (.fail) input))))
(defun .time-range ()
(.let* ((start (.time))
|
51faefd0 |
(_ (.prog1
|
9ba1ff14 |
(.time-range-separator)
(.peek (.not (.char= #\,)))))
(done (.optional (.time)))
(mod (.optional (.time-mod))))
(when (and mod (not done))
(.fail))
(if done
(if mod
(.identity
(list start done mod))
(.identity (list start done)))
(.identity (list start)))))
(defun .zero-or-more (parser)
(.plus (.let* ((x parser)
(xs (.zero-or-more parser)))
(.identity (cons x xs)))
(.identity ())))
(defun .range-list-separator ()
(.char= #\,))
(defun .range-list ()
(.let*
((ranges (.prog1
(.map 'list
(.prog1 (.time-range)
(.optional (.progn (.range-list-separator)
(.zero-or-more (.char= #\Space))))))
(.char= #\Newline))))
(let ((lengths (map 'vector #'length ranges)))
(if (/= 0 (elt lengths (1- (length lengths))))
(.identity ranges)
(.fail)))))
|
34aff00c |
(defun .whitespace-char ()
(.or (.char= #\tab) (.char= #\space)))
(defun .whitespace ()
(.map 'string (.whitespace-char)))
(defun .valid-initial-space ()
(.or (.string= (string #\tab))
(.string= " ")))
(defun .extra-whitespace ()
(.let* ((_ (.valid-initial-space))
(extra-space (.optional (.whitespace))))
(if extra-space
(error 'invalid-whitespace :failed-chunk extra-space)
|
af9f5f80 |
(.fail))))
|
34aff00c |
|
9ba1ff14 |
(defun .initial-space ()
|
34aff00c |
(.or (.extra-whitespace)
(.valid-initial-space)))
|
9ba1ff14 |
(defun .time-line-start ()
(.progn (.initial-space)
(.string= "start@")))
(defun .time-line ()
(.progn
(.time-line-start)
(.range-list)))
(defun .client-separator ()
(.char= #\:))
(defun .client-name ()
(.prog1
(.map 'string (.and (.not (.client-separator)) (.item)) :at-least 0)
(.client-separator)))
(defun .memo ()
(.prog2
(.map 'string (.char= #\Space))
(.first
(.map 'string
(.and (.not (.char= #\Newline))
(.item))))
(.optional (.not (.item)))))
(defun .memo-line ()
(.progn
(.initial-space)
(.let* ((client (.client-name))
(memo (.memo)))
(.identity (list client memo)))))
(defun .record ()
(.let* ((time-line (.time-line))
(memo-line (.memo-line)))
(.identity (make-time-record time-line memo-line))))
(defun .records ()
(.first
(.map 'list (.prog1 (.record)
(.or (.map 'string (.char= #\Newline))
(.progn
(.char= #\Newline)
(.not (.item)))
(.not (.item)))))))
(defun .weekday ()
(.or (.string-equal "Sunday")
(.string-equal "Monday")
(.string-equal "Tuesday")
(.string-equal "Wednesday")
(.string-equal "Thursday")
(.string-equal "Friday")
(.string-equal "Saturday")))
(defun .year ()
(.let* ((fi (.digit))
(se (.digit))
(th (.digit))
(fo (.digit)))
(.identity (coerce (list fi se th fo) 'string))))
(defun .first-month-char ()
|
870680c2 |
(.or (.char= #\0) (.char= #\1) (.char= #\2) (.char= #\3)))
|
9ba1ff14 |
(defun .first-day-char ()
(.or (.char= #\0) (.char= #\1) (.char= #\2) (.char= #\3)))
(defun .month ()
(.let* ((fi (.first-month-char))
(se (.digit)))
|
870680c2 |
(let ((res (when (char= fi #\3)
(unless (member se '(#\1 #\0))
(.fail)))))
(if (not res)
(.identity (coerce (list fi se) 'string))
res))))
|
9ba1ff14 |
(defun .day ()
(.let* ((fi (.first-month-char))
(se (.digit)))
(when (and (char= fi #\3) (not (member se '(#\0 #\1))))
(.fail))
(.identity (coerce (list fi se) 'string))) )
(defun .date-separator ()
|
b953500d |
(.or (.char= #\-)
(.char= #\/)))
|
9ba1ff14 |
(defun .date ()
(.let* ((dow (.weekday))
|
b953500d |
(_ (.optional (.char= #\,)))
|
9ba1ff14 |
(_ (.char= #\Space))
(year (.year))
|
b953500d |
(sep1 (.date-separator))
|
9ba1ff14 |
(month (.month))
|
b953500d |
(sep2 (.date-separator))
|
9ba1ff14 |
(day (.day)))
|
2d700b03 |
(let ((year (parse-integer year))
(month (parse-integer month))
(day (parse-integer day)))
(.identity (make-date-obj dow year month day)))))
|
9ba1ff14 |
(defun .date-start ()
(.string= "-- "))
(defun .date-line ()
(.prog2 (.date-start)
(.date)
(.char= #\Newline)))
(defun .date-record ()
(.let* ((date (.date-line))
|
74adbb8b |
(records (.records)))
(.identity (make-day-entry date records))))
|
9ba1ff14 |
(defun .date-records ()
(.first (.map 'list (.date-record))))
(defun .parse-all-records ()
(.prog1 (.date-records) (.not (.item))))
;; This will help make sure everything is consumed when
;; we don't care about the parser's output.
(defun cdar-equal (a b) (== (cdar a) (cdar b)))
|