読者です 読者をやめる 読者になる 読者になる

可変配列

common lisp read macro

今試していることで可変の配列が使いたくなったので、用意。

;; indexが(length vector)の範囲を越えている場合は、自動で配列を拡張
;; vectorはsymbolである必要がある
(defmacro assure-access (vector index)
  `(locally
    (declare ,@(if (symbolp index) `((fixnum ,index)))
         (vector ,vector)
         (optimize (safety 0)))
    (when (>= ,index (length ,vector))
       (setf ,vector (adjust-array ,vector (* ,index 2))))
    ,vector))

> (defvar as #(1))

> (assure-access as 2)
--> #(1 0 0 0)

> (setf (aref (assure-access as 5) 5) #\a)
--> #\a

> as
--> #(1 0 0 0 0 #\a 0 0 0 0)

このままだと長いのでリードマクロを定義。
ついでに、範囲チェックをしない版も選択できるようにしておく。

(flet ((split (str)
         (dotimes (i (length str))
       (case (char str i)
         ((#\? #\#)
          (return-from split
            (values (read-from-string (subseq str 0 i))
            (char str i)
            (read-from-string (subseq str (1+ i))))))))
     (error "string ~S don't include delimiter(#\\? or #\\#)" str)))

  (set-macro-character #\@
    (lambda (stream c)
      (declare (ignore c))
      (multiple-value-bind (array delim index) (split (s (read stream)))
        (if (char= delim #\?)
        `(aref (assure-access ,array ,index) ,index)
      `(aref ,array ,index))))))

;;;;
;; @as?index --> (aref (assure-access as index) index)
;; @as#index --> (aref as index)

> (setf @as#100 "100")
--> Error

> (setf @as?100 "100")
--> "100"

とりあえず、開発で試したいだけなので、これで充分だろう。

追記

「可変」というよりは、範囲外のアクセスをしてもエラーを出さない配列。末尾に順々に追加したいだけなら、vector-push-extendを使えばいい。

追記2

sという未定義の関数*1を使っていたので、修正。
ついでに、symbol以外も扱えるように変更(ex. @(car '(#(1) 2 (3)))?10))。
正しく使うことが前提なので、エラーチェックは無し。

(defmacro assure-access (vector index)
  `(locally
    (declare ,@(if (symbolp index)  `((fixnum ,index)))
	     ,@(if (symbolp vector) `((vector ,vector)))
	     (optimize (safety 0)))
    (when (>= ,index (length ,vector))
       (setf ,vector (adjust-array ,vector (* ,index 2))))
    ,vector))

(flet ((split (str)
         (dotimes (i (length str))
       (case (char str i)
         ((#\? #\#)
          (return-from split
            (values (read-from-string (subseq str 0 i))
            (char str i)
            (read-from-string (subseq str (1+ i))))))))
     (error "string ~S didn't include delimiter(#\\? or #\\#)" str)))

  (set-macro-character #\@
    (lambda (stream c)
      (declare (ignore c))
      (let ((exp (read stream)))
	(multiple-value-bind (array delim index) 
			     (if (symbolp exp)
				 (split (symbol-name exp))
			       (values exp (read-char stream) (read stream)))
	  (if (char= delim #\?)
	      `(aref (assure-access ,array ,index) ,index)
	    `(aref ,array ,index)))))))

*1:mkstr(『On Lisp』4章)と機能的には同じ