Permalink
Browse files

Macros before functions in decompiled source

Also section headers
  • Loading branch information...
1 parent d66ca54 commit e66a4d8873013740fea830f953f58e5761c7660f @KronicDeth committed Jan 7, 2017
@@ -17,6 +17,8 @@
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 {
@@ -70,33 +72,32 @@ private static void appendExports(@NotNull StringBuilder decompiled, @NotNull Be
private static void appendExports(@NotNull StringBuilder decompiled,
@NotNull Exports exports,
@NotNull Atoms atoms) {
- Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>>
- exportByArityByNameNamelessExportSet = exports.exportByArityByName(atoms);
- SortedMap<String, SortedMap<Integer, Export>> exportByArityByName =
- exportByArityByNameNamelessExportSet.first;
-
- boolean first = true;
- for (SortedMap.Entry<String, SortedMap<Integer, Export>> nameExportByArity :
- exportByArityByName.entrySet()) {
- String name = nameExportByArity.getKey();
-
- SortedMap<Integer, Export> exportByArity = nameExportByArity.getValue();
-
- for (SortedMap.Entry<Integer, Export> arityExport : exportByArity.entrySet()) {
- if (!first) {
- decompiled.append("\n");
- }
+ 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");
+ }
- @Nullable Integer arity = arityExport.getKey();
+ decompiled.append("\n");
- MacroNameArity macroNameArity = new MacroNameArity(name, arity);
- appendMacroNameArity(decompiled, macroNameArity);
+ appendMacroNameArity(decompiled, macroNameArity);
- first = false;
- }
+ 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;
@@ -1,5 +1,6 @@
package org.elixir_lang.beam;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -9,7 +10,7 @@
/**
* The macro ({@code def} or {@code defmacro}), name of the call definition and arity to define an export
*/
-public class MacroNameArity {
+public class MacroNameArity implements Comparable<MacroNameArity> {
/*
* CONSTANTS
*/
@@ -37,30 +38,79 @@
* Elixir macros are defined as Erlang functions that take an extra Caller argument, so the exportArity is +1 for
* macros compared to this arity, which is the number of parameters to the call definition head passed to
* {@code defmacro}.
- *
- * Only {@code null} if {@code exportArity} is {@code null}, which would indicate a malformed {@code .beam}.
*/
- @Nullable
+ @NotNull
public final Integer arity;
/*
* Constructors
*/
- public MacroNameArity(@NotNull String exportName, @Nullable Integer exportArity) {
+ public MacroNameArity(@NotNull String exportName, int exportArity) {
if (exportName.startsWith(MACRO_EXPORT_PREFIX)) {
macro = DEFMACRO;
name = exportName.substring(MACRO_EXPORT_PREFIX.length());
-
- if (exportArity != null) {
- arity = exportArity - 1;
- } else {
- arity = null;
- }
+ arity = exportArity - 1;
} else {
macro = DEF;
name = exportName;
arity = exportArity;
}
}
+
+ /*
+ * Instance Methods
+ */
+
+ @Override
+ public int compareTo(@NotNull MacroNameArity other) {
+ int comparison = compareMacroTo(other.macro);
+
+ if (comparison == 0) {
+ comparison = compareNameTo(other.name);
+
+ if (comparison == 0) {
+ comparison = compareArityTo(other.arity);
+ }
+ }
+
+ return comparison;
+ }
+
+ @Contract(pure = true)
+ private int compareArityTo(int otherArity) {
+ return Double.compare(arity, otherArity);
+ }
+
+ @Contract(pure = true)
+ private int compareMacroTo(@NotNull String otherMacro) {
+ int comparison;
+
+ if (macro.equals(DEFMACRO)) {
+ if (otherMacro.equals(DEFMACRO)) {
+ comparison = 0;
+ } else if (otherMacro.equals(DEF)) {
+ comparison = - 1;
+ } else {
+ throw new IllegalArgumentException("otherMacro must be \"" + DEFMACRO + "\" or \"" + DEF + "\"");
+ }
+ } else if (macro.equals(DEF)) {
+ if (otherMacro.equals(DEFMACRO)) {
+ comparison = 1;
+ } else if (otherMacro.equals(DEF)) {
+ comparison = 0;
+ } else {
+ throw new IllegalArgumentException("otherMacro must be \"" + DEFMACRO + "\" or \"" + DEF + "\"");
+ }
+ } else {
+ throw new IllegalArgumentException("macro must be \"" + DEFMACRO + "\" or \"" + DEF + "\"");
+ }
+
+ return comparison;
+ }
+
+ @Contract(pure = true)
+ private int compareNameTo(@NotNull String otherName) {
+ return name.compareTo(otherName);
+ }
}
@@ -2,6 +2,7 @@
import com.intellij.openapi.util.Pair;
import gnu.trove.THashSet;
+import org.elixir_lang.beam.MacroNameArity;
import org.elixir_lang.beam.chunk.exports.Export;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -67,28 +68,36 @@ public static Exports from(@NotNull Chunk chunk) {
return exports;
}
- public Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>> exportByArityByName(@NotNull Atoms atoms) {
- SortedMap<String, SortedMap<Integer, Export>> exportByArityByName =
- new TreeMap<String, SortedMap<Integer, Export>>();
- SortedSet<Export> nameless = new TreeSet<Export>();
+ /**
+ * Set of {@link MacroNameArity} sorted as
+ * 1. {@link MacroNameArity#macro}, so that {@code defmacro} is before {@code def}
+ * 2. {@link MacroNameArity#name} is sorted alphabetically
+ * 3. {@link MacroNameArity#arity} is sorted ascending
+ *
+ * @param atoms used to look up the names of the {@link Export}s in {@link #exportCollection}.
+ * @return The sorted set will be the same size as {@link #exportCollection} unless {@link Export#name(Atoms)}
+ * returns {@code null} for some {@link Export}s.
+ */
+ @NotNull
+ public SortedSet<MacroNameArity> macroNameAritySortedSet(@NotNull Atoms atoms) {
+ SortedSet<MacroNameArity> macroNameAritySortedSet = new TreeSet<MacroNameArity>();
for (Export export : exportCollection) {
- String name = export.name(atoms);
-
- if (name == null) {
- nameless.add(export);
- } else {
- SortedMap<Integer, Export> exportByArity = exportByArityByName.get(name);
-
- if (exportByArity == null) {
- exportByArity = new TreeMap<Integer, Export>();
- exportByArityByName.put(name, exportByArity);
- }
+ String exportName = export.name(atoms);
- exportByArity.put((int) export.arity(), export);
+ if (exportName != null) {
+ MacroNameArity macroNameArity = new MacroNameArity(exportName, (int) export.arity());
+ macroNameAritySortedSet.add(macroNameArity);
}
}
- return pair(exportByArityByName, nameless);
+ return macroNameAritySortedSet;
+ }
+
+ /**
+ * The number of exports
+ */
+ public int size() {
+ return exportCollection.size();
}
}
@@ -132,25 +132,10 @@ private static void buildCallDefinitions(@NotNull ModuleStub parentStub, @NotNul
Exports exports = beam.exports();
if (exports != null) {
- Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>>
- exportByArityByNameNamelessExports = exports.exportByArityByName(atoms);
+ SortedSet<MacroNameArity> macroNameAritySortedSet = exports.macroNameAritySortedSet(atoms);
- SortedMap<String, SortedMap<Integer, Export>> exportByArityByName =
- exportByArityByNameNamelessExports.first;
-
- for (SortedMap.Entry<String, SortedMap<Integer, Export>> nameExportByArity :
- exportByArityByName.entrySet()) {
- String exportName = nameExportByArity.getKey();
-
- SortedMap<Integer, Export> exportByArity = nameExportByArity.getValue();
-
- for (SortedMap.Entry<Integer, Export> arityExport : exportByArity.entrySet()) {
- MacroNameArity macroNameArity = new MacroNameArity(exportName, arityExport.getKey());
-
- if (macroNameArity.arity != null) {
- buildCallDefinition(parentStub, macroNameArity);
- }
- }
+ for (MacroNameArity macroNameArity : macroNameAritySortedSet) {
+ buildCallDefinition(parentStub, macroNameArity);
}
}
}
@@ -1,20 +1,16 @@
package org.elixir_lang.beam;
import com.ericsson.otp.erlang.OtpErlangDecodeException;
-import com.intellij.openapi.util.Pair;
-import gnu.trove.THashSet;
+import com.intellij.openapi.util.Condition;
+import com.intellij.util.containers.ContainerUtil;
import org.elixir_lang.beam.chunk.Atoms;
import org.elixir_lang.beam.chunk.Exports;
-import org.elixir_lang.beam.chunk.exports.Export;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import java.io.*;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
+import java.util.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -41,27 +37,28 @@ public void elixirModule() throws IOException, OtpErlangDecodeException {
assertNotNull(exports);
- Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>> exportByArityByNameNamelessExportSet =
- exports.exportByArityByName(atoms);
+ int exportCount = exports.size();
- assertNotNull(exportByArityByNameNamelessExportSet);
+ assertTrue("There are no exports", exportCount > 0);
- Set<Export> namelessExportSet = exportByArityByNameNamelessExportSet.second;
+ SortedSet<MacroNameArity> macroNameAritySortedSet = exports.macroNameAritySortedSet(atoms);
- assertTrue("There are nameless exports", namelessExportSet.isEmpty());
+ assertEquals("There are nameless exports", exportCount, macroNameAritySortedSet.size());
- SortedMap<String, SortedMap<Integer, Export>> exportByArityByName = exportByArityByNameNamelessExportSet.first;
-
- // a name with multiple arities
- SortedMap<Integer, Export> exportByArity = exportByArityByName.get("node");
-
- assertNotNull(exportByArity);
+ List<MacroNameArity> nodes = ContainerUtil.filter(
+ macroNameAritySortedSet,
+ new Condition<MacroNameArity>() {
+ @Override
+ public boolean value(MacroNameArity macroNameArity) {
+ return "node".equals(macroNameArity.name);
+ }
+ }
+ );
- Set<Integer> expectedAritySet = new THashSet<Integer>();
- expectedAritySet.add(0);
- expectedAritySet.add(1);
+ assertEquals(nodes.size(), 2);
- assertEquals("node arities do not match", expectedAritySet, exportByArity.keySet());
+ assertEquals(0, (int) nodes.get(0).arity);
+ assertEquals(1, (int) nodes.get(1).arity);
}
@Test
@@ -80,24 +77,27 @@ public void erlangModule() throws IOException, OtpErlangDecodeException {
assertNotNull(exports);
- Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>> exportByArityByNameNamelessExportSet =
- exports.exportByArityByName(atoms);
+ int exportCount = exports.size();
- assertNotNull(exportByArityByNameNamelessExportSet);
+ assertTrue("There are no exports", exportCount > 0);
- Set<Export> namelessExportSet = exportByArityByNameNamelessExportSet.second;
+ SortedSet<MacroNameArity> macroNameAritySortedSet = exports.macroNameAritySortedSet(atoms);
- assertTrue("There are nameless exports", namelessExportSet.isEmpty());
+ assertEquals("There are nameless exports", exportCount, macroNameAritySortedSet.size());
- SortedMap<String, SortedMap<Integer, Export>> exportByArityByName = exportByArityByNameNamelessExportSet.first;
-
- Map<Integer, Export> exportByArity = exportByArityByName.get("extract");
-
- assertNotNull(exportByArity);
+ List<MacroNameArity> extracts = ContainerUtil.filter(
+ macroNameAritySortedSet,
+ new Condition<MacroNameArity>() {
+ @Override
+ public boolean value(MacroNameArity macroNameArity) {
+ return "extract".equals(macroNameArity.name);
+ }
+ }
+ );
- Export export = exportByArity.get(6);
+ assertEquals(1, extracts.size());
- assertNotNull(export);
+ assertEquals(6, (int) extracts.get(0).arity);
}
private Beam beam(@NotNull String baseName) throws IOException, OtpErlangDecodeException {
@@ -32,6 +32,9 @@ public void testIssue575() throws IOException, OtpErlangDecodeException {
assertEquals(
"# Source code recreated from a .beam file by IntelliJ Elixir\n" +
"defmodule Bitwise do\n" +
+ "\n" +
+ " # Macros\n" +
+ "\n" +
" defmacro left &&& right do\n" +
" # body not decompiled\n" +
" end\n" +
@@ -84,6 +87,8 @@ public void testIssue575() throws IOException, OtpErlangDecodeException {
" # body not decompiled\n" +
" end\n" +
"\n" +
+ " # Functions\n" +
+ "\n" +
" def __info__(p0) do\n" +
" # body not decompiled\n" +
" end\n" +

0 comments on commit e66a4d8

Please sign in to comment.