;; ————————————————————————————————————— ;; PACKAGE (defpackage :bf-aheago (:use :cl :anaphora) (:export :interpret) (:nicknames :bf-a)) (in-package :bf-aheago) ;; ————————————————————————————————————— ;; MACROS (defmacro interpret-char (char) `(progn (when debug-p (format *error-output* "~A" ,char)) (cond ((eq ,char #\.) (output-cell tape pointer)) ((eq ,char #\,) (input-cell tape pointer)) ((eq ,char #\<) (bound-decf pointer tape-length)) ((eq ,char #\>) (bound-incf pointer tape-length)) ((eq ,char #\+) (inc-cell tape pointer)) ((eq ,char #\-) (dec-cell tape pointer)) ((eq ,char #\[) (loop-advance tape pointer input-stream)) ((eq ,char #\]) (loop-rewind tape pointer input-stream))))) ;; VARYING [NUMBER] [NUMBER] → NUMBER (defmacro bound-incf (object &optional (max 256) (min 0)) "Increment (destructive) an object, but bounds-check with #'bound-ensure." `(setf ,object (bound-ensure (1+ ,object) ,max ,min))) ;; VARYING [NUMBER] [NUMBER] → NUMBER (defmacro bound-decf (object &optional (max 256) (min 0)) "Decrement (destructive) an object, but bounds-check with #'bound-ensure." `(setf ,object (bound-ensure (1- ,object) ,max ,min))) ;; ————————————————————————————————————— ;; INTERPRETER ;; STREAM → ARRAY (defmethod interpret ((input-stream stream) &key (tape-length 30000) (debug-p nil)) "Interpret the brainfuck code within the given stream: returns the tape." (let ((tape (make-tape tape-length)) (pointer 0)) (loop :if (not (listen input-stream)) :return tape :do (alet (read-char input-stream) (interpret-char it))))) (defmethod interpret ((string string) &key (tape-length 30000) (debug-p nil)) (interpret (make-input-string string) :tape-length tape-length :debug-p debug-p)) (defmethod interpret ((pathname pathname) &key (tape-length 30000) (debug-p nil)) (with-open-file (stream pathname) (interpret stream :tape-length tape-length :debug-p debug-p))) ;; ————————————————————————————————————— ;; CELLS ;; ARRAY NUMBER → NUMBER (defun inc-cell (tape index) "Increment the given cell." (bound-incf (aref tape index))) ; (setf (aref tape index) (bound-ensure (1+ (aref tape index))))) ;; ARRAY NUMBER → NUMBENR (defun dec-cell (tape index) "Decrement the given cell." (bound-decf (aref tape index))) ; (setf (aref tape index) (bound-ensure (1- (aref tape index))))) ;; ARRAY NUMBER → NIL (defun output-cell (tape index) "Print the given cell in the tape to stdout." (format t "~A" (code-char (aref tape index)))) ;; ARRAY NUMBER → CHAR (defun input-cell (tape index) "Input a char's int into the tape at given index." (alet (read-char *standard-input* nil 0) (setf (aref tape index) (if (numberp it) it (char-code it))))) ;; [NUMBER] → ARRAY (defun make-tape (&optional (length 30000)) "Make a clean, 0-initialized BF tape." (make-array (list length) :initial-element 0)) ;; ————————————————————————————————————— ;; LOOPING [] ;; ARRAY NUMBER STREAM → NIL (defun loop-rewind (tape index stream) "Restart the loop (move pointer to last '[') if nonzero cell value." (if (not (zerop (aref tape index))) (stream-rewind-to stream #\[))) ;; ARRAY NUMBER STREAM → NIL (defun loop-advance (tape index stream) "Skip the loop (move to next ']') if cell value is zero." (if (zerop (aref tape index)) (stream-advance-to stream #\]))) ;; ————————————————————————————————————— ;; STREAM MANIP ;; STREAM → CHAR (defun retroread-char (stream) "Read the previous character in a file-stream." (alet (file-position stream) (file-position stream (- it 2))) (read-char stream)) ;; STREAM CHAR → NIL (defun stream-advance-to (stream char) "Advance a stream's pointer until the given character is read." (if (not (eq char (read-char stream))) (stream-advance-to stream char))) ;; STREAM CHAR → NIL (defun stream-rewind-to (stream char) "Reverse a stream's pointer until the given character is read." (if (not (eq char (retroread-char stream))) (stream-rewind-to stream char))) ;; ————————————————————————————————————— ;; MISC ;; NUMBER [NUMBER] [NUMBER] (defun bound-ensure (number &optional (max 256) (min 0)) "Ensure the given number remains within the given bounds (with overflow)." (cond ((> min number) (bound-ensure (+ number max) max min)) ((< max number) (bound-ensure (- max number) max min)) (T number)))