Skip to content
This repository has been archived by the owner on May 27, 2021. It is now read-only.

Actors and macros

MJ edited this page Apr 17, 2016 · 1 revision

Note: this article covers an advanced topic: LML tag parsing internals. You might find it useful if you want to add an advanced custom actor tag or macro in Java code.

Tags, actors, macros and dragons

Macro marker - : - is not there just to clearly separate macros from actors, making your life a little easier. This sign is also an important hint for the parser: tags starting with : are supposed to be parsed in a completely different manner. But first things first - let's clarify what a "regular" tag is.

Actor tags 101

<this> is a regular tag. Its name does not start with : - that's all the parser needs to determine that this should be an actor tag. Such tags are processed sequentially, one after another. The parser creates actors as it goes along - due to conditionals, loops and whatnot, the final structure of the LML file is not clear and it couldn't be simply read all at once. The parser has to adapt to the changes in template structure and be prepared to insert or remove some part of the template at all times.

Actor tags are simple: once one of them is opened, an actor is created and customized with tag attributes. Any direct children are automatically appended - the tag decides what should be done with children actors. For example, Table tag might add children to its cells, while Slider tag might just throw an exception, complaining that it doesn't know what to do with children.

That's why most actor tags are just actor tags. They cannot significantly modify the template.

But there are some things that need to be created immediately and have not need to turn the template upside down. They aren't exactly your regular actors either - examples might include listeners or tooltips. They should be attached to their parents, but aren't regular actors immediately added to the Stage. Following the Table example: you obviously don't want to add a tooltip directly to the table's cell - you want to display it after the cursor is over the table for long enough.

As always, there are exceptions...

Some more advanced actor tags that can be parsed sequentially, but still need some kind of special treatment, are called attachables. One is able to identify these tags using LmlTag#isAttachable() in Java. They all provide their custom LmlTag#attachTo(LmlTag) implementation which is used instead of regular actor appending method. The key is that they are parsed along with the rest of the template, but not necessarily shown or used immediately. For example, <tooltip> can have any children and be fully customized, but rather than added to Stage right after parsing, it is shown when a specific event occurs (in this case: cursor hovering).

So, for example, if <tooltip> tag was inside <window>, Tooltip instance would be attached to Table using attachTo rather than simply added to its cells. In this particular case, Tooltip would be added as an EventListener to Table instance.

Attachables include tooltips, listeners (click, change, input) and text field validators in gdx-lml-vis.

As mentioned, attachables still follow the same rules as other actor tags. Consider this template:

        <button>
          <tooltip>
            <label>This is a label.</label>
          </tooltip>
        </button>

As soon as <tooltip> ends, parser immediately begins processing <label>. Yes: the Label instance is added to the Tooltip, but other than that, the process of parsing children of attachables is no different than parsing regular actors. Attachables do not modify the way how parser works: they change the way they are added to their parents. And that's it.

Needless to say, there are times when you need to tell the parser what to do.

Here come the macros

When the parser detects : (the macro marker), the parsing process is entirely different. Instead of processing the tags sequentially, parser tries to find the closing tag of the macro - storing all data before it does - and then asks the macro to process the collected text that was between its tags. Given this example:

        <:macro>
          <label>Some unfortunate label</label>
        </:macro>

Macro tag implementation mapped to :macro would be asked to process \n <label>Some unfortunate label</label>\n. Note that Label instance would NOT be created immediately - or even at all, if the macro decides to discard or delay the actor creation. This gives the macro creator the freedom to process raw text and change the structure of parsed template. This is why conditionals, loops, iterations and other interesting features are implemented through macros: they are the most flexible mechanism around.

Funny thing: comment macro logic required "zero" lines - it just ignores all incoming data. And the parser never bothers to process macro content. Unless asked to do so.

Template reader

LML templates are read character by character by a LmlTemplateReader implementation. Not only is it prepared to read any CharSequence implementation (String, StringBuilder), but also allows to appends a new sequence at any time. For example, consider this template:

        <table><:loop times="2"><label test="OK"/></:loop></table>

The parser will begin processing the template, reading <, t, a, b, l, e, ... - no surprise there.

But as soon as it detects a macro (<:loop times="2">), it will burn the characters up to </:loop> and let the :loop tag handle its text.

As you'd expect, loop macro will tell the template reader to parse it's content between tags twice (times="2"). So template reader data would look like this:

        QUEUED:  <table><:loop times="2"><label test="OK"/></:loop>|CURRENT_INDEX|</table>
        CURRENT: |CURRENT_INDEX|<label test="OK"/><label test="OK"/>

Instead of processing <, /, t, a, b, ... from the original template, reader will now return the characters from the new registered snippet: <, l, a, b, e, l, .... The parser will start processing label as if there were there originally: the parser itself does not see the difference between original or appended data, it just parses it. Once it's done with the labels snippet, the reader will start feeding it <, /, t, a, b, ... - which is the rest of the original template at the index where the macro ended.

Once you think about it, this is a pretty powerful mechanism.

Custom macros

gdx-lml-tests and gdx-lml-vis-tests both feature custom macros examples.

The abstract class you're looking for is AbstractMacroLmlTag:

        return new LmlTagProvider() {
            @Override
            public LmlTag create(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) {
                return new AbstractMacroLmlTag(parser, parentTag, rawTagData) {
                    @Override
                    public void handleDataBetweenTags(final CharSequence rawData) {
                        // Changes passed text to upper case:
                        appendTextToParse(rawData.toString().toUpperCase());
                    }
                };
            }
        };

LmlTagProvider is the standard functional interface for providing tag instances - you can register them with LmlSyntax#addMacroTagProvider or LmlParserBuilder#macro methods. (LmlParserBuilder is the object that actually handles your LmlParser creation when you use Lml/VisLml utilities.)

As you can see, appendTextToParse method will automatically inform the LmlTemplateReader that you want to parser to handle some additional text. This gives you pretty much endless options, as you can do anything with the raw text that comes in. Make sure to go through AbstractMacroLmlTag API, as you'll also find methods for attribute handling, so you can customize your macros.

If the mechanism is still unclear, try playing with gdx-lml-tests and gdx-lml-vis-tests projects.