Как стать автором
Обновить

Эзотерический язык LMC

Время на прочтение 15 мин
Количество просмотров 7.6K
Часть I
Часть II
Часть III
Часть IV
Часть V

Данная статья посвящена созданию интерпретатора некого эзотерического языка, в основе которого лежит архитектура Little Man Computer.
Фактически, это эмулятор ассемблера для LMC, только здесь вместо ассемблерных команд
INP (загрузить число в аккумулятор из устройства ввода),
STA (сохранить число из аккумулятора в память),
ADD (прибавить к числу в аккумуляторе число из памяти),
SUB (вычесть из числа в аккумуляторе число из памяти),
OUT (вывести число в аккумуляторе устройство вывода)
… используются спец-символы.
Команды переходов от ячейки к ячейке в памяти команд соответствуют командам, сдвигающим считывающую головку машины Тьюринга в языках brainfuck и P′′.
В языке bf команда + (плюс) производит замену текущего символа $ c $ следующим за ним (в ASCII-таблице) символом $ c_{n+1} $. Так, если на ленте Тьюринга изначально находится символ «0», то команда $ c \rightarrow c_{n+1} $ запишет вместо него символ «1».
Здесь же эта команда производит арифметическое сложение числа из текущей ячейки массива данных (памяти данных) с числом, хранящимся в аккумуляторе.
Ячейки массива данных изначально заполнены нулями, загрузка числа в текущую ячейку $ n $ из аккумулятора производится соответствующей командой
$ acc \rightarrow data \; memory[n] $.
Например, в аккумуляторе асс находится число а.
Для того, чтобы удвоить это число, загрузим его в текущую ячейку n
$ acc \rightarrow data \; memory[n] $
затем сложим два числа
$ acc + data \; memory[n] $
В результате этих действий в аккумуляторе окажется удвоенное число, т.е.

  • Пусть команде INP соответствует ,
  • команде OUT соответствует .
  • команде ADD соответствует +
  • команде SUB соответствует
  • команде STA соответствует ~
  • команде LDA соответствует ^

Напишем программу, которая загружает число из устройства ввода в аккумулятор, сохраняет число в памяти, прибавляет число из памяти к аккумулятору (удваивает число) и выводит удвоенное число в устройство вывода.

На ассемблере LMC эта программа будет выглядеть вот так (20- это номер произвольной ячейки в памяти)

 INP
 STA 20
 ADD 20 
 OUT


На языке LMC эта программа будет выглядеть вот так ,~+.
Загрузить число в аккумулятор
Загрузить число в текущую ячейку памяти
Сложить два числа
Вывести результат в консоль

В нашей LMC-машине память кодов command_mem и память данных data_mem разделены (гарвардская архитектура).

Код на языке Си
#include <stdio.h>
int main(void) {
int i=0; // индекс текущей команды
int j=0; // индекс массива данных
int acc = 0; //аккумулятор
char command_mem[100] = ",~+."; //память команд
int data_mem[10]={0};           // память данных
     
while (command_mem[i] != '\0') {
if (command_mem[i]==',') // считываем число в аккумулятор 
    scanf("%d", &acc);   
if (command_mem[i]=='+') // прибавляем число из data_mem
    acc=acc+data_mem[j]; // к аккумулятору
if (command_mem[i]=='~') // загружаем число из аккумулятора
    data_mem[j]=acc;     // в память данных  
if (command_mem[i]=='.') // выводим число из аккумулятора на экран  
    printf("Output: %d",acc);
i++; //увеличиваем индекс текущей команды         
}
//переход на новую строку
printf("\n"); 
// выводим массив данных
for (int k = 0; k<10; k++)
   printf("%d ", data_mem[k]);
return 0;
} 

При загрузке в устройство ввода числа 12 получаем число 24.

Примечание
На самом деле не обязательно искать символ окончания строки '\0'. Вместо этого можно просто перебрать все элементы массива command_mem[]

