- Suchst du nach ausführlichen Dokumentationen zu Lua? Schau dir das Lua-Referenzhandbuch auf MediaWiki.org an!
Scribunto ist eine Erweiterung, mit der Skriptsprachen in MediaWiki verwendet werden können. Ohne Scribunto müssten die Bearbeiter versuchen, Wikitext als Skriptsprache zu verwenden, was zu schlechter Performance und unlesbarem Code führt.
Scribunto verwendet Lua, eine schnelle und leistungsstarke Sprache, die häufig als eingebettete Sprache in Spielen wie Garry's Mod und World of Warcraft zu finden ist.
Bevor du mit Lua in MediaWiki anfängst, ist es am besten, wenn du ein Grundverständnis von Lua selbst hast. Das offizielle Handbuch in Lua programmieren ist eine gute Quelle, um Lua zu lernen, wenn du bereits etwas Programmiererfahrung hast (die Kenntnis einer Sprache wie JavaScript ist sehr hilfreich, da es viele Ähnlichkeiten gibt), ansonsten können die Tutorials auf lua-users.org und die Wikipedia-Hilfeseite Lua für Anfänger (englisch) hilfreich sein.
Dieser Leitfaden behandelt die allgemeine Verwendung von Scribunto, nicht die Verwendung von Lua selbst. Alle Beispiele werden unter Modul:Beispiel und Vorlage:Scribunto Beispiel (und deren Unterseiten) gespeichert. Verwende das Lua-Referenzhandbuch für die detaillierte Verwendung aller Funktionen.
Bevor wir anfangen[]
Hinweis: der Boilerplate-Code (
und local p = {}
) wird in den Beispielen nicht wiederholt, nachdem sie einmal verwendet wurden, es sei denn es ist relevant.
return p
Der Anfang[]
Scribunto speichert Skripte im Modul-Namensraum. Diese Module können dann auf den Wikiseiten dann mit der {{#invoke:}}
Parserfunktion verwendet werden (als „Aufrufen“ des Moduls bezeichnet). Skripte können nicht direkt in Wikitext geschrieben werden, und es wird empfohlen, alle Module von einer Vorlage aus aufzurufen, anstatt die {{#invoke:}}
-Parserfunktion auf einer Seite zu verwenden, um die Unordnung im Wikitext zu reduzieren.
Scribunto-Skripte, die aufgerufen werden sollen, müssen eine Tabelle zurückgeben. Diese Tabelle muss einige Funktionen enthalten, auf die in {{#invoke:}}
-Anweisungen Bezug genommen werden kann. Diese Funktionen müssen eine Wikitext-Zeichenkette zurückgeben, die tatsächlich auf der Seite ausgegeben wird. Schau dir dieses einfache Skript an:
local p = {}
p.helloWorld = function()
return 'Hello, world!'
end
return p
Welches aufgerufen wird duch {{#invoke: Beipiel | helloWorld }}
und das ausgibt: Hello, world!
Dieses Skript erstellt (und gibt später aus) eine Tabelle, die p
genannt wird und die Funktion helloWorld
zur Tabelle hinzufügt, welche dann den Text Hello, world!
auf der Wikiseite anzeigt.
Argumente erhalten[]
Dies wäre jedoch nicht sehr nützlich, wenn man dem Skript keine Argumente übergeben könnte, wie es bei Vorlagen der Fall ist. Scribunto speichert die Argumente in einem „Parser-Frame“. Dieser Rahmen ist im Grunde eine Tabelle, die einige nützliche Funktionen im Zusammenhang mit dem Wikitext-Parser enthält, sowie die Tabelle mit den Argumenten. Er ist als erster Parameter der Funktion im Skript verfügbar und kann auch mit der Funktion mw.getCurrentFrame()
abgerufen werden.
Direkte Argumente[]
Direkte Argumente oder „normale“ Argumente sind diejenigen, die in der Parserfunktion {{#invoke:}}
gesetzt werden. Hier ist ein Beispiel, das Argumente verwendet:
p.helloName = function( f )
local args = f.args
return 'Hello, ' .. args.name .. '!'
end
Welches aufgerufen wird durch: {{#invoke: Beispiel | helloName | name = John Doe }}
: Hello, John Doe!
Oder in einer Vorlage wie: {{#invoke: Beispiel | helloName | name = {{{name|}}} }}
: Hello, John Doe!
Dieses Skript weist der f
-Variable das Frame zu, erhält dann die Argumente aus der Tabelle vom Frame und weist diese der args
-Variable zu, und gibt schlussendlich den Textinhalt im name
-Argument aus, mit Hello,
und !
darum umschlossen. Nummerierte oder anonyme Argumente (z. B. {{{1}}}
) sind auch als args[1]
verfügbar.
Die Argumente sind immer Zeichenketten. Wie bei Vorlagen werden bei benannten und nummerierten Argumenten Leerzeichen abgeschnitten, bei anonymen Argumenten hingegen nicht; und Argumente, die angegeben werden, aber keinen Wert haben, sind leere Zeichenketten und nicht nil
.
Parent-Argument[]
Bei Vorlagen, die eine Untervorlage verwenden, ist es üblich, die von der übergeordneten Vorlage erhaltenen Argumente an die Untervorlage weiterzugeben.
{{Vorlage|{{{1}}}|arg1={{{arg1|}}}|arg2={{{arg2|}}}}}
Code wie dieser kann ziemlich unübersichtlich werden und zu Leistungsproblemen führen, da alle Argumente verarbeitet werden, unabhängig davon, ob die Vorlage sie verwendet.
Scribunto bietet eine Möglichkeit, auf diese „Parent-Argumente“ direkt zuzugreifen, ohne sie alle manuell durchreichen zu müssen. Dies ist sehr nützlich, da du fast immer ein Skript aus einer Vorlage heraus aufrufen wirst, und es erlaubt dir eine unendliche Anzahl möglicher Argumente, etwas, das mit traditionellen Vorlagen nicht möglich war.
Um auf die Argumente der übergeordneten Vorlage zuzugreifen, benötigst du den übergeordneten Frame. Die Verwendung der Funktion f:getParent()
für den aktuellen Frame gibt den Frame der übergeordneten Vorlage zurück, der dann auf die gleiche Weise wie der aktuelle Frame verwendet werden kann.
Das Konzept eines „übergeordneten Frames“ ist anfangs vielleicht schwer zu verstehen, daher hier das vorherige Beispiel, in dem es verwendet wird:
p.parentHello = function( f )
local args = f:getParent().args
return 'Hello, ' .. args.name .. '!'
end
Jetzt können wir diese Funktion nicht mehr direkt aufrufen, da wir die Argumente des aktuellen Frames nicht mehr lesen. Stattdessen fügen wir die {{#invoke:}}
-Parserfunktion in eine Vorlage ein und verwenden sie von dort aus. Beachte das Fehlen von Vorlagenargumenten, die mitgegeben werden.
{{#invoke: Beispiel | parentHello }}
, {{Scribunto Beispiel/Parent hello|name=John Doe}}
: Hello, John Doe!
Es funktioniert genau wie im vorherigen Beispiel, obwohl das Argument name nicht manuell an die Parserfunktion {{#invoke:}}
übergeben wird.
Für ein einzelnes Argument wie dieses stellt es keine große Verbesserung dar. Aber denke an Vorlagen, bei denen viele Argumente übergeben werden, wie z. B. eine Navbox. Typischerweise haben diese Vorlagen eine Grenze für die Anzahl der Zeilen, die sie unterstützen, weil dies die Anzahl der Argumente ist, die manuell festgelegt wurden. Die direkte Verwendung des übergeordneten Rahmens würde eine Navbox ohne Zeilenbegrenzung ermöglichen, und es müsste nicht jede Zeile bis zu ihrer Begrenzung überprüft werden, um zu sehen, ob eine davon einen Wert hat. Dies ist nicht nur schneller, sondern führt auch zu viel besserem und weniger repetitivem Code.
Unterstützung von beiden[]
Die Verwendung von direkten und übergeordneten Argumenten für verschiedene Zwecke ist recht einfach:
p.makeConfigGreeting = function( f )
local args = f.args
local parentArgs = f:getParent().args
return args.greeting .. ', ' .. parentArgs.name .. '!'
end
Die direkten Argumente werden als „Konfiguration“ für die Art der Begrüßung verwendet, und die übergeordneten Argumente werden verwendet, um festzulegen, wer begrüßt wird.
{{#invoke: Beispiel | makeConfigGreeting | greeting = Hello }}
, {{Scribunto Beispiel/Config hello|name=John Doe}}
: Hello, John Doe!
{{#invoke: Beispiel | makeConfigGreeting | greeting = G'day }}
, {{Scribunto Beispiel/Config g'day|name=John Doe}}
: G'day, John Doe!
Hier gibt es zwei Vorlagen, die dasselbe Modul aufrufen und dessen direkte Argumente verwenden, um es so zu konfigurieren, dass unterschiedliche Begrüßungen verwendet werden. Dann werden die Vorlagen wie üblich transkludiert und die übergeordneten Argumente der Vorlagen werden für den Namen der zu begrüßenden Person verwendet.
Damit ein Modul direkte Argumente oder übergeordnete Argumente verwenden kann, musst du nur prüfen, ob eine der beiden Tabellen irgendwelche Werte enthält:
p.makeFlexableGreeting = function( f )
local args = f.args
local parentArgs = f:getParent().args
for _ in pairs( parentArgs ) do
args = parentArgs
break
end
return args.greeting .. ', ' .. args.name .. '!'
end
Dieses Modul holt sowohl die direkten Argumente als auch die übergeordneten Argumente und startet dann eine Schleife über die übergeordneten Argumente. Wenn die Tabelle der übergeordneten Argumente leer ist, wird der Code innerhalb der Schleife nicht ausgeführt, und die direkten Argumente bleiben der Variablen args
zugewiesen. Andernfalls wird die args
-Variable wieder der übergeordneten args-Tabelle zugewiesen, und die Schleife wird beendet, da die Kenntnis der Existenz eines Wertes ausreicht.
Um ein Modul in die Lage zu versetzen, direkte Argumente und übergeordnete Argumente zu verwenden, könnte man einfach etwas wie folgt tun:
p.makeFlexableConfigGreeting = function( f )
local args = f.args
local parentArgs = f:getParent().args
local greeting = parentArgs.greeting or args.greeting
local name = parentArgs.name or args.name
return greeting .. ', ' .. name .. '!'
end
Für ein einfaches Beispiel wie dieses ist das in Ordnung. Bei Modulen mit vielen Argumenten wird dies jedoch unübersichtlich. Der richtige Weg, um es zu tun ist, um über beide Tabellen zu wiederholen und verschmelzen sie in einem:
p.makeMergedGreeting = function( f )
local directArgs = f.args
local parentArgs = f:getParent().args
local args = {}
for _, argType in ipairs{ directArgs, parentArgs } do
for key, val in pairs( argType ) do
args[key] = val
end
end
return args.greeting .. ', ' .. args.name .. '!'
end
Dieses Modul durchläuft beide arg-Tabellen, führt sie zusammen und überschreibt die direkten Argumente mit den übergeordneten Argumenten. Dies ist nützlich für komplexere Konfigurationen, bei denen eine Vorlage Standardkonfigurationseinstellungen festlegt, die dann bei Bedarf überschrieben werden können, wenn die Vorlage transkludiert wird.
Bei beiden Beispielen könnte es zu Problemen kommen, wenn „leere“ übergeordnete Argumente die direkten Argumente überschreiben, da Lua leere Zeichenketten als „wahrheitsgemäße“ Werte betrachtet. Dies könnte behoben werden, indem die Leerzeichen aus den Werten entfernt werden (mw.text.trim()
) und dann geprüft wird, ob sie gleich einer leeren Zeichenkette sind, bevor der Wert gesetzt wird.
Verwendung von Wikitext[]
Skripte können Wikitext ausgeben, genau wie gewöhnliche Vorlagen, allerdings wird nur die letzte Expansionsstufe des Parsers auf diesen Wikitext angewendet. Das bedeutet, dass Vorlagen, Parserfunktionen, Erweiterungs-Tags und alles andere, was Wikitext ausgeben kann, nicht verarbeitet wird, wenn es in der Ausgabe verwendet wird. Um diese Funktionen richtig nutzen zu können, stellt Scribunto einige Funktionen zur Verfügung, um sie zu ihrem endgültigen Wikitext zu erweitern. Andere Dinge, die auf den ersten Blick nicht funktionieren sollten, wie Variablen ({{PAGENAME}}
) und Verhaltensschalter (__NOTOC__
), funktionieren, weil sie keinen Wikitext ausgeben und daher keine zusätzliche Verarbeitung erfordern.
f:preprocess()
[]
Dies ist die grundlegendste Vorverarbeitungsfunktion. Sie führt die Vorverarbeitungsstufe des Parsers manuell an dem Text aus, den du ihr gibst. Theoretisch kannst du dies auf jeden Wikitext anwenden, bevor du ihn zurückgibst, um (fast) zu garantieren, dass alle Wikitexte funktionieren. (Beachte, dass es einfachen Wikitext nicht in HTML umwandelt, wie '''Textformatierung'''
oder [[Links]]
). Die Verwendung dieser Funktion wird nicht empfohlen, nicht nur wegen der Leistungseinbußen, die durch die unnötige Ausführung des Parsers entstehen, und der Tatsache, dass es sich um vollständigen Wikitext handelt und daher den gleichen Einschränkungen unterliegt wie vollständiger Wikitext, sondern auch, weil es sich um einen ziemlich brachialen Ansatz handelt. Du wirst wahrscheinlich immer eine der spezielleren Funktionen unten verwenden wollen.
f:expandTemplate()
[]
Diese Funktion ist schneller und weniger fehleranfällig als die manuelle Konstruktion einer Wikitext-Transkludierung zur Verwendung in der obigen Funktion. Du bist wahrscheinlich mit Vorlagen wie {{!}}
vertraut, die es erlauben, dass spezielle Wikitext-Zeichen vom Präprozessor ignoriert werden. Die Funktion f:expandTemplate()
unterliegt diesen Beschränkungen nicht. Etwas wie dies würde gut funktionieren:
f:expandTemplate{ title = 'Example', args = { 'unnamed value 1', 'kittens are cute', named_arg_1 = 'Pipe characters? | No problem! =)' }}
Dies ist gleichbedeutend mit dem Folgenden, du kannst unbenannte Argumente so oder so ausschreiben:
f:expandTemplate{ title = 'Example', args = { [1] = 'unnamed value 1', [2] = 'kittens are cute', named_arg_1 = 'Pipe characters? | No problem! =)' }}
Bei einer normalen Vorlagen-Transkludierung musst du dies hingegen tun:
{{Beispiel|arg1=Pipe-Buchstaben? {{!}} Kein Umgehen nötig! {{=}}(}}
Wie sein Wikitext-Äquivalent kann es Seiten in jedem Namensraum einschließen (oder im Hauptnamensraum, indem der Titel mit :
beginnt).
f:callParserFunction()
[]
Das Gleiche wie die vorherige Funktion, aber diese ist für Parserfunktionen. Benutze sie nicht, um Parserfunktionen aufzurufen, für die es ein Lua-Äquivalent gibt, wie {{urlencode:}}
(mw.uri.encode()
). Das Lua-Äquivalent wird immer schneller und zuverlässiger sein.
f:extensionTag()
[]
Diese Funktion ist für Erweiterungs-Tags wie <nowiki/>
gedacht (aber verwende sie nicht dafür, verwende mw.text.nowiki()
). Dies ist sozusagen ein Alias für f:callParserFunction()
mit der Parserfunktion {{#tag}}
und dem Tag-Inhalt, der den Argumenten vorangestellt wird.
Modulare Module[]
Module können mit der Funktion equire()
in anderen Modulen verwendet werden. Alle globalen Variablen im erforderlichen Modul sind global verfügbar, und der Rückgabewert des erforderlichen Moduls wird von require
zurückgegeben.
Hier ist ein einfaches Beispielmodul, das benötigt wird; beachte die Verwendung von globalen Variablen.
name = 'John Doe'
constructHello = function( person )
return 'Hello, ' .. person .. '!'
end
</source>
Now to require it:
<source lang="lua">
p.acquireGlobals = function( f )
require( 'Modul:Beispiel/AcquireGlobals' )
return constructHello( name )
end
{{#invoke: Beispiel | acquireGlobals }}
: Hello, John Doe!
Da das erforderliche Modul keine Funktionstabelle zurückgibt, kann es nicht direkt aufgerufen werden, was es weniger nützlich und auch schwieriger zu debuggen macht. Es wird empfohlen, lokale Variablen zu verwenden und die Variable zurückzugeben, auf die die erforderlichen Skripte zugreifen sollen, da dies flexibler ist und die Fehlersuche erleichtert. Die Formatierung von erforderlichen Skripten in ähnlicher Weise wie bei aufgerufenen Modulen (Rückgabe einer Tabelle mit Funktionen und vielleicht anderen Werten) ist sogar noch besser, da es relativ einfach ist, das Skript so anzupassen, dass es erforderlich und aufrufbar ist.
Dieses Skript entspricht eher dem typischen Aufrufstil:
local p = {}
p.name = 'John Doe'
p.constructHello = function( person )
return 'Hello, ' .. person .. '!'
end
return p
p.requiredHello = function( f )
local helloModule = require( 'Modul:Beispiel/Hello' )
local name = helloModule.name
return helloModule.constructHello( name )
end
{{#invoke: Beispiel | requiredHello }}
: Hello, John Doe!
Hier ist eine einfache Möglichkeit, ein Modul einzurichten, das erforderlich ist oder von Vorlagen aufgerufen werden kann:
local p = {}
p.constructHello = function( f )
local args = f
if f == mw.getCurrentFrame() then
args = f:getParent().args
else
f = mw.getCurrentFrame()
end
return 'Hello, ' .. args.name .. '!'
end
return p
Zu Beginn wird alles, was f
enthält, unter args gespeichert. Dann wird geprüft, ob f
ein Frame ist; ist dies der Fall, wird das Modul aufgerufen, und daher werden die übergeordneten Argumente des Frames abgerufen und in args
gespeichert. Andernfalls wird das Modul von einem anderen Modul angefordert, sodass f
die args
enthält, weshalb es zu Beginn args
zugewiesen wurde. Es wird dann wieder dem aktuellen Frame zugewiesen, sodass es für seine nützlichen Funktionen verwendet werden kann, als ob es aufgerufen worden wäre. Wenn das Modul keine der Funktionen des Rahmens verwendet, kannst du die Neuzuweisung überspringen.
Dies könnte dann von einer Vorlage auf die übliche Weise oder von einem Modul wie diesem aufgerufen werden:
p.requiredInvoke = function( f )
local helloModule = require( 'Modul:Beispiel/FlexableHello' )
return helloModule.constructHello{ name = 'John Doe' }
end
{{#invoke: Beispiel | requiredInvoke }}
: Hello, John Doe!
Beachte, wie das Modul eine Wertetabelle direkt an die Funktion des Moduls übergibt, das es benötigt.
Große Datentabellen laden[]
Wahrscheinlich wirst du irgendwann eine große Tabelle mit Daten haben wollen, auf die verschiedene Skripte verweisen. Diese große Tabelle, die immer dieselbe sein wird, hunderte Male für einzelne Skripte auf einer einzigen Seite zu verarbeiten, ist eine Verschwendung von Zeit und Speicher. Scribunto bietet genau für diesen Zweck die Funktion mw.loadData()
. Diese Funktion ist ähnlich wie require()
, nur dass das Modul, das sie benötigt, eine Tabelle zurückgeben muss, die nur statische Daten enthält. Keine Funktionen, keine Metatabellen. Alle nachfolgenden Aufrufe von mw.loadData()
für dasselbe Modul in beliebigen Skripten auf der Seite geben die bereits geparste Tabelle zurück.
Die Tabelle ist schreibgeschützt und der Versuch, die Tabelle mit tomw.clone()
zu klonen, wird dazu führen, dass die Schreibschutz-Kennzeichnung ebenfalls geklont wird; derzeit führt dies jedoch zu einem Lua-Fehler, der besagt, dass die Datentabelle schreibgeschützt ist. Wenn du die Tabelle ändern musst, solltest du entweder zu
require()
zurückkehren oder die Tabelle wiederholen und eine neue Tabelle aus ihren Werten erstellen.
return {
name = 'John Doe'
-- ... and lots of other data
}
p.bigData = function( f )
local data = mw.loadData( 'Modul:Beispiel/Data' )
return 'Hello, ' .. data.name .. '!'
end
{{#invoke: Beispiel | bigData }}
: Hello, John Doe!
Fehlersuche[]
Scribunto verhindert das Speichern eines Moduls mit „Syntax“-Fehlern, es sei denn, die Option "Speichern von Code mit Fehlern erlauben" ist aktiviert (bei Modulen, die noch in Arbeit sind). Andere Fehler können jedoch gespeichert werden und müssen daher behoben werden.
Skriptfehler[]
Skriptfehler: Die Funktion „das wird nicht funktionieren“ ist nicht vorhanden.
Wenn ein Modul in einer Seite defekt ist, wird ein Skriptfehler wie der oben abgebildete ausgegeben. Wenn du darauf klickst, wird die Fehlermeldung angezeigt und, wenn es zumindest teilweise ausgeführt werden konnte, ein Backtrace zu der Stelle, an der der Fehler aufgetreten ist. Die Seite wird außerdem in die Seiten mit Skriptfehlern-Kategorie aufgenommen.
Debug-Konsole[]
Es ist wahrscheinlich am besten, Fehler zu finden, bevor du deine Änderungen speicherst, und zu diesem Zweck gibt es eine Debug-Konsole unterhalb des Bearbeitungsbereichs. Die Verwendung der Konsole ist anfangs nicht sehr einfach, da sie sich eher so verhält, als wäre das Modul angefordert worden, als dass es aufgerufen worden wäre.
Für ein Modul, das keine Frame-Funktionen verwendet, ist die Verwendung der Debug-Konsole relativ einfach.
Allerdings ist ein Modul, das nicht für den Gebrauch eingerichtet ist und Frame-Funktionen verwendet, etwas schwieriger zu debuggen.
Ein Modul, das nur übergeordnete Argumente akzeptiert, muss zuerst bearbeitet werden, um das Anfordern richtig zu unterstützen; du könntest auch einfach die ursprüngliche Logik vorübergehend auskommentieren und die Argumente dem Rahmen zuweisen.
Alle Aufrufe der Funktionen mw.log()
und mw.logObject()
im Modul werden ebenfalls in der Konsole angezeigt, und zwar vor dem Rückgabewert, sofern das Modul keine Fehler aufweist.
Bekannte Probleme und Lösungen[]
Unerwartetes Verhalten[]
mw.clone() schlägt bei Tabellen fehl, die mit mw.loadData() erstellt wurden[]
Datentabellen, die von mw.loadData()
zurückgegeben werden, können nicht geändert werden, und dies führt zu einem Fehler. Seltsamerweise wird derselbe Fehler durch die Verwendung von mw.clone()
verursacht, wahrscheinlich aufgrund der „Magie“ von der Metatabelle, die von loadData()
-Ergebnissen verwendet wird, um sie unveränderbar zu machen. Die Lösung könnte sein, stattdessen das Datenmodul require()
zu verwenden, aber das wird nicht empfohlen; mw.loadData()
ist etwas effizienter, da das Modul nur einmal im Prozess der Seitengenerierung geladen wird, anstatt einmal bei jedem #invoke
. Wenn du eine andere Version eines Datenmoduls benötigst (z. B. mit einem anderen Layout), kannst du ein separates Datenmodul erstellen und mit loadData()
laden, das loadData()
auf dem ersten Modul aufruft, die abgeleitete Tabelle manuell erstellt und sie später zurückgibt. (Datenmodule können beliebigen Code enthalten, die Anforderungen beziehen sich nur darauf, was sie zurückgeben).
Die Reihenfolge der Wiederholung in „pairs()“ ist nicht festgelegt[]
Wenn deine Vorlage eine variable Anzahl von nicht-numerischen Argumenten wie „image1“, „image2“, „image3“, „image4“, „image5“ usw. annehmen soll, wirst du feststellen, dass die Verwendung von pairs()
, um über die Tabelle der Argumente zu iterieren, diese Argumente nicht in der Reihenfolge ihrer numerischen Suffixe liefert. Außerdem gibt es keine Möglichkeit, auf die Reihenfolge zuzugreifen, in der die Argumente im Wikitext angegeben wurden.
Das Problem mit String-Parametern mit Zahlensuffix kann umgangen werden, indem man eigenen Code schreibt. Zum Beispiel können diese als Bibliotheksfunktionen verwendet werden:
--[=[
Extracts the sequence of values from "someTable" that have a string prefix "prefix"
and an integral suffix. If "allowNumberless" is specified as true, the value without
a prefix is treated like the one with prefix 1.
Examples:
```
local t = { arg1 = "one", arg2 = "two", arg3 = "three" }
local t2 = p.extractPrefixedSequence(t, "arg")
-- t2 = { "one", "two", "three" }
```
If "allowNumberless" is true, and "someTable" has both a value with no suffix and
one with suffix 1, the function may return either value.
```
local t = { arg = "one", arg1 = "also one", arg2 = "two", arg3 = "three" }
local t2 = p.extractPrefixedSequence(t, "arg", true)
-- depending on the implementation, t2[1] may be "one" or "also one"
```
The produced sequence may have holes, which can cause problems with the # operator
and ipairs.
```
local t = { arg1 = "one", arg2 = "two", arg4 = "suddenly four" }
local t2 = p.extractPrefixedSequence(t, "arg")
-- t2 = { "one", "two", nil, "suddenly four" }
```
]=]
function p.extractPrefixedSequence(someTable, prefix, allowNumberless)
local values = {}
if allowNumberless and someTable[prefix] then
values[1] = someTable[prefix]
end
local prefixPattern = "^" .. prefix .. "(%d+)$";
for key, value in pairs(someTable) do
local index = tonumber(key:match(prefixPattern))
if index and index > 0 then
values[index] = value
end
end
return values
end
--[=[
Acts like ipairs for a sequence with a prefix.
This code:
```
for k, v in p.ipairsWithPrefix(someTable, prefix, allowNumberless) do
-- ...
end
```
should be the same as this code:
```
for k, v in ipairs(p.extractPrefixedSequence(someTable, prefix, allowNumberless)) do
-- ...
end
```
however, in the edge case when "allowNumberless" is true, and both a numberless and
a 1-suffixed value are present, the functions may return different values for index 1.
]=]
function p.ipairsWithPrefix(someTable, prefix, allowNumberless)
local i = 0
return function()
i = i + 1
local value = someTable[prefix .. tostring(i)]
if i == 1 and allowNumberless and someTable[prefix] ~= nil then
value = someTable[prefix]
end
if value ~= nil then
return i, value
end
end
end
Der Operator # verhält sich bei Sequenzen mit „Löchern“ unspezifisch[]
Wenn eine Sequenztabelle „Löcher“ hat - Null-Werte vor Nicht-Null-Werten, - kann der #
-Operator jedes „Loch“ wie das Ende der Sequenz behandeln. Dies kann zum Beispiel ein Problem sein, wenn du ihn auf die Argumenttabelle für ein Modul anwendest, nachdem du eine Bibliothek zur Verarbeitung von Argumenten benutzt hast. Solche Bibliotheken ersetzen oft substanzlose (leere oder nur mit Leerzeichen versehene) Argumente durch Nullwerte.
Die ipairs
-Iteratorfunktion wird stattdessen bei der ersten Lücke anhalten.
Wenn du #
oder ipairs auf eine Sequenz anwenden musst, die Löcher enthalten kann, solltest du stattdessen table.maxn()
in deinem Code verwenden. Diese Funktion gibt den höchsten numerischen Index zurück, der mit einem Wert ungleich Null verknüpft ist. Bei Bedarf kannst du die Lücken auch mit Platzhalterwerten füllen, nachdem du mit der Funktion maxn
über die Tabelle wiederholt hast, um den Endindex zu erhalten.
local leaky_table = {1, 2, 3, 4, 5, nil, 7, 8, 9, nil, 11, 12}
for index = 1, table.maxn(leaky_table) do
if leaky_table[index] == nil then
leaky_table[index] = index -- insert whatever you need, of course
end
end
-- leaky_table is now {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
Äußerst technische Details der Implementierung: Der Grund für das nicht spezifizierte Verhalten von #
ist wahrscheinlich, dass Lua-Tabellen intern einen „Sequenz“-Teil für Zahlen-Arrays und einen „Hash“-Teil für alle anderen Daten haben. Die Verwendung nur des „Sequenz“-Teils für alle numerischen Indizes würde dazu führen, dass dünn besetzte Arrays (insbesondere sehr dünn besetzte Arrays mit sehr großen Indizes) speicherineffizient sind, weshalb nach einem „Loch“ nachfolgende Elemente mit Zahlenindex stattdessen im „Hash“-Teil gespeichert werden. So werden beispielsweise durch { [1000000000] = "not Google Chrome" }
keine Gigabytes an leerem Speicherplatz zugewiesen.
Lua-Zahlen sind doppelpräzise Fließkommazahlen und runden sehr große ganze Zahlen stillschweigend ab[]
In der von Scribunto verwendeten Version von Lua sind alle Zahlen nicht vom Typ Integral, sondern vom Typ „doppelt genaue Gleitkommazahl“ (oder einfach double
). Dieser Typ hat seine Tücken, die für Lua-Anfänger nicht offensichtlich sind. Zum Beispiel können ab einem bestimmten Punkt nicht mehr alle Ganzzahlen als Double gespeichert werden. Bei Paschas ist die kleinste nicht darstellbare positive Ganzzahl 9.007.199.254.740.992. Solche Ganzzahlen werden gerundet, so dass der Ausdruck tonumber("9007199254740993") == tonumber("9007199254740992")
true
ist. Normalerweise ist dies kein Problem, aber wenn man beispielsweise viele oder große ganze Zahlen miteinander verknüpft und versucht, das Ergebnis als Zahl zu interpretieren, kann dies zu unerwartetem Verhalten führen.
Es gibt kaum eine beste Lösung, aber es kann helfen, sich des Problems bewusst zu sein und seine Module so zu schreiben, dass sie es nicht verursachen. Während Lua 5.3 64-Bit-Ganzzahlen unterstützt und die Möglichkeit bietet, etwas mehr Ganzzahlen exakt darzustellen, basiert Scribunto auf 5.1 und wird wahrscheinlich nicht einmal alle 5.2-Funktionen vollständig implementieren.
binser sollte nicht zum Speichern von Zahlen verwendet werden[]
Wenn dein Wiki die „binser“-Bibliothek aktiviert hat, solltest du nicht versuchen, callParserFunction
mit Variablen #vardefine
zu verwenden, während du ihr die Ausgabe der Serialisierungsfunktion von binser übergibst. „Bin“ in „binser“ steht für „binär“, und die Serialisierung kann zu beliebigen Bytes führen. Gleichzeitig erwartet #vardefine
aber auch richtigen Text. Ein Benutzer hatte zum Beispiel den Fehler, dass die Serialisierung von 5
mit binser zu einem Leerzeichen führte, das callParserFunction
, wie in typischem Wikitext, in einen leeren Wert umwandelte. Die Serialisierung der Zahl 256
führte zu Bytes, die nicht gültig UTF-8 sind, und der gespeicherte Text endete in zwei Ersatzzeichen.
Als Lösung kann man mw.text.jsonEncode
und mw.text.jsonDecode
für den Umgang mit Variablen verwenden. Dies funktioniert nicht nur mit Tabellen, sondern auch mit anderen Typen (z. B. Zahlen und Strings), und der dekodierte Wert sollte bereits den richtigen Typ haben.
Leistung[]
mw.text.split ist sehr langsam[]
In einigen Tests war diese Funktion am Ende über 60 Mal langsamer als eine individuelle Neuimplementierung. Wenn möglich, verwende stattdessen die string
-Bibliothek, um deine Quellen mit Lua-Mustern aufzuteilen.
Zum Beispiel:
--[[
Splits a string `str` using a pattern `pattern` and returns a sequence
table with the parts.
Much faster than `mw.text.split`, which it is inspired by, but does
not work if the pattern needs to be Unicode-aware.
]]
function split(str, pattern)
local out = {}
local i = 1
local split_start, split_end = string.find(str, pattern, i)
while split_start do
out[#out+1] = string.sub(str, i, split_start - 1)
i = split_end + 1
split_start, split_end = string.find(str, pattern, i)
end
out[#out+1] = string.sub(str, i)
return out
end
mw.text.trim ist langsam[]
Ähnlich wie die obige Funktion ist auch die trim
-Funktion recht langsam. Außerdem ist sie recht speicherintensiv und kann bei sehr großen Zeichenketten zu Scribunto-Fehlern führen. Wenn du keine Nicht-ASCII-Whitespace-Funktionen brauchst, kannst du stattdessen etwas wie das hier verwenden:
local function trim( s )
return (s:gsub( '^[\t\r\n\f ]+', '' ):gsub( '[\t\r\n\f ]+$', '' ))
end
Beachte, dass diese Implementierung absichtlich zwei gsub
-Aufrufe anstelle eines Aufrufs mit einer Erfassungsgruppe verwendet. Tests haben gezeigt, dass die Version mit einer Erfassungsgruppe mehr Speicher verbrauchen würde und etwas weniger leistungsfähig wäre. Die zusätzliche Schicht von Klammern um den Ausdruck ist relevant, weil gsub
zwei Werte zurückgibt und die Funktion nur einen zurückgeben sollte.
Lua-Fehler: Interner Fehler: Der Interpreter hat sich mit dem Signal „24“ beendet.[]
Dieses Problem tritt auf, wenn der Interpreter zu lange läuft und abgebrochen wird. Dies kann zum Beispiel durch eine Endlosschleife in deinem Modul verursacht werden.
Dieser Fehler kann auch auftreten, wenn lange oder ressourcenintensive Module ausgeführt werden und der Server, auf dem sich das Wiki befindet, stark belastet ist. Es ist möglich, diesen Fehler zu vermeiden, indem man das Modul so verbessert, dass es schneller läuft.
Siehe auch[]
Mehr Hilfe[]
- Ein Stichwortverzeichnis zu verschiedensten Hilfeseiten findest du auf Hilfe:Index.
- Einen geordneten Einstieg zur Hilfe gibt es auf der Seite Hilfe:Übersicht.
- Fragen an andere Benutzer kannst du in den Diskussionen des Community-Wikis stellen.
- Anmerkungen zu dieser Hilfeseite kannst du auf der dazugehörigen Diskussionsseite hinterlassen.
- Um Fehler zu melden, kontaktiere bitte einen Ansprechpartner oder nutze das entsprechende Kontaktformular.