<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://linguifex.com/w/index.php?action=history&amp;feed=atom&amp;title=Module%3Apatterns</id>
	<title>Module:patterns - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://linguifex.com/w/index.php?action=history&amp;feed=atom&amp;title=Module%3Apatterns"/>
	<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:patterns&amp;action=history"/>
	<updated>2026-05-28T15:55:51Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.6</generator>
	<entry>
		<id>https://linguifex.com/w/index.php?title=Module:patterns&amp;diff=449855&amp;oldid=prev</id>
		<title>Sware: Created page with &quot;local export = {}  local byte = string.byte local find = string.find local gsub = string.gsub local match = string.match  do 	local pattern_chars 	local function get_pattern_chars() 		pattern_chars, get_pattern_chars = { 			[&quot;\0&quot;] = &quot;%z&quot;, [&quot;$&quot;] = &quot;%$&quot;, [&quot;%&quot;] = &quot;%%&quot;, [&quot;(&quot;] = &quot;%(&quot;, [&quot;)&quot;] = &quot;%)&quot;, 			[&quot;*&quot;] = &quot;%*&quot;, [&quot;+&quot;] = &quot;%+&quot;, [&quot;-&quot;] = &quot;%-&quot;, [&quot;.&quot;] = &quot;%.&quot;, [&quot;?&quot;] = &quot;%?&quot;, 			[&quot;[&quot;] = &quot;%[&quot;, [&quot;]&quot;] = &quot;%]&quot;, [&quot;^&quot;] = &quot;%^&quot;, 		}, nil 		return pattern_chars 	end  	--[==[Escapes the magic...&quot;</title>
		<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:patterns&amp;diff=449855&amp;oldid=prev"/>
		<updated>2025-04-13T15:00:15Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;local export = {}  local byte = string.byte local find = string.find local gsub = string.gsub local match = string.match  do 	local pattern_chars 	local function get_pattern_chars() 		pattern_chars, get_pattern_chars = { 			[&amp;quot;\0&amp;quot;] = &amp;quot;%z&amp;quot;, [&amp;quot;$&amp;quot;] = &amp;quot;%$&amp;quot;, [&amp;quot;%&amp;quot;] = &amp;quot;%%&amp;quot;, [&amp;quot;(&amp;quot;] = &amp;quot;%(&amp;quot;, [&amp;quot;)&amp;quot;] = &amp;quot;%)&amp;quot;, 			[&amp;quot;*&amp;quot;] = &amp;quot;%*&amp;quot;, [&amp;quot;+&amp;quot;] = &amp;quot;%+&amp;quot;, [&amp;quot;-&amp;quot;] = &amp;quot;%-&amp;quot;, [&amp;quot;.&amp;quot;] = &amp;quot;%.&amp;quot;, [&amp;quot;?&amp;quot;] = &amp;quot;%?&amp;quot;, 			[&amp;quot;[&amp;quot;] = &amp;quot;%[&amp;quot;, [&amp;quot;]&amp;quot;] = &amp;quot;%]&amp;quot;, [&amp;quot;^&amp;quot;] = &amp;quot;%^&amp;quot;, 		}, nil 		return pattern_chars 	end  	--[==[Escapes the magic...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local export = {}&lt;br /&gt;
&lt;br /&gt;
local byte = string.byte&lt;br /&gt;
local find = string.find&lt;br /&gt;
local gsub = string.gsub&lt;br /&gt;
local match = string.match&lt;br /&gt;
&lt;br /&gt;
do&lt;br /&gt;
	local pattern_chars&lt;br /&gt;
	local function get_pattern_chars()&lt;br /&gt;
		pattern_chars, get_pattern_chars = {&lt;br /&gt;
			[&amp;quot;\0&amp;quot;] = &amp;quot;%z&amp;quot;, [&amp;quot;$&amp;quot;] = &amp;quot;%$&amp;quot;, [&amp;quot;%&amp;quot;] = &amp;quot;%%&amp;quot;, [&amp;quot;(&amp;quot;] = &amp;quot;%(&amp;quot;, [&amp;quot;)&amp;quot;] = &amp;quot;%)&amp;quot;,&lt;br /&gt;
			[&amp;quot;*&amp;quot;] = &amp;quot;%*&amp;quot;, [&amp;quot;+&amp;quot;] = &amp;quot;%+&amp;quot;, [&amp;quot;-&amp;quot;] = &amp;quot;%-&amp;quot;, [&amp;quot;.&amp;quot;] = &amp;quot;%.&amp;quot;, [&amp;quot;?&amp;quot;] = &amp;quot;%?&amp;quot;,&lt;br /&gt;
			[&amp;quot;[&amp;quot;] = &amp;quot;%[&amp;quot;, [&amp;quot;]&amp;quot;] = &amp;quot;%]&amp;quot;, [&amp;quot;^&amp;quot;] = &amp;quot;%^&amp;quot;,&lt;br /&gt;
		}, nil&lt;br /&gt;
		return pattern_chars&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	--[==[Escapes the magic characters used in a pattern: {$%()*+-.?[]^}, and converts the null character to {%z}. For example, {&amp;quot;^$()%.[]*+-?\0&amp;quot;} becomes {&amp;quot;%^%$%(%)%%%.%[%]%*%+%-%?%z&amp;quot;}. This is necessary when constructing a pattern involving arbitrary text (e.g. from user input).]==]&lt;br /&gt;
	function export.pattern_escape(str)&lt;br /&gt;
		return (gsub(str, &amp;quot;[%z$%%()*+%-.?[%]^]&amp;quot;, pattern_chars or get_pattern_chars()))&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
do&lt;br /&gt;
	local charset_chars&lt;br /&gt;
	local function get_charset_chars()&lt;br /&gt;
		charset_chars, get_charset_chars = {&lt;br /&gt;
			[&amp;quot;\0&amp;quot;] = &amp;quot;%z&amp;quot;, [&amp;quot;%&amp;quot;] = &amp;quot;%%&amp;quot;, [&amp;quot;-&amp;quot;] = &amp;quot;%-&amp;quot;, [&amp;quot;]&amp;quot;] = &amp;quot;%]&amp;quot;, [&amp;quot;^&amp;quot;] = &amp;quot;%^&amp;quot;&lt;br /&gt;
		}, nil&lt;br /&gt;
		return charset_chars&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	--[==[Escapes the magic characters used in pattern character sets: {%-]^}, and converts the null character to {%z}.]==]&lt;br /&gt;
	function export.charset_escape(str)&lt;br /&gt;
		return (gsub(str, &amp;quot;[%z%%%-%]^]&amp;quot;, charset_chars or get_charset_chars()))&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[Escapes {%}, which is the only magic character used in replacement strings, which are given as the third argument to {string.gsub} and {mw.ustring.gsub}.]==]&lt;br /&gt;
