跳转到内容

模組:PythonScript/Runtime

维基百科,自由的百科全书
-- Module:PythonScript/Runtime
-- 執行期核心環境:Traceback 狀態機、例外拋出介面與 builtins 內建函式庫
local Runtime = {}
local PyClass = require("Module:PythonScript/Class")
local Utils = require("Module:PythonScript/Runtime/Utils")
Utils.bind_runtime(Runtime, PyClass)

local SAFE_LIMIT = Utils.SAFE_LIMIT
local safe_pcall = Utils.safe_pcall
local unwrap_num = Utils.unwrap_num
local get_type_str = Utils.get_type_str
local to_bool = Utils.to_bool
local to_str = Utils.to_str
local runtime_is_equal = Utils.runtime_is_equal
local runtime_is_less = Utils.runtime_is_less
local get_iterator = Utils.get_iterator
local wrap_as_class = Utils.wrap_as_class

-- ==========================================
-- 1. 全域 Traceback 狀態追蹤與例外拋出引擎
-- ==========================================
Runtime.Traceback = {
    files = { [1] = { name = "__main__", raw_code = "" } },
    current_file_idx = 1,
    current_func_name = "<module>",
    current_line = 1,
    stack = {}, eval_depth = 0
}
Runtime.active_exception = nil

-- 虛擬 I/O 緩衝區
Runtime.stdout = {}
Runtime.stdin = {}

function Runtime.raise(exc_type_or_str, msg)
    Runtime.Traceback.eval_depth = 0
    local exc_inst
    if type(exc_type_or_str) == "string" then
        local exc_class = Runtime.builtins[exc_type_or_str] or Runtime.builtins.Exception
        exc_inst = PyClass.make_instance(exc_class)
        if msg ~= nil then
            PyClass.set_attr(exc_inst, "msg", tostring(msg))
            PyClass.set_attr(exc_inst, "args", { __type = "tuple", elements = { msg } })
        else
            PyClass.set_attr(exc_inst, "args", { __type = "tuple", elements = {} })
        end
    elseif type(exc_type_or_str) == "table" then
        if exc_type_or_str.__type == "class" then
            exc_inst = PyClass.make_instance(exc_type_or_str)
            if msg ~= nil then
                PyClass.set_attr(exc_inst, "msg", tostring(msg))
                PyClass.set_attr(exc_inst, "args", { __type = "tuple", elements = { msg } })
            else
                PyClass.set_attr(exc_inst, "args", { __type = "tuple", elements = {} })
            end
        else
            exc_inst = exc_type_or_str
            if msg ~= nil and not exc_inst.__dict.msg then
                PyClass.set_attr(exc_inst, "msg", tostring(msg))
            end
        end
    else
        local exc_class = Runtime.builtins.Exception
        exc_inst = PyClass.make_instance(exc_class)
        PyClass.set_attr(exc_inst, "msg", tostring(exc_type_or_str))
        PyClass.set_attr(exc_inst, "args", { __type = "tuple", elements = { tostring(exc_type_or_str) } })
    end

    if not exc_inst.__dict.__traceback_info__ then
        local tb_clone = {}
        for _, frame in ipairs(Runtime.Traceback.stack) do
            table.insert(tb_clone, {frame[1], frame[2], frame[3]})
        end
        table.insert(tb_clone, {Runtime.Traceback.current_file_idx, Runtime.Traceback.current_func_name or "<module>", Runtime.Traceback.current_line})
        PyClass.set_attr(exc_inst, "__traceback_info__", tb_clone)
    end

    if exc_type_or_str == "SystemExit" or (exc_inst.__class and exc_inst.__class.__name == "SystemExit") then
        if exc_inst.code == nil then exc_inst.code = msg or 0 end
        PyClass.set_attr(exc_inst, "code", exc_inst.code)
    elseif exc_type_or_str == "StopIteration" or (exc_inst.__class and exc_inst.__class.__name == "StopIteration") then
        if exc_inst.__dict.value == nil and msg ~= nil then
            PyClass.set_attr(exc_inst, "value", msg)
        end
    else
        if Runtime.active_exception and exc_inst ~= Runtime.active_exception then
            if not exc_inst.__dict.__context__ then
                PyClass.set_attr(exc_inst, "__context__", Runtime.active_exception)
            end
        end
    end

    error(exc_inst, 0)
end

-- ==========================================
-- 2. 基礎 object 類別與例外繼承樹初始化
-- ==========================================
Runtime.builtins = {}
local B = Runtime.builtins

