;;; The scsh argv switch parser.
;;; Copyright (c) 1995 by Olin Shivers. See file COPYING.

;;; Imports:
;;;	COMMAND-PROCESSOR: set-batch-mode?! command-loop
;;; 	ENSURES-LOADED: really-ensure-loaded
;;;     ENVIRONMENTS: set-interaction-environment! environment-ref
;;;		environment-define!
;;;	ERROR-PACKAGE: error
;;; 	EVALUATION: eval
;;; 	EXTENDED-PORTS: make-string-input-port
;;;	INTERFACES: make-simple-interface
;;;     INTERRUPTS: interrupt-before-heap-overflow!
;;; 	PACKAGE-COMMANDS-INTERNAL: user-environment config-package 
;;;		get-reflective-tower
;;; 	PACKAGE-MUTATION: package-open!
;;;	PACKAGES: structure-package structure? make-structure 
;;;		make-simple-package
;;;	RECEIVING: mv return stuff
;;;	SCSH-LEVEL-0-INTERNALS: set-command-line-args!
;;;	SCSH-VERSION: scsh-version-string
;;;	

;;; This should be defined by the package code, but it isn't.

(define (get-struct config-pack struct-name)
  (let ((s (environment-ref config-pack struct-name)))
    (cond ((structure? s) s)
	  (else (error "not a structure" s struct-name)))))

;;; ensure-loaded and load-into now write to noise-port anyway

(define (load-quietly filename p)
  (load-into filename p))

(define (really-ensure-loaded noise . structs)
  (apply ensure-loaded structs))
		      
;;; The switches:
;;; 	-o <struct>		Open the structure in current package.
;;; 	-n <package>		Create new package, make it current package.
;;; 	-m <struct>		<struct>'s package becomes current package.
;;; 	
;;; 	-l  <file>		Load <file> into current package.
;;;	-lm <file>		Load <file> into config package.
;;;
;;;                             These two require a terminating -s or -sfd arg:
;;; 	-ds			Load terminating script into current package.
;;; 	-dm			Load terminating script into config package.
;;; 	
;;; 	-e <entry>		Call (<entry>) to start program.
;;; 	
;;;				Terminating switches:
;;; 	-c <exp>		Eval <exp>, then exit.
;;; 	-s <script>		Specify <script> to be loaded by a -ds or -dm.
;;;	-sfd <num>		Script is on file descriptor <num>.
;;; 	--  			Interactive scsh.


;;; Return switch list, terminating switch, with arg, top-entry, 
;;; and command-line args. 
;;; - We first expand out any initial \ <filename> meta-arg.
;;; - A switch-list elt is either "-ds", "-dm", or a (switch . arg) pair
;;;   for a -o, -n, -m, -l, or -lm switch.
;;; - Terminating switch is one of {s, c, #f} for -s or -sfd, -c, 
;;;   and -- respectively.
;;; - Terminating arg is the <exp> arg to -c, the <script> arg to -s, 
;;;   the input port for -sfd, otw #f.
;;; - top-entry is the <entry> arg to a -e; #f if none.
;;; - command-line args are what's left over after picking off the scsh
;;;   switches.

(define (parse-scsh-args args)
  (let lp ((args (meta-arg-process-arglist args))
	   (switches '())	; A list of handler thunks
	   (top-entry #f)	; -t <entry>
	   (need-script? #f))	; Found a -ds or -dm?
;    (display args    (current-output-port))
    (if (pair? args)
	(let ((arg  (car args))
	      (args (cdr args)))

	  (cond ((string=? arg "-c")
		 (if (or need-script? top-entry (not (pair? args)))
		     (bad-arg)
		     (values (reverse switches) 'c (car args)
			     top-entry (cdr args))))

		((string=? arg "-s")
		 (if (not (pair? args))
		     (bad-arg "-s switch requires argument")
		     (values (reverse switches) 's (car args)
			     top-entry (cdr args))))

		;; -sfd <num>
		((string=? arg "-sfd")
		 (if (not (pair? args))
		     (bad-arg "-sfd switch requires argument")
		     (let* ((fd (string->number (car args)))
			    (p (fdes->inport fd)))
		       (release-port-handle p)	; Unreveal the port.
		       (values (reverse switches) 's p
			       top-entry (cdr args)))))

		((string=? arg "--")
		 (if need-script?
		     (bad-arg "-ds or -dm switch requires -s <script>")
		     (values (reverse switches) #f #f top-entry args)))

		((or (string=? arg "-ds")
		     (string=? arg "-dm"))
		 (lp args (cons arg switches) top-entry #t))
	    
		((or (string=? arg "-l")
		     (string=? arg "-lm"))
		 (if (pair? args)
		     (lp (cdr args)
			 (cons (cons arg (car args)) switches)
			 top-entry
			 need-script?)
		     (bad-arg "Switch requires argument" arg)))

		((or (string=? arg "-o")
		     (string=? arg "-n")
		     (string=? arg "-m"))
		 (if (pair? args)
		     (let* ((s (car args))
			    (name (if (and (string=? arg "-n")
					   (string=? s "#f"))
				      #f ; -n #f  treated specially.
				      (string->symbol s))))
		       (lp (cdr args)
			   (cons (cons arg name) switches)
			   top-entry
			   need-script?))
		     (bad-arg "Switch requires argument" arg)))

		((string=? arg "-e")
		 (lp (cdr args)                  switches
		     (string->symbol (car args)) need-script?))

	    (else (bad-arg "Unknown switch" arg))))

	(values (reverse switches) #f #f top-entry '()))))

;;; Do each -ds, -dm, -o, -n, -m, -l, and -lm switch, and return the final 
;;; result package and a flag saying if the script was loaded by a -ds or -dm.

(define (do-switches switches script-file)
; (format #t "Switches = ~a~%" switches)
  (let lp ((switches switches)
	   (script-loaded? #f))
    (if (pair? switches)
	(let ((switch (car switches))
	      (switches (cdr switches)))
;	  (format #t "Doing switch ~a~%" switch)
	  (cond

	    ((equal? switch "-ds")
	     (load-quietly script-file (interaction-environment))
;	     (format #t "loaded script ~s~%" script-file)
	     (lp switches #t))

	    ((equal? switch "-dm")
	     (load-quietly script-file (config-package))
;	     (format #t "loaded module ~s~%" script-file)
	     (lp switches #t))

	    ((string=? (car switch) "-l")
;	     (format #t "loading file ~s~%" (cdr switch))
	     (load-quietly (cdr switch) (interaction-environment))
	     (lp switches script-loaded?))

	    ((string=? (car switch) "-lm")
;	     (format #t "loading module file ~s~%" (cdr switch))
	     (load-quietly (cdr switch) (config-package))
	     (lp switches script-loaded?))

	    ((string=? (car switch) "-o")
	     (let ((struct-name (cdr switch))
		   (cp (config-package)))
	       ;; Should not be necessary to do this ensure-loaded, but it is.
	       (really-ensure-loaded #f (get-struct cp struct-name))
	       (package-open! (interaction-environment)
			      (lambda () (get-struct cp struct-name)))
;	       (format #t "Opened ~s~%" struct-name)
	       (lp switches script-loaded?)))

	    ((string=? (car switch) "-n")
	     (let* ((name (cdr switch))
		    (pack (new-empty-package name)))	; Contains nothing
	       (if name					; & exports nothing.
		   (let* ((iface  (make-simple-interface #f '()))
			  (struct (make-structure pack iface)))
		     (environment-define! (config-package) name struct)))
	       (set-interaction-environment! pack)
	       (lp switches script-loaded?)))

	    ((string=? (car switch) "-m")
;	     (format #t "struct-name ~s~%" (cdr switch))
	     (let ((struct (get-struct (config-package) (cdr switch))))
;	       (format #t "struct-name ~s, struct ~s~%" (cdr switch) struct)
	       (let ((pack (structure-package struct)))
;		 (format #t "package ~s~%" pack)
		 (set-interaction-environment! pack)
		 (really-ensure-loaded #f struct)
;		 (format #t "Switched to ~s~%" pack)
		 (lp switches script-loaded?))))

	    (else (error "Impossible error in do-switches. Report to developers."))))
	script-loaded?)))
	    

;;; (user-environment) probably isn't right. What is this g-r-t stuff?
;;; Check w/jar.

(define (new-empty-package name)
  (make-simple-package '() #t
		       (get-reflective-tower (user-environment)) ; ???
		       name)) 

(define (parse-switches-and-execute all-args context)
   (receive (switches term-switch term-val top-entry args)
	    (parse-scsh-args (cdr all-args))
   (begin 
     ;;; restart-command-processor will provide one, but we need 
     ;;; one already in do-switches
    (start-new-session context    
 			(current-input-port)
 			(current-output-port)
 			(current-error-port)
 			args
 			term-switch)
     (with-interaction-environment     
      (user-environment)
       (lambda ()
	 (with-scsh-sighandlers 
	  (not term-switch)
	  (lambda ()
	    (with-autoreaping
	     (lambda ()
	       (install-env)

	 ;; Have to do these before calling DO-SWITCHES, because actions
	 ;; performed while processing the switches may use these guys.
	 (set-command-line-args!
	  (cons (if (eq? term-switch 's)
		    (if (string? term-val)
			term-val			; Script file.
			"file-descriptor-script"); -sfd <num>
		    (car all-args))
		args))
	     ;; Set HOME-DIRECTORY and EXEC-PATH-LIST,
	 ;; quietly if not running an interactive script.
	 (init-scsh-vars term-switch)
	 
	 (let* ((script-loaded?  (do-switches switches term-val)))
	   (if (and (not script-loaded?)	; There wasn't a -ds or -dm,
		    (eq? term-switch 's))	; but there is a script,
	       (load-quietly term-val	; so load it now.
				 (interaction-environment)))
	   
	   (cond ((not term-switch) ; -- interactive
		  (exit 
		   (restart-command-processor 
		    args 
		    context 
		    (lambda () 
		      (display "welcome to scsh-0.6 alpha " 
			       (current-output-port))
		      (newline (current-output-port))
		      (in-package (user-environment) '())))))

		 ((eq? term-switch 'c)
		  (eval (read-exactly-one-sexp-from-string term-val)
			(interaction-environment))
		  (exit 0))
		 
		 (top-entry		; There was a -e <entry>.
		  ((eval top-entry (interaction-environment))
		   (command-line))
		  (exit 0))
		 
		     ;; Otherwise, the script executed as it loaded,
		 ;; so we're done.
		 (else (exit 0)))))))))))))



(define (read-exactly-one-sexp-from-string s)
  (with-current-input-port (make-string-input-port s)
    (let ((val (read)))
      (if (eof-object? (read)) val
	  (error "More than one value read from string" s)))))


(define (bad-arg . msg)
  (with-current-output-port (current-error-port)
    (for-each (lambda (x) (display x) (write-char #\space)) msg)
    (newline)
    (display "Usage: scsh [meta-arg] [switch ..] [end-option arg ...]

meta-arg: \\ <script-file-name>

switch:	-e <entry-point>	Specify top-level entry point.
	-o <structure>		Open structure in current package.
	-m <package>		Switch to package.
	-n <new-package>	Switch to new package.


	-lm <module-file-name>	Load module into config package.
	-l  <file-name>		Load file into current package.

	-ds 			Do script.
	-dm			Do script module.

end-option:	-s <script>	Specify script.
		-sfd <num>	Script is on file descriptor <num>.
		-c <exp>	Evaluate expression.
		--		Interactive session.
" (current-error-port)))
  (exit -1))


(define (repl)
  (command-loop (lambda () (set-batch-mode?! #f))
		#f))