;;; Brainfuck to LLVM compiler
;;; $Id: brainfuck.lisp,v 1.4 2009/12/24 16:57:59 bernd Exp $

(defpackage "BRAINFUCK"
  (:nicknames "BF")
  (:use "COMMON-LISP")
  (:export :compile-file)
  (:shadow :compile-file))

(in-package "BRAINFUCK")

(defvar *head* 0)
(defvar *tape* 0)
(defvar *label* 0)
(defvar *last-label* 0)
(defvar *test* 0)
(defvar *loop-stack* '())

(defun llvm-header (stream)
  (let ((head *head*)
	(label *label*))
    (format stream "~&declare void @llvm.memset.i32(i8* nocapture, i8, i32, i32) nounwind~%~
                    declare i32 @getchar()~%~
                    declare i32 @putchar(i32)~2%~
                    define void @main() {~%~
                    main.~D:~%~
                    ~4T%arr = malloc i8, i32 65536~%~
                    ~4Tcall void @llvm.memset.i32(i8* %arr, i8 0, i32 65536, i32 1)~%~
                    ~4T%head.~D = getelementptr i8* %arr, i32 32768~2%" label head)))

(defun llvm-footer (stream)
  (format stream "~&~4Tfree i8* %arr~%~
                  ~4Tret void~%~
                  }~%"))

(defun plus (stream)
  (let* ((head *head*)
	 (tape0 (incf *tape*))
	 (tape1 (incf *tape*)))
    (format stream "~&~4T%tape.~D = load i8* %head.~D ; +~%" tape0 head)
    (format stream "~4T%tape.~D = add i8 %tape.~D, 1~%" tape1 tape0)
    (format stream "~4Tstore i8 %tape.~D, i8* %head.~D~%" tape1 head)))

(defun minus (stream)
  (let* ((head *head*)
	 (tape0 (incf *tape*))
	 (tape1 (incf *tape*)))
    (format stream "~&~4T%tape.~D = load i8* %head.~D ; -~%" tape0 head)
    (format stream "~4T%tape.~D = sub i8 %tape.~D, 1~%" tape1 tape0)
    (format stream "~4Tstore i8 %tape.~D, i8* %head.~D~%" tape1 head)))

(defun left (stream)
  (let* ((head0 *head*)
	 (head1 (incf *head*)))
    (format stream "~&~4T%head.~D = getelementptr i8* %head.~D, i32 -1 ; <~%" head1 head0)))

(defun right (stream)
  (let* ((head0 *head*)
	 (head1 (incf *head*)))
    (format stream "~&~4T%head.~D = getelementptr i8* %head.~D, i32 1 ; >~%" head1 head0)))

(defun dot (stream)
  (let* ((head *head*)
	 (tape0 (incf *tape*))
	 (tape1 (incf *tape*)))
    (format stream "~&~4T%tape.~D = load i8* %head.~D ; .~%" tape0 head)
    (format stream "~4T%tape.~D = sext i8 %tape.~D to i32~%" tape1 tape0)
    (format stream "~4Tcall i32 @putchar(i32 %tape.~D)~%" tape1)))

(defun comma (stream)
  (let* ((head *head*)
	 (tape0 (incf *tape*))
	 (tape1 (incf *tape*)))
    (format stream "~&~4T%tape.~D = call i32 @getchar() ; , ~%" tape0)
    (format stream "~4T%tape.~D = trunc i32 %tape.~D to i8~%" tape1 tape0)
    (format stream "~4Tstore i8 %tape.~D, i8* %head.~D~%" tape1 head)))

(defun left-bracket (stream)
  (let* ((head0 *head*)
	 (head1 (incf *head*))
	 (loop-before *last-label*)
	 (loop-test (incf *label*))
	 (loop-body (incf *label*))	 
	 (loop-after (incf *label*)))
    (format stream "~&~4Tbr label %main.~D ; [~2%" loop-test)
    (format stream "main.~D: ; loop-body~%" loop-body)
    (push head0 *loop-stack*)
    (push head1 *loop-stack*)
    (push loop-before *loop-stack*)
    (push loop-test *loop-stack*)
    (push loop-body *loop-stack*)
    (push loop-after *loop-stack*)
    (setf *last-label* loop-body)))

(defun right-bracket (stream)
  (let* ((loop-after (pop *loop-stack*))
	 (loop-body (pop *loop-stack*))
	 (loop-test (pop *loop-stack*))
	 (loop-before (pop *loop-stack*))
	 (head2 (pop *loop-stack*))
	 (head0 (pop *loop-stack*))
	 (head1 *head*)
	 (head3 (incf *head*))
	 (last-label *last-label*)
	 (tape (incf *tape*))
	 (test (incf *test*)))
    (format stream "~&~4Tbr label %main.~D ; ]~2%" loop-test)
    (format stream "main.~D: ; loop-test~%" loop-test)
    (format stream "~4T%head.~D = phi i8* [%head.~D, %main.~D], [%head.~D, %main.~D]~%"
	    head2 head0 loop-before head1 last-label)
    (format stream "~4T%tape.~D = load i8* %head.~D~%" tape head2)
    (format stream "~4T%test.~D = icmp eq i8 %tape.~D, 0~%" test tape)
    (format stream "~4Tbr i1 %test.~D, label %main.~D, label %main.~D~2%" test loop-after loop-body)
    (format stream "main.~D: ; loop-after~%" loop-after)
    (format stream "~4T%head.~D = phi i8* [%head.~D, %main.~D]~%" head3 head2 loop-test)
    (setf *last-label* loop-after)))

(defun initialize ()
  (setf *head* 0  *tape* 0
	*label* 0  *last-label* 0  *test* 0
	*loop-stack* '()))

(defun %compile (string &optional (stream t))
  (loop for c across string do
       (cond ((char= #\+ c) (plus stream))
	     ((char= #\- c) (minus stream))
	     ((char= #\< c) (left stream))
	     ((char= #\> c) (right stream))
	     ((char= #\. c) (dot stream))
	     ((char= #\, c) (comma stream))
	     ((char= #\[ c) (left-bracket stream))
	     ((char= #\] c) (right-bracket stream)))))

(defun compile-file (input-file &key output-file)
  (initialize)
  (let ((outfile (or output-file 
		     (make-pathname :name (pathname-name input-file) :type "ll"))))
    (with-open-file (istream input-file)
      (with-open-file (ostream outfile :direction :output :if-exists :supersede)
	(llvm-header ostream)
	(loop for line = (read-line istream nil nil)
	   while line do (%compile line ostream))
	(llvm-footer ostream)))))
