Interested Article - Использование Lua
- 2020-09-18
- 1
Это информационная страница.
Она описывает сложившуюся среди редакторов практику по одному или нескольким аспектам норм и обычаев Википедии. Она не является
правилом или руководством русской Википедии
, и она не проходила
полноценное обсуждение в сообществе
.
|
Это руководство о том, как написать или преобразовать шаблон с использованием расширения MediaWiki . Scribunto — расширение, разработанное и Виктором Васильевым и позволяющее встраивать скриптовые языки в MediaWiki. В настоящее время поддерживается только скриптовый язык Lua . Здесь даётся общий обзор использования Lua, а также ссылки на дополнительную информацию в различных местах.
Шаблоны, использующие Lua, состоят из двух частей: самого шаблона и одного или нескольких
модулей
в пространстве имён
Модуль:
, которые содержат программы, запускающиеся на
при генерации вики-текста, в который шаблон раскрывается. Шаблон вызывает функцию в модуле, используя
функцию парсера
по имени
{{#invoke:}}
.
Идея использования Lua в том, чтобы повысить производительность обработки шаблонов. При этом исчезает надобность
в программировании шаблонов функциями парсера
, такими как
{{#if:}}
,
{{#ifeq:}}
,
{{#switch:}}
и
{{#expr:}}
. Вместо этого всё делается в модуле, на языке, который был действительно разработан как язык программирования, а не система шаблонов, к которой были со временем прикручены различные расширения, чтобы попытаться получить из неё язык программирования. Использование Lua также делает ненужным раскрытие шаблонов в последующие шаблоны, что может привести к упиранию в
. Использующий Lua в полной мере шаблон никогда не нуждается во
включении
других шаблонов
.
Lua
Lua — это язык, на котором написаны модули. В отличие от системы функций парсера и шаблонов, Lua был действительно предназначен не только быть полноценным языком программирования, но и быть языком программирования, подходящим для так называемых встроенных скриптов . Модули в MediaWiki — пример встроенных скриптов. Есть несколько встроенных скриптовых языков, которые могли бы быть использованы, в том числе REXX и tcl ; и, на самом деле, первоначальная цель Scribunto заключалась в возможности выбора таких языков. На данный момент, однако, доступен только Lua.
Исходная спецификация API — стандартной библиотеки функций Lua и переменных, которые должны быть доступны в модулях — дана в . Однако, даже это не соответствует действительности. То, чем вы на самом деле располагаете, документировано в , которая является версией 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: }}
. Вот, например, типичный код вызывающего модуль шаблона {{
}}:
<includeonly>{{#invoke:citation|reflabel}}</includeonly><noinclude>{{documentation}}</noinclude>
В нём мы видим вызов функции
reflabel
из модуля
citation
без непосредственно передаваемых в коде шаблона в модуль параметров, которые бы шли после. Не следует помещать в вызов модуля другие шаблоны, функции парсера или
в качестве его аргументов, это лишь замедлит вызов модуля.
Вызванный из шаблона модуль будет иметь доступ к объекту
frame
на
один
уровень вверх (родительскому), в частности — будет иметь данные о имени страницы, где был вызван данный шаблон, о том, с какими параметрами шаблон был вызван, то есть в такой ситуации не нужен проброс переменных (указание в шаблоне при вызове модуля
{{{1|}}}
в качестве аргументов). В случае, если страница, на которой производится представление результата работы шаблонов и модулей пользователю, разворачивает первый шаблон, а он в свою очередь разворачивает второй шаблон, который вызывает модуль — в такой ситуации родительским объектом
frame
для модуля будет объект, связанный с первым шаблоном, и без дополнительных действий получить параметры вызова первого шаблона на странице из модуля не получится, см.
.
Основы построения модуля
Общая структура
Рассмотрим гипотетический модуль,
модуль:Population
. (См.
— аналогичный реальный, но более сложный). Он может быть оформлен одним из двух способов:
Именованная локальная таблица
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:}}
на самом деле двухступенчатое:
- Модуль загружается и весь скрипт выполняется. При этом загружаются дополнительные модули, нужные данному (через функцию ), создаются (вызываемые) функции, которые модуль предоставит шаблону, и возвращается таблица из таковых.
-
Функция, названная в
{{#invoke:}}
, выбирается из таблицы, построенной на первом этапе, и вызывается с аргументами шаблона и аргументами{{#invoke:}}
(подробнее об этом ).
Первый скрипт на Lua выполняет вполне ясным образом первый этап. Он создаёт
локальную переменную
с именем
p
в строке 1, при инициализации таблицы; создаёт и добавляет функцию к ней (строки 3—5), сообщая функции имя
India
в таблице, названной
p
(
function
p
.
India
— то же самое, что и
p
[
"India"
]
=
function
); а потом, в последней строке 7 скрипта, таблица возвращается. Функции, которые не будут привязаны к возвращаемой модулем таблице при вызове модуля из шаблона, называются локальными функциями вашего модуля — другие модули не могут вызывать их, также они недоступны для вызова со страниц Википедии, однако данные подпрограммы можно использовать в других функциях вашего модуля, которые определены ниже (в
области видимости
локальных функций). Возвращаемую локальную переменную необязательно называть
p
(её необязательно вообще как-то называть, в некоторых случаях удобно начинать свой модуль сразу с
return
, как можно увидеть на примере второго модуля). Ей можно дать любое допустимое в Lua имя переменной
, это имя, которое можно будет использовать для тестирования скрипта в отладочной консоли редактора модулей.
Второй Lua-скрипт делает то же самое, но более «идиоматически». Вместо создания имени переменной в таблице он создает анонимную таблицу на лету, внутри выражения
return
, которое является единственным (из выполняемых на первом этапе) оператором в сценарии.
India
=
function
(
frame
)
…
end
в строках 2—4 создаёт (также анонимную) функцию и вставляет её в таблицу под названием
India
. Чтобы дополнить такой скрипт бо́льшим числом (вызываемых) функций, их добавляют в качестве дополнительных полей в таблице. (Невызываемые
локальные
функции могут, опять же, добавляться
перед
самим оператором
return
.)
В обоих случаях код шаблона будет
{{#invoke:Population|India}}
, чтобы
вызвать
функцию под названием
India
из модуля
Модуль:Population
. Также обратите внимание, что выражение
function
определяет
функцию как объект, который будет вызываться. Оно не
объявляет
её, к чему вы, вероятно, привыкли в других языках программирования; функция не выполняется, пока её не
вызвали
.
Можно, конечно, делать и более сложные вещи. Например, можно объявить другие локальные переменные в дополнение к
p
, чтобы хранить таблицы данных (такие как списки языков или названия стран), используемые модулем. Но это
базовая
структура модуля. Вы делаете таблицу, наполненную чем-то, и возвращаете её.
Получение аргументов шаблона
Обычная функция Lua может принимать (практически) произвольное число аргументов. Посмотрите на эту функцию из , которую можно вызвать с числом аргументов от одного до трёх:
function z.oxfordlist(args,separator,ampersand)
Функции, вызываемые
{{#invoke:}}
— особый случай. Они рассчитывают получить ровно один аргумент — таблицу, которая называется
фрейм
(потому её параметр в списке параметров функции обычно называют
frame
). Она называется
фрейм
, потому что, увы, разработчики решили назвать её так для своего удобства. Это имя дано в честь внутренней структуры в коде самого MediaWiki, которую он
как бы
представляет
.
Этот фрейм содержит (под)таблицу под названием
args
. Также он поддерживает средства для доступа к своему
родительскому фрейму
(опять же названному в честь кое-чего из MediaWiki). В родительском фрейме
также
есть (под)таблица, также названная
args
.
-
Параметры в дочернем фрейме — то есть значении параметра
frame
функции — это параметры, переданные{{#invoke:}}
в тексте шаблона . Так, например, если Вы написали{{#invoke:Population|India|a|b|class="popdata"}}
в коде шаблона, то подтаблица параметров дочернего фрейма будет (в форме Lua){ "a" , "b" , class = "popdata" }
. -
Параметры в родительском фрейме — это параметры,
переданные в шаблон, когда он был включён
. Так, например, если пользователь шаблона пишет
{{население Индии|с|d|язык=хинди}}
, то подтаблица параметров из родительского фрейма будет (в форме Lua){ "c" , "d" , [ "язык" ] = "хинди" }
.
Можно использовать удобную идиому программирования, чтобы сделать это немного легче — иметь локальные переменные с именами, допустим,
config
и
args
в функции для ссылки на эти две таблицы параметров. Пример из
:
-- 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()
.
Советы и хитрости
Таблицы аргументов — «особенные»
По причинам, лежащими за рамками настоящего руководства
, подтаблица
args
фрейма не совсем похожа на обычную таблицу. Она поначалу пуста, и заполняется параметрами тогда и в той мере, в какой код к ним обращается
. (Собственные таблицы тоже можно сделать особенными, присоединив к ним
метатаблицу
; это также за рамками данного руководства).
Неприятный побочный эффект этого заключается в том, что некоторые нормальные операторы для таблиц в Lua не работают с
args
. Оператор длины
#
и функции из библиотеки
table
работают только в стандартных таблицах, и на особенных таблицах
args
не получаются. Тем не менее,
pairs()
и
ipairs()
будут работать, так как код для того, чтобы сделать их использование возможным, был добавлен разработчиками.
Копируйте содержимое таблиц в локальные переменные
Имя в Lua — это либо доступ к локальной переменной, либо поиск по таблице
.
math.floor
, например, — это поиск ключа
"floor"
в глобальной таблице
math
. Поиск по таблице выполняется медленнее, чем поиск локальной переменной. Поиск в таких таблицах, как
frame.args
, при их «
» —
намного
медленнее.
Функция в Lua может содержать до 250 локальных переменных . Так что пользуйтесь ими свободно:
-
Если вызываете
math.floor
много раз, скопируйте её в локальную переменную и используйте через неё:
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, чтобы включать (см. ). Волшебные слова, тем не менее, — не шаблоны.
- Изобретатели языка называют это « синтаксическим сахаром » .
-
, например, использует
z
. - В MediaWiki как таковом фреймов больше двух.
- Если хотите знать, прочтите о том, как MediaWiki — отчасти из-за бремени, наложенного на него старой системой шаблонов-условно-включающих-шаблоны — выполняет ленивое вычисление параметров.
- Поэтому не удивляйтесь, увидев в стеке вызовов обращение к какому-то другому модулю там, где, как вы думали, вы всего лишь ссылаетесь на параметр шаблона. Это произошло потому, что тот параметр включал другой шаблон, использующий Scribunto.
Ссылки
- (англ.)
- Таблица викитекстом может создавать перед собой пустую строку. Обход бага: <nowiki/> до invoke, или таблица html тегами, или таблица обернутая div.
Перекрёстные ссылки
- , §DATA.
- , §EVAL AND ENVIRONMENTS.
- ↑ , p. 17.
Источники
-
Ierusalimschy, Roberto; de Figueiredo, Luiz Henrique; Celes, Waldemar (12 May 2011).
.
Queue
. Association for Computing Machinery.
9
(5). ACM 1542-7730/11/0500.
{{ cite journal }}
: Недопустимый|ref=harv
( справка ) - Ierusalimschy, Roberto. // Lua Programming Gems. — Lua.org, December 2008. — ISBN 978-85-903798-4-3 .
Дальнейшее чтение
- . — Lua.org, August 2006. — ISBN 85-903798-3-3 .
- Programming in Lua : [] . — Second. — Lua.org, March 2006. — ISBN 9788590379829 .
- Ierusalimschy, Roberto. : [] . — First. — Lua.org, December 2003. — ISBN 85-903798-1-7 .
- Jung, Kurt. Beginning Lua Programming. — Wrox, February 2007. — ISBN 978-0-470-06917-2 .
- 2020-09-18
- 1