Variablennamen drucken [geschlossen]

20

Schreiben Sie eine Funktion (kein vollständiges Programm), sodass beim Aufruf der Funktion mit einer einzelnen globalen Variablen (oder dem nächstgelegenen Äquivalent Ihrer Sprache) als Argument der Name dieser Variablen ausgegeben (dh gedruckt oder zurückgegeben) wird. Wenn das Argument keine Variable ist, geben Sie stattdessen einen Falsey-Wert aus. (Sie müssen nicht den Fall behandeln, in dem das Argument eine Variable ist, aber nicht global.)

Zwischen dem Funktionsaufruf und der Funktionsdefinition darf während der Kompilierung keine Verbindung bestehen (insbesondere darf die Funktionsdefinition kein Makro oder ähnliches Konstrukt sein, das Argumente in Form eines wörtlichen Quellcodefragments empfängt, weder in Text noch in Form eines abstrakten Syntaxbaums bilden). Das heißt: In Sprachen, die eine separate Kompilierung unterstützen, muss das Programm auch dann funktionieren, wenn der Funktionsaufruf zuerst kompiliert wird (ohne Kenntnis der Funktionsdefinition, aber möglicherweise einer Typensignatur oder einer Entsprechung). Anschließend wird die Funktionsdefinition kompiliert. Wenn die Sprache keine separate Zusammenstellung hat, müssen Sie dennoch eine ähnliche Trennung zwischen Aufruf und Definition anstreben.

Wenn Sie eine kompilierte Sprache verwenden, können Sie die kompilierte Form des vollständigen Programms von der Festplatte lesen und davon ausgehen, dass das Programm mit Debug-Informationen kompiliert wurde. (Daher sind Lösungen zulässig, bei denen ein Debugger aus einem Programm an sich selbst angehängt wird.)

Beachten Sie, dass diese Aufgabe möglicherweise nicht in jeder Sprache möglich ist.

Beispiele:

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
Caleb Kleveter
quelle
Kommentare sind nicht für eine längere Diskussion gedacht. Diese Unterhaltung wurde in den Chat verschoben .
Dennis

Antworten:

31

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<String> javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
    int byteCodeStart = -1;
    Map<Integer, Integer> byteCodePointerToJavaPLine = new HashMap<Integer, Integer>();
    Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
    for (int n = 0;n < javapLines.size();n++) {
        String javapLine = javapLines.get(n);
        if (byteCodeStart > -1 && (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 < javapLines.size();i++) {
        if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) {
            varLoadIndex = i;
            continue;
        }

        if (varLoadIndex > -1 && 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;
}

Dies funktioniert derzeit mit ein paar Fallstricken:

  1. Wenn Sie zum Kompilieren eine IDE verwenden, funktioniert diese möglicherweise nur, wenn sie als Administrator ausgeführt wird (je nachdem, wo die temporären Klassendateien gespeichert sind).
  2. Sie müssen javacmit dem -gFlag kompilieren . Dadurch werden alle Debugging-Informationen einschließlich der lokalen Variablennamen in der kompilierten Klassendatei generiert.
  3. Hierbei wird eine interne Java-API verwendet, com.sun.tools.javapdie den Bytecode einer Klassendatei analysiert und ein für den Menschen lesbares Ergebnis liefert. Auf diese API kann nur in den JDK-Bibliotheken zugegriffen werden. Sie müssen daher entweder die JDK-Java-Laufzeit verwenden oder tools.jar zu Ihrem Klassenpfad hinzufügen.

Dies sollte nun auch dann funktionieren, wenn die Methode im Programm mehrfach aufgerufen wird. Leider funktioniert es noch nicht, wenn Sie mehrere Aufrufe in einer einzigen Zeile haben. (Für eine, die das tut, siehe unten)

Probieren Sie es online!


Erläuterung

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";

In diesem ersten Teil erhalten Sie allgemeine Informationen darüber, in welcher Klasse wir uns befinden und wie die Funktion heißt. Dies wird erreicht, indem eine Ausnahme erstellt und die ersten 2 Einträge des Stack-Trace analysiert werden.

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

Der erste Eintrag ist die Zeile, in der die Ausnahme ausgelöst wird, von der wir den Methodennamen abrufen können, und der zweite Eintrag ist die Zeile, von der aus die Funktion aufgerufen wurde.

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

In dieser Zeile führen wir die im JDK enthaltene ausführbare Java-Datei aus. Dieses Programm analysiert die Klassendatei (Bytecode) und zeigt ein für Menschen lesbares Ergebnis an. Wir werden dies zum rudimentären "Parsen" verwenden.

List<String> javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
int byteCodeStart = -1;
Map<Integer, Integer> byteCodePointerToJavaPLine = new HashMap<Integer, Integer>();
Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
for (int n = 0;n < javapLines.size();n++) {
    String javapLine = javapLines.get(n);
    if (byteCodeStart > -1 && (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));
    }
}

Wir machen hier ein paar verschiedene Dinge. Zunächst lesen wir die Ausgabe von javap zeilenweise in eine Liste ein. Zweitens erstellen wir eine Zuordnung von Bytecode-Zeilenindizes zu Java-Zeilenindizes. Dies hilft uns später zu bestimmen, welchen Methodenaufruf wir analysieren möchten. Schließlich verwenden wir die bekannte Zeilennummer aus dem Stack-Trace, um zu bestimmen, welchen Bytecode-Zeilenindex wir betrachten möchten.

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

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

Hier iterieren wir noch einmal über die Java-Zeilen, um die Stelle zu finden, an der unsere Methode aufgerufen wird und an der die lokale Variablentabelle beginnt. Wir benötigen die Zeile, in der die Methode aufgerufen wird, da die Zeile vor ihr den Aufruf zum Laden der Variablen enthält und angibt, welche Variable (nach Index) geladen werden soll. Mithilfe der lokalen Variablentabelle können wir den Namen der Variablen anhand des von uns erfassten Index nachschlagen.

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;
}

Dieser Teil analysiert tatsächlich den Ladeaufruf, um den Variablenindex zu erhalten. Dies kann eine Ausnahme auslösen, wenn die Funktion nicht tatsächlich mit einer Variablen aufgerufen wird, sodass hier null zurückgegeben werden kann.

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;

Schließlich analysieren wir den Namen der Variablen aus der Zeile in der lokalen Variablentabelle. Gibt null zurück, wenn es nicht gefunden wird, obwohl ich keinen Grund gesehen habe, warum dies passieren sollte.

Alles zusammen

 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;

Dies ist im Grunde, was wir suchen. Im Beispielcode lautet der erste Aufruf Zeile 17. Zeile 17 in der LineNumberTable zeigt, dass der Anfang dieser Zeile der Bytecode-Zeilenindex 18 ist. Das ist die System.outLast. Dann müssen wir aload_2direkt vor dem Methodenaufruf nach der Variablen in Slot 2 der LocalVariableTable suchen, was strin diesem Fall der Fall ist.


Hier ist zum Spaß einer, der mehrere Funktionsaufrufe auf derselben Leitung verarbeitet. Dies führt dazu, dass die Funktion nicht idempotent ist, aber genau darum geht es. Probieren Sie es online!

Sack
quelle
1
Liebe diese Antwort. Dachte an etwas in der gleichen Richtung. Ein Hinweis: Wenn Sie mehrere Aufrufe in dieselbe Zeile des Programms aufnehmen, kann nicht festgestellt werden, welcher die Methode aufruft (nicht sicher, ob dies mit Ihrem aktuellen Ansatz behoben werden kann). Versuchen Sie beispielsweise, System.out.println(e.getParamName(str));System.out.println(e.getParamName(str2));System.out.println(e.getParamName(str4));auf zu verschieben eine Zeile im TIO-Link.
PunPun1000
Sie können die abrufen javapLage wie folgt aus : Paths.get(System.getProperty("java.home"), "bin", "javap").
Olivier Grégoire
@ OlivierGrégoire Danke, aber ich habe Java über die interne Java-API aufgerufen, damit ich nicht mehr den genauen Speicherort auf der Festplatte im Code (nur den Klassenpfad) abrufen muss
Poke
@ PunPun1000 Ja, ich bin mir nicht sicher, ob es einen guten Weg gibt, das zu beheben, während eine idempotente Funktion beibehalten wird, aber ich könnte etwas zusammenstellen, das nur zum Spaß einen Zustand außerhalb der Funktion verwendet.
Poke
1
@ PunPun1000 Es wurde eine Version hinzugefügt, die mehrere Anrufe auf einer Leitung verarbeiten kann. Es ist mehr als nur die Funktion, also habe ich nur einen weiteren TryItOnline-Link hinzugefügt, anstatt die Antwort zu aktualisieren.
Poke
14

