Słowo się rzekło… krówka u płotu

W poście napisanym jakiś czas temu prosiłam o pomoc w wygraniu konkursu w którym nagrodą była wejściówka na festiwal informatyczny 4Developers. Obiecałam wówczas, że jeśli wygram, opiszę jak to się dzieje, że ten zakręcony kod działa i wypisuje oczekiwany rezultat.

Dla przypomnienia, kod był napisany w Perlu i przedstawiał się następująco:

#!/usr/bin/perl
$_=<<'MOO';
                 (__)s
    r            (00)
           /---e--\/     p
        o / |     ||        l
    e    *  /\----/\   v
        e   ~~    ~~
MOO
sub MOO{pop.qq,\Ud,}y/c-x//cd;@_=split//;$_=q@^@;$_=ord;$_=MOO split//;
while($;=pop @_){$_.=$;}print

Efektem wywołania tego kodu jest wypisanie tekstu 4Developers. Gdyby ktoś chciał to sprawdzić zapraszam do użycia bardzo fajnego narzędzia jakim jest Ideone, które umożliwia kompilowanie i uruchamianie online wielu języków.

Zacznijmy zatem po kolei. Ponieważ jest to tak naprawdę niemalże jednolinijkowiec, więc najprościej będzie na początek rozdzielić go na kolejne części z podziałem na linie, by zyskać większą czytelność. Zatem:

#!/usr/bin/perl
$_=<<'MOO';
                 (__)s
    r            (00)
           /---e--\/     p
        o / |     ||        l
    e    *  /\----/\   v
        e   ~~    ~~
MOO
sub MOO{pop.qq,\Ud,}
y/c-x//cd;
@_=split//;
$_=q@^@;
$_=ord;
$_=MOO split//;
while($;=pop @_)
{
    $_.=$;
}
print

Pierwsza linijka to standardowy początek skrypów Perla wykorzystujący tzw Shebang. Nie jest wymagany na Windowsie, ale na Linuxie już tak. Dlatego dobrze go używać, gdyż gwarantuje nam, że skrypt będzie działał na wszystkich platformach.

Kolejne linijki to nasza mucząca krówka:)

$_=<<'MOO';
                 (__)s
    r            (00)
           /---e--\/     p
        o / |     ||        l
    e    *  /\----/\   v
        e   ~~    ~~
MOO

Otoż jest to po prostu sposób na zapisywanie wielolinijkowych tekstów w kodzie - tzw here-document. Zaczynamy je znakami mniejszości << oraz jakimś dowolnym tekstem i kończymy tym samym tekstem. Apostrofy, których użyłam w sekcji rozpoczynającej tekst nie są koniecznie Tak więc równie dobrze moglibyśmy zapisać powyższe w następujący sposób:

$_=<<MESSAGE;
                 (__)s
    r            (00)
           /---e--\/     p
        o / |     ||        l
    e    *  /\----/\   v
        e   ~~    ~~
MESSAGE

Jednak MOO brzmi w tym kontekście o wiele lepiej, prawda? Oczywiście wspomniany wielolinijkowy tekst zawiera - jak już kiedyś pisałam - krówkę, która pochodzi z wywołania Debianowej komendy apt-get moo. W moim tekście jest ona dodatkowo otoczona literami słowa evelopers (brak d to nie pomyłka) zapisanymi w odwróconej kolejności. Można by teraz zapytać - ale gdzie duża litera D i cyfra 4? Zaraz do tego dojdziemy:)

Ten wielolinijkowy tekst jest przypisany do zmiennej $_. W Perlu jest to tzw zmienna domyślna. Istnieją operacje, które przyjmują ją jako argument, jeśli nie podamy innego. Przykład zobaczymy już niedługo.

Kolejna linijka

sub MOO{pop.qq,\Ud,}

to definicja metody MOO. Nazwałam ją tak, by wprowadzić dodatkową konsternację i skojarzenia z MOO, które było powyżej. Jak widać skojarzenia te nie są słuszne. Działanie samej metody opiszę nieco niżej, gdy będziemy omawiać jej wywołanie.

Następnie mamy:

y/c-x//cd;

Jest to pierwszy przykład operacji, gdzie nie mamy bezpośrednio podanego argumentu, więc tak jak pisałam powyżej, będzie ona wykonana na zawartości zmiennej domyślnej $_ i tą zawartość zmodyfikuje. Pozwala ona na wyciągnięcie wszystkich liter z danego ciągu znaków. Można to również zrobić tak:

