/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package illarion.easynpc;
import illarion.common.util.AppIdent;
import illarion.common.util.Crypto;
import illarion.common.util.TableLoader;
import illarion.easynpc.docu.DocuEntry;
import illarion.easynpc.grammar.EasyNpcLexer;
import illarion.easynpc.grammar.EasyNpcParser;
import illarion.easynpc.grammar.EasyNpcParser.ScriptContext;
import illarion.easynpc.gui.Config;
import illarion.easynpc.parser.ParsedNpcVisitor;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.TokenMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class parses a easyNPC script that contains the plain script data to a parsed script that contains the
* analyzed script data.
*
* @author Martin Karing <nitram@illarion.org>
*/
public final class Parser implements DocuEntry {
/**
* The identifier of this application.
*/
public static final AppIdent APPLICATION = new AppIdent("Illarion easyNPC Editor");
/**
* The singleton instance of this class.
*/
private static final Parser INSTANCE = new Parser();
/**
* The private constructor to avoid any instances but the singleton instance. This also prepares the list that
* are required to work and the registers the parsers working in this parser.
*/
private Parser() {
Crypto crypt = new Crypto();
crypt.loadPublicKey();
TableLoader.setCrypto(crypt);
}
/**
* Get the singleton instance of this parser.
*
* @return the singleton instance of this class
*/
@Nonnull
public static Parser getInstance() {
return INSTANCE;
}
private static boolean verbose;
private static boolean quiet;
/**
* This function starts the parser without GUI and is used to parse some
* scripts directly.
*
* @param args the path to the script or the folder with the scripts to parse
* @throws IOException in case the script can't be read
*/
public static void main(@Nonnull String... args) throws IOException {
Config.getInstance().init();
if (args.length == 0) {
System.out.println("You need to set a script to parse, else nothing can be done.");
}
for (String arg : args) {
switch (arg) {
case "-v":
case "--verbose":
verbose = true;
continue;
case "-q":
case "--quiet":
quiet = true;
continue;
default:
}
Path sourceFile = Paths.get(arg);
if (!Files.isDirectory(sourceFile) && Files.isReadable(sourceFile)) {
parseScript(sourceFile);
} else if (Files.isDirectory(sourceFile)) {
ExecutorService executor = Executors.newCachedThreadPool();
Files.walkFileTree(sourceFile, EnumSet.noneOf(FileVisitOption.class), 1, new SimpleFileVisitor<Path>() {
@Nonnull
@Override
public FileVisitResult visitFile(@Nonnull Path file, BasicFileAttributes attrs)
throws IOException {
if (file.toUri().toString().endsWith(".npc")) {
executor.submit(new Callable<Void>() {
@Nullable
@Override
public Void call() throws Exception {
parseScript(file);
return null;
}
});
}
return FileVisitResult.CONTINUE;
}
});
executor.shutdown();
try {
executor.awaitTermination(20, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Nonnull
private static ParsedNpc parseScript(@Nonnull CharStream stream) {
EasyNpcLexer lexer = new EasyNpcLexer(stream);
EasyNpcParser parser = new EasyNpcParser(new CommonTokenStream(lexer));
ParsedNpcVisitor visitor = new ParsedNpcVisitor();
lexer.removeErrorListeners();
lexer.addErrorListener(visitor);
ScriptContext context = parser.script();
context.accept(visitor);
return visitor.getParsedNpc();
}
private static void parseScript(@Nonnull Path file) throws IOException {
try (Reader stream = Files.newBufferedReader(file, EasyNpcScript.DEFAULT_CHARSET)) {
ParsedNpc parsedNPC = parseScript(new ANTLRInputStream(stream));
StringBuilder output = new StringBuilder();
output.append("File \"").append(file.getFileName()).append("\" parsed - Encoding: ")
.append(EasyNpcScript.DEFAULT_CHARSET.name()).append(" - Errors: ");
if (parsedNPC.hasErrors()) {
output.append(parsedNPC.getErrorCount()).append('\n');
int errorCount = parsedNPC.getErrorCount();
for (int i = 0; i < errorCount; ++i) {
ParsedNpc.Error error = parsedNPC.getError(i);
output.append("\tLine ").append(Integer.toString(error.getLine())).append(": ")
.append(error.getMessage()).append('\n');
}
if (!quiet) {
output.setLength(output.length() - 1);
System.err.println(output);
}
System.exit(-1);
}
if (verbose) {
output.append("done");
System.out.println(output);
}
ScriptWriter writer = new ScriptWriter();
writer.setSource(parsedNPC);
Path luaTargetFile = file.getParent().resolveSibling(parsedNPC.getLuaFilename());
try (Writer outputWriter = Files.newBufferedWriter(luaTargetFile, EasyNpcScript.DEFAULT_CHARSET)) {
writer.setWritingTarget(outputWriter);
writer.write();
outputWriter.flush();
}
}
}
@Nonnull
@Override
public DocuEntry getChild(int index) {
throw new IndexOutOfBoundsException("No child available to display.");
}
@Override
public int getChildCount() {
return 0;
}
@Nonnull
@Override
public String getDescription() {
return Lang.getMsg(getClass(), "Docu.description");
}
@Nullable
@Override
public String getExample() {
return null;
}
@Nullable
@Override
public String getSyntax() {
return null;
}
@Override
public String getTitle() {
return Lang.getMsg(getClass(), "Docu.title");
}
/**
* Parse the NPC and return the parsed version of the NPC.
*
* @param source the string containing the text of the script
* @return the parsed version of the NPC
*/
@Nonnull
public static ParsedNpc parse(@Nonnull String source) {
return parseScript(new ANTLRInputStream(source));
}
/**
* Parse the NPC and return the parsed version of the NPC.
*
* @param source the reader supplying the script data
* @return the parsed version of the NPC
*/
@Nonnull
public static ParsedNpc parse(@Nonnull Reader source) throws IOException {
return parseScript(new ANTLRInputStream(source));
}
/**
* Parse the NPC and return the parsed version of the NPC.
*
* @param source the path holding the script file
* @return the parsed version of the NPC
*/
@Nonnull
public static ParsedNpc parse(@Nonnull Path source) throws IOException {
try (Reader reader = Files.newBufferedReader(source, EasyNpcScript.DEFAULT_CHARSET)) {
return parseScript(new ANTLRInputStream(reader));
}
}
public static void enlistHighlightedWords(@Nonnull TokenMap map) {
Pattern tokenPattern = Pattern.compile("'([a-zA-Z]+)'");
for (String token : EasyNpcLexer.tokenNames) {
Matcher matcher = tokenPattern.matcher(token);
if (matcher.matches()) {
map.put(matcher.group(1), Token.RESERVED_WORD);
}
}
}
}