Еще одна вариация Brainfuck

—

от автора

Если вы ждали что-то серьезное или умное, то пропускайте эту статью. Спасибо.

Мне очень нравится читать на хабре и esolnag про Brainfuck (далее — BF). И, конечно же, самое больше удовольствие появляется от выдумывания и написания очередной никому не нужной версии языка с новыми командами. Я уже писал свои варианты BF с процедурами, арифметикой и т.д., но захотелось чуточку больше. И я смело могу сказать, что еще не видел версии лучше (если не считать технически сложных, но полностью бесполезных многопоточных и сетевых BF).

Что же может язык?

100[{Step }>&+s0i<{: Hello Habr!}-n]

Вот результат выполнения этого кода

Да, все просто — это цикл от 0 до 100 с выводом текста. Но обо всем по порядку!

Регистры и память!
В моем языке 10 регистров общего назначения, которым можно обращаться по индексу, написав s (set) или g (get) и индекс (0-9).
Например, сишной строчке
regiser[4] = 3
будет соответствовать такая команда:
+++s4

Есть еще три регистра:
1) для результата арифметических и логических операций
2) шаг цикла (да, стандартного [] цикла)
3) регистр для ошибок

Сами регистры
// 10 common registers [0-9]
private int[] _common_registers = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
// register for math results
private int _result_register = 0;
// loop counter
private int _loop_register = 0;
// register for 'safed' errors (DivideByZero, ParseError, ProcessStartError)
private byte _error_register = 0;

И простота парсинга кода (знаю, что уйду в минусы еще и за вычисление индекса):
// set register[index] value
else if (code[i] == 's')
{
var next = code[i + 1];
if (next >= '0' && next <= '9')
{
int index = Math.Abs('0' - next);
_common_registers[index] = _context.GetCurrentCell();
i++;
}
}
// get register[index] value
else if (code[i] == 'g')
{
var next = code[i + 1];
if (next >= '0' && next <= '9')
{
int index = Math.Abs('0' - next);
_context.SetCurrentCell(_common_registers[index]);
i++;
}
}

Складываем числа 4 и 5 и записываем результат в текущую ячейку.
++++s0>+++++s1!+?

Конечно же, не обошлось без прямой записи десятичных и шестнадцатеричных чисел:
100>0xff
Так мы записали я ячейку 100, перешли в другую и записали 256.

Вот как устроен парсинг операций:

// calc -> result_register = register[0] <operator> register[1]                 else if (code[i] == '!')                 {                     var next = code[i + 1];                     // a + b                     if (next == '+')                     {                         _result_register = _common_registers[0] + _common_registers[1];                         i++;                     }                     // a - b                     else if (next == '-')                     {                         _result_register = _common_registers[0] - _common_registers[1];                         i++;                     }                     // a * b                     else if (next == '*')                     {                         _result_register = _common_registers[0] * _common_registers[1];                         i++;                     }                     // a / b                     else if (next == '/')                     {                         if (_common_registers[1] != 0)                         {                             _result_register = _common_registers[0] / _common_registers[1];                         } else                         {                             _result_register = 0;                             _error_register = 1;                         }                         i++;                     }                     // a % b                     else if (next == '%')                     {                         _result_register = _common_registers[0] % _common_registers[1];                         i++;                     }                     // a ** b                     else if (next == '^')                     {                         _result_register = (int)Math.Pow(_common_registers[0], _common_registers[1]);                         i++;                     }                     // a == b                     else if (next == '=')                     {                         _result_register = _common_registers[0] == _common_registers[1] ? 1 : 0;                         i++;                     }                     // a < b                     else if (next == '<')                     {                         _result_register = _common_registers[0] < _common_registers[1] ? 1 : 0;                         i++;                     }                     // a > b                     else if (next == '>')                     {                         _result_register = _common_registers[0] > _common_registers[1] ? 1 : 0;                         i++;                     }                 }

Сначала идет символ <!>, а далее символ операции, которая и будет производиться с первыми двумя регистрами (индексы 0 и 1 соответственно).

Текстовый режим с константами и выводом значений регистров

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

Куда же без этой строчки:
{Hello World!}
Здороваемся с пользователем по имени:
{Hello {UserName}}
А так можно вывести текущее время:
Hs0>Ms1>Ss2{{0:i}:{1:i}:{2:i}}

Вложенные фигурные скобки подставляют на свое место некое значение и парсятся простой регуляркой. В случае {UserName} — это просто константа.

    private static Dictionary<string, string> _constants = new Dictionary<string, string>     {         ["UserName"] = Environment.UserName,         ["ComputerName"] = Environment.MachineName,         ["CurrentDir"] = Environment.CurrentDirectory,         ["SystemDir"] = Environment.SystemDirectory,         ["OsName"] = Environment.OSVersion.Platform.ToString(),         ["OsVersion"] = Environment.OSVersion.Version.ToString(),         ["IsOs64"] = Environment.Is64BitOperatingSystem.ToString(),         ["ProcessId"] = Environment.ProcessId.ToString(),     }; 

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

var re = new Regex(@"({(\d):(\w)})");  MatchEvaluator me = (x) => {              int reg_index = int.Parse(x.Groups[2].Value);              var cast_type = x.Groups[3].Value;                       int reg_value = _common_registers[reg_index];             // int             if (cast_type == "i") return reg_value.ToString();             // hex int             if (cast_type == "h") return reg_value.ToString("x");             // char             else if (cast_type == "c") return ((char)reg_value).ToString();             // bool -> true | false             else if (cast_type == "b") return (reg_value > 0 ? true : false).ToString().ToLower();             // bool -> True | False             else if (cast_type == "Bb") return (reg_value > 0 ? true : false).ToString();             // bool -> TRUE | FALSE             else if (cast_type == "B") return (reg_value > 0 ? true : false).ToString().ToUpper();             else                  _error_register = 1;                          return string.Empty;         }; 

И куда же без взаимодействия с внешним миром? Мой язык умеет запускать процессы с аргументами. Механизм повторяет режим ввода, но записывается все в круглых скобках:

(cmd.exe /k echo Hello {UserName})

else if (code[i] == '(') {     int j = i + 1;     int brk = 1;     while (brk > 0 && j < code.Length) {     if (code[j] == '(')        brk++;     if (code[j] == ')')        brk--;                        if (brk == 0) {        var command = new string(code.Skip(i + 1).Take(j - i - 1).ToArray());        var fullCommand = InsertValues(command);        var words = fullCommand.Split(' ');        var proc = new Process();        proc.StartInfo.FileName = words.FirstOrDefault();        proc.StartInfo.Arguments = new string(string.Join(' ', words.Skip(1)));        proc.StartInfo.UseShellExecute = true;        try        {           proc.Start();        }         catch        {             _error_register = 1;        }      }      j++;    }    i = j - 1;  } 

Помимо прочего в языке есть уже упомянутые код для получения текущего времени (H, M, S), рандом и всякие примочки, вроде форматированного вывода и условных переходов, которыми никого не удивить, кто видел хоть парочку модификацию BF.


ссылка на оригинал статьи https://habr.com/ru/post/667168/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *