Компилятор Brainfuck в PHP на PHP с оптимизацией
Всем привет.
В последнее время здесь часто появляются посты про интерпретаторы и даже компиляторы Brainfuck на различных языках. Вот и я решил написать хабрастатью о создании компилятора с оптимизацией кода из Brainfuck в PHP, написанного на PHP.
Теперь в переменной $out будет полный код скрипта. Можно записать его в файл или выполнить функцией eval().
сохраняем в файл hello.txt.
В последнее время здесь часто появляются посты про интерпретаторы и даже компиляторы Brainfuck на различных языках. Вот и я решил написать хабрастатью о создании компилятора с оптимизацией кода из Brainfuck в PHP, написанного на PHP.
Вступление
Наш компилятор будет запускаться не из браузера, как обычные PHP-скрипты, а из консоли. Это необходимо по двум причинам:- Так удобнее передавать входные данные (сам исходник)
- При таком подходе можно реализовать ввод данных в выполняемой программе.
Шаг 1. Проверяем и оптимизируем код
Будем считать, что исходный код у нас в переменной $src.Проверяем
Вся проверка кода сводится к проверке уровней вложенности циклов (т.е. после каждой [ в коде должна присутствовать ]). Реализовать подобную проверку можно, например, так:$level=0;
for($i=0;$i<strlen($src);$i++){
if($src{$i}=="[")$level++;
if($src{$i}=="]")$level--;
}
if($level!=0){
echo "Нарушена вложенность операторов цикла.";
exit;
}
Оптимизируем
Код было бы неплохо оптимизировать. Мы распарсим исходный код и занесем в массив сами операторы в порядке их выполнения и количество раз, которое каждый оператор необходимо выполнить подряд (т.е. количество его повторов).$prevop=""; // Предыдущий оператор
$count=1; // Кол-во идущих подряд одинаковых
$valid="<>+-.,[]"; // Возможные операторы
$ops=array(); // Собственно массив
for($i=0;$i<strlen($src);$i++){
if(strspn($src{$i}, $valid)){ // Обрабатываем только известные символы, остальные игнорируем
$op=$src{$i};
if($op==$prevop)$count++;
else{
$ops[]=array($prevop, $count);
$count=1;
$prevop=$op;
}
}
}
$ops[]=array($prevop, $count); // И последний
Шаг 2. Компилируем
Тут нет ничего сложного — просто заменяем каждый оператор эквивалентным ему кодом в PHP. Встроенной функции для ввода данных с консоли в PHP нету, поэтому мы вручную будем читать данные из стандартного потока ввода.$out="<?\r\n".'$memory=array();'."\r\n".'$pointer=0;'."\r\n".'$in=fopen("php://input", "r");'."\r\n\r\n";
for($i=0;$i<count($ops);$i++){
switch($ops[$i][0]){
case ">":
$out.='$pointer+='.$ops[$i][1].';'."\r\n";
break;
case "<":
$out.='$pointer-='.$ops[$i][1].';'."\r\n";
break;
case "+":
$out.='$memory[$pointer]+='.$ops[$i][1].';'."\r\n";
break;
case "-":
$out.='$memory[$pointer]-='.$ops[$i][1].';'."\r\n";
break;
case ".":
for($j=0;$j<$ops[$i][1];$j++)$out.='echo chr($memory[$pointer]);'."\r\n";
break;
case ",":
for($j=0;$j<$ops[$i][1];$j++)$out.='$memory[$pointer]=ord(fread($in, 1));'."\r\n";
break;
case "[":
for($j=0;$j<$ops[$i][1];$j++)$out.='while($memory[$pointer]!=0){'."\r\n";
break;
case "]":
for($j=0;$j<$ops[$i][1];$j++)$out.='}'."\r\n";
break;
}
}
$out.='fclose($in);'."\r\n?>";
Теперь в переменной $out будет полный код скрипта. Можно записать его в файл или выполнить функцией eval().
Шаг 3. Запускаем!
Берем стандартный «Hello world»++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
сохраняем в файл hello.txt.
Полный код компилятора
<?
if($_SERVER['argc']<2){
echo "Передайте имя входного файла в параметре командной строки.";
exit;
}
$src=file_get_contents($_SERVER['argv'][1]);
$level=0;
for($i=0;$i<strlen($src);$i++){
if($src{$i}=="[")$level++;
if($src{$i}=="]")$level--;
}
if($level!=0){
echo "Нарушена вложенность операторов цикла.";
exit;
}
$prevop="";
$count=1;
$valid="<>+-.,[]";
$ops=array();
for($i=0;$i<strlen($src);$i++){
if(strspn($src{$i}, $valid)){
$op=$src{$i};
if($op==$prevop)$count++;
else{
$ops[]=array($prevop, $count);
$count=1;
$prevop=$op;
}
}
}
$ops[]=array($prevop, $count);
$out="<?\r\n".'$memory=array();'."\r\n".'$pointer=0;'."\r\n".'$in=fopen("php://input", "r");'."\r\n\r\n";
for($i=0;$i<count($ops);$i++){
switch($ops[$i][0]){
case ">":
$out.='$pointer+='.$ops[$i][1].';'."\r\n";
break;
case "<":
$out.='$pointer-='.$ops[$i][1].';'."\r\n";
break;
case "+":
$out.='$memory[$pointer]+='.$ops[$i][1].';'."\r\n";
break;
case "-":
$out.='$memory[$pointer]-='.$ops[$i][1].';'."\r\n";
break;
case ".":
for($j=0;$j<$ops[$i][1];$j++)$out.='echo chr($memory[$pointer]);'."\r\n";
break;
case ",":
for($j=0;$j<$ops[$i][1];$j++)$out.='$memory[$pointer]=ord(fread($in, 1));'."\r\n";
break;
case "[":
for($j=0;$j<$ops[$i][1];$j++)$out.='while($memory[$pointer]!=0){'."\r\n";
break;
case "]":
for($j=0;$j<$ops[$i][1];$j++)$out.='}'."\r\n";
break;
}
}
$out.='fclose($in);'."\r\n?>";
file_put_contents($_SERVER['argv'][1].".php", $out);
include($_SERVER['argv'][1].".php");
?>
0 комментариев