Wednesday, August 12, 2009

Passing Variables Between the Scripts

As you already know, each SHP scripts are compiled into its own procedures, and hence the scopes of procedures apply - there are no shared variables across scripts. Unless you want to pass everything through parameters (which is painful as you will need a toplevel script to parameterize the value of the parameter to ensure different threads not overriding each other).

Since each scripts are procedures, the best way to pass variables around is to pass them as arguments to each procedure call. Something like the following should work:

(include! "/path/to/script" var1 var2 ...)

This means we need a way to specify the arguments of the script! Something like:

;; inner script
(:args x y z)
;; ... the rest of the script

What we need then is to lift out the args clause (there should only be one!) during the compilation phase and compile it into part of the procedure signature. Let's see how this would work!

First we need to determine which particular expression is the argument expression, and then we need to filter out the arg expression from each of the script:

;; determining the args
(define (args-exp? term)
(and (pair? term) (equal? (car term) ':args)))

;; get the args expressions
(define (terms->args terms)
(define (helper args)
(cond ((null? args) '()) ;; if none just return null
((not (null? (cdr args))) ;; cannot have more than one
(error 'filter-args "multiple args statement: ~a" args))
(else (cdr (car args)))))
(helper (filter args-exp? terms)))


Then we should push the args expression into the eval statement:

;; terms->exps
(define (terms->exps terms)
(let ((exps (filter (lambda (exp)
(and (not (require-exp? exp))
(not (args-exp? exp))))
terms)))
(if (null? exps)
'("") ;; ensure there is at least one exp in the lambda.
exps)))

;; abstract the eval process
(define (evaluate-terms terms)
(require-modules! terms) ;; first register the required modules
;; then we filter out the required statement and evaluate the rest of the terms as a proc.
(eval `(lambda ,(terms->args terms)
. ,(terms->exps terms))
handler-namespace))

Note that previous we actually have request as the first argument, but since we already passed request via parameter, we no longer need it to take up the spot in the procedure, so we should fix all of the places that calls evaluate-terms:

(define (eval-script-if-changed! script)
(unless (not (file-exists? (script-path script)))
(let ((timestamp (file-or-directory-modify-seconds (script-path script))))
(when (> timestamp (script-timestamp script))
(set-script-timestamp! script timestamp)
(let ((proc
(evaluate-terms
(file->values (script-path script)))))
(proc))))))

(define-struct shp-handler (path default not-found required)
#:property prop:procedure
(lambda ($struct request)
;; evaluate if
(parameterize (($pathinfo ($pathinfo))
($request request)
($server $struct))
(eval-script-if-changed! (shp-handler-required ($server)))
(let ((proc (evaluate-terms
(file->values
(url->shp-path (request-uri request))))))
(make-response (proc))))))

(define (include! path . args)
(let ((proc (evaluate-terms
(file->values
(segments->path (path->segments path) #f)))))
(apply proc args)))

Notice that in both shp-handler and eval-script-if-changed, the calling of proc takes zero arguments. It means that the required script and other top level scripts should not require any arguments. include! takes a list of parameters, and it works for regular, optional, and rest parameters.

Allright - with the declaration of formal arguments we can now pass variables around! We might enable the parameters to be bound by contracts in the future, but we'll tackle other problems first.

No comments:

Post a Comment