模組:沙盒/a2569875/PythonScript/Librarys/DataTypes
外观
-- Module:PythonScript/Librarys/DataTypes
-- 進階資料結構標準庫:完整實作 enum 與 array 模組
local DataTypes = {}
local PyClass = require("Module:沙盒/a2569875/PythonScript/Class")
local Runtime = require("Module:沙盒/a2569875/PythonScript/Runtime")
local RUtils = require("Module:沙盒/a2569875/PythonScript/Runtime/Utils")
local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
local B = Runtime.builtins
local enum_initialized = false
local enum_mod_cache = nil
local array_initialized = false
local array_mod_cache = nil
-- ==========================================
-- 0. 純 Lua 52-bit 位元運算與輔助函式 (供 Flag 與 array 使用)
-- ==========================================
local function bit_and(a, b)
a, b = math.floor(a), math.floor(b)
local res, bit = 0, 1
while a > 0 and b > 0 do
local ra, rb = a % 2, b % 2
if ra == 1 and rb == 1 then res = res + bit end
a = (a - ra) / 2
b = (b - rb) / 2
bit = bit * 2
end
return res
end
local function bit_or(a, b)
a, b = math.floor(a), math.floor(b)
local res, bit = 0, 1
while a > 0 or b > 0 do
local ra, rb = a % 2, b % 2
if ra == 1 or rb == 1 then res = res + bit end
a = (a - ra) / 2
b = (b - rb) / 2
bit = bit * 2
end
return res
end
local function bit_xor(a, b)
a, b = math.floor(a), math.floor(b)
local res, bit = 0, 1
while a > 0 or b > 0 do
local ra, rb = a % 2, b % 2
if ra ~= rb then res = res + bit end
a = (a - ra) / 2
b = (b - rb) / 2
bit = bit * 2
end
return res
end
local function is_power_of_two(n)
if type(n) ~= "number" or n <= 0 or math.floor(n) ~= n then return false end
return bit_and(n, n - 1) == 0
end
local function fn(name, func)
return { __type = "builtin_method", __name = name, __call = func }
end
-- ==========================================
-- 1. ENUM 模組核心實作
-- ==========================================
local function ensure_enum_init()
if enum_initialized then return end
enum_initialized = true
local auto_counter = 0
local AUTO_SENTINEL = {}
-- [A] 實作 auto() 與延遲運算式支援 (如 RW = R | W)
local auto_cls
local function make_auto_expr(op, left, right)
local inst = PyClass.make_instance(auto_cls)
auto_counter = auto_counter + 1
inst.__dict._seq = auto_counter
inst.__dict._expr = { op = op, left = left, right = right }
inst.__dict.value = AUTO_SENTINEL
return inst
end
auto_cls = PyClass.make_class("auto", { B.object }, {
__init__ = fn("__init__", function(a)
local self = a[1]
auto_counter = auto_counter + 1
self.__dict._seq = auto_counter
self.__dict.value = (a[2] ~= nil) and a[2] or AUTO_SENTINEL
return nil
end),
__or__ = fn("__or__", function(a) return make_auto_expr("|", a[1], a[2]) end),
__ror__ = fn("__ror__", function(a) return make_auto_expr("|", a[2], a[1]) end),
__and__ = fn("__and__", function(a) return make_auto_expr("&", a[1], a[2]) end),
__rand__ = fn("__rand__", function(a) return make_auto_expr("&", a[2], a[1]) end),
__xor__ = fn("__xor__", function(a) return make_auto_expr("^", a[1], a[2]) end),
__rxor__ = fn("__rxor__", function(a) return make_auto_expr("^", a[2], a[1]) end),
__invert__ = fn("__invert__", function(a) return make_auto_expr("~", a[1], nil) end),
__add__ = fn("__add__", function(a) return make_auto_expr("+", a[1], a[2]) end),
__radd__ = fn("__radd__", function(a) return make_auto_expr("+", a[2], a[1]) end),
})
auto_cls.__dict["__module__"] = "enum"
-- [B] member / nonmember / property 包裝器
local member_cls = PyClass.make_class("member", { B.object }, {
__init__ = fn("__init__", function(a)
auto_counter = auto_counter + 1
a[1].__dict._seq = auto_counter
a[1].__dict.value = a[2]
a[1]._is_force_member = true
return nil
end)
})
local nonmember_cls = PyClass.make_class("nonmember", { B.object }, {
__init__ = fn("__init__", function(a)
a[1].__dict.value = a[2]
a[1]._is_nonmember = true
return nil
end)
})
local enum_property = fn("property", function(a)
return { __type = "property", _is_enum_property = true, fget = a[1], fset = a[2], fdel = a[3] }
end)
-- [C] EnumType 元類別
local EnumType = PyClass.make_class("EnumType", { B.type }, {})
EnumType.__dict["__module__"] = "enum"
-- [D] 成員方法與比較/位元運算建構
local Enum, ReprEnum, IntEnum, StrEnum, Flag, IntFlag
local create_composite_flag
local function unwrap_enum_val(v)
if type(v) == "table" and v._is_enum_member then
return v.__dict._value
end
return v
end
local enum_dict = {
__module__ = "enum",
name = {
__type = "property", _is_enum_property = true,
fget = fn("name", function(a) return a[1].__dict._name end)
},
value = {
__type = "property", _is_enum_property = true,
fget = fn("value", function(a) return a[1].__dict._value end)
},
__repr__ = fn("__repr__", function(a)
local self = a[1]
local cls = self.__class
local cname = cls.__name
local mname = self.__dict._name or tostring(self.__dict._value)
if cls._is_global_enum then
local mod_name = (cls.__dict and cls.__dict["__module__"]) or "__main__"
return mod_name .. "." .. mname
end
local v_repr = RUtils.to_str(self.__dict._value, true)
if self.__dict._name == nil and cls._is_flag_class then
return string.format("<%s: %s>", cname, v_repr)
end
return string.format("<%s.%s: %s>", cname, mname, v_repr)
end),
__str__ = fn("__str__", function(a)
local self = a[1]
local cls = self.__class
local mname = self.__dict._name
if cls._is_global_enum and mname then return mname end
if mname == nil and cls._is_flag_class then
return string.format("%s(%s)", cls.__name, tostring(self.__dict._value))
end
return cls.__name .. "." .. tostring(mname)
end),
__format__ = fn("__format__", function(a)
local self, spec = a[1], a[2] or ""
local s = B.str.__call({ self })
return RUtils.format_value({ s, spec })
end),
__hash__ = fn("__hash__", function(a)
local self = a[1]
if self.__builtin_value ~= nil then
return RUtils.hash_impl({ self.__builtin_value })
end
return RUtils.hash_impl({ self.__dict._name or self.__dict._value })
end),
__eq__ = fn("__eq__", function(a)
local self, other = a[1], a[2]
if self == other then return true end
if self.__class._is_int_mixin then
if type(other) == "table" and other._is_enum_member then
if not other.__class._is_int_mixin and other.__class ~= self.__class then return false end
return RUtils.runtime_is_equal(self.__dict._value, other.__dict._value)
end
if type(other) == "number" or (type(other) == "table" and other.__type == "int") or type(other) == "boolean" then
return RUtils.runtime_is_equal(self.__dict._value, other)
end
return false
elseif self.__class._is_str_mixin then
if type(other) == "table" and other._is_enum_member then
if not other.__class._is_str_mixin and other.__class ~= self.__class then return false end
return self.__dict._value == other.__dict._value
end
return type(other) == "string" and (self.__dict._value == other)
else
if type(other) == "table" and other._is_enum_member and other.__class == self.__class then
return RUtils.runtime_is_equal(self.__dict._value, other.__dict._value)
end
return false
end
end),
__ne__ = fn("__ne__", function(a)
local eq_m = PyClass.get_attr(a[1], "__eq__")
return not RUtils.to_bool(Evaluator.invoke(eq_m, { a[2] }, {}))
end),
__bool__ = fn("__bool__", function(a)
local self = a[1]
if self.__class._is_flag_class then
return self.__dict._value ~= 0
end
return true
end),
__setattr__ = fn("__setattr__", function(a)
local self, k, v = a[1], a[2], a[3]
if not self._enum_locked then
PyClass.raw_set_attr(self, k, v)
return nil
end
Runtime.raise("AttributeError", string.format("Cannot reassign member attribute '%s'", tostring(k)))
end),
__delattr__ = fn("__delattr__", function(a)
Runtime.raise("AttributeError", string.format("%s: cannot delete attribute '%s'", a[1].__class.__name, tostring(a[2])))
end),
-- 供類別層級 [] 取值使用 (支援 Color['RED'])
__getitem__ = {
__type = "classmethod",
__func = fn("__getitem__", function(a)
local cls, key = a[1], a[2]
local member = cls._member_map and cls._member_map[key]
if not member then Runtime.raise("KeyError", tostring(key)) end
return member
end)
},
}
enum_dict.__class_getitem__ = enum_dict.__getitem__
Enum = PyClass.make_class("Enum", { B.object }, enum_dict)
Enum._is_enum_class = true
Enum._is_enum_base = true
Enum.__class = EnumType
-- [E] ReprEnum:保留混入型別 (int / str) 的 __str__ 與 __format__
local repr_enum_dict = {
__module__ = "enum",
__str__ = fn("__str__", function(a)
local self = a[1]
if self.__class._is_global_enum and self.__dict._name then
return self.__dict._name
end
return RUtils.to_str(self.__dict._value, false)
end),
__format__ = fn("__format__", function(a)
local self, spec = a[1], a[2] or ""
return RUtils.format_value({ self.__dict._value, spec })
end)
}
ReprEnum = PyClass.make_class("ReprEnum", { Enum }, repr_enum_dict)
ReprEnum._is_enum_class = true
ReprEnum._is_enum_base = true
ReprEnum._is_repr_enum = true
ReprEnum.__class = EnumType
-- [F] IntEnum:混入 int 的比較與算術運算
local int_mixin_methods = {
__module__ = "enum",
__int__ = fn("__int__", function(a) return a[1].__dict._value end),
__index__ = fn("__index__", function(a) return a[1].__dict._value end),
__float__ = fn("__float__", function(a) return tonumber(a[1].__dict._value) * 1.0 end),
__lt__ = fn("__lt__", function(a) return RUtils.runtime_is_less(unwrap_enum_val(a[1]), unwrap_enum_val(a[2])) end),
__le__ = fn("__le__", function(a)
local v1, v2 = unwrap_enum_val(a[1]), unwrap_enum_val(a[2])
return RUtils.runtime_is_equal(v1, v2) or RUtils.runtime_is_less(v1, v2)
end),
__gt__ = fn("__gt__", function(a) return RUtils.runtime_is_less(unwrap_enum_val(a[2]), unwrap_enum_val(a[1])) end),
__ge__ = fn("__ge__", function(a)
local v1, v2 = unwrap_enum_val(a[1]), unwrap_enum_val(a[2])
return RUtils.runtime_is_equal(v1, v2) or RUtils.runtime_is_less(v2, v1)
end),
__add__ = fn("__add__", function(a) return unwrap_enum_val(a[1]) + unwrap_enum_val(a[2]) end),
__radd__ = fn("__radd__", function(a) return unwrap_enum_val(a[2]) + unwrap_enum_val(a[1]) end),
__sub__ = fn("__sub__", function(a) return unwrap_enum_val(a[1]) - unwrap_enum_val(a[2]) end),
__rsub__ = fn("__rsub__", function(a) return unwrap_enum_val(a[2]) - unwrap_enum_val(a[1]) end),
__mul__ = fn("__mul__", function(a) return unwrap_enum_val(a[1]) * unwrap_enum_val(a[2]) end),
__rmul__ = fn("__rmul__", function(a) return unwrap_enum_val(a[2]) * unwrap_enum_val(a[1]) end),
__truediv__ = fn("__truediv__", function(a) return unwrap_enum_val(a[1]) / unwrap_enum_val(a[2]) end),
__rtruediv__ = fn("__rtruediv__", function(a) return unwrap_enum_val(a[2]) / unwrap_enum_val(a[1]) end),
__floordiv__ = fn("__floordiv__", function(a) return math.floor(unwrap_enum_val(a[1]) / unwrap_enum_val(a[2])) end),
__rfloordiv__= fn("__rfloordiv__",function(a) return math.floor(unwrap_enum_val(a[2]) / unwrap_enum_val(a[1])) end),
__mod__ = fn("__mod__", function(a) return unwrap_enum_val(a[1]) % unwrap_enum_val(a[2]) end),
__rmod__ = fn("__rmod__", function(a) return unwrap_enum_val(a[2]) % unwrap_enum_val(a[1]) end),
__neg__ = fn("__neg__", function(a) return -unwrap_enum_val(a[1]) end),
__pos__ = fn("__pos__", function(a) return unwrap_enum_val(a[1]) end),
__abs__ = fn("__abs__", function(a) return math.abs(unwrap_enum_val(a[1])) end),
__or__ = fn("__or__", function(a) return bit_or(unwrap_enum_val(a[1]), unwrap_enum_val(a[2])) end),
__ror__ = fn("__ror__", function(a) return bit_or(unwrap_enum_val(a[2]), unwrap_enum_val(a[1])) end),
__and__ = fn("__and__", function(a) return bit_and(unwrap_enum_val(a[1]), unwrap_enum_val(a[2])) end),
__rand__ = fn("__rand__", function(a) return bit_and(unwrap_enum_val(a[2]), unwrap_enum_val(a[1])) end),
__xor__ = fn("__xor__", function(a) return bit_xor(unwrap_enum_val(a[1]), unwrap_enum_val(a[2])) end),
__rxor__ = fn("__rxor__", function(a) return bit_xor(unwrap_enum_val(a[2]), unwrap_enum_val(a[1])) end),
}
IntEnum = PyClass.make_class("IntEnum", { B.int, ReprEnum }, int_mixin_methods)
IntEnum._is_enum_class = true
IntEnum._is_enum_base = true
IntEnum._is_int_mixin = true
IntEnum.__class = EnumType
-- [G] StrEnum:混入 str 的比較與串接運算
local str_mixin_methods = {
__module__ = "enum",
__lt__ = fn("__lt__", function(a) return tostring(unwrap_enum_val(a[1])) < tostring(unwrap_enum_val(a[2])) end),
__le__ = fn("__le__", function(a) return tostring(unwrap_enum_val(a[1])) <= tostring(unwrap_enum_val(a[2])) end),
__gt__ = fn("__gt__", function(a) return tostring(unwrap_enum_val(a[1])) > tostring(unwrap_enum_val(a[2])) end),
__ge__ = fn("__ge__", function(a) return tostring(unwrap_enum_val(a[1])) >= tostring(unwrap_enum_val(a[2])) end),
__add__ = fn("__add__", function(a) return tostring(unwrap_enum_val(a[1])) .. tostring(unwrap_enum_val(a[2])) end),
__radd__ = fn("__radd__", function(a) return tostring(unwrap_enum_val(a[2])) .. tostring(unwrap_enum_val(a[1])) end),
__contains__ = fn("__contains__", function(a)
local s, sub = tostring(unwrap_enum_val(a[1])), tostring(unwrap_enum_val(a[2]))
return s:find(sub, 1, true) ~= nil
end),
__len__ = fn("__len__", function(a) return mw.ustring.len(a[1].__dict._value) end),
}
StrEnum = PyClass.make_class("StrEnum", { B.str, ReprEnum }, str_mixin_methods)
StrEnum._is_enum_class = true
StrEnum._is_enum_base = true
StrEnum._is_str_mixin = true
StrEnum.__class = EnumType
-- [H] Flag 與 IntFlag 位元合成引擎
local function decompose_flag_members(cls, val)
local members = {}
local covered = 0
for _, m in ipairs(cls._member_list or {}) do
local mv = m.__dict._value
if mv > 0 and is_power_of_two(mv) and bit_and(val, mv) == mv then
table.insert(members, m)
covered = bit_or(covered, mv)
end
end
-- 若有複合名稱但含單一位元未涵蓋,再檢查其他成員
if covered ~= bit_and(val, cls._all_flags_mask or 0) then
for _, m in ipairs(cls._member_list or {}) do
local mv = m.__dict._value
if mv > 0 and not is_power_of_two(mv) and bit_and(val, mv) == mv then
if bit_and(covered, mv) ~= mv then
table.insert(members, m)
covered = bit_or(covered, mv)
end
end
end
end
local leftover = val - bit_and(val, covered)
return members, leftover
end
create_composite_flag = function(cls, raw_val)
if type(raw_val) ~= "number" or math.floor(raw_val) ~= raw_val then
Runtime.raise("ValueError", string.format("%s is not a valid %s", RUtils.to_str(raw_val, true), cls.__name))
end
local boundary = cls._boundary_mode or (cls._is_int_mixin and "KEEP" or "STRICT")
local mask = cls._all_flags_mask or 0
local val = raw_val
if val < 0 then
if boundary == "EJECT" then return val end
if boundary == "STRICT" then
Runtime.raise("ValueError", string.format("%d is not a valid %s", val, cls.__name))
elseif boundary == "CONFORM" then
-- 二補數轉換後與 mask 交集
local two_comp = (mask + 1) * 2 + val
val = bit_and(two_comp, mask)
end
end
if cls._value_map and cls._value_map[val] then
return cls._value_map[val]
end
local unknown = val - bit_and(val, mask)
if unknown ~= 0 then
if boundary == "STRICT" then
Runtime.raise("ValueError", string.format("%d is not a valid %s", raw_val, cls.__name))
elseif boundary == "CONFORM" then
val = bit_and(val, mask)
if cls._value_map and cls._value_map[val] then return cls._value_map[val] end
elseif boundary == "EJECT" then
return raw_val
end
-- KEEP 模式則保留原 val 繼續往下建構複合 Flag
end
local parts, leftover = decompose_flag_members(cls, val)
local name_parts = {}
for _, m in ipairs(parts) do table.insert(name_parts, m.__dict._name) end
if leftover > 0 then table.insert(name_parts, tostring(leftover)) end
local comp_name = (#name_parts > 0) and table.concat(name_parts, "|") or nil
local inst = PyClass.make_instance(cls)
inst._is_enum_member = true
inst.__dict._name = comp_name
inst.__dict._value = val
if cls._is_int_mixin then inst.__builtin_value = val end
inst._enum_locked = true
cls._value_map[val] = inst
return inst
end
local function extract_flag_int(self, other, allow_int)
if type(other) == "table" and other._is_enum_member and other.__class == self.__class then
return other.__dict._value
elseif allow_int and (type(other) == "number" or type(other) == "boolean") and math.floor(RUtils.unwrap_num(other)) == RUtils.unwrap_num(other) then
return RUtils.unwrap_num(other)
end
return nil
end
local flag_dict = {
__module__ = "enum",
__or__ = fn("__or__", function(a)
local self, other = a[1], a[2]
local ov = extract_flag_int(self, other, self.__class._is_int_mixin)
if ov == nil then Runtime.raise("TypeError", "unsupported operand type(s) for |") end
return create_composite_flag(self.__class, bit_or(self.__dict._value, ov))
end),
__ror__ = fn("__ror__", function(a)
local self, other = a[1], a[2]
local ov = extract_flag_int(self, other, self.__class._is_int_mixin)
if ov == nil then Runtime.raise("TypeError", "unsupported operand type(s) for |") end
return create_composite_flag(self.__class, bit_or(ov, self.__dict._value))
end),
__and__ = fn("__and__", function(a)
local self, other = a[1], a[2]
local ov = extract_flag_int(self, other, self.__class._is_int_mixin)
if ov == nil then Runtime.raise("TypeError", "unsupported operand type(s) for &") end
return create_composite_flag(self.__class, bit_and(self.__dict._value, ov))
end),
__rand__ = fn("__rand__", function(a)
local self, other = a[1], a[2]
local ov = extract_flag_int(self, other, self.__class._is_int_mixin)
if ov == nil then Runtime.raise("TypeError", "unsupported operand type(s) for &") end
return create_composite_flag(self.__class, bit_and(ov, self.__dict._value))
end),
__xor__ = fn("__xor__", function(a)
local self, other = a[1], a[2]
local ov = extract_flag_int(self, other, self.__class._is_int_mixin)
if ov == nil then Runtime.raise("TypeError", "unsupported operand type(s) for ^") end
return create_composite_flag(self.__class, bit_xor(self.__dict._value, ov))
end),
__rxor__ = fn("__rxor__", function(a)
local self, other = a[1], a[2]
local ov = extract_flag_int(self, other, self.__class._is_int_mixin)
if ov == nil then Runtime.raise("TypeError", "unsupported operand type(s) for ^") end
return create_composite_flag(self.__class, bit_xor(ov, self.__dict._value))
end),
__invert__ = fn("__invert__", function(a)
local self = a[1]
local cls = self.__class
if cls._boundary_mode == "EJECT" then
return -(self.__dict._value + 1)
end
local mask = cls._all_flags_mask or 0
local inv = bit_xor(mask, bit_and(mask, self.__dict._value))
return create_composite_flag(cls, inv)
end),
__contains__ = fn("__contains__", function(a)
local self, other = a[1], a[2]
local ov = extract_flag_int(self, other, self.__class._is_int_mixin)
if ov == nil then
Runtime.raise("TypeError", "unsupported operand type(s) for 'in'")
end
if ov == 0 then return false end
return bit_and(self.__dict._value, ov) == ov
end),
__iter__ = fn("__iter__", function(a)
local self = a[1]
local parts = decompose_flag_members(self.__class, self.__dict._value)
return B.iter({ { __type = "list", elements = parts } })
end),
__len__ = fn("__len__", function(a)
local self = a[1]
local parts = decompose_flag_members(self.__class, self.__dict._value)
return #parts
end),
}
Flag = PyClass.make_class("Flag", { Enum }, flag_dict)
Flag._is_enum_class = true
Flag._is_enum_base = true
Flag._is_flag_class = true
Flag.__class = EnumType
local int_flag_dict = {}
for k, v in pairs(int_mixin_methods) do int_flag_dict[k] = v end
for k, v in pairs(flag_dict) do int_flag_dict[k] = v end
IntFlag = PyClass.make_class("IntFlag", { B.int, ReprEnum, Flag }, int_flag_dict)
IntFlag._is_enum_class = true
IntFlag._is_enum_base = true
IntFlag._is_flag_class = true
IntFlag._is_int_mixin = true
IntFlag.__class = EnumType
-- ==========================================
-- [I] 攔截 PyClass.make_class:建構 Enum 子類別的黑魔法核心
-- ==========================================
local orig_make_class = PyClass.make_class
local function find_source_positions(cls_name, keys)
local pos_map = {}
local tb = Runtime.Traceback
local file_entry = tb and tb.files and tb.files[tb.current_file_idx or 1]
local raw_code = file_entry and file_entry.raw_code or ""
if raw_code == "" then return pos_map end
local cls_start = raw_code:find("class%s+" .. cls_name .. "[%s%(%:]+") or 1
for _, k in ipairs(keys) do
local pat = "[\r\n;][ \t]*" .. k:gsub("([^%w_])", "%%%1") .. "[ \t]*[:=]"
local s = raw_code:find(pat, cls_start)
if s then pos_map[k] = s end
end
return pos_map
end
local function eval_auto_expr(node, resolved_map)
if type(node) == "number" then return node end
if type(node) == "table" then
if node.__class == auto_cls then
if node.__dict._expr then
local e = node.__dict._expr
local lv = eval_auto_expr(e.left, resolved_map)
local rv = e.right and eval_auto_expr(e.right, resolved_map) or 0
if e.op == "|" then return bit_or(lv, rv)
elseif e.op == "&" then return bit_and(lv, rv)
elseif e.op == "^" then return bit_xor(lv, rv)
elseif e.op == "+" then return lv + rv
elseif e.op == "~" then return bit_xor(0xFFFFFFFF, lv) end
end
return resolved_map[node] or node.__dict.value
elseif node._is_enum_member then
return node.__dict._value
end
end
return tonumber(node) or 0
end
PyClass.make_class = function(name, bases, dict)
local is_enum_sub = false
local is_flag_sub = false
local is_int_sub = false
local is_str_sub = false
for _, b in ipairs(bases or {}) do
if type(b) == "table" then
if b._is_enum_class then is_enum_sub = true end
if b._is_flag_class then is_flag_sub = true end
if b._is_int_mixin or b == B.int then is_int_sub = true end
if b._is_str_mixin or b == B.str then is_str_sub = true end
end
end
if not is_enum_sub or not dict or dict._skip_enum_meta then
if dict then dict._skip_enum_meta = nil end
return orig_make_class(name, bases, dict)
end
-- 1. 讀取 _ignore_ 與 _boundary_ 設定
local ignore_set = {
_ignore_ = true, _order_ = true, __order__ = true,
_boundary_ = true, boundary = true,
_generate_next_value_ = true, _missing_ = true
}
local raw_ignore = dict["_ignore_"]
if type(raw_ignore) == "string" then
for w in raw_ignore:gmatch("%S+") do ignore_set[w] = true end
elseif type(raw_ignore) == "table" and raw_ignore.elements then
for _, w in ipairs(raw_ignore.elements) do ignore_set[tostring(w)] = true end
end
local boundary_raw = dict["_boundary_"] or dict["boundary"]
local boundary_mode = nil
if boundary_raw ~= nil then
if type(boundary_raw) == "table" and boundary_raw._is_enum_member then
boundary_mode = tostring(boundary_raw.__dict._name or boundary_raw.__dict._value):upper()
elseif type(boundary_raw) == "string" then
boundary_mode = boundary_raw:upper()
end
end
-- 2. 分離成員候選者與一般方法/描述器
local raw_candidates = {}
local cand_keys = {}
local class_dict = {}
local custom_init = dict["__init__"]
local custom_gnv = dict["_generate_next_value_"]
for k, v in pairs(dict) do
if type(k) == "string" and (k:match("^__.*__$") or ignore_set[k]) then
if k ~= "_ignore_" and k ~= "__init__" then
class_dict[k] = v
end
elseif type(v) == "table" and v._is_nonmember then
class_dict[k] = v.__dict.value
elseif type(v) == "table" and v._is_force_member then
table.insert(cand_keys, k)
raw_candidates[k] = { val = v.__dict.value, seq = v.__dict._seq }
elseif type(v) == "table" and (
v.__type == "function" or v.__type == "builtin_method" or
v.__type == "classmethod" or v.__type == "staticmethod" or
v.__type == "property" or v._is_enum_property or v.__type == "class"
) then
class_dict[k] = v
else
local seq = (type(v) == "table" and v.__class == auto_cls and v.__dict._seq) or nil
table.insert(cand_keys, k)
raw_candidates[k] = { val = v, seq = seq }
end
end
-- 3. 依據原始碼位置與 auto() 序號還原定義順序
local pos_map = find_source_positions(name, cand_keys)
local order_hint = dict["_order_"] or dict["__order__"]
local order_idx = {}
if type(order_hint) == "string" then
local idx = 0
for w in order_hint:gsub(",", " "):gmatch("%S+") do idx = idx + 1; order_idx[w] = idx end
elseif type(order_hint) == "table" and order_hint.elements then
for i, w in ipairs(order_hint.elements) do order_idx[tostring(w)] = i end
end
table.sort(cand_keys, function(a, b)
if order_idx[a] and order_idx[b] then return order_idx[a] < order_idx[b] end
local pa, pb = pos_map[a], pos_map[b]
if pa and pb and pa ~= pb then return pa < pb end
local sa, sb = raw_candidates[a].seq, raw_candidates[b].seq
if sa and sb and sa ~= sb then return sa < sb end
return tostring(a) < tostring(b)
end)
-- 4. 處理 auto() 自動賦值
local last_values = {}
local resolved_autos = {}
local count = 0
for _, k in ipairs(cand_keys) do
local entry = raw_candidates[k]
local v = entry.val
if type(v) == "table" and v.__class == auto_cls then
if v.__dict._expr then
v = eval_auto_expr(v, resolved_autos)
elseif v.__dict.value ~= AUTO_SENTINEL then
v = v.__dict.value
else
if custom_gnv then
local fn_gnv = (custom_gnv.__type == "staticmethod" or custom_gnv.__type == "classmethod") and custom_gnv.__func or custom_gnv
v = Evaluator.invoke(fn_gnv, { k, 1, count, { __type = "list", elements = last_values } }, {})
elseif is_str_sub then
v = k:lower()
elseif is_flag_sub then
local max_bit = 0
for _, lv in ipairs(last_values) do
if type(lv) == "number" and lv > max_bit then max_bit = lv end
end
if max_bit == 0 then v = 1
else
local p = 1
while p <= max_bit do p = p * 2 end
v = p
end
else
local max_int = 0
for _, lv in ipairs(last_values) do
if type(lv) == "number" and math.floor(lv) == lv and lv > max_int then
max_int = lv
end
end
v = max_int + 1
end
end
resolved_autos[entry.val] = v
entry.val = v
end
count = count + 1
table.insert(last_values, entry.val)
end
-- 5. 建立 Enum 子類別本體
local cls = orig_make_class(name, bases, class_dict)
cls._is_enum_class = true
cls._is_flag_class = is_flag_sub
cls._is_int_mixin = is_int_sub
cls._is_str_mixin = is_str_sub
cls._boundary_mode = boundary_mode or (is_flag_sub and (is_int_sub and "KEEP" or "STRICT") or nil)
cls.__class = EnumType
cls._member_list = {}
cls._member_map = {}
cls._value_map = {}
cls._aliases = {}
cls._all_flags_mask = 0
cls._single_bit_mask = 0
-- 6. 實例化列舉成員與處理別名 (Aliases)
for _, k in ipairs(cand_keys) do
local raw_val = raw_candidates[k].val
local member_val = raw_val
local init_args = { raw_val }
if is_int_sub then
if type(raw_val) == "table" and raw_val.__type == "tuple" then
member_val = B.int.__call({ raw_val.elements[1] })
init_args = raw_val.elements
else
member_val = B.int.__call({ raw_val })
end
elseif is_str_sub then
if type(raw_val) == "table" and raw_val.__type == "tuple" then
member_val = tostring(raw_val.elements[1])
init_args = raw_val.elements
else
if type(raw_val) ~= "string" then
Runtime.raise("TypeError", string.format("%s is not a string", RUtils.to_str(raw_val, true)))
end
member_val = raw_val
end
elseif custom_init and type(raw_val) == "table" and raw_val.__type == "tuple" then
init_args = raw_val.elements
end
-- 檢查是否為既有成員的別名 (Alias)
local existing_canonical = nil
for _, m in ipairs(cls._member_list) do
if RUtils.runtime_is_equal(m.__dict._value, member_val) and type(m.__dict._value) == type(member_val) then
existing_canonical = m
break
end
end
if existing_canonical then
cls._member_map[k] = existing_canonical
cls.__dict[k] = existing_canonical
table.insert(cls._aliases, { alias = k, canonical = existing_canonical.__dict._name, value = member_val })
else
local inst = PyClass.make_instance(cls)
inst._is_enum_member = true
inst.__dict._name = k
inst.__dict._value = member_val
if is_int_sub or is_str_sub then
inst.__builtin_value = member_val
end
if custom_init then
local call_args = { inst }
for _, arg in ipairs(init_args) do table.insert(call_args, arg) end
Evaluator.invoke(custom_init, call_args, {})
end
inst._enum_locked = true
table.insert(cls._member_list, inst)
cls._member_map[k] = inst
cls.__dict[k] = inst
if type(member_val) ~= "table" then
cls._value_map[member_val] = inst
end
if is_flag_sub and type(member_val) == "number" and member_val > 0 then
cls._all_flags_mask = bit_or(cls._all_flags_mask, member_val)
if is_power_of_two(member_val) then
cls._single_bit_mask = bit_or(cls._single_bit_mask, member_val)
end
end
end
end
-- 7. 建構 __members__ 字典 (含別名)
local members_pairs = {}
for _, k in ipairs(cand_keys) do
table.insert(members_pairs, { key = k, value = cls._member_map[k] })
end
cls.__dict["__members__"] = { __type = "dict", pairs = members_pairs }
-- 8. 掛載 Cls(val) 值反查構造器
local function enum_lookup_by_value(args)
local offset = (args and args[1] == cls) and 1 or 0
if not args or #args <= offset then
Runtime.raise("TypeError", string.format("%s() missing required argument: 'value'", cls.__name))
end
local target_val = args[offset + 1]
if type(target_val) == "table" and target_val._is_enum_member and target_val.__class == cls then
return target_val
end
if type(target_val) ~= "table" and cls._value_map[target_val] ~= nil then
return cls._value_map[target_val]
end
for _, m in ipairs(cls._member_list) do
if RUtils.runtime_is_equal(m.__dict._value, target_val) and type(m.__dict._value) == type(target_val) then
return m
end
end
if cls._is_flag_class and type(target_val) == "number" and math.floor(target_val) == target_val then
return create_composite_flag(cls, target_val)
end
local missing_fn = dict["_missing_"]
if missing_fn then
local fn_m = (missing_fn.__type == "classmethod" or missing_fn.__type == "staticmethod") and missing_fn.__func or missing_fn
local res = Evaluator.invoke(fn_m, { cls, target_val }, {})
if res ~= nil and res ~= B.None then return res end
end
Runtime.raise("ValueError", string.format("%s is not a valid %s", RUtils.to_str(target_val, true), cls.__name))
end
cls.__dict["__new__"] = {
__type = "staticmethod",
__func = fn("__new__", enum_lookup_by_value)
}
cls.__dict["__init__"] = fn("__init__", function() return nil end)
cls.__call = enum_lookup_by_value
setmetatable(cls, { __call = function(_, args, kwargs) return enum_lookup_by_value(args, kwargs) end })
cls._enum_locked = true
return cls
end
-- ==========================================
-- [J] 掛載系統級攔截:支援 iter(Color), len(Color), reversed(Color) 與屬性鎖定
-- ==========================================
if not B._enum_patched then
B._enum_patched = true
local orig_len = B.len
local orig_iter = B.iter
local orig_reversed = B.reversed
local orig_raw_set_attr = PyClass.raw_set_attr
local orig_runtime_eq = RUtils.runtime_is_equal
B.len = function(args)
local obj = args and args[1]
if type(obj) == "table" and obj.__type == "class" and obj._is_enum_class then
return #(obj._member_list or {})
end
return orig_len(args)
end
B.iter = function(args)
local obj = args and args[1]
if type(obj) == "table" and obj.__type == "class" and obj._is_enum_class then
return orig_iter({ { __type = "list", elements = obj._member_list or {} } })
end
return orig_iter(args)
end
B.reversed = function(args)
local obj = args and args[1]
if type(obj) == "table" and obj.__type == "class" and obj._is_enum_class then
return orig_reversed({ { __type = "list", elements = obj._member_list or {} } })
end
return orig_reversed(args)
end
PyClass.raw_set_attr = function(obj, attr_name, value)
if type(obj) == "table" and obj.__type == "class" and obj._is_enum_class and obj._enum_locked then
if obj._member_map and obj._member_map[attr_name] then
Runtime.raise("AttributeError", "cannot reassign member '" .. tostring(attr_name) .. "'")
end
end
return orig_raw_set_attr(obj, attr_name, value)
end
RUtils.runtime_is_equal = function(a, b, seen)
if a == b then return true end
if type(a) == "table" and a._is_enum_member and a.__builtin_value ~= nil then
if type(b) == "table" and b._is_enum_member then
if b.__builtin_value ~= nil then return orig_runtime_eq(a.__builtin_value, b.__builtin_value, seen) end
return false
end
return orig_runtime_eq(a.__builtin_value, b, seen)
end
if type(b) == "table" and b._is_enum_member and b.__builtin_value ~= nil then
return orig_runtime_eq(a, b.__builtin_value, seen)
end
return orig_runtime_eq(a, b, seen)
end
end
-- ==========================================
-- [K] 建立 EnumCheck、FlagBoundary、@unique、@verify、@global_enum
-- ==========================================
local EnumCheck = PyClass.make_class("EnumCheck", { StrEnum }, {
CONTINUOUS = "no skipped integer values",
NAMED_FLAGS = "multi-flag aliases may not contain unnamed flags",
UNIQUE = "one name per value",
_order_ = "CONTINUOUS NAMED_FLAGS UNIQUE"
})
local FlagBoundary = PyClass.make_class("FlagBoundary", { StrEnum }, {
STRICT = "strict",
CONFORM = "conform",
EJECT = "eject",
KEEP = "keep",
_order_ = "STRICT CONFORM EJECT KEEP"
})
local function check_unique(cls)
if cls._aliases and #cls._aliases > 0 then
local parts = {}
for _, al in ipairs(cls._aliases) do
table.insert(parts, string.format("%s -> %s", al.alias, al.canonical))
end
Runtime.raise("ValueError", string.format("duplicate values found in <enum '%s'>: %s", cls.__name, table.concat(parts, ", ")))
end
return cls
end
local function check_continuous(cls)
local vals = {}
for _, m in ipairs(cls._member_list or {}) do
local v = m.__dict._value
if type(v) ~= "number" or math.floor(v) ~= v then
Runtime.raise("ValueError", string.format("invalid enum '%s': non-integer values present", cls.__name))
end
table.insert(vals, v)
end
if #vals >= 2 then
table.sort(vals)
local val_set = {}
for _, v in ipairs(vals) do val_set[v] = true end
local missing = {}
for i = vals[1], vals[#vals] do
if not val_set[i] then table.insert(missing, tostring(i)) end
end
if #missing > 0 then
Runtime.raise("ValueError", string.format("invalid enum '%s': missing values %s", cls.__name, table.concat(missing, ", ")))
end
end
return cls
end
local function check_named_flags(cls)
local single_mask = cls._single_bit_mask or 0
local bad = {}
for _, m in ipairs(cls._member_list or {}) do
local v = m.__dict._value
if type(v) == "number" and v > 0 and not is_power_of_two(v) then
if bit_and(v, single_mask) ~= v then
table.insert(bad, m.__dict._name)
end
end
end
if #bad > 0 then
Runtime.raise("ValueError", string.format("invalid Flag '%s': alias %s is missing value", cls.__name, table.concat(bad, ", ")))
end
return cls
end
local unique_fn = fn("unique", function(a)
local offset = (a[1] == enum_mod_cache) and 1 or 0
return check_unique(a[offset + 1])
end)
local verify_fn = fn("verify", function(a)
local start_i = (a[1] == enum_mod_cache) and 2 or 1
local checks = {}
for i = start_i, #a do table.insert(checks, a[i]) end
return fn("verify_decorator", function(dec_args)
local cls = dec_args[1]
for _, chk in ipairs(checks) do
local cname = (type(chk) == "table" and chk._is_enum_member) and chk.__dict._name or tostring(chk)
if cname == "UNIQUE" or chk == EnumCheck.__dict.UNIQUE then
check_unique(cls)
elseif cname == "CONTINUOUS" or chk == EnumCheck.__dict.CONTINUOUS then
check_continuous(cls)
elseif cname == "NAMED_FLAGS" or chk == EnumCheck.__dict.NAMED_FLAGS then
check_named_flags(cls)
end
end
return cls
end)
end)
local global_enum_fn = fn("global_enum", function(a)
local offset = (a[1] == enum_mod_cache) and 1 or 0
local cls = a[offset + 1]
cls._is_global_enum = true
local scope = Runtime.Traceback.current_scope
if scope then
local root = scope
while root.parent do root = root.parent end
for k, m in pairs(cls._member_map or {}) do
root.vars[k] = m
end
end
return cls
end)
-- 封裝匯出 enum 模組
enum_mod_cache = PyClass.make_instance(PyClass.make_class("module", {}, {}))
local d = enum_mod_cache.__dict
d["__name__"] = "enum"
d.Enum = Enum
d.IntEnum = IntEnum
d.StrEnum = StrEnum
d.Flag = Flag
d.IntFlag = IntFlag
d.ReprEnum = ReprEnum
d.EnumType = EnumType
d.EnumMeta = EnumType
d.EnumCheck = EnumCheck
d.CONTINUOUS = EnumCheck.__dict.CONTINUOUS
d.NAMED_FLAGS = EnumCheck.__dict.NAMED_FLAGS
d.UNIQUE = EnumCheck.__dict.UNIQUE
d.FlagBoundary = FlagBoundary
d.STRICT = FlagBoundary.__dict.STRICT
d.CONFORM = FlagBoundary.__dict.CONFORM
d.EJECT = FlagBoundary.__dict.EJECT
d.KEEP = FlagBoundary.__dict.KEEP
d.auto = auto_cls
d.unique = unique_fn
d.verify = verify_fn
d.property = enum_property
d.member = member_cls
d.nonmember = nonmember_cls
d.global_enum = global_enum_fn
end
-- ==========================================
-- 2. ARRAY 模組核心實作
-- ==========================================
local TYPECODE_SPECS = {
b = { size = 1, kind = "int", signed = true, min = -128, max = 127 },
B = { size = 1, kind = "int", signed = false, min = 0, max = 255 },
u = { size = 2, kind = "uchar" },
w = { size = 4, kind = "uchar" },
h = { size = 2, kind = "int", signed = true, min = -32768, max = 32767 },
H = { size = 2, kind = "int", signed = false, min = 0, max = 65535 },
i = { size = 4, kind = "int", signed = true, min = -2147483648, max = 2147483647 },
I = { size = 4, kind = "int", signed = false, min = 0, max = 4294967295 },
l = { size = 4, kind = "int", signed = true, min = -2147483648, max = 2147483647 },
L = { size = 4, kind = "int", signed = false, min = 0, max = 4294967295 },
q = { size = 8, kind = "int64", signed = true },
Q = { size = 8, kind = "int64", signed = false },
f = { size = 4, kind = "float", single = true },
d = { size = 8, kind = "float", single = false },
}
-- Little-Endian 二進位整數與 IEEE-754 浮點數打包/解包器
local function pack_uint_le(val, nbytes)
local bytes = {}
for i = 1, nbytes do
local r = val % 256
bytes[i] = string.char(r)
val = (val - r) / 256
end
return table.concat(bytes)
end
local function unpack_uint_le(s, pos, nbytes)
local val, mult = 0, 1
for i = 0, nbytes - 1 do
val = val + string.byte(s, pos + i) * mult
mult = mult * 256
end
return val
end
local function pack_float32_le(x)
if x == 0 then
local is_neg = (1 / x == -math.huge)
return string.char(0, 0, 0, is_neg and 128 or 0)
elseif x ~= x then
return string.char(0, 0, 192, 127)
elseif x == math.huge or x == -math.huge then
return string.char(0, 0, 128, x < 0 and 255 or 127)
end
local sign = 0
if x < 0 then sign = 128; x = -x end
local mant, exp = math.frexp(x)
exp = exp + 126
if exp <= 0 then
mant = math.floor(mant * 2 ^ (24 + exp) + 0.5)
exp = 0
else
mant = math.floor((mant * 2 - 1) * 2 ^ 23 + 0.5)
if mant >= 2 ^ 23 then mant = 0; exp = exp + 1 end
if exp >= 255 then return string.char(0, 0, 128, sign + 127) end
end
local b1 = mant % 256; mant = (mant - b1) / 256
local b2 = mant % 256; local b3_m = (mant - b2) / 256
local b3 = b3_m + (exp % 2) * 128
local b4 = sign + math.floor(exp / 2)
return string.char(b1, b2, b3, b4)
end
local function unpack_float32_le(s, pos)
local b1, b2, b3, b4 = string.byte(s, pos, pos + 3)
local sign = (b4 >= 128) and -1 or 1
local exp = (b4 % 128) * 2 + math.floor(b3 / 128)
local mant = ((b3 % 128) * 256 + b2) * 256 + b1
if exp == 0 and mant == 0 then return 0.0 * sign
elseif exp == 255 then return (mant == 0) and (math.huge * sign) or (0/0)
elseif exp == 0 then return sign * math.ldexp(mant, -149)
else return sign * math.ldexp(mant + 2 ^ 23, exp - 150) end
end
local function pack_float64_le(x)
if x == 0 then
local is_neg = (1 / x == -math.huge)
return string.char(0, 0, 0, 0, 0, 0, 0, is_neg and 128 or 0)
elseif x ~= x then
return string.char(0, 0, 0, 0, 0, 0, 248, 127)
elseif x == math.huge or x == -math.huge then
return string.char(0, 0, 0, 0, 0, 0, 240, x < 0 and 255 or 127)
end
local sign = 0
if x < 0 then sign = 128; x = -x end
local mant, exp = math.frexp(x)
exp = exp + 1022
if exp <= 0 then
mant = math.floor(mant * 2 ^ (53 + exp) + 0.5)
exp = 0
else
mant = math.floor((mant * 2 - 1) * 2 ^ 52 + 0.5)
if mant >= 2 ^ 52 then mant = 0; exp = exp + 1 end
if exp >= 2047 then return string.char(0, 0, 0, 0, 0, 0, 240, sign + 127) end
end
local bytes = {}
for i = 1, 6 do
local r = mant % 256
bytes[i] = string.char(r)
mant = (mant - r) / 256
end
bytes[7] = string.char(mant + (exp % 16) * 16)
bytes[8] = string.char(sign + math.floor(exp / 16))
return table.concat(bytes)
end
local function unpack_float64_le(s, pos)
local b1, b2, b3, b4, b5, b6, b7, b8 = string.byte(s, pos, pos + 7)
local sign = (b8 >= 128) and -1 or 1
local exp = (b8 % 128) * 16 + math.floor(b7 / 16)
local mant = ((((((b7 % 16) * 256 + b6) * 256 + b5) * 256 + b4) * 256 + b3) * 256 + b2) * 256 + b1
if exp == 0 and mant == 0 then return 0.0 * sign
elseif exp == 2047 then return (mant == 0) and (math.huge * sign) or (0/0)
elseif exp == 0 then return sign * math.ldexp(mant, -1074)
else return sign * math.ldexp(mant + 2 ^ 52, exp - 1075) end
end
local function validate_and_coerce_item(tc, spec, val)
if type(val) == "table" and val.__type == "instance" and val.__builtin_value ~= nil then
val = val.__builtin_value
end
if spec.kind == "uchar" then
if type(val) ~= "string" or mw.ustring.len(val) ~= 1 then
Runtime.raise("TypeError", "array item must be unicode character")
end
return val
elseif spec.kind == "float" then
if type(val) == "boolean" then val = val and 1.0 or 0.0 end
local n = RUtils.unwrap_num(val)
if type(n) ~= "number" then
Runtime.raise("TypeError", "must be real number, not " .. RUtils.get_type_str(val))
end
if spec.single then
return unpack_float32_le(pack_float32_le(n), 1)
end
return n
elseif spec.kind == "int" then
if type(val) == "number" and math.floor(val) ~= val then
Runtime.raise("TypeError", "'" .. RUtils.get_type_str(val) .. "' object cannot be interpreted as an integer")
end
if type(val) == "table" and val.__type == "int" and val.is_big then
Runtime.raise("OverflowError", "int too big to convert")
end
local n = RUtils.unwrap_num(val)
if type(n) ~= "number" or math.floor(n) ~= n then
Runtime.raise("TypeError", "'" .. RUtils.get_type_str(val) .. "' object cannot be interpreted as an integer")
end
if n < spec.min then
Runtime.raise("OverflowError", spec.signed and "signed integer is less than minimum" or "can't convert negative value to unsigned int")
elseif n > spec.max then
Runtime.raise("OverflowError", (spec.signed and "signed" or "unsigned") .. " integer is greater than maximum")
end
return n
elseif spec.kind == "int64" then
if type(val) == "number" and math.floor(val) ~= val then
Runtime.raise("TypeError", "'float' object cannot be interpreted as an integer")
end
local n = RUtils.unwrap_num(val)
if type(n) ~= "number" then
Runtime.raise("TypeError", "'" .. RUtils.get_type_str(val) .. "' object cannot be interpreted as an integer")
end
if not spec.signed and n < 0 then
Runtime.raise("OverflowError", "can't convert negative value to unsigned int")
end
return n
end
end
local function pack_item(tc, spec, v)
if spec.kind == "uchar" then
return pack_uint_le(mw.ustring.codepoint(v, 1), spec.size)
elseif spec.kind == "float" then
return spec.single and pack_float32_le(v) or pack_float64_le(v)
elseif spec.kind == "int" or spec.kind == "int64" then
local u = v
if u < 0 then u = u + (256 ^ spec.size) end
return pack_uint_le(u, spec.size)
end
end
local function unpack_item(tc, spec, s, pos)
if spec.kind == "uchar" then
local cp = unpack_uint_le(s, pos, spec.size)
return mw.ustring.char(cp)
elseif spec.kind == "float" then
return spec.single and unpack_float32_le(s, pos) or unpack_float64_le(s, pos)
elseif spec.kind == "int" or spec.kind == "int64" then
local u = unpack_uint_le(s, pos, spec.size)
if spec.signed then
local half = 2 ^ (spec.size * 8 - 1)
if u >= half then u = u - 2 * half end
end
return u
end
end
local function ensure_array_init()
if array_initialized then return end
array_initialized = true
local array_cls
local function make_array_inst(tc, elements)
local spec = TYPECODE_SPECS[tc]
local inst = PyClass.make_instance(array_cls)
inst._is_array = true
inst.__dict._tc = tc
inst.__dict._spec = spec
inst.__dict._elements = elements or {}
return inst
end
local array_dict = {
__module__ = "array",
typecode = {
__type = "property",
fget = fn("typecode", function(a) return a[1].__dict._tc end)
},
itemsize = {
__type = "property",
fget = fn("itemsize", function(a) return a[1].__dict._spec.size end)
},
__init__ = fn("__init__", function(a)
local self, tc, init = a[1], a[2], a[3]
if type(tc) ~= "string" or #tc ~= 1 then
Runtime.raise("TypeError", "array() argument 1 must be a unicode character, not " .. RUtils.get_type_str(tc))
end
local spec = TYPECODE_SPECS[tc]
if not spec then
Runtime.raise("ValueError", "bad typecode (must be b, B, u, h, H, i, I, l, L, q, Q, f, d or w)")
end
self._is_array = true
self.__dict._tc = tc
self.__dict._spec = spec
self.__dict._elements = {}
if init ~= nil then
if type(init) == "table" and (init.__type == "bytes" or init.__type == "bytearray" or init.__type == "memoryview") then
Evaluator.invoke(PyClass.get_attr(self, "frombytes"), { init }, {})
elseif type(init) == "string" then
if spec.kind == "uchar" then
Evaluator.invoke(PyClass.get_attr(self, "fromunicode"), { init }, {})
else
Runtime.raise("TypeError", "cannot use a str to initialize an array with typecode '" .. tc .. "'")
end
else
Evaluator.invoke(PyClass.get_attr(self, "extend"), { init }, {})
end
end
return nil
end),
append = fn("append", function(a)
local self, v = a[1], a[2]
local coerced = validate_and_coerce_item(self.__dict._tc, self.__dict._spec, v)
table.insert(self.__dict._elements, coerced)
return B.None
end),
extend = fn("extend", function(a)
local self, iter_obj = a[1], a[2]
if type(iter_obj) == "table" and (iter_obj.__type == "bytes" or iter_obj.__type == "bytearray") then
Runtime.raise("TypeError", "can only extend with array of same kind")
end
if type(iter_obj) == "table" and iter_obj._is_array then
if iter_obj.__dict._tc ~= self.__dict._tc then
Runtime.raise("TypeError", "can only extend with array of same kind")
end
local src = {}
for _, item in ipairs(iter_obj.__dict._elements) do table.insert(src, item) end
for _, item in ipairs(src) do table.insert(self.__dict._elements, item) end
return B.None
end
local tc, spec = self.__dict._tc, self.__dict._spec
local elts = self.__dict._elements
local iter_fn = RUtils.get_iterator(iter_obj)
while true do
local has_next, item = iter_fn()
if not has_next then break end
table.insert(elts, validate_and_coerce_item(tc, spec, item))
end
return B.None
end),
insert = fn("insert", function(a)
local self, idx, v = a[1], RUtils.unwrap_num(a[2]), a[3]
if type(idx) ~= "number" or math.floor(idx) ~= idx then
Runtime.raise("TypeError", "integer argument expected")
end
local coerced = validate_and_coerce_item(self.__dict._tc, self.__dict._spec, v)
local elts = self.__dict._elements
local len = #elts
if idx < 0 then idx = math.max(0, len + idx)
elseif idx > len then idx = len end
table.insert(elts, idx + 1, coerced)
return B.None
end),
pop = fn("pop", function(a)
local self = a[1]
local elts = self.__dict._elements
if #elts == 0 then Runtime.raise("IndexError", "pop from empty array") end
local idx = (a[2] ~= nil) and RUtils.unwrap_num(a[2]) or -1
if type(idx) ~= "number" or math.floor(idx) ~= idx then
Runtime.raise("TypeError", "integer argument expected")
end
if idx < 0 then idx = #elts + idx end
if idx < 0 or idx >= #elts then Runtime.raise("IndexError", "pop index out of range") end
return table.remove(elts, idx + 1)
end),
remove = fn("remove", function(a)
local self, v = a[1], a[2]
local elts = self.__dict._elements
for i, item in ipairs(elts) do
if RUtils.runtime_is_equal(item, v) then
table.remove(elts, i)
return B.None
end
end
Runtime.raise("ValueError", "array.remove(x): x not in array")
end),
index = fn("index", function(a)
local self, v = a[1], a[2]
local elts = self.__dict._elements
local len = #elts
local start_i = (a[3] ~= nil) and RUtils.unwrap_num(a[3]) or 0
local stop_i = (a[4] ~= nil) and RUtils.unwrap_num(a[4]) or len
if start_i < 0 then start_i = math.max(0, len + start_i) end
if stop_i < 0 then stop_i = math.max(0, len + stop_i) end
stop_i = math.min(len, stop_i)
for i = start_i + 1, stop_i do
if RUtils.runtime_is_equal(elts[i], v) then return i - 1 end
end
Runtime.raise("ValueError", "array.index(x): x not in array")
end),
count = fn("count", function(a)
local self, v = a[1], a[2]
local c = 0
for _, item in ipairs(self.__dict._elements) do
if RUtils.runtime_is_equal(item, v) then c = c + 1 end
end
return c
end),
reverse = fn("reverse", function(a)
local elts = a[1].__dict._elements
local i, j = 1, #elts
while i < j do
elts[i], elts[j] = elts[j], elts[i]
i = i + 1; j = j - 1
end
return B.None
end),
tolist = fn("tolist", function(a)
local copy = {}
for i, v in ipairs(a[1].__dict._elements) do copy[i] = v end
return { __type = "list", elements = copy }
end),
fromlist = fn("fromlist", function(a)
local self, lst = a[1], a[2]
if type(lst) ~= "table" or lst.__type ~= "list" then
Runtime.raise("TypeError", "arg must be list")
end
local tc, spec = self.__dict._tc, self.__dict._spec
local staged = {}
for i, item in ipairs(lst.elements) do
staged[i] = validate_and_coerce_item(tc, spec, item)
end
for _, item in ipairs(staged) do
table.insert(self.__dict._elements, item)
end
return B.None
end),
tobytes = fn("tobytes", function(a)
local self = a[1]
local tc, spec = self.__dict._tc, self.__dict._spec
local chunks = {}
for i, v in ipairs(self.__dict._elements) do
chunks[i] = pack_item(tc, spec, v)
end
return { __type = "bytes", _data = table.concat(chunks) }
end),
frombytes = fn("frombytes", function(a)
local self, b = a[1], a[2]
local data
if type(b) == "table" and (b.__type == "bytes" or b.__type == "bytearray" or b.__type == "memoryview") then
if b.__type == "memoryview" and b._released then
Runtime.raise("ValueError", "operation forbidden on released memoryview object")
end
data = b._data
else
Runtime.raise("TypeError", "a bytes-like object is required, not '" .. RUtils.get_type_str(b) .. "'")
end
local tc, spec = self.__dict._tc, self.__dict._spec
local sz = spec.size
if #data % sz ~= 0 then
Runtime.raise("ValueError", "bytes length not a multiple of item size")
end
local elts = self.__dict._elements
for pos = 1, #data, sz do
table.insert(elts, unpack_item(tc, spec, data, pos))
end
return B.None
end),
tounicode = fn("tounicode", function(a)
local self = a[1]
if self.__dict._spec.kind ~= "uchar" then
Runtime.raise("ValueError", "tounicode() may only be called on unicode type arrays")
end
return table.concat(self.__dict._elements)
end),
fromunicode = fn("fromunicode", function(a)
local self, s = a[1], a[2]
if self.__dict._spec.kind ~= "uchar" then
Runtime.raise("ValueError", "fromunicode() may only be called on unicode type arrays")
end
if type(s) ~= "string" then
Runtime.raise("TypeError", "fromunicode() argument must be str, not " .. RUtils.get_type_str(s))
end
local elts = self.__dict._elements
for i = 1, mw.ustring.len(s) do
table.insert(elts, mw.ustring.sub(s, i, i))
end
return B.None
end),
tofile = fn("tofile", function(a)
local self, f = a[1], a[2]
local ok, write_m = pcall(PyClass.get_attr, f, "write")
if not ok or not write_m then
Runtime.raise("TypeError", "tofile() argument must be a file-like object with a write() method")
end
local bytes_obj = Evaluator.invoke(PyClass.get_attr(self, "tobytes"), {}, {})
-- 相容 io.BytesIO 與 io.StringIO
local payload = bytes_obj
if type(f) == "table" and f.__dict and f.__dict._is_bytes == false then
payload = bytes_obj._data
end
Evaluator.invoke(write_m, { payload }, {})
return B.None
end),
fromfile = fn("fromfile", function(a)
local self, f, n = a[1], a[2], RUtils.unwrap_num(a[3])
if type(n) ~= "number" or math.floor(n) ~= n then
Runtime.raise("TypeError", "an integer is required")
end
if n < 0 then Runtime.raise("ValueError", "negative count") end
local ok, read_m = pcall(PyClass.get_attr, f, "read")
if not ok or not read_m then
Runtime.raise("TypeError", "fromfile() argument 1 must be a file-like object with a read() method")
end
local tc, spec = self.__dict._tc, self.__dict._spec
local sz = spec.size
local need_bytes = n * sz
local res = Evaluator.invoke(read_m, { need_bytes }, {})
local raw_data = (type(res) == "table" and res._data) or (type(res) == "string" and res) or ""
local full_items = math.floor(#raw_data / sz)
local elts = self.__dict._elements
for i = 0, full_items - 1 do
table.insert(elts, unpack_item(tc, spec, raw_data, i * sz + 1))
end
if full_items < n then
Runtime.raise("EOFError", "read() didn't return enough bytes")
end
return B.None
end),
byteswap = fn("byteswap", function(a)
local self = a[1]
local tc, spec = self.__dict._tc, self.__dict._spec
local sz = spec.size
if sz == 1 then return B.None end
local elts = self.__dict._elements
for i, v in ipairs(elts) do
local packed = pack_item(tc, spec, v)
local rev = packed:reverse()
elts[i] = unpack_item(tc, spec, rev, 1)
end
return B.None
end),
buffer_info = fn("buffer_info", function(a)
local self = a[1]
return { __type = "tuple", elements = { B.id({ self }), #self.__dict._elements } }
end),
__len__ = fn("__len__", function(a) return #a[1].__dict._elements end),
__iter__ = fn("__iter__", function(a)
return B.iter({ { __type = "list", elements = a[1].__dict._elements } })
end),
__contains__ = fn("__contains__", function(a)
local self, v = a[1], a[2]
for _, item in ipairs(self.__dict._elements) do
if RUtils.runtime_is_equal(item, v) then return true end
end
return false
end),
__getitem__ = fn("__getitem__", function(a)
local self, idx = a[1], a[2]
local elts = self.__dict._elements
local len = #elts
if type(idx) == "table" and idx.__type == "instance" and idx.__class == B.slice then
local step = (idx.__dict.step ~= B.None and idx.__dict.step ~= nil) and RUtils.unwrap_num(idx.__dict.step) or 1
if step == 0 then Runtime.raise("ValueError", "slice step cannot be zero") end
local start_i, stop_i
if step > 0 then
start_i = (idx.__dict.start ~= B.None and idx.__dict.start ~= nil) and RUtils.unwrap_num(idx.__dict.start) or 0
stop_i = (idx.__dict.stop ~= B.None and idx.__dict.stop ~= nil) and RUtils.unwrap_num(idx.__dict.stop) or len
if start_i < 0 then start_i = math.max(0, len + start_i) else start_i = math.min(len, start_i) end
if stop_i < 0 then stop_i = math.max(0, len + stop_i) else stop_i = math.min(len, stop_i) end
else
start_i = (idx.__dict.start ~= B.None and idx.__dict.start ~= nil) and RUtils.unwrap_num(idx.__dict.start) or (len - 1)
stop_i = (idx.__dict.stop ~= B.None and idx.__dict.stop ~= nil) and RUtils.unwrap_num(idx.__dict.stop) or -1
if start_i < 0 then start_i = len + start_i end
if start_i >= len then start_i = len - 1 end
if idx.__dict.stop ~= B.None and idx.__dict.stop ~= nil and stop_i < 0 then stop_i = len + stop_i end
end
local sliced = {}
local cur = start_i
while (step > 0 and cur < stop_i) or (step < 0 and cur > stop_i) do
if cur >= 0 and cur < len then
table.insert(sliced, elts[cur + 1])
end
cur = cur + step
end
return make_array_inst(self.__dict._tc, sliced)
end
local n = RUtils.unwrap_num(idx)
if type(n) ~= "number" or math.floor(n) ~= n then
Runtime.raise("TypeError", "array indices must be integers")
end
if n < 0 then n = len + n end
if n < 0 or n >= len then Runtime.raise("IndexError", "array index out of range") end
return elts[n + 1]
end),
__setitem__ = fn("__setitem__", function(a)
local self, idx, v = a[1], a[2], a[3]
local elts = self.__dict._elements
local len = #elts
local n = RUtils.unwrap_num(idx)
if type(n) ~= "number" or math.floor(n) ~= n then
Runtime.raise("TypeError", "array indices must be integers")
end
if n < 0 then n = len + n end
if n < 0 or n >= len then Runtime.raise("IndexError", "array assignment index out of range") end
elts[n + 1] = validate_and_coerce_item(self.__dict._tc, self.__dict._spec, v)
return B.None
end),
__delitem__ = fn("__delitem__", function(a)
local self, idx = a[1], a[2]
local elts = self.__dict._elements
local len = #elts
local n = RUtils.unwrap_num(idx)
if type(n) ~= "number" or math.floor(n) ~= n then
Runtime.raise("TypeError", "array indices must be integers")
end
if n < 0 then n = len + n end
if n < 0 or n >= len then Runtime.raise("IndexError", "array assignment index out of range") end
table.remove(elts, n + 1)
return B.None
end),
__add__ = fn("__add__", function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or not other._is_array then
Runtime.raise("TypeError", "can only append array to array")
end
if self.__dict._tc ~= other.__dict._tc then
Runtime.raise("TypeError", "bad argument type for built-in operation")
end
local combined = {}
for _, v in ipairs(self.__dict._elements) do table.insert(combined, v) end
for _, v in ipairs(other.__dict._elements) do table.insert(combined, v) end
return make_array_inst(self.__dict._tc, combined)
end),
__mul__ = fn("__mul__", function(a)
local self, n = a[1], RUtils.unwrap_num(a[2])
if type(n) ~= "number" or math.floor(n) ~= n then
Runtime.raise("TypeError", "can't multiply sequence by non-int")
end
local res = {}
for _ = 1, math.max(0, n) do
for _, v in ipairs(self.__dict._elements) do table.insert(res, v) end
end
return make_array_inst(self.__dict._tc, res)
end),
__rmul__ = fn("__rmul__", function(a)
return Evaluator.invoke(PyClass.get_attr(a[1], "__mul__"), { a[2] }, {})
end),
__eq__ = fn("__eq__", function(a)
local self, other = a[1], a[2]
if type(other) ~= "table" or not other._is_array then return false end
local e1, e2 = self.__dict._elements, other.__dict._elements
if #e1 ~= #e2 then return false end
for i = 1, #e1 do
if not RUtils.runtime_is_equal(e1[i], e2[i]) then return false end
end
return true
end),
__ne__ = fn("__ne__", function(a)
local eq_m = PyClass.get_attr(a[1], "__eq__")
return not RUtils.to_bool(Evaluator.invoke(eq_m, { a[2] }, {}))
end),
__repr__ = fn("__repr__", function(a)
local self = a[1]
local tc = self.__dict._tc
local elts = self.__dict._elements
if #elts == 0 then
return string.format("array('%s')", tc)
end
if self.__dict._spec.kind == "uchar" then
local s = table.concat(elts)
return string.format("array('%s', %s)", tc, RUtils.to_str(s, true))
end
local list_repr = RUtils.to_str({ __type = "list", elements = elts }, true)
return string.format("array('%s', %s)", tc, list_repr)
end),
}
array_dict.__str__ = array_dict.__repr__
array_cls = PyClass.make_class("array", { B.object }, array_dict)
array_mod_cache = PyClass.make_instance(PyClass.make_class("module", {}, {}))
local d = array_mod_cache.__dict
d["__name__"] = "array"
d.array = array_cls
d.ArrayType = array_cls
d.typecodes = "bBuhHiIlLqQfdw"
end
-- ==========================================
-- 3. 對外模組匯出介面
-- ==========================================
function DataTypes.init_enum()
ensure_enum_init()
return enum_mod_cache
end
function DataTypes.init_array()
ensure_array_init()
return array_mod_cache
end
function DataTypes.get_module(name)
if name == "enum" then return DataTypes.init_enum() end
if name == "array" then return DataTypes.init_array() end
Runtime.raise("ModuleNotFoundError", "No module named '" .. tostring(name) .. "'")
end
return DataTypes