模組:PythonScript/Runtime/Utils
外观
-- Module:PythonScript/Runtime/Utils
-- Runtime 內部輔助函式、例外繼承樹、格式化引擎與迭代器實作
local Utils = {}
local Runtime, PyClass
function Utils.bind_runtime(rt, cls)
Runtime = rt
PyClass = cls
end
local SAFE_LIMIT = 9007199254740990
Utils.SAFE_LIMIT = SAFE_LIMIT
local function safe_pcall(fn, ...)
local prev_depth = Runtime.Traceback.eval_depth
local ok, res = pcall(fn, ...)
Runtime.Traceback.eval_depth = prev_depth
return ok, res
end
Utils.safe_pcall = safe_pcall
local function unwrap_num(obj)
if type(obj) == "table" and obj.__type == "instance" and obj.__builtin_value ~= nil then
obj = obj.__builtin_value
end
if type(obj) == "boolean" then return obj and 1 or 0 end
if type(obj) == "table" and obj.__type == "int" and obj.is_big then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
return tonumber(MathCore.BigInt.to_string(obj.sign, obj.limbs))
end
return obj
end
Utils.unwrap_num = unwrap_num
local function get_type_str(obj)
local t = type(obj)
if t == "number" then return (math.floor(obj) == obj and math.abs(obj) <= SAFE_LIMIT) and "int" or "float"
elseif t == "string" then return "str"
elseif t == "boolean" then return "bool"
elseif t == "nil" then return "NoneType"
elseif t == "table" then
if obj.__class and obj.__class.__name then return obj.__class.__name end
return obj.__type or "object"
end
return "unknown"
end
Utils.get_type_str = get_type_str
local function format_bytes_repr(s)
local out = {}
for i = 1, #s do
local b = string.byte(s, i)
if b == 92 then table.insert(out, "\\\\")
elseif b == 39 then table.insert(out, "\\'")
elseif b == 10 then table.insert(out, "\\n")
elseif b == 13 then table.insert(out, "\\r")
elseif b == 9 then table.insert(out, "\\t")
elseif b >= 32 and b <= 126 then table.insert(out, string.char(b))
else table.insert(out, string.format("\\x%02x", b)) end
end
return "b'" .. table.concat(out) .. "'"
end
local function to_bool(obj)
if obj == nil or obj == false then return false end
if type(obj) == "table" and obj.__class and obj.__class.__name == "NoneType" then return false end
if type(obj) == "number" and obj == 0 then return false end
if type(obj) == "table" and obj.__type == "int" and obj.is_big then
return not (#obj.limbs == 1 and obj.limbs[1] == 0)
end
if type(obj) == "table" and obj.__type == "complex" then
return not (obj.real == 0 and obj.imag == 0 and (obj.k or 0) == 0 and (obj.jk or 0) == 0)
end
if type(obj) == "table" and obj.__type == "range" then
if obj.step > 0 then return obj.start < obj.stop else return obj.start > obj.stop end
end
if type(obj) == "table" and obj.__type == "instance" then
local success, attr = safe_pcall(PyClass.get_attr, obj, "__bool__")
if success and attr and attr.__type == "bound_method" then
local Evaluator = require("Module:PythonScript/Evaluator")
local res = Evaluator.invoke(attr.__func, {}, {}, attr.__self)
return res == true or res == 1
end
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")
local len_val = Evaluator.invoke(attr.__func, {}, {}, attr.__self)
return unwrap_num(len_val) > 0
end
if obj.__builtin_value ~= nil then return to_bool(obj.__builtin_value) end
return true
end
if type(obj) == "string" and obj == "" then return false end
if type(obj) == "table" then
if obj.__type == "bytes" or obj.__type == "bytearray" or obj.__type == "memoryview" then
return #(obj._data or "") > 0
end
if obj.__type == "list" or obj.__type == "tuple" or obj.__type == "dict" or obj.__type == "set" or obj.__type == "frozenset" then
if obj.elements and #obj.elements == 0 then return false end
if obj.pairs and #obj.pairs == 0 then return false end
end
end
return true
end
Utils.to_bool = to_bool
local function to_str(obj, in_repr, seen)
local t = type(obj)
if t == "nil" then return "None"
elseif t == "table" and obj.__class and obj.__class.__name == "NoneType" then return "None"
elseif t == "boolean" then return obj and "True" or "False"
elseif t == "number" then
if obj ~= obj then return "nan"
elseif obj == math.huge then return "inf"
elseif obj == -math.huge then return "-inf" end
local s = tostring(obj)
if s:match("%.0$") then return s:sub(1, -3) else return s end
elseif t == "string" then
return in_repr and ("'" .. obj:gsub("\\", "\\\\"):gsub("'", "\\'") .. "'") or obj
elseif t == "table" then
if obj.__type == "int" and obj.is_big then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
return MathCore.BigInt.to_string(obj.sign, obj.limbs)
elseif obj._unpacked_to_str then
if (obj.__class or{}).__name == "QuaternionImagPart" then --保持複數行為不變
obj._unpacked_to_str = nil --復位
else obj.__type_orig = obj.__type --保存舊型態名稱
obj.__type = "tuple" --讓下方的tuple分支輸出內容
end
elseif obj.__type == "bytes" then
return format_bytes_repr(obj._data or "")
elseif obj.__type == "bytearray" then
return "bytearray(" .. format_bytes_repr(obj._data or "") .. ")"
elseif obj.__type == "memoryview" then
return "<memory at " .. (tostring(obj):match("0x%w+") or (obj.__module__ and ("'"..obj.__module__.."'")) or '<unknown>') .. ">"
elseif obj.__type == "complex" then
return require("Module:PythonScript/Runtime/Cmath").Cmath.to_str(obj)
elseif obj.__type == "range" then
return "range(" .. obj.start .. ", " .. obj.stop .. (obj.step == 1 and "" or ", " .. obj.step) .. ")"
elseif obj.__type == "class" then
local meta = obj.__class
local B = Runtime.builtins
if type(meta) == "table" and meta.__type == "class" and B and meta ~= B.type then
local magic = in_repr and "__repr__" or "__str__"
for _, m_base in ipairs(meta.__mro or {}) do
if m_base == B.type or m_base == B.object then break end
local fn_m = m_base.__dict and m_base.__dict[magic]
if not fn_m and not in_repr then fn_m = m_base.__dict and m_base.__dict["__repr__"] end
if fn_m then
local Evaluator = require("Module:PythonScript/Evaluator")
local ok_inv, inv_res = safe_pcall(Evaluator.invoke, fn_m, { obj }, {})
if ok_inv then return inv_res else error(inv_res, 0) end
end
end
end
local m_name = obj.__dict and obj.__dict["__module__"]
local c_name = obj.__name or "unknown"
if m_name and m_name ~= "__main__" and m_name ~= "builtins" then return "<class '" .. m_name .. "." .. c_name .. "'>" end
return "<class '" .. c_name .. "'>"
elseif obj.__type == "function" then
local name = obj.__qualname__ or obj.__name or "<lambda>"
return "<function " .. name .. " at " .. (tostring(obj):match("0x%w+") or (obj.__module__ and ("'"..obj.__module__.."'")) or '<unknown>') .. ">"
elseif obj.__type == "builtin_method" then
return "<built-in function " .. (obj.__name or "unknown") .. ">"
elseif obj.__type == "property" then
return "<property object at " .. (tostring(obj):match("0x%w+") or (obj.__module__ and ("'"..obj.__module__.."'")) or '<unknown>') .. ">"
elseif obj.__type == "LazyNamespace" then
return "<module '" .. (obj._name or obj.__module__ or "unknown") .. "'>"
end
seen = seen or {}
if seen[obj] then
if obj.__type == "list" then return "[...]" end
if obj.__type == "dict" then return "{...}" end
if obj.__type == "set" or obj.__type == "frozenset" then return obj.__type .. "(...)" end
if obj.__type == "tuple" then return "(...)" end
return "<...>"
end
seen[obj] = true
local result_str
if obj.__type == "list" or obj.__type == "tuple" then
local open = obj.__type == "list" and "[" or (obj._unpacked_to_str and "" or "(") --如果是unpacked tuple就沒有括號
local close = obj.__type == "list" and "]" or (obj._unpacked_to_str and "" or ")")
local res = {}
local elements = obj.elements or {}
if obj._unpacked_to_str then elements = obj._elements or {} end
for _, v in ipairs(elements) do table.insert(res, to_str(v, true, seen)) end
if obj.__type == "tuple" and #res == 1 then result_str = open .. res[1] .. "," .. close
else result_str = open .. table.concat(res, ", ") .. close end
if obj._unpacked_to_str then --字串產製完畢,復位成原始型態
obj.__type = obj.__type_orig
obj.__type_orig = nil
obj._unpacked_to_str = nil
end
elseif obj.__type == "set" or obj.__type == "frozenset" then
if not obj.elements or #obj.elements == 0 then result_str = obj.__type .. "()"
else
local res = {}
for _, v in ipairs(obj.elements) do table.insert(res, to_str(v, true, seen)) end
local inner = "{" .. table.concat(res, ", ") .. "}"
result_str = (obj.__type == "frozenset") and ("frozenset(" .. inner .. ")") or inner
end
elseif obj.__type == "dict" then
local res = {}
for _, p in ipairs(obj.pairs or {}) do table.insert(res, to_str(p.key, true, seen) .. ": " .. to_str(p.value, true, seen)) end
result_str = "{" .. table.concat(res, ", ") .. "}"
elseif obj.__type == "instance" then
local magic = in_repr and "__repr__" or "__str__"
local success, attr = safe_pcall(PyClass.get_attr, obj, magic)
if not success and not in_repr then success, attr = safe_pcall(PyClass.get_attr, obj, "__repr__") end
if success and attr and attr.__type == "bound_method" then
local Evaluator = require("Module:PythonScript/Evaluator")
local ok_inv, inv_res = safe_pcall(Evaluator.invoke, attr.__func, {}, {}, attr.__self)
seen[obj] = nil
if ok_inv then return inv_res else error(inv_res, 0) end
end
if obj.__builtin_value ~= nil then
seen[obj] = nil
return to_str(obj.__builtin_value, in_repr, seen)
end
if obj.__class and obj.__class.__name == "module" then
local m_name = (obj.__dict and obj.__dict["__name__"]) or (obj.__dict and obj.__dict._name)
local m_file = obj.__dict and obj.__dict["__file__"]
local is_ext = obj.__dict and obj.__dict._is_external
if m_file then result_str = "<module '" .. m_name .. "' from '" .. m_file .. "'>"
elseif is_ext then result_str = "<module '" .. m_name .. "'>"
else result_str = "<module '" .. (m_name or "unknown") .. "' (built-in)>" end
else
result_str = "<" .. ((obj.__class and obj.__class.__name) or "object") .. " object>"
end
elseif obj.__type == "bound_method" then
local fname = (obj.__func and obj.__func.__name) or "unknown"
local class_name = (obj.__self and obj.__self.__class and obj.__self.__class.__name) or (obj.__self and obj.__self.__name) or "?"
result_str = "<bound method " .. class_name .. "." .. fname .. " of " .. to_str(obj.__self, true, seen) .. ">"
else
result_str = "<object>"
end
seen[obj] = nil
return result_str
end
return tostring(obj)
end
Utils.to_str = to_str
-- ==========================================
-- 1. 內部通用相等與大小比較器
-- ==========================================
local function runtime_is_equal(a, b, seen)
if a == b then return true end
if type(a) == "boolean" and type(b) == "number" then return (a and 1 or 0) == b end
if type(a) == "number" and type(b) == "boolean" then return a == (b and 1 or 0) end
if (type(a) == "table" and a.__type == "instance" and a.__builtin_value ~= nil) or (type(b) == "table" and b.__type == "instance" and b.__builtin_value ~= nil) then
local ua = (type(a) == "table" and a.__type == "instance" and a.__builtin_value ~= nil) and a.__builtin_value or a
local ub = (type(b) == "table" and b.__type == "instance" and b.__builtin_value ~= nil) and b.__builtin_value or b
return runtime_is_equal(ua, ub, seen)
end
if type(a) ~= type(b) then
local is_big_a = type(a) == "table" and a.__type == "int"
local is_big_b = type(b) == "table" and b.__type == "int"
if is_big_a or is_big_b then
local na = type(a) == "boolean" and (a and 1 or 0) or a
local nb = type(b) == "boolean" and (b and 1 or 0) or b
if (type(na) == "number" or is_big_a) and (type(nb) == "number" or is_big_b) then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
local sa = is_big_a and na.sign or (na < 0 and -1 or 1)
local sb = is_big_b and nb.sign or (nb < 0 and -1 or 1)
if na == 0 then sa = 1 end; if nb == 0 then sb = 1 end
return sa == sb and MathCore.BigInt.cmp_limbs(MathCore.BigInt.to_limbs(na), MathCore.BigInt.to_limbs(nb)) == 0
end
end
if (type(a) == "table" and a.__type == "complex") or (type(b) == "table" and b.__type == "complex") then
return require("Module:PythonScript/Runtime/Cmath").Cmath.eq(a, b)
end
return false
end
if type(a) == "table" then
local is_b1 = a.__type == "bytes" or a.__type == "bytearray" or a.__type == "memoryview"
local is_b2 = b.__type == "bytes" or b.__type == "bytearray" or b.__type == "memoryview"
if is_b1 or is_b2 then return (is_b1 and is_b2) and (a._data == b._data) or false end
if a.__type == "int" and b.__type == "int" then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
return a.sign == b.sign and MathCore.BigInt.cmp_limbs(a.limbs, b.limbs) == 0
end
if a.__type == "complex" and b.__type == "complex" then
return require("Module:PythonScript/Runtime/Cmath").Cmath.eq(a, b)
end
seen = seen or {}
if seen[a] and seen[a][b] then return true end
seen[a] = seen[a] or {}; seen[a][b] = true
if (a.__type == "list" and b.__type == "list") or (a.__type == "tuple" and b.__type == "tuple") then
if #a.elements ~= #b.elements then return false end
for i = 1, #a.elements do if not runtime_is_equal(a.elements[i], b.elements[i], seen) then return false end end
return true
end
local is_s1 = a.__type == "set" or a.__type == "frozenset"
local is_s2 = b.__type == "set" or b.__type == "frozenset"
if is_s1 and is_s2 then
if #a.elements ~= #b.elements then return false end
for _, ea in ipairs(a.elements) do
local found = false
for _, eb in ipairs(b.elements) do if runtime_is_equal(ea, eb, seen) then found = true; break end end
if not found then return false end
end
return true
end
if a.__type == "dict" and b.__type == "dict" then
if #a.pairs ~= #b.pairs then return false end
for _, pa in ipairs(a.pairs) do
local found = false
for _, pb in ipairs(b.pairs) do
if runtime_is_equal(pa.key, pb.key, seen) and runtime_is_equal(pa.value, pb.value, seen) then found = true; break end
end
if not found then return false end
end
return true
end
end
return a == b
end
Utils.runtime_is_equal = runtime_is_equal
local function runtime_is_less(a, b)
if type(a) == "boolean" then a = a and 1 or 0 end
if type(b) == "boolean" then b = b and 1 or 0 end
if type(a) == "number" and type(b) == "number" then return a < b end
if type(a) == "string" and type(b) == "string" then return a < b end
if type(a) == "table" and type(b) == "table" then
local is_b1 = a.__type == "bytes" or a.__type == "bytearray"
local is_b2 = b.__type == "bytes" or b.__type == "bytearray"
if is_b1 and is_b2 then return a._data < b._data end
if a.__type == b.__type and (a.__type == "list" or a.__type == "tuple") then
for i = 1, math.min(#a.elements, #b.elements) do
if not runtime_is_equal(a.elements[i], b.elements[i]) then
return runtime_is_less(a.elements[i], b.elements[i])
end
end
return #a.elements < #b.elements
end
if a.__type == "instance" then
local ok, lt_method = safe_pcall(PyClass.get_attr, a, "__lt__")
if ok and lt_method then
local Evaluator = require("Module:PythonScript/Evaluator")
return to_bool(Evaluator.invoke(lt_method, {b}, {}))
end
end
end
local is_big_a = type(a) == "table" and a.__type == "int"
local is_big_b = type(b) == "table" and b.__type == "int"
if is_big_a or is_big_b then
if (type(a) == "number" or is_big_a) and (type(b) == "number" or is_big_b) then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
local sa = is_big_a and a.sign or (a < 0 and -1 or 1)
local sb = is_big_b and b.sign or (b < 0 and -1 or 1)
if a == 0 then sa = 1 end; if b == 0 then sb = 1 end
if sa ~= sb then return sa < sb end
local c = MathCore.BigInt.cmp_limbs(MathCore.BigInt.to_limbs(a), MathCore.BigInt.to_limbs(b))
return (sa == 1) and (c < 0) or (c > 0)
end
end
Runtime.raise("TypeError", "'<' not supported between instances of '" .. get_type_str(a) .. "' and '" .. get_type_str(b) .. "'")
end
Utils.runtime_is_less = runtime_is_less
-- ==========================================
-- 2. 內部迭代器與類別包裝工廠
-- ==========================================
local function get_iterator(iterable)
if type(iterable) == "table" then
if iterable.__type == "bytes" or iterable.__type == "bytearray" or iterable.__type == "memoryview" then
if iterable.__type == "memoryview" and iterable._released then
Runtime.raise("ValueError", "operation forbidden on released memoryview object")
end
local idx, data = 0, iterable._data
return function() idx = idx + 1; if idx <= #data then return true, string.byte(data, idx) else return false, nil end end
elseif iterable.__type == "list" or iterable.__type == "tuple" or iterable.__type == "set" or iterable.__type == "frozenset" then
local idx, elts = 0, iterable.elements
return function() idx = idx + 1; if idx <= #elts then return true, elts[idx] else return false, nil end end
elseif iterable.__type == "dict" then
local idx, pairs_list = 0, iterable.pairs
return function() idx = idx + 1; if idx <= #pairs_list then return true, pairs_list[idx].key else return false, nil end end
elseif iterable.__type == "range" then
local curr, stop, step = iterable.start, iterable.stop, iterable.step
return function()
if (step > 0 and curr < stop) or (step < 0 and curr > stop) then
local ret = curr; curr = curr + step; return true, ret
end
return false, nil
end
end
elseif type(iterable) == "string" then
local idx, len = 0, mw.ustring.len(iterable)
return function()
idx = idx + 1
if idx <= len then return true, mw.ustring.sub(iterable, idx, idx) else return false, nil end
end
end
local B = Runtime.builtins
local iter_obj = B.iter({iterable}); local sentinel = {}
return function()
local val = B.next({iter_obj, sentinel})
if val == sentinel then return false, nil end
return true, val
end
end
Utils.get_iterator = get_iterator
function Utils.wrap_as_class(name, func)
local object_class = Runtime.builtins.object
local is_mutable_container = (name == "dict" or name == "list" or name == "set" or name == "bytearray")
local cls
local init_method = is_mutable_container and {
__type = "builtin_method",
__name = "__init__",
__call = function(args, kwargs)
local self = args and args[1]
if type(self) == "table" and self.__type == "instance" then
local rest_args = {}
for i = 2, #(args or {}) do table.insert(rest_args, args[i]) end
if #rest_args > 0 or (kwargs and next(kwargs) ~= nil) or self.__builtin_value == nil then
self.__builtin_value = func(rest_args, kwargs)
end
end
return nil
end
} or nil
local new_method = {
__type = "builtin_method",
__name = "__new__",
__call = function(args, kwargs)
local target_cls = args and args[1]
if type(target_cls) ~= "table" or target_cls.__type ~= "class" then
Runtime.raise("TypeError", name .. ".__new__(X): X is not a type object")
end
local rest_args = {}
for i = 2, #(args or {}) do table.insert(rest_args, args[i]) end
if target_cls == cls then return func(rest_args, kwargs) end
local inst = PyClass.make_instance(target_cls)
if is_mutable_container then
local c_init = PyClass.lookup_class_attr(inst, "__init__")
if c_init and c_init ~= init_method and c_init ~= (object_class and object_class.__dict.__init__) then
inst.__builtin_value = func({}, {})
return inst
end
end
inst.__builtin_value = func(rest_args, kwargs)
return inst
end
}
cls = {
__type = "class", __name = name, __bases = { object_class }, __call = func,
__dict = {
__new__ = { __type = "staticmethod", __func = new_method },
__init__ = init_method
}
}
cls.__mro = { cls, object_class }
return setmetatable(cls, { __call = function(self, args, kwargs) return self.__call(args, kwargs) end })
end
-- ==========================================
-- 3. 二進位、複數與進位轉換輔助函式
-- ==========================================
function Utils.construct_bytes_data(args, kwargs, func_name)
local val = args and args[1]
if val == nil then return "" end
if type(val) == "string" then
return val
elseif type(val) == "number" or (type(val) == "table" and val.__type == "int") then
local n = unwrap_num(val)
if type(n) ~= "number" or math.floor(n) ~= n then
Runtime.raise("TypeError", "'" .. get_type_str(val) .. "' object cannot be interpreted as an integer")
end
if n < 0 then Runtime.raise("ValueError", "negative count") end
if n > 1000000 then Runtime.raise("ValueError", "byte count exceeds maximum limit") end
return string.rep("\0", n)
elseif type(val) == "table" then
if val.__type == "bytes" or val.__type == "bytearray" or val.__type == "memoryview" then
if val.__type == "memoryview" and val._released then
Runtime.raise("ValueError", "operation forbidden on released memoryview object")
end
return val._data
end
if val.__type == "instance" then
local ok, bytes_m = safe_pcall(PyClass.get_attr, val, "__bytes__")
if ok and bytes_m and bytes_m.__type == "bound_method" then
local Evaluator = require("Module:PythonScript/Evaluator")
local res = Evaluator.invoke(bytes_m.__func, {}, {}, bytes_m.__self)
if type(res) == "table" and res.__type == "bytes" then
return res._data
end
Runtime.raise("TypeError", "__bytes__ returned non-bytes")
end
end
local iter_fn = get_iterator(val)
local chars = {}
while true do
local has_next, item = iter_fn()
if not has_next then break end
local b = unwrap_num(item)
if type(b) ~= "number" or math.floor(b) ~= b then
Runtime.raise("TypeError", "'" .. get_type_str(item) .. "' object cannot be interpreted as an integer")
end
if b < 0 or b > 255 then
Runtime.raise("ValueError", func_name .. " must be in range(0, 256)")
end
table.insert(chars, string.char(b))
end
return table.concat(chars)
end
Runtime.raise("TypeError", "cannot convert '" .. get_type_str(val) .. "' object to " .. func_name)
end
function Utils.make_bytes_fromhex(target_type)
return {
__type = "staticmethod",
__func = {
__type = "builtin_method",
__call = function(args)
local s = args[1]
if type(s) == "table" and (s.__type == "class" or s.__type == "bytes" or s.__type == "bytearray") then s = args[2] end
if type(s) ~= "string" then Runtime.raise("TypeError", "fromhex() argument must be str") end
local clean = s:gsub("%s+", "")
if #clean % 2 ~= 0 or clean:match("[^0-9a-fA-F]") then
Runtime.raise("ValueError", "non-hexadecimal number found in fromhex() arg")
end
local data = (clean:gsub("(%x%x)", function(h) return string.char(tonumber(h, 16)) end))
return { __type = target_type, _data = data }
end
}
}
end
local function parse_float_token(ns)
if not ns or ns == "" then return nil end
if ns:sub(1, 1) == "_" or ns:sub(-1) == "_" or ns:find("__") then return nil end
local clean = ns
while clean:find("(%d)_(%d)") do clean = clean:gsub("(%d)_(%d)", "%1%2") end
if clean:find("_") then return nil end
if clean == "inf" or clean == "+inf" or clean == "infinity" or clean == "+infinity" then return math.huge
elseif clean == "-inf" or clean == "-infinity" then return -math.huge
elseif clean == "nan" or clean == "+nan" or clean == "-nan" then return 0/0 end
return tonumber(clean)
end
function Utils.construct_complex(args, kwargs)
local Cmath = require("Module:PythonScript/Runtime/Cmath").Cmath
local v1 = (args and args[1] ~= nil) and args[1] or (kwargs and kwargs.real)
local v2 = (args and args[2] ~= nil) and args[2] or (kwargs and kwargs.imag)
if v1 == nil and v2 == nil then return Cmath.make_complex(0, 0) end
if type(v1) == "string" then
if v2 ~= nil or (kwargs and next(kwargs)) then
Runtime.raise("TypeError", "complex() can't take second arg if first is a string")
end
local s = v1:match("^%s*(.-)%s*$"):lower()
if s:sub(1, 1) == "(" and s:sub(-1) == ")" then s = s:sub(2, -2):match("^%s*(.-)%s*$") end
if s == "" or s:find("%s") then Runtime.raise("ValueError", "complex() arg is a malformed string") end
local real, imag = 0, 0
if s:sub(-1) == "j" then
s = s:sub(1, -2)
if s == "" or s == "+" then return Cmath.make_complex(0, 1) end
if s == "-" then return Cmath.make_complex(0, -1) end
local split_idx = nil
for i = #s, 1, -1 do
local c = s:sub(i, i)
if c == "+" or c == "-" then
if i == 1 then break end
if s:sub(i-1, i-1) ~= "e" then split_idx = i; break end
end
end
if split_idx then
local r_str, i_str = s:sub(1, split_idx - 1), s:sub(split_idx)
if i_str == "+" then i_str = "1" elseif i_str == "-" then i_str = "-1" end
real, imag = parse_float_token(r_str), parse_float_token(i_str)
if not real or not imag then Runtime.raise("ValueError", "complex() arg is a malformed string") end
else
imag = parse_float_token(s)
if not imag then Runtime.raise("ValueError", "complex() arg is a malformed string") end
end
else
real = parse_float_token(s)
if not real then Runtime.raise("ValueError", "complex() arg is a malformed string") end
end
return Cmath.make_complex(real, imag)
end
local function get_r_i(v)
if type(v) == "table" and v.__type == "instance" and v.__builtin_value ~= nil then v = v.__builtin_value end
if type(v) == "number" then return v, 0 end
if type(v) == "boolean" then return v and 1 or 0, 0 end
if type(v) == "table" and v.__type == "int" then return unwrap_num(v), 0 end
if type(v) == "table" and v.__type == "complex" then
if (v.k and v.k ~= 0) or (v.jk and v.jk ~= 0) then
Runtime.raise("TypeError", "can't convert quaternion to complex")
end
return v.real or 0, v.imag or 0
end
if v == nil then return 0, 0 end
Runtime.raise("TypeError", "complex() argument must be a string or a number")
end
local r1, i1 = get_r_i(v1); local r2, i2 = get_r_i(v2)
return Cmath.make_complex(r1 - i2, i1 + r2)
end
function Utils.construct_quaternion(args, kwargs)
local Cmath = require("Module:PythonScript/Runtime/Cmath").Cmath
local n_args = args and #args or 0
if n_args > 4 then
Runtime.raise("TypeError", "Quaternion() takes at most 4 arguments (" .. n_args .. " given)")
end
local v1 = (args and args[1] ~= nil) and args[1] or (kwargs and (kwargs.real or kwargs.r))
local v2 = (args and args[2] ~= nil) and args[2] or (kwargs and (kwargs.imag or kwargs.j))
local v3 = (args and args[3] ~= nil) and args[3] or (kwargs and kwargs.k)
local v4 = (args and args[4] ~= nil) and args[4] or (kwargs and kwargs.jk)
if v1 == nil and v2 == nil and v3 == nil and v4 == nil then
return Cmath.make_complex(0, 0, 0, 0)
end
-- 1. 字串解析模式:支援如 "3+4j+5k"、"(1 - 2j + 3.5k - 4jk)"、"-5k" 等格式
if type(v1) == "string" then
if n_args > 1 or (kwargs and next(kwargs)) then
Runtime.raise("TypeError", "Quaternion() can't take second arg if first is a string")
end
local s = v1:match("^%s*(.-)%s*$"):lower()
if s:sub(1, 1) == "(" and s:sub(-1) == ")" then
s = s:sub(2, -2):match("^%s*(.-)%s*$")
end
if s == "" then Runtime.raise("ValueError", "Quaternion() arg is a malformed string") end
s = s:gsub("%s*([%+%-])%s*", "%1")
if s:find("%s") then Runtime.raise("ValueError", "Quaternion() arg is a malformed string") end
local terms = {}
local start_i = 1
for i = 2, #s do
local c = s:sub(i, i)
if (c == "+" or c == "-") and s:sub(i - 1, i - 1) ~= "e" then
table.insert(terms, s:sub(start_i, i - 1))
start_i = i
end
end
table.insert(terms, s:sub(start_i))
local comps = { real = 0, j = 0, k = 0, jk = 0 }
local seen = {}
for _, t in ipairs(terms) do
if t == "" then Runtime.raise("ValueError", "Quaternion() arg is a malformed string") end
local unit, num_str
if t:sub(-2) == "jk" then
unit = "jk"; num_str = t:sub(1, -3)
elseif t:sub(-1) == "j" then
unit = "j"; num_str = t:sub(1, -2)
elseif t:sub(-1) == "k" then
unit = "k"; num_str = t:sub(1, -2)
else
unit = "real"; num_str = t
end
if seen[unit] then
Runtime.raise("ValueError", "Quaternion() arg is a malformed string")
end
seen[unit] = true
local val
if unit ~= "real" and (num_str == "" or num_str == "+") then
val = 1.0
elseif unit ~= "real" and num_str == "-" then
val = -1.0
else
val = parse_float_token(num_str)
end
if not val then
Runtime.raise("ValueError", "Quaternion() arg is a malformed string")
end
comps[unit] = val
end
return Cmath.make_complex(comps.real, comps.j, comps.k, comps.jk)
end
-- 2. 支援 Quaternion(r, q.imag) 或 Quaternion(r, (b, c, d)) 自動展開三維虛部向量
if n_args == 2 and v3 == nil and v4 == nil and type(v2) == "table" then
if v2._is_unpacked_tuple and v2._elements then
v2, v3, v4 = v2._elements[1], v2._elements[2], v2._elements[3]
elseif (v2.__type == "tuple" or v2.__type == "list") and v2.elements and #v2.elements <= 3 then
v2, v3, v4 = v2.elements[1], v2.elements[2], v2.elements[3]
end
end
-- 3. 數值與超複數分量合成:v1 + v2*j + v3*k + v4*jk
local function to_q_obj(v)
if v == nil then return Cmath.make_complex(0, 0, 0, 0) end
if type(v) == "table" and v.__type == "instance" and v.__builtin_value ~= nil then v = v.__builtin_value end
if type(v) == "number" then return Cmath.make_complex(v, 0, 0, 0) end
if type(v) == "boolean" then return Cmath.make_complex(v and 1 or 0, 0, 0, 0) end
if type(v) == "table" and v.__type == "int" then return Cmath.make_complex(unwrap_num(v), 0, 0, 0) end
if type(v) == "table" and v.__type == "complex" then return v end
Runtime.raise("TypeError", "Quaternion() argument must be a string or a number")
end
local q1 = to_q_obj(v1)
local q2 = Cmath.mul(to_q_obj(v2), Cmath.make_complex(0, 1, 0, 0))
local q3 = Cmath.mul(to_q_obj(v3), Cmath.make_complex(0, 0, 1, 0))
local q4 = Cmath.mul(to_q_obj(v4), Cmath.make_complex(0, 0, 0, 1))
return Cmath.add(Cmath.add(q1, q2), Cmath.add(q3, q4))
end
function Utils.int_to_base_str(val, base, prefix)
local digits = "0123456789abcdef"
if type(val) == "table" and val.__type == "instance" then
local ok, idx_m = safe_pcall(PyClass.get_attr, val, "__index__")
if ok and idx_m and idx_m.__type == "bound_method" then
local Evaluator = require("Module:PythonScript/Evaluator")
val = Evaluator.invoke(idx_m.__func, {}, {}, idx_m.__self)
elseif val.__builtin_value ~= nil then
val = val.__builtin_value
end
end
if type(val) == "boolean" then val = val and 1 or 0 end
if type(val) == "number" and math.floor(val) == val and math.abs(val) <= 2147483647 then
if val == 0 then return prefix .. "0" end
local is_neg = val < 0; local n = math.abs(val); local t = {}
while n > 0 do
local r = n % base
table.insert(t, 1, digits:sub(r + 1, r + 1))
n = math.floor(n / base)
end
return (is_neg and "-" or "") .. prefix .. table.concat(t)
elseif (type(val) == "number" and math.floor(val) == val) or (type(val) == "table" and val.__type == "int") then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
local BigInt = MathCore.BigInt
local sign = (type(val) == "table" and val.sign) or (val < 0 and -1 or 1)
local limbs = BigInt.to_limbs(val)
if #limbs == 1 and limbs[1] == 0 then return prefix .. "0" end
local base_limbs, t = { base }, {}
while not (#limbs == 1 and limbs[1] == 0) do
local q, r_limbs = BigInt.divmod_limbs(limbs, base_limbs)
local r = r_limbs[1] or 0
table.insert(t, 1, digits:sub(r + 1, r + 1))
limbs = q
end
return (sign < 0 and "-" or "") .. prefix .. table.concat(t)
end
Runtime.raise("TypeError", "'" .. get_type_str(val) .. "' object cannot be interpreted as an integer")
end
-- ==========================================
-- 4. Format Mini-Language 排版引擎
-- ==========================================
local function apply_grouping(digits_str, sep, group_size)
if not sep or sep == "" or #digits_str <= group_size then return digits_str end
local parts, len = {}, #digits_str
local i = len
while i > 0 do
local start_i = math.max(1, i - group_size + 1)
table.insert(parts, 1, digits_str:sub(start_i, i))
i = start_i - 1
end
return table.concat(parts, sep)
end
local function pad_formatted_string(prefix, body, width, align, fill)
local total_len = mw.ustring.len(prefix) + mw.ustring.len(body)
if not width or width <= total_len then return prefix .. body end
local pad_count = width - total_len
local pad_str = mw.ustring.rep(fill, pad_count)
if align == "<" then return prefix .. body .. pad_str
elseif align == "=" then return prefix .. pad_str .. body
elseif align == "^" then
local left_p = math.floor(pad_count / 2)
local right_p = pad_count - left_p
return mw.ustring.rep(fill, left_p) .. prefix .. body .. mw.ustring.rep(fill, right_p)
else return pad_str .. prefix .. body end
end
function Utils.format_value(args)
local B = Runtime.builtins
local object_class = B.object
local val = args and args[1]
local spec = (args and args[2] ~= nil) and args[2] or ""
if type(spec) == "table" and spec.__type == "instance" and spec.__builtin_value ~= nil then
spec = spec.__builtin_value
end
if type(spec) ~= "string" then
Runtime.raise("TypeError", "format() argument 2 must be str, not " .. get_type_str(spec))
end
if type(val) == "table" and val.__type == "instance" then
local custom_fmt = nil
for _, base_cls in ipairs(val.__class.__mro or {}) do
local fn = base_cls.__dict and base_cls.__dict["__format__"]
if fn ~= nil then
if base_cls ~= object_class and base_cls.__name ~= "object" then custom_fmt = fn end
break
end
end
if custom_fmt then
local Evaluator = require("Module:PythonScript/Evaluator")
local res = Evaluator.invoke(custom_fmt, {val, spec}, {})
if type(res) == "table" and res.__type == "instance" and res.__builtin_value ~= nil then
res = res.__builtin_value
end
if type(res) ~= "string" then
Runtime.raise("TypeError", "__format__ must return a str, not " .. get_type_str(res))
end
return res
elseif val.__builtin_value ~= nil then
if spec == "" then return B.str.__call({val}) end
val = val.__builtin_value
else
return object_class.__dict.__format__.__call({val, spec})
end
end
if spec == "" then return to_str(val, false) end
local fill, align, rem = nil, nil, spec
local ulen = mw.ustring.len(spec)
if ulen >= 2 and mw.ustring.sub(spec, 2, 2):match("^[<>^=]$") then
fill = mw.ustring.sub(spec, 1, 1); align = mw.ustring.sub(spec, 2, 2); rem = mw.ustring.sub(spec, 3)
elseif ulen >= 1 and mw.ustring.sub(spec, 1, 1):match("^[<>^=]$") then
fill = " "; align = mw.ustring.sub(spec, 1, 1); rem = mw.ustring.sub(spec, 2)
end
local sign, alt, zero, width_s, grp, dot_prec, ftype = rem:match("^([%+%- ]?)(#?)(0?)(%d*)([,_]?)(%.?%d*)([sbdoxXceEfFgG%%]?)$")
if not sign or dot_prec == "." then
Runtime.raise("ValueError", "Invalid format specifier '" .. spec .. "' for object of type '" .. get_type_str(val) .. "'")
end
local width = (width_s ~= "") and tonumber(width_s) or nil
local prec = (dot_prec ~= "") and tonumber(dot_prec:sub(2)) or nil
if type(val) == "string" or (type(val) == "boolean" and ftype == "s") then
if type(val) == "boolean" then Runtime.raise("ValueError", "Unknown format code 's' for object of type 'bool'") end
if ftype ~= "" and ftype ~= "s" then Runtime.raise("ValueError", "Unknown format code '" .. ftype .. "' for object of type 'str'") end
if sign ~= "" or alt ~= "" or zero ~= "" or grp ~= "" or align == "=" then
Runtime.raise("ValueError", "Invalid format specifier '" .. spec .. "' for object of type 'str'")
end
local s_body = prec and mw.ustring.sub(val, 1, prec) or val
return pad_formatted_string("", s_body, width, align or "<", fill or " ")
end
if type(val) == "boolean" then val = val and 1 or 0 end
local is_big = (type(val) == "table" and val.__type == "int")
local is_num = (type(val) == "number") or is_big
if not is_num then return object_class.__dict.__format__.__call({val, spec}) end
if ftype == "s" then Runtime.raise("ValueError", "Unknown format code 's' for object of type '" .. get_type_str(val) .. "'") end
if zero == "0" then
fill = fill or "0"; align = align or "="
else
fill = fill or " "; align = align or ">"
end
local is_int_val = is_big or (math.floor(val) == val and math.abs(val) <= SAFE_LIMIT)
local int_codes = { d = true, b = true, o = true, x = true, X = true, c = true }
if int_codes[ftype] or (ftype == "" and prec == nil and is_int_val) then
if not is_int_val then Runtime.raise("ValueError", "Unknown format code '" .. ftype .. "' for object of type 'float'") end
if prec ~= nil then Runtime.raise("ValueError", "Precision not allowed in integer format specifier") end
if ftype == "c" then
if sign ~= "" or alt ~= "" or grp ~= "" then Runtime.raise("ValueError", "Invalid format specifier for 'c'") end
return pad_formatted_string("", B.chr({val}), width, align, fill)
end
local is_neg = is_big and (val.sign < 0) or (not is_big and val < 0)
local digits_str, alt_prefix = "", ""
if ftype == "b" then
digits_str = B.bin({val}):gsub("^%-?0b", ""); if alt == "#" then alt_prefix = "0b" end
elseif ftype == "o" then
digits_str = B.oct({val}):gsub("^%-?0o", ""); if alt == "#" then alt_prefix = "0o" end
elseif ftype == "x" or ftype == "X" then
digits_str = B.hex({val}):gsub("^%-?0x", "")
if ftype == "X" then digits_str = digits_str:upper() end
if alt == "#" then alt_prefix = "0" .. ftype end
else
if is_big then
digits_str = require("Module:PythonScript/Runtime/Cmath").BigInt.to_string(1, val.limbs)
else
digits_str = string.format("%.0f", math.abs(val))
end
end
local g_size = (ftype == "b" or ftype == "o" or ftype == "x" or ftype == "X") and 4 or 3
digits_str = apply_grouping(digits_str, grp, g_size)
local sign_str = is_neg and "-" or (sign == "+" and "+" or (sign == " " and " " or ""))
return pad_formatted_string(sign_str .. alt_prefix, digits_str, width, align, fill)
else
local num = unwrap_num(val)
local is_neg = (num < 0)
local abs_num = math.abs(num)
local suffix, eff_type = "", ftype
if ftype == "%" then abs_num = abs_num * 100; eff_type = "f"; suffix = "%"
elseif ftype == "" then eff_type = prec and "g" or "f"
elseif ftype == "F" then eff_type = "f" end
local fmt_p = dot_prec ~= "" and dot_prec or (ftype == "%" and ".6" or (ftype == "" and "" or ".6"))
local body_str
if ftype == "" and dot_prec == "" then body_str = to_str(abs_num, false)
else
local ok_f, res_f = pcall(string.format, "%" .. fmt_p .. eff_type, abs_num)
body_str = ok_f and res_f or tostring(abs_num)
end
if grp ~= "" then
local int_part, rest_part = body_str:match("^(%d+)(.*)$")
if int_part then body_str = apply_grouping(int_part, grp, 3) .. rest_part end
end
local sign_str = is_neg and "-" or (sign == "+" and "+" or (sign == " " and " " or ""))
return pad_formatted_string(sign_str, body_str .. suffix, width, align, fill)
end
end
-- ==========================================
-- 5. 內建高階函式實作 (min/max/sum/reversed/dir/hash/compile/eval/exec/map/filter/iter/next)
-- ==========================================
function Utils.min_or_max(args, kwargs, is_min)
local B = Runtime.builtins
local key_func = kwargs and kwargs.key
local has_default = kwargs and kwargs.default ~= nil
local default_val = kwargs and kwargs.default
local best_val, best_key, has_item = nil, nil, false
local iter_fn
if #args == 1 then iter_fn = get_iterator(args[1]) else
local idx = 0
iter_fn = function() idx = idx + 1; if idx <= #args then return true, args[idx] else return false, nil end end
end
local Evaluator = key_func and require("Module:PythonScript/Evaluator") or nil
while true do
local has_next, v = iter_fn(); if not has_next then break end
local k = (key_func and key_func ~= B.None) and Evaluator.invoke(key_func, {v}, {}) or v
if not has_item or (is_min and runtime_is_less(k, best_key) or (not is_min and runtime_is_less(best_key, k))) then
best_val = v; best_key = k; has_item = true
end
end
if not has_item then
if has_default then return default_val end
Runtime.raise("ValueError", (is_min and "min" or "max") .. "() arg is an empty sequence")
end
return best_val
end
function Utils.sum_impl(args, kwargs)
local total = (args[2] ~= nil and args[2]) or (kwargs and kwargs.start) or 0
if type(total) == "string" then
Runtime.raise("TypeError", "sum() can't sum strings [use ''.join(seq) instead]")
end
local iter_fn = get_iterator(args[1])
local Evaluator = require("Module:PythonScript/Evaluator")
while true do
local has_next, v = iter_fn(); if not has_next then break end
if type(total) == "number" and type(v) == "number" then
local s = total + v
if math.abs(s) > SAFE_LIMIT and math.floor(total) == total and math.floor(v) == v then
total = Evaluator.bigint_op(total, v, "+")
else total = s end
elseif (type(total) == "table" and total.__type == "int") or (type(v) == "table" and v.__type == "int") then
total = Evaluator.bigint_op(total, v, "+")
elseif type(total) == "table" and total.__type == "list" and type(v) == "table" and v.__type == "list" then
local combined = {}
for _, e in ipairs(total.elements) do table.insert(combined, e) end
for _, e in ipairs(v.elements) do table.insert(combined, e) end
total = { __type = "list", elements = combined }
else
local res = nil
-- 嘗試呼叫左側物件的 __add__
if type(total) == "table" and total.__type == "instance" then
local ok, attr = pcall(PyClass.get_attr, total, "__add__")
if ok and attr and attr.__type == "bound_method" then
res = Evaluator.invoke(attr.__func, {v}, {}, attr.__self)
if res == Runtime.builtins.NotImplemented then res = nil end
end
end
-- 若左側沒有 __add__ 或是回傳 NotImplemented,嘗試呼叫右側的 __radd__
if res == nil and type(v) == "table" and v.__type == "instance" then
local ok, attr = pcall(PyClass.get_attr, v, "__radd__")
if ok and attr and attr.__type == "bound_method" then
res = Evaluator.invoke(attr.__func, {total}, {}, attr.__self)
if res == Runtime.builtins.NotImplemented then res = nil end
end
end
if res ~= nil then
total = res
else
-- 若雙方都不是 Python 實例物件,才退回原本的轉型與 Lua 加法
local u_tot, u_v = unwrap_num(total), unwrap_num(v)
if type(u_tot) ~= "number" or type(u_v) ~= "number" then
Runtime.raise("TypeError", "unsupported operand type(s) for + in sum()")
end
total = u_tot + u_v
end
end
end
return total
end
function Utils.reversed_impl(args)
local B = Runtime.builtins
local obj = args[1]; local elements = {}
if type(obj) == "table" and obj.__type == "instance" then
local success, attr = safe_pcall(PyClass.get_attr, obj, "__reversed__")
if success and attr and attr.__type == "bound_method" then
return require("Module:PythonScript/Evaluator").invoke(attr.__func, {}, {}, attr.__self)
end
end
if type(obj) == "table" and (obj.__type == "list" or obj.__type == "tuple") then
for i = #obj.elements, 1, -1 do table.insert(elements, obj.elements[i]) end
elseif type(obj) == "table" and obj.__type == "range" then
local len = B.len({obj})
for i = len - 1, 0, -1 do table.insert(elements, obj.start + i * obj.step) end
elseif type(obj) == "string" then
for i = mw.ustring.len(obj), 1, -1 do table.insert(elements, mw.ustring.sub(obj, i, i)) end
elseif type(obj) == "table" and (obj.__type == "bytes" or obj.__type == "bytearray" or obj.__type == "memoryview") then
for i = #obj._data, 1, -1 do table.insert(elements, string.byte(obj._data, i)) end
else Runtime.raise("TypeError", "'" .. get_type_str(obj) .. "' object is not reversible") end
return B.iter({{ __type = "list", elements = elements }})
end
function Utils.dir_impl(args)
local obj = args and args[1]
if not obj then
local scope = Runtime.Traceback.current_scope
local keys = {}
if scope then for k, _ in pairs(scope.vars) do table.insert(keys, k) end end
table.sort(keys)
return { __type = "list", elements = keys }
end
local keys, seen = {}, {}
local function add_key(k) if type(k) == "string" and not seen[k] then table.insert(keys, k); seen[k] = true end end
if type(obj) == "table" then
if obj.__type == "instance" or obj.__type == "class" or obj.__type == "module" then
if obj.__class and obj.__class.__name == "LazyNamespace" and type(obj.__dict._raw_lua_tbl) == "table" then
for k, _ in pairs(obj.__dict._raw_lua_tbl) do add_key(tostring(k)) end
end
if obj.__dict and type(obj.__dict["__all__"]) == "table" and obj.__dict["__all__"].elements then
for _, k in ipairs(obj.__dict["__all__"].elements) do add_key(tostring(k)) end
end
for k, _ in pairs(obj.__dict or {}) do add_key(k) end
local mro = obj.__mro or (obj.__class and obj.__class.__mro) or {}
for _, cls in ipairs(mro) do for k, _ in pairs(cls.__dict or {}) do add_key(k) end end
elseif obj.__type == "dict" then
for _, m in ipairs({"clear", "copy", "get", "items", "keys", "pop", "update", "values", "__iter__"}) do add_key(m) end
elseif obj.__type == "list" or obj.__type == "tuple" then
for _, m in ipairs({"append", "clear", "copy", "count", "extend", "index", "insert", "pop", "remove", "reverse", "sort", "__iter__", "shape"}) do add_key(m) end
elseif obj.__type == "set" or obj.__type == "frozenset" then
for _, m in ipairs({"add", "clear", "difference", "discard", "intersection", "remove", "union", "__iter__"}) do add_key(m) end
end
elseif type(obj) == "string" then
for _, m in ipairs({"capitalize", "count", "encode", "endswith", "find", "format", "index", "isalnum", "isalpha", "isdigit", "join", "lower", "lstrip", "replace", "rstrip", "split", "startswith", "strip", "title", "upper", "__iter__", "__mod__"}) do add_key(m) end
end
table.sort(keys); return { __type = "list", elements = keys }
end
function Utils.hash_impl(args)
local raw = args[1]
if type(raw) == "table" and (raw.__type == "list" or raw.__type == "dict" or raw.__type == "set" or raw.__type == "bytearray") then
Runtime.raise("TypeError", "unhashable type: '" .. raw.__type .. "'")
end
local obj = unwrap_num(raw)
if type(obj) == "number" then return math.floor(obj) end
if type(obj) == "string" then
local h = 0; for i = 1, #obj do h = (h * 31 + string.byte(obj, i)) % 4294967296 end; return h
end
if type(obj) == "boolean" then return obj and 1 or 0 end
if type(obj) == "table" then
if obj.__type == "bytes" or obj.__type == "memoryview" then
local s, h = obj._data, 0
for i = 1, #s do h = (h * 31 + string.byte(s, i)) % 4294967296 end
return h
end
if obj.__type == "instance" then
local ok, hm = safe_pcall(PyClass.get_attr, obj, "__hash__")
if ok and hm and hm.__type == "bound_method" then
return require("Module:PythonScript/Evaluator").invoke(hm.__func, {}, {}, hm.__self)
end
end
return tonumber(tostring(obj):match("0x%w+") or "0", 16) or 0
end
return 0
end
function Utils.compile_impl(args, kwargs)
local B = Runtime.builtins
local source = (args and args[1] ~= nil) and args[1] or (kwargs and kwargs.source)
local filename = (args and args[2] ~= nil) and args[2] or (kwargs and kwargs.filename)
local mode = (args and args[3] ~= nil) and args[3] or (kwargs and kwargs.mode)
if source == nil or filename == nil or mode == nil then
Runtime.raise("TypeError", "compile() missing required argument (source, filename, or mode)")
end
if type(source) == "table" and (source.__type == "bytes" or source.__type == "bytearray" or source.__type == "memoryview") then
source = source._data
elseif type(source) == "table" and source.__type == "instance" and source.__builtin_value ~= nil then
source = source.__builtin_value
end
if type(source) ~= "string" then Runtime.raise("TypeError", "compile() arg 1 must be a string, bytes or AST object") end
if type(filename) ~= "string" then filename = to_str(filename, false) end
if mode ~= "exec" and mode ~= "eval" and mode ~= "single" then
Runtime.raise("ValueError", "compile() mode must be 'exec', 'eval' or 'single'")
end
local clean_src = (mode == "eval") and source:gsub("^[ \t]+", "") or source
local new_file_idx = #Runtime.Traceback.files + 1
Runtime.Traceback.files[new_file_idx] = { name = filename, raw_code = clean_src }
local prev_file_idx = Runtime.Traceback.current_file_idx
local prev_func_name = Runtime.Traceback.current_func_name or "<module>"
local prev_line = Runtime.Traceback.current_line
table.insert(Runtime.Traceback.stack, { prev_file_idx, prev_func_name, prev_line })
Runtime.Traceback.current_file_idx = new_file_idx
Runtime.Traceback.current_func_name = "<module>"
Runtime.Traceback.current_line = 1
local Lexer = require("Module:PythonScript/Lexer")
local Parser = require("Module:PythonScript/Parser")
local ok, ast_or_err = safe_pcall(function()
return Parser.parse(Lexer.tokenize(clean_src), mode)
end)
table.remove(Runtime.Traceback.stack)
Runtime.Traceback.current_file_idx = prev_file_idx
Runtime.Traceback.current_func_name = prev_func_name
Runtime.Traceback.current_line = prev_line
if not ok then error(ast_or_err, 0) end
local code_inst = PyClass.make_instance(B.code_type)
code_inst.__dict._ast = ast_or_err
code_inst.__dict._mode = mode
code_inst.__dict._file_idx = new_file_idx
code_inst.__dict.co_filename = filename
code_inst.__dict.co_name = "<module>"
return code_inst
end
function Utils.eval_impl(args, kwargs)
if not args or #args == 0 then Runtime.raise("TypeError", "eval expected at least 1 argument, got 0") end
local B = Runtime.builtins
local src_or_code = args[1]
local globals_arg = args[2] or (kwargs and kwargs.globals)
local locals_arg = args[3] or (kwargs and kwargs.locals)
local code_obj
if type(src_or_code) == "table" and src_or_code.__type == "instance" and src_or_code.__class == B.code_type then
code_obj = src_or_code
elseif type(src_or_code) == "string" or (type(src_or_code) == "table" and (src_or_code.__type == "bytes" or src_or_code.__type == "bytearray" or src_or_code.__type == "memoryview" or src_or_code.__builtin_value ~= nil)) then
code_obj = B.compile({ src_or_code, "<string>", "eval" })
else Runtime.raise("TypeError", "eval() arg 1 must be a string, bytes or code object") end
return require("Module:PythonScript/Evaluator").eval_code(code_obj, globals_arg, locals_arg, "eval")
end
function Utils.exec_impl(args, kwargs)
if not args or #args == 0 then Runtime.raise("TypeError", "exec expected at least 1 argument, got 0") end
local B = Runtime.builtins
local src_or_code = args[1]
local globals_arg = args[2] or (kwargs and kwargs.globals)
local locals_arg = args[3] or (kwargs and kwargs.locals)
local code_obj
if type(src_or_code) == "table" and src_or_code.__type == "instance" and src_or_code.__class == B.code_type then
code_obj = src_or_code
elseif type(src_or_code) == "string" or (type(src_or_code) == "table" and (src_or_code.__type == "bytes" or src_or_code.__type == "bytearray" or src_or_code.__type == "memoryview" or src_or_code.__builtin_value ~= nil)) then
code_obj = B.compile({ src_or_code, "<string>", "exec" })
else Runtime.raise("TypeError", "exec() arg 1 must be a string, bytes or code object") end
require("Module:PythonScript/Evaluator").eval_code(code_obj, globals_arg, locals_arg, "exec")
return B.None
end
function Utils.map_impl(args)
local object_class = Runtime.builtins.object
local func, iterables = args[1], {}
for i = 2, #args do table.insert(iterables, get_iterator(args[i])) end
local map_dict = {
__iter__ = { __type = "builtin_method", __call = function(self_args) return self_args[1] end },
__next__ = {
__type = "builtin_method",
__call = function(self_args)
local self, call_args = self_args[1], {}
local Evaluator = require("Module:PythonScript/Evaluator")
for _, it in ipairs(self.__dict.iterables) do
local has_next, v = it(); if not has_next then Runtime.raise("StopIteration") end
table.insert(call_args, v)
end
return Evaluator.invoke(self.__dict.func, call_args, {})
end
}
}
local inst = PyClass.make_instance(PyClass.make_class("map", { object_class }, map_dict))
inst.__dict.func = func; inst.__dict.iterables = iterables
return inst
end
function Utils.filter_impl(args)
local B = Runtime.builtins
local func, it = args[1], get_iterator(args[2])
local filter_dict = {
__iter__ = { __type = "builtin_method", __call = function(self_args) return self_args[1] end },
__next__ = {
__type = "builtin_method",
__call = function(self_args)
local self = self_args[1]; local Evaluator = require("Module:PythonScript/Evaluator")
while true do
local has_next, v = self.__dict.it(); if not has_next then Runtime.raise("StopIteration") end
local keep = (self.__dict.func ~= nil and self.__dict.func ~= B.None)
and to_bool(Evaluator.invoke(self.__dict.func, {v}, {})) or to_bool(v)
if keep then return v end
end
end
}
}
local inst = PyClass.make_instance(PyClass.make_class("filter", { B.object }, filter_dict))
inst.__dict.func = func; inst.__dict.it = it
return inst
end
function Utils.iter_impl(args)
local object_class = Runtime.builtins.object
local obj = args[1]
if type(obj) == "table" and obj.__type == "instance" then
local success, attr = safe_pcall(PyClass.get_attr, obj, "__iter__")
if success and attr and attr.__type == "bound_method" then
return require("Module:PythonScript/Evaluator").invoke(attr.__func, {}, {}, attr.__self)
end
if obj.__builtin_value ~= nil then obj = obj.__builtin_value end
end
local iter_list = {}
if type(obj) == "string" then
for i = 1, mw.ustring.len(obj) do table.insert(iter_list, mw.ustring.sub(obj, i, i)) end
elseif 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
for i = 1, #obj._data do table.insert(iter_list, string.byte(obj._data, i)) end
elseif obj.__type == "list" or obj.__type == "tuple" or obj.__type == "set" or obj.__type == "frozenset" then
iter_list = obj.elements
elseif obj.__type == "dict" then
for _, p in ipairs(obj.pairs) do table.insert(iter_list, p.key) end
elseif obj.__type == "range" then
local curr, stop, step = obj.start, obj.stop, obj.step
if step > 0 then while curr < stop do table.insert(iter_list, curr); curr = curr + step end
else while curr > stop do table.insert(iter_list, curr); curr = curr + step end end
else Runtime.raise("TypeError", "'" .. get_type_str(obj) .. "' object is not iterable") end
else Runtime.raise("TypeError", "'" .. get_type_str(obj) .. "' object is not iterable") end
local iter_dict = {
__iter__ = { __type = "builtin_method", __call = function(self_args) return self_args[1] end },
__next__ = {
__type = "builtin_method",
__call = function(self_args)
local self = self_args[1]
self.__dict.idx = self.__dict.idx + 1
if self.__dict.idx > #self.__dict.elements then Runtime.raise("StopIteration") end
return self.__dict.elements[self.__dict.idx]
end
}
}
local inst = PyClass.make_instance(PyClass.make_class("list_iterator", { object_class }, iter_dict))
inst.__dict.elements = iter_list; inst.__dict.idx = 0
return inst
end
function Utils.next_impl(args)
local obj, has_default, default = args[1], (#args >= 2), args[2]
local success, next_method = safe_pcall(PyClass.get_attr, obj, "__next__")
if not success or not next_method then
Runtime.raise("TypeError", "'" .. get_type_str(obj) .. "' object is not an iterator")
end
local eval_success, res = safe_pcall(function()
local Evaluator = require("Module:PythonScript/Evaluator")
if next_method.__type == "bound_method" then return Evaluator.invoke(next_method.__func, {}, {}, next_method.__self) end
end)
if not eval_success then
if type(res) == "table" and res.__class and res.__class.__name == "StopIteration" then
if has_default then return default end
end
if type(res) == "table" and res.__type == "instance" then error(res, 0) end
Runtime.raise("RuntimeError", tostring(res))
end
return res
end
-- ==========================================
-- 6. 內建例外樹、單例與基礎系統類別初始化
-- ==========================================
function Utils.init_exceptions_and_singletons(B, object_class)
local exception_dict = {
__init__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
local call_args = {}
for i = 2, #args do table.insert(call_args, args[i]) end
self.__dict.args = { __type = "tuple", elements = call_args }
self.__dict.msg = (args[2] ~= nil) and to_str(args[2], false) or ""
if self.__class and self.__class.__name == "StopIteration" then self.__dict.value = args[2]
elseif self.__class and self.__class.__name == "SystemExit" then
self.code = args[2] or 0; self.__dict.code = self.code
end
return nil
end
},
__str__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
if self.__dict.msg ~= nil and self.__dict.msg ~= "" then return tostring(self.__dict.msg) end
local elts = self.__dict.args and self.__dict.args.elements
if elts and #elts == 1 then return to_str(elts[1], false)
elseif elts and #elts > 1 then return to_str(self.__dict.args, true) end
return ""
end
},
__repr__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
local cname = (self.__class and self.__class.__name) or "Exception"
local elts = self.__dict.args and self.__dict.args.elements
if elts and #elts > 0 then
local parts = {}
for _, v in ipairs(elts) do table.insert(parts, to_str(v, true)) end
return cname .. "(" .. table.concat(parts, ", ") .. ")"
elseif self.__dict.msg and self.__dict.msg ~= "" then
return cname .. "('" .. tostring(self.__dict.msg) .. "')"
end
return cname .. "()"
end
}
}
local function make_exc_class(name, parent)
local dict = {}
for k, v in pairs(exception_dict) do dict[k] = v end
local cls = PyClass.make_class(name, { parent or object_class }, dict)
B[name] = cls
return cls
end
local BaseException = make_exc_class("BaseException", object_class)
local Exception = make_exc_class("Exception", BaseException)
make_exc_class("SystemExit", BaseException)
make_exc_class("GeneratorExit", BaseException)
make_exc_class("KeyboardInterrupt", BaseException)
local ArithmeticError = make_exc_class("ArithmeticError", Exception)
make_exc_class("ZeroDivisionError", ArithmeticError)
make_exc_class("OverflowError", ArithmeticError)
make_exc_class("FloatingPointError", ArithmeticError)
local LookupError = make_exc_class("LookupError", Exception)
make_exc_class("IndexError", LookupError)
make_exc_class("KeyError", LookupError)
local RuntimeErrorCls = make_exc_class("RuntimeError", Exception)
make_exc_class("NotImplementedError", RuntimeErrorCls)
make_exc_class("RecursionError", RuntimeErrorCls)
local SyntaxErrorCls = make_exc_class("SyntaxError", Exception)
make_exc_class("IndentationError", SyntaxErrorCls)
local OSErrorCls = make_exc_class("OSError", Exception)
B.IOError = OSErrorCls
make_exc_class("FileNotFoundError", OSErrorCls)
make_exc_class("FileExistsError", OSErrorCls)
make_exc_class("PermissionError", OSErrorCls)
local ValueErrorCls = make_exc_class("ValueError", Exception)
local UnicodeErrorCls = make_exc_class("UnicodeError", ValueErrorCls)
make_exc_class("UnicodeDecodeError", UnicodeErrorCls)
make_exc_class("UnicodeEncodeError", UnicodeErrorCls)
local ImportErrorCls = make_exc_class("ImportError", Exception)
make_exc_class("ModuleNotFoundError", ImportErrorCls)
make_exc_class("TypeError", Exception)
make_exc_class("AttributeError", Exception)
make_exc_class("NameError", Exception)
make_exc_class("UnboundLocalError", B.NameError)
make_exc_class("StopIteration", Exception)
make_exc_class("StopAsyncIteration", Exception)
make_exc_class("AssertionError", Exception)
make_exc_class("EOFError", Exception)
make_exc_class("MemoryError", Exception)
make_exc_class("BufferError", Exception)
make_exc_class("SystemError", Exception)
local WarningCls = make_exc_class("Warning", Exception)
make_exc_class("UserWarning", WarningCls)
make_exc_class("DeprecationWarning", WarningCls)
make_exc_class("RuntimeWarning", WarningCls)
-- 特殊單例物件
B.NoneType = PyClass.make_class("NoneType", { object_class }, {})
B.None = PyClass.make_instance(B.NoneType)
local NotImplementedType = PyClass.make_class("NotImplementedType", { object_class }, {
__call = { __type = "builtin_method", __call = function() return B.NotImplemented end },
__repr__ = { __type = "builtin_method", __call = function() return "NotImplemented" end }
})
NotImplementedType.__dict.__str__ = NotImplementedType.__dict.__repr__
B.NotImplemented = PyClass.make_instance(NotImplementedType)
local EllipsisType = PyClass.make_class("ellipsis", { object_class }, {
__call = { __type = "builtin_method", __call = function() return B.Ellipsis end },
__repr__ = { __type = "builtin_method", __call = function() return "Ellipsis" end }
})
EllipsisType.__dict.__str__ = EllipsisType.__dict.__repr__
B.Ellipsis = PyClass.make_instance(EllipsisType)
-- code, slice, super 與元型別定義
B.code_type = PyClass.make_class("code", { object_class }, {
__repr__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
local fname = (self.__dict and self.__dict.co_filename) or "<string>"
return string.format('<code object <module> at %s, file "%s", line 1>', tostring(self):match("0x%w+") or "0x0", fname)
end
}
})
B.code_type.__dict.__str__ = B.code_type.__dict.__repr__
local slice_dict = {
__init__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
if #args == 2 then
self.__dict.start = B.None; self.__dict.stop = args[2] == nil and B.None or args[2]; self.__dict.step = B.None
elseif #args >= 3 then
self.__dict.start = args[2] == nil and B.None or args[2]; self.__dict.stop = args[3] == nil and B.None or args[3]; self.__dict.step = args[4] == nil and B.None or args[4]
else Runtime.raise("TypeError", "slice expected at least 1 argument, got " .. (#args - 1)) end
return nil
end
},
__repr__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
local function fmt_v(v) return (v == nil or v == B.None) and "None" or to_str(v, true) end
return "slice(" .. fmt_v(self.__dict.start) .. ", " .. fmt_v(self.__dict.stop) .. ", " .. fmt_v(self.__dict.step) .. ")"
end
}
}
slice_dict.__str__ = slice_dict.__repr__
B.slice = PyClass.make_class("slice", { object_class }, slice_dict)
local super_proxy_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, attr_name = args[1], args[2]
local cls, obj = self.__dict._cls, self.__dict._obj
local mro
if type(obj) == "table" and obj.__type == "instance" then
mro = obj.__class.__mro
elseif type(obj) == "table" and obj.__type == "class" then
mro = obj.__mro
local found_in_cls = false
for _, base in ipairs(mro or {}) do if base == cls then found_in_cls = true; break end end
if not found_in_cls and obj.__class and obj.__class.__mro then
mro = obj.__class.__mro
end
else Runtime.raise("TypeError", "super(type, obj): obj must be an instance or subtype of type") end
local start_idx = -1
for i, base in ipairs(mro or {}) do if base == cls then start_idx = i; break end end
if start_idx == -1 then Runtime.raise("TypeError", "super(type, obj): obj must be an instance or subtype of type") end
for i = start_idx + 1, #mro do
local val = mro[i].__dict[attr_name]
if val ~= nil then
if type(val) == "table" then
if val.__type == "function" or val.__type == "builtin_method" then return { __type = "bound_method", __self = obj, __func = val }
elseif val.__type == "classmethod" then return { __type = "bound_method", __self = obj.__type == "class" and obj or obj.__class, __func = val.__func }
elseif val.__type == "staticmethod" then return val.__func
elseif val.__type == "property" then
if val.fget then return require("Module:PythonScript/Evaluator").invoke(val.fget, {obj}, {}) end
return val
end
end
return val
end
end
if type(obj) == "table" and obj.__type == "instance" and obj.__builtin_value ~= nil then
local ok_b, bm = safe_pcall(PyClass.get_attr, obj.__builtin_value, attr_name)
if ok_b and bm ~= nil then return bm end
end
if object_class.__dict[attr_name] then
local oval = object_class.__dict[attr_name]
if oval.__type == "staticmethod" then return oval.__func end
return { __type = "bound_method", __self = obj, __func = oval }
end
Runtime.raise("AttributeError", "'super' object has no attribute '" .. attr_name .. "'")
end
},
__repr__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
local cls_name = self.__dict._cls and self.__dict._cls.__name or "unknown"
local obj_name = self.__dict._obj and (self.__dict._obj.__class and self.__dict._obj.__class.__name or self.__dict._obj.__type) or "unknown"
return "<super: <class '" .. cls_name .. "'>, <" .. obj_name .. " object>>"
end
}
}
super_proxy_dict.__str__ = super_proxy_dict.__repr__
local SuperProxyClass = PyClass.make_class("super_proxy", { object_class }, super_proxy_dict)
B.super = Utils.wrap_as_class("super", function(args)
local cls, obj = args and args[1], args and args[2]
if not cls or not obj then
local curr_scope = Runtime.Traceback.current_scope
while curr_scope do
if curr_scope.__super_cls then
cls = cls or curr_scope.__super_cls; obj = obj or curr_scope.__super_self; break
end
curr_scope = curr_scope.parent
end
if not cls or not obj then Runtime.raise("RuntimeError", "super(): no arguments") end
end
local inst = PyClass.make_instance(SuperProxyClass)
inst.__dict._cls = cls; inst.__dict._obj = obj
return inst
end)
B.builtin_function_or_method = PyClass.make_class("builtin_function_or_method", { object_class }, {})
B.function_type = PyClass.make_class("function", { object_class }, {})
B.module_type = PyClass.make_class("module", { object_class }, {})
B.method_type = PyClass.make_class("method", { object_class }, {})
B.property_type = PyClass.make_class("property", { object_class }, {})
-- ==========================================
-- UnpackedTuple 與 QuaternionImagPart 代理類別
-- ==========================================
local function unwrap_ut(v)
if type(v) == "table" and v._is_unpacked_tuple then return v.__builtin_value end
return v
end
local function ut_bin_op(op_str, is_right)
return {
__type = "builtin_method",
__call = function(args)
local a, b = unwrap_ut(args[1]), unwrap_ut(args[2])
if is_right then a, b = b, a end
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 = op_str,
left = { type = "DirectValue", value = a },
right = { type = "DirectValue", value = b }
}, dummy_scope)
end
}
end
local function ut_unary_op(op_str)
return {
__type = "builtin_method",
__call = function(args)
local a = unwrap_ut(args[1])
local eval_expr = require("Module:PythonScript/Evaluator/Core").getCoreFunction()
local dummy_scope = require("Module:PythonScript/Evaluator/Utils").create_scope(nil)
return eval_expr({
type = "UnaryOp", op = op_str,
right = { type = "DirectValue", value = a }
}, dummy_scope)
end
}
end
local function ut_cmp_op(op_str)
return {
__type = "builtin_method",
__call = function(args)
local a, b = unwrap_ut(args[1]), unwrap_ut(args[2])
local EvalUtils = require("Module:PythonScript/Evaluator/Utils")
if op_str == "==" then return EvalUtils.py_eq(a, b)
elseif op_str == "!=" then return not EvalUtils.py_eq(a, b)
elseif op_str == "<" then return EvalUtils.py_cmp(a, b) < 0
elseif op_str == "<=" then return EvalUtils.py_cmp(a, b) <= 0
elseif op_str == ">" then return EvalUtils.py_cmp(a, b) > 0
elseif op_str == ">=" then return EvalUtils.py_cmp(a, b) >= 0 end
end
}
end
local unpacked_tuple_dict = {
__new__ = {
__type = "staticmethod",
__func = {
__type = "builtin_method",
__call = function(args)
return PyClass.make_instance(args[1])
end
}
},
__init__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
local elts = {}
for i = 2, #args do table.insert(elts, args[i]) end
self._is_unpacked_tuple = true
self._elements = elts
self.__builtin_value = (elts[1] ~= nil) and elts[1] or B.None
return nil
end
},
__iter__ = {
__type = "builtin_method",
__call = function(args)
return B.iter({ { __type = "tuple", elements = args[1]._elements or {} } })
end
},
__getitem__ = {
__type = "builtin_method",
__call = function(args)
local self, idx = args[1], unwrap_num(args[2])
local elts = self._elements or {}
if type(idx) == "number" and math.floor(idx) == idx then
if idx < 0 then idx = #elts + idx end
if idx < 0 or idx >= #elts then Runtime.raise("IndexError", "UnpackedTuple index out of range") end
return elts[idx + 1]
end
Runtime.raise("TypeError", "UnpackedTuple indices must be integers")
end
},
__repr__ = { __type = "builtin_method", __call = function(args) return to_str(args[1].__builtin_value, true) end },
__str__ = { __type = "builtin_method", __call = function(args) return to_str(args[1].__builtin_value, false) end },
__bool__ = { __type = "builtin_method", __call = function(args) return to_bool(args[1].__builtin_value) end },
__int__ = { __type = "builtin_method", __call = function(args) return B.int.__call({ args[1].__builtin_value }) end },
__float__ = { __type = "builtin_method", __call = function(args) return B.float.__call({ args[1].__builtin_value }) end },
__index__ = { __type = "builtin_method", __call = function(args) return B.int.__call({ args[1].__builtin_value }) end },
__hash__ = { __type = "builtin_method", __call = function(args) return B.hash({ args[1].__builtin_value }) end },
__abs__ = { __type = "builtin_method", __call = function(args) return B.abs({ args[1].__builtin_value }) end },
__round__ = { __type = "builtin_method", __call = function(args) return B.round({ args[1].__builtin_value, args[2] }) end },
__add__ = ut_bin_op("+", false), __radd__ = ut_bin_op("+", true),
__sub__ = ut_bin_op("-", false), __rsub__ = ut_bin_op("-", true),
__mul__ = ut_bin_op("*", false), __rmul__ = ut_bin_op("*", true),
__truediv__ = ut_bin_op("/", false), __rtruediv__ = ut_bin_op("/", true),
__floordiv__ = ut_bin_op("//", false), __rfloordiv__ = ut_bin_op("//", true),
__mod__ = ut_bin_op("%", false), __rmod__ = ut_bin_op("%", true),
__pow__ = ut_bin_op("**", false), __rpow__ = ut_bin_op("**", true),
__matmul__ = ut_bin_op("@", false), __rmatmul__ = ut_bin_op("@", true),
__and__ = ut_bin_op("&", false), __rand__ = ut_bin_op("&", true),
__or__ = ut_bin_op("|", false), __ror__ = ut_bin_op("|", true),
__xor__ = ut_bin_op("^", false), __rxor__ = ut_bin_op("^", true),
__lshift__ = ut_bin_op("<<", false), __rlshift__ = ut_bin_op("<<", true),
__rshift__ = ut_bin_op(">>", false), __rrshift__ = ut_bin_op(">>", true),
__neg__ = ut_unary_op("-"),
__pos__ = ut_unary_op("+"),
__invert__ = ut_unary_op("~"),
__eq__ = ut_cmp_op("=="), __ne__ = ut_cmp_op("!="),
__lt__ = ut_cmp_op("<"), __le__ = ut_cmp_op("<="),
__gt__ = ut_cmp_op(">"), __ge__ = ut_cmp_op(">=")
}
PyClass._UnpackedTuple = PyClass.make_class("UnpackedTuple", { object_class }, unpacked_tuple_dict)
PyClass._QuaternionImagPart = PyClass.make_class("QuaternionImagPart", { PyClass._UnpackedTuple }, {})
function Utils.make_quaternion_imag(b, c, d)
local inst = PyClass.make_instance(PyClass._QuaternionImagPart)
inst._is_unpacked_tuple = true
inst._elements = { b or 0, c or 0, d or 0 }
inst.__builtin_value = b or 0
return inst
end
local function extract_mapping_to_table(dict_obj)
local class_dict = {}
if type(dict_obj) ~= "table" then
Runtime.raise("TypeError", "type.__new__() argument 3 must be dict or mapping")
end
if dict_obj._class_vars then
for k, v in pairs(dict_obj._class_vars) do class_dict[k] = v end
end
if dict_obj.__type == "dict" then
for _, p in ipairs(dict_obj.pairs or {}) do
if type(p.key) == "string" then class_dict[p.key] = p.value end
end
elseif dict_obj.__type == "instance" then
if type(dict_obj.__builtin_value) == "table" and dict_obj.__builtin_value.__type == "dict" then
for _, p in ipairs(dict_obj.__builtin_value.pairs or {}) do
if type(p.key) == "string" then class_dict[p.key] = p.value end
end
elseif dict_obj.__dict and type(dict_obj.__dict.data) == "table" and dict_obj.__dict.data.__type == "dict" then
for _, p in ipairs(dict_obj.__dict.data.pairs or {}) do
if type(p.key) == "string" then class_dict[p.key] = p.value end
end
else
local ok_k, keys_m = safe_pcall(PyClass.get_attr, dict_obj, "keys")
local ok_g, get_m = safe_pcall(PyClass.get_attr, dict_obj, "__getitem__")
if ok_k and keys_m and ok_g and get_m then
local Evaluator = require("Module:PythonScript/Evaluator")
local k_iter = get_iterator(Evaluator.invoke(keys_m, {}, {}))
while true do
local hn, k = k_iter()
if not hn then break end
if type(k) == "string" then
class_dict[k] = Evaluator.invoke(get_m, { k }, {})
end
end
end
end
else
Runtime.raise("TypeError", "type.__new__() argument 3 must be dict, not " .. get_type_str(dict_obj))
end
return class_dict
end
local function create_class_from_type(mcls, name, bases_tup, dict_obj, kwargs)
if type(name) ~= "string" then Runtime.raise("TypeError", "type() argument 1 must be str") end
if type(bases_tup) ~= "table" or bases_tup.__type ~= "tuple" then Runtime.raise("TypeError", "type() argument 2 must be tuple") end
local bases = {}
for _, b in ipairs(bases_tup.elements) do table.insert(bases, b) end
local winner_meta = PyClass.resolve_metaclass(mcls, bases)
local class_dict = extract_mapping_to_table(dict_obj)
if winner_meta ~= mcls and type(winner_meta) == "table" and winner_meta.__type == "class" then
local Evaluator = require("Module:PythonScript/Evaluator")
return Evaluator.invoke(winner_meta, { name, bases_tup, dict_obj }, kwargs or {})
end
local cls = PyClass.make_class(name, bases, class_dict)
if not cls.__dict["__module__"] then
local scope = Runtime.Traceback.current_scope
if scope then
local root = scope; while root.parent do root = root.parent end
cls.__dict["__module__"] = root.vars["__name__"] or "__main__"
end
end
return PyClass.init_class_hooks(cls, winner_meta, kwargs)
end
B.type = Utils.wrap_as_class("type", function(args, kwargs)
if #args == 3 then
return create_class_from_type(B.type, args[1], args[2], args[3], kwargs)
elseif #args == 1 then
local obj = args[1]; local t = type(obj)
if t == "number" then return (math.floor(obj) == obj and math.abs(obj) <= SAFE_LIMIT) and B.int or B.float
elseif t == "string" then return B.str
elseif t == "boolean" then return B.bool
elseif t == "nil" then return B.NoneType
elseif t == "function" then return B.builtin_function_or_method
elseif t == "table" then
if obj.__class and obj.__class.__name == "NoneType" then return B.NoneType end
if obj.__class then return obj.__class end
if obj.__type == "class" then return B.type end
local pt = obj.__type
if pt == "int" then return B.int
elseif pt == "bytes" then return B.bytes
elseif pt == "bytearray" then return B.bytearray
elseif pt == "memoryview" then return B.memoryview
elseif pt == "list" then return B.list
elseif pt == "dict" then return B.dict
elseif pt == "tuple" then return B.tuple
elseif pt == "set" then return B.set
elseif pt == "frozenset" then return B.frozenset
elseif pt == "complex" then return B.complex
elseif pt == "function" then return B.function_type
elseif pt == "builtin_method" then return B.builtin_function_or_method
elseif pt == "bound_method" then return B.method_type
elseif pt == "property" then return B.property_type
elseif pt == "LazyNamespace" or pt == "module" then return B.module_type end
end
return B.type
else Runtime.raise("TypeError", "type() takes 1 or 3 arguments") end
end)
B.type.__dict["__new__"] = {
__type = "staticmethod",
__func = {
__type = "builtin_method",
__name = "__new__",
__call = function(args, kwargs)
local mcls = args and args[1]
if type(mcls) ~= "table" or mcls.__type ~= "class" or not PyClass.is_subclass(mcls, B.type) then
Runtime.raise("TypeError", "type.__new__(X): X is not a type object (subtype of type)")
end
if #args == 2 and mcls == B.type then
return B.type.__call({ args[2] })
end
if #args ~= 4 then
Runtime.raise("TypeError", "type.__new__() takes exactly 3 arguments (" .. (#args - 1) .. " given)")
end
return create_class_from_type(mcls, args[2], args[3], args[4], kwargs)
end
}
}
B.type.__dict["__init__"] = {
__type = "builtin_method",
__name = "__init__",
__call = function() return B.None end
}
B.type.__dict["__prepare__"] = {
__type = "classmethod",
__func = {
__type = "builtin_method",
__name = "__prepare__",
__call = function() return { __type = "dict", pairs = {} } end
}
}
B.type.__dict["__call__"] = {
__type = "builtin_method",
__name = "__call__",
__call = function(args, kwargs)
local cls = args and args[1]
local call_args = {}
for i = 2, #(args or {}) do table.insert(call_args, args[i]) end
return require("Module:PythonScript/Evaluator").default_instantiate_class(cls, call_args, kwargs)
end
}
B.type.__dict["__getattribute__"] = object_class.__dict["__getattribute__"]
B.type.__dict["__setattr__"] = object_class.__dict["__setattr__"]
B.type.__dict["__delattr__"] = object_class.__dict["__delattr__"]
end
return Utils