--[[--------------------------------------------------
 File       : lua_extensions.lua
 Description: Extensions of the Lua standard libraries
 Copyright  : 2017 © Prosectors Project
 Author(s)  : Nazgool
------------------------------------------------------
             List of the change :

#####   C O N S T   ##############################################################################################
--[=[
----------------|-------------------------------------------------------------------------------------------------
LUA_PRINT_DIR   |  Default = "$logs$". Директория хранения файла лога библиотеки Lua Extension
----------------|-------------------------------------------------------------------------------------------------
LUA_PRINT_FILE  |  Default = 'LuaEx_log.txt'. Имя файла, в который производится запись отладочных сообщений
----------------|-------------------------------------------------------------------------------------------------
LUA_PRINT_MODE  |  Default = 'a'. Режим записи log-файла. Режим дозаписи в конец файла 'lua_log.txt'
----------------|-------------------------------------------------------------------------------------------------
LUA_PRINT_SEP   |  Default = ' = '. Разделитель выводимых на печать аргументов, передаваемых в функцию 'print'
----------------|-------------------------------------------------------------------------------------------------
LUA_PRINT_PATH  |  Полный путь к log-файлу
----------------|-------------------------------------------------------------------------------------------------
--]=]
#####  G L O B A L  ##############################################################################################
--[=[
-----------------------------|------------------------------------------------------------------------------------
is_table (val)               |  return 'boolean' -- Является ли значение 'val' таблицей
-----------------------------|------------------------------------------------------------------------------------
is_array (val)               |  return 'boolean' -- Является ли значение 'val' массивом
-----------------------------|------------------------------------------------------------------------------------
is_boolean (val)             |  return 'boolean' -- Является ли значение 'val' булевым значением
-----------------------------|------------------------------------------------------------------------------------
is_nil (val)                 |  return 'boolean' -- Существует ли значение 'val' 
-----------------------------|------------------------------------------------------------------------------------
is_true (val)                |  return 'boolean' -- Равно ли значение 'val' логическому значению 'true'
-----------------------------|------------------------------------------------------------------------------------
is_false (val)               |  return 'boolean' -- Равно ли значение 'val' логическому значению 'false'
-----------------------------|------------------------------------------------------------------------------------
is_number (val)              |  return 'boolean' -- Является ли значение 'val' числом
-----------------------------|------------------------------------------------------------------------------------
is_function (val)            |  return 'boolean' -- Является ли значение 'val' функцией
-----------------------------|------------------------------------------------------------------------------------
is_string (val)              |  return 'boolean' -- Является ли значение 'val' строкой
-----------------------------|------------------------------------------------------------------------------------
is_userdata (val)            |  return 'boolean' -- Является ли значение 'val' пользовательскими данными
-----------------------------|------------------------------------------------------------------------------------
is_thread (val)              |  return 'boolean' -- Является ли значение 'val' потоком
-----------------------------|------------------------------------------------------------------------------------
is_empty (val, mode)         |  return 'boolean' -- Пуста ли таблица или строка 'val'
                             |  Если 'mode' положительное значение, то строка, состоящая только из пробельных
                             |  символов и(или) символов неразрывного пробела будет считаться пустой.
-----------------------------|------------------------------------------------------------------------------------
toboolean (val)              |  return 'boolean' -- Преобразование значения 'val' в булево значение
-----------------------------|------------------------------------------------------------------------------------
str_to_bool (str [, mode])   |  return 'boolean' -- Преобразование строки 'str' в булево значение
                             | Алгоритм аналогичен r_bool: как true трактуются следующие значения:
                             | "on", "yes", "true", "1".
                             | Если опциональный аргумент 'mode' == true, проверяются также ложные значения:
                             | "off", "no", "false", "0"; и при несоответствии возвращается nil
-----------------------------|------------------------------------------------------------------------------------
print (...)                  |  Функция вывода множества аргументов в файл лога, определённого константой
                             |  LUA_PRINT_PATH. Способ отображения определяется константами LUA_PRINT_MODE и
                             |  LUA_PRINT_SEP.  По умолчанию каждый вызов функции 'print' будет дописывать новую
                             |  строку в файл лога.

                             |  Все передаваемые аргументы будут разделены сепаратором LUA_PRINT_SEP
-----------------------------|------------------------------------------------------------------------------------
_trace (msg, level)          |  Запись цепочки вызовов в лог
-----------------------------|------------------------------------------------------------------------------------
error (msg, ...)             |  Функция записи сообщений о критических ошибках. Работу lua необходимо прекращать.
-----------------------------|------------------------------------------------------------------------------------
assert (cond, msg, ...)      |  Аналог библиотечной функции с дополнительными аргументами и трассировкой в лог
-----------------------------|------------------------------------------------------------------------------------
--]=]
#####   T A B L E   ##############################################################################################
--[=[
------------------------------------------|-----------------------------------------------------------------------
table.size (tab)                          | RvP addon. Возвращает реальный размер таблицы 'tab'
------------------------------------------|-----------------------------------------------------------------------
table.keys (tab)                          | RvP addon. Возвращает массив ключей таблицы 'tab'
------------------------------------------|-----------------------------------------------------------------------
table.random (tab)                        | RvP addon. Возвращает случайный елемент таблицы 'tab' (key,value)
------------------------------------------|-----------------------------------------------------------------------
table.array (tab)                         | У RvP есть такая (table.values), но я её всю же перепишу.
                                          | Возвращает массив значений таблицы 'tab'
------------------------------------------|-----------------------------------------------------------------------
table.values (tab)                        | Возвращает таблицу, где ключами являются значения таблицы 'tab'	({value = true})
------------------------------------------|-----------------------------------------------------------------------
table.copy (tab [, mode])                 | Копирование таблиц любой вложенности и метаданных.
                                          | Возвращает новую таблицу. При ошибке возвращается 'tab'.
                                          | "tab"  - Исходная таблица
                                          | "mode" - Опционально. Спецификатор степени вложенности метаданных.
                                          | Может принимать следующие значения :
                                          | 'Отрицательное значение' - копируется только исходная таблица
                                          | 0 - копируется только исходная таблица, вложенные таблицы не 
                                          | копируются
                                          | 1 - для таблицы устанавливается метатаблица, соответствующая исходной
                                          | 2 - копируются метаданные всех уровней вложенности
------------------------------------------|-----------------------------------------------------------------------
table.reverse (tab)                       | Возвращает массив, в котором значения массива 'tab' располаются
                                          | в обратном порядке.
                                          | При ошибке возвращается 'tab'.
                                          | Если вместо массива 'tab' была передана хэш-таблица, то будет
                                          | возвращена её индекстрованная часть в реверированном виде.
------------------------------------------|-----------------------------------------------------------------------
table.invert (tab [, func [, params] ])   | Возвращается таблица, в которой ключи и значения таблицы 'tab'
                                          | меняются местами. При ошибке возвращается 'tab'.
                                          | Естественно нужно помнить о том, что одинаковые значения, становясь
                                          | ключами в возвращаемой таблице, затрут друг друга
                                          | Karlan add: можно добавлять функцию с параметрами для указания
                                          | значений полей в инвертированной таблице
------------------------------------------|-----------------------------------------------------------------------
table.append (tab, added)                 | Присоединяет массив 'added' в конец массива'tab'.
------------------------------------------|-----------------------------------------------------------------------
table.add (tab, added [, pos])            | Возвращается таблица, копия таблицы 'tab', в которую добавляется
                                          | элемент 'added'. При ошибке возвращается 'tab'.
                                          | Если 'added' таблица, то добавляются все поля этой таблицы
                                          | Если ключи совпадают, то значения из 'added' будут добавлены в
                                          | индексированную часть возвращаемой таблицы.
                                          | Если 'added' любой другой тип данных, то в конец индексированной
                                          | части возвращаемой таблицы добавляется поле с этим значением.
                                          | Аргумент 'pos' - number - опционально. Указывает в какую позицию
                                          | индексированной части возвращаемой таблицы будут вставлены элементы
                                          | По умолчанию значения будут вставлены в конец таблицы
------------------------------------------|-----------------------------------------------------------------------
table.find (tab, ...)                     | Проверка, одержит ли таблица все перечисленные значения. При ошибке
                                          | возвращается 'false'. Возвращает 'boolean'.
                                          | Значения перечисляются как : 'value1, value2', ... , 'valueN'
                                          | Для проверки одного значения лучше использовать 'table.index'
                                          | как более быструю
------------------------------------------|-----------------------------------------------------------------------
table.max (tab [, mode])                  | Возвращаетс максимальное значение таблицы 'tab'.
                                          | При ошибке возвращается ноль.
                                          | Аргумент 'mode' указывает какие данные сравнивать.
                                          | Если 'mode' - 'отрицательное значение', то сначала сравниваются поля
                                          | с числовыми значениями.
                                          | Если таковых нет, то затем сравниваются поля со строковыми значениями.
                                          | Причем сравнивается длина строк (в отличии от стандартного побайтового
                                          | сравнения). Если и таковых нет, то возвращается 'nil'
                                          | Если 'mode' - 'положительное значения', сразу сравниваются строковые
                                          | значения, и если их не окажется возвращается 'nil'
------------------------------------------|-----------------------------------------------------------------------
table.min (tab [, mode]                   | Возвращает минимальное значение таблицы 'tab'. При ошибке возвращается
                                          | ноль. Аргументы - см. table.max
------------------------------------------|-----------------------------------------------------------------------
table.merge (tab1, tab2)                  | Слияние таблиц. При совпадении ключей, значения из 'tab1' заменяются
                                          | значениями из 'tab2'. При ошибке возвращается 'tab1'.
------------------------------------------|-----------------------------------------------------------------------
table.part(array, i [, j] [,mode])        | Возвращает таблицу cо значениями полей массива 'array', от поля с
                                          | индексом 'i' до поля с индексом 'j'. При ошибке возвращается 'array'.
                                          | Аргумент 'j' - опционально. По умолчанию равен длине таблицы.
                                          | Аргумент 'mode' - опционально. Его значения может быть либо 'true'
                                          | либо 'nil'.
                                          | Если 'true', то поля записываются с теми же индексами, что были и в
                                          | массиве 'array'. Т.е. при 'i'>1 и 'j'>1 возвращается хеш-таблица.
                                          | Иначе возвращается массив.
                                          | Если 'j < i', то значения реверсируются относительно индексов.
------------------------------------------|-----------------------------------------------------------------------
table.pairs (tab)                         | Возвращает массив, состоящий из таблиц, значениями полей которых
                                          | являются ключ и значение таблицы 'tab'.
                                          | При ошибке возвращается таблица {{}}.
------------------------------------------|-----------------------------------------------------------------------
table.index(tab, value [, mode])          | Если таблица содержит значение 'value' то возвращается ключ этого
                                          | значения, иначе 'nil'. При ошибке возвращается 'nil'.
                                          | 'value' может быть любым типом данных.
                                          | 'mode' указывает как сравнивать найденные значения.
                                          | Значения этого аргумента описанны в 'table.equal'
------------------------------------------|-----------------------------------------------------------------------
table.include (tab1, tab2)                | Возвращает таблицу с одинаковыми полями из таблиц 't1' и 't2'.
                                          | При ошибке возвращается пустая таблица.
------------------------------------------|-----------------------------------------------------------------------
table.exclude (tab, del [, mode] )        |  Возвращает таблицу с теми полями таблицы 'tab', которые отсутствуют
                                          | в 'del'. При ошибке возвращается пустая таблица.
                                          | Аргумент 'del' может быть любым типом данных.
                                          | Если 'del' не таблица, то будет исключено поле таблицы 'tab',
                                          | со значением 'del'
                                          | Аргумент 'mode' может принимать следующие значения :
                                          | mode==1   Исключаются поля при совпадении ключей
                                          | mode==2   Исключаются поля при совпадении значений
                                          | mode==nil Исключаются поля при совпадении и ключей и значений
------------------------------------------|-----------------------------------------------------------------------
table.shuffle(tab)                        | Случайным образом меняет местами значения массива tab
                                          | Возвращает tab.
------------------------------------------|-----------------------------------------------------------------------
table.equal(tab1, tab2 [, mode])          | Стандартный оператор '==' сравнивает таблицы как объекты.
                                          | Поэтому сравнение двух различных таблицы, но с совершенно одинаковым
                                          | содержимым даст отрицательный результат.
                                          | Функция сравнивает таблицы 'tab1' и 'tab2' по соделжимому.
                                          | Возвращает 'boolean'.
                                          | 'tab1' и 'tab2' также могут быть и любым другим типом данных.
                                          | 'mode' может принимать значения :
                                          | 'Отрицательное значение' - сравнивается содержимое таблиц как объектов
                                          | Т.е. t1={1,2,3} будет равна t2={1,2,3), но t1={1,{2},3} и t2={1,{2},3}
                                          | нет, т.к. таблицы внутри 't1' и 't2' хоть и равны по содержимому,
                                          | но являются разными объектами. Такой способ подходит для сравнения
                                          | массивов и простых одномерных хэшей, не содержащих таблиц,
                                          | как наиболее быстрый.
                                          | 1 - (любое число x <= 1) - сравниваются все вложенности таблиц
                                          | 2 - (любое число 1 < x <= 2) - то же, что и mode==1 плюс сравниваются
                                          |      метатаблицы (если есть)
                                          | 3 - (любое число x > 2) - если для таблиц установлен метод '__eq',
                                          |      то сравнение производиться так, как описано в этом метаметоде.
                                          |      Иначе выполняются действия как при mode==2
------------------------------------------|-----------------------------------------------------------------------
table.string(array [,sep [, i [, j] ] ])  | Аналог стандартной функции 'table.concat'.
                                          | Можно было бы так и назвать, но вроде игре это не понравилось.
                                          | Проверю, и если это не так переименую. А пока пусть будет так.
                                          | Работает также, но при 'j' < 'i' результат будет возвращаться в
                                          | реверсированном виде (от t[j] до t[i])
                                          | При ошибке возвращается пустая строка.
------------------------------------------|-----------------------------------------------------------------------
table.tree (tab [, indent][ , mode])      | Возвращается строка, с элементами таблицы 'tab', представленными
                                          | древовидным образом. При ошибке возвращается пустая строка.
                                          | 'indent' - отступ для каждой ветки иерахии таблицы. Может быть либо
                                          |            строкой, либо числом, которое определяет кол-во символов
                                          |            пробела для отступа.
                                          | 'mode' - 'отрицательное значение' отображает ключи в обьектном виде
                                          | 'положительное значение' отображает ключи в строковом виде
                                          | Обьекты (function, userdata, thread) отображаются с префиксом '*'.
                                          | Ссылки на метаданные отображаются в угловых скобках "<.....>"
                                          | !!! WARNING !!! При распечатке, размер файла может быть достаточно
                                          | внушительным. Например распечатка таблицы '_G' при загруженной игре,
                                          | может заполнить файл более чем на 5 Mb.
------------------------------------------|-----------------------------------------------------------------------
table.print (tab [, indent][ , mode])     | Производит распечатку таблицы 'tab'. Аргументы см. в 'table.tree'
------------------------------------------|-----------------------------------------------------------------------
table.serialize (x, recur)                | Преобразует таблицу в Lua строку
------------------------------------------|-----------------------------------------------------------------------
table.unserialize (str)                   | Преобразует строку в Lua таблицу
------------------------------------------|-----------------------------------------------------------------------
table.transpose (tab)                     | Транспонирует таблицу
------------------------------------------|-----------------------------------------------------------------------
--]=]
#####      I O      ##############################################################################################
--[=[
---------------------------|--------------------------------------------------------------------------------------
io.save (file, mode, ...)  | Записывает аргументы '...' в файл 'file'.
                           | Если 'file' строка, то открывает этот файл в режиме "mode".
                           | Если 'file' хендлер, то работает с этим хендлером (если был открыт в режиме чтения,
                           | то запись не будет произведена)
                           | 'mode' - спецификатор, определяющий как будет записан файл. Может быть
                           | 'w','a','r+','w+','a+'.
                           | Во множестве аргументов '...' можно передавать строки и массивы, значения которых
                           | будут записаны как строки.
                           | Если хотя бы один из записываемых аргументов не будет являться строкой или массивом,
                           | то не записывается ничего.
                           | Каждое значения поля массива, дополняется символом перевода строки
---------------------------|--------------------------------------------------------------------------------------
io.load(file [, target])   | Возвращает массив с полями, значения которых являются строками файла 'filename'
                           | 'target' - опционально - может быть таблицей или строкой, в которые буду добавлены
                           | поля-строки файла.
                           | Если 'target' таблица, то возвратиться эта таблица
                           | Если 'target' строка, то возвратиться эта строка.
---------------------------|--------------------------------------------------------------------------------------
--]=]
#####  S T R I N G  ##############################################################################################
--[=[
-------------------------------------|----------------------------------------------------------------------------
string.lower (str)                   |  К нижнему регистру также можно приводить символы кириллицы кодировки
                                     |  windows-1251
-------------------------------------|----------------------------------------------------------------------------
string.upper (str)                   |  К верхнему регистру также можно приводить символы кириллицы кодировки
                                     |  windows-1251
-------------------------------------|----------------------------------------------------------------------------
string.part (str, i, j)              | 
-------------------------------------|----------------------------------------------------------------------------
string.patt (str)                    | 
-------------------------------------|----------------------------------------------------------------------------
string.case (str, mode)              | 
-------------------------------------|----------------------------------------------------------------------------
string.insert (s1, s2, pos, count)   | 
-------------------------------------|----------------------------------------------------------------------------
string.split (str, sep)              | 
-------------------------------------|----------------------------------------------------------------------------
string.trim (str, mode, set)         | 
-------------------------------------|----------------------------------------------------------------------------
string.width(str, width, indent, p)  | 
-------------------------------------|----------------------------------------------------------------------------
__mul                                | 
-------------------------------------|----------------------------------------------------------------------------
__unm                                | 
-------------------------------------|----------------------------------------------------------------------------
__add                                | 
-------------------------------------|----------------------------------------------------------------------------
__pow                                | 
-------------------------------------|----------------------------------------------------------------------------
__div                                | 
-------------------------------------|----------------------------------------------------------------------------
__index                              | 
-------------------------------------|----------------------------------------------------------------------------
__sub                                | 
-------------------------------------|----------------------------------------------------------------------------
__mod                                | 
-------------------------------------|----------------------------------------------------------------------------
--]=]
#####    M A T H    ##############################################################################################
--[=[
-----------------------|------------------------------------------------------------------------------------------
math.is_int (num)      | return 'boolean' -- Является ли значение 'val' целым числом
-----------------------|------------------------------------------------------------------------------------------
math.is_inf (num)      | 
-----------------------|------------------------------------------------------------------------------------------
math.is_finite (num)   |
-----------------------|------------------------------------------------------------------------------------------
math.is_nan (num)      | return 'boolean' -- Является ли значение 'val' неопределённым числом.
                       | Remember : К операциям, приводящим к появлению NaN относятся:
                       | * все математические операции, содержащие NaN в качестве одного из операндов
                       | * деление ноля на ноль
                       | * деление бесконечности на бесконечность
                       | * умножение ноля на бесконечность
                       | * сложение бесконечности с бесконечностью противоположного знака
                       | * вычисление квадратного корня отрицательного числа
                       | * логарифмирование отрицательного числа
-----------------------|------------------------------------------------------------------------------------------
math.is_float (num)    | return 'boolean' -- Является ли значение 'val' не целым числом
-----------------------|------------------------------------------------------------------------------------------
math.is_positive (num) | return 'boolean' -- Является ли значение 'val' положительным числом
-----------------------|------------------------------------------------------------------------------------------
math.is_negative (num) | return 'boolean' -- Является ли значение 'val' отрицательным числом
-----------------------|------------------------------------------------------------------------------------------
--]=]
#####   D E B U G   ##############################################################################################
--[=[
-------------------------------------|----------------------------------------------------------------------------
debug.timer ([name][, log][, limit]) |  Дебаговый таймер. Вызов с аргументами опционально в произвольном порядке
                                     |  name (string)  - имя таймера
                                     |  log  (boolean) - true - данные будут записываться в лог,
                                     |                    иначе их нужно получить (x = timer.time)
                                     |  limit (number) - кол-во циклов срабатывания таймера (напр. в апдейте)
                                     |    -- Properties --
                                     |  timer - объект 'profile_timer'
                                     |  time  - время последнего (или единственного) срабатываня
                                     |  name  - имя таймера, заданное при инициализации или пустая строка
                                     |  total - сумма времени по всем контрольным точкам (timer:point())
                                     |  step  - счетчик кол-ва срабатываний таймера, независимо от 'limit'
                                     |  log   - установка/чтение способа вывода результатов
                                     |  limit - установка/чтение лимита срабатываний таймера
                                     |    -- Использование --
                                     |  local tm = debug.timer ([name][, log][, limit])
                                     |  tm:start()       -- запуск таймера
                                     |  tm:point([name]) -- контрольная точка с именем 'name' (опционально)
                                     |  tm:stop()        -- остановка таймера
                                     |  print(tm.total)
-------------------------------------|----------------------------------------------------------------------------
debug.reboot (script_name)           |  Перезагрузка скриптов без выхода из игры
                                     |  'script_name' может быть как именем файла*.script, так и файла *.lua
-------------------------------------|----------------------------------------------------------------------------
--]=]
#####     N I L     ##############################################################################################
--[=[
--------------------------|---------------------------------------------------------------------------------------
__index                   | Множественные обращения к несуществующим полям таблицы не приведут к вылетам
--------------------------|---------------------------------------------------------------------------------------
__concat                  | Конкатецнация с 'nil' не приведет к вылету
--------------------------|---------------------------------------------------------------------------------------
--]=]
--]]--------------------------------------------------

module '_G'

prefetch 'lua_debug'

local _match  = string.match
local _find   = string.find
local _sub    = string.sub
local _gsub   = string.gsub
local _lower  = string.lower
local _upper  = string.upper
local _char   = string.char --// Karlan: не хватало
local _concat = table.concat
local _assert = assert
empty_function = function() end

function error (msg, ...)
    local reason = string.exformat(msg or '<>',...) or '<Not reason!>'
    log1('! ERROR :>'..string.rep('~',94))
    log1('! ERROR :>ВНИМАНИЕ! Критическая ошибка! Ниже строка с информацией по ошибке!')
    log1('! ERROR :>ОШИБКА: \n')
    log1('! Описание ошибки: '..debug.traceback(reason, 3)..'\n')
    if not PROSECTORS_DEBUG then engine_stack('! SCRIPT ERROR:') end
    log1('! ERROR :>ВНИМАНИЕ! ЭТИ ДАННЫЕ НУЖНО ОБЯЗАТЕЛЬНО ОТПРАВИТЬ РАЗРАБОТЧИКУ!')
    log1('! ERROR :>'..string.rep('~',94))
    get_console():execute('flush') --// для более полного дампа, иногда бывают сбои
    --// Karlan: если мы не под дебагом, то привалим и игру тоже
    if not PROSECTORS_DEBUG then fatal_error("Отправьте SAVE и *.log-файлы разработчику! ИНФОРМАЦИЯ ПО ОШИБКЕ НАХОДИТСЯ ВЫШЕ!") end
    _assert(false)  -- остановить Lua
end

function error_warn (msg, ...)
    local reason = string.exformat(msg or '<>',...) or '<Not reason!>'
    log1('~ WARNING :>'..string.rep('~',94))
    log1('~ WARNING :>ВНИМАНИЕ! Некритическая ошибка! Ниже строка с информацией по ошибке!')
    log1('~ WARNING :>ОШИБКА: \n')
    log1('~ Описание ошибки: '..debug.traceback(reason, 3)..'\n')
    if not PROSECTORS_DEBUG then engine_stack('~ SCRIPT WARNING:') end
    log1('~ WARNING :>'..string.rep('~',94))
    if PROSECTORS_DEBUG then play_system_sound("warning.wav") end
    return false
end

function verify_fail (msg, ...)
    local reason = string.exformat(msg or '<>',...) or '<Not reason!>'
    log1('~ VERIFY FAILED: '..debug.traceback(reason, 3)..'\n')
--    if not PROSECTORS_DEBUG then engine_stack('~ SCRIPT WARNING:') end
--    if PROSECTORS_DEBUG then play_system_sound("warning.wav") end
    return false
end

function traceback (msg, ...)
    local reason = string.exformat(msg or '<>',...) or '<Not reason!>'
    log1('# TRACEBACK :>'..string.rep('~',94))
    log1('# TRACEBACK INFO: '..debug.traceback(reason, 3)..'\n')
    if not PROSECTORS_DEBUG then engine_stack('# TRACEBACK INFO:') end
    log1('# TRACEBACK :>'..string.rep('~',94))
    return false
end

function assert (cond, msg, ...)
    if not (cond) then
        error(msg, ...)
    end
end

function verify (cond, msg, ...)
	if not (cond) then
		verify_fail(msg, ...)
		return false
	end
	return true
end

function verify_warning (cond, msg, ...)
	if not (cond) then
		error_warn(msg, ...)
		return false
	end
	return true
end
--// Karlan->Team: это надо для проверки тестеров, у них слишком прямые руки и постоянно в ярлык вбивают ключи типа =key вместо -key, ну или просто key
function print_core_params()
    log1('CORE PARAMS: '..command_line())
end

ASSERT = assert --// дубль для таймеров

--####################  C O N S T  ##############################################################################

LUA_PRINT_DIR  = "$logs$"
LUA_PRINT_FILE = 'lua_log.txt'
LUA_PRINT_MODE = 'a'
LUA_PRINT_SEP  = ' = '
LUA_PRINT_PATH = getFS():update_path(LUA_PRINT_DIR, LUA_PRINT_FILE)

--// Karlan
TRUE    = 1
FALSE   = 0
NULL    = nil

EPS_S               = 0.0000001
EPS                 = 0.0000100
EPS_L               = 0.0010000

--####################  G L O B A L  #############################################################################
-- function toboolean  (x) return not not x end --// Karlan->Nazgool: позволил себе изменить эту функцию (см. ниже)

function is_table   (x) return type(x) == 'table'    end
function is_boolean (x) return type(x) == 'boolean'  end
function is_number  (x) return type(x) == 'number'   end
function is_function(x) return type(x) == 'function' end
function is_string  (x) return type(x) == 'string'   end
function is_userdata(x) return type(x) == 'userdata' end
function is_thread  (x) return type(x) == 'thread'   end
function is_nil     (x) return x == nil end

function is_true    (x) return x == true  end -- а это просто для поддержания стиля :)
function is_false   (x) return x == false end -- -//-

