The ability to make your own style definitions allows you to extend MINSE in new ways to represent what you want to convey. This is not just a "nice" feature of the system; it is practically a requirement, for fields of scientific study are always evolving and people often need to invent new representations for new concepts.
After parsing, the result is a semantic tree, where each leaf node represents a fundamental element and the non-leaf nodes represent compounds composed of these elements. Each non-leaf node contains the name of the compound and has the compound's sub-elements as its children.
To form the result, we traverse the tree in depth-first order, calling routines which you get to write for each compound type. This process of turning the parse tree into rendering information is called transformation.
This extensibility is accomplished by writing the entire style definition in Python. The semantic tree is stored and passed as a Python tuple (similar to LISP's lists). The style definition interfaces with the parser like this:
init. This will be the starting value of the state variable, passed to the routine corresponding to the root node of the tree.
glyphd, it will have its own syntax that you need to follow.)
text, which accept as their second argument the fundamental element and produce its transformation. The
numberfunctions receive a string argument; the
textfunction receives a tuple whose elements alternate string, tuple, string, tuple, ... (where the strings are pieces of text and the tuples are the trees of embedded expressions).
postprocesswhich accepts as one argument your rendering information type. If it is defined, it will be called with the result of the complete transformation after the routine for the root node returns something. The return value of
postprocesswill then be used as output. (Thus, what your other routines return doesn't have to be same type as the "real" rendering information;
postprocesscan do an extra translation step afterwards if you need one.)
handlerwhich handles exceptions. All exceptions that occur during transformation will then get passed to this routine; it should expect three arguments: the original expression as entered, the exception type, and the exception value. This way, you can render the exceptions to the output media as well, so they can provide useful information (this is how i currently get error messages to appear in the HTML when expressions are invalid).
'foothat only makes sense with three sub-elements, for example, you would write a routine called
'fooaccepted any number of sub-elements, or if you wanted a routine to catch any number of arguments other than three, you would define a routine called
xfwhich is in the
xf's job is to take two arguments, a state variable and a tree, and to either call the appropriate fundamental-element routine for the tree or call the compund-rendering element named as the first element of the tuple using the rest of the elements in the tuple as arguments (much like LISP takes a list and applies its first element as a function on the rest of the list).
xfon the root node of the tree.
xfwill then dispatch to your routine based on what's in the tree. It is then up to your routine to decide what should be done with the arguments, and, if necessary, pass back a subtree to
xf(this happens often, so you probably want to import
None. The names and text may contain symbols; it is up to your routines to split them on the question marks.
Here's a snippet from the current style file showing how some operators are defined. Right now, the "state information" is just a number representing the precedence level of the outer expression.
def group(arg): global gl gl = gl - 1 if gl % 2: return "@bracket("+arg+")" else: return "@paren("+arg+")" def serial(outpl, inpl, args, op): if outpl < inpl: global gl; gl = gl + 1 result = xf(inpl, args) for arg in args[1:]: result = result + op + xf(inpl, arg) if outpl < inpl: return group(result) else: return result def _2prod(pl, A, B): return serial(pl, 4, [A, B], "@sp(0,4)") def _2divby(pl, A, B): return serial(pl, 4, [A, B], " @(dotbardot) ") def _2compose(pl, A, B): return serial(pl, 4, [A, B], " @vcen(@(supcircle)) ") def _2intersect(pl, A, B): return serial(pl, 4, [A, B], " @(uphump) ")
The oft-called utility routine
serial joins its arguments
with a given operator string, grouping the result with parentheses or
brackets if the precedence level of the outer expression is higher then
the precedence level of the inner expression. The
routine just alternates between parentheses and brackets on the way down.
The rendering is performed by
glyphd, a standalone HTTP
server that renders the GIF images on the fly. It accepts as a local URL
an expression (produced by the above style module) which is much like
a MINSE canonical structured expression, except that concatenation is
allowed, and primitives are preceded by the at-symbol ("@") instead of
the single-quote. The following are the rendering primitives currently
In addition, the rendering daemon first replaces all underscores
in its URL with spaces. You can make reference to one of a number
of defined "symbol entities" by writing
(a table of entities will be forthcoming, but for now you can look
at the image URLs produced on the demonstration pages to see what
is going on). You can also enter arbitrary data in hexadecimal
@(01234abcd), where the leading zero is the
indicator that the contents of the parentheses are not a symbol name.
The leading zero is thrown away and the rest of the characters are
paired up as nybbles. To produce an at-symbol, a right parenthesis,
or a comma you can escape it as
@, in a similar fashion as with MINSE syntax.
That's about it for now.