local object_class = PyClass.make_class("object", {}, {
    __new__ = {
        __type = "staticmethod",
        __func = {
            __type = "builtin_method",
            __name = "__new__",
            __call = function(args)
                local cls = args and args[1]
                if type(cls) ~= "table" or cls.__type ~= "class" then
                    Runtime.raise("TypeError", "object.__new__(X): X is not a type object")
                end
                return PyClass.make_instance(cls)
            end
        }
    },
    __init__ = { __type = "builtin_method", __name = "__init__", __call = function() return nil end },
    __init_subclass__ = {
        __type = "classmethod",
        __func = {
            __type = "builtin_method",
            __name = "__init_subclass__",
            __call = function(args, kwargs)
                if kwargs and next(kwargs) ~= nil then
                    Runtime.raise("TypeError", "object.__init_subclass__() takes no keyword arguments")
                end
                return Runtime.builtins.None
            end
        }
    },
    __getattribute__ = {
        __type = "builtin_method",
        __name = "__getattribute__",
        __call = function(args) return PyClass.raw_get_attr(args[1], args[2]) end
    },
    __setattr__ = {
        __type = "builtin_method",
        __name = "__setattr__",
        __call = function(args)
            local val = args[3] == nil and Runtime.builtins.None or args[3]
            PyClass.raw_set_attr(args[1], args[2], val)
            return nil
        end
    },
    __delattr__ = {
        __type = "builtin_method",
        __name = "__delattr__",
        __call = function(args)
            PyClass.raw_set_attr(args[1], args[2], nil)
            return nil
        end
    },
    __format__ = {
        __type = "builtin_method",
        __name = "__format__",
        __call = function(args)
            local self, spec = args[1], args[2]
            if spec == nil then spec = "" end
            if type(spec) ~= "string" then
                Runtime.raise("TypeError", "__format__() argument must be str, not " .. get_type_str(spec))
            end
            if spec ~= "" then
                Runtime.raise("TypeError", "unsupported format string passed to " .. get_type_str(self) .. ".__format__")
            end
            return B.str.__call({self})
        end
    }
})
B.object = object_class

-- 委派 Utils 建立內建例外樹、單例物件 (None/NotImplemented/Ellipsis) 與基礎描述器類別
Utils.init_exceptions_and_singletons(B, object_class)

-- ==========================================
-- 3. 內建裝飾器、I/O 與核心資料型別
-- ==========================================
B.staticmethod = function(args) return { __type = "staticmethod", __func = args[1] } end
B.classmethod = function(args) return { __type = "classmethod", __func = args[1] } end
B.property = function(args) return { __type = "property", fget = args[1], fset = args[2], fdel = args[3] } end

B.print = function(args, kwargs)
    local sep = " "
    if kwargs and kwargs.sep ~= nil and kwargs.sep ~= B.None then
        if type(kwargs.sep) ~= "string" then Runtime.raise("TypeError", "sep must be None or a string") end
        sep = kwargs.sep
    end
    local end_str = "\n"
    local raw_end = kwargs and (kwargs["end"] ~= nil and kwargs["end"] or kwargs.end_)
    if raw_end ~= nil and raw_end ~= B.None then
        if type(raw_end) ~= "string" then Runtime.raise("TypeError", "end must be None or a string") end
        end_str = raw_end
    end
    local res = {}
    for i, v in ipairs(args or {}) do table.insert(res, to_str(v, false)) end
    table.insert(Runtime.stdout, table.concat(res, sep) .. end_str)
    return nil
end

B.input = function(args)
    local prompt = args and args[1]
    if prompt ~= nil and prompt ~= B.None then table.insert(Runtime.stdout, to_str(prompt, false)) end
    if #Runtime.stdin > 0 then return table.remove(Runtime.stdin, 1) else return "" end
end

