;;; uim-sh.scm: uim interactive shell for debugging, batch processing
;;;             and serving as generic inferior process
;;;
;;; Copyright (c) 2003-2009 uim Project http://code.google.com/p/uim/
;;;
;;; All rights reserved.
;;;
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; 1. Redistributions of source code must retain the above copyright
;;;    notice, this list of conditions and the following disclaimer.
;;; 2. Redistributions in binary form must reproduce the above copyright
;;;    notice, this list of conditions and the following disclaimer in the
;;;    documentation and/or other materials provided with the distribution.
;;; 3. Neither the name of authors nor the names of its contributors
;;;    may be used to endorse or promote products derived from this software
;;;    without specific prior written permission.
;;;
;;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
;;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
;;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
;;; OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
;;; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
;;; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
;;; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
;;; SUCH DAMAGE.
;;;;

(require-extension (srfi 1 2 6 23 34 48))

(define %HYPHEN-SYM (string->symbol "-"))

(define uim-sh-prompt "uim> ")
(define uim-sh-opt-expression #f)
(define uim-sh-opt-arg-expression
  "(error \"no <expr> is passed as argument\")")

(define uim-sh-option-table
  `((("-b" "--batch")          . batch)
    (("-B" "--strict-batch")   . strict-batch)
    (("-r" "--require-module") . ,(lambda (args)
				    (and-let* ((name (safe-car args))
					       ((require-module name)))
				      (safe-cdr args))))
    (("--editline")            . ,(lambda (args)
				    (require-module "editline")
				    args))
    (("-e" "--expression")     . ,(lambda (args)
				    (set! uim-sh-opt-expression #t)
				    (and-let* ((expr (safe-car args)))
				      (set! uim-sh-opt-arg-expression expr)
				      (safe-cdr args))))
    (("-V" "--version")        . version)
    (("-h" "--help")           . help)))

(define uim-sh-usage
  (lambda ()
    (display "Usage: uim-sh [options] [file [arg ...]]
  -b
  --batch                 batch mode. suppress shell prompts
  -B
  --strict-batch          strict batch mode, implies -b. suppress shell prompts
                          and evaluated results
  -r <name>
  --require-module <name> require module
  --editline              require editline module for Emacs-like line editing
  -e <expr>
  --expression <expr>     evaluate <expr> (after loading the file, and disables
                          'main' procedure of it)
  -V
  --version               show software version
  -h
  --help                  show this help
  file                    absolute path or relative to system scm directory
  arg ...                 string argument(s) for 'main' procedure of the file
")))

(define uim-sh-display-version
  (lambda ()
    (format #t "uim-sh ~a [SigScheme ~a]" (uim-version) (sscm-version))
    (newline)))

(define uim-sh-define-opt-vars
  (lambda (opt-table prefix)
    (for-each (lambda (name)
		(let ((sym (symbol-append prefix %HYPHEN-SYM 'opt- name)))
		  (eval `(define ,sym #f)
			(interaction-environment))))
	      (filter symbol? (map cdr opt-table)))))

(define uim-sh-parse-args
  (lambda (opt-table prefix args)
    (uim-sh-define-opt-vars opt-table prefix)
    (let rec ((args args))
      (or (and-let* (((pair? args))
		     (opt (car args))
		     (rest (cdr args))
		     (ent (assoc opt opt-table member))
		     (action (cdr ent)))
	    (cond
	     ((symbol? action)
	      (set-symbol-value!
	       (symbol-append prefix %HYPHEN-SYM 'opt- action) #t))
	     ((procedure? action)
	      (set! rest (action rest)))
	     (else
	      (error "invalid action on option table")))
	    (rec rest))
	  args
	  '()))))  ;; an action possibly returns #f

(define uim-sh-loop
  (lambda (my-read)
    (if (and (not uim-sh-opt-batch)
	     (not uim-sh-opt-strict-batch)
	     (not (provided? "editline")))
	(display uim-sh-prompt))
    ;; Non-recoverable read error is turned into fatal errorr such as
    ;; non-ASCII char in token on a non-Unicode port.
    (let ((expr (guard (read-err
			(else (%%fatal-error read-err)))
		  (my-read))))
      (and (not (eof-object? expr))
	   (let ((res (eval expr (interaction-environment))))
	     (if (not uim-sh-opt-strict-batch)
		 (writeln res))
	     (uim-sh-loop my-read))))))

;; Loop even if error has been occurred. This is required to run
;; GaUnit-based unit test for uim.
(define uim-sh
  (lambda (args)
    (let* ((file.args (uim-sh-parse-args uim-sh-option-table
					 'uim-sh
					 (cdr args))) ;; drop the command name
	   (script (safe-car file.args))
	   (my-read (if (provided? "editline")
			(begin
			  (set! *editline-prompt-beginning* uim-sh-prompt)
			  editline-read)
			read))
	   (EX_OK       0)
	   (EX_SOFTWARE 70))
      (cond
       (uim-sh-opt-help
	(uim-sh-usage)
	EX_OK)

       (uim-sh-opt-version
	(uim-sh-display-version)
	EX_OK)

       (uim-sh-opt-expression
	(let* ((expr (read (open-input-string uim-sh-opt-arg-expression)))
	       (result (eval expr (interaction-environment))))
	  (if (not uim-sh-opt-strict-batch)
	      (begin
		(write result)
		(newline)))
	  EX_OK))

       (script
	(require script)
	(if (symbol-bound? 'main)
	    (let ((status (main file.args)))
	      (if (integer? status)
		  status
		  EX_SOFTWARE))
	    EX_OK))

       (else
	(let reloop ()
	  (and (guard (err (else
			    (%%inspect-error err)
			    #t))
		 (uim-sh-loop my-read))
	       (reloop)))
	EX_OK)))))

;; Verbose level must be greater than or equal to 1 to print anything.
(if (< (verbose) 1)
    (verbose 1))
