Print a Variable's Name Write a function (not a full program), so that if the function is called with a single global variable (or your language's closest equivalent) as its argument, it outputs (i.e. prints or returns) the name of that variable. If the argument is not a variable, output a falsey value instead. (You don't have to handle the case where the argument is a variable, but not global.)

There must not be a compile-time connection between the function call and function definition (notably, the function definition cannot be a macro or similar construct that receives arguments in the form of a literal fragment of source code, neither in text nor abstract syntax tree form). That is: in languages which support separate compilation, the program must work even if the function call is compiled first (with no knowledge of the function definition but possibly a type signature or equivalent), then the function definition is compiled afterwards. If the language doesn't have separate compilation, you must nonetheless aim for a similar separation between the call and definition.

If using a compiled language, you may read the compiled form of the complete program from disk, and may assume that the program was compiled with debug information. (Thus, solutions that work by attaching a debugger from a program to itself are allowed.)

Note that this task may not be possible in every language.

Examples:

var apple = "Hello"
p(apple) // apple

var nil = "Nothing"
p(nil) // nil

var SOMETHING = 69
p(SOMETHING) // SOMETHING

function foo(){}
p(foo) // foo

p(3.14159) // false

p(function foo(){}) // false
Java

String getParamName(String param) throws Exception {
    StackTraceElement[] strace = new Exception().getStackTrace();
    String methodName = strace[0].getMethodName();
    int lineNum = strace[1].getLineNumber();

    String className = strace[1].getClassName().replaceAll(".{5}$", "");
    String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";

    StringWriter javapOut = new StringWriter();
    com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
    List&lt;String&gt; javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
    int byteCodeStart = -1;
    Map&lt;Integer, Integer&gt; byteCodePointerToJavaPLine = new HashMap&lt;Integer, Integer&gt;();
    Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
    for (int n = 0;n &lt; javapLines.size();n++) {
        String javapLine = javapLines.get(n);
        if (byteCodeStart &gt; -1 &amp;&amp; (javapLine == null || "".equals(javapLine))) {
            break;
        }
        Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
        if (byteCodeIndexMatcher.find()) {
            byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
        } else if (javapLine.contains("line " + lineNum + ":")) {
            byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
        }
    }

    int varLoadIndex = -1;
    int varTableIndex = -1;
    for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i &lt; javapLines.size();i++) {
        if (varLoadIndex &lt; 0 &amp;&amp; javapLines.get(i).contains("Method " + methodName + ":")) {
            varLoadIndex = i;
            continue;
        }

        if (varLoadIndex &gt; -1 &amp;&amp; javapLines.get(i).contains("LocalVariableTable:")) {
            varTableIndex = i;
            break;
        }
    }

    String loadLine = javapLines.get(varLoadIndex - 1).trim();
    int varNumber;
    try {
        varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
    } catch (NumberFormatException e) {
        return null;
    }
    int j = varTableIndex + 2;
    while(!"".equals(javapLines.get(j))) {
        Matcher varName = Pattern.compile("\\s*" + varNumber + "\\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));  
        if (varName.find()) {
            return varName.group(1);
        }
        j++;
    }
    return null;
}


This currently works with a few gotchas:


If you use an IDE to compile this it might not work unless it is run as Admin (depending on where the temporary class files are saved)
You must compile using javac with the -g flag. This generates all debugging information including local variable names in the compiled class file.
This uses an internal Java API com.sun.tools.javap which parses the bytecode of a classfile and produces a human readable result. This API is only accessible in the JDK libraries so you must either use the JDK java runtime or add tools.jar to your classpath.


This should now work even if the method is called multiple times in the program. Unfortunately it does not yet work if you have multiple invocations on a single line. (For one that does, see below)

Try it online!



Explanation

StackTraceElement[] strace = new Exception().getStackTrace();
String methodName = strace[0].getMethodName();
int lineNum = strace[1].getLineNumber();

String className = strace[1].getClassName().replaceAll(".{5}$", "");
String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";


This first part gets some general information about what class we're in and what the name of the function is. This is accomplished by creating an exception and parsing the first 2 entries of the stack trace. 

java.lang.Exception
    at E.getParamName(E.java:28)
    at E.main(E.java:17)


The first entry is the line that the exception is thrown on which we can grab the methodName from and the second entry is where the function was called from.

StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));


