Wskazówki dotyczące tworzenia poliglotów


48

to program, który można uruchomić w 2 lub więcej różnych językach programowania.

Jakie masz ogólne wskazówki na temat tworzenia poliglotów lub wybierania języków, które można łatwo napisać dla konkretnego zadania?

Proszę zamieścić wskazówki, które można zastosować w większości sytuacji. Tzn. Nie powinny działać tylko w poliglotach dwóch określonych języków. (Możesz po prostu opublikować odpowiedź na pytanie dotyczące wielogłosu, jeśli masz zbyt konkretną wskazówkę). Możesz jednak wprowadzić funkcje języka, które ułatwiają pracę z wieloma językami lub można je dodać do dowolnych istniejących poliglotów.

Proszę zamieścić jedną wskazówkę na odpowiedź. Nie krępuj się zasugerować edycję, jeśli wskazówka dla konkretnego języka dotyczy także innego języka.

Odpowiedzi:


25

Wykorzystaj symbole komentarzy

Prostym sposobem na utworzenie dwujęzycznego poliglota jest podzielenie kodu na dwie części w następujący sposób:

  1. Pierwsza część wykonuje rzeczywistą pracę w języku A, jest nieszkodliwa w języku B (bez błędów), a kończy się na komentarzu w języku A, który ukrywa drugą część w języku A.
  2. Druga część wykonuje rzeczywistą pracę w języku B.

A zatem

  • Język A widzi pierwszą część, która wykonuje zadanie, a następnie komentarz.
  • Język B widzi bezużyteczną pierwszą część, a następnie drugą część, która wykonuje zadanie.

Jedyną trudną częścią jest tutaj znalezienie zestawu instrukcji (pierwsza część), które wykonują zadanie w języku A, nie popełniając błędów w języku B. Kilka sugestii:

  • Większość języków opartych na stosie umożliwia wyświetlanie tylko górnej części stosu na końcu programu (czasami jest to nawet domyślne, jak w 05AB1E).
  • Niektóre języki ignorują niezdefiniowane instrukcje (na przykład Golfscript).

Prosty przykład wykorzystujący te wytyczne można znaleźć tutaj . Językami A i B są odpowiednio MATL i 05AB1E .


24

Używaj języków dwuwymiarowych

W przeciwieństwie do języków jednowymiarowych, które generalnie analizują cały kod źródłowy i powodują błędy składniowe lub niepożądane efekty w czasie wykonywania na rzeczach, których nie rozumieją (zmuszając w ten sposób do ukrywania przed nimi kodu innych języków), języki dwuwymiarowe mają tendencję tylko parsuj kod na ścieżce wykonania, co oznacza, że ​​cała reszta programu jest ignorowana. Jest także o wiele więcej miejsca na dzielenie ścieżek wykonawczych od siebie w dwóch wymiarach; możesz wysłać wskaźnik instrukcji obracający się w nietypowym kierunku, np. w dół, a nawet w lewo (owijanie w prawą stronę programu), aby bardzo szybko zejść mu z drogi. Techniki przydatne w językach jednowymiarowych uogólniają się również na języki dwuwymiarowe (np. Można pominąć kod za pomocą;; w Befunge-98, oprócz wysyłania IP w dziwny sposób), co czyni go w większości tylko ścisłym zyskiem w porównaniu do rozwiązania jednowymiarowego.

Jako bonus, kilka języków dwuwymiarowych ma punkt wejścia inny niż lewy górny róg programu, co oznacza, że ​​nie musisz podejmować żadnych wysiłków, aby oddzielić je od innych języków; naturalnie oderwą się od grupy.


20

Poznaj swoje prawdy i fałsze

Każdy język postrzega „prawda” i „fałsz” w nieco inny sposób. Jeśli mają podobną składnię, możesz to wykorzystać, dodając decyzję, że języki będą obsługiwały inaczej.

Jeden przykład z wątku Trick or Treat: ''pusty ciąg. W Lua jest to prawda, ale w Pythonie prawda, ale fałsz:

print(''and'trick'or'treat')

.. wydrukuje inny ciąg w każdym języku.

Wystarczy znaleźć taką wartość. Na przykład możesz użyć '0', który ocenia falsew PHP, ale truew Pythonie.


17

Cytaty blokowe w co najmniej jednym języku

Oto przykład, który działa zarówno w Pythonie, jak i C ++

#include <iostream> /*
""" */
int main() {
    std::cout << "Hello World!\n";
}

/* """
print("Hello World!")
# */

Luis Mendo zaproponował, jak sądzę, najłatwiejsze rozwiązanie, polegające na użyciu komentarzy.

