Компилятор Brainfuck в PHP на PHP с оптимизацией

PHP
Всем привет.
В последнее время здесь часто появляются посты про интерпретаторы и даже компиляторы Brainfuck на различных языках. Вот и я решил написать хабрастатью о создании компилятора с оптимизацией кода из Brainfuck в PHP, написанного на PHP.

Вступление

Наш компилятор будет запускаться не из браузера, как обычные PHP-скрипты, а из консоли. Это необходимо по двум причинам:
  1. Так удобнее передавать входные данные (сам исходник)
  2. При таком подходе можно реализовать ввод данных в выполняемой программе.

Шаг 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 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.