user6952907
user6952907

Reputation:

Brainf**k implemented in Common Lisp

I tried implementing Brainf**k in Common Lisp, SBCL. I have encountered some problems.

(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)

Edit: I edited code (again - thank you for help, Sylwester). Everything but input seems to work.

Upvotes: 1

Views: 281

Answers (1)

Sylwester
Sylwester

Reputation: 48745

Without knowing too much about how you think its supposed to work you need to define tape, pointer and output as global variables, preferrably with *earmuffs* so that you can see they are globals.

(defparameter *tape* (make-array '(1) :adjustable t))

Then I noticed > extends the *tape* with a default element nil. Thus for every > you do you should set it to 0 if it's not true (every value is true except nil) It also seem to think that pointer is always at the end of the tape. When doing >>>++++<<< the element with 4 in it is long gone.

loop-body is a global variable. You should have used let here to not clobber package level variables. You use loopwrong. See examples in Loop for black belts. Eg.

(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)))

Notice the declare there that tells Common Lisp to ignore ch not being used. The nesting is done automatically since read-deliited-list calls start-loop at a new [.

print-one-char doesn't add the char based on the ascii value but adds it as a number. Also usually it's common to print right away in BF so print-char might be better. You can print to a string input stream if you want to continue keeping it in memory until you press =.

read reads lisp data. Thus you would need to give it #\a instead of an a. Use read-char instead.

I guess you have enough to tacke at this point. Doing it with macros and reader-macros looked cool, but it is difficult to debug and extending since after the reader macros are added you have problems with code consisting those characters. Making one function for each operation except [ would simplify testing since you can test that and the macro would just expand to calling it.

(defun move-pointer-left ()
  (assert (> *pointer* 0) (*pointer*) "Tape pointer out of bounds: ~a" *pointer*) 
  (decf *pointer*))

(set-macro-character #\< (constantly '(move-pointer-left)))

Upvotes: 2

Related Questions