Eugene Shymko
Eugene Shymko

Reputation: 172

How to get a tree structure ANLTR4

I need to parse config file in form like this

"agent 1"(
"ip"("192.67.4.1"),
"port"("12345"),
"neighbours"(
    "agent 2"(
        "ip"("192.67.4.2"),
        "port"("12345")),
    "agent 3"(
        "ip"("192.67.4.1"),
        "port"("12346"))),
"measurements"(
    "voltage"("4.2V"),
    "power"("7KW")))

What i got so far is grammar(mostly obtained just by trial and error method from few tutorials)

grammar Tree;

compileUnit
    :   group EOF
    ;

group
    :   (node '(' group ')' (',')? )*                # root
    |   node                                         # value
    ;
node
    : STRING                                         # label
    ;

STRING   :   '"'[ a-zA-Z0-9\.]+'"';
WS      :   [ \t\r\n] -> channel(HIDDEN);

and the result of it is tree like this in preview Tree

I already have functionality to add nodes to my tree in a form(other requirement):

ADD "label 1" "label 2" ... "label n"

So the perfect outcome of parser would be String array like:

But I couldn't get further and implement my parser to obtain such String array. So my question is how to do it? I get that I need to extend something like Parser of BasicListener but couldn't manage this.

Upvotes: 2

Views: 139

Answers (1)

quepas
quepas

Reputation: 1003

Solution

First, let's change the named alternative root to:

group
    :   node '(' (group (',')?)+ ')'     # root
    |   node                             # value
    ;

This allows us to track the header (node) of every group. When I refer further to a tree what I mean is config tree structure. Then let's make two definitions:

  • path - is a string of values collected from tree's root to tree's leaf (e.g. "Agent 1" "neighbours" "agent 2" "ip" "192.67.4.2"),
  • partial path - is a path that doesn't end with tree's leaf (an unfinished path, e.g. "Agent 1" "neighbours" "agent 2" "ip").

Now we will write a simple tree listener that will collect paths.

public class TreeToArray extends TreeBaseListener {
    Stack<String> partialPath = new Stack<>();
    public List<String> paths = new ArrayList<>();

    @Override
    public void enterRoot(TreeParser.RootContext ctx) {
        if (partialPath.isEmpty()) {
            // We are in top-root, partial path consists of top-root's name
            partialPath.push(ctx.node().getText());
        } else {
            // We are in one of the sub-roots, partialPath.peek() returns a current partial path
            partialPath.push(partialPath.peek() + " " + ctx.node().getText());
        }
    }

    @Override
    public void enterValue(TreeParser.ValueContext ctx) {
        paths.add(partialPath.peek() + " " + ctx.getText());
    }

    @Override
    public void exitRoot(TreeParser.RootContext ctx) {
        partialPath.pop();
    }
}

The stack partialPath keeps information about currently built partial path. When we visist the main, first root (e.g. "Agent 1") we don't have any partial paths, so we put onto the stack the root's header. Otherwise we retrive a partial path and add to it root's header. When we stumble across value we are at the tree's leaf, we finished the path. We pop() the partial path, then we add to it node value. The result string (the full path) goes into the paths list.

Upvotes: 1

Related Questions