Poszukujesz jednego języka, w którym komentowanie blokowe, i drugiego, w którym regularna składnia w pierwszym komentuje składnię w drugim.

Jeszcze łatwiejsze są dwa języki z różnymi stylami komentowania bloków, które są wymiennie poprawną składnią, ale nie przeszkadzało mi to sprawdzić.

Sprawdź to w Python 3.5 i C ++


2
Pierwszy wiersz nie powinien mieć średnika.

Prawdziwe. Dobra uwaga
dexgecko

15

Dziel i rządź

Kiedy piszesz poliglotę w wielu językach, niekoniecznie będziesz w stanie od razu oddzielić wszystkie przepływy kontrolne języka od siebie. W związku z tym będziesz musiał „prawdziwie poliglotyczny” niektóre języki przez pewien czas, pozwalając na uruchomienie tego samego kodu w każdym z nich. Robiąc to, należy pamiętać o dwóch głównych zasadach:

  • Przepływ sterowania w dowolnych dwóch językach powinien być albo bardzo podobny, albo bardzo różny . Próba obsługi dużej liczby przeplatanych przepływów sterowania jest receptą na dezorientację i utrudnia modyfikację programu. Zamiast tego powinieneś ograniczyć ilość pracy, którą musisz wykonać, upewniając się, że wszystkie programy znajdujące się w tym samym miejscu są dostępne z tego samego powodu i mogą być uruchomione równolegle tak długo, jak potrzebujesz. Tymczasem, jeśli język jest bardzo różny od innych, chcesz, aby jego wykonanie jak najszybciej przeniosło się do zupełnie innej lokalizacji, abyś nie musiał próbować dostosowywać kodu do dwóch różnych modeli składniowych jednocześnie.

  • Szukaj możliwości rozdzielenia jednego języka lub grupy podobnych języków od siebie. Pracuj od większych grup do mniejszych grup. Kiedy już będziesz mieć grupę podobnych języków w pewnym momencie programu, będziesz musiał je w pewnym momencie podzielić. Na początku programu możesz, powiedzmy, chcieć podzielić języki używane #jako znacznik komentarza z dala od języków, które używają innego znacznika komentarza. Później być może masz punkt, w którym wszystkie języki używają f(x)składni wywołań funkcji, oddzielają polecenia średnikami i mają podobne podobieństwa składniowe. W tym momencie możesz użyć czegoś znacznie bardziej specyficznego dla języka, aby je podzielić, np. Fakt, że Ruby i Perl nie przetwarzają sekwencji specjalnych w ''ciągach, ale Python i JavaScript to robią.

Ogólnie rzecz biorąc, logiczny przepływ programu powinien skończyć się jako drzewo, wielokrotnie dzieląc się na grupy języków, które są bardziej do siebie podobne. To stawia większość trudności w pisaniu poliglota na samym początku, przed pierwszym podziałem. Ponieważ przepływ sterowania rozgałęzia się coraz bardziej, a języki działające w danym punkcie stają się coraz bardziej podobne, zadanie staje się łatwiejsze, ponieważ można używać bardziej zaawansowanej składni bez powodowania błędów składniowych.

Dobrym przykładem jest zestaw {JavaScript, Ruby, Perl, Python 3}; wszystkie te języki akceptują wywołania funkcji w nawiasach i mogą rozdzielić instrukcje średnikami. Wszystkie obsługują także evalinstrukcję, która skutecznie pozwala na kontrolę przepływu w przenośny sposób. (Perl jest najlepszym z tych języków, aby wcześnie oddzielić się od grupy, ponieważ ma inną składnię dla zmiennych niż inne).


13

Ukryj kod w literałach ciągów

W większości języków literał ciąg znaków sam w sobie nie robi nic lub robi coś, co można łatwo odwrócić (na przykład wepchnięcie łańcucha na stos). Składnia literału łańcuchowego jest również stosunkowo niestandardowa, szczególnie w przypadku alternatywnych składni, których wiele języków używa do obsługi ciągów znaków z osadzonymi znakami nowej linii; na przykład Python ma """ ... """, Perl ma q( ... ), a Lua ma [[ ... ]].

Istnieją dwa główne ich zastosowania. Jednym z nich jest umożliwienie przeplatania sekcji przeznaczonych dla różnych języków poprzez rozpoczęcie łańcucha na końcu pierwszej sekcji jednego języka i wznowienie go na początku drugiego: powinno być dość łatwo uniknąć przypadkowego zamknięcia łańcucha ze względu na różnorodność ograniczniki ciągu między różnymi językami. Drugi polega na tym, że wiele ograniczników łańcuchów ma znaczenie jako polecenie w innych językach (często bardziej niż znaczniki komentarzy), więc możesz zrobić coś takiego x = [[4] ], co jest nieszkodliwym przypisaniem w językach, które używają notacji JSON dla list, ale która zaczyna się ciąg znaków w Lua (a tym samym pozwala ci oddzielić kod Lua od reszty, biorąc pod uwagę, że skutecznie „przeskakuje” do następnego ]]).