B.int = wrap_as_class("int", function(args)
    if not args or #args == 0 then return 0 end
    local val, base = args[1], args[2]

    if type(val) == "table" and val.__type == "instance" then
        local ok, int_m = safe_pcall(PyClass.get_attr, val, "__int__")
        if ok and int_m and int_m.__type == "bound_method" then
            local Evaluator = require("Module:PythonScript/Evaluator")
            return Evaluator.invoke(int_m.__func, {}, {}, int_m.__self)
        end
        if val.__builtin_value ~= nil then val = val.__builtin_value end
    end
    if type(val) == "table" and (val.__type == "bytes" or val.__type == "bytearray" or val.__type == "memoryview") then
        val = val._data
    end

    if base ~= nil then
        base = unwrap_num(base)
        if type(base) ~= "number" or math.floor(base) ~= base then
            Runtime.raise("TypeError", "'base' object cannot be interpreted as an integer")
        end
        if type(val) ~= "string" then Runtime.raise("TypeError", "int() can't convert non-string with explicit base") end
        if base ~= 0 and (base < 2 or base > 36) then Runtime.raise("ValueError", "int() base must be >= 2 and <= 36, or 0") end
    else base = 10 end

    if type(val) == "number" then
        if val == math.huge or val == -math.huge then Runtime.raise("OverflowError", "cannot convert float infinity to integer") end
        if val ~= val then Runtime.raise("ValueError", "cannot convert float NaN to integer") end
        local f = val >= 0 and math.floor(val) or math.ceil(val)
        if math.abs(f) <= SAFE_LIMIT then return f end
        local MathCore = require("Module:PythonScript/Runtime/Cmath")
        return { __type = "int", is_big = true, value = f, sign = f < 0 and -1 or 1, limbs = MathCore.BigInt.to_limbs(f) }
    end
    if type(val) == "table" and val.__type == "int" then return val end
    if type(val) == "boolean" then return val and 1 or 0 end
    if type(val) == "string" then
        local orig_val = val
        local s = val:match("^%s*(.-)%s*$")
        if s == "" then Runtime.raise("ValueError", "invalid literal for int() with base " .. base .. ": '" .. orig_val .. "'") end

        local sign = 1
        if s:sub(1, 1) == "-" then sign = -1; s = s:sub(2)
        elseif s:sub(1, 1) == "+" then s = s:sub(2) end

        local pfx = s:sub(1, 2):lower()
        if pfx == "0x" and (base == 0 or base == 16) then base = 16; s = s:sub(3)
        elseif pfx == "0o" and (base == 0 or base == 8) then base = 8; s = s:sub(3)
        elseif pfx == "0b" and (base == 0 or base == 2) then base = 2; s = s:sub(3)
        elseif base == 0 then
            if s:sub(1, 1) == "0" and s:find("[^0_]") then
                Runtime.raise("ValueError", "invalid literal for int() with base 0: '" .. orig_val .. "'")
            end
            base = 10
        end

        if s:sub(1, 1) == "_" or s:sub(-1) == "_" or s:find("__") then
            Runtime.raise("ValueError", "invalid literal for int() with base " .. base .. ": '" .. orig_val .. "'")
        end
        s = s:gsub("_", "")
        if s == "" then Runtime.raise("ValueError", "invalid literal for int() with base " .. base .. ": '" .. orig_val .. "'") end

        if base == 10 then
            if not s:match("^%d+$") then Runtime.raise("ValueError", "invalid literal for int() with base 10: '" .. orig_val .. "'") end
            local signed_s = (sign < 0 and "-" or "") .. s
            if #s <= 15 then
                local num = tonumber(signed_s)
                if num and math.abs(num) <= SAFE_LIMIT then return math.floor(num) end
            end
            local MathCore = require("Module:PythonScript/Runtime/Cmath")
            local limbs = MathCore.BigInt.to_limbs(s)
            if #limbs == 1 and limbs[1] == 0 then return 0 end
            return { __type = "int", is_big = true, value = tonumber(signed_s), sign = sign, limbs = limbs }
        else
            local res = tonumber(s, base)
            if not res then Runtime.raise("ValueError", "invalid literal for int() with base " .. base .. ": '" .. orig_val .. "'") end
            res = res * sign
            if math.abs(res) <= SAFE_LIMIT then return math.floor(res) end
            local MathCore = require("Module:PythonScript/Runtime/Cmath")
            return { __type = "int", is_big = true, value = res, sign = sign, limbs = MathCore.BigInt.to_limbs(math.abs(res)) }
        end
    end
    Runtime.raise("TypeError", "int() argument must be a string, a bytes-like object or a real number, not '" .. get_type_str(val) .. "'")
end)

B.float = wrap_as_class("float", function(args)
    if not args or #args == 0 then return 0.0 end
    local val = args[1]
    if type(val) == "table" and val.__type == "instance" then
        local ok, flt_m = safe_pcall(PyClass.get_attr, val, "__float__")
        if ok and flt_m and flt_m.__type == "bound_method" then
            local Evaluator = require("Module:PythonScript/Evaluator")
            return Evaluator.invoke(flt_m.__func, {}, {}, flt_m.__self)
        end
        if val.__builtin_value ~= nil then val = val.__builtin_value end
    end
    if type(val) == "table" and (val.__type == "bytes" or val.__type == "bytearray" or val.__type == "memoryview") then val = val._data end
    if type(val) == "number" then return val end
    if type(val) == "table" and val.__type == "int" and val.is_big then
        local MathCore = require("Module:PythonScript/Runtime/Cmath")
        return tonumber(MathCore.BigInt.to_string(val.sign, val.limbs))
    end
    if type(val) == "string" then
        local s = val:match("^%s*(.-)%s*$")
        local low = s:lower()
        if low == "inf" or low == "+inf" or low == "infinity" or low == "+infinity" then return math.huge
        elseif low == "-inf" or low == "-infinity" then return -math.huge
        elseif low == "nan" or low == "+nan" or low == "-nan" then return 0/0 end
        if not s:sub(1, 1):match("^_") and not s:sub(-1):match("_$") and not s:find("__") then
            s = s:gsub("(%d)_(%d)", "%1%2")
        end
        local res = tonumber(s)
        if not res then Runtime.raise("ValueError", "could not convert string to float: '" .. val .. "'") end
        return res
    end
    if type(val) == "boolean" then return val and 1.0 or 0.0 end
    Runtime.raise("TypeError", "float() argument must be a string or a real number, not '" .. get_type_str(val) .. "'")
end)

