我尝试在Common Lisp,SBCL中实现Brainfuck。我遇到了一些问题。
(defparameter *tape* (make-array '(1) :adjustable t))
(defparameter *pointer* 0)
(defparameter *tape-size* 1)
(defparameter *output* (make-array '(0) :element-type 'base-char :fill-pointer 0 :adjustable t))
(defun move-pointer-right (a b)
(declare (ignore a))
(declare (ignore b))
'(progn
(incf *tape-size*)
(adjust-array *tape* (list *tape-size*))
(incf *pointer*)))
(defun move-pointer-left (a b)
(declare (ignore a))
(declare (ignore b))
'(progn (decf *pointer*)))
(defun increment-byte (a b)
(declare (ignore a))
(declare (ignore b))
'(incf (aref *tape* *pointer*)))
(defun decrement-byte (a b)
(declare (ignore a))
(declare (ignore b))
'(decf (aref *tape* *pointer*)))
(defun start-loop (stream ch)
(declare (ignore ch))
(let ((loop-body (read-delimited-list #\] stream t)))
`(loop :until (zerop (aref *tape* *pointer*))
:do ,@loop-body)))
(defun print-one-char (a b)
(declare (ignore a))
(declare (ignore b))
'(with-output-to-string (s *output*) (write-char (code-char (aref *tape* *pointer*)) s)))
(defun read-one-char (a b)
(declare (ignore a))
(declare (ignore b))
'(setf (aref *tape* *pointer*) (char-code (read-char *standard-input*))))
(defun flush-output (a b)
(declare (ignore a))
(declare (ignore b))
'(progn *output*))
(defun reset-me (a b)
(declare (ignore a))
(declare (ignore b))
'(progn
(setf *output* (make-array '(0) :element-type 'base-char :fill-pointer 0 :adjustable t))
(adjust-array *tape* '(1))
(setf (aref *tape* 0) 0)
(setf *pointer* 0)))
(set-macro-character #\< #'move-pointer-left)
(set-macro-character #\> #'move-pointer-right)
(set-macro-character #\+ #'increment-byte)
(set-macro-character #\[ #'start-loop)
(set-macro-character #\= #'flush-output)
(set-macro-character #\. #'print-one-char)
(set-macro-character #\, #'read-one-char)
(set-macro-character #\! #'reset-me)
(set-macro-character #\- #'decrement-byte)
编辑:我编辑了代码(再次 - 谢谢你的帮助,Sylwester)。除了输入之外的一切似乎都有效。
关于输入:我使用了read-char
,但它不能按我想要的方式工作。例如,D
输入“D”。我想重做它,以便在每个,
停止评估并等待用户输入。
问题:是否有progn
的替代方法不会返回值(我只想评估但不返回)?例如,(what-i-look-for (setf a 1) 1 2)
将a
设置为1
,但不会返回2.
答案 0 :(得分:2)
如果不太了解你认为它应该如何运作,你需要将tape
,pointer
和output
定义为全局变量,最好使用*earmuffs*
以便你可以看出它们是全局的。
(defparameter *tape* (make-array '(1) :adjustable t))
然后我注意到>
使用默认元素*tape*
扩展了nil
。因此,对于每个>
,你应该将它设置为0,如果它不是真的(除了nil
之外每个值都是真的)它似乎也认为pointer
始终位于胶带。在执行>>>++++<<<
时,其中包含4
的元素早已消失。
loop-body
是一个全局变量。你应该在这里使用let
来破坏包级别变量。您使用loop
错误。请参阅Loop for black belts中的示例。例如。
(defun start-loop (stream ch)
(declare (ignore ch))
(let ((loop-body (read-delimited-list #\] stream t)))
`(loop :until (zerop (aref *tape* *pointer*))
:do ,@loop-body)))
注意那里的declare
告诉Common Lisp忽略ch
没有被使用。嵌套是自read-deliited-list
调用新start-loop
[
后自动完成的。
print-one-char
不会根据ascii值添加char,而是将其添加为数字。通常,在BF中立即打印也是很常见的,因此print-char
可能会更好。如果要继续将其保留在内存中,直到按=
,就可以打印到字符串输入流。
read
读取lisp数据。因此,您需要提供#\a
而不是a
。请改用read-char
。
我想你有足够的时间来解决这个问题。使用宏和读取器宏看起来很酷,但是很难调试和扩展,因为在添加读取器宏之后,你会遇到包含这些字符的代码的问题。为除[
之外的每个操作创建一个函数将简化测试,因为您可以测试它,宏只会扩展为调用它。
(defun move-pointer-left ()
(assert (> *pointer* 0) (*pointer*) "Tape pointer out of bounds: ~a" *pointer*)
(decf *pointer*))
(set-macro-character #\< (constantly '(move-pointer-left)))