Um idioma pequeno merece um pequeno intérprete

21

Aqui está uma definição de linguagem muito simples:

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

Programas de exemplo (observe que a cadeia vazia é uma variável, mas eu a usarei com moderação por questões de clareza, e algumas variáveis ​​são zeradas no programa quando geralmente são 0 por padrão):

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

Seu objetivo é escrever o menor intérprete para esse idioma.

  1. O valor de uma variável pode ser arbitrariamente grande e deve ser limitado apenas pela memória total à qual seu idioma tem acesso, em teoria, mas você só precisa lidar com valores de até 2 ^ 256.

  2. Seu programa deve ser capaz de lidar com programas arbitrariamente longos, em teoria, mas você só precisará trabalhar em programas com menos de 2 ^ 32 caracteres. Você também deve lidar com loops de profundidade aninhados até 2 ^ 32.

  3. Você pode assumir que o programa é um programa válido e que você só receberá números inteiros não negativos quando solicitar entrada. Você também pode assumir que apenas caracteres imprimíveis ASCII estão incluídos na sequência de entrada.

  4. A velocidade do programa que você interpreta não importa, já será dolorosamente lento para coisas tão simples quanto a multiplicação de 5 dígitos, sem otimização.

  5. Se você deseja usar um idioma que não possa razoavelmente aceitar entrada ou produzir saída da maneira descrita pelo idioma, use qualquer interpretação que desejar. Isso se aplica a qualquer razão que seu idioma não possa implementar algum comportamento necessário. Eu quero que todos os idiomas possam competir.

  6. O programa mais curto vence. Aplicam-se brechas padrão.

Melão Fricativo
fonte
Como um desafio secundário, quero ver quão curto um programa eu posso escrever que gera o número de 2016, mas primeiro preciso esperar que um intérprete seja escrito para poder testar meu código.
Neil
11
Eu tenho um intérprete em Python 2.7 aqui .
Fricative Melon
2
Como se chama essa linguagem? Ele merece um lugar no esolangs.org #
wizzwizz4
@Neil eu consegui fazê-lo em 72 caracteres
Fricativas Melon
@FricativeMelon 72? Eu posso fazer isso em 43!
Neil

Respostas:

4

Ruby, 182 bytes

$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 *$*

Tente assim:

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

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

Como funciona

A rfunção tokeniza uma sequência de entrada e executa cada token:

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

Procuramos alguma $2correspondência de nome de variável [^!?^<>]*, seguida por

  • <...>onde ...corresponde a zero ou mais programas ( \gé recursão); nesse caso, $4não énil
  • Um !, ?ou ^caráter, capturado por $3, caso em que $4é nil.

Então a lógica para executar um token é bastante simples ao recuá-lo um pouco:

$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
fonte
Estou realmente curioso como isso funciona.
Jerry Jeremiah
1

JavaScript (ES6) 184 194 209

Editar simplificado (usar parâmetros de função para entrada e saída parecia uma boa idéia, mas não era), mais 1 byte salvo thx @ ӍѲꝆΛҐӍΛПҒЦꝆ

Editar 2 Análise modificada. A lógica para incremento / entrada é emprestada da resposta de @ 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")

Menos golfe

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

TESTE O snippet começa a avaliar 2016 usando o programa publicado por @Neil. Seja paciente...

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
fonte
Está usando evalpara evitar returnnão uma opção?
Mama Fun Roll
@ ҒЦꝆПҒЦꝆ sim, eval economiza 1 byte. Eu ainda estou procurando algo mais substancial
edc65
0

Perl, 251 bytes

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

Versão mais fácil de ler:

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

Isso desperdiça um monte de bytes consertando loops para serem saltos diretos, mas a digitalização para trás em busca do loop ofendeu meu senso de estética.

David Morris
fonte
0

C ++ padrão, 400 bytes

Isso compila com 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]);}

Talvez eu possa encurtar um pouco mais. Se você tiver algumas sugestões, por favor, comente.

Jerry Jeremiah
fonte
367 bytes
ceilingcat 22/02