source: trunk/playground/abcl-test/lisp-code/base-tools/base-tools.lisp

Last change on this file was 395, checked in by lgiessmann, 14 years ago

playground: added a project that uses some test cases with ABCL

File size: 16.2 KB
Line 
1;;+-----------------------------------------------------------------------------
2;;+  Isidorus
3;;+  (c) 2008-2010 Marc Kuester, Christoph Ludwig, Lukas Georgieff
4;;+
5;;+  Isidorus is freely distributable under the LLGPL license.
6;;+  You can find a detailed description in trunk/docs/LLGPL-LICENSE.txt and
7;;+  trunk/docs/LGPL-LICENSE.txt.
8;;+-----------------------------------------------------------------------------
9
10(defpackage :base-tools
11  (:use :cl)
12  (:nicknames :tools)
13  (:export :push-string
14           :concat
15           :when-do
16           :string-replace
17           :remove-null
18           :full-path
19           :trim-whitespace-left
20           :trim-whitespace-right
21           :trim-whitespace
22           :string-starts-with
23           :string-ends-with
24           :string-ends-with-one-of
25           :string-starts-with-char
26           :string-starts-with-one-of
27           :string-until
28           :string-after
29           :search-first
30           :search-first-ignore-literals
31           :concatenate-uri
32           :absolute-uri-p
33           :string-starts-with-digit
34           :string-after-number
35           :separate-leading-digits
36           :white-space
37           :white-space-p
38           :escape-string
39           :search-first-unclosed-paranthesis 
40           :search-first-unopened-paranthesis
41           :in-literal-string-p
42           :find-literal-end
43           :get-literal-quotation
44           :get-literal
45           :return-if-starts-with))
46
47(in-package :base-tools)
48
49
50(defparameter *white-space*
51  (list #\Space #\Tab #\Newline (code-char 13))
52  "Contains all characters that are treated as white space.")
53
54
55(defun white-space()
56  "Returns a lit os string that represents a white space."
57  (map 'list #'(lambda(char)
58                 (string char))
59       *white-space*))
60
61
62(defmacro concat (&rest strings)
63  `(concatenate 'string ,@strings))
64
65
66(defmacro push-string (obj place)
67  "Imitates the push macro but instead of pushing object in a list,
68   there will be appended the given string to the main string object."
69  `(setf ,place (concat ,place ,obj)))
70
71
72(defmacro when-do (result-bounding condition-statement do-with-result)
73  "Executes the first statement and stores its result in the variable result.
74   If result isn't nil the second statement is called.
75   The second statement can use the variable tools:result as a parameter."
76  `(let ((,result-bounding ,condition-statement))
77     (if ,result-bounding
78         ,do-with-result
79         nil)))
80
81
82(defun white-space-p (str)
83  "Returns t if the passed str contains only white space characters."
84  (cond ((and (= (length str) 1)
85              (string-starts-with-one-of str (white-space)))
86         t)
87        ((string-starts-with-one-of str (white-space))
88         (white-space-p (subseq str 1)))
89        (t
90         nil)))
91
92
93(defun remove-null (lst)
94  "Removes all null values from the passed list."
95  (remove-if #'null lst))
96
97
98(defun full-path (pathname)
99  "Returns a string that represents the full path of the passed
100   CL:Pathname construct."
101  (declare (CL:Pathname pathname))
102  (let ((segments
103         (remove-if #'null
104                    (map 'list #'(lambda(item)
105                                   (when (stringp item)
106                                     (concat "/" item)))
107                         (pathname-directory pathname))))
108        (full-path-string ""))
109    (dolist (segment segments)
110      (push-string segment full-path-string))
111    (concat full-path-string "/" (pathname-name pathname))))
112
113
114(defun trim-whitespace-left (value)
115  "Uses string-left-trim with a predefined character-list."
116  (declare (String value))
117  (string-left-trim *white-space* value))
118
119
120(defun trim-whitespace-right (value)
121  "Uses string-right-trim with a predefined character-list."
122  (declare (String value))
123  (string-right-trim *white-space* value))
124
125
126(defun trim-whitespace (value)
127  "Uses string-trim with a predefined character-list."
128  (declare (String value))
129  (string-trim *white-space* value))
130
131
132(defun string-starts-with (str prefix &key (ignore-case nil))
133  "Checks if string str starts with a given prefix."
134  (declare (String str prefix)
135           (Boolean ignore-case))
136  (let ((str-i (if ignore-case
137                   (string-downcase str :start 0 :end (min (length str)
138                                                           (length prefix)))
139                   str))
140        (prefix-i (if ignore-case
141                      (string-downcase prefix)
142                      prefix)))
143    (string= str-i prefix-i :start1 0 :end1
144             (min (length prefix-i)
145                  (length str-i)))))
146
147
148(defun string-starts-with-one-of (str prefixes &key (ignore-case nil))
149  "Returns t if str ends with one of the string contained in suffixes."
150  (declare (String str)
151           (List prefixes)
152           (Boolean ignore-case))
153  (loop for prefix in prefixes
154     when (string-starts-with str prefix :ignore-case ignore-case)
155     return t))
156
157
158(defun string-ends-with (str suffix &key (ignore-case nil))
159  "Checks if string str ends with a given suffix."
160  (declare (String str suffix)
161           (Boolean ignore-case))
162  (let ((str-i (if ignore-case
163                   (string-downcase str :start (max (- (length str)
164                                                       (length suffix))
165                                                    0)
166                                    :end (length str))
167                   str))
168        (suffix-i (if ignore-case
169                      (string-downcase suffix)
170                      suffix)))
171    (string= str-i suffix-i :start1 (max (- (length str)
172                                            (length suffix))
173                                         0))))
174
175
176(defun string-ends-with-one-of (str suffixes &key (ignore-case nil))
177  "Returns t if str ends with one of the string contained in suffixes."
178  (declare (String str)
179           (List suffixes)
180           (Boolean ignore-case))
181  (loop for suffix in suffixes
182     when (string-ends-with str suffix :ignore-case ignore-case)
183     return t))
184
185
186(defun string-replace (main-string string-to-replace new-string)
187  "Replaces every occurrence of string-to-replace by new-string
188   in main-string."
189  (declare (String main-string string-to-replace new-string))
190  (if (string= string-to-replace new-string)
191      main-string
192      (let ((search-idx (search-first (list string-to-replace) main-string)))
193        (if (not search-idx)
194            main-string
195            (let ((modified-string
196                   (concat (subseq main-string 0 search-idx)
197                           new-string
198                           (subseq main-string
199                                   (+ search-idx (length string-to-replace))))))
200              (string-replace modified-string string-to-replace new-string))))))
201
202
203
204(defun string-starts-with-digit (str)
205  "Checks whether the passed string starts with a digit."
206  (declare (String str))
207  (loop for item in (list 0 1 2 3 4 5 6 7 8 9)
208     when (string-starts-with str (write-to-string item))
209     return t))
210
211
212(defun string-after-number (str)
213  "If str starts with a digit, there is returned the first
214   substring after a character that is a non-digit.
215   If str does not start with a digit str is returned."
216  (declare (String str))
217  (if (and (string-starts-with-digit str)
218           (> (length str) 0))
219      (string-after-number (subseq str 1))
220      str))
221
222
223(defun separate-leading-digits (str &optional digits)
224  "If str starts with a number the number is returned."
225  (declare (String str)
226           (type (or Null String) digits))
227  (if (string-starts-with-digit str)
228      (separate-leading-digits
229       (subseq str 1) (concat digits (subseq str 0 1)))
230      digits))
231
232
233(defun string-starts-with-char (begin str)
234  (equal (char str 0) begin))
235
236
237(defun string-until (str anchor)
238  "Returns a substring until the position of the passed anchor."
239  (declare (String str anchor))
240  (let ((pos (search anchor str)))
241    (if pos
242        (subseq str 0 pos)
243        str)))
244
245
246(defun string-after (str prefix)
247  "Returns the substring after the found prefix.
248   If there is no substring equal to prefix nil is returned."
249  (declare (String str prefix))
250  (let ((pos (search prefix str)))
251    (if pos
252        (subseq str (+ pos (length prefix)))
253        nil)))
254
255
256(defun search-first (search-strings main-string &key from-end)
257  "Returns the position of one of the search-strings. The returned position
258   is the one closest to 0. If no search-string is found, nil is returned."
259  (declare (String main-string)
260           (List search-strings))
261  (let ((positions
262         (remove-null
263          (map 'list #'(lambda(search-str)
264                         (search search-str main-string :from-end from-end))
265               search-strings))))
266    (let ((sorted-positions (if from-end
267                                (sort positions #'>)
268                                (sort positions #'<))))
269      (when sorted-positions
270        (first sorted-positions)))))
271
272
273(defun find-literal-end (query-string delimiter &optional (overall-pos 0))
274  "Returns the end of the literal corresponding to the passed delimiter
275   string. The query-string must start after the opening literal delimiter.
276   The return value is an int that represents the start index of closing
277   delimiter. delimiter must be either \", ', or '''.
278   If the returns value is nil, there is no closing delimiter."
279  (declare (String query-string delimiter)
280           (Integer overall-pos))
281  (let ((current-pos (search delimiter query-string)))
282    (if current-pos
283        (if (string-ends-with (subseq query-string 0 current-pos) "\\")
284            (find-literal-end (subseq query-string (+ current-pos
285                                                      (length delimiter)))
286                              delimiter (+ overall-pos current-pos 1))
287            (+ overall-pos current-pos (length delimiter)))
288        nil)))
289
290
291(defun get-literal-quotation (str)
292  "Returns ', ''', \" or \"\"\" when the string starts with a literal delimiter."
293  (cond ((string-starts-with str "'''")
294         "'")
295        ((string-starts-with str "\"\"\"")
296         "\"\"\"")
297        ((string-starts-with str "'")
298         "'")
299        ((string-starts-with str "\"")
300         "\"")))
301
302
303(defun get-literal (query-string &key (quotation nil))
304  "Returns a list of the form (:next-string <string> :literal <string>
305   where next-query is the query after the found literal and literal
306   is the literal string."
307  (declare (String query-string)
308           (type (or Null String) quotation))
309  (let ((local-quotation quotation))
310    (cond ((or (string-starts-with query-string "\"\"\"")
311               (string-starts-with query-string "'''"))
312           (unless local-quotation
313             (setf local-quotation (subseq query-string 0 3)))
314           (let ((literal-end
315                  (find-literal-end (subseq query-string 3) (subseq query-string 0 3))))
316             (when literal-end
317               (list :next-string (subseq query-string (+ 3 literal-end))
318                     :literal (concat quotation
319                                      (subseq query-string 3 literal-end)
320                                      quotation)))))
321          ((or (string-starts-with query-string "\"")
322               (string-starts-with query-string "'"))
323           (unless local-quotation
324             (setf local-quotation (subseq query-string 0 1)))
325           (let ((literal-end
326                  (find-literal-end (subseq query-string 1)
327                                    (subseq query-string 0 1))))
328             (when literal-end
329               (let ((literal
330                      (escape-string (subseq query-string 1 literal-end) "\"")))
331                 (list :next-string (subseq query-string (+ 1 literal-end))
332                       :literal (concat local-quotation literal
333                                        local-quotation)))))))))
334
335
336(defun search-first-ignore-literals (search-strings main-string &key from-end)
337  (declare (String main-string)
338           (List search-strings)
339           (Boolean from-end))
340  (let ((first-pos
341         (search-first search-strings main-string :from-end from-end)))
342    (when first-pos
343      (if (not (in-literal-string-p main-string first-pos))
344          first-pos
345          (let* ((literal-start
346                  (search-first (list "\"" "'") (subseq main-string 0 first-pos)
347                                :from-end from-end))
348                 (next-str
349                  (if from-end
350                     
351
352                      (subseq main-string 0 literal-start)
353                     
354                     
355                      (let* ((sub-str (subseq main-string literal-start))
356                             (literal-result (get-literal sub-str)))
357                        (getf literal-result :next-string)))))
358            (let ((next-pos
359                   (search-first-ignore-literals search-strings next-str
360                                                 :from-end from-end)))
361              (when next-pos
362                (+ (- (length main-string) (length next-str)) next-pos))))))))
363
364
365(defun concatenate-uri (absolute-ns value)
366  "Returns a string conctenated of the absolut namespace an the given value
367   separated by either '#' or '/'."
368  (declare (string absolute-ns value))
369  (unless (and (> (length absolute-ns) 0)
370               (> (length value) 0))
371    (error "From concatenate-uri(): absolute-ns and value must be of length > 0"))
372  (unless (absolute-uri-p absolute-ns)
373    (error "From concatenate-uri(): absolute-ns has to be an absolute URI: ~a" absolute-ns))
374  (let ((last-char
375         (elt absolute-ns (- (length absolute-ns) 1)))
376        (first-char
377         (elt value 0)))
378    (let ((separator
379           (cond
380             ((or (eql first-char #\#)
381                  (eql first-char #\/))
382              "")
383             ((or (eql last-char #\#)
384                  (eql last-char #\/))
385              "")
386             (t
387              "/"))))
388      (let ((prep-ns
389             (if (and (eql last-char first-char)
390                      (or (eql last-char #\#)
391                          (eql last-char #\/)))
392                 (subseq absolute-ns 0 (- (length absolute-ns) 1))
393                 (if (and (eql last-char #\#)
394                          (find #\/ value))
395                     (progn
396                       (when (not (eql first-char #\/))
397                         (setf separator "/"))
398                       (subseq absolute-ns 0 (- (length absolute-ns) 1)))
399                     absolute-ns))))
400        (concat prep-ns separator value)))))
401
402
403(defun absolute-uri-p (uri)
404  "Returns t if the passed uri is an absolute one. This
405   is indicated by a ':' with no leadgin '/'."
406  (when uri
407    (let ((position-of-colon
408           (position #\: uri)))
409      (declare (string uri))
410      (and position-of-colon (> position-of-colon 0)
411           (not (find #\/ (subseq uri 0 position-of-colon)))))))
412
413
414(defun escape-string (str char-to-escape)
415  "Escapes every occurrence of char-to-escape in str, if it is
416   not escaped."
417  (declare (String str char-to-escape))
418  (let ((result ""))
419    (dotimes (idx (length str))
420      (let ((current-char (subseq str idx (1+ idx)))
421            (previous-char (if (= idx 0) "" (subseq str (1- idx) idx))))
422        (cond ((and (string= current-char char-to-escape)
423                    (string/= previous-char "\\"))
424               (push-string "\\" result)
425               (push-string current-char result))
426              (t
427               (push-string current-char result)))))
428    result))
429
430
431(defun in-literal-string-p(filter-string pos)
432  "Returns t if the passed pos is within a literal string value."
433  (declare (String filter-string)
434           (Integer pos))
435  (let ((result nil))
436    (dotimes (idx (length filter-string) result)
437      (let ((current-char (subseq filter-string idx (1+ idx))))
438        (cond ((or (string= current-char "'")
439                   (string= current-char "\""))
440               (let* ((l-result (get-literal (subseq filter-string idx)))
441                      (next-idx
442                       (when l-result
443                         (- (length filter-string)
444                            (length (getf l-result :next-string))))))
445                 (when (and next-idx (< pos next-idx))
446                   (setf result t)
447                   (setf idx (length filter-string)))
448                 (when (<= pos idx)
449                   (setf idx (length filter-string)))))
450              (t
451               (when (<= pos idx)
452                 (setf idx (length filter-string)))))))))
453
454
455(defun search-first-unclosed-paranthesis (str &key ignore-literals)
456  "Returns the idx of the first ( that is not closed, the search is
457   started from the end of the string.
458   If ignore-literals is set to t all paranthesis that are within
459   \", \"\"\", ' and ''' are ignored."
460  (declare (String str)
461           (Boolean ignore-literals))
462  (let ((open-brackets 0)
463        (result-idx nil))
464    (do ((idx (1- (length str)))) ((< idx 0))
465      (let ((current-char (subseq str idx (1+ idx))))
466        (cond ((string= current-char ")")
467               (when (or ignore-literals
468                         (not (in-literal-string-p str idx)))
469                 (decf open-brackets)))
470              ((string= current-char "(")
471               (when (or ignore-literals
472                         (not (in-literal-string-p str idx)))
473                 (incf open-brackets)
474                 (when (> open-brackets 0)
475                   (setf result-idx idx)
476                   (setf idx 0)))))
477        (decf idx)))
478    result-idx))
479
480
481(defun search-first-unopened-paranthesis (str &key ignore-literals)
482  "Returns the idx of the first paranthesis that is not opened in str.
483   If ignore-literals is set to t all mparanthesis that are within
484   \", \"\"\", ' and ''' are ignored."
485  (declare (String str)
486           (Boolean ignore-literals))
487  (let ((closed-brackets 0)
488        (result-idx nil))
489    (dotimes (idx (length str))
490      (let ((current-char (subseq str idx (1+ idx))))
491        (cond ((string= current-char "(")
492               (when (or ignore-literals
493                         (not (in-literal-string-p str idx)))
494                 (decf closed-brackets)
495                 (setf result-idx nil)))
496              ((string= current-char ")")
497               (when (or ignore-literals
498                         (not (in-literal-string-p str idx)))
499                 (incf closed-brackets)
500                 (when (> closed-brackets 0)
501                   (setf result-idx idx)
502                   (setf idx (length str))))))))
503    result-idx))
504
505
506(defun return-if-starts-with (str to-be-matched &key from-end ignore-case
507                              ignore-leading-whitespace)
508  "Returns the string that is contained in to-be-matched and that is the
509   start of the string str."
510  (declare (String str)
511           (List to-be-matched)
512           (Boolean from-end ignore-case ignore-leading-whitespace))
513  (let ((cleaned-str (if ignore-leading-whitespace
514                         (trim-whitespace-left str)
515                         str)))
516    (loop for try in to-be-matched
517       when (if from-end
518                (string-ends-with cleaned-str try :ignore-case ignore-case)
519                (string-starts-with cleaned-str try :ignore-case ignore-case))
520       return try)))
Note: See TracBrowser for help on using the repository browser.