跳转到内容

模組:PythonScript/Evaluator

维基百科,自由的百科全书
-- Module:PythonScript/Evaluator
-- 核心 AST 樹狀直譯器、無堆疊協程狀態機與對外呼叫介面
-- 子模組:
--  -[[Module:PythonScript/Evaluator]]
--     -[[Module:PythonScript/Evaluator/Core]]
--        -[[Module:PythonScript/Evaluator/Core/Ops]]
--        -[[Module:PythonScript/Evaluator/Core/Stmt]]
--        -[[Module:PythonScript/Evaluator/Core/Coroutine]]
local Evaluator = {}

local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local Utils = require("Module:PythonScript/Evaluator/Utils")

-- 引入 Utils 內部輔助函式作為區域常數以維持最佳效能
local create_scope = Utils.create_scope
local resolve_index = Utils.resolve_index
local bigint_op = Utils.bigint_op

-- 核心函式前向宣告
local eval_expr, raw_eval_expr, exec_stmt, exec_block, bind_and_exec_func, exec_generator_step = require("Module:PythonScript/Evaluator/Core").getCoreFunction()

function Evaluator.default_instantiate_class(callee, passed_args, passed_kwargs)
    if callee.__call then return callee.__call(passed_args, passed_kwargs) end

    -- 1. 查找並呼叫 __new__(cls, *args, **kwargs)
    local new_attr = nil
    for _, base_cls in ipairs(callee.__mro or {callee}) do
        if base_cls.__dict and base_cls.__dict["__new__"] ~= nil then
            new_attr = base_cls.__dict["__new__"]
            break
        end
    end

    local instance
    if new_attr then
        local new_func = (type(new_attr) == "table" and (new_attr.__type == "staticmethod" or new_attr.__type == "classmethod")) and new_attr.__func or new_attr
        local new_args = { callee }
        for _, v in ipairs(passed_args) do table.insert(new_args, v) end
        instance = bind_and_exec_func(new_func, new_args, passed_kwargs)
    else
        instance = PyClass.make_instance(callee)
    end

    -- 2. 若 __new__ 回傳的是當前類別 (或其子類別) 的實例 (含元類別建構出的 class),才繼續執行 __init__
    if type(instance) == "table" and (instance.__type == "instance" or instance.__type == "class") and PyClass.is_instance(instance, callee) then
        local init = nil
        local inst_cls = instance.__class
        for _, base_cls in ipairs((inst_cls and inst_cls.__mro) or callee.__mro or {}) do
            if base_cls.__dict and base_cls.__dict["__init__"] ~= nil then
                init = base_cls.__dict["__init__"]
                break
            end
        end
        local default_obj_init = Runtime.builtins.object and Runtime.builtins.object.__dict.__init__
        local default_type_init = Runtime.builtins.type and Runtime.builtins.type.__dict.__init__
        if init and init ~= default_obj_init and init ~= default_type_init then
            local init_res
            if type(init) == "table" then
                if init.__type == "function" or init.__type == "builtin_method" then
                    init_res = bind_and_exec_func(init, passed_args, passed_kwargs, instance)
                elseif init.__type == "classmethod" then
                    init_res = bind_and_exec_func(init.__func, passed_args, passed_kwargs, instance.__class)
                elseif init.__type == "staticmethod" then
                    init_res = bind_and_exec_func(init.__func, passed_args, passed_kwargs)
                end
            end
            if init_res ~= nil and init_res ~= Runtime.builtins.None then
                Runtime.raise("TypeError", "__init__() should return None")
            end
        elseif instance.__type == "instance" and #passed_args > 0 and PyClass.is_subclass(callee, Runtime.builtins.BaseException) then
            instance.__dict.args = { __type = "tuple", elements = passed_args }
            instance.__dict.msg = Runtime.builtins.str.__call({passed_args[1]})
            if callee.__name == "StopIteration" then instance.__dict.value = passed_args[1] end
            if callee.__name == "SystemExit" then instance.code = passed_args[1] end
        end
    end
    return instance
