First, the build process for the VM has changed. It now produces two files, a VM engine library and a VM executable.
On Windows and Mac OS X, the library is built as a shared library, on other Unices it is built as a static library. The reason is that on Linux, there is no way to build an executable which looks for a required shared library in the same directory as the executable itself. The only alternative is to install the shared library in a known location, such as
/usr/lib, or to set the
LD_LIBRARY_PATHenvironment variable. This is unacceptable since it complicates matters for people who want to try Factor. It should be possible to just run
makethen run Factor from the current directory. So, no shared library on Linux.
The VM executable is very small; in fact, it consists of a single source file:
int main(int argc, char **argv)
factor.hfile defines the exported entry points into the Factor VM library. So far, there are only a small handful of those:
void init_factor_from_args(char *image, int argc, char **argv, bool embedded);
char *factor_eval_string(char *string);
void factor_eval_free(char *result);
Here is a description of each:
init_factor_from_args()initializes Factor. C applications embedding Factor should always set the
embeddedflag to true; this causes
init_factor_from_args()to return as soon as Factor has been initialized.
factor_eval_string()evaluates a Factor expression and captures any output it performs to a new string. This string is then returned. The expression must not take any inputs from the stack, or leave values on the stack.
factor_eval_free()frees a string returned by
factor_yield()yields a time-slice to any Factor threads. This should be called if you evaluate an expression which spawns a thread with
in-threador a similar Factor word.
Here is an example:
int main(int argc, char **argv)
char *result = factor_eval_string("2 2 + .");
This API has a number of limitations:
- On Unix, Factor takes over a number of signal handlers. Signal handlers suck for this reason -- but Factor really does need to use signals.
- Only one Factor instance can exist per C process, and there's no way to de-initialize a Factor instance and free its resources. This will be addressed at some point in the future.
- The Factor instance can only be accessed from a single native thread for its entire life time -- this is because the Factor runtime is not thread-safe. This will be addressed in Factor 2.0, which will bring first-class support for native threading.
- The default Factor image is quite large (~7mb) and building minimal images involves having a load file. This will be addressed soon; not only for embedding, but also for deployment, it makes sense to be able to build minimal images containing only a certain set of modules.
As you can see, right now this is more of a novelty than a useful feature, but over time I plan on improving this interface and make Factor a viable choice for scripting C applications -- you will be able to build minimal images containing only the code your application needs. Unlike Lua and Python, Factor is natively-compiled, and Factor's FFI for calling back into C is extremely powerful.
In fact, I didn't even plan on working on a C embedding API at this point, however a seemingly unrelated task required it -- Doug is porting Factor to Windows CE, and on Windows CE,
.exefiles cannot dynamically look up their own symbols. Factor's compiler does this because generated code often has to call into various VM routines -- so we went for the simplest fix, moving the entire VM into a DLL and only leaving a small stub function in the executable. I polished this a bit and made it minimally useful in other contexts, resulting in the the above embedding API.