representation.Generator.java Source code

Java tutorial

Introduction

Here is the source code for representation.Generator.java

Source

/**
 * MIT License
 *
 * Copyright (c) 2017 Valeriy Manenkov and contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package representation;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

/**
 * Java project generator.
 *
 * @author Valeriy Manenkov (v.manenkov@gmail.com)
 * @version $Id$
 * @since 0.1
 */
public final class Generator {

    /**
     * Templates path.
     */
    private static final String TEMPLATES_PATH = "src/main/resources/representation/";

    /**
     * Output path.
     */
    private static String OUTPUT_PATH = "output/";

    /**
     * Set of nonterminals.
     */
    private Set<String> nonterminals = new LinkedHashSet<>();

    /**
     * Generated source code for nonterminals.
     */
    private Map<String, String> nonterminalsSourceCode = new HashMap<>();

    /**
     * Set of terminals.
     */
    private Set<String> terminals = new LinkedHashSet<>();

    /**
     * List (string) of symbols, which can begin a terminals.
     */
    private Map<String, String> terminalsCanStartsWith = new HashMap<>();

    /**
     * Generated source code for terminals.
     */
    private Map<String, String> terminalsSourceCode = new HashMap<>();

    /**
     * AST nodes and their constructor arguments
     */
    private Map<String, LinkedList<String>> astNodes = new HashMap<>();

    /**
     * Ctor.
     *
     * @param nonterminals Set of nonterminals
     * @param nonterminalsSourceCode Generated source code for nonterminals
     * @param terminals Set of terminals
     * @param terminalsCanStartsWith List (string) of symbols, which can begin
     *                               a terminals
     * @param terminalsSourceCode Generated source code for terminals
     * @param astNodes AST nodes and their constructor arguments
     */
    public Generator(final Set<String> nonterminals, final Map<String, String> nonterminalsSourceCode,
            final Set<String> terminals, final Map<String, String> terminalsCanStartsWith,
            final Map<String, String> terminalsSourceCode, final Map<String, LinkedList<String>> astNodes) {
        this.nonterminals = nonterminals;
        this.nonterminalsSourceCode = nonterminalsSourceCode;
        this.terminals = terminals;
        this.terminalsCanStartsWith = terminalsCanStartsWith;
        this.terminalsSourceCode = terminalsSourceCode;
        this.astNodes = astNodes;
    }

    /**
     * Ctor.
     *
     * @param outputPath Output path
     * @param generatorData Generator data
     */
    public Generator(final String outputPath, final GeneratorData generatorData) {
        this.nonterminals = generatorData.getNonterminals();
        this.nonterminalsSourceCode = generatorData.getNonterminalsSourceCode();
        this.terminals = generatorData.getTerminals();
        this.terminalsCanStartsWith = generatorData.getTerminalsCanStartsWith();
        this.terminalsSourceCode = generatorData.getTerminalsSourceCode();
        this.astNodes = generatorData.getAstNodes();

        this.OUTPUT_PATH = outputPath;
    }

