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.