y/a-z//cd;

a-z tudzież c-x to zakresy liter, które biorą udział w operacji. W słowie evelopers litery mieszczą się w zakresie c-x i wygląda to dużo bardziej zagadkowo niż proste a-z:) Litera y to alias operatora tr czyli operatora transliteracji. Jeśli zaś chodzi o litery c i d - c oznacza, że operacja dotyczy zakresu znaków (character) a d - że usuwamy (delete) wszystko inne niż podane znaki.
Kolejna linijka to:

@_=split//;

Tutaj akurat jest w miarę prosto, ponieważ split robi tutaj dokładnie to, czego moglibyśmy się spodziewać czyli pozwala rozbić string na tablicę elementów. Jak już wiemy, jeśli nie podaliśmy argumentu funkcja wykona się ze zmienną $_ jako argumentem. Podajemy jedynie jaki znak ma być separatorem. W tym przypadku seperator jest pusty, więc tablica wynikowa będzie zawierała po prostu litery tekstu zawartego w zmiennej $_. Zatem teraz w zmiennej @_ znajduje się tablica z poszczególnymi literami słowa evelopers. Należy zauważyć, że nie ma tutaj nawiasów obejmujących argumenty wywołania funkcji. Otóż w Perlu nawiasy w tej sytuacji są opcjonalne.

Następnie:

$_=q@^@;

Co to za emotka?:) To nie emotka, to po prostu znak karetki oddzielony znakami, które w połączeniu z operatorem q są w Perlu alternatywą cudzysłowów. Pozwala to na ograniczenie łańcucha tekstowego innymi znakami niż typowe cudzysłowy i wówczas pierwszy znak po q staje się zastępstwem cudzysłowu. Łańcuch przypisujemy domyślnej perlowej zmiennej czyli $_.
Ale po co nam ta karetka? Po to, żeby w kolejnej linijce zrobić coś takiego:

$_=ord;

W ten sposób wywołujemy funkcję ord na zmiennej $_ i przypisujemy z powrotem do $_ nadpisując ja tym samym. Efektem tego wywołania jest kod ASCII znaku karetki - czyli 94. Wygląda nieprzydatnie? Nie bardziej mylnego! Tutaj dochodzimy w końcu do wywołania metody MOO zdefiniowanej powyżej.

$_=MOO split//;

Co tu się dzieje? Tak skonstruowany split, jak już wiemy, zwróci nam tablicę znaków z tekstu, który znajduje się obecnie w zmiennej $_. Więc będzie to tablica zawierająca znaki ‘9’ oraz ‘4’. Używamy jej jako parametru a rezultat wywołania funkcji MOO ląduje w zmiennej $_. Teraz przyszedł zatem czas, by opisać co robi owa funkcja.

sub MOO
{
    pop.qq,\Ud,
}

W pierwszej części pobiera za pomocą funkcji pop ostatni element wyżej wspomnianej tablicy, czyli będzie to cyfra 4. W drugiej - poddaje modyfikacji literę d by uzyskać dużą literę D. Służy do tego celu escape sequence \u. Te dwie rzeczy moglibyśmy przedstawić również jako:

return pop()."\Ud"

Perl udostępnia nam operator qq, który również pozwala na użycie innych znaków niż cudzysłów do ograniczenia tekstu, co tutaj pozwoliło nam na zastosowanie takiej ciekawej konstrukcji. Ponieważ kropka to operator konkatenacji, na koniec mamy zatem w bieżącej zmiennej następującą zawartość - 4D.

Zatem nie pozostaje nam nic innego jak dokleić do tego pozostałe znaki (evelopers), a wiemy, że w odwróconej kolejności mamy je już w tablicy @_.

while($;=pop @_)
{
    $_.=$;
}

Doklejamy je (służy do tego operator .=) zatem do zmiennej $_, w kolejności od ostatniego do pierwszego. Należy tu zwrócić uwagę na zmienną $; - jest to jedna ze zmiennych specjalnych Perla.

Na koniec jeszcze tylko wywołanie funkcji print. Nie używamy średnika, ponieważ możliwe jest jego pominięcie, gdy jest to ostatnia linijka skryptu. Wywołanie bez argumentu operuje na zmiennej domyślnej $_, w której po wszystkich powyższych operacjach znajduje się teraz tekst 4Developers.

I oto jest. Tak wygląda historia działania tego zakręconego kodu.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *