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