模組: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