function is_array   (x) return is_table(x) and table.size(x) == #x or false end
--// Karlan [+]
function is_vector  (x) return is_userdata(x) and is_function(x.similar) and is_number(x.x) and is_number(x.y) and is_number(x.z) and not is_number(x.w) end
function is_vector2 (x) return is_userdata(x) and is_function(x.similar) and is_number(x.x) and is_number(x.y) and not is_number(x.z) end
function is_vector3 (x) return is_vector(x) end
function is_vector4 (x) return is_userdata(x) and is_function(x.similar) and is_number(x.x) and is_number(x.y) and is_number(x.z) and is_number(x.w) end
function is_matrix  (x) return is_userdata(x) and is_function(x.mul_43) end
function is_reader  (x) return is_userdata(x) and is_function(x.r_tell) and not is_function(x.w_tell) end
function is_packet  (x) return is_userdata(x) and is_function(x.r_tell) and is_function(x.w_tell) end
function is_CTime   (x) return is_userdata(x) and is_function(x.setHMSms) end

function is_correct (x) return not (is_false(x) or is_nil(x)) end
function is_callable(x) return is_function(x) or type((debug.getmetatable(x) or {}).__call) == "function" end
function types_equal(x,y) return type(x) == type(y) end

function toboolean  (x, ext) if ext then if x == 'false' or x == 'nil' or x == FALSE then return false end end return not not x end

function str_to_bool (str, mode)
	if str == "true" or str == "1" or str == "on" or str == "yes" then
		return true
	end
	if not mode or str == "false" or str == "0" or str == "off" or str == "no" then
		return false
	end
end

function is_empty_string (x) return is_string(x) and x == '' --[[or x == nil]] end
function is_not_empty_string (x) return is_string(x) and x ~= '' end
--// Karlan [-]
function is_empty (x, space)
    if is_string(x) then
        if space then return
            not x:match('[^%s\160]+')
        end
        return #x == 0
    elseif is_table(x) then
        return next(x) == nil
    else
            
    end
        
    return false
end
------------------------------------------------------------------------------------------------------------------
local _tostring = tostring
local clsid_invert
function clsid_name(clsid)
	if not clsid_invert then clsid_invert = table.invert(clsid_table) end
	return clsid_invert[clsid] or ""
end
-- Проверки для разных юзердат пока слабые, позже доделать
function tostring(data)
    local typ = type(data)
    if typ == 'userdata' then
        local cn = data.class_name
        local str = nil
        --// vector
        if cn == 'vector' then
            str = '*Fvector'..(data.x and string.format('[x=%.3f,y=%.3f,z=%.3f]', data.x, data.y, data.z):gsub('%.0+', ''):gsub('(%.[1-9]+)0+', '%1') or '')
        --// vector2
        elseif cn == 'vector2' then
            str = '*Fvector2'..(data.x and string.format('[x=%.3f,y=%.3f]', data.x, data.y):gsub('%.0+', ''):gsub('(%.[1-9]+)0+', '%1') or '')        
        elseif cn == 'vector4' then
            str = '*Fvector4'..(data.x and string.format('[x=%.3f,y=%.3f,z=%.3f,w=%.3f]', data.x, data.y, data.z, data.w):gsub('%.0+', ''):gsub('(%.[1-9]+)0+', '%1') or '')
        --// CTime
        elseif is_function(data.setHMSms) then
            if data == game.CTime then
                str = '*game.CTime'
            else
                str = '*CTime['..data:dateToString(0)..","..data:timeToString(3).."]"
            end
        --// fcolor
        elseif is_function(data.mul_rgb) then
            str = '*Fcolor'..(data.a and string.format('[a=%d,r=%d,g=%d,b=%d]', data.a, data.r, data.g, data.b) or '')
        --// flags
        elseif is_function(data.is_any) then
            str = '*flags[16/32]'
        --// matrix
        elseif is_function(data.getHPB) then
            str = '*Fmatrix'..(data._14_ and string.format('[_14_=%d,_24_=%d,_34_=%d,_44_=%d,c=%s,i=%s,j=%s,k=%s]',
                                                   data._14_, data._24_, data._34_, data._44_, -- ХЗ. Тут только целые числа???
                                                   tostring(data.c), tostring(data.i), tostring(data.j), tostring(data.k)
                                                ) or '')
        --// sound_object
        elseif is_function(data.play_no_feedback) then
            str = '*sound_object'..(data ~= sound_object and '[length='.. data:length() ..',position='..tostring(data:get_position())..']' or '')
        --// net_packet
        elseif is_function(data.w_begin) then
            str = '*net_packet'..(data ~= net_packet and '[length='..tostring(data.length)..']' or '')
        --// sound
        elseif is_function(data.set_sound_type) then
            str = '*sound'
        --// particles_object
        elseif is_function(data.last_position) then
            str = '*particles_object'..(data ~= particles_object and '[last_position='..tostring(data:last_position())..']' or '')
        --// particle
        elseif is_function(data.set_particle) then
            str = '*particle'
        --// move
        elseif is_function(data.body) then
            str = '*move'
        --// entity_action
        elseif is_function(data.all) then
            str = '*entity_action'
        --// effector
        elseif is_function(data.finish) then
            str = '*effector'
        --// object_binder
        elseif is_function(data.net_Relcase) then
            str = '*object_binder'
        --// ini_file
        elseif is_function(data.section_exist) then
            str = '*ini_file'
        --// look or hit
        elseif is_function(data.bone) then
            if is_function(data.direct) then
                str = '*look'
            else
                str = '*hit'
            end
        --// patrol
        elseif is_function(data.get_nearest) then
            str = '*patrol'
        --// FS
        elseif is_function(data.file_length) then
            str = '*FS'
        --// game_object
        elseif is_function(data.activate_slot) or is_number(data.m_story_id) then
            if data == game_object then
                str = '*game_object(class)'
            else
                local name = data:name():gsub('\r\n','') -- имя "tip_esc_trader_about_anomalies" имеет в конце(почему-то) символы "\r\n"
                local sid = tostring(data.m_story_id or data:story_id()):gsub(_tostring(INVALID_STORY_ID),'')
                sid = (sid == '') and '' or ',sid='..sid..''
                local pid = tostring(data.parent_id or get_id(data:parent())):gsub(_tostring(INVALID_ID),'')
                sid = (pid == '') and sid..']' or sid..',pid='..pid..']'
                local class_id = clsid_name(data:clsid()) --// иначе на дестрое обвалится, там его и так нет если что
                if is_function(data.id) then
                    str = string.format('*cobj[clsid.%s,name=%s,id=%d',class_id ,name ,data:id()) .. sid
                elseif is_number(data.id) then
                    str = string.format('*sobj[сlsid.%s,name=%s,id=%d,level=%s,online=%s',class_id ,name ,data.id ,data.level_name ,data.online) .. sid
                else -- пока для проверки
                    str = '*game_object'
                end
            end
        --// COptionsManager
        elseif is_function(data.IsGroupChanged) then
            str = '*COptionsManager'
        --// color_animator
        elseif is_function(data.calculate) then
            str = '*color_animator'
        --// Fbox
        elseif cn == 'Fbox' then
            str = string.format('*Fbox[max=%s,min=%s]', tostring(data.max), tostring(data.min))        
        --// Fsphere
        elseif cn == 'Fsphere' then
            str = string.format('*Fsphere[P=%s,R=%s]', tostring(data.P), data.R)
        --// Frect
        elseif cn == 'Frect' then
            str = string.format('*Frect[x1=%d,x2=%d,y1=%d,y2=%d]', data.x1, data.x2, data.y1, data.y2)        
        elseif cn == 'SRotation' then
            str = string.format('*SRotation[yaw=%.4f,pitch=%.4f,roll=%.4f]', data.yaw, data.pitch, data.roll)
        end
        
        if not str and is_string(cn) then
             str = '*'..cn
        end
        
        return str or 'userdata'
    else
        return _tostring(data)
    end