function export.replacement_escape(str)&lt;br /&gt;
	return (gsub(str, &amp;quot;%%&amp;quot;, &amp;quot;%%%%&amp;quot;))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
do&lt;br /&gt;
	local function parse_charset(pattern, pos)&lt;br /&gt;
		local nxt, ch = byte(pattern, pos)&lt;br /&gt;
		-- &amp;quot;^&amp;quot; indicates a negative charset, so the search begins from the next character.&lt;br /&gt;
		if nxt == 0x5E then -- ^&lt;br /&gt;
			pos = pos + 1&lt;br /&gt;
			nxt = byte(pattern, pos)&lt;br /&gt;
		end&lt;br /&gt;
		-- &amp;quot;]&amp;quot; is non-magic if it&amp;#039;s the first character of a charset (including after &amp;quot;^&amp;quot;), so ignore it.&lt;br /&gt;
		if nxt == 0x5D then -- ]&lt;br /&gt;
			pos = pos + 1&lt;br /&gt;
		end&lt;br /&gt;
		repeat&lt;br /&gt;
			ch, pos = match(pattern, &amp;quot;([%%%]])()&amp;quot;, pos)&lt;br /&gt;
			-- Escaping &amp;quot;%&amp;quot;.&lt;br /&gt;
			if ch == &amp;quot;%&amp;quot; then&lt;br /&gt;
				pos = pos + 1&lt;br /&gt;
			-- End of charset.&lt;br /&gt;
			elseif ch == &amp;quot;]&amp;quot; then&lt;br /&gt;
				return pos&lt;br /&gt;
			end&lt;br /&gt;
		until not ch&lt;br /&gt;
		-- End of string throws an error, as the charset is incomplete.&lt;br /&gt;
		return false, &amp;quot;unclosed charset: must be closed with &amp;#039;]&amp;#039;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local function _validate_pattern(pattern, str_lib)&lt;br /&gt;
		if pattern == &amp;quot;&amp;quot; then&lt;br /&gt;
			return true&lt;br /&gt;
		-- &amp;quot;\0&amp;quot; can be used in ustring patterns, and with string.find iff the `plain` flag is set.&lt;br /&gt;
		elseif str_lib == &amp;quot;string&amp;quot; and find(pattern, &amp;quot;\0&amp;quot;, nil, true) then&lt;br /&gt;
			return false, &amp;quot;string library pattern cannot contain the null character &amp;#039;\\000&amp;#039;&amp;quot;&lt;br /&gt;
		elseif str_lib == &amp;quot;ustring&amp;quot; and #pattern &amp;gt; 10000 then&lt;br /&gt;
			return false, &amp;quot;ustring library pattern cannot be more than 10,000 bytes&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		local pos, cap_open, cap_complete, ch = 1, 0, 0&lt;br /&gt;
		repeat&lt;br /&gt;
			ch, pos = match(pattern, &amp;quot;([%%()[])()&amp;quot;, pos)&lt;br /&gt;
			-- Escaping &amp;quot;%&amp;quot;.&lt;br /&gt;
			if ch == &amp;quot;%&amp;quot; then&lt;br /&gt;
				local nxt = byte(pattern, pos)&lt;br /&gt;
				-- Balanced string &amp;quot;%bxy&amp;quot;.&lt;br /&gt;
				if nxt == 0x62 then -- b&lt;br /&gt;
					-- Must be followed by two characters, which are always treated as literals.&lt;br /&gt;
					if byte(pattern, pos + 2) == nil then&lt;br /&gt;
						return false, &amp;quot;incomplete balanced string: &amp;#039;%b&amp;#039; must be followed by two characters&amp;quot;&lt;br /&gt;
					end&lt;br /&gt;
					pos = pos + 3&lt;br /&gt;
				-- Frontier pattern &amp;quot;%f[abc]&amp;quot;.&lt;br /&gt;
				elseif nxt == 0x66 then -- f&lt;br /&gt;
					if byte(pattern, pos + 1) ~= 0x5B then -- [&lt;br /&gt;
						return false, &amp;quot;incomplete frontier pattern: &amp;#039;%f&amp;#039; must be followed by a charset&amp;quot;&lt;br /&gt;
					end&lt;br /&gt;
					-- Charset after &amp;quot;%f&amp;quot;.&lt;br /&gt;
					local result, err_msg = parse_charset(pattern, pos + 2)&lt;br /&gt;
					if not result then&lt;br /&gt;
						return false, err_msg&lt;br /&gt;
					end&lt;br /&gt;
					pos = result&lt;br /&gt;
				-- Back-reference to a complete capture group (e.g. &amp;quot;(foo)%1&amp;quot;); not possible to reference groups above &amp;quot;%9&amp;quot;.&lt;br /&gt;
				elseif nxt &amp;gt;= 0x31 and nxt &amp;lt;= 0x39 then -- 1-9&lt;br /&gt;
					-- References to open, unstarted or undefined capture groups are invalid (e.g. &amp;quot;%1(foo)&amp;quot;).&lt;br /&gt;
					local n = nxt - 0x30&lt;br /&gt;
					if n &amp;gt; cap_complete then&lt;br /&gt;
						return false, &amp;quot;invalid capture index &amp;#039;%&amp;quot; .. n .. &amp;quot;&amp;#039;&amp;quot;&lt;br /&gt;
					end&lt;br /&gt;
					pos = pos + 1&lt;br /&gt;
				-- &amp;quot;%0&amp;quot; is a reference to the full match, which can never be valid in patterns since it will always be incomplete; only valid in replacement strings.&lt;br /&gt;
				elseif nxt == 0x30 then -- 0&lt;br /&gt;
					return false, &amp;quot;invalid capture index &amp;#039;%0&amp;#039;&amp;quot;&lt;br /&gt;
				-- End of string throws an error, as the escape sequence is incomplete.&lt;br /&gt;
				elseif nxt == nil then&lt;br /&gt;
					return false, &amp;quot;incomplete escape sequence: final &amp;#039;%&amp;#039; must be followed by a character&amp;quot;&lt;br /&gt;
				else&lt;br /&gt;
					pos = pos + 1&lt;br /&gt;
				end&lt;br /&gt;
			-- New capture group.&lt;br /&gt;
			elseif ch == &amp;quot;(&amp;quot; then&lt;br /&gt;
				-- String library patterns cannot have more than 32 capture groups.&lt;br /&gt;
				if str_lib == &amp;quot;string&amp;quot; and cap_open + cap_complete &amp;gt;= 32 then&lt;br /&gt;
					return false, &amp;quot;string library pattern cannot contain more than 32 capture groups&amp;quot;&lt;br /&gt;
				end&lt;br /&gt;
				-- Increment the number of open groups.&lt;br /&gt;
				cap_open = cap_open + 1&lt;br /&gt;
			-- End of capture group.&lt;br /&gt;
			elseif ch == &amp;quot;)&amp;quot; then&lt;br /&gt;
				-- There must be at least one open group.&lt;br /&gt;
				if cap_open &amp;lt; 1 then&lt;br /&gt;
					return false, &amp;quot;cannot close a capture group with &amp;#039;)&amp;#039; when none are open&amp;quot;&lt;br /&gt;
				end&lt;br /&gt;
				-- Decrement the number of open groups, and increment the number of complete groups.&lt;br /&gt;
				cap_open, cap_complete = cap_open - 1, cap_complete + 1&lt;br /&gt;
			-- Charset &amp;quot;[abc]&amp;quot;.&lt;br /&gt;
			elseif ch == &amp;quot;[&amp;quot; then&lt;br /&gt;
				local result, err_msg = parse_charset(pattern, pos)&lt;br /&gt;
				if not result then&lt;br /&gt;
					return false, err_msg&lt;br /&gt;
				end&lt;br /&gt;
				pos = result&lt;br /&gt;
			end&lt;br /&gt;
		until not ch&lt;br /&gt;
		-- End of string throws an error if any capture groups are open, as they are incomplete.&lt;br /&gt;
		if cap_open &amp;gt; 0 then&lt;br /&gt;
			return false, &amp;quot;unclosed capture group: must be closed with &amp;#039;)&amp;#039;&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	function export.validate_pattern(pattern, safe, str_lib)&lt;br /&gt;
		local ok, err_msg = _validate_pattern(pattern, str_lib or &amp;quot;string&amp;quot;)&lt;br /&gt;
		if ok then&lt;br /&gt;
			return ok&lt;br /&gt;
		elseif safe then&lt;br /&gt;
			return ok, err_msg&lt;br /&gt;
		end&lt;br /&gt;
		error(err_msg)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return export&lt;/div&gt;</summary>
		<author><name>Sware</name></author>
	</entry>
</feed>