13

Zakończenie programu

Możesz zakończyć program nagle w jednym języku, aby zignorował kod w innym języku.

Zasadniczo można użyć tego formatu

code_in_language1 end_program_in_language1 code_for_language2 end_program_in_language2 ...

gdzie end_program_in_languageNjest polecenie zakończenia programu.

Na przykład w mojej odpowiedzi w temacie Co przyniesiesz na Święto Dziękczynienia? , Zakończyłem program w Dipie, a następnie napisałem kod dla innego języka, V, aby interpreter Dip go zignorował.

"turkey"e#"corn"??"gravy"p&Ssalad
"turkey"e#"corn"??"gravy"                 
                         p&            # print stack and exit program (Dip) 
                           Ssalad      # Now that the program ended in Dip,
                                       # I can write V code that would otherwise
                                       # have caused errors in Dip

Ale nie wszystkie języki mają polecenie, które może tak zakończyć program. Jeśli jednak taki język ma tę funkcję, należy go używać mądrze.

Jak sugeruje @LuisMendo, możesz utworzyć błąd (jeśli jest dozwolony), aby zakończyć program, jeśli język nie ma jeszcze wbudowanego „programu końcowego”.


2
Nawet jeśli w języku nie ma funkcji ani instrukcji kończącej program, zwykle wystarczy błąd
Luis Mendo

1
@LuisMendo: Zgadzam się, chociaż zauważ, że wiele problemów z poliglotą w szczególności zabrania ucieczki przez awarię, ponieważ powoduje to zbyt łatwe. Warto jednak go wykorzystać, gdy tego nie robią.

1
Prawdopodobnie powinieneś wspomnieć, że kod drugiej części nadal powinien być poprawny pod względem składniowym w pierwszym języku, w przeciwnym razie najbardziej praktyczne języki spowodują błąd.
MilkyWay90

13

Zmienna lub kod w literałach łańcuchowych

Cudzysłowy ciąg literałów jest w większości języków nieszkodliwy. Ale w niektórych językach mogą również zawierać kod.

W Bash możesz użyć `...`(to nie kończy programu):

"`echo Hello world! >/proc/$$/fd/1`"

W Tcl możesz użyć [...]:

"[puts {hello world!};exit]"

W PHP możesz użyć ${...}(powoduje to błąd w Bash, więc musi pojawić się po kodzie Bash):

"${die(print(Hello.chr(32).world.chr(33)))}";

W Ruby możesz użyć #{...}:

"#{puts 'Hello world!';exit}"

Mogą być też inni.

Te gramatyki nie są kompatybilne. Oznacza to, że możesz umieścić cały kod tych języków w jednym ciągu w nieszkodliwym miejscu. I po prostu zignoruje nierozpoznany kod w innych językach i zinterpretuje je jako treść ciągu.

W wielu przypadkach można również łatwo skomentować znak podwójnego cudzysłowu i uczynić bardziej tradycyjnym poliglotą.


12

Zmienne aliasing

Jest to prawdopodobnie jedna z najprostszych, jak dotąd (IMO), sztuczek, których można użyć, zwłaszcza że może ona dotrzeć do tak wielu języków.

Przykład:

print=alert;print("Hello World!")

Działa to nie tylko w Javascript, ale także w Pythonie, Ruby itp. Więcej przykładów później, gdy pomyślę o kilku innych. Oczywiście mile widziane są sugestie dotyczące komentarzy / zmiany postów.


5
Zauważ, że robiąc np. JS / Python, zwykle jest krótszy alias alertdo printw Pythonie (tylko 3), ponieważ składnia komentarza JS //może być łatwo przetworzona w program Python, podczas gdy Python #nie może być przetworzony w JS.
ETHprodukcje

11

#komentarze na podstawie

Ta wskazówka jest podzbiorem symboli komentarzy wykorzystujących lukę i cytatów blokowych w co najmniej jednym języku

Podczas tworzenia poliglotów z wieloma językami, zwłaszcza językami gotowymi do produkcji w przeciwieństwie do esolangów, warto spojrzeć na języki, które są używane #w komentarzach blokowych lub jednowierszowych.

  • Istnieje wiele języków, w których składające się na komentarze blokowe zaczynają się od #, a po znakach jest duża różnorodność znaków #.
  • Większość tych języków zezwala również na pojedynczy #komentarz liniowy, co oznacza, że ​​coś, co może rozpocząć komentarz blokowy w jednym języku, jest zwykłym komentarzem w innym, co ułatwia dopasowanie.