Python 2

Dies ist der schmutzigste Code, den ich geschrieben habe, aber er funktioniert. ¯ \ _ (ツ) _ / ¯ Wirft einen Fehler auf eine nicht existierende Variable, da Python Sie sofort nicht mag, wenn Sie die Funktion mit einer aufrufen. Wirft auch einen Fehler auf Nicht-Variablen, aber dies kann mit einem Versuch / außer bei Bedarf behoben werden.

import inspect
import re

def name_of(var):
    for i in inspect.getframeinfo(inspect.currentframe().f_back)[3]:
        return re.search(r'\bname_of\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)', i).groups()[0]

Probieren Sie es online!

Wenn wir das Argument als Zeichenfolge verwenden dürfen, erfüllt dies die Anforderungen für die Ausgabe eines falschen Werts für eine ungültige Eingabe.

import inspect
import re

def name_of(var):
    # return var :P

    try:
        eval(var)
    except NameError:
        return False

    for i in inspect.getframeinfo(inspect.currentframe().f_back)[3]:
        try:
            return re.search(r'\bname_of\s*\(\s*[\'"]([A-Za-z_][A-Za-z0-9_]*)[\'"]\s*\)', i).groups()[0]
        except AttributeError:
            return False

Probieren Sie es online!

total menschlich
quelle
Sie haben fast die gleiche Antwort gepostet, als ich meine Erklärung
Dead Possum
1
@DeadPossum Posten und beantworten Sie immer und fügen Sie später eine Erklärung als Bearbeitung hinzu. ;)
Arjun
8
@Arjun, oh toll, also müssen wir EditGolf und CodeGolf spielen: P
Wossname
12

Mathematica

f[x_] := ValueQ @ x && ToString @ HoldForm @ x
SetAttributes[f, HoldFirst]

Das HoldFirstAttribut verhindert, dass fsein Argument ausgewertet wird, bevor die Funktion aufgerufen wird. ValueQ @ xprüft dann, ob das angegebene Argument eine Variable ist, der ein Wert zugewiesen wurde. Wenn nicht, kehren wir nur Falsewegen eines Kurzschlusses zurück. Ansonsten erhalten wir den Variablennamen mit ToString @ HoldForm @ x.

Martin Ender
quelle
Verdammt, Andich missbrauche, wie Mathematica damit umgeht ... Ich mag, dass das richtige Argument von Andnicht einmal ein boolescher Ausdruck sein muss.
JungHwan Min
Ich weiß, es ist kein Codegolf, aber warum nicht einfach f[x_] := ValueQ @ x && HoldForm @ x? Wenn es nicht erforderlich wäre, zu überprüfen, ob die Eingabe eine Variable ist, HoldFormwürde dies alleine funktionieren.
Genisis
HoldAllstatt HoldFirst?
Greg Martin
1
@ngenisis Weil das zurückkommt HoldForm[a]und nicht "a". Während sie im Mathematica-Notizbuch gleich angezeigt werden, gibt es die Funktion unabhängig vom Frontend. Daher wollte ich eine Lösung, die einen String zurückgibt.
Martin Ender
1
Anfrage zum Entfernen von Leerzeichen, weil ... um ... weil
CalculatorFeline
7

Mathematica

f~SetAttributes~HoldAll;
f[_] = False;
f[x_Symbol?ValueQ] := SymbolName@Unevaluated@x;

Mathematica bewertet gerne alles . Um aufzuhören, müssen wir dagegen ankämpfen. In seiner eigenen Sprache.

