Home > Software engineering >  How to obtain multiple commands in antlr4?
How to obtain multiple commands in antlr4?

Time:12-14

I'm developing a shell implementation in Java and I'm using antlr4 to parse the grammar. I would like to parse the input commands one by one and store them in an arraylist where I will execute the commands later.

For example, an input of "echo hello; echo world" should return an arraylist of two Call objects. If it helps, a Call object represents a simple command.

However, the return value of the visitChildren method is being overwritten by the latest parsed command. How can I parse one command, add it to my arraylist, then continue parsing the next command and so on?

CommandConverter.java

package parse;

import java.util.ArrayList;

import app.ApplicationFactory;
import shell.ShellGrammarBaseVisitor;
import shell.ShellGrammarParser;

public class CommandConverter extends ShellGrammarBaseVisitor<Command> {
    
    ApplicationFactory appFactory = new ApplicationFactory();

    @Override
    public Command visitCommands(ShellGrammarParser.CommandsContext ctx) {
        //ArrayList<Command> commands = new ArrayList<>();
        return visitChildren(ctx);
    }
    @Override
    public Command visitAtomicCommand(ShellGrammarParser.AtomicCommandContext ctx) {
        int childCount = ctx.getChildCount();
        String appName = ctx.getChild(0).getText();
        ArrayList<String> appArgs = new ArrayList<>();
        if(childCount > 1) {
            for (int i = 1; i < childCount; i  ) {
                appArgs.add(ctx.getChild(i).getText());
            }
        }
        return new Call(appFactory.getApplication(appName), appArgs);
    }
}

ShellGrammar.g4 (partially)

grammar ShellGrammar;

/*
 * Parser Rules
 */

commands : atomicCommand (';' atomicCommand )*

atomicCommand : NONSPECIAL (value)*;

value : (NONSPECIAL | DOUBLEQUOTED | SINGLEQUOTED);

/*
 * Lexer Rules
 */
NONSPECIAL : ~['";\r\n\t ] ;
DOUBLEQUOTED : '"' (~'"')* '"';
SINGLEQUOTED : '\'' (~'\'')* '\'';

WHITESPACE : [\r\n\t ]  -> skip ;

CodePudding user response:

If you read it out loud, this definition, doesn't make much sense:

public Command visitCommands(ShellGrammarParser.CommandsContext ctx) {

You're visiting commands but expect a Command in return??

In this simple case, it's probably best to remember that a Visitor doesn't need to handle visiting EVERY Context type (it's rather rare that that works for a full grammar, as finding a common return type applicable for all nodes proves to be difficult). You can just override the methods for Context types you actually intend to visit.

In your case, commands is you "top-level" rule, so the parse tree node you get back from your parse (using the parser.commands(); call) will be a CommandsContext

you could do something akin to: (where pt is the node you got back from the parse.)

  for (AtomicCommandContext child : pt.children) {
   // handle the child command
   // could be adding it to a list
   // could be just processing the command 
  }

The benefit of commands is that you're in charge of calling the visitor for each child. visitChildren is just a convenience methods that iterates through all the children calling the visitor for you.

There are methods in the Visitor class that you can override if you want to make your visitor return a List<Command> from calling visitChildren(), but the code above is probably simplest for what you're asking.

  • Related