Login
7 branches 0 tags
Ben (X13/Arch) CI fixes 8856ed9 2 years ago 1106 Commits
nujel / stdlib / string / string.nuj
;;; Nujel - Copyright (C) 2020-2021 - Benjamin Vincent Schulenburg
;;; This project uses the MIT license, a copy should be included under /LICENSE
;;;
;;; Various functions dealing with strings of all sorts

(defn pad-start (text pad-length padding)
      "Pad a string to a certain length by prepending another string.

      This function will, if necessary, prepend padding on to the beginning of
      text repeatedly until the desired pad-length is reached.

      Because of this the string in padding can be truncated if the amount of
      characters that need to be added can't be evenly divided through the length of padding.

      This means that you have to be careful not to use unicode characters as a padding, or make sure that it can be used without truncation.

      text: The input string
      pad-length: The minimum length of the result
      padding: A string that will be used to pad text, defaults to a single space

      Returns the padded string"
      :cat :string-manipulation
      :related pad-middle
      :related pad-end
      :related trim
      (deftest "   123" (pad-start "123" 6))
      (deftest "000123" (pad-start 123 6 "0"))
      (deftest "-.-123" (pad-start '123 6 ".-"))


      (when-not padding (set! padding " "))
      (when-not (string? text) (set! text (:string text)))
      (when-not (string? padding)
                (throw (list :type-error "pad-start needs char as a string, so that one can pad with multiple characters" padding (current-lambda))))
      (while (< (:length text) pad-length)
             (set! text (cat padding text)))
      (if (> (:length text) pad-length)
          (:cut text (- (:length text) pad-length) (:length text))
          text))

(defn pad-end (text pad-length padding)
      "Pad a string to a certain length by appending another string.

      This function will, if necessary, append padding on to the end of
      text repeatedly until the desired pad-length is reached.

      Because of this the string in padding can be truncated if the amount of
      characters that need to be added can't be evenly divided through the length of padding.

      This means that you have to be careful not to use unicode characters as a padding, or make sure that it can be used without truncation.

      text: The input string
      pad-length: The minimum length of the result
      padding: A string that will be used to pad text, defaults to a single space

      Returns the padded string"
      :cat :string-manipulation
      :related pad-middle
      :related pad-start
      :related trim
      (deftest "123   " (pad-end "123" 6))
      (deftest "123000" (pad-end 123 6 "0"))
      (deftest "123.-." (pad-end '123 6 ".-"))


      (when-not padding (set! padding " "))
      (when-not (string? text) (set! text (:string text)))
      (when-not (string? padding)
                (throw (list :type-error "pad-end needs char as a string, so that one can pad with multiple characters" padding (current-lambda))))
      (while (< (:length text) pad-length)
             (set! text (cat text padding)))
      (if (> (:length text) pad-length)
          (:cut text 0 pad-length)
          text))

(defn pad-middle (text pad-length padding)
      "Pad a string to a certain length by surrounding it with another string.

      This function will, if necessary, append and prepend padding to the
      beginning and end of text repeatedly until the desired pad-length is reached.

      Because of this the string in padding can be truncated if the amount of
      characters that need to be added can't be evenly divided through the length of padding.

      This means that you have to be careful not to use unicode characters as a padding, or make sure that it can be used without truncation.

      text: The input string
      pad-length: The minimum length of the result
      padding: A string that will be used to pad text, defaults to a single space

      Returns the padded string"
      :cat :string-manipulation
      :related pad-start
      :related pad-end
      :related trim
      (deftest " 123  " (pad-middle "123" 6))
      (deftest "012300" (pad-middle 123 6 "0"))
      (deftest "-123.-" (pad-middle '123 6 ".-"))

      (when-not padding (set! padding " "))
      (when-not (string? text) (set! text (:string text)))
      (when-not (string? padding)
                (throw (list :type-error "string/pad-middle needs char as a string, so that one can pad with multiple characters" padding (current-lambda))))
      (while (< (:length text) pad-length)
             (set! text (cat padding text padding)))
      (if (> (:length text) pad-length)
          (let ((end-overflow (div/int (- (:length text) pad-length) 2))
                (start-overflow (- (- (:length text) pad-length) end-overflow)))
               (:cut text start-overflow (+ start-overflow pad-length)))
          text))

(defn trim (text)
      "Remove all whitespace from the start and end of text

      Whitespace is determined by the whitespace? predicate.

      text: The input string

      Returns a trimmed version of text"
      :cat :string-manipulation
      :related pad-start
      :related pad-middle
      :related pad-end
      :related trim
      (deftest "Test" (trim " Test"))
      (deftest "Test" (trim "Test "))
      (deftest "Test" (trim "Test"))
      (deftest "" (trim ""))
      (deftest "Test" (trim "   Test\t\n"))
      (deftest "Trim Test" (trim "   Trim Test    \n"))

      (def start 0)
      (def end (:length text))
      (while (and (< start end) (whitespace? (ref text start))) (set! start (+ start 1)))
      (while (and (> end 0) (whitespace? (ref text (- end 1)))) (set! end (- end 1)))
      (:cut text start end))

(defn upper-case (text)
      "Convert text into a version using only capitals or uppercase letters.

      text: The input string

      Returns the resulting upper-case version"
      :cat :string-manipulation
      :related lower-case
      :related capitalize
      (deftest "TESTING THE CAPITALIZATION!" (upper-case "Testing THE Capitalization!"))

      (def ret (buffer/allocate (:length text)))
      (dotimes (i (:length text) (buffer->string ret))
        (set! ret i (upper-case-char (ref text i)))))

(defn lower-case (text)
      "Convert text into a version using only lowercase letters.

      text: The input string

      Returns the resulting lower-case version"
      :cat :string-manipulation
      :related upper-case
      :related capitalize
      (deftest "testing the capitalization!" (lower-case "Testing THE Capitalization!"))

      (def ret (buffer/allocate (:length text)))
      (dotimes (i (:length text) (buffer->string ret))
        (set! ret i (lower-case-char (ref text i)))))

(defn capitalize (text)
      "Convert text into a capitalized version.

      This is simplified by having the first character be an upper-case version, as well
      as any character following a whitespace character, and all other characters be in lower-case.

      text: The input string

      Returns the capitalized version"
      :cat :string-manipulation
      :related upper-case
      :related lower-case
      (deftest "Testing The Capitalization!" (capitalize "Testing THE Capitalization!"))

      (def ret (buffer/allocate (:length text)))
      (def caps? #t)
      (dotimes (i (:length text) (buffer->string ret))
         (set! ret i ((if caps? upper-case-char lower-case-char) (ref text i)))
        (set! caps? (if (whitespace? (ref text i)) #t #f))))

(defn string/round (text decimal-digits)
      "Round the floating point representation in TEXT to have at most DECIMAL-DIGITS after the period"
      (def pos (:last-index-of text "."))
      (if (>= pos 0)
          (:cut text 0 (+ pos 1 decimal-digits))
          text))

(def split (let*
             (defn split/empty (str separator)
                   (def slen (:length str))
                   (def start 0)
                   (def ret #nil)
                   (while (< start slen)
                     (set! ret (cons (:cut str start (+ 1 start)) ret))
                     (inc! start))
                   (reverse ret))

             (defn split/string (str separator start)
                   (when-not start (set! start 0))
                   (def pos-found (:index-of str separator start))
                   (if (>= pos-found 0)
                       (cons (:cut str start pos-found)
                             (split/string str separator (+ pos-found (:length separator))))
                       (cons (:cut str start (:length str))
                             #nil)))

             (defn split (str separator)
                   "Splits STR into a list at every occurunse of SEPARATOR"
                   (typecheck/only str :string)
                   (typecheck/only separator :string)
                   (case (:length separator)
                         (0 (split/empty str))
                         (otherwise (split/string str separator 0))))))

(defn read/single (text)
      "Uses the reader and returns the first single value read from string TEXT"
      (typecheck/only text :string)
      (car (read text)))

(defn read/int (text)
      "Reads the first string from TEXT"
      (int (read/single text)))

(defn read/float (text)
      "Reads the first float from TEXT"
      (float (read/single text)))

(defn println (str)
      "Print STR on a single line"
      (print (cat str "\r\n")))

(defn errorln (str)
      "Print to stderr STR on a single line"
      (error (cat str "\r\n")))