模組:PythonScript/Class/Utils
外观
-- Module:PythonScript/Class/Utils
-- Class 內部輔助工具與內建型別 (str/bytes/number/container) 方法庫
local Utils = {}
local PyClassModel
function Utils.bind_class_model(cls_model)
PyClassModel = cls_model
end
local function safe_pcall(fn, ...)
local Runtime = require("Module:PythonScript/Runtime")
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 is_none(val)
return val == nil or (type(val) == "table" and val.__class and val.__class.__name == "NoneType")
end
Utils.is_none = is_none
local function unwrap_int(val)
if type(val) == "boolean" then return val and 1 or 0 end
if type(val) == "table" and val.__type == "int" then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
return val.value or tonumber(MathCore.BigInt.to_string(val.sign, val.limbs))
end
return val
end
Utils.unwrap_int = unwrap_int
local function extract_iterable_elements(iter)
if iter == nil then return {} end
if type(iter) == "table" and (iter.__type == "list" or iter.__type == "tuple" or iter.__type == "set" or iter.__type == "frozenset") then
return iter.elements
end
return require("Module:PythonScript/Runtime").builtins.list.__call({iter}).elements
end
Utils.extract_iterable_elements = extract_iterable_elements
local function resolve_search_bounds(total_len, start_arg, end_arg)
local s = not is_none(start_arg) and unwrap_int(start_arg) or 0
local e = not is_none(end_arg) and unwrap_int(end_arg) or total_len
if s < 0 then s = math.max(0, total_len + s) elseif s > total_len then s = total_len end
if e < 0 then e = math.max(0, total_len + e) elseif e > total_len then e = total_len end
return s + 1, e
end
function Utils.did_you_mean(target, keys)
if type(target) ~= "string" then return nil end
local best, min_d = nil, 999
for _, k in ipairs(keys) do
if type(k) == "string" then
local len1, len2 = #target, #k
local matrix = {}
for i = 0, len1 do matrix[i] = {[0] = i} end
for j = 0, len2 do matrix[0][j] = j end
for i = 1, len1 do
for j = 1, len2 do
local cost = (target:sub(i, i) == k:sub(j, j) and 0 or 1)
matrix[i][j] = math.min(matrix[i-1][j] + 1, matrix[i][j-1] + 1, matrix[i-1][j-1] + cost)
end
end
local d = matrix[len1][len2]
if d < min_d and d <= math.max(1, math.floor(#target / 2)) then min_d = d; best = k end
end
end
return best
end
Utils.IMPLICIT_DUNDERS = {
__str__ = true, __repr__ = true, __bool__ = true, __len__ = true,
__iter__ = true, __next__ = true, __reversed__ = true, __contains__ = true,
__getitem__ = true, __setitem__ = true, __delitem__ = true,
__eq__ = true, __ne__ = true, __lt__ = true, __le__ = true, __gt__ = true, __ge__ = true,
__add__ = true, __sub__ = true, __mul__ = true, __truediv__ = true, __floordiv__ = true,
__mod__ = true, __pow__ = true, __matmul__ = true, __ldiv__ = true,
__and__ = true, __or__ = true, __xor__ = true, __lshift__ = true, __rshift__ = true,
__radd__ = true, __rsub__ = true, __rmul__ = true, __rtruediv__ = true, __rfloordiv__ = true,
__rmod__ = true, __rpow__ = true, __rmatmul__ = true, __rldiv__ = true,
__rand__ = true, __ror__ = true, __rxor__ = true, __rlshift__ = true, __rrshift__ = true,
__neg__ = true, __pos__ = true, __invert__ = true, __abs__ = true, __divmod__ = true,
__int__ = true, __float__ = true, __bytes__ = true, __fspath__ = true, __hash__ = true,
__format__ = true, __call__ = true, __enter__ = true, __exit__ = true
}
local function is_eq(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) ~= type(b) then
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 is_eq(ua, ub, seen)
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
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 is_eq(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 is_eq(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 is_eq(pa.key, pb.key, seen) and is_eq(pa.value, pb.value, seen) then found = true; break end
end
if not found then return false end
end
return true
end
if a.__type == "instance" then
local ok, eq_m = safe_pcall(PyClassModel.get_attr, a, "__eq__")
if ok and eq_m and eq_m.__type == "bound_method" then
local Evaluator = require("Module:PythonScript/Evaluator")
local Runtime = require("Module:PythonScript/Runtime")
local res = Evaluator.invoke(eq_m.__func, {b}, {}, eq_m.__self)
if res ~= Runtime.builtins.NotImplemented then
return Runtime.builtins.bool.__call({res})
end
end
end
if (a.__type == "instance" and a.__builtin_value ~= nil) or (b.__type == "instance" and b.__builtin_value ~= nil) then
return is_eq(a.__builtin_value ~= nil and a.__builtin_value or a, b.__builtin_value ~= nil and b.__builtin_value or b, seen)
end
end
return a == b
end
Utils.is_eq = is_eq
-- ==========================================
-- 1. 字串與二進位家族方法 (str, bytes, bytearray, memoryview)
-- ==========================================
function Utils.get_str_or_bytes_attr(obj, attr_name)
local is_str = (type(obj) == "string")
local is_bytes_family = (type(obj) == "table" and (obj.__type == "bytes" or obj.__type == "bytearray"))
local is_memview = (type(obj) == "table" and obj.__type == "memoryview")
if is_str or is_bytes_family then
local raw_data = is_str and obj or obj._data
local str_lib = is_bytes_family and string or mw.ustring
local function wrap_res(res)
if is_bytes_family and type(res) == "string" then
local target_type = obj.__type
local creator = require("Module:PythonScript/Runtime").builtins[target_type]
if creator then return creator.__call({res}) end
return { __type = target_type, _data = res }
end
return res
end
local function unwrap_arg(val)
if type(val) == "table" and (val.__type == "bytes" or val.__type == "bytearray" or val.__type == "memoryview") then
return val._data
end
return val
end
if attr_name == "lower" then return { __type = "builtin_method", __name = "lower", __call = function() return wrap_res(str_lib.lower(raw_data)) end }
elseif attr_name == "upper" then return { __type = "builtin_method", __name = "upper", __call = function() return wrap_res(str_lib.upper(raw_data)) end }
elseif attr_name == "capitalize" then return { __type = "builtin_method", __name = "capitalize", __call = function() return wrap_res(str_lib.upper(str_lib.sub(raw_data, 1, 1)) .. str_lib.lower(str_lib.sub(raw_data, 2))) end }
elseif attr_name == "title" then return { __type = "builtin_method", __name = "title", __call = function() return wrap_res((str_lib.gsub(raw_data, "(%w)(%w*)", function(first, rest) return str_lib.upper(first) .. str_lib.lower(rest) end))) end }
elseif attr_name == "strip" or attr_name == "lstrip" or attr_name == "rstrip" then
return { __type = "builtin_method", __name = attr_name, __call = function(args)
local chars = args and args[1]
if is_none(chars) then
if attr_name == "strip" then return wrap_res((str_lib.gsub(str_lib.gsub(raw_data, "^%s+", ""), "%s+$", "")))
elseif attr_name == "lstrip" then return wrap_res((str_lib.gsub(raw_data, "^%s+", "")))
else return wrap_res((str_lib.gsub(raw_data, "%s+$", ""))) end
end
local c_str = unwrap_arg(chars)
if type(c_str) ~= "string" then require("Module:PythonScript/Runtime").raise("TypeError", attr_name .. " arg must be None, str or bytes") end
local len = str_lib.len(raw_data)
local s_idx, e_idx = 1, len
if attr_name == "strip" or attr_name == "lstrip" then
while s_idx <= e_idx do
local ch = str_lib.sub(raw_data, s_idx, s_idx)
if str_lib.find(c_str, ch, 1, true) then s_idx = s_idx + 1 else break end
end
end
if attr_name == "strip" or attr_name == "rstrip" then
while e_idx >= s_idx do
local ch = str_lib.sub(raw_data, e_idx, e_idx)
if str_lib.find(c_str, ch, 1, true) then e_idx = e_idx - 1 else break end
end
end
return wrap_res(str_lib.sub(raw_data, s_idx, e_idx))
end }
elseif attr_name == "startswith" or attr_name == "endswith" then
return { __type = "builtin_method", __name = attr_name, __call = function(args)
local prefix_arg = args[1]
local total_len = str_lib.len(raw_data)
local s_pos, e_pos = resolve_search_bounds(total_len, args[2], args[3])
local target_sub = (s_pos <= e_pos) and str_lib.sub(raw_data, s_pos, e_pos) or ""
local t_len = str_lib.len(target_sub)
local candidates = {}
if type(prefix_arg) == "table" and prefix_arg.__type == "tuple" then
for _, el in ipairs(prefix_arg.elements) do table.insert(candidates, unwrap_arg(el)) end
else table.insert(candidates, unwrap_arg(prefix_arg)) end
for _, p in ipairs(candidates) do
if type(p) ~= "string" then require("Module:PythonScript/Runtime").raise("TypeError", attr_name .. " first arg must be str/bytes or a tuple of str/bytes") end
local plen = str_lib.len(p)
if plen <= t_len then
if attr_name == "startswith" then
if str_lib.sub(target_sub, 1, plen) == p then return true end
else
if plen == 0 or str_lib.sub(target_sub, -plen) == p then return true end
end
end
end
return false
end }
elseif attr_name == "find" or attr_name == "index" then
return { __type = "builtin_method", __name = attr_name, __call = function(args)
local sub_str = unwrap_arg(args[1])
if type(sub_str) ~= "string" then require("Module:PythonScript/Runtime").raise("TypeError", "must be str or bytes") end
local total_len = str_lib.len(raw_data)
local s_pos, e_pos = resolve_search_bounds(total_len, args[2], args[3])
local idx = nil
if s_pos <= e_pos + 1 then
local search_area = str_lib.sub(raw_data, 1, e_pos)
local found_s, found_e = str_lib.find(search_area, sub_str, s_pos, true)
if found_s and found_e <= e_pos then idx = found_s - 1 end
end
if idx then return idx end
if attr_name == "index" then require("Module:PythonScript/Runtime").raise("ValueError", "substring not found") end
return -1
end }
elseif attr_name == "rfind" or attr_name == "rindex" then
return { __type = "builtin_method", __name = attr_name, __call = function(args)
local sub_str = unwrap_arg(args[1])
if type(sub_str) ~= "string" then require("Module:PythonScript/Runtime").raise("TypeError", "must be str or bytes") end
local total_len = str_lib.len(raw_data)
local s_pos, e_pos = resolve_search_bounds(total_len, args[2], args[3])
local last_idx = nil
if sub_str == "" then
if s_pos <= e_pos + 1 then last_idx = e_pos end
elseif s_pos <= e_pos then
local search_area = str_lib.sub(raw_data, 1, e_pos)
local cur = s_pos
while cur <= e_pos do
local ni, nj = str_lib.find(search_area, sub_str, cur, true)
if not ni or nj > e_pos then break end
last_idx = ni - 1; cur = ni + 1
end
end
if last_idx then return last_idx end
if attr_name == "rindex" then require("Module:PythonScript/Runtime").raise("ValueError", "substring not found") end
return -1
end }
elseif attr_name == "count" then
return { __type = "builtin_method", __name = "count", __call = function(args)
local sub_str = unwrap_arg(args[1])
if type(sub_str) ~= "string" then require("Module:PythonScript/Runtime").raise("TypeError", "must be str or bytes") end
local total_len = str_lib.len(raw_data)
local s_pos, e_pos = resolve_search_bounds(total_len, args[2], args[3])
if s_pos > e_pos + 1 then return 0 end
if sub_str == "" then return math.max(0, e_pos - s_pos + 2) end
local search_area = str_lib.sub(raw_data, 1, e_pos)
local c, i = 0, s_pos
while i <= e_pos do
local s_idx, e_idx = str_lib.find(search_area, sub_str, i, true)
if s_idx and e_idx <= e_pos then c = c + 1; i = e_idx + 1 else break end
end
return c
end }
elseif attr_name == "replace" then
return { __type = "builtin_method", __name = "replace", __call = function(args)
local old, new, count = unwrap_arg(args[1]), unwrap_arg(args[2]), args[3]
if type(old) ~= "string" or type(new) ~= "string" then require("Module:PythonScript/Runtime").raise("TypeError", "replace() arguments must be str or bytes") end
if not is_none(count) then count = unwrap_int(count) else count = nil end
if count and count == 0 then return wrap_res(raw_data) end
if old == "" then
local chars = {}; local len = str_lib.len(raw_data)
local max_rep = (count and count >= 0) and count or (len + 1)
local rep_done = 0
for i = 1, len do
if rep_done < max_rep then table.insert(chars, new); rep_done = rep_done + 1 end
table.insert(chars, str_lib.sub(raw_data, i, i))
end
if rep_done < max_rep then table.insert(chars, new) end
return wrap_res(table.concat(chars))
end
local safe_old = str_lib.gsub(old, '[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%1')
local safe_new = str_lib.gsub(new, '%%', '%%%%')
local res = (count and count > 0) and str_lib.gsub(raw_data, safe_old, safe_new, count) or str_lib.gsub(raw_data, safe_old, safe_new)
return wrap_res(res)
end }
elseif attr_name == "split" then
return { __type = "builtin_method", __name = "split", __call = function(args, kwargs)
local raw_sep = (args and args[1] ~= nil) and args[1] or (kwargs and kwargs.sep)
local maxsplit = unwrap_int((args and args[2] ~= nil) and args[2] or (kwargs and kwargs.maxsplit) or -1)
local sep = is_none(raw_sep) and nil or unwrap_arg(raw_sep)
local res = {}
if sep == nil then
local s = str_lib.gsub(raw_data, "^%s+", "")
if s == "" then return { __type = "list", elements = {} } end
local splits, pos, len = 0, 1, str_lib.len(s)
while pos <= len do
if maxsplit >= 0 and splits >= maxsplit then
local rest = str_lib.gsub(str_lib.sub(s, pos), "%s+$", "")
if rest ~= "" then table.insert(res, wrap_res(rest)) end
break
end
local ws_s, ws_e = str_lib.find(s, "%s+", pos)
if not ws_s then table.insert(res, wrap_res(str_lib.sub(s, pos))); break end
table.insert(res, wrap_res(str_lib.sub(s, pos, ws_s - 1)))
pos = ws_e + 1; splits = splits + 1
end
else
if sep == "" then require("Module:PythonScript/Runtime").raise("ValueError", "empty separator") end
local start, splits = 1, 0
while true do
if maxsplit >= 0 and splits >= maxsplit then
table.insert(res, wrap_res(str_lib.sub(raw_data, start))); break
end
local s_idx, e_idx = str_lib.find(raw_data, sep, start, true)
if not s_idx then table.insert(res, wrap_res(str_lib.sub(raw_data, start))); break end
table.insert(res, wrap_res(str_lib.sub(raw_data, start, s_idx - 1)))
start = e_idx + 1; splits = splits + 1
end
end
return { __type = "list", elements = res }
end }
elseif attr_name == "rsplit" then
return { __type = "builtin_method", __name = "rsplit", __call = function(args, kwargs)
local raw_sep = (args and args[1] ~= nil) and args[1] or (kwargs and kwargs.sep)
local maxsplit = unwrap_int((args and args[2] ~= nil) and args[2] or (kwargs and kwargs.maxsplit) or -1)
if maxsplit < 0 then return Utils.get_str_or_bytes_attr(obj, "split").__call({raw_sep, -1}, {}) end
local sep = is_none(raw_sep) and nil or unwrap_arg(raw_sep)
if sep == nil then
local s = str_lib.gsub(raw_data, "%s+$", "")
if s == "" then return { __type = "list", elements = {} } end
local tokens, spans, pos, len = {}, {}, 1, str_lib.len(s)
while pos <= len do
local ns_s, ns_e = str_lib.find(s, "%S+", pos)
if not ns_s then break end
table.insert(tokens, str_lib.sub(s, ns_s, ns_e))
table.insert(spans, {ns_s, ns_e}); pos = ns_e + 1
end
if #tokens <= maxsplit + 1 then
local res = {}; for _, tk in ipairs(tokens) do table.insert(res, wrap_res(tk)) end
return { __type = "list", elements = res }
end
local cut_idx = #tokens - maxsplit
local head_str = str_lib.gsub(str_lib.sub(s, 1, spans[cut_idx][2]), "^%s+", "")
local res = { wrap_res(head_str) }
for i = cut_idx + 1, #tokens do table.insert(res, wrap_res(tokens[i])) end
return { __type = "list", elements = res }
end
if sep == "" then require("Module:PythonScript/Runtime").raise("ValueError", "empty separator") end
local res, cur = {}, str_lib.len(raw_data)
while maxsplit > 0 and cur >= 0 do
local best_i, best_j, s = nil, nil, 1
while s <= cur do
local ni, nj = str_lib.find(raw_data, sep, s, true)
if not ni or ni > cur then break end
if nj <= cur then best_i = ni; best_j = nj end
s = ni + 1
end
if best_i then
table.insert(res, 1, wrap_res(str_lib.sub(raw_data, best_j + 1, cur)))
cur = best_i - 1; maxsplit = maxsplit - 1
else break end
end
table.insert(res, 1, wrap_res(str_lib.sub(raw_data, 1, cur)))
return { __type = "list", elements = res }
end }
elseif attr_name == "join" then
return { __type = "builtin_method", __name = "join", __call = function(args)
local iter_list = extract_iterable_elements(args[1])
local elts = {}
for i, v in ipairs(iter_list) do
local uv = is_bytes_family and unwrap_arg(v) or v
if type(uv) ~= "string" or (not is_bytes_family and type(v) ~= "string") or (is_bytes_family and type(v) == "string") then
require("Module:PythonScript/Runtime").raise("TypeError", "sequence item " .. (i - 1) .. ": expected str or bytes-like instance")
end
table.insert(elts, uv)
end
return wrap_res(table.concat(elts, raw_data))
end }
elseif attr_name == "isalpha" then return { __type = "builtin_method", __name = "isalpha", __call = function() return str_lib.match(raw_data, "^%a+$") ~= nil end }
elseif attr_name == "isdigit" then return { __type = "builtin_method", __name = "isdigit", __call = function() return str_lib.match(raw_data, "^%d+$") ~= nil end }
elseif attr_name == "isalnum" then return { __type = "builtin_method", __name = "isalnum", __call = function() return str_lib.match(raw_data, "^[%w]+$") ~= nil end }
elseif attr_name == "__format__" and is_str then
return {
__type = "builtin_method", __name = "__format__",
__call = function(args)
return require("Module:PythonScript/Runtime").builtins.format({raw_data, args and args[1] or ""})
end
}
elseif attr_name == "format" and is_str then
return {
__type = "builtin_method", __name = "format",
__call = function(args, kwargs)
local Runtime = require("Module:PythonScript/Runtime")
local auto_idx = 1
local L_SEN, R_SEN = "\1__PY_LB__\2", "\1__PY_RB__\2"
local escaped = raw_data:gsub("{{", L_SEN):gsub("}}", R_SEN)
local function resolve_field(match_str)
local field_part, fmt_spec = match_str:match("^([^:]*):(.*)$")
if not field_part then field_part = match_str; fmt_spec = "" end
local key_expr, conv = field_part:match("^([^!]*)!([rsa])$")
if not key_expr then key_expr = field_part end
local root_key, rest_path = key_expr:match("^([^%.%[]+)(.*)$")
if not root_key then root_key = key_expr; rest_path = "" end
local val
if root_key == "" then
val = args[auto_idx]; auto_idx = auto_idx + 1
if val == nil then Runtime.raise("IndexError", "Replacement index out of range") end
elseif root_key:match("^%d+$") then
val = args[tonumber(root_key) + 1]
if val == nil then Runtime.raise("IndexError", "Replacement index " .. root_key .. " out of range") end
else
val = kwargs and kwargs[root_key]
if val == nil then Runtime.raise("KeyError", root_key) end
end
while rest_path and rest_path ~= "" do
local dot_attr, rem1 = rest_path:match("^%.([^%.%[]+)(.*)$")
if dot_attr then val = PyClassModel.get_attr(val, dot_attr); rest_path = rem1
else
local idx_str, rem2 = rest_path:match("^%[(.-)%](.*)$")
if idx_str then
if type(val) == "table" and val.__type == "dict" then
local found = false
for _, p in ipairs(val.pairs) do if tostring(p.key) == idx_str then val = p.value; found = true; break end end
if not found then Runtime.raise("KeyError", idx_str) end
elseif type(val) == "table" and val.elements then
val = val.elements[(tonumber(idx_str) or 0) + 1]
end
rest_path = rem2
else break end
end
end
if conv == "r" then val = Runtime.builtins.repr({val})
elseif conv == "s" then val = Runtime.builtins.str.__call({val})
elseif conv == "a" then val = Runtime.builtins.ascii({val}) end
return Runtime.builtins.format({val, fmt_spec or ""})
end
-- 支援巢狀格式規範,如 "{0:{1}}".format("hi", ">5")
while escaped:find("{[^{}]*{.-}[^{}]*}") do
escaped = str_lib.gsub(escaped, "{([^{}]+)}", resolve_field)
end
local formatted = str_lib.gsub(escaped, "{(.-)}", resolve_field)
return (formatted:gsub(L_SEN, "{"):gsub(R_SEN, "}"))
end
}
elseif attr_name == "__mod__" then
return {
__type = "builtin_method",
__call = function(args)
local right = args[1]; local Runtime = require("Module:PythonScript/Runtime")
if type(right) == "table" and right.__type == "dict" then
return wrap_res((str_lib.gsub(raw_data, "%%%(.-%)[diouxXeEfFgGcrsa]", function(match_str)
local key = str_lib.match(match_str, "%%%((.-)%)"); local fmt = str_lib.match(match_str, "[diouxXeEfFgGcrsa]$"); local val = nil
for _, p in ipairs(right.pairs) do if unwrap_arg(p.key) == key then val = p.value; break end end
if val == nil then Runtime.raise("KeyError", key) end
if fmt == "s" or fmt == "r" or fmt == "a" then
local fn = (fmt == "r" or fmt == "a") and Runtime.builtins.repr or Runtime.builtins.str.__call
local success, s = safe_pcall(fn, {unwrap_arg(val)}); return success and s or tostring(val)
else
local nval = unwrap_int(val)
if type(nval) == "number" then return str_lib.format("%" .. fmt, nval) end
local success, s = safe_pcall(Runtime.builtins.str.__call, {unwrap_arg(val)}); return success and s or tostring(val)
end
end)))
end
local vals = (type(right) == "table" and right.__type == "tuple") and right.elements or {right}; local v_idx = 1
return wrap_res((str_lib.gsub(raw_data, "%%[%-+#0]*[%d%.]*[diouxXeEfFgGcrsa%%]", function(match_str)
if match_str == "%%" then return "%" end
local val = vals[v_idx]; v_idx = v_idx + 1
if val == nil then Runtime.raise("TypeError", "not enough arguments for format string") end
local fmt = str_lib.sub(match_str, -1)
if fmt == "s" or fmt == "r" or fmt == "a" then
local fn = (fmt == "r" or fmt == "a") and Runtime.builtins.repr or Runtime.builtins.str.__call
local success, s = safe_pcall(fn, {unwrap_arg(val)}); return success and s or tostring(val)
else
local nval = unwrap_int(val)
if type(nval) == "number" then return str_lib.format(match_str, nval) end
local success, s = safe_pcall(Runtime.builtins.str.__call, {unwrap_arg(val)}); return success and s or tostring(val)
end
end)))
end
}
elseif attr_name == "__iter__" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Runtime").builtins.iter({obj}) end }
elseif attr_name == "casefold" then return { __type = "builtin_method", __call = function() return wrap_res(str_lib.lower(raw_data)) end }
elseif attr_name == "center" then return { __type = "builtin_method", __call = function(args)
local w = unwrap_int(args[1] or 0); local fill = unwrap_arg(args[2]) or " "
local len = str_lib.len(raw_data)
if len >= w then return wrap_res(raw_data) end
local pad = w - len; local left = math.floor(pad / 2); local right = pad - left
return wrap_res(str_lib.rep(fill, left) .. raw_data .. str_lib.rep(fill, right))
end }
elseif attr_name == "expandtabs" then return { __type = "builtin_method", __call = function(args)
local tabsize = unwrap_int(args[1] or 8)
if tabsize <= 0 then return wrap_res((str_lib.gsub(raw_data, "\t", ""))) end
return wrap_res((str_lib.gsub(raw_data, "([^\t]*)\t", function(s) return s .. str_lib.rep(" ", tabsize - (str_lib.len(s) % tabsize)) end)))
end }
elseif attr_name == "format_map" and is_str then return { __type = "builtin_method", __call = function(args)
local mapping = args[1]
return wrap_res((str_lib.gsub(raw_data, "{(.-)}", function(key)
local Runtime = require("Module:PythonScript/Runtime")
if type(mapping) == "table" and mapping.__type == "dict" then
for _, p in ipairs(mapping.pairs) do
if p.key == key then
local success, s = safe_pcall(Runtime.builtins.str.__call, {p.value}); return success and s or tostring(p.value)
end
end
Runtime.raise("KeyError", key)
elseif type(mapping) == "table" and mapping.__type == "instance" then
local Evaluator = require("Module:PythonScript/Evaluator")
local getitem = PyClassModel.get_attr(mapping, "__getitem__")
return Runtime.builtins.str.__call({Evaluator.invoke(getitem, {key}, {})})
end
Runtime.raise("TypeError", "format_map() argument must be a mapping")
end)))
end }
elseif attr_name == "isascii" then return { __type = "builtin_method", __call = function() return str_lib.match(raw_data, "^[\0-\127]*$") ~= nil end }
elseif attr_name == "isdecimal" and is_str then return { __type = "builtin_method", __call = function() return raw_data ~= "" and str_lib.match(raw_data, "^[0-9]+$") ~= nil end }
elseif attr_name == "isidentifier" and is_str then return { __type = "builtin_method", __call = function() return raw_data ~= "" and str_lib.match(raw_data, "^[%a_][%w_]*$") ~= nil end }
elseif attr_name == "islower" then return { __type = "builtin_method", __call = function() return str_lib.match(raw_data, "[%a]") ~= nil and raw_data == str_lib.lower(raw_data) end }
elseif attr_name == "isnumeric" and is_str then return { __type = "builtin_method", __call = function() return raw_data ~= "" and str_lib.match(raw_data, "^[0-9]+$") ~= nil end }
elseif attr_name == "isprintable" and is_str then return { __type = "builtin_method", __call = function() return not str_lib.find(raw_data, "[%c]") end }
elseif attr_name == "isspace" then return { __type = "builtin_method", __call = function() return raw_data ~= "" and str_lib.match(raw_data, "^%s+$") ~= nil end }
elseif attr_name == "istitle" then return { __type = "builtin_method", __call = function()
if not str_lib.match(raw_data, "[%a]") then return false end
local expected = str_lib.gsub(raw_data, "(%w)(%w*)", function(first, rest) return str_lib.upper(first) .. str_lib.lower(rest) end)
return raw_data == expected
end }
elseif attr_name == "isupper" then return { __type = "builtin_method", __call = function() return str_lib.match(raw_data, "[%a]") ~= nil and raw_data == str_lib.upper(raw_data) end }
elseif attr_name == "ljust" then return { __type = "builtin_method", __call = function(args)
local w = unwrap_int(args[1] or 0); local fill = unwrap_arg(args[2]) or " "
local len = str_lib.len(raw_data)
return wrap_res(len >= w and raw_data or raw_data .. str_lib.rep(fill, w - len))
end }
elseif attr_name == "rjust" then return { __type = "builtin_method", __call = function(args)
local w = unwrap_int(args[1] or 0); local fill = unwrap_arg(args[2]) or " "
local len = str_lib.len(raw_data)
return wrap_res(len >= w and raw_data or str_lib.rep(fill, w - len) .. raw_data)
end }
elseif attr_name == "zfill" then return { __type = "builtin_method", __call = function(args)
local w = unwrap_int(args[1] or 0); local len = str_lib.len(raw_data)
if len >= w then return wrap_res(raw_data) end
local pad = w - len
if str_lib.match(raw_data, "^[%+%-]") then return wrap_res(str_lib.sub(raw_data, 1, 1) .. str_lib.rep("0", pad) .. str_lib.sub(raw_data, 2)) end
return wrap_res(str_lib.rep("0", pad) .. raw_data)
end }
elseif attr_name == "partition" then return { __type = "builtin_method", __call = function(args)
local sep = unwrap_arg(args[1]); if not sep or sep == "" then require("Module:PythonScript/Runtime").raise("ValueError", "empty separator") end
local i, j = str_lib.find(raw_data, sep, 1, true)
if i then return { __type = "tuple", elements = { wrap_res(str_lib.sub(raw_data, 1, i - 1)), wrap_res(sep), wrap_res(str_lib.sub(raw_data, j + 1)) } }
else return { __type = "tuple", elements = { wrap_res(raw_data), wrap_res(""), wrap_res("") } } end
end }
elseif attr_name == "rpartition" then return { __type = "builtin_method", __call = function(args)
local sep = unwrap_arg(args[1]); if not sep or sep == "" then require("Module:PythonScript/Runtime").raise("ValueError", "empty separator") end
local i, j, cur = nil, nil, 1
while true do
local ni, nj = str_lib.find(raw_data, sep, cur, true)
if not ni then break end; i, j = ni, nj; cur = ni + 1
end
if i then return { __type = "tuple", elements = { wrap_res(str_lib.sub(raw_data, 1, i - 1)), wrap_res(sep), wrap_res(str_lib.sub(raw_data, j + 1)) } }
else return { __type = "tuple", elements = { wrap_res(""), wrap_res(""), wrap_res(raw_data) } } end
end }
elseif attr_name == "removeprefix" then return { __type = "builtin_method", __call = function(args)
local p = unwrap_arg(args[1]) or ""; if p == "" then return wrap_res(raw_data) end
local plen = str_lib.len(p)
if str_lib.sub(raw_data, 1, plen) == p then return wrap_res(str_lib.sub(raw_data, plen + 1)) else return wrap_res(raw_data) end
end }
elseif attr_name == "removesuffix" then return { __type = "builtin_method", __call = function(args)
local s = unwrap_arg(args[1]) or ""; if s == "" then return wrap_res(raw_data) end
local slen = str_lib.len(s)
if str_lib.sub(raw_data, -slen) == s then return wrap_res(str_lib.sub(raw_data, 1, -(slen + 1))) else return wrap_res(raw_data) end
end }
elseif attr_name == "splitlines" then return { __type = "builtin_method", __call = function(args, kwargs)
local keepends = (args and args[1] ~= nil) and args[1] or (kwargs and kwargs.keepends) or false
keepends = require("Module:PythonScript/Runtime").builtins.bool.__call({keepends})
local i, len = 1, str_lib.len(raw_data); local list = {}
while i <= len do
local rn = str_lib.find(raw_data, "\r\n", i, true); local n = str_lib.find(raw_data, "\n", i, true); local r = str_lib.find(raw_data, "\r", i, true)
local next_match, match_len, end_char = len + 1, 0, ""
if rn and rn < next_match then next_match = rn; match_len = 2; end_char = "\r\n" end
if n and n < next_match then next_match = n; match_len = 1; end_char = "\n" end
if r and r < next_match then next_match = r; match_len = 1; end_char = "\r" end
if next_match <= len then
local line = str_lib.sub(raw_data, i, next_match - 1)
if keepends then line = line .. end_char end
table.insert(list, wrap_res(line)); i = next_match + match_len
else table.insert(list, wrap_res(str_lib.sub(raw_data, i))); break end
end
return { __type = "list", elements = list }
end }
elseif attr_name == "swapcase" then return { __type = "builtin_method", __call = function()
return wrap_res((str_lib.gsub(raw_data, ".", function(c)
local l, u = str_lib.lower(c), str_lib.upper(c)
if c == l then return u elseif c == u then return l else return c end
end)))
end }
elseif attr_name == "translate" then return { __type = "builtin_method", __call = function(args)
local table_map = args[1]; local delete_bytes = unwrap_arg(args[2]); local del_set = {}
if delete_bytes and type(delete_bytes) == "string" then
for i = 1, #delete_bytes do del_set[string.byte(delete_bytes, i)] = true end
end
if is_bytes_family and type(table_map) == "table" and (table_map.__type == "bytes" or table_map.__type == "bytearray") then
local tdata = table_map._data
if #tdata ~= 256 then require("Module:PythonScript/Runtime").raise("ValueError", "translation table must be 256 characters long") end
local out = {}
for i = 1, #raw_data do
local b = string.byte(raw_data, i)
if not del_set[b] then table.insert(out, tdata:sub(b + 1, b + 1)) end
end
return wrap_res(table.concat(out))
end
if type(table_map) ~= "table" or table_map.__type ~= "dict" then require("Module:PythonScript/Runtime").raise("TypeError", "translate() takes a dict or 256-byte table") end
local lua_map = {}; for _, p in ipairs(table_map.pairs) do lua_map[p.key] = p.value end
local res = {}
local function process_cp(cp)
if del_set[cp] then return end
local mapped = lua_map[cp]
if mapped == nil then table.insert(res, str_lib.char(cp))
elseif is_none(mapped) then
elseif type(mapped) == "number" then table.insert(res, str_lib.char(mapped))
else local um = unwrap_arg(mapped); if type(um) == "string" then table.insert(res, um) end end
end
if is_bytes_family then for i = 1, str_lib.len(raw_data) do process_cp(str_lib.byte(raw_data, i)) end
else for cp in mw.ustring.gcodepoint(raw_data) do process_cp(cp) end end
return wrap_res(table.concat(res))
end }
elseif attr_name == "encode" and is_str then return { __type = "builtin_method", __call = function()
local B = require("Module:PythonScript/Runtime").builtins
return B.bytes and B.bytes.__call({raw_data}) or { __type = "bytes", _data = raw_data }
end }
elseif attr_name == "decode" and is_bytes_family then return { __type = "builtin_method", __call = function() return raw_data end }
elseif attr_name == "hex" and is_bytes_family then return { __type = "builtin_method", __call = function(args)
local sep = args and args[1] and unwrap_arg(args[1]) or ""
local bytes_hex = {}
for i = 1, #raw_data do table.insert(bytes_hex, string.format("%02x", string.byte(raw_data, i))) end
return table.concat(bytes_hex, sep)
end }
elseif attr_name == "fromhex" and is_bytes_family then
return require("Module:PythonScript/Runtime").builtins[obj.__type].__dict["fromhex"].__func
elseif attr_name == "maketrans" then
if is_bytes_family then
return { __type = "builtin_method", __call = function(args)
local a, b = unwrap_arg(args[1]), unwrap_arg(args[2])
if type(a) ~= "string" or type(b) ~= "string" then require("Module:PythonScript/Runtime").raise("TypeError", "maketrans arguments must be bytes-like objects") end
if #a ~= #b then require("Module:PythonScript/Runtime").raise("ValueError", "maketrans arguments must have same length") end
local tbl = {}; for i = 0, 255 do tbl[i + 1] = string.char(i) end
for i = 1, #a do tbl[string.byte(a, i) + 1] = b:sub(i, i) end
return { __type = "bytes", _data = table.concat(tbl) }
end }
end
return require("Module:PythonScript/Runtime").builtins.str.__dict["maketrans"].__func
elseif obj.__type == "bytearray" then
local Runtime = require("Module:PythonScript/Runtime")
if attr_name == "append" then return { __type = "builtin_method", __call = function(args)
local b = unwrap_int(args[1])
if type(b) ~= "number" or math.floor(b) ~= b then Runtime.raise("TypeError", "an integer is required") end
if b < 0 or b > 255 then Runtime.raise("ValueError", "byte must be in range(0, 256)") end
obj._data = obj._data .. string.char(b); return nil
end }
elseif attr_name == "extend" then return { __type = "builtin_method", __call = function(args)
local ext = Runtime.builtins.bytes.__call({args[1]})
obj._data = obj._data .. ext._data; return nil
end }
elseif attr_name == "insert" then return { __type = "builtin_method", __call = function(args)
local idx, b = unwrap_int(args[1]), unwrap_int(args[2])
if type(b) ~= "number" or math.floor(b) ~= b then Runtime.raise("TypeError", "an integer is required") end
if b < 0 or b > 255 then Runtime.raise("ValueError", "byte must be in range(0, 256)") end
local len = #obj._data
if idx < 0 then idx = math.max(0, len + idx) elseif idx > len then idx = len end
obj._data = obj._data:sub(1, idx) .. string.char(b) .. obj._data:sub(idx + 1); return nil
end }
elseif attr_name == "pop" then return { __type = "builtin_method", __call = function(args)
local len = #obj._data
if len == 0 then Runtime.raise("IndexError", "pop from empty bytearray") end
local raw_idx = (args and not is_none(args[1])) and unwrap_int(args[1]) or nil
local idx = raw_idx and (raw_idx < 0 and (len + raw_idx + 1) or (raw_idx + 1)) or len
if idx < 1 or idx > len then Runtime.raise("IndexError", "pop index out of range") end
local val = string.byte(obj._data, idx)
obj._data = obj._data:sub(1, idx - 1) .. obj._data:sub(idx + 1); return val
end }
elseif attr_name == "remove" then return { __type = "builtin_method", __call = function(args)
local b = unwrap_int(args[1])
if type(b) ~= "number" or math.floor(b) ~= b then Runtime.raise("TypeError", "an integer is required") end
if b < 0 or b > 255 then Runtime.raise("ValueError", "byte must be in range(0, 256)") end
local idx = string.find(obj._data, string.char(b), 1, true)
if not idx then Runtime.raise("ValueError", "value not found in bytearray") end
obj._data = obj._data:sub(1, idx - 1) .. obj._data:sub(idx + 1); return nil
end }
elseif attr_name == "clear" then return { __type = "builtin_method", __call = function() obj._data = ""; return nil end }
elseif attr_name == "copy" then return { __type = "builtin_method", __call = function() return { __type = "bytearray", _data = obj._data } end }
elseif attr_name == "reverse" then return { __type = "builtin_method", __call = function() obj._data = string.reverse(obj._data); return nil end }
end
end
elseif is_memview then
local Runtime = require("Module:PythonScript/Runtime")
if attr_name == "release" then return { __type = "builtin_method", __call = function() obj._released = true; return nil end } end
if obj._released then Runtime.raise("ValueError", "operation forbidden on released memoryview object") end
if attr_name == "tobytes" then return { __type = "builtin_method", __call = function() return { __type = "bytes", _data = obj._data } end }
elseif attr_name == "tolist" then return { __type = "builtin_method", __call = function()
local elts = {}; for i = 1, #obj._data do table.insert(elts, string.byte(obj._data, i)) end
return { __type = "list", elements = elts }
end }
elseif attr_name == "hex" then return { __type = "builtin_method", __call = function(args)
local sep = (args and type(args[1]) == "string") and args[1] or ""
local bytes_hex = {}; for i = 1, #obj._data do table.insert(bytes_hex, string.format("%02x", string.byte(obj._data, i))) end
return table.concat(bytes_hex, sep)
end }
elseif attr_name == "toreadonly" then return { __type = "builtin_method", __call = function()
return { __type = "memoryview", _data = obj._data, _obj = obj._obj, _readonly = true, _released = false }
end }
elseif attr_name == "obj" then return obj._obj
elseif attr_name == "nbytes" then return #obj._data
elseif attr_name == "readonly" then return obj._readonly
elseif attr_name == "itemsize" then return 1
elseif attr_name == "ndim" then return 1
elseif attr_name == "shape" then return { __type = "tuple", elements = { #obj._data } }
elseif attr_name == "strides" then return { __type = "tuple", elements = { 1 } }
elseif attr_name == "format" then return "B"
elseif attr_name == "__iter__" then return { __type = "builtin_method", __call = function() return Runtime.builtins.iter({obj}) end }
end
end
return nil
end
-- ==========================================
-- 2. 數值家族方法 (int, float, bool, complex)
-- ==========================================
function Utils.get_number_attr(obj, attr_name)
if attr_name == "__format__" then
return {
__type = "builtin_method", __name = "__format__",
__call = function(args)
return require("Module:PythonScript/Runtime").builtins.format({obj, args and args[1] or ""})
end
}
end
if type(obj) == "number" or type(obj) == "boolean" or (type(obj) == "table" and obj.__type == "int") then
local is_integer = (type(obj) == "boolean") or (type(obj) == "number" and math.floor(obj) == obj) or (type(obj) == "table" and obj.__type == "int")
if is_integer then
local int_val = (type(obj) == "boolean") and (obj and 1 or 0) or obj
if attr_name == "real" or attr_name == "numerator" then return int_val end
if attr_name == "imag" or attr_name == "k" or attr_name == "jk" then return 0 end
if attr_name == "denominator" then return 1 end
if attr_name == "conjugate" then return { __type = "builtin_method", __call = function() return int_val end } end
if attr_name == "is_integer" then return { __type = "builtin_method", __call = function() return true end } end
if attr_name == "as_integer_ratio" then
return { __type = "builtin_method", __call = function() return { __type = "tuple", elements = { int_val, 1 } } end }
end
if attr_name == "bit_length" then
return { __type = "builtin_method", __call = function()
if int_val == 0 or (type(int_val) == "table" and #int_val.limbs == 1 and int_val.limbs[1] == 0) then return 0 end
local bstr = require("Module:PythonScript/Runtime").builtins.bin({int_val}):gsub("^%-?0b", "")
return #bstr
end }
end
if attr_name == "bit_count" then
return { __type = "builtin_method", __call = function()
if int_val == 0 or (type(int_val) == "table" and #int_val.limbs == 1 and int_val.limbs[1] == 0) then return 0 end
local bstr = require("Module:PythonScript/Runtime").builtins.bin({int_val}):gsub("^%-?0b", "")
local _, count = bstr:gsub("1", "")
return count
end }
end
if attr_name == "to_bytes" then
return { __type = "builtin_method", __call = function(args, kwargs)
local Runtime = require("Module:PythonScript/Runtime")
local length = unwrap_int((args and args[1]) or (kwargs and kwargs.length) or 1)
local byteorder = (args and args[2]) or (kwargs and kwargs.byteorder) or "big"
local signed = (args and args[3] ~= nil and args[3]) or (kwargs and kwargs.signed) or false
local n = unwrap_int(int_val)
if n < 0 and not signed then Runtime.raise("OverflowError", "can't convert negative int to unsigned") end
if n < 0 then n = (2 ^ (length * 8)) + n end
local bytes_arr = {}
for i = 1, length do
local b = n % 256; n = math.floor(n / 256)
if byteorder == "little" then table.insert(bytes_arr, string.char(b))
else table.insert(bytes_arr, 1, string.char(b)) end
end
if n > 0 then Runtime.raise("OverflowError", "int too big to convert") end
return { __type = "bytes", _data = table.concat(bytes_arr) }
end }
end
if attr_name == "from_bytes" then
return { __type = "builtin_method", __call = function(args, kwargs)
local Runtime = require("Module:PythonScript/Runtime")
local b_obj = args[1]
if type(b_obj) == "table" and b_obj.__type == "class" then b_obj = args[2] end
local b_data = Runtime.builtins.bytes.__call({b_obj})._data
local byteorder = (args and args[2] and type(args[2]) == "string" and args[2]) or (args and args[3]) or (kwargs and kwargs.byteorder) or "big"
local res = 0
if byteorder == "little" then for i = #b_data, 1, -1 do res = res * 256 + string.byte(b_data, i) end
else for i = 1, #b_data do res = res * 256 + string.byte(b_data, i) end end
return res
end }
end
end
end
if type(obj) == "number" then
if attr_name == "real" then return obj end
if attr_name == "imag" or attr_name == "k" or attr_name == "jk" then return 0.0 end
if attr_name == "conjugate" then return { __type = "builtin_method", __call = function() return obj end } end
if attr_name == "is_integer" then return { __type = "builtin_method", __call = function() return math.floor(obj) == obj end } end
elseif type(obj) == "table" and obj.__type == "complex" then
if attr_name == "real" then return obj.real or 0 end
if attr_name == "imag" then
return require("Module:PythonScript/Runtime/Utils").make_quaternion_imag(obj.imag or 0, obj.k or 0, obj.jk or 0)
end
if attr_name == "k" then return obj.k or 0 end
if attr_name == "jk" then return obj.jk or 0 end
if attr_name == "conjugate" then
return {
__type = "builtin_method",
__call = function()
return require("Module:PythonScript/Runtime/Cmath").Cmath.make_complex(
obj.real or 0, -(obj.imag or 0), -(obj.k or 0), -(obj.jk or 0)
)
end
}
end
end
return nil
end
-- ==========================================
-- 3. 容器家族方法 (dict, set, frozenset, list, tuple)
-- ==========================================
function Utils.get_container_attr(obj, attr_name)
if type(obj) ~= "table" then return nil end
if obj.__type == "dict" then
if attr_name == "items" or attr_name == "keys" or attr_name == "values" then
return { __type = "builtin_method", __name = attr_name, __call = function()
local function get_view_elements()
local res = {}
for _, p in ipairs(obj.pairs or {}) do
if attr_name == "keys" then table.insert(res, p.key)
elseif attr_name == "values" then table.insert(res, p.value)
else table.insert(res, {__type="tuple", elements={p.key, p.value}}) end
end
return res
end
local view_methods = {
__len__ = { __type = "builtin_method", __call = function() return #(obj.pairs or {}) end },
mapping = { __type = "property", fget = { __type = "builtin_method", __call = function()
local MappingProxyType = PyClassModel.make_class("MappingProxyType", {}, {
__getitem__ = { __type="builtin_method", __call = function(a)
for _, p in ipairs(obj.pairs) do if is_eq(p.key, a[2]) then return p.value end end
require("Module:PythonScript/Runtime").raise("KeyError", tostring(a[2]))
end},
__str__ = { __type="builtin_method", __call = function() return "mappingproxy(" .. require("Module:PythonScript/Runtime").builtins.repr({obj}) .. ")" end }
})
MappingProxyType.__dict.__repr__ = MappingProxyType.__dict.__str__
local proxy = PyClassModel.make_instance(MappingProxyType); proxy.__dict._mapping = obj; return proxy
end}},
__iter__ = { __type = "builtin_method", __call = function()
return require("Module:PythonScript/Runtime").builtins.iter({ {__type="list", elements=get_view_elements()} })
end},
__reversed__ = { __type = "builtin_method", __call = function()
local elts = get_view_elements(); local rev = {}
for i = #elts, 1, -1 do table.insert(rev, elts[i]) end
return require("Module:PythonScript/Runtime").builtins.iter({ {__type="list", elements=rev} })
end},
__contains__ = { __type = "builtin_method", __call = function(args)
for _, e in ipairs(get_view_elements()) do if is_eq(e, args[2]) then return true end end
return false
end},
__repr__ = { __type = "builtin_method", __call = function()
local inner = require("Module:PythonScript/Runtime").builtins.repr({ {__type="list", elements=get_view_elements()} })
return "dict_" .. attr_name .. "(" .. inner .. ")"
end},
__eq__ = { __type = "builtin_method", __call = function(args)
if attr_name == "values" then return args[1] == args[2] end
return is_eq({__type="set", elements=get_view_elements()}, {__type="set", elements=extract_iterable_elements(args[2])})
end},
__and__ = { __type = "builtin_method", __call = function(args) return Utils.get_container_attr({__type="set", elements=get_view_elements()}, "intersection").__call({args[2]}) end},
__or__ = { __type = "builtin_method", __call = function(args) return Utils.get_container_attr({__type="set", elements=get_view_elements()}, "union").__call({args[2]}) end},
__sub__ = { __type = "builtin_method", __call = function(args) return Utils.get_container_attr({__type="set", elements=get_view_elements()}, "difference").__call({args[2]}) end},
__xor__ = { __type = "builtin_method", __call = function(args) return Utils.get_container_attr({__type="set", elements=get_view_elements()}, "symmetric_difference").__call({args[2]}) end}
}
view_methods.__str__ = view_methods.__repr__
return PyClassModel.make_instance(PyClassModel.make_class("dict_" .. attr_name, {}, view_methods))
end }
elseif attr_name == "get" then return { __type = "builtin_method", __name = "get", __call = function(args)
local k = args[1]; local default = (#args >= 2) and args[2] or require("Module:PythonScript/Runtime").builtins.None
for _, p in ipairs(obj.pairs) do if is_eq(p.key, k) then return p.value end end
return default
end }
elseif attr_name == "setdefault" then return { __type = "builtin_method", __name = "setdefault", __call = function(args)
local k = args[1]; local default = (#args >= 2) and args[2] or require("Module:PythonScript/Runtime").builtins.None
for _, p in ipairs(obj.pairs) do if is_eq(p.key, k) then return p.value end end
table.insert(obj.pairs, {key = k, value = default})
if obj.__scope_ref and type(k) == "string" then obj.__scope_ref[k] = default end
return default
end }
elseif attr_name == "pop" then return { __type = "builtin_method", __name = "pop", __call = function(args)
local k = args[1]
for i, p in ipairs(obj.pairs) do
if is_eq(p.key, k) then
table.remove(obj.pairs, i)
if obj.__scope_ref and type(k) == "string" then obj.__scope_ref[k] = nil end
return p.value
end
end
if #args >= 2 then return args[2] end
require("Module:PythonScript/Runtime").raise("KeyError", tostring(k))
end }
elseif attr_name == "popitem" then return { __type = "builtin_method", __name = "popitem", __call = function()
if #obj.pairs == 0 then require("Module:PythonScript/Runtime").raise("KeyError", "'popitem(): dictionary is empty'") end
local p = table.remove(obj.pairs)
if obj.__scope_ref and type(p.key) == "string" then obj.__scope_ref[p.key] = nil end
return { __type = "tuple", elements = { p.key, p.value } }
end }
elseif attr_name == "clear" then return { __type = "builtin_method", __name = "clear", __call = function()
obj.pairs = {}; if obj.__scope_ref then for k in pairs(obj.__scope_ref) do obj.__scope_ref[k] = nil end end; return nil
end }
elseif attr_name == "copy" then return { __type = "builtin_method", __name = "copy", __call = function()
local res = {__type="dict", pairs={}}; for _, p in ipairs(obj.pairs) do table.insert(res.pairs, {key=p.key, value=p.value}) end; return res
end }
elseif attr_name == "update" then return { __type = "builtin_method", __name = "update", __call = function(args, kwargs)
local other = args and args[1]
local function put_kv(k, v)
local found = false
for _, p in ipairs(obj.pairs) do if is_eq(p.key, k) then p.value = v; found = true; break end end
if not found then table.insert(obj.pairs, {key=k, value=v}) end
if obj.__scope_ref and type(k) == "string" then obj.__scope_ref[k] = v end
end
if other ~= nil and not is_none(other) then
if type(other) == "table" and other.__type == "dict" then
for _, op in ipairs(other.pairs) do put_kv(op.key, op.value) end
else
for _, item in ipairs(extract_iterable_elements(other)) do
if type(item) == "table" and (item.__type == "list" or item.__type == "tuple") and #item.elements == 2 then
put_kv(item.elements[1], item.elements[2])
else require("Module:PythonScript/Runtime").raise("ValueError", "dictionary update sequence element has length ~= 2") end
end
end
end
if kwargs then for k, v in pairs(kwargs) do put_kv(k, v) end end
return nil
end }
elseif attr_name == "fromkeys" then return { __type = "builtin_method", __name = "fromkeys", __call = function(args)
local iter_arg, val_arg = args[1], args[2]
if type(iter_arg) == "table" and iter_arg.__type == "class" then iter_arg = args[2]; val_arg = args[3] end
if val_arg == nil then val_arg = require("Module:PythonScript/Runtime").builtins.None end
local res = { __type = "dict", pairs = {} }
for _, k in ipairs(extract_iterable_elements(iter_arg)) do
local found = false
for _, p in ipairs(res.pairs) do if is_eq(p.key, k) then p.value = val_arg; found = true; break end end
if not found then table.insert(res.pairs, {key = k, value = val_arg}) end
end
return res
end }
elseif attr_name == "__iter__" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Runtime").builtins.iter({obj}) end }
elseif attr_name == "__len__" then return { __type = "builtin_method", __name = "__len__", __call = function() return #(obj.pairs or {}) end }
elseif attr_name == "__contains__" then return { __type = "builtin_method", __name = "__contains__", __call = function(args)
local k = args[1]
for _, p in ipairs(obj.pairs) do if is_eq(p.key, k) then return true end end
return false
end }
elseif attr_name == "__getitem__" then return { __type = "builtin_method", __name = "__getitem__", __call = function(args)
local k = args[1]
for _, p in ipairs(obj.pairs) do if is_eq(p.key, k) then return p.value end end
require("Module:PythonScript/Runtime").raise("KeyError", tostring(k))
end }
elseif attr_name == "__setitem__" then return { __type = "builtin_method", __name = "__setitem__", __call = function(args)
local k, v = args[1], args[2]
local found = false
for _, p in ipairs(obj.pairs) do if is_eq(p.key, k) then p.value = v; found = true; break end end
if not found then table.insert(obj.pairs, {key = k, value = v}) end
if obj.__scope_ref and type(k) == "string" then obj.__scope_ref[k] = v end
return require("Module:PythonScript/Runtime").builtins.None
end }
elseif attr_name == "__delitem__" then return { __type = "builtin_method", __name = "__delitem__", __call = function(args)
local k = args[1]
for i, p in ipairs(obj.pairs) do
if is_eq(p.key, k) then
table.remove(obj.pairs, i)
if obj.__scope_ref and type(k) == "string" then obj.__scope_ref[k] = nil end
return require("Module:PythonScript/Runtime").builtins.None
end
end
require("Module:PythonScript/Runtime").raise("KeyError", tostring(k))
end }
end
elseif obj.__type == "set" or obj.__type == "frozenset" then
local is_mutable_set = (obj.__type == "set")
local set_type = obj.__type
if is_mutable_set and attr_name == "add" then return { __type = "builtin_method", __name = "add", __call = function(args)
local val = args[1]; local exists = false
for _, e in ipairs(obj.elements) do if is_eq(e, val) then exists = true; break end end
if not exists then table.insert(obj.elements, val) end; return nil
end }
elseif is_mutable_set and attr_name == "remove" then return { __type = "builtin_method", __name = "remove", __call = function(args)
local val = args[1]
for i, e in ipairs(obj.elements) do if is_eq(e, val) then table.remove(obj.elements, i); return nil end end
require("Module:PythonScript/Runtime").raise("KeyError", tostring(val))
end }
elseif is_mutable_set and attr_name == "discard" then return { __type = "builtin_method", __name = "discard", __call = function(args)
local val = args[1]
for i, e in ipairs(obj.elements) do if is_eq(e, val) then table.remove(obj.elements, i); break end end; return nil
end }
elseif is_mutable_set and attr_name == "pop" then return { __type = "builtin_method", __name = "pop", __call = function()
if #obj.elements == 0 then require("Module:PythonScript/Runtime").raise("KeyError", "'pop from an empty set'") end
return table.remove(obj.elements, 1)
end }
elseif is_mutable_set and attr_name == "clear" then return { __type = "builtin_method", __name = "clear", __call = function() obj.elements = {}; return nil end }
elseif attr_name == "copy" then return { __type = "builtin_method", __name = "copy", __call = function()
local res = {__type = set_type, elements = {}}; for _, v in ipairs(obj.elements) do table.insert(res.elements, v) end; return res
end }
elseif attr_name == "union" then return { __type = "builtin_method", __name = "union", __call = function(args)
local res = {__type = set_type, elements = {}}
for _, v in ipairs(obj.elements) do table.insert(res.elements, v) end
for _, other in ipairs(args or {}) do
for _, v in ipairs(extract_iterable_elements(other)) do
local exists = false
for _, e in ipairs(res.elements) do if is_eq(e, v) then exists = true; break end end
if not exists then table.insert(res.elements, v) end
end
end
return res
end }
elseif attr_name == "intersection" then return { __type = "builtin_method", __name = "intersection", __call = function(args)
local curr = {}; for _, v in ipairs(obj.elements) do table.insert(curr, v) end
for _, other in ipairs(args or {}) do
local other_elts = extract_iterable_elements(other); local next_curr = {}
for _, v in ipairs(curr) do
for _, v2 in ipairs(other_elts) do if is_eq(v, v2) then table.insert(next_curr, v); break end end
end
curr = next_curr
end
return {__type = set_type, elements = curr}
end }
elseif attr_name == "difference" then return { __type = "builtin_method", __name = "difference", __call = function(args)
local curr = {}; for _, v in ipairs(obj.elements) do table.insert(curr, v) end
for _, other in ipairs(args or {}) do
local other_elts = extract_iterable_elements(other); local next_curr = {}
for _, v in ipairs(curr) do
local found = false
for _, v2 in ipairs(other_elts) do if is_eq(v, v2) then found = true; break end end
if not found then table.insert(next_curr, v) end
end
curr = next_curr
end
return {__type = set_type, elements = curr}
end }
elseif attr_name == "symmetric_difference" then return { __type = "builtin_method", __name = "symmetric_difference", __call = function(args)
local other_elts = extract_iterable_elements(args[1]); local res = {__type = set_type, elements = {}}
for _, v in ipairs(obj.elements) do
local found = false
for _, v2 in ipairs(other_elts) do if is_eq(v, v2) then found = true; break end end
if not found then table.insert(res.elements, v) end
end
for _, v2 in ipairs(other_elts) do
local found = false
for _, v in ipairs(obj.elements) do if is_eq(v, v2) then found = true; break end end
if not found then
local dup = false
for _, e in ipairs(res.elements) do if is_eq(e, v2) then dup = true; break end end
if not dup then table.insert(res.elements, v2) end
end
end
return res
end }
elseif is_mutable_set and attr_name == "update" then return { __type = "builtin_method", __name = "update", __call = function(args)
obj.elements = Utils.get_container_attr(obj, "union").__call(args).elements; return nil
end }
elseif is_mutable_set and attr_name == "intersection_update" then return { __type = "builtin_method", __name = "intersection_update", __call = function(args)
obj.elements = Utils.get_container_attr(obj, "intersection").__call(args).elements; return nil
end }
elseif is_mutable_set and attr_name == "difference_update" then return { __type = "builtin_method", __name = "difference_update", __call = function(args)
obj.elements = Utils.get_container_attr(obj, "difference").__call(args).elements; return nil
end }
elseif is_mutable_set and attr_name == "symmetric_difference_update" then return { __type = "builtin_method", __name = "symmetric_difference_update", __call = function(args)
obj.elements = Utils.get_container_attr(obj, "symmetric_difference").__call(args).elements; return nil
end }
elseif attr_name == "issubset" then return { __type = "builtin_method", __name = "issubset", __call = function(args)
local other_elts = extract_iterable_elements(args[1])
for _, v in ipairs(obj.elements) do
local found = false
for _, v2 in ipairs(other_elts) do if is_eq(v, v2) then found = true; break end end
if not found then return false end
end
return true
end }
elseif attr_name == "issuperset" then return { __type = "builtin_method", __name = "issuperset", __call = function(args)
local other_elts = extract_iterable_elements(args[1])
for _, v2 in ipairs(other_elts) do
local found = false
for _, v in ipairs(obj.elements) do if is_eq(v, v2) then found = true; break end end
if not found then return false end
end
return true
end }
elseif attr_name == "isdisjoint" then return { __type = "builtin_method", __name = "isdisjoint", __call = function(args)
local other_elts = extract_iterable_elements(args[1])
for _, v in ipairs(obj.elements) do
for _, v2 in ipairs(other_elts) do if is_eq(v, v2) then return false end end
end
return true
end }
elseif attr_name == "__iter__" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Runtime").builtins.iter({obj}) end }
end
elseif obj.__type == "list" or obj.__type == "tuple" then
if obj.is_matrix then
if attr_name == "shape" then
local s = obj.shape or require("Module:PythonScript/Library").Matrix.get_shape(obj) or {}
return { __type = "tuple", elements = s }
elseif attr_name == "T" then return require("Module:PythonScript/Library").Matrix.transpose(obj)
elseif attr_name == "I" then return require("Module:PythonScript/Library").Matrix.pinv(obj)
elseif attr_name == "inv" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Library").Matrix.inv(obj) end }
elseif attr_name == "pinv" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Library").Matrix.pinv(obj) end }
elseif attr_name == "det" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Library").Matrix.det(obj) end }
elseif attr_name == "trace" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Library").Matrix.trace(obj) end }
elseif attr_name == "rank" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Library").Matrix.rank(obj) end }
end
end
if attr_name == "append" and obj.__type == "list" then return { __type = "builtin_method", __name = "append", __call = function(args) table.insert(obj.elements, args[1]); return nil end }
elseif attr_name == "extend" and obj.__type == "list" then return { __type = "builtin_method", __name = "extend", __call = function(args)
for _, v in ipairs(extract_iterable_elements(args[1])) do table.insert(obj.elements, v) end; return nil
end }
elseif attr_name == "insert" and obj.__type == "list" then return { __type = "builtin_method", __name = "insert", __call = function(args)
local raw_idx, val = unwrap_int(args[1]), args[2]; local len = #obj.elements
if raw_idx < 0 then raw_idx = len + raw_idx end
if raw_idx < 0 then raw_idx = 0 elseif raw_idx > len then raw_idx = len end
table.insert(obj.elements, raw_idx + 1, val); return nil
end }
elseif attr_name == "pop" and obj.__type == "list" then return { __type = "builtin_method", __name = "pop", __call = function(args)
local len = #obj.elements
if len == 0 then require("Module:PythonScript/Runtime").raise("IndexError", "pop from empty list") end
local raw_idx = (args and not is_none(args[1])) and unwrap_int(args[1]) or nil
local idx = raw_idx and (raw_idx < 0 and (len + raw_idx + 1) or (raw_idx + 1)) or len
if idx < 1 or idx > len then require("Module:PythonScript/Runtime").raise("IndexError", "pop index out of range") end
return table.remove(obj.elements, idx)
end }
elseif attr_name == "remove" and obj.__type == "list" then return { __type = "builtin_method", __name = "remove", __call = function(args)
local val = args[1]
for i, v in ipairs(obj.elements) do if is_eq(v, val) then table.remove(obj.elements, i); return nil end end
require("Module:PythonScript/Runtime").raise("ValueError", "list.remove(x): x not in list")
end }
elseif attr_name == "clear" and obj.__type == "list" then return { __type = "builtin_method", __name = "clear", __call = function() obj.elements = {}; return nil end }
elseif attr_name == "index" then return { __type = "builtin_method", __name = "index", __call = function(args)
local val = args[1]
local s_pos, e_pos = resolve_search_bounds(#obj.elements, args[2], args[3])
for i = s_pos, math.min(e_pos, #obj.elements) do
if is_eq(obj.elements[i], val) then return i - 1 end
end
require("Module:PythonScript/Runtime").raise("ValueError", "item is not in " .. obj.__type)
end }
elseif attr_name == "count" then return { __type = "builtin_method", __name = "count", __call = function(args)
local val, c = args[1], 0
for _, v in ipairs(obj.elements) do if is_eq(v, val) then c = c + 1 end end
return c
end }
elseif attr_name == "reverse" and obj.__type == "list" then return { __type = "builtin_method", __name = "reverse", __call = function()
local n = #obj.elements
for i = 1, math.floor(n / 2) do obj.elements[i], obj.elements[n - i + 1] = obj.elements[n - i + 1], obj.elements[i] end
return nil
end }
elseif attr_name == "copy" and obj.__type == "list" then return { __type = "builtin_method", __name = "copy", __call = function()
local res = {__type="list", elements={}}; for _, v in ipairs(obj.elements) do table.insert(res.elements, v) end; return res
end }
elseif attr_name == "sort" and obj.__type == "list" then
return { __type = "builtin_method", __name = "sort", __call = function(args, kwargs)
local Runtime = require("Module:PythonScript/Runtime")
local Evaluator = require("Module:PythonScript/Evaluator")
local EvalUtils = require("Module:PythonScript/Evaluator/Utils")
local key_func = kwargs and kwargs.key
if is_none(key_func) then key_func = nil end
local reverse = kwargs and kwargs.reverse
reverse = (reverse ~= nil and not is_none(reverse)) and Runtime.builtins.bool.__call({reverse}) or false
local decorated = {}
for i, v in ipairs(obj.elements) do
local k = key_func and Evaluator.invoke(key_func, {v}, {}) or v
decorated[i] = { key = k, orig_idx = i, val = v }
end
table.sort(decorated, function(a, b)
if is_eq(a.key, b.key) then return a.orig_idx < b.orig_idx end
local cmp = EvalUtils.py_cmp(a.key, b.key)
if reverse then return cmp > 0 else return cmp < 0 end
end)
for i, d in ipairs(decorated) do obj.elements[i] = d.val end
return nil
end }
elseif attr_name == "__iter__" then return { __type = "builtin_method", __call = function() return require("Module:PythonScript/Runtime").builtins.iter({obj}) end }
elseif attr_name == "__len__" then return { __type = "builtin_method", __name = "__len__", __call = function() return #(obj.elements or {}) end }
elseif attr_name == "__contains__" then return { __type = "builtin_method", __name = "__contains__", __call = function(args)
local v = args[1]
for _, e in ipairs(obj.elements) do if is_eq(e, v) then return true end end
return false
end }
elseif attr_name == "__getitem__" then return { __type = "builtin_method", __name = "__getitem__", __call = function(args)
local idx = unwrap_int(args[1])
local len = #obj.elements
if type(idx) == "number" and math.floor(idx) == idx then
if idx < 0 then idx = len + idx end
if idx < 0 or idx >= len then require("Module:PythonScript/Runtime").raise("IndexError", "index out of range") end
return obj.elements[idx + 1]
end
require("Module:PythonScript/Runtime").raise("TypeError", "indices must be integers")
end }
elseif attr_name == "__setitem__" and obj.__type == "list" then return { __type = "builtin_method", __name = "__setitem__", __call = function(args)
local idx, val = unwrap_int(args[1]), args[2]
local len = #obj.elements
if type(idx) == "number" and math.floor(idx) == idx then
if idx < 0 then idx = len + idx end
if idx < 0 or idx >= len then require("Module:PythonScript/Runtime").raise("IndexError", "list assignment index out of range") end
obj.elements[idx + 1] = val
return require("Module:PythonScript/Runtime").builtins.None
end
require("Module:PythonScript/Runtime").raise("TypeError", "list indices must be integers")
end }
elseif attr_name == "__delitem__" and obj.__type == "list" then return { __type = "builtin_method", __name = "__delitem__", __call = function(args)
local idx = unwrap_int(args[1])
local len = #obj.elements
if type(idx) == "number" and math.floor(idx) == idx then
if idx < 0 then idx = len + idx end
if idx < 0 or idx >= len then require("Module:PythonScript/Runtime").raise("IndexError", "list assignment index out of range") end
table.remove(obj.elements, idx + 1)
return require("Module:PythonScript/Runtime").builtins.None
end
require("Module:PythonScript/Runtime").raise("TypeError", "list indices must be integers")
end }
end
end
return nil
end
return Utils