模組:PythonScript/Evaluator/Utils
外观
-- Module:PythonScript/Evaluator/Utils
-- Evaluator 內部輔助函式庫(作用域、比較器、大數/矩陣路由、迭代器、模式匹配、解包賦值與 f-string 引擎)
local Utils = {}
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
-- 確保 RecursionError 與 IndentationError 存在於 Runtime.builtins 中
if not Runtime.builtins.RecursionError then
Runtime.builtins.RecursionError = PyClass.make_class("RecursionError", {Runtime.builtins.RuntimeError or Runtime.builtins.Exception}, {
__str__ = { __type = "builtin_method", __call = function(args) return args[1].__dict.msg or "" end }
})
end
if not Runtime.builtins.IndentationError then
Runtime.builtins.IndentationError = PyClass.make_class("IndentationError", {Runtime.builtins.SyntaxError or Runtime.builtins.Exception}, {
__str__ = { __type = "builtin_method", __call = function(args) return args[1].__dict.msg or "" end }
})
end
-- 由 Module:PythonScript/Evaluator 於載入時注入核心回呼 (eval_expr, bind_and_exec_func, invoke)
local Core = {}
function Utils.bind_core(core_callbacks)
Core = core_callbacks
end
-- ==========================================
-- 1. 控制流訊號與作用域管理
-- ==========================================
Utils.SIGNAL_BREAK = { type = "break" }
Utils.SIGNAL_CONTINUE = { type = "continue" }
function Utils.make_return(val)
return { type = "return", value = val }
end
local function scan_goto_block(stmts, labels)
if not stmts then return end
for idx, stmt in ipairs(stmts) do
if stmt.type == "Label" then
if labels[stmt.target] then
Runtime.Traceback.current_line = stmt.line or Runtime.Traceback.current_line
Runtime.raise("SyntaxError", "duplicate label '" .. tostring(stmt.target) .. "'")
end
labels[stmt.target] = { block = stmts, pc = idx }
elseif stmt.type == "If" or stmt.type == "While" or stmt.type == "For" then
scan_goto_block(stmt.body, labels)
scan_goto_block(stmt.orelse, labels)
elseif stmt.type == "Try" then
scan_goto_block(stmt.body, labels)
for _, h in ipairs(stmt.handlers or {}) do scan_goto_block(h.body, labels) end
scan_goto_block(stmt.orelse, labels)
scan_goto_block(stmt.finalbody, labels)
elseif stmt.type == "With" then
scan_goto_block(stmt.body, labels)
elseif stmt.type == "Match" then
for _, c in ipairs(stmt.cases or {}) do scan_goto_block(c.body, labels) end
end
-- 注意:刻意不進入 FunctionDef 與 ClassDef,確保 label 嚴格限制在當前函數 Scope
end
end
function Utils.build_goto_labels(stmts)
local labels = {}
scan_goto_block(stmts, labels)
return labels
end
function Utils.create_scope(parent)
return { vars = {}, parent = parent, globals = {}, nonlocals = {} }
end
function Utils.lookup_var(name, scope)
local curr = scope
local root = scope
while curr do
if curr.vars[name] ~= nil then return curr.vars[name] end
if curr.custom_ns and type(curr.custom_ns) == "table" and curr.custom_ns.__type == "instance" then
local prev_d = Runtime.Traceback.eval_depth
local ok_gi, gi_m = pcall(PyClass.get_attr, curr.custom_ns, "__getitem__")
Runtime.Traceback.eval_depth = prev_d
if ok_gi and gi_m then
local ok_v, val = pcall(Core.invoke, gi_m, { name }, {})
Runtime.Traceback.eval_depth = prev_d
if ok_v and val ~= nil then return val end
end
end
-- 支援 from xxx import * 的延遲查找機制
if curr.star_imports then
for i = #curr.star_imports, 1, -1 do
local mod = curr.star_imports[i]
local allowed = true
if mod.__dict and type(mod.__dict["__all__"]) == "table" and mod.__dict["__all__"].elements then
allowed = false
for _, allowed_name in ipairs(mod.__dict["__all__"].elements) do
if allowed_name == name then allowed = true; break end
end
elseif string.sub(name, 1, 1) == "_" then
allowed = false
end
if allowed then
local ok, val = pcall(PyClass.get_attr, mod, name)
if ok and val ~= nil then
curr.vars[name] = val
return val
end
end
end
end
if not curr.parent then root = curr end
curr = curr.parent
end
-- 檢查頂層作用域是否覆寫或禁用了 __builtins__ (例如 eval(expr, {"__builtins__": None}, safe_dict))
local custom_builtins = root and root.vars and root.vars["__builtins__"]
if custom_builtins ~= nil then
if custom_builtins == Runtime.builtins.None or (type(custom_builtins) == "table" and custom_builtins.__class and custom_builtins.__class.__name == "NoneType") then
Runtime.raise("NameError", "name '" .. name .. "' is not defined")
elseif type(custom_builtins) == "table" then
local b_dict = custom_builtins
if b_dict.__type == "instance" and type(b_dict.__builtin_value) == "table" and b_dict.__builtin_value.__type == "dict" then
b_dict = b_dict.__builtin_value
end
if b_dict.__type == "dict" then
for _, p in ipairs(b_dict.pairs or {}) do
if p.key == name then return p.value end
end
Runtime.raise("NameError", "name '" .. name .. "' is not defined")
elseif custom_builtins.__type == "instance" or custom_builtins.__type == "module" then
local prev_d = Runtime.Traceback.eval_depth
local ok_b, val = pcall(PyClass.get_attr, custom_builtins, name)
Runtime.Traceback.eval_depth = prev_d
if ok_b and val ~= nil then return val end
Runtime.raise("NameError", "name '" .. name .. "' is not defined")
end
end
Runtime.raise("NameError", "name '" .. name .. "' is not defined")
end
if Runtime.builtins[name] ~= nil then return Runtime.builtins[name] end
Runtime.raise("NameError", "name '" .. name .. "' is not defined")
end
function Utils.set_var(name, value, scope)
if scope.globals[name] then
local root = scope
while root.parent do root = root.parent end
root.vars[name] = value
return
end
if scope.nonlocals[name] then
local curr = scope.parent
while curr do
if curr.vars[name] ~= nil then curr.vars[name] = value; return end
curr = curr.parent
end
Runtime.raise("SyntaxError", "no binding for nonlocal '" .. name .. "' found")
end
scope.vars[name] = value
if scope.custom_ns and type(scope.custom_ns) == "table" then
local ns = scope.custom_ns
if ns.__type == "dict" then
local found = false
for _, p in ipairs(ns.pairs) do
if p.key == name then p.value = value; found = true; break end
end
if not found then table.insert(ns.pairs, { key = name, value = value }) end
elseif ns.__type == "instance" then
local prev_d = Runtime.Traceback.eval_depth
local ok_si, si_m = pcall(PyClass.get_attr, ns, "__setitem__")
Runtime.Traceback.eval_depth = prev_d
if ok_si and si_m then
Core.invoke(si_m, { name, value }, {})
end
end
end
end
function Utils.del_var(name, scope)
if scope.globals[name] then
local root = scope
while root.parent do root = root.parent end
if root.vars[name] == nil then Runtime.raise("NameError", "name '" .. name .. "' is not defined") end
root.vars[name] = nil
return
end
if scope.nonlocals[name] then
local curr = scope.parent
while curr do
if curr.vars[name] ~= nil then curr.vars[name] = nil; return end
curr = curr.parent
end
Runtime.raise("SyntaxError", "no binding for nonlocal '" .. name .. "' found")
end
if scope.vars[name] == nil then
Runtime.raise("NameError", "name '" .. name .. "' is not defined")
end
scope.vars[name] = nil
if scope.custom_ns and type(scope.custom_ns) == "table" then
local ns = scope.custom_ns
if ns.__type == "dict" then
for i, p in ipairs(ns.pairs) do
if p.key == name then table.remove(ns.pairs, i); break end
end
elseif ns.__type == "instance" then
local prev_d = Runtime.Traceback.eval_depth
local ok_di, di_m = pcall(PyClass.get_attr, ns, "__delitem__")
Runtime.Traceback.eval_depth = prev_d
if ok_di and di_m then pcall(Core.invoke, di_m, { name }, {}) end
end
end
end
function Utils.is_truthy(val)
return Runtime.builtins.bool.__call({val})
end
-- ==========================================
-- 2. 索引與切片正規化
-- ==========================================
local function unwrap_int_index(idx)
if type(idx) == "table" and idx.__type == "instance" and idx.__builtin_value ~= nil then
idx = idx.__builtin_value
end
if type(idx) == "boolean" then return idx and 1 or 0 end
if type(idx) == "table" and idx.__type == "int" then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
return idx.value or tonumber(MathCore.BigInt.to_string(idx.sign, idx.limbs))
end
return idx
end
Utils.unwrap_int_index = unwrap_int_index
local function resolve_index(obj, idx)
idx = unwrap_int_index(idx)
if type(idx) ~= "number" or math.floor(idx) ~= idx then Runtime.raise("TypeError", "indices must be integers") end
local length = 0
if type(obj) == "table" and obj.elements then length = #(obj.elements)
elseif type(obj) == "table" and obj._data then length = #(obj._data)
elseif type(obj) == "string" then length = mw.ustring.len(obj) end
if idx < 0 then idx = length + idx end
if idx < 0 or idx >= length then Runtime.raise("IndexError", "index out of range") end
return idx + 1
end
Utils.resolve_index = resolve_index
local function resolve_slice(length, lower, upper, step)
lower = lower ~= nil and unwrap_int_index(lower) or nil
upper = upper ~= nil and unwrap_int_index(upper) or nil
step = step ~= nil and unwrap_int_index(step) or nil
if type(lower) == "table" and lower.__class and lower.__class.__name == "NoneType" then lower = nil end
if type(upper) == "table" and upper.__class and upper.__class.__name == "NoneType" then upper = nil end
if type(step) == "table" and step.__class and step.__class.__name == "NoneType" then step = nil end
local s_step = step or 1
if s_step == 0 then Runtime.raise("ValueError", "slice step cannot be zero") end
local s_start = lower
if s_start == nil then
s_start = (s_step > 0) and 0 or (length - 1)
else
if s_start < 0 then s_start = s_start + length end
if s_step > 0 then
if s_start < 0 then s_start = 0 end
if s_start > length then s_start = length end
else
if s_start < -1 then s_start = -1 end
if s_start >= length then s_start = length - 1 end
end
end
local s_stop = upper
if s_stop == nil then
s_stop = (s_step > 0) and length or -1
else
if s_stop < 0 then s_stop = s_stop + length end
if s_step > 0 then
if s_stop < 0 then s_stop = 0 end
if s_stop > length then s_stop = length end
else
if s_stop < -1 then s_stop = -1 end
if s_stop >= length then s_stop = length - 1 end
end
end
return s_start, s_stop, s_step
end
Utils.resolve_slice = resolve_slice
-- ==========================================
-- 3. 相等與大小比較引擎 (含循環參照防護)
-- ==========================================
Utils.COMPARE_OPS = {
["=="] = true, ["!="] = true, ["<"] = true, [">"] = true,
["<="] = true, [">="] = true, ["is"] = true, ["is not"] = true,
["in"] = true, ["not in"] = true
}
local py_cmp, py_eq
py_cmp = function(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
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 is_float_a = type(a) == "number" and math.floor(a) ~= a
local is_float_b = type(b) == "number" and math.floor(b) ~= b
if is_float_a or is_float_b then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
local num_a = is_big_a and tonumber(MathCore.BigInt.to_string(a.sign, a.limbs)) or a
local num_b = is_big_b and tonumber(MathCore.BigInt.to_string(b.sign, b.limbs)) or b
if num_a < num_b then return -1 elseif num_a > num_b then return 1 else return 0 end
end
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 sign_a = is_big_a and a.sign or (a < 0 and -1 or 1)
local sign_b = is_big_b and b.sign or (b < 0 and -1 or 1)
if a == 0 then sign_a = 1 end; if b == 0 then sign_b = 1 end
if sign_a ~= sign_b then return sign_a < sign_b and -1 or 1 end
local c = MathCore.BigInt.cmp_limbs(MathCore.BigInt.to_limbs(a), MathCore.BigInt.to_limbs(b))
return sign_a == 1 and c or -c
end
end
if type(a) == "table" and type(b) == "table" and a.__type == b.__type and (a.__type == "list" or a.__type == "tuple") then
local len_a, len_b = #a.elements, #b.elements
for i = 1, math.min(len_a, len_b) do
if not py_eq(a.elements[i], b.elements[i]) then
return py_cmp(a.elements[i], b.elements[i])
end
end
if len_a < len_b then return -1 elseif len_a > len_b then return 1 else return 0 end
end
if type(a) == "number" and type(b) == "number" then
if a < b then return -1 elseif a > b then return 1 else return 0 end
end
if type(a) == "string" and type(b) == "string" then
if a < b then return -1 elseif a > b then return 1 else return 0 end
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
if a._data < b._data then return -1 elseif a._data > b._data then return 1 else return 0 end
end
end
-- 支援 instance 與任意型別 (含原生 number) 的比較魔術方法
if type(a) == "table" and a.__type == "instance" then
local prev_depth = Runtime.Traceback.eval_depth
local success, lt = pcall(PyClass.get_attr, a, "__lt__")
Runtime.Traceback.eval_depth = prev_depth
if success and lt and lt.__type == "bound_method" then
local res = Core.bind_and_exec_func(lt.__func, {b}, {}, lt.__self)
if res ~= nil and res ~= Runtime.builtins.None and res ~= Runtime.builtins.NotImplemented then
if Utils.is_truthy(res) then return -1 end
if py_eq(a, b) then return 0 end
return 1
end
end
end
if type(b) == "table" and b.__type == "instance" then
local prev_depth = Runtime.Traceback.eval_depth
local success, gt = pcall(PyClass.get_attr, b, "__gt__")
Runtime.Traceback.eval_depth = prev_depth
if success and gt and gt.__type == "bound_method" then
local res = Core.bind_and_exec_func(gt.__func, {a}, {}, gt.__self)
if res ~= nil and res ~= Runtime.builtins.None and res ~= Runtime.builtins.NotImplemented then
if Utils.is_truthy(res) then return -1 end
if py_eq(a, b) then return 0 end
return 1
end
end
end
local has_bv_a = type(a) == "table" and a.__type == "instance" and a.__builtin_value ~= nil
local has_bv_b = type(b) == "table" and b.__type == "instance" and b.__builtin_value ~= nil
if has_bv_a or has_bv_b then
return py_cmp(has_bv_a and a.__builtin_value or a, has_bv_b and b.__builtin_value or b)
end
Runtime.raise("TypeError", "unorderable types")
end
Utils.py_cmp = py_cmp
py_eq = function(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
-- 若其中一方為自定義實例 (如 Fraction(2, 1) == 2),先嘗試觸發 __eq__
if type(a) == "table" and a.__type == "instance" then
local prev_depth = Runtime.Traceback.eval_depth
local success, eq_method = pcall(PyClass.get_attr, a, "__eq__")
Runtime.Traceback.eval_depth = prev_depth
if success and eq_method and eq_method.__type == "bound_method" then
local res = Core.bind_and_exec_func(eq_method.__func, {b}, {}, eq_method.__self)
if res ~= nil and res ~= Runtime.builtins.None and res ~= Runtime.builtins.NotImplemented then return Utils.is_truthy(res) end
end
end
if type(b) == "table" and b.__type == "instance" then
local prev_depth = Runtime.Traceback.eval_depth
local success, eq_method = pcall(PyClass.get_attr, b, "__eq__")
Runtime.Traceback.eval_depth = prev_depth
if success and eq_method and eq_method.__type == "bound_method" then
local res = Core.bind_and_exec_func(eq_method.__func, {a}, {}, eq_method.__self)
if res ~= nil and res ~= Runtime.builtins.None and res ~= Runtime.builtins.NotImplemented then return Utils.is_truthy(res) end
end
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 py_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
return py_cmp(na, 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
if a.__type == "memoryview" and a._released then return false end
if b.__type == "memoryview" and b._released then return false end
return (is_b1 and is_b2) and (a._data == b._data) or false
end
if a.__type == "int" and b.__type == "int" then
return py_cmp(a, b) == 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 py_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 py_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 py_eq(pa.key, pb.key, seen) and py_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 prev_depth = Runtime.Traceback.eval_depth
local success, eq_method = pcall(PyClass.get_attr, a, "__eq__")
Runtime.Traceback.eval_depth = prev_depth
if success and eq_method and eq_method.__type == "bound_method" then
local res = Core.bind_and_exec_func(eq_method.__func, {b}, {}, eq_method.__self)
if res ~= Runtime.builtins.NotImplemented then return Utils.is_truthy(res) end
end
end
if b.__type == "instance" then
local prev_depth = Runtime.Traceback.eval_depth
local success, eq_method = pcall(PyClass.get_attr, b, "__eq__")
Runtime.Traceback.eval_depth = prev_depth
if success and eq_method and eq_method.__type == "bound_method" then
local res = Core.bind_and_exec_func(eq_method.__func, {a}, {}, eq_method.__self)
if res ~= Runtime.builtins.NotImplemented then return Utils.is_truthy(res) end
end
end
if (a.__type == "instance" and a.__builtin_value ~= nil) or (b.__type == "instance" and b.__builtin_value ~= nil) then
return py_eq(a.__builtin_value ~= nil and a.__builtin_value or a, b.__builtin_value ~= nil and b.__builtin_value or b, seen)
end
return a == b
end
return a == b
end
Utils.py_eq = py_eq
function Utils.py_is(a, b)
return a == b
end
-- ==========================================
-- 4. 矩陣與大數引擎路由器
-- ==========================================
local function bigint_op(a, b, op)
if type(a) == "table" and a.__type == "instance" and a.__builtin_value ~= nil then a = a.__builtin_value end
if type(b) == "table" and b.__type == "instance" and b.__builtin_value ~= nil then b = b.__builtin_value end
local is_float_a = type(a) == "number" and (math.floor(a) ~= a or math.abs(a) > 9007199254740990)
local is_float_b = type(b) == "number" and (math.floor(b) ~= b or math.abs(b) > 9007199254740990)
if is_float_a or is_float_b then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
local num_a = (type(a) == "table" and a.__type == "int") and tonumber(MathCore.BigInt.to_string(a.sign, a.limbs)) or a
local num_b = (type(b) == "table" and b.__type == "int") and tonumber(MathCore.BigInt.to_string(b.sign, b.limbs)) or b
if op == "+" then return num_a + num_b
elseif op == "-" then return num_a - num_b
elseif op == "*" then return num_a * num_b
elseif op == "/" then return num_a / num_b
elseif op == "//" then return math.floor(num_a / num_b)
elseif op == "%" then return num_a % num_b
elseif op == "**" then return num_a ^ num_b
end
end
local MathCore = require("Module:PythonScript/Runtime/Cmath")
local BigInt = MathCore.BigInt
local limbs_a, limbs_b = BigInt.to_limbs(a), BigInt.to_limbs(b)
local sign_a = (type(a) == "table" and a.sign) or (a < 0 and -1 or 1)
local sign_b = (type(b) == "table" and b.sign) or (b < 0 and -1 or 1)
if a == 0 then sign_a = 1 end
if b == 0 then sign_b = 1 end
local res_sign, res_limbs
if op == "+" then
if sign_a == sign_b then res_sign = sign_a; res_limbs = BigInt.add_limbs(limbs_a, limbs_b)
else
local cmp = BigInt.cmp_limbs(limbs_a, limbs_b)
if cmp == 0 then return 0 end
if cmp > 0 then res_sign = sign_a; res_limbs = BigInt.sub_limbs(limbs_a, limbs_b)
else res_sign = sign_b; res_limbs = BigInt.sub_limbs(limbs_b, limbs_a) end
end
elseif op == "-" then
if sign_a ~= sign_b then res_sign = sign_a; res_limbs = BigInt.add_limbs(limbs_a, limbs_b)
else
local cmp = BigInt.cmp_limbs(limbs_a, limbs_b)
if cmp == 0 then return 0 end
if cmp > 0 then res_sign = sign_a; res_limbs = BigInt.sub_limbs(limbs_a, limbs_b)
else res_sign = -sign_a; res_limbs = BigInt.sub_limbs(limbs_b, limbs_a) end
end
elseif op == "*" then
res_sign = sign_a * sign_b; res_limbs = BigInt.mul_limbs(limbs_a, limbs_b)
elseif op == "//" or op == "%" then
if #limbs_b == 1 and limbs_b[1] == 0 then Runtime.raise("ZeroDivisionError", "integer division or modulo by zero") end
local q, r = BigInt.divmod_limbs(limbs_a, limbs_b)
local r_is_zero = (#r == 1 and r[1] == 0)
local q_sign = sign_a * sign_b; local r_sign = sign_a
if not r_is_zero and sign_a ~= sign_b then
q = BigInt.add_limbs(q, {1}); r = BigInt.sub_limbs(limbs_b, r); r_sign = sign_b
end
if op == "//" then res_sign = q_sign; res_limbs = q else res_sign = r_sign; res_limbs = r end
elseif op == "**" then
local exp = tonumber(BigInt.to_string(sign_b, limbs_b))
if exp < 0 then return tonumber(BigInt.to_string(sign_a, limbs_a)) ^ exp end
if exp == 0 then return 1 end
local base_limbs = limbs_a
res_limbs = {1}; res_sign = (sign_a == -1 and exp % 2 == 1) and -1 or 1
while exp > 0 do
if exp % 2 == 1 then res_limbs = BigInt.mul_limbs(res_limbs, base_limbs) end
if exp > 1 then base_limbs = BigInt.mul_limbs(base_limbs, base_limbs) end
exp = math.floor(exp / 2)
end
elseif op == "/" then
if #limbs_b == 1 and limbs_b[1] == 0 then Runtime.raise("ZeroDivisionError", "division by zero") end
return tonumber(BigInt.to_string(sign_a, limbs_a)) / tonumber(BigInt.to_string(sign_b, limbs_b))
elseif op == "<<" or op == ">>" or op == "&" or op == "|" or op == "^" then
Runtime.raise("NotImplementedError", "bitwise operations on BigInt are not fully supported in this environment")
end
if #res_limbs == 1 and res_limbs[1] == 0 then return 0 end
local num_str = BigInt.to_string(res_sign, res_limbs)
local num = tonumber(num_str)
if num and math.abs(num) <= BigInt.SAFE_LIMIT then return num end
return { __type = "int", is_big = true, value = num, sign = res_sign, limbs = res_limbs }
end
Utils.bigint_op = bigint_op
local function get_matrix_engine()
return require("Module:PythonScript/Library").Matrix
end
Utils.get_matrix_engine = get_matrix_engine
function Utils.elementwise_op(a, b, op)
return get_matrix_engine().elementwise_op(a, b, op)
end
function Utils.matmul(A, B)
return get_matrix_engine().matmul(A, B)
end
local UnionTypeClass = nil
local function is_single_unionable(x)
if type(x) ~= "table" then return false end
if x.__type == "class" then return true end
if x.__class and x.__class.__name == "NoneType" then return true end
if x.__dict and (x.__dict._is_union or x.__dict._is_typing) then return true end
return false
end
function Utils.is_unionable_type(a, b)
return is_single_unionable(a) and is_single_unionable(b)
end
local function format_type_item(t)
if type(t) == "table" then
if t == Runtime.builtins.NoneType or (t.__class and t.__class.__name == "NoneType") then
return "None"
elseif t.__type == "class" then
local mod = t.__dict and t.__dict["__module__"]
if mod and mod ~= "builtins" and mod ~= "__main__" then
return mod .. "." .. t.__name
end
return t.__name
end
end
return Runtime.builtins.repr({t})
end
function Utils.make_union_type(items)
if not UnionTypeClass then
local u_dict = {
__repr__ = { __type = "builtin_method", __call = function(a)
local args_tup = a[1].__dict.__args__
local parts = {}
for i, t in ipairs(args_tup.elements) do
parts[i] = format_type_item(t)
end
return table.concat(parts, " | ")
end },
__or__ = { __type = "builtin_method", __call = function(a)
if not is_single_unionable(a[2]) then return Runtime.builtins.NotImplemented end
return Utils.make_union_type({ a[1], a[2] })
end },
__ror__ = { __type = "builtin_method", __call = function(a)
if not is_single_unionable(a[2]) then return Runtime.builtins.NotImplemented end
return Utils.make_union_type({ a[2], a[1] })
end },
__eq__ = { __type = "builtin_method", __call = function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or not other.__dict or not other.__dict._is_union then
return false
end
local e1 = self.__dict.__args__.elements
local e2 = other.__dict.__args__.elements
if #e1 ~= #e2 then return false end
for _, x in ipairs(e1) do
local found = false
for _, y in ipairs(e2) do
if py_eq(x, y) then found = true; break end
end
if not found then return false end
end
return true
end },
__hash__ = { __type = "builtin_method", __call = function(a)
return Runtime.builtins.hash({ a[1].__dict.__args__ })
end }
}
u_dict.__str__ = u_dict.__repr__
UnionTypeClass = PyClass.make_class("UnionType", {}, u_dict)
UnionTypeClass.__dict["__module__"] = "types"
end
local flat = {}
local function add_item(item)
if type(item) == "table" and item.__class and item.__class.__name == "NoneType" then
item = Runtime.builtins.NoneType
end
if type(item) == "table" and item.__dict and item.__dict._is_union and item.__dict.__args__ then
for _, sub in ipairs(item.__dict.__args__.elements) do add_item(sub) end
return
end
for _, existing in ipairs(flat) do
if py_eq(existing, item) then return end
end
table.insert(flat, item)
end
for _, it in ipairs(items) do add_item(it) end
if #flat == 1 then return flat[1] end
local inst = PyClass.make_instance(UnionTypeClass)
inst.__dict._is_union = true
inst.__dict._is_typing = true
inst.__dict.__args__ = { __type = "tuple", elements = flat }
inst.__dict.__parameters__ = { __type = "tuple", elements = {} }
return inst
end
-- ==========================================
-- 5. f-string 解析與格式化引擎
-- ==========================================
local function format_fstring_value(val, conv, fmt_spec)
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
local function split_fstring_field(field_str)
local b_level = 0
local in_str = false
local quote = nil
local conv_pos, colon_pos = nil, nil
local n = #field_str
local i = 1
while i <= n do
local c = field_str:sub(i, i)
if not in_str then
if c == "(" or c == "[" or c == "{" then b_level = b_level + 1
elseif c == ")" or c == "]" or c == "}" then b_level = math.max(0, b_level - 1)
elseif c == "'" or c == '"' then in_str = true; quote = c
elseif b_level == 0 then
if c == "!" and field_str:sub(i+1, i+1) ~= "=" and not colon_pos then
local next_c = field_str:sub(i+1, i+1)
if next_c == "r" or next_c == "s" or next_c == "a" then
conv_pos = i
end
elseif c == ":" and not colon_pos then
colon_pos = i
break
end
end
else
if c == "\\" then i = i + 1
elseif c == quote then in_str = false end
end
i = i + 1
end
local expr_part = field_str
local conv = nil
local fmt_spec = nil
if colon_pos then
fmt_spec = field_str:sub(colon_pos + 1)
expr_part = field_str:sub(1, colon_pos - 1)
end
if conv_pos and (not colon_pos or conv_pos < colon_pos) then
conv = expr_part:sub(conv_pos + 1)
expr_part = expr_part:sub(1, conv_pos - 1)
end
return expr_part, conv, fmt_spec
end
function Utils.eval_fstring(content, scope)
local current_f_line = Runtime.Traceback.current_line
local prev_depth = Runtime.Traceback.eval_depth
local success_f, final_content = pcall(function()
local res_parts = {}
local i = 1
local n = #content
while i <= n do
local c = content:sub(i, i)
if c == "{" then
if i < n and content:sub(i+1, i+1) == "{" then
table.insert(res_parts, "{")
i = i + 2
else
local expr_start = i + 1
local bracket_level = 1
local j = expr_start
local in_string = false
local str_quote = nil
while j <= n do
local char = content:sub(j, j)
if not in_string then
if char == "{" then bracket_level = bracket_level + 1
elseif char == "}" then
bracket_level = bracket_level - 1
if bracket_level == 0 then break end
elseif char == "'" or char == '"' then
in_string = true; str_quote = char
end
else
if char == "\\" then j = j + 1
elseif char == str_quote then in_string = false end
end
j = j + 1
end
if bracket_level > 0 then Runtime.raise("SyntaxError", "f-string: unclosed '{'") end
local raw_field = content:sub(expr_start, j - 1)
if raw_field == "" then
table.insert(res_parts, "{}")
else
local expr_str, conv, fmt_spec = split_fstring_field(raw_field)
if fmt_spec and fmt_spec:find("{", 1, true) then
fmt_spec = Utils.eval_fstring(fmt_spec, scope)
end
local Lexer = require("Module:PythonScript/Lexer")
local Parser = require("Module:PythonScript/Parser")
local d_save = Runtime.Traceback.eval_depth
local clean_expr = expr_str:match("^%s*(.-)%s*$")
local success, inner_tokens = pcall(Lexer.tokenize, clean_expr)
Runtime.Traceback.eval_depth = d_save
if success then
for _, t in ipairs(inner_tokens) do t.line = current_f_line end
local success2, inner_ast = pcall(Parser.parse, inner_tokens)
Runtime.Traceback.eval_depth = d_save
if success2 and inner_ast.body[1] and inner_ast.body[1].type == "ExprStatement" then
local evaluated_val = Core.eval_expr(inner_ast.body[1].expr, scope)
table.insert(res_parts, format_fstring_value(evaluated_val, conv, fmt_spec))
else
table.insert(res_parts, "{" .. raw_field .. "}")
end
else
table.insert(res_parts, "{" .. raw_field .. "}")
end
end
i = j + 1
end
elseif c == "}" then
if i < n and content:sub(i+1, i+1) == "}" then
table.insert(res_parts, "}")
i = i + 2
else
Runtime.raise("SyntaxError", "f-string: single '}' is not allowed")
end
else
table.insert(res_parts, c)
i = i + 1
end
end
return table.concat(res_parts)
end)
Runtime.Traceback.eval_depth = prev_depth
if not success_f then error(final_content, 0) end
return final_content
end
-- ==========================================
-- 6. 模式匹配、迭代器狀態與解包賦值
-- ==========================================
local match_pattern, make_iter_state, get_next_item, eval_comprehension, assign_target
match_pattern = function(pat, val, scope, exec_scope)
if pat.type == "Identifier" then
if pat.name == "_" then return true end
scope.vars[pat.name] = val; return true
elseif pat.type == "MatchAs" then
if match_pattern(pat.pattern, val, scope, exec_scope) then scope.vars[pat.alias] = val; return true end
return false
elseif pat.type == "Number" or pat.type == "String" or pat.type == "Boolean" or pat.type == "None" or pat.type == "UnaryOp" then
return py_eq(Core.eval_expr(pat, exec_scope), val)
elseif pat.type == "Attribute" then
return py_eq(Core.eval_expr(pat, exec_scope), val)
elseif pat.type == "Tuple" or pat.type == "List" then
if type(val) == "table" and val.__type == "instance" and val.__builtin_value ~= nil then
val = val.__builtin_value
end
if type(val) ~= "table" or (val.__type ~= "list" and val.__type ~= "tuple") then return false end
local starred_idx = nil
for i, p_elem in ipairs(pat.elements) do
if p_elem.type == "Starred" then starred_idx = i; break end
end
if starred_idx then
local min_len = #pat.elements - 1
if #val.elements < min_len then return false end
local star_len = #val.elements - min_len
local cur = 1
for i, p_elem in ipairs(pat.elements) do
if i == starred_idx then
local star_elts = {}
for j = 1, star_len do table.insert(star_elts, val.elements[cur]); cur = cur + 1 end
if p_elem.value.type == "Identifier" and p_elem.value.name ~= "_" then
scope.vars[p_elem.value.name] = { __type = "list", elements = star_elts }
end
else
if not match_pattern(p_elem, val.elements[cur], scope, exec_scope) then return false end
cur = cur + 1
end
end
return true
else
if #pat.elements ~= #val.elements then return false end
for i, p_elem in ipairs(pat.elements) do
if not match_pattern(p_elem, val.elements[i], scope, exec_scope) then return false end
end
return true
end
elseif pat.type == "Dict" then
local val_pairs = nil
if type(val) == "table" then
if val.__type == "dict" then
val_pairs = val.pairs
elseif val.__type == "instance" then
if type(val.__builtin_value) == "table" and val.__builtin_value.__type == "dict" then
val_pairs = val.__builtin_value.pairs
elseif val.__dict and type(val.__dict.data) == "table" and val.__dict.data.__type == "dict" then
val_pairs = val.__dict.data.pairs
end
end
end
if not val_pairs then return false end
local matched_indices = {}
local rest_var = nil
for _, p_pair in ipairs(pat.pairs) do
if p_pair.key == nil then
rest_var = p_pair.value.name
else
local k_val = Core.eval_expr(p_pair.key, exec_scope)
local found_idx, found_v = nil, nil
for v_i, v_pair in ipairs(val_pairs) do
if py_eq(v_pair.key, k_val) then
found_idx = v_i
found_v = v_pair.value
break
end
end
if found_idx == nil or not match_pattern(p_pair.value, found_v, scope, exec_scope) then
return false
end
matched_indices[found_idx] = true
end
end
if rest_var then
local rest_pairs = {}
for v_i, v_pair in ipairs(val_pairs) do
if not matched_indices[v_i] then
table.insert(rest_pairs, { key = v_pair.key, value = v_pair.value })
end
end
scope.vars[rest_var] = { __type = "dict", pairs = rest_pairs }
end
return true
elseif pat.type == "BinaryOp" and pat.op == "|" then
local snap = {}; for k, v in pairs(scope.vars) do snap[k] = v end
if match_pattern(pat.left, val, scope, exec_scope) then return true end
scope.vars = {}; for k, v in pairs(snap) do scope.vars[k] = v end
if match_pattern(pat.right, val, scope, exec_scope) then return true end
return false
elseif pat.type == "Call" then
local class_obj = Core.eval_expr(pat.callee, exec_scope)
if type(class_obj) ~= "table" or class_obj.__type ~= "class" then
Runtime.raise("TypeError", "called match pattern must be a type")
end
if not Runtime.builtins.isinstance({val, class_obj}) then return false end
local pos_args = pat.args or {}
local seen_attrs = {}
if #pos_args > 0 then
local B = Runtime.builtins
local SELF_MATCH_BUILTINS = {
[B.bool] = true, [B.bytearray] = true, [B.bytes] = true, [B.dict] = true,
[B.float] = true, [B.frozenset] = true, [B.int] = true, [B.list] = true,
[B.set] = true, [B.str] = true, [B.tuple] = true
}
local prev_d = Runtime.Traceback.eval_depth
local has_ma, match_args = pcall(PyClass.get_attr, class_obj, "__match_args__")
Runtime.Traceback.eval_depth = prev_d
if has_ma and match_args ~= nil then
if type(match_args) ~= "table" or match_args.__type ~= "tuple" then
Runtime.raise("TypeError", tostring(class_obj.__name) .. ".__match_args__ must be a tuple")
end
local ma_elts = match_args.elements or {}
if #pos_args > #ma_elts then
Runtime.raise("TypeError", string.format("%s() accepts %d positional sub-pattern%s (%d given)",
tostring(class_obj.__name), #ma_elts, #ma_elts == 1 and "" or "s", #pos_args))
end
for i, pos_pat in ipairs(pos_args) do
local attr_name = ma_elts[i]
if type(attr_name) ~= "string" then
Runtime.raise("TypeError", "__match_args__ elements must be strings")
end
seen_attrs[attr_name] = true
prev_d = Runtime.Traceback.eval_depth
local ok_attr, attr_val = pcall(PyClass.get_attr, val, attr_name)
Runtime.Traceback.eval_depth = prev_d
if not ok_attr or not match_pattern(pos_pat, attr_val, scope, exec_scope) then
return false
end
end
else
local is_self_match = SELF_MATCH_BUILTINS[class_obj] == true
if not is_self_match then
for b_cls, _ in pairs(SELF_MATCH_BUILTINS) do
if PyClass.is_subclass(class_obj, b_cls) then is_self_match = true; break end
end
end
if is_self_match then
if #pos_args > 1 then
Runtime.raise("TypeError", string.format("%s() accepts 1 positional sub-pattern (%d given)", tostring(class_obj.__name), #pos_args))
end
if not match_pattern(pos_args[1], val, scope, exec_scope) then return false end
else
Runtime.raise("TypeError", string.format("%s() accepts 0 positional sub-patterns (%d given)", tostring(class_obj.__name), #pos_args))
end
end
end
for _, kw in ipairs(pat.kwargs or {}) do
if seen_attrs[kw.key] then
Runtime.raise("TypeError", string.format("%s() got multiple sub-patterns for attribute '%s'", tostring(class_obj.__name), tostring(kw.key)))
end
seen_attrs[kw.key] = true
local prev_d = Runtime.Traceback.eval_depth
local ok_attr, attr_val = pcall(PyClass.get_attr, val, kw.key)
Runtime.Traceback.eval_depth = prev_d
if not ok_attr or not match_pattern(kw.value, attr_val, scope, exec_scope) then
return false
end
end
return true
end
return false
end
Utils.match_pattern = match_pattern
make_iter_state = function(obj)
local prev_depth = Runtime.Traceback.eval_depth
local success, iter_func = pcall(PyClass.get_attr, obj, "__iter__")
Runtime.Traceback.eval_depth = prev_depth
local py_iter = obj
if success and iter_func and type(iter_func) == "table" and iter_func.__type == "bound_method" then
py_iter = Core.bind_and_exec_func(iter_func.__func, {}, {}, iter_func.__self)
end
prev_depth = Runtime.Traceback.eval_depth
local success2, next_func = pcall(PyClass.get_attr, py_iter, "__next__")
Runtime.Traceback.eval_depth = prev_depth
if success2 and next_func then return { type = "iterator", obj = py_iter, next_func = next_func } end
if type(obj) == "table" and obj.__type == "instance" and obj.__builtin_value ~= nil then
obj = obj.__builtin_value
end
local iter_list = {}
if type(obj) == "table" then
if 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
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 == "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
else
Runtime.raise("TypeError", "'" .. (obj.__class and obj.__class.__name or obj.__type or "object") .. "' object is not iterable")
end
elseif type(obj) == "string" then
for i = 1, mw.ustring.len(obj) do table.insert(iter_list, mw.ustring.sub(obj, i, i)) end
else
Runtime.raise("TypeError", "'" .. type(obj) .. "' object is not iterable")
end
return { type = "list", elements = iter_list, idx = 0 }
end
Utils.make_iter_state = make_iter_state
get_next_item = function(state)
if state.type == "iterator" then
local prev_depth = Runtime.Traceback.eval_depth
local success, res = pcall(function()
if state.next_func.__type == "bound_method" then
return Core.bind_and_exec_func(state.next_func.__func, {}, {}, state.next_func.__self)
end
end)
Runtime.Traceback.eval_depth = prev_depth
if not success then
if type(res) == "table" and res.__class and res.__class.__name == "StopIteration" then return false, nil end
error(res, 0)
end
return true, res
else
state.idx = state.idx + 1
if state.idx <= #state.elements then return true, state.elements[state.idx] end
return false, nil
end
end
Utils.get_next_item = get_next_item
eval_comprehension = function(generators, gen_idx, scope, callback)
if gen_idx > #generators then callback(); return end
local gen = generators[gen_idx]
local iter_state = make_iter_state(Core.eval_expr(gen.iter, scope))
while true do
local has_next, v = get_next_item(iter_state)
if not has_next then break end
assign_target(gen.target, v, scope)
local condition_pass = true
for _, if_node in ipairs(gen.ifs) do
if not Utils.is_truthy(Core.eval_expr(if_node, scope)) then condition_pass = false; break end
end
if condition_pass then eval_comprehension(generators, gen_idx + 1, scope, callback) end
end
end
Utils.eval_comprehension = eval_comprehension
assign_target = function(target_node, value, scope)
if value == nil then value = Runtime.builtins.None end
-- 支援單獨星號打包賦值:*u = (a+bj+ck+djk).imag 或 *a = foo()
if target_node.type == "Starred" then
local elts = Runtime.builtins.list.__call({ value }).elements
local is_ut = (type(value) == "table" and value._is_unpacked_tuple == true)
local packed = { __type = is_ut and "tuple" or "list", elements = elts }
assign_target(target_node.value, packed, scope)
return
end
if target_node.type == "Identifier" then
Utils.set_var(target_node.name, value, scope)
elseif target_node.type == "Index" then
if target_node.index.type == "Slice" then
local target = Core.eval_expr(target_node.target, scope)
if type(target) == "table" and target.__type == "instance" and target.__builtin_value ~= nil then
target = target.__builtin_value
end
if type(target) == "table" and (target.__type == "bytearray" or target.__type == "memoryview") then
if target.__type == "memoryview" then
if target._released then Runtime.raise("ValueError", "operation forbidden on released memoryview object") end
if target._readonly then Runtime.raise("TypeError", "cannot modify read-only memory") end
end
local lower = target_node.index.lower and Core.eval_expr(target_node.index.lower, scope) or nil
local upper = target_node.index.upper and Core.eval_expr(target_node.index.upper, scope) or nil
local step = target_node.index.step and Core.eval_expr(target_node.index.step, scope) or nil
if step and step ~= 1 then Runtime.raise("NotImplementedError", "extended slice assignment on bytes-like object is not supported") end
local repl = Runtime.builtins.bytes.__call({value})._data
local p_start, p_stop, _ = resolve_slice(#target._data, lower, upper, 1)
if target.__type == "memoryview" and #repl ~= (p_stop - p_start) then
Runtime.raise("ValueError", "memoryview assignment: lvalue and rvalue have different structures")
end
target._data = target._data:sub(1, p_start) .. repl .. target._data:sub(p_stop + 1)
if target.__type == "memoryview" and target._obj then target._obj._data = target._data end
return
end
if type(target) ~= "table" or target.__type ~= "list" then Runtime.raise("TypeError", "object does not support slice assignment") end
local lower = target_node.index.lower and Core.eval_expr(target_node.index.lower, scope) or nil
local upper = target_node.index.upper and Core.eval_expr(target_node.index.upper, scope) or nil
local step = target_node.index.step and Core.eval_expr(target_node.index.step, scope) or nil
local val_list = Runtime.builtins.list.__call({value}).elements
local length = #target.elements
local p_start, p_stop, p_step = resolve_slice(length, lower, upper, step)
if p_step ~= 1 then
local slice_len = 0
if p_step > 0 then for i = p_start, p_stop - 1, p_step do slice_len = slice_len + 1 end
else for i = p_start, p_stop + 1, p_step do slice_len = slice_len + 1 end end
if slice_len ~= #val_list then Runtime.raise("ValueError", "attempt to assign sequence of size " .. #val_list .. " to extended slice of size " .. slice_len) end
local v_idx = 1
if p_step > 0 then for i = p_start, p_stop - 1, p_step do target.elements[i + 1] = val_list[v_idx]; v_idx = v_idx + 1 end
else for i = p_start, p_stop + 1, p_step do target.elements[i + 1] = val_list[v_idx]; v_idx = v_idx + 1 end end
else
local head, tail = {}, {}
for i = 1, p_start do table.insert(head, target.elements[i]) end
for i = p_stop + 1, length do table.insert(tail, target.elements[i]) end
local new_elements = {}
for _, v in ipairs(head) do table.insert(new_elements, v) end
for _, v in ipairs(val_list) do table.insert(new_elements, v) end
for _, v in ipairs(tail) do table.insert(new_elements, v) end
target.elements = new_elements
end
else
local target = Core.eval_expr(target_node.target, scope)
local idx = Core.eval_expr(target_node.index, scope)
if type(target) == "table" and target.__type == "instance" then
local prev_depth = Runtime.Traceback.eval_depth
local success, attr = pcall(PyClass.get_attr, target, "__setitem__")
Runtime.Traceback.eval_depth = prev_depth
if success and attr and attr.__type == "bound_method" then
Core.bind_and_exec_func(attr.__func, {idx, value}, {}, attr.__self)
return
elseif target.__builtin_value ~= nil then
target = target.__builtin_value
end
end
if type(target) == "table" and target.__type == "dict" then
local found = false
for _, p in ipairs(target.pairs) do if py_eq(p.key, idx) then p.value = value; found = true; break end end
if not found then table.insert(target.pairs, {key = idx, value = value}) end
if target.__scope_ref and type(idx) == "string" then target.__scope_ref[idx] = value end
else
local lua_idx = resolve_index(target, idx)
if type(target) == "table" and target.__type == "list" then
target.elements[lua_idx] = value
elseif type(target) == "table" and (target.__type == "bytearray" or target.__type == "memoryview") then
if target.__type == "memoryview" then
if target._released then Runtime.raise("ValueError", "operation forbidden on released memoryview object") end
if target._readonly then Runtime.raise("TypeError", "cannot modify read-only memory") end
end
local bval = unwrap_int_index(value)
if type(bval) ~= "number" or math.floor(bval) ~= bval then Runtime.raise("TypeError", "an integer is required") end
if bval < 0 or bval > 255 then Runtime.raise("ValueError", "byte must be in range(0, 256)") end
target._data = target._data:sub(1, lua_idx - 1) .. string.char(bval) .. target._data:sub(lua_idx + 1)
if target.__type == "memoryview" and target._obj then target._obj._data = target._data end
else
Runtime.raise("TypeError", "object does not support item assignment")
end
end
end
elseif target_node.type == "Attribute" then
local target = Core.eval_expr(target_node.target, scope)
PyClass.set_attr(target, target_node.attr, value)
elseif target_node.type == "Tuple" or target_node.type == "List" then
local iter_state = make_iter_state(value); local unpacked = {}
while true do local has_next, v = get_next_item(iter_state); if not has_next then break end; table.insert(unpacked, v) end
local starred_idx = nil
for i, elem_node in ipairs(target_node.elements) do
if elem_node.type == "Starred" then
if starred_idx then Runtime.raise("SyntaxError", "two starred expressions in assignment") end
starred_idx = i
end
end
if starred_idx then
local min_len = #target_node.elements - 1
if #unpacked < min_len then
Runtime.raise("ValueError", "not enough values to unpack (expected at least " .. min_len .. ", got " .. #unpacked .. ")")
end
local star_len = #unpacked - min_len
local current_idx = 1
for i, elem_node in ipairs(target_node.elements) do
if i == starred_idx then
local star_list = {}
for j = 1, star_len do
table.insert(star_list, unpacked[current_idx])
current_idx = current_idx + 1
end
local starred_container_type = (type(value) == "table" and value._is_unpacked_tuple == true) and "tuple" or "list"
assign_target(elem_node.value, { __type = starred_container_type, elements = star_list }, scope)
else
assign_target(elem_node, unpacked[current_idx], scope)
current_idx = current_idx + 1
end
end
else
if #unpacked > #target_node.elements then Runtime.raise("ValueError", "too many values to unpack (expected " .. #target_node.elements .. ")") end
if #unpacked < #target_node.elements then Runtime.raise("ValueError", "not enough values to unpack (expected " .. #target_node.elements .. ", got " .. #unpacked .. ")") end
for i, elem_node in ipairs(target_node.elements) do assign_target(elem_node, unpacked[i], scope) end
end
else
Runtime.raise("SyntaxError", "invalid syntax for assignment")
end
end
Utils.assign_target = assign_target
return Utils