Маленький язык заслуживает небольшого переводчика

21

Вот очень простое определение языка:

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

Примеры программ (обратите внимание, что пустая строка является переменной, но я буду использовать ее экономно для ясности, а некоторые переменные обнуляются в программе, когда они обычно равны 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

Ваша цель - написать самого маленького переводчика для этого языка.

  1. Значение переменной может быть произвольно большим и должно быть ограничено только общим объемом памяти, к которому у вашего языка есть доступ, теоретически, но вы должны обрабатывать только значения до 2 ^ 256.

  2. Ваша программа должна быть в состоянии обрабатывать произвольно длинные программы, теоретически, но вам нужно будет работать только с программами длиной до 2 ^ 32 символов. Вы также должны обрабатывать вложенные циклы глубиной до 2 ^ 32.

  3. Вы можете предположить, что эта программа является допустимой, и что вы всегда получите неотрицательные целые числа только тогда, когда попросите ввести. Вы также можете предположить, что во входную строку включены только печатные символы ASCII.

  4. Скорость интерпретируемой вами программы не имеет значения, она уже будет мучительно медленной для таких простых вещей, как пятизначное умножение без оптимизации.

  5. Если вы хотите использовать язык, который не может разумно принимать ввод или производить вывод способом, описанным языком, используйте любую интерпретацию, которую вы хотите сделать возможной. Это относится к любой причине, по которой ваш язык не может реализовать требуемое поведение. Я хочу, чтобы все языки могли конкурировать.

  6. Кратчайшая программа выигрывает. Применяются стандартные лазейки.

Фрикативная дыня
источник
В качестве дополнительной задачи я хочу посмотреть, насколько короткая программа, которую я могу написать, выдает число 2016, но сначала мне нужно дождаться написания интерпретатора, чтобы я мог проверить свой код.
Нил
1
У меня есть переводчик в Python 2.7 здесь .
Фрикативная дыня
2
Как называется этот язык? Это заслуживает места в esolangs.org
wizzwizz4
@Neil Мне удалось сделать это за 72 символа
Fricative Melon
@FricativeMelon 72? Я могу сделать это в 43!
Нил,

Ответы:

4

Рубин, 182 байта

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

Попробуйте это так:

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

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

Как это устроено

rФункция размечает входную строку и выполняет каждый маркер:

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

Мы ищем какое-то $2совпадение имен переменных [^!?^<>]*, а затем либо

  • <...>где ...соответствует нулю или более программ ( \gэто рекурсия), в этом случае $4неnil
  • !, ?Или ^характер, захвачен $3, и в этом случае $4это nil.

Тогда логика выполнения токена довольно проста, если сделать для него отступ:

$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.
Линн
источник
Мне действительно любопытно, как это работает.
Джерри Иеремия
1

JavaScript (ES6) 184 194 209

Edit Упрощенный (использование параметров функции для ввода и вывода казалось хорошей идеей, но это не так), еще 1 байт сохранен thx @ ӍѲꝆΛҐӍΛПҐӍЦꝆ

Редактировать 2 Модифицированный разбор. Логика приращения / ввода заимствована из ответа @ Линн

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

Меньше гольфа

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

ТЕСТ Фрагмент начала оценки 2016 года с помощью программы, опубликованной @Neil. Потерпи...

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
источник
Использование, evalчтобы избежать returnне вариант?
Мама Fun Roll
@ ӍѲꝆΛҐӍΛПҒЦꝆ да, eval сохраняет 1 байт. Я все еще ищу что-то более существенное
edc65
0

Perl, 251 байт

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

Легче читать версию:

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

Это тратит кучу байтов, исправляя циклы, чтобы быть прямыми прыжками, но сканирование назад для начала цикла оскорбило мое чувство эстетики.

Дэвид Моррис
источник
0

Стандарт С ++, 400 байт

Это компилируется с 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]);}

Я мог бы сократить это еще немного. Если у вас есть предложения, пожалуйста, прокомментируйте.

Джерри Иеремия
источник
367 байт
floorcat