Oto krótka lista języków używanych #w komentarzu blokowym (niewyczerpujący):

Language            Start       End      Single-line #?     Notes
------------------------------------------------------------------------------------------
Agena               #/          /#             ✓
AutoIt              #cs         #ce
Brat                #*          *#             ✓
C                   #if 0       #endif                      Not actually a comment
CoffeeScript        ###         ###            ✓            Needs to be on separate line
Common Lisp         #|          |#
Julia               #=          =#             ✓
Lily                #[          ]#             ✓
Objeck              #~          ~#             ✓
Perl 6              #`{         }#             ✓            Any bracketing chars will do
Picolisp            #{          }#             ✓
Scheme              #|          |#

Aby uzyskać więcej przykładów, zobacz Kod Rosetta .

Oto szybki i łatwy przykład, jako demonstracja:

#|
###
#`[

print("Julia")
#=

|#
(format t "Common Lisp")
#|

###
alert("CoffeeScript")
###

]#
say "Perl 6"
#`[

...

# ]# # ### # |# ; =#

Zephyr ma #- ... -#.
DLosc

11

Rozbieżności operatorów arytmetycznych

W przypadku podobnych języków lub prostych poliglotów czasem warto poszukać różnic w działaniu arytmetyki. Wynika to z faktu, że większość (nie-ezoterycznych) języków ma operatory arytmetyczne z poprawkami, a arytmetyka może być szybkim i łatwym sposobem wprowadzenia różnicy.

Na przykład:

  • ^ jest bitowy XOR w niektórych językach i potęgowanie w innych
  • / jest dzieleniem liczb całkowitych w niektórych językach i dzieleniem zmiennoprzecinkowym w innych
    • W przypadku języków podziału na liczby całkowite -1/2występuje -1w niektórych językach (zaokrąglenie w dół) i 0w innych (zaokrąglenie do zera)
  • -1%2jest -1w niektórych językach i 1w innych
  • --x jest zakazem w niektórych językach (podwójne negowanie) i wstępnym zmniejszeniem w innych
  • 1/0 daje nieskończoność w niektórych językach i błędy w innych
  • 1<<64daje 0 w niektórych językach (przepełnienie) i 36893488147419103232w innych

3
Prostym przykładem byłoby x=1;["JS","Python"][--x]zwrócenie nazwy języka, w którym jest uruchomiony (między JS a Pythonem).
ETHprodukcje

10

Użyj Brainfuck

Prawie wszystkie implementacje BF wyrzucają znaki, które nie są +-<>[].,, co akurat działa na naszą korzyść!

BF jest prawdopodobnie jednym z najłatwiejszych języków do pracy w poliglocie z powodu tej funkcji, o ile najpierw piszesz część BF. Po wypisaniu kodu BF wystarczy jedynie modelować dowolny inny kod wokół struktury BF.

Oto naprawdę prosty przykład:

.+[.+]

To prawie inkrementy i kodowanie znaków wyjściowych „na zawsze” (w zależności od ustawień środowiska wykonawczego). Teraz, jeśli chcesz napisać losowy fragment kodu, powiedzmy w JS, możesz:

x=>"asdf".repeat(+x)[x*Math.random()*2+1|0]

Zauważ, jak JS jest formowany wokół BF.

Pamiętaj, że działa to najlepiej, jeśli naprawdę zaczynasz od BF; znacznie trudniej jest zacząć od innego języka i spróbować włączyć BF.


6
W przypadku większych poliglotów, w których kilka bajtów oszczędności związanych z integracją BF niewiele pomaga, napisałbym BF na końcu i owinąłbym drugi kod tak wieloma, []jak to konieczne.
Sp3000,

6
Dotyczy to nie tylko pieprzenia mózgu, ale także ogromnej liczby języków podobnych do pieprzenia mózgu i wielu innych plandek Turinga.
0

2
Pierwsza x=>zmienia komórkę, co w tym przypadku nie ma znaczenia, ale chciałem tylko powiedzieć
Roman Gräf

7

Używaj języków, w których większość znaków nie ma znaczenia

