Wednesday, August 12, 2009

If we want to develop complex scripts we will need to be able to modularize the pages so we are not completely repeating ourselves, so we want to be able to include other scripts!

We should have something like the following:

(include! "/path/to/script/file")

The include would then be embeddable within the output xexprs, or just for the side effects (for example, the include file contains the common required modules).

Although relative path seem interesting - we want include! to work as similar to the script dispatcher as possible, so we will only support the full path.

Getting the Root Path

Currently the root path is held in the closure of make-shp-handler so is inaccessible outside its scope. We can pass store the value in a parameter, but as previously noted, if we started to increase the number of configuration values it's probably best to store the whole value into a configuration object. It's time to create that configuration object.

In a way - the configuration object basically contains the settings of the server, so we'll call this the $server object. And since we are in schemeland, there is no reason this object should just hold configuration values - we can have it hold the server (well - the shp handler) itself, so it's full invokable.

With the above definition we now can make a shp-handler by calling make-shp-handler and use it as a procedure (we also need to remove the previous make-shp-handler to avoid name conflict). And since we parameterize $server to $struct itself, we now have access to the shp-handler structure within the scope of the request.

Since we now can access the config values through accessors such as shp-handler-path, shp-handler-default, and shp-handler-notfound within the scope, we no longer have to pass them around through function parameters, so the following functions have their signatures changed.

For scripts that exists primarily for side effects (such as a common require module list), because we need to generate a procedure (even if it's a dummy procedure), we'll insert a dummy return value (i.e. an empty string) to satisfy the procedure creation rules if it does not exists after the filtering.

;; 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 (request) . ,(terms->exps terms)) handler-namespace))

With this we can have a script that only contains side effects without having to worry about it returning any value, because the platform takes care of it for us.

Bug - Evaluation Order

The code as written above has one subtle bug, and that is the evaluation order of the include file. Basically - if you include a common required module script, the first time the script gets evaluated is when the containing script gets compiled, but because the definition resides inside the inner include, it does not get fully evaluated until the compilation failed. So the first time around when you are executing the script you will get:

reference to an identifier before its definition: ...

We'll talk about how to solve this problem in the following post, stay tuned.