    /**
     * Create new file from template.
     *
     * @param path Directory
     * @param name File name
     * @param template Template name
     */
    public static void createFile(final String path, final String name, final String template) {
        new File(OUTPUT_PATH + path).mkdirs();
        String data = null;
        try {
            data = IOUtils.toString(new FileReader(TEMPLATES_PATH + template + ".template"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            new File(OUTPUT_PATH + path + name).createNewFile();
            final FileOutputStream out = new FileOutputStream(OUTPUT_PATH + path + name);
            out.write(data.getBytes());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Append file with content.
     *
     * @param path Directory
     * @param name File name
     * @param string Content
     */
    public static void appendFile(final String path, final String name, final String string) {
        try {
            FileUtils.writeStringToFile(new File(OUTPUT_PATH + path + name), string, Charset.defaultCharset(),
                    true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Append file with template.
     *
     * @param path Directory
     * @param name File name
     * @param template Template name
     */
    public static void appendFileWithTemplate(final String path, final String name, final String template) {
        String string = null;
        try {
            string = IOUtils.toString(new FileReader(TEMPLATES_PATH + template + ".template"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileUtils.writeStringToFile(new File(OUTPUT_PATH + path + name), string, Charset.defaultCharset(),
                    true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Generate Java project. Main method of this class.
     */
    public void generate() {
        try {
            FileUtils.deleteDirectory(new File(OUTPUT_PATH));
        } catch (IOException e) {
            e.printStackTrace();
        }

        createFile("", "pom.xml", "pom.xml");
        createFile("src/main/java/", "Token.java", "Token.java");
        createFile("src/main/java/", "Main.java", "Main.java");
        createTokenTypeFile();
        createLexerFile();
        createFile("src/main/java/", "AST.java", "AST");
        createParserFile();
        createASTTreeNodeFiles();
        createInterpreterFile();
    }

    /**
     * Generate Interpreter.java file.
     */
    private void createInterpreterFile() {
        String visitMethodContent = "";
        for (String node : astNodes.keySet()) {
            visitMethodContent += "if (node instanceof AST" + node + ") {\n";
            visitMethodContent += "    return visit" + node + "((AST" + node + ") node);\n";
            visitMethodContent += "}\n";
        }
        if (!visitMethodContent.equals("")) {
            visitMethodContent += "else {\n";
            visitMethodContent += "    return \"\";\n";
            visitMethodContent += "}\n";
        }

        String visitMethodsStubs = "";
        for (String node : astNodes.keySet()) {
            visitMethodsStubs += "public String visit" + node + "(AST" + node + " node) {\n";
            visitMethodsStubs += "    String result = \"\";\n";
            visitMethodsStubs += "    return result;\n";
            visitMethodsStubs += "}\n\n";
        }

        createFile("src/main/java/", "Interpreter.java", "InterpreterHeader");
        appendFile("src/main/java/", "Interpreter.java", "public String visit(AST node) {\n");
        appendFile("src/main/java/", "Interpreter.java", visitMethodContent);
        appendFile("src/main/java/", "Interpreter.java", "}\n");
        appendFile("src/main/java/", "Interpreter.java", visitMethodsStubs);
        appendFileWithTemplate("src/main/java/", "Interpreter.java", "InterpreterFooter");
    }

    /**
     * Generate AST*.java files.
     */
    private void createASTTreeNodeFiles() {
        for (final String node : astNodes.keySet()) {
            final Integer numArguments = astNodes.get(node).size();
            final LinkedList<String> types = astNodes.get(node);

            // constructor args
            String argsString = "";
            for (int i = 0; i < numArguments; i++) {
                argsString += types.get(i).equals("n") ? "AST node" + i + ", " : "Token node" + i + ", ";
            }
            if (argsString.length() > 2) {
                argsString = argsString.substring(0, argsString.length() - 2);
            }

            // fields
            String fieldsString = "";
            for (int i = 0; i < numArguments; i += 1) {
                fieldsString += types.get(i).equals("n") ? "private AST node" + i + ";\n"
                        : "private Token node" + i + ";\n";
            }

            // constructor content
            String constructorContent = "";
            for (int i = 0; i < numArguments; i += 1) {
                constructorContent += "this.node" + i + " = node" + i + ";\n";
            }

            final String filename = "AST" + node + ".java";
            createFile("src/main/java/", filename, "Empty");
            appendFile("src/main/java/", filename, "public class AST" + node + " implements AST {\n");
            appendFile("src/main/java/", filename, fieldsString);
            appendFile("src/main/java/", filename, "public AST" + node + "(" + argsString + ") {\n");
            appendFile("src/main/java/", filename, constructorContent);
            appendFile("src/main/java/", filename, "}\n");
            appendFile("src/main/java/", filename, "}\n");
        }
    }

    /**
     * Generate TokenType.java file.
     */
    public void createTokenTypeFile() {
        createFile("src/main/java/", "TokenType.java", "TokenTypeHeader");
        for (String terminal : terminals) {
            appendFile("src/main/java/", "TokenType.java", "    " + terminal + ",\n");
        }
        appendFile("src/main/java/", "TokenType.java", "    EOF\n}");
    }

    /**
     * Generate Lexer.java file.
     */
    public void createLexerFile() {
        createFile("src/main/java/", "Lexer.java", "LexerHeader");
        for (final String terminal : terminals) {
            final String line = "    public String " + terminal.toLowerCase() + "() throws Exception {\n"
                    + "    String result = \"\";\n" + terminalsSourceCode.get(terminal) + "\n"
                    + "    return result;" + "    }\n\n";
            appendFile("src/main/java/", "Lexer.java", line);
        }
        appendFile("src/main/java/", "Lexer.java",
                "    public Token getNextToken() throws Exception {\n"
                        + "        while (this.currentChar != null) {\n"
                        + "             if (Character.isWhitespace(this.currentChar)) {\n"
                        + "                 this.skipWhitespace();\n" + "                 continue;\n"
                        + "             }\n");

        for (final String terminal : terminals) {
            String orExpr = "";

            final String cases = terminalsCanStartsWith.get(terminal);
            for (int i = 0; i < cases.length(); i++) {
                orExpr += "this.currentChar.equals('" + cases.charAt(i) + "') || ";
            }
            orExpr = orExpr.substring(0, orExpr.length() - 4);
            appendFile("src/main/java/", "Lexer.java", "if (" + orExpr + ") {\n");
            appendFile("src/main/java/", "Lexer.java",
                    "    return new Token(TokenType." + terminal + ", this." + terminal.toLowerCase() + "());\n");
            appendFile("src/main/java/", "Lexer.java", "}\n\n");
        }

        appendFile("src/main/java/", "Lexer.java", "            this.error();\n        }\n");
        appendFile("src/main/java/", "Lexer.java", "        return new Token(TokenType.EOF, null);\n");
        appendFile("src/main/java/", "Lexer.java", "    }\n");
        appendFileWithTemplate("src/main/java/", "Lexer.java", "LexerFooter");
    }

    /**
     * Generate parser.java file.
     */
    public void createParserFile() {
        createFile("src/main/java/", "Parser.java", "ParserHeader");
        appendFile("src/main/java/", "Parser.java",
                "    public AST parse() throws Exception {\n" + "        AST node = this."
                        + nonterminals.iterator().next() + "();\n"
                        + "        if (this.currentToken.type != TokenType.EOF) {\n" + "            this.error();\n"
                        + "        }\n" + "        return node;\n" + "    }\n");
        for (String nonterminal : nonterminals) {
            final String str = "public AST " + nonterminal + "() throws Exception {\n"
                    + nonterminalsSourceCode.get(nonterminal) + "\n" + "}\n\n";
            appendFile("src/main/java/", "Parser.java", str);
        }
        appendFileWithTemplate("src/main/java/", "Parser.java", "ParserFooter");
    }
}