Jest to uogólnienie twierdzenia Mama Fun Roll o BF . Esolang, który ignoruje większość postaci, jest bardzo przydatny w poliglotach. Przydatny także: esolang, w którym duży zestaw znaków jest wymienny. Kilka przykładów:

  • Białe znaki ignorują wszystko, co nie jest spacją, tabulatorem ani znakiem nowej linii.
  • Brain-Flak w zasadzie ignoruje wszystko poza tym ()[]{}<>. ( @czasami powoduje błąd, gdy interpreter próbuje go parsować jako początek flagi debugowania).
  • oOo KOD ignoruje wszystko oprócz liter. Ponadto wszystkie małe litery są wymienne, podobnie jak wszystkie wielkie litery.
  • Wierd rozróżnia jedynie znaki spacji i spacji.
  • W Wordy niektóre znaki interpunkcyjne są ignorowane, a wszystkie litery są wymienne.
  • Zarówno w nawiasach, jak i w nawiasach piekło ignoruje wszystko oprócz nawiasów.

Naprawiłem ten @błąd.
Wheat Wizard

Spróbuj połączyć
białe znaki

@enedil Nie musisz mieć zakładek w Pythonie. Możesz użyćexec('''...\t\n\40''')
MilkyWay90

5

Uważaj na komentarze zagnieżdżonego bloku

Czasami wiele języków będzie używać tej samej składni do komentowania bloków, co często stanowi przeszkodę w tworzeniu poliglota z tymi dwoma językami. Jednak bardzo rzadko jeden z języków pozwala na zagnieżdżone komentarze blokowe, które można wykorzystywać do tworzenia oddzielnych ścieżek kodu.

Weźmy na przykład tę poliglota:

#[#[]#print("Lily")#]#echo"Nim"

Nim i Lily zarówno używają, jak #[i ]#rozpoczynają i kończą komentarze blokowe, ale tylko Nim pozwala na zagnieżdżone komentarze blokowe.

Lily uważa drugą #[za część pojedynczego komentarza do bloku, a pierwszą ]#za zakończenie komentarza do bloku. ( #Poniższa instrukcja drukowania Lily jest komentarzem do wiersza, który ukrywa kod Nima.)

Nim alternatywnie, widzi #[]#jako zagnieżdżony (choć pusty) komentarz do bloku i print("Lily")#jako komentarz do bloku zewnętrznego.


4

Nie jestem pewien, czy to się liczy, ale ...

Użyj linii shebang, aby zmienić wszystko w prawidłowy perlprogram

Zgodnie z tą odpowiedzią i dokumentacją Perla, jeśli przekażesz dowolny plik, który zaczyna się od linii shebang perl, wywołuje odpowiedni program do jego uruchomienia. Na przykład to

#!/usr/bin/python

for i in range(6):
    print i**2

zostanie wywołany przez interpreter Pythona, jeśli zadzwonisz perl filename.py.


3
Chociaż program można wywołać za pomocą perl, nie staje się on programem Perla.
Paŭlo Ebermann

2
@ PaŭloEbermann Zdaję sobie sprawę, że to granica, dlatego zacząłem swoją odpowiedź od „nie jestem pewien, czy to się liczy”. :) Ale co definiuje prawdziwy Perl, jeśli nie „co jest zapisane w dokumentacji i zwrócone przez implementację referencyjną perl”? Brzmi jak dobry mem filozof -aptor ...
Federico Poloni

1
(Zobacz także tę meta odpowiedź .)
Federico Poloni,

4

Wywołaj nieistniejące funkcje, a następnie wyjdź, oceniając ich argumenty

Wiele języków programowania jest w stanie analizować dowolny identyfikator, a następnie parę nawiasów z wyrażeniami wewnątrz:

identifier(1 + 1)

Czasami forma omawianego identyfikatora może zostać naprawiona, ponieważ konieczne jest nadanie kodu w innym używanym języku. Może to początkowo sprawiać problemy, jeśli identyfikator nie odpowiada funkcji, którą faktycznie posiada język.

Jednak wiele języków programowania ocenia argumenty funkcji, zanim sprawdzą, czy sama funkcja faktycznie istnieje (np. Lua), więc i tak możesz użyć tego rodzaju konstrukcji; wystarczy wyjść z programu gdzieś w argumentach funkcji.

Oto przykład poliglota dc / Lua:

c2pq(1 + #os.exit(print(3)))

c2pqto program dc do wypisania 2 i wyjścia; Lua postrzega to jako nazwę funkcji, ale Lua można zapobiec błędom poprzez umieszczenie komendy exit w argumencie. Dużą zaletą tej konstrukcji jest to, że w przeciwieństwie do przypisania ( c2pq =), nie jest ona automatycznie niezgodna z językami, w których nazwy zmiennych zaczynają się od sigil; składnia nazwy funkcji jest znacznie bardziej spójna we wszystkich językach niż składnia nazwy zmiennej.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.