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