;;;;
;;;; i n i t . s t k			-- The file launched at startup
;;;;
;;;; Copyright (C) 1993,1994,1995 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
;;;; 
;;;; Permission to use, copy, and/or distribute this software and its
;;;; documentation for any purpose and without fee is hereby granted, provided
;;;; that both the above copyright notice and this permission notice appear in
;;;; all copies and derived works.  Fees for distribution or use of this
;;;; software or derived works may only be charged with express written
;;;; permission of the copyright holder.  
;;;; This software is provided ``as is'' without express or implied warranty.
;;;;
;;;;           Author: Erick Gallesio [eg@kaolin.unice.fr]
;;;;    Creation date: ??-Sep-1993 ??:??
;;;; Last file update: 17-Jul-1995 15:55
;;;;


(define *debug*    	#f)	; #t for debuggging (disable macro inlining)
(define *gc-verbose* 	#f)	; #t to have a message at start/stop of a GC
(define *print-banner*  #t)	; #f to avoid the copyright message


(define @undefined (if #f #t))
(define *argc* (length *argv*)) 

(define call/cc		call-with-current-continuation)
(define !		system)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Some stuff for defining macros
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define define-macro #f)
(define %replace     #f)
(define %beginify    #f)

(let ((if if) (and and) (begin begin) (set-car! set-car!) (set-cdr! set-cdr!)
      (not not) (pair? pair?) (car car) (cdr cdr) 
      (null? null?) (cons cons) 
      (let let) (macro macro) (list list) (append append))

  (set! %replace
	(lambda (before after)
	  (if (and (not *debug*) (pair? before) (pair? after))
	      (begin
		(set-car! before (car after))
		(set-cdr! before (cdr after))))
	  after))

  (set! %beginify 
	(lambda (forms)
	  (if (null? (cdr forms)) (car forms) (cons 'begin forms))))

  (set! define-macro 
	(macro form
	  (let ((name (car (car (cdr form))))
		(args (cdr (car (cdr form)))))
	    (list 'define name
		  (list 'macro 'params
			(list '%replace 
			      'params
			      (list 'apply
				    (append (list 'lambda args)
					    (cdr (cdr form)))
				    (list 'cdr 'params)))))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Some utilities
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define gensym 
  (let ((counter 0))
    (lambda prefix
      (set! counter (+ counter 1))
      (string->symbol
         (string-append (if (null? prefix) "G" (car prefix))
			(number->string counter))))))

(define (apropos s)
  (if (not (symbol? s)) (error "apropos: bad symbol" s))
  (let ((res '())
	(env (the-environment)) 
	(str (symbol->string s)))

    (do ((l (cdr (environment->list env)) (cdr l))); cdr to avoid the binding to "s"
	((null? l) (if (null? res) #f res))
      (do ((v (car l) (cdr v)))
	  ((null? v))
	(if (and (string-find? str (symbol->string (caar v))) 
		 (symbol-bound? (caar v)))
	    (set! res (cons (caar v) res)))))))

(define (documentation x)
  "provides documentation for its parameter if it exists"
  (define (nodoc)
    (format #t "No documentation available for ~A\n" x))
  (cond
    ((closure? x) (let ((body (procedure-body x)))
		    (if (string? (caddr body)) 
			(format #t "~A\n" (caddr body))
			(nodoc))))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Random 
;;;;	This version of random is constructed over the C one. It can return
;;;;	bignum numbers. Idea is due to Nobuyuki Hikichi <hikichi@sra.co.jp>
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define random 
  (let ((C-random random)
	(max-rand #x7fffffff)) ; Probably more on 64 bits machines
    (letrec ((rand (lambda (n)
		     (cond 
		      ((zero? n)	   0)
		      ((< n max-rand) (C-random n))
		      (else 	  (+ (* (rand (quotient n max-rand)) max-rand)
				     (rand (remainder n max-rand))))))))
      (lambda (n)
	(if (zero? n)
	    (error "random: bad number: 0")
	    (rand n))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; do
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-macro (do inits test . body)
  (let ((loop-name (gensym)))
    `(letrec ((,loop-name 
	       (lambda ,(map car inits)
		 (if ,(car test)
		     (begin ,@(if (null? (cdr test)) 
				  (list @undefined) 
				  (cdr test)))
		     (begin ,@body
			    (,loop-name ,@(map (lambda (init)
						 (if (null? (cddr init))
						     (car init)
						     (caddr init)))
					       inits)))))))
       (,loop-name ,@(map cadr inits)))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; dotimes
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-macro (dotimes binding . body)
  (if (list? binding)
      ;; binding is a list
      (let ((var   #f) (count #f) (result #f))
	(case (length binding)
	  (2  (set! var    (car binding))
	      (set! count  (cadr binding)))
	  (3  (set! var    (car binding))
	      (set! count  (cadr binding))
	      (set! result (caddr binding)))
	  (else (error "dotimes: bad binding construct: ~S" binding)))
	`(do ((,var 0 (+ ,var 1)))
	     ((= ,var ,count) ,result)
	   ,@body))
      ;; binding is ill-formed
      (error "dotimes: binding is not a list: ~S" binding)))



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; case
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-macro (case key . clauses)
  ;; conditionally execute the clause eqv? to key
  (define (case-make-clauses key)
    `(cond ,@(map
              (lambda (clause)
                (if (pair? clause)
                    (let ((case (car clause))
                          (exprs (cdr clause)))
                      (cond ((eq? case 'else)
                             `(else ,@exprs))
                            ((pair? case)
                             (if (= (length case) 1)
                                 `((eqv? ,key ',(car case)) ,@exprs)
                                 `((memv ,key ',case) ,@exprs)))
                            (else
                             `((eqv? ,key ',case) ,@exprs))))
                    (error "case: invalid syntax in ~a" clause)))
              clauses)))
  (if (pair? key)
      (let ((newkey (gensym)))
        `(let ((,newkey ,key))
           ,(case-make-clauses newkey)))
      (case-make-clauses key)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Some usal macros 
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-macro (unquote form) 
  (error "Usage of unquote outside of quasiquote in ,~A" form))

(define-macro (unquote-splicing form) 
  (error "Usage of unquote-splicing outside of quasiquote in ,@~A" form))

(define 1+ (macro form (list + (cadr form) 1)))
(define 1- (macro form (list - (cadr form) 1)))

(define macroexpand-1 macro-expand)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Section 6.10
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (call-with-input-file string proc)
  (let* ((file   (open-input-file string))
	 (result (proc file)))
    (close-input-port file)
    result))

(define (call-with-output-file string proc)
  (let* ((file   (open-output-file string))
	 (result (proc file)))
    (close-output-port file)
    result))

(define (call-with-input-string string proc)
  (proc (open-input-string string)))

(define (call-with-output-string proc)
  (let ((str (open-output-string)))
    (proc str)
    (get-output-string str)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; File management
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define *load-verbose* 	 #f)
(define *load-suffixes*	'("stk" "stklos" "scm" "so"))
(define *load-path*
        (let* ((build-path (lambda (path)
			     (and path 
				  (let ((len (string-length path))
					(new '())
					(i 0))
				    (do ((j 0 (+ j 1)))
					((= j len))
				      (if (eqv? (string-ref path j) #\:)
					  (begin
					    (set! new (cons (substring path i j) 
							    new))
					    (set! i (+ j 1)))))
				    ;; don't forget the last path
				    (reverse (cons (substring path i len) 
						   new))))))
	       (path	    (build-path (getenv "STK_LOAD_PATH"))))

	  ;; If user has specified a load path with STK_LOAD_PATH, use it
	  ;; Always append STK_LIBRARY at end to be sure to find our files
	  (append (list ".")
		  (or path '())
		  (let ((lib (getenv "STK_LIBRARY")))
		    (list lib
			  (string-append lib "/STk")
			  (string-append lib "/" (machine-type)))))))

;
; Require/Provide/Provided?
;
(define require   #f)
(define provide   #f)
(define provided? #f)

(let ((provided '()))

  (set! require (lambda (what)
		  (unless (member what provided)
		     (load what)
		     (unless (member what provided)
			(format #t "WARNING: ~S was not provided~%" what)))
		  what))

  (set! provide (lambda (what)
		  (unless (member what provided)
			  (set! provided (cons what provided)))
		  what))
	
  (set! provided? (lambda (what)
		    (and (member what provided) #t))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Port conversions
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (port->string p)
  (unless (or (input-port? p) (input-string-port? p))
     (error "port->string: Bad port ~S" p))
  ;; Read all the lines of port and put them in a string
  (let loop ((res "") (line (read-line p)))
    (if (eof-object? line)
	res
	(loop (string-append res line "\n") (read-line p)))))
      
(define (port->list reader p)
  (unless (or (input-port? p) (input-string-port? p))
     (error "port->list: Bad port ~S" p))
  ;; Read all the lines of port and put them in a list
  (let loop ((res '()) (sexp (reader p)))
    (if (eof-object? sexp) 
	(reverse res)
	(loop (cons sexp res) (reader p)))))
 
(define (port->sexp-list p) 
  (port->list read p))

(define (port->string-list p)
  (port->list read-line p))

(define (exec command)
  (call-with-input-file (string-append "| " command) port->string))

(define (exec-string-list command)
  (call-with-input-file (string-append "| " command) port->string-list))
    

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Misc
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (closure? obj)
  (and (procedure? obj) (procedure-body obj) #t))

(define (primitive? obj)
  (and (procedure? obj) (not (procedure-body obj)) #t))

(define (widget? obj)
  (and (tk-command? obj) (not (catch (obj 'configure)))))

(define-macro (unwind-protect body . unwind-forms)
  `(dynamic-wind
    (lambda () #f)
    (lambda () ,body)
    (lambda () ,@unwind-forms)))

(define-macro (when test . body)
  `(if ,test ,@(if (= (length body) 1) body `((begin ,@body)))))

(define-macro (unless test . body)
  `(if (not ,test) ,@(if (= (length body) 1) body `((begin ,@body)))))

(define-macro (multiple-value-bind vars form . body)
  `(apply (lambda ,vars ,@body) ,form))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Autoloads
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(autoload "unix" 	basename dirname decompose-file-name)
(autoload "process"	run-process process?)
(autoload "regexp"	string->regexp regexp? regexp-replace regexp-replace-all)

;; STklos
(autoload "stklos" 	define-class define-method make define-generic)
(autoload "describe"	describe)
(autoload "hash"   	make-hash-table hash-table-hash)
(autoload "socket"   	make-server-socket make-client-socket)


;; martine packages 
(autoload "pp"  pp)
(autoload "trace" tracef)



;;;
;;; quit and bye procedures. Since Tk redefine exit, they cannot be simple aliases
;;;
(define quit (lambda l (apply exit l)))
(define bye  (lambda l (apply exit l)))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Tk initializations
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define Tk:initialized?  #f)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Try to load user init file 
;;;; Idea from (Olaf Burkart) burkart@zeus.informatik.rwth-aachen.de
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(let ((user-init ".stkrc"))
  ;; First look in the current directory for an user initialization file.
  (or (try-load (string-append "./" user-init))
      ;; Otherwise have a look in the HOME directory.
      (let ((home-dir (getenv "HOME")))
	(and home-dir
	     (try-load (string-append home-dir "/" user-init))))))
