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.)
name
,
number
, and text
, which accept
as their second argument the fundamental element and produce
its transformation. The name
and number
functions receive a string argument; the text
function 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).
postprocess
which 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 postprocess
will then be
used as output. (Thus, what your other routines return
doesn't have to be same type as the "real" rendering
information; postprocess
can do an extra
translation step afterwards if you need one.)
handler
which 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).
'foo
that only makes sense
with three sub-elements, for example, you would write
a routine called _3foo
. If 'foo
accepted 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 _foo
.
xf
which is in the minse
module.
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).
xf
on
the root node of the tree. xf
will 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 xf
from the
minse
module).
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[0]) 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 group
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
understood by glyphd
:
@sup
@sub
@ss
@vcen
@pile
@bar
@stk
@slam
@sp
@tbl
@paren
@bkt
@brace
@bars
@rad
@enc
@big
@sml
@bold
@ital
@sans
@srif
@uprt
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 @(entityname)
(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
by writing @(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 @@
, @)
,
or @,
in a similar fashion as with MINSE syntax.
That's about it for now.