Mały język zasługuje na małego tłumacza

21

Oto bardzo prosta definicja języka:

A Variable is any string that does not contain ^, <, >, !, or ?
The empty string is a valid variable identifier
The value of every variable starts at 0.
A Statement is one of (var is a Variable, P is a Program):
    var^   -> changes var to be equal to 1 more than itself
    var<P> -> while var > 0, changes var to be equal to 1 less than itself, then runs P
    var! -> output value of var
    var? -> ask for non-negative integer as input, increase var by that value
A Program is a concatenation of Statements, running a Program means running each Statement in order

Przykładowe programy (zwróć uwagę, że pusty ciąg znaków jest zmienną, ale używam go oszczędnie dla zachowania przejrzystości, a niektóre zmienne są zerowane w programie, gdy zwykle mają domyślnie 0):

<>: sets the value of the empty string variable to 0
b<>b?b<a^>: asks for b, then adds the value stored in b to a, zeroing b in the process
b<>b?a<>b<a^>: asks for b, then sets a to the value of b, zeroing b in the process
a<>c<>b<a^c^>c<b^> : copies the value in b into a without zeroing it
b<>c<>a<c^c^c<b^>>b! : outputs a multiplied by 2
b^b<a<>a?a!b^> : outputs what you input, forever

Twoim celem jest napisanie najmniejszego tłumacza dla tego języka.

  1. Wartość zmiennej może być dowolnie duża i powinna być ograniczona tylko całkowitą pamięcią, do której ma dostęp Twój język, teoretycznie, ale wymagana jest jedynie obsługa wartości do 2 ^ 256.

  2. Twój program powinien teoretycznie obsługiwać dowolnie długie programy, ale będziesz musiał pracować tylko nad programami o długości poniżej 2 ^ 32 znaków. Musisz również obsługiwać zagnieżdżone pętle o głębokości do 2 ^ 32.

  3. Możesz założyć, że program jest prawidłowym programem i że zawsze otrzymasz nieujemne liczby całkowite, gdy poprosisz o dane wejściowe. Możesz również założyć, że w ciągu wejściowym zawarte są tylko znaki drukowalne ASCII.

  4. Szybkość interpretowanego programu nie ma znaczenia, będzie już boleśnie powolny dla rzeczy tak prostych jak 5-cyfrowe mnożenie, bez optymalizacji.

  5. Jeśli chcesz użyć języka, który nie może racjonalnie zaakceptować danych wejściowych lub wygenerować danych wyjściowych w sposób opisany przez ten język, użyj dowolnej interpretacji, która ma to umożliwić. Dotyczy to każdego powodu, dla którego Twój język nie może wdrożyć niektórych wymaganych zachowań. Chcę, aby wszystkie języki mogły konkurować.

  6. Najkrótszy program wygrywa. Obowiązują standardowe luki.

Fricative Melon
źródło
Jako wyzwanie poboczne chcę zobaczyć, jak krótki program mogę napisać, który wypisuje liczbę 2016, ale najpierw muszę poczekać na napisanie interpretera, aby mógł przetestować mój kod.
Neil,
1
Mam tłumacza w Pythonie 2.7 tutaj .
Fricative Melon
2
Jak nazywa się ten język? Zasługuje na miejsce na esolangs.org
wizzwizz4
@Neil Udało mi się to zrobić 72 znakami
Fricative Melon
@FricativeMelon 72? Mogę to zrobić w 43!
Neil

Odpowiedzi:

4

Rubinowy, 182 bajty

$h=Hash.new 0
def r(c)c.scan(/(([^!?^<>]*)(<(\g<1>*)>|[!?^]))/){$4?($1=~/(.*?)<(.*)>/
($h[$1]-=1;r$2)while$h[$1]>0):$3<?"?p($h[$2]):$h[$2]+=$3<?@?STDIN.gets.to_i:
1}end
r IO.read *$*

Spróbuj tak:

$ cat code
a?b<>c<>a<c^c^c<b^>>b!

$ ruby lynn.rb code
3                           <-- input
6                           <-- output

Jak to działa

rFunkcja tokenizes ciąg wejściowy i wykonuje każdy żeton:

def r(c)
    c.scan(/(([^!?^<>]*)(<(\g<1>*)>|[!?^]))/){
        ...
    }
end

Szukamy $2dopasowania nazwy zmiennej [^!?^<>]*, po którym następuje jedno z nich

  • <...>gdzie ...pasuje zero lub więcej programów ( \gjest rekurencją), w którym $4to przypadku nie jestnil
  • A !, ?lub ^znak, przechwycony przez $3, w którym $4to przypadku jest nil.

Zatem logika wykonywania tokena jest dość prosta, gdy wcina się go trochę:

$4 ? (                                    # If it's a loop:
    $1 =~ /(.*?)<(.*)>/                   #   Re-match token*
    ($h[$1]-=1; r $2) while $h[$1] > 0    #   Recurse to run loop
) :                                       # Else:
    $3 < ?"                               #   If it's an !:
      ? p($h[$2])                         #     Print the var
      : $h[$2] +=                         #   Else, increment it by:
          $3 < ?@                         #     If it's a ?:
              ? STDIN.gets.to_i           #       User input
              : 1                         #     Else: 1

* There's an oniguruma bug, I think, that keeps me from simply using $3 here.
Lynn
źródło
Jestem naprawdę ciekawy, jak to działa.
Jerry Jeremiah
1

JavaScript (ES6) 184 194 209

Edycja Uproszczona (użycie parametrów funkcji dla danych wejściowych i wyjściowych wydawało się dobrym pomysłem, ale tak nie było), zapisano jeszcze 1 bajt thx @ ӍѲꝆΛҐӍΛПҒЦꝆ

Edytuj 2 Zmodyfikowane parsowanie. Logika inkrementacji / wprowadzania jest zapożyczona z odpowiedzi @ Lynn

F=(p,i=0,v={},n='')=>eval("for(;c='>?^!<'.indexOf(q=p[i++]||'');n=~c?'':n+q)if(c>3){for(;v[n]--;)F(p,i,v);i=F(p,i,v[n]=0)}else~c&&v?c>2?alert(v[n]|0):v[n]=~~v[n]+(--c||+prompt()):0;i")

Mniej golfa

F=(p,      // program 
   i = 0,  // initial instruction pointer  
   v = {}, // variables (default to empty) or if 0, flag of dummy execution
   n = ''    // name of current variable (has to be local for recursive calls)
{
  for(; c='>?^!<'.indexOf(q=p[i++]||''); )
  // q = current character
  // c = current command (int 0..4 or -1 id not recognized)
  //     note 0 end of subprogram or end of program
  {
    if(c>3) // 4='<' call subprogram - recursive
    {
      for(;v[n]--;)
        F(p,i,v); // conditional call, repeated - using real environment
      v[n] = 0; // Reset variable at loop end
      i=F(p,i,0) // one more unconditional dummy call, just to advance i
    }
    else
      ~c&&v? // if valid command (1..3) and not dummy
      c>2?
        alert(v[n]|0) // output, undefined becomes 0
        :v[n]=~~v[n]+(--c||+prompt()) // inc with 1 or user input
      :0     // not valid command or dummy, do nothing
    n=~c?'':n+q // reset or update current variable name
  }
  return i // return current istruction pointer (for recursive calls)
}

TEST Fragment rozpoczyna ocenę 2016 za pomocą programu opublikowanego przez @Neil. Bądź cierpliwy...

F=(p,i=0,v={},n='')=>eval("for(;c='>?^!<'.indexOf(q=p[i++]||'');n=~c?'':n+q)if(c>3){for(;v[n]--;)F(p,i,v);i=F(p,i,v[n]=0)}else~c&&v?c>2?alert(v[n]|0):v[n]=~~v[n]+(--c||+prompt()):0;i")

// TEST
function definput(){  I.disabled = KI.checked; }
function defoutput(){  O.disabled = KO.checked; }

function run()
{
  var prog=P.value, irows = I.value.split('\n'), pi=0;
  var fout=x=>O.value+=x+'\n';
  var fin=x=>irows[pi++];
  var saveAlert=alert, savePrompt=prompt
  if (!KO.checked) alert=fout,O.value=''
  if (!KI.checked) prompt=fin
  
  F(prog);
  
  alert=saveAlert
  prompt=savePrompt
}

P.value="^^^^<a^a^>a<^^^^><a^b^>a<c<b^^>b<c^^>>!"

run()
Program <button onclick="run()">RUN</button><br>
<textarea id=P></textarea><br>
Input (or <input type=checkbox id=KI onclick="definput()"> interactive prompt)<br>
<textarea id=I>5</textarea><br>
Output (or <input type=checkbox id=KO onclick="defoutput()"> popup)<br>
<textarea id=O readonly></textarea><br>

edc65
źródło
Czy używanie, evalaby uniknąć returnnie jest opcją?
Mama Fun Roll
@ ҒЦꝆПҒЦꝆ tak, eval zapisuje 1 bajt. Wciąż szukam czegoś bardziej znaczącego
edc65,
0

Perl, 251 bajtów

@p=split/([<>!?^])/,<>;for$c(0..$#p){$_=$p[$c];/</&&push@j,$c;if(/>/){$a=pop@j;$p[$c]=">$a";$p[$a]="<$c";}}while($c<$#p){$_=$p[$c];/\^/&&$v{$l}++;/!/&&print$v{$l};/\?/&&($v{$l}=<>);/<(\d+)/&&($v{$l}?$v{$l}--:($c=$1));/>(\d+)/&&($c=$1-2);$l=$_;$c++;} 

Wersja łatwiejsza do odczytania:

# treat the first line of input as a program

# split on punctuation keywords; @p will contain the program as a list
# of tokens (including whitespace between adjacent punctuation)
@p = split /([<>!?^])/, <>;

# rewrite jump addresses

# the interpreter could scan backwards to avoid this, but that idea
# makes me feel dirty
for $c (0..$#p) {
    $_ = $p[$c];
    # save loop-start address on stack
    /</ && push @j, $c;
    if (/>/) {
        # if we encounter a loop-end instruction, rewrite it and the
        # corresponding loop-start to include the address (of the
        # instruction---jumps have to offset from this)
        $a = pop @j;
        $p[$c] = ">$a";
        $p[$a] = "<$c";
    }
}

# execute the program

# our program is already in @p

# $c will contain our program counter

# $l will contain the name of the last-referenced variable

while ($c < $#p) {
    # move current instruction into $_ for shorter matching
    $_ = $p[$c];

    # increment instruction
    /\^/ && $v{$l}++;

    # output instruction
    /!/ && print $v{$l};

    # input instruction
    /\?/ && ($v{$l} = <>);

    # loop start, including address
    /<(\d+)/ && ($v{$l} ? $v{$l}-- : ($c = $1));

    # loop end, including address
    />(\d+)/ && ($c = $1-2);

    # copy current instruction into "last variable name"---this will
    # sometimes contain operators, but we have null-string
    # instructions between adjacent operators, so it'll be fine
    $l = $_;

    # advance the program counter
    $c++;
}

To marnuje wiązkę bajtów ustalającą pętle na bezpośrednie skoki, ale skanowanie wstecz w poszukiwaniu pętli uraziło moje poczucie estetyki.

David Morris
źródło
0

Standardowy C ++, 400 bajtów

To się kompiluje z g++ -g test.cpp -Wall -Wextra -pedantic -std=gnu++11

#include<map>
#include<cstring>
#define b ;break;case
#define u unsigned long long
std::map<std::string,u>V;void r(char*s){char*p,*q,*e;for(u c;*s;s=p){p=strpbrk(s,"^<?!");c=*p;*p++=0;switch(c){b'^':V[s]++b'<':for(e=p,c=0;*e!='>'||c;e++)c+=(*e=='<')-(*e=='>');*e++=0;while(V[s]>0){V[s]--;r(q=strdup(p));free(q);}p=e;b'?':scanf("%llu",&V[s])b'!':printf("%llu",V[s]);}}}int main(int,char*v[]){r(v[1]);}

Może uda mi się to trochę skrócić. Jeśli masz jakieś sugestie, prosimy o komentarz.

Jerry Jeremiasz
źródło
367 bajtów
pułapkot