Une petite langue mérite un petit interprète

21

Voici une définition de langage très 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

Exemples de programmes (notez que la chaîne vide est une variable, mais je vais l'utiliser avec parcimonie par souci de clarté, et certaines variables sont remises à zéro dans le programme lorsqu'elles sont généralement égales à 0 par défaut):

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

Votre objectif est d'écrire le plus petit interprète pour cette langue.

  1. La valeur d'une variable peut être arbitrairement grande et ne devrait être limitée que par la mémoire totale à laquelle votre langue a accès, en théorie, mais vous n'êtes tenu de gérer que des valeurs allant jusqu'à 2 ^ 256.

  2. Votre programme devrait être capable de gérer des programmes arbitrairement longs, en théorie, mais vous ne devrez travailler que sur des programmes de moins de 2 ^ 32 caractères. Vous devez également gérer des boucles imbriquées d'une profondeur allant jusqu'à 2 ^ 32.

  3. Vous pouvez supposer que le programme est un programme valide et que vous n'obtiendrez que des nombres entiers non négatifs lorsque vous demandez une entrée. Vous pouvez également supposer que seuls les caractères imprimables ASCII sont inclus dans la chaîne d'entrée.

  4. La vitesse du programme que vous interprétez n'a pas d'importance, il sera déjà douloureusement lent pour des choses aussi simples qu'une multiplication à 5 chiffres, sans optimisation.

  5. Si vous souhaitez utiliser une langue qui ne peut raisonnablement accepter une entrée ou produire une sortie de la manière décrite par la langue, utilisez toute interprétation que vous souhaitez rendre possible. Cela s'applique à toute raison pour laquelle votre langue ne peut pas implémenter un comportement requis. Je veux que toutes les langues puissent être compétitives.

  6. Le programme le plus court gagne. Des échappatoires standard s'appliquent.

Melon fricatif
la source
Comme défi secondaire, je veux voir à quel point je peux écrire un programme qui produit le numéro 2016, mais je dois d'abord attendre qu'un interprète soit écrit pour pouvoir tester mon code.
Neil
1
J'ai un interprète en Python 2.7 ici .
Fricative Melon
2
Comment s'appelle cette langue? Il mérite une place sur esolangs.org
wizzwizz4
@Neil J'ai réussi à le faire en 72 caractères
Fricative Melon
@FricativeMelon 72? Je peux le faire en 43!
Neil

Réponses:

4

Rubis, 182 octets

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

Essayez-le comme ceci:

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

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

Comment ça marche

La rfonction tokenize une chaîne d'entrée et exécute chaque token:

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

Nous recherchons une $2correspondance de nom de variable [^!?^<>]*, suivie soit

  • <...>...correspond à zéro ou plusieurs programmes ( \gest la récursivité), auquel cas $4n'est pasnil
  • A !, ?ou ^caractère capturé par $3, auquel cas il l' $4est nil.

Ensuite, la logique d'exécution d'un jeton est assez simple lors de son retrait un peu:

$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
la source
Je suis vraiment curieux de savoir comment cela fonctionne.
Jerry Jeremiah
1

JavaScript (ES6) 184 194 209

Édition simplifiée (l'utilisation des paramètres de fonction pour l'entrée et la sortie semblait une bonne idée, mais ce n'était pas le cas), 1 octet de plus enregistré thx @ ӍѲꝆΛҐӍΛПҒЦꝆ

Edit 2 Parsing modifié. La logique d'incrémentation / entrée est empruntée à la réponse 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")

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

TEST L'extrait commence à évaluer 2016 à l'aide du programme publié par @Neil. Sois patient...

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
la source
Utiliser evalpour éviter returnn'est-il pas une option?
Mama Fun Roll
@ ӍѲꝆΛҐӍΛПҒЦꝆ oui, eval enregistre 1 octet. Je cherche toujours quelque chose de plus substantiel
edc65
0

Perl, 251 octets

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

Version plus facile à lire:

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

Cela gaspille un tas d'octets fixant les boucles pour être des sauts directs, mais le balayage vers l'arrière pour le début de la boucle a offensé mon sens de l'esthétique.

David Morris
la source
0

C ++ standard, 400 octets

Cela compile avec 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]);}

Je pourrais peut-être raccourcir encore un peu. Si vous avez des suggestions, veuillez commenter.

Jerry Jeremiah
la source
367 octets
plafond