Wie?

f~SetAttributes~HoldAll;

Sagt Mathematica, dass beim fAufruf einer Funktion deren Argumente nicht ausgewertet (dh festgehalten) werden sollen.


f[_] = False;

Wenn fmit einem Argument aufgerufen wird, wird ausgegeben False. (?!)


f[x_Symbol?ValueQ] := SymbolName@Unevaluated@x;

Wenn fmit einem definierten Symbol (das einen Wert hat) aufgerufen wird, geben Sie den Symbolnamen der Eingabe aus.

Die vorherige Zeile hat keinen Vorrang, obwohl sie zuvor definiert wurde, da diese Definition spezifischer ist. In der Wolfram-Dokumentation heißt es: Mathematica "versucht, bestimmte Definitionen vor allgemeinere zu setzen."

Mathematica ist sehr eigensinnig und versucht nach Möglichkeit, Variablen auszuwerten. Dafür Unevaluatedsorgt das Extra .

JungHwan min
quelle
7

C #

string n<T>(Expression<Func<T>>m) =>
    (m.Body as MemberExpression)?.Member?.Name ?? null;

Voll / Formatierte Version:

using System;
using System.Linq.Expressions;

class P
{
    static void Main()
    {
        var apple = "Hello";
        var nil = "Nothing";
        var SOMETHING = 69;

        Console.WriteLine(n(() => apple));
        Console.WriteLine(n(() => nil));
        Console.WriteLine(n(() => SOMETHING));
        Console.WriteLine(n(() => 3.14159));

        Console.ReadLine();
    }

    static string n<T>(Expression<Func<T>>m)
        => (m.Body as MemberExpression)?.Member?.Name ?? null;
}

Eine andere Möglichkeit, dies zu tun, besteht darin, den Typ wie folgt zu reflektieren:

public static string GetParameterName<T>(T item)
{
    var properties = typeof(T).GetProperties();

    return properties.Length > 0 ? properties[0].Name : null;
}

Sie müssen es jedoch so nennen:

GetParameterName(new { apple });

Es schlägt dann auch fehl, 3.14159indem Sie zurückkehren, Lengthund dafür müssen Sie es stattdessen auch wie folgt aufrufen:

GetParameterName(new double[]{ 3.14159 });

In C # 6.0 haben wir auch:

nameof

Dies wird jedoch nicht kompiliert, wenn Sie etwas übergeben, das keine Variable ist, z 3.14159. Ich glaube, es wertet auch die Eingabe zur Kompilierungszeit aus, so dass es den Anschein hat, als ob diese Anforderung ebenfalls nicht erfüllt wird.

TheLethalCoder
quelle
Das ist ein bisschen zwielichtig ... weil das Lambda-Zeug absolut notwendig ist (dh Sie können die Variable nicht einfach übergeben, Sie müssen etwas übergeben, das das Compile erkennen wird, wenn es eine werden soll Expression). Sie sollten auch zählen using System.Linq.Expressions;.
VisualMelon
Gilt dies jedoch als "Empfangen von Argumenten in Form eines abstrakten Syntaxbaums"?
Matti Virkkunen
@VisualMelon Ich konnte keinen anderen Weg finden, um dies in C # zu tun, also habe ich es so gemacht. Auch das ist kein Code-Golf, also spielt es keine Rolle,
TheLethalCoder
... sorry, habe nicht gemerkt, dass es keine Byteanzahl gibt!
VisualMelon
6

MATLAB

varname = @(x) inputname(1);

Innerhalb einer Funktion gibt es einige voreingestellte Variablen wie varargin oder nargin, unter denen wir auch haben inputname.

von hier gestohlen

fehlerhaft
quelle
Ich wusste nie, dass es das gibt. Ich habe ein bisschen damit gespielt, und vielleicht wird dir dieses Juwel x=42;y=54; f=@(x)eval(inputname(1));f(x),f(y)
gefallen
Haha, eval==evil= D (es funktioniert tatsächlich mit x=42;y=54; f=@(x)eval('inputname(1)');f(x),f(y))
Fehler
Ja, es ist nur so, dass eine anonyme Funktion keine Arbeitsbereichsvariable kennt, so dass eval nur auswerten xkann und dann evaldas Funktionsargument ist, nicht die Arbeitsbereichsvariable.
Sanchises
6

