跳转到内容

模組:PythonScript/Librarys/Csv

维基百科,自由的百科全书
-- Module:PythonScript/Librarys/Csv
-- 本檔案包含了CSV和JSON的編碼與解碼模型。
local CsvMod = {}
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")

-- 定義 csv 專屬例外類別
local CsvErrorClass = PyClass.make_class("Error", {Runtime.builtins.Exception}, {})

-- ==========================================
-- CSV 解析核心 (跨行引號狀態機)
-- ==========================================
local function parse_csv_line(iter, delimiter, quotechar)
    local parsed_row = {}
    local current_val = {}
    local quote_open = false
    local line_str = ""

    while true do
        -- 嘗試透過 Python 迭代器拿取下一行
        local success, res = pcall(Runtime.builtins.next, {iter})
        if not success then
            -- 若遇到 StopIteration,代表資料結束
            if type(res) == "table" and res.__class and res.__class.__name == "StopIteration" then
                if line_str == "" and not quote_open then
                    Runtime.raise("StopIteration")
                else
                    -- 如果資料已經結束,但引號還沒閉合,就拋出 csv.Error
                    if quote_open then
                        Runtime.raise(CsvErrorClass, "unexpected end of data")
                    end
                end
            end
            -- 其他預期外錯誤則向上拋出
            error(res, 0)
        end

        -- 確保拿到的物件能轉為字串
        local success_str, s = pcall(Runtime.builtins.str.__call, {res})
        local chunk = success_str and s or tostring(res)
        
        -- 濾除原生的結尾換行,因為 CSV 模組的解析會自行補上跨行換行
        chunk = chunk:gsub("\r?\n$", "")
        
        if line_str == "" then 
            line_str = chunk 
        else 
            line_str = line_str .. "\n" .. chunk 
        end

        local i = 1
        local n = #line_str
        parsed_row = {}
        current_val = {}
        quote_open = false

        while i <= n do
            local c = line_str:sub(i, i)
            if c == quotechar then
                -- 處理連續兩個引號的跳脫字元 ("")
                if quote_open and i < n and line_str:sub(i+1, i+1) == quotechar then
                    table.insert(current_val, quotechar)
                    i = i + 1
                else
                    quote_open = not quote_open
                end
            elseif c == delimiter and not quote_open then
                table.insert(parsed_row, table.concat(current_val))
                current_val = {}
            else
                table.insert(current_val, c)
            end
            i = i + 1
        end

        if not quote_open then
            table.insert(parsed_row, table.concat(current_val))
            return parsed_row
        else
            -- 引號依然開啟,代表這是一個跨行欄位,繼續進入下一個迴圈拿取下一行
        end
    end
end

-- ==========================================
-- reader 類別
-- ==========================================
local reader_dict = {
    __init__ = {
        __type = "builtin_method",
        __call = function(args, kwargs)
            local self = args[1]
            local csvfile = args[2]
            local kw = kwargs or {}
            
            self.__dict.delimiter = kw.delimiter or ","
            self.__dict.quotechar = kw.quotechar or '"'
            
            if type(self.__dict.delimiter) ~= "string" or #self.__dict.delimiter ~= 1 then
                Runtime.raise("TypeError", '"delimiter" must be a 1-character string')
            end
            if type(self.__dict.quotechar) ~= "string" or #self.__dict.quotechar ~= 1 then
                Runtime.raise("TypeError", '"quotechar" must be a 1-character string')
            end

            self.__dict.iterator = Runtime.builtins.iter({csvfile})
            self.__dict.line_num = 0
            return nil
        end
    },
    __iter__ = {
        __type = "builtin_method",
        __call = function(args) return args[1] end
    },
    __next__ = {
        __type = "builtin_method",
        __call = function(args)
            local self = args[1]
            local row_table = parse_csv_line(self.__dict.iterator, self.__dict.delimiter, self.__dict.quotechar)
            self.__dict.line_num = self.__dict.line_num + 1
            
            local elements = {}
            for _, v in ipairs(row_table) do table.insert(elements, v) end
            return { __type = "list", elements = elements }
        end
    },
    line_num = {
        __type = "property",
        fget = { __type = "builtin_method", __call = function(args) return args[1].__dict.line_num end }
    }
}
local ReaderClass = PyClass.make_class("reader", {}, reader_dict)

-- ==========================================
-- writer 類別 (實作錯誤防護)
-- ==========================================
local writer_dict = {
    __init__ = {
        __type = "builtin_method",
        __call = function(args, kwargs)
            -- 初始化不報錯,直到實際 write 才會擋下
            return nil
        end
    },
    writerow = {
        __type = "builtin_method",
        __call = function() Runtime.raise("OSError", "write not supported in this environment") end
    },
    writerows = {
        __type = "builtin_method",
        __call = function() Runtime.raise("OSError", "write not supported in this environment") end
    }
}
local WriterClass = PyClass.make_class("writer", {}, writer_dict)

