Wydrukuj nazwę zmiennej [zamknięte]

20

Napisz funkcję (nie pełny program), aby jeśli funkcja została wywołana z jedną zmienną globalną (lub najbliższym odpowiednikiem twojego języka) jako argumentem, wypisuje (tj. Wypisuje lub zwraca) nazwę tej zmiennej. Jeśli argument nie jest zmienną, zamiast tego wypisz wartość falsey. (Nie musisz zajmować się przypadkiem, w którym argument jest zmienną, ale nie globalną.)

Pomiędzy wywołaniem funkcji a definicją funkcji nie może istnieć połączenie w czasie kompilacji (w szczególności definicja funkcji nie może być makrem lub podobną konstrukcją, która odbiera argumenty w postaci dosłownego fragmentu kodu źródłowego, ani w tekście, ani w abstrakcyjnym drzewie składni Formularz). To znaczy: w językach, które obsługują oddzielną kompilację, program musi działać, nawet jeśli wywołanie funkcji jest najpierw kompilowane (bez znajomości definicji funkcji, ale możliwe, że jest to podpis typu lub równoważny), a następnie definicja funkcji jest następnie kompilowana. Jeśli język nie ma osobnej kompilacji, musisz jednak dążyć do podobnej separacji między wywołaniem a definicją.

Jeśli używasz skompilowanego języka, możesz odczytać skompilowaną formę całego programu z dysku i możesz założyć, że program został skompilowany z informacjami debugowania. (W ten sposób dozwolone są rozwiązania, które działają poprzez dołączenie debuggera z programu do siebie.)

Pamiętaj, że to zadanie może nie być możliwe w każdym języku.

Przykłady:

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
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Dennis

Odpowiedzi:

31

Jawa

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

Obecnie działa to z kilkoma gotchas:

  1. Jeśli używasz IDE do skompilowania tego, może nie działać, chyba że zostanie uruchomione jako Administrator (w zależności od tego, gdzie są zapisywane tymczasowe pliki klas)
  2. Musisz skompilować korzystając javacz-g flagą. Generuje to wszystkie informacje debugowania, w tym nazwy zmiennych lokalnych w skompilowanym pliku klasy.
  3. Wykorzystuje to wewnętrzny interfejs API języka Java, com.sun.tools.javapktóry analizuje kod bajtowy pliku klasy i daje wynik czytelny dla człowieka. Ten interfejs API jest dostępny tylko w bibliotekach JDK, więc musisz użyć środowiska wykonawczego JDK java lub dodać tools.jar do ścieżki klasy.

To powinno teraz działać, nawet jeśli metoda jest wywoływana wielokrotnie w programie. Niestety to nie działa, jeśli masz wiele wywołań w jednym wierszu. (Dla takiego, patrz poniżej)

Wypróbuj online!


Wyjaśnienie

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

Ta pierwsza część zawiera ogólne informacje o tym, w jakiej klasie się znajdujemy i jak nazywa się ta funkcja. Można to osiągnąć, tworząc wyjątek i analizując pierwsze 2 wpisy śledzenia stosu.

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

Pierwszy wpis to wiersz, w którym zgłaszany jest wyjątek, z którego możemy pobrać metodę methodName, a drugi wpis to miejsce, z którego wywołano funkcję.

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

W tym wierszu wykonujemy plik wykonywalny javap, który jest dostarczany z JDK. Ten program analizuje plik klasy (kod bajtowy) i przedstawia wynik czytelny dla człowieka. Wykorzystamy to do podstawowego „parsowania”.

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

Robimy tutaj kilka różnych rzeczy. Po pierwsze, czytamy wynik wyjściowy javap linia po linii do listy. Po drugie, tworzymy mapę indeksów linii kodu bajtowego na indeksy linii javap. To pomaga nam później ustalić, które wywołanie metody chcemy przeanalizować. Na koniec używamy znanego numeru linii ze śladu stosu, aby określić, który indeks linii kodu bajtowego chcemy oglądać.

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

