Википедия:Использование Lua (Fntnhy;nx&Nvhkl,[kfguny Lua)
Это информационная страница. Она описывает сложившуюся среди редакторов практику по одному или нескольким аспектам норм и обычаев Википедии. Она не является правилом или руководством русской Википедии, и она не проходила полноценное обсуждение в сообществе. |
Это руководство о том, как написать или преобразовать шаблон с использованием расширения MediaWiki Scribunto. Scribunto — расширение, разработанное Тимом Старлингом и Виктором Васильевым и позволяющее встраивать скриптовые языки в MediaWiki. В настоящее время поддерживается только скриптовый язык Lua. Здесь даётся общий обзор использования Lua, а также ссылки на дополнительную информацию в различных местах.
Шаблоны, использующие Lua, состоят из двух частей: самого шаблона и одного или нескольких модулей в пространстве имён Модуль:
, которые содержат программы, запускающиеся на серверах Викимедиа при генерации вики-текста, в который шаблон раскрывается. Шаблон вызывает функцию в модуле, используя функцию парсера по имени {{#invoke:}}
.
Идея использования Lua в том, чтобы повысить производительность обработки шаблонов. При этом исчезает надобность в программировании шаблонов функциями парсера, такими как {{#if:}}
, {{#ifeq:}}
, {{#switch:}}
и {{#expr:}}
. Вместо этого всё делается в модуле, на языке, который был действительно разработан как язык программирования, а не система шаблонов, к которой были со временем прикручены различные расширения, чтобы попытаться получить из неё язык программирования. Использование Lua также делает ненужным раскрытие шаблонов в последующие шаблоны, что может привести к упиранию в предел глубины раскрытия. Использующий Lua в полной мере шаблон никогда не нуждается во включении других шаблонов[a].
Lua
[править код]Lua — это язык, на котором написаны модули. В отличие от системы функций парсера и шаблонов, Lua был действительно предназначен не только быть полноценным языком программирования, но и быть языком программирования, подходящим для так называемых встроенных скриптов. Модули в MediaWiki — пример встроенных скриптов. Есть несколько встроенных скриптовых языков, которые могли бы быть использованы, в том числе REXX и tcl; и, на самом деле, первоначальная цель Scribunto заключалась в возможности выбора таких языков. На данный момент, однако, доступен только Lua.
Исходная спецификация API — стандартной библиотеки функций Lua и переменных, которые должны быть доступны в модулях — дана в MW:Extension:Scribunto/API specification. Однако, даже это не соответствует действительности. То, чем вы на самом деле располагаете, документировано в MW:Extension:Scribunto/Lua reference manual/ru, которая является версией 1-го издания руководства по Lua, урезанной и изменённой Тимом Старлингом, для соответствия реальности вики-проектов. И это также справочник, а не учебник.
Вещи, с которыми вы чаще всего будете иметь дело, разрабатывая шаблоны с использованием Lua — это таблицы, строки, числа, логические значения, nil
, if … then … else … end
, while … do … end
, for … in … do … end
(порождённый for
), for … do … end
(числовой for
), repeat … until
, function … end
, local
, return
, break
, выражения и различные операторы (включая #
, ..
, арифметические операторы +
, -
, *
, /
, ^
и %
), и глобальные таблицы (библиотеки) string
, math
и mw
. Обратите внимание, оператор сравнения «не равно» в lua выглядит как ~=
.
Вызов модуля
[править код]
Вызова модуля производится функцией парсера {{#invoke: }}
. Вот, например, типичный код вызывающего модуль шаблона {{ref label}}:
<includeonly>{{#invoke:citation|reflabel}}</includeonly><noinclude>{{documentation}}</noinclude>
В нём мы видим вызов функции reflabel
из модуля citation
без непосредственно передаваемых в коде шаблона в модуль параметров, которые бы шли после. Не следует помещать в вызов модуля другие шаблоны, функции парсера или переменные в качестве его аргументов, это лишь замедлит вызов модуля.
Вызванный из шаблона модуль будет иметь доступ к объекту frame
на один уровень вверх (родительскому), в частности — будет иметь данные о имени страницы, где был вызван данный шаблон, о том, с какими параметрами шаблон был вызван, то есть в такой ситуации не нужен проброс переменных (указание в шаблоне при вызове модуля {{{1|}}}
в качестве аргументов). В случае, если страница, на которой производится представление результата работы шаблонов и модулей пользователю, разворачивает первый шаблон, а он в свою очередь разворачивает второй шаблон, который вызывает модуль — в такой ситуации родительским объектом frame
для модуля будет объект, связанный с первым шаблоном, и без дополнительных действий получить параметры вызова первого шаблона на странице из модуля не получится, см. #Получение аргументов шаблона.
Основы построения модуля
[править код]Общая структура
[править код]Рассмотрим гипотетический модуль, модуль:Population
. (См. en:Module:Population clocks — аналогичный реальный, но более сложный). Он может быть оформлен одним из двух способов:
Именованная локальная таблица
[править код]local p = {}
function p.India(frame)
return "1,21,01,93,422 people at (nominally) 2011-03-01 00:00:00 +0530"
end
return p
Безымянная таблица, генерируемая на лету
[править код]return {
India = function(frame)
return "1,21,01,93,422 people at (nominally) 2011-03-01 00:00:00 +0530"
end
}
Выполнение
[править код]Исполнение модуля в {{#invoke:}}
на самом деле двухступенчатое:
- Модуль загружается и весь скрипт выполняется. При этом загружаются дополнительные модули, нужные данному (через функцию
require()
), создаются (вызываемые) функции, которые модуль предоставит шаблону, и возвращается таблица из таковых. - Функция, названная в
{{#invoke:}}
, выбирается из таблицы, построенной на первом этапе, и вызывается с аргументами шаблона и аргументами{{#invoke:}}
(подробнее об этом ниже).
Первый скрипт на Lua выполняет вполне ясным образом первый этап. Он создаёт локальную переменную с именем p
в строке 1, при инициализации таблицы; создаёт и добавляет функцию к ней (строки 3—5), сообщая функции имя India
в таблице, названной p
(function p.India
— то же самое, что и p["India"] = function
[b]); а потом, в последней строке 7 скрипта, таблица возвращается. Функции, которые не будут привязаны к возвращаемой модулем таблице при вызове модуля из шаблона, называются локальными функциями вашего модуля — другие модули не могут вызывать их, также они недоступны для вызова со страниц Википедии, однако данные подпрограммы можно использовать в других функциях вашего модуля, которые определены ниже (в области видимости локальных функций). Возвращаемую локальную переменную необязательно называть p
(её необязательно вообще как-то называть, в некоторых случаях удобно начинать свой модуль сразу с return
, как можно увидеть на примере второго модуля). Ей можно дать любое допустимое в Lua имя переменной[c], это имя, которое можно будет использовать для тестирования скрипта в отладочной консоли редактора модулей.
Второй Lua-скрипт делает то же самое, но более «идиоматически». Вместо создания имени переменной в таблице он создает анонимную таблицу на лету, внутри выражения return
, которое является единственным (из выполняемых на первом этапе) оператором в сценарии. India = function(frame) … end
в строках 2—4 создаёт (также анонимную) функцию и вставляет её в таблицу под названием India
. Чтобы дополнить такой скрипт бо́льшим числом (вызываемых) функций, их добавляют в качестве дополнительных полей в таблице. (Невызываемые локальные функции могут, опять же, добавляться перед самим оператором return
.)
В обоих случаях код шаблона будет {{#invoke:Population|India}}
, чтобы вызвать функцию под названием India
из модуля Модуль:Population
. Также обратите внимание, что выражение function
определяет функцию как объект, который будет вызываться. Оно не объявляет её, к чему вы, вероятно, привыкли в других языках программирования; функция не выполняется, пока её не вызвали.
Можно, конечно, делать и более сложные вещи. Например, можно объявить другие локальные переменные в дополнение к p
, чтобы хранить таблицы данных (такие как списки языков или названия стран), используемые модулем. Но это базовая структура модуля. Вы делаете таблицу, наполненную чем-то, и возвращаете её.
Получение аргументов шаблона
[править код]Обычная функция Lua может принимать (практически) произвольное число аргументов. Посмотрите на эту функцию из en:Module:Wikitext, которую можно вызвать с числом аргументов от одного до трёх:
function z.oxfordlist(args,separator,ampersand)
Функции, вызываемые {{#invoke:}}
— особый случай. Они рассчитывают получить ровно один аргумент — таблицу, которая называется фрейм (потому её параметр в списке параметров функции обычно называют frame
). Она называется фрейм, потому что, увы, разработчики решили назвать её так для своего удобства. Это имя дано в честь внутренней структуры в коде самого MediaWiki, которую он как бы представляет[d].
Этот фрейм содержит (под)таблицу под названием args
. Также он поддерживает средства для доступа к своему родительскому фрейму (опять же названному в честь кое-чего из MediaWiki). В родительском фрейме также есть (под)таблица, также названная args
.
- Параметры в дочернем фрейме — то есть значении параметра
frame
функции — это параметры, переданные{{#invoke:}}
в тексте шаблона. Так, например, если Вы написали{{#invoke:Population|India|a|b|class="popdata"}}
в коде шаблона, то подтаблица параметров дочернего фрейма будет (в форме Lua){ "a", "b", class="popdata" }
. - Параметры в родительском фрейме — это параметры, переданные в шаблон, когда он был включён. Так, например, если пользователь шаблона пишет
{{население Индии|с|d|язык=хинди}}
, то подтаблица параметров из родительского фрейма будет (в форме Lua){"c", "d", ["язык"] = "хинди"}
.
Можно использовать удобную идиому программирования, чтобы сделать это немного легче — иметь локальные переменные с именами, допустим, config
и args
в функции для ссылки на эти две таблицы параметров. Пример из Module:Citation:
-- This is used by template {{efn}}.
function z.efn(frame)
local pframe = frame:getParent()
local config = frame.args -- параметры, переданные САМИМ ШАБЛОНОМ в тексте САМОГО ШАБЛОНА
local args = pframe.args -- параметры, переданные ШАБЛОНУ в тексте ВЫЗВАВШЕЙ СТРАНИЦЫ
Всё в config
, таким образом, — это параметры, которые Вы указали в вашем шаблоне; они доступны с помощью кода типа config[1]
и config.class
. Они сообщат функции из модуля её «настройки» (например, имя класса CSS, которое может варьироваться в зависимости от того, в каком шаблоне она используется).
А всё в args
— это параметры, которые укажет пользователь шаблона там, куда его включит, которые доступны через код типа args[1]
и args["язык"]
. Это будут обычные аргументы шаблона, описанные на его /doc
-подстранице.
Для обоих наборов параметров имя и значение параметра будет точно таким же, как в тексте, за исключением начальных и конечных пробельных символов. На Ваш код повлияет применение параметров включения из вызова, не являющихся корректными именами переменных в Lua. Тогда вместо формы с точкой (args.language
) понадобится обращение в форме с квадратными скобками (args["язык"]
).
Именованные параметры перечислены в таблицах args
, разумеется, по названию. Нумерованные параметры (как в результате явного 1=
, так и позиционные) индексируются в них числом, а не строкой. args[1]
не тождественно args["1"]
, и последнее никак не установить из викитекста.
Наконец, отметим, что модули Lua могут различать параметры, которые были использованы в викитексте и просто установлены в пустую строку, и параметры, которых нет вообще. Последние не существуют в таблице args
, и обращение к ним даст nil
— а первые существуют и равны пустой строке ""
(логически положительному значению).
Ошибки
[править код]Давайте скажем одну вещь с самого начала: Ошибка выполнения — это гиперссылка. Вы можете поместить на неё указатель мыши и нажать.
Если у вас в браузере включён JavaScript, при этом появится всплывающее окно с подробностями ошибки, стеком вызовов и даже гиперссылками на место в коде, где произошла ошибка в соответствующем модуле.
Вы можете сгенерировать ошибку функцией error()
.
Советы и хитрости
[править код]Таблицы аргументов — «особенные»
[править код]По причинам, лежащими за рамками настоящего руководства[e], подтаблица args
фрейма не совсем похожа на обычную таблицу. Она поначалу пуста, и заполняется параметрами тогда и в той мере, в какой код к ним обращается[f]. (Собственные таблицы тоже можно сделать особенными, присоединив к ним метатаблицу; это также за рамками данного руководства).
Неприятный побочный эффект этого заключается в том, что некоторые нормальные операторы для таблиц в Lua не работают с args
. Оператор длины #
и функции из библиотеки table
работают только в стандартных таблицах, и на особенных таблицах args
не получаются. Тем не менее, pairs()
и ipairs()
будут работать, так как код для того, чтобы сделать их использование возможным, был добавлен разработчиками.
Копируйте содержимое таблиц в локальные переменные
[править код]Имя в Lua — это либо доступ к локальной переменной, либо поиск по таблице[2]. math.floor
, например, — это поиск ключа "floor"
в глобальной таблице math
. Поиск по таблице выполняется медленнее, чем поиск локальной переменной. Поиск в таких таблицах, как frame.args
, при их «особенности» — намного медленнее.
Функция в Lua может содержать до 250 локальных переменных[3]. Так что пользуйтесь ими свободно:
- Если вызываете
math.floor
много раз, скопируйте её в локальную переменную и используйте через неё:[3]
local floor = math.floor
local a = floor((14 - date.mon) / 12)
local y = date.year + 4800 - a
local m = date.mon + 12 * a - 3
return date.day + floor((153 * m + 2) / 5) + 365 * y + floor(y / 4) - floor(y / 100) + floor(y / 400) - 2432046
- Не используйте
args.xxx
снова и снова. Скопируйте его значение в локальную переменную и используйте через неё:
local Tab = args.tab
(Даже сама переменная args
является способом избежать повторные поиски args
в таблице frame
.)
- Приём для альтернативных названий одного и того же параметра. Если параметр шаблона может быть передан под разными названиями — например, в прописной и строчной форме или на русском и на английском — можно использовать оператор Lua
or
, чтобы выбрать наиболее приоритетное имя, которое фактически поставлено:
local Title = args["энциклопедия"] or args["Энциклопедия"] or args.encyclopaedia or args.encyclopedia or args.dictionary
local ISBN = args.isbn13 or args.isbn or args.ISBN
Это работает по двум причинам:
nil
иfalse
эквивалентны в логических операциях.- В Lua оператор
or
имеет, что называется, семантику «сокращения»: если левый операнд имеет значение, отличное отnil
илиfalse
, он даже не вычисляет значение правого операнда. (Таким образом, хотя пример может на первый взгляд выглядеть как четыре поиска, в самых частых случаях, когда в шаблоне используется|энциклопедия=
, поиск на самом деле производится один).
- Приём по умолчанию — пустая строка. Иногда полезно, чтобы незаданный параметр шаблона давал
nil
. В других случаях, однако, Вы можете хотеть, чтобы недостающие параметры были эквивалентны пустым строкам. Для этого достаточно просто поставить в конце выраженияor ""
:
local ID = args.id or args.ID or args[1] or ""
Не раскрывайте шаблоны, даже несмотря на то, что можете
[править код]Если локальные переменные дёшевы, а поиск по таблице — дорог, то раскрытие шаблона намного выше Вашего потолка по цене.
Бегите от frame:preprocess()
, как от чумы. Раскрытие вложенных шаблонов через препроцессор MediaWiki — это то, от чего мы, в конце-то концов, и пытаемся уйти. Большинство вещей, которые Вы бы могли сделать так, делаются проще, быстрее и удобнее простыми функциями Lua.
Аналогичным образом, не храните в шаблоне то, что фактически является записью в базе данных. Чтение это будет вложенным вызовом парсера с сопутствующими запросами к базе данных, и всё это затем, чтобы сопоставить одной строке другую. Просто сделайте модуль с табличными данными, как Модуль:Languages/data.
Отладка
[править код]В Scribunto доступна отладка — вы можете ввести имя целевой страницы, на которой разворачивается использующий данный модуль шаблон, нажать кнопку «Предварительный просмотр страницы с использованием этого шаблона или модуля», и увидеть результат работы того кода, который вы только что правили и который пока не сохранён.
Также вы можете работать с функциями lua и возвращаемой вашим модулем таблицей из консоли. Так как в качестве аргумента функции, которые вызываются со страниц Википедии, должны получать объекты типа frame
, то и из консоли их следует вызывать таким же образом. Это можно сделать, например, строкой =p.main(mw.getCurrentFrame():newChild{title="smth",args={"arg1","arg2",["namedArg1"]="1",["namedArg2"]="2"}})
, где main
— это название функции, которая прикреплена к возвращаемой модулем таблице, mw.getCurrentFrame():newChild{ }
— встроенные функции, которые создают объект frame
с заданными параметрами, который будет передан функции для выполнения, smth
— обязательная заглушка, в некоторых случаях вам необходимо будет получать доступ к названию страницы, откуда был вызван модуль и вам потребуется осмысленное значение этого параметра при отладке, далее идут неименованные аргументы, аналогичные записи {{#invoke:MyModule|main|arg1|arg2|namedArg1=1|namedArg2=2}}
— обратите внимание что цифры передаются как текст. Подробнее см. Проект:Технические работы/Модули#ЧаВо.
Примечания
[править код]- ↑ Они могут быть нужны до той поры, когда весь предоставляемый функционал станет доступен модулям Scribunto, чтобы включать волшебные слова (см. #Советы и хитрости). Волшебные слова, тем не менее, — не шаблоны.
- ↑ Изобретатели языка называют это «синтаксическим сахаром»[1].
- ↑ en:Module:Citation, например, использует
z
. - ↑ В MediaWiki как таковом фреймов больше двух.
- ↑ Если хотите знать, прочтите о том, как MediaWiki — отчасти из-за бремени, наложенного на него старой системой шаблонов-условно-включающих-шаблоны — выполняет ленивое вычисление параметров.
- ↑ Поэтому не удивляйтесь, увидев в стеке вызовов обращение к какому-то другому модулю там, где, как вы думали, вы всего лишь ссылаетесь на параметр шаблона. Это произошло потому, что тот параметр включал другой шаблон, использующий Scribunto.
Ссылки
[править код]- Краткое введение в синтаксис Lua (англ.)
- en:Help:Lua for beginners#Issues with the current implementation
- phab:T18700 Таблица викитекстом может создавать перед собой пустую строку. Обход бага: <nowiki/> до invoke, или таблица html тегами, или таблица обернутая div.
- mw:Lua/Tutorial
Перекрёстные ссылки
[править код]- ↑ Ierusalimschy, de Figueiredo, Celes, 2011, §DATA.
- ↑ Ierusalimschy, de Figueiredo, §EVAL AND ENVIRONMENTS.
- ↑ 1 2 Ierusalimschy, 2008, p. 17.
Источники
[править код]- Ierusalimschy, Roberto; de Figueiredo, Luiz Henrique; Celes, Waldemar (12 May 2011). "Passing a Language through the Eye of a Needle". Queue. 9 (5). Association for Computing Machinery. ACM 1542-7730/11/0500.
{{cite journal}}
: Недопустимый|ref=harv
(справка) - Ierusalimschy, Roberto. Lua Performance Tips // Lua Programming Gems. — Lua.org, December 2008. — ISBN 978-85-903798-4-3.
Дальнейшее чтение
[править код]- Документация по Lua на mediawiki.
- Lua 5.1 Reference Manual. — Lua.org, August 2006. — ISBN 85-903798-3-3.
- Programming in Lua : []. — Second. — Lua.org, March 2006. — ISBN 9788590379829.
- Ierusalimschy, Roberto. Programming in Lua : []. — First. — Lua.org, December 2003. — ISBN 85-903798-1-7.
- Jung, Kurt. Beginning Lua Programming. — Wrox, February 2007. — ISBN 978-0-470-06917-2.