跳转到内容

模組:PythonScript/Evaluator/Core/Ops

维基百科,自由的百科全书
-- Module:PythonScript/Evaluator/Core/Ops
-- 運算子求值引擎:負責 Index (下標與切片)、UnaryOp、BinaryOp 與 Assign (含原地增量運算)
local Ops = {}

local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local Utils = require("Module:PythonScript/Evaluator/Utils")
local bit32 = require("bit32")

local is_truthy = Utils.is_truthy
local unwrap_int_index = Utils.unwrap_int_index
local resolve_index = Utils.resolve_index
local resolve_slice = Utils.resolve_slice
local py_cmp = Utils.py_cmp
local py_eq = Utils.py_eq
local py_is = Utils.py_is
local COMPARE_OPS = Utils.COMPARE_OPS
local bigint_op = Utils.bigint_op
local get_matrix_engine = Utils.get_matrix_engine
local elementwise_op = Utils.elementwise_op
local matmul = Utils.matmul
local make_iter_state = Utils.make_iter_state
local get_next_item = Utils.get_next_item
local assign_target = Utils.assign_target

function Ops.create(eval_expr, bind_and_exec_func)
	local invoke = nil

	-- ==========================================
	-- 1. 下標與切片求值 (Index & Slice)
	-- ==========================================
	local function eval_index(node, scope)
		local target = eval_expr(node.target, scope)
		local is_bytes_like = type(target) == "table" and (target.__type == "bytes" or target.__type == "bytearray" or target.__type == "memoryview")
		if is_bytes_like and target.__type == "memoryview" and target._released then
			Runtime.raise("ValueError", "operation forbidden on released memoryview object")
		end
		if node.index.type == "Slice" then
			local lower = node.index.lower and eval_expr(node.index.lower, scope) or nil
			local upper = node.index.upper and eval_expr(node.index.upper, scope) or nil
			local step = node.index.step and eval_expr(node.index.step, scope) or nil
			local length = 0
			if type(target) == "table" and (target.__type == "list" or target.__type == "tuple") then length = #target.elements
			elseif is_bytes_like then length = #target._data
			elseif type(target) == "string" then length = mw.ustring.len(target)
			elseif type(target) == "table" and (target.__type == "instance" or target.__type == "class") then
				if type(invoke) ~= type(function()end) then invoke = require("Module:PythonScript/Evaluator").invoke end
				local s_arg = (lower ~= nil) and lower or Runtime.builtins.None
				local e_arg = (upper ~= nil) and upper or Runtime.builtins.None
				local st_arg = (step ~= nil) and step or Runtime.builtins.None
				local slice_obj = invoke(Runtime.builtins.slice, { s_arg, e_arg, st_arg }, {})
				local prev_depth = Runtime.Traceback.eval_depth
				local success, attr = pcall(PyClass.get_attr, target, "__getitem__")
				Runtime.Traceback.eval_depth = prev_depth
				if success and attr and attr.__type == "bound_method" then
					return bind_and_exec_func(attr.__func, {slice_obj}, {}, attr.__self)
				end
				if target.__builtin_value ~= nil then
					return eval_expr({ type = "Index", target = { type = "DirectValue", value = target.__builtin_value }, index = node.index, line = node.line }, scope)
				end
				Runtime.raise("TypeError", "object is not subscriptable or sliceable")
			else Runtime.raise("TypeError", "object is not subscriptable or sliceable") end
			local p_start, p_stop, p_step = resolve_slice(length, lower, upper, step)
			if is_bytes_like then
				local res_bytes = {}
				if p_step > 0 then for i = p_start, p_stop - 1, p_step do table.insert(res_bytes, string.sub(target._data, i + 1, i + 1)) end
				else for i = p_start, p_stop + 1, p_step do table.insert(res_bytes, string.sub(target._data, i + 1, i + 1)) end end
				local s = table.concat(res_bytes)
				if target.__type == "memoryview" then
					return { __type = "memoryview", _data = s, _obj = target._obj, _readonly = target._readonly, _released = false }
				end
				return { __type = target.__type, _data = s }
			elseif type(target) == "table" then
				local res_elements = {}
				if p_step > 0 then for i = p_start, p_stop - 1, p_step do table.insert(res_elements, target.elements[i + 1]) end
				else for i = p_start, p_stop + 1, p_step do table.insert(res_elements, target.elements[i + 1]) end end
				return { __type = target.__type, elements = res_elements }
			else
				local res_str = {}
				if p_step > 0 then for i = p_start, p_stop - 1, p_step do table.insert(res_str, mw.ustring.sub(target, i + 1, i + 1)) end
				else for i = p_start, p_stop + 1, p_step do table.insert(res_str, mw.ustring.sub(target, i + 1, i + 1)) end end
				return table.concat(res_str)
			end
		else
			local idx = eval_expr(node.index, scope)
			local orig_target = target
			if type(target) == "table" and (target.__type == "instance" or target.__type == "class") then
				local prev_depth = Runtime.Traceback.eval_depth
				local success, attr = pcall(PyClass.get_attr, target, "__getitem__")
				Runtime.Traceback.eval_depth = prev_depth
				if success and attr and attr.__type == "bound_method" then
					return bind_and_exec_func(attr.__func, {idx}, {}, attr.__self)
				end
				if target.__builtin_value ~= nil then target = target.__builtin_value end
			end
			if type(target) == "table" and target.__type == "dict" then
				for _, p in ipairs(target.pairs) do if py_eq(p.key, idx) then return p.value end end
				if type(orig_target) == "table" and orig_target.__type == "instance" then
					local prev_depth = Runtime.Traceback.eval_depth
					local ok_m, missing_m = pcall(PyClass.get_attr, orig_target, "__missing__")
					Runtime.Traceback.eval_depth = prev_depth
					if ok_m and missing_m then
						if missing_m.__type == "bound_method" then
							return bind_and_exec_func(missing_m.__func, {idx}, {}, missing_m.__self)
						end
						if type(invoke) ~= type(function()end) then invoke = require("Module:PythonScript/Evaluator").invoke end
						return invoke(missing_m, {orig_target, idx}, {})
					end
				end
				Runtime.raise("KeyError", tostring(idx))
			end
			local lua_idx = resolve_index(target, idx)
			if is_bytes_like then return string.byte(target._data, lua_idx)
			elseif type(target) == "table" and target.elements then return target.elements[lua_idx]
			elseif type(target) == "string" then return mw.ustring.sub(target, lua_idx, lua_idx)
			else Runtime.raise("TypeError", "object is not subscriptable") end
		end
	end

	-- ==========================================
	-- 2. 一元運算子求值 (UnaryOp)
	-- ==========================================
	local function eval_unary_op(node, scope)
		local right = eval_expr(node.right, scope)
		local um_name = ({ ["-"] = "__neg__", ["+"] = "__pos__", ["not"] = "__bool__", ["~"] = "__invert__" })[node.op]
		if um_name and type(right) == "table" and right.__type == "instance" then
			local prev_depth = Runtime.Traceback.eval_depth
			local success, attr = pcall(PyClass.get_attr, right, um_name)
			Runtime.Traceback.eval_depth = prev_depth
			if success and attr and attr.__type == "bound_method" then
				local res = bind_and_exec_func(attr.__func, {}, {}, attr.__self)
				if node.op == "not" then return not is_truthy(res) end
				return res
			end
		end
		if node.op == "not" then return not is_truthy(right) end
		if type(right) == "table" and right.__type == "instance" and right.__builtin_value ~= nil then
			right = right.__builtin_value
		end
		if type(right) == "table" and right.__type == "complex" then
			local Cmath = require("Module:PythonScript/Runtime/Cmath").Cmath
			if node.op == "-" then return Cmath.neg(right) elseif node.op == "+" then return Cmath.pos(right) end
		end
		if type(right) == "table" and (right.__type == "list" or right.__type == "tuple") then
			if right.is_matrix and (node.op == "-" or node.op == "+") then
				if node.op == "-" then return get_matrix_engine().unary_op(right, function(x) return -x end)
				else return get_matrix_engine().unary_op(right, function(x) return x end) end
			end
			Runtime.raise("TypeError", "bad operand type for unary " .. node.op .. ": '" .. right.__type .. "'")
		end
		if type(right) == "boolean" then right = right and 1 or 0 end
		if node.op == "-" then
			if type(right) == "table" and right.__type == "int" then
				return { __type = "int", is_big = true, value = right.value and -right.value or nil, sign = -right.sign, limbs = right.limbs }
			end
			if type(right) ~= "number" then Runtime.raise("TypeError", "bad operand type for unary -") end
			return -right
		elseif node.op == "+" then
			if type(right) ~= "number" and not (type(right) == "table" and right.__type == "int") then
				Runtime.raise("TypeError", "bad operand type for unary +")
			end
			return right
		elseif node.op == "~" then
			if type(right) == "table" and right.__type == "int" then return bigint_op(-1, right, "-")
			elseif type(right) == "number" and math.floor(right) == right then return -(right + 1) end
			Runtime.raise("TypeError", "bad operand type for unary ~")
		end
	end

	-- ==========================================
	-- 3. 二元運算子求值 (BinaryOp)
	-- ==========================================
	local function eval_binary_op(node, scope)
		if COMPARE_OPS[node.op] and node.left.type == "BinaryOp" and COMPARE_OPS[node.left.op] and not node.left.parenthesized then
			local chain_nodes, chain_ops = {}, {}
			local curr = node
			while curr.type == "BinaryOp" and COMPARE_OPS[curr.op] do
				table.insert(chain_ops, 1, curr.op)
				table.insert(chain_nodes, 1, curr.right)
				curr = curr.left
				if curr.parenthesized then break end
			end
			table.insert(chain_nodes, 1, curr)

			local left_val = eval_expr(chain_nodes[1], scope)
			for i = 1, #chain_ops do
				local right_val = eval_expr(chain_nodes[i + 1], scope)
				local step_ok = eval_expr({
					type = "BinaryOp", op = chain_ops[i],
					left = { type = "DirectValue", value = left_val },
					right = { type = "DirectValue", value = right_val },
					line = node.line
				}, scope)
				if not is_truthy(step_ok) then return false end
				left_val = right_val
			end
			return true
		end

		local left = eval_expr(node.left, scope)
		if node.op == "and" then
			if is_truthy(left) then return eval_expr(node.right, scope) else return left end
		elseif node.op == "or" then
			if is_truthy(left) then return left else return eval_expr(node.right, scope) end
		end

		local right = eval_expr(node.right, scope)
		local op_magic = { ["+"] = "__add__", ["-"] = "__sub__", ["*"] = "__mul__", ["/"] = "__truediv__", ["\\"] = "__ldiv__", ["//"] = "__floordiv__", ["%"] = "__mod__", ["**"] = "__pow__", ["=="] = "__eq__", ["!="] = "__ne__", ["<"] = "__lt__", [">"] = "__gt__", ["<="] = "__le__", [">="] = "__ge__", ["@"] = "__matmul__", ["|"] = "__or__", ["&"] = "__and__", ["^"] = "__xor__", ["<<"] = "__lshift__", [">>"] = "__rshift__" }
		local m_name = op_magic[node.op]

		if m_name then
			if type(left) == "table" and left.__type == "instance" then
				local prev_depth = Runtime.Traceback.eval_depth
				local success, attr = pcall(PyClass.get_attr, left, m_name)
				Runtime.Traceback.eval_depth = prev_depth
				if success and attr and attr.__type == "bound_method" then
					local res = bind_and_exec_func(attr.__func, {right}, {}, attr.__self)
					if res ~= nil and res ~= Runtime.builtins.None and res ~= Runtime.builtins.NotImplemented then return res end
				end
			end
			local rm_name = ({ ["+"] = "__radd__", ["-"] = "__rsub__", ["*"] = "__rmul__", ["/"] = "__rtruediv__", ["\\"] = "__rldiv__", ["//"] = "__rfloordiv__", ["%"] = "__rmod__", ["**"] = "__rpow__", ["=="] = "__eq__", ["!="] = "__ne__", ["<"] = "__gt__", [">"] = "__lt__", ["<="] = "__ge__", [">="] = "__le__", ["@"] = "__rmatmul__", ["|"] = "__ror__", ["&"] = "__rand__", ["^"] = "__rxor__", ["<<"] = "__rlshift__", [">>"] = "__rrshift__" })[node.op]
			if rm_name and type(right) == "table" and right.__type == "instance" then
				local prev_depth = Runtime.Traceback.eval_depth
				local success, attr = pcall(PyClass.get_attr, right, rm_name)
				Runtime.Traceback.eval_depth = prev_depth
				if success and attr and attr.__type == "bound_method" then
					local res = bind_and_exec_func(attr.__func, {left}, {}, attr.__self)
					if res ~= nil and res ~= Runtime.builtins.None and res ~= Runtime.builtins.NotImplemented then return res end
				end
			end
		end

		if node.op ~= "is" and node.op ~= "is not" and node.op ~= "in" and node.op ~= "not in" then
			if type(left) == "table" and left.__type == "instance" and left.__builtin_value ~= nil then left = left.__builtin_value end
			if type(right) == "table" and right.__type == "instance" and right.__builtin_value ~= nil then right = right.__builtin_value end
		end
		if (type(left) == "table" and left.__type == "complex") or (type(right) == "table" and right.__type == "complex") then
			local Cmath = require("Module:PythonScript/Runtime/Cmath").Cmath
			if node.op == "+" then return Cmath.add(left, right)
			elseif node.op == "-" then return Cmath.sub(left, right)
			elseif node.op == "*" then return Cmath.mul(left, right)
			elseif node.op == "/" then return Cmath.div(left, right)
			elseif node.op == "\\" then return Cmath.ldiv(left, right)
			elseif node.op == "**" then return Cmath.pow(left, right)
			elseif node.op == "==" then return Cmath.eq(left, right)
			elseif node.op == "!=" then return not Cmath.eq(left, right) end
		end

		local is_set_left = type(left) == "table" and (left.__type == "set" or left.__type == "frozenset")
		local is_set_right = type(right) == "table" and (right.__type == "set" or right.__type == "frozenset")
		if is_set_left and is_set_right then
			local res_type = left.__type
			if node.op == "|" then
				local r = PyClass.get_attr(left, "union").__call({right}); r.__type = res_type; return r
			elseif node.op == "&" then
				local r = PyClass.get_attr(left, "intersection").__call({right}); r.__type = res_type; return r
			elseif node.op == "-" then
				local r = PyClass.get_attr(left, "difference").__call({right}); r.__type = res_type; return r
			elseif node.op == "^" then
				local d1 = PyClass.get_attr(left, "difference").__call({right})
				local d2 = PyClass.get_attr(right, "difference").__call({left})
				local r = PyClass.get_attr(d1, "union").__call({d2}); r.__type = res_type; return r
			elseif node.op == "<=" or node.op == "<" then
				for _, ea in ipairs(left.elements) do
					local found = false
					for _, eb in ipairs(right.elements) do if py_eq(ea, eb) then found = true; break end end
					if not found then return false end
				end
				if node.op == "<" then return #left.elements < #right.elements end
				return true
			elseif node.op == ">=" or node.op == ">" then
				for _, eb in ipairs(right.elements) do
					local found = false
					for _, ea in ipairs(left.elements) do if py_eq(ea, eb) then found = true; break end end
					if not found then return false end
				end
				if node.op == ">" then return #left.elements > #right.elements end
				return true
			end
		end

		if node.op == "|" and type(left) == "table" and left.__type == "dict" and type(right) == "table" and right.__type == "dict" then
			local merged = PyClass.get_attr(left, "copy").__call({})
			PyClass.get_attr(merged, "update").__call({right})
			return merged
		end

		if node.op == "|" and Utils.is_unionable_type(left, right) then
			return Utils.make_union_type({left, right})
		end

		local is_list_left = type(left) == "table" and left.__type == "list"
		local is_list_right = type(right) == "table" and right.__type == "list"
		local is_tuple_left = type(left) == "table" and left.__type == "tuple"
		local is_tuple_right = type(right) == "table" and right.__type == "tuple"
		local is_mat_left = is_list_left and left.is_matrix == true
		local is_mat_right = is_list_right and right.is_matrix == true
		local has_matrix = is_mat_left or is_mat_right

		local arith_ops = { ["+"]=true, ["-"]=true, ["*"]=true, ["/"]=true, ["//"]=true, ["%"]=true, ["**"]=true, ["<<"]=true, [">>"]=true }
		if arith_ops[node.op] then
			if type(left) == "boolean" then left = left and 1 or 0 end
			if type(right) == "boolean" then right = right and 1 or 0 end
		end

		if node.op == "@" then
			if (type(left) == "table" and left.__type == "complex") or (type(right) == "table" and right.__type == "complex") then
				local Cmath = require("Module:PythonScript/Runtime/Cmath").Cmath
				return Cmath.cross(left, right)
			end
			if type(left) == "number" and is_list_right then return elementwise_op(left, right, "*")
			elseif is_list_left and type(right) == "number" then return elementwise_op(left, right, "*")
			elseif is_list_left and is_list_right then return matmul(left, right) end
			Runtime.raise("TypeError", "unsupported operand type(s) for @")
		elseif node.op == "\\" then
			if has_matrix or is_list_left or is_list_right or is_tuple_left or is_tuple_right then return get_matrix_engine().ldiv(left, right) end
			local is_big_left = type(left) == "table" and left.__type == "int"
			local is_big_right = type(right) == "table" and right.__type == "int"
			if (type(left) == "number" or type(left) == "boolean" or is_big_left) and (type(right) == "number" or type(right) == "boolean" or is_big_right) then
				local l_val = type(left) == "boolean" and (left and 1 or 0) or left
				local r_val = type(right) == "boolean" and (right and 1 or 0) or right
				if l_val == 0 or (is_big_left and #l_val.limbs == 1 and l_val.limbs[1] == 0) then
					Runtime.raise("ZeroDivisionError", "division by zero")
				end
				if is_big_left or is_big_right then return bigint_op(r_val, l_val, "/") end
				return r_val / l_val
			end
			Runtime.raise("TypeError", "unsupported operand type(s) for \\")
		elseif node.op == "/" then
			if has_matrix or is_list_left or is_list_right or is_tuple_left or is_tuple_right then return get_matrix_engine().rdiv(left, right) end
			local is_big_left = type(left) == "table" and left.__type == "int"
			local is_big_right = type(right) == "table" and right.__type == "int"
			if right == 0 or (is_big_right and #right.limbs == 1 and right.limbs[1] == 0) then
				Runtime.raise("ZeroDivisionError", "division by zero")
			end
			if is_big_left or is_big_right then return bigint_op(left, right, "/") end
			return left / right
		elseif node.op == "**" then
			if has_matrix or is_list_left or is_list_right or is_tuple_left or is_tuple_right then return get_matrix_engine().pow(left, right) end
			local is_big_left = type(left) == "table" and left.__type == "int"
			local is_big_right = type(right) == "table" and right.__type == "int"
			if is_big_left or is_big_right then return bigint_op(left, right, "**") end
			if left == 0 and type(right) == "number" and right < 0 then
				Runtime.raise("ZeroDivisionError", "0.0 cannot be raised to a negative power")
			end
			local res = left ^ right
			if type(res) == "number" and math.abs(res) > 9007199254740990 and math.floor(left)==left and math.floor(right)==right and right >= 0 then
				return bigint_op(left, right, "**")
			end
			return res
		elseif node.op == "-" or node.op == "//" or node.op == "%" then
			if node.op == "%" and (type(left) == "string" or (type(left) == "table" and (left.__type == "bytes" or left.__type == "bytearray"))) then
				return PyClass.get_attr(left, "__mod__").__call({right})
			end
			if has_matrix or is_list_left or is_list_right or is_tuple_left or is_tuple_right then return elementwise_op(left, right, node.op) end
			local is_big_left = type(left) == "table" and left.__type == "int"
			local is_big_right = type(right) == "table" and right.__type == "int"
			if (node.op == "//" or node.op == "%") and (right == 0 or (is_big_right and #right.limbs == 1 and right.limbs[1] == 0)) then
				Runtime.raise("ZeroDivisionError", "integer division or modulo by zero")
			end
			if is_big_left or is_big_right then return bigint_op(left, right, node.op) end
			local res
			if node.op == "-" then res = left - right elseif node.op == "//" then res = math.floor(left / right) else res = left % right end
			if type(res) == "number" and math.abs(res) > 9007199254740990 and math.floor(left)==left and math.floor(right)==right then
				return bigint_op(left, right, node.op)
			end
			return res
		elseif node.op == "+" then
			if has_matrix then
				if (is_list_left or type(left) == "number") and (is_list_right or type(right) == "number") then
					return elementwise_op(left, right, "+")
				end
				Runtime.raise("TypeError", "unsupported operand for matrix +")
			end
			if (is_list_left and is_list_right) or (is_tuple_left and is_tuple_right) then
				local res = {__type = left.__type, elements = {}}
				for _, v in ipairs(left.elements) do table.insert(res.elements, v) end
				for _, v in ipairs(right.elements) do table.insert(res.elements, v) end
				return res
			end
			local is_big_left = type(left) == "table" and left.__type == "int"
			local is_big_right = type(right) == "table" and right.__type == "int"
			if is_big_left or is_big_right then return bigint_op(left, right, "+") end
			if type(left) == "number" and type(right) == "number" then
				local res = left + right
				if math.abs(res) > 9007199254740990 and math.floor(left)==left and math.floor(right)==right then return bigint_op(left, right, "+") end
				return res
			end
			if type(left) == "string" and type(right) == "string" then return left .. right end
			if type(left) == "table" and type(right) == "table" and (left.__type == "bytes" or left.__type == "bytearray") and (right.__type == "bytes" or right.__type == "bytearray") then
				local res_type = (left.__type == "bytearray" or right.__type == "bytearray") and "bytearray" or "bytes"
				return { __type = res_type, _data = left._data .. right._data }
			end
			Runtime.raise("TypeError", "unsupported operand for +")
		elseif node.op == "*" then
			if has_matrix then
				if (is_list_left or type(left) == "number") and (is_list_right or type(right) == "number") then
					return elementwise_op(left, right, "*")
				end
				Runtime.raise("TypeError", "unsupported operand for matrix *")
			end
			if (is_list_left or is_tuple_left) and type(right) == "number" and math.floor(right) == right then
				local res = {__type = left.__type, elements = {}}
				for i = 1, right do for _, v in ipairs(left.elements) do table.insert(res.elements, v) end end; return res
			end
			if type(left) == "number" and math.floor(left) == left and (is_list_right or is_tuple_right) then
				local res = {__type = right.__type, elements = {}}
				for i = 1, left do for _, v in ipairs(right.elements) do table.insert(res.elements, v) end end; return res
			end
			if type(left) == "string" and type(right) == "number" then return string.rep(left, math.max(0, math.floor(right))) end
			if type(left) == "table" and (left.__type == "bytes" or left.__type == "bytearray") and type(right) == "number" then
				return { __type = left.__type, _data = string.rep(left._data, math.max(0, math.floor(right))) }
			end
			if type(left) == "number" and type(right) == "table" and (right.__type == "bytes" or right.__type == "bytearray") then
				return { __type = right.__type, _data = string.rep(right._data, math.max(0, math.floor(left))) }
			end
			if type(left) == "number" and type(right) == "string" then return string.rep(right, math.max(0, math.floor(left))) end

			local is_big_left = type(left) == "table" and left.__type == "int"
			local is_big_right = type(right) == "table" and right.__type == "int"
			if is_big_left or is_big_right then return bigint_op(left, right, "*") end
			if type(left) == "number" and type(right) == "number" then
				local res = left * right
				if math.abs(res) > 9007199254740990 and math.floor(left)==left and math.floor(right)==right then return bigint_op(left, right, "*") end
				return res
			end
			Runtime.raise("TypeError", "unsupported operand for *")
		elseif node.op == "==" then return py_eq(left, right) elseif node.op == "!=" then return not py_eq(left, right)
		elseif node.op == "<" then return py_cmp(left, right) < 0 elseif node.op == ">" then return py_cmp(left, right) > 0
		elseif node.op == "<=" then return py_cmp(left, right) <= 0 elseif node.op == ">=" then return py_cmp(left, right) >= 0
		elseif node.op == "is" then return py_is(left, right)
		elseif node.op == "is not" then return not py_is(left, right)
		elseif node.op == "in" or node.op == "not in" then
			local is_in = (node.op == "in")
			if type(right) == "table" and right.__type == "instance" then
				local prev_depth = Runtime.Traceback.eval_depth
				local success, attr = pcall(PyClass.get_attr, right, "__contains__")
				Runtime.Traceback.eval_depth = prev_depth
				if success and attr and attr.__type == "bound_method" then
					local res = is_truthy(bind_and_exec_func(attr.__func, {left}, {}, attr.__self))
					if is_in then return res else return not res end
				end
				if right.__builtin_value ~= nil then right = right.__builtin_value end
			end
			if type(left) == "table" and left.__type == "instance" and left.__builtin_value ~= nil then
				left = left.__builtin_value
			end

			local found = false
			if type(right) == "string" then
				if type(left) ~= "string" then Runtime.raise("TypeError", "'in <string>' requires string as left operand") end
				found = string.find(right, left, 1, true) ~= nil
			elseif type(right) == "table" and (right.__type == "bytes" or right.__type == "bytearray" or right.__type == "memoryview") then
				if right.__type == "memoryview" and right._released then
					Runtime.raise("ValueError", "operation forbidden on released memoryview object")
				end
				if type(left) == "number" and math.floor(left) == left then
					if left < 0 or left > 255 then Runtime.raise("ValueError", "byte must be in range(0, 256)") end
					found = string.find(right._data, string.char(left), 1, true) ~= nil
				elseif type(left) == "table" and (left.__type == "bytes" or left.__type == "bytearray" or left.__type == "memoryview") then
					found = string.find(right._data, left._data, 1, true) ~= nil
				else
					Runtime.raise("TypeError", "a bytes-like object or int is required")
				end
			elseif type(right) == "table" and right.__type == "range" then
				local val = unwrap_int_index(left)
				if type(val) == "number" and math.floor(val) == val then
					if right.step > 0 then found = (val >= right.start and val < right.stop and (val - right.start) % right.step == 0)
					else found = (val <= right.start and val > right.stop and (val - right.start) % right.step == 0) end
				end
			elseif type(right) == "table" and right.__type == "dict" then
				for _, p in ipairs(right.pairs) do if py_eq(p.key, left) then found = true; break end end
			elseif type(right) == "table" and (right.__type == "set" or right.__type == "frozenset" or right.__type == "list" or right.__type == "tuple") then
				for _, v in ipairs(right.elements) do if py_eq(v, left) then found = true; break end end
			else
				local prev_depth = Runtime.Traceback.eval_depth
				local success, iter_state = pcall(make_iter_state, right)
				Runtime.Traceback.eval_depth = prev_depth
				if not success then Runtime.raise("TypeError", "argument of type '" .. (type(right)=="table" and (right.__class and right.__class.__name or right.__type) or type(right)) .. "' is not iterable") end
				while true do
					local has_next, v = get_next_item(iter_state)
					if not has_next then break end
					if py_eq(v, left) then found = true; break end
				end
			end

			if is_in then return found else return not found end
		elseif node.op == "|" or node.op == "&" or node.op == "^" or node.op == "<<" or node.op == ">>" then
			if type(left) == "boolean" and type(right) == "boolean" and (node.op == "|" or node.op == "&" or node.op == "^") then
				if node.op == "|" then return left or right
				elseif node.op == "&" then return left and right
				elseif node.op == "^" then return left ~= right end
			end
			if type(left) == "boolean" then left = left and 1 or 0 end
			if type(right) == "boolean" then right = right and 1 or 0 end
			local is_big_left = type(left) == "table" and left.__type == "int"
			local is_big_right = type(right) == "table" and right.__type == "int"
			if is_big_left or is_big_right then return bigint_op(left, right, node.op) end
			if type(left) ~= "number" or type(right) ~= "number" then
				Runtime.raise("TypeError", "unsupported operand type(s) for " .. node.op)
			end
			if (node.op == "<<" or node.op == ">>") and right < 0 then
				Runtime.raise("ValueError", "negative shift count")
			end
			if node.op == "|" then return bit32.bor(left, right) elseif node.op == "&" then return bit32.band(left, right) elseif node.op == "^" then return bit32.bxor(left, right) elseif node.op == "<<" then return bit32.lshift(left, right) elseif node.op == ">>" then return bit32.rshift(left, right) end
		end
	end

	-- ==========================================
	-- 4. 賦值與原地增量運算求值 (Assign & In-place Ops)
	-- ==========================================
	local function eval_assign(node, scope)
		local val = eval_expr(node.value, scope)
		if node.op ~= "=" then
			local current = eval_expr(node.target, scope); local temp_op = node.op:sub(1, -2)
			local iop_magic = {
				["+"] = "__iadd__", ["-"] = "__isub__", ["*"] = "__imul__",
				["/"] = "__itruediv__", ["\\"] = "__ildiv__", ["//"] = "__ifloordiv__",
				["%"] = "__imod__", ["**"] = "__ipow__", ["@"] = "__imatmul__",
				["|"] = "__ior__", ["&"] = "__iand__", ["^"] = "__ixor__",
				["<<"] = "__ilshift__", [">>"] = "__irshift__"
			}
			local im_name = iop_magic[temp_op]
			local handled_iop = false

			-- 1. 優先查找自訂類別實例的原地增量魔術方法 (__iadd__, __imul__ 等)
			if im_name and type(current) == "table" and current.__type == "instance" then
				local prev_depth = Runtime.Traceback.eval_depth
				local success, attr = pcall(PyClass.get_attr, current, im_name)
				Runtime.Traceback.eval_depth = prev_depth
				if success and attr and attr.__type == "bound_method" then
					local res = bind_and_exec_func(attr.__func, {val}, {}, attr.__self)
					if res ~= Runtime.builtins.NotImplemented then
						val = (res == nil) and Runtime.builtins.None or res
						handled_iop = true
					end
				end
			end

			if not handled_iop then
				local raw_cur = (type(current) == "table" and current.__type == "instance" and current.__builtin_value ~= nil) and current.__builtin_value or current
				if temp_op == "+" and type(raw_cur) == "table" and raw_cur.__type == "list" and not raw_cur.is_matrix then
					PyClass.get_attr(raw_cur, "extend").__call({val})
					val = current
				elseif temp_op == "*" and type(raw_cur) == "table" and raw_cur.__type == "list" and not raw_cur.is_matrix then
					local count = unwrap_int_index(val)
					if type(count) ~= "number" or math.floor(count) ~= count then
						Runtime.raise("TypeError", "can't multiply sequence by non-int")
					end
					local og = {}; for _, v in ipairs(raw_cur.elements) do table.insert(og, v) end
					raw_cur.elements = {}; for _ = 1, math.max(0, count) do for _, v in ipairs(og) do table.insert(raw_cur.elements, v) end end
					val = current
				elseif temp_op == "+" and type(raw_cur) == "table" and raw_cur.__type == "bytearray" then
					if type(val) == "table" and (val.__type == "bytes" or val.__type == "bytearray" or val.__type == "memoryview") then
						raw_cur._data = raw_cur._data .. val._data; val = current
					else Runtime.raise("TypeError", "can't concat " .. tostring(type(val) == "table" and val.__type or type(val)) .. " to bytearray") end
				elseif temp_op == "*" and type(raw_cur) == "table" and raw_cur.__type == "bytearray" then
					local count = unwrap_int_index(val)
					if type(count) == "number" and math.floor(count) == count then
						raw_cur._data = string.rep(raw_cur._data, math.max(0, count)); val = current
					else Runtime.raise("TypeError", "can't multiply sequence by non-int") end
				elseif temp_op == "|" and type(raw_cur) == "table" and raw_cur.__type == "dict" then
					PyClass.get_attr(raw_cur, "update").__call({val})
					val = current
				elseif type(raw_cur) == "table" and raw_cur.__type == "set" and (temp_op == "|" or temp_op == "&" or temp_op == "-" or temp_op == "^") then
					local set_m = ({ ["|"] = "update", ["&"] = "intersection_update", ["-"] = "difference_update", ["^"] = "symmetric_difference_update" })[temp_op]
					PyClass.get_attr(raw_cur, set_m).__call({val})
					val = current
				else
					val = eval_expr({ type="BinaryOp", op=temp_op, left={type="DirectValue", value=current}, right={type="DirectValue", value=val} }, scope)
				end
			end
		end

		-- 特殊攔截:處理多維陣列 Tuple 索引賦值 (如 xy[0, :] = val),其餘交給 Utils.assign_target 處理
		if node.target.type == "Index" and node.target.index.type == "Tuple" then
			local t_obj = eval_expr(node.target.target, scope)
			if type(t_obj) == "table" and t_obj.__type == "instance" then
				local prev_depth = Runtime.Traceback.eval_depth
				local ok_set, setitem = pcall(PyClass.get_attr, t_obj, "__setitem__")
				Runtime.Traceback.eval_depth = prev_depth
				if ok_set and setitem then
					-- Tuple 索引直接求值即可 (因為 Parser 已經把它包裝成包含 Slice 與一般數值的 Tuple 節點了)
					local idx_val = eval_expr(node.target.index, scope)
					if setitem.__type == "bound_method" then
						bind_and_exec_func(setitem.__func, { idx_val, val }, {}, setitem.__self)
					else
						if type(invoke) ~= type(function()end) then invoke = require("Module:PythonScript/Evaluator").invoke end
						invoke(setitem, { t_obj, idx_val, val }, {})
					end
					return val
				end
			end
		end

		assign_target(node.target, val, scope)
		return val
	end

	return eval_index, eval_unary_op, eval_binary_op, eval_assign
end

return Ops