B.str = wrap_as_class("str", function(args, kwargs)
    if not args or #args == 0 then return "" end
    local val = args[1]
    local encoding = args[2] or (kwargs and kwargs.encoding)
    if encoding ~= nil then
        if type(val) == "table" and (val.__type == "bytes" or val.__type == "bytearray" or val.__type == "memoryview") then
            return val._data
        end
        Runtime.raise("TypeError", "decoding to str: need a bytes-like object")
    end
	if type(val) == type({}) and val._is_unpacked_tuple then val._unpacked_to_str = true end
    return to_str(val, false)
end)

B.str.__dict["maketrans"] = {
    __type = "staticmethod",
    __func = {
        __type = "builtin_method",
        __call = function(args)
            local a, b, c = args[1], args[2], args[3]
            local res = { __type = "dict", pairs = {} }
            if b == nil then
                if type(a) == "table" and a.__type == "dict" then
                    for _, p in ipairs(a.pairs) do
                        local k = (type(p.key) == "string" and mw.ustring.len(p.key) == 1) and mw.ustring.codepoint(p.key) or p.key
                        table.insert(res.pairs, {key = k, value = p.value})
                    end
                end
            else
                if mw.ustring.len(a) ~= mw.ustring.len(b) then Runtime.raise("ValueError", "the first two maketrans arguments must have equal length") end
                local a_cps, b_cps = {}, {}
                for cp in mw.ustring.gcodepoint(a) do table.insert(a_cps, cp) end
                for cp in mw.ustring.gcodepoint(b) do table.insert(b_cps, cp) end
                for i = 1, #a_cps do table.insert(res.pairs, {key = a_cps[i], value = b_cps[i]}) end
                if c then
                    for cp in mw.ustring.gcodepoint(c) do
                        local found = false
                        for _, p in ipairs(res.pairs) do if p.key == cp then p.value = B.None; found = true; break end end
                        if not found then table.insert(res.pairs, {key = cp, value = B.None}) end
                    end
                end
            end
            return res
        end
    }
}
B.str.__dict["encode"] = {
    __type = "builtin_method",
    __name = "encode",
    __call = function(args, kwargs)
        local self = args[1]
        local encoding = (args[2] ~= nil) and args[2] or (kwargs and kwargs.encoding) or "utf-8"
        local errors = (args[3] ~= nil) and args[3] or (kwargs and kwargs.errors) or "strict"
        local encoding_str = require("Module:PythonScript/Runtime/Utils").to_str(encoding, false)
        local errors_str = require("Module:PythonScript/Runtime/Utils").to_str(errors, false)
        return require("Module:PythonScript/Librarys/Codecs").Codecs.lua_encode(self, encoding_str, errors_str)
    end
}

B.bool = wrap_as_class("bool", function(args) return to_bool(args and args[1]) end)
B.bool.__bases = { B.int }
B.bool.__mro = { B.bool, B.int, object_class }

B.list = wrap_as_class("list", function(args)
    local iterable = args and args[1]; local elements = {}
    if iterable ~= nil then
        local iter_fn = get_iterator(iterable)
        while true do
            local has_next, v = iter_fn(); if not has_next then break end
            table.insert(elements, v)
        end
    end
    return { __type = "list", elements = elements }
end)

B.tuple = wrap_as_class("tuple", function(args)
    local lst = B.list.__call(args); lst.__type = "tuple"; return lst
end)

B.set = wrap_as_class("set", function(args)
    local iterable = args and args[1]; local elements = {}
    if iterable ~= nil then
        local iter_fn = get_iterator(iterable)
        while true do
            local has_next, val = iter_fn(); if not has_next then break end
            local exists = false
            for _, e in ipairs(elements) do if runtime_is_equal(e, val) then exists = true; break end end
            if not exists then table.insert(elements, val) end
        end
    end
    return { __type = "set", elements = elements }
end)

B.frozenset = wrap_as_class("frozenset", function(args)
    local s = B.set.__call(args); s.__type = "frozenset"; return s
end)

B.bytes = wrap_as_class("bytes", function(args, kwargs)
    return { __type = "bytes", _data = Utils.construct_bytes_data(args, kwargs, "bytes") }
end)
B.bytes.__dict["fromhex"] = Utils.make_bytes_fromhex("bytes")
B.bytes.__dict["maketrans"] = B.str.__dict["maketrans"]
B.bytes.__dict["decode"] = {
    __type = "builtin_method",
    __name = "decode",
    __call = function(args, kwargs)
        local self = args[1]
        local encoding = (args[2] ~= nil) and args[2] or (kwargs and kwargs.encoding) or "utf-8"
        local errors = (args[3] ~= nil) and args[3] or (kwargs and kwargs.errors) or "strict"
        local encoding_str = require("Module:PythonScript/Runtime/Utils").to_str(encoding, false)
        local errors_str = require("Module:PythonScript/Runtime/Utils").to_str(errors, false)
        return require("Module:PythonScript/Librarys/Codecs").Codecs.lua_decode(self, encoding_str, errors_str)
    end
}

