Minotaur for Perl, Python, andTcl
Minotaur is an extension which can be used from Tcl, Python, or Perl to
run scripts in any of the other languages.
Description
Minotaur works by embedding one language in the other (at run time), i.e.
the context ends up as a single process. This is possible because each
of the languages supports not only dynamic extensions but also being embedded
in a C-based main program. In this case, the main program happens to be
another scripting language. All combinations of main vs. embedding language
choice are possible, but due to some differences in how each of the languages
deals with extensions and embedding, you may end up choosing Perl to embed
Tcl, for example, even though your main language is Tcl, and you want to
be able to run some Perl code.
The following code illustrates the current capabilities of Minotaur,
here in Python:
import minotaur
minotaur.initPerl()
print minotaur.Perl("1111 + 2222")
minotaur.Perl("""
Python("result = '$]'")
""")
print result
minotaur.initTcl()
print minotaur.Tcl("expr {2222 + 3333}")
minotaur.Tcl("""
Python "result = '[info patchlevel]'"
""")
print result
Installation
Right now (July 1999), installation is a tricky process - not because Minotaur
is complex, but because each of the scripting languages has different (and
sometimes conflicting) requirements for extension, embedding, path names,
and shared libraries. First of all, you must have two or three of the Python,
Perl, and Tcl releases installed. If they were compiled with different
compilers or compiler versions, you may run into trouble. The following
assumes you want to combine all three languages, otherwise simply leave
out the one you don't care about.
Files and paths
The hardest part of getting Minotaur up and running is probably in figuring
out where to install what. The good news is that there are no compiled-in
path dependencies (other than in Minoperl, see below). Once the C core
of Minotaur has been properly built, it is unlikely that you need to go
back and recompile, even when adding new languages. The bad news is that
there are nevertheless several scripts which critically depend on proper
setup of path names. There simply is no way to get shared library linkage
to work without some help, especially since Perl, Tcl, and Python all have
their own preferences about locations and names of extensions libraries
and scripts.
Environment variables
Environment variables are not used by Minotaur. Or rather, things like
PATH, LD_LIBRARY_PATH, TCL_LIBRARY, and PYTHONPATH will continue to work,
but Minotaur is built with the assumption that these settings should not
affect it. The reasoning is that files on disk are a more stable context
than RAM: once you work out the proper settings and save them in files/scripts,
they are less likely to be affected than environment variables, which depend
on how you logged in, what other tools you have set up, etc.
Unix
Only Linux has been tested so far. Linux supports "back-linking": unresolved
symbols in shared libraries can be resolved against symbols in the main
program, if that main program is linked with the "-rdynamic" option.
The installation of Minotaur consists of the following files:
minocore.so This is the core of Minotaur, written in C
minoperl.pm Needed when interfacing with Perl
minoperl.so Needed when interfacing with Perl
minotaur.pm For "use minotaur" in your Perl scripts
minotaur.py For "import minotaur" in your Python scripts
minotaur.tcl For "package require minotaur" in your Tcl scripts
Windows
Minotaur has also been verified to work on Windows
(apart from one to-do item: no return value from Perl).
Since Windows does not support back-linking, the main program of Perl,
Tcl, and Python is usually very small and linked against a DLL which contains
the language implementation. Extensions loaded dynamically are then able
to link to that same DLL. Although Tcl has recently adopted "stubs" as
a way to use dynamic extensions with stand-alone executables, this mechanism
is not yet used by Minotaur.
Macintosh
Minotaur is currently being ported to Macintosh.
The Mac supports back-linking, AFAIK.
Perl
The Perl binding requires a secondary shared library called "Minoperl",
which is more or less a standard XS-style extension linked dynamically
to Perl. It is needed because Perl's DynaLoader package has to be linked
in, which is only possible with load-time linkage to several Perl C interface
routines. Minoperl has that linkage, it gets loaded only when Minotaur
is asked to interface to Perl. When Perl is the main program, back-linking
can probably be used on Linux and Mac to let Minotaur locate C run-time
support functions. But to use Perl from Python or Tcl, it must be reconfigured
and rebuilt as shared library. On my Linux system, the path "/usr/lib/perl5/5.00503/i586-linux/CORE/libperl.so"
is where that library ended up being installed.
Python
On most Unix systems, Python needs to be rebuilt, if you want to be able
to use Python scripts in other languages, because it is built as standalone
application on those systems which support back-linking. To do this on
Linux, I did something like:
make distclean
./configure
make OPT="-fpic -O2"
Then all files were extracted from libpython1.5.a and linked using
"gcc -shared -o libpython1.5.so *.o".
The result was stored in "/usr/local/lib/libpython1.5.so".
The current Python linkage has a limitation because it uses "PyRun_SimpleString":
there is no integer return value when running Python scripts, e.g in Tcl
the command "puts [Python {123 + 456}]" returns zero, not 579.
The solution is to rewrite the interface with lower-leve calls to Python.
Note that Python 1.5.2 loads Unix shared libraries without the
RTLD_GLOBAL flag, which causes unexpected interactions (sometimes loading two
copies of the pForth globals). The solution is to add "| RTLD_GLOBAL" to all
calls of dlopen(), in "python/importdl.c".
Tcl
Tcl turns out to be easiest language for Minotaur, both when used as main
and when used from other languages, because it normally gets built with
a shared library. On Linux, this file was called "/usr/local/lib/libtcl8.0.so".
Implementation note: the "minotaur::init" command (see below)
returns a pointer to the current interpreter.
Other languages
Although the current release limits itself to Tcl, Perl, and Python, many
more languages could be tied into this using the same approach. As proof-of-concept,
basic bindings to Java, Lua, ICI, and PHP have been created, as well as
language-independent bindings using COM (the Component Object Model) and
a generic DLL (callable from C, Visual Basic, Delphi, etc). To avoid running
into a NxN combinatorial explosion of little issues to solve, incorporation
of these bindings has been postponed until more tricks and tools are found
to simplify the task a bit.
Implementation details
Minotaur is based on yet another language: Forth. In fact, Minotaur is
little more than a slightly modified version of the public-domain pForth
system, and a few low-level primitives to deal with shared libraries on
Unix, Windows, and Macintosh. Everything else is "scripted", as you can
see when you look inside the minotaur.pm, minotaur.py,
and minotaur.tcl text files. In each of the languages, Minotaur
is an extension which implements the following three commands (using Python
as example):
minotaur.init() Initialize the Forth runtime system
minotaur.doit(...) Set up values on the stack and execute commands
minotaur.term() Shut down Forth (not used)
Initialization will be ignored if called a second time. Caveat: the current
release is totally ignorant of threading (read: will probably crash) and
relies on some global variables.
The main interface is the doit command, which can be used in two ways:
result = minotaur.doit("... Forth script ...")
result = minotaur.doit("template", ...)
In both cases, the result is the topmost integer popped off the Forth stack,
or zero if the stack is empty ("None" in Python). An example of the first
call sequence is 'print minotaur.doit("123 456 +")'. The template
in the second format is used to describe each of the following arguments.
The number of characters in this string must match the number of remaining
arguments. Each character indicates an action:
i push the next arg as an integer on the Forth data stack
c push the next arg as a (char*) string pointer on the data stack
s push two values: a (char*) string pointer, then the string length
p push the next argument as object pointer on the data stack
r push as object ptr, but also increment its refcount (Python/Tcl)
e evaluate the next string as Forth
x execute the Forth word indicated by the next integer arg
Note that the call 'doit("BASE .")' is equivalent to 'doit("e",
"BASE .")'. Also, the call 'doit("ie", 123, "1+")' will return
124, but it does a slow lookup on "1+" on each call, so if you need to
use this inside a loop, the following will offer much higher performance:
onePlus = minotaur.doit("' 1+") # do this once
v = minotaur.doit("ix", 123, onePlus) # this is really fast
So far, you are simply looking at Forth, embedded in a scripting language.
What Minotaur adds, are C-based Forth "words" to operate with shared libraries.
The following description uses the standard Forth stack notation:
ShLibOpen ( $name $extra -- handle )
ShLibLookup ( handle $name -- fptr )
ShLibCall ( i*x fptr nargs -- result )
ShLibClose ( handle -- )
Looking ahead
The Forth system inside Minotaur is used to call back into the scripting
language implementations, it could also be used to call any shared library
routine available on your system. It's a matter of setting up the proper
arguments on the Forth data stack, and then calling the entry point. Though
none of this has yet been optimized, early tests indicate that the performance
overhead of doing a system call to 'time(0)' through Minotaur
is neglegible, making Forth a candidate to be used as "super-glue" between
scripting languages and system calls, standard libraries, and application-/domain-specific
shared libraries. The difference with C is that such shared library bindings
can be written/edited quickly and used on several platforms without requiring
compilation.
Eh, SWIG, anyone?
An interesting option would be to extend David Beazley's Simplified
Wrapper Interface Generator to generate output for use with Minotaur.
The huge advantage of this is that one could interface to all sorts of
code present in shared libraries without compilation. To get
there, SWIG needs to be able to generate the Forth "super-glue" as well
as thin wrappers in the different scripting languages. All of this
looks quite feasible, and would greatly extend the level of ad-hoc interfacing
which can be achieved with Perl, Python, and Tcl. On Unix, one could
pull all "/usr/include/*.h" files through SWIG, for example...
License and copyright
Minotaur stands on the shoulders of open source giants, i.e. Perl, Python,
Tcl, and the public domain pForth system. It would be meaningless without
them. Minotaur itself is also distributed under a BSD-style license, similar
to Tcl and Python:
--------------------------------------------------------------------
Copyright 1999 by Jean-Claude Wippler.
All Rights Reserved
Permission to use, copy, modify, distribute, and license this
software and its documentation for any purpose is hereby granted,
provided that existing copyright notices are retained in all copies
and that this notice is included verbatim in any distributions.
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
--------------------------------------------------------------------
$Id: minotaur.html,v 1.6 1999/07/01 10:37:11 jcw Exp $