Как добавить в Notepad++ парсер для Function List
Для начала нужно установить Notepad++ БЕЗ инсталлятора (из архива).
Если Notepad++ поставить при помощи инсталлятора, то все, что описано ниже, работать НЕ будет.
В файле functionList.xml в папке Notepad++ в секцию <associationMap> добавить строку
<association id= «lua_function» langID=»23″ />
ВАЖНО: если изменить строку «lua_function» на что-либо другое (например, на «lua_parse»), то работать НЕ будет.
В конец секции <parsers> добавить парсер, взятый отсюда (первая выдача в гугле по запросу «notepad++ lua function list»):
<!-- Basic lua parser for functionList.xml in Notepad++ 6.5.3 -->
<!-- See http://notepad-plus-plus.org/features/function-list.html -->
<parser id="lua_function" displayName="Lua" commentExpr="--.*?$">
<!-- Basic lua table view, nested lua table not supported -->
<classRange
mainExpr="[.\w]+[\s]*=[\s]*\{"
openSymbole="\{"
closeSymbole="\}"
displayMode="node">
<className>
<nameExpr expr="[.\w]+"/>
</className>
<function
mainExpr="[.\w]+[\s]*=[\s]*['"]?[\w]+['"]?">
<functionName>
<funcNameExpr expr=".*"/>
</functionName>
</function>
</classRange>
<!-- Basic lua functions support -->
<function
mainExpr="(function[\s]+[.\w]+(:[\w]+)?)|([.\w]+[\s]*=[\s]*function)"
displayMode="$className->$functionName">
<functionName>
<nameExpr expr="((?<=function)[\s]+[.:\w]+)|(([.\w]+)(?=([\s]*=[\s]*function)))"/>
</functionName>
<className>
<nameExpr expr="[.\w]+(?=:)"/>
</className>
</function>
</parser>
Профит!
Заполнение таблиц данными
Нередко встает задача заполнить таблицу данными.
Допустим, нам нужно создать список параметров.
Каждый параметр — это таблица вида:
{ name = 'Name', value = '', type = 'string', }
Соответственно, список параметров будет выглядеть как-то так:
{ { name = 'Name', value = '', type = 'string', }, { name = 'Order', value = 1, type = 'number', }, { name = 'File', value = 'default.lua', type = 'string', }, }
Если параметров много, то для удобства заполнения я создаю функцию:
local function createParameter(name, value, type) return { name = name, value = value, type = type, } end
Список параметров начинает выглядеть так:
{ createParameter('Name', '', 'string'), createParameter('Order', 1, 'number'), createParameter('File', 'default.lua', 'string'), }
Однако, если параметров в функцию передается много, то при добавлении новой записи часто трудно понять, какой параметр за какое поле отвечает. Чтобы облегчить себе задачу, я в таблицу данных добавляю функции заполнения параметров:
local function Property(name) return { name = name, Value = function(self, value) self.value = value return self end, Type = function(self, type) self.type = type return self end, } end
Теперь заполнение таблицы параметров выглядит так:
{ Property('Name') :Value('') :Type('string'), Property('Order') :Value(1) :Type('number'), Property('File') :Value('default.lua') :Type('string'), }
Теперь каждый параметр подписан, а если есть опциональные поля, то их можно пропустить, а не писать такое:
createParameter('Name', '', nil, nil, nil, 'string'),
PS
Не помню, сам я это придумал, или где-то украл 🙂
Lua 5.3 Руководство
Добавил руководство по Lua 5.3, переведенное Николаем Ведерниковым.
К сожалению, слетела оригинальная верстка. Кому надо, оригиналы лежат тут:
Книжка по Lua на русском языке
Вышла книга Роберту Иерузалимски: Программирование на языке Lua
Подробнее: http://www.labirint.ru/books/433606/
Подробнее: http://www.ozon.ru/context/detail/id/26893654/
Подробнее: http://www.combook.ru/product/10780471/
Судя по описанию, это перевод третьего издания «Programming in Lua».
Избавление от циклических зависимостей модулей
Итак, у вас есть два модуля, каждый из которых использует другой.
ModuleA.lua:
local ModuleB = require('ModuleB') return { fun1 = function() print('~~~ModuleA fun1') end, fun2 = function() print('~~~ModuleA calls ModuleB.fun1()', ModuleB().fun1()) end, }
ModuleB.lua:
local ModuleA = require('ModuleA') return { fun1 = function() print('~~~ModuleB fun1') end, fun2 = function() print('~~~ModuleB calls ModuleA.fun1()', ModuleA().fun1()) end, }
В главной программе вы наивно пытаетесь их включить:
local ModuleA = require('ModuleA') local ModuleB = require('ModuleB')
и получаете ошибку:
can't load module_test.lua: [string "ModuleB.lua"]:1: loop or previous error loading module 'ModuleA'
Как быть? Шаблон Медиатор приходит на помощь!
ModuleMediator.lua:
return { getModuleA = function() return require('ModuleA') end, getModuleB = function() return require('ModuleB') end, }
Исправляем файлы модулей.
ModuleA.lua:
local ModuleMediator = require('ModuleMediator') return { fun1 = function() print('~~~ModuleA fun1') end, fun2 = function() print('~~~ModuleA calls ModuleB.fun1()', ModuleMediator.getModuleB().fun1()) end, }
ModuleB.lua:
local ModuleMediator = require('ModuleMediator') return { fun1 = function() print('~~~ModuleB fun1') end, fun2 = function() print('~~~ModuleB calls ModuleA.fun1()', ModuleMediator.getModuleA().fun1()) end, }
В главной программе все осталось по старому:
local ModuleA = require('ModuleA') local ModuleB = require('ModuleB') ModuleA.fun1() ModuleA.fun2() ModuleB.fun1() ModuleB.fun2()
Запускаем:
~~~ModuleA fun1
~~~ModuleB fun1
~~~ModuleA calls ModuleB.fun1()
~~~ModuleB fun1
~~~ModuleA fun1
~~~ModuleB calls ModuleA.fun1()
Работает!
Я там маленько ошибся, когда вызывал функцию другого модуля, которая ничего не возвращает, внутри функции print()
, но переписывать не хочется, извините.
Перехватчик для события удаления стейта.
Если Lua-библиотека, написанная на С, взаимодействует с несколькими Lua стейтами, то может потребоваться узнать о том, что какой-то из Lua стейтов удаляется. Сделать это можно так: при регистрации библиотеки в Lua стейте создаем безымянную userdata переменную, и у нее устанавливаем метатаблицу с методом __gc
. Поскольку при удалении стейта у всех объектов вызываются финализаторы, то будет вызвана и эта функция безымянного объекта.
int onDestroyLuaState(lua_State* L) { // делаем что-то полезное return 0; } const luaL_Reg libname[] = { // common functions {"Func1", func1}, {"Func2", func2}, ... {0, 0}, }; extern "C" __declspec(dllexport) int luaopen_libname(lua_State* L) { lua_newuserdata(L, 0); // dummy for GC lua_createtable(L, 0, 1); // metatable lua_pushcfunction(L, onDestroyLuaState); lua_setfield(L, -2, "__gc"); lua_setmetatable(L, -2); luaL_register(L, luaModuleName, libname); return 1; }
Новые и старые модули
Это перевод вот этой статьи.
Создание и использование модулей
Есть два способа создания модулей — старый(и не рекомендуемый) для 5.0 и ранних версий 5.1, и новый для 5.1 и 5.2. Кроме нового способа мы также рассмотрим старый, поскольку вам могут встретиться программы, в которых он используется.
Создадим файл примера с следующим содержанием:
local mymodule = {} function mymodule.foo() print("Hello World!") end return mymodule
Теперь, чтобы использовать этот модуль в интерактивном интерпретаторе, напишите:
> mymodule = require "mymodule"
> mymodule.foo()
Hello World!
В обычном файле скрипта рекомендуется сделать переменную mymodule
локальной:
local mymodule = require "mymodule" mymodule.foo()
Если мы сделаем переменную mymodule
локальной в интерактивном интерпретаторе, то на следующей строке она выйдет из области видимости и мы не сможем ей воспользоваться. Поэтому мы должны сделать переменную mymodule
глобальной.
Поскольку вы можете подключать один и тот же модуль в различных файлах, Lua кеширует модули в таблице package.loaded
. Чтобы показать, как работает кеширование, изменим, например, функцию foo
в mymodule.lua
чтобы она печатала "Hello Module!"
. Если мы продолжим сессию в интерпретаторе, то произойдет следующее:
> mymodule = require "mymodule"
> mymodule.foo()
Hello World!
Чтобы действительно перезагрузить модуль, нужно сначала удалить его из кеша:
> package.loaded.mymodule = nil
> mymodule = require "mymodule"
> mymodule.foo()
Hello Module!
Еще одна приятная вещь — это возможность именовать модули произвольным образом, поскольку модули это обычные таблицы, сохраненные в переменной. Если название mymodule.lua
покажется нам слишком длинным, мы можем его сократить:
> m = require "mymodule"
> m.foo()
Hello Module!
Другие способы создания модулей
Существуют различные способы создания модулей, и вы можете выбрать нужный вам в зависимости от задачи и ваших предпочтений.
Создать таблицу в начале скрипта и добавлять функции в нее:
local mymodule = {} local function private() print("in private function") end function mymodule.foo() print("Hello World!") end function mymodule.bar() private() mymodule.foo() -- need to prefix function call with module end return mymodule
Сделать все функции локальными и добавить их в таблицу в конце скрипта:
local function private() print("in private function") end local function foo() print("Hello World!") end local function bar() private() foo() -- do not prefix function call with module end return { foo = foo, bar = bar, }
Комбинация двух предыдущих способов:
local mymodule = {} local function private() print("in private function") end local function foo() print("Hello World!") end mymodule.foo = foo local function bar() private() foo() end mymodule.bar = bar return mymodule
Вы даже можете изменить окружение блока кода(the chunk’s environment), чтобы сохранить в нем только необходимые глобальные переменные:
local print = print -- the new env will prevent you from seeing global variables local M = {} if setfenv then setfenv(1, M) -- for 5.1 else _ENV = M -- for 5.2 end local function private() print("in private function") end function foo() print("Hello World!") end function bar() private() foo() end return M
Или, если вы не хотите сохранять глобальные переменные:
local M = {} do local globaltbl = _G local newenv = setmetatable({}, { __index = function (t, k) local v = t[k] if v == nil then return globaltbl[k] end return v end, __newindex = M, }) if setfenv then setfenv(1, newenv) -- for 5.1 else _ENV = newenv -- for 5.2 end end local function private() print("in private function") end function foo() print("Hello World!") end function bar() private() foo() end return M
Обратите внимание, что в этом случае доступ к глобальным переменным и переменным модуля может стать медленнее, поскольку используется функция __index
. Пустая «прокси» таблица используется вместо таблицы _G(глобальное пространство имен) для того, чтобы не было возможности из модуля обращаться к глобальным переменным и не происходило следующее:
> require "mymodule"
> mymodule.foo()
Hello World!
> mymodule.print("example") -- unwanted __index metamethod
example
Старый способ создания модулей
В Lua 5.0 and 5.1 есть функция module
, которая используется таким образом:
mymodule.lua:
module("mymodule", package.seeall) function foo() -- create it as if it's a global function print("Hello World!") end
И модуль может использоваться так:
> require "mymodule"
> mymodule.foo()
Hello World!
Работает это так: создается новая таблица для модуля, сохраняется в глобальной переменной, имя которой задается первым параметром функции module
, затем эта таблица устанавливается окружением для кода модуля, так что если вы создаете в модуле глобальную переменную, то она сохраняется в таблице модуля.
Из-за этого вы внутри модуля не видите глобальных переменных(например, print
). Одно из решений — сохранить все необходимые глобальные переменные в локальных перед вызовом функции module
, но это может быть утомительно, и решением было добавить второй параметр в функцию module
, который должен быть функцией, в которую таблица модуля передается как параметр. package.seeall
устанавливает для модуля метатаблицу с функцией __index
, которая указывает на глобальное пространство имен, и благодаря этому модуль может обращаться к глобальным переменным. Проблема этого решения в том, что пользователь модуля может обращаться к глобальным переменным через модуль:
> require "mymodule"
> mymodule.foo()
Hello World!
> mymodule.print("example")
example
В лучшем случае это странно и неожиданно, и может быть дырой в безопасности(если вы используете модуль в защищенном(sandboxed) скрипте).
Причина, по которой не рекомендуется использовать функцию module
, кроме тех, что приведены выше, это то, что пользователь модуля получает глобальную переменную с именем модуля, а так же то, что в Lua 5.2 функция не может изменять окружение своего вызывающего(по крайней мере без помощи отладочной библиотеки), что делает невозможным реализацию функции module
.
Таблица package
Как было сказано ранее, Lua использует библиотеку package для управления модулями.
package.path
(для модулей написанных на Lua) и package.cpath
(для модулей написанных на C) это места, где Lua ищет модули.
> =package.path
./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua
> =package.cpath
./?.so;/usr/local/lib/lua/5.1/?.so;/usr/lib/x86_64-linux-gnu/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so
package.loaded
это таблица, где уже загруженные модули хранятся по именам. Как обсуждалось ранее, это работает как кеш, чтобы модули не загружались многократно, функция require
сначала ищет модуль в этой таблице, и, если ничего не найдено, пытается его загрузить.
package.preload
это таблица функций, связанных с именами модулей. Прежде чем искать в файловой системе, require
проверяет, не содержит ли package.preload
нужный ключ. Если функция найдена, то результат ее вызова и будет значением, которое возвратит require
.
Остальные поля не очень важны для обычного использования модулей, но если вас интересует, как работает система модулей, это детально описано в мануале здесь.
Загрузка модулей
Если вы решили часть модулей вынести в отдельную папку, например:
./mylib/
./mylib/Module1.lua
./mylib/Module2.lua
./mylib/Module3.lua
то обращаться к ним из других модулей можно так:
local module1 = require('mylib.Module1')
Только нужно помнить, что в файле с модулем нужно также указывать относительный путь.
Например, в файле ./mylib/Module1.lua нужно написать:
module('mylib.Module1')
Вот здесь http://lua-users.org/wiki/ModulesTutorial написано, как нужно оформлять модули «по-новому».
Мы в своем проекте пока используем функцию module()
. Возможно, вскоре мы от нее откажемся.
Итераторы
После того, как написал несколько раз такой код:
function(command, ...) ... local combos = command.combos if combos then local deviceCombos = combos[deviceName] if deviceCombos then for i, combo in ipairs(deviceCombos) do -- что-то сделать с combo ... end end end ... end
мне стало понятно, что нужно придумать что-нибудь поизящнее, чем эти двойные проверки. И тут я вспомнил про итераторы! Programming in Lua говорит нам, что итератором может быть функция, которая при каждом вызове возвращает следующий элемент коллекции. Поскольку deviceCombos это массив, а мне при переборе индекс элемента не нужен, то задача упрощается:
function commandCombos(command, deviceName) local pos = 0 local combos local deviceCombos if command then combos = command.combos if combos then deviceCombos = combos[deviceName] end end return function() if deviceCombos then pos = pos + 1 return deviceCombos[pos] end end end
Обратите внимание, что переменная pos — это upvalue.
Теперь проверим получившуюся функцию:
local commandNil = nil local commandNoCombos = {} local commandEmptyCombos = { combos = {}, } local command = { combos = { mouse = { {key = 'MOUSE1', action = 1}, {key = 'MOUSE2', action = 2}, }, keyboard = { {key = 'A', action = 11}, {key = 'B', action = 22}, }, } } for combo in commandCombos(commandNil) do assert(false) end for combo in commandCombos(commandNoCombos) do assert(false) end for combo in commandCombos(commandNoCombos) do assert(false) end for combo in commandCombos(commandEmptyCombos) do assert(false) end for combo in commandCombos(command, 'mouse') do print('~~~mouse', combo.key, combo.action) end for combo in commandCombos(command, 'keyboard') do print('~~~keyboard', combo.key, combo.action) end
На выходе ожидаемо получаем:
~~~mouse MOUSE1 1
~~~mouse MOUSE2 2
~~~keyboard A 11
~~~keyboard B 22
Скорость цикла for
В чужом коде увидел такое:
for i = 1, #data.values do local value = data.values[i] ... end
И тут же осудил внутри себя этого нерадивого разработчика «Экий ты, брат, ленивец! Нет бы написать «правильно»!»:
for i, value in ipairs(data.values) do ... end
И тут меня сомнение взяло: второй вариант мне лично кажется красивее и правильнее, но быстр ли он? И быстро был написан тест:
local data = { values = {} } for i = 1, 10000 do table.insert(data.values, i) end local function simpleForTest() local j local t = os.clock() for k = 1, 100 do for i = 1, #data.values do j = data.values[i] end end print('~~~#', os.clock() - t) end local function ipairsTest() local j local t = os.clock() for k = 1, 100 do for i, val in ipairs(data.values) do j = val end end print('~~~ipairs', os.clock() - t) end for i = 1, 10 do simpleForTest() ipairsTest() end
А результаты он давал такие:
~~~# 0.063
~~~ipairs 0.121
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.062
~~~ipairs 0.121
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.062
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
То есть «некрасивый» код работает в два раза быстрее «красивого». Вот так.
leave a comment