B.bytearray = wrap_as_class("bytearray", function(args, kwargs)
    return { __type = "bytearray", _data = Utils.construct_bytes_data(args, kwargs, "bytearray") }
end)
B.bytearray.__dict["fromhex"] = Utils.make_bytes_fromhex("bytearray")
B.bytearray.__dict["maketrans"] = B.str.__dict["maketrans"]
B.bytearray.__dict["decode"] = B.bytes.__dict["decode"]

B.memoryview = wrap_as_class("memoryview", function(args)
    local obj = args and args[1]
    if type(obj) ~= "table" or (obj.__type ~= "bytes" and obj.__type ~= "bytearray" and obj.__type ~= "memoryview") then
        Runtime.raise("TypeError", "memoryview: a bytes-like object is required")
    end
    if obj.__type == "memoryview" and obj._released then
        Runtime.raise("ValueError", "operation forbidden on released memoryview object")
    end
    local root_obj = obj.__type == "memoryview" and obj._obj or obj
    return { __type = "memoryview", _data = obj._data, _obj = root_obj, _readonly = (root_obj.__type == "bytes"), _released = false }
end)

B.dict = wrap_as_class("dict", function(args, kwargs)
    local kv_pairs = {}
    local function set_kv(k, v)
        for _, p in ipairs(kv_pairs) do
            if runtime_is_equal(p.key, k) then p.value = v; return end
        end
        table.insert(kv_pairs, {key = k, value = v})
    end
    if args and args[1] then
        if type(args[1]) == "table" and args[1].__type == "dict" then
            for _, p in ipairs(args[1].pairs) do set_kv(p.key, p.value) end
        else
            local iter_fn = get_iterator(args[1])
            while true do
                local has_next, v = iter_fn(); if not has_next then break end
                if type(v) == "table" and (v.__type == "list" or v.__type == "tuple") and #v.elements == 2 then
                    set_kv(v.elements[1], v.elements[2])
                else Runtime.raise("TypeError", "cannot convert dictionary update sequence element to a sequence") end
            end
        end
    end
    if kwargs then for k, v in pairs(kwargs) do set_kv(k, v) end end
    return { __type = "dict", pairs = kv_pairs }
end)

B.complex = wrap_as_class("complex", Utils.construct_complex)

-- ==========================================
-- 4. 數學、序列、反射與動態執行內建函式
-- ==========================================
B.abs = function(args)
    local obj = args[1]
    if type(obj) == "table" and obj.__type == "instance" then
        local ok, abs_m = safe_pcall(PyClass.get_attr, obj, "__abs__")
        if ok and abs_m and abs_m.__type == "bound_method" then
            local Evaluator = require("Module:PythonScript/Evaluator")
            return Evaluator.invoke(abs_m.__func, {}, {}, abs_m.__self)
        end
        if obj.__builtin_value ~= nil then obj = obj.__builtin_value end
    end
    if type(obj) == "table" and obj.__type == "list" and obj.is_matrix then
        return require("Module:PythonScript/Library").Matrix.unary_op(obj, function(x) return B.abs({x}) end)
    end
    if type(obj) == "table" and obj.__type == "int" and obj.is_big then
        local v = obj.value; if v then v = math.abs(v) end
        return { __type = "int", is_big = true, sign = 1, value = v, limbs = obj.limbs }
    end
    if type(obj) == "table" and obj.__type == "complex" then
        return require("Module:PythonScript/Runtime/Cmath").Cmath.abs(obj)
    end
    local n = unwrap_num(obj)
    if type(n) ~= "number" then Runtime.raise("TypeError", "bad operand type for abs(): '" .. get_type_str(obj) .. "'") end
    return math.abs(n)
end

B.round = function(args)
    local obj, ndigits = args[1], args[2]
    if ndigits == B.None then ndigits = nil end
    if type(obj) == "table" and obj.__type == "instance" then
        local ok, rnd_m = safe_pcall(PyClass.get_attr, obj, "__round__")
        if ok and rnd_m and rnd_m.__type == "bound_method" then
            local Evaluator = require("Module:PythonScript/Evaluator")
            local call_args = ndigits ~= nil and {ndigits} or {}
            return Evaluator.invoke(rnd_m.__func, call_args, {}, rnd_m.__self)
        end
    end
    local mult = 10 ^ (ndigits or 0)
    if type(obj) == "table" and obj.__type == "list" and obj.is_matrix then
        return require("Module:PythonScript/Library").Matrix.unary_op(obj, function(x)
            return math.floor(unwrap_num(x) * mult + 0.5) / mult
        end)
    end
    local val = unwrap_num(obj)
    if type(val) ~= "number" then Runtime.raise("TypeError", "type " .. get_type_str(obj) .. " doesn't define __round__ method") end
    local res = math.floor(val * mult + 0.5) / mult
    return ndigits == nil and math.floor(res) or res
end

B.pow = function(args)
    local a1, a2, a3 = args[1], args[2], args[3]
    if type(a1) == "table" and a1.__type == "instance" and a1.__builtin_value ~= nil then a1 = a1.__builtin_value end
    if type(a2) == "table" and a2.__type == "instance" and a2.__builtin_value ~= nil then a2 = a2.__builtin_value end
    if type(a3) == "table" and a3.__type == "instance" and a3.__builtin_value ~= nil then a3 = a3.__builtin_value end
    if type(a1) == "table" and a1.__type == "list" and a1.is_matrix then
        return require("Module:PythonScript/Library").Matrix.pow(a1, a2)
    end
    -- 三參數模冪 pow(base, exp, mod):直接交由 BigInt.powmod 進行二進位平方取模
    if a3 ~= nil and a3 ~= B.None then
        return require("Module:PythonScript/Runtime/Cmath").BigInt.powmod(a1, a2, a3)
    end
    -- 支援複數/四元數與自定義實例的雙參數 pow(a1, a2)
    if (type(a1) == "table" and (a1.__type == "complex" or a1.__type == "instance"))
       or (type(a2) == "table" and (a2.__type == "complex" or a2.__type == "instance")) then
        local eval_expr = require("Module:PythonScript/Evaluator/Core").getCoreFunction()
        local dummy_scope = require("Module:PythonScript/Evaluator/Utils").create_scope(nil)
        return eval_expr({
            type = "BinaryOp", op = "**",
            left = { type = "DirectValue", value = a1 },
            right = { type = "DirectValue", value = a2 }
        }, dummy_scope)
    end
    local Evaluator = require("Module:PythonScript/Evaluator")
    local is_big = (type(a1) == "table" and a1.__type == "int") or (type(a2) == "table" and a2.__type == "int")
    if is_big then
        return Evaluator.bigint_op(a1, a2, "**")
    end
    local v1, v2 = unwrap_num(a1), unwrap_num(a2)
    if type(v1) ~= "number" or type(v2) ~= "number" then Runtime.raise("TypeError", "unsupported operand type(s) for pow()") end
    if v1 == 0 and v2 < 0 then
        Runtime.raise("ZeroDivisionError", "0.0 cannot be raised to a negative power")
    end
    local res = v1 ^ v2
    if math.abs(res) > SAFE_LIMIT and math.floor(v1) == v1 and math.floor(v2) == v2 and v2 >= 0 then
        return Evaluator.bigint_op(v1, v2, "**")
    end
    return res
end

B.divmod = function(args)
    local x, y = args[1], args[2]
    if type(x) == "table" and x.__type == "instance" then
        local success, attr = safe_pcall(PyClass.get_attr, x, "__divmod__")
        if success and attr and attr.__type == "bound_method" then
            local Evaluator = require("Module:PythonScript/Evaluator")
            return Evaluator.invoke(attr.__func, {y}, {}, attr.__self)
        end
        if x.__builtin_value ~= nil then x = x.__builtin_value end
    end
    if type(y) == "table" and y.__type == "instance" and y.__builtin_value ~= nil then y = y.__builtin_value end
    if (type(x) == "table" and x.__type == "int") or (type(y) == "table" and y.__type == "int") then
        local Evaluator = require("Module:PythonScript/Evaluator")
        return { __type = "tuple", elements = { Evaluator.bigint_op(x, y, "//"), Evaluator.bigint_op(x, y, "%") } }
    end
    local ux, uy = unwrap_num(x), unwrap_num(y)
    if uy == 0 then Runtime.raise("ZeroDivisionError", "integer division or modulo by zero") end
    return { __type = "tuple", elements = { math.floor(ux / uy), ux % uy } }
end

B.max = function(args, kwargs) return Utils.min_or_max(args, kwargs, false) end
B.min = function(args, kwargs) return Utils.min_or_max(args, kwargs, true) end
B.sum = Utils.sum_impl

B.all = function(args)
    local iter_fn = get_iterator(args[1])
    while true do local has_next, v = iter_fn(); if not has_next then break end; if not to_bool(v) then return false end end
    return true
end

B.any = function(args)
    local iter_fn = get_iterator(args[1])
    while true do local has_next, v = iter_fn(); if not has_next then break end; if to_bool(v) then return true end end
    return false
end

B.len = function(args)
    local obj = args and args[1]
    if type(obj) == "string" then return mw.ustring.len(obj) end
    if type(obj) == "table" then
        if obj.__type == "bytes" or obj.__type == "bytearray" or obj.__type == "memoryview" then
            if obj.__type == "memoryview" and obj._released then Runtime.raise("ValueError", "operation forbidden on released memoryview object") end
            return #(obj._data)
        end
        if obj.__type == "instance" then
            local success, attr = safe_pcall(PyClass.get_attr, obj, "__len__")
            if success and attr and attr.__type == "bound_method" then
                local Evaluator = require("Module:PythonScript/Evaluator")
                return Evaluator.invoke(attr.__func, {}, {}, attr.__self)
            end
            if obj.__builtin_value ~= nil then return B.len({obj.__builtin_value}) end
            Runtime.raise("TypeError", "object of type '" .. (obj.__class.__name or "instance") .. "' has no len()")
        end
        if obj.__type == "list" or obj.__type == "tuple" or obj.__type == "set" or obj.__type == "frozenset" then return #(obj.elements) end
        if obj.__type == "dict" then return #(obj.pairs) end
        if obj.__type == "range" then
            local start, stop, step = obj.start, obj.stop, obj.step
            if step > 0 and start < stop then return math.floor((stop - start - 1) / step) + 1
            elseif step < 0 and start > stop then return math.floor((start - stop - 1) / (-step)) + 1
            else return 0 end
        end
    end
    Runtime.raise("TypeError", "object of type '" .. get_type_str(obj) .. "' has no len()")
end

B.range = function(args)
    local start, stop, step
    if #args == 1 then start, stop, step = 0, unwrap_num(args[1]), 1
    elseif #args == 2 then start, stop, step = unwrap_num(args[1]), unwrap_num(args[2]), 1
    elseif #args == 3 then start, stop, step = unwrap_num(args[1]), unwrap_num(args[2]), unwrap_num(args[3])
    else Runtime.raise("TypeError", "range expected at most 3 arguments") end
    if step == 0 then Runtime.raise("ValueError", "range() arg 3 must not be zero") end
    return { __type = "range", start = start, stop = stop, step = step }
end

B.reversed = Utils.reversed_impl

B.sorted = function(args, kwargs)
    local lst = B.list.__call({args[1]})
    PyClass.get_attr(lst, "sort").__call({}, kwargs)
    return lst
end

B.enumerate = function(args, kwargs)
    local iter_fn = get_iterator(args[1])
    local start = (args[2] and unwrap_num(args[2])) or (kwargs and kwargs.start and unwrap_num(kwargs.start)) or 0
    local elements, i = {}, start
    while true do
        local has_next, v = iter_fn(); if not has_next then break end
        table.insert(elements, { __type = "tuple", elements = { i, v } }); i = i + 1
    end
    return { __type = "list", elements = elements }
end

B.zip = function(args)
    if not args or #args == 0 then return { __type = "list", elements = {} } end
    local iter_fns = {}; for _, arg in ipairs(args) do table.insert(iter_fns, get_iterator(arg)) end
    local elements = {}
    while true do
        local tup_elements, all_has_next = {}, true
        for _, fn in ipairs(iter_fns) do
            local has_next, v = fn(); if not has_next then all_has_next = false; break end
            table.insert(tup_elements, v)
        end
        if not all_has_next then break end
        table.insert(elements, { __type = "tuple", elements = tup_elements })
    end
    return { __type = "list", elements = elements }
end

local _id_registry = setmetatable({}, { __mode = "k" })
local _next_obj_id = 1

B.id = function(args)
    local obj = args[1]
    if obj == nil then return 0 end
    local id_val = _id_registry[obj]
    if not id_val then
        id_val = _next_obj_id
        _next_obj_id = _next_obj_id + 1
        _id_registry[obj] = id_val
    end
    return id_val
end

B.chr = function(args)
    local n = unwrap_num(args[1])
    if type(n) ~= "number" or math.floor(n) ~= n then
        Runtime.raise("TypeError", "'" .. get_type_str(args[1]) .. "' object cannot be interpreted as an integer")
    end
    if n < 0 or n > 0x10FFFF then Runtime.raise("ValueError", "chr() arg not in range(0x110000)") end
    return mw.ustring.char(n)
end

B.ord = function(args)
    local c = args[1]
    if type(c) == "table" and (c.__type == "bytes" or c.__type == "bytearray") then
        if #c._data ~= 1 then Runtime.raise("TypeError", "ord() expected a character, but string of length " .. #c._data .. " found") end
        return string.byte(c._data, 1)
    elseif type(c) == "string" then
        if mw.ustring.len(c) ~= 1 then Runtime.raise("TypeError", "ord() expected a character, but string of length " .. mw.ustring.len(c) .. " found") end
        return mw.ustring.codepoint(c, 1)
    end
    Runtime.raise("TypeError", "ord() expected string of length 1, but " .. get_type_str(c) .. " found")
end