In this line we are executing the javap executable that comes with the JDK. This program parses the class file (bytecode) and presents a human-readable result. We'll use this for rudimentary "parsing".

List&lt;String&gt; javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
int byteCodeStart = -1;
Map&lt;Integer, Integer&gt; byteCodePointerToJavaPLine = new HashMap&lt;Integer, Integer&gt;();
Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
for (int n = 0;n &lt; javapLines.size();n++) {
    String javapLine = javapLines.get(n);
    if (byteCodeStart &gt; -1 &amp;&amp; (javapLine == null || "".equals(javapLine))) {
        break;
    }
    Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
    if (byteCodeIndexMatcher.find()) {
        byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
    } else if (javapLine.contains("line " + lineNum + ":")) {
        byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
    }
}


We're doing a couple different things here. First, we are reading the javap output line by line into a list. Second we are creating a map of bytecode line indexes to javap line indexes. This helps us later to determine which method invocation we want to analyze. Finally we are using the known line number from the stack trace to determine which bytecode line index we want to be looking at.

int varLoadIndex = -1;
int varTableIndex = -1;
for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i &lt; javapLines.size();i++) {
    if (varLoadIndex &lt; 0 &amp;&amp; javapLines.get(i).contains("Method " + methodName + ":")) {
        varLoadIndex = i;
        continue;
    }

    if (varLoadIndex &gt; -1 &amp;&amp; javapLines.get(i).contains("LocalVariableTable:")) {
        varTableIndex = i;
        break;
    }
}


Here we are iterating over the javap lines one more time in order to find the spot where our method is being invoked and where the Local Variable Table starts. We need the line where the method is invoked because the line before it contains the call to load the variable and identifies which variable (by index) to load. The Local Variable Table helps us actually look up the name of the variable based on the index we grabbed.

String loadLine = javapLines.get(varLoadIndex - 1).trim();
int varNumber;
try {
    varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
} catch (NumberFormatException e) {
    return null;
}


This part is actually parsing the load call to get the variable index. This can throw an exception if the function isn't actually called with a variable so we can return null here.

int j = varTableIndex + 2;
while(!"".equals(javapLines.get(j))) {
    Matcher varName = Pattern.compile("\\s*" + varNumber + "\\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));  
    if (varName.find()) {
        return varName.group(1);
    }
    j++;
}
return null;


Finally we parse out the name of the variable from the line in the Local Variable Table. Return null if it isn't found although I've seen no reason why this should happen.

Putting it all together

 public static void main(java.lang.String[]);
    Code:
...
      18: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
      21: aload_1
      22: aload_2
      23: invokevirtual #25                 // Method getParamName:(Ljava/lang/String;)Ljava/lang/String;
...
    LineNumberTable:
...
      line 17: 18
      line 18: 29
      line 19: 40
...
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      83     0  args   [Ljava/lang/String;
          8      75     1     e   LE;
         11      72     2   str   Ljava/lang/String;
         14      69     3  str2   Ljava/lang/String;
         18      65     4  str4   Ljava/lang/String;
         77       5     5    e1   Ljava/lang/Exception;


This is basically what we're looking at. In the example code the first invocation is line 17. line 17 in the LineNumberTable shows that the beginning of that line is bytecode line index 18. That is the System.out load. Then we have aload_2 right before the method call so we look for the variable in slot 2 of the LocalVariableTable which is str in this case.



For fun, here's one which handles multiple function calls on the same line. This causes the function to not be idempotent but that's kind of the point. Try it online!