-- ==========================================
-- DictReader 類別
-- ==========================================
local DictReader_dict = {
    __init__ = {
        __type = "builtin_method",
        __call = function(args, kwargs)
            local self = args[1]; local f = args[2]
            local kw = kwargs or {}
            self.__dict.fieldnames = (args[3] ~= nil and args[3]) or kw.fieldnames
            self.__dict.restkey = (args[4] ~= nil and args[4]) or kw.restkey or Runtime.builtins.None
            self.__dict.restval = (args[5] ~= nil and args[5]) or kw.restval or Runtime.builtins.None
            
            self.__dict.reader = PyClass.make_instance(ReaderClass)
            -- 將 f 以及其他參數轉傳給原生的 reader 初始化
            ReaderClass.__dict.__init__.__call({self.__dict.reader, f}, kwargs)
            return nil
        end
    },
    __iter__ = {
        __type = "builtin_method",
        __call = function(args) return args[1] end
    },
    __next__ = {
        __type = "builtin_method",
        __call = function(args)
            local self = args[1]
            local row_list = ReaderClass.__dict.__next__.__call({self.__dict.reader})
            local row = row_list.elements
            
            -- 若未提供 fieldnames,則將第一行作為 fieldnames
            if self.__dict.fieldnames == nil or self.__dict.fieldnames == Runtime.builtins.None then
                self.__dict.fieldnames = row
                row_list = ReaderClass.__dict.__next__.__call({self.__dict.reader})
                row = row_list.elements
            end
            
            local fieldnames = (type(self.__dict.fieldnames) == "table" and self.__dict.fieldnames.elements) or self.__dict.fieldnames
            local d = { __type = "dict", pairs = {} }
            
            for i, field in ipairs(fieldnames) do
                local val = row[i]
                if val == nil then val = self.__dict.restval end
                table.insert(d.pairs, { key = field, value = val })
            end
            
            -- 欄位多於標題時,歸入 restkey
            if #row > #fieldnames then
                local rest = { __type = "list", elements = {} }
                for i = #fieldnames + 1, #row do
                    table.insert(rest.elements, row[i])
                end
                table.insert(d.pairs, { key = self.__dict.restkey, value = rest })
            end
            
            return d
        end
    },
    fieldnames = {
        __type = "property",
        fget = { __type = "builtin_method", __call = function(args) return args[1].__dict.fieldnames end },
        fset = { __type = "builtin_method", __call = function(args) args[1].__dict.fieldnames = args[2]; return nil end }
    },
    line_num = {
        __type = "property",
        fget = { __type = "builtin_method", __call = function(args) return args[1].__dict.reader.__dict.line_num end }
    }
}
local DictReaderClass = PyClass.make_class("DictReader", {}, DictReader_dict)

-- ==========================================
-- DictWriter 類別 (實作錯誤防護)
-- ==========================================
local DictWriter_dict = {
    __init__ = {
        __type = "builtin_method",
        __call = function(args, kwargs) return nil end
    },
    writeheader = {
        __type = "builtin_method",
        __call = function() Runtime.raise("OSError", "write not supported in this environment") end
    },
    writerow = {
        __type = "builtin_method",
        __call = function() Runtime.raise("OSError", "write not supported in this environment") end
    },
    writerows = {
        __type = "builtin_method",
        __call = function() Runtime.raise("OSError", "write not supported in this environment") end
    }
}
local DictWriterClass = PyClass.make_class("DictWriter", {}, DictWriter_dict)

-- ==========================================
-- 模組打包
-- ==========================================
function CsvMod.get_module()
    local dict = {
        QUOTE_MINIMAL = 0,
        QUOTE_ALL = 1,
        QUOTE_NONNUMERIC = 2,
        QUOTE_NONE = 3,
        Error = CsvErrorClass,
        reader = ReaderClass,
        writer = WriterClass,
        DictReader = DictReaderClass,
        DictWriter = DictWriterClass
    }
    
    local inst = PyClass.make_instance(PyClass.make_class("module", {}, dict))
    inst.__dict["__name__"] = "csv"
    return inst
end