end

--####################  D E B U G - L O G  ######################################################################

function print (...)
    local count = select('#',...)
    local arg  = {...}
    for i = 1, count do
        arg[i] = tostring(arg[i])
    end
    local f = io.open(LUA_PRINT_PATH, LUA_PRINT_MODE)
    f:write(table.concat(arg, LUA_PRINT_SEP)..'\n')
    f:flush()
    f:close()
end

function push_tip(str,time,...)
	if actor and not level.in_process_destroy() then
		actor:give_game_news(string.exformat(str,...),"ui\\ui_hud_map_arrow",Frect():set(0,0,32,32),0,time)
	end
end


--// Karlan->Team: загружаем логи прямо здесь минуя lua_injections
local base_print = function(fmt,...) log1(string.exformat(fmt,...)) end
printf, log, logf, logc, logp, logt, clog, warning, abort  = empty_function, empty_function, empty_function, base_print, empty_function, empty_function, empty_function, error_warn, error
prefetch 'lua_log'

--####################   T A B L E   #############################################################################
function table.array (tab)
    assert(is_table(tab), [[bad argument #1 to 'table.array' => table expected, got '%s']], tostring(tab))

    local t = {}
    local n = 1
    
    for k,v in pairs(tab) do
        t[n] = v
        n = n + 1
    end
        
    return t
end
--------------------------------------------------------------------------------------
function table.values (tab)
    assert(is_table(tab), [[bad argument #1 to 'table.values' => table expected, got '%s']], tostring(tab))

    local t = {}

    for k,v in pairs(tab) do
        t[v] = true
    end
    
    return t
end
--------------------------------------------------------------------------------------
function table.copy (src, mode)
    assert(is_table(src), [[bad argument #1 to 'table.copy' => table expected, got '%s']], tostring(src))

    if mode and (mode < 0 or mode > 2) then
        error([[bad argument #2 to 'table.copy' => number in 0..2 interval expected, got '%s']], tostring(src))
    end

    if mode == 0 then
        local new = {}
        for k, v in pairs(src) do
            new[k] = v
        end
        return new
    end

    local function tcopy(src, mode)
        if not is_table(src) then
            return src
        end

        local new = {}

        if mode == 2 then
            for k, v in pairs(src) do
                new[tcopy(k, mode)] = tcopy(v, mode)
            end

            setmetatable(new, tcopy(getmetatable(src), mode))
        else
            for k, v in pairs(src) do
                new[k] = tcopy(v, mode)
            end

            if mode == 1 then
                setmetatable(new, getmetatable(src))
            end
        end

        return new
    end

    return tcopy(src, mode)
end
--------------------------------------------------------------------------------------
function table.reverse (tab)
    assert(is_table(tab), [[bad argument #1 to 'table.reverse' => table expected, got '%s']], tostring(arr))
         
    local t = {}
    local n = #tab
    
    for i = 1, n do
        t[n] = tab[i]
        n = n - 1
    end
        
    return t
end
--------------------------------------------------------------------------------------
function table.invert (tab, func, ...)
	assert(is_table(tab), [[bad argument #1 to 'table.invert' => table expected, got '%s']], tostring(tab))

	local t = {}

	if func then
		for k, v in pairs(tab) do
			t[v] = func(...)
		end
	else
		for k, v in pairs(tab) do
			t[v] = k
		end
	end

	return t
end
--------------------------------------------------------------------------------------
function table.append (tab, add)
	for k,v in ipairs(add) do
		table.insert(tab, v)
	end
end
--------------------------------------------------------------------------------------
function table.add (tab, add, pos)
    assert(is_table(tab), [[bad argument #1 to 'table.add' => table expected, got '%s']], tostring(tab))
    
    add = is_table(add) and add or {add}
    
    if not is_number(pos) then
        assert(pos ~= nil, [[bad argument #3 to 'table.add' => number or 'nil' expected, got '%s']], tostring(pos))
        pos = #tab+1
    end
    
    if not next(add) then return end
    
    local t   = {}
    
    for k,v in pairs(tab) do
        t[k] = v
    end
        
    for k,v in pairs(add) do
        if tab[k] then
            table.insert(t, pos, v)
            pos = pos + 1
        else
            t[k]=v
        end
    end
        
    return t
end
--------------------------------------------------------------------------------------
function table.find(tab, ...)
    assert(is_table(tab), [[bad argument #1 to 'table.find' => table expected, got '%s']], tostring(tab))
    
    local arg = {...}
    local len = #arg
    local t = {}
    
    for _,v in pairs(tab) do
        t[v] = true
    end
    
    for i = 1, len do
        if t[arg[i]] then
            len = len - 1
        end
    end
    
    return len == 0
end
--------------------------------------------------------------------------------------
function table.max(tab, mode)
    assert(is_table(tab), [[bad argument #1 to 'table.max' => table expected, got '%s']], tostring(tab))
    
    local t_num = {}
    local t_str = {}
    
    for _,v in pairs(tab) do
        if     is_number(v) then t_num[#t_num+1] = v
        elseif is_string(v) then t_str[#t_str+1] = v
        end
    end
        
    if not mode and next(t_num) then
        table.sort(t_num)
        return t_num[#t_num]
    end
        
    if next(t_str) then
        table.sort(t_str, function(a,b) return #a > #b end)
        return t_str[1]
    end

    return nil
end
--------------------------------------------------------------------------------------
function table.min(tab, mode)
    assert(is_table(tab), [[bad argument #1 to 'table.min' => table expected, got '%s']], tostring(tab))
    
    local t_num = {}
    local t_str = {}
    
    for _,v in pairs(tab) do
        if     is_number(v) then t_num[#t_num+1] = v
        elseif is_string(v) then t_str[#t_str+1] = v
        end
    end
        
    if not mode and next(t_num) then
        table.sort(t_num)
        return t_num[1]
    end
        
    if next(t_str) then
        table.sort(t_str, function(a,b) return #a < #b end)
        return t_str[1]
    end
        
    return nil
end
--------------------------------------------------------------------------------------
function table.merge(tab1, tab2)
    if not is_table(tab1) then
        error([[bad argument #1 to 'table.merge' => table expected, got '%s']], tostring(tab1))
    elseif not is_table(tab2) then
        error([[bad argument #2 to 'table.merge' => table expected, got '%s']], tostring(tab1))
    end
    
    local t = {}
    
    for k,v in pairs(tab1) do t[k] = v end
    for k,v in pairs(tab2) do t[k] = v end
    
    return t
end
--------------------------------------------------------------------------------------
function table.part(arr, i, j, mode)
    if not is_table(arr) then
        error([[bad argument #1 to 'table.part' => table expected, got '%s']], tostring(arr))
    elseif not (math.is_int(i) and math.is_positive(i)) then
        error([[bad argument #2 to 'table.part' => positive integer number expected, got '%s']], tostring(i))
    elseif math.is_int(j) then
        if math.is_negative(j) then
            error([[bad argument #3 to 'table.part' => positive integer number, true or nil expected, got '%s']], tostring(j))
        elseif mode ~= true and mode ~= nil then
            error([[bad argument #4 to 'table.part' => true or nil expected, got '%s']], tostring(j))
        end
    elseif j ~= true and j~=nil then
        error([[bad argument #3 to 'table.part' => positive integer number, true or nil expected, got '%s']], tostring(j))
    else
        j, mode = #arr, j
    end
    
    local len = #arr
    -- ограничить кол-во циклов при числе, большем размера массива
    i = i > len and len or i 
    j = j > len and len or j
    
    local t = {}
    local reverse = j<i
    
    if mode then
        for n = i, j, reverse and -1 or 1 do
            t[n] = arr[reverse and j or n]
            j=j+1
        end
    else
        for n = i, j, reverse and -1 or 1 do
            t[#t+1] = arr[n]
        end
    end
    
    return t
end
--------------------------------------------------------------------------------------
function table.pairs(tab)
    assert(is_table(tab), [[bad argument #1 to 'table.pairs' => table expected, got '%s']], tostring(tab))
    
    local t = {}
    
    for k,v in pairs(tab) do
        t[#t+1] = {k,v}
    end
    
    return t
end
--------------------------------------------------------------------------------------
function table.index(tab, value, mode)
    assert(is_table(tab), [[bad argument #1 to 'table.index' => table expected, got '%s']], tostring(tab))
    
    if mode then
        for k,v in pairs(tab) do
            if table.equal(v, value, mode) then
                return k
            end
        end
    else
        for k,v in pairs(tab) do
            if v == value then
                return k
            end
        end
    end
    return nil
end
--------------------------------------------------------------------------------------
function table.include(tab1, tab2)
    if not is_table(tab1) then
        error([[bad argument #1 to 'table.include' => table expected, got '%s']], tostring(tab1))
    elseif not is_table(tab2) then
        error([[bad argument #2 to 'table.include' => table expected, got '%s']], tostring(tab2))
    end
    
    local w = {}
    
    for k,v in pairs(tab2) do
        if tab1[k] == v then
            w[k] = v
        end
    end
    
    return w
end
--------------------------------------------------------------------------------------
function table.exclude(tab, del, mode)
    assert(is_table(tab), [[bad argument #1 to 'table.exclude' => table expected, got '%s']], tostring(tab))

    -- mode = mode or 0
    -- del  = is_table(del) and del or {del}

    if not next(del) then return table.copy(tab,0) end

    local w = {}

    if mode == 2 then
        local t   = {}
        for k,v in pairs(del) do t[v]=k end
        for k,v in pairs(tab) do
            if not t[v] then w[k]=v end
        end
    elseif mode == 1 then
        for k,v in pairs(tab) do
            if not del[k] then w[k]=v end
        end
    else -- mode == nil
        for k,v in pairs(tab) do
            if tab[k] ~= del[k] then w[k]=v end
        end
    end

    return w
end
--------------------------------------------------------------------------------------
function table.shuffle(tab)
	local function SF(t, i1, i2)
		local ti1 = t[i1]
		local ti2 = t[i2]
		t[i1] = ti2
		t[i2] = ti1
	end
	local tc = #tab
	while tc > 1 do
		local idx = math.random(tc)
		SF(tab, idx, tc)
		tc = tc - 1
	end
	return tab
end
--------------------------------------------------------------------------------------
function table.equal(t1, t2, mode)
    local type1 = type(t1)
        
    if type1 ~= type(t2)                then return false    end
    if type1 ~= 'table'                 then return t1 == t2 end
    if #t1 ~= #t2                       then return false    end
    if table.size(t1) ~= table.size(t2) then return false    end
        
    if mode then
        local mt1 = getmetatable(t1)
        local mt2 = getmetatable(t2)
        
        if 2 < mode then -- mode 3 virtual
            if mt1 and mt2 and mt1.__eq == mt2.__eq then
                return t1 == t2
            end
        end
        -- mode 1 virtual
        for k,v1 in pairs(t1) do
            local v2 = t2[k]
            if v2 == nil or not table.equal(v1, v2, mode) then
                return false
            end
        end
        
        if 1 < mode then-- mode 2 virtual
            if mt1 ~= mt2 then
                if not table.equal(mt1, mt2, mode) then
                    return false
                end
            end
        end
    else
        for k,v in pairs(t1) do
            if t1[k] ~= t2[k] then
                return false
            end
        end
    end
    
    return true
end
--------------------------------------------------------------------------------------
function table.string(arr, sep, i, j)
    assert(is_array(arr), [[bad argument #1 to 'table.string' => array expected, got '%s']], tostring(arr))
    
    i = (i == nil) and 1    or i
    j = (j == nil) and #arr or j
    
    if not is_number(i) then
        error([[bad argument #2 to 'table.string' => number or nil expected, got '%s']], tostring(i))
    elseif not is_number(j) then
        error([[bad argument #3 to 'table.string' => number or nil expected, got '%s']], tostring(j))
    end
    
    if j < i then
        local u = {}
        
        for n = i, j, -1 do
            u[#u+1] = arr[n]
        end
        
        return _concat(u, sep)
    end
    
    return _concat(arr, sep, i, j)
end
--------------------------------------------------------------------------------------
function table.tree(tab)
    local indent = '    '
    local memory = {}
    
    local function parse(t, i, p)
        local temp  = {}
        local typ   = type(t)
        local space = indent..i
        
        if memory[t] then return '<'..memory[t]..'>', '' end
    
        if typ == "nil" then
            return "nil"
        elseif typ == "string" then
            local x = (string.find(t,'\n') and '[['..t..']]') or string.format("%q", t)
            return '['..x..']', '.'..t
        elseif typ == "number" or typ == "boolean" then
            local x = tostring(t)
            return '['..x..']', '['..x..']'
        elseif typ == "function" then
            memory[t] = p
            local gi = debug.getinfo(t)
            local line = gi.linedefined
            local counter = 1
            local name
            if gi.what == 'Lua' then
                for w in io.lines(gi.source:sub(2)) do
                    if counter == line then name = w:match('function%s*(.+)%s-%(') end
                    counter = counter + 1
                end
            end
            local x = string.format('function[name:%s,id:'..tostring(t):match('%w+$')..',file:%s,line:%s]',name or '?', gi.short_src:match('[^\\]+$'), line)
            x = x:gsub('name:%?,',''):gsub(',line:%-1',''):gsub('%[C%]','C')
            return '<'..x..'>', '['..x..']'
        elseif typ == "thread" or typ == "userdata" or not p then
            memory[t] = p
            local x = tostring(t)
            return '<'..x..'>', '['..x..']'
        else --if typ == "table" then
            memory[t] = p
            
            for k, v in pairs(t) do
                local key, path = parse(k, space..indent)
                local val       = parse(v, space..indent, p..path)
                
                temp[#temp+1] = space..key..' = '..val..',\n'
            end
        end
        
        return '\n'..i..'{ ----- id '..tostring(t)..' -----\n'..table.concat(temp)..i..'}'
    end
    
    return parse(tab, '', (tab == _G) and '_G' or 'main')
end
--------------------------------------------------------------------------------------
function table.print(t, name, ...)
    if not is_table(t) then verify_fail("t is [%s] name [%s]",t,name) return end
    if not name then
        print(table.tree(t))
        return
    end
    if not PROSECTORS_DEBUG then return end
    -- if not t_print then return log1(table.tree(t)) end
    if not t_print then return end --// для тестеров, в релизе не забыть обратно поменять
    t_print(t, debug.getinfo(2), "Print ["..name.."] table:", "[size("..table.size(t)..")]:", ...)
end
--------------------------------------------------------------------------------------
function table.serialize(x, recur)
    local t = type(x)
    recur = recur or {}

    if t == "nil" then
        return "nil"
    elseif t == "string" then
        return string.format("%q", x)
    elseif t == "number" then
        return tostring(x), false
    elseif t == "boolean" then
        return x and "true" or "false"
    elseif getmetatable(x) then
        -- Can not serialize a table that has a metatable associated with it.
        return nil, true
    elseif t == "table" then
        if recur[x] then
            -- Can not serialize recursive tables.
            return nil, true
        end
        recur[x] = true

        if not next(x) then return "{}" end -- need to check if table is empty
        local s, key, val = "{"
        for k, v in pairs(x) do
            key, err = table.serialize(k, recur)
            if err then    --if something srong with table's keys
                return nil, true
            end
            val, err = table.serialize(v, recur)
            if err then    --if something srong with table's values
                return nil, true
            end
            s = s .. "[" .. key .. "]" .. "=" .. val .. ","
        end

        return s:sub(0, s:len() - 1) .. "}"
    end

    -- Can not serialize value of passed type
    return nil, true
end
--------------------------------------------------------------------------------------
function table.unserialize(str)
    if type(str)~='string' or str=='' then return {} end
    return loadstring("return " .. str)() or {}
end
--------------------------------------------------------------------------------------
--// Karlan: возможно стоит доделать ключи, но подумал что не надо, так как уже есть специальная инвертирующая функция, другой вопрос что ключи могут быть нужны не только для инвертирования
function table.map(tab, value, ...)
    assert(is_table(tab), [[bad argument #1 to 'table.map' => table expected, got '%s']], tostring(tab))

    if type(value) ~= "function" then
        local v = value or error([[bad argument #2 to 'table.map' => value is 'nil']])
        value = function() return v end
    end
    
    local result = {}
    for _, v in ipairs(tab) do
        result[v] = value(v, ...)
    end
    return result
end
--------------------------------------------------------------------------------------
function table.compose(tab, mode)
    assert(is_table(tab), [[bad argument #1 to 'table.compose' => table expected, got '%s']], tostring(tab))
    
    local t = {}
    
    if mode == 0 then
        for i=1, #tab do
            if t[tab[i]] then
                t[tab[i]] = t[tab[i]]+1
            else
                t[tab[i]] = 0
            end
        end
    end
    
    if mode == 1 then
        for k,v in pairs(tab) do
            for i=1,v do
                table.insert(t, k)
            end
        end
    end
    
    return t
end
--------------------------------------------------------------------------------------
function table.verify_len(tab)
    assert(is_table(tab), [[bad argument #1 to 'table.verify_len' => table expected, got '%s']], tostring(tab))
    assert(is_function(table.size), [[function 'table.size' is not implemented!]])
    
    local size1, size2 = select('#', unpack(tab)), table.size(tab)
    assert(size1 >= size2, '%s >= %s', size1, size2)
    if size1 >= size2 then
        return size1
    else
        return size2
    end
end
--------------------------------------------------------------------------------------
--// via Thijs
function table.safe_pack(...)
    return {n=select('#',...); ...}
end
--------------------------------------------------------------------------------------
function table.safe_unpack(t, i, j)
    return unpack(t, i or 1, j or t.n or #t)
end
--------------------------------------------------------------------------------------
prefetch 'lua_vftable' --// расширенные функции таблиц для выявления 'дырявых' списков
--------------------------------------------------------------------------------------
--###########################################   I O   ############################################################
-- таблица спецификаторов записи(изменения) для функции io.save
local io_open_modes = {w=true, a=true, ['r+']=true, ['w+']=true, ['a+']=true}
--------------------------------------------------------------------------------------
function io.save(file, mode, ...)
    local arg = {...}
    if is_empty(arg) then return end -- если данных для записи нет, то выйти
        
    local handler = (io.type(file) == 'file') -- получить валидность, если 'file' это хендлера файла
        
    if not is_string(file) and not handler then -- если 'file' не является строкой или хендлером
        return error([[bad argument #1 to 'io.save' => string path or handler file expected, got '%s']], tostring(file))
    end
        
    if mode and not io_open_modes[mode] then -- проверка валидности аргумента 'mode'
        return error([[bad argument #2 to 'io.save' => string 'r+','w','w+','a' or 'a+', expected, got '%s']], tostring(mode))
    end
    
    local hash = {}
    for i = 1, #arg do
        local text = arg[i]
        if is_string(text) then
            hash[#hash+1] = text
        elseif is_array(text) then
            hash[#hash+1] = _concat(text,'\n')
        else
            return error([[bad argument #%d to 'io.save' => string or table expected, got '%s']], i+2, tostring(text))
        end
    end
        
    local f = handler and file or io.open(file, mode) -- если был передан хендлер, то использовать его, иначе создать новый
        
    if not f then -- если файл не открылся
        return error([[to 'io.save' => file '%s' is not loaded]], tostring(file))
    end
        
    f:write(_concat(hash,'\n')..'\n')
    f:close()
end
--------------------------------------------------------------------------------------
function io.load(file, target)
    local handler = (io.type(file) == 'file') -- получить валидность, если 'file' это хендлера файла
    if not is_string(file) and not handler then -- если 'file' не является строкой или хендлером
        return error([[bad argument #1 to 'io.load' => string or handler file expected, got '%s']], tostring(filename))
    end
        
    local f = handler and file or io.open(file) -- если был передан хендлер, то использовать его, иначе создать новый
        
    if not f then -- если файл не открылся
        return error([[to 'io.load' => file '%s' is not loaded]], tostring(file))
    end
    
    target = not target and {} or target
        
    if is_table(target) then
        for line in f:lines() do
            target[#target+1] = line
        end
    elseif is_string(target) then
        target = target .. f:read('*a')
    else
        assert(false, [[to 'io.load' => string or table file expected, got '%s']], tostring(target))
    end
        
    f:close()
    return target
end
--------------------------------------------------------------------------------------
function io.shell(cmd)
    if type(cmd) ~= 'string' then return end
    
    local h = io.popen(cmd)
    local s
    
    if h then
        s = h:read('*a')
        h:close()
    end

    return s
end
--#######################################   S T R I N G   ########################################################
string.is_string   = function(s) return is_string(s) end --// Karlan
--------------------------------------------------------------------------------------
function string.lower(s)
    return _lower(s:gsub("([А-Я])",function(c) return _char(c:byte()+32) end):gsub("Ё", "ё"))
end
--------------------------------------------------------------------------------------
function string.upper(s)
    return _upper(s:gsub("([а-я])",function(c) return _char(c:byte()-32) end):gsub("ё", "Ё"))
end
--------------------------------------------------------------------------------------
function string.part(s, i, j) -- можно попробовать meta '%'
    if j and i ~= j then
        local len = #s + 1
        i = i < 0 and len + i or i
        j = j < 0 and len + j or j
        if j < i then
            s = string.reverse(s)
            i = len - i
            j = len - j
        end
    end
    return _sub(s, i, j)
end
--------------------------------------------------------------------------------------
function string.patt (s) return (_gsub (s, "(%p)", "%%%1")) end
--------------------------------------------------------------------------------------
function string.case (s, c) -- meta '^' регистр слова !!! Сделать для русского текста позиции !!!! Или разделить функции на ENG и RUS варианты
    if     c == 0 then return string.lower(s)
    elseif c == 1 then return string.upper(s)
    elseif c == 2 then
        s = s:lower()
        return _gsub(s, s:sub(1,1), string.upper, 1) -- для всего текста 's' только первая буква upper
    elseif c == 3 then
        --s = lower(s)
        return _gsub(s, '[^\n]+', function(a) return a:sub(1,1):upper()..a:sub(2):lower() end) -- для каждой строки текста 's'
    elseif c == 4 then 
        return _gsub(s, '(%a)(%a+)', function(a,b) return a:upper()..b end) -- для каждого слова текста 's'
    end
    return s
end
--------------------------------------------------------------------------------------
function string.insert (s1, s2, pos, count) -- meta '+' вставить в строку в указанную позицию указанное кол-во символов
    if pos then
        local p1 = s1:sub(0, pos-1)
        local p2 = s1:sub(pos)
        count = count or #s2
        local s = p1 .. string.sub(s2, 1, count)
        return s .. p2, #s + 1
    end
    local s = s1 .. s2
    return s, #s + 1
end
--------------------------------------------------------------------------------------
function string.split (str, sep) -- разделение строки по сепаратору
    local t = {}
    sep = sep ~= '' and sep or '%s'
    for w in str:gmatch('[^'..sep..']+') do t[#t+1] = w end
    return t
end
--------------------------------------------------------------------------------------
--[[function string.remove(s1, s2) -- meta '-' удалить из строки символ или набор символов, часть строки по символам или по позиции
    
end]]
--------------------------------------------------------------------------------------
--[[function string.fill(str, pos, width, fill) -- заполнить строку символами
    
end]]
--------------------------------------------------------------------------------------
function string.trim (str, mode, set)
    set = set or '%s'
    if mode then
        if _match(mode, 'a') then
            str = _match(str, '^['..set..']*(.-)['..set..']*$')
            str = _gsub(str, '['..set..']+', ' ')
        else
            if _match(mode, 'l') then str = _match(str, '^['..set..']*(.-)$') end
            if _match(mode, 'r') then str = _match(str, '^(.-)['..set..']*$') end
            if _match(mode, 'c') then
                local l, m, r = _match(str, '^(['..set..']*)(.-)(['..set..']*)$')
                m = _gsub(m, '['..set..']+', ' ')
                str = l .. m .. r
            end
        end
    else str = _match(str, '^['..set..']*(.-)['..set..']*$')
    end
    return str
end
--------------------------------------------------------------------------------------
-- Форматирование текста по ширине
function string.width(s, w, paragrapf, indent)
    w = w or 80
    paragrapf = paragrapf or 0
    indent = indent or 0
    s = string.rep(" ", paragrapf) .. s
    local lstart, len = 1, string.len(s)
    
    while len - lstart > w do
        local i = lstart + w
        while i > lstart and string.sub(s, i, i) ~= " " do i = i - 1 end
        local j = i
        while j > lstart and string.sub(s, j, j) == " " do j = j - 1 end
        s = string.sub(s, 1, j) .. "\n" .. string.rep(" ", indent) ..
            string.sub(s, i + 1, -1)
        local change = indent + 1 - (i - j)
        lstart = j + change
        len = len + change
    end
    return s
end
--------------------------------------------------------------------------------------
--// Karlan [+]
function string.translate(s)
    return game.translate_string(s)
end
--------------------------------------------------------------------------------------
function string.exformat(fmt, ...)
    local pt1, pt2
    --// Karlan->Team: у кого есть время, напишите компромиссный вариант, по второму возможно уже все так и есть, но по первому определенно нет
    pt1, pt2 = '%%[sdf]', '%%s' --// нормальный луа 204
    -- pt1, pt2 = '%\%[s|d|f]', '%\%s' --// сталкерский луа 118
    local val2str = function(val,lua_type)
        if     lua_type == 'table'    then  return table.tree(val)
        elseif lua_type == 'userdata' then  return tostring(val)
        elseif lua_type == 'string'   then  return val
        elseif lua_type == 'boolean'  then  return val and "true" or "false"
        elseif lua_type == 'number'   then  return _tostring(val)
        end
        return '<'..lua_type..'>'
    end
    if type(fmt) == 'string' then
        local buff = fmt:match(pt1) --// Karlan fixed 310117
        if not buff then
            pt1 = '%%%d*%.?%d*f' --// %%%d*%.?%d*f / %%[0-9%.f]+ (этот эстетичнее, но жрет больше чем надо)
            buff = fmt:match(pt1)
        end
        if buff then --// есть патерн(ы)?
            local buff_arg = {...}
            local count = select('#',...) --// Karlan: Серега так делал, и Майк тоже самое Саше говорил, попробую без своих приблуд
            if PROSECTORS_DEBUG and (count < table.size(buff_arg)) then
                error('count < table.size(buff_arg) '..count..' | '..table.size(buff_arg))
            end
            -- if LJ2 then buff_arg = tablib.safe(buff_arg) end
            -- if next(buff_arg) then --// есть аргумент(ы)?
                local arg,val,lua_type,i = buff_arg,nil,nil,nil
                for i=1,count do --// можно конечно и цикл переделать, чтобы дважды массив не прокручивать, но функция больше дебаговая, так что фиг с ним
                    val = arg[i] --// значение текущего аргумента
                    lua_type = type(val) --// тип текущего аргумента
                    if lua_type == 'number' then
                        fmt = fmt:gsub(pt1,buff:format(val),1)
                    else
                        fmt = fmt:gsub(pt2,val2str(val,lua_type),1)
                    end
                end
            -- end
            fmt = fmt:gsub(pt1, '<NOT_argument!>')
        end
    else
        fmt = val2str(fmt,type(fmt))
    end
    return fmt
end
function make_string(fmt, ...)
    return string.exformat(fmt, ...)
end
--------------------------------------------------------------------------------------
-- Создание анаграммы
function string.anagram(s)
    local t = {}
    for i = 1, #s do
        t[i] = s:sub(i,i)
    end
    t = table.shuffle(t)
    return table.concat(t)
end
--// Karlan [-]
--================= New metametods ===============================================================================
local mt_string = getmetatable("") --local oldMeta = mt_string.__index
----------------------------------------------------------------
mt_string.__mul = string.rep
----------------------------------------------------------------
mt_string.__unm = string.reverse
----------------------------------------------------------------
mt_string.__add = string.insert
----------------------------------------------------------------
mt_string.__pow = string.case
----------------------------------------------------------------
mt_string.__div = string.split
----------------------------------------------------------------
mt_string.__index = function(s, i) return is_number(i) and _sub(s, i, i) or string[i] end
----------------------------------------------------------------
mt_string.__call =
    function(s,i,j)
        if is_number(i) then
            return _sub(s,i,j)
        elseif is_string(i) then
            return _find(s, string.patt(i))
        elseif is_table(i) then
            local t={} 
            for k,v in ipairs(i) do
                t[k] = _sub(s,v,v)
            end
            return table.concat(t)
        end
    end
----------------------------------------------------------------
mt_string.__sub =
    function(s, p)-- print(s,p)
        if is_number(p) then
            if p == 1 then
                s = _gsub(s, "^%s*(.-)%s*$", '%1')
            elseif p == 2 then
                s = _gsub(s, '%s+', ' ')
            end
            return s
        end

        local w = _match(p, '^%*(.-)$')
        local l = _match(p, '^%~(.-)$')
        
        if l then 
            for n = 1, #l do
                local i = _sub(l, n, n)
                s = _gsub(s, string.patt(i), '')
            end
        else 
            p = w or string.patt(p)
            s = _gsub(s, p, '')
        end
        return s
    end
----------------------------------------------------------------
--function(s, p)
        -- p = patt (p)
        --[[local t = {}
        local q = ''
        for w, l in gmatch(s, '(.-)'..p..'()') do
            q = sub(s, l)
            if #w > 0 then t[#t+1] = w end
        end
        if #q > 0 then t[#t+1] = q end
        return t]]
        
        --[[p = '^(.-)'..patt (p)..'(.-)$'
        local t = {}
        local w, l = match(s, p)
        local q = l
        while w do
            if #w > 0 then t[#t+1] = w end
            q = l
            w, l = match(l, p)
        end
        if  q and #q > 0 then t[#t+1] = q end
        return t]]
    --end
----------------------------------------------------------------
mt_string.__mod = function (s, t) -- %
    print(s,t)
end

--####################  M A T H  #################################################################################

-- Можно будет обращаться как к методу. Напр. x=4 ; print(x:sqrt())
debug.setmetatable(0, {__index  = math})

math.is_number   = function(x) return is_number(x) end --// Karlan

math.nan = -(0/0)
math.inf = 1/0

-- маитематические проверки
math.is_inf      = function(x) return (x == math.inf) or (x == -math.inf) end
math.is_finite   = function(x) return x > -(math.inf) and x < math.inf end
math.is_nan      = function(x) return is_number(x) and x ~= x or false end
math.is_int      = function(x) return is_number(x) and math.modf(x) == x or false end
math.is_float    = function(x) return is_number(x) and (not math.is_int(x)) or false end --// Karlan FIXED
math.is_positive = function(x) return is_number(x) and x > 0 or false end
math.is_negative = function(x) return is_number(x) and (not math.is_positive(x) and x ~= 0) or false end --// Karlan FIXED
math.is_zero     = function(x) return x == 0 end --// Karlan: требует дополнительной проверки, либо метода в метатаблице

-- округление числа до знака
-- первый аргумент - число которое надо округлить, второй аргумент - количество символов после запятой.
math.round =
    function (num, accuracy)
        return tonumber(string.format('%0.'..(accuracy or 0)..'f', num))
    end

--// Karlan: написал функцию с 0.5, пока полет нормальный
math.rounding = 
    function(value)
        value = tonumber(value) or error([[bad argument #1 to 'math.rounding' => value is not convert to number, got %s]], tostring(value))
        return (value+0.5>=math.ceil(value)) and math.ceil(value) or math.floor(value)
    end
    
-- Karlan.25.08.15
-- TODO: добавить в шапку!
-- проверяет целую часть на нечетность (из _g)
-- Nazgool FIX 05.09.02 16:11 
math.even = 
    function(value)
        value = tonumber(value) or error([[bad argument #1 to 'math.even' => value is not convert to number, got %s]], tostring(value))
        return math.floor(math.abs(value))%2 == 0
    end
    
-- округляет число в заданном диапазоне
math.clamp = 
    function(value, min, max)
        value = tonumber(value) or error([[bad argument #1 to 'math.clamp' => value is not convert to number, got %s]], tostring(value))
        return (value < min and min) or (value > max and max) or value
    end
--// Karlan UPD 05.11.13
math.exclamp = 
    function(value, min, max, min_value, max_value)
        value = tonumber(value) or error([[bad argument #1 to 'math.exclamp' => value is not convert to number, got %s]], tostring(value))
        return (value < min and min_value) or (value > max and max_value) or value
    end

math.in_interval =
    function(value, min, max, soft)
        value = tonumber(value) or error([[bad argument #1 to 'math.in_interval' => value is not convert to number, got %s]], tostring(value))
        return soft and (value >= min and value <= max) or (value > min and value < max)
    end
    
--[[local dec2hex = {
    [0] = 0,
    [1] = 1,
    [2] = 2,
    [3] = 3,
    [4] = 4,
    [5] = 5,
    [6] = 6,
    [7] = 7,
    [8] = 8,
    [9] = 9,
    [10] = 'A',
    [11] = 'B',
    [12] = 'C',
    [13] = 'D',
    [14] = 'E',
    [15] = 'F',
}]]

math.dec2hex = function(value)
    value = tonumber(value) or error([[bad argument #1 to 'math.dec2hex' => value is not convert to number, got %s]], tostring(value))
    return string.format('%x', value)
--[[    local m1,m2 = math.modf(value/16)
    if m1 > 15 then
        m1 = math.dec2hex(m1)
    else
        m1 = dec2hex[m1]
    end
    return m1..dec2hex[m2*16]]--]
end


math.flerp = function(v1, v2, i)
	if (i >= 1) then
		v1 = v2;
	else
		v1 = v1*(1-i) + v2*i;
	end
	return v1
end

math.fmove = function(v1, v2, i)
	local diff = v2-v1;
	v1 = v1 + (diff > 0 and math.min(i,diff) or math.max(-i,diff));
	return v1
end


--####################  D E B U G  ###############################################################################

local function call_timer(tab, ...)
    local name, log, limit = '', false, math.huge -- значения по умолчанию
    
    local args = {...}
    for i = 1, #args do -- если проставлены, то меняем значения по умолчанию
        local arg = args[i]
        if     type(arg) == 'string'  then name = arg
        elseif type(arg) == 'boolean' then log  = arg
        elseif type(arg) == 'number'  then limit = arg
        end
    end
     
    local tm = {timer = profile_timer(),
                log   = log ,
                limit = limit,
                name  = name,
                time  = 0,
                total = 0,
                step  = 0,
            }
    
    function tm:start()
        if self.step < self.limit then
            self.timer:start()
        end
        return self 
    end
    
    function tm:stop(msg)
        self.step = self.step + 1
        if self.step < self.limit then
            self.timer:stop()
            local last	= self.total
            self.total	= self.timer:time()
            self.time	= self.total - last
            if self.log then
                print("TIMER '" .. self.name .. "' > " .. (msg or 'STOP') ..
                      ' >  TIME = '  .. string.format('%.3f', self.time)  ..
                      ' >  TOTAL = ' .. string.format('%.3f', self.total) ..
                      ' >  STEP = ' .. self.step)
            end
        end
        return self
    end
    
    function tm:point(name)
        return self:stop("POINT '"..(name or '').."'"):start()
    end
    
    return tm
end

debug.timer = setmetatable({}, {__call = call_timer})

function debug.reboot(script_name)
	if package.loaded[script_name] then	-- для файлов *.lua
		package.loaded[script_name] = nil
		require (script_name)
	else								-- для файлов *.script
		local saved_data
		if is_function(_G[script_name]._reboot_collect) then
			saved_data = _G[script_name]._reboot_collect()
		end
		_G[script_name] = nil
		 if _G[script_name] then
			if saved_data and is_function(_G[script_name]._reboot_restore) then
				_G[script_name]._reboot_restore(saved_data)
			end
		 end
	end
end

-- prof_spy
local function call_spy()
	local ps = {store = {},global_timer = time_global()}
	function ps:start(name)
		if not self.store[name] then
			self.store[name] = {timer = profile_timer(),count = 0,time = 0,min_time = 0,max_time = 0,prev_ptime = 0}
		end
		local st = self.store[name]
		if not st.working then
			st.working = true
			st.timer:start()
		end
	end

	function ps:finish(name)
		local st = self.store[name]
	--	assert(st,"spy finish %s",name)
		if not st.working then
			return
		end
		st.timer:stop()
		st.working = false
		local ptime = st.timer:time()
		local rtime = ptime-st.prev_ptime
		if rtime == 0 then
			return
		end
		st.prev_ptime = ptime
		st.time = st.time+rtime
		st.count = st.count+1
		st.min_time = (st.min_time == 0 or rtime < st.min_time) and rtime or st.min_time
		st.max_time = rtime > st.max_time and rtime or st.max_time
	end

	function ps:get_stat(name)
		local function p(n)
			return n >= 100 and math.floor(n) or n
		end
		if name then
			local st = self.store[name]
			if not st then
				return "nil"
			end
			local global_time = time_global()-self.global_timer
			local exec_ms_in_sec = st.time/global_time
			local str = string.format("[%s]: cnt=[%s] time=[%s] min=[%s] max=[%s] prd=[%s] ret=[%s] ms/sec=[%s]",name,st.count,p(st.time),p(st.min_time),p(st.max_time),p(global_time/st.count),p(st.time/st.count),exec_ms_in_sec)
			return str
		else
			local tt = {}
			for name,st in pairs(self.store) do
				local global_time = time_global()-self.global_timer
				local exec_ms_in_sec = st.time/global_time
				local str = string.format("[%s]: cnt=[%s] time=[%s] min=[%s] max=[%s] prd=[%s] ret=[%s] ms/sec=[%s]",name,st.count,p(st.time),p(st.min_time),p(st.max_time),p(global_time/st.count),p(st.time/st.count),exec_ms_in_sec)
				table.insert(tt,str)
			end
			return tt
		end
	end

	function ps:print_stat(name)
		if name then
			log1(self:get_stat(name))
			return
		end
		print("==============================================================")
		print(string.format("print_stat: level %s",actor and level.name() or '-'))
		for i,s in ipairs(self:get_stat()) do
			print(s)
			s = '\n'..s
			log1(s)
		end
		print("==============================================================")
	end

	return ps
end

debug.prof_spy = setmetatable({}, {__call = call_spy})
debug.g_spy = call_spy()

--####################  N I L  ###################################################################################
--// Karlan->Team: все же добавлю немного черной магии
local nil_functions = {
-- is_table = function() return false end, --// need set mt
-- is_boolean = function() return false end, --// need set mt
is_number = function() return false end,
is_string = function() return false end,
-- is_nil = function() return true end, --// need set mt
}
debug.setmetatable  ( nil, 
    {   __index     = nil_functions, --function() end,
--[[        __newindex  = function() end,
        __call      = function() end,
        __unm       = function() end,
        __sub       = function(a) return a end,
        __mul       = function(a) return a end,
        __mod       = function(a) return a end,
        __pow       = function(a) return a end,
        __concat    = function(a, b) return a or b end,
        __add       = function(a, b) return a or b end,
        __lt        = function(a, b) return b end,]]
    }
)

--####################  U S E R D A T A  #########################################################################
--// В игре сравнение двух юзердат не работает. Код ниже исправляет это
--// Rulix: а вот херить имеющийся оператор не надо
local userdata_mt = debug.getmetatable(CUIStatic())

function userdata_mt.__eq (a,b)
	if a.__eq and a.__eq == b.__eq then
		return a:__eq(b)
	end
    return rawequal(a,b)
end

--####################  F F I  ###################################################################################
if not LJ2 and not ffi then return end --// Karlan: если шамански не прикручено, то нафиг все

local cache = setmetatable({}, {__mode = 'v'})

local inline_comment = '^%s*//[^\n]*\n()'
local multi_line_comment = '^%s*/%*.-*/%s*()'
local enumpat = '^(%s*([%w_][%a_]*)%s*(=?)%s*([x%x]*)%s*())'

function enum(defs)
  local cached = cache[defs]
  if cached then return cached end


  local N = 0
  local pos = 1
  local len = #defs
  local res = {}
  local coma = false

  while true do
    if pos == len + 1 then break end
    if pos > len + 1 then error("LARGER: "..pos.." "..len) end

    local p = defs:match(inline_comment, pos) or defs:match(multi_line_comment, pos)

    if not p then
      if coma then
        p = defs:match('^%s*,%s*()', pos)
        if not p then error "malformed enum: coma expected" end
        coma = false
      else
        local chunk, name, eq, value
        chunk, name, eq, value, p = defs:match(enumpat, pos)
        if not p then error "malformed enum definition" end

        if value ~= "" then
          assert(value:find'^%-?%d+$' or value:find'0x%x+', "badly formed number "..value.." in enum")
          N = tonumber(value)
        end

        local i = N
        N = N + 1

        if eq == "" and value == "" or eq == "=" and value ~= "" then
          res[#res+1] = "static const int "..name.." = "..i..";"
        else
          error("badly formed enum: " .. chunk)
        end
        coma = true
      end
    end

    pos = p
  end

  res = ffi.new("struct{ \n"..table.concat(res, "\n").."\n}")
  cache[defs] = res
  return res
end

local definepat = '^(#define[ \t]+([%w_][%a_]*)[ \t]+([x%x]+)[ \t]*(\n?)())'

function define(defs)
  local cached = cache[defs]
  if cached then return cached end

  local pos = defs:match('^%s*\n()') or 1
  local len = #defs
  local res = {}

  while true do
    if pos == len + 1 then break end
    if pos > len + 1 then error("LARGER: "..pos.." "..len) end

    local chunk, name, value, lf, p = defs:match(definepat, pos)
    p = p or defs:match(inline_comment, pos) or defs:match(multi_line_comment, pos)
    if chunk then
      if lf ~= '\n' and p ~= len + 1 then
        error("end of line expected after: "..chunk)
      end
      assert(value:find'^%-?%d+$' or value:find'0x%x+', "badly formed number "..value.." in enum")

      res[#res+1] = "static const int "..name.." = "..value..";"
    elseif not p then
      p = defs:match('^[ \t]+()', pos)
      assert(p, "malformed #define")
    end
    pos = p
  end
  res = ffi.new("struct{ \n"..table.concat(res, "\n").."\n}")
  cache[defs] = res
  return res
end
