ANTLR4使用DOTGenerator解析树到DOT

时间:2018-03-21 18:20:07

标签: java antlr4 graphviz dot

如何在ANTLR4中使用DOTGenerator将解析树转换为DOT / graphviz格式?

我找到this related question,但唯一的答案是使用TreeViewer在JPanel中显示树,而这不是我之后的。 This other question是我所需要的,但它没有得到回答。我偶然发现的其他任何东西都与ANTLR3的DOTTreeGenerator有关,但没有用。

我使用Java和IntelliJ的ANTLR4插件。

2 个答案:

答案 0 :(得分:1)

我有一个小项目,有各种实用方法w.r.t. ANTLR4语法调试/测试。我没有时间提供一些适当的文档,以便我可以把它放在Github上。但是这里有一部分负责从语法创建DOT文件。

将它全部放在一个名为Main.java的文件中(当然为Expression.g4生成词法分析器和解析器),你会看到一个DOT字符串被打印到你的控制台:

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.*;

public class Main {

  public static void main(String[] args) {

    /*
        // Expression.g4

        grammar Expression;

        expression
         : '-' expression
         | expression ('*' | '/') expression
         | expression ('+' | '-') expression
         | '(' expression ')'
         | NUMBER
         | VARIABLE
         ;

        NUMBER
         : [0-9]+ ( '.' [0-9]+ )?
         ;

        VARIABLE
         : [a-zA-Z] [a-zA-Z0-9]+
         ;

        SPACE
         : [ \t\r\n] -> skip
         ;
    */

    String source = "3 + 42 * (PI - 3.14159)";

    ExpressionLexer lexer = new ExpressionLexer(CharStreams.fromString(source));
    ExpressionParser parser = new ExpressionParser(new CommonTokenStream(lexer));

    SimpleTree tree = new SimpleTree.Builder()
            .withParser(parser)
            .withParseTree(parser.expression())
            .withDisplaySymbolicName(false)
            .build();

    DotOptions options = new DotOptions.Builder()
            .withParameters("  labelloc=\"t\";\n  label=\"Expression Tree\";\n\n")
            .withLexerRuleShape("circle")
            .build();

    System.out.println(new DotTreeRepresentation().display(tree, options));
  }
}


class DotTreeRepresentation {

  public String display(SimpleTree tree) {
    return display(tree, DotOptions.DEFAULT);
  }

  public String display(SimpleTree tree, DotOptions options) {
    return display(new InOrderTraversal().traverse(tree), options);
  }

  public String display(List<SimpleTree.Node> nodes, DotOptions options) {

    StringBuilder builder = new StringBuilder("graph tree {\n\n");
    Map<SimpleTree.Node, String> nodeNameMap = new HashMap<>();
    int nodeCount = 0;

    if (options.parameters != null) {
      builder.append(options.parameters);
    }

    for (SimpleTree.Node node : nodes) {

      nodeCount++;
      String nodeName = String.format("node_%s", nodeCount);
      nodeNameMap.put(node, nodeName);

      builder.append(String.format("  %s [label=\"%s\", shape=%s];\n",
              nodeName,
              node.getLabel().replace("\"", "\\\""),
              node.isTokenNode() ? options.lexerRuleShape : options.parserRuleShape));
    }

    builder.append("\n");

    for (SimpleTree.Node node : nodes) {

      String name = nodeNameMap.get(node);

      for (SimpleTree.Node child : node.getChildren()) {
        String childName = nodeNameMap.get(child);
        builder.append("  ").append(name).append(" -- ").append(childName).append("\n");
      }
    }

    return builder.append("}\n").toString();
  }
}

class InOrderTraversal {

  public List<SimpleTree.Node> traverse(SimpleTree tree) {

    if (tree == null)
      throw new IllegalArgumentException("tree == null");

    List<SimpleTree.Node> nodes = new ArrayList<>();

    traverse(tree.root, nodes);

    return nodes;
  }

  private void traverse(SimpleTree.Node node, List<SimpleTree.Node> nodes) {

    if (node.hasChildren()) {
      traverse(node.getChildren().get(0), nodes);
    }

    nodes.add(node);

    for (int i = 1; i < node.getChildCount(); i++) {
      traverse(node.getChild(i), nodes);
    }
  }
}

class DotOptions {

  public static final DotOptions DEFAULT = new DotOptions.Builder().build();

  public static final String DEFAULT_PARAMETERS = null;
  public static final String DEFAULT_LEXER_RULE_SHAPE = "box";
  public static final String DEFAULT_PARSER_RULE_SHAPE = "ellipse";

  public static class Builder {

    private String parameters = DEFAULT_PARAMETERS;
    private String lexerRuleShape = DEFAULT_LEXER_RULE_SHAPE;
    private String parserRuleShape = DEFAULT_PARSER_RULE_SHAPE;

    public DotOptions.Builder withParameters(String parameters) {
      this.parameters = parameters;
      return this;
    }

    public DotOptions.Builder withLexerRuleShape(String lexerRuleShape) {
      this.lexerRuleShape = lexerRuleShape;
      return this;
    }

    public DotOptions.Builder withParserRuleShape(String parserRuleShape) {
      this.parserRuleShape = parserRuleShape;
      return this;
    }

    public DotOptions build() {

      if (lexerRuleShape == null)
        throw new IllegalStateException("lexerRuleShape == null");

      if (parserRuleShape == null)
        throw new IllegalStateException("parserRuleShape == null");

      return new DotOptions(parameters, lexerRuleShape, parserRuleShape);
    }
  }

  public final String parameters;
  public final String lexerRuleShape;
  public final String parserRuleShape;

  private DotOptions(String parameters, String lexerRuleShape, String parserRuleShape) {
    this.parameters = parameters;
    this.lexerRuleShape = lexerRuleShape;
    this.parserRuleShape = parserRuleShape;
  }
}

class SimpleTree {

  public static class Builder {

    private Parser parser = null;
    private ParseTree parseTree = null;
    private Set<Integer> ignoredTokenTypes = new HashSet<>();
    private boolean displaySymbolicName = true;

    public SimpleTree build() {

      if (parser == null) {
        throw new  IllegalStateException("parser == null");
      }

      if (parseTree == null) {
        throw new  IllegalStateException("parseTree == null");
      }

      return new SimpleTree(parser, parseTree, ignoredTokenTypes, displaySymbolicName);
    }

    public SimpleTree.Builder withParser(Parser parser) {
      this.parser = parser;
      return this;
    }

    public SimpleTree.Builder withParseTree(ParseTree parseTree) {
      this.parseTree = parseTree;
      return this;
    }

    public SimpleTree.Builder withIgnoredTokenTypes(Integer... ignoredTokenTypes) {
      this.ignoredTokenTypes = new HashSet<>(Arrays.asList(ignoredTokenTypes));
      return this;
    }

    public SimpleTree.Builder withDisplaySymbolicName(boolean displaySymbolicName) {
      this.displaySymbolicName = displaySymbolicName;
      return this;
    }
  }

  public final SimpleTree.Node root;

  private SimpleTree(Parser parser, ParseTree parseTree, Set<Integer> ignoredTokenTypes, boolean displaySymbolicName) {
    this.root = new SimpleTree.Node(parser, parseTree, ignoredTokenTypes, displaySymbolicName);
  }

  public SimpleTree(SimpleTree.Node root) {

    if (root == null)
      throw new IllegalArgumentException("root == null");

    this.root = root;
  }

  public SimpleTree copy() {
    return new SimpleTree(root.copy());
  }

  public String toLispTree() {

    StringBuilder builder = new StringBuilder();

    toLispTree(this.root, builder);

    return builder.toString().trim();
  }

  private void toLispTree(SimpleTree.Node node, StringBuilder builder) {

    if (node.isLeaf()) {
      builder.append(node.getLabel()).append(" ");
    }
    else {
      builder.append("(").append(node.label).append(" ");

      for (SimpleTree.Node child : node.children) {
        toLispTree(child, builder);
      }

      builder.append(") ");
    }
  }

  @Override
  public String toString() {
    return String.format("%s", this.root);
  }

  public static class Node {

    protected String label;
    protected int level;
    protected boolean isTokenNode;
    protected List<SimpleTree.Node> children;

    Node(Parser parser, ParseTree parseTree, Set<Integer> ignoredTokenTypes, boolean displaySymbolicName) {
      this(parser.getRuleNames()[((RuleContext)parseTree).getRuleIndex()], 0, false);
      traverse(parseTree, this, parser, ignoredTokenTypes, displaySymbolicName);
    }

    public Node(String label, int level, boolean isTokenNode) {
      this.label = label;
      this.level = level;
      this.isTokenNode = isTokenNode;
      this.children = new ArrayList<>();
    }

    public void replaceWith(SimpleTree.Node node) {
      this.label = node.label;
      this.level = node.level;
      this.isTokenNode = node.isTokenNode;
      this.children.remove(node);
      this.children.addAll(node.children);
    }

    public SimpleTree.Node copy() {

      SimpleTree.Node copy = new SimpleTree.Node(this.label, this.level, this.isTokenNode);

      for (SimpleTree.Node child : this.children) {
        copy.children.add(child.copy());
      }

      return copy;
    }

    public void normalizeLevels(int level) {

      this.level = level;

      for (SimpleTree.Node child : children) {
        child.normalizeLevels(level + 1);
      }
    }

    public boolean hasChildren() {
      return !children.isEmpty();
    }

    public boolean isLeaf() {
      return !hasChildren();
    }

    public int getChildCount() {
      return children.size();
    }

    public SimpleTree.Node getChild(int index) {
      return children.get(index);
    }

    public int getLevel() {
      return level;
    }

    public String getLabel() {
      return label;
    }

    public boolean isTokenNode() {
      return isTokenNode;
    }

    public List<SimpleTree.Node> getChildren() {
      return new ArrayList<>(children);
    }

    private void traverse(ParseTree parseTree, SimpleTree.Node parent, Parser parser, Set<Integer> ignoredTokenTypes, boolean displaySymbolicName) {

      List<SimpleTree.ParseTreeParent> todo = new ArrayList<>();

      for (int i = 0; i < parseTree.getChildCount(); i++) {

        ParseTree child = parseTree.getChild(i);

        if (child.getPayload() instanceof CommonToken) {

          CommonToken token = (CommonToken) child.getPayload();

          if (!ignoredTokenTypes.contains(token.getType())) {

            String tempText = displaySymbolicName ?
                    String.format("%s: '%s'",
                            parser.getVocabulary().getSymbolicName(token.getType()),
                            token.getText()
                                    .replace("\r", "\\r")
                                    .replace("\n", "\\n")
                                    .replace("\t", "\\t")
                                    .replace("'", "\\'")) :
                    String.format("%s",
                            token.getText()
                                    .replace("\r", "\\r")
                                    .replace("\n", "\\n")
                                    .replace("\t", "\\t"));

            if (parent.label == null) {
              parent.label = tempText;
            }
            else {
              parent.children.add(new SimpleTree.Node(tempText, parent.level + 1, true));
            }
          }
        }
        else {
          SimpleTree.Node node = new SimpleTree.Node(parser.getRuleNames()[((RuleContext)child).getRuleIndex()], parent.level + 1, false);
          parent.children.add(node);
          todo.add(new SimpleTree.ParseTreeParent(child, node));
        }
      }

      for (SimpleTree.ParseTreeParent wrapper : todo) {
        traverse(wrapper.parseTree, wrapper.parent, parser, ignoredTokenTypes, displaySymbolicName);
      }
    }

    @Override
    public String toString() {
      return String.format("{label=%s, level=%s, isTokenNode=%s, children=%s}", label, level, isTokenNode, children);
    }
  }

  private static class ParseTreeParent {

    final ParseTree parseTree;
    final SimpleTree.Node parent;

    private ParseTreeParent(ParseTree parseTree, SimpleTree.Node parent) {
      this.parseTree = parseTree;
      this.parent = parent;
    }
  }
}

如果您将输出粘贴到DOT viewer,您将得到:

enter image description here

答案 1 :(得分:1)

您可能还想查看替代方案。 DOT图不是可能的图表表示之间的美妙。也许你喜欢svg图?如果是这样的话,看看ANTLR4 grammar extension for Visual Studio Code,生成并导出svg图形,只需单击鼠标按钮(你可以用自己的CSS代码设置样式)。

enter image description here