(defun string-version-lessp (s1 s2 &optional ignore-case) "Return t if first arg string is less than second in version order. Case is significant in this comparison if IGNORE-CASE is nil. Symbols are also allowed; their print names are used instead. See also `string-logical-lessp'. Example: (sort '(\"foo.zml-1.gz\" => (\"foo.zml-1.gz\" \"foo.zml-100.gz\" \"foo.zml-2.gz\" \"foo.zml-12.gz\" \"foo.zml-6.gz\" \"foo.zml-13.gz\" \"foo.zml-12.gz\" \"foo.zml-2.gz\" \"foo.zml-13.gz\" \"foo.zml-25.gz\" \"foo.zml-25.gz\" \"foo.zml-6.gz\") \"foo.zml-100.gz\") 'string-version-lessp) (sort '(\"abc-1.01a.tgz\" => (\"abc-1.007.tgz\" \"abc-1.007.tgz\" \"abc-1.012b.tgz\" \"abc-1.012b.tgz\") \"abc-1.01a.tgz\") 'string-version-lessp) (sort '(\"9.000001.10.tgz\" => (\"009.01.91.tgz\" \"009.01.91.tgz\") \"9.000001.10.tgz\") 'string-version-lessp) " (let* (;; states (S_N #x0) ; normal (S_I #x3) ; comparing integral part (S_F #x6) ; comparing fractionnal parts (S_Z #x9) ; idem but with leading Zeroes only ;; Symbol(s) 0 [1-9] others ;; Transition (10) 0 (01) d (00) x ;; ;; x d 0 ; state (next-state (vector S_N S_I S_Z ; S_N S_N S_I S_I ; S_I S_N S_F S_F ; S_F S_N S_F S_Z)) ; S_Z ;; result-type (CMP 2) ; return diff (LEN 3) ; compare using len_diff/diff ;; `glibc-2.11.1/string/strverscmp.c' ;; ;; *p1 *p1 ; pair ;; *p2 *p2 ; ;; *p3 *p3 ; ;; ;; x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 ; state (result-type (vector CMP CMP CMP CMP LEN CMP CMP CMP CMP ; S_N CMP -1 -1 +1 LEN LEN +1 LEN LEN ; S_I CMP CMP CMP CMP CMP CMP CMP CMP CMP ; S_F CMP +1 +1 -1 CMP CMP -1 CMP CMP)) ; S_Z ;;; ;; like `coreutils-6.12/lib/strverscmp.c' ;;; ;; ;;; ;; x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 ; state ;;; (result-type (vector CMP CMP CMP CMP LEN CMP CMP CMP CMP ; S_N ;;; CMP -1 -1 +1 LEN LEN +1 LEN LEN ; S_I ;;; CMP CMP CMP CMP LEN CMP CMP CMP CMP ; S_F ;;; CMP +1 +1 -1 CMP CMP -1 CMP CMP)) ; S_Z ret ; same style as return value of C language `strcmp' l1 l2 ; length of string s1, s2 (n 0) ; index of string s1, s2 c1 c2 ; character of string s1, s2 at index n state diff) (setq ret (catch 'end (if (eq s1 s2) (throw 'end 0)) (if (symbolp s1) (setq s1 (symbol-name s1))) (if (symbolp s2) (setq s2 (symbol-name s2))) (unless (stringp s1) (signal 'wrong-type-argument `(stringp ,s1))) (unless (stringp s2) (signal 'wrong-type-argument `(stringp ,s2))) (if ignore-case (setq s1 (upcase s1) s2 (upcase s2))) (setq l1 (length s1) l2 (length s2) c1 (if (< n l1) (elt s1 n) ?\0) ; ?\0: null terminator c2 (if (< n l2) (elt s2 n) ?\0) n (1+ n) ;; Hint: '0' is a digit too. state (+ S_N (if (= c1 ?0) 1 0) (if (and (<= ?0 c1) (<= c1 ?9)) 1 0))) ; (isdigit (c1) != 0) (while (= (setq diff (- c1 c2)) 0) (if (= c1 ?\0) (throw 'end diff)) (setq state (aref next-state state) c1 (if (< n l1) (elt s1 n) ?\0) c2 (if (< n l2) (elt s2 n) ?\0) n (1+ n) state (+ state (if (= c1 ?0) 1 0) (if (and (<= ?0 c1) (<= c1 ?9)) 1 0)))) (setq state (aref result-type (+ (* state 3) (if (= c2 ?0) 1 0) (if (and (<= ?0 c2) (<= c2 ?9)) 1 0)))) (cond ((= state CMP) (setq ret diff)) ((= state LEN) (while (progn (setq c1 (if (< n l1) (elt s1 n) ?\0) c2 (if (< n l2) (elt s2 n) ?\0) n (1+ n)) (and (<= ?0 c1) (<= c1 ?9))) (if (not (and (<= ?0 c2) (<= c2 ?9))) (throw 'end 1))) (setq ret (if (and (<= ?0 c2) (<= c2 ?9)) -1 diff))) (t (setq ret state))) ret)) ;; convert ret to the style of `string-lessp' (< ret 0))) (defalias 'string-version< 'string-version-lessp) (defun string-logical-lessp (s1 s2 &optional ignore-case) "Return t if first arg string is less than second in logical version order. Case is significant in this comparison if IGNORE-CASE is nil. Symbols are also allowed; their print names are used instead. See also `string-version-lessp'. Example: (sort '(\"foo.zml-1.gz\" => (\"foo.zml-1.gz\" \"foo.zml-100.gz\" \"foo.zml-2.gz\" \"foo.zml-12.gz\" \"foo.zml-6.gz\" \"foo.zml-13.gz\" \"foo.zml-12.gz\" \"foo.zml-2.gz\" \"foo.zml-13.gz\" \"foo.zml-25.gz\" \"foo.zml-25.gz\" \"foo.zml-6.gz\") \"foo.zml-100.gz\") 'string-logical-lessp) (sort '(\"abc-1.01a.tgz\" => (\"abc-1.01a.tgz\" \"abc-1.007.tgz\" \"abc-1.007.tgz\" \"abc-1.012b.tgz\") \"abc-1.012b.tgz\") 'string-logical-lessp) (sort '(\"9.000001.10.tgz\" => (\"9.000001.10.tgz\" \"009.01.91.tgz\") \"009.01.91.tgz\") 'string-logical-lessp) " (let (ret ; same style as return value of C language `strcmp' l1 l2 ; length of string s1, s2 (n1 0) ; index of string s1, s2 (n2 0) (c1 -1) ; character of string s1, s2 at index n1, n2 (c2 -1) ; (set dummy code as initial (and invalid as character) value) diff) (if (symbolp s1) (setq s1 (symbol-name s1))) (if (symbolp s2) (setq s2 (symbol-name s2))) (unless (stringp s1) (signal 'wrong-type-argument `(stringp ,s1))) (unless (stringp s2) (signal 'wrong-type-argument `(stringp ,s2))) (if ignore-case (setq s1 (upcase s1) s2 (upcase s2))) (setq l1 (length s1) l2 (length s2)) (setq ret (catch 'end (while (= (setq diff (- c1 c2)) 0) (if (or (= c1 ?\0) (= c2 ?\0)) (throw 'end diff)) (setq c1 (if (< n1 l1) (elt s1 n1) ?\0) ; ?\0: null terminator c2 (if (< n2 l2) (elt s2 n2) ?\0)) ;; encounter numbers ? (if (and (<= ?0 c1) (<= c1 ?9) (<= ?0 c2) (<= c2 ?9)) (let (sub-s1 sub-s2 sub-l1 sub-l2) ;; skip needless "0" ;; ;; example: ;; "00...0" => "0" ;; "010" => "10" ;; "000305" => "305" (string-match "0*\\([0-9]+\\)" s1 n1) (setq sub-s1 (match-string 1 s1) sub-l1 (length sub-s1) n1 (match-end 1)) (string-match "0*\\([0-9]+\\)" s2 n2) (setq sub-s2 (match-string 1 s2) sub-l2 (length sub-s2) n2 (match-end 1)) ;; number whose length is shorter is smaller than another (cond ((< sub-l1 sub-l2) (throw 'end -1)) ((> sub-l1 sub-l2) (throw 'end 1)) (t ;; don't use `number-to-string' because of overflow (setq ret (compare-strings sub-s1 0 nil sub-s2 0 nil)) (unless (eq ret t) (throw 'end ret)))) ;; as both numbers are equal, prepare for next step (setq c1 (if (< n1 l1) (elt s1 n1) ?\0) c2 (if (< n2 l2) (elt s2 n2) ?\0)))) (setq n1 (1+ n1) n2 (1+ n2))) diff)) ;; convert ret to the style of `string-lessp' (< ret 0))) (defalias 'string-logical< 'string-logical-lessp)