--
local flag_map = {
	a = 'anchored', i = 'caseless', m = 'multiline', s = 'dotall', u = 'unicode', U = 'ungreedy', x ='extended',
};

local posix_class_names = {
	alnum = true, alpha = true, ascii = true, blank = true, cntrl = true, digit = true, graph = true, lower = true, print = true, punct = true, space = true, upper = true, word = true, xdigit = true,
};

local escape_chars = {
	-- grouped
	-- digit, spaces and words
	[0x44] = { "class", "digit", true }, [0x53] = { "class", "space", true }, [0x57] = { "class", "word", true },
	[0x64] = { "class", "digit", false }, [0x73] = { "class", "space", false }, [0x77] = { "class", "word", false },
	-- horizontal/vertical whitespace and newline
	[0x48] = { "class", "blank", true }, [0x56] = { "class", "vertical_tab", true },
	[0x68] = { "class", "blank", false }, [0x76] = { "class", "vertical_tab", false },
	[0x4E] = { 0x4E }, [0x52] = { 0x52 },

	-- not grouped
	[0x42] = 0x08,
	[0x6E] = 0x0A, [0x72] = 0x0D, [0x74] = 0x09,
};

local b_escape_chars = {
	-- word boundary and not word boundary
	[0x62] = { 0x62, { "class", "word", false } }, [0x42] = { 0x42, { "class", "word", false } },

	-- keep match out
	[0x4B] = { 0x4B },

	-- start & end of string
	[0x47] = { 0x47 }, [0x4A] = { 0x4A }, [0x5A] = { 0x5A }, [0x7A] = { 0x7A },
};

local valid_categories = {
	C = true, Cc = true, Cf = true, Cn = true, Co = true, Cs = true,
	L = true, Ll = true, Lm = true, Lo = true, Lt = true, Lu = true,
	M = true, Mc = true, Me = true, Mn = true,
	N = true, Nd = true, Nl = true, No = true,
	P = true, Pc = true, Pd = true, Pe = true, Pf = true, Pi = true, Po = true, Ps = true,
	S = true, Sc = true, Sk = true, Sm = true, So = true,
	Z = true, Zl = true, Zp = true, Zs = true,

	Xan = true, Xps = true, Xsp = true, Xuc = true, Xwd = true,
};

local class_ascii_punct = {
	[0x21] = true, [0x22] = true, [0x23] = true, [0x24] = true, [0x25] = true, [0x26] = true, [0x27] = true, [0x28] = true, [0x29] = true, [0x2A] = true, [0x2B] = true, [0x2C] = true, [0x2D] = true, [0x2E] = true, [0x2F] = true,
	[0x3A] = true, [0x3B] = true, [0x3C] = true, [0x3D] = true, [0x3E] = true, [0x3F] = true, [0x40] = true, [0x5B] = true, [0x5C] = true, [0x5D] = true, [0x5E] = true, [0x5F] = true, [0x60] = true, [0x7B] = true, [0x7C] = true,
	[0x7D] = true, [0x7E] = true,
};

local end_str = { 0x24 };
local dot = { 0x2E };
local beginning_str = { 0x5E };
local alternation = { 0x7C };

local function check_re(re_type, name, func)
	if re_type == "Match" then
		return function(...)
			local arg_n = select('#', ...);
			if arg_n < 1 then
				error("missing argument #1 (Match expected)", 2);
			end;
			local arg0, arg1 = ...;
			if not (proxy[arg0] and proxy[arg0].name == "Match") then
				error(string.format("invalid argument #1 to %q (Match expected, got %s)", name, typeof(arg0)), 2);
			else
				arg0 = proxy[arg0];
			end;
			if name == "group" or name == "span" then
				if arg1 == nil then
					arg1 = 0;
				end;
			end;
			return func(arg0, arg1);
		end;
	end;
	return function(...)
		local arg_n = select('#', ...);
		if arg_n < 1 then
			error("missing argument #1 (RegEx expected)", 2);
		elseif arg_n < 2 then
			error("missing argument #2 (string expected)", 2);
		end;
		local arg0, arg1, arg2, arg3, arg4, arg5 = ...;
		if not (proxy[arg0] and proxy[arg0].name == "RegEx") then
			if type(arg0) ~= "string" and type(arg0) ~= "number" then
				error(string.format("invalid argument #1 to %q (RegEx expected, got %s)", name, typeof(arg0)), 2);
			end;
			arg0 = re.fromstring(arg0);
		elseif name == "sub" then
			if type(arg2) == "number" then
				arg2 ..= '';
			elseif type(arg2) ~= "string" then
				error(string.format("invalid argument #3 to 'sub' (string expected, got %s)", typeof(arg2)), 2);
			end;
		elseif type(arg1) == "number" then
			arg1 ..= '';
		elseif type(arg1) ~= "string" then
			error(string.format("invalid argument #2 to %q (string expected, got %s)", name, typeof(arg1)), 2);
		end;
		if name ~= "sub" and name ~= "split" then
			local init_type = typeof(arg2);
			if init_type ~= 'nil' then
				arg2 = tonumber(arg2);
				if not arg2 then
					error(string.format("invalid argument #3 to %q (number expected, got %s)", name, init_type), 2);
				elseif arg2 < 0 then
					arg2 = #arg1 + math.floor(arg2 + 0.5) + 1;
				else
					arg2 = math.max(math.floor(arg2 + 0.5), 1);
				end;
			end;
		end;
		arg0 = proxy[arg0];
		if name == "match" or name == "matchiter" then
			arg3 = ...;
		elseif name == "sub" then
			arg5 = ...;
		end;
		return func(arg0, arg1, arg2, arg3, arg4, arg5);
	end;
end;