5 месяцев назад История
README.md

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

Конвейеры
0 успешных
0 с ошибкой