;; smartloader.kl
;; the autoloading code using directories

(if (not (boundp 'smartloader))
  (put *error-correctors* -1 'smartloader)
)

(defun smartloader (errorcode &rest data &aux
    (variable (getn data 0))		;the undefined symbol triggering error
    file-to-load
    (result :nil)
  )
  (if (or (= errorcode 'Errors:UndefinedVariable)
      (= errorcode 'Errors:UndefinedFunction)
    )
    (if (and (typep variable Atom)
	(setq file-to-load (smartloader:find-file variable))
      )
      (progn
	(if (seek "/~" (get file-to-load 0 #\.)) ;expand path if needed
	  (*:load file-to-load) 
	  (*:load (+ *current-directory* "/" file-to-load))
	)
	(if (boundp variable) (setq result (eval variable)))
      )
    )
  )
  result
)

;;AUTODOC: AUTOLOAD.DIR "format of the smartloader directory cache"
"\
The smart autoloader \"smartloader\" looks for files \"AUTOLOAD.DIR\" in the
directories in *load-pathname*, and looks for the symbol to find the file
to be loaded. The format of this file is:

#!<any-path>/klone-update NL		;auto-recreating script
AUTOLOAD				;magic number
<version-number> NL			;version number is 3
<file-size-in-bytes> NL			;used as checksum
<hash-function> NL			;present (a line), but ignored in v3
<number-of-slots> NL			;modulo for hash
<number-of-digits-of-slot-indexes> NL	;
<newline-separated-list-of-slot-indexes> NL ; the list of byte offsets in file
					;begining just after header
<slots>

slots is a nil-teminated list of slots-descriptions, one per line.
slot-descriptions:
(symbol \"file-name-relative-to-this-dir\" required-symbol ...)
where required-symbol can be a symbol that must be defined or an expression
that must evaluate to t, if present.
"

(defun smartloader:find-file (name &aux
    (hash (*:hash (String name)))
  )
  (catch 'Found
    (dolist (path *load-pathname*)
      (smartloader:parse-dir path name hash))
))

(defun smartloader:parse-dir (path name hash &aux
    (dir-name (+ (if (= "" path) "." path) "/AUTOLOAD.DIR"))
    dir
    dir-file
    line
    version-number
    file-size
    number-of-slots
    number-of-digits
    file-offset
  )
  (catch 'EOF
    (if (setq dir-file (with (*load-pathname* '(".") *load-extensions* '(""))
	    (find-file dir-name)))
      (progn
	(setq dir (open dir-file))
	(setq line (read-line dir))	;#!/.../klone-update line
	(setq line (read-line dir))	;AUTOLOAD
	(if (/= "AUTOLOAD" line)
	  (error 'smartloader:Error:InvalidDir dir-name)
	)
	(setq version-number (read dir))
	(setq file-size (read dir))
	(if 
	  (= version-number 3)		;v3: ignore hash definition
	  (read-line dir)		
	  (= version-number 2)		;v2: no such line
	  ()
	  (= version-number 1)		;v1: use file hash-function
	  (setq hash (apply (read dir) (list name)))
	)
	(setq number-of-slots (read dir))
	(if (= 0 number-of-slots)
	  (throw 'EOF ())
	)
	(setq number-of-digits (read dir))
	(file-position dir 
	  (* (mod hash number-of-slots) (+ number-of-digits 1))
	  1
	)
	(setq file-offset (read dir))
	(file-position dir file-offset 0)
					;look at all slot entries
	(while (setq line (read dir))
	  (if (and
	      (eq name (getn line 0))
	      (or (not (getn line 2))
		(if (typep (getn line 2) Symbol)
		  (boundp (getn line 2))
		  (eval (getn line 2))
	      ))
	    )
	    (throw 'Found (+ path "/" (getn line 1)))
	))
    ))
  )
  ()
)

(set 'smartloader:Error:InvalidDir "Bad smartloader directory: %0")