end

function Evaluator.invoke(callee, args, kwargs, self_obj)
    local passed_args = args or {}
    local passed_kwargs = kwargs or {}
    local r
    if type(callee) == "function" then
        r = callee(passed_args, passed_kwargs); return r == nil and Runtime.builtins.None or r
    elseif type(callee) == "table" then
        if callee.__type == "class" then
            -- 若該類別擁有自訂元類別且定義了 __call__,優先交由元類別的 __call__(cls, *args, **kwargs) 攔截
            local meta = callee.__class
            if type(meta) == "table" and meta.__type == "class" and meta ~= Runtime.builtins.type then
                for _, m_base in ipairs(meta.__mro or {}) do
                    if m_base == Runtime.builtins.type or m_base == Runtime.builtins.object then break end
                    local meta_call = m_base.__dict and m_base.__dict["__call__"]
                    if meta_call ~= nil then
                        r = bind_and_exec_func(meta_call, passed_args, passed_kwargs, callee)
                        return r == nil and Runtime.builtins.None or r
                    end
                end
            end
            return Evaluator.default_instantiate_class(callee, passed_args, passed_kwargs)
        elseif callee.__type == "instance" then
            local prev_depth = Runtime.Traceback.eval_depth
            local success, attr = pcall(PyClass.get_attr, callee, "__call__")
            Runtime.Traceback.eval_depth = prev_depth
            if success and attr then return Evaluator.invoke(attr, passed_args, passed_kwargs) end
        elseif callee.__type == "bound_method" or callee.__type == "function" or callee.__type == "builtin_method" then
            r = bind_and_exec_func(callee, passed_args, passed_kwargs, self_obj)
            return r == nil and Runtime.builtins.None or r
        elseif callee.__type == "complex" and callee.__call then
            r = callee.__call(passed_args, passed_kwargs)
            return r == nil and Runtime.builtins.None or r
        end
    end
    
    local err_name = type(callee) == "table" and (callee.__class and callee.__class.__name or callee.__type) or type(callee)
    Runtime.raise("TypeError", "'" .. err_name .. "' object is not callable")
end


function Evaluator.execute(ast, global_env)
    local global_scope = create_scope(nil)
    if global_env then for k, v in pairs(global_env) do global_scope.vars[k] = v end end
    if ast.type == "Module" then
        global_scope.__return_signal = exec_block(ast.body, global_scope)
    else
        Runtime.raise("RuntimeError", "Evaluator expected a Module AST")
    end
    return global_scope
end

-- 輔助:將直譯器內部 scope.vars 雙向同步回 Python dict 物件
local function sync_scope_to_dict(scope_vars, dict_obj)
    if type(dict_obj) ~= "table" or dict_obj.__type ~= "dict" then return end
    for i = #dict_obj.pairs, 1, -1 do
        local p = dict_obj.pairs[i]
        if type(p.key) == "string" and p.key ~= "__builtins__" and scope_vars[p.key] == nil then
            table.remove(dict_obj.pairs, i)
            if dict_obj.__scope_ref and dict_obj.__scope_ref ~= scope_vars then
                dict_obj.__scope_ref[p.key] = nil
            end
        end
    end
    for k, v in pairs(scope_vars) do
        local found = false
        for _, p in ipairs(dict_obj.pairs) do
            if p.key == k then p.value = v; found = true; break end
        end
        if not found then table.insert(dict_obj.pairs, { key = k, value = v }) end
        if dict_obj.__scope_ref and dict_obj.__scope_ref ~= scope_vars then
            dict_obj.__scope_ref[k] = v
        end
    end
    if not dict_obj.__scope_ref then
        dict_obj.__scope_ref = scope_vars
    end
end

