Interested Article - Использование Lua

Короткая ссылка-перенаправление

Это руководство о том, как написать или преобразовать шаблон с использованием расширения 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:}} на самом деле двухступенчатое:

  1. Модуль загружается и весь скрипт выполняется. При этом загружаются дополнительные модули, нужные данному (через функцию ), создаются (вызываемые) функции, которые модуль предоставит шаблону, и возвращается таблица из таковых.
  2. Функция, названная в {{#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}} — обратите внимание что цифры передаются как текст. Подробнее см. Проект:Технические работы/Модули#ЧаВо .

Примечания

  1. Они могут быть нужны до той поры, когда весь предоставляемый функционал станет доступен модулям Scribunto, чтобы включать (см. ). Волшебные слова, тем не менее, — не шаблоны.
  2. Изобретатели языка называют это « синтаксическим сахаром » .
  3. , например, использует z .
  4. В MediaWiki как таковом фреймов больше двух.
  5. Если хотите знать, прочтите о том, как MediaWiki — отчасти из-за бремени, наложенного на него старой системой шаблонов-условно-включающих-шаблоны — выполняет ленивое вычисление параметров.
  6. Поэтому не удивляйтесь, увидев в стеке вызовов обращение к какому-то другому модулю там, где, как вы думали, вы всего лишь ссылаетесь на параметр шаблона. Это произошло потому, что тот параметр включал другой шаблон, использующий Scribunto.

Ссылки

  • (англ.)
  • Таблица викитекстом может создавать перед собой пустую строку. Обход бага: <nowiki/> до invoke, или таблица html тегами, или таблица обернутая div.

Перекрёстные ссылки

  1. , §DATA.
  2. , §EVAL AND ENVIRONMENTS.
  3. , 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 .

Дальнейшее чтение

Источник —

Same as Использование Lua