Skip to content

Commit

Permalink
Macros before functions in decompiled source
Browse files Browse the repository at this point in the history
Also section headers
  • Loading branch information
KronicDeth committed Jan 7, 2017
1 parent d66ca54 commit e66a4d8
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 101 deletions.
43 changes: 22 additions & 21 deletions src/org/elixir_lang/beam/Decompiler.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@


import java.util.*; 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; import static org.elixir_lang.psi.call.name.Module.ELIXIR_PREFIX;


public class Decompiler implements BinaryFileDecompiler { public class Decompiler implements BinaryFileDecompiler {
Expand Down Expand Up @@ -70,33 +72,32 @@ private static void appendExports(@NotNull StringBuilder decompiled, @NotNull Be
private static void appendExports(@NotNull StringBuilder decompiled, private static void appendExports(@NotNull StringBuilder decompiled,
@NotNull Exports exports, @NotNull Exports exports,
@NotNull Atoms atoms) { @NotNull Atoms atoms) {
Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>> SortedSet<MacroNameArity> macroNameAritySortedSet = exports.macroNameAritySortedSet(atoms);
exportByArityByNameNamelessExportSet = exports.exportByArityByName(atoms); MacroNameArity lastMacroNameArity = null;
SortedMap<String, SortedMap<Integer, Export>> exportByArityByName =
exportByArityByNameNamelessExportSet.first; for (MacroNameArity macroNameArity : macroNameAritySortedSet) {

if (lastMacroNameArity == null) {
boolean first = true; appendHeader(decompiled, "Macros");
for (SortedMap.Entry<String, SortedMap<Integer, Export>> nameExportByArity : } else if (lastMacroNameArity.macro.equals(DEFMACRO) && macroNameArity.macro.equals(DEF)) {
exportByArityByName.entrySet()) { appendHeader(decompiled, "Functions");
String name = nameExportByArity.getKey(); }

SortedMap<Integer, Export> exportByArity = nameExportByArity.getValue();

for (SortedMap.Entry<Integer, Export> arityExport : exportByArity.entrySet()) {
if (!first) {
decompiled.append("\n");
}


@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, private static void appendMacroNameArity(@NotNull StringBuilder decompiled,
@NotNull MacroNameArity macroNameArity) { @NotNull MacroNameArity macroNameArity) {
boolean accepted = false; boolean accepted = false;
Expand Down
72 changes: 61 additions & 11 deletions src/org/elixir_lang/beam/MacroNameArity.java
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.elixir_lang.beam; package org.elixir_lang.beam;


import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;


Expand All @@ -9,7 +10,7 @@
/** /**
* The macro ({@code def} or {@code defmacro}), name of the call definition and arity to define an export * 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 * CONSTANTS
*/ */
Expand Down Expand Up @@ -37,30 +38,79 @@ public class MacroNameArity {
* Elixir macros are defined as Erlang functions that take an extra Caller argument, so the exportArity is +1 for * 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 * macros compared to this arity, which is the number of parameters to the call definition head passed to
* {@code defmacro}. * {@code defmacro}.
*
* Only {@code null} if {@code exportArity} is {@code null}, which would indicate a malformed {@code .beam}.
*/ */
@Nullable @NotNull
public final Integer arity; public final Integer arity;


/* /*
* Constructors * Constructors
*/ */


public MacroNameArity(@NotNull String exportName, @Nullable Integer exportArity) { public MacroNameArity(@NotNull String exportName, int exportArity) {
if (exportName.startsWith(MACRO_EXPORT_PREFIX)) { if (exportName.startsWith(MACRO_EXPORT_PREFIX)) {
macro = DEFMACRO; macro = DEFMACRO;
name = exportName.substring(MACRO_EXPORT_PREFIX.length()); name = exportName.substring(MACRO_EXPORT_PREFIX.length());

arity = exportArity - 1;
if (exportArity != null) {
arity = exportArity - 1;
} else {
arity = null;
}
} else { } else {
macro = DEF; macro = DEF;
name = exportName; name = exportName;
arity = exportArity; 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);
}
} }
43 changes: 26 additions & 17 deletions src/org/elixir_lang/beam/chunk/Exports.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Pair;
import gnu.trove.THashSet; import gnu.trove.THashSet;
import org.elixir_lang.beam.MacroNameArity;
import org.elixir_lang.beam.chunk.exports.Export; import org.elixir_lang.beam.chunk.exports.Export;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -67,28 +68,36 @@ public static Exports from(@NotNull Chunk chunk) {
return exports; return exports;
} }


public Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>> exportByArityByName(@NotNull Atoms atoms) { /**
SortedMap<String, SortedMap<Integer, Export>> exportByArityByName = * Set of {@link MacroNameArity} sorted as
new TreeMap<String, SortedMap<Integer, Export>>(); * 1. {@link MacroNameArity#macro}, so that {@code defmacro} is before {@code def}
SortedSet<Export> nameless = new TreeSet<Export>(); * 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) { for (Export export : exportCollection) {
String name = export.name(atoms); String exportName = 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);
}


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();
} }
} }
21 changes: 3 additions & 18 deletions src/org/elixir_lang/beam/psi/BeamFileImpl.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -132,25 +132,10 @@ private static void buildCallDefinitions(@NotNull ModuleStub parentStub, @NotNul
Exports exports = beam.exports(); Exports exports = beam.exports();


if (exports != null) { if (exports != null) {
Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>> SortedSet<MacroNameArity> macroNameAritySortedSet = exports.macroNameAritySortedSet(atoms);
exportByArityByNameNamelessExports = exports.exportByArityByName(atoms);


SortedMap<String, SortedMap<Integer, Export>> exportByArityByName = for (MacroNameArity macroNameArity : macroNameAritySortedSet) {
exportByArityByNameNamelessExports.first; buildCallDefinition(parentStub, macroNameArity);

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);
}
}
} }
} }
} }
Expand Down
68 changes: 34 additions & 34 deletions tests/org/elixir_lang/beam/BeamTest.java
Original file line number Original file line Diff line number Diff line change
@@ -1,20 +1,16 @@
package org.elixir_lang.beam; package org.elixir_lang.beam;


import com.ericsson.otp.erlang.OtpErlangDecodeException; import com.ericsson.otp.erlang.OtpErlangDecodeException;
import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Condition;
import gnu.trove.THashSet; import com.intellij.util.containers.ContainerUtil;
import org.elixir_lang.beam.chunk.Atoms; import org.elixir_lang.beam.chunk.Atoms;
import org.elixir_lang.beam.chunk.Exports; import org.elixir_lang.beam.chunk.Exports;
import org.elixir_lang.beam.chunk.exports.Export;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;


import java.io.*; import java.io.*;
import java.util.Map; import java.util.*;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;


import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
Expand All @@ -41,27 +37,28 @@ public void elixirModule() throws IOException, OtpErlangDecodeException {


assertNotNull(exports); assertNotNull(exports);


Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>> exportByArityByNameNamelessExportSet = int exportCount = exports.size();
exports.exportByArityByName(atoms);


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; List<MacroNameArity> nodes = ContainerUtil.filter(

macroNameAritySortedSet,
// a name with multiple arities new Condition<MacroNameArity>() {
SortedMap<Integer, Export> exportByArity = exportByArityByName.get("node"); @Override

public boolean value(MacroNameArity macroNameArity) {
assertNotNull(exportByArity); return "node".equals(macroNameArity.name);
}
}
);


Set<Integer> expectedAritySet = new THashSet<Integer>(); assertEquals(nodes.size(), 2);
expectedAritySet.add(0);
expectedAritySet.add(1);


assertEquals("node arities do not match", expectedAritySet, exportByArity.keySet()); assertEquals(0, (int) nodes.get(0).arity);
assertEquals(1, (int) nodes.get(1).arity);
} }


@Test @Test
Expand All @@ -80,24 +77,27 @@ public void erlangModule() throws IOException, OtpErlangDecodeException {


assertNotNull(exports); assertNotNull(exports);


Pair<SortedMap<String, SortedMap<Integer, Export>>, SortedSet<Export>> exportByArityByNameNamelessExportSet = int exportCount = exports.size();
exports.exportByArityByName(atoms);


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; List<MacroNameArity> extracts = ContainerUtil.filter(

macroNameAritySortedSet,
Map<Integer, Export> exportByArity = exportByArityByName.get("extract"); new Condition<MacroNameArity>() {

@Override
assertNotNull(exportByArity); 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 { private Beam beam(@NotNull String baseName) throws IOException, OtpErlangDecodeException {
Expand Down
5 changes: 5 additions & 0 deletions tests/org/elixir_lang/beam/DecompilerTest.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public void testIssue575() throws IOException, OtpErlangDecodeException {
assertEquals( assertEquals(
"# Source code recreated from a .beam file by IntelliJ Elixir\n" + "# Source code recreated from a .beam file by IntelliJ Elixir\n" +
"defmodule Bitwise do\n" + "defmodule Bitwise do\n" +
"\n" +
" # Macros\n" +
"\n" +
" defmacro left &&& right do\n" + " defmacro left &&& right do\n" +
" # body not decompiled\n" + " # body not decompiled\n" +
" end\n" + " end\n" +
Expand Down Expand Up @@ -84,6 +87,8 @@ public void testIssue575() throws IOException, OtpErlangDecodeException {
" # body not decompiled\n" + " # body not decompiled\n" +
" end\n" + " end\n" +
"\n" + "\n" +
" # Functions\n" +
"\n" +
" def __info__(p0) do\n" + " def __info__(p0) do\n" +
" # body not decompiled\n" + " # body not decompiled\n" +
" end\n" + " end\n" +
Expand Down

0 comments on commit e66a4d8

Please sign in to comment.