R

function(x) if (identical(substitute(x), x)) FALSE else substitute(x)

substituteGibt den Analysebaum für einen nicht bewerteten Ausdruck zurück. Die identicalBedingung stellt sicher, dass dieser nicht bewertete Ausdruck nicht mit dem Ausdruck selbst identisch ist. dh dass der übergebene Parameter kein Literal ist.

mb7744
quelle
4

Python 2

Einige Untersuchungen wurden durchgeführt. Und es scheint in Python möglich zu sein, aber ich erwarte immer noch, dass einige Probleme gefunden werden.
Die Lösung ist nicht perfekt, da sie eine gewisse Struktur des Codes annimmt. Trotzdem habe ich es nicht noch gebrochen.

import inspect
import re

def get_name(var):
    called = inspect.getouterframes(inspect.currentframe())[1][4][0]
    return re.search('(?<=get_name\().*(?=\))', called).group().lstrip().rstrip()

Dies verwendet inspect, um das Surround-Oszilloskop zu untersuchen und festzustellen, wo die Funktion get_nameaufgerufen wird. Zum Beispiel inspect...[1]wird zurückkehren

(< frame object at 0x01E29030>, 'C:/Users/---/PycharmProjects/CodeGolf/Name_var.py', 14, '< module>', ['print get_name( a )\n'], 0)

Und inspect...[1][4]zeigt uns eine Liste mit Code, in der die Funktion aufgerufen wird:

['print get_name( a )\n']

Danach habe ich Regex verwendet, um den Namen des Arguments abzurufen

re.search('(?<=get_name\().*(?=\))', called).group()

Und .lstrip().rstrip()um alle Leerzeichen zu entfernen, die in Klammern gesetzt werden können.

Beispiel mit einigen kniffligen Eingaben

Totes Opossum
quelle
4

Power Shell

function t($t){(variable($MyInvocation.Line.Split(' $'))[-1]-ea si).Name}

$apple = 'hi'
t $apple
apple

t 3.141

Aber es funktioniert nicht zuverlässig, es ist rudimentär.

TessellatingHeckler
quelle
Dies ist kein Code Golf.
Okx
@Okx oh, hoppla, also ist es nicht.
TessellatingHeckler
4

PHP

Erfordert, dass der Variablenwert im Array der globalen Variablen eindeutig ist

function v($i){echo"$".array_search($i,$GLOBALS);}

Probieren Sie es online!

PHP , 96 Bytes

function v($i){
echo($i==end($g=$GLOBALS))?"$".key(array_slice($g,-1)):0;
}
$hello=4;
v($hello);

Probieren Sie es online!

Benötigt, dass die Variable direkt auf den Funktionsaufruf initialisiert wird.

Überprüft, ob die letzte globale Variable gleich der übergebenden Variable ist

Jörg Hülsermann
quelle
Dies ist kein Code Golf.
Okx
@Okx Ich weiß Im Moment habe ich keine bessere Idee.
Jörg Hülsermann
4

JavaScript (ES6)

Versuchen Sie, für alle Eigenschaftsnamen in window( Object.getOwnPropertyNames(w)) einen Getter für diese Eigenschaft zu definieren, der den Eigenschaftsnamen zurückgibt.

Fügen Sie dann einer Map einen Eintrag hinzu, Mbei dem der Schlüssel der (möglicherweise überschriebene) Wert der Eigenschaft und der Wert der Name der Eigenschaft ist.

Die Funktion fnimmt einfach eine Variable (dh eine Eigenschaft von window) und gibt den Eintrag Mfür diesen Wert zurück.

let
    w = window,
    M = new Map(),
    L = console.log,
    P = Object.getOwnPropertyNames(w),
    D = Object.defineProperty

for(const p of P){
    try {
        D(w, p, {
            get(){
                return p
            }
        })
    } catch(e){ L(e) }

    try {
        M.set(w[p], p)
    } catch(e){ L(e) }
}

