<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
    <Meta name="ExplicitAutoJoints">true</Meta>
    <External>null</External>
    <External>nil</External>
    <Item class="ModuleScript" referent="RBX0B105B33CA7E49A29DACA77A3877D8CF">
        <Properties>
            <BinaryString name="AttributesSerialize"></BinaryString>
            <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
            <bool name="DefinesCapabilities">false</bool>
            <Content name="LinkedSource"><null></null></Content>
            <string name="Name">zyntex-sdk</string>
            <string name="ScriptGuid">{50D9646E-3223-4626-94F3-EC8AF071DDE2}</string>
            <ProtectedString name="Source"><![CDATA[local Zyntex = require(script:FindFirstChild("Zyntex"))
local Session = require(script:FindFirstChild("api"))

return setmetatable({}, {
	--[[
		Gets the current version of this module.
	]]
	__index = function(self, index)
		if string.lower(index) == "version" then
			return Zyntex.VERSION
		end
		if string.lower(index) == "isZyntex" then --// Important for the Zyntex upgrader plugin. When it searches for the module, make sure it has this.
			return true
		end
	end,
	--[[
		Returns a new Zyntex object.
		https://docs.zyntex.dev
	]]
	__call = function(self, gameToken: string): Zyntex.Zyntex
		return Zyntex.new(gameToken)
	end,
})
]]></ProtectedString>
            <int64 name="SourceAssetId">-1</int64>
            <BinaryString name="Tags"></BinaryString>
        </Properties>
        <Item class="ModuleScript" referent="RBX213001A71DC24AC2AF0EB0CBF83CF89C">
            <Properties>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="DefinesCapabilities">false</bool>
                <Content name="LinkedSource"><null></null></Content>
                <string name="Name">Loadstring</string>
                <string name="ScriptGuid">{F3735809-AA6E-4504-A762-CA917A99C36A}</string>
                <ProtectedString name="Source"><![CDATA[--[[
		For support or to check out our other projects, join us on the Bleu Pigs Discord:
		https://discord.gg/H73NsjfBbP
		---------------
		vLua 5.1 - Lua written in Lua Virtual Machine
		---------------
		vLua is a virtual machine and compiler for dynamically compiling and executing Lua.
		It'll work on both client and server, regardless of LoadStringEnabled. This module is
		designed to be a drop in replacement for loadstring, meaning you can do the following:
		
		Example:
			local loadstring = require(workspace.Loadstring)
			local executable, compileFailReason = loadstring("print('hello from vLua!')")
			executable()
		
		Please note, vLua IS SLOWER COMPARED TO vanilla Lua, although Luau does improve performance.
		Do not attempt to run performance intensive tasks without testing first, otherwise you
		may have a bad time.
		
		Changelog:
			[8/13/2022]
				- updated FiOne to latest release - https://github.com/Rerumu/FiOne/commit/b983f11a0a318dae6c7804161b1cbc3aa52a8236
				- removed link to Minecraft server Discord
				- added link to Bleu Pigs General Discord
			[1/18/2022]
				- updated FiOne to latest release - https://github.com/Rerumu/FiOne/commit/900413a8491a44daa7770d799c85ad6df8610eea
				- added link to Minecraft server Discord
			[1/1/2022]
				- fixed environment not being properly set for compiled function
			[11/12/2021]
				- removed previous changelogs
				- updated FiOne to latest release - https://github.com/Rerumu/FiOne/blob/f443116e947e5bb3fe8bb7e6abca78214a245145/source.lua
				- fixed attempt to call a nil value error
		
		Credits:
			- FiOne LBI (created by same author as Rerubi) - https://github.com/Rerumu/FiOne
			- Yueliang 5 (Lua compiler in Lua) - http://yueliang.luaforge.net/
			- Moonshine (improved version of Yeuliang) - https://github.com/gamesys/moonshine
]]
local compile = require(script:WaitForChild("Yueliang"))
local createExecutable = require(script:WaitForChild("FiOne"))
getfenv().script = nil

return function(source, env)
	local executable
	local env = env or getfenv(2)
	local name = (env.script and env.script:GetFullName())
	local ran, failureReason = pcall(function()
		local compiledBytecode = compile(source, name)
		executable = createExecutable(compiledBytecode, env)
	end)

	if ran then
		return setfenv(executable, env)
	end
	return nil, failureReason
end
]]></ProtectedString>
                <int64 name="SourceAssetId">4689019964</int64>
                <BinaryString name="Tags"></BinaryString>
            </Properties>
            <Item class="ModuleScript" referent="RBXC53EF71FA5EA426E9124149739C6DC33">
                <Properties>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="DefinesCapabilities">false</bool>
                    <Content name="LinkedSource"><null></null></Content>
                    <string name="Name">FiOne</string>
                    <string name="ScriptGuid">{4A8F75B0-FE61-433E-8F28-E9CD53400632}</string>
                    <ProtectedString name="Source"><![CDATA[--[[
FiOne
Copyright (C) 2021  Rerumu

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
]]
--
local bit = bit or bit32 or require("bit")

if not table.create then
	function table.create(_)
		return {}
	end
end

if not table.unpack then
	table.unpack = unpack
end

if not table.pack then
	function table.pack(...)
		return { n = select("#", ...), ... }
	end
end

if not table.move then
	function table.move(src, first, last, offset, dst)
		for i = 0, last - first do
			dst[offset + i] = src[first + i]
		end
	end
end

local lua_bc_to_state
local lua_wrap_state
local stm_lua_func

-- SETLIST config
local FIELDS_PER_FLUSH = 50

-- remap for better lookup
local OPCODE_RM = {
	-- level 1
	[22] = 18, -- JMP
	[31] = 8, -- FORLOOP
	[33] = 28, -- TFORLOOP
	-- level 2
	[0] = 3, -- MOVE
	[1] = 13, -- LOADK
	[2] = 23, -- LOADBOOL
	[26] = 33, -- TEST
	-- level 3
	[12] = 1, -- ADD
	[13] = 6, -- SUB
	[14] = 10, -- MUL
	[15] = 16, -- DIV
	[16] = 20, -- MOD
	[17] = 26, -- POW
	[18] = 30, -- UNM
	[19] = 36, -- NOT
	-- level 4
	[3] = 0, -- LOADNIL
	[4] = 2, -- GETUPVAL
	[5] = 4, -- GETGLOBAL
	[6] = 7, -- GETTABLE
	[7] = 9, -- SETGLOBAL
	[8] = 12, -- SETUPVAL
	[9] = 14, -- SETTABLE
	[10] = 17, -- NEWTABLE
	[20] = 19, -- LEN
	[21] = 22, -- CONCAT
	[23] = 24, -- EQ
	[24] = 27, -- LT
	[25] = 29, -- LE
	[27] = 32, -- TESTSET
	[32] = 34, -- FORPREP
	[34] = 37, -- SETLIST
	-- level 5
	[11] = 5, -- SELF
	[28] = 11, -- CALL
	[29] = 15, -- TAILCALL
	[30] = 21, -- RETURN
	[35] = 25, -- CLOSE
	[36] = 31, -- CLOSURE
	[37] = 35, -- VARARG
}

-- opcode types for getting values
local OPCODE_T = {
	[0] = "ABC",
	"ABx",
	"ABC",
	"ABC",
	"ABC",
	"ABx",
	"ABC",
	"ABx",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"AsBx",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"ABC",
	"AsBx",
	"AsBx",
	"ABC",
	"ABC",
	"ABC",
	"ABx",
	"ABC",
}

local OPCODE_M = {
	[0] = { b = "OpArgR", c = "OpArgN" },
	{ b = "OpArgK", c = "OpArgN" },
	{ b = "OpArgU", c = "OpArgU" },
	{ b = "OpArgR", c = "OpArgN" },
	{ b = "OpArgU", c = "OpArgN" },
	{ b = "OpArgK", c = "OpArgN" },
	{ b = "OpArgR", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgN" },
	{ b = "OpArgU", c = "OpArgN" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgU", c = "OpArgU" },
	{ b = "OpArgR", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgR", c = "OpArgN" },
	{ b = "OpArgR", c = "OpArgN" },
	{ b = "OpArgR", c = "OpArgN" },
	{ b = "OpArgR", c = "OpArgR" },
	{ b = "OpArgR", c = "OpArgN" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgK", c = "OpArgK" },
	{ b = "OpArgR", c = "OpArgU" },
	{ b = "OpArgR", c = "OpArgU" },
	{ b = "OpArgU", c = "OpArgU" },
	{ b = "OpArgU", c = "OpArgU" },
	{ b = "OpArgU", c = "OpArgN" },
	{ b = "OpArgR", c = "OpArgN" },
	{ b = "OpArgR", c = "OpArgN" },
	{ b = "OpArgN", c = "OpArgU" },
	{ b = "OpArgU", c = "OpArgU" },
	{ b = "OpArgN", c = "OpArgN" },
	{ b = "OpArgU", c = "OpArgN" },
	{ b = "OpArgU", c = "OpArgN" },
}

-- int rd_int_basic(string src, int s, int e, int d)
-- @src - Source binary string
-- @s - Start index of a little endian integer
-- @e - End index of the integer
-- @d - Direction of the loop
local function rd_int_basic(src, s, e, d)
	local num = 0

	-- if bb[l] > 127 then -- signed negative
	-- 	num = num - 256 ^ l
	-- 	bb[l] = bb[l] - 128
	-- end

	for i = s, e, d do
		local mul = 256 ^ math.abs(i - s)

		num = num + mul * string.byte(src, i, i)
	end

	return num
end

-- float rd_flt_basic(byte f1..8)
-- @f1..4 - The 4 bytes composing a little endian float
local function rd_flt_basic(f1, f2, f3, f4)
	local sign = (-1) ^ bit.rshift(f4, 7)
	local exp = bit.rshift(f3, 7) + bit.lshift(bit.band(f4, 0x7F), 1)
	local frac = f1 + bit.lshift(f2, 8) + bit.lshift(bit.band(f3, 0x7F), 16)
	local normal = 1

	if exp == 0 then
		if frac == 0 then
			return sign * 0
		else
			normal = 0
			exp = 1
		end
	elseif exp == 0x7F then
		if frac == 0 then
			return sign * (1 / 0)
		else
			return sign * (0 / 0)
		end
	end

	return sign * 2 ^ (exp - 127) * (1 + normal / 2 ^ 23)
end

-- double rd_dbl_basic(byte f1..8)
-- @f1..8 - The 8 bytes composing a little endian double
local function rd_dbl_basic(f1, f2, f3, f4, f5, f6, f7, f8)
	local sign = (-1) ^ bit.rshift(f8, 7)
	local exp = bit.lshift(bit.band(f8, 0x7F), 4) + bit.rshift(f7, 4)
	local frac = bit.band(f7, 0x0F) * 2 ^ 48
	local normal = 1

	frac = frac + (f6 * 2 ^ 40) + (f5 * 2 ^ 32) + (f4 * 2 ^ 24) + (f3 * 2 ^ 16) + (f2 * 2 ^ 8) + f1 -- help

	if exp == 0 then
		if frac == 0 then
			return sign * 0
		else
			normal = 0
			exp = 1
		end
	elseif exp == 0x7FF then
		if frac == 0 then
			return sign * (1 / 0)
		else
			return sign * (0 / 0)
		end
	end

	return sign * 2 ^ (exp - 1023) * (normal + frac / 2 ^ 52)
end

-- int rd_int_le(string src, int s, int e)
-- @src - Source binary string
-- @s - Start index of a little endian integer
-- @e - End index of the integer
local function rd_int_le(src, s, e)
	return rd_int_basic(src, s, e - 1, 1)
end

-- int rd_int_be(string src, int s, int e)
-- @src - Source binary string
-- @s - Start index of a big endian integer
-- @e - End index of the integer
local function rd_int_be(src, s, e)
	return rd_int_basic(src, e - 1, s, -1)
end

-- float rd_flt_le(string src, int s)
-- @src - Source binary string
-- @s - Start index of little endian float
local function rd_flt_le(src, s)
	return rd_flt_basic(string.byte(src, s, s + 3))
end

-- float rd_flt_be(string src, int s)
-- @src - Source binary string
-- @s - Start index of big endian float
local function rd_flt_be(src, s)
	local f1, f2, f3, f4 = string.byte(src, s, s + 3)
	return rd_flt_basic(f4, f3, f2, f1)
end

-- double rd_dbl_le(string src, int s)
-- @src - Source binary string
-- @s - Start index of little endian double
local function rd_dbl_le(src, s)
	return rd_dbl_basic(string.byte(src, s, s + 7))
end

-- double rd_dbl_be(string src, int s)
-- @src - Source binary string
-- @s - Start index of big endian double
local function rd_dbl_be(src, s)
	local f1, f2, f3, f4, f5, f6, f7, f8 = string.byte(src, s, s + 7) -- same
	return rd_dbl_basic(f8, f7, f6, f5, f4, f3, f2, f1)
end

-- to avoid nested ifs in deserializing
local float_types = {
	[4] = { little = rd_flt_le, big = rd_flt_be },
	[8] = { little = rd_dbl_le, big = rd_dbl_be },
}

-- byte stm_byte(Stream S)
-- @S - Stream object to read from
local function stm_byte(S)
	local idx = S.index
	local bt = string.byte(S.source, idx, idx)

	S.index = idx + 1
	return bt
end

-- string stm_string(Stream S, int len)
-- @S - Stream object to read from
-- @len - Length of string being read
local function stm_string(S, len)
	local pos = S.index + len
	local str = string.sub(S.source, S.index, pos - 1)

	S.index = pos
	return str
end

-- string stm_lstring(Stream S)
-- @S - Stream object to read from
local function stm_lstring(S)
	local len = S:s_szt()
	local str

	if len ~= 0 then
		str = string.sub(stm_string(S, len), 1, -2)
	end

	return str
end

-- fn cst_int_rdr(string src, int len, fn func)
-- @len - Length of type for reader
-- @func - Reader callback
local function cst_int_rdr(len, func)
	return function(S)
		local pos = S.index + len
		local int = func(S.source, S.index, pos)
		S.index = pos

		return int
	end
end

-- fn cst_flt_rdr(string src, int len, fn func)
-- @len - Length of type for reader
-- @func - Reader callback
local function cst_flt_rdr(len, func)
	return function(S)
		local flt = func(S.source, S.index)
		S.index = S.index + len

		return flt
	end
end

local function stm_inst_list(S)
	local len = S:s_int()
	local list = table.create(len)

	for i = 1, len do
		local ins = S:s_ins()
		local op = bit.band(ins, 0x3F)
		local args = OPCODE_T[op]
		local mode = OPCODE_M[op]
		local data = { value = ins, op = OPCODE_RM[op], A = bit.band(bit.rshift(ins, 6), 0xFF) }

		if args == "ABC" then
			data.B = bit.band(bit.rshift(ins, 23), 0x1FF)
			data.C = bit.band(bit.rshift(ins, 14), 0x1FF)
			data.is_KB = mode.b == "OpArgK" and data.B > 0xFF -- post process optimization
			data.is_KC = mode.c == "OpArgK" and data.C > 0xFF
		elseif args == "ABx" then
			data.Bx = bit.band(bit.rshift(ins, 14), 0x3FFFF)
			data.is_K = mode.b == "OpArgK"
		elseif args == "AsBx" then
			data.sBx = bit.band(bit.rshift(ins, 14), 0x3FFFF) - 131071
		end

		list[i] = data
	end

	return list
end

local function stm_const_list(S)
	local len = S:s_int()
	local list = table.create(len)

	for i = 1, len do
		local tt = stm_byte(S)
		local k

		if tt == 1 then
			k = stm_byte(S) ~= 0
		elseif tt == 3 then
			k = S:s_num()
		elseif tt == 4 then
			k = stm_lstring(S)
		end

		list[i] = k -- offset +1 during instruction decode
	end

	return list
end

local function stm_sub_list(S, src)
	local len = S:s_int()
	local list = table.create(len)

	for i = 1, len do
		list[i] = stm_lua_func(S, src) -- offset +1 in CLOSURE
	end

	return list
end

local function stm_line_list(S)
	local len = S:s_int()
	local list = table.create(len)

	for i = 1, len do
		list[i] = S:s_int()
	end

	return list
end

local function stm_loc_list(S)
	local len = S:s_int()
	local list = table.create(len)

	for i = 1, len do
		list[i] = { varname = stm_lstring(S), startpc = S:s_int(), endpc = S:s_int() }
	end

	return list
end

local function stm_upval_list(S)
	local len = S:s_int()
	local list = table.create(len)

	for i = 1, len do
		list[i] = stm_lstring(S)
	end

	return list
end

function stm_lua_func(S, psrc)
	local proto = {}
	local src = stm_lstring(S) or psrc -- source is propagated

	proto.source = src -- source name

	S:s_int() -- line defined
	S:s_int() -- last line defined

	proto.num_upval = stm_byte(S) -- num upvalues
	proto.num_param = stm_byte(S) -- num params

	stm_byte(S) -- vararg flag
	proto.max_stack = stm_byte(S) -- max stack size

	proto.code = stm_inst_list(S)
	proto.const = stm_const_list(S)
	proto.subs = stm_sub_list(S, src)
	proto.lines = stm_line_list(S)

	stm_loc_list(S)
	stm_upval_list(S)

	-- post process optimization
	for _, v in ipairs(proto.code) do
		if v.is_K then
			v.const = proto.const[v.Bx + 1] -- offset for 1 based index
		else
			if v.is_KB then
				v.const_B = proto.const[v.B - 0xFF]
			end

			if v.is_KC then
				v.const_C = proto.const[v.C - 0xFF]
			end
		end
	end

	return proto
end

function lua_bc_to_state(src)
	-- func reader
	local rdr_func

	-- header flags
	local little
	local size_int
	local size_szt
	local size_ins
	local size_num
	local flag_int

	-- stream object
	local stream = {
		-- data
		index = 1,
		source = src,
	}

	assert(stm_string(stream, 4) == "\27Lua", "invalid Lua signature")
	assert(stm_byte(stream) == 0x51, "invalid Lua version")
	assert(stm_byte(stream) == 0, "invalid Lua format")

	little = stm_byte(stream) ~= 0
	size_int = stm_byte(stream)
	size_szt = stm_byte(stream)
	size_ins = stm_byte(stream)
	size_num = stm_byte(stream)
	flag_int = stm_byte(stream) ~= 0

	rdr_func = little and rd_int_le or rd_int_be
	stream.s_int = cst_int_rdr(size_int, rdr_func)
	stream.s_szt = cst_int_rdr(size_szt, rdr_func)
	stream.s_ins = cst_int_rdr(size_ins, rdr_func)

	if flag_int then
		stream.s_num = cst_int_rdr(size_num, rdr_func)
	elseif float_types[size_num] then
		stream.s_num = cst_flt_rdr(size_num, float_types[size_num][little and "little" or "big"])
	else
		error("unsupported float size")
	end

	return stm_lua_func(stream, "@virtual")
end

local function close_lua_upvalues(list, index)
	for i, uv in pairs(list) do
		if uv.index >= index then
			uv.value = uv.store[uv.index] -- store value
			uv.store = uv
			uv.index = "value" -- self reference
			list[i] = nil
		end
	end
end

local function open_lua_upvalue(list, index, memory)
	local prev = list[index]

	if not prev then
		prev = { index = index, store = memory }
		list[index] = prev
	end

	return prev
end

local function on_lua_error(failed, err)
	local src = failed.source
	local line = failed.lines[failed.pc - 1]

	error(string.format("%s:%i: %s", src, line, err), 0)
end

local function run_lua_func(state, env, upvals)
	local code = state.code
	local subs = state.subs
	local vararg = state.vararg

	local top_index = -1
	local open_list = {}
	local memory = state.memory
	local pc = state.pc

	while true do
		local inst = code[pc]
		local op = inst.op
		pc = pc + 1

		if op < 18 then
			if op < 8 then
				if op < 3 then
					if op < 1 then
						--[[LOADNIL]]
						for i = inst.A, inst.B do
							memory[i] = nil
						end
					elseif op > 1 then
						--[[GETUPVAL]]
						local uv = upvals[inst.B]

						memory[inst.A] = uv.store[uv.index]
					else
						--[[ADD]]
						local lhs, rhs

						if inst.is_KB then
							lhs = inst.const_B
						else
							lhs = memory[inst.B]
						end

						if inst.is_KC then
							rhs = inst.const_C
						else
							rhs = memory[inst.C]
						end

						memory[inst.A] = lhs + rhs
					end
				elseif op > 3 then
					if op < 6 then
						if op > 4 then
							--[[SELF]]
							local A = inst.A
							local B = inst.B
							local index

							if inst.is_KC then
								index = inst.const_C
							else
								index = memory[inst.C]
							end

							memory[A + 1] = memory[B]
							memory[A] = memory[B][index]
						else
							--[[GETGLOBAL]]
							memory[inst.A] = env[inst.const]
						end
					elseif op > 6 then
						--[[GETTABLE]]
						local index

						if inst.is_KC then
							index = inst.const_C
						else
							index = memory[inst.C]
						end

						memory[inst.A] = memory[inst.B][index]
					else
						--[[SUB]]
						local lhs, rhs

						if inst.is_KB then
							lhs = inst.const_B
						else
							lhs = memory[inst.B]
						end

						if inst.is_KC then
							rhs = inst.const_C
						else
							rhs = memory[inst.C]
						end

						memory[inst.A] = lhs - rhs
					end
				else --[[MOVE]]
					memory[inst.A] = memory[inst.B]
				end
			elseif op > 8 then
				if op < 13 then
					if op < 10 then
						--[[SETGLOBAL]]
						env[inst.const] = memory[inst.A]
					elseif op > 10 then
						if op < 12 then
							--[[CALL]]
							local A = inst.A
							local B = inst.B
							local C = inst.C
							local params

							if B == 0 then
								params = top_index - A
							else
								params = B - 1
							end

							local ret_list = table.pack(memory[A](table.unpack(memory, A + 1, A + params)))
							local ret_num = ret_list.n

							if C == 0 then
								top_index = A + ret_num - 1
							else
								ret_num = C - 1
							end

							table.move(ret_list, 1, ret_num, A, memory)
						else
							--[[SETUPVAL]]
							local uv = upvals[inst.B]

							uv.store[uv.index] = memory[inst.A]
						end
					else
						--[[MUL]]
						local lhs, rhs

						if inst.is_KB then
							lhs = inst.const_B
						else
							lhs = memory[inst.B]
						end

						if inst.is_KC then
							rhs = inst.const_C
						else
							rhs = memory[inst.C]
						end

						memory[inst.A] = lhs * rhs
					end
				elseif op > 13 then
					if op < 16 then
						if op > 14 then
							--[[TAILCALL]]
							local A = inst.A
							local B = inst.B
							local params

							if B == 0 then
								params = top_index - A
							else
								params = B - 1
							end

							close_lua_upvalues(open_list, 0)

							return memory[A](table.unpack(memory, A + 1, A + params))
						else
							--[[SETTABLE]]
							local index, value

							if inst.is_KB then
								index = inst.const_B
							else
								index = memory[inst.B]
							end

							if inst.is_KC then
								value = inst.const_C
							else
								value = memory[inst.C]
							end

							memory[inst.A][index] = value
						end
					elseif op > 16 then
						--[[NEWTABLE]]
						memory[inst.A] = {}
					else
						--[[DIV]]
						local lhs, rhs

						if inst.is_KB then
							lhs = inst.const_B
						else
							lhs = memory[inst.B]
						end

						if inst.is_KC then
							rhs = inst.const_C
						else
							rhs = memory[inst.C]
						end

						memory[inst.A] = lhs / rhs
					end
				else
					--[[LOADK]]
					memory[inst.A] = inst.const
				end
			else
				--[[FORLOOP]]
				local A = inst.A
				local step = memory[A + 2]
				local index = memory[A] + step
				local limit = memory[A + 1]
				local loops

				if step == math.abs(step) then
					loops = index <= limit
				else
					loops = index >= limit
				end

				if loops then
					memory[A] = index
					memory[A + 3] = index
					pc = pc + inst.sBx
				end
			end
		elseif op > 18 then
			if op < 28 then
				if op < 23 then
					if op < 20 then
						--[[LEN]]
						memory[inst.A] = #memory[inst.B]
					elseif op > 20 then
						if op < 22 then
							--[[RETURN]]
							local A = inst.A
							local B = inst.B
							local len

							if B == 0 then
								len = top_index - A + 1
							else
								len = B - 1
							end

							close_lua_upvalues(open_list, 0)

							return table.unpack(memory, A, A + len - 1)
						else
							--[[CONCAT]]
							local B = inst.B
							local str = memory[B]

							for i = B + 1, inst.C do
								str = str .. memory[i]
							end

							memory[inst.A] = str
						end
					else
						--[[MOD]]
						local lhs, rhs

						if inst.is_KB then
							lhs = inst.const_B
						else
							lhs = memory[inst.B]
						end

						if inst.is_KC then
							rhs = inst.const_C
						else
							rhs = memory[inst.C]
						end

						memory[inst.A] = lhs % rhs
					end
				elseif op > 23 then
					if op < 26 then
						if op > 24 then
							--[[CLOSE]]
							close_lua_upvalues(open_list, inst.A)
						else
							--[[EQ]]
							local lhs, rhs

							if inst.is_KB then
								lhs = inst.const_B
							else
								lhs = memory[inst.B]
							end

							if inst.is_KC then
								rhs = inst.const_C
							else
								rhs = memory[inst.C]
							end

							if (lhs == rhs) == (inst.A ~= 0) then
								pc = pc + code[pc].sBx
							end

							pc = pc + 1
						end
					elseif op > 26 then
						--[[LT]]
						local lhs, rhs

						if inst.is_KB then
							lhs = inst.const_B
						else
							lhs = memory[inst.B]
						end

						if inst.is_KC then
							rhs = inst.const_C
						else
							rhs = memory[inst.C]
						end

						if (lhs < rhs) == (inst.A ~= 0) then
							pc = pc + code[pc].sBx
						end

						pc = pc + 1
					else
						--[[POW]]
						local lhs, rhs

						if inst.is_KB then
							lhs = inst.const_B
						else
							lhs = memory[inst.B]
						end

						if inst.is_KC then
							rhs = inst.const_C
						else
							rhs = memory[inst.C]
						end

						memory[inst.A] = lhs ^ rhs
					end
				else
					--[[LOADBOOL]]
					memory[inst.A] = inst.B ~= 0

					if inst.C ~= 0 then
						pc = pc + 1
					end
				end
			elseif op > 28 then
				if op < 33 then
					if op < 30 then
						--[[LE]]
						local lhs, rhs

						if inst.is_KB then
							lhs = inst.const_B
						else
							lhs = memory[inst.B]
						end

						if inst.is_KC then
							rhs = inst.const_C
						else
							rhs = memory[inst.C]
						end

						if (lhs <= rhs) == (inst.A ~= 0) then
							pc = pc + code[pc].sBx
						end

						pc = pc + 1
					elseif op > 30 then
						if op < 32 then
							--[[CLOSURE]]
							local sub = subs[inst.Bx + 1] -- offset for 1 based index
							local nups = sub.num_upval
							local uvlist

							if nups ~= 0 then
								uvlist = {}

								for i = 1, nups do
									local pseudo = code[pc + i - 1]

									if pseudo.op == OPCODE_RM[0] then -- @MOVE
										uvlist[i - 1] = open_lua_upvalue(open_list, pseudo.B, memory)
									elseif pseudo.op == OPCODE_RM[4] then -- @GETUPVAL
										uvlist[i - 1] = upvals[pseudo.B]
									end
								end

								pc = pc + nups
							end

							memory[inst.A] = lua_wrap_state(sub, env, uvlist)
						else
							--[[TESTSET]]
							local A = inst.A
							local B = inst.B

							if (not memory[B]) ~= (inst.C ~= 0) then
								memory[A] = memory[B]
								pc = pc + code[pc].sBx
							end
							pc = pc + 1
						end
					else
						--[[UNM]]
						memory[inst.A] = -memory[inst.B]
					end
				elseif op > 33 then
					if op < 36 then
						if op > 34 then
							--[[VARARG]]
							local A = inst.A
							local len = inst.B

							if len == 0 then
								len = vararg.len
								top_index = A + len - 1
							end

							table.move(vararg.list, 1, len, A, memory)
						else
							--[[FORPREP]]
							local A = inst.A
							local init, limit, step

							init = assert(tonumber(memory[A]), "`for` initial value must be a number")
							limit = assert(tonumber(memory[A + 1]), "`for` limit must be a number")
							step = assert(tonumber(memory[A + 2]), "`for` step must be a number")

							memory[A] = init - step
							memory[A + 1] = limit
							memory[A + 2] = step

							pc = pc + inst.sBx
						end
					elseif op > 36 then
						--[[SETLIST]]
						local A = inst.A
						local C = inst.C
						local len = inst.B
						local tab = memory[A]
						local offset

						if len == 0 then
							len = top_index - A
						end

						if C == 0 then
							C = inst[pc].value
							pc = pc + 1
						end

						offset = (C - 1) * FIELDS_PER_FLUSH

						table.move(memory, A + 1, A + len, offset + 1, tab)
					else
						--[[NOT]]
						memory[inst.A] = not memory[inst.B]
					end
				else
					--[[TEST]]
					if (not memory[inst.A]) ~= (inst.C ~= 0) then
						pc = pc + code[pc].sBx
					end
					pc = pc + 1
				end
			else
				--[[TFORLOOP]]
				local A = inst.A
				local base = A + 3

				local vals = { memory[A](memory[A + 1], memory[A + 2]) }

				table.move(vals, 1, inst.C, base, memory)

				if memory[base] ~= nil then
					memory[A + 2] = memory[base]
					pc = pc + code[pc].sBx
				end

				pc = pc + 1
			end
		else
			--[[JMP]]
			pc = pc + inst.sBx
		end

		state.pc = pc
	end
end

function lua_wrap_state(proto, env, upval)
	local function wrapped(...)
		local passed = table.pack(...)
		local memory = table.create(proto.max_stack)
		local vararg = { len = 0, list = {} }

		table.move(passed, 1, proto.num_param, 0, memory)

		if proto.num_param < passed.n then
			local start = proto.num_param + 1
			local len = passed.n - proto.num_param

			vararg.len = len
			table.move(passed, start, start + len - 1, 1, vararg.list)
		end

		local state = { vararg = vararg, memory = memory, code = proto.code, subs = proto.subs, pc = 1 }

		local result = table.pack(pcall(run_lua_func, state, env, upval))

		if result[1] then
			return table.unpack(result, 2, result.n)
		else
			local failed = { pc = state.pc, source = proto.source, lines = proto.lines }

			on_lua_error(failed, result[2])

			return
		end
	end

	return wrapped
end

return function(bCode, env)
	return lua_wrap_state(lua_bc_to_state(bCode), env or getfenv(0))
end
]]></ProtectedString>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                </Properties>
            </Item>
            <Item class="ModuleScript" referent="RBX4CD2BE88049B404FB263BDBC8ABCF4D9">
                <Properties>
                    <BinaryString name="AttributesSerialize"></BinaryString>
                    <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                    <bool name="DefinesCapabilities">false</bool>
                    <Content name="LinkedSource"><null></null></Content>
                    <string name="Name">Yueliang</string>
                    <string name="ScriptGuid">{49C94FD8-1396-4D53-8B2E-3600BB6892A5}</string>
                    <ProtectedString name="Source"><![CDATA[-- Adapted from the amazing Yueliang project
-- http://yueliang.luaforge.net/

--[[--------------------------------------------------------------------

luac.lua
Primitive luac in Lua
This file is part of Yueliang.

Copyright (c) 2005-2007 Kein-Hong Man <khman@users.sf.net>
The COPYRIGHT file describes the conditions
under which this software may be distributed.

See the ChangeLog for more information.

----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- Notes:
-- * based on luac.lua in the test directory of the 5.1.2 distribution
-- * usage: lua luac.lua file.lua
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- load and initialize the required modules
------------------------------------------------------------------------
local luaZ = {}
local luaY = {}
local luaX = {}
local luaP = {}
local luaU = {}
local luaK = {}
local size_size_t = 8

-- currently asserts are enabled because the codebase hasn't been tested
-- much (if you don't want asserts, just comment them out)
local function lua_assert(test)
	if not test then
		error("assertion failed!")
	end
end

-- dofile("lzio.lua")

------------------------------------------------------------------------
-- * reader() should return a string, or nil if nothing else to parse.
--   Additional data can be set only during stream initialization
-- * Readers are handled in lauxlib.c, see luaL_load(file|buffer|string)
-- * LUAL_BUFFERSIZE=BUFSIZ=512 in make_getF() (located in luaconf.h)
-- * Original Reader typedef:
--   const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz);
-- * This Lua chunk reader implementation:
--   returns string or nil, no arguments to function
------------------------------------------------------------------------

------------------------------------------------------------------------
-- create a chunk reader from a source string
------------------------------------------------------------------------
function luaZ:make_getS(buff)
	local b = buff
	return function() -- chunk reader anonymous function here
		if not b then
			return nil
		end
		local data = b
		b = nil
		return data
	end
end

------------------------------------------------------------------------
-- create a chunk reader from a source file
------------------------------------------------------------------------
-- function luaZ:make_getF(filename)
--   local LUAL_BUFFERSIZE = 512
--   local h = io.open(filename, "r")
--   if not h then return nil end
--   return function() -- chunk reader anonymous function here
--     if not h or io.type(h) == "closed file" then return nil end
--     local buff = h:read(LUAL_BUFFERSIZE)
--     if not buff then h:close(); h = nil end
--     return buff
--   end
-- end

function luaZ:make_getF(source)
	local LUAL_BUFFERSIZE = 512
	local pos = 1

	return function() -- chunk reader anonymous function here
		local buff = source:sub(pos, pos + LUAL_BUFFERSIZE - 1)
		pos = math.min(#source + 1, pos + LUAL_BUFFERSIZE)
		return buff
	end
end

------------------------------------------------------------------------
-- creates a zio input stream
-- returns the ZIO structure, z
------------------------------------------------------------------------
function luaZ:init(reader, data)
	if not reader then
		return
	end
	local z = {}
	z.reader = reader
	z.data = data or ""
	z.name = name
	-- set up additional data for reading
	if not data or data == "" then
		z.n = 0
	else
		z.n = #data
	end
	z.p = 0
	return z
end

------------------------------------------------------------------------
-- fill up input buffer
------------------------------------------------------------------------
function luaZ:fill(z)
	local buff = z.reader()
	z.data = buff
	if not buff or buff == "" then
		return "EOZ"
	end
	z.n, z.p = #buff - 1, 1
	return string.sub(buff, 1, 1)
end

------------------------------------------------------------------------
-- get next character from the input stream
-- * local n, p are used to optimize code generation
------------------------------------------------------------------------
function luaZ:zgetc(z)
	local n, p = z.n, z.p + 1
	if n > 0 then
		z.n, z.p = n - 1, p
		return string.sub(z.data, p, p)
	else
		return self:fill(z)
	end
end

-- dofile("llex.lua")

-- FIRST_RESERVED is not required as tokens are manipulated as strings
-- TOKEN_LEN deleted; maximum length of a reserved word not needed

------------------------------------------------------------------------
-- "ORDER RESERVED" deleted; enumeration in one place: luaX.RESERVED
------------------------------------------------------------------------

-- terminal symbols denoted by reserved words: TK_AND to TK_WHILE
-- other terminal symbols: TK_NAME to TK_EOS
luaX.RESERVED = [[
TK_AND and
TK_BREAK break
TK_DO do
TK_ELSE else
TK_ELSEIF elseif
TK_END end
TK_FALSE false
TK_FOR for
TK_FUNCTION function
TK_IF if
TK_IN in
TK_LOCAL local
TK_NIL nil
TK_NOT not
TK_OR or
TK_REPEAT repeat
TK_RETURN return
TK_THEN then
TK_TRUE true
TK_UNTIL until
TK_WHILE while
TK_CONCAT ..
TK_DOTS ...
TK_EQ ==
TK_GE >=
TK_LE <=
TK_NE ~=
TK_NAME <name>
TK_NUMBER <number>
TK_STRING <string>
TK_EOS <eof>]]

-- NUM_RESERVED is not required; number of reserved words

--[[--------------------------------------------------------------------
-- Instead of passing seminfo, the Token struct (e.g. ls.t) is passed
-- so that lexer functions can use its table element, ls.t.seminfo
--
-- SemInfo (struct no longer needed, a mixed-type value is used)
--
-- Token (struct of ls.t and ls.lookahead):
--   token  -- token symbol
--   seminfo  -- semantics information
--
-- LexState (struct of ls; ls is initialized by luaX:setinput):
--   current  -- current character (charint)
--   linenumber  -- input line counter
--   lastline  -- line of last token 'consumed'
--   t  -- current token (table: struct Token)
--   lookahead  -- look ahead token (table: struct Token)
--   fs  -- 'FuncState' is private to the parser
--   L -- LuaState
--   z  -- input stream
--   buff  -- buffer for tokens
--   source  -- current source name
--   decpoint -- locale decimal point
--   nestlevel  -- level of nested non-terminals
----------------------------------------------------------------------]]

-- luaX.tokens (was luaX_tokens) is now a hash; see luaX:init

luaX.MAXSRC = 80
luaX.MAX_INT = 2147483645 -- constants from elsewhere (see above)
luaX.LUA_QS = "'%s'"
luaX.LUA_COMPAT_LSTR = 1
--luaX.MAX_SIZET = 4294967293

------------------------------------------------------------------------
-- initialize lexer
-- * original luaX_init has code to create and register token strings
-- * luaX.tokens: TK_* -> token
-- * luaX.enums:  token -> TK_* (used in luaX:llex)
------------------------------------------------------------------------
function luaX:init()
	local tokens, enums = {}, {}
	for v in string.gmatch(self.RESERVED, "[^\n]+") do
		local _, _, tok, str = string.find(v, "(%S+)%s+(%S+)")
		tokens[tok] = str
		enums[str] = tok
	end
	self.tokens = tokens
	self.enums = enums
end

------------------------------------------------------------------------
-- returns a suitably-formatted chunk name or id
-- * from lobject.c, used in llex.c and ldebug.c
-- * the result, out, is returned (was first argument)
------------------------------------------------------------------------
function luaX:chunkid(source, bufflen)
	local out
	local first = string.sub(source, 1, 1)
	if first == "=" then
		out = string.sub(source, 2, bufflen) -- remove first char
	else -- out = "source", or "...source"
		if first == "@" then
			source = string.sub(source, 2) -- skip the '@'
			bufflen = bufflen - #" '...' "
			local l = #source
			out = ""
			if l > bufflen then
				source = string.sub(source, 1 + l - bufflen) -- get last part of file name
				out = out .. "..."
			end
			out = out .. source
		else -- out = [string "string"]
			local len = string.find(source, "[\n\r]") -- stop at first newline
			len = len and (len - 1) or #source
			bufflen = bufflen - #' [string "..."] '
			if len > bufflen then
				len = bufflen
			end
			out = '[string "'
			if len < #source then -- must truncate?
				out = out .. string.sub(source, 1, len) .. "..."
			else
				out = out .. source
			end
			out = out .. '"]'
		end
	end
	return out
end

--[[--------------------------------------------------------------------
-- Support functions for lexer
-- * all lexer errors eventually reaches lexerror:
		 syntaxerror -> lexerror
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- look up token and return keyword if found (also called by parser)
------------------------------------------------------------------------
function luaX:token2str(ls, token)
	if string.sub(token, 1, 3) ~= "TK_" then
		if string.find(token, "%c") then
			return string.format("char(%d)", string.byte(token))
		end
		return token
	else
		return self.tokens[token]
	end
end

------------------------------------------------------------------------
-- throws a lexer error
-- * txtToken has been made local to luaX:lexerror
-- * can't communicate LUA_ERRSYNTAX, so it is unimplemented
------------------------------------------------------------------------
function luaX:lexerror(ls, msg, token)
	local function txtToken(ls, token)
		if token == "TK_NAME" or token == "TK_STRING" or token == "TK_NUMBER" then
			return ls.buff
		else
			return self:token2str(ls, token)
		end
	end
	local buff = self:chunkid(ls.source, self.MAXSRC)
	local msg = string.format("%s:%d: %s", buff, ls.linenumber, msg)
	if token then
		msg = string.format("%s near " .. self.LUA_QS, msg, txtToken(ls, token))
	end
	-- luaD_throw(ls->L, LUA_ERRSYNTAX)
	error(msg)
end

------------------------------------------------------------------------
-- throws a syntax error (mainly called by parser)
-- * ls.t.token has to be set by the function calling luaX:llex
--   (see luaX:next and luaX:lookahead elsewhere in this file)
------------------------------------------------------------------------
function luaX:syntaxerror(ls, msg)
	self:lexerror(ls, msg, ls.t.token)
end

------------------------------------------------------------------------
-- move on to next line
------------------------------------------------------------------------
function luaX:currIsNewline(ls)
	return ls.current == "\n" or ls.current == "\r"
end

function luaX:inclinenumber(ls)
	local old = ls.current
	-- lua_assert(currIsNewline(ls))
	self:nextc(ls) -- skip '\n' or '\r'
	if self:currIsNewline(ls) and ls.current ~= old then
		self:nextc(ls) -- skip '\n\r' or '\r\n'
	end
	ls.linenumber = ls.linenumber + 1
	if ls.linenumber >= self.MAX_INT then
		self:syntaxerror(ls, "chunk has too many lines")
	end
end

------------------------------------------------------------------------
-- initializes an input stream for lexing
-- * if ls (the lexer state) is passed as a table, then it is filled in,
--   otherwise it has to be retrieved as a return value
-- * LUA_MINBUFFER not used; buffer handling not required any more
------------------------------------------------------------------------
function luaX:setinput(L, ls, z, source)
	if not ls then
		ls = {}
	end -- create struct
	if not ls.lookahead then
		ls.lookahead = {}
	end
	if not ls.t then
		ls.t = {}
	end
	ls.decpoint = "."
	ls.L = L
	ls.lookahead.token = "TK_EOS" -- no look-ahead token
	ls.z = z
	ls.fs = nil
	ls.linenumber = 1
	ls.lastline = 1
	ls.source = source
	self:nextc(ls) -- read first char
end

--[[--------------------------------------------------------------------
-- LEXICAL ANALYZER
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- checks if current character read is found in the set 'set'
------------------------------------------------------------------------
function luaX:check_next(ls, set)
	if not string.find(set, ls.current, 1, 1) then
		return false
	end
	self:save_and_next(ls)
	return true
end

------------------------------------------------------------------------
-- retrieve next token, checking the lookahead buffer if necessary
-- * note that the macro next(ls) in llex.c is now luaX:nextc
-- * utilized used in lparser.c (various places)
------------------------------------------------------------------------
function luaX:next(ls)
	ls.lastline = ls.linenumber
	if ls.lookahead.token ~= "TK_EOS" then -- is there a look-ahead token?
		-- this must be copy-by-value
		ls.t.seminfo = ls.lookahead.seminfo -- use this one
		ls.t.token = ls.lookahead.token
		ls.lookahead.token = "TK_EOS" -- and discharge it
	else
		ls.t.token = self:llex(ls, ls.t) -- read next token
	end
end

------------------------------------------------------------------------
-- fill in the lookahead buffer
-- * utilized used in lparser.c:constructor
------------------------------------------------------------------------
function luaX:lookahead(ls)
	-- lua_assert(ls.lookahead.token == "TK_EOS")
	ls.lookahead.token = self:llex(ls, ls.lookahead)
end

------------------------------------------------------------------------
-- gets the next character and returns it
-- * this is the next() macro in llex.c; see notes at the beginning
------------------------------------------------------------------------
function luaX:nextc(ls)
	local c = luaZ:zgetc(ls.z)
	ls.current = c
	return c
end

------------------------------------------------------------------------
-- saves the given character into the token buffer
-- * buffer handling code removed, not used in this implementation
-- * test for maximum token buffer length not used, makes things faster
------------------------------------------------------------------------

function luaX:save(ls, c)
	local buff = ls.buff
	-- if you want to use this, please uncomment luaX.MAX_SIZET further up
	--if #buff > self.MAX_SIZET then
	--  self:lexerror(ls, "lexical element too long")
	--end
	ls.buff = buff .. c
end

------------------------------------------------------------------------
-- save current character into token buffer, grabs next character
-- * like luaX:nextc, returns the character read for convenience
------------------------------------------------------------------------
function luaX:save_and_next(ls)
	self:save(ls, ls.current)
	return self:nextc(ls)
end

------------------------------------------------------------------------
-- LUA_NUMBER
-- * luaX:read_numeral is the main lexer function to read a number
-- * luaX:str2d, luaX:buffreplace, luaX:trydecpoint are support functions
------------------------------------------------------------------------

------------------------------------------------------------------------
-- string to number converter (was luaO_str2d from lobject.c)
-- * returns the number, nil if fails (originally returns a boolean)
-- * conversion function originally lua_str2number(s,p), a macro which
--   maps to the strtod() function by default (from luaconf.h)
------------------------------------------------------------------------
function luaX:str2d(s)
	local result = tonumber(s)
	if result then
		return result
	end
	-- conversion failed
	if string.lower(string.sub(s, 1, 2)) == "0x" then -- maybe an hexadecimal constant?
		result = tonumber(s, 16)
		if result then
			return result
		end -- most common case
		-- Was: invalid trailing characters?
		-- In C, this function then skips over trailing spaces.
		-- true is returned if nothing else is found except for spaces.
		-- If there is still something else, then it returns a false.
		-- All this is not necessary using Lua's tonumber.
	end
	return nil
end

------------------------------------------------------------------------
-- single-character replacement, for locale-aware decimal points
------------------------------------------------------------------------
function luaX:buffreplace(ls, from, to)
	local result, buff = "", ls.buff
	for p = 1, #buff do
		local c = string.sub(buff, p, p)
		if c == from then
			c = to
		end
		result = result .. c
	end
	ls.buff = result
end

------------------------------------------------------------------------
-- Attempt to convert a number by translating '.' decimal points to
-- the decimal point character used by the current locale. This is not
-- needed in Yueliang as Lua's tonumber() is already locale-aware.
-- Instead, the code is here in case the user implements localeconv().
------------------------------------------------------------------------
function luaX:trydecpoint(ls, Token)
	-- format error: try to update decimal point separator
	local old = ls.decpoint
	-- translate the following to Lua if you implement localeconv():
	-- struct lconv *cv = localeconv();
	-- ls->decpoint = (cv ? cv->decimal_point[0] : '.');
	self:buffreplace(ls, old, ls.decpoint) -- try updated decimal separator
	local seminfo = self:str2d(ls.buff)
	Token.seminfo = seminfo
	if not seminfo then
		-- format error with correct decimal point: no more options
		self:buffreplace(ls, ls.decpoint, ".") -- undo change (for error message)
		self:lexerror(ls, "malformed number", "TK_NUMBER")
	end
end

------------------------------------------------------------------------
-- main number conversion function
-- * "^%w$" needed in the scan in order to detect "EOZ"
------------------------------------------------------------------------
function luaX:read_numeral(ls, Token)
	-- lua_assert(string.find(ls.current, "%d"))
	repeat
		self:save_and_next(ls)
	until string.find(ls.current, "%D") and ls.current ~= "."
	if self:check_next(ls, "Ee") then -- 'E'?
		self:check_next(ls, "+-") -- optional exponent sign
	end
	while string.find(ls.current, "^%w$") or ls.current == "_" do
		self:save_and_next(ls)
	end
	self:buffreplace(ls, ".", ls.decpoint) -- follow locale for decimal point
	local seminfo = self:str2d(ls.buff)
	Token.seminfo = seminfo
	if not seminfo then -- format error?
		self:trydecpoint(ls, Token) -- try to update decimal point separator
	end
end

------------------------------------------------------------------------
-- count separators ("=") in a long string delimiter
-- * used by luaX:read_long_string
------------------------------------------------------------------------
function luaX:skip_sep(ls)
	local count = 0
	local s = ls.current
	-- lua_assert(s == "[" or s == "]")
	self:save_and_next(ls)
	while ls.current == "=" do
		self:save_and_next(ls)
		count = count + 1
	end
	return (ls.current == s) and count or -count - 1
end

------------------------------------------------------------------------
-- reads a long string or long comment
------------------------------------------------------------------------
function luaX:read_long_string(ls, Token, sep)
	local cont = 0
	self:save_and_next(ls) -- skip 2nd '['
	if self:currIsNewline(ls) then -- string starts with a newline?
		self:inclinenumber(ls) -- skip it
	end
	while true do
		local c = ls.current
		if c == "EOZ" then
			self:lexerror(ls, Token and "unfinished long string" or "unfinished long comment", "TK_EOS")
		elseif c == "[" then
			--# compatibility code start
			if self.LUA_COMPAT_LSTR then
				if self:skip_sep(ls) == sep then
					self:save_and_next(ls) -- skip 2nd '['
					cont = cont + 1
					--# compatibility code start
					if self.LUA_COMPAT_LSTR == 1 then
						if sep == 0 then
							self:lexerror(ls, "nesting of [[...]] is deprecated", "[")
						end
					end
					--# compatibility code end
				end
			end
			--# compatibility code end
		elseif c == "]" then
			if self:skip_sep(ls) == sep then
				self:save_and_next(ls) -- skip 2nd ']'
				--# compatibility code start
				if self.LUA_COMPAT_LSTR and self.LUA_COMPAT_LSTR == 2 then
					cont = cont - 1
					if sep == 0 and cont >= 0 then
						break
					end
				end
				--# compatibility code end
				break
			end
		elseif self:currIsNewline(ls) then
			self:save(ls, "\n")
			self:inclinenumber(ls)
			if not Token then
				ls.buff = ""
			end -- avoid wasting space
		else -- default
			if Token then
				self:save_and_next(ls)
			else
				self:nextc(ls)
			end
		end --if c
	end --while
	if Token then
		local p = 3 + sep
		Token.seminfo = string.sub(ls.buff, p, -p)
	end
end

------------------------------------------------------------------------
-- reads a string
-- * has been restructured significantly compared to the original C code
------------------------------------------------------------------------

function luaX:read_string(ls, del, Token)
	self:save_and_next(ls)
	while ls.current ~= del do
		local c = ls.current
		if c == "EOZ" then
			self:lexerror(ls, "unfinished string", "TK_EOS")
		elseif self:currIsNewline(ls) then
			self:lexerror(ls, "unfinished string", "TK_STRING")
		elseif c == "\\" then
			c = self:nextc(ls) -- do not save the '\'
			if self:currIsNewline(ls) then -- go through
				self:save(ls, "\n")
				self:inclinenumber(ls)
			elseif c ~= "EOZ" then -- will raise an error next loop
				-- escapes handling greatly simplified here:
				local i = string.find("abfnrtv", c, 1, 1)
				if i then
					self:save(ls, string.sub("\a\b\f\n\r\t\v", i, i))
					self:nextc(ls)
				elseif not string.find(c, "%d") then
					self:save_and_next(ls) -- handles \\, \", \', and \?
				else -- \xxx
					c, i = 0, 0
					repeat
						c = 10 * c + ls.current
						self:nextc(ls)
						i = i + 1
					until i >= 3 or not string.find(ls.current, "%d")
					if c > 255 then -- UCHAR_MAX
						self:lexerror(ls, "escape sequence too large", "TK_STRING")
					end
					self:save(ls, string.char(c))
				end
			end
		else
			self:save_and_next(ls)
		end --if c
	end --while
	self:save_and_next(ls) -- skip delimiter
	Token.seminfo = string.sub(ls.buff, 2, -2)
end

------------------------------------------------------------------------
-- main lexer function
------------------------------------------------------------------------
function luaX:llex(ls, Token)
	ls.buff = ""
	while true do
		local c = ls.current
		----------------------------------------------------------------
		if self:currIsNewline(ls) then
			self:inclinenumber(ls)
			----------------------------------------------------------------
		elseif c == "-" then
			c = self:nextc(ls)
			if c ~= "-" then
				return "-"
			end
			-- else is a comment
			local sep = -1
			if self:nextc(ls) == "[" then
				sep = self:skip_sep(ls)
				ls.buff = "" -- 'skip_sep' may dirty the buffer
			end
			if sep >= 0 then
				self:read_long_string(ls, nil, sep) -- long comment
				ls.buff = ""
			else -- else short comment
				while not self:currIsNewline(ls) and ls.current ~= "EOZ" do
					self:nextc(ls)
				end
			end
			----------------------------------------------------------------
		elseif c == "[" then
			local sep = self:skip_sep(ls)
			if sep >= 0 then
				self:read_long_string(ls, Token, sep)
				return "TK_STRING"
			elseif sep == -1 then
				return "["
			else
				self:lexerror(ls, "invalid long string delimiter", "TK_STRING")
			end
			----------------------------------------------------------------
		elseif c == "=" then
			c = self:nextc(ls)
			if c ~= "=" then
				return "="
			else
				self:nextc(ls)
				return "TK_EQ"
			end
			----------------------------------------------------------------
		elseif c == "<" then
			c = self:nextc(ls)
			if c ~= "=" then
				return "<"
			else
				self:nextc(ls)
				return "TK_LE"
			end
			----------------------------------------------------------------
		elseif c == ">" then
			c = self:nextc(ls)
			if c ~= "=" then
				return ">"
			else
				self:nextc(ls)
				return "TK_GE"
			end
			----------------------------------------------------------------
		elseif c == "~" then
			c = self:nextc(ls)
			if c ~= "=" then
				return "~"
			else
				self:nextc(ls)
				return "TK_NE"
			end
			----------------------------------------------------------------
		elseif c == '"' or c == "'" then
			self:read_string(ls, c, Token)
			return "TK_STRING"
			----------------------------------------------------------------
		elseif c == "." then
			c = self:save_and_next(ls)
			if self:check_next(ls, ".") then
				if self:check_next(ls, ".") then
					return "TK_DOTS" -- ...
				else
					return "TK_CONCAT" -- ..
				end
			elseif not string.find(c, "%d") then
				return "."
			else
				self:read_numeral(ls, Token)
				return "TK_NUMBER"
			end
			----------------------------------------------------------------
		elseif c == "EOZ" then
			return "TK_EOS"
			----------------------------------------------------------------
		else -- default
			if string.find(c, "%s") then
				-- lua_assert(self:currIsNewline(ls))
				self:nextc(ls)
			elseif string.find(c, "%d") then
				self:read_numeral(ls, Token)
				return "TK_NUMBER"
			elseif string.find(c, "[_%a]") then
				-- identifier or reserved word
				repeat
					c = self:save_and_next(ls)
				until c == "EOZ" or not string.find(c, "[_%w]")
				local ts = ls.buff
				local tok = self.enums[ts]
				if tok then
					return tok
				end -- reserved word?
				Token.seminfo = ts
				return "TK_NAME"
			else
				self:nextc(ls)
				return c -- single-char tokens (+ - / ...)
			end
			----------------------------------------------------------------
		end --if c
	end --while
end

--dofile("lopcodes.lua")

--[[
===========================================================================
	We assume that instructions are unsigned numbers.
	All instructions have an opcode in the first 6 bits.
	Instructions can have the following fields:
				'A' : 8 bits
				'B' : 9 bits
				'C' : 9 bits
				'Bx' : 18 bits ('B' and 'C' together)
				'sBx' : signed Bx

	A signed argument is represented in excess K; that is, the number
	value is the unsigned value minus K. K is exactly the maximum value
	for that argument (so that -max is represented by 0, and +max is
	represented by 2*max), which is half the maximum for the corresponding
	unsigned argument.
===========================================================================
--]]

luaP.OpMode = { iABC = 0, iABx = 1, iAsBx = 2 } -- basic instruction format

------------------------------------------------------------------------
-- size and position of opcode arguments.
-- * WARNING size and position is hard-coded elsewhere in this script
------------------------------------------------------------------------
luaP.SIZE_C = 9
luaP.SIZE_B = 9
luaP.SIZE_Bx = luaP.SIZE_C + luaP.SIZE_B
luaP.SIZE_A = 8

luaP.SIZE_OP = 6

luaP.POS_OP = 0
luaP.POS_A = luaP.POS_OP + luaP.SIZE_OP
luaP.POS_C = luaP.POS_A + luaP.SIZE_A
luaP.POS_B = luaP.POS_C + luaP.SIZE_C
luaP.POS_Bx = luaP.POS_C

------------------------------------------------------------------------
-- limits for opcode arguments.
-- we use (signed) int to manipulate most arguments,
-- so they must fit in LUAI_BITSINT-1 bits (-1 for sign)
------------------------------------------------------------------------
-- removed "#if SIZE_Bx < BITS_INT-1" test, assume this script is
-- running on a Lua VM with double or int as LUA_NUMBER

luaP.MAXARG_Bx = math.ldexp(1, luaP.SIZE_Bx) - 1
luaP.MAXARG_sBx = math.floor(luaP.MAXARG_Bx / 2) -- 'sBx' is signed

luaP.MAXARG_A = math.ldexp(1, luaP.SIZE_A) - 1
luaP.MAXARG_B = math.ldexp(1, luaP.SIZE_B) - 1
luaP.MAXARG_C = math.ldexp(1, luaP.SIZE_C) - 1

-- creates a mask with 'n' 1 bits at position 'p'
-- MASK1(n,p) deleted, not required
-- creates a mask with 'n' 0 bits at position 'p'
-- MASK0(n,p) deleted, not required

--[[--------------------------------------------------------------------
	Visual representation for reference:

	 31    |    |     |            0      bit position
		+-----+-----+-----+----------+
		|  B  |  C  |  A  |  Opcode  |      iABC format
		+-----+-----+-----+----------+
		-  9  -  9  -  8  -    6     -      field sizes
		+-----+-----+-----+----------+
		|   [s]Bx   |  A  |  Opcode  |      iABx | iAsBx format
		+-----+-----+-----+----------+

----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- the following macros help to manipulate instructions
-- * changed to a table object representation, very clean compared to
--   the [nightmare] alternatives of using a number or a string
-- * Bx is a separate element from B and C, since there is never a need
--   to split Bx in the parser or code generator
------------------------------------------------------------------------

-- these accept or return opcodes in the form of string names
function luaP:GET_OPCODE(i)
	return self.ROpCode[i.OP]
end
function luaP:SET_OPCODE(i, o)
	i.OP = self.OpCode[o]
end

function luaP:GETARG_A(i)
	return i.A
end
function luaP:SETARG_A(i, u)
	i.A = u
end

function luaP:GETARG_B(i)
	return i.B
end
function luaP:SETARG_B(i, b)
	i.B = b
end

function luaP:GETARG_C(i)
	return i.C
end
function luaP:SETARG_C(i, b)
	i.C = b
end

function luaP:GETARG_Bx(i)
	return i.Bx
end
function luaP:SETARG_Bx(i, b)
	i.Bx = b
end

function luaP:GETARG_sBx(i)
	return i.Bx - self.MAXARG_sBx
end
function luaP:SETARG_sBx(i, b)
	i.Bx = b + self.MAXARG_sBx
end

function luaP:CREATE_ABC(o, a, b, c)
	return { OP = self.OpCode[o], A = a, B = b, C = c }
end

function luaP:CREATE_ABx(o, a, bc)
	return { OP = self.OpCode[o], A = a, Bx = bc }
end

------------------------------------------------------------------------
-- create an instruction from a number (for OP_SETLIST)
------------------------------------------------------------------------
function luaP:CREATE_Inst(c)
	local o = c % 64
	c = (c - o) / 64
	local a = c % 256
	c = (c - a) / 256
	return self:CREATE_ABx(o, a, c)
end

------------------------------------------------------------------------
-- returns a 4-char string little-endian encoded form of an instruction
------------------------------------------------------------------------
function luaP:Instruction(i)
	if i.Bx then
		-- change to OP/A/B/C format
		i.C = i.Bx % 512
		i.B = (i.Bx - i.C) / 512
	end
	local I = i.A * 64 + i.OP
	local c0 = I % 256
	I = i.C * 64 + (I - c0) / 256 -- 6 bits of A left
	local c1 = I % 256
	I = i.B * 128 + (I - c1) / 256 -- 7 bits of C left
	local c2 = I % 256
	local c3 = (I - c2) / 256
	return string.char(c0, c1, c2, c3)
end

------------------------------------------------------------------------
-- decodes a 4-char little-endian string into an instruction struct
------------------------------------------------------------------------
function luaP:DecodeInst(x)
	local byte = string.byte
	local i = {}
	local I = byte(x, 1)
	local op = I % 64
	i.OP = op
	I = byte(x, 2) * 4 + (I - op) / 64 -- 2 bits of c0 left
	local a = I % 256
	i.A = a
	I = byte(x, 3) * 4 + (I - a) / 256 -- 2 bits of c1 left
	local c = I % 512
	i.C = c
	i.B = byte(x, 4) * 2 + (I - c) / 512 -- 1 bits of c2 left
	local opmode = self.OpMode[tonumber(string.sub(self.opmodes[op + 1], 7, 7))]
	if opmode ~= "iABC" then
		i.Bx = i.B * 512 + i.C
	end
	return i
end

------------------------------------------------------------------------
-- Macros to operate RK indices
-- * these use arithmetic instead of bit ops
------------------------------------------------------------------------

-- this bit 1 means constant (0 means register)
luaP.BITRK = math.ldexp(1, luaP.SIZE_B - 1)

-- test whether value is a constant
function luaP:ISK(x)
	return x >= self.BITRK
end

-- gets the index of the constant
function luaP:INDEXK(r)
	return x - self.BITRK
end

luaP.MAXINDEXRK = luaP.BITRK - 1

-- code a constant index as a RK value
function luaP:RKASK(x)
	return x + self.BITRK
end

------------------------------------------------------------------------
-- invalid register that fits in 8 bits
------------------------------------------------------------------------
luaP.NO_REG = luaP.MAXARG_A

------------------------------------------------------------------------
-- R(x) - register
-- Kst(x) - constant (in constant table)
-- RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x)
------------------------------------------------------------------------

------------------------------------------------------------------------
-- grep "ORDER OP" if you change these enums
------------------------------------------------------------------------

--[[--------------------------------------------------------------------
Lua virtual machine opcodes (enum OpCode):
------------------------------------------------------------------------
name          args    description
------------------------------------------------------------------------
OP_MOVE       A B     R(A) := R(B)
OP_LOADK      A Bx    R(A) := Kst(Bx)
OP_LOADBOOL   A B C   R(A) := (Bool)B; if (C) pc++
OP_LOADNIL    A B     R(A) := ... := R(B) := nil
OP_GETUPVAL   A B     R(A) := UpValue[B]
OP_GETGLOBAL  A Bx    R(A) := Gbl[Kst(Bx)]
OP_GETTABLE   A B C   R(A) := R(B)[RK(C)]
OP_SETGLOBAL  A Bx    Gbl[Kst(Bx)] := R(A)
OP_SETUPVAL   A B     UpValue[B] := R(A)
OP_SETTABLE   A B C   R(A)[RK(B)] := RK(C)
OP_NEWTABLE   A B C   R(A) := {} (size = B,C)
OP_SELF       A B C   R(A+1) := R(B); R(A) := R(B)[RK(C)]
OP_ADD        A B C   R(A) := RK(B) + RK(C)
OP_SUB        A B C   R(A) := RK(B) - RK(C)
OP_MUL        A B C   R(A) := RK(B) * RK(C)
OP_DIV        A B C   R(A) := RK(B) / RK(C)
OP_MOD        A B C   R(A) := RK(B) % RK(C)
OP_POW        A B C   R(A) := RK(B) ^ RK(C)
OP_UNM        A B     R(A) := -R(B)
OP_NOT        A B     R(A) := not R(B)
OP_LEN        A B     R(A) := length of R(B)
OP_CONCAT     A B C   R(A) := R(B).. ... ..R(C)
OP_JMP        sBx     pc+=sBx
OP_EQ         A B C   if ((RK(B) == RK(C)) ~= A) then pc++
OP_LT         A B C   if ((RK(B) <  RK(C)) ~= A) then pc++
OP_LE         A B C   if ((RK(B) <= RK(C)) ~= A) then pc++
OP_TEST       A C     if not (R(A) <=> C) then pc++
OP_TESTSET    A B C   if (R(B) <=> C) then R(A) := R(B) else pc++
OP_CALL       A B C   R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
OP_TAILCALL   A B C   return R(A)(R(A+1), ... ,R(A+B-1))
OP_RETURN     A B     return R(A), ... ,R(A+B-2)  (see note)
OP_FORLOOP    A sBx   R(A)+=R(A+2);
											if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }
OP_FORPREP    A sBx   R(A)-=R(A+2); pc+=sBx
OP_TFORLOOP   A C     R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));
											if R(A+3) ~= nil then R(A+2)=R(A+3) else pc++
OP_SETLIST    A B C   R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B
OP_CLOSE      A       close all variables in the stack up to (>=) R(A)
OP_CLOSURE    A Bx    R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))
OP_VARARG     A B     R(A), R(A+1), ..., R(A+B-1) = vararg
----------------------------------------------------------------------]]

luaP.opnames = {} -- opcode names
luaP.OpCode = {} -- lookup name -> number
luaP.ROpCode = {} -- lookup number -> name

------------------------------------------------------------------------
-- ORDER OP
------------------------------------------------------------------------
local i = 0
for v in
	string.gmatch(
		[[
MOVE LOADK LOADBOOL LOADNIL GETUPVAL
GETGLOBAL GETTABLE SETGLOBAL SETUPVAL SETTABLE
NEWTABLE SELF ADD SUB MUL
DIV MOD POW UNM NOT
LEN CONCAT JMP EQ LT
LE TEST TESTSET CALL TAILCALL
RETURN FORLOOP FORPREP TFORLOOP SETLIST
CLOSE CLOSURE VARARG
]],
		"%S+"
	)
do
	local n = "OP_" .. v
	luaP.opnames[i] = v
	luaP.OpCode[n] = i
	luaP.ROpCode[i] = n
	i = i + 1
end
luaP.NUM_OPCODES = i

--[[
===========================================================================
	Notes:
	(*) In OP_CALL, if (B == 0) then B = top. C is the number of returns - 1,
			and can be 0: OP_CALL then sets 'top' to last_result+1, so
			next open instruction (OP_CALL, OP_RETURN, OP_SETLIST) may use 'top'.
	(*) In OP_VARARG, if (B == 0) then use actual number of varargs and
			set top (like in OP_CALL with C == 0).
	(*) In OP_RETURN, if (B == 0) then return up to 'top'
	(*) In OP_SETLIST, if (B == 0) then B = 'top';
			if (C == 0) then next 'instruction' is real C
	(*) For comparisons, A specifies what condition the test should accept
			(true or false).
	(*) All 'skips' (pc++) assume that next instruction is a jump
===========================================================================
--]]

--[[--------------------------------------------------------------------
	masks for instruction properties. The format is:
	bits 0-1: op mode
	bits 2-3: C arg mode
	bits 4-5: B arg mode
	bit 6: instruction set register A
	bit 7: operator is a test

	for OpArgMask:
	OpArgN - argument is not used
	OpArgU - argument is used
	OpArgR - argument is a register or a jump offset
	OpArgK - argument is a constant or register/constant
----------------------------------------------------------------------]]

-- was enum OpArgMask
luaP.OpArgMask = { OpArgN = 0, OpArgU = 1, OpArgR = 2, OpArgK = 3 }

------------------------------------------------------------------------
-- e.g. to compare with symbols, luaP:getOpMode(...) == luaP.OpCode.iABC
-- * accepts opcode parameter as strings, e.g. "OP_MOVE"
------------------------------------------------------------------------

function luaP:getOpMode(m)
	return self.opmodes[self.OpCode[m]] % 4
end

function luaP:getBMode(m)
	return math.floor(self.opmodes[self.OpCode[m]] / 16) % 4
end

function luaP:getCMode(m)
	return math.floor(self.opmodes[self.OpCode[m]] / 4) % 4
end

function luaP:testAMode(m)
	return math.floor(self.opmodes[self.OpCode[m]] / 64) % 2
end

function luaP:testTMode(m)
	return math.floor(self.opmodes[self.OpCode[m]] / 128)
end

-- luaP_opnames[] is set above, as the luaP.opnames table

-- number of list items to accumulate before a SETLIST instruction
luaP.LFIELDS_PER_FLUSH = 50

------------------------------------------------------------------------
-- build instruction properties array
-- * deliberately coded to look like the C equivalent
------------------------------------------------------------------------
local function opmode(t, a, b, c, m)
	local luaP = luaP
	return t * 128 + a * 64 + luaP.OpArgMask[b] * 16 + luaP.OpArgMask[c] * 4 + luaP.OpMode[m]
end

-- ORDER OP
luaP.opmodes = {
	-- T A B C mode opcode
	opmode(0, 1, "OpArgK", "OpArgN", "iABx"), -- OP_LOADK
	opmode(0, 1, "OpArgU", "OpArgU", "iABC"), -- OP_LOADBOOL
	opmode(0, 1, "OpArgR", "OpArgN", "iABC"), -- OP_LOADNIL
	opmode(0, 1, "OpArgU", "OpArgN", "iABC"), -- OP_GETUPVAL
	opmode(0, 1, "OpArgK", "OpArgN", "iABx"), -- OP_GETGLOBAL
	opmode(0, 1, "OpArgR", "OpArgK", "iABC"), -- OP_GETTABLE
	opmode(0, 0, "OpArgK", "OpArgN", "iABx"), -- OP_SETGLOBAL
	opmode(0, 0, "OpArgU", "OpArgN", "iABC"), -- OP_SETUPVAL
	opmode(0, 0, "OpArgK", "OpArgK", "iABC"), -- OP_SETTABLE
	opmode(0, 1, "OpArgU", "OpArgU", "iABC"), -- OP_NEWTABLE
	opmode(0, 1, "OpArgR", "OpArgK", "iABC"), -- OP_SELF
	opmode(0, 1, "OpArgK", "OpArgK", "iABC"), -- OP_ADD
	opmode(0, 1, "OpArgK", "OpArgK", "iABC"), -- OP_SUB
	opmode(0, 1, "OpArgK", "OpArgK", "iABC"), -- OP_MUL
	opmode(0, 1, "OpArgK", "OpArgK", "iABC"), -- OP_DIV
	opmode(0, 1, "OpArgK", "OpArgK", "iABC"), -- OP_MOD
	opmode(0, 1, "OpArgK", "OpArgK", "iABC"), -- OP_POW
	opmode(0, 1, "OpArgR", "OpArgN", "iABC"), -- OP_UNM
	opmode(0, 1, "OpArgR", "OpArgN", "iABC"), -- OP_NOT
	opmode(0, 1, "OpArgR", "OpArgN", "iABC"), -- OP_LEN
	opmode(0, 1, "OpArgR", "OpArgR", "iABC"), -- OP_CONCAT
	opmode(0, 0, "OpArgR", "OpArgN", "iAsBx"), -- OP_JMP
	opmode(1, 0, "OpArgK", "OpArgK", "iABC"), -- OP_EQ
	opmode(1, 0, "OpArgK", "OpArgK", "iABC"), -- OP_LT
	opmode(1, 0, "OpArgK", "OpArgK", "iABC"), -- OP_LE
	opmode(1, 1, "OpArgR", "OpArgU", "iABC"), -- OP_TEST
	opmode(1, 1, "OpArgR", "OpArgU", "iABC"), -- OP_TESTSET
	opmode(0, 1, "OpArgU", "OpArgU", "iABC"), -- OP_CALL
	opmode(0, 1, "OpArgU", "OpArgU", "iABC"), -- OP_TAILCALL
	opmode(0, 0, "OpArgU", "OpArgN", "iABC"), -- OP_RETURN
	opmode(0, 1, "OpArgR", "OpArgN", "iAsBx"), -- OP_FORLOOP
	opmode(0, 1, "OpArgR", "OpArgN", "iAsBx"), -- OP_FORPREP
	opmode(1, 0, "OpArgN", "OpArgU", "iABC"), -- OP_TFORLOOP
	opmode(0, 0, "OpArgU", "OpArgU", "iABC"), -- OP_SETLIST
	opmode(0, 0, "OpArgN", "OpArgN", "iABC"), -- OP_CLOSE
	opmode(0, 1, "OpArgU", "OpArgN", "iABx"), -- OP_CLOSURE
	opmode(0, 1, "OpArgU", "OpArgN", "iABC"), -- OP_VARARG
}
-- an awkward way to set a zero-indexed table...
luaP.opmodes[0] = opmode(0, 1, "OpArgR", "OpArgN", "iABC") -- OP_MOVE

--dofile("ldump.lua")

--requires luaP

-- mark for precompiled code ('<esc>Lua') (from lua.h)
luaU.LUA_SIGNATURE = "\27Lua"

-- constants used by dumper (from lua.h)
luaU.LUA_TNUMBER = 3
luaU.LUA_TSTRING = 4
luaU.LUA_TNIL = 0
luaU.LUA_TBOOLEAN = 1
luaU.LUA_TNONE = -1

-- constants for header of binary files (from lundump.h)
luaU.LUAC_VERSION = 0x51 -- this is Lua 5.1
luaU.LUAC_FORMAT = 0 -- this is the official format
luaU.LUAC_HEADERSIZE = 12 -- size of header of binary files

--[[--------------------------------------------------------------------
-- Additional functions to handle chunk writing
-- * to use make_setS and make_setF, see test_ldump.lua elsewhere
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- create a chunk writer that writes to a string
-- * returns the writer function and a table containing the string
-- * to get the final result, look in buff.data
------------------------------------------------------------------------
function luaU:make_setS()
	local buff = {}
	buff.data = ""
	local writer = function(s, buff) -- chunk writer
		if not s then
			return 0
		end
		buff.data = buff.data .. s
		-- print (#buff.data, #s, string.byte(s,1,1), s)
		return 0
	end
	return writer, buff
end

------------------------------------------------------------------------
-- create a chunk writer that writes to a file
-- * returns the writer function and a table containing the file handle
-- * if a nil is passed, then writer should close the open file
------------------------------------------------------------------------
function luaU:make_setF(filename)
	local buff = {}
	buff.h = io.open(filename, "wb")
	if not buff.h then
		return nil
	end
	local writer = function(s, buff) -- chunk writer
		if not buff.h then
			return 0
		end
		if not s then
			if buff.h:close() then
				return 0
			end
		else
			if buff.h:write(s) then
				return 0
			end
		end
		return 1
	end
	return writer, buff
end

------------------------------------------------------------------------
-- works like the lobject.h version except that TObject used in these
-- scripts only has a 'value' field, no 'tt' field (native types used)
------------------------------------------------------------------------
function luaU:ttype(o)
	local tt = type(o.value)
	if tt == "number" then
		return self.LUA_TNUMBER
	elseif tt == "string" then
		return self.LUA_TSTRING
	elseif tt == "nil" then
		return self.LUA_TNIL
	elseif tt == "boolean" then
		return self.LUA_TBOOLEAN
	else
		return self.LUA_TNONE -- the rest should not appear
	end
end

-----------------------------------------------------------------------
-- converts a IEEE754 double number to an 8-byte little-endian string
-- * luaU:from_double() and luaU:from_int() are adapted from ChunkBake
-- * supports +/- Infinity, but not denormals or NaNs
-----------------------------------------------------------------------
function luaU:from_double(x)
	local function grab_byte(v)
		local c = v % 256
		return (v - c) / 256, string.char(c)
	end
	local sign = 0
	if x < 0 then
		sign = 1
		x = -x
	end
	local mantissa, exponent = math.frexp(x)
	if x == 0 then -- zero
		mantissa, exponent = 0, 0
	elseif x == 1 / 0 then
		mantissa, exponent = 0, 2047
	else
		mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53)
		exponent = exponent + 1022
	end
	local v, byte = "" -- convert to bytes
	x = math.floor(mantissa)
	for i = 1, 6 do
		x, byte = grab_byte(x)
		v = v .. byte -- 47:0
	end
	x, byte = grab_byte(exponent * 16 + x)
	v = v .. byte -- 55:48
	x, byte = grab_byte(sign * 128 + x)
	v = v .. byte -- 63:56
	return v
end

-----------------------------------------------------------------------
-- converts a number to a little-endian 32-bit integer string
-- * input value assumed to not overflow, can be signed/unsigned
-----------------------------------------------------------------------
function luaU:from_int(x)
	local v = ""
	x = math.floor(x)
	if x < 0 then
		x = 4294967296 + x
	end -- ULONG_MAX+1
	for i = 1, 4 do
		local c = x % 256
		v = v .. string.char(c)
		x = math.floor(x / 256)
	end
	return v
end

--[[--------------------------------------------------------------------
-- Functions to make a binary chunk
-- * many functions have the size parameter removed, since output is
--   in the form of a string and some sizes are implicit or hard-coded
----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- struct DumpState:
--   L  -- lua_State (not used in this script)
--   writer  -- lua_Writer (chunk writer function)
--   data  -- void* (chunk writer context or data already written)
--   strip  -- if true, don't write any debug information
--   status  -- if non-zero, an error has occured
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- dumps a block of bytes
-- * lua_unlock(D.L), lua_lock(D.L) unused
------------------------------------------------------------------------
function luaU:DumpBlock(b, D)
	if D.status == 0 then
		-- lua_unlock(D->L);
		D.status = D.write(b, D.data)
		-- lua_lock(D->L);
	end
end

------------------------------------------------------------------------
-- dumps a char
------------------------------------------------------------------------
function luaU:DumpChar(y, D)
	self:DumpBlock(string.char(y), D)
end

------------------------------------------------------------------------
-- dumps a 32-bit signed or unsigned integer (for int) (hard-coded)
------------------------------------------------------------------------
function luaU:DumpInt(x, D)
	self:DumpBlock(self:from_int(x), D)
end

------------------------------------------------------------------------
-- dumps a 32-bit signed or unsigned integer (for int) (hard-coded)
------------------------------------------------------------------------
function luaU:DumpSizeT(x, D)
	self:DumpBlock(self:from_int(x), D)
	if size_size_t == 8 then
		self:DumpBlock(self:from_int(0), D)
	end
end

------------------------------------------------------------------------
-- dumps a lua_Number (hard-coded as a double)
------------------------------------------------------------------------
function luaU:DumpNumber(x, D)
	self:DumpBlock(self:from_double(x), D)
end

------------------------------------------------------------------------
-- dumps a Lua string (size type is hard-coded)
------------------------------------------------------------------------
function luaU:DumpString(s, D)
	if s == nil then
		self:DumpSizeT(0, D)
	else
		s = s .. "\0" -- include trailing '\0'
		self:DumpSizeT(#s, D)
		self:DumpBlock(s, D)
	end
end

------------------------------------------------------------------------
-- dumps instruction block from function prototype
------------------------------------------------------------------------
function luaU:DumpCode(f, D)
	local n = f.sizecode
	--was DumpVector
	self:DumpInt(n, D)
	for i = 0, n - 1 do
		self:DumpBlock(luaP:Instruction(f.code[i]), D)
	end
end

------------------------------------------------------------------------
-- dump constant pool from function prototype
-- * bvalue(o), nvalue(o) and rawtsvalue(o) macros removed
------------------------------------------------------------------------
function luaU:DumpConstants(f, D)
	local n = f.sizek
	self:DumpInt(n, D)
	for i = 0, n - 1 do
		local o = f.k[i] -- TValue
		local tt = self:ttype(o)
		self:DumpChar(tt, D)
		if tt == self.LUA_TNIL then
		elseif tt == self.LUA_TBOOLEAN then
			self:DumpChar(o.value and 1 or 0, D)
		elseif tt == self.LUA_TNUMBER then
			self:DumpNumber(o.value, D)
		elseif tt == self.LUA_TSTRING then
			self:DumpString(o.value, D)
		else
			--lua_assert(0)  -- cannot happen
		end
	end
	n = f.sizep
	self:DumpInt(n, D)
	for i = 0, n - 1 do
		self:DumpFunction(f.p[i], f.source, D)
	end
end

------------------------------------------------------------------------
-- dump debug information
------------------------------------------------------------------------
function luaU:DumpDebug(f, D)
	local n
	n = D.strip and 0 or f.sizelineinfo -- dump line information
	--was DumpVector
	self:DumpInt(n, D)
	for i = 0, n - 1 do
		self:DumpInt(f.lineinfo[i], D)
	end
	n = D.strip and 0 or f.sizelocvars -- dump local information
	self:DumpInt(n, D)
	for i = 0, n - 1 do
		self:DumpString(f.locvars[i].varname, D)
		self:DumpInt(f.locvars[i].startpc, D)
		self:DumpInt(f.locvars[i].endpc, D)
	end
	n = D.strip and 0 or f.sizeupvalues -- dump upvalue information
	self:DumpInt(n, D)
	for i = 0, n - 1 do
		self:DumpString(f.upvalues[i], D)
	end
end

------------------------------------------------------------------------
-- dump child function prototypes from function prototype
------------------------------------------------------------------------
function luaU:DumpFunction(f, p, D)
	local source = f.source
	if source == p or D.strip then
		source = nil
	end
	self:DumpString(source, D)
	self:DumpInt(f.lineDefined, D)
	self:DumpInt(f.lastlinedefined, D)
	self:DumpChar(f.nups, D)
	self:DumpChar(f.numparams, D)
	self:DumpChar(f.is_vararg, D)
	self:DumpChar(f.maxstacksize, D)
	self:DumpCode(f, D)
	self:DumpConstants(f, D)
	self:DumpDebug(f, D)
end

------------------------------------------------------------------------
-- dump Lua header section (some sizes hard-coded)
------------------------------------------------------------------------
function luaU:DumpHeader(D)
	local h = self:header()
	assert(#h == self.LUAC_HEADERSIZE) -- fixed buffer now an assert
	self:DumpBlock(h, D)
end

------------------------------------------------------------------------
-- make header (from lundump.c)
-- returns the header string
------------------------------------------------------------------------
function luaU:header()
	local x = 1
	return self.LUA_SIGNATURE
		.. string.char(
			self.LUAC_VERSION,
			self.LUAC_FORMAT,
			x, -- endianness (1=little)
			4, -- sizeof(int)
			size_size_t, -- sizeof(size_t)
			4, -- sizeof(Instruction)
			8, -- sizeof(lua_Number)
			0
		) -- is lua_Number integral?
end

------------------------------------------------------------------------
-- dump Lua function as precompiled chunk
-- (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip)
-- * w, data are created from make_setS, make_setF
------------------------------------------------------------------------
function luaU:dump(L, f, w, data, strip)
	local D = {} -- DumpState
	D.L = L
	D.write = w
	D.data = data
	D.strip = strip
	D.status = 0
	self:DumpHeader(D)
	self:DumpFunction(f, nil, D)
	-- added: for a chunk writer writing to a file, this final call with
	-- nil data is to indicate to the writer to close the file
	D.write(nil, D.data)
	return D.status
end

--dofile("lcode.lua")

------------------------------------------------------------------------
-- constants used by code generator
------------------------------------------------------------------------
-- maximum stack for a Lua function
luaK.MAXSTACK = 250 -- (from llimits.h)

--[[--------------------------------------------------------------------
-- other functions
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- emulation of TValue macros (these are from lobject.h)
-- * TValue is a table since lcode passes references around
-- * tt member field removed, using Lua's type() instead
-- * for setsvalue, sethvalue, parameter L (deleted here) in lobject.h
--   is used in an assert for testing, see checkliveness(g,obj)
------------------------------------------------------------------------
function luaK:ttisnumber(o)
	if o then
		return type(o.value) == "number"
	else
		return false
	end
end
function luaK:nvalue(o)
	return o.value
end
function luaK:setnilvalue(o)
	o.value = nil
end
function luaK:setsvalue(o, x)
	o.value = x
end
luaK.setnvalue = luaK.setsvalue
luaK.sethvalue = luaK.setsvalue
luaK.setbvalue = luaK.setsvalue

------------------------------------------------------------------------
-- The luai_num* macros define the primitive operations over numbers.
-- * this is not the entire set of primitive operations from luaconf.h
-- * used in luaK:constfolding()
------------------------------------------------------------------------
function luaK:numadd(a, b)
	return a + b
end
function luaK:numsub(a, b)
	return a - b
end
function luaK:nummul(a, b)
	return a * b
end
function luaK:numdiv(a, b)
	return a / b
end
function luaK:nummod(a, b)
	return a % b
end
-- ((a) - floor((a)/(b))*(b)) /* actual, for reference */
function luaK:numpow(a, b)
	return a ^ b
end
function luaK:numunm(a)
	return -a
end
function luaK:numisnan(a)
	return not a == a
end
-- a NaN cannot equal another NaN

--[[--------------------------------------------------------------------
-- code generator functions
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- Marks the end of a patch list. It is an invalid value both as an absolute
-- address, and as a list link (would link an element to itself).
------------------------------------------------------------------------
luaK.NO_JUMP = -1

------------------------------------------------------------------------
-- grep "ORDER OPR" if you change these enums
------------------------------------------------------------------------
luaK.BinOpr = {
	OPR_ADD = 0,
	OPR_SUB = 1,
	OPR_MUL = 2,
	OPR_DIV = 3,
	OPR_MOD = 4,
	OPR_POW = 5,
	OPR_CONCAT = 6,
	OPR_NE = 7,
	OPR_EQ = 8,
	OPR_LT = 9,
	OPR_LE = 10,
	OPR_GT = 11,
	OPR_GE = 12,
	OPR_AND = 13,
	OPR_OR = 14,
	OPR_NOBINOPR = 15,
}

-- * UnOpr is used by luaK:prefix's op argument, but not directly used
--   because the function receives the symbols as strings, e.g. "OPR_NOT"
luaK.UnOpr = {
	OPR_MINUS = 0,
	OPR_NOT = 1,
	OPR_LEN = 2,
	OPR_NOUNOPR = 3,
}

------------------------------------------------------------------------
-- returns the instruction object for given e (expdesc), was a macro
------------------------------------------------------------------------
function luaK:getcode(fs, e)
	return fs.f.code[e.info]
end

------------------------------------------------------------------------
-- codes an instruction with a signed Bx (sBx) field, was a macro
-- * used in luaK:jump(), (lparser) luaY:forbody()
------------------------------------------------------------------------
function luaK:codeAsBx(fs, o, A, sBx)
	return self:codeABx(fs, o, A, sBx + luaP.MAXARG_sBx)
end

------------------------------------------------------------------------
-- set the expdesc e instruction for multiple returns, was a macro
------------------------------------------------------------------------
function luaK:setmultret(fs, e)
	self:setreturns(fs, e, luaY.LUA_MULTRET)
end

------------------------------------------------------------------------
-- there is a jump if patch lists are not identical, was a macro
-- * used in luaK:exp2reg(), luaK:exp2anyreg(), luaK:exp2val()
------------------------------------------------------------------------
function luaK:hasjumps(e)
	return e.t ~= e.f
end

------------------------------------------------------------------------
-- true if the expression is a constant number (for constant folding)
-- * used in constfolding(), infix()
------------------------------------------------------------------------
function luaK:isnumeral(e)
	return e.k == "VKNUM" and e.t == self.NO_JUMP and e.f == self.NO_JUMP
end

------------------------------------------------------------------------
-- codes loading of nil, optimization done if consecutive locations
-- * used in luaK:discharge2reg(), (lparser) luaY:adjust_assign()
------------------------------------------------------------------------
function luaK:_nil(fs, from, n)
	if fs.pc > fs.lasttarget then -- no jumps to current position?
		if fs.pc == 0 then -- function start?
			if from >= fs.nactvar then
				return -- positions are already clean
			end
		else
			local previous = fs.f.code[fs.pc - 1]
			if luaP:GET_OPCODE(previous) == "OP_LOADNIL" then
				local pfrom = luaP:GETARG_A(previous)
				local pto = luaP:GETARG_B(previous)
				if pfrom <= from and from <= pto + 1 then -- can connect both?
					if from + n - 1 > pto then
						luaP:SETARG_B(previous, from + n - 1)
					end
					return
				end
			end
		end
	end
	self:codeABC(fs, "OP_LOADNIL", from, from + n - 1, 0) -- else no optimization
end

------------------------------------------------------------------------
--
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:jump(fs)
	local jpc = fs.jpc -- save list of jumps to here
	fs.jpc = self.NO_JUMP
	local j = self:codeAsBx(fs, "OP_JMP", 0, self.NO_JUMP)
	j = self:concat(fs, j, jpc) -- keep them on hold
	return j
end

------------------------------------------------------------------------
-- codes a RETURN instruction
-- * used in luaY:close_func(), luaY:retstat()
------------------------------------------------------------------------
function luaK:ret(fs, first, nret)
	self:codeABC(fs, "OP_RETURN", first, nret + 1, 0)
end

------------------------------------------------------------------------
--
-- * used in luaK:jumponcond(), luaK:codecomp()
------------------------------------------------------------------------
function luaK:condjump(fs, op, A, B, C)
	self:codeABC(fs, op, A, B, C)
	return self:jump(fs)
end

------------------------------------------------------------------------
--
-- * used in luaK:patchlistaux(), luaK:concat()
------------------------------------------------------------------------
function luaK:fixjump(fs, pc, dest)
	local jmp = fs.f.code[pc]
	local offset = dest - (pc + 1)
	lua_assert(dest ~= self.NO_JUMP)
	if math.abs(offset) > luaP.MAXARG_sBx then
		luaX:syntaxerror(fs.ls, "control structure too long")
	end
	luaP:SETARG_sBx(jmp, offset)
end

------------------------------------------------------------------------
-- returns current 'pc' and marks it as a jump target (to avoid wrong
-- optimizations with consecutive instructions not in the same basic block).
-- * used in multiple locations
-- * fs.lasttarget tested only by luaK:_nil() when optimizing OP_LOADNIL
------------------------------------------------------------------------
function luaK:getlabel(fs)
	fs.lasttarget = fs.pc
	return fs.pc
end

------------------------------------------------------------------------
--
-- * used in luaK:need_value(), luaK:removevalues(), luaK:patchlistaux(),
--   luaK:concat()
------------------------------------------------------------------------
function luaK:getjump(fs, pc)
	local offset = luaP:GETARG_sBx(fs.f.code[pc])
	if offset == self.NO_JUMP then -- point to itself represents end of list
		return self.NO_JUMP -- end of list
	else
		return (pc + 1) + offset -- turn offset into absolute position
	end
end

------------------------------------------------------------------------
--
-- * used in luaK:need_value(), luaK:patchtestreg(), luaK:invertjump()
------------------------------------------------------------------------
function luaK:getjumpcontrol(fs, pc)
	local pi = fs.f.code[pc]
	local ppi = fs.f.code[pc - 1]
	if pc >= 1 and luaP:testTMode(luaP:GET_OPCODE(ppi)) ~= 0 then
		return ppi
	else
		return pi
	end
end

------------------------------------------------------------------------
-- check whether list has any jump that do not produce a value
-- (or produce an inverted value)
-- * return value changed to boolean
-- * used only in luaK:exp2reg()
------------------------------------------------------------------------
function luaK:need_value(fs, list)
	while list ~= self.NO_JUMP do
		local i = self:getjumpcontrol(fs, list)
		if luaP:GET_OPCODE(i) ~= "OP_TESTSET" then
			return true
		end
		list = self:getjump(fs, list)
	end
	return false -- not found
end

------------------------------------------------------------------------
--
-- * used in luaK:removevalues(), luaK:patchlistaux()
------------------------------------------------------------------------
function luaK:patchtestreg(fs, node, reg)
	local i = self:getjumpcontrol(fs, node)
	if luaP:GET_OPCODE(i) ~= "OP_TESTSET" then
		return false -- cannot patch other instructions
	end
	if reg ~= luaP.NO_REG and reg ~= luaP:GETARG_B(i) then
		luaP:SETARG_A(i, reg)
	else -- no register to put value or register already has the value
		-- due to use of a table as i, i cannot be replaced by another table
		-- so the following is required; there is no change to ARG_C
		luaP:SET_OPCODE(i, "OP_TEST")
		local b = luaP:GETARG_B(i)
		luaP:SETARG_A(i, b)
		luaP:SETARG_B(i, 0)
		-- *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); /* C */
	end
	return true
end

------------------------------------------------------------------------
--
-- * used only in luaK:codenot()
------------------------------------------------------------------------
function luaK:removevalues(fs, list)
	while list ~= self.NO_JUMP do
		self:patchtestreg(fs, list, luaP.NO_REG)
		list = self:getjump(fs, list)
	end
end

------------------------------------------------------------------------
--
-- * used in luaK:dischargejpc(), luaK:patchlist(), luaK:exp2reg()
------------------------------------------------------------------------
function luaK:patchlistaux(fs, list, vtarget, reg, dtarget)
	while list ~= self.NO_JUMP do
		local _next = self:getjump(fs, list)
		if self:patchtestreg(fs, list, reg) then
			self:fixjump(fs, list, vtarget)
		else
			self:fixjump(fs, list, dtarget) -- jump to default target
		end
		list = _next
	end
end

------------------------------------------------------------------------
--
-- * used only in luaK:code()
------------------------------------------------------------------------
function luaK:dischargejpc(fs)
	self:patchlistaux(fs, fs.jpc, fs.pc, luaP.NO_REG, fs.pc)
	fs.jpc = self.NO_JUMP
end

------------------------------------------------------------------------
--
-- * used in (lparser) luaY:whilestat(), luaY:repeatstat(), luaY:forbody()
------------------------------------------------------------------------
function luaK:patchlist(fs, list, target)
	if target == fs.pc then
		self:patchtohere(fs, list)
	else
		lua_assert(target < fs.pc)
		self:patchlistaux(fs, list, target, luaP.NO_REG, target)
	end
end

------------------------------------------------------------------------
--
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:patchtohere(fs, list)
	self:getlabel(fs)
	fs.jpc = self:concat(fs, fs.jpc, list)
end

------------------------------------------------------------------------
-- * l1 was a pointer, now l1 is returned and callee assigns the value
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:concat(fs, l1, l2)
	if l2 == self.NO_JUMP then
		return l1
	elseif l1 == self.NO_JUMP then
		return l2
	else
		local list = l1
		local _next = self:getjump(fs, list)
		while _next ~= self.NO_JUMP do -- find last element
			list = _next
			_next = self:getjump(fs, list)
		end
		self:fixjump(fs, list, l2)
	end
	return l1
end

------------------------------------------------------------------------
--
-- * used in luaK:reserveregs(), (lparser) luaY:forlist()
------------------------------------------------------------------------
function luaK:checkstack(fs, n)
	local newstack = fs.freereg + n
	if newstack > fs.f.maxstacksize then
		if newstack >= self.MAXSTACK then
			luaX:syntaxerror(fs.ls, "function or expression too complex")
		end
		fs.f.maxstacksize = newstack
	end
end

------------------------------------------------------------------------
--
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:reserveregs(fs, n)
	self:checkstack(fs, n)
	fs.freereg = fs.freereg + n
end

------------------------------------------------------------------------
--
-- * used in luaK:freeexp(), luaK:dischargevars()
------------------------------------------------------------------------
function luaK:freereg(fs, reg)
	if not luaP:ISK(reg) and reg >= fs.nactvar then
		fs.freereg = fs.freereg - 1
		lua_assert(reg == fs.freereg)
	end
end

------------------------------------------------------------------------
--
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:freeexp(fs, e)
	if e.k == "VNONRELOC" then
		self:freereg(fs, e.info)
	end
end

------------------------------------------------------------------------
-- * TODO NOTE implementation is not 100% correct, since the assert fails
-- * luaH_set, setobj deleted; direct table access used instead
-- * used in luaK:stringK(), luaK:numberK(), luaK:boolK(), luaK:nilK()
------------------------------------------------------------------------
function luaK:addk(fs, k, v)
	local L = fs.L
	local idx = fs.h[k.value]
	--TValue *idx = luaH_set(L, fs->h, k); /* C */
	local f = fs.f
	if self:ttisnumber(idx) then
		--TODO this assert currently FAILS (last tested for 5.0.2)
		--lua_assert(fs.f.k[self:nvalue(idx)] == v)
		--lua_assert(luaO_rawequalObj(&fs->f->k[cast_int(nvalue(idx))], v)); /* C */
		return self:nvalue(idx)
	else -- constant not found; create a new entry
		idx = {}
		self:setnvalue(idx, fs.nk)
		fs.h[k.value] = idx
		-- setnvalue(idx, cast_num(fs->nk)); /* C */
		luaY:growvector(L, f.k, fs.nk, f.sizek, nil, luaP.MAXARG_Bx, "constant table overflow")
		-- loop to initialize empty f.k positions not required
		f.k[fs.nk] = v
		-- setobj(L, &f->k[fs->nk], v); /* C */
		-- luaC_barrier(L, f, v); /* GC */
		local nk = fs.nk
		fs.nk = fs.nk + 1
		return nk
	end
end

------------------------------------------------------------------------
-- creates and sets a string object
-- * used in (lparser) luaY:codestring(), luaY:singlevar()
------------------------------------------------------------------------
function luaK:stringK(fs, s)
	local o = {} -- TValue
	self:setsvalue(o, s)
	return self:addk(fs, o, o)
end

------------------------------------------------------------------------
-- creates and sets a number object
-- * used in luaK:prefix() for negative (or negation of) numbers
-- * used in (lparser) luaY:simpleexp(), luaY:fornum()
------------------------------------------------------------------------
function luaK:numberK(fs, r)
	local o = {} -- TValue
	self:setnvalue(o, r)
	return self:addk(fs, o, o)
end

------------------------------------------------------------------------
-- creates and sets a boolean object
-- * used only in luaK:exp2RK()
------------------------------------------------------------------------
function luaK:boolK(fs, b)
	local o = {} -- TValue
	self:setbvalue(o, b)
	return self:addk(fs, o, o)
end

------------------------------------------------------------------------
-- creates and sets a nil object
-- * used only in luaK:exp2RK()
------------------------------------------------------------------------
function luaK:nilK(fs)
	local k, v = {}, {} -- TValue
	self:setnilvalue(v)
	-- cannot use nil as key; instead use table itself to represent nil
	self:sethvalue(k, fs.h)
	return self:addk(fs, k, v)
end

------------------------------------------------------------------------
--
-- * used in luaK:setmultret(), (lparser) luaY:adjust_assign()
------------------------------------------------------------------------
function luaK:setreturns(fs, e, nresults)
	if e.k == "VCALL" then -- expression is an open function call?
		luaP:SETARG_C(self:getcode(fs, e), nresults + 1)
	elseif e.k == "VVARARG" then
		luaP:SETARG_B(self:getcode(fs, e), nresults + 1)
		luaP:SETARG_A(self:getcode(fs, e), fs.freereg)
		luaK:reserveregs(fs, 1)
	end
end

------------------------------------------------------------------------
--
-- * used in luaK:dischargevars(), (lparser) luaY:assignment()
------------------------------------------------------------------------
function luaK:setoneret(fs, e)
	if e.k == "VCALL" then -- expression is an open function call?
		e.k = "VNONRELOC"
		e.info = luaP:GETARG_A(self:getcode(fs, e))
	elseif e.k == "VVARARG" then
		luaP:SETARG_B(self:getcode(fs, e), 2)
		e.k = "VRELOCABLE" -- can relocate its simple result
	end
end

------------------------------------------------------------------------
--
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:dischargevars(fs, e)
	local k = e.k
	if k == "VLOCAL" then
		e.k = "VNONRELOC"
	elseif k == "VUPVAL" then
		e.info = self:codeABC(fs, "OP_GETUPVAL", 0, e.info, 0)
		e.k = "VRELOCABLE"
	elseif k == "VGLOBAL" then
		e.info = self:codeABx(fs, "OP_GETGLOBAL", 0, e.info)
		e.k = "VRELOCABLE"
	elseif k == "VINDEXED" then
		self:freereg(fs, e.aux)
		self:freereg(fs, e.info)
		e.info = self:codeABC(fs, "OP_GETTABLE", 0, e.info, e.aux)
		e.k = "VRELOCABLE"
	elseif k == "VVARARG" or k == "VCALL" then
		self:setoneret(fs, e)
	else
		-- there is one value available (somewhere)
	end
end

------------------------------------------------------------------------
--
-- * used only in luaK:exp2reg()
------------------------------------------------------------------------
function luaK:code_label(fs, A, b, jump)
	self:getlabel(fs) -- those instructions may be jump targets
	return self:codeABC(fs, "OP_LOADBOOL", A, b, jump)
end

------------------------------------------------------------------------
--
-- * used in luaK:discharge2anyreg(), luaK:exp2reg()
------------------------------------------------------------------------
function luaK:discharge2reg(fs, e, reg)
	self:dischargevars(fs, e)
	local k = e.k
	if k == "VNIL" then
		self:_nil(fs, reg, 1)
	elseif k == "VFALSE" or k == "VTRUE" then
		self:codeABC(fs, "OP_LOADBOOL", reg, (e.k == "VTRUE") and 1 or 0, 0)
	elseif k == "VK" then
		self:codeABx(fs, "OP_LOADK", reg, e.info)
	elseif k == "VKNUM" then
		self:codeABx(fs, "OP_LOADK", reg, self:numberK(fs, e.nval))
	elseif k == "VRELOCABLE" then
		local pc = self:getcode(fs, e)
		luaP:SETARG_A(pc, reg)
	elseif k == "VNONRELOC" then
		if reg ~= e.info then
			self:codeABC(fs, "OP_MOVE", reg, e.info, 0)
		end
	else
		lua_assert(e.k == "VVOID" or e.k == "VJMP")
		return -- nothing to do...
	end
	e.info = reg
	e.k = "VNONRELOC"
end

------------------------------------------------------------------------
--
-- * used in luaK:jumponcond(), luaK:codenot()
------------------------------------------------------------------------
function luaK:discharge2anyreg(fs, e)
	if e.k ~= "VNONRELOC" then
		self:reserveregs(fs, 1)
		self:discharge2reg(fs, e, fs.freereg - 1)
	end
end

------------------------------------------------------------------------
--
-- * used in luaK:exp2nextreg(), luaK:exp2anyreg(), luaK:storevar()
------------------------------------------------------------------------
function luaK:exp2reg(fs, e, reg)
	self:discharge2reg(fs, e, reg)
	if e.k == "VJMP" then
		e.t = self:concat(fs, e.t, e.info) -- put this jump in 't' list
	end
	if self:hasjumps(e) then
		local final -- position after whole expression
		local p_f = self.NO_JUMP -- position of an eventual LOAD false
		local p_t = self.NO_JUMP -- position of an eventual LOAD true
		if self:need_value(fs, e.t) or self:need_value(fs, e.f) then
			local fj = (e.k == "VJMP") and self.NO_JUMP or self:jump(fs)
			p_f = self:code_label(fs, reg, 0, 1)
			p_t = self:code_label(fs, reg, 1, 0)
			self:patchtohere(fs, fj)
		end
		final = self:getlabel(fs)
		self:patchlistaux(fs, e.f, final, reg, p_f)
		self:patchlistaux(fs, e.t, final, reg, p_t)
	end
	e.f, e.t = self.NO_JUMP, self.NO_JUMP
	e.info = reg
	e.k = "VNONRELOC"
end

------------------------------------------------------------------------
--
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:exp2nextreg(fs, e)
	self:dischargevars(fs, e)
	self:freeexp(fs, e)
	self:reserveregs(fs, 1)
	self:exp2reg(fs, e, fs.freereg - 1)
end

------------------------------------------------------------------------
--
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:exp2anyreg(fs, e)
	self:dischargevars(fs, e)
	if e.k == "VNONRELOC" then
		if not self:hasjumps(e) then -- exp is already in a register
			return e.info
		end
		if e.info >= fs.nactvar then -- reg. is not a local?
			self:exp2reg(fs, e, e.info) -- put value on it
			return e.info
		end
	end
	self:exp2nextreg(fs, e) -- default
	return e.info
end

------------------------------------------------------------------------
--
-- * used in luaK:exp2RK(), luaK:prefix(), luaK:posfix()
-- * used in (lparser) luaY:yindex()
------------------------------------------------------------------------
function luaK:exp2val(fs, e)
	if self:hasjumps(e) then
		self:exp2anyreg(fs, e)
	else
		self:dischargevars(fs, e)
	end
end

------------------------------------------------------------------------
--
-- * used in multiple locations
------------------------------------------------------------------------
function luaK:exp2RK(fs, e)
	self:exp2val(fs, e)
	local k = e.k
	if k == "VKNUM" or k == "VTRUE" or k == "VFALSE" or k == "VNIL" then
		if fs.nk <= luaP.MAXINDEXRK then -- constant fit in RK operand?
			-- converted from a 2-deep ternary operator expression
			if e.k == "VNIL" then
				e.info = self:nilK(fs)
			else
				e.info = (e.k == "VKNUM") and self:numberK(fs, e.nval) or self:boolK(fs, e.k == "VTRUE")
			end
			e.k = "VK"
			return luaP:RKASK(e.info)
		end
	elseif k == "VK" then
		if e.info <= luaP.MAXINDEXRK then -- constant fit in argC?
			return luaP:RKASK(e.info)
		end
	else
		-- default
	end
	-- not a constant in the right range: put it in a register
	return self:exp2anyreg(fs, e)
end

------------------------------------------------------------------------
--
-- * used in (lparser) luaY:assignment(), luaY:localfunc(), luaY:funcstat()
------------------------------------------------------------------------
function luaK:storevar(fs, var, ex)
	local k = var.k
	if k == "VLOCAL" then
		self:freeexp(fs, ex)
		self:exp2reg(fs, ex, var.info)
		return
	elseif k == "VUPVAL" then
		local e = self:exp2anyreg(fs, ex)
		self:codeABC(fs, "OP_SETUPVAL", e, var.info, 0)
	elseif k == "VGLOBAL" then
		local e = self:exp2anyreg(fs, ex)
		self:codeABx(fs, "OP_SETGLOBAL", e, var.info)
	elseif k == "VINDEXED" then
		local e = self:exp2RK(fs, ex)
		self:codeABC(fs, "OP_SETTABLE", var.info, var.aux, e)
	else
		lua_assert(0) -- invalid var kind to store
	end
	self:freeexp(fs, ex)
end

------------------------------------------------------------------------
--
-- * used only in (lparser) luaY:primaryexp()
------------------------------------------------------------------------
function luaK:_self(fs, e, key)
	self:exp2anyreg(fs, e)
	self:freeexp(fs, e)
	local func = fs.freereg
	self:reserveregs(fs, 2)
	self:codeABC(fs, "OP_SELF", func, e.info, self:exp2RK(fs, key))
	self:freeexp(fs, key)
	e.info = func
	e.k = "VNONRELOC"
end

------------------------------------------------------------------------
--
-- * used in luaK:goiftrue(), luaK:codenot()
------------------------------------------------------------------------
function luaK:invertjump(fs, e)
	local pc = self:getjumpcontrol(fs, e.info)
	lua_assert(
		luaP:testTMode(luaP:GET_OPCODE(pc)) ~= 0
			and luaP:GET_OPCODE(pc) ~= "OP_TESTSET"
			and luaP:GET_OPCODE(pc) ~= "OP_TEST"
	)
	luaP:SETARG_A(pc, (luaP:GETARG_A(pc) == 0) and 1 or 0)
end

------------------------------------------------------------------------
--
-- * used in luaK:goiftrue(), luaK:goiffalse()
------------------------------------------------------------------------
function luaK:jumponcond(fs, e, cond)
	if e.k == "VRELOCABLE" then
		local ie = self:getcode(fs, e)
		if luaP:GET_OPCODE(ie) == "OP_NOT" then
			fs.pc = fs.pc - 1 -- remove previous OP_NOT
			return self:condjump(fs, "OP_TEST", luaP:GETARG_B(ie), 0, cond and 0 or 1)
		end
		-- else go through
	end
	self:discharge2anyreg(fs, e)
	self:freeexp(fs, e)
	return self:condjump(fs, "OP_TESTSET", luaP.NO_REG, e.info, cond and 1 or 0)
end

------------------------------------------------------------------------
--
-- * used in luaK:infix(), (lparser) luaY:cond()
------------------------------------------------------------------------
function luaK:goiftrue(fs, e)
	local pc -- pc of last jump
	self:dischargevars(fs, e)
	local k = e.k
	if k == "VK" or k == "VKNUM" or k == "VTRUE" then
		pc = self.NO_JUMP -- always true; do nothing
	elseif k == "VFALSE" then
		pc = self:jump(fs) -- always jump
	elseif k == "VJMP" then
		self:invertjump(fs, e)
		pc = e.info
	else
		pc = self:jumponcond(fs, e, false)
	end
	e.f = self:concat(fs, e.f, pc) -- insert last jump in `f' list
	self:patchtohere(fs, e.t)
	e.t = self.NO_JUMP
end

------------------------------------------------------------------------
--
-- * used in luaK:infix()
------------------------------------------------------------------------
function luaK:goiffalse(fs, e)
	local pc -- pc of last jump
	self:dischargevars(fs, e)
	local k = e.k
	if k == "VNIL" or k == "VFALSE" then
		pc = self.NO_JUMP -- always false; do nothing
	elseif k == "VTRUE" then
		pc = self:jump(fs) -- always jump
	elseif k == "VJMP" then
		pc = e.info
	else
		pc = self:jumponcond(fs, e, true)
	end
	e.t = self:concat(fs, e.t, pc) -- insert last jump in `t' list
	self:patchtohere(fs, e.f)
	e.f = self.NO_JUMP
end

------------------------------------------------------------------------
--
-- * used only in luaK:prefix()
------------------------------------------------------------------------
function luaK:codenot(fs, e)
	self:dischargevars(fs, e)
	local k = e.k
	if k == "VNIL" or k == "VFALSE" then
		e.k = "VTRUE"
	elseif k == "VK" or k == "VKNUM" or k == "VTRUE" then
		e.k = "VFALSE"
	elseif k == "VJMP" then
		self:invertjump(fs, e)
	elseif k == "VRELOCABLE" or k == "VNONRELOC" then
		self:discharge2anyreg(fs, e)
		self:freeexp(fs, e)
		e.info = self:codeABC(fs, "OP_NOT", 0, e.info, 0)
		e.k = "VRELOCABLE"
	else
		lua_assert(0) -- cannot happen
	end
	-- interchange true and false lists
	e.f, e.t = e.t, e.f
	self:removevalues(fs, e.f)
	self:removevalues(fs, e.t)
end

------------------------------------------------------------------------
--
-- * used in (lparser) luaY:field(), luaY:primaryexp()
------------------------------------------------------------------------
function luaK:indexed(fs, t, k)
	t.aux = self:exp2RK(fs, k)
	t.k = "VINDEXED"
end

------------------------------------------------------------------------
--
-- * used only in luaK:codearith()
------------------------------------------------------------------------
function luaK:constfolding(op, e1, e2)
	local r
	if not self:isnumeral(e1) or not self:isnumeral(e2) then
		return false
	end
	local v1 = e1.nval
	local v2 = e2.nval
	if op == "OP_ADD" then
		r = self:numadd(v1, v2)
	elseif op == "OP_SUB" then
		r = self:numsub(v1, v2)
	elseif op == "OP_MUL" then
		r = self:nummul(v1, v2)
	elseif op == "OP_DIV" then
		if v2 == 0 then
			return false
		end -- do not attempt to divide by 0
		r = self:numdiv(v1, v2)
	elseif op == "OP_MOD" then
		if v2 == 0 then
			return false
		end -- do not attempt to divide by 0
		r = self:nummod(v1, v2)
	elseif op == "OP_POW" then
		r = self:numpow(v1, v2)
	elseif op == "OP_UNM" then
		r = self:numunm(v1)
	elseif op == "OP_LEN" then
		return false -- no constant folding for 'len'
	else
		lua_assert(0)
		r = 0
	end
	if self:numisnan(r) then
		return false
	end -- do not attempt to produce NaN
	e1.nval = r
	return true
end

------------------------------------------------------------------------
--
-- * used in luaK:prefix(), luaK:posfix()
------------------------------------------------------------------------
function luaK:codearith(fs, op, e1, e2)
	if self:constfolding(op, e1, e2) then
		return
	else
		local o2 = (op ~= "OP_UNM" and op ~= "OP_LEN") and self:exp2RK(fs, e2) or 0
		local o1 = self:exp2RK(fs, e1)
		if o1 > o2 then
			self:freeexp(fs, e1)
			self:freeexp(fs, e2)
		else
			self:freeexp(fs, e2)
			self:freeexp(fs, e1)
		end
		e1.info = self:codeABC(fs, op, 0, o1, o2)
		e1.k = "VRELOCABLE"
	end
end

------------------------------------------------------------------------
--
-- * used only in luaK:posfix()
------------------------------------------------------------------------
function luaK:codecomp(fs, op, cond, e1, e2)
	local o1 = self:exp2RK(fs, e1)
	local o2 = self:exp2RK(fs, e2)
	self:freeexp(fs, e2)
	self:freeexp(fs, e1)
	if cond == 0 and op ~= "OP_EQ" then
		-- exchange args to replace by `<' or `<='
		o1, o2 = o2, o1 -- o1 <==> o2
		cond = 1
	end
	e1.info = self:condjump(fs, op, cond, o1, o2)
	e1.k = "VJMP"
end

------------------------------------------------------------------------
--
-- * used only in (lparser) luaY:subexpr()
------------------------------------------------------------------------
function luaK:prefix(fs, op, e)
	local e2 = {} -- expdesc
	e2.t, e2.f = self.NO_JUMP, self.NO_JUMP
	e2.k = "VKNUM"
	e2.nval = 0
	if op == "OPR_MINUS" then
		if not self:isnumeral(e) then
			self:exp2anyreg(fs, e) -- cannot operate on non-numeric constants
		end
		self:codearith(fs, "OP_UNM", e, e2)
	elseif op == "OPR_NOT" then
		self:codenot(fs, e)
	elseif op == "OPR_LEN" then
		self:exp2anyreg(fs, e) -- cannot operate on constants
		self:codearith(fs, "OP_LEN", e, e2)
	else
		lua_assert(0)
	end
end

------------------------------------------------------------------------
--
-- * used only in (lparser) luaY:subexpr()
------------------------------------------------------------------------
function luaK:infix(fs, op, v)
	if op == "OPR_AND" then
		self:goiftrue(fs, v)
	elseif op == "OPR_OR" then
		self:goiffalse(fs, v)
	elseif op == "OPR_CONCAT" then
		self:exp2nextreg(fs, v) -- operand must be on the 'stack'
	elseif
		op == "OPR_ADD"
		or op == "OPR_SUB"
		or op == "OPR_MUL"
		or op == "OPR_DIV"
		or op == "OPR_MOD"
		or op == "OPR_POW"
	then
		if not self:isnumeral(v) then
			self:exp2RK(fs, v)
		end
	else
		self:exp2RK(fs, v)
	end
end

------------------------------------------------------------------------
--
-- * used only in (lparser) luaY:subexpr()
------------------------------------------------------------------------
-- table lookups to simplify testing
luaK.arith_op = {
	OPR_ADD = "OP_ADD",
	OPR_SUB = "OP_SUB",
	OPR_MUL = "OP_MUL",
	OPR_DIV = "OP_DIV",
	OPR_MOD = "OP_MOD",
	OPR_POW = "OP_POW",
}
luaK.comp_op = {
	OPR_EQ = "OP_EQ",
	OPR_NE = "OP_EQ",
	OPR_LT = "OP_LT",
	OPR_LE = "OP_LE",
	OPR_GT = "OP_LT",
	OPR_GE = "OP_LE",
}
luaK.comp_cond = {
	OPR_EQ = 1,
	OPR_NE = 0,
	OPR_LT = 1,
	OPR_LE = 1,
	OPR_GT = 0,
	OPR_GE = 0,
}
function luaK:posfix(fs, op, e1, e2)
	-- needed because e1 = e2 doesn't copy values...
	-- * in 5.0.x, only k/info/aux/t/f copied, t for AND, f for OR
	--   but here, all elements are copied for completeness' sake
	local function copyexp(e1, e2)
		e1.k = e2.k
		e1.info = e2.info
		e1.aux = e2.aux
		e1.nval = e2.nval
		e1.t = e2.t
		e1.f = e2.f
	end
	if op == "OPR_AND" then
		lua_assert(e1.t == self.NO_JUMP) -- list must be closed
		self:dischargevars(fs, e2)
		e2.f = self:concat(fs, e2.f, e1.f)
		copyexp(e1, e2)
	elseif op == "OPR_OR" then
		lua_assert(e1.f == self.NO_JUMP) -- list must be closed
		self:dischargevars(fs, e2)
		e2.t = self:concat(fs, e2.t, e1.t)
		copyexp(e1, e2)
	elseif op == "OPR_CONCAT" then
		self:exp2val(fs, e2)
		if e2.k == "VRELOCABLE" and luaP:GET_OPCODE(self:getcode(fs, e2)) == "OP_CONCAT" then
			lua_assert(e1.info == luaP:GETARG_B(self:getcode(fs, e2)) - 1)
			self:freeexp(fs, e1)
			luaP:SETARG_B(self:getcode(fs, e2), e1.info)
			e1.k = "VRELOCABLE"
			e1.info = e2.info
		else
			self:exp2nextreg(fs, e2) -- operand must be on the 'stack'
			self:codearith(fs, "OP_CONCAT", e1, e2)
		end
	else
		-- the following uses a table lookup in place of conditionals
		local arith = self.arith_op[op]
		if arith then
			self:codearith(fs, arith, e1, e2)
		else
			local comp = self.comp_op[op]
			if comp then
				self:codecomp(fs, comp, self.comp_cond[op], e1, e2)
			else
				lua_assert(0)
			end
		end --if arith
	end --if op
end

------------------------------------------------------------------------
-- adjusts debug information for last instruction written, in order to
-- change the line where item comes into existence
-- * used in (lparser) luaY:funcargs(), luaY:forbody(), luaY:funcstat()
------------------------------------------------------------------------
function luaK:fixline(fs, line)
	fs.f.lineinfo[fs.pc - 1] = line
end

------------------------------------------------------------------------
-- general function to write an instruction into the instruction buffer,
-- sets debug information too
-- * used in luaK:codeABC(), luaK:codeABx()
-- * called directly by (lparser) luaY:whilestat()
------------------------------------------------------------------------
function luaK:code(fs, i, line)
	local f = fs.f
	self:dischargejpc(fs) -- 'pc' will change
	-- put new instruction in code array
	luaY:growvector(fs.L, f.code, fs.pc, f.sizecode, nil, luaY.MAX_INT, "code size overflow")
	f.code[fs.pc] = i
	-- save corresponding line information
	luaY:growvector(fs.L, f.lineinfo, fs.pc, f.sizelineinfo, nil, luaY.MAX_INT, "code size overflow")
	f.lineinfo[fs.pc] = line
	local pc = fs.pc
	fs.pc = fs.pc + 1
	return pc
end

------------------------------------------------------------------------
-- writes an instruction of type ABC
-- * calls luaK:code()
------------------------------------------------------------------------
function luaK:codeABC(fs, o, a, b, c)
	lua_assert(luaP:getOpMode(o) == luaP.OpMode.iABC)
	lua_assert(luaP:getBMode(o) ~= luaP.OpArgMask.OpArgN or b == 0)
	lua_assert(luaP:getCMode(o) ~= luaP.OpArgMask.OpArgN or c == 0)
	return self:code(fs, luaP:CREATE_ABC(o, a, b, c), fs.ls.lastline)
end

------------------------------------------------------------------------
-- writes an instruction of type ABx
-- * calls luaK:code(), called by luaK:codeAsBx()
------------------------------------------------------------------------
function luaK:codeABx(fs, o, a, bc)
	lua_assert(luaP:getOpMode(o) == luaP.OpMode.iABx or luaP:getOpMode(o) == luaP.OpMode.iAsBx)
	lua_assert(luaP:getCMode(o) == luaP.OpArgMask.OpArgN)
	return self:code(fs, luaP:CREATE_ABx(o, a, bc), fs.ls.lastline)
end

------------------------------------------------------------------------
--
-- * used in (lparser) luaY:closelistfield(), luaY:lastlistfield()
------------------------------------------------------------------------
function luaK:setlist(fs, base, nelems, tostore)
	local c = math.floor((nelems - 1) / luaP.LFIELDS_PER_FLUSH) + 1
	local b = (tostore == luaY.LUA_MULTRET) and 0 or tostore
	lua_assert(tostore ~= 0)
	if c <= luaP.MAXARG_C then
		self:codeABC(fs, "OP_SETLIST", base, b, c)
	else
		self:codeABC(fs, "OP_SETLIST", base, b, 0)
		self:code(fs, luaP:CREATE_Inst(c), fs.ls.lastline)
	end
	fs.freereg = base + 1 -- free registers with list values
end

--dofile("lparser.lua")

--[[--------------------------------------------------------------------
-- Expression descriptor
-- * expkind changed to string constants; luaY:assignment was the only
--   function to use a relational operator with this enumeration
-- VVOID       -- no value
-- VNIL        -- no value
-- VTRUE       -- no value
-- VFALSE      -- no value
-- VK          -- info = index of constant in 'k'
-- VKNUM       -- nval = numerical value
-- VLOCAL      -- info = local register
-- VUPVAL,     -- info = index of upvalue in 'upvalues'
-- VGLOBAL     -- info = index of table; aux = index of global name in 'k'
-- VINDEXED    -- info = table register; aux = index register (or 'k')
-- VJMP        -- info = instruction pc
-- VRELOCABLE  -- info = instruction pc
-- VNONRELOC   -- info = result register
-- VCALL       -- info = instruction pc
-- VVARARG     -- info = instruction pc
} ----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- * expdesc in Lua 5.1.x has a union u and another struct s; this Lua
--   implementation ignores all instances of u and s usage
-- struct expdesc:
--   k  -- (enum: expkind)
--   info, aux -- (int, int)
--   nval -- (lua_Number)
--   t  -- patch list of 'exit when true'
--   f  -- patch list of 'exit when false'
----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- struct upvaldesc:
--   k  -- (lu_byte)
--   info -- (lu_byte)
----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- state needed to generate code for a given function
-- struct FuncState:
--   f  -- current function header (table: Proto)
--   h  -- table to find (and reuse) elements in 'k' (table: Table)
--   prev  -- enclosing function (table: FuncState)
--   ls  -- lexical state (table: LexState)
--   L  -- copy of the Lua state (table: lua_State)
--   bl  -- chain of current blocks (table: BlockCnt)
--   pc  -- next position to code (equivalent to 'ncode')
--   lasttarget   -- 'pc' of last 'jump target'
--   jpc  -- list of pending jumps to 'pc'
--   freereg  -- first free register
--   nk  -- number of elements in 'k'
--   np  -- number of elements in 'p'
--   nlocvars  -- number of elements in 'locvars'
--   nactvar  -- number of active local variables
--   upvalues[LUAI_MAXUPVALUES]  -- upvalues (table: upvaldesc)
--   actvar[LUAI_MAXVARS]  -- declared-variable stack
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- constants used by parser
-- * picks up duplicate values from luaX if required
------------------------------------------------------------------------
luaY.LUA_QS = luaX.LUA_QS or "'%s'" -- (from luaconf.h)

luaY.SHRT_MAX = 32767 -- (from <limits.h>)
luaY.LUAI_MAXVARS = 200 -- (luaconf.h)
luaY.LUAI_MAXUPVALUES = 60 -- (luaconf.h)
luaY.MAX_INT = luaX.MAX_INT or 2147483645 -- (from llimits.h)
-- * INT_MAX-2 for 32-bit systems
luaY.LUAI_MAXCCALLS = 200 -- (from luaconf.h)

luaY.VARARG_HASARG = 1 -- (from lobject.h)
-- NOTE: HASARG_MASK is value-specific
luaY.HASARG_MASK = 2 -- this was added for a bitop in parlist()
luaY.VARARG_ISVARARG = 2
-- NOTE: there is some value-specific code that involves VARARG_NEEDSARG
luaY.VARARG_NEEDSARG = 4

luaY.LUA_MULTRET = -1 -- (lua.h)

--[[--------------------------------------------------------------------
-- other functions
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- LUA_QL describes how error messages quote program elements.
-- CHANGE it if you want a different appearance. (from luaconf.h)
------------------------------------------------------------------------
function luaY:LUA_QL(x)
	return "'" .. x .. "'"
end

------------------------------------------------------------------------
-- this is a stripped-down luaM_growvector (from lmem.h) which is a
-- macro based on luaM_growaux (in lmem.c); all the following does is
-- reproduce the size limit checking logic of the original function
-- so that error behaviour is identical; all arguments preserved for
-- convenience, even those which are unused
-- * set the t field to nil, since this originally does a sizeof(t)
-- * size (originally a pointer) is never updated, their final values
--   are set by luaY:close_func(), so overall things should still work
------------------------------------------------------------------------
function luaY:growvector(L, v, nelems, size, t, limit, e)
	if nelems >= limit then
		error(e) -- was luaG_runerror
	end
end

------------------------------------------------------------------------
-- initialize a new function prototype structure (from lfunc.c)
-- * used only in open_func()
------------------------------------------------------------------------
function luaY:newproto(L)
	local f = {} -- Proto
	-- luaC_link(L, obj2gco(f), LUA_TPROTO); /* GC */
	f.k = {}
	f.sizek = 0
	f.p = {}
	f.sizep = 0
	f.code = {}
	f.sizecode = 0
	f.sizelineinfo = 0
	f.sizeupvalues = 0
	f.nups = 0
	f.upvalues = {}
	f.numparams = 0
	f.is_vararg = 0
	f.maxstacksize = 0
	f.lineinfo = {}
	f.sizelocvars = 0
	f.locvars = {}
	f.lineDefined = 0
	f.lastlinedefined = 0
	f.source = nil
	return f
end

------------------------------------------------------------------------
-- converts an integer to a "floating point byte", represented as
-- (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if
-- eeeee != 0 and (xxx) otherwise.
------------------------------------------------------------------------
function luaY:int2fb(x)
	local e = 0 -- exponent
	while x >= 16 do
		x = math.floor((x + 1) / 2)
		e = e + 1
	end
	if x < 8 then
		return x
	else
		return ((e + 1) * 8) + (x - 8)
	end
end

--[[--------------------------------------------------------------------
-- parser functions
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- true of the kind of expression produces multiple return values
------------------------------------------------------------------------
function luaY:hasmultret(k)
	return k == "VCALL" or k == "VVARARG"
end

------------------------------------------------------------------------
-- convenience function to access active local i, returns entry
------------------------------------------------------------------------
function luaY:getlocvar(fs, i)
	return fs.f.locvars[fs.actvar[i]]
end

------------------------------------------------------------------------
-- check a limit, string m provided as an error message
------------------------------------------------------------------------
function luaY:checklimit(fs, v, l, m)
	if v > l then
		self:errorlimit(fs, l, m)
	end
end

--[[--------------------------------------------------------------------
-- nodes for block list (list of active blocks)
-- struct BlockCnt:
--   previous  -- chain (table: BlockCnt)
--   breaklist  -- list of jumps out of this loop
--   nactvar  -- # active local variables outside the breakable structure
--   upval  -- true if some variable in the block is an upvalue (boolean)
--   isbreakable  -- true if 'block' is a loop (boolean)
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- prototypes for recursive non-terminal functions
------------------------------------------------------------------------
-- prototypes deleted; not required in Lua

------------------------------------------------------------------------
-- reanchor if last token is has a constant string, see close_func()
-- * used only in close_func()
------------------------------------------------------------------------
function luaY:anchor_token(ls)
	if ls.t.token == "TK_NAME" or ls.t.token == "TK_STRING" then
		-- not relevant to Lua implementation of parser
		-- local ts = ls.t.seminfo
		-- luaX_newstring(ls, getstr(ts), ts->tsv.len); /* C */
	end
end

------------------------------------------------------------------------
-- throws a syntax error if token expected is not there
------------------------------------------------------------------------
function luaY:error_expected(ls, token)
	luaX:syntaxerror(ls, string.format(self.LUA_QS .. " expected", luaX:token2str(ls, token)))
end

------------------------------------------------------------------------
-- prepares error message for display, for limits exceeded
-- * used only in checklimit()
------------------------------------------------------------------------
function luaY:errorlimit(fs, limit, what)
	local msg = (fs.f.linedefined == 0) and string.format("main function has more than %d %s", limit, what)
		or string.format("function at line %d has more than %d %s", fs.f.linedefined, limit, what)
	luaX:lexerror(fs.ls, msg, 0)
end

------------------------------------------------------------------------
-- tests for a token, returns outcome
-- * return value changed to boolean
------------------------------------------------------------------------
function luaY:testnext(ls, c)
	if ls.t.token == c then
		luaX:next(ls)
		return true
	else
		return false
	end
end

------------------------------------------------------------------------
-- check for existence of a token, throws error if not found
------------------------------------------------------------------------
function luaY:check(ls, c)
	if ls.t.token ~= c then
		self:error_expected(ls, c)
	end
end

------------------------------------------------------------------------
-- verify existence of a token, then skip it
------------------------------------------------------------------------
function luaY:checknext(ls, c)
	self:check(ls, c)
	luaX:next(ls)
end

------------------------------------------------------------------------
-- throws error if condition not matched
------------------------------------------------------------------------
function luaY:check_condition(ls, c, msg)
	if not c then
		luaX:syntaxerror(ls, msg)
	end
end

------------------------------------------------------------------------
-- verifies token conditions are met or else throw error
------------------------------------------------------------------------
function luaY:check_match(ls, what, who, where)
	if not self:testnext(ls, what) then
		if where == ls.linenumber then
			self:error_expected(ls, what)
		else
			luaX:syntaxerror(
				ls,
				string.format(
					self.LUA_QS .. " expected (to close " .. self.LUA_QS .. " at line %d)",
					luaX:token2str(ls, what),
					luaX:token2str(ls, who),
					where
				)
			)
		end
	end
end

------------------------------------------------------------------------
-- expect that token is a name, return the name
------------------------------------------------------------------------
function luaY:str_checkname(ls)
	self:check(ls, "TK_NAME")
	local ts = ls.t.seminfo
	luaX:next(ls)
	return ts
end

------------------------------------------------------------------------
-- initialize a struct expdesc, expression description data structure
------------------------------------------------------------------------
function luaY:init_exp(e, k, i)
	e.f, e.t = luaK.NO_JUMP, luaK.NO_JUMP
	e.k = k
	e.info = i
end

------------------------------------------------------------------------
-- adds given string s in string pool, sets e as VK
------------------------------------------------------------------------
function luaY:codestring(ls, e, s)
	self:init_exp(e, "VK", luaK:stringK(ls.fs, s))
end

------------------------------------------------------------------------
-- consume a name token, adds it to string pool, sets e as VK
------------------------------------------------------------------------
function luaY:checkname(ls, e)
	self:codestring(ls, e, self:str_checkname(ls))
end

------------------------------------------------------------------------
-- creates struct entry for a local variable
-- * used only in new_localvar()
------------------------------------------------------------------------
function luaY:registerlocalvar(ls, varname)
	local fs = ls.fs
	local f = fs.f
	self:growvector(ls.L, f.locvars, fs.nlocvars, f.sizelocvars, nil, self.SHRT_MAX, "too many local variables")
	-- loop to initialize empty f.locvar positions not required
	f.locvars[fs.nlocvars] = {} -- LocVar
	f.locvars[fs.nlocvars].varname = varname
	-- luaC_objbarrier(ls.L, f, varname) /* GC */
	local nlocvars = fs.nlocvars
	fs.nlocvars = fs.nlocvars + 1
	return nlocvars
end

------------------------------------------------------------------------
-- creates a new local variable given a name and an offset from nactvar
-- * used in fornum(), forlist(), parlist(), body()
------------------------------------------------------------------------
function luaY:new_localvarliteral(ls, v, n)
	self:new_localvar(ls, v, n)
end

------------------------------------------------------------------------
-- register a local variable, set in active variable list
------------------------------------------------------------------------
function luaY:new_localvar(ls, name, n)
	local fs = ls.fs
	self:checklimit(fs, fs.nactvar + n + 1, self.LUAI_MAXVARS, "local variables")
	fs.actvar[fs.nactvar + n] = self:registerlocalvar(ls, name)
end

------------------------------------------------------------------------
-- adds nvars number of new local variables, set debug information
------------------------------------------------------------------------
function luaY:adjustlocalvars(ls, nvars)
	local fs = ls.fs
	fs.nactvar = fs.nactvar + nvars
	for i = nvars, 1, -1 do
		self:getlocvar(fs, fs.nactvar - i).startpc = fs.pc
	end
end

------------------------------------------------------------------------
-- removes a number of locals, set debug information
------------------------------------------------------------------------
function luaY:removevars(ls, tolevel)
	local fs = ls.fs
	while fs.nactvar > tolevel do
		fs.nactvar = fs.nactvar - 1
		self:getlocvar(fs, fs.nactvar).endpc = fs.pc
	end
end

------------------------------------------------------------------------
-- returns an existing upvalue index based on the given name, or
-- creates a new upvalue struct entry and returns the new index
-- * used only in singlevaraux()
------------------------------------------------------------------------
function luaY:indexupvalue(fs, name, v)
	local f = fs.f
	for i = 0, f.nups - 1 do
		if fs.upvalues[i].k == v.k and fs.upvalues[i].info == v.info then
			lua_assert(f.upvalues[i] == name)
			return i
		end
	end
	-- new one
	self:checklimit(fs, f.nups + 1, self.LUAI_MAXUPVALUES, "upvalues")
	self:growvector(fs.L, f.upvalues, f.nups, f.sizeupvalues, nil, self.MAX_INT, "")
	-- loop to initialize empty f.upvalues positions not required
	f.upvalues[f.nups] = name
	-- luaC_objbarrier(fs->L, f, name); /* GC */
	lua_assert(v.k == "VLOCAL" or v.k == "VUPVAL")
	-- this is a partial copy; only k & info fields used
	fs.upvalues[f.nups] = { k = v.k, info = v.info }
	local nups = f.nups
	f.nups = f.nups + 1
	return nups
end

------------------------------------------------------------------------
-- search the local variable namespace of the given fs for a match
-- * used only in singlevaraux()
------------------------------------------------------------------------
function luaY:searchvar(fs, n)
	for i = fs.nactvar - 1, 0, -1 do
		if n == self:getlocvar(fs, i).varname then
			return i
		end
	end
	return -1 -- not found
end

------------------------------------------------------------------------
-- * mark upvalue flags in function states up to a given level
-- * used only in singlevaraux()
------------------------------------------------------------------------
function luaY:markupval(fs, level)
	local bl = fs.bl
	while bl and bl.nactvar > level do
		bl = bl.previous
	end
	if bl then
		bl.upval = true
	end
end

------------------------------------------------------------------------
-- handle locals, globals and upvalues and related processing
-- * search mechanism is recursive, calls itself to search parents
-- * used only in singlevar()
------------------------------------------------------------------------
function luaY:singlevaraux(fs, n, var, base)
	if fs == nil then -- no more levels?
		self:init_exp(var, "VGLOBAL", luaP.NO_REG) -- default is global variable
		return "VGLOBAL"
	else
		local v = self:searchvar(fs, n) -- look up at current level
		if v >= 0 then
			self:init_exp(var, "VLOCAL", v)
			if base == 0 then
				self:markupval(fs, v) -- local will be used as an upval
			end
			return "VLOCAL"
		else -- not found at current level; try upper one
			if self:singlevaraux(fs.prev, n, var, 0) == "VGLOBAL" then
				return "VGLOBAL"
			end
			var.info = self:indexupvalue(fs, n, var) -- else was LOCAL or UPVAL
			var.k = "VUPVAL" -- upvalue in this level
			return "VUPVAL"
		end --if v
	end --if fs
end

------------------------------------------------------------------------
-- consume a name token, creates a variable (global|local|upvalue)
-- * used in prefixexp(), funcname()
------------------------------------------------------------------------
function luaY:singlevar(ls, var)
	local varname = self:str_checkname(ls)
	local fs = ls.fs
	if self:singlevaraux(fs, varname, var, 1) == "VGLOBAL" then
		var.info = luaK:stringK(fs, varname) -- info points to global name
	end
end

------------------------------------------------------------------------
-- adjust RHS to match LHS in an assignment
-- * used in assignment(), forlist(), localstat()
------------------------------------------------------------------------
function luaY:adjust_assign(ls, nvars, nexps, e)
	local fs = ls.fs
	local extra = nvars - nexps
	if self:hasmultret(e.k) then
		extra = extra + 1 -- includes call itself
		if extra <= 0 then
			extra = 0
		end
		luaK:setreturns(fs, e, extra) -- last exp. provides the difference
		if extra > 1 then
			luaK:reserveregs(fs, extra - 1)
		end
	else
		if e.k ~= "VVOID" then
			luaK:exp2nextreg(fs, e)
		end -- close last expression
		if extra > 0 then
			local reg = fs.freereg
			luaK:reserveregs(fs, extra)
			luaK:_nil(fs, reg, extra)
		end
	end
end

------------------------------------------------------------------------
-- tracks and limits parsing depth, assert check at end of parsing
------------------------------------------------------------------------
function luaY:enterlevel(ls)
	ls.L.nCcalls = ls.L.nCcalls + 1
	if ls.L.nCcalls > self.LUAI_MAXCCALLS then
		luaX:lexerror(ls, "chunk has too many syntax levels", 0)
	end
end

------------------------------------------------------------------------
-- tracks parsing depth, a pair with luaY:enterlevel()
------------------------------------------------------------------------
function luaY:leavelevel(ls)
	ls.L.nCcalls = ls.L.nCcalls - 1
end

------------------------------------------------------------------------
-- enters a code unit, initializes elements
------------------------------------------------------------------------
function luaY:enterblock(fs, bl, isbreakable)
	bl.breaklist = luaK.NO_JUMP
	bl.isbreakable = isbreakable
	bl.nactvar = fs.nactvar
	bl.upval = false
	bl.previous = fs.bl
	fs.bl = bl
	lua_assert(fs.freereg == fs.nactvar)
end

------------------------------------------------------------------------
-- leaves a code unit, close any upvalues
------------------------------------------------------------------------
function luaY:leaveblock(fs)
	local bl = fs.bl
	fs.bl = bl.previous
	self:removevars(fs.ls, bl.nactvar)
	if bl.upval then
		luaK:codeABC(fs, "OP_CLOSE", bl.nactvar, 0, 0)
	end
	-- a block either controls scope or breaks (never both)
	lua_assert(not bl.isbreakable or not bl.upval)
	lua_assert(bl.nactvar == fs.nactvar)
	fs.freereg = fs.nactvar -- free registers
	luaK:patchtohere(fs, bl.breaklist)
end

------------------------------------------------------------------------
-- implement the instantiation of a function prototype, append list of
-- upvalues after the instantiation instruction
-- * used only in body()
------------------------------------------------------------------------
function luaY:pushclosure(ls, func, v)
	local fs = ls.fs
	local f = fs.f
	self:growvector(ls.L, f.p, fs.np, f.sizep, nil, luaP.MAXARG_Bx, "constant table overflow")
	-- loop to initialize empty f.p positions not required
	f.p[fs.np] = func.f
	fs.np = fs.np + 1
	-- luaC_objbarrier(ls->L, f, func->f); /* C */
	self:init_exp(v, "VRELOCABLE", luaK:codeABx(fs, "OP_CLOSURE", 0, fs.np - 1))
	for i = 0, func.f.nups - 1 do
		local o = (func.upvalues[i].k == "VLOCAL") and "OP_MOVE" or "OP_GETUPVAL"
		luaK:codeABC(fs, o, 0, func.upvalues[i].info, 0)
	end
end

------------------------------------------------------------------------
-- opening of a function
------------------------------------------------------------------------
function luaY:open_func(ls, fs)
	local L = ls.L
	local f = self:newproto(ls.L)
	fs.f = f
	fs.prev = ls.fs -- linked list of funcstates
	fs.ls = ls
	fs.L = L
	ls.fs = fs
	fs.pc = 0
	fs.lasttarget = -1
	fs.jpc = luaK.NO_JUMP
	fs.freereg = 0
	fs.nk = 0
	fs.np = 0
	fs.nlocvars = 0
	fs.nactvar = 0
	fs.bl = nil
	f.source = ls.source
	f.maxstacksize = 2 -- registers 0/1 are always valid
	fs.h = {} -- constant table; was luaH_new call
	-- anchor table of constants and prototype (to avoid being collected)
	-- sethvalue2s(L, L->top, fs->h); incr_top(L); /* C */
	-- setptvalue2s(L, L->top, f); incr_top(L);
end

------------------------------------------------------------------------
-- closing of a function
------------------------------------------------------------------------
function luaY:close_func(ls)
	local L = ls.L
	local fs = ls.fs
	local f = fs.f
	self:removevars(ls, 0)
	luaK:ret(fs, 0, 0) -- final return
	-- luaM_reallocvector deleted for f->code, f->lineinfo, f->k, f->p,
	-- f->locvars, f->upvalues; not required for Lua table arrays
	f.sizecode = fs.pc
	f.sizelineinfo = fs.pc
	f.sizek = fs.nk
	f.sizep = fs.np
	f.sizelocvars = fs.nlocvars
	f.sizeupvalues = f.nups
	--lua_assert(luaG_checkcode(f))  -- currently not implemented
	lua_assert(fs.bl == nil)
	ls.fs = fs.prev
	-- the following is not required for this implementation; kept here
	-- for completeness
	-- L->top -= 2;  /* remove table and prototype from the stack */
	-- last token read was anchored in defunct function; must reanchor it
	if fs then
		self:anchor_token(ls)
	end
end

------------------------------------------------------------------------
-- parser initialization function
-- * note additional sub-tables needed for LexState, FuncState
------------------------------------------------------------------------
function luaY:parser(L, z, buff, name)
	local lexstate = {} -- LexState
	lexstate.t = {}
	lexstate.lookahead = {}
	local funcstate = {} -- FuncState
	funcstate.upvalues = {}
	funcstate.actvar = {}
	-- the following nCcalls initialization added for convenience
	L.nCcalls = 0
	lexstate.buff = buff
	luaX:setinput(L, lexstate, z, name)
	self:open_func(lexstate, funcstate)
	funcstate.f.is_vararg = self.VARARG_ISVARARG -- main func. is always vararg
	luaX:next(lexstate) -- read first token
	self:chunk(lexstate)
	self:check(lexstate, "TK_EOS")
	self:close_func(lexstate)
	lua_assert(funcstate.prev == nil)
	lua_assert(funcstate.f.nups == 0)
	lua_assert(lexstate.fs == nil)
	return funcstate.f
end

--[[--------------------------------------------------------------------
-- GRAMMAR RULES
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- parse a function name suffix, for function call specifications
-- * used in primaryexp(), funcname()
------------------------------------------------------------------------
function luaY:field(ls, v)
	-- field -> ['.' | ':'] NAME
	local fs = ls.fs
	local key = {} -- expdesc
	luaK:exp2anyreg(fs, v)
	luaX:next(ls) -- skip the dot or colon
	self:checkname(ls, key)
	luaK:indexed(fs, v, key)
end

------------------------------------------------------------------------
-- parse a table indexing suffix, for constructors, expressions
-- * used in recfield(), primaryexp()
------------------------------------------------------------------------
function luaY:yindex(ls, v)
	-- index -> '[' expr ']'
	luaX:next(ls) -- skip the '['
	self:expr(ls, v)
	luaK:exp2val(ls.fs, v)
	self:checknext(ls, "]")
end

--[[--------------------------------------------------------------------
-- Rules for Constructors
----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- struct ConsControl:
--   v  -- last list item read (table: struct expdesc)
--   t  -- table descriptor (table: struct expdesc)
--   nh  -- total number of 'record' elements
--   na  -- total number of array elements
--   tostore  -- number of array elements pending to be stored
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- parse a table record (hash) field
-- * used in constructor()
------------------------------------------------------------------------
function luaY:recfield(ls, cc)
	-- recfield -> (NAME | '['exp1']') = exp1
	local fs = ls.fs
	local reg = ls.fs.freereg
	local key, val = {}, {} -- expdesc
	if ls.t.token == "TK_NAME" then
		self:checklimit(fs, cc.nh, self.MAX_INT, "items in a constructor")
		self:checkname(ls, key)
	else -- ls->t.token == '['
		self:yindex(ls, key)
	end
	cc.nh = cc.nh + 1
	self:checknext(ls, "=")
	local rkkey = luaK:exp2RK(fs, key)
	self:expr(ls, val)
	luaK:codeABC(fs, "OP_SETTABLE", cc.t.info, rkkey, luaK:exp2RK(fs, val))
	fs.freereg = reg -- free registers
end

------------------------------------------------------------------------
-- emit a set list instruction if enough elements (LFIELDS_PER_FLUSH)
-- * used in constructor()
------------------------------------------------------------------------
function luaY:closelistfield(fs, cc)
	if cc.v.k == "VVOID" then
		return
	end -- there is no list item
	luaK:exp2nextreg(fs, cc.v)
	cc.v.k = "VVOID"
	if cc.tostore == luaP.LFIELDS_PER_FLUSH then
		luaK:setlist(fs, cc.t.info, cc.na, cc.tostore) -- flush
		cc.tostore = 0 -- no more items pending
	end
end

------------------------------------------------------------------------
-- emit a set list instruction at the end of parsing list constructor
-- * used in constructor()
------------------------------------------------------------------------
function luaY:lastlistfield(fs, cc)
	if cc.tostore == 0 then
		return
	end
	if self:hasmultret(cc.v.k) then
		luaK:setmultret(fs, cc.v)
		luaK:setlist(fs, cc.t.info, cc.na, self.LUA_MULTRET)
		cc.na = cc.na - 1 -- do not count last expression (unknown number of elements)
	else
		if cc.v.k ~= "VVOID" then
			luaK:exp2nextreg(fs, cc.v)
		end
		luaK:setlist(fs, cc.t.info, cc.na, cc.tostore)
	end
end

------------------------------------------------------------------------
-- parse a table list (array) field
-- * used in constructor()
------------------------------------------------------------------------
function luaY:listfield(ls, cc)
	self:expr(ls, cc.v)
	self:checklimit(ls.fs, cc.na, self.MAX_INT, "items in a constructor")
	cc.na = cc.na + 1
	cc.tostore = cc.tostore + 1
end

------------------------------------------------------------------------
-- parse a table constructor
-- * used in funcargs(), simpleexp()
------------------------------------------------------------------------
function luaY:constructor(ls, t)
	-- constructor -> '{' [ field { fieldsep field } [ fieldsep ] ] '}'
	-- field -> recfield | listfield
	-- fieldsep -> ',' | ';'
	local fs = ls.fs
	local line = ls.linenumber
	local pc = luaK:codeABC(fs, "OP_NEWTABLE", 0, 0, 0)
	local cc = {} -- ConsControl
	cc.v = {}
	cc.na, cc.nh, cc.tostore = 0, 0, 0
	cc.t = t
	self:init_exp(t, "VRELOCABLE", pc)
	self:init_exp(cc.v, "VVOID", 0) -- no value (yet)
	luaK:exp2nextreg(ls.fs, t) -- fix it at stack top (for gc)
	self:checknext(ls, "{")
	repeat
		lua_assert(cc.v.k == "VVOID" or cc.tostore > 0)
		if ls.t.token == "}" then
			break
		end
		self:closelistfield(fs, cc)
		local c = ls.t.token

		if c == "TK_NAME" then -- may be listfields or recfields
			luaX:lookahead(ls)
			if ls.lookahead.token ~= "=" then -- expression?
				self:listfield(ls, cc)
			else
				self:recfield(ls, cc)
			end
		elseif c == "[" then -- constructor_item -> recfield
			self:recfield(ls, cc)
		else -- constructor_part -> listfield
			self:listfield(ls, cc)
		end
	until not self:testnext(ls, ",") and not self:testnext(ls, ";")
	self:check_match(ls, "}", "{", line)
	self:lastlistfield(fs, cc)
	luaP:SETARG_B(fs.f.code[pc], self:int2fb(cc.na)) -- set initial array size
	luaP:SETARG_C(fs.f.code[pc], self:int2fb(cc.nh)) -- set initial table size
end

-- }======================================================================

------------------------------------------------------------------------
-- parse the arguments (parameters) of a function declaration
-- * used in body()
------------------------------------------------------------------------
function luaY:parlist(ls)
	-- parlist -> [ param { ',' param } ]
	local fs = ls.fs
	local f = fs.f
	local nparams = 0
	f.is_vararg = 0
	if ls.t.token ~= ")" then -- is 'parlist' not empty?
		repeat
			local c = ls.t.token
			if c == "TK_NAME" then -- param -> NAME
				self:new_localvar(ls, self:str_checkname(ls), nparams)
				nparams = nparams + 1
			elseif c == "TK_DOTS" then -- param -> `...'
				luaX:next(ls)
				-- [[
				-- #if defined(LUA_COMPAT_VARARG)
				-- use `arg' as default name
				self:new_localvarliteral(ls, "arg", nparams)
				nparams = nparams + 1
				f.is_vararg = self.VARARG_HASARG + self.VARARG_NEEDSARG
				-- #endif
				--]]
				f.is_vararg = f.is_vararg + self.VARARG_ISVARARG
			else
				luaX:syntaxerror(ls, "<name> or " .. self:LUA_QL("...") .. " expected")
			end
		until f.is_vararg ~= 0 or not self:testnext(ls, ",")
	end --if
	self:adjustlocalvars(ls, nparams)
	-- NOTE: the following works only when HASARG_MASK is 2!
	f.numparams = fs.nactvar - (f.is_vararg % self.HASARG_MASK)
	luaK:reserveregs(fs, fs.nactvar) -- reserve register for parameters
end

------------------------------------------------------------------------
-- parse function declaration body
-- * used in simpleexp(), localfunc(), funcstat()
------------------------------------------------------------------------
function luaY:body(ls, e, needself, line)
	-- body ->  '(' parlist ')' chunk END
	local new_fs = {} -- FuncState
	new_fs.upvalues = {}
	new_fs.actvar = {}
	self:open_func(ls, new_fs)
	new_fs.f.lineDefined = line
	self:checknext(ls, "(")
	if needself then
		self:new_localvarliteral(ls, "self", 0)
		self:adjustlocalvars(ls, 1)
	end
	self:parlist(ls)
	self:checknext(ls, ")")
	self:chunk(ls)
	new_fs.f.lastlinedefined = ls.linenumber
	self:check_match(ls, "TK_END", "TK_FUNCTION", line)
	self:close_func(ls)
	self:pushclosure(ls, new_fs, e)
end

------------------------------------------------------------------------
-- parse a list of comma-separated expressions
-- * used is multiple locations
------------------------------------------------------------------------
function luaY:explist1(ls, v)
	-- explist1 -> expr { ',' expr }
	local n = 1 -- at least one expression
	self:expr(ls, v)
	while self:testnext(ls, ",") do
		luaK:exp2nextreg(ls.fs, v)
		self:expr(ls, v)
		n = n + 1
	end
	return n
end

------------------------------------------------------------------------
-- parse the parameters of a function call
-- * contrast with parlist(), used in function declarations
-- * used in primaryexp()
------------------------------------------------------------------------
function luaY:funcargs(ls, f)
	local fs = ls.fs
	local args = {} -- expdesc
	local nparams
	local line = ls.linenumber
	local c = ls.t.token
	if c == "(" then -- funcargs -> '(' [ explist1 ] ')'
		if line ~= ls.lastline then
			luaX:syntaxerror(ls, "ambiguous syntax (function call x new statement)")
		end
		luaX:next(ls)
		if ls.t.token == ")" then -- arg list is empty?
			args.k = "VVOID"
		else
			self:explist1(ls, args)
			luaK:setmultret(fs, args)
		end
		self:check_match(ls, ")", "(", line)
	elseif c == "{" then -- funcargs -> constructor
		self:constructor(ls, args)
	elseif c == "TK_STRING" then -- funcargs -> STRING
		self:codestring(ls, args, ls.t.seminfo)
		luaX:next(ls) -- must use 'seminfo' before 'next'
	else
		luaX:syntaxerror(ls, "function arguments expected")
		return
	end
	lua_assert(f.k == "VNONRELOC")
	local base = f.info -- base register for call
	if self:hasmultret(args.k) then
		nparams = self.LUA_MULTRET -- open call
	else
		if args.k ~= "VVOID" then
			luaK:exp2nextreg(fs, args) -- close last argument
		end
		nparams = fs.freereg - (base + 1)
	end
	self:init_exp(f, "VCALL", luaK:codeABC(fs, "OP_CALL", base, nparams + 1, 2))
	luaK:fixline(fs, line)
	fs.freereg = base + 1 -- call remove function and arguments and leaves
	-- (unless changed) one result
end

--[[--------------------------------------------------------------------
-- Expression parsing
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- parses an expression in parentheses or a single variable
-- * used in primaryexp()
------------------------------------------------------------------------
function luaY:prefixexp(ls, v)
	-- prefixexp -> NAME | '(' expr ')'
	local c = ls.t.token
	if c == "(" then
		local line = ls.linenumber
		luaX:next(ls)
		self:expr(ls, v)
		self:check_match(ls, ")", "(", line)
		luaK:dischargevars(ls.fs, v)
	elseif c == "TK_NAME" then
		self:singlevar(ls, v)
	else
		luaX:syntaxerror(ls, "unexpected symbol")
	end --if c
	return
end

------------------------------------------------------------------------
-- parses a prefixexp (an expression in parentheses or a single variable)
-- or a function call specification
-- * used in simpleexp(), assignment(), exprstat()
------------------------------------------------------------------------
function luaY:primaryexp(ls, v)
	-- primaryexp ->
	--    prefixexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs }
	local fs = ls.fs
	self:prefixexp(ls, v)
	while true do
		local c = ls.t.token
		if c == "." then -- field
			self:field(ls, v)
		elseif c == "[" then -- '[' exp1 ']'
			local key = {} -- expdesc
			luaK:exp2anyreg(fs, v)
			self:yindex(ls, key)
			luaK:indexed(fs, v, key)
		elseif c == ":" then -- ':' NAME funcargs
			local key = {} -- expdesc
			luaX:next(ls)
			self:checkname(ls, key)
			luaK:_self(fs, v, key)
			self:funcargs(ls, v)
		elseif c == "(" or c == "TK_STRING" or c == "{" then -- funcargs
			luaK:exp2nextreg(fs, v)
			self:funcargs(ls, v)
		else
			return
		end --if c
	end --while
end

------------------------------------------------------------------------
-- parses general expression types, constants handled here
-- * used in subexpr()
------------------------------------------------------------------------
function luaY:simpleexp(ls, v)
	-- simpleexp -> NUMBER | STRING | NIL | TRUE | FALSE | ... |
	--              constructor | FUNCTION body | primaryexp
	local c = ls.t.token
	if c == "TK_NUMBER" then
		self:init_exp(v, "VKNUM", 0)
		v.nval = ls.t.seminfo
	elseif c == "TK_STRING" then
		self:codestring(ls, v, ls.t.seminfo)
	elseif c == "TK_NIL" then
		self:init_exp(v, "VNIL", 0)
	elseif c == "TK_TRUE" then
		self:init_exp(v, "VTRUE", 0)
	elseif c == "TK_FALSE" then
		self:init_exp(v, "VFALSE", 0)
	elseif c == "TK_DOTS" then -- vararg
		local fs = ls.fs
		self:check_condition(
			ls,
			fs.f.is_vararg ~= 0,
			"cannot use " .. self:LUA_QL("...") .. " outside a vararg function"
		)
		-- NOTE: the following substitutes for a bitop, but is value-specific
		local is_vararg = fs.f.is_vararg
		if is_vararg >= self.VARARG_NEEDSARG then
			fs.f.is_vararg = is_vararg - self.VARARG_NEEDSARG -- don't need 'arg'
		end
		self:init_exp(v, "VVARARG", luaK:codeABC(fs, "OP_VARARG", 0, 1, 0))
	elseif c == "{" then -- constructor
		self:constructor(ls, v)
		return
	elseif c == "TK_FUNCTION" then
		luaX:next(ls)
		self:body(ls, v, false, ls.linenumber)
		return
	else
		self:primaryexp(ls, v)
		return
	end --if c
	luaX:next(ls)
end

------------------------------------------------------------------------
-- Translates unary operators tokens if found, otherwise returns
-- OPR_NOUNOPR. getunopr() and getbinopr() are used in subexpr().
-- * used in subexpr()
------------------------------------------------------------------------
function luaY:getunopr(op)
	if op == "TK_NOT" then
		return "OPR_NOT"
	elseif op == "-" then
		return "OPR_MINUS"
	elseif op == "#" then
		return "OPR_LEN"
	else
		return "OPR_NOUNOPR"
	end
end

------------------------------------------------------------------------
-- Translates binary operator tokens if found, otherwise returns
-- OPR_NOBINOPR. Code generation uses OPR_* style tokens.
-- * used in subexpr()
------------------------------------------------------------------------
luaY.getbinopr_table = {
	["+"] = "OPR_ADD",
	["-"] = "OPR_SUB",
	["*"] = "OPR_MUL",
	["/"] = "OPR_DIV",
	["%"] = "OPR_MOD",
	["^"] = "OPR_POW",
	["TK_CONCAT"] = "OPR_CONCAT",
	["TK_NE"] = "OPR_NE",
	["TK_EQ"] = "OPR_EQ",
	["<"] = "OPR_LT",
	["TK_LE"] = "OPR_LE",
	[">"] = "OPR_GT",
	["TK_GE"] = "OPR_GE",
	["TK_AND"] = "OPR_AND",
	["TK_OR"] = "OPR_OR",
}
function luaY:getbinopr(op)
	local opr = self.getbinopr_table[op]
	if opr then
		return opr
	else
		return "OPR_NOBINOPR"
	end
end

------------------------------------------------------------------------
-- the following priority table consists of pairs of left/right values
-- for binary operators (was a static const struct); grep for ORDER OPR
-- * the following struct is replaced:
--   static const struct {
--     lu_byte left;  /* left priority for each binary operator */
--     lu_byte right; /* right priority */
--   } priority[] = {  /* ORDER OPR */
------------------------------------------------------------------------
luaY.priority = {
	{ 6, 6 },
	{ 6, 6 },
	{ 7, 7 },
	{ 7, 7 },
	{ 7, 7 }, -- `+' `-' `/' `%'
	{ 10, 9 },
	{ 5, 4 }, -- power and concat (right associative)
	{ 3, 3 },
	{ 3, 3 }, -- equality
	{ 3, 3 },
	{ 3, 3 },
	{ 3, 3 },
	{ 3, 3 }, -- order
	{ 2, 2 },
	{ 1, 1 }, -- logical (and/or)
}

luaY.UNARY_PRIORITY = 8 -- priority for unary operators

------------------------------------------------------------------------
-- Parse subexpressions. Includes handling of unary operators and binary
-- operators. A subexpr is given the rhs priority level of the operator
-- immediately left of it, if any (limit is -1 if none,) and if a binop
-- is found, limit is compared with the lhs priority level of the binop
-- in order to determine which executes first.
------------------------------------------------------------------------

------------------------------------------------------------------------
-- subexpr -> (simpleexp | unop subexpr) { binop subexpr }
-- where 'binop' is any binary operator with a priority higher than 'limit'
-- * for priority lookups with self.priority[], 1=left and 2=right
-- * recursively called
-- * used in expr()
------------------------------------------------------------------------
function luaY:subexpr(ls, v, limit)
	self:enterlevel(ls)
	local uop = self:getunopr(ls.t.token)
	if uop ~= "OPR_NOUNOPR" then
		luaX:next(ls)
		self:subexpr(ls, v, self.UNARY_PRIORITY)
		luaK:prefix(ls.fs, uop, v)
	else
		self:simpleexp(ls, v)
	end
	-- expand while operators have priorities higher than 'limit'
	local op = self:getbinopr(ls.t.token)
	while op ~= "OPR_NOBINOPR" and self.priority[luaK.BinOpr[op] + 1][1] > limit do
		local v2 = {} -- expdesc
		luaX:next(ls)
		luaK:infix(ls.fs, op, v)
		-- read sub-expression with higher priority
		local nextop = self:subexpr(ls, v2, self.priority[luaK.BinOpr[op] + 1][2])
		luaK:posfix(ls.fs, op, v, v2)
		op = nextop
	end
	self:leavelevel(ls)
	return op -- return first untreated operator
end

------------------------------------------------------------------------
-- Expression parsing starts here. Function subexpr is entered with the
-- left operator (which is non-existent) priority of -1, which is lower
-- than all actual operators. Expr information is returned in parm v.
-- * used in multiple locations
------------------------------------------------------------------------
function luaY:expr(ls, v)
	self:subexpr(ls, v, 0)
end

-- }====================================================================

--[[--------------------------------------------------------------------
-- Rules for Statements
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- checks next token, used as a look-ahead
-- * returns boolean instead of 0|1
-- * used in retstat(), chunk()
------------------------------------------------------------------------
function luaY:block_follow(token)
	if token == "TK_ELSE" or token == "TK_ELSEIF" or token == "TK_END" or token == "TK_UNTIL" or token == "TK_EOS" then
		return true
	else
		return false
	end
end

------------------------------------------------------------------------
-- parse a code block or unit
-- * used in multiple functions
------------------------------------------------------------------------
function luaY:block(ls)
	-- block -> chunk
	local fs = ls.fs
	local bl = {} -- BlockCnt
	self:enterblock(fs, bl, false)
	self:chunk(ls)
	lua_assert(bl.breaklist == luaK.NO_JUMP)
	self:leaveblock(fs)
end

------------------------------------------------------------------------
-- structure to chain all variables in the left-hand side of an
-- assignment
-- struct LHS_assign:
--   prev  -- (table: struct LHS_assign)
--   v  -- variable (global, local, upvalue, or indexed) (table: expdesc)
------------------------------------------------------------------------

------------------------------------------------------------------------
-- check whether, in an assignment to a local variable, the local variable
-- is needed in a previous assignment (to a table). If so, save original
-- local value in a safe place and use this safe copy in the previous
-- assignment.
-- * used in assignment()
------------------------------------------------------------------------
function luaY:check_conflict(ls, lh, v)
	local fs = ls.fs
	local extra = fs.freereg -- eventual position to save local variable
	local conflict = false
	while lh do
		if lh.v.k == "VINDEXED" then
			if lh.v.info == v.info then -- conflict?
				conflict = true
				lh.v.info = extra -- previous assignment will use safe copy
			end
			if lh.v.aux == v.info then -- conflict?
				conflict = true
				lh.v.aux = extra -- previous assignment will use safe copy
			end
		end
		lh = lh.prev
	end
	if conflict then
		luaK:codeABC(fs, "OP_MOVE", fs.freereg, v.info, 0) -- make copy
		luaK:reserveregs(fs, 1)
	end
end

------------------------------------------------------------------------
-- parse a variable assignment sequence
-- * recursively called
-- * used in exprstat()
------------------------------------------------------------------------
function luaY:assignment(ls, lh, nvars)
	local e = {} -- expdesc
	-- test was: VLOCAL <= lh->v.k && lh->v.k <= VINDEXED
	local c = lh.v.k
	self:check_condition(ls, c == "VLOCAL" or c == "VUPVAL" or c == "VGLOBAL" or c == "VINDEXED", "syntax error")
	if self:testnext(ls, ",") then -- assignment -> ',' primaryexp assignment
		local nv = {} -- LHS_assign
		nv.v = {}
		nv.prev = lh
		self:primaryexp(ls, nv.v)
		if nv.v.k == "VLOCAL" then
			self:check_conflict(ls, lh, nv.v)
		end
		self:checklimit(ls.fs, nvars, self.LUAI_MAXCCALLS - ls.L.nCcalls, "variables in assignment")
		self:assignment(ls, nv, nvars + 1)
	else -- assignment -> '=' explist1
		self:checknext(ls, "=")
		local nexps = self:explist1(ls, e)
		if nexps ~= nvars then
			self:adjust_assign(ls, nvars, nexps, e)
			if nexps > nvars then
				ls.fs.freereg = ls.fs.freereg - (nexps - nvars) -- remove extra values
			end
		else
			luaK:setoneret(ls.fs, e) -- close last expression
			luaK:storevar(ls.fs, lh.v, e)
			return -- avoid default
		end
	end
	self:init_exp(e, "VNONRELOC", ls.fs.freereg - 1) -- default assignment
	luaK:storevar(ls.fs, lh.v, e)
end

------------------------------------------------------------------------
-- parse condition in a repeat statement or an if control structure
-- * used in repeatstat(), test_then_block()
------------------------------------------------------------------------
function luaY:cond(ls)
	-- cond -> exp
	local v = {} -- expdesc
	self:expr(ls, v) -- read condition
	if v.k == "VNIL" then
		v.k = "VFALSE"
	end -- 'falses' are all equal here
	luaK:goiftrue(ls.fs, v)
	return v.f
end

------------------------------------------------------------------------
-- parse a break statement
-- * used in statements()
------------------------------------------------------------------------
function luaY:breakstat(ls)
	-- stat -> BREAK
	local fs = ls.fs
	local bl = fs.bl
	local upval = false
	while bl and not bl.isbreakable do
		if bl.upval then
			upval = true
		end
		bl = bl.previous
	end
	if not bl then
		luaX:syntaxerror(ls, "no loop to break")
	end
	if upval then
		luaK:codeABC(fs, "OP_CLOSE", bl.nactvar, 0, 0)
	end
	bl.breaklist = luaK:concat(fs, bl.breaklist, luaK:jump(fs))
end

------------------------------------------------------------------------
-- parse a while-do control structure, body processed by block()
-- * with dynamic array sizes, MAXEXPWHILE + EXTRAEXP limits imposed by
--   the function's implementation can be removed
-- * used in statements()
------------------------------------------------------------------------
function luaY:whilestat(ls, line)
	-- whilestat -> WHILE cond DO block END
	local fs = ls.fs
	local bl = {} -- BlockCnt
	luaX:next(ls) -- skip WHILE
	local whileinit = luaK:getlabel(fs)
	local condexit = self:cond(ls)
	self:enterblock(fs, bl, true)
	self:checknext(ls, "TK_DO")
	self:block(ls)
	luaK:patchlist(fs, luaK:jump(fs), whileinit)
	self:check_match(ls, "TK_END", "TK_WHILE", line)
	self:leaveblock(fs)
	luaK:patchtohere(fs, condexit) -- false conditions finish the loop
end

------------------------------------------------------------------------
-- parse a repeat-until control structure, body parsed by chunk()
-- * used in statements()
------------------------------------------------------------------------
function luaY:repeatstat(ls, line)
	-- repeatstat -> REPEAT block UNTIL cond
	local fs = ls.fs
	local repeat_init = luaK:getlabel(fs)
	local bl1, bl2 = {}, {} -- BlockCnt
	self:enterblock(fs, bl1, true) -- loop block
	self:enterblock(fs, bl2, false) -- scope block
	luaX:next(ls) -- skip REPEAT
	self:chunk(ls)
	self:check_match(ls, "TK_UNTIL", "TK_REPEAT", line)
	local condexit = self:cond(ls) -- read condition (inside scope block)
	if not bl2.upval then -- no upvalues?
		self:leaveblock(fs) -- finish scope
		luaK:patchlist(ls.fs, condexit, repeat_init) -- close the loop
	else -- complete semantics when there are upvalues
		self:breakstat(ls) -- if condition then break
		luaK:patchtohere(ls.fs, condexit) -- else...
		self:leaveblock(fs) -- finish scope...
		luaK:patchlist(ls.fs, luaK:jump(fs), repeat_init) -- and repeat
	end
	self:leaveblock(fs) -- finish loop
end

------------------------------------------------------------------------
-- parse the single expressions needed in numerical for loops
-- * used in fornum()
------------------------------------------------------------------------
function luaY:exp1(ls)
	local e = {} -- expdesc
	self:expr(ls, e)
	local k = e.k
	luaK:exp2nextreg(ls.fs, e)
	return k
end

------------------------------------------------------------------------
-- parse a for loop body for both versions of the for loop
-- * used in fornum(), forlist()
------------------------------------------------------------------------
function luaY:forbody(ls, base, line, nvars, isnum)
	-- forbody -> DO block
	local bl = {} -- BlockCnt
	local fs = ls.fs
	self:adjustlocalvars(ls, 3) -- control variables
	self:checknext(ls, "TK_DO")
	local prep = isnum and luaK:codeAsBx(fs, "OP_FORPREP", base, luaK.NO_JUMP) or luaK:jump(fs)
	self:enterblock(fs, bl, false) -- scope for declared variables
	self:adjustlocalvars(ls, nvars)
	luaK:reserveregs(fs, nvars)
	self:block(ls)
	self:leaveblock(fs) -- end of scope for declared variables
	luaK:patchtohere(fs, prep)
	local endfor = isnum and luaK:codeAsBx(fs, "OP_FORLOOP", base, luaK.NO_JUMP)
		or luaK:codeABC(fs, "OP_TFORLOOP", base, 0, nvars)
	luaK:fixline(fs, line) -- pretend that `OP_FOR' starts the loop
	luaK:patchlist(fs, isnum and endfor or luaK:jump(fs), prep + 1)
end

------------------------------------------------------------------------
-- parse a numerical for loop, calls forbody()
-- * used in forstat()
------------------------------------------------------------------------
function luaY:fornum(ls, varname, line)
	-- fornum -> NAME = exp1,exp1[,exp1] forbody
	local fs = ls.fs
	local base = fs.freereg
	self:new_localvarliteral(ls, "(for index)", 0)
	self:new_localvarliteral(ls, "(for limit)", 1)
	self:new_localvarliteral(ls, "(for step)", 2)
	self:new_localvar(ls, varname, 3)
	self:checknext(ls, "=")
	self:exp1(ls) -- initial value
	self:checknext(ls, ",")
	self:exp1(ls) -- limit
	if self:testnext(ls, ",") then
		self:exp1(ls) -- optional step
	else -- default step = 1
		luaK:codeABx(fs, "OP_LOADK", fs.freereg, luaK:numberK(fs, 1))
		luaK:reserveregs(fs, 1)
	end
	self:forbody(ls, base, line, 1, true)
end

------------------------------------------------------------------------
-- parse a generic for loop, calls forbody()
-- * used in forstat()
------------------------------------------------------------------------
function luaY:forlist(ls, indexname)
	-- forlist -> NAME {,NAME} IN explist1 forbody
	local fs = ls.fs
	local e = {} -- expdesc
	local nvars = 0
	local base = fs.freereg
	-- create control variables
	self:new_localvarliteral(ls, "(for generator)", nvars)
	nvars = nvars + 1
	self:new_localvarliteral(ls, "(for state)", nvars)
	nvars = nvars + 1
	self:new_localvarliteral(ls, "(for control)", nvars)
	nvars = nvars + 1
	-- create declared variables
	self:new_localvar(ls, indexname, nvars)
	nvars = nvars + 1
	while self:testnext(ls, ",") do
		self:new_localvar(ls, self:str_checkname(ls), nvars)
		nvars = nvars + 1
	end
	self:checknext(ls, "TK_IN")
	local line = ls.linenumber
	self:adjust_assign(ls, 3, self:explist1(ls, e), e)
	luaK:checkstack(fs, 3) -- extra space to call generator
	self:forbody(ls, base, line, nvars - 3, false)
end

------------------------------------------------------------------------
-- initial parsing for a for loop, calls fornum() or forlist()
-- * used in statements()
------------------------------------------------------------------------
function luaY:forstat(ls, line)
	-- forstat -> FOR (fornum | forlist) END
	local fs = ls.fs
	local bl = {} -- BlockCnt
	self:enterblock(fs, bl, true) -- scope for loop and control variables
	luaX:next(ls) -- skip `for'
	local varname = self:str_checkname(ls) -- first variable name
	local c = ls.t.token
	if c == "=" then
		self:fornum(ls, varname, line)
	elseif c == "," or c == "TK_IN" then
		self:forlist(ls, varname)
	else
		luaX:syntaxerror(ls, self:LUA_QL("=") .. " or " .. self:LUA_QL("in") .. " expected")
	end
	self:check_match(ls, "TK_END", "TK_FOR", line)
	self:leaveblock(fs) -- loop scope (`break' jumps to this point)
end

------------------------------------------------------------------------
-- parse part of an if control structure, including the condition
-- * used in ifstat()
------------------------------------------------------------------------
function luaY:test_then_block(ls)
	-- test_then_block -> [IF | ELSEIF] cond THEN block
	luaX:next(ls) -- skip IF or ELSEIF
	local condexit = self:cond(ls)
	self:checknext(ls, "TK_THEN")
	self:block(ls) -- `then' part
	return condexit
end

------------------------------------------------------------------------
-- parse an if control structure
-- * used in statements()
------------------------------------------------------------------------
function luaY:ifstat(ls, line)
	-- ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END
	local fs = ls.fs
	local escapelist = luaK.NO_JUMP
	local flist = self:test_then_block(ls) -- IF cond THEN block
	while ls.t.token == "TK_ELSEIF" do
		escapelist = luaK:concat(fs, escapelist, luaK:jump(fs))
		luaK:patchtohere(fs, flist)
		flist = self:test_then_block(ls) -- ELSEIF cond THEN block
	end
	if ls.t.token == "TK_ELSE" then
		escapelist = luaK:concat(fs, escapelist, luaK:jump(fs))
		luaK:patchtohere(fs, flist)
		luaX:next(ls) -- skip ELSE (after patch, for correct line info)
		self:block(ls) -- 'else' part
	else
		escapelist = luaK:concat(fs, escapelist, flist)
	end
	luaK:patchtohere(fs, escapelist)
	self:check_match(ls, "TK_END", "TK_IF", line)
end

------------------------------------------------------------------------
-- parse a local function statement
-- * used in statements()
------------------------------------------------------------------------
function luaY:localfunc(ls)
	local v, b = {}, {} -- expdesc
	local fs = ls.fs
	self:new_localvar(ls, self:str_checkname(ls), 0)
	self:init_exp(v, "VLOCAL", fs.freereg)
	luaK:reserveregs(fs, 1)
	self:adjustlocalvars(ls, 1)
	self:body(ls, b, false, ls.linenumber)
	luaK:storevar(fs, v, b)
	-- debug information will only see the variable after this point!
	self:getlocvar(fs, fs.nactvar - 1).startpc = fs.pc
end

------------------------------------------------------------------------
-- parse a local variable declaration statement
-- * used in statements()
------------------------------------------------------------------------
function luaY:localstat(ls)
	-- stat -> LOCAL NAME {',' NAME} ['=' explist1]
	local nvars = 0
	local nexps
	local e = {} -- expdesc
	repeat
		self:new_localvar(ls, self:str_checkname(ls), nvars)
		nvars = nvars + 1
	until not self:testnext(ls, ",")
	if self:testnext(ls, "=") then
		nexps = self:explist1(ls, e)
	else
		e.k = "VVOID"
		nexps = 0
	end
	self:adjust_assign(ls, nvars, nexps, e)
	self:adjustlocalvars(ls, nvars)
end

------------------------------------------------------------------------
-- parse a function name specification
-- * used in funcstat()
------------------------------------------------------------------------
function luaY:funcname(ls, v)
	-- funcname -> NAME {field} [':' NAME]
	local needself = false
	self:singlevar(ls, v)
	while ls.t.token == "." do
		self:field(ls, v)
	end
	if ls.t.token == ":" then
		needself = true
		self:field(ls, v)
	end
	return needself
end

------------------------------------------------------------------------
-- parse a function statement
-- * used in statements()
------------------------------------------------------------------------
function luaY:funcstat(ls, line)
	-- funcstat -> FUNCTION funcname body
	local v, b = {}, {} -- expdesc
	luaX:next(ls) -- skip FUNCTION
	local needself = self:funcname(ls, v)
	self:body(ls, b, needself, line)
	luaK:storevar(ls.fs, v, b)
	luaK:fixline(ls.fs, line) -- definition 'happens' in the first line
end

------------------------------------------------------------------------
-- parse a function call with no returns or an assignment statement
-- * used in statements()
------------------------------------------------------------------------
function luaY:exprstat(ls)
	-- stat -> func | assignment
	local fs = ls.fs
	local v = {} -- LHS_assign
	v.v = {}
	self:primaryexp(ls, v.v)
	if v.v.k == "VCALL" then -- stat -> func
		luaP:SETARG_C(luaK:getcode(fs, v.v), 1) -- call statement uses no results
	else -- stat -> assignment
		v.prev = nil
		self:assignment(ls, v, 1)
	end
end

------------------------------------------------------------------------
-- parse a return statement
-- * used in statements()
------------------------------------------------------------------------
function luaY:retstat(ls)
	-- stat -> RETURN explist
	local fs = ls.fs
	local e = {} -- expdesc
	local first, nret -- registers with returned values
	luaX:next(ls) -- skip RETURN
	if self:block_follow(ls.t.token) or ls.t.token == ";" then
		first, nret = 0, 0 -- return no values
	else
		nret = self:explist1(ls, e) -- optional return values
		if self:hasmultret(e.k) then
			luaK:setmultret(fs, e)
			if e.k == "VCALL" and nret == 1 then -- tail call?
				luaP:SET_OPCODE(luaK:getcode(fs, e), "OP_TAILCALL")
				lua_assert(luaP:GETARG_A(luaK:getcode(fs, e)) == fs.nactvar)
			end
			first = fs.nactvar
			nret = self.LUA_MULTRET -- return all values
		else
			if nret == 1 then -- only one single value?
				first = luaK:exp2anyreg(fs, e)
			else
				luaK:exp2nextreg(fs, e) -- values must go to the 'stack'
				first = fs.nactvar -- return all 'active' values
				lua_assert(nret == fs.freereg - first)
			end
		end --if
	end --if
	luaK:ret(fs, first, nret)
end

------------------------------------------------------------------------
-- initial parsing for statements, calls a lot of functions
-- * returns boolean instead of 0|1
-- * used in chunk()
------------------------------------------------------------------------
function luaY:statement(ls)
	local line = ls.linenumber -- may be needed for error messages
	local c = ls.t.token
	if c == "TK_IF" then -- stat -> ifstat
		self:ifstat(ls, line)
		return false
	elseif c == "TK_WHILE" then -- stat -> whilestat
		self:whilestat(ls, line)
		return false
	elseif c == "TK_DO" then -- stat -> DO block END
		luaX:next(ls) -- skip DO
		self:block(ls)
		self:check_match(ls, "TK_END", "TK_DO", line)
		return false
	elseif c == "TK_FOR" then -- stat -> forstat
		self:forstat(ls, line)
		return false
	elseif c == "TK_REPEAT" then -- stat -> repeatstat
		self:repeatstat(ls, line)
		return false
	elseif c == "TK_FUNCTION" then -- stat -> funcstat
		self:funcstat(ls, line)
		return false
	elseif c == "TK_LOCAL" then -- stat -> localstat
		luaX:next(ls) -- skip LOCAL
		if self:testnext(ls, "TK_FUNCTION") then -- local function?
			self:localfunc(ls)
		else
			self:localstat(ls)
		end
		return false
	elseif c == "TK_RETURN" then -- stat -> retstat
		self:retstat(ls)
		return true -- must be last statement
	elseif c == "TK_BREAK" then -- stat -> breakstat
		luaX:next(ls) -- skip BREAK
		self:breakstat(ls)
		return true -- must be last statement
	else
		self:exprstat(ls)
		return false -- to avoid warnings
	end --if c
end

------------------------------------------------------------------------
-- parse a chunk, which consists of a bunch of statements
-- * used in parser(), body(), block(), repeatstat()
------------------------------------------------------------------------
function luaY:chunk(ls)
	-- chunk -> { stat [';'] }
	local islast = false
	self:enterlevel(ls)
	while not islast and not self:block_follow(ls.t.token) do
		islast = self:statement(ls)
		self:testnext(ls, ";")
		lua_assert(ls.fs.f.maxstacksize >= ls.fs.freereg and ls.fs.freereg >= ls.fs.nactvar)
		ls.fs.freereg = ls.fs.nactvar -- free registers
	end
	self:leavelevel(ls)
end

-- }======================================================================

luaX:init() -- required by llex
local LuaState = {} -- dummy, not actually used, but retained since
-- the intention is to complete a straight port

------------------------------------------------------------------------
-- interfacing to yueliang
------------------------------------------------------------------------

return function(source, name)
	name = name or "compiled-lua"
	-- luaZ:make_getF returns a file chunk reader
	-- luaZ:init returns a zio input stream
	local zio = luaZ:init(luaZ:make_getF(source), nil)
	if not zio then
		return
	end
	-- luaY:parser parses the input stream
	-- func is the function prototype in tabular form; in C, func can
	-- now be used directly by the VM, this can't be done in Lua

	local func = luaY:parser(LuaState, zio, nil, "@" .. name)
	-- luaU:make_setS returns a string chunk writer
	local writer, buff = luaU:make_setS()
	-- luaU:dump builds a binary chunk
	luaU:dump(LuaState, func, writer, buff)
	-- a string.dump equivalent in returned

	return buff.data
end
]]></ProtectedString>
                    <int64 name="SourceAssetId">-1</int64>
                    <BinaryString name="Tags"></BinaryString>
                </Properties>
            </Item>
        </Item>
        <Item class="ModuleScript" referent="RBX9897D4B033164DE29C13BFB8E87F630F">
            <Properties>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="DefinesCapabilities">false</bool>
                <Content name="LinkedSource"><null></null></Content>
                <string name="Name">Zyntex</string>
                <string name="ScriptGuid">{7350D5EC-0E5C-4E93-B6F2-CF12F4B8922B}</string>
                <ProtectedString name="Source"><![CDATA[--[[
	Zyntex Roblox SDK
	Version: 6
	Last Updated: 2025-09-01
	Author: Deluct
	
	This module is the main client for the Zyntex Advanced Admin/Ops Panel.
	It handles communication with the Zyntex API, manages server status, player events,
	and listens for administrative actions from the Zyntex dashboard.
	
	For full documentation, visit: https://docs.zyntex.dev/
]]

local Stats       = game:GetService("Stats")
local RunService  = game:GetService("RunService")
local Players     = game:GetService("Players")
local LogService  = game:GetService("LogService")
local Chat        = game:GetService("Chat")
local TCS         = game:GetService("TextChatService")
local MS          = game:GetService("MarketplaceService")

local API         = require(script.Parent:FindFirstChild("api"))
local TYPES       = require(script.Parent:FindFirstChild("types"))
local Telemetry   = require(script.Parent:FindFirstChild("Telemetry"))
local Experiments = require(script.Parent:FindFirstChild("Experiments"))

local CLIENT_VERSION = 6



--[[
	@function now
	@return string
	
	Generates a UTC timestamp in ISO 8601 format. This is used for 'since'
	parameters in API calls to ensure all data is synchronized correctly.
]]
local function now()
	local dt = DateTime.now():ToUniversalTime()
	return string.format(
		"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
		dt.Year, dt.Month, dt.Day,
		dt.Hour, dt.Minute, dt.Second,
		dt.Millisecond
	)
end

--[[
	@function toFormat
	@param date DateTime
	@return string
	
	Converts a given Roblox DateTime object into a UTC timestamp in ISO 8601 format.
]]
local function toFormat(date: DateTime)
	local date = date:ToUniversalTime()
	return string.format(
		"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
		date.Year, date.Month, date.Day,
		date.Hour, date.Minute, date.Second,
		date.Millisecond
	)
end

--[[
	https://devforum.roblox.com/t/mute-players-with-textchatservice/2519300/4?u=deluc_t
]]
local function muteUserId(mutedUserId)
	-- listen for future TextSources
	TCS.DescendantAdded:Connect(function(child)
		if child:IsA("TextSource") then
			if child.UserId == mutedUserId then
				child:Destroy()
			end
		end
	end)

	-- mute any current TextSources
	for _, child in TCS:GetDescendants() do
		if child:IsA("TextSource") then
			if child.UserId == mutedUserId then
				child:Destroy()
			end
		end
	end

	game:GetService("ReplicatedStorage"):FindFirstChild("zyntex.events"):FindFirstChild("SystemChat"):FireClient(Players:GetPlayerByUserId(mutedUserId), `You're muted.`)
end

--[[
  _____  _                       
 |  __ \| |                      
 | |__) | | __ _ _   _  ___ _ __ 
 |  ___/| |/ _` | | | |/ _ \ '__|
 | |    | | (_| | |_| |  __/ |   
 |_|    |_|\__,_|\__, |\___|_|   
                  __/ |          
                 |___/           
                 
	This section defines the 'ZyntexPlayer' object, which represents a Player
	in the Zyntex dashboard.
]]

local ZyntexPlayer = {}
ZyntexPlayer.__index = ZyntexPlayer

-- Represents a single, recorded instance of an invoked event.
type ZyntexPlayerType = {
	id: number; -- The unique ID of this specific player. Equivalent to the Roblox UserId
	name: string; -- The player's username as it appears on Roblox.
	avatar_url: string; -- The player's headshot avatar url.
	avatar_url_cache_expiry: string; -- When the player's avatar URL expires. Not important for Roblox.
	reputation: string; -- The player's reputation enum. Can be: 'clean', 'suspect', or 'offender'. https://docs.zyntex.dev/moderation/reputation
	raw_reputation: number; -- The player's raw reputation as an integer. https://docs.zyntex.dev/moderation/reputation
}

export type ZyntexPlayer = typeof(setmetatable({} :: ZyntexPlayerType, ZyntexPlayer))

function ZyntexPlayer.new(id: number, name: string, avatar_url: string, avatar_url_cache_expiry: string, reputation: string, raw_reputation: number)
	local self = {}
	self.id = id;
	self.name = name
	self.avatar_url = avatar_url
	self.avatar_url_cache_expiry = avatar_url_cache_expiry
	self.reputation = reputation
	self.raw_reputation = raw_reputation

	return setmetatable(self, ZyntexPlayer)
end

local SERVER_ACTIVITY = {} -- stores player joins & leaves to send to Zyntex in the case that the servers go down during the server
local sendingStatus = false

--[[

  _____                                _   _               
 |_   _|                              | | (_)              
   | |  _ ____   _____   ___ __ _ | |_ _  ___  _ __   ___ 
   | | | '_ \ \ / / _ \ / __/ _` | __| |/ _ \| '_ \ / __|
  _| |_| | | \ V / (_) | (_| (_| | |_| | (_) | | | \__ \
 |_____|_| |_|\_/ \___/ \___\__,_|\__|_|\___/|_| |_|___/
                                                       
                                                       

	This section defines the 'Invocation' object, which represents a single,
	successful execution of an Event.
]]

export type Data = {{["key"]: string, ["value"]: any}}
export type DataSchema = {{["key"]: string, ["type"]: string}}

local Invocation = {}
Invocation.__index = Invocation

-- Represents a single, recorded instance of an invoked event.
type InvocationType = {
	id: number; -- The unique ID of this specific invocation.
	eventId: number; -- The ID of the Event that was invoked.
	data: Data; -- The data payload that was sent with the invocation.
	to: string; -- The destination of the invocation (e.g., a specific server or 'global').
	sender: string; -- Who or what sent the invocation (e.g., 'dashboard', 'server').
	fromServer: string; -- The ID of the server that sent the invocation, if applicable.
	invokedAt: string; -- The ISO 8601 timestamp of when the invocation occurred.
	toServer: string; -- The ID of the specific server this was sent to, if applicable.
	gameId: number; -- The Roblox Place ID where the invocation originated.
	invokedBy: number; -- The UserID of the admin who triggered the invocation from the dashboard, if applicable.
}

export type Invocation = typeof(setmetatable({} :: InvocationType, Invocation))

--[[
	@constructor Invocation.new
	@description Constructs a new Invocation object from raw data. This is typically used internally
	by the client to wrap data received from the API into a usable object.
	@param id number
	@param eventId number
	@param data Data
	@param to string
	@param sender string
	@param fromServer string
	@param invokedAt string
	@param toServer string
	@param gameId string
	@param invokedBy number
	@return Invocation
]]
function Invocation.new(id: number, eventId: number, data: Data, to: string, sender: string, fromServer: string, invokedAt: string, toServer: string, gameId: string, invokedBy: number)
	local self = {}
	self.id = id;
	self.eventId = eventId;
	self.data = data;
	self.to = to;
	self.sender = sender;
	self.fromServer = fromServer;
	self.invokedAt = invokedAt;
	self.toServer = toServer;
	self.gameId = gameId;
	self.invokedBy = invokedBy;

	return setmetatable(self, Invocation)
end

--[[

 ______                _      
|  ____|              | |     
| |__    _____ _ __  | |_ ___ 
|  __|  / / _ \ '_ \| __/ __|
| |____ V /  __/ | | | |_\__ \
|______\_/ \___|_| |_|\__|___/
                               
                               
	This section defines the 'Event' object, which represents a configurable,
	remote event that can be invoked from the game server or listened to.
]]

local Event = {}
Event.__index = Event

-- Represents a remote event defined in the Zyntex dashboard.
type EventType = {
	id: number; -- The unique ID of the event.
	name: string; -- The user-defined name of the event.
	description: string; -- The user-defined description of the event.
	dataStructure: Data; -- The schema defining the expected data payload.
	deleted: boolean; -- Whether the event has been marked as deleted.
	_zyntex: Zyntex; -- Internal reference to the main Zyntex client instance.
}

export type Event = typeof(setmetatable({} :: EventType, Event))

--[[
	@method Event:Invoke
	@description Sends an invocation for this Event to the Zyntex backend.
	This allows the game server to trigger events that can be logged or listened to by other systems.
	The data structure provided must match the schema defined on the Zyntex dashboard.
	
	@param self Event -- The event object to invoke.
	@param data Data -- A table containing the key-value data payload for the event.
	@return Invocation -- Returns an Invocation object if the API call is successful.
	
	@see https://docs.zyntex.dev/ for more details on creating and using events.
	
	@example
	local killEvent = Zyntex:GetEvent("PlayerKilled")
	killEvent:Invoke({
		killerId = 12345,
		victimId = 54321,
		weapon = "Sword"
	})
]]
function Event.Invoke(self: Event, data: Data): Invocation
	-- Enqueue; response comes back via /push listen_data later.
	self._zyntex._session:post(
		`/roblox/events/invoke`,
		{
			["server_id"] = self._zyntex._session.jobID,
			["event_id"] = self.id;
			["data"] = data
		}
	)

	-- Return a synthetic "queued" invocation placeholder
	return Invocation.new(
		-1,
		self.id,
		data,
		"web",
		"roblox",
		self._zyntex._session.jobID,
		now(),
		nil,
		0,
		0
	)
end

--[[
	@method Event:Connect
	@description Establishes a listener for this specific event. The provided callback function
	will be executed whenever an invocation for this event is received from the Zyntex backend (e.g., sent from the dashboard).
	
	@param self Event -- The event object to listen to.
	@param listener (({[string]: any}, Invocation) -> nil) -- The callback function to execute.
		- The first argument passed to the listener is a simplified data table: `{["key"] = value}`.
		- The second argument is the full, raw Invocation object.
	
	@example
	local broadcastEvent = Zyntex:GetEvent("BroadcastMessage")
	broadcastEvent:Connect(function(data, invocation)
		print(`Received broadcast from user {invocation.invokedBy}: {data.Message}`)
	end)
]]
function Event.Connect(self: Event, listener: ({[string]: any}, Invocation) -> nil)
	local function wrapper(inv)
		if inv.event_id == self.id then
			local simpleData = {}

			for _,v in pairs(inv.data) do
				simpleData[v["key"]] = v["value"]
			end

			listener(
				simpleData, 
				Invocation.new(
					inv.id,
					inv.event_id,
					inv.data,
					inv.to,
					inv.sender,
					inv.from_server,
					inv.invoked_at,
					inv.to_server,
					inv.game_id,
					inv.invoked_by
				)
			)
		end
	end
	if not self._zyntex._listeners["invocation"] then
		self._zyntex._listeners["invocation"] = {}
	end
	table.insert(
		self._zyntex._listeners["invocation"], 
		wrapper
	)
end

--[[
	@constructor Event.new
	@description Constructs a new Event object. This is used internally during the initialization
	process to populate the list of available events from the Zyntex backend.
]]
function Event.new(zyntex: Zyntex, id: number, name: string, description: string, dataStructure: DataSchema, deleted: boolean)
	local self = {}
	self.id = id
	self.name = name
	self.description = description
	self.dataStructure = dataStructure
	self.deleted = deleted
	self._zyntex = zyntex

	return setmetatable(self, Event)
end

--[[
  ______                _   _                 
 |  ____|              | | (_)                
 | |__ _   _ _ __   ___| |_ _  ___  _ __  ___ 
 |  __| | | | '_ \ / __| __| |/ _ \| '_ \/ __|
 | |  | |_| | | | | (__| |_| | (_) | | | \__ \
 |_|   \__,_|_| |_|\___|\__|_|\___/|_| |_|___/
                                              
	This section defines the 'Function' object, which is similar to events, but
	can only be invoked from the web-dashboard and it sends back data
]]

local Function = {}
Function.__index = Function

type FunctionParameter = {
	name: string; -- The name of the paramater
	type: string; -- The type of the parameter's value
}

-- Represents a remote function defined in the Zyntex dashboard.
type FunctionType = {
	id: number; -- The unique ID of the function.
	name: string; -- The user-defined name of the event.
	description: string; -- The user-defined description of the event.
	parameters: {FunctionParameter}; -- The list of parameters the function accepts
	_zyntex: Zyntex;

	Connect: (self: Function, listener: ({[string]: any}) -> any) -> nil;

}

export type Function = typeof(setmetatable({} :: FunctionType, Function))

--[[
	@constructor Function.new
	@description Constructs a new Function object that represents a callable remote function
	defined in the Zyntex dashboard. This is typically used internally when the client
	initializes and materializes the functions available to your experience.

	@param zyntex Zyntex -- The active Zyntex client, used for networking and configuration.
	@param id number -- The unique ID of the remote function.
	@param name string -- The user-defined name of the function.
	@param description string -- A human-readable description of the function.
	@param parameters {FunctionParameter} -- The schema describing the parameters this function accepts.

	@return Function -- The constructed Function instance.
]]
function Function.new(zyntex: Zyntex, id: number, name: string, description: string, parameters: {FunctionParameter})
	local self = {}
	self.id = id
	self.name = name
	self.description = description
	self.parameters = parameters
	self._zyntex = zyntex

	return setmetatable(self, Function)
end

--[[
	@method Function:Connect
	@description Registers a listener that will be invoked whenever a call targeting this
	remote function is received from the Zyntex backend. The listener is passed a simple
	parameters table and is expected to return a value (typically a table) which will be
	sent back to Zyntex as the payload response.

	@param self Function -- The Function instance to listen on.
	@param listener (({[string]: any}) -> any) -- Callback executed when this function is invoked.
		- Receives: a table of parameter key/value pairs (`metadata.parameters`).
		- Returns: any serializable value (commonly a table) that becomes the payload response.

	@return nil -- This method registers the listener and does not return a connection handle.

	@errors Asserts if the payload POST fails; the assertion message is the API's `user_message`.

	@example
	-- Assume a remote function "Add" with parameters: { a: number, b: number }
	local addFn = Zyntex:GetFunction("Add")
	addFn:Connect(function(params)
		local a = params.a
		local b = params.b
		-- Whatever is returned here is sent back to Zyntex as the payload:
		return { sum = a + b }
	end)
]]
function Function.Connect(self: Function, listener: ({[string]: any}) -> any)
	local function wrapper(data)
		if data.metadata.function_id == self.id then
			if self._zyntex._config.debug then
				print(`[Zyntex]: Function received, calling listener...`)
			end

			local payload = listener(
				data.metadata.parameters
			)

			if self._zyntex._config.debug then
				print(`[Zyntex]: Sending payload response...`)
			end

			assert(payload ~= nil, 'Function:Connect hook must return a value')

			local callRes = self._zyntex._session:post(
				`/roblox/actions/{data.id}/send_payload`,
				{ ["data"] = payload },
				true
			)

			if self._zyntex._config.debug then
				print(`[Zyntex]: Successfully sent payload response to function`)
			end
		end
	end
	if not self._zyntex._listeners["function"] then
		self._zyntex._listeners["function"] = {}
	end
	table.insert(
		self._zyntex._listeners["function"], 
		wrapper
	)
end


--[[

  ______           _            
 |___  /          | |           
    / /_ __ _ _ __ | |_ _____  __
   / /| | | | '_ \| __/ _ \ \/ /
  / /_| |_| | | | | ||  __/>  < 
 /_____\__, |_| |_|\__\___/_/\_\
        __/ |                  
       |___/                   

	This section defines the main 'Zyntex' client object, which serves as the primary
	interface for interacting with the entire Zyntex system.
]]

local Zyntex = { VERSION = CLIENT_VERSION }
Zyntex.__index = Zyntex

-- The main state container for the Zyntex client.
type ZyntexType = {
	_session: API.Session; -- Handles authenticated requests to the Zyntex API.
	_config:  TYPES.Config; -- Stores the user-defined configuration for the client.
	_events:  {Event}; -- A list of all available Event objects fetched from the backend.
	_functions: {Function}; -- A list of all available Function objects fetched from the backend.
	_listeners: {[string]: ({[string]: any}) -> nil}; -- A dictionary of listeners for incoming actions (e.g., 'shutdown', 'rce').
	_version: number;
	_pendingJoins: {[string]: Player}; -- key: batch rid -> Player
}

export type Zyntex = typeof(setmetatable({} :: ZyntexType, Zyntex))

local maxPlayers = 0;

--[[
	@function serverStatus
	@description Gathers real-time performance metrics about the current server instance.
	@return table -- A dictionary containing health status, memory usage, network traffic, and FPS.
]]
local function serverStatus()
	local maxMemory = 6400 + 100 * maxPlayers
	local stats = {
		["memory_usage"] = Stats:GetTotalMemoryUsageMb() / maxMemory,
		["data_receive_kbps"] = Stats.DataReceiveKbps or 0,
		["data_send_kbps"] = Stats.DataSendKbps or 0,
		["server_fps"] = math.clamp(1/RunService.Heartbeat:Wait(), 0, 100),
		["activity"] = SERVER_ACTIVITY
	}

	if stats.memory_usage > .5 or stats.data_send_kbps > 500 or stats.data_receive_kbps > 500 or stats.server_fps < 30 then
		stats["health"] = "unhealthy"
		return stats
	end

	stats["health"] = "healthy"
	return stats
end

--[[
	@method Zyntex:statusUpdate
	@description Sends the server's current health and performance metrics to the Zyntex dashboard.
	@param self Zyntex
	@param status {string: number} -- A table of metrics generated by `serverStatus()`.
]]
function Zyntex.statusUpdate(self: Zyntex, status: {string: number})
	if sendingStatus then return end
	sendingStatus = true
	if self._config.debug then
		print("[Zyntex]: Submitting server status update...")
	end

	pcall(function()
		self._session:post(
			"/roblox/servers/status",
			table.freeze({
				["server_id"] = self._session.jobID,
				["memory_usage"] = status["memory_usage"],
				["server_fps"] = status["server_fps"],
				["data_send_kbps"] = status["data_send_kbps"],
				["data_receive_kbps"] = status["data_receive_kbps"],
				["activity"] = status["activity"],
				["health"] = if status["health"] == "unhealthy" then "unhealthy" else "healthy"
			})
		)
		if self._config.debug then
			print("[Zyntex]: Sent server status update (queued)")
		end
	end)

	sendingStatus = false
end

--[[
	@function statusUpdateLoop
	@description A background loop that periodically collects and sends server status updates.
	It intelligently sends updates only when metrics have changed to reduce unnecessary network traffic.
	@param self Zyntex
]]
local function statusUpdateLoop(self: Zyntex)
	local lastStatus = serverStatus()

	while true do
		task.wait(15)

		local newStatus = serverStatus()

		self:statusUpdate(newStatus)
	end
end

--[[
	@function onPlayerAdd
	@description Handles all tasks associated with a player joining the game, including
	registering them with the Zyntex backend and setting up client-side scripts.
	@param self Zyntex
	@param player Player -- The player who just joined.
]]
local function onPlayerAdd(self: Zyntex, player: Player)
	if self._config.debug then
		print(`[Zyntex]: Handling player join for {player.Name}`)
	end

	local when = now()
	local activity = {
		player_id = player.UserId,
		player_name = player.Name,
		joined_at = when
	}

	-- Enqueue join; **capture the batch item's rid** returned by api.post
	local rid = self._session:post(
		"/roblox/players",
		{
			["server_id"] = self._session.jobID,
			["id"] = player.UserId,
			["name"] = player.Name,
			["timestamp"] = when
		}
	)
	-- Track pending join by the **batch rid** so we can reconcile on push results
	self._pendingJoins[rid] = player

	pcall(function()
		-- No immediate response under /push; moderation blocks are surfaced via server on next poll.

		if self._config.debug then
			print("[Zyntex]: Player join submitted.")
		end

		local zyntexEvents = Instance.new("Folder")
		zyntexEvents.Name = "zyntex.events"
		zyntexEvents.Parent = script.Parent

		local SystemChat = Instance.new("RemoteEvent")
		SystemChat.Name = "SystemChat"
		SystemChat.Parent = zyntexEvents

		-- Clone necessary client-side remote events into ReplicatedStorage if they don't exist.
		if not game:GetService("ReplicatedStorage"):FindFirstChild("zyntex.events") then
			zyntexEvents:Clone().Parent = game:GetService("ReplicatedStorage")
		end

		-- Provide the player with the client-side script handler.
		script.Parent:FindFirstChild("zyntex.client"):Clone().Parent = player.PlayerGui

		-- Listen for player chats and log them.
		pcall(function()
			player.Chatted:Connect(function(msg)
				self._session:post(
					"/roblox/players/chat",
					{
						["server_id"] = self._session.jobID,
						["message"] = msg;
						["player_id"] = player.UserId
					}
				)
			end)
		end)

		-- Track the maximum concurrent players for server health calculations.
		local count = #Players:GetPlayers()
		if count > maxPlayers then
			maxPlayers = count
		end
	end)

	-- visit_id will be reconciled on the backend; we don't have it synchronously here.

	table.insert(SERVER_ACTIVITY, activity)
end

--[[
	@function onPlayerRemove
	@description Handles a player leaving the game by notifying the Zyntex backend,
	which marks the player's session as ended.
	@param self Zyntex
	@param player Player -- The player who just left.
]]
local function onPlayerRemove(self: Zyntex, player: Player)
	if self._config.debug then
		print(`[Zyntex]: Handling player leave for {player.Name}`)
	end

	local when = now()

	-- Clean up any pending joins for this player
	for rid, pendingPlayer in pairs(self._pendingJoins) do
		if pendingPlayer.UserId == player.UserId then
			self._pendingJoins[rid] = nil
			if self._config.debug then
				print(("[Zyntex]: Cleaned up pending join for leaving player %s (RID: %s)"):format(player.Name, rid))
			end
			break
		end
	end

	-- Enqueue leave (handled by /roblox/push). Fire-and-forget.
	self._session:delete(
		"/roblox/players",
		{
			["server_id"] = self._session.jobID,
			["id"] = player.UserId,
			["timestamp"] = when
		}
	)

	if self._config.debug then
		print("[Zyntex]: Player leave submitted.")
	end

	for _,activity in SERVER_ACTIVITY do
		if activity.player_id == player.UserId and activity.left_at == nil then
			activity["left_at"] = when
		end
	end
end

--[[
	@method Zyntex:GetEventByID
	@description Fetches a pre-loaded Event object using its unique numerical ID.
	@param self Zyntex
	@param eventId number -- The ID of the event to retrieve.
	@return Event? -- The corresponding Event object, if exists.
]]
function Zyntex.GetEventByID(self: Zyntex, eventId: number): Event?
	for i,event in pairs(self._events) do
		if event.id == eventId then
			return event
		end
	end
	return nil
end

--[[
	@method Zyntex:GetEvent
	@description Fetches a pre-loaded Event object using its unique string name. This is the most common way to get an event.
	@param self Zyntex
	@param eventName string -- The case-sensitive name of the event.
	@return Event? -- The corresponding Event object, if it exists.
]]
function Zyntex.GetEvent(self: Zyntex, eventName: string): Event?
	for i,event in pairs(self._events) do
		if event.name == eventName then
			return event
		end
	end
	return nil
end

--[[
	@method Zyntex:GetFunctionByID
	@description Fetches a pre-loaded Function object using its unique numerical ID.
	@param self Zyntex
	@param functionId number -- The ID of the function to retrieve.
	@return Function? -- The corresponding Function object, if it exists.
]]
function Zyntex.GetFunctionByID(self: Zyntex, functionId: number): Function?
	for i,func in self._functions do
		if func.id == functionId then
			return func
		end
	end
	return nil
end

--[[
	@method Zyntex:GetFunction
	@description Fetches a pre-loaded Function object using its unique string name. This is the most common way to get a function.
	@param self Zyntex
	@param functionName string -- The case-sensitive name of the function.
	@return Function? -- The corresponding Function object, if it exists.
]]
function Zyntex.GetFunction(self: Zyntex, functionName: string): Function?
	for i,func in self._functions do
		if func.name == functionName then
			return func
		end
	end
	return nil
end

--[[
	@method Zyntex:Moderate
	@description Submits a generic moderation action to the Zyntex backend. This is a flexible, low-level function;
	it is often easier to use the shorthand methods (:Ban, :Kick, :Mute, :Report).
	
	@warning If the `test` parameter is `false` or `nil` (and not in Studio), this action WILL permanently affect a player's reputation.
	
	@param self Zyntex
	@param player (Player | number) -- The Player object or the UserId of the target.
	@param type string -- The type of moderation (e.g., "ban", "kick", "mute", "report").
	@param reason string -- The reason for the moderation action. This will be visible to staff.
	@param expiresAt (DateTime?) -- An optional DateTime for when the moderation expires. If nil, it may be permanent depending on the type.
	@param test (boolean?) -- If `true`, the moderation is treated as a test and does not affect reputation. Defaults to `true` in Studio.
	@return boolean -- Returns `true` on success.
]]
function Zyntex.Moderate(self: Zyntex, player: Player | number, type: string, reason: string, expiresAt: DateTime?, test: boolean?)
	local playerId = if typeof(player) == "Player" then player.UserId else player
	local playerObject = Players:GetPlayerByUserId(playerId)
	if self._config.debug then
		print(`[Zyntex]: Creating moderation {type} for {playerId}...`)
	end
	if expiresAt then
		expiresAt = toFormat(expiresAt)
	end

	-- Default to test mode if running in Roblox Studio unless explicitly overridden.
	if test == nil then
		test = RunService:IsStudio()
	end

	if type == "ban" then
		playerObject:Kick(reason)
	end

	local res = self._session:post(
		`/roblox/players/{playerId}/moderate`,
		{
			["server_id"] = self._session.jobID,
			["type"] = type,
			["reason"] = reason,
			["expires_at"] = expiresAt,
			["test"] = test
		}
	)

	if self._config.debug then
		print(`[Zyntex]: Moderation created successfully.`)
	end

	return true
end

--[[
	@method Zyntex:Report
	@description Shorthand method to create a "report" moderation. Reports are used for logging player behavior
	and contribute to their reputation score but do not have direct in-game consequences by default.
	@param self Zyntex
	@param player (Player | number) -- The Player object or the UserId of the target.
	@param reason string -- The reason for the report.
	@param test (boolean?) -- If true, the report will not affect player reputation. Defaults to `true` in Studio.
	@return boolean -- Returns `true` on success.
]]
function Zyntex.Report(self: Zyntex, player: Player | number, reason: string, test: boolean?)
	return self:Moderate(player, "report", reason, nil, test)
end

--[[
	@method Zyntex:Ban
	@description Shorthand method to create a "ban" moderation. This logs the ban action.
	Handles automatic kick automatically.
	@param self Zyntex
	@param player (Player | number) -- The Player object or the UserId of the target.
	@param reason string -- The reason for the ban.
	@param expiresAt (DateTime?) -- Optional expiration for the ban. If nil, the ban is permanent.
	@param test (boolean?) -- If true, the ban will not affect player reputation. Defaults to `true` in Studio.
	@return boolean -- Returns `true` on success.
]]
function Zyntex.Ban(self: Zyntex, player: Player | number, reason: string, expiresAt: DateTime?, test: boolean?)
	return self:Moderate(player, "ban", reason, expiresAt, test)
end

--[[
	@method Zyntex:Mute
	@description Shorthand method to create a "mute" moderation. This logs the mute action.
	Mutes the player automatically.
	@param self Zyntex
	@param player (Player | number) -- The Player object or the UserId of the target.
	@param reason string -- The reason for the mute.
	@param expiresAt (DateTime?) -- Optional expiration for the mute. If nil, the mute is permanent.
	@param test (boolean?) -- If true, the mute will not affect player reputation. Defaults to `true` in Studio.
	@return boolean -- Returns `true` on success.
]]
function Zyntex.Mute(self: Zyntex, player: Player | number, reason: string, expiresAt: DateTime?, test: boolean?)
	return self:Moderate(player, "mute", reason, expiresAt, test)
end

--[[
	@method Zyntex:Kick
	@description Shorthand method to create a "kick" moderation. This logs the kick action.
	Calls Player:Kick() automatically.
	@param self Zyntex
	@param player (Player | number) -- The Player object or the UserId of the target.
	@param reason string -- The reason for the kick.
	@param test (boolean?) -- If true, the kick will not affect player reputation. Defaults to `true` in Studio.
	@return boolean -- Returns `true` on success.
]]
function Zyntex.Kick(self: Zyntex, player: Player | number, reason: string, test: boolean?)
	return self:Moderate(player, "kick", reason, nil, test)
end



--[[
	@method Zyntex:CleanupPendingJoins
	@description Manually clean up pending joins that may have been stuck.
	This is useful for debugging or in case of network issues.
	@param self Zyntex
	@return number -- Returns the number of pending joins that were cleaned up.
]]
-- Note: CleanupPendingJoins removed - joins are cleaned up immediately on result

--[[
	@method Zyntex:GetPendingJoinsCount
	@description Get the current count of pending joins.
	This is useful for monitoring the state of join operations.
	@param self Zyntex
	@return number -- Returns the number of pending joins.
]]
function Zyntex.GetPendingJoinsCount(self: Zyntex)
	local count = 0
	for _ in pairs(self._pendingJoins) do
		count = count + 1
	end
	return count
end

--[[
	@method Zyntex:poll
	@description Initiates the long-polling loop to listen for real-time actions and invocations
	from the Zyntex dashboard. This runs in its own thread.
	@param self Zyntex
	@param since string -- An ISO 8601 timestamp to start listening from.
	@return (-> ()) -- Returns the polling function to be spawned in a new thread.
]]
function Zyntex.poll(self: Zyntex, since: string)
	return function()
		local nextWaitTime = Random.new():NextInteger(4, 7)
		while true do
			if self._config.debug then
				print("[Zyntex]: Flushing /push buffer (poll)...")
			end

			local items = {}
			local ok, resp = pcall(function()
				return self._session:Flush(since)
			end)
			if ok and resp and resp.success then
				items = (resp.data and resp.data.listen_data) or {}
				if #items == 0 then
					nextWaitTime = math.clamp(nextWaitTime + 0.1, 2, 10)
				else
					nextWaitTime = 2
					since = now()
				end
			else
				nextWaitTime = nextWaitTime + 10
			end

			-- Dispatch any received events
			for _, entry in pairs(items) do
				local bucket = self._listeners[entry.type]
				if bucket then
					for _, listener in pairs(bucket) do
						listener(entry.data)
					end
				end
			end

			-- React to per-item results: moderate on failed joins (401/403)
			if resp and resp.data and resp.data.results then
				for _, result in pairs(resp.data.results) do
					-- We only care about failed player joins here
					if result.method == "POST /roblox/players" and result.rid then
						local rid = result.rid
						local player = self._pendingJoins[rid]
						if player then
							if result.success == true then
								-- successfully admitted; clear pending
								self._pendingJoins[rid] = nil
								if self._config.debug then
									print(`[Zyntex]: Join confirmed for {player.Name} (RID: {rid})`)
								end
							else
								-- Parse "CODE: reason" -> e.g. "403: racism" / "401: spam"
								local codeStr, reason = nil, nil
								if typeof(result.error) == "string" then
									codeStr, reason = string.match(result.error, "^(%d+)%s*:%s*(.+)$")
								end
								local code = tonumber(codeStr or "")
								reason = reason or "Moderation"

								if code == 403 then
									-- BAN: kick immediately + fire ban listeners
									local p = Players:GetPlayerByUserId(player.UserId)
									if p then p:Kick(reason) end
									if self._listeners["ban"] then
										for _,cb in self._listeners["ban"] do
											cb(player.UserId, reason)
										end
									end
									if self._config.debug then
										warn(`[Zyntex]: {player.Name} blocked (BAN) — {reason}`)
									end
								elseif code == 401 then
									-- MUTE: silence chat + fire mute listeners
									muteUserId(player.UserId)
									if self._listeners["mute"] then
										for _,cb in self._listeners["mute"] do
											cb(player.UserId, reason)
										end
									end
									if self._config.debug then
										warn(`[Zyntex]: {player.Name} auto-muted — {reason}`)
									end
								else
									-- Unknown failure; just warn
									warn(`[Zyntex]: Join failed for {player.Name} (RID: {rid}) — {tostring(result.error)}`)
								end

								-- In all error cases, clear pending
								self._pendingJoins[rid] = nil
							end
						elseif self._config.debug then
							print(`[Zyntex]: Result for unknown RID (possibly already cleaned): {result.rid}`)
						end
					end
				end
			end

			task.wait(nextWaitTime)
		end
	end
end

--[[
	@method Zyntex:logServiceMessage
	@description Internal handler for capturing messages from Roblox's LogService (print, warn, error)
	and forwarding them to the Zyntex logs for the server.
	@param self Zyntex
	@param msg string -- The content of the log message.
	@param type Enum.MessageType -- The type of message (Output, Info, Warning, Error).
]]
function Zyntex.logServiceMessage(self: Zyntex, msg: string, type: Enum.MessageType)
	local convertedType: string?
	if type == Enum.MessageType.MessageOutput then
		convertedType = "info.print"
	end
	if type == Enum.MessageType.MessageError then
		convertedType = "info.error"
	end
	if type == Enum.MessageType.MessageWarning then
		convertedType = "info.warning"
	end
	if convertedType == nil then
		return type
	end

	-- Truncate long messages to prevent overly large payloads.
	local msg = string.sub(msg, 1, 100)    

	pcall(function()
		self._session:post(
			"/roblox/logservice",
			{
				["server_id"] = self._session.jobID,
				["message"] = msg,
				["type"] = convertedType
			}
		)
	end)
end

--[[
	@method Zyntex:Log
	@description Logs a custom message to the Zyntex servers. This is useful for tracking specific
	game events. The log will appear in the main logs, the server's log tab, and the associated player's log tab if a player is provided.
	
	@param self Zyntex
	@param message string -- The custom message to log.
	@param player (Player? | number?) -- Optional. The Player or UserId to associate with this log entry.
	@return boolean -- Returns `true` on success.
]]
function Zyntex.Log(self: Zyntex, message: string, player: Player? | number?)
	if self._config.debug then
		print(`[Zyntex]: Posting log...`)
	end
	local playerId, playerName;
	if player then
		playerId = if typeof(player) == "Player" then player.UserId else player
		playerName = if typeof(player) == "Player" then player.Name else Players:GetPlayerByUserId(player).Name
	end

	local payload = {
		["message"] = message
	}

	if playerId then
		payload["player_id"]   = playerId
		payload["player_name"] = playerName
	end

	local res = self._session:post(
		`/roblox/log`,
		payload
	)

	-- Under /push this is enqueued; no synchronous assert.

	if self._config.debug then
		print(`[Zyntex]: Log posted succesfully.`)
	end

	return true
end

--[[
	@method Zyntex:ProcessPurchase
	@description Logs a player's in-game purchase. This should be wired up to `MarketplaceService.ProcessReceipt`
	to track player spending and LTV on the dashboard.
	
	@param self Zyntex
	@param player (Player | number) -- The Player object or UserId who made the purchase.
	@param price number -- The price of the item in Robux.
	@param productName string -- The name of the product purchased. Using the name is preferred over the ID for clarity on the dashboard.
	@return boolean -- Returns `true` on success.
]]
function Zyntex.ProcessPurchase(self: Zyntex, player: Player | number, price: number, productName: string): boolean
	local res = self._session:post(
		`/roblox/players/{if type(player) == "number" then player else player.UserId}/robux-spent`,
		{
			["server_id"] = self._session.jobID,
			["robux_spent"] = price;
			["metadata"]    = productName
		}
	)

	return true
end

--[[
	@constructor Zyntex.new
	@description Constructs the main Zyntex client object and validates the API connection.
	It also checks if the client version is up-to-date with the latest version recommended by the backend.
	
	@param gameToken string -- The unique game token obtained from the Zyntex dashboard.
	@return Zyntex -- The newly created Zyntex instance.
]]
function Zyntex.new(gameToken: string): Zyntex
	local self = {}

	-- Pass JobId to enable the API push buffer/flush worker
	self._session   = API.new(gameToken)
	self._config    = {}
	self._events    = {}
	self._functions = {}
	self._listeners = {}
	self._pendingJoins = {}
	self._version   = CLIENT_VERSION

	local current_version = self._session:get("/roblox/latest-client-version") -- direct HTTP (not push)
	assert(current_version and current_version.success, `Zyntex API is currently down.`)
	if (current_version.data ~= CLIENT_VERSION) then
		warn(`[Zyntex]: Your client is outdated, please download the latest version. current: v{CLIENT_VERSION}; latest: v{current_version.data}`)
	end
	
	-- Inside Zyntex.init, right after `self._session` is created
	local function handlePush(data)
		-- reuse the same logic currently inside Zyntex.poll
		if data and data.results then
			for _, result in ipairs(data.results) do
				if result.method == "POST /roblox/players" and result.rid then
					local player = self._pendingJoins[result.rid]
					if not player then continue end

					if not result.success and typeof(result.error) == "string" then
						local codeStr, reason = string.match(
							result.error,
							"^(%d+)%s*:%s*(.+)$"
						)
						local code = tonumber(codeStr)
						reason = reason or "Moderation"

						if code == 403 then
							local p = Players:GetPlayerByUserId(player.UserId)
							if p then p:Kick(reason) end
						elseif code == 401 then
							muteUserId(player.UserId)
						end
					end
					self._pendingJoins[result.rid] = nil
				end
			end
		end
	end

	-- register the universal handler
	self._session:onFlush(handlePush)

	return setmetatable(self, Zyntex)
end

--[[
	@function randomUsername
	@description Generates a random username for simulation purposes.
]]
local function randomUsername()
	return "User_" .. math.random(1000, 9999)
end

--[[
	@function simulateActivity
	@description When `config.simulate` is true, this function creates fake player join/leave events
	to help test the system in Studio without needing real players.
]]
local function simulateActivity(self)
	local fakePlayers = {}

	while true do
		task.wait(math.random(0.1, 10))

		local action = math.random(1, 4)

		if action == 1 or action == 2 then
			-- Simulate Player Join
			local name = randomUsername()
			local id = 16054156146
			local fakePlayer = {
				Name = name,
				UserId = id
			}
			table.insert(fakePlayers, fakePlayer)
			pcall(onPlayerAdd, self, fakePlayer)

		elseif (action == 3 or action == 4) and #fakePlayers > 0 then
			-- Simulate Player Leave
			local index = math.random(1, #fakePlayers)
			local fakePlayer = table.remove(fakePlayers, index)
			pcall(onPlayerRemove, self, fakePlayer)
		end
	end
end

--[[
	@method Zyntex:GetPlayerInfo
	@description Fetches and returns player information, such as reputation, total robux spent, and total time played.
	
	@param self Zyntex
	@param player Player|number -- Either a Player object or the player's UserId. If UserId, the player does not have to be in the server.
]]
function Zyntex.GetPlayerInfo(self: Zyntex, player: Player | number): {player: ZyntexPlayer, total_robux_spent: number, total_time_played: number}
	local res = self._session:get(`/roblox/players/{if type(player) == "number" then player else player.UserId}`)

	assert(res.success, `Failed when attempting to fetch player: {res.user_message}`)

	local playerInfo = res.data
	local playerInfoRaw = res.data.player

	playerInfo["player"] = ZyntexPlayer.new(
		playerInfoRaw.id,
		playerInfoRaw.name,
		playerInfoRaw.avatar_url,
		playerInfoRaw.avatar_url_cache_expiry,
		playerInfoRaw.reputation,
		playerInfoRaw.raw_reputation
	)

	return playerInfo
end

--[[
	@method Zyntex:OnModeration
	@description Creates a hook to the moderation action.
	
	@param self Zyntex
	@param moderationType string -- Returns a moderationType. Either 'ban', 'mute', or 'kick'.
	@param callback (playerId: number, reason: string) -> nil -- The callback that is called whenever the moderation occurs.
]]
function Zyntex.OnModeration(self: Zyntex, moderationType: string, callback: (player: number, reason: string) -> nil)
	table.insert(self._listeners[moderationType], callback)
end

--[[
	@method Zyntex:OnModeration
	@description Creates a hook to the 'kick' mo	eration action.
	
	@param self Zyntex
	@param callback (playerId: number, reason: string) -> nil -- The callback that is called whenever the kick occurs.
]]
function Zyntex.OnKick(self: Zyntex, callback: (player: number, reason: string) -> nil)
	return self:OnModeration("kick", callback)
end

--[[
	@method Zyntex:OnModeration
	@description Creates a hook to the 'ban' moderation action.
	
	@param self Zyntex
	@param callback (playerId: number, reason: string) -> nil -- The callback that is called whenever the ban occurs.
]]
function Zyntex.OnBan(self: Zyntex, callback: (player: number, reason: string) -> nil)
	return self:OnModeration("ban", callback)
end

--[[
	@method Zyntex:OnMute
	@description Creates a hook to the 'mute' moderation action.
	
	@param self Zyntex
	@param callback (playerId: number, reason: string) -> nil -- The callback that is called whenever the mute occurs.
]]
function Zyntex.OnMute(self: Zyntex, callback: (player: number, reason: string) -> nil)
	return self:OnModeration("mute", callback)
end

--[[
	@method Zyntex:init
	@description The main entry point to start the Zyntex client. This function initializes all core processes:
	it registers the server with the Zyntex backend, starts the status update and polling loops,
	sets up event listeners, and connects player tracking signals.
	
	@param self Zyntex
	@param config TYPES.Config -- A configuration table that controls client behavior (e.g., `debug`, `simulate`).
]]
function Zyntex.init(self: Zyntex, config: TYPES.Config)
	self._config = config

	local since = now()

	if config.debug then
		print("[Zyntex]: Zyntex.init")
		print("[Zyntex]: Initializing server...")
	end

	--// Initial server creation (ENQUEUED for /roblox/push)
	local privateServerId = game.PrivateServerId
	local privateServerParameter = ""
	if privateServerId ~= "" and game.PrivateServerOwnerId ~= 0 then
		privateServerParameter = privateServerId
	end
	self._session:post(
		"/roblox/servers",
		{
			["server_id"] = self._session.jobID,
			["version"] = game.PlaceVersion,
			["isPrivate"] = privateServerParameter
		}
	)

	-- FIRST FLUSH: create server + fetch manifest + initial listen data + moderation lists
	local firstPush = self._session:Flush(since)
	if not firstPush or not firstPush.success then
		error(`Error when attempting to initialize server via /push`)
	end
	local manifest = firstPush.data and firstPush.data.manifest
	local listen_data = firstPush.data and firstPush.data.listen_data

	-- Materialize functions + events from manifest (from /push)
	if manifest then
		for _,v in pairs(manifest) do
			if v.type == "function" then
				table.insert(self._functions, Function.new(
					self,
					v.id,
					v.name,
					v.description or "",
					v.parameters or {}
					))
			elseif v.type == "event" then
				table.insert(self._events, Event.new(
					self,
					v.id,
					v.name,
					v.description or "",
					v.data or {},
					false
					))
			end
		end
	end

	--// Capture and send historical logs from before the client initialized.
	-- Don't want to send 300 messages all at once :)
	task.spawn(function()
		local history = LogService:GetLogHistory()
		local delay   = 0
		if #history > 5 then
			delay = 1
		end
		-- Avoid sending an excessive number of historical logs.
		if #history > 30 then
			return
		end
		for _,message in pairs(history) do
			self:logServiceMessage(message.message, message.messageType)
			task.wait(delay)
		end
	end)

	--// Connect live LogService listener.
	LogService.MessageOut:Connect(function(msg: string, type: Enum.MessageType)
		self:logServiceMessage(msg, type)
	end)

	if config.debug then
		print("[Zyntex]: Server initialized")
	end

	--// On server shutdown, notify the Zyntex backend.
	game:BindToClose(function()
		if config.debug then
			print("[Zyntex]: Server closing...")
		end
		sendingStatus = true
		self._session:delete("/roblox/servers", { ["server_id"] = self._session.jobID })
		self._session:Flush()

		-- Clean up pending joins on shutdown
		local pendingCount = 0
		for rid, _ in pairs(self._pendingJoins) do
			pendingCount = pendingCount + 1
		end
		if pendingCount > 0 and config.debug then
			print(("[Zyntex]: Cleaning up %d pending joins on shutdown"):format(pendingCount))
		end
		self._pendingJoins = {}
	end)

	--// Start the background processes.
	task.spawn(statusUpdateLoop, self)
	task.spawn(self:poll(since)) -- poll now uses /roblox/push flush

	-- (No direct /manifest fetch: handled via first /push response)

	--// Handle server shutdown action from the dashboard.
	self._listeners["shutdown"] = {}
	table.insert(self._listeners["shutdown"], function(action)
		local reason = action.metadata

		-- Kick all current and future players with the provided reason.
		for _,player in pairs(Players:GetPlayers()) do
			player:Kick(reason)
		end

		Players.PlayerAdded:Connect(function(player)
			player:Kick(reason)
		end)

		-- Notify the dashboard that the action has been fulfilled.
		local res = self._session:post(
			`/roblox/actions/{action.id}/fulfill`,
			{},
			true
		)

		if not res.success then
			warn(`[Zyntex]: Server shutdown fulfillment failed: {res.user_message}`)
		end

		if config.debug then
			print(`[Zyntex]: Server shutdown fulfilled.`)
		end
	end)

	--// Handle remote code execution (RCE) action from the dashboard.
	self._listeners["rce"] = {}
	table.insert(self._listeners["rce"], function(action)
		local code = action.metadata

		if config.debug then
			print(`[Zyntex]: Fulfilling RCE request...`)
		end

		-- Execute the code in a protected call to prevent crashes.
		task.spawn(function()
			local success,data = pcall(function()
				local executable,msg = require(script.Parent:FindFirstChild("Loadstring") :: ModuleScript)(code)
				executable()
			end)

			if not success then
				warn(`RCE Failure: {data}`)
			end
		end)

		local res = self._session:post(
			`/roblox/actions/{action.id}/fulfill`,
			{},
			true
		)

		if config.debug then
			print(`[Zyntex]: RCE fulfilled.`)
		end
	end)

	--// Handle system chat message action from the dashboard.
	self._listeners["chat"] = {}
	table.insert(self._listeners["chat"], function(action)
		if config.debug then
			print(`[Zyntex]: Fulfilling chat request...`)
		end

		-- Fire a remote event to all clients to display a system message.
		game:GetService("ReplicatedStorage"):FindFirstChild("zyntex.events"):FindFirstChild("SystemChat"):FireAllClients(action.metadata)

		local res = self._session:post(
			`/roblox/actions/{action.id}/fulfill`,
			{},
			true
		)

		if not res.success then
			warn(`[Zyntex]: Chat fulfillment failed: {res.user_message}`)
		end

		if config.debug then
			print(`[Zyntex]: Chat fulfilled.`)
		end
	end)

	--// Handle moderation action from the dashboard.
	self._listeners["moderation"] = {}
	self._listeners["ban"]  = {}
	self._listeners["mute"] = {}
	self._listeners["kick"] = {}
	self._listeners["report"] = {}
	table.insert(self._listeners["moderation"], function(action)
		if config.debug then
			print(`[Zyntex]: Fulfilling moderation request...`)
			print(`[Zyntex]: {action}`)
		end

		local fullfilled = false

		local type = action.metadata.type
		local player_id = action.metadata.player_id
		local reason = action.metadata.reason


		if #self._listeners[type] == 0 then
			if type == "ban" or type == "kick" then
				local player = Players:GetPlayerByUserId(player_id)
				if player then
					player:Kick(reason)
					fullfilled = true
				end
			end

			if type == "mute" then
				local player = Players:GetPlayerByUserId(player_id)
				if player then
					muteUserId(player.UserId)
					fullfilled = true
				end
			end
		else
			for _,listener in self._listeners[type] do
				listener(player_id, reason)
			end
			fullfilled = true
		end

		if fullfilled then
			local res = self._session:post(
				`/roblox/actions/{action.id}/fulfill`,
				{},
				true
			)

			if not res.success then
				warn(`[Zyntex]: Moderation fulfillment failed: {res.user_message}`)
			end
		end

		if config.debug then
			print(`[Zyntex]: Moderation request fulfilled.`)
		end
	end)

	--// If simulation is enabled in the config, start the activity simulator.
	if config.simulate then
		task.spawn(function()
			simulateActivity(self)
		end)
	end

	--// Connect player join/leave listeners.
	Players.PlayerAdded:Connect(function(player: Player)
		return onPlayerAdd(self, player)
	end)

	--// Ensure any players already present at initialization are registered.
	for i,player in Players:GetPlayers() do
		onPlayerAdd(self, player)
	end

	Players.PlayerRemoving:Connect(function(player: Player)
		return onPlayerRemove(self, player)
	end)
end

--[[
	@method Zyntex:link
	@description A utility function used during initial setup. When called, it attempts
	to link the provided game token to the user's Roblox account via the Zyntex backend.
	This is typically only run once from the command bar in Studio.
]]
function Zyntex.link(self: Zyntex)
	local response = self._session:post("/links/games/link")

	if response.success == false then
		if response.statusCode == 404 then
			error(`Game token "{self._session.gameToken}" not found. Please make sure you pasted the full linking script from the site.`)
		end
		error(`Linking failed: {response.user_message}`)
	end

	print(`[Zyntex]: Linking success!`)
end

--[[
	@method Zyntex:Telemetry
	@description Constructs a new Telemetry object used for prometheus-style metrics.
	@param flushEvery number? -- How often to flush the buffer in seconds. Default is 10,
	which is the minimum to not get ratelimited.
	@param registryName string? -- The name of the registry. Default is "default"
]]
function Zyntex.Telemetry(self: Zyntex, flushEvery: number?, registryName: string?)
	if not flushEvery then
		flushEvery = 10
	end
	assert(flushEvery >= 10, `flushEvery must be no less than 10`)
	return Telemetry.new(
		registryName,
		flushEvery,
		self._session
	)
end

--[[
	@method Zyntex:Experiments
	@description Constructs a new Experiments object used for A/B testing.
	@param experimentId string -- The ID of the experiment, i.e. "new_shop_ui". Set when creating experiment.
	@return Experiment? -- The Experiment, if exists and is not Archived
]]
function Zyntex.GetExperiment(self: Zyntex, experimentId: string): Experiments.Experiment?
	return Experiments.new(
		self._session,
		experimentId
	)
end

return Zyntex
]]></ProtectedString>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
            </Properties>
        </Item>
        <Item class="ModuleScript" referent="RBX9F7FEE96C9EC4C10B78441CD04599FB6">
            <Properties>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="DefinesCapabilities">false</bool>
                <Content name="LinkedSource"><null></null></Content>
                <string name="Name">api</string>
                <string name="ScriptGuid">{7CCFD349-4F3D-4CF7-AE9E-F94B1A257CDB}</string>
                <ProtectedString name="Source"><![CDATA[--!strict
local HttpService: HttpService = game:GetService("HttpService")
local RunService = game:GetService("RunService")
local Random = Random.new()

--==============================--
-- Session
--==============================--

local Session = {}
Session.__index = Session

-- Randomized small interval to naturally jitter pushes across servers
local FLUSH_DELTA = Random:NextInteger(5, 8)

local BUFFER: { { [string]: any } } = {}

local _onFlush: (data: any) -> () = function(_)
	-- no-op by default
end

--==============================--
-- Types
--==============================--

export type SessionType = {
	rootUrl: string,
	gameToken: string,
	jobID: string,

	_firstFlushDone: boolean,
}

local Response = {}
Response.__index = Response

export type ResponseType = {
	success: boolean,
	user_message: string,
	data: any?,
	statusCode: number,
}

export type Session = typeof(setmetatable({} :: SessionType, Session))
export type Response = typeof(setmetatable({} :: ResponseType, Response))

--==============================--
-- Utilities
--==============================--

local function nowISO(): string
	local dt = DateTime.now():ToUniversalTime()
	return string.format(
		"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
		dt.Year, dt.Month, dt.Day,
		dt.Hour, dt.Minute, dt.Second,
		dt.Millisecond
	)
end

local function toBatchItem(method: string, path: string, body: { [string]: any }?): { [string]: any }
	return {
		method = string.upper(method) .. " " .. path,
		t = nowISO(),
		-- keep native Lua table; server expects an object
		payload = body or {},
		-- lightweight client-side correlation id (handy for debugging server logs)
		rid = HttpService:GenerateGUID(false),
	}
end

--==============================--
-- Response helpers
--==============================--

function Response.new(success: boolean, user_message: string, statusCode: number, data: any?): Response
	local self = {}
	self.success = success
	self.user_message = user_message
	self.data = data
	self.statusCode = statusCode
	return setmetatable(self, Response)
end

function Response.fromRaw(res: { [string]: any }, statusCode: number): Response
	local self = {}
	local decoded = HttpService:JSONDecode(res["Body"])

	self.success = decoded.success :: boolean
	self.user_message = decoded.user_message :: string
	self.data = decoded.data
	self.statusCode = statusCode

	return setmetatable(self, Response)
end

--==============================--
-- Low-level HTTP
--==============================--

function Session:_requestRaw(url: string, method: string, body: any?): Response
	if not string.find(url, "http") then
		url = self.rootUrl .. url
	end
	local res = HttpService:RequestAsync({
		Url = url,
		Method = string.upper(method),
		Body = if body ~= nil then HttpService:JSONEncode(body) else nil,
		Headers = {
			["Authorization"] = `Game-Token {self.gameToken}`,
			["Job-ID"] = self.jobID,
			["Content-Type"] = "application/json",
		},
	})
	return Response.fromRaw(res, res.StatusCode)
end

--==============================--
-- Public API (enqueue writes, direct GET)
--==============================--

-- Queue a POST (batched via /roblox/push)
function Session.post(self: Session, path: string, body: { [string]: any }?, bypassBuffer: boolean?): string?
	if string.find(path, "telemetry") or bypassBuffer then
		self:_requestRaw(path, "POST", body)
		return
	end
	local item = toBatchItem("POST", path, body)
	table.insert(BUFFER, item)
	return item.rid
end

-- Queue a DELETE (batched via /roblox/push)
function Session.delete(self: Session, path: string, body: { [string]: any }?, _autoError: boolean?): string
	local item = toBatchItem("DELETE", path, body)
	table.insert(BUFFER, item)
	return item.rid
end

-- Direct GET (rare; kept for convenience)
function Session.get(self: Session, path: string): Response
	return self:_requestRaw(self.rootUrl .. path, "GET", nil)
end

--==============================--
-- Flush (push)
--==============================--

-- Flush the buffer immediately to /roblox/push.
function Session.Flush(self: Session, sinceISO: string?): Response?
	if #BUFFER == 0 then
		return nil
	end

	-- drain atomically
	local payload = BUFFER
	BUFFER = {}

	local url = self.rootUrl .. "/roblox/push"
	if sinceISO then
		url ..= "?since=" .. HttpService:UrlEncode(sinceISO)
	end

	local ok, resOrErr = pcall(function()
		return self:_requestRaw(url, "POST", payload)
	end)
	
	if not ok then
		-- put the batch back so next cycle retries
		for _, item in payload do
			table.insert(BUFFER, item)
		end
		warn(`[Zyntex]: /roblox/push flush failed, retrying later: {tostring(resOrErr)}`)
		return nil
	end

	local res: Response = resOrErr :: Response

	-- Invoke hook so Zyntex can react (ban/mute handling, manifest, listen_data)
	-- This is intentionally fire-and-forget; hook can be a small, synchronous handler.
	-- Expected shape: res.data = { results = [...], manifest?, listen_data? }
	local okHook, hookErr = pcall(function()
		if res.data ~= nil then
			_onFlush(res.data)
		end
	end)
	if not okHook then
		warn(`[Zyntex]: onFlush callback errored: {tostring(hookErr)}`)
	end

	-- Track “first flush that contained POST /servers”
	if not self._firstFlushDone then
		for _, item in payload do
			-- cheap prefix check; format is "METHOD /path"
			if string.sub(item.method, 1, 13) == "POST /servers" then
				self._firstFlushDone = true
				break
			end
		end
	end

	return res
end

-- Alias; some callers prefer explicit naming
function Session.FlushNow(self: Session, sinceISO: string?): Response?
	return self:Flush(sinceISO)
end

--==============================--
-- onFlush hook setter
--==============================--

-- Zyntex sets this to handle per-result errors, e.g.:
--   - method == "POST /players"
--   - success == false
--   - error == "403: reason" (ban) or "401: reason" (mute)
-- From there, Zyntex can kick/mute locally and/or print the reason.
function Session.onFlush(self: Session, cb: (data: any) -> ()): ()
	_onFlush = cb or function(_) end
end

--==============================--
-- Constructor
--==============================--

function Session.new(gameToken: string, rootUrl: string?, jobId: string?): Session
	local self: any = {}
	self.gameToken = gameToken
	self.rootUrl = rootUrl or "https://api.zyntex.dev"
	self.jobID = RunService:IsStudio() and `DEV-SERVER-{HttpService:GenerateGUID(false)}` or game.JobId
	self._firstFlushDone = false

	return setmetatable(self, Session)
end

return Session
]]></ProtectedString>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
            </Properties>
        </Item>
        <Item class="ModuleScript" referent="RBXBE37390F2C374E78870A2948BA4C6ABA">
            <Properties>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="DefinesCapabilities">false</bool>
                <Content name="LinkedSource"><null></null></Content>
                <string name="Name">types</string>
                <string name="ScriptGuid">{D0E30B3B-AC37-447D-9058-600FA13CB21C}</string>
                <ProtectedString name="Source"><![CDATA[--[[
	The Zyntex configuration type.
]]
export type Config = {
	--[[
		Wether or not to list to and execute requests made by the dashboard server.
		Admins require the servers.rce permission to remotely execute code.
		Defaults to "true"
	]]
	enableRCE: boolean?;
	--[[
		Whether or not to display debug logging
	]]
	debug: boolean?;
	--[[
		Wether or not to simulate high-requests
	]]
	simulate: boolean?;
}

return true;
]]></ProtectedString>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
            </Properties>
        </Item>
        <Item class="LocalScript" referent="RBX30CDE4A9CCD845AEBD88BE23A8139585">
            <Properties>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="DefinesCapabilities">false</bool>
                <bool name="Disabled">false</bool>
                <Content name="LinkedSource"><null></null></Content>
                <string name="Name">zyntex.client</string>
                <token name="RunContext">0</token>
                <string name="ScriptGuid">{A9B475BE-9EDA-401C-B367-C4E5F194851D}</string>
                <ProtectedString name="Source"><![CDATA[local SystemChatEvent: RemoteEvent = game:GetService("ReplicatedStorage"):WaitForChild("zyntex.events"):WaitForChild("SystemChat") :: RemoteEvent
local Channel = game.TextChatService:WaitForChild("TextChannels"):WaitForChild("RBXGeneral")

SystemChatEvent.OnClientEvent:Connect(function(msg: string)
	Channel:DisplaySystemMessage(msg)
end)
]]></ProtectedString>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
            </Properties>
        </Item>
        <Item class="ModuleScript" referent="RBX9F7FEE96C9EC4C10B78441CD04599FB6">
            <Properties>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="DefinesCapabilities">false</bool>
                <Content name="LinkedSource"><null></null></Content>
                <string name="Name">Telemetry</string>
                <string name="ScriptGuid">{7CCFD349-4F3D-4CF7-AE9E-F94B1A257CDB}</string>
                <ProtectedString name="Source"><![CDATA[local Session = require(script.Parent:WaitForChild("api"))
local HttpService = game:GetService("HttpService")

local Telemetry = {}
Telemetry.__index = Telemetry

export type LabelSet = { [string]: string }

export type MetricOpts = {
	--[[
	Optional human-readable description
	]]
	description: string?,
	--[[
	Labels, used for filtering metrics on the dashboard
	]]
	labels: { string }?,
	--[[
	Only for Histogram
	]]
	buckets: { number }?,
}

export type CounterMetric = {
	inc: (self: CounterMetric, delta: number?, labels: LabelSet?) -> (),
}

export type GaugeMetric = {
	set: (self: GaugeMetric, value: number,  labels: LabelSet?) -> (),
	inc: (self: GaugeMetric, delta: number?, labels: LabelSet?) -> (),
	dec: (self: GaugeMetric, delta: number?, labels: LabelSet?) -> (),
}

export type HistogramMetric = {
	observe: (self: HistogramMetric, value: number, labels: LabelSet?) -> (),
}

export type SummaryMetric = {
	observe: (self: SummaryMetric, value: number, labels: LabelSet?) -> (),
}

function Telemetry.new(registryName: string?, flushEvery: number?, session: Session.Session)
	return setmetatable({
		_name           = registryName or "default",
		_buffer         = {},           -- pending samples
		_flushEvery     = flushEvery or 10,
		_lastFlush      = os.clock(),
		_session        = session
	}, Telemetry)
end

--[[
	Generic pushSample shared by all metric types
]]
local function pushSample(self, kind: string, name: string, value, labels: LabelSet)
	table.insert(self._buffer, {
		t  = os.time(), -- UTC seconds
		k  = kind,      -- counter | gauge | hist | sum
		n  = name,      -- metric name
		v  = value,     -- number or bucket-map
		l  = labels     -- table<string,string>
	})

	if os.clock() - self._lastFlush >= self._flushEvery then
		self:flush()
	end
end

function Telemetry:flush()
	if #self._buffer == 0 then return end
	pcall(function()
		self._session:post(
			"/telemetry/push",
			{buffer = self._buffer}
		)
	end)
	self._buffer, self._lastFlush = {}, os.clock()
end

--[[
	Creates (or fetches) a monotonically increasing **counter** metric.

	@param self  Telemetry         – the registry instance.
	@param name  string            – metric name (snake_case).
	@param opts  MetricOpts?       – optional descriptor & label schema.

	@return CounterMetric handle   – call `:inc()` to push samples.

	Example
	```lua
	local shots = registry:Counter("weapon_shots_total", {
		description = "Number of shots fired per weapon",
		labels      = {"weapon"}
	})

	shots:inc()                    -- +1
	shots:inc(3, {weapon="AK-47"}) -- +3 with a label
	```
]]--
function Telemetry:Counter(name: string, opts: MetricOpts?): CounterMetric
	local total = 0
	return ({
		inc = function(_: CounterMetric, delta: number?, lbls: LabelSet?)
			total += delta or 1
			pushSample(self, "counter", name, total, lbls or {})
		end,
	} :: any) :: CounterMetric
end

--[[
	Creates a **gauge** metric – an instantaneous value that can go up
	or down (e.g. memory, player count, FPS).

	`set()` overrides the value directly, while `inc()` / `dec()` apply
	deltas.

	@return GaugeMetric handle.
]]--
function Telemetry:Gauge(name: string, opts: MetricOpts?): GaugeMetric
	return ({

		set = function(_: GaugeMetric, value: number, lbls: LabelSet?)
			pushSample(self, "gauge", name, value, lbls or {})
		end,

		inc = function(_: GaugeMetric, delta: number?, lbls: LabelSet?)
			pushSample(self, "gauge", name,  delta or 1, lbls or {})
		end,

		dec = function(_: GaugeMetric, delta: number?, lbls: LabelSet?)
			pushSample(self, "gauge", name, -(delta or 1), lbls or {})
		end,

	} :: any) :: GaugeMetric
end

--[[
	Creates a **histogram** metric – captures a distribution of values
	(e.g. damage dealt, ping). `buckets` may be provided in `opts`.

	The raw sample value is sent unchanged; bucketing happens on the
	back-end so bucket definitions can evolve without client updates.

	@return HistogramMetric handle.
]]--
function Telemetry:Histogram(name: string, opts: MetricOpts?): HistogramMetric
	local buckets: {number}? = opts and opts.buckets
	return ({

		observe = function(_: HistogramMetric, value: number, lbls: LabelSet?)
			local combined: LabelSet = lbls and table.clone(lbls) or {}
			if buckets then combined._buckets = HttpService:JSONEncode(buckets) end
			pushSample(self, "hist", name, value, combined)
		end,

	} :: any) :: HistogramMetric
end

--[[
	Creates a **summary** metric – similar to histogram but intended
	for quantile estimation (P-style summaries). Uses a single `observe`
	method.

	@return SummaryMetric handle.
]]--
function Telemetry:Summary(name: string, opts: MetricOpts?): SummaryMetric
	return ({

		observe = function(_: SummaryMetric, value: number, lbls: LabelSet?)
			pushSample(self, "sum", name, value, lbls or {})
		end,

	} :: any) :: SummaryMetric
end

return Telemetry
]]></ProtectedString>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
            </Properties>
        </Item>
        <Item class="ModuleScript" referent="RBX9F7FEE96C9EC4C10B78441CD04599FB6">
            <Properties>
                <BinaryString name="AttributesSerialize"></BinaryString>
                <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
                <bool name="DefinesCapabilities">false</bool>
                <Content name="LinkedSource"><null></null></Content>
                <string name="Name">Experiments</string>
                <string name="ScriptGuid">{7CCFD349-4F3D-4CF7-AE9E-F94B1A257CDB}</string>
                <ProtectedString name="Source"><![CDATA[local Session = require(script.Parent:WaitForChild("api"))

local Experiment = {}
Experiment.__index = Experiment

-- Represents an experiment instance
type ExperimentType = {
	id: string; -- The unique ID of the experiment (e.g., "new_shop_ui").
	_session: Session.Session; -- Reference to the main Zyntex session.
}

export type Experiment = typeof(setmetatable({} :: ExperimentType, Experiment))

local Group = {}
Group.__index = Group

-- Represents a group instance
type GroupType = {
	id: string; -- The unique ID of the group (e.g., "group_a").
	playerId: number; -- The ID of the player that is in this group
	_experiment: Experiment;
}

export type Group = typeof(setmetatable({} :: GroupType, Group))

--[[
	@function Group:Convert
	@description Registers a conversion for that player in the group.
	@param value number? -- The value this conversion provided. For example: total robux spent, time spent completing tutorial
]]
function Group.Convert(self: Group, value: number?)
	self._experiment._session:post(`/experiments/{self._experiment.id}`, {
		value = value or 0;
		group_id = self.id;
		player_id = self.playerId;
	},
		true
	)
end

--[[
	@constructor Group.new
	@description Constructs a new Group object.
	@param session Session -- Reference to the main client (for API calls).
	@param id string -- Group ID as defined in the dashboard.
	@return Group
]]
function Group.new(Experiment: Experiment, id: string, playerId: number)
	local self = {}
	self.id = id
	self.playerId = playerId
	self._experiment = Experiment
	
	return setmetatable(self, Group)
end

--[[
	@constructor Experiment.new
	@description Constructs a new Experiment object.
	@param session Session -- Reference to the main client (for API calls).
	@param id string -- Experiment ID as defined in the dashboard.
	@return Experiment
]]
function Experiment.new(session: Session.Session, id: string): Experiment?
	local self = {}
	self._session = session
	self.id = id
	
	local manifest = session:get(`/experiments/{id}/manifest`)
	
	if not manifest.success then
		if manifest.statusCode == 404 then
			return nil
		end
		
		error(`Error when fetching experiment {id}: {manifest.user_message}`)
	end
	
	return setmetatable(self, Experiment)
end

--[[
	@function GetStatus
	@description Fetches the current status of the experiment.
	@return Experiment
]]
function Experiment.GetStatus(self: Experiment): "active" | "paused" | "archived"
	local res = self._session:get(`/experiments/{self.id}/manifest`)
	
	if not res.succeess then
		error(`Failure when fetching experiment {self.id} status: {res.user_message}`)
	end
	
	return res.data.status
end

--[[
	@function GetGroup
	@description Registers an entry and server assigns player a group.  
	@param playerId Player | number -- The Player to register, as a Player object or number (userId)
	@return string
]]
function Experiment.GetGroup(self: Experiment, player: Player | number): Group
	local playerId = if type(player) == "number" then player else player.UserId
	local res = self._session:get(`/experiments/{self.id}/group/{playerId}`)
	
	if not res.success then
		error(`Failure fetching group for player {playerId}: {res.user_message}`)
	end
	
	return Group.new(self, res.data, playerId)
end

return Experiment
]]></ProtectedString>
                <int64 name="SourceAssetId">-1</int64>
                <BinaryString name="Tags"></BinaryString>
            </Properties>
        </Item>
    </Item>
</roblox>