;; Copyright 2006 Pablo Barenbaum

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

(defvar *blanks* '(#\Space #\Tab #\Newline #\Return))
(defvar *tabs* '(#\Tab))
(defvar *comment-markers* `(#\;))
(defvar *tab-width* 8)

(defun width (char)
 (if (member char *tabs*)
   *tab-width*
   1))

(defun distance (str)
(do ((col 0 (+ col (width (aref str col)))))
  ((not (member (aref str col) *blanks* :test #'char=))
   col)))

(defun trim-blanks (str)
 (string-trim *blanks* str))

(defun blank-line-p (str)
(let ((trimmed (trim-blanks str)))
  (or (string= "" trimmed)
      (member (aref trimmed 0) *comment-markers* :test #'char=))))

(defun last-line-p (line)
(or (eq line 'eof) (string= (trim-blanks line) "!#")))

(defun read-off-side-to-string (stream)
(let ((prev-lines (list (list "PROGN")))
      (prev-dists (list -1))
      (prev-dist -1))
  (flet ((pop-expr ()
           (let* ((tail (pop prev-lines))
                  (head (pop (first prev-lines))))
             (push
               (format nil "(~A~%~{~A~%~})" head (nreverse tail))
               (first prev-lines))))
         (next-line ()
           (loop for line = (read-line stream nil 'eof)
                  while (and (not (eq line 'eof)) (blank-line-p line))
                  finally (return line))))
    (with-output-to-string (s)
      (loop
        for line = (next-line)
        while (not (last-line-p line))
        do (let ((dist (distance line)))
             (when (> dist prev-dist)
               (push dist prev-dists)
               (push (list) prev-lines))
             (loop while (and (not (null (cdr prev-dists)))
                              (< dist (car prev-dists)))
                   do (pop prev-dists)
                   do (pop-expr))
             (push line (first prev-lines))
             (setf prev-dist dist)))

      (loop while (not (null (cdr prev-dists)))
            do (pop prev-dists)
            do (pop-expr))
      (format s "~{~A~%~}" (nreverse (pop prev-lines)))))))

(defun read-off-side (stream c n)
 (let ((r (nth-value 0 (read-from-string (read-off-side-to-string stream)))))
   (if (atom r)
     (list r)
     r)))