-- ==========================================
-- 整合Json解碼於此
-- ==========================================
local JsonMod = {}
local initialized = false
local cache = {}
local function init_json()
	local Evaluator = require("Module:PythonScript/Evaluator")
	local EvalUtils = require("Module:PythonScript/Evaluator/Utils")
	local RUtils = require("Module:PythonScript/Runtime/Utils")
	
	local function is_none(v)
    	return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
	end
	
	-- ==========================================
	-- 1. 字串跳脫與單次遍歷 JSON 編碼核心 (Encoder)
	-- ==========================================
	local escape_map = {
	    ['"']  = '\\"',
	    ['\\'] = '\\\\',
	    ['\b'] = '\\b',
	    ['\f'] = '\\f',
	    ['\n'] = '\\n',
	    ['\r'] = '\\r',
	    ['\t'] = '\\t'
	}
	
	local function encode_json_string(s, ensure_ascii)
	    local out = { '"' }
	    if not ensure_ascii then
	        local escaped = s:gsub('[%z\1-\31"\\]', function(c)
	            return escape_map[c] or string.format("\\u%04x", string.byte(c))
	        end)
	        out[#out + 1] = escaped
	    else
	        for cp in mw.ustring.gcodepoint(s) do
	            if cp == 34 then out[#out + 1] = '\\"'
	            elseif cp == 92 then out[#out + 1] = '\\\\'
	            elseif cp == 8 then out[#out + 1] = '\\b'
	            elseif cp == 12 then out[#out + 1] = '\\f'
	            elseif cp == 10 then out[#out + 1] = '\\n'
	            elseif cp == 13 then out[#out + 1] = '\\r'
	            elseif cp == 9 then out[#out + 1] = '\\t'
	            elseif cp < 32 or (cp >= 127 and cp <= 0xFFFF) then
	                out[#out + 1] = string.format("\\u%04x", cp)
	            elseif cp > 0xFFFF then
	                local sub = cp - 0x10000
	                local high = 0xD800 + math.floor(sub / 1024)
	                local low = 0xDC00 + (sub % 1024)
	                out[#out + 1] = string.format("\\u%04x\\u%04x", high, low)
	            else
	                out[#out + 1] = string.char(cp)
	            end
	        end
	    end
	    out[#out + 1] = '"'
	    return table.concat(out)
	end
	
	-- 格式化字典鍵 (JSON 規範要求 key 必須轉為字串)
	local function coerce_dict_key(k, skipkeys, ensure_ascii)
	    if type(k) == "string" then return k end
	    if type(k) == "boolean" then return k and "true" or "false" end
	    if is_none(k) then return "null" end
	    if type(k) == "number" then
	        if k ~= k then return "NaN"
	        elseif k == math.huge then return "Infinity"
	        elseif k == -math.huge then return "-Infinity" end
	        return tostring(k)
	    end
	    if type(k) == "table" then
	        if k.__type == "int" and k.is_big then
	            local MathCore = require("Module:PythonScript/Runtime/Cmath")
	            return MathCore.BigInt.to_string(k.sign, k.limbs)
	        elseif k.__type == "instance" and k.__builtin_value ~= nil then
	            return coerce_dict_key(k.__builtin_value, skipkeys, ensure_ascii)
	        end
	    end
	    if skipkeys then return nil end
	    Runtime.raise("TypeError", "keys must be str, int, float, bool or None, not " .. RUtils.get_type_str(k))
	end
	
	local function serialize_value(val, opts, depth, seen, buf)
	    -- 處理自定義實例但底層綁定原生型別 (如 IntEnum / StrEnum / NamedTuple)
	    if type(val) == "table" and val.__type == "instance" and val.__builtin_value ~= nil then
	        local bv = val.__builtin_value
	        if type(bv) == "number" or type(bv) == "string" or type(bv) == "boolean" or (type(bv) == "table" and (bv.__type == "tuple" or bv.__type == "list" or bv.__type == "dict" or bv.__type == "int")) then
	            val = bv
	        end
	    end
	
	    if is_none(val) then
	        buf[#buf + 1] = "null"
	        return
	    elseif type(val) == "boolean" then
	        buf[#buf + 1] = val and "true" or "false"
	        return
	    elseif type(val) == "number" then
	        if val ~= val then
	            if not opts.allow_nan then Runtime.raise("ValueError", "Out of range float values are not JSON compliant") end
	            buf[#buf + 1] = "NaN"
	        elseif val == math.huge then
	            if not opts.allow_nan then Runtime.raise("ValueError", "Out of range float values are not JSON compliant") end
	            buf[#buf + 1] = "Infinity"
	        elseif val == -math.huge then
	            if not opts.allow_nan then Runtime.raise("ValueError", "Out of range float values are not JSON compliant") end
	            buf[#buf + 1] = "-Infinity"
	        else
	            buf[#buf + 1] = tostring(val)
	        end
	        return
	    elseif type(val) == "string" then
	        buf[#buf + 1] = encode_json_string(val, opts.ensure_ascii)
	        return
	    elseif type(val) == "table" then
	        if val.__type == "int" and val.is_big then
	            local MathCore = require("Module:PythonScript/Runtime/Cmath")
	            buf[#buf + 1] = MathCore.BigInt.to_string(val.sign, val.limbs)
	            return
	        elseif val.__type == "list" or val.__type == "tuple" then
	            local elts = val.elements
	            if #elts == 0 then
	                buf[#buf + 1] = "[]"
	                return
	            end
	            if opts.check_circular then
	                if seen[val] then Runtime.raise("ValueError", "Circular reference detected") end
	                seen[val] = true
	            end
	            buf[#buf + 1] = "["
	            local next_depth = depth + 1
	            local item_sep = opts.item_separator
	            local indent_str = opts.indent_str
	            local step_indent = indent_str and ("\n" .. string.rep(indent_str, next_depth)) or nil
	
	            for i = 1, #elts do
	                if i > 1 then buf[#buf + 1] = item_sep end
	                if step_indent then buf[#buf + 1] = step_indent end
	                serialize_value(elts[i], opts, next_depth, seen, buf)
	            end
	            if indent_str then
	                buf[#buf + 1] = "\n" .. string.rep(indent_str, depth)
	            end
	            buf[#buf + 1] = "]"
	            if opts.check_circular then seen[val] = nil end
	            return
	        elseif val.__type == "dict" then
	            local pairs_arr = val.pairs
	            if #pairs_arr == 0 then
	                buf[#buf + 1] = "{}"
	                return
	            end
	            if opts.check_circular then
	                if seen[val] then Runtime.raise("ValueError", "Circular reference detected") end
	                seen[val] = true
	            end
	
	            local valid_items = {}
	            for i = 1, #pairs_arr do
	                local p = pairs_arr[i]
	                local k_str = coerce_dict_key(p.key, opts.skipkeys, opts.ensure_ascii)
	                if k_str ~= nil then
	                    valid_items[#valid_items + 1] = { key = k_str, value = p.value }
	                end
	            end
	
	            if #valid_items == 0 then
	                if opts.check_circular then seen[val] = nil end
	                buf[#buf + 1] = "{}"
	                return
	            end
	
	            if opts.sort_keys then
	                table.sort(valid_items, function(a, b) return a.key < b.key end)
	            end
	
	            buf[#buf + 1] = "{"
	            local next_depth = depth + 1
	            local item_sep = opts.item_separator
	            local kv_sep = opts.key_separator
	            local indent_str = opts.indent_str
	            local step_indent = indent_str and ("\n" .. string.rep(indent_str, next_depth)) or nil
	
	            for i = 1, #valid_items do
	                if i > 1 then buf[#buf + 1] = item_sep end
	                if step_indent then buf[#buf + 1] = step_indent end
	                buf[#buf + 1] = encode_json_string(valid_items[i].key, opts.ensure_ascii)
	                buf[#buf + 1] = kv_sep
	                serialize_value(valid_items[i].value, opts, next_depth, seen, buf)
	            end
	            if indent_str then
	                buf[#buf + 1] = "\n" .. string.rep(indent_str, depth)
	            end
	            buf[#buf + 1] = "}"
	            if opts.check_circular then seen[val] = nil end
	            return
	        end
	    end
	
	    -- 遇到預設不支援的型別:委派給 default 回呼或 JSONEncoder.default 方法
	    if opts.check_circular and type(val) == "table" then
	        if seen[val] then Runtime.raise("ValueError", "Circular reference detected") end
	        seen[val] = true
	    end
	
	    local replacement
	    if opts.encoder_self then
	        local def_m = PyClass.get_attr(opts.encoder_self, "default")
	        replacement = Evaluator.invoke(def_m, { val }, {})
	    elseif opts.default_fn and not is_none(opts.default_fn) then
	        replacement = Evaluator.invoke(opts.default_fn, { val }, {})
	    else
	        Runtime.raise("TypeError", "Object of type " .. RUtils.get_type_str(val) .. " is not JSON serializable")
	    end
	
	    serialize_value(replacement, opts, depth, seen, buf)
	    if opts.check_circular and type(val) == "table" then
	        seen[val] = nil
	    end
	end
	
	local function build_encode_opts(kw, encoder_self)
	    kw = kw or {}
	    local d = encoder_self and encoder_self.__dict or {}
	
	    local function pick_bool(key, def_val)
	        if kw[key] ~= nil then return EvalUtils.is_truthy(kw[key]) end
	        if d[key] ~= nil then return EvalUtils.is_truthy(d[key]) end
	        return def_val
	    end
	
	    local indent_raw = (kw.indent ~= nil) and kw.indent or d.indent
	    local indent_str = nil
	    if not is_none(indent_raw) then
	        if type(indent_raw) == "number" then
	            indent_str = string.rep(" ", math.max(0, math.floor(indent_raw)))
	        elseif type(indent_raw) == "string" then
	            indent_str = indent_raw
	        end
	    end
	
	    local sep_raw = (kw.separators ~= nil) and kw.separators or d.separators
	    local item_sep = indent_str and "," or ", "
	    local key_sep = ": "
	    if not is_none(sep_raw) and type(sep_raw) == "table" and sep_raw.elements and #sep_raw.elements >= 2 then
	        item_sep = tostring(sep_raw.elements[1])
	        key_sep = tostring(sep_raw.elements[2])
	    end
	
	    local default_fn = (kw.default ~= nil) and kw.default or d._default_fn
	
	    return {
	        skipkeys = pick_bool("skipkeys", false),
	        ensure_ascii = pick_bool("ensure_ascii", true),
	        check_circular = pick_bool("check_circular", true),
	        allow_nan = pick_bool("allow_nan", true),
	        sort_keys = pick_bool("sort_keys", false),
	        indent_str = indent_str,
	        item_separator = item_sep,
	        key_separator = key_sep,
	        default_fn = default_fn,
	        encoder_self = encoder_self
	    }
	end
	
	-- ==========================================
	-- 2. 單次遍歷高速游標 JSON 解碼核心 (Decoder)
	-- ==========================================
	local function raise_decode_error(msg, doc, pos)
	    local B = Runtime.builtins
	    local sub_doc = doc:sub(1, math.max(0, pos - 1))
	    local lineno = 1
	    for _ in sub_doc:gmatch("\n") do lineno = lineno + 1 end
	    local last_nl = sub_doc:match(".*()\n")
	    local colno = last_nl and (pos - last_nl) or pos
	
	    local full_msg = string.format("%s: line %d column %d (char %d)", msg, lineno, colno, pos - 1)
	    local exc = PyClass.make_instance(cache.JSONDecodeError)
	    exc.__dict.msg = full_msg
	    exc.__dict.doc = doc
	    exc.__dict.pos = pos - 1
	    exc.__dict.lineno = lineno
	    exc.__dict.colno = colno
	    exc.__dict.args = { __type = "tuple", elements = { full_msg, doc, pos - 1 } }
	    Runtime.raise(exc)
	end
	
	local function skip_ws(s, pos, len)
	    local _, e = s:find("^[ \t\r\n]+", pos)
	    return e and (e + 1) or pos
	end
	
	local function parse_json_string(s, pos, len, strict)
	    -- pos 指向開頭的 '"'
	    local i = pos + 1
	    local out = {}
	    local chunk_start = i
	
	    while i <= len do
	        local b = string.byte(s, i)
	        if b == 34 then -- '"'
	            if i > chunk_start then out[#out + 1] = s:sub(chunk_start, i - 1) end
	            return table.concat(out), i + 1
	        elseif b == 92 then -- '\'
	            if i > chunk_start then out[#out + 1] = s:sub(chunk_start, i - 1) end
	            i = i + 1
	            if i > len then raise_decode_error("Unterminated string starting at", s, pos) end
	            local esc = string.sub(s, i, i)
	            if esc == '"' or esc == '\\' or esc == '/' then out[#out + 1] = esc; i = i + 1
	            elseif esc == 'b' then out[#out + 1] = '\b'; i = i + 1
	            elseif esc == 'f' then out[#out + 1] = '\f'; i = i + 1
	            elseif esc == 'n' then out[#out + 1] = '\n'; i = i + 1
	            elseif esc == 'r' then out[#out + 1] = '\r'; i = i + 1
	            elseif esc == 't' then out[#out + 1] = '\t'; i = i + 1
	            elseif esc == 'u' then
	                local hex = s:sub(i + 1, i + 4)
	                if #hex < 4 or not hex:match("^%x%x%x%x$") then
	                    raise_decode_error("Invalid \\uXXXX escape", s, i - 1)
	                end
	                local cp = tonumber(hex, 16)
	                i = i + 5
	                -- 處理 UTF-16 Surrogate Pair (\uD800..\uDBFF 搭配 \uDC00..\uDFFF)
	                if cp >= 0xD800 and cp <= 0xDBFF and s:sub(i, i + 1) == "\\u" then
	                    local hex2 = s:sub(i + 2, i + 5)
	                    if #hex2 == 4 and hex2:match("^%x%x%x%x$") then
	                        local cp2 = tonumber(hex2, 16)
	                        if cp2 >= 0xDC00 and cp2 <= 0xDFFF then
	                            cp = 0x10000 + (cp - 0xD800) * 1024 + (cp2 - 0xDC00)
	                            i = i + 6
	                        end
	                    end
	                end
	                out[#out + 1] = mw.ustring.char(cp)
	            else
	                raise_decode_error("Invalid \\escape", s, i - 1)
	            end
	            chunk_start = i
	        elseif strict and b < 32 then
	            raise_decode_error("Invalid control character at", s, i)
	        else
	            i = i + 1
	        end
	    end
	    raise_decode_error("Unterminated string starting at", s, pos)
	end
	
	local parse_json_value
	
	local function parse_json_array(s, pos, len, hooks)
	    local i = skip_ws(s, pos + 1, len)
	    local elts = {}
	    if i <= len and string.byte(s, i) == 93 then -- ']'
	        return { __type = "list", elements = elts }, i + 1
	    end
	    while true do
	        if i > len then raise_decode_error("Expecting value", s, i) end
	        local val
	        val, i = parse_json_value(s, i, len, hooks)
	        elts[#elts + 1] = val
	        i = skip_ws(s, i, len)
	        local b = string.byte(s, i)
	        if b == 93 then -- ']'
	            return { __type = "list", elements = elts }, i + 1
	        elseif b == 44 then -- ','
	            i = skip_ws(s, i + 1, len)
	        else
	            raise_decode_error("Expecting ',' delimiter", s, i)
	        end
	    end
	end
	
	local function parse_json_object(s, pos, len, hooks)
	    local i = skip_ws(s, pos + 1, len)
	    local kv_pairs = {}
	    if i <= len and string.byte(s, i) == 125 then -- '}'
	        local empty_dict = { __type = "dict", pairs = kv_pairs }
	        if hooks.object_pairs_hook then
	            return Evaluator.invoke(hooks.object_pairs_hook, { { __type = "list", elements = {} } }, {}), i + 1
	        elseif hooks.object_hook then
	            return Evaluator.invoke(hooks.object_hook, { empty_dict }, {}), i + 1
	        end
	        return empty_dict, i + 1
	    end
	
	    while true do
	        if i > len or string.byte(s, i) ~= 34 then -- '"'
	            raise_decode_error("Expecting property name enclosed in double quotes", s, i)
	        end
	        local key
	        key, i = parse_json_string(s, i, len, hooks.strict)
	        i = skip_ws(s, i, len)
	        if i > len or string.byte(s, i) ~= 58 then -- ':'
	            raise_decode_error("Expecting ':' delimiter", s, i)
	        end
	        i = skip_ws(s, i + 1, len)
	        if i > len then raise_decode_error("Expecting value", s, i) end
	        local val
	        val, i = parse_json_value(s, i, len, hooks)
	        kv_pairs[#kv_pairs + 1] = { key = key, value = val }
	
	        i = skip_ws(s, i, len)
	        local b = string.byte(s, i)
	        if b == 125 then -- '}'
	            i = i + 1
	            if hooks.object_pairs_hook then
	                local tup_list = {}
	                for idx = 1, #kv_pairs do
	                    local p = kv_pairs[idx]
	                    tup_list[idx] = { __type = "tuple", elements = { p.key, p.value } }
	                end
	                return Evaluator.invoke(hooks.object_pairs_hook, { { __type = "list", elements = tup_list } }, {}), i
	            end
	            -- 若有重複 key,後出現的覆蓋先出現的 (維持 CPython dict 行為)
	            local dedup_pairs = {}
	            local key_idx = {}
	            for idx = 1, #kv_pairs do
	                local p = kv_pairs[idx]
	                local existing = key_idx[p.key]
	                if existing then
	                    dedup_pairs[existing].value = p.value
	                else
	                    local new_i = #dedup_pairs + 1
	                    key_idx[p.key] = new_i
	                    dedup_pairs[new_i] = p
	                end
	            end
	            local dict_obj = { __type = "dict", pairs = dedup_pairs }
	            if hooks.object_hook then
	                return Evaluator.invoke(hooks.object_hook, { dict_obj }, {}), i
	            end
	            return dict_obj, i
	        elseif b == 44 then -- ','
	            i = skip_ws(s, i + 1, len)
	        else
	            raise_decode_error("Expecting ',' delimiter", s, i)
	        end
	    end
	end
	
	parse_json_value = function(s, pos, len, hooks)
	    pos = skip_ws(s, pos, len)
	    if pos > len then raise_decode_error("Expecting value", s, pos) end
	
	    local b = string.byte(s, pos)
	    if b == 34 then -- '"'
	        return parse_json_string(s, pos, len, hooks.strict)
	    elseif b == 123 then -- '{'
	        return parse_json_object(s, pos, len, hooks)
	    elseif b == 91 then -- '['
	        return parse_json_array(s, pos, len, hooks)
	    elseif b == 116 and s:sub(pos, pos + 3) == "true" then
	        return true, pos + 4
	    elseif b == 102 and s:sub(pos, pos + 4) == "false" then
	        return false, pos + 5
	    elseif b == 110 and s:sub(pos, pos + 3) == "null" then
	        return Runtime.builtins.None, pos + 4
	    elseif b == 78 and s:sub(pos, pos + 2) == "NaN" then
	        if hooks.parse_constant then
	            return Evaluator.invoke(hooks.parse_constant, { "NaN" }, {}), pos + 3
	        end
	        return 0/0, pos + 3
	    elseif b == 73 and s:sub(pos, pos + 7) == "Infinity" then
	        if hooks.parse_constant then
	            return Evaluator.invoke(hooks.parse_constant, { "Infinity" }, {}), pos + 8
	        end
	        return math.huge, pos + 8
	    elseif s:sub(pos, pos + 8) == "-Infinity" then
	        if hooks.parse_constant then
	            return Evaluator.invoke(hooks.parse_constant, { "-Infinity" }, {}), pos + 9
	        end
	        return -math.huge, pos + 9
	    else
	        -- 匹配 JSON 數字字面量
	        local _, num_end, num_str = s:find("^(-?%d+%.?%d*[eE]?[%+%-]?%d*)", pos)
	        if num_str and num_str ~= "" then
	            -- 檢查前導零 (如 01 非法,但 0 或 0.1 合法)
	            local int_part = num_str:match("^-?(%d+)")
	            if int_part and #int_part > 1 and int_part:sub(1, 1) == "0" then
	                raise_decode_error("Expecting value", s, pos)
	            end
	            local is_float_token = num_str:find("[%.eE]") ~= nil
	            if is_float_token then
	                if hooks.parse_float then
	                    return Evaluator.invoke(hooks.parse_float, { num_str }, {}), num_end + 1
	                end
	                local n = tonumber(num_str)
	                if not n then raise_decode_error("Invalid number", s, pos) end
	                return n, num_end + 1
	            else
	                if hooks.parse_int then
	                    return Evaluator.invoke(hooks.parse_int, { num_str }, {}), num_end + 1
	                end
	                return Runtime.builtins.int.__call({ num_str }), num_end + 1
	            end
	        end
	        raise_decode_error("Expecting value", s, pos)
	    end
	end
	
	local function build_decode_hooks(kw, decoder_self)
	    kw = kw or {}
	    local d = decoder_self and decoder_self.__dict or {}
	    local function pick_hook(name)
	        local h = (kw[name] ~= nil) and kw[name] or d[name]
	        if is_none(h) then return nil end
	        return h
	    end
	    local strict = true
	    if kw.strict ~= nil then strict = EvalUtils.is_truthy(kw.strict)
	    elseif d.strict ~= nil then strict = EvalUtils.is_truthy(d.strict) end
	
	    return {
	        object_hook = pick_hook("object_hook"),
	        object_pairs_hook = pick_hook("object_pairs_hook"),
	        parse_float = pick_hook("parse_float"),
	        parse_int = pick_hook("parse_int"),
	        parse_constant = pick_hook("parse_constant"),
	        strict = strict
	    }
	end
	
	local function extract_json_input(s)
	    if type(s) == "string" then
	        -- 剝除 UTF-8 BOM (若有)
	        if s:sub(1, 3) == "\239\187\191" then s = s:sub(4) end
	        return s
	    elseif type(s) == "table" then
	        if s.__type == "bytes" or s.__type == "bytearray" then
	            return s._data
	        elseif s.__type == "memoryview" then
	            if s._released then Runtime.raise("ValueError", "operation forbidden on released memoryview object") end
	            return s._data
	        end
	    end
	    Runtime.raise("TypeError", "the JSON object must be str, bytes or bytearray, not " .. RUtils.get_type_str(s))
	end
	
	-- ==========================================
	-- 3. 初始化 JSONEncoder / JSONDecoder / 頂層函式
	-- ==========================================
	local function ensure_init()
	    if initialized then return end
	    initialized = true
	
	    local B = Runtime.builtins
	
	    -- [A] JSONDecodeError 例外類別
	    local jde_dict = {
	        __init__ = { __type = "builtin_method", __call = function(a)
	            local self, msg, doc, pos = a[1], tostring(a[2] or ""), tostring(a[3] or ""), tonumber(a[4]) or 0
	            local sub_doc = doc:sub(1, pos)
	            local lineno = 1
	            for _ in sub_doc:gmatch("\n") do lineno = lineno + 1 end
	            local last_nl = sub_doc:match(".*()\n")
	            local colno = last_nl and (pos - last_nl + 1) or (pos + 1)
	            local full_msg = string.format("%s: line %d column %d (char %d)", msg, lineno, colno, pos)
	            self.__dict.msg = msg
	            self.__dict.doc = doc
	            self.__dict.pos = pos
	            self.__dict.lineno = lineno
	            self.__dict.colno = colno
	            self.__dict.args = { __type = "tuple", elements = { full_msg } }
	            return nil
	        end },
	        __str__ = { __type = "builtin_method", __call = function(a)
	            local d = a[1].__dict
	            if d.args and d.args.elements and d.args.elements[1] then return tostring(d.args.elements[1]) end
	            return tostring(d.msg or "")
	        end }
	    }
	    cache.JSONDecodeError = PyClass.make_class("JSONDecodeError", { B.ValueError }, jde_dict)
	    cache.JSONDecodeError.__dict["__module__"] = "json.decoder"
	
	    -- [B] JSONEncoder 類別 (支援子類別繼承與 super().default(obj))
	    local encoder_dict = {
	        item_separator = ", ",
	        key_separator = ": ",
	        __init__ = { __type = "builtin_method", __call = function(a, kw)
	            local self = a[1]
	            kw = kw or {}
	            local d = self.__dict
	            d.skipkeys = (a[2] ~= nil and EvalUtils.is_truthy(a[2])) or (kw.skipkeys ~= nil and EvalUtils.is_truthy(kw.skipkeys)) or false
	            d.ensure_ascii = (a[3] ~= nil and EvalUtils.is_truthy(a[3])) or (kw.ensure_ascii ~= nil and EvalUtils.is_truthy(kw.ensure_ascii)) or (a[3] == nil and kw.ensure_ascii == nil)
	            d.check_circular = (a[4] ~= nil and EvalUtils.is_truthy(a[4])) or (kw.check_circular ~= nil and EvalUtils.is_truthy(kw.check_circular)) or (a[4] == nil and kw.check_circular == nil)
	            d.allow_nan = (a[5] ~= nil and EvalUtils.is_truthy(a[5])) or (kw.allow_nan ~= nil and EvalUtils.is_truthy(kw.allow_nan)) or (a[5] == nil and kw.allow_nan == nil)
	            d.sort_keys = (a[6] ~= nil and EvalUtils.is_truthy(a[6])) or (kw.sort_keys ~= nil and EvalUtils.is_truthy(kw.sort_keys)) or false
	            d.indent = (a[7] ~= nil and a[7]) or kw.indent or B.None
	            d.separators = (a[8] ~= nil and a[8]) or kw.separators or B.None
	            local def_fn = (a[9] ~= nil and a[9]) or kw.default
	            if not is_none(def_fn) then d._default_fn = def_fn end
	            return nil
	        end },
	        default = { __type = "builtin_method", __name = "default", __call = function(a)
	            local self, o = a[1], a[2]
	            if self.__dict and self.__dict._default_fn and not is_none(self.__dict._default_fn) then
	                return Evaluator.invoke(self.__dict._default_fn, { o }, {})
	            end
	            Runtime.raise("TypeError", "Object of type " .. RUtils.get_type_str(o) .. " is not JSON serializable")
	        end },
	        encode = { __type = "builtin_method", __name = "encode", __call = function(a)
	            local self, o = a[1], a[2]
	            local opts = build_encode_opts(nil, self)
	            local buf = {}
	            serialize_value(o, opts, 0, {}, buf)
	            return table.concat(buf)
	        end },
	        iterencode = { __type = "builtin_method", __name = "iterencode", __call = function(a)
	            local self, o = a[1], a[2]
	            local enc_m = PyClass.get_attr(self, "encode")
	            local s = Evaluator.invoke(enc_m, { o }, {})
	            return B.iter({ { __type = "list", elements = { s } } })
	        end }
	    }
	    cache.JSONEncoder = PyClass.make_class("JSONEncoder", {}, encoder_dict)
	    cache.JSONEncoder.__dict["__module__"] = "json.encoder"
	
	    -- [C] JSONDecoder 類別
	    local decoder_dict = {
	        __init__ = { __type = "builtin_method", __call = function(a, kw)
	            local self = a[1]
	            kw = kw or {}
	            local d = self.__dict
	            d.object_hook = (a[2] ~= nil and a[2]) or kw.object_hook or B.None
	            d.parse_float = (a[3] ~= nil and a[3]) or kw.parse_float or B.None
	            d.parse_int = (a[4] ~= nil and a[4]) or kw.parse_int or B.None
	            d.parse_constant = (a[5] ~= nil and a[5]) or kw.parse_constant or B.None
	            d.strict = (a[6] ~= nil and EvalUtils.is_truthy(a[6])) or (kw.strict ~= nil and EvalUtils.is_truthy(kw.strict)) or (a[6] == nil and kw.strict == nil)
	            d.object_pairs_hook = (a[7] ~= nil and a[7]) or kw.object_pairs_hook or B.None
	            return nil
	        end },
	        raw_decode = { __type = "builtin_method", __name = "raw_decode", __call = function(a, kw)
	            local self = a[1]
	            local s = extract_json_input(a[2] or (kw and kw.s))
	            local idx = tonumber(a[3] or (kw and kw.idx)) or 0
	            if idx < 0 then raise_decode_error("Expecting value", s, 1) end
	            local hooks = build_decode_hooks(nil, self)
	            local obj, end_pos = parse_json_value(s, idx + 1, #s, hooks)
	            return { __type = "tuple", elements = { obj, end_pos - 1 } }
	        end },
	        decode = { __type = "builtin_method", __name = "decode", __call = function(a, kw)
	            local self = a[1]
	            local s = extract_json_input(a[2] or (kw and kw.s))
	            local hooks = build_decode_hooks(nil, self)
	            local len = #s
	            local pos = skip_ws(s, 1, len)
	            local obj, next_pos = parse_json_value(s, pos, len, hooks)
	            next_pos = skip_ws(s, next_pos, len)
	            if next_pos <= len then
	                raise_decode_error("Extra data", s, next_pos)
	            end
	            return obj
	        end }
	    }
	    cache.JSONDecoder = PyClass.make_class("JSONDecoder", {}, decoder_dict)
	    cache.JSONDecoder.__dict["__module__"] = "json.decoder"
	
	    -- [D] dumps / dump / loads / load 頂層函式
	    cache.dumps = {
	        __type = "builtin_method", __name = "dumps",
	        __call = function(a, kw)
	            local obj = a[1]
	            kw = kw or {}
	            local cls = kw.cls
	            if not is_none(cls) then
	                local init_kw = {}
	                for k, v in pairs(kw) do
	                    if k ~= "cls" then init_kw[k] = v end
	                end
	                local encoder_inst = Evaluator.invoke(cls, {}, init_kw)
	                local encode_m = PyClass.get_attr(encoder_inst, "encode")
	                return Evaluator.invoke(encode_m, { obj }, {})
	            end
	            local opts = build_encode_opts(kw, nil)
	            local buf = {}
	            serialize_value(obj, opts, 0, {}, buf)
	            return table.concat(buf)
	        end
	    }
	
	    cache.dump = {
	        __type = "builtin_method", __name = "dump",
	        __call = function(a, kw)
	            local obj, fp = a[1], a[2]
	            if not fp then Runtime.raise("TypeError", "dump() missing required argument: 'fp'") end
	            local s = cache.dumps.__call({ obj }, kw)
	            local write_m = PyClass.get_attr(fp, "write")
	            Evaluator.invoke(write_m, { s }, {})
	            return B.None
	        end
	    }
	
	    cache.loads = {
	        __type = "builtin_method", __name = "loads",
	        __call = function(a, kw)
	            local raw_s = a[1] or (kw and kw.s)
	            local s = extract_json_input(raw_s)
	            kw = kw or {}
	            local cls = kw.cls
	            if not is_none(cls) then
	                local init_kw = {}
	                for k, v in pairs(kw) do
	                    if k ~= "cls" then init_kw[k] = v end
	                end
	                local decoder_inst = Evaluator.invoke(cls, {}, init_kw)
	                local decode_m = PyClass.get_attr(decoder_inst, "decode")
	                return Evaluator.invoke(decode_m, { s }, {})
	            end
	            local hooks = build_decode_hooks(kw, nil)
	            local len = #s
	            local pos = skip_ws(s, 1, len)
	            local obj, next_pos = parse_json_value(s, pos, len, hooks)
	            next_pos = skip_ws(s, next_pos, len)
	            if next_pos <= len then
	                raise_decode_error("Extra data", s, next_pos)
	            end
	            return obj
	        end
	    }
	
	    cache.load = {
	        __type = "builtin_method", __name = "load",
	        __call = function(a, kw)
	            local fp = a[1]
	            if not fp then Runtime.raise("TypeError", "load() missing required argument: 'fp'") end
	            local read_m = PyClass.get_attr(fp, "read")
	            local content = Evaluator.invoke(read_m, {}, {})
	            return cache.loads.__call({ content }, kw)
	        end
	    }
	end
	
	function JsonMod.get_module()
	    ensure_init()
	    local mod_dict = {
	        __getattr__ = {
	            __type = "builtin_method",
	            __call = function(args)
	                local self, attr = args[1], args[2]
	                local val = cache[attr]
	                if val ~= nil then
	                    self.__dict[attr] = val
	                    return val
	                end
	                Runtime.raise("AttributeError", "module 'json' has no attribute '" .. tostring(attr) .. "'")
	            end
	        }
	    }
	    local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
	    mod.__dict["__name__"] = "json"
	    mod.__dict["__all__"] = {
	        __type = "list",
	        elements = { "dump", "dumps", "load", "loads", "JSONDecoder", "JSONDecodeError", "JSONEncoder" }
	    }
	    return mod
	end
	
	return JsonMod
end

return {
	Csv = CsvMod,
	Json = init_json
}