Часть I
Часть II
Часть III
Часть IV
Часть V
Данная статья посвящена созданию интерпретатора некого эзотерического языка, в основе которого лежит архитектура Little Man Computer.
Фактически, это эмулятор ассемблера для LMC, только здесь вместо ассемблерных команд
INP (загрузить число в аккумулятор из устройства ввода),
STA (сохранить число из аккумулятора в память),
ADD (прибавить к числу в аккумуляторе число из памяти),
SUB (вычесть из числа в аккумуляторе число из памяти),
OUT (вывести число в аккумуляторе устройство вывода)
… используются спец-символы.
Команды переходов от ячейки к ячейке в памяти команд соответствуют командам, сдвигающим считывающую головку машины Тьюринга в языках brainfuck и P′′.
В языке bf команда + (плюс) производит замену текущего символа следующим за ним (в ASCII-таблице) символом . Так, если на ленте Тьюринга изначально находится символ «0», то команда запишет вместо него символ «1».
Здесь же эта команда производит арифметическое сложение числа из текущей ячейки массива данных (памяти данных) с числом, хранящимся в аккумуляторе.
Ячейки массива данных изначально заполнены нулями, загрузка числа в текущую ячейку из аккумулятора производится соответствующей командой
.
Например, в аккумуляторе асс находится число а.
Для того, чтобы удвоить это число, загрузим его в текущую ячейку n
затем сложим два числа
В результате этих действий в аккумуляторе окажется удвоенное число, т.е. 2а
Напишем программу, которая загружает число из устройства ввода в аккумулятор, сохраняет число в памяти, прибавляет число из памяти к аккумулятору (удваивает число) и выводит удвоенное число в устройство вывода.
На ассемблере LMC эта программа будет выглядеть вот так (20- это номер произвольной ячейки в памяти)
На языке LMC эта программа будет выглядеть вот так ,~+.
Загрузить число в аккумулятор
Загрузить число в текущую ячейку памяти
Сложить два числа
Вывести результат в консоль
В нашей LMC-машине память кодов command_mem и память данных data_mem разделены (гарвардская архитектура).
Код на языке Си
При загрузке в устройство ввода числа 12 получаем число 24.
Добавим команды для
При обработке символа > будем увеличивать индекс j массива данных data_mem
При обработке символа < будем уменьшать индекс j массива данных data_mem
Для прыжка вперёд по команде ? необходимо осуществить переход на метку !
Для этого необходимо пропускать все символы между ? и ! при переборе символов.
Для сравнения напишем программу, в которой пользователь вносит число (например 5) командой ,, которое затем загружается в пять ячеек подряд командами
~>~>~>~>~
В итоге получим массив 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).
Переход вперёд по условию pzflag == 1 будем осуществлять командами { и }. Для перехода на метку будем перебирать все символы подряд, пока не встретим символ }
Далее, пусть в памяти у нас хранятся два числа
data_mem[0]=3 и data_mem[1]=5
Напишем программу, которая выводит большее (максимальное) из двух хранящихся в памяти чисел.
ideone.com
Для прыжка назад добавим переменную pz_prev.
Если текущим символом является }, то «поднимаем флаг» pz_prev
Если метка } предшествует команде {, значит надо совершать прыжок назад
Напишем программу, которая выводит чётные числа с 10 до 0.
Загрузим числа 10 и 2 в массив data_mem, далее, пока число в acc больше или равно нулю, будем от 10 отнимать 2 и выводить результат
ideone.com
Об организации циклов с помощью спец-операторов, коими являются квадратные скобки, после спойлера. Про циклы с использованием переходов под спойлером.
UPD.
В этих переходах
метки никак не обозначаются.
Пусть оператор !a означает безусловный переход на метку A, т.е. название метки, на которую необходимо перейти должно представляться в виде строчной буквы, а сама метка, на которую осуществляется переход, в виде прописной.
Расстояние между строчной и прописной буквами в ASCII-таблице составляет 32 символа.
Добавим переменную store_label_name, в которую будет загружаться строчная буква, тогда для перехода к метке нам надо будет найти (store_label_name)-32, т.е. прописную букву, пропуская остальные команды.
Будем искать метку, перебирая все символы подряд от старших адресов к младшим, пока не достигнем начала массива.
Если метка так и не встретилась, идём в обратном направлении: от младших адресов к старшим
Обработка символа перехода на метку
Проверить можно здесь
Условный оператор
Пусть для обозначения условного оператора используется символ? (знак вопроса). За ним следует само условие > (больше), < (меньше), = (равно).
При обработке знака > осуществляется проверка acc>0. Если условие выполняется, то обработка текста программы продолжается. Если не выполняется, то до знака останова текст программы пропускается:
Пускай в начальных ячейках лежат числа 2 и 3
Поместим первое число в аккумулятор, перейдём на следующую ячейку и вычтем второе число из аккумулятора и затем проверим, выводится ли в консоль число, находящиеся в аккумуляторе.
^>- ?>.;
Если число не выводится, значит число в ячейке data_mem[1] больше числа в ячейке data_mem[0].
Циклы
Языками, в которых для организации циклов используются квадратные скобки, являются языки Plankalkül и Brainfuck.
В языке Р", созданном — как гласит Wiki — специально для того, чтобы отказаться от использования GOTO при организации циклов, вместо квадратных скобок используются спец- символы.
В язык LMC также можно добавить квадратные скобки для обработки команд в цикле.
Для этого при переходе на новый символ в памяти команд command_mem[] будем проверять, не является ли этот символ открывающей квадратной скобкой, т.е. началом цикла. Если является, то надо загрузить число из аккумулятора acc в счётчик loop_counter
При обработке закрывающей квадратной скобки ] надо уменьшить счётчик цикла loop_counter на число, хранящиеся в аккумуляторе acc
Т.о. можно изменять размер шага итерации.
Для завершения итерации и перехода в начало цикла надо перебирать все символы от закрывающей квадратной скобки к открывающей.
Вот так выглядит вывод чётных чисел с 10 до 0 в цикле While на языке Си
Для вывод чётных чисел с 10 до 0 в цикле на LMC сперва внесём числа 10 и 2 в память данных
Далее в программе перед началом выполнения цикла необходимо перейти на 1-ую ячейку, в которой хранится число 2, задающее шаг итерации. Это необходимо сделать потому, что в начале цикла происходит переход на нулевую ячейку, в которой хранится уменьшаемое значение.
Код программы на LMC ^.>[<^>-.<_>^]
Код на языке Си, выполняющий программу ^.>[<^>-.<_>^]
Проверить можно по адресу ideone.com/qZRxCh
Подробнее о создании простейшего «низкоуровнего»(написанного на уровне ассемблера) компилятора, использующего квадратные скобки для организации циклов можно прочитать здесь.
Оставил заметку о языке LMC на сайте esolang.org здесь.
Часть II
Часть III
Часть IV
Часть V
Данная статья посвящена созданию интерпретатора некого эзотерического языка, в основе которого лежит архитектура Little Man Computer.
Фактически, это эмулятор ассемблера для LMC, только здесь вместо ассемблерных команд
INP (загрузить число в аккумулятор из устройства ввода),
STA (сохранить число из аккумулятора в память),
ADD (прибавить к числу в аккумуляторе число из памяти),
SUB (вычесть из числа в аккумуляторе число из памяти),
OUT (вывести число в аккумуляторе устройство вывода)
… используются спец-символы.
Команды переходов от ячейки к ячейке в памяти команд соответствуют командам, сдвигающим считывающую головку машины Тьюринга в языках brainfuck и P′′.
В языке bf команда + (плюс) производит замену текущего символа следующим за ним (в ASCII-таблице) символом . Так, если на ленте Тьюринга изначально находится символ «0», то команда запишет вместо него символ «1».
Здесь же эта команда производит арифметическое сложение числа из текущей ячейки массива данных (памяти данных) с числом, хранящимся в аккумуляторе.
Ячейки массива данных изначально заполнены нулями, загрузка числа в текущую ячейку из аккумулятора производится соответствующей командой
.
Например, в аккумуляторе асс находится число а.
Для того, чтобы удвоить это число, загрузим его в текущую ячейку n
затем сложим два числа
В результате этих действий в аккумуляторе окажется удвоенное число, т.е. 2а
- Пусть команде 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
ideone.com
Добавим безусловные переходы назад. Для этого добавим переменную prev.
Также добавим переходы вперёд/назад по условию acc=0. Для таких переходов создадим флаг zflag(Zero_Flag) и переменную z_prev.
Переходы по условию zflag == 1 будем осуществлять командами ( и )
Перемножим 5 и 5 используя безусловный переход и переход по условию zflag == 1.
Предварительно поместим необходимые значения в data_arr
LMC-программе !>>>^<+>~<<<^>-<~(?)>>>^. соответствует ассемблерная программа
Код на Си
ideone.com
Вообще, флаги можно было не создавать, а вместо них сразу проверять, чему равно число в аккумуляторе (просто с флагами нагляднее).
Вычисление чисел Фибоначчи.
ideone.com
Будем в цикле на каждой итерации вычитать из 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].
Код на Си
Проверить можно по ссылке ideone.com/T738HK
#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 здесь.