逆引きxyzzy lisp(文字列) - circleratio/xyzzy GitHub Wiki
- 作成
- 部分文字列操作
- 文字列比較
- 逐次処理
- 検索・置換
- 文字列の長さを数える
- 文字列の最後の文字を取り出す
- 分割
- 変換
- 整形
- ヒアドキュメントの終端文字列をインデントする
- 文字列中の式を評価し値を展開する
- 文字列を決められた幅に収める
文字の集合から作る場合。
(coerce '(#\あ #\い #\う #\え #\お #\a #\b #\c) 'string)
=> "あいうえおabc"
(concatenate 'string '(#\a #\b #\c) '(#\d #\e #\f))
=> "abcdef"
(let ((clist '(#\a #\b #\c)))
(string (make-vector (length clist) :initial-contents clist)))
=> "abc"
1文字だけからなる文字列を作る場合は
(string #\a)
=> "a"
でよい。 ただし、elispのように
(string ?a ?b ?c)
=> "abc"
とはできないので注意。
ひとつの文字が連続する場合。
(make-sequence 'string 15 :initial-element #\SPC)
=>" "
(concat "abc" "def" "ghi")
=> "abcdefghi"
(concatenate 'string "abc" "def" "ghi")
=>"abcdefghi"
"abc" を 5回つなげる. dotimes は x を 0, 1, 2, 3, 4 と変化させるので,5個繋げるなら引数は4.
次の関数のほうが綺麗.
(defun repeat-string (str n)
(if (= n 0)
""
(concat str (repeat-string str (1- n)))))
(repeat-string "a" 10)
=> "aaaaaaaaaa"
テンポラリのバッファを作るのが定石.
(defun runcmd (cmd)
(let ((buf (create-new-buffer "*command*"))
proc)
(save-window-excursion
(with-set-buffer
(set-buffer buf)
(setq proc (make-process cmd))
(while (eql :run (process-status proc))
(do-events))
(prog1
(buffer-substring (point-min) (point-max))
(delete-buffer buf))))))
(setq result (runcmd "cmd /c dir c:"))
言語による支援があるわけではないので省略. 「コマンドの実行結果を文字列に設定する」の結果を複数個つなげること!
行単位でものを考えるのでなく,カッコや引用符の対応で構文解析するので,ヒアドキュメントのような考え方は不要.
(setq s "This is test.
Ruby, the Object Oriented Script Language.")
=> "This is test.
Ruby, the Object Oriented Script Language."
2文字目から4文字目までを取り出す.
(subseq "ABcDE" 1 4)
=> "BcD"
3文字目以降をすべて取り出す.
(subseq "ABcDE" 2)
=> "cDE"
xyzzy lisp には substring というほぼ同機能の関数もあるが,Common Lisp 標準の関数は subseq だけなので、こちらを使うほうがベター.
(concatenate 'string "Vine" (subseq s 5))
=> "Vine Banana Orange"
(concatenate 'string (subseq s 0 6) "Lemon" (subseq s 12))
=> "Apple Lemon Orange"
(string-equal "あいう" "あいう")
=> t
(string= "あいう" "あいう")
=> t
(equal "あいう" "あいう")
=> t
(eq "あいう" "あいう")
=> nil
(= "あいう" "あいう")
=> 不正なデータ型です: "あいう": number
(string-lessp "abc" "abz")
=>2
(string-greaterp "abc" "abz")
=>nil
(string< "abc" "abz")
=>2
(string> "abc" "ab0")
=>2
"<" (string-lessp, string<)または ">" (string-greaterp, string>)を満たせば,一致しない文字のインデックスを返す. 満たさなければ nil を返す.
(let ((s "Lisp") (sum 0))
(dotimes (idx (length s) sum)
(setq sum (+ sum (char-code (elt s idx))))))
=> 408
文字コードはそれぞれ L(76), i(105), s(115), p(112) で合計408.
文字列からストリームを作る.
(setq text "This is test.
Ruby, the Object Oriented Script Language.")
(let ((stream (make-string-input-stream text)) line (linenum 0))
(while (setq line (read-line stream nil))
(setq linenum (+ linenum 1))
(print (format nil "~D: ~A" linenum line))))
=> "1: This is test."
"2: "
"3: Ruby, the Object Oriented Script Language."
nil
「"A001" の次は "A002"」といった形でシーケンシャルな文字列を作っていく処理は自前で作るしかない.
(defun next-string (str)
(labels
((fill-with-zero (str)
(format nil (concat "~"
(format nil "~D" (length str))
",'0D")
(1+ (read-from-string str))))
(next-num-string (str)
(cond ((string-match "^0" str) (fill-with-zero str))
(t (format nil "~D" (1+ (read-from-string str)))))))
(if (string-match "\\([^0-9]*\\)\\([0-9]+\\)\\([^0-9]*\\)" str)
(let ((m1 (match-string 1))
(m2 (match-string 2))
(m3 (match-string 3)))
(concat m1 (next-num-string m2) m3))
str)))
(next-string "A001")
=> "A002"
(next-string "A002")
=> "A003"
(substitute-string "あいうえお/かきくけこ/さしすせそ" "/" "*")
=>"あいうえお*かきくけこ*さしすせそ"
(let ((str "1234:5678"))
(when (string-match "^\\([0-9]+\\):\\([0-9]+\\)" str)
(setq str (string-replace-match str "[\\1][\\2]"))))
先頭の位置はstring-matchが返す(先頭の文字が0なので注意). 終了位置は match-end で取得.
(string-match "cd" "abcde")
=> 2
(match-end 0)
=> 4
文字列自身を取得することはできないなので substring を使う.
(let* ((str "abcde12345fghij")
(beg (string-match "[0-9]+" str))
(end (match-end 0)))
(substring str beg end))
=> "12345"
(let ((str "hoge:045-111-2222 boke:045-222-2222 ")
(result))
(while (string-match "\\([a-z]+:[0-9-]+\\)\\(.*\\)$" str)
(push (match-string 1) result)
(setq str (string-replace-match str "\\2")))
result)
マルチバイトを考慮する必要は特になし.
(length "あいうえお")
=> 5
(let ((s "あいうえお"))
(subseq s (- (length s) 1)))
=>"お"
(split-string "/usr/work/xyzzy" #\/)
=> ("usr" "work" "xyzzy")
(defun string-cut (str &rest args)
(let ((f (lambda (str offset &rest args)
(if (eql args nil)
nil
(let ((pos (car args)))
(cons (subseq str offset pos)
(apply 'string-cut-recur (cons str (cons pos (cdr args))))))))))
(apply f (cons str (cons 0 args)))))
(string-cut "abcdefghijklmn" 2 4 6 9)
=> ("ab" "cd" "ef" "ghi")
(split-string "121,,12321" #\,)
=> ("121" "12321")
(split-string "121,,12321" #\, t)
=> ("121" "" "12321")
split-string は IGNORE-EMPTY がデフォルトでは nil なのが perl, ruby 利用者には直感的でないかも.
(read-from-string "999")
=> 999
3
戻り値は多値. ひとつめは変換結果,ふたつめは読み込まなかった最初の位置(全部読めた場合は文字列の長さと等しくなる).
整数の場合は parse-integer でもよい.ただし,浮動小数点の場合はこれに相当する関数はない(read-from-string を使うこと).
(parse-integer "123")
=> 123
(parse-integer "123.4")
=> 不正な数値の形式です: "123.4"
(read-from-string "10.5")
=> 10.5
4
read-from-string は何でも読めます.
(read-from-string "#o17")
=> 15
4
read-from-string は何でも読めます.
(read-from-string "#xff")
=> 255
4
read-from-string は何でも読めます.
format を使う.
(format nil "~A" 0.1)
=> "0.1"
(format nil "~A" 100)
=> "100"
(format nil "~B" 7)
=> "111"
(format nil "~O" 16)
=> "20"
(format nil "~X" 16)
=> "10"
(format nil "~R" 1010)
=> "one thousand, ten"
(format nil "~F" 10100000)
=> "1.01e7"
(symbolp 'abcde)
=> t
(string 'abcde)
=> "abcde"
(coerce "あいうえおabc" 'list)
=> (#\あ #\い #\う #\え #\お #\a #\b #\c)
(map 'list #'string "あいうえおabc")
=> ("あ" "い" "う" "え" "お" "a" "b" "c")
(char-code (elt "A" 0))
=> 65
(format nil "~C" (code-char 65))
=> "A"
(let ((v 256))
(cond
((stringp v) v)
((integerp v) (format nil "~D" v))
((floatp v) (format nil "~F" v))
((symbolp v) (format nil "~A" v))
((characterp v) (format nil "~C" v))
((rationalp v) (format nil "~F" v))))
=> "256"
xyzzy ではバッファと文字コードがセットで管理されており,文字列ごとの文字コードを意識するようにすることは困難.
変換後の漢字コードに別のバッファを割り当てるなら簡単. lisp の処理では漢字コードを意識せず,最後に change-fileio-encoding でバッファの漢字コードを設定してやればよい.
ちゃんとした暗号化を実装するのは大変なので rot13/47 にする(ネタばれ防止程度にしか使っちゃダメ!)。
(defun rot13 (str)
(coerce (mapcar (lambda (c)
(cond ((and (char>= c #\a) (char<= c #\m)) (code-char (+ (char-code c) 13)))
((and (char>= c #\n) (char<= c #\z)) (code-char (- (char-code c) 13)))
((and (char>= c #\A) (char<= c #\M)) (code-char (+ (char-code c) 13)))
((and (char>= c #\N) (char<= c #\Z)) (code-char (- (char-code c) 13)))))
(coerce str 'list))
'string))
(defun rot47 (str)
(coerce (mapcar (lambda (c)
(cond ((and (char>= c #\!) (char<= c #\O)) (code-char (+ (char-code c) 47)))
((and (char>= c #\P) (char<= c #\~)) (code-char (- (char-code c) 47)))))
(remove #\ESC (coerce str 'list)))
'string))
(defun rot13/47 (str)
(apply 'concat
(mapcar (lambda (s)
(cond ((string-match "^\\$B\\(.*\\)" s)
(map-jis-to-internal (concat (string #\ESC) "$B" (rot47 (match-string 1)))))
((string-match "^(B\\(.*\\)" s) (rot13 (match-string 1)))
(t (rot13 s))))
(split-string (map-internal-to-jis str) (string #\ESC)))))
(rot13/47 "abcあいうえおdef")
=> "nop嗔嗷嗾嘛噎qrs"
(rot13/47 "nop嗔嗷嗾嘛噎qrs")
=> "abcあいうえおdef"
(rot13 "abcABC")
=> "nopNOP"
(rot13 "nopNOP")
=> "abcABC"
(rot47 "abcABC")
=> "234pqr"
(rot47 "234pqr")
=> "abcABC"
(si:www-url-encode (map-internal-to-utf-8 "あいうえお"))
=> "%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A"
(format nil "~5,'0D" 123)
=> "00123"
(string-upcase "lisp")
=> "LISP"
(string-downcase "LISP")
=> "lisp"
nstring-upcase/downcase もある. こちらの場合は引数が破壊されます.
(setq s "abcde")
=> "abcde"
(nstring-upcase s)
=> "ABCDE"
s
=> "ABCDE"
また,文字列の先頭だけを大文字にするなら
(string-capitalize "abc")
=> "Abc"
(map 'string
(lambda (ch)
(cond
((lower-case-p ch) (char-upcase ch))
((upper-case-p ch) (char-downcase ch))))
"abCde")
(substitute-string " abcbbbbdc " "^[ \t]*\\([^ \t]*\\)[ \t]*$" "\\1")
=> "abcbbbbdc"
(let ((str "abcde\n"))
(when (string-match "^\\(.*\\)\n$" str)
(string-replace-match str "\\1")))
=> "abcde"
(let ((str "あいうえお"))
(when (string-match "^\\(.*\\).$" str)
(setq str (string-replace-match str "\\1"))))
=> "あいうえ"
(defun ljust (num str)
(let ((l (length str)))
(if (< l num)
(concat str (make-sequence 'string (- num l) :initial-element #\SPC))
str)))
(defun rjust (num str)
(let ((l (length str)))
(if (< l num)
(concat (make-sequence 'string (- num l) :initial-element #\SPC) str)
str)))
(defun center (num str)
(let ((l (length str)))
(if (< l num)
(let ((padding (make-sequence 'string (truncate (/ (- num l) 2)) :initial-element #\SPC)))
(concat padding
(if (oddp l) " " "")
str
padding))
str)))
(ljust 15 "xyzzy")
=> "xyzzy "
(rjust 15 "xyzzy")
=> " xyzzy"
(center 15 "xyzzy")
=> " xyzzy "
ヒアドキュメントがない.
Perl, Ruby のように文字列内に変数を埋め込む機能はないので、自前で書く必要がある。
(defun eval-string (str)
(if (string-match "#{\\([^}]+\\)}" str)
(concat (substring str 0 (match-beginning 0))
(format nil "~A" (eval (read-from-string (match-string 1))))
(eval-string (substring str (match-end 0))))
str))
使用例。
(setq str "test" val 99)
(eval-string "String is #{str} and value is #{cde}.")
=> "String is test and value is 99."
(defun hello (str)
(concat "Hello, " str "."))
(eval-string "#{(hello \"Taro\")}")
=> "Hello, Taro."
(eval-string "sum of 1, 2 and 3 is #{(+ 1 2 3)}")
=> "Sum of 1, 2 and 3 is 6"
(abbreviate-display-string "AbcdeFghijKlmno" 10)
=> "Abc...mno"
(abbreviate-string-column "AbcdeFghijKlmno" 10)
=> "AbcdeFghij"