package org.elixir_lang.beam;
import com.google.common.base.Joiner;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.fileTypes.BinaryFileDecompiler;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import org.elixir_lang.beam.chunk.Atoms;
import org.elixir_lang.beam.chunk.Exports;
import org.elixir_lang.beam.chunk.exports.Export;
import org.elixir_lang.beam.decompiler.Default;
import org.elixir_lang.beam.decompiler.InfixOperator;
import org.elixir_lang.beam.decompiler.PrefixOperator;
import org.elixir_lang.beam.decompiler.SpecialForm;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static org.elixir_lang.psi.call.name.Function.DEF;
import static org.elixir_lang.psi.call.name.Function.DEFMACRO;
import static org.elixir_lang.psi.call.name.Module.ELIXIR_PREFIX;
public class Decompiler implements BinaryFileDecompiler {
private static final List<org.elixir_lang.beam.decompiler.MacroNameArity> MACRO_NAME_ARITY_DECOMPILER_LIST =
new ArrayList<org.elixir_lang.beam.decompiler.MacroNameArity>();
static {
MACRO_NAME_ARITY_DECOMPILER_LIST.add(InfixOperator.INSTANCE);
MACRO_NAME_ARITY_DECOMPILER_LIST.add(PrefixOperator.INSTANCE);
MACRO_NAME_ARITY_DECOMPILER_LIST.add(SpecialForm.INSTANCE);
MACRO_NAME_ARITY_DECOMPILER_LIST.add(Default.INSTANCE);
}
@NotNull
private static CharSequence decompiled(@Nullable Beam beam) {
StringBuilder decompiled = new StringBuilder("Decompilated Error");
if (beam != null) {
Atoms atoms = beam.atoms();
if (atoms != null) {
String moduleName = atoms.moduleName();
if (moduleName != null) {
String defmoduleArgument = defmoduleArgument(moduleName);
decompiled = new StringBuilder(
"# Source code recreated from a .beam file by IntelliJ Elixir\n"
)
.append("defmodule ")
.append(defmoduleArgument)
.append(" do\n");
appendExports(decompiled, beam, atoms);
decompiled.append("end\n");
}
}
}
return decompiled;
}
private static void appendExports(@NotNull StringBuilder decompiled, @NotNull Beam beam, @NotNull Atoms atoms) {
Exports exports = beam.exports();
if (exports != null) {
appendExports(decompiled, exports, atoms);
}
}
private static void appendExports(@NotNull StringBuilder decompiled,
@NotNull Exports exports,
@NotNull Atoms atoms) {
SortedSet<MacroNameArity> macroNameAritySortedSet = exports.macroNameAritySortedSet(atoms);
MacroNameArity lastMacroNameArity = null;
for (MacroNameArity macroNameArity : macroNameAritySortedSet) {
if (lastMacroNameArity == null) {
appendHeader(decompiled, "Macros");
} else if (lastMacroNameArity.macro.equals(DEFMACRO) && macroNameArity.macro.equals(DEF)) {
appendHeader(decompiled, "Functions");
}
decompiled.append("\n");
appendMacroNameArity(decompiled, macroNameArity);
lastMacroNameArity = macroNameArity;
}
}
private static void appendHeader(@NotNull StringBuilder decompiled, @NotNull String name) {
decompiled
.append("\n")
.append(" # ")
.append(name)
.append("\n");
}
private static void appendMacroNameArity(@NotNull StringBuilder decompiled,
@NotNull MacroNameArity macroNameArity) {
boolean accepted = false;
for (org.elixir_lang.beam.decompiler.MacroNameArity decompiler : MACRO_NAME_ARITY_DECOMPILER_LIST) {
if (decompiler.accept(macroNameArity)) {
decompiler.append(decompiled, macroNameArity);
accepted = true;
break;
}
}
if (!accepted) {
error(macroNameArity);
}
}
private static void error(@NotNull MacroNameArity macroNameArity) {
com.intellij.openapi.diagnostic.Logger logger = com.intellij.openapi.diagnostic.Logger.getInstance(
Decompiler.class
);
String fullUserMessage = "No decompiler for MacroNameArity (" + macroNameArity +")";
logger.error(
LogMessageEx.createEvent(
fullUserMessage,
Joiner
.on("\n")
.join(
new Throwable().getStackTrace()
),
fullUserMessage,
null,
Collections.<Attachment>emptyList()
)
);
}
@NotNull
public static String defmoduleArgument(String moduleName) {
String defmoduleArgument;
if (moduleName.startsWith(ELIXIR_PREFIX)) {
defmoduleArgument = moduleName.substring(ELIXIR_PREFIX.length());
} else {
defmoduleArgument = ":" + moduleName;
}
return defmoduleArgument;
}
@NotNull
@Override
public CharSequence decompile(@NotNull VirtualFile virtualFile) {
Beam beam = Beam.from(virtualFile);
return decompiled(beam);
}
}