function Evaluator.eval_code(code_obj, globals_arg, locals_arg, caller_kind)
    local function is_none(v)
        return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
    end

    local has_g = not is_none(globals_arg)
    local has_l = not is_none(locals_arg)

    if has_g and type(globals_arg) == "table" and globals_arg.__type == "instance" and type(globals_arg.__builtin_value) == "table" and globals_arg.__builtin_value.__type == "dict" then
        globals_arg = globals_arg.__builtin_value
    end
    if has_g and (type(globals_arg) ~= "table" or globals_arg.__type ~= "dict") then
        Runtime.raise("TypeError", caller_kind .. "() globals must be a dict")
    end
    if has_l and (type(locals_arg) ~= "table" or (locals_arg.__type ~= "dict" and locals_arg.__type ~= "instance")) then
        Runtime.raise("TypeError", caller_kind .. "() locals must be a mapping")
    end

    local curr_scope = Runtime.Traceback.current_scope
    local g_scope, exec_scope

    if not has_g and not has_l then
        exec_scope = curr_scope or create_scope(nil)
    else
        if has_g then
            g_scope = create_scope(nil)
            for _, p in ipairs(globals_arg.pairs) do
                if type(p.key) == "string" then g_scope.vars[p.key] = p.value end
            end
            globals_arg.__scope_ref = g_scope.vars
        else
            local root = curr_scope
            while root and root.parent do root = root.parent end
            g_scope = root or create_scope(nil)
        end

        if not has_l or locals_arg == globals_arg then
            exec_scope = g_scope
        else
            exec_scope = create_scope(g_scope)
            local l_dict = (locals_arg.__type == "instance" and type(locals_arg.__builtin_value) == "table" and locals_arg.__builtin_value.__type == "dict") and locals_arg.__builtin_value or locals_arg
            if l_dict.__type == "dict" then
                for _, p in ipairs(l_dict.pairs) do
                    if type(p.key) == "string" then exec_scope.vars[p.key] = p.value end
                end
                l_dict.__scope_ref = exec_scope.vars
            else
                exec_scope.custom_ns = locals_arg
            end
        end
    end

    local ast = code_obj.__dict._ast
    local file_idx = code_obj.__dict._file_idx or Runtime.Traceback.current_file_idx

    local prev_file_idx = Runtime.Traceback.current_file_idx
    local prev_func_name = Runtime.Traceback.current_func_name or "<module>"
    local prev_line = Runtime.Traceback.current_line
    local prev_scope = Runtime.Traceback.current_scope
    local prev_depth = Runtime.Traceback.eval_depth
    local prev_stack_len = #Runtime.Traceback.stack

    table.insert(Runtime.Traceback.stack, { prev_file_idx, prev_func_name, prev_line })
    Runtime.Traceback.current_file_idx = file_idx
    Runtime.Traceback.current_func_name = "<module>"
    Runtime.Traceback.current_line = 1
    Runtime.Traceback.current_scope = exec_scope

    local ok, res = pcall(function()
        if ast.type == "Expression" then
            return eval_expr(ast.body, exec_scope)
        else
            exec_block(ast.body, exec_scope)
            return Runtime.builtins.None
        end
    end)

    if has_g then sync_scope_to_dict(g_scope.vars, globals_arg) end
    if has_l and locals_arg ~= globals_arg then sync_scope_to_dict(exec_scope.vars, locals_arg) end

    while #Runtime.Traceback.stack > prev_stack_len do table.remove(Runtime.Traceback.stack) end
    Runtime.Traceback.current_file_idx = prev_file_idx
    Runtime.Traceback.current_func_name = prev_func_name
    Runtime.Traceback.current_line = prev_line
    Runtime.Traceback.current_scope = prev_scope
    Runtime.Traceback.eval_depth = prev_depth

    if not ok then error(res, 0) end
    if caller_kind == "exec" then return Runtime.builtins.None end
    return res == nil and Runtime.builtins.None or res
end

-- 將核心回呼注入 Utils,完成無循環依賴的雙向綁定
Utils.bind_core({
    eval_expr = eval_expr,
    bind_and_exec_func = bind_and_exec_func,
    invoke = Evaluator.invoke
})

-- 保持對外匯出介面 100% 相容
Evaluator.resolve_index = resolve_index
Evaluator.bigint_op = bigint_op

return Evaluator