Добавим команды для

  • перехода к следующей ячейке >
  • перехода к предыдущей ячейке <

При обработке символа > будем увеличивать индекс j массива данных data_mem

if(command_mem[i]=='>')  j++;

При обработке символа < будем уменьшать индекс j массива данных data_mem
if(command_mem[i]=='<')  j--;

Для прыжка вперёд по команде ? необходимо осуществить переход на метку !
Для этого необходимо пропускать все символы между ? и ! при переборе символов.

if(command_mem[i]=='?') {
   while(command_mem[i] != '!' ) {
    i++;
    } 
} 

Для сравнения напишем программу, в которой пользователь вносит число (например 5) командой ,, которое затем загружается в пять ячеек подряд командами
~>~>~>~>~
#include <stdio.h>
int main(void) {
int i=0; // индекс текущей команды
int j=0; // индекс массива данных
int acc = 0; //аккумулятор
char command_mem[100] = ",~>~>~>~>~"; //память команд
int data_mem[10]={0};                // память данных
     
while (command_mem[i] != '\0') {
if (command_mem[i]==',') // считываем число в аккумулятор 
    scanf("%d", &acc);   
if (command_mem[i]=='+') // прибавляем число из data_mem
    acc=acc+data_mem[j]; // к аккумулятору
if (command_mem[i]=='~') // загружаем число из аккумулятора
    data_mem[j]=acc;     // в память данных  
if (command_mem[i]=='.') // выводим число из аккумулятора на экран  
 printf("Output: %d",acc);
if(command_mem[i]=='>')      //переход на следующий элемент данных
    j++;
if(command_mem[i]=='<')      //переход на предыдущий элемент данных
    j--;	
if(command_mem[i]=='?') {   // прыжок на метку !
    while(command_mem[i] != '!') 
        i++; 
    } 	
i++; //увеличиваем индекс текущей команды         
}
//переход на новую строку
printf("\n"); 
// выводим массив данных
for (int k = 0; k<10; k++)
   printf("%d ", data_mem[k]);
return 0;
} 

В итоге получим массив 5 5 5 5 5 0 0 0 0 0
ideone.com

и ту же самую программу, в которой несколько шагов вперёд пропускаются командой безусловного перехода ,~>~?>~>~>~!

Получим массив 5 5 0 0 0 0 0 0 0 0
ideone.com
Об обозначении меток именами и переходе на метку по имени можно прочитать в конце статьи.

Добавим флаг pzflag
Флаг будет поднят только тогда, когда число, хранящееся в аккумуляторе, больше нуля либо равняется нулю (переменную можно так и назвать Pozitive_Zero_Flag).

    if(acc>=0){
    	 pzflag=1;}
    else {
    	 pzflag=0;}	

Переход вперёд по условию pzflag == 1 будем осуществлять командами { и }. Для перехода на метку будем перебирать все символы подряд, пока не встретим символ }

if(command_mem[i]=='{') && (pzflag==1){
    	while(command_mem[i] != '}' ) 
       i++; 
       } 


Далее, пусть в памяти у нас хранятся два числа
data_mem[0]=3 и data_mem[1]=5
Напишем программу, которая выводит большее (максимальное) из двух хранящихся в памяти чисел.

