In my previous post I covered the lexer. Here I will describe the
parser, the parser context and the listener interface. After the
lexer's extensive job att providing a reasonably well formed token
stream, the parser's job becomes completely straightforward.
== The parser
For inlined elemements, the parser will just mindlessly report these
to the context object:
inline_element:
word|space|special|br|html_entity|link_element|format|nowiki|table_of_contents|html_inline_tag
;
space: token = SPACE_TAB {IE(CX->onSpace(CX, $token->getText($token));)}
;
etc.
The lexer guarantees that a closing token will not appear before
a corresponding opening token, and the parser context takes care of
nesting formats and removing empty format tags.
For block elements, the only special thing the parser need to pay
attention to is the fact that end tokens may be missing. Therefore,
end-of-file is always accepted instead of the closing token, for
instance:
html_div:
token = HTML_DIV_OPEN
{
CX->beginHtmlDiv(CX, $token->custom);
}
block_element_contents
(HTML_DIV_CLOSE|EOF)
{
CX->endHtmlDiv(CX);
}
;
The rule 'block_element_contents' covers all parser productions. The
lexer will restrict which tokens that may appear. For instance
'HTML_DIV_CLOSE' will never appear before a corresponding
'HTML_DIV_OPEN'. Also, list items and table cells will not appear
unless the current block context is correct. I have also introduced
a max nesting level limit in the lexer, so stack space is also not an
issue.
== The parser context
The parser context relays the parser events to a listener, but it will
insert and remove events to produce a well formed output. For instance:
text '' italic <b><strong /> bold-italic
bold </b> text
will result in an event stream to the listener that will look like this:
text <i> italic <b> bold-italic </b></i>
<b> bold </b> text
Two mechanisms are used to implement this:
* The call to the "begin" method is delayed until some actual inlined
content is produced. The call is never taken if an "end" event is
recieved before such content.
* The order of the formats is maintained so that inner formats can be
closed and reopened when a non-matching end token is recieved.
So, most of the parser context's methods look like this:
static void
beginHtmlStrong(MWPARSERCONTEXT *context, pANTLR3_VECTOR attr)
{
MW_DELAYED_CALL( context, beginHtmlStrong, endHtmlStrong,
attr, NULL);
MW_BEGIN_ORDERED_FORMAT(context, beginHtmlStrong, endHtmlStrong,
attr, NULL, false);
MWLISTENER *l = &context->listener;
l->beginHtmlStrong(l, attr);
}
static void
endHtmlStrong(MWPARSERCONTEXT *context)
{
MW_SKIP_IF_EMPTY( context, beginHtmlStrong, endHtmlStrong, NULL);
MW_END_ORDERED_FORMAT(context, beginHtmlStrong, endHtmlStrong, NULL);
MWLISTENER *l = &context->listener;
l->endHtmlStrong(l);
}
Block elements are already guaranteed by the lexer to be well nested,
so the context typically does not need to do anything special about
those. Only the wikitext list elements needs to be resolved by the
context.
== The listener
The listening application needs to implement the MWLISTENER interface.
I haven't added support for all features yet, but at the moment, there
are 91 methods in this interface. They are trivial to implement,
though. The only thing to think about is that it is the listener's
responsibility to escape the contents of nowiki and special
characters, and also to filter the attribute lists.
/Andreas