Pull to refresh

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

Reading time4 min
Views2.7K

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

Мне очень нравится читать на хабре и 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.

Tags:
Hubs:
+2
Comments0

Articles