#include <stdio.h>
int main(void) {
int i=0;         // индекс текущей команды
int j=0;         // индекс массива данных
int acc = 0;
int pzflag = 1; // Флаг acc>=0
char command_mem[100] = "^>-{^?}<^!."; // максимум
int data_mem[10]={0}; 
data_mem[0]=3;   //инициализируем начальные значения
data_mem[1]=5;
 
while ( command_mem[i] != '\0') {
if(command_mem[i]==',')      // считываем число в аккумулятор
    scanf("%d", &acc);	
if(command_mem[i]=='+')      // прибавляем число из data_mem
    acc=acc+data_mem[j];     // к аккумулятору
if(command_mem[i]=='-')      // вычитаем число data_mem
    acc=acc-data_mem[j];     // из аккумулятора
if(command_mem[i]=='>')      // переход на следующий элемент данных
    j++;
if(command_mem[i]=='<')      //переход на предыдущий элемент данных
    j--;
if(command_mem[i]=='~')      // загружаем число из аккумулятора
    data_mem[j]=acc;         // в память данных
if(command_mem[i]=='^')      // загружаем число из data_mem
    acc=data_mem[j];         // в аккумулятор
if(command_mem[i]=='.') {    // выводим число из аккумулятора на экран
    printf("Output: %d",acc); 
    printf(" ");
	};
if(command_mem[i]=='?') {     // прыжок на метку !
    while(command_mem[i] != '!') 
        i++; 
    } 
if (command_mem[i]=='{' && pzflag==1) { // прыжок по условию acc>=0
    while(command_mem[i] != '}') 
        i++; 	 
    } 
if(acc>=0){    // Поднимаем флаг, если acc>=0
    pzflag=1; }
else {
    pzflag=0; }	
i++;   //увеличиваем индекс текущей команды         
}
//переход на новую строку
printf("\n");
// выводим массив данных
for (int k = 0; k<10; k++)
printf("%d ", data_mem[k]);
return 0;
}

ideone.com

Для прыжка назад добавим переменную pz_prev.
Если текущим символом является }, то «поднимаем флаг» pz_prev

if (command_mem[i]=='}') 
     pz_prev=1; 

Если метка } предшествует команде {, значит надо совершать прыжок назад

if (command_mem[i]=='{' && pzflag==1 && pz_prev==1) {
	while(command_mem[i] != '}') 
         i--; 	 
         } 


Напишем программу, которая выводит чётные числа с 10 до 0.

Загрузим числа 10 и 2 в массив data_mem, далее, пока число в acc больше или равно нулю, будем от 10 отнимать 2 и выводить результат
#include <stdio.h>
int main(void) {
int i=0;          // индекс текущей команды
int j=0;          // индекс массива данных
int acc = 0;    
int pzflag = 1;  // Флаг acc>=0
int pz_prev=0;   // прыжок назад по условию acc>=0
     
char command_mem[100] = "}^.>-<~{"; // вывод чётных чисел с 10 до 0
int data_mem[10]={0}; 
data_mem[0]=10;   //инициализируем начальные значения    
data_mem[1]=2;
     
while ( command_mem[i] != '\0') {
if(command_mem[i]==',')  // считываем число в аккумулятор
    scanf("%d", &acc);	
if(command_mem[i]=='+')  // прибавляем число из data_mem
    acc=acc+data_mem[j]; // к аккумулятору
if(command_mem[i]=='-')  // вычитаем число data_mem 
    acc=acc-data_mem[j]; // из аккумулятора
if(command_mem[i]=='>')  // переход на следующий элемент данных
    j++;
if(command_mem[i]=='<')  // переход на предыдущий элемент данных
    j--;
if(command_mem[i]=='~')  // загружаем число из аккумулятора 
    data_mem[j]=acc;     // в память данных
if(command_mem[i]=='^')  // загружаем число из data_mem
    acc=data_mem[j];     // в аккумулятор
if(command_mem[i]=='.') {  // выводим число из аккумулятора на экран 
    printf("Output: %d",acc); 
    printf(" ");
    };
if (command_mem[i]=='}')  // прыгаем назад?
    pz_prev=1; 
     
if(command_mem[i]=='?') {      // прыжок на метку !
    while(command_mem[i] != '!') 
        i++;  
    } 
if (command_mem[i]=='{' && pzflag==1 && pz_prev==0) { // прыжок вперёд
    while(command_mem[i] != '}')                      // по условию acc>=0
        i++;  
    } 
if (command_mem[i]=='{' && pzflag==1 && pz_prev==1) { // прыжок назад
    while(command_mem[i] != '}')                      // по условию acc>=0
        i--; 	 
    } 
if(acc>=0){      // Поднимаем флаг, если acc>=0
    pzflag=1;}
else {
    pzflag=0;}	
     
//printf("i=%d",i);printf(" ");
i++;   //увеличиваем индекс текущей команды
}
//переход на новую строку
printf("\n");
// выводим массив данных
for (int k = 0; k<10; k++)
printf("%d ", data_mem[k]);
return 0;
}

ideone.com

Об организации циклов с помощью спец-операторов, коими являются квадратные скобки, после спойлера. Про циклы с использованием переходов под спойлером.
Циклы с переходами
Для того, чтобы перемножить два числа A и B, необходимо A раз к B прибавить B.

Будем в цикле на каждой итерации вычитать из A единицу, и, пока А не ноль, прибавлять В к В.

LMCode-программа }>>>^<+>~<<< ^>-<~{>>>^. перемножает числа А+1 и В, т.е. один множитель необходимо заведомо уменьшить на единицу.

Так происходит потому, что цикл завершится только тогда, когда в acc окажется -1.

Например, умножим 5 на 5.

Для этого предварительно поместим необходимые значения в data_mem

      data_mem[0]=4; 
      data_mem[1]=1;
      data_mem[2]=5;

ideone.com

Добавим безусловные переходы назад. Для этого добавим переменную prev.

Также добавим переходы вперёд/назад по условию acc=0. Для таких переходов создадим флаг zflag(Zero_Flag) и переменную z_prev.

Переходы по условию zflag == 1 будем осуществлять командами ( и )
Перемножим 5 и 5 используя безусловный переход и переход по условию zflag == 1.

Предварительно поместим необходимые значения в data_arr
      data_arr[0]=5; 
      data_arr[1]=1;
      data_arr[2]=5;

LMC-программе !>>>^<+>~<<<^>-<~(?)>>>^. соответствует ассемблерная программа
 INP
 STA 20
 INP 
 STA 21
 INP 
 STA 22
 LDA 23
 ADD 22
 STA 23
 LDA 20
 SUB 21
 STA 20
 BRZ 14
 BRA 06
 LDA 23
 OUT
 HLT

Код на Си
<source lang="cpp">
#include <stdio.h>
int main(void) {
int i=0;        // индекс текущей команды
int j=0;        // индекс массива данных
int acc = 0;
int pzflag = 1; // Флаг acc>=0
int zflag =1;   // Флаг acc==0
int pz_prev=0;  // прыжок назад по условию acc>=0
int z_prev=0;   // прыжок назад по условию acc==0
int prev=0;     // безусловный прыжок назад 

char command_mem[100] ="!>>>^<+>~<<<^>-<~(?)>>>^.";
int data_mem[10]={0}; 
data_mem[0]=5;  //инициализируем начальные значения    
data_mem[1]=1;
data_mem[2]=5;
     
while ( command_mem[i] != '\0') {
if(command_mem[i]==',')  // считываем число в аккумулятор
    scanf("%d", &acc);	
if(command_mem[i]=='+')  // прибавляем число из data_mem
    acc=acc+data_mem[j];     // к аккумулятору
if(command_mem[i]=='-')  // вычитаем число data_mem 
    acc=acc-data_mem[j];     // из аккумулятора
if(command_mem[i]=='>')  // переход на следующий элемент данных  
    j++;
if(command_mem[i]=='<')  // переход на предыдущий элемент данных  
    j--;
if(command_mem[i]=='~')  // загружаем число из аккумулятора 
    data_mem[j]=acc;         // в память данных
if(command_mem[i]=='^')  // загружаем число из data_mem  
    acc=data_mem[j];         // в аккумулятор
if(command_mem[i]=='.') {   // выводим число из аккумулятора на экран 
    printf("Output: %d",acc); 
    printf(" ");
    };
if (command_mem[i]=='}') // прыгаем назад?
    pz_prev=1;
if (command_mem[i]==')') // прыгаем назад?
    z_prev=1;   
if (command_mem[i]=='!') // прыгаем назад?
    prev=1;   
// безусловный переход вперёд
if (command_mem[i]=='?' && prev==0) {
    while(command_mem[i] != '!') 
        i++;  
    } 
// безусловный переход назад
if (command_mem[i]=='?' && prev==1) {
    while(command_mem[i] != '!') 
        i--; 	 
    } 
// переход вперёд по условию acc=0 
if (command_mem[i]=='(' && zflag==1 && z_prev==0) {
    while(command_mem[i] != ')') 
        i++;  
    } 
// переход назад по условию acc=0
if (command_mem[i]=='(' && zflag==1 && z_prev==1) {
    while(command_mem[i] != ')') 
        i--; 	 
    } 
// переход вперёд по условию acc>=0 
if (command_mem[i]=='{' && pzflag==1 && pz_prev==0) {
    while(command_mem[i] != '}') 
        i++;  
    } 
// переход назад по условию acc>=0 
if (command_mem[i]=='{' && pzflag==1 && pz_prev==1) {
while(command_mem[i] != '}') 
i--; 	 
}
// флаги		  
if(acc>=0){
    pzflag=1;}
else {
    pzflag=0;}	
if(acc==0){
    zflag=1;}
else {
    zflag=0;}		  
     
//printf("i=%d",i);printf(" ");
i++;   //увеличиваем индекс текущей команды
}
//переход на новую строку
printf("\n");
// выводим массив данных
for (int k = 0; k<10; k++)
   printf("%d ", data_mem[k]);
return 0;
}


ideone.com

Вообще, флаги можно было не создавать, а вместо них сразу проверять, чему равно число в аккумуляторе (просто с флагами нагляднее).

Вычисление чисел Фибоначчи.
#include <stdio.h>
int main(void) {
int i=0;        // индекс текущей команды
int j=0;        // индекс массива данных
int acc = 0;
int pzflag = 1; // Флаг acc>=0
int zflag =1;   // Флаг acc==0
int pz_prev=0;  // прыжок назад по условию acc>=0
int z_prev=0;   // прыжок назад по условию acc==0
int prev=0;     // безусловный прыжок назад 

char command_mem[100] ="}>>^>+.~<+.~<<^>-<~{";
int data_mem[10]={0}; 
data_mem[0]=5;  //инициализируем начальные значения    
data_mem[1]=1;
data_mem[2]=1;
     
while ( command_mem[i] != '\0') {
if(command_mem[i]==',')  // считываем число в аккумулятор
    scanf("%d", &acc);	
if(command_mem[i]=='+')  // прибавляем число из data_mem
    acc=acc+data_mem[j];     // к аккумулятору
if(command_mem[i]=='-')  // вычитаем число data_mem 
    acc=acc-data_mem[j];     // из аккумулятора
if(command_mem[i]=='>')  // переход на следующий элемент данных  
    j++;
if(command_mem[i]=='<')  // переход на предыдущий элемент данных  
    j--;
if(command_mem[i]=='~')  // загружаем число из аккумулятора 
    data_mem[j]=acc;         // в память данных
if(command_mem[i]=='^')  // загружаем число из data_mem  
    acc=data_mem[j];         // в аккумулятор
if(command_mem[i]=='.') {   // выводим число из аккумулятора на экран 
    printf("Output: %d",acc); 
    printf(" ");
    };
if (command_mem[i]=='}') // прыгаем назад?
    pz_prev=1;
if (command_mem[i]==')') // прыгаем назад?
    z_prev=1;   
if (command_mem[i]=='!') // прыгаем назад?
    prev=1;   
// безусловный переход вперёд
if (command_mem[i]=='?' && prev==0) {
    while(command_mem[i] != '!') 
        i++;  
    } 
// безусловный переход назад
if (command_mem[i]=='?' && prev==1) {
    while(command_mem[i] != '!') 
        i--; 	 
    } 
// переход вперёд по условию acc=0 
if (command_mem[i]=='(' && zflag==1 && z_prev==0) {
    while(command_mem[i] != ')') 
        i++;  
    } 
// переход назад по условию acc=0
if (command_mem[i]=='(' && zflag==1 && z_prev==1) {
    while(command_mem[i] != ')') 
        i--; 	 
    } 
// переход вперёд по условию acc>=0 
if (command_mem[i]=='{' && pzflag==1 && pz_prev==0) {
    while(command_mem[i] != '}') 
        i++;  
    } 
// переход назад по условию acc>=0 
if (command_mem[i]=='{' && pzflag==1 && pz_prev==1) {
while(command_mem[i] != '}') 
i--; 	 
}
// флаги		  
if(acc>=0){
    pzflag=1;}
else {
    pzflag=0;}	
if(acc==0){
    zflag=1;}
else {
    zflag=0;}		  
     
//printf("i=%d",i);printf(" ");
i++;   //увеличиваем индекс текущей команды
}
//переход на новую строку
printf("\n");
// выводим массив данных
for (int k = 0; k<10; k++)
   printf("%d ", data_mem[k]);
return 0;
}

ideone.com


UPD.
В этих переходах
? Безусловный переход на метку !
! Метка условного перехода ?
{ Условный переход на метку } в том случае, если число в аккумуляторе больше или равно нулю
} Метка условного перехода {
( Условный переход на метку ) в том случае, если число в аккумуляторе равно нулю
) Метка условного перехода (

метки никак не обозначаются.

Пусть оператор !a означает безусловный переход на метку A, т.е. название метки, на которую необходимо перейти должно представляться в виде строчной буквы, а сама метка, на которую осуществляется переход, в виде прописной.

Расстояние между строчной и прописной буквами в ASCII-таблице составляет 32 символа.
Добавим переменную store_label_name, в которую будет загружаться строчная буква, тогда для перехода к метке нам надо будет найти (store_label_name)-32, т.е. прописную букву, пропуская остальные команды.

Будем искать метку, перебирая все символы подряд от старших адресов к младшим, пока не достигнем начала массива.
while(command_mem[i]!=(char)( (int)(store_label_name)-32 ) && i!=0  ) i=i-1;

Если метка так и не встретилась, идём в обратном направлении: от младших адресов к старшим
if (i==0){
while(command_mem[i]!=(char)( (int)(store_label_name)-32 )  ) i=i+1;
    }

Обработка символа перехода на метку
if(command_mem[i]=='!'){
i=i+1;
 //сохраняем имя метки
store_label_name=command_mem[i]; 
// ищем метку, перебирая символы в обратном направлении
// пока не достигнем начала массива 
while(command_mem[i]!=(char)( (int)(store_label_name)-32 ) && i!=0  ) i=i-1;
// если метка не была найдена
// перебираем символы в прямом направлении
if(i==0){
        while(command_mem[i]!=(char)( (int)(store_label_name)-32 )) i=i+1;
        }
    }


Код полностью
#include <stdio.h>
int main(void) {
int i=0; // индекс текущей команды
int j=0; // индекс массива данных
int acc = 0; //аккумулятор
char command_mem[100] = ",~>~>!a~>~>~>A~>~"; //память команд
char store_label_name;
int data_mem[10]={0};                // память данных
 
while (command_mem[i] != '\0') {
if (command_mem[i]==',') // считываем число в аккумулятор 
    scanf("%d", &acc);   
if (command_mem[i]=='+') // прибавляем число из data_mem
    acc=acc+data_mem[j]; // к аккумулятору
if (command_mem[i]=='~') // загружаем число из аккумулятора
    data_mem[j]=acc;     // в память данных  
if (command_mem[i]=='.') // выводим число из аккумулятора на экран  
 printf("Output: %d",acc);
if(command_mem[i]=='>')      //переход на следующий элемент данных
    j++;
if(command_mem[i]=='<')      //переход на предыдущий элемент данных
    j--;	
 // прыжок на метку !    
if(command_mem[i]=='!') {  
	i++;
	store_label_name=command_mem[i];
	    while(command_mem[i]!=(char)( (int)(store_label_name)-32 ) && i!=0) i--; 
	    if(i==0){
	    		while(command_mem[i]!=(char)( (int)(store_label_name)-32 ) ) i++;	
	    }
    } 	
i++; //увеличиваем индекс текущей команды         
}
//переход на новую строку
printf("\n"); 
// выводим массив данных
for (int k = 0; k<10; k++)
   printf("%d ", data_mem[k]);
return 0;
} 


Проверить можно здесь

Условный оператор
Пусть для обозначения условного оператора используется символ? (знак вопроса). За ним следует само условие > (больше), < (меньше), = (равно).
При обработке знака > осуществляется проверка acc>0. Если условие выполняется, то обработка текста программы продолжается. Если не выполняется, то до знака останова текст программы пропускается:
if(acc<=0){while(command_mem[i] != ';' ) i++;}


Пускай в начальных ячейках лежат числа 2 и 3
data_mem[0]=2;
data_mem[1]=3;

Поместим первое число в аккумулятор, перейдём на следующую ячейку и вычтем второе число из аккумулятора и затем проверим, выводится ли в консоль число, находящиеся в аккумуляторе.
^>- ?>.;
Если число не выводится, значит число в ячейке data_mem[1] больше числа в ячейке data_mem[0].
Код на Си
#include <stdio.h>
int main(void) {
int i=0; // индекс текущей команды
int j=0; // индекс массива данных
int acc = 0; //аккумулятор
char command_mem[100] = "^>- ?>.; "; //память команд
int data_mem[10]={0};           // память данных
data_mem[0]=2;
data_mem[1]=3;
 
while (command_mem[i] != '\0') {
 
if (command_mem[i]==',') // считываем число в аккумулятор 
    scanf("%d", &acc);  
 
if (command_mem[i]=='^') // загружаем число из аккумулятора
    acc=data_mem[j];     // в память данных
if (command_mem[i]=='~') // загружаем число из аккумулятора
    data_mem[j]=acc;     // в память данных  
if (command_mem[i]=='.') // выводим число из аккумулятора на экран  
    printf("Output: %d",acc);
 
if (command_mem[i]=='+') // прибавляем число из data_mem
    acc=acc+data_mem[j]; // к аккумулятору
if (command_mem[i]=='-') // прибавляем число из data_mem
    acc=acc-data_mem[j];    
 
if(command_mem[i]=='>')      //переход на следующий элемент массива данных
    j++;
if(command_mem[i]=='<')      //переход на предыдущий элемент массива данных
    j--;	
 
if(command_mem[i]=='?'){
 i++;
 if(command_mem[i]=='>' && acc<=0){ while(command_mem[i] != ';') i++; }
 if(command_mem[i]=='<' && acc>=0){ while(command_mem[i] != ';') i++; }	
 if(command_mem[i]=='=' && acc!=0){ while(command_mem[i] != ';') i++; }
} // обработка команды ?
 
i++; //увеличиваем индекс текущей команды         
}
//переход на новую строку
printf("\n"); 
// выводим массив данных
for (int k = 0; k<10; k++)
   printf("%d ", data_mem[k]);
return 0;
} 


Проверить можно по ссылке ideone.com/T738HK


Циклы
Языками, в которых для организации циклов используются квадратные скобки, являются языки Plankalkül и Brainfuck.
В языке Р", созданном — как гласит Wiki — специально для того, чтобы отказаться от использования GOTO при организации циклов, вместо квадратных скобок используются спец- символы.
В язык LMC также можно добавить квадратные скобки для обработки команд в цикле.

Для этого при переходе на новый символ в памяти команд command_mem[] будем проверять, не является ли этот символ открывающей квадратной скобкой, т.е. началом цикла. Если является, то надо загрузить число из аккумулятора acc в счётчик loop_counter
if(command_mem[i]=='[') loop_counter=acc;

При обработке закрывающей квадратной скобки ] надо уменьшить счётчик цикла loop_counter на число, хранящиеся в аккумуляторе acc
if(command_mem[i]==']') loop_counter-=acc;