Tutaj iterujemy jeszcze raz po wierszach javap, aby znaleźć miejsce, w którym wywoływana jest nasza metoda i gdzie zaczyna się lokalna tabela zmiennych. Potrzebujemy linii, w której metoda jest wywoływana, ponieważ linia przed nią zawiera wywołanie do załadowania zmiennej i identyfikuje, którą zmienną (według indeksu) załadować. Tabela zmiennych lokalnych pomaga nam właściwie wyszukać nazwę zmiennej na podstawie przechwyconego przez nas indeksu.

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

Ta część analizuje wywołanie load, aby uzyskać indeks zmiennej. Może to spowodować wyjątek, jeśli funkcja nie jest faktycznie wywoływana ze zmienną, dzięki czemu możemy tutaj zwrócić wartość 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;

Na koniec analizujemy nazwę zmiennej z wiersza w lokalnej tabeli zmiennych. Zwróć null, jeśli nie zostanie znaleziony, chociaż nie widziałem powodu, dla którego miałoby się tak stać.

Kładąc wszystko razem

 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;

Właśnie na to patrzymy. W przykładowym kodzie pierwsze wywołanie to linia 17. linia 17 w LineNumberTable pokazuje, że początek tej linii to indeks kodu linii bajtowej 18. To jest System.outobciążenie. Mamy więc aload_2tuż przed wywołaniem metody, więc szukamy zmiennej w gnieździe 2 tabeli LocalVariableTable, która jest strw tym przypadku.


Dla zabawy, oto jedno, które obsługuje wiele wywołań funkcji na tej samej linii. To powoduje, że funkcja nie jest idempotentna, ale o to właśnie chodzi. Wypróbuj online!

Szturchać
źródło
1
Uwielbiam tę odpowiedź. Myślał o czymś podobnym. Jedna uwaga, jeśli umieścisz wiele wywołań w tej samej linii programu, to nie będzie w stanie określić, które z nich wywołuje metodę (nie jestem pewien, czy można to naprawić przy obecnym podejściu), na przykład spróbuj przenieść System.out.println(e.getParamName(str));System.out.println(e.getParamName(str2));System.out.println(e.getParamName(str4));na jedna linia w łączu TIO.
PunPun1000
Można pobrać javaplokalizację takiego: Paths.get(System.getProperty("java.home"), "bin", "javap").
Olivier Grégoire,
@ OlivierGrégoire Dzięki, ale przełączyłem się na wywoływanie javap poprzez wewnętrzny interfejs Java, więc nie muszę już uzyskiwać dokładnej lokalizacji na dysku w kodzie (tylko ścieżka klas)
Poke
@ PunPun1000 tak, nie jestem pewien, czy istnieje dobry sposób, aby to naprawić, utrzymując idempotentną funkcję, ale mogę połączyć coś, co po prostu używa stanu poza funkcją dla zabawy.
Poke
1
@ PunPun1000 Dodano wersję, która może obsługiwać wiele połączeń na jednej linii. To coś więcej niż tylko funkcja, więc dodałem kolejny link TryItOnline zamiast aktualizować odpowiedź.
Poke
14

Python 2

Chodzi o najbardziej brudny kod, który napisałem, ale działa. ¯ \ _ (ツ) _ / ¯ Zgłasza błąd w nieistniejącej zmiennej, ponieważ Python od razu cię nie polubi za wywołanie tej funkcji. Zgłasza również błąd dla zmiennych, ale można to naprawić za pomocą try / z wyjątkiem potrzeby.

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]

Wypróbuj online!

Jeśli wolno nam traktować argument jako ciąg znaków, spełnia to wymagania wypisania wartości fałszowania przy nieprawidłowym wejściu.

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

Wypróbuj online!

całkowicie ludzki
źródło
Napisałeś prawie taką samą odpowiedź, podczas gdy ja pisałem wyjaśnienie do mojej: D +1 dla ciebie
Dead Possum
1
@DeadPossum Zawsze publikuj posty, odpowiadaj i dodawaj wyjaśnienia później jako zmiany. ;)
Arjun
8
@Arjun, och świetnie, więc musimy grać zarówno w EditGolf, jak i CodeGolf: P
Wossname
12

Matematyka

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

HoldFirstCecha uniemożliwia fod oceny jej argumentu przed wywołaniem funkcji.ValueQ @ xnastępnie sprawdza, czy podany argument jest zmienną, której nadano wartość. Jeśli nie, po prostu wrócimy z Falsepowodu zwarcia. W przeciwnym razie otrzymujemy nazwę zmiennej za pomocą ToString @ HoldForm @ x.

Martin Ender
źródło
Dang, nadużywanie tego, jak radzi sobie Mathematica And... Podoba mi się, że właściwy argument Andnie musi być nawet logicznym wyrażeniem.
JungHwan Min
Wiem, że to nie jest golf golfowy, ale dlaczego nie tylko f[x_] := ValueQ @ x && HoldForm @ x? Gdyby nie wymóg sprawdzania, czy dane wejściowe są zmienne, HoldFormsam by działał.
ngenisis
HoldAllzamiast HoldFirst?
Greg Martin
1
@ngenisis Ponieważ to wraca HoldForm[a]i nie "a". Chociaż są one wyświetlane tak samo w zeszycie Mathematica, funkcja istnieje niezależnie od interfejsu użytkownika, więc chciałem mieć rozwiązanie zwracające ciąg znaków.
Martin Ender
1
Prośba o usunięcie białych znaków, ponieważ ... um ... ponieważ
CalculatorFeline
7

Matematyka

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

Mathematica lubi wszystko oceniać , więc aby się zatrzymać, musimy z tym walczyć. We własnym języku.

W jaki sposób?

f~SetAttributes~HoldAll;

Mówi Mathematica, że ​​za każdym razem, gdy fwywoływana jest funkcja , jej argumenty nie powinny być oceniane (tzn. Wstrzymywane).


f[_] = False;

Kiedy fjest wywoływany z argumentem, wyjście False. (?!)


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

Gdy f zostanie wywołany ze zdefiniowanym Symbolem (który ma wartość), wypisz nazwę symbolu wejścia.

Poprzedni wiersz nie ma pierwszeństwa, mimo że został wcześniej zdefiniowany, ponieważ ta definicja jest bardziej szczegółowa. Jak stwierdza Dokumentacja Wolfram: Mathematica „próbuje umieścić określone definicje przed bardziej ogólnymi definicjami”.

Mathematica jest bardzo uparta i stara się oceniać zmienne, gdy tylko jest to możliwe. Dodatkowy Unevaluateddba o to.

JungHwan Min
źródło
7

DO#

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

Wersja pełna / sformatowana:

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

Innym sposobem na to jest zastanowienie się nad typem:

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

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

Musisz to jednak nazwać tak:

GetParameterName(new { apple });

Następnie również nie powiedzie 3.14159się, powracając, Lengtha do tego musisz również wywołać go w następujący sposób:

GetParameterName(new double[]{ 3.14159 });

W C # 6.0 mamy również:

nameof

Ale to się nie skompiluje, jeśli przekażesz coś, co nie jest zmienną, np 3.14159. Wierzę, że ocenia również dane wejściowe w czasie kompilacji, więc wydaje się, że nie spełnia również tego wymagania.

TheLethalCoder
źródło
Jest to trochę podejrzane ... ponieważ lambda jest absolutnie niezbędne (tzn. Nie możesz po prostu przekazać zmiennej, musisz przekazać coś, co kompilacja rozpozna, powinno stać się Expression). Powinieneś także liczyć using System.Linq.Expressions;.
VisualMelon
Czy jednak liczy się to jako „odbieranie argumentów w abstrakcyjnej formie drzewa składniowego”?
Matti Virkkunen
@VisualMelon Nie mogłem znaleźć innego sposobu na zrobienie tego w C #, więc zrobiłem to w ten sposób. Również nie jest to golf kodowy, więc to nie ma znaczenia,
TheLethalCoder
... przepraszam, nie zdawałem sobie sprawy, że nie było liczby bajtów!
VisualMelon,
6

MATLAB

varname = @(x) inputname(1);

W ramach funkcji znajduje się kilka predefiniowanych zmiennych, takich jak varargin lub nargin, wśród tych, które również mamy inputname.

skradziony stąd

wada
źródło
Nigdy nie wiedziałem, że to istnieje. x=42;y=54; f=@(x)eval(inputname(1));f(x),f(y)
Bawiłem się
Haha, eval==evil= D (faktycznie współpracuje x=42;y=54; f=@(x)eval('inputname(1)');f(x),f(y))
flawr
Tak, po prostu anonimowa funkcja nie wie o żadnej zmiennej obszaru roboczego, więc eval może tylko oceniać x, a następnie sprawdza evalargument funkcji, a nie zmienną obszaru roboczego.
Sanchises
6

R

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

substitutezwraca drzewo parsowania dla nieocenionego wyrażenia. Te identicalmarki warunkowe pewien, że ten unevaluated ekspresja nie jest identyczny do samego wyrazu; tzn. że przekazany parametr nie jest literałem.

mb7744
źródło
4

Python 2

Niektóre badania zostały wykonane. I wydaje się, że jest to możliwe w Pythonie, ale nadal spodziewam się problemów.
Rozwiązanie nie jest idealne, ponieważ zakłada pewną strukturę kodu. Jeszcze go nie złamałem.

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()

Używa inspekcji, aby spojrzeć na zasięg przestrzenny i znaleźć, gdzie get_namewywoływana jest funkcja . Na przykład inspect...[1]wróci

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

I inspect...[1][4]pokaże nam listę z kodem, w którym wywoływana jest funkcja:

['print get_name( a )\n']

Następnie użyłem wyrażenia regularnego, aby pobrać nazwę argumentu

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

I .lstrip().rstrip()aby usunąć wszystkie białe spacje, które mogą być umieszczone w bransoletach.

Przykład z trudnymi danymi wejściowymi

Dead Possum
źródło
4

PowerShell

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

$apple = 'hi'
t $apple
apple

t 3.141

Ale to nie działa niezawodnie, jest szczątkowe.

TessellatingHeckler
źródło
To nie jest kod golfowy.
Okx,
@Okx o, oops, więc nie jest.
TessellatingHeckler
4

PHP

Wymaga, aby wartość zmiennej była unikalna w tablicy zmiennych globalnych

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

Wypróbuj online!

PHP , 96 bajtów

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

Wypróbuj online!

Wymaga zainicjowania zmiennej bezpośrednio w wywołaniu funkcji.

Sprawdza, czy ostatnia zmienna globalna jest równą zmienną poddającą się

Jörg Hülsermann
źródło
To nie jest kod golfowy.
Okx,
@Okx Wiem W tej chwili nie mam lepszego pomysłu.
Jörg Hülsermann
4

JavaScript (ES6)

Dla wszystkich nazw właściwości w window(Object.getOwnPropertyNames(w) ) spróbuj zdefiniować moduł pobierający dla tej właściwości, która zwraca nazwę właściwości.

Następnie dodaj wpis do mapy M którym klucz jest (prawdopodobnie przesłoniętą) wartością właściwości, a wartość jest nazwą właściwości.

Funkcja fpo prostu pobiera zmienną (tj. Właściwość window) i zwraca wpis Mdla tej wartości.

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)

Działa to dla wszystkich wbudowanych zmiennych globalnych z wyjątkiem windowsiebie, ponieważ nie ma sposobu, aby je odróżnić top(chyba, że ​​jest uruchamiany w ramce):

L( P.filter(p => p !== f(w[p])) )
// -> ['window']
darrylyeo
źródło
Z jakiegoś powodu, ja otrzymuję błąd: L not a function. Dlaczego miałby to się stało?
Caleb Kleveter
Łał! To jest głębsze w ES6, niż widziałem od jakiegoś czasu.
MD XF
@CalebKleveter D'oh! Zapomniałem przecinka w drugiej linii. To mogło, ale nie musi być problematyczne.
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);