let f = v => M.get(v)

Dies funktioniert für alle eingebauten globalen Variablen außer für sich windowselbst, da es keinen Unterschied gibt top(es sei denn, sie werden in einem Frame ausgeführt):

L( P.filter(p => p !== f(w[p])) )
// -> ['window']
darrylyeo
quelle
Aus irgendeinem Grund erhalte ich eine Fehlermeldung: L not a function. Warum sollte das passieren?
Caleb Kleveter
Wow! Das ist tiefer in ES6 als ich in einer Weile gesehen habe.
MD XF
@CalebKleveter D'oh! Ich habe ein Komma in der zweiten Zeile vergessen. Das kann problematisch sein oder auch nicht.
darrylyeo
3

Perl 5 + Devel :: Caller

use 5.010;
use Devel::Caller qw/caller_vars/;
use Scalar::Util qw/refaddr/;

sub v {
   # find all operands used in the call, formatted as variable names
   my @varsused = caller_vars(0,1);
   scalar @varsused == 1 or return; # exactly one operand, or return false
   $varsused[0] =~ s/^\$// or return; # the operand actually is a variable
   # it's possible we were given an expression like "~$test" which has only
   # one operand, but is not a variable in its own right; compare memory
   # addresses to work this out
   refaddr \ ($_[0]) == refaddr \ (${$varsused[0]}) or return;
   return '$' . $varsused[0];
}

# some test code

our $test = 1;
our $test2 = 2;
our $test3 = 3;

say v($test2);
say v(2);
say v(~$test3);
say v($test);
say v($test + 1);
say v(++$test);
say v($test3);

Wir verwenden Devel :: Caller (ein Debugger-ähnliches Modul), um den Aufruf-Stack zu durchsuchen, nach dem Aufruf der Funktion zu suchen und alle Operanden innerhalb des Arguments als Variablennamen zurückzugeben. Wenn es mehr (oder weniger) als einen Operanden gibt, wurden wir nicht mit einer Variablen aufgerufen. Wenn der Operand keine Variable wäre, hätte er keinen Namen, und das können wir auch feststellen.

Der schwierigste Fall ist, wenn wir einen Ein-Operanden-Ausdruck mit einer Variablen erhalten, wie z ~$x. Wir können herausfinden, ob dies aufgetreten ist, indem wir einen Verweis auf die Variable direkt aus der Symboltabelle (unter Verwendung der ${…}symbolischen Referenzsyntax) nehmen und ihre Speicheradresse mit dem Wert vergleichen, den wir als Argument übergeben haben (was günstigerweise als Verweis übergeben wird) ). Wenn sie unterschiedlich sind, haben wir eher einen Ausdruck als eine einzelne Variable.

Beachten Sie, dass wir, wenn wir diese Funktion mit einem Vorinkrementierungs- oder Vordekrementierungsausdruck für eine einzelne Variable wie in aufrufen v(--$x), $xzurückgegeben werden. Dies liegt daran, dass in diesem Fall tatsächlich die Variable selbst an die Funktion übergeben wird. es wird nur vorher inkrementiert oder dekrementiert. Ich hoffe das disqualifiziert die Antwort nicht. (In gewisser Weise macht es es besser, weil es zeigt, dass wir das Argument selbst überprüfen, anstatt nur den Quellcode zu lesen.)


quelle
3

PHP

Während die anderen PHP-Übermittlungen nur testen, ob der angegebene Wert mit einem Wert eines globalen übereinstimmt, funktioniert diese Version, indem sie auf den Wert verweist:

// take a reference to the global variable
function f(&$i){
  foreach(array_reverse($GLOBALS) as $key => $value)
    if($key != 'GLOBALS') {
      // Set the value of each global to its name
      $GLOBALS[$key] = $key;
    }

  foreach($GLOBALS as $key => $value)
    if($key != 'GLOBALS' && $key != $value) {
      // The values mismatch so it had a reference to another value
      // we delete it
      unset($GLOBALS[$key]);
      // and update the value to its name again
      $GLOBALS[$key] = $key;
    }

  echo '$', is_array($i) ? 'GLOBALS' : $i, "\n";
}