B.hex = function(args) return Utils.int_to_base_str(args[1], 16, "0x") end
B.oct = function(args) return Utils.int_to_base_str(args[1], 8, "0o") end
B.bin = function(args) return Utils.int_to_base_str(args[1], 2, "0b") end
B.format = Utils.format_value

B.getattr = function(args)
    local obj, name, default = args[1], args[2], args[3]
    local success, res = safe_pcall(PyClass.get_attr, obj, name)
    if success then return res
    elseif #args >= 3 then return default
    else Runtime.raise("AttributeError", "'" .. get_type_str(obj) .. "' object has no attribute '" .. tostring(name) .. "'") end
end
B.setattr = function(args) PyClass.set_attr(args[1], args[2], args[3]); return nil end
B.hasattr = function(args) local success = safe_pcall(PyClass.get_attr, args[1], args[2]); return success end
B.delattr = function(args) PyClass.set_attr(args[1], args[2], nil); return nil end

local function check_isinstance_single(obj, cls)
    if cls == B.object then return true end
    if type(cls) == "table" and cls.__type == "tuple" then
        for _, c in ipairs(cls.elements) do if check_isinstance_single(obj, c) then return true end end
        return false
    end
    if type(obj) == "boolean" and cls == B.int then return true end
    return PyClass.is_instance(obj, cls)
end

local function check_issubclass_single(cls, parent_cls)
    if parent_cls == B.object and type(cls) == "table" and cls.__type == "class" then return true end
    if type(parent_cls) == "table" and parent_cls.__type == "tuple" then
        for _, p in ipairs(parent_cls.elements) do if check_issubclass_single(cls, p) then return true end end
        return false
    end
    return PyClass.is_subclass(cls, parent_cls)
end

B.isinstance = function(args) return check_isinstance_single(args[1], args[2]) end
B.issubclass = function(args) return check_issubclass_single(args[1], args[2]) end
B.repr = function(args) return to_str(args[1], true) end
B.ascii = B.repr

B.vars = function(args)
    local obj = args and args[1]
    if not obj then return B.locals({}) end
    if type(obj) == "table" and (obj.__type == "instance" or obj.__type == "class" or obj.__type == "module") then
        local kv_pairs = {}
        for k, v in pairs(obj.__dict or {}) do table.insert(kv_pairs, {key = k, value = v}) end
        return { __type = "dict", pairs = kv_pairs, __scope_ref = obj.__dict }
    end
    Runtime.raise("TypeError", "vars() argument must have __dict__ attribute")
end

B.dir = Utils.dir_impl

B.callable = function(args)
    local obj = args[1]
    if type(obj) == "function" then return true end
    if type(obj) == "table" then
        if obj.__class and obj.__class.__name == "NoneType" then return false end
        if obj.__type == "function" or obj.__type == "builtin_method" or obj.__type == "bound_method" or obj.__type == "class" then return true end
        if obj.__type == "instance" then
            local success, attr = safe_pcall(PyClass.get_attr, obj, "__call__")
            return success and attr ~= nil
        end
    end
    return false
end

B.hash = Utils.hash_impl

B.globals = function()
    local scope = Runtime.Traceback.current_scope
    if not scope then return { __type = "dict", pairs = {} } end
    local root = scope
    while root.parent do root = root.parent end
    local kv_pairs = {}
    for k, v in pairs(root.vars) do table.insert(kv_pairs, {key = k, value = v}) end
    return { __type = "dict", pairs = kv_pairs, __scope_ref = root.vars }
end

B.locals = function()
    local scope = Runtime.Traceback.current_scope
    if not scope then return { __type = "dict", pairs = {} } end
    local kv_pairs = {}
    for k, v in pairs(scope.vars) do table.insert(kv_pairs, {key = k, value = v}) end
    return { __type = "dict", pairs = kv_pairs, __scope_ref = scope.vars }
end

B.compile = Utils.compile_impl
B.eval = Utils.eval_impl
B.exec = Utils.exec_impl
B.exit = function(args) Runtime.raise("SystemExit", args and args[1] or 0) end
B.quit = B.exit

B.map = Utils.map_impl
B.filter = Utils.filter_impl
B.iter = Utils.iter_impl
B.next = Utils.next_impl

-- 將 UnpackedTuple 家族正式註冊為 tuple 的子類別 (確保 isinstance/issubclass 正常運作)
if PyClass._UnpackedTuple and B.tuple and B.object then
	PyClass._UnpackedTuple.__bases = { B.tuple }
	PyClass._UnpackedTuple.__mro = { PyClass._UnpackedTuple, B.tuple, B.object }

	PyClass._QuaternionImagPart.__bases = { PyClass._UnpackedTuple }
	PyClass._QuaternionImagPart.__mro = { PyClass._QuaternionImagPart, PyClass._UnpackedTuple, B.tuple, B.object }
end

return Runtime