CommonLispのLDB/DPBの挙動について

chip8 emulatorを作る際にLSDとDPBが出てきたが、ドキュメントの説明がわかりづらかったので自分なりに纏めておく。間違っていたらtwitterでリプライをいただけると幸い。


基数の取扱い

common lispでは2進数、8進数、16進数を以下のように取扱える。他の言語とあまり変わらない。

bit演算は基本的には2進数で扱うほうがわかりやすいので、以下は基本的に2進数を使う。

> #b110 ;; 2進数
6
> #o110 ;; 8進数
72
> #x110 ;; 16進数
272

byte

url: CLHS: Function BYTE, BYTE-SIZE, BYTE-POSITION

Syntax:
byte size position => bytespec
byte-size bytespec => size
byte-position bytespec => position

byte関数はbyte specifierというオブジェクトを返す。byte specifier(bytespec)はドキュメントによると、「整数のビット範囲を指定する」オブジェクトのようだ。以下のldbとdpbで使う。

> (byte 100 200)
(100 . 200)
> (byte-size (byte 100 200))
100
> (byte-position (byte 100 200))
200

logbitp

url: Function LOGBITP - CLHS

Syntax:
logbitp index integer => generalized-boolean

logbitの挙動は極めて単純だ。指定したindexのbitがtrue/falseかを返すだけだ。indexは最下位bitを0番目としてそこからの数をしめす。

> (logbitp 0 #b10101) ;; 最下位bitが1か0か
T
> (logbitp 2 #b10101) ;; 最下位bitから2番目が1か0か
T
> (logbitp 3 #b10101) ;; 最下位bitから3番目が1か0か
NIL

ldb

url: Accessor LDB - CLHS

ldbはbytespecを基にbyteを抽出する関数だ。

position 1 (最下位bitから1番目)から size 3 (3bit)を指定して #b11101 からbyteを取得する例は以下だ。

> (ldb (byte 3 1) #b11101)
6 (3 bits, #x6, #o6, #b110)

positionに異常値(引数bitよりも大きな値)を入れると 0 が返ってくるようだ。sizeは大きい値をいれても最上位bitまで見てくれる。

> (ldb (byte 3 10000) #b11101)
0
> (ldb (byte 10000 3) #b11101)
3 (2 bits, #x3, #o3, #b11)

CLHSには以下のようなメモがある。これは上記の挙動を考えれば自明だ。

(logbitp j (ldb (byte s p) n))
== (and (< j s) (logbitp (+ j p) n))

dpb

url: CLHS: Function DPB - LispWorks

Syntax:
dpb newbyte bytespec integer => result-integer

dpbはbytespecで指定した箇所にnewbyteをreplaceしてintegerを返す関数だ。

> (dpb 1 (byte 1 1) #b10000) ;; 最下位bitから1番目bit1つ分を1に書き換える
18 (5 bits, #x12, #o22, #b10010)

> (dpb 3 (byte 2 1) #b10000) ;; 最下位bitから1番目サイズ2を3(#b11)に書き換える
22 (5 bits, #x16, #o26, #b10110)

> (dpb -1 (byte 2 1) #b10000) ;; 最下位bitから1番目サイズ2を-1(bitを反転)に書き換える
22 (5 bits, #x16, #o26, #b10110)

newbyteを十分に大きな値を入れた場合は以下のように挙動する。newbyteの下位byte position分のみ評価されるので当然っちゃ当然。byteのpositionとsizeはLDBの時と同じ。

> (dpb 100000 (byte 1 1) #b10000)
18 (5 bits, #x12, #o22, #b10010)
> (dpb 100001 (byte 1 1) #b10000)
16 (5 bits, #x10, #o20, #b10000)

CLHSには以下のようなメモがある。これもまた挙動としてか上記を考慮すると理解できる。

 (logbitp j (dpb m (byte s p) n))
 ==  (if (and (>= j p) (< j (+ p s)))
        (logbitp (- j p) m)
        (logbitp j n))

emacsの設定でhyperspecを起動できるようにしているので開発時に簡単にドキュメントを引け、式を評価することができる。まぁそもそもhyperspecは読みにくい。

(with-eval-after-load 'hyperspec
  (general-define-key "C-c h" 'hyperspec-lookup)

  (setq common-lisp-hyperspec-root "~/.roswell/HyperSpec/")

  (defun common-lisp-hyperspec (symbol-name)
    (interactive (list (common-lisp-hyperspec-read-symbol-name)))
    (let ((name (common-lisp-hyperspec--strip-cl-package
                 (downcase symbol-name))))
      (cl-maplist (lambda (entry)
                    (eww-open-file (concat common-lisp-hyperspec-root "Body/"
                                           (car entry)))
                    (when (cdr entry)
                      (sleep-for 1.5)))
                  (or (common-lisp-hyperspec--find name)
                      (error "The symbol `%s' is not defined in Common Lisp"
                             symbol-name)))))

  (defun common-lisp-hyperspec-lookup-reader-macro (macro)
    (interactive
     (list
      (let ((completion-ignore-case t))
        (completing-read "Look up reader-macro: "
                         common-lisp-hyperspec--reader-macros nil t
                         (common-lisp-hyperspec-reader-macro-at-point)))))
    (eww-open-file
     (concat common-lisp-hyperspec-root "Body/"
             (gethash macro common-lisp-hyperspec--reader-macros))))

  (defun common-lisp-hyperspec-format (character-name)
    (interactive (list (common-lisp-hyperspec--read-format-character)))
    (cl-maplist (lambda (entry)
                  (eww-open-file (common-lisp-hyperspec-section (car entry))))
                (or (gethash character-name
                             common-lisp-hyperspec--format-characters)
                    (error "The symbol `%s' is not defined in Common Lisp"
                           character-name))))

  (defadvice common-lisp-hyperspec (around common-lisp-hyperspec-around activate)
    (let ((buf (current-buffer)))
      ad-do-it
      (switch-to-buffer buf)
      (pop-to-buffer "*eww*")))

  (defadvice common-lisp-hyperspec-lookup-reader-macro (around common-lisp-hyperspec-lookup-reader-macro-around activate)
    (let ((buf (current-buffer)))
      ad-do-it
      (switch-to-buffer buf)
      (pop-to-buffer "*eww*")))

  (defadvice common-lisp-hyperspec-format (around common-lisp-hyperspec-format activate)
    (let ((buf (current-buffer)))
      ad-do-it
      (switch-to-buffer buf)
      (pop-to-buffer "*eww*"))))