package name.admitriev.jhelper.generation;
import com.intellij.codeInsight.actions.ReformatCodeProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.SearchScope;
import name.admitriev.jhelper.components.Configurator;
import name.admitriev.jhelper.configuration.TaskConfiguration;
import name.admitriev.jhelper.exceptions.NotificationException;
import net.egork.chelper.task.StreamConfiguration;
import net.egork.chelper.task.Test;
import net.egork.chelper.task.TestType;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
public class CodeGenerationUtils {
private CodeGenerationUtils() {
}
/**
* Generates main function for testing purposes.
*
* @param project Project to get configuration from
*/
public static void generateRunFile(Project project, @NotNull PsiFile inputFile, TaskConfiguration task) {
if (!FileUtils.isCppFile(inputFile)) {
throw new NotificationException("Not a cpp file", "Only cpp files are currently supported");
}
if (project == null) {
throw new NotificationException("No project found", "Are you in any project?");
}
PsiFile psiOutputFile = getRunFile(project);
FileUtils.writeToFile(
psiOutputFile,
generateRunFileContent(project, task, inputFile.getVirtualFile().getPath())
);
}
/**
* Generates code for submission.
* Adds main function, inlines all used code except standard library and puts it to output file from configuration
*
* @param project Project to get configuration from
*/
public static void generateSubmissionFile(Project project, @NotNull PsiFile inputFile, TaskConfiguration task) {
if (!FileUtils.isCppFile(inputFile)) {
throw new NotificationException("Not a cpp file", "Only cpp files are currently supported");
}
if (project == null) {
throw new NotificationException("No project found", "Are you in any project?");
}
String result = IncludesProcessor.process(inputFile);
PsiFile psiOutputFile = getOutputFile(project);
FileUtils.writeToFile(
psiOutputFile,
authorComment(project),
generateSubmissionFileContent(project, result, task)
);
Configurator configurator = project.getComponent(Configurator.class);
Configurator.State configuration = configurator.getState();
if (configuration.isCodeEliminationOn()) {
removeUnusedCode(psiOutputFile);
}
if (configuration.isCodeReformattingOn()) {
new ReformatCodeProcessor(psiOutputFile, false).run();
}
}
private static String generateRunFileContent(Project project, TaskConfiguration task, String path) {
String template = TemplatesUtils.getTemplate(project, "run");
template = TemplatesUtils.replaceAll(template, TemplatesUtils.TASK_FILE, path);
template = TemplatesUtils.replaceAll(template, TemplatesUtils.TESTS, generateTestDeclaration(task.getTests()));
template = TemplatesUtils.replaceAll(template, TemplatesUtils.CLASS_NAME, task.getClassName());
template = TemplatesUtils.replaceAll(template, TemplatesUtils.SOLVER_CALL, generateSolverCall(task.getTestType()));
return template;
}
@NotNull
private static String generateTestDeclaration(Test[] tests) {
StringBuilder result = new StringBuilder();
for (Test test : tests) {
result.append(
"{" +
quote(test.input) + ", " +
quote(test.output != null ? test.output : "") + ", " +
Boolean.toString(test.active) + ", " +
Boolean.toString(test.output != null) +
"},"
);
}
return result.toString();
}
private static CharSequence quote(String input) {
StringBuilder sb = new StringBuilder("");
sb.append('"');
for (char c : input.toCharArray()) {
if (c == '\n') {
sb.append("\\n");
continue;
}
if (c == '"' || c == '\'' || c == '\\') {
sb.append('\\');
}
sb.append(c);
}
sb.append('"');
return sb;
}
private static String generateSubmissionFileContent(Project project, String code, TaskConfiguration task) {
String template = TemplatesUtils.getTemplate(project, "submission");
if (task.getInput().type == StreamConfiguration.StreamType.LOCAL_REGEXP) {
code = code + '\n' + generateFileNameGetter();
}
template = TemplatesUtils.replaceAll(template, TemplatesUtils.CODE, code);
template = TemplatesUtils.replaceAll(template, TemplatesUtils.CLASS_NAME, task.getClassName());
template = TemplatesUtils.replaceAll(template, TemplatesUtils.INPUT, getInputDeclaration(task));
template = TemplatesUtils.replaceAll(template, TemplatesUtils.OUTPUT, getOutputDeclaration(task));
template = TemplatesUtils.replaceAll(template, TemplatesUtils.SOLVER_CALL, generateSolverCall(task.getTestType()));
return template;
}
private static String generateFileNameGetter() {
return "#include <dirent.h>\n" +
"#include <stdexcept>\n" +
"#include <regex>\n" +
"#include <sys/stat.h>\n" +
"#include <cstdint>\n" +
"\n" +
"std::string getLastFileName(const std::string& regexString) {\n" +
"\tDIR* dir;\n" +
"\tdirent* entry;\n" +
"\tstd::string result = \"\";\n" +
"\tint64_t resultModificationTime = 0;\n" +
"\tstd::regex regex(regexString);\n" +
"\tif ((dir = opendir (\".\")) != NULL) {\n" +
"\t\twhile ((entry = readdir (dir)) != NULL) {\n" +
"\t\t\tif (std::regex_match(entry->d_name, regex)) {\n" +
"\t\t\t\tstruct stat buffer;\n" +
"\t\t\t\tstat(entry->d_name, &buffer);\n" +
"\t\t\t\tint64_t modificationTime = static_cast<int64_t>(buffer.st_mtimespec.tv_sec) * 1000000000 +\n" +
"\t\t\t\t\t\tstatic_cast<int64_t>(buffer.st_mtimespec.tv_nsec);\n" +
"\n" +
"\t\t\t\tif (modificationTime > resultModificationTime) {\n" +
"\t\t\t\t\tresultModificationTime = modificationTime;\n" +
"\t\t\t\t\tresult = entry->d_name;\n" +
"\t\t\t\t}\n" +
"\t\t\t}\n" +
"\t\t}\n" +
"\t\tclosedir (dir);\n" +
"\t} else {\n" +
"\t\tthrow std::runtime_error(\"Couldn't open current directory\");\n" +
"\t}\n" +
"\tif (result.empty()) {\n" +
"\t\tthrow std::runtime_error(\"No file found\");\n" +
"\t}" +
"\treturn result;\n" +
"}";
}
private static String generateSolverCall(TestType testType) {
switch (testType) {
case SINGLE:
return "solver.solve(in, out);";
case MULTI_NUMBER:
return "int n;\n" +
"in >> n;\n" +
"for(int i = 0; i < n; ++i) {\n" +
"\tsolver.solve(in, out);\n" +
"}\n";
case MULTI_EOF:
return "while(in.good()) {\n" +
"\tsolver.solve(in, out);\n" +
"}\n";
default:
throw new IllegalArgumentException("Unknown testType:" + testType);
}
}
private static String getOutputDeclaration(TaskConfiguration task) {
String outputFileName = task.getOutput().getFileName(task.getName(), ".out");
if (outputFileName == null) {
return "std::ostream& out(std::cout);";
}
else if (task.getOutput().type == StreamConfiguration.StreamType.LOCAL_REGEXP) {
throw new NotificationException("Your task is in inconsistent state", "Can't output to local regexp");
}
else {
return "std::ofstream out(\"" + outputFileName + "\");";
}
}
private static String getInputDeclaration(TaskConfiguration task) {
if (task.getInput().type == StreamConfiguration.StreamType.LOCAL_REGEXP) {
return "std::ifstream in(getLastFileName(" + quote(task.getInput().fileName) + "));";
}
else if (task.getInput().type == StreamConfiguration.StreamType.STANDARD) {
return "std::istream& in(std::cin);";
}
else {
String inputFileName = task.getInput().getFileName(task.getName(), ".in");
return "std::ifstream in(\"" + inputFileName + "\");";
}
}
@NotNull
private static PsiFile getOutputFile(Project project) {
Configurator configurator = project.getComponent(Configurator.class);
Configurator.State configuration = configurator.getState();
VirtualFile outputFile = project.getBaseDir().findFileByRelativePath(configuration.getOutputFile());
if (outputFile == null) {
throw new NotificationException(
"No output file found.",
"You should configure output file to point to existing file"
);
}
PsiFile psiOutputFile = PsiManager.getInstance(project).findFile(outputFile);
if (psiOutputFile == null) {
throw new NotificationException("Couldn't open output file as PSI");
}
return psiOutputFile;
}
@NotNull
private static PsiFile getRunFile(Project project) {
Configurator configurator = project.getComponent(Configurator.class);
Configurator.State configuration = configurator.getState();
VirtualFile outputFile = project.getBaseDir().findFileByRelativePath(configuration.getRunFile());
if (outputFile == null) {
throw new NotificationException(
"No run file found.",
"You should configure run file to point to existing file"
);
}
PsiFile psiOutputFile = PsiManager.getInstance(project).findFile(outputFile);
if (psiOutputFile == null) {
throw new NotificationException("Couldn't open run file as PSI");
}
return psiOutputFile;
}
private static String authorComment(Project project) {
Configurator configurator = project.getComponent(Configurator.class);
Configurator.State configuration = configurator.getState();
return "/**\n" +
" * code generated by JHelper\n" +
" * More info: https://github.com/AlexeyDmitriev/JHelper\n" +
" * @author " + configuration.getAuthor() + '\n' +
" */\n\n";
}
private static void removeUnusedCode(PsiFile file) {
while (true) {
Collection<PsiElement> toDelete = new ArrayList<>();
Project project = file.getProject();
SearchScope scope = GlobalSearchScope.fileScope(project, file.getVirtualFile());
file.acceptChildren(new DeletionMarkingVisitor(toDelete, scope));
if (toDelete.isEmpty()) {
break;
}
new WriteCommandAction.Simple<Object>(project, file) {
@Override
public void run() {
for (PsiElement element : toDelete) {
element.delete();
}
}
}.execute();
}
}
}