commit ac0bf7108272763fa16a318d8cf7bb8f53c727cc Spenser Truex <spensertruexonline@gmail.com> 2019-01-07 11:38:47 -0800 Initial
.gitignore | 18 ++++++++++++++++++ README.md | 34 ++++++++++++++++++++++++++++++++++ csv.asd | 9 +++++++++ csv.lisp | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ package.lisp | 4 ++++ utils.lisp | 20 ++++++++++++++++++++ 6 files changed, 140 insertions(+)
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25a2bf4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.FASL +*.fasl +*.lisp-temp +*.dfsl +*.pfsl +*.d64fsl +*.p64fsl +*.lx64fsl +*.lx32fsl +*.dx64fsl +*.dx32fsl +*.fx64fsl +*.fx32fsl +*.sx64fsl +*.sx32fsl +*.wx64fsl +*.wx32fsl +README.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fd1c77 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# CSV +Defines a lisp reader to read is CSV: + +`#!field,field,field!#` + +which is used to read in CSV files with `slurp`: + +`(slurp #p"example.csv")` + +and the result is a list: + +``` common-lisp +CL-USER> (csv:slurp #p"example.csv") +("example" "csv" "list" "result" "like" " this") +``` + +This program invokes the lisp reader, making it: +1) Potentially risky to use if the input it not trusted. +2) Resource intensive for medium to large files (>5MB). + +use with care. + +# Features: +Supports escaping quotes the CSV way, with quad quotes: +csv -> lisp +`""""`->`"\""` + +# Installation +Use ASDF: + +``` common-lisp +CL-USER> (asdf:load-system :csv) +T +``` diff --git a/csv.asd b/csv.asd new file mode 100644 index 0000000..35b1887 --- /dev/null +++ b/csv.asd @@ -0,0 +1,9 @@ +(asdf:defsystem :csv + :version "0.1.0" + :description "Read CSV files into lists." + :author "Spenser Truex spensertruex.com" + :serial t + :license "GNU GPL, version 3" + :components ((:file "package") + (:file "utils") + (:file "csv"))) diff --git a/csv.lisp b/csv.lisp new file mode 100644 index 0000000..6d5bbb0 --- /dev/null +++ b/csv.lisp @@ -0,0 +1,55 @@ +(in-package :csv) +(defun make-escapep (prev curr) + (char= #\" prev curr)) +(defun end-tokenp (prev curr) + (declare (ignore curr)) + (char= #\" prev)) +(defun openp (prev curr) + (declare (ignore curr)) + (char= #\" prev)) +(let ((in nil) + (escape nil)) + (setf (fdefinition 'switches) + (lambda (prev curr str) + (let ((str (if (array-has-fill-pointer-p str) + str + (make-array 1 :fill-pointer 0 + :adjustable t + :element-type 'character)))) + (if (null curr) + (progn (setf in nil escape nil) str) + (if in + (if escape + (progn (toggle escape) (push-on prev str)) + (if (make-escapep prev curr) + (progn (toggle escape) str) + (if (end-tokenp prev curr) + (progn (toggle in) (push-on #\Nul str)) + (push-on prev str)))) + (if (openp prev curr) + (progn (toggle in) str) + str))))))) +(defun splice (str) + (labels ((inner (str acc!) + (if (string= "" str) + (nreverse acc!) + (let ((res (search (string #\Nul) str))) + (inner (subseq str (1+ res)) (push (subseq str 0 res) acc!)))))) + (inner str nil))) +(make-reader my-csv (stream #\# #\!) + (macrolet ((once (&body body) ``',,@body)) + (do ((prev #1= (read-char stream nil nil) curr) + (curr #1# #1#) + (str (make-array 0 :adjustable t :element-type 'character) + (switches prev curr str))) + ((and (char= curr #\#) + (char= prev #\!)) (once (splice (switches nil nil str))))))) +(defun read-to-string (stream) + (with-output-to-string (s) + (dolines (line stream) + (princ line s) + (format s "~%")))) +(defun slurp (path) + "Read in a CSV file. Resource intensive, invokes the lisp reader." + (with-open-file (s path) + (concatenate 'string "#!" (read-to-string s) "!#"))) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..c0b5a5c --- /dev/null +++ b/package.lisp @@ -0,0 +1,4 @@ +(in-package :cl-user) +(defpackage #:csv + (:use #:cl) + (:export :slurp)) diff --git a/utils.lisp b/utils.lisp new file mode 100644 index 0000000..e26831a --- /dev/null +++ b/utils.lisp @@ -0,0 +1,20 @@ +(in-package :csv) +(defmacro dolines ((var stream) &body body) + `(do ((,var #1=(read-line ,stream nil nil) #1#)) + ((null ,var)) + ,@body)) +(defmacro with-gensyms (symbols &body body) + "Create gensyms for those symbols." + `(let (,@(mapcar #'(lambda (sym) + `(,sym ',(gensym))) symbols)) + ,@body)) +(defmacro make-reader (name (stream-var dispatch-1 dispatch-2) &body body) + "Simplify the reader macro creation process in most cases." + (with-gensyms (sub-char numarg) + `(progn (defun ,name (,stream-var ,sub-char ,numarg) + (declare (ignore ,sub-char ,numarg)) + ,@body) + (set-dispatch-macro-character ,dispatch-1 ,dispatch-2 #',name)))) +(defun push-on (elt stack) + (vector-push-extend elt stack) stack) +(define-modify-macro toggle () not "Flipping vars.")