views:

51

answers:

0

A while back, I needed a solution to sanely import libraries in VBScript.

VBScript, for reference, has no build-in import capabilities. The traditional method of importing files is to use SSI, which dumps the contents of the includee verbatim into the includer. This is less-than-optimal for a number of reasons: there is no way to avoid multiple inclusion, there's no way to specify a library directory, etc. So I wrote my own function. It's fairly simple, using executeGlobal with a dictionary to keep track of imported modules and wrapping the whole thing in an object for encapsulation:

class ImportFunction
    private libraries_

    private sub CLASS_INITIALIZE
        set libraries_ = Server.createObject("Scripting.Dictionary")
    end sub

    public default property get exec (name)
        if not libraries_.exists(name) then
            ' The following line will find the actual path of the named library '
            dim lib_path: set lib_path = Path.resource_path(name & ".lib", "libraries")

            on error resume next
            ' Filesystem is a class of mine; its operation should be fairly obvious '
            with FileSystem.open(lib_path, "")
                executeGlobal .readAll
                if Err.number <> 0 then 
                    Response.write "Error importing library "
                    Response.write lib_path & "<br>"
                    Response.write Err.source & ": " & Err.description
                end if
            end with
            on error goto 0

            libraries_.add name, null
        end if
    end property
end class
dim import: set import = new ImportFunction

' Example:
import "MyLibrary"

Anyway, this works pretty well, but it's a lot of work if I don't end up using the library. I'd like to make it lazy, so that the filesystem search, loading, and executing are only done if and when the library is actually used. This is simplified by the fact that each library's features are accessed solely through a singleton object in global scope of the same name as the library. For example:

' StringBuilder.lib '

class StringBuilderClass ... end class

class StringBuilderModule
    public function [new]
        set [new] = new StringBuilderClass
    end function

    ...
end class
dim StringBuilder: set StringBuilder = new StringBuilderModule

 

import "StringBuilder"
dim sb: set sb = StringBuilder.new

So it seems that the obvious approach is for the lazy importer to define StringBuilder as an object that, when accessed, will load StringBuilder.lib and replace itself.

Unfortunately, this is made difficult by VBScripts sad lack of metaprogramming constructs. For instance, there is no analogue to Ruby's method_missing, which would have made the implementation trivial.

My first thought was for the main import function to use executeGlobal to create a global function named StringBuilder taking no arguments which would in turn load StringBuilder.lib and then use executeGlobal to "shadow" itself (the function) with the StringBuilder singleton. There are two problems with this: first, using executeGlobal to define a function which then overrides itself using executeGlobal seems like a rather sketchy idea in general, and second, it turns out that in VBScript, you can only override a function with a variable if the function in question is a builtin. Oooookay.

The next thought I had was doing the same thing, except instead of using executeGlobal to replace the function with a variable, use it to replace the function with another function which simply returned the singleton. This would require that the singleton be stored in a separate global variable. The disadvantages to this approach (aside from the inherent unkosherness of the strategy) are that accessing the singleton would add function call overhead and that, due to the interpreter's parsing eccentricities, the singleton could no longer use default properties.

Overall, it's a rather sticky problem, and VBScript's odd quirks are of no help. Any ideas or suggestions would be welcome.