
Если вы ждали что-то серьезное или умное, то пропускайте эту статью. Спасибо.
Мне очень нравится читать на хабре и 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.
