CodeGeneratorForJava.java

/*
 * Copyright 2013 Michael Mackenzie High
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mackenziehigh.snowflake.parsergen;

import com.mackenziehigh.snowflake.Grammar;
import com.mackenziehigh.snowflake.GrammarBuilder;
import com.mackenziehigh.snowflake.IGrammarBuilder;
import com.mackenziehigh.snowflake.IParser;
import com.mackenziehigh.snowflake.ITreeNode;
import com.mackenziehigh.snowflake.ITreeNodeVisitor;
import com.mackenziehigh.snowflake.NewlineStyles;
import com.mackenziehigh.snowflake.ParserOutput;
import com.mackenziehigh.snowflake.Utils;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * An instance of this class generates Java source code for a parser-file and a visitor-file.
 *
 * @author Mackenzie High
 */
final class CodeGeneratorForJava
        implements ICodeGenerator
{
    /**
     * This object builds a dynamic version of the grammar, while source-code is also generated.
     */
    private final GrammarBuilder grammar_builder = new GrammarBuilder();

    /**
     * This is the name of the root grammar-rule.
     */
    private String root_name = null;

    /**
     * This flag is true, iff the parser and visitor will have public-access.
     */
    private boolean hide = false;

    /**
     * This is the name of the package that will contain the generated parser and visitor.
     */
    private String package_name = null;

    /**
     * This is the name of the generated parser.
     */
    private String parser_name = null;

    /**
     * This is the name of the generated visitor.
     */
    private String visitor_name = null;

    /**
     * This is the number of trace-records the generated parser will store concurrently.
     */
    private int trace_count = 1024;

    /**
     * These are the files to export the parser to.
     */
    private final List<File> parser_exports = new LinkedList<File>();

    /**
     * These are the files to export the visitor to.
     */
    private final List<File> visitor_exports = new LinkedList<File>();

    /**
     * This set stores the source-code of the statements that initialize character-classes.
     */
    private final Set<String> classes = new LinkedHashSet<String>();

    /**
     * This set stores the source-code of the statements that initialize grammar-rules.
     */
    private final Set<String> grammar = new TreeSet<String>();

    /**
     * This set stores the conditions needed to dispatch to visitation methods.
     */
    private final List<String> dispatch_table = new LinkedList<String>();

    /**
     * This set stores the source-code of the visitation methods in the generated visitor.
     */
    private final Set<String> visitor_methods = new TreeSet<String>();

    private void addCharacterClass(final String code)
    {
        classes.add("    " + "    " + code);
    }

    private void addGrammarRule(final String code)
    {
        grammar.add("    " + "    " + code);
    }

    private void addVisitor(final String name)
    {
        this.addVisitorDispatchTableEntry(name);
        this.addVisitorMethod(name);
    }

    private void addVisitorMethod(final String name)
    {
        if (name.contains("@"))
        {
            return;
        }

        final StringBuilder visitor = new StringBuilder();

        visitor.append("    /**\n");
        visitor.append("     * This method visits a parse-tree node created by rule \"").append(name).append("\".\n");
        visitor.append("     */\n");
        visitor.append("    protected void visit_").append(name).append("(ITreeNode node)\n");
        visitor.append("    {\n");
        visitor.append("        // You should *not* place your code right here. \n");
        visitor.append("        // Instead, you should override this method via a subclass.\n");
        visitor.append("        visitUnknown(node); // Default Behavior\n");
        visitor.append("    }\n\n");

        visitor_methods.add(visitor.toString());
    }

    private void addVisitorDispatchTableEntry(final String name)
    {
        if (name.contains("@"))
        {
            return;
        }

        final String entry = "if(\"" + name + "\".equals(name)) { visit_" + name + "(node); }";

        if (dispatch_table.isEmpty())
        {
            dispatch_table.add(entry);
        }
        else
        {
            dispatch_table.add("else " + entry);
        }
    }

    private String getVisitorDispatchTable()
    {
        final StringBuilder result = new StringBuilder();

        result.append("\n");

        for (String entry : dispatch_table)
        {
            result.append("        ").append(entry).append('\n');
        }

        result.append("        ").append("else { visitUnknown(node); }\n");

        return result.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Grammar build()
    {
        return grammar_builder.build();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getParserFile()
    {
        grammar_builder.build();

        final StringBuilder parser = new StringBuilder();

        final String access = hide ? "" : "public ";

        final String name_of_parser = parser_name == null ? "PARSER_NAME" : parser_name;

        final String name_of_root = root_name == null ? "root" : root_name;

        // Package Declaration
        if (package_name != null)
        {
            parser.append("package ").append(package_name).append(";\n");
            parser.append('\n');
        }

        // Imports
        parser.append("import ").append(Grammar.class.getName()).append(";\n");
        parser.append("import ").append(GrammarBuilder.class.getName()).append(";\n");
        parser.append("import ").append(IParser.class.getName()).append(";\n");
        parser.append("import ").append(ParserOutput.class.getName()).append(";\n");
        parser.append('\n');
        parser.append('\n');

        // Start of Class
        parser.append("/**\n");
        parser.append(" * This class was auto-generated using the Snowflake parser-generator.\n");
        parser.append(" *\n");
        parser.append(" * <p>Generated On: $</p>\n".replace("$", (new Date()).toString()));
        parser.append(" */\n");
        parser.append(access)
                .append("final class ")
                .append(name_of_parser)
                .append(" implements ")
                .append(IParser.class.getSimpleName()).append("\n");
        parser.append("{\n");
        parser.append('\n');

        // Generate a field to store the grammar that is used by the parser.
        parser.append("    private static final Grammar grammar = grammar();\n");

        parser.append('\n');
        parser.append('\n');

        // Generate the method that creates the grammar object used by the parser.
        parser.append("    /**\n");
        parser.append("     * This method constructs the grammar object.\n");
        parser.append("     */\n");
        parser.append("    private static Grammar grammar()\n");
        parser.append("    {\n");
        parser.append("        final GrammarBuilder g = new GrammarBuilder();\n");
        parser.append('\n');
        parser.append("        // Character Classes\n");
        for (String c : classes)
        {
            parser.append(c).append("\n");
        }
        parser.append('\n');
        parser.append("        // Grammar Rules\n");
        for (String r : grammar)
        {
            parser.append(r).append("\n");
        }
        parser.append('\n');
        parser.append("        // Specify which rule is the root of the grammar.\n");
        parser.append("        g.setRoot(\"").append(name_of_root).append("\");\n");
        parser.append('\n');
        parser.append("        // Specify the number of tracing records to store concurrently.\n");
        parser.append("        g.setTraceCount(").append(trace_count).append(");\n");
        parser.append('\n');
        parser.append("        // Perform the actual construction of the grammar object.\n");
        parser.append("        return g.build();\n");
        parser.append("    }\n");
        parser.append('\n');
        parser.append('\n');

        // Implement the parse(char[]) method.
        parser.append("    /**\n");
        parser.append("     * {@inheritDoc}\n");
        parser.append("     */\n");
        parser.append("    @Override\n");
        parser.append("    public ParserOutput parse(final char[] input)\n");
        parser.append("    {\n");
        parser.append("        return grammar.newParser().parse(input);\n");
        parser.append("    }\n");
        parser.append('\n');
        parser.append('\n');

        // Implement the parse(String) method.
        parser.append("    /**\n");
        parser.append("     * {@inheritDoc}\n");
        parser.append("     */\n");
        parser.append("    @Override\n");
        parser.append("    public ParserOutput parse(final String input)\n");
        parser.append("    {\n");
        parser.append("        return parse(input.toCharArray());\n");
        parser.append("    }\n");

        // End of Class
        parser.append("}\n");

        final String result = parser.toString().replace("\n", NewlineStyles.fromSystem().newline());

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getVisitorFile()
    {
        final StringBuilder visitor = new StringBuilder();

        final String access = hide ? "" : "public ";

        final String name_of_visitor = visitor_name == null ? "VISITOR_NAME" : visitor_name;

        // Package Declaration
        if (package_name != null)
        {
            visitor.append("package ").append(package_name).append(";\n");
            visitor.append('\n');
        }

        // Imports
        visitor.append("import ").append(ITreeNode.class.getName()).append(";\n");
        visitor.append("import ").append(ITreeNodeVisitor.class.getName()).append(";\n");
        visitor.append('\n');
        visitor.append('\n');

        // Start of Class
        visitor.append("/**\n");
        visitor.append(" * This class was auto-generated using the Snowflake parser-generator.\n");
        visitor.append(" *\n");
        visitor.append(" * <p>Generated On: $</p>\n".replace("$", (new Date()).toString()));
        visitor.append(" */\n");
        visitor.append(access)
                .append("abstract class ")
                .append(name_of_visitor).append(" implements ")
                .append(ITreeNodeVisitor.class.getSimpleName()).append("\n");
        visitor.append("{\n");
        visitor.append("\n");

        // Implement the visit(ITreeNode) method.
        visitor.append("    /**\n");
        visitor.append("     * {@inheritDoc}\n");
        visitor.append("     */\n");
        visitor.append("    @Override\n");
        visitor.append("    public void visit(ITreeNode node)\n");
        visitor.append("    {\n");
        visitor.append("        final String name = node.rule();\n");
        visitor.append(getVisitorDispatchTable());
        visitor.append("    }\n");
        visitor.append("\n");

        // Implement the visitUnknown(ITreeNode) method.
        visitor.append("    /**\n");
        visitor.append("     * {@inheritDoc}\n");
        visitor.append("     */\n");
        visitor.append("    @Override\n");
        visitor.append("    public void visitUnknown(ITreeNode node)\n");
        visitor.append("    {\n");
        visitor.append("        // You should *not* place your code right here.\n");
        visitor.append("        // Instead, you should override this method via a subclass.\n");
        visitor.append("    }\n");
        visitor.append("\n");

        // Add the overridable visitation methods.
        for (String method : visitor_methods)
        {
            visitor.append(method);
        }

        // End of Class
        visitor.append("}\n");

        final String result = visitor.toString().replace("\n", NewlineStyles.fromSystem().newline());

        return result;
    }

    /**
     * {@inheritDoc}
     */
    public void exportFiles()
    {
        final String parser = getParserFile();

        exportFiles(parser_exports, parser);

        final String visitor = getVisitorFile();

        exportFiles(visitor_exports, visitor);
    }

    /**
     * This method writes a specified string of text to a series of files.
     *
     * <p>
     * Specifically, this method is used to export generated parsers and generated visitors.
     * </p>
     *
     * @param files are the files to write the text to.
     * @param content is the text to write.
     */
    private void exportFiles(final List<File> files,
                             final String content)
    {
        for (File file : files)
        {
            final FileOutputStream fos;
            final BufferedOutputStream bos;
            PrintStream pos = null;

            try
            {
                fos = new FileOutputStream(file);
                bos = new BufferedOutputStream(fos);
                pos = new PrintStream(bos);

                pos.print(content);
            }
            catch (IOException ex)
            {
                throw new RuntimeException("I could not export file: " + file, ex);
            }
            finally
            {
                if (pos != null)
                {
                    pos.close();
                }
            }

        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder and(String name,
                               String target)
    {
        grammar_builder.and(name, target);

        addVisitor(name);

        addGrammarRule("g.and(\"" + name + "\", \"" + target + "\");");

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder not(String name,
                               String target)
    {
        grammar_builder.not(name, target);

        addVisitor(name);

        addGrammarRule("g.not(\"" + name + "\", \"" + target + "\");");

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder sequence(String name,
                                    String... elements)
    {
        grammar_builder.sequence(name, elements);

        addVisitor(name);

        final StringBuilder xoo = new StringBuilder();

        xoo.append("g.sequence(\"").append(name).append("\"");

        for (String element : elements)
        {
            xoo.append(", \"").append(element).append("\"");
        }

        xoo.append(");");

        addGrammarRule(xoo.toString());

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder sequenceDLR(String name,
                                       String base,
                                       String... shared)
    {
        grammar_builder.sequenceDLR(name, base, shared);

        addVisitor(name);

        final StringBuilder xoo = new StringBuilder();

        xoo.append("g.sequenceDLR(\"").append(name).append("\"");

        xoo.append(", \"").append(base).append("\"");

        for (String element : shared)
        {
            xoo.append(", \"").append(element).append("\"");
        }

        xoo.append(");");

        addGrammarRule(xoo.toString());

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder choose(String name,
                                  String... options)
    {
        grammar_builder.choose(name, options);

        addVisitor(name);

        final StringBuilder xoo = new StringBuilder();

        xoo.append("g.choose(\"").append(name).append("\"");

        for (String option : options)
        {
            xoo.append(", \"").append(option).append("\"");
        }

        xoo.append(");");

        addGrammarRule(xoo.toString());

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder repeat(String name,
                                  String target,
                                  int minimum,
                                  int maximum)
    {
        grammar_builder.repeat(name, target, minimum, maximum);

        addVisitor(name);

        final StringBuilder xoo = new StringBuilder();

        xoo.append("g.repeat(")
                .append("\"").append(name).append("\"")
                .append(", ")
                .append("\"").append(target).append("\"")
                .append(", ")
                .append(minimum)
                .append(", ")
                .append(maximum)
                .append(");");

        addGrammarRule(xoo.toString());

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder star(String name,
                                String target)
    {
        return repeat(name, target, 0, Integer.MAX_VALUE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder plus(String name,
                                String target)
    {
        return repeat(name, target, 1, Integer.MAX_VALUE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder option(String name,
                                  String target)
    {
        return repeat(name, target, 0, 1);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder str(String name,
                               String string)
    {
        grammar_builder.str(name, string);

        addVisitor(name);

        addGrammarRule("g.str(\"" + name + "\", \"" + string + "\");");

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder chr(String name,
                               String clazz)
    {
        grammar_builder.chr(name, clazz);

        addVisitor(name);

        addGrammarRule("g.chr(\"" + name + "\", \"" + clazz + "\");");

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder range(String name,
                                 char character)
    {
        return range(name, character, character);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder range(String name,
                                 char minimum,
                                 char maximum)
    {
        grammar_builder.range(name, minimum, maximum);

        addCharacterClass("g.range(\"" + name + "\", "
                          + "(char) " + (int) minimum + ", "
                          + "(char) " + (int) maximum + ");");

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder combine(String name,
                                   String... elements)
    {
        grammar_builder.combine(name, elements);

        final StringBuilder builder = new StringBuilder();

        builder.append("g.combine(");

        builder.append('"').append(name).append('"');

        for (String element : elements)
        {
            builder.append(", ").append('"').append(element).append('"');
        }

        builder.append(");");

        final String result = builder.toString().replace("(, ", "");

        addCharacterClass(result);

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder exclude(String name,
                                   String include,
                                   String exclude)
    {
        grammar_builder.exclude(name, include, exclude);

        final StringBuilder builder = new StringBuilder();

        builder.append("g.exclude(");

        builder.append('"').append(name).append('"');

        builder.append(", ");

        builder.append('"').append(include).append('"');

        builder.append(", ");

        builder.append('"').append(exclude).append('"');

        builder.append(");");

        addCharacterClass(builder.toString());

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder negate(String name,
                                  String negates)
    {
        grammar_builder.negate(name, negates);

        final StringBuilder builder = new StringBuilder();

        builder.append("g.negate(");

        builder.append('"').append(name).append('"');

        builder.append(", ");

        builder.append('"').append(negates).append('"');

        builder.append(");");

        addCharacterClass(builder.toString());

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ICodeGenerator setPackageName(String name)
    {
        Utils.checkNonNull(name);

        if (package_name != null)
        {
            throw new IllegalStateException("The package name was already specified.");
        }
        else
        {
            this.package_name = name;
        }

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ICodeGenerator setParserName(String name)
    {
        Utils.checkNonNull(name);

        if (parser_name != null)
        {
            throw new IllegalStateException("The parser name was already specified.");
        }
        else
        {
            this.parser_name = name;
        }

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ICodeGenerator setVisitorName(String name)
    {
        Utils.checkNonNull(name);

        if (visitor_name != null)
        {
            throw new IllegalStateException("The visitor name was already specified.");
        }
        else
        {
            this.visitor_name = name;
        }

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder setRoot(String name)
    {
        Utils.checkNonNull(name);

        if (root_name != null)
        {
            throw new IllegalStateException("The root rule was already specified.");
        }
        else
        {
            this.grammar_builder.setRoot(name);

            this.root_name = name;
        }

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IGrammarBuilder setTraceCount(int count)
    {
        if (count <= 0)
        {
            throw new IllegalArgumentException("A negative trace-count was specified.");
        }
        else
        {
            this.grammar_builder.setTraceCount(count);

            this.trace_count = count;
        }

        return this;
    }

    /**
     * {@inheritDoc}
     */
    public ICodeGenerator hide()
    {
        this.hide = true;

        return this;
    }

    /**
     * {@inheritDoc}
     */
    public ICodeGenerator addExportParserFile(final File file)
    {
        this.parser_exports.add(file);

        return this;
    }

    /**
     * {@inheritDoc}
     */
    public ICodeGenerator addExportVisitorFile(final File file)
    {
        this.visitor_exports.add(file);

        return this;
    }
}