Używamy Devel :: Caller (moduł podobny do debuggera) do przejścia przez stos wywołań, szukając wywołania funkcji i zwracając wszystkie argumenty argumentu, zwracając je jako nazwy zmiennych. Jeśli jest więcej (lub mniej) niż jeden operand, nie jesteśmy wywoływani ze zmienną. Jeśli operand nie był zmienną, nie będzie miał nazwy i my też możemy to wykryć.

Najtrudniejszy jest przypadek, gdy otrzymamy wyrażenie z jednym argumentem zawierające zmienną, na przykład ~$x. Możemy dowiedzieć się, czy tak się stało, odwołując się do zmiennej bezpośrednio z tabeli symboli (używając${…} składni symbolicznego odwołania) i porównując jej adres pamięci z wartością, którą przekazaliśmy jako argument (który dogodnie jest przekazywany przez odwołanie ). Jeśli są różne, mamy wyrażenie zamiast pojedynczej zmiennej.

Zauważ, że jeśli wywołamy tę funkcję z wyrażeniem wstępnym lub wstępnym na jednej zmiennej, tak jak w v(--$x), otrzymamy zwrot $x. Jest tak, ponieważ w rzeczywistości w tym przypadku przekazywana jest funkcja. po prostu zwiększa się lub zmniejsza wcześniej. Mam nadzieję, że to nie dyskwalifikuje odpowiedzi. (W pewnym sensie poprawia to, ponieważ pokazuje, że sprawdzamy sam argument, a nie tylko czytamy kod źródłowy.)


źródło
3

PHP

Podczas gdy inne zgłoszenia PHP sprawdzają tylko, czy podana wartość odpowiada wartości globalnej, ta wersja działa, odwołując się do wartości:

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

To powinno teraz działać, nawet jeśli zmienna globalna jest odniesieniem do innej wartości w momencie wywołania.

Sprawdź to tutaj .

Christoph
źródło
Świetnie i bardzo dziękuję za naukę
Jörg Hülsermann
@ JörgHülsermann Nawet znalazł sposób, aby to poprawić!
Christoph
3

Röda

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

Wypróbuj online!

Röda ma do tego wbudowaną funkcję - name. Niestety nie zwraca wartości falsy, gdy nie jest podana zmienna.

Ten kod narusza kilka funkcji semantyki odniesienia. Objaśnienie linia po linii:

f(&a...) {

Funkcja przyjmuje zmienną liczbę argumentów referencyjnych (&a... ). Jest to jedyny sposób, aby w Röda utworzyć listę referencji.

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

W drugiej linii elementy a są wypychane do strumienia (a() ). Te elementy są referencjami, jeśli funkcja otrzyma zmienne, a w przeciwnym razie wartości normalne.

Elementy są iterowane za pomocą pętli podkreślenia. Podkreśleniem jest składnia cukru dla forpętli, więc name(_)jest równoważna z name(var) for var. Nazwa zmiennej używanej w forpętli ma postać, w <sfvN>której N się zmienia. (Tj.)name(<sfv1>) for <sfv1> ”) Zmienna zdefiniowana przez użytkownika zawiera <lub> , więc wygenerowane nazwy nie kolidują z istniejącymi nazwami zmiennych.

name()Funkcja zwraca nazwę danej zmiennej. Jeśli aiterowany element jest odwołaniem, to jest to zmienna podana do name. W przeciwnym razie, jeśli element miałby wartość normalną, podana zmienna namejest zmienną podkreślenia<sfvN> . Wynika to z semantyki odniesień w Röda: jeśli funkcja przyjmuje odwołanie, a funkcja jest przekazywana do zmiennej odniesienia, przekazywana wartość nie wskazuje na zmienną odniesienia, ale na zmienną, na którą wskazuje zmienna odniesienia.

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

W trzecim wierszu sprawdzamy, czy nazwa zmiennej w strumieniu zawiera < znak. Jeśli tak, jest to zmienna podkreślenia, a przekazana wartość fnie była odwołaniem. W przeciwnym razie wypisujemy nazwę referencji.

To rozwiązanie nie działa, jeśli zmienna podana do funkcji jest zmienną odniesienia lub podkreślenia. Jednak pytanie określa, że ​​należy obsługiwać tylko zmienne globalne i nie mogą one być odwołaniami ani zmiennymi podkreślającymi w Rödzie.

