Community-Wiki

Achtung: Fandom stellt ab dem 31.12.2023 bis auf Weiteres den Support für die deutsche Sprache ein. Nach diesem Datum müssen alle Anfragen im englischen Community Central oder über das Kontaktformular gestellt werden.

MEHR ERFAHREN

Community-Wiki
Community-Wiki
Advertisement
Community-Wiki

Nuvola apps kservices
Dies ist eine Standard-Erweiterung!
Standard-Erweiterungen sind generell in allen Communitys aktiviert.
Suchst du nach ausführlichen Dokumentationen zu Lua? Schau dir MediaWiki-2020-small-icon 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 MediaWiki-2020-small-icon das Lua-Referenzhandbuch für die detaillierte Verwendung aller Funktionen.

Bevor wir anfangen[]

Hinweis: der Boilerplate-Code (local p = {} und return p) wird in den Beispielen nicht wiederholt, nachdem sie einmal verwendet wurden, es sei denn es ist relevant.

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 MediaWiki-2020-small-icon 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[]

Advertisement