(defpackage :multi-fun (:shadow :rest) (:use :cl ) (:export #:filter-functions #:number-recognizer #:delimited-field #:ignore-char #:whitespace #:iso-8601-timestamp #:parse-qs #:rest #:as #:splat-result #:defun #:month-recognizer #:take-field #:parse-format #:parse-file #:subformat #:treeify)) (in-package :multi-fun) (defun filter-functions (funs list) (loop for fun in funs for results = (loop for value in list for (result . num) = (multiple-value-list (funcall fun value)) when result collect (list (car num) result)) when (and results (= (length results) (length list))) collect fun)) (defun number-recognizer (range) (destructuring-bind (min max) (etypecase range (integer (list 0 range)) (list range)) (flet ((in-range-p (n) (< min n (1+ max)))) (lambda (str) (loop for digits from (min (length str) (ceiling (log max 10))) downto (if (<= min 0) 1 (max 1 (floor (log min 10)))) for (result . rest) = (multiple-value-list (parse-integer str :end digits :junk-allowed t)) for relevant-result = (and result (in-range-p result)) until relevant-result finally (when relevant-result (return (values-list (cons result rest))))))))) (defun delimited-field (delimiter) (lambda (str) (alexandria:if-let ((val (position delimiter str))) (values (subseq str 0 val) (min (length str) (1+ val))) (values str (length str))))) (defun ignore-char (char) (lambda (str) (when (char= (elt str 0) char) (values 'drop 1)))) (defun whitespace () (lambda (str) (values 'drop (position-if-not 'serapeum:whitespacep str)))) (defun iso-8601-timestamp () (lambda (str) (values (local-time:parse-timestring (subseq str 0 32)) 32))) (defun parse-qs (str) (serapeum:mapply 'cons (map 'list (serapeum:op (coerce (fwoar.string-utils:split #\= _ :count 2) 'list)) (fwoar.string-utils:split #\& str)))) (defun rest (&optional (as #'identity)) (lambda (str) (values (funcall as str) (length str)))) (defun as (fun transform) (flet ((do-transform (val) (funcall transform val))) (lambda (str) (fw.lu:transform-first-value (do-transform (funcall fun str)))))) (defun splat-result (fun) (flet ((splatize (val) (list* 'splat (alexandria:ensure-list val)))) (lambda (str) (fw.lu:transform-first-value (splatize (funcall fun str)))))) (defun month-recognizer (str) (macrolet ((match-char (elt &body body) (let ((cases (mapcar (serapeum:op (if (listp _1) `(,(elt (car _1) elt) (match-char ,(1+ elt) ,@_1)) `(,(elt _1 elt) (when (alexandria:starts-with-subseq ,_1 str) ,_1)))) body))) `(when (> (length str) ,elt) (case (elt str ,elt) ,@cases))))) (alexandria:if-let ((value (match-char 0 ("April" "August") "December" "February" ("January" ("July" "June")) (("March" "May")) "November" "October" "September"))) (values value (length value)) (values nil 0)))) (defun take-field (fun strs) (loop for str in strs for (val chars-read) = (multiple-value-list (funcall fun str)) do (format t "~&VAL: ~s CHARS-READ: ~s" val chars-read) collect (list val (subseq str chars-read)))) (defun parse-format (funs str) (loop for line = str then (subseq line chars-read) for fun in funs for (val chars-read) = (multiple-value-list (funcall fun line)) for total-chars = chars-read then (+ total-chars chars-read) if (and (consp val) (eql (car val) 'splat)) append (cdr val) into result else when (not (eql val 'drop)) collect val into result finally (return (values result total-chars)))) (defmacro subformat ((v-sym &rest parsers) &body transform) `(flet ((transform (,v-sym) ,@(if transform transform (list v-sym)))) (lambda (str) (fw.lu:transform-first-value (transform (parse-format (list ,@parsers) str)))))) (defun parse-file (format file &optional (get-record #'read-line)) (let* ((records ()) (cur records)) (flet ((collect-record (record) (let ((new-cdr (list record))) (if cur (setf (cdr cur) new-cdr) (setf records new-cdr)) (setf cur new-cdr)))) (loop (let* ((line (funcall get-record file nil 'eof))) (when (eql line 'eof) (return records)) (with-simple-restart (skip-record "Skip line ~s" line) (collect-record (coerce (parse-format format line) 'vector)))))))) (defun treeify (strings) (declare (optimize (speed 3)) (inline data-lens:over data-lens:transform-tail data-lens:applicable-when data-lens:of-min-length data-lens:on data-lens:over data-lens:slice data-lens:compress-runs data-lens:combine-matching-lists data-lens:juxt data-lens:element data-lens:sorted)) (let* ((strip-prefixes (alexandria:compose (data-lens:over (data-lens:transform-tail (data-lens:over (data-lens:transform-head (data-lens:slice 1))))) (data-lens:compress-runs :collector 'data-lens:combine-matching-lists))) (extract-keys-and-sort (alexandria:compose (data-lens:over (data-lens:juxt (alexandria:compose (data-lens:element 0) (data-lens:element 0)) 'identity)) (data-lens:sorted 'char< :key (alexandria:compose (data-lens:element 0) (data-lens:element 0))))) (recurse (data-lens:over (data-lens:transform-tail (data-lens:applicable-when (lambda (x) (if (equal (caar x) "") (cons (cons nil (cdar x)) (treeify (cdr x))) (treeify x))) (data-lens:of-min-length 2))))) (step (data-lens:on strip-prefixes extract-keys-and-sort))) (funcall (alexandria:compose recurse step) strings))) (defparameter +months+ '("January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December"))