fergusq
źródło
1

Rubin , 46 bajtów

Czuję się jak najbrudniejszy kod, jaki kiedykolwiek napisałem dla Ruby.

Wymaga, aby wywoływane zmienne globalne miały unikalną wartość, która nie występuje w żadnej innej zmiennej globalnej, ponieważ jedynym sposobem na wykonanie tego wyzwania w Ruby jest przeszukanie wszystkich zmiennych globalnych i zwrócenie pierwszego dopasowania. OP twierdzi, że jest w porządku i może ocenić, czy moje rozwiązanie jest prawidłowe.

Zauważ, że zmienne globalne zaczynają się od $ w Ruby, bo jeśli chcesz dodać dodatkowe rzeczy z przypadków testowych.

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

Wypróbuj online!

Wartość tuszu
źródło
1

PHP

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

jeśli wartość zostanie znaleziona, wypisz nazwę zmiennej i zakończ. nic nie drukuj i nie wychodź inaczej.

61 bajtów, aby zwrócić nazwę zmiennej lub NULL:

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

Nie znajdzie funkcji nazwanych, tylko te przypisane do zmiennych.
A funkcja PHP nie może wykryć, czy parametr został podany przez referencję lub wartość. Funkcja zwróci tylko imię, w którym wartość odpowiada wartości parametru funkcji.

Przetestuj online

Tytus
źródło
1

PowerShell

Nowa wersja, ale działa począwszy od PowerShell 3.0

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

Wypróbuj online!

Poprzednia wersja

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

Wypróbuj online!

Andrei Odegov
źródło
0

tcl

proc p a {puts [subst $a]}

próbny

sergiol
źródło
0

JavaScript (ES6)

Wymaga, aby wartość zmiennej przekazywanej do funkcji była unikalna dla tej zmiennej. Zwraca, undefinedjeśli zmienna nie została przekazana.

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

Spróbuj

Z jakiegoś powodu po przekazaniu „literału” zgłasza błąd pochylenia we fragmencie.

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))


Wyjaśnienie

Zapętlaj wszystkie wpisy w obiekcie globalnym ( this), sprawdzając, czy wartość każdego z nich jest ściśle równa wartości argumentu przekazanego do funkcji. Jeśli zostanie znaleziony pasujący wpis, zwracany jest jego klucz (nazwa), wychodząc z funkcji.


Alternatywny

Z tymi samymi wymaganiami jak powyżej

arg=>
    [...Object.keys(this)].filter(key=>
        this[key]==arg
    ).pop()
Kudłaty
źródło
2
Myślę, że to się nie powiedzie, jeśli dwa globale mają tę samą wartość.
Martin Ender
1
Otrzymuję bardzo dziwny wynik .
Caleb Kleveter
@MartinEnder; tak, zakłada, że ​​wartość przypisana do przekazywanej zmiennej jest unikalna dla tej zmiennej; zapomniałem dołączyć to, kiedy pisałem.
Shaggy
@CalebKleveter; niektóre z nich wynikają z faktu, że wartość przekazywanej zmiennej nie jest unikalna dla tej zmiennej, a część z powodu nieprawidłowych nazw zmiennych (np hello--.). Musisz też użyć varzamiast let.
Shaggy
1
@CalebKleveter, zobacz tutaj, aby uzyskać więcej informacji na temat różnicy w zakresie określania zakresu między leti var. Na twoje drugie pytanie: Stało się tak, ponieważ masz zmienną nazwaną IN_GLOBAL_SCOPEw swoim globalnym zasięgu i ma ona wartość 1. Możesz sprawdzić istniejące zmienne w swoim globalnym zasięgu i ich wartości, uruchamiając to przed przetestowaniem powyższego:for(x in this)console.log(x+": "+this[x])
Shaggy
0

Szybki, 45 bajtów

var a = 3
print(Mirror(reflecting: a).label!)
Dimitrie-Toma Furdui
źródło
Nie mogę tego uruchomić. Dostaję:value of type 'Mirror' has no member 'label'
Caleb Kleveter