Dies sollte jetzt auch dann funktionieren, wenn die globale Variable zum Zeitpunkt des Aufrufs eine Referenz auf einen anderen Wert ist.

Teste es hier .

Christoph
quelle
Großartig und vielen Dank für die Lernanstrengung
Jörg Hülsermann
@ JörgHülsermann Hab sogar einen Weg gefunden, es zu verbessern!
Christoph
3

Röda

f(&a...) {
	a() | name(_) | for str do
		false if [ "<" in str ] else [str]
	done
}

Probieren Sie es online!

Röda hat dafür eine eingebaute Funktion - name. Leider wird kein falscher Wert zurückgegeben, wenn keine Variable angegeben wird.

Dieser Code missbraucht mehrere Funktionen der Referenzsemantik. Erklärung Zeile für Zeile:

f(&a...) {

Die Funktion akzeptiert eine variable Anzahl von Referenzargumenten ( &a...). Nur so kann in Röda eine Referenzliste erstellt werden.

a() | name(_) | for str do

In der zweiten Zeile werden die Elemente von ain den Stream ( a()) verschoben . Diese Elemente sind Referenzen, wenn der Funktion Variablen und ansonsten Normalwerte gegeben wurden.

Die Elemente werden mit einer Unterstreichungsschleife iteriert. Die Unterstreichungssyntax ist Syntaxzucker für forSchleifen, name(_)entspricht also name(var) for var. Der Name der in der forSchleife verwendeten Variablen hat die Form, <sfvN>in der N variiert. (Dh. " name(<sfv1>) for <sfv1>") Es ist nicht zulässig, dass eine benutzerdefinierte Variable <oder enthält >, damit die generierten Namen nicht mit vorhandenen Variablennamen kollidieren.

Die name()Funktion gibt den Namen der angegebenen Variablen zurück. Wenn es sich bei dem Element a, das iteriert wird, um eine Referenz handelt, handelt es sich um die Variable, die an übergeben wird name. Wenn das Element ein normaler Wert war, ist die angegebene Variable namedie Unterstrichvariable <sfvN>. Dies liegt an der Semantik von Referenzen in Röda: Wenn eine Funktion eine Referenz akzeptiert und der Funktion eine Referenzvariable übergeben wird, zeigt der übergebene Wert nicht auf die Referenzvariable, sondern auf die Variable, auf die die Referenzvariable verweist.

false if [ "<" in str ] else [str]

In der dritten Zeile prüfen wir, ob der Variablenname im Stream ein <Zeichen enthält . In diesem Fall handelt es sich um eine Unterstrichvariable, und der übergebene Wert fwar keine Referenz. Ansonsten geben wir den Namen der Referenz aus.

Diese Lösung funktioniert nicht, wenn die der Funktion zugewiesene Variable eine Referenz- oder Unterstrichvariable ist. Die Frage gibt jedoch an, dass nur globale Variablen behandelt werden müssen und es sich in Röda nicht um Verweise oder Unterstreichungsvariablen handeln darf.

fergusq
quelle
1

Ruby , 46 Bytes

Fühlt sich an wie der schmutzigste Code, den ich je für Ruby geschrieben habe.

Erfordert, dass die von Ihnen aufgerufenen globalen Variablen einen eindeutigen Wert haben, der sich auf keiner anderen globalen Variablen befindet, da die einzige Möglichkeit, diese Herausforderung in Ruby durchzuführen, darin besteht, alle globalen Variablen zu durchsuchen und die erste Übereinstimmung zurückzugeben. OP sagt, dass es in Ordnung ist und es steht mir frei zu beurteilen, ob meine Lösung gültig ist.

Beachten Sie, dass globale Variablen mit $Ruby beginnen, wenn Sie zusätzliche Testfälle hinzufügen möchten.

->v{global_variables.find{|n|eval(n.to_s)==v}}

Probieren Sie es online!

Wert Tinte
quelle
1

PHP

function f($v){foreach($GLOBALS as$n=>$x)$x!=$v?:die($n);}

Wenn ein Wert gefunden wird, geben Sie den Variablennamen aus und beenden Sie das Programm. drucke nichts und beende sonst nicht.

61 Bytes zum Zurückgeben des Variablennamens oder NULL:

function f($v){foreach($GLOBALS as$n=>$x)if($x==$v)return$n;}

Es werden keine benannten Funktionen gefunden, nur die, die Variablen zugewiesen sind.
Und eine PHP-Funktion kann nicht erkennen, ob ein Parameter als Referenz oder als Wert angegeben wurde. Die Funktion gibt nur den Vornamen zurück, bei dem der Wert mit dem Funktionsparameterwert übereinstimmt.

Testen Sie es online

Titus
quelle
1

Power Shell

Neue Version funktioniert aber ab PowerShell 3.0

function p{[scriptblock]::create($myinvocation.line).ast.findall({$args[0]-is[Management.Automation.Language.VariableExpressionAst]},0)[0]|% v*h|% u*|%{($_,!1)[!$_]}}

Probieren Sie es online!

Vorherige Version

function p{$t=[management.automation.psparser]::tokenize($myinvocation.line,[ref]@())|? type -match '^[cv]'|? start|% content;($t,!1)[!$t]}

Probieren Sie es online!

Andrei Odegov
quelle
0

tcl

proc p a {puts [subst $a]}

Demo

Sergiol
quelle
0

JavaScript (ES6)

Erfordert, dass der Wert der an die Funktion übergebenen Variablen für diese Variable eindeutig ist. Gibt zurück, undefinedwenn eine Variable nicht übergeben wurde.

arg=>{
    for(key in this)
        if(this[key]===arg)
            return key
}

Versuch es

Aus irgendeinem Grund wird in einem Snippet ein ursprungsübergreifender Fehler ausgegeben, wenn ein "Literal" übergeben wird.

var fn=
    arg=>{
        for(key in this)
            if(this[key]===arg)
                return key
    },
str="string",
int=8,
arr=[1,2,3],
obj={a:1}
console.log(fn(fn))
console.log(fn(str))
console.log(fn(int))
console.log(fn(arr))
console.log(fn(obj))


Erläuterung

Durchlaufen Sie alle Einträge im globalen Objekt ( this) und überprüfen Sie, ob der Wert jedes einzelnen Objekts genau dem Wert des an die Funktion übergebenen Arguments entspricht. Wird ein passender Eintrag gefunden, wird dessen Schlüssel (Name) zurückgegeben und die Funktion verlassen.


Alternative

Mit den gleichen Anforderungen wie oben

arg=>
    [...Object.keys(this)].filter(key=>
        this[key]==arg
    ).pop()
Zottelig
quelle
2
Ich denke, dass dies fehlschlägt, wenn zwei Globale den gleichen Wert haben.
Martin Ender
1
Ich bekomme eine sehr seltsame Ausgabe .
Caleb Kleveter
@MartinEnder; Ja, es wird davon ausgegangen, dass der der übergebenen Variablen zugewiesene Wert für diese Variable eindeutig ist. habe vergessen, das bei der Veröffentlichung anzugeben.
Shaggy
@CalebKleveter; Einige davon sind auf die Tatsache zurückzuführen, dass der Wert der übergebenen Variablen nicht für diese Variable spezifisch ist, andere auf ungültige Variablennamen (z hello--. B. ). Außerdem müssten Sie vareher als verwenden let.
Shaggy
1
@CalebKleveter, hier finden Sie weitere Informationen zum Unterschied zwischen letund var. Zu Ihrer zweiten Frage: Das ist passiert, weil Sie eine Variable haben, die IN_GLOBAL_SCOPEin Ihrem globalen Gültigkeitsbereich benannt ist und einen Wert von hat 1. Sie können die vorhandenen Variablen in Ihrem globalen Bereich und ihre Werte überprüfen, indem Sie diese for(x in this)console.log(x+": "+this[x])
Shaggy,
0

Schnell, 45 Bytes

var a = 3
print(Mirror(reflecting: a).label!)
Dimitrie-Toma Furdui
quelle
Ich kann das nicht ausführen. Ich value of type 'Mirror' has no member 'label'
erhalte