Т.о. можно изменять размер шага итерации.

Для завершения итерации и перехода в начало цикла надо перебирать все символы от закрывающей квадратной скобки к открывающей.
if( loop_counter!=0){
    while(command_mem[i]!='[') i=i-1;
       }


Вот так выглядит вывод чётных чисел с 10 до 0 в цикле While на языке Си
#include <stdio.h> 
int main(void) {
	int i=10;
	while(i!=0){
		printf("%d\n",i);
		i-=2;
	}
	return 0;
}

Для вывод чётных чисел с 10 до 0 в цикле на LMC сперва внесём числа 10 и 2 в память данных
data_mem[10]=10;
data_mem[1]=2;

Далее в программе перед началом выполнения цикла необходимо перейти на 1-ую ячейку, в которой хранится число 2, задающее шаг итерации. Это необходимо сделать потому, что в начале цикла происходит переход на нулевую ячейку, в которой хранится уменьшаемое значение.
Код программы на LMC ^.>[<^>-.<_>^]
Код на языке Си, выполняющий программу ^.>[<^>-.<_>^]
#include <stdio.h>
int main(void) {
int i=0; // индекс текущей команды
int j=0; // индекс массива данных
int acc = 0; //аккумулятор
int loop_counter=0; // счётчик цикла
 
int data_mem[10]={0};           // память данных
data_mem[0]=10;
data_mem[1]=2;
 
char command_mem[100] = "^.>[<^>-.<~>^ ] "; //память команд
 
while (command_mem[i] != '\0') {
 
// открывающая квадратная скобка
if(command_mem[i]=='[') loop_counter=acc;
 
if (command_mem[i]==',') // считываем число в аккумулятор 
    scanf("%d", &acc);
if (command_mem[i]=='.') // выводим число из аккумулятора на экран  
    printf("\nOutput: %d",acc);
 
if (command_mem[i]=='+') // прибавляем число из data_mem
    acc=acc+data_mem[j]; // к аккумулятору
if (command_mem[i]=='-') // вычитаем число, хранящиеся в data_mem,
    acc=acc-data_mem[j]; // из аккумулятора    
 
if (command_mem[i]=='~') // загружаем число из аккумулятора
    data_mem[j]=acc;     // в память данных
if (command_mem[i]=='^') // загружаем число из памяти данных
    acc=data_mem[j];     // в аккумулятор     
 
if (command_mem[i]=='>') j++; // переходы
if (command_mem[i]=='<') j--;
 
// закрывающая квадратная скобка
if (command_mem[i]==']'){
	loop_counter-=acc;
	if(loop_counter!=0){while(command_mem[i]!='[')i--;}
	}
 
i++; //увеличиваем индекс текущей команды         
}
//переход на новую строку
printf("\n"); 
// выводим массив данных
for (int k = 0; k<10; k++)
   printf("%d ", data_mem[k]);
return 0;
} 

Проверить можно по адресу ideone.com/qZRxCh

Подробнее о создании простейшего «низкоуровнего»(написанного на уровне ассемблера) компилятора, использующего квадратные скобки для организации циклов можно прочитать здесь.

Оставил заметку о языке LMC на сайте esolang.org здесь.
Теги:
Хабы:
+14
Комментарии 4
Комментарии Комментарии 4

Публикации

Истории

Работа

Программист С
42 вакансии

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн