2019-12-15 22:00:59 UTC
I'm delving into Common Lisp for a longer time yet since a couple of month (I do not have too much time for it during a day) more efficiently and more continuously with the wonderful textbooks of Barsky (Land of Lisp) and Seibel (Practical Common Lisp).
I finished the first one and will now go on to finish the second. Yet as a little intermezzo I needed to play around by myself a bit.
I wrote two functions, whereby the one that does most of the work became a bit huge.
Since the examples in most of the textbooks I had in my fingers up to now (a longer time ago I accidentally stepped over Brian Harveys very nice Logo textbooks and its follow-up about Scheme with which my journey to Lisp began and let me meander finally to Common Lisp, at first with Touretzky's Gentle Introduction which appeared a lot like Harveys textbooks) are mostly short, I have qualms about bloating the procedures. That is because I do appreciate, that small procedures are more assessible.
However, in the following example it appeared to me more easy to avoid repetition by bloating my workhorse.
When you read it, my cumbersome approach may hit you right into the face. I hope you still can give me a feedback about how to use a better style or whether this is a case, where it is ok to formulate a solution like this. (If this is an important style issue as well: I do like the DO macro which appears somehow more consistent to me for such simple loops than the LOOP. I want to experiment with LOOP in cases to come. Especially, when I start with the practical examples of Practical Common Lisp within a short time.)
After this long opening speech (I'm a chatterbox, I'm afraid), my code:
;;;; Maybe the begin for a library that helps to split arguments apart for evaluating the underlying
;;;; scheme ... someday. At least an apprentice piece for playing around with primitives.
(defun first-sent (text)
"Takes a TEXT of type string as input and returns either its first sentence (if it is of one piece) or a list (if it is divided in several comma separated parts)."
(let* ((pos-full-stop (position #\. text))
(first-sentence (if (not pos-full-stop)
(subseq text 0 pos-full-stop)) ) ; extracting the first sentence
(amount-parts (1+ (count #\, first-sentence))) ; commas in sent. + 1 = no. of parts of sent.
(ret-lst nil) ) ; list to return a sentence with clauses
;; auxiliary procedures
(labels ((partsp (sent) ; tests for a sentence with more than one parts
(find #\, sent) )
(remove-first-space (sent) ; handles spaces at the beginning of extracted (partial)
(do ((i 0 (1+ i))) ; sentences
((not (string= sent " " :start1 i :end1 (1+ i))) (subseq sent i) )))
(first-part-sent (sent) ; extracts the first partial sentence of the first sentence
(let ((pos-comma (position #\, sent)))
(remove-first-space (subseq sent 0 pos-comma)) )))
(if (not (partsp first-sentence)) ; The first sentence is of one piece
(remove-first-space first-sentence) ; return the first sentence as a string
(do ((i amount-parts (1- i)))
((= i 0) (nreverse ret-lst)) ; else return it splitted into a list of strings
(if (not (partsp first-sentence)) ; as soon as the first sentence is eaten up
(push (remove-first-space first-sentence) ret-lst) ; push the last part into the list
(progn ; until then
(push (first-part-sent first-sentence) ret-lst) ; push any part into the list
(setf first-sentence ; and eat up the first sentence part by part
(subseq first-sentence (1+ (position #\, first-sentence))) ))))))))
(defun split-text (text)
"Takes a TEXT of type string as input and returns an array with the single sentences as elements."
(let* ((full-stops (count #\. text))
(container (make-array full-stops :fill-pointer 0))
(tmp text) )
(do ((i full-stops (1- i)))
((= i 0) container)
(vector-push (first-sent tmp) container)
(setf tmp (subseq tmp (1+ (position #\. tmp)))) )))