Brainfucktor
Утилита и модуль на языке Lua
для трансляции данных в код brainfuck
. Будучи исполненным полученный код сгенерирует закодированный в нём текст.
brainfucktor.lua
- модульbrainfucktor
- утилита коммандной строки на основе модуля
Зависимости
Любой интерпретатор языка Lua
на выбор sudo apt install lua
5.1
;5.2
;5.3
;5.4
;luajit
;
Выбрать интерпретатор можно во время исполнения через ключ -l
Установка и удаление
По умолчанию устанавливается только модуль, если нужно установить утилиту то требуется передать параметр install-cli
Глобально
#Только модуль
sudo make install
sudo make uninstall
#Модуль и утилита
sudo make install install-cli
sudo make uninstall uninstall-cli
Локально
#Только модуль
make PREFIX=$HOME/.local install
make PREFIX=$HOME/.local uninstall
#Модуль и утилита
make PREFIX=$HOME/.local install install-cli
make PREFIX=$HOME/.local uninstall uninstall-cli
Независимо
#Только модуль
make DESTDIR=$HOME/some_you_dir install
make DESTDIR=$HOME/some_you_dir uninstall
#Модуль и утилита
make DESTDIR=$HOME/some_you_dir install install-cli
make DESTDIR=$HOME/some_you_dir uninstall uninstall-cli
Информацию для установки и удаления можно получить через make help
Использование
Утилита
Описание использования находится в справке
Usage: brainfucktor -i [FILE] -o [FILE]
brainfucktor -e 'encode this text to brainfuck'
cat file | brainfucktor - #read from pipe
brainfuck photo.png -o photo.bf
Encode input data to code in brainfuck language
Arguments:
-h --help This help information
- Input read from stdin
-i [FILE] Input filename for read
-o [FILE] Output filename for write
-e [TEXT] Input from command line argument
-l [TEXT] Select lua implementation for execute
lua5.1, lua5.2, lua5.3, lua5.4, luajit
By default used system '#!/usr/bin/env lua'
-cb [NUMBER] Cycle body size factor (>=1 default=2 )
-cs [NUMBER] Cycle split factor (>=1 default=16)
-cd [NUMBER] Cycle distance factor (>=1 default=64)
-vd [NUMBER] Value distance factor (>=1 default=8 )
-ch [NUMBER] Separate input by chunks size (>=1 default=1024 bytes)
-ml [NUMBER] Set max line size for split output ( >=1 default 80 bytes)
-sl Create single line output (this disabled -ml option)
-pc Print currect configuration for code generation
You can skip -i option and just set filename, but filename expect first
Все аргументы являются опциональными, можно просто передать имя файла, или спользовать пайп. По умолчанию данные читаются блоками по 1024
байта, затем этот блок сбрасывается на вывод, вывод по умолчанию разбивается на строки длинной не более 80
символов. Если имя для чтения указано первым то ключ -i
можно опустить, если не указано ничего явного для ввода то читается stdin
-cb
это делитель байта, задаёт размер цикла до достижения значения байта-cs
максимум размера тела цикла, при достижении цикл расщепляется на два-cd
разница между значениями провоцирующая создание цикла-vd
разница между значениями подавляющая создание циклов
Параметры генерации влияют на то как и будет ли переиспользорвано значение байта для достижения значения следующего байта, это определяется тем какой будет размер кода требуемый для достижения разницы. Каждые данные уникальны в этом плане нет универсальной настройки для максимально компактного вывода, но есть для максимально большого -vd 255
при этом значении будут исключены циклы (кроме первого)
Простые примеры:
brainfucktor -e 'hello'
brainfucktor file.txt -o out.bf
cat /dev/random | brainfucktor > out.bf
Далее исходные данные можно восстановить исполнив полученный код в любом корректном brainfuck
интерпретаторе, желательно допускающим более 30000
ячеек. Трансляция идёт очень медленно, в среднем 1
мегабайт данных за 10
секунд. :D
Пример вывода Hello World! с немного разными настройками генерации
brainfucktor -e 'Hello World!'
+++++++++++[>+++++++<-]>-----.>+++++[<++++++>-]<-.+++++++..+++.>+++++++[>+++++<
-]>---.>++++++++++[<++++++>-]<-----.>+++++[<+++++>-]<-.+++.------.--------.>+++
+++[>++++++<-]>---.
brainfucktor -e 'Hello World!' -vd 50b
+++++++++++[>+++++++<-]>-----.+++++++++++++++++++++++++++++.+++++++..+++.>+++++
++[>+++++<-]>---.>++++++++++[<++++++>-]<-----.++++++++++++++++++++++++.+++.----
--.--------.>++++++[>++++++<-]>---.
brainfucktor -e 'Hello World!' -cd 4
+++++++++++[>+++++++<-]>-----.>++++++++++++[>+++++++++<-]>-------.+++++++..+++.
>+++++++[>+++++<-]>---.>++++++++++[>+++++++++<-]>---.>+++++++++++++[>+++++++++<
-]>------.+++.------.--------.>++++++[>++++++<-]>---.
brainfucktor -e "Hello World!" -vd 128
+++++++++++[>+++++++<-]>-----.+++++++++++++++++++++++++++++.+++++++..+++.-------
------------------------------------------------------------------------.+++++++
++++++++++++++++++++++++++++++++++++++++++++++++.++++++++++++++++++++++++.+++.--
----.--------.------------------------------------------------------------------
-.
brainfucktor -e "Hello World!" -vd 1 -cs 8
>+++[<++++>-]<-[>+++++++<-]>-----.>+++++[<++++++>-]<-.>+++[<+++>-]<--..>+[<+++>-
]<.>+++++++[>+++++<-]>---.>++++++++++[<++++++>-]<-----.>+++++[<+++++>-]<-.>+[<++
+>-]<.>++[<--->-]<.>+++[<--->-]<+.>++++++[>++++++<-]>---.
Модуль
Минимальный пример, с параметрами генерации по умолчанию
local tbf = require('brainfucktor')
local out = tbf:generate('Привет Мир')
print(out)
Cначала опционально можно задать параметры герерации (ключи описаны ниже)
brainfucktor:configure(config_table)
Не указанные парамерты устанавливаются на значения по умолчанию. Конфигурация сохраняется между вызовами, генерации для сброса на значения по умолчанию можно просто выполнить конфигурацию с пустой таблицей
brainfucktor:configure({})
Если не задан механизм записи то выход идёт возвратом от функции brainfucktor:generate()
.
brainfucktor:generate
может принимать
-
На чтение
- строку
- файл (должен быть читаемым и существовать)
- таблицу (содержащую как отдельные символы так и многосимвольные строки)
-
На запись
- таблицу
- файл (должен быть записываемым, может не существовать)
- функцию (первый парамерт строка, второй таблица, данные одни и теже)
Опционально можно установить параметры трансляции Это не обязательно делать, но можно в широком диапазоне регулировать то как будет выглядеть результат и какой будет его размер
local myconfig =
{
chunk_size = 1024; -- размер блоков на которые разделяются входящие данные
cycle_factor = 2; -- влиятет на размер тела циклов
cycle_divide_factor = 16; -- влияет на раделение одного большого цикла на несколько меньших
cycle_distance_factor = 64;-- влияет на использование цикла для переиспользования текущей ячейки
value_distance_factor = 8; -- влияет на переиспользования текущей ячейки, используя '+' и '-'
fmt_maxline = 80; -- по умолчанию результат разбит на строки
fmt_oneline = false; -- получать ли результат в одну строку вместо разделения на строки
};
locat status_1 = brainfucktor:configure(myconfig)
Важно помнить что конфигурация генерации заданная через brainfucktor:configure()
сохраняется между вызовами brainfucktor:generate()
конфигурацию можно вызвать лишь один раз, а затем вызывать герерацию. Повторюcь что для сброса параметров генерации на значения по умолчению нужно просто вызвать функцию конфигурации с пустой таблицей в качестве аргумента функции.
Далее некое подобие полной документации API
------------------------------------------
: API Reference documentation :
------------------------------------------
------------------------------------------
-- Optional genetator configuration
------------------------------------------
-- input as table : can contains keys
-- : all keys optional
------------------------------------------
-- default input configuration values
------------------------------------------
-- input =
-- {
-- chunk_size = 1024; -- min 1 max 2^31
-- cycle_factor = 2; -- min 1 max 255
-- cycle_divide_factor = 16; -- min 1 max 255
-- cycle_distance_factor = 64;-- min 1 max 255
-- value_distance_factor = 8; -- min 1 max 255
-- fmt_maxline = 80; -- min 1 max 2^31
-- fmt_oneline = false; -- true or false (disabled fmt_maxline)
-- }
------------------------------------------
-- empty input reset config to default
------------------------------------------
brainfucktor:configure(input) -- set options
brainfucktor:configure({ }) -- reset options
------------------------------------------
-- Simple generate mode no warite handler
------------------------------------------
-- input as table : contains only string
-- input as file : exist and readable
-- input as string : any string
------------------------------------------
-- return : brainfuck string
------------------------------------------
local result = brainfucktor:generate(input)
-------------------------------------------
-- Extend generate mode use write handler
-------------------------------------------
-- input as table : contains only string
-- input as file : exist and readable
-- input as string : any string
-------------------------------------------
-- output as file : expects wratable
-- output as table : adding mode if no empty
-- output as function : callback for write
-------------------------------------------
-- callback(outstring,outtable)
-- outstring == outtable it just different
-- presentation equals data puts to callback
-------------------------------------------
-- return : boolean status
-------------------------------------------
local result = brainfucktor:generate(input,output)
--------------------------------------------
-- Get error message after internal error
--------------------------------------------
-- if brainfucktor:configure() or
-- brainfucktor:generate() return 'false'
-- we can get string with error message
--------------------------------------------
-- input as string: optional string addding
-- as first in error message line output
--------------------------------------------
local errmsg = brainfucktor:error_message(input)
--------------------------------------------
-- Differet examples
--------------------------------------------
-- simplest example case
print(brainfucktor:generate('hello'))
-- extend example
local input = 'hello':rep(100) -- can be table,file,string
local output = io.stdout -- can be function,table,file
local config = { fmt_oneline=true; } -- all configurations optionals
-- calling configure is optional
if not brainfucktor:configure(config) then
print(brainfucktor:error_message('optional message'))
return false;
end
-- calling generator
if not brainfucktor:generate(input,output) then
print(brainfucktor:error_message())
return false
end
-- reset prev configuration
brainfucktor:configure({})
-- call generator again
local out = brainfucktor:generate("simple generate")
print(out)
-- use function writer callback
local status = brainfucktor:generate("hello world",function(str,tab)
print(str)
print(table.concat(tab))
end)
print(status) -- true or false
-- use simple io for pipe operations
brainfucktor:generate(io.stdin,io.stdout)
-- use table as out and input
local out = {}
brainfucktor:generate({'Hel','l','o wo','rld!'},out)
print(table.concat(out))
На заметку
В случае установки в $HOME/.local/
утилита автоматически найдёт модуль. В случае установки в /usr/local
или /usr
сработают стандартные пути. Но в ином варианте потребудется явное указание package.path
как для использования модуля самого по себе так и для сопровождающей утилиты
Для утилиты вариантом было просто добавить код модуля в код утилиты, но ладно.
Не знаю кому будет нужно транслировать данные в brainfuck
, это даже мне не нужно. Но зато это прикольно ::) Результат генерации сильно зависит от парамеров, разбивать ли большие циклы на два, переиспользовать ли текущую ячейку для получения следующего значения, как переиспользовать через операции +
и -
или через циклы если операций +
и -
нужно слишком много. Операции переиспользования ячеек через <<<<<.>>>>>
исключены так как сильно влияют на размер итогового кода и бесполезны без вложенных циклов, а с вложенными циклами интепретеция результата будет сродни бенчмаку коих и так навалом.
Так как вывод буферизируется и после чего выводится, напрямую транслировать например гигабайтный файл напрямую не получится, результат просто не поместится в ограничения lua
по памяти, но это и не нужно так как мы всегда упрёмся в подобную проблему рано или поздно упёршись в ограничение физической памяти или даже перехода на использование swap. Всё что нужно для обработки больших файлов это обрабатывать их кусками разумных размеров и делать вывод тоже кусками, если пренебречь переиспользовнаием 1
байта между кусками (чанками данных) то трансляцию данных можно распаралелить на несколько процессов. В любом случае, с одной стороны проблема есть, нельзя просто скормить программе большие данные, с другой стороны пробелемы нет, так как подобный вариант использования целиком и полностью лежит на том подходе разбиения и обработки больших данных какие какой захочет сделать их разработчик.
Сначала была цель генерировать компактный код, но потом наигрался и забил :)
Описание
Трансляция данных в код на brainfuck