Un lenguaje pequeño merece un intérprete pequeño


21

Aquí hay una definición de lenguaje muy simple:

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 ejemplo (tenga en cuenta que la cadena vacía es una variable, pero la usaré con moderación en aras de la claridad, y algunas variables se ponen a cero en el programa cuando generalmente son 0 por defecto):

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

Su objetivo es escribir el intérprete más pequeño para este idioma.

  1. El valor de una variable puede ser arbitrariamente grande y solo debe estar limitado por la memoria total a la que tiene acceso su idioma, en teoría, pero solo debe manejar valores de hasta 2 ^ 256.

  2. Su programa debería ser capaz de manejar programas arbitrariamente largos, en teoría, pero solo se le pedirá que trabaje en programas de menos de 2 ^ 32 caracteres de longitud. También debe manejar bucles anidados de profundidad de hasta 2 ^ 32.

  3. Puede suponer que el programa es válido y que solo obtendrá enteros no negativos cuando solicite información. También puede suponer que solo los caracteres imprimibles ASCII se incluyen en la cadena de entrada.

  4. La velocidad del programa que interpreta no importa, ya será muy lenta para cosas tan simples como la multiplicación de 5 dígitos, sin optimización.

  5. Si desea utilizar un idioma que no puede aceptar razonablemente la entrada o producir la salida de la manera descrita por el idioma, utilice cualquier interpretación que desee para que sea posible. Esto se aplica a cualquier razón por la cual su idioma no pueda implementar algún comportamiento requerido. Quiero que todos los idiomas puedan competir.

  6. El programa más corto gana. Se aplican lagunas estándar.


Como desafío adicional, quiero ver qué tan breve es un programa que puedo escribir que genere el número 2016, pero primero tengo que esperar a que se escriba un intérprete para poder probar mi código.
Neil

1
Tengo un intérprete en Python 2.7 aquí .
Melón Fricativo

2
¿Cómo se llama este lenguaje? Se merece un lugar en esolangs.org
wizzwizz4

@Neil logré hacerlo en 72 caracteres
Fricative Melon

@FricativeMelon 72? ¡Puedo hacerlo en 43!
Neil

Respuestas:


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

Pruébalo así:

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

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

Cómo funciona

La rfunción tokeniza una cadena de entrada y ejecuta cada token:

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

Buscamos alguna $2coincidencia de nombre de variable [^!?^<>]*, seguida de

  • <...>donde ...coincide con cero o más programas ( \ges recursividad), en cuyo caso $4no esnil
  • A !, ?o ^carácter, capturado por $3, en cuyo caso $4es nil.

Entonces, la lógica para ejecutar un token es bastante simple cuando se sangra un poco:

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

Tengo mucha curiosidad por cómo funciona esto.
Jerry Jeremiah

1

JavaScript (ES6) 184 194 209

Edición simplificada (usar parámetros de función para entrada y salida parecía una buena idea, pero no lo fue), 1 byte más guardado thx @ ӍѲꝆΛҐӍΛПҒЦꝆ

Editar 2 Análisis modificado. La lógica para el incremento / entrada se toma prestada de la respuesta 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 golf

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

PRUEBA El fragmento comienza a evaluar 2016 utilizando el programa publicado por @Neil. Se 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>


¿Usar evalpara evitar returnno es una opción?
Mama Fun Roll

@ ӍѲꝆΛҐӍΛПҒЦꝆ sí, eval ahorra 1 byte. Todavía estoy buscando algo más sustancial
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++;} 

Versión más fácil de leer:

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

Esto desperdicia un montón de bytes arreglando bucles para ser saltos directos, pero escanear hacia atrás para el inicio del bucle ofendió mi sentido de la estética.


0

C ++ estándar, 400 bytes

Esto compila con 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]);}

Podría acortarlo un poco más. Si tiene alguna sugerencia por favor comente.


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.