模組:PythonScript/Evaluator/Core
外观
-- Module:PythonScript/Evaluator/Core
-- 核心求值器主幹與輕量表達式分派器 (搭配 Core/Ops, Core/Stmt, Core/Coroutine 子模組)
local Core = {}
local cached_funcs = nil
function Core.getCoreFunction()
if cached_funcs then
return cached_funcs[1], cached_funcs[2], cached_funcs[3], cached_funcs[4], cached_funcs[5], cached_funcs[6]
end
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local Utils = require("Module:PythonScript/Evaluator/Utils")
local OpsMod = require("Module:PythonScript/Evaluator/Core/Ops")
local StmtMod = require("Module:PythonScript/Evaluator/Core/Stmt")
local CoroMod = require("Module:PythonScript/Evaluator/Core/Coroutine")
local invoke = nil
local create_scope = Utils.create_scope
local lookup_var = Utils.lookup_var
local is_truthy = Utils.is_truthy
local py_eq = Utils.py_eq
local make_iter_state = Utils.make_iter_state
local get_next_item = Utils.get_next_item
local eval_comprehension = Utils.eval_comprehension
local assign_target = Utils.assign_target
local eval_fstring = Utils.eval_fstring
-- 核心函式與子模組算子前向宣告
local eval_expr, raw_eval_expr, exec_stmt, exec_block, bind_and_exec_func, exec_generator_step
local eval_index, eval_unary_op, eval_binary_op, eval_assign
-- ==========================================
-- AST 表達式求值核心
-- ==========================================
raw_eval_expr = function(node, scope)
if node.line then Runtime.Traceback.current_line = node.line end
local ntype = node.type
if ntype == "DirectValue" then
return node.value
elseif ntype == "Number" then
if node.is_complex then
local Cmath = require("Module:PythonScript/Runtime/Cmath").Cmath
local s_low = node.value:lower()
if s_low:sub(-2) == "jk" then
return Cmath.make_complex(0, 0, 0, tonumber(s_low:sub(1, -3)) or 0)
elseif s_low:sub(-1) == "k" then
return Cmath.make_complex(0, 0, tonumber(s_low:sub(1, -2)) or 0, 0)
else
return Cmath.make_complex(0, tonumber(s_low:sub(1, -2)) or 0, 0, 0)
end
end
local val_str = node.value
local prefix = val_str:sub(1, 2):lower()
if prefix == "0x" then return Runtime.builtins.int.__call({val_str, 16}) end
if prefix == "0o" then return Runtime.builtins.int.__call({val_str:sub(3), 8}) end
if prefix == "0b" then return Runtime.builtins.int.__call({val_str:sub(3), 2}) end
if string.find(val_str, "[.eE]") then return tonumber(val_str) end
return Runtime.builtins.int.__call({val_str})
elseif ntype == "String" then
local content = node.value; local prefix = content:match("^([a-zA-Z]+)")
if prefix then content = content:sub(#prefix + 1) end
if content:sub(1, 3) == '"""' or content:sub(1, 3) == "'''" then content = content:sub(4, -4) else content = content:sub(2, -2) end
if node.is_wiki then
if mw and mw.text and type(mw.text.decode) == type(function()end) then
content = mw.text.decode(content, true)
else
local named = { lt="<", gt=">", amp="&", quot='"', apos="'", nbsp=" " }
content = content:gsub("&#[xX](%x+);", function(h) return mw.ustring.char(tonumber(h, 16)) end)
content = content:gsub("&#(%d+);", function(d) return mw.ustring.char(tonumber(d, 10)) end)
content = content:gsub("&([a-zA-Z]+);", function(n) return named[n:lower()] or ("&" .. n .. ";") end)
end
end
if not node.is_raw then
content = content:gsub("\\n", "\n"):gsub("\\t", "\t"):gsub("\\r", "\r"):gsub("\\\\", "\\"):gsub('\\"', '"'):gsub("\\'", "'")
content = content:gsub("\\x(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
end
if node.is_bytes then return { __type = "bytes", _data = content } end
if node.is_fstring then content = eval_fstring(content, scope) end
return content
elseif ntype == "Boolean" then return node.value
elseif ntype == "None" then return Runtime.builtins.None
elseif ntype == "Identifier" then return lookup_var(node.name, scope)
elseif ntype == "Starred" then
Runtime.raise("SyntaxError", "can't use starred expression here")
elseif ntype == "Yield" or ntype == "YieldFrom" then
Runtime.raise("SyntaxError", "yield/yield from in nested expressions is not supported in this stackless environment. Please assign it to a standalone variable first.")
elseif ntype == "Await" then
local coro = eval_expr(node.value, scope)
local prev_depth = Runtime.Traceback.eval_depth
local success, send_method = pcall(PyClass.get_attr, coro, "send")
Runtime.Traceback.eval_depth = prev_depth
if not success then return coro end
while true do
prev_depth = Runtime.Traceback.eval_depth
local invoke_success, next_val = pcall(bind_and_exec_func, send_method.__func, {Runtime.builtins.None}, {}, send_method.__self)
Runtime.Traceback.eval_depth = prev_depth
if not invoke_success then
if type(next_val) == "table" and next_val.__class and next_val.__class.__name == "StopIteration" then
local ret = next_val.__dict.value
return ret == nil and Runtime.builtins.None or ret
end
error(next_val, 0)
end
end
elseif ntype == "Lambda" then
local defaults_val = {}
if node.defaults then for arg_name, expr_node in pairs(node.defaults) do defaults_val[arg_name] = eval_expr(expr_node, scope) end end
local func_node = { type = "FunctionDef", name = "<lambda>", args = node.args, posonly_count = node.posonly_count, kwonlyargs = node.kwonlyargs, defaults = {}, vararg = node.vararg, kwarg = node.kwarg, body = { { type = "Return", value = node.body, line = node.line } }, is_generator = node.is_generator, line = node.line }
return { __type = "function", __name = "<lambda>", __node = func_node, __scope = scope, __defaults = defaults_val }
elseif ntype == "List" or ntype == "Tuple" then
local elements = {}
for _, el in ipairs(node.elements) do
if el.type == "Starred" then
local iter_state = make_iter_state(eval_expr(el.value, scope))
while true do local has_next, v = get_next_item(iter_state); if not has_next then break end; table.insert(elements, v) end
else table.insert(elements, eval_expr(el, scope)) end
end
return { __type = (ntype == "List" and "list" or "tuple"), elements = elements }
elseif ntype == "Dict" then
local kv_pairs = {}
local function set_kv(k, v)
for _, ep in ipairs(kv_pairs) do
if py_eq(ep.key, k) then ep.value = v; return end
end
table.insert(kv_pairs, { key = k, value = v })
end
for _, p in ipairs(node.pairs) do
if p.key == nil then
local map_obj = eval_expr(p.value, scope)
if type(map_obj) == "table" and map_obj.__type == "instance" and map_obj.__builtin_value ~= nil then
map_obj = map_obj.__builtin_value
end
if type(map_obj) == "table" and map_obj.__type == "dict" then
for _, mp in ipairs(map_obj.pairs) do set_kv(mp.key, mp.value) end
else
Runtime.raise("TypeError", "'" .. tostring(type(map_obj) == "table" and (map_obj.__class and map_obj.__class.__name or map_obj.__type) or type(map_obj)) .. "' object is not a mapping")
end
else
set_kv(eval_expr(p.key, scope), eval_expr(p.value, scope))
end
end
return { __type = "dict", pairs = kv_pairs }
elseif ntype == "Set" then
local elements = {}
for _, el in ipairs(node.elements) do
if el.type == "Starred" then
local iter_state = make_iter_state(eval_expr(el.value, scope))
while true do
local has_next, val = get_next_item(iter_state); if not has_next then break end
local exists = false
for _, e in ipairs(elements) do if py_eq(e, val) then exists = true; break end end
if not exists then table.insert(elements, val) end
end
else
local val = eval_expr(el, scope); local exists = false
for _, e in ipairs(elements) do if py_eq(e, val) then exists = true; break end end
if not exists then table.insert(elements, val) end
end
end
return { __type = "set", elements = elements }
elseif ntype == "IfExp" then
if is_truthy(eval_expr(node.test, scope)) then return eval_expr(node.body, scope) else return eval_expr(node.orelse, scope) end
elseif ntype == "ListComp" then
local comp_scope = create_scope(scope); comp_scope.is_comp_scope = true; local elements = {}
eval_comprehension(node.generators, 1, comp_scope, function() table.insert(elements, eval_expr(node.elt, comp_scope)) end)
return { __type = "list", elements = elements }
elseif ntype == "SetComp" then
local comp_scope = create_scope(scope); comp_scope.is_comp_scope = true; local elements = {}
eval_comprehension(node.generators, 1, comp_scope, function()
local val = eval_expr(node.elt, comp_scope); local exists = false
for _, e in ipairs(elements) do if py_eq(e, val) then exists = true; break end end
if not exists then table.insert(elements, val) end
end)
return { __type = "set", elements = elements }
elseif ntype == "DictComp" then
local comp_scope = create_scope(scope); comp_scope.is_comp_scope = true; local kv_pairs = {}
eval_comprehension(node.generators, 1, comp_scope, function()
local k = eval_expr(node.key, comp_scope); local v = eval_expr(node.value, comp_scope); local overwritten = false
for _, p in ipairs(kv_pairs) do if py_eq(p.key, k) then p.value = v; overwritten = true; break end end
if not overwritten then table.insert(kv_pairs, { key = k, value = v }) end
end)
return { __type = "dict", pairs = kv_pairs }
elseif ntype == "GeneratorExp" then
local body_ast = { type = "ExprStatement", expr = { type = "Yield", value = node.elt } }
for i = #node.generators, 1, -1 do
local gen = node.generators[i]; local inner_body = body_ast
for j = #gen.ifs, 1, -1 do inner_body = { type = "If", test = gen.ifs[j], body = { inner_body }, orelse = {} } end
body_ast = { type = "For", target = gen.target, iter = gen.iter, body = { inner_body }, orelse = {} }
end
local gen_func_node = { type = "FunctionDef", name = "<genexpr>", args = {}, defaults = {}, body = { body_ast }, is_generator = true }
return bind_and_exec_func({ __type = "function", __name = "<genexpr>", __node = gen_func_node, __scope = scope, __defaults = {} }, {}, {})
elseif ntype == "Attribute" then
local target = eval_expr(node.target, scope)
if type(target) == "table" and target.__type == "property" then
if node.attr == "setter" then return { __type = "builtin_method", __call = function(args) target.fset = args[1]; return target end } end
end
if type(target) == "table" and target.__type == "complex" then
if node.attr == "imag" then
return require("Module:PythonScript/Runtime/Utils").make_quaternion_imag(target.imag or 0, target.k or 0, target.jk or 0)
end
local attr_val = require("Module:PythonScript/Runtime/Cmath").Cmath.get_attr(target, node.attr)
if attr_val ~= nil then return attr_val end
end
local val = PyClass.get_attr(target, node.attr)
if type(val) == "table" and val.__type == "property" then
if target.__type == "instance" then
if not val.fget then Runtime.raise("AttributeError", "unreadable attribute") end
return eval_expr({ type = "Call", callee = { type = "DirectValue", value = val.fget }, args = { { type = "DirectValue", value = target } } }, scope)
end
end
return val
elseif ntype == "Call" then
local call_line = node.line or Runtime.Traceback.current_line
local callee = eval_expr(node.callee, scope); local passed_args = {}
for _, arg_node in ipairs(node.args or {}) do
if arg_node.type == "Starred" then
local iterable = eval_expr(arg_node.value, scope); local iter_state = make_iter_state(iterable)
while true do
local has_next, v = get_next_item(iter_state); if not has_next then break end
table.insert(passed_args, v == nil and Runtime.builtins.None or v)
end
else
local v = eval_expr(arg_node, scope)
table.insert(passed_args, v == nil and Runtime.builtins.None or v)
end
end
local passed_kwargs = {}
for _, kw_node in ipairs(node.kwargs or {}) do
if kw_node.key then
local val = eval_expr(kw_node.value, scope)
if passed_kwargs[kw_node.key] ~= nil then
Runtime.raise("TypeError", "got multiple values for keyword argument '" .. tostring(kw_node.key) .. "'")
end
passed_kwargs[kw_node.key] = val
else
local dict_obj = eval_expr(kw_node.value, scope)
if type(dict_obj) == "table" and dict_obj.__type == "dict" then
for _, pair in ipairs(dict_obj.pairs) do
if type(pair.key) ~= "string" then
Runtime.raise("TypeError", "keywords must be strings")
end
if passed_kwargs[pair.key] ~= nil then
Runtime.raise("TypeError", "got multiple values for keyword argument '" .. pair.key .. "'")
end
passed_kwargs[pair.key] = pair.value
end
else Runtime.raise("TypeError", "argument after ** must be a mapping") end
end
end
Runtime.Traceback.current_line = call_line
local prev_scope = Runtime.Traceback.current_scope
local prev_depth = Runtime.Traceback.eval_depth
local prev_stack_len = #Runtime.Traceback.stack
local prev_file_idx = Runtime.Traceback.current_file_idx
local prev_func_name = Runtime.Traceback.current_func_name
Runtime.Traceback.current_scope = scope
if type(invoke) ~= type(function()end) then invoke = require("Module:PythonScript/Evaluator").invoke end
local success, r = pcall(invoke, callee, passed_args, passed_kwargs)
Runtime.Traceback.current_scope = prev_scope
Runtime.Traceback.eval_depth = prev_depth
if not success then
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 = call_line
error(r, 0)
end
return r
elseif ntype == "Slice" then
local low = node.lower and eval_expr(node.lower, scope) or Runtime.builtins.None
local high = node.upper and eval_expr(node.upper, scope) or Runtime.builtins.None
local st = node.step and eval_expr(node.step, scope) or Runtime.builtins.None
if type(invoke) ~= type(function()end) then invoke = require("Module:PythonScript/Evaluator").invoke end
return invoke(Runtime.builtins.slice, { low, high, st }, {})
elseif ntype == "Index" then
return eval_index(node, scope)
elseif ntype == "UnaryOp" then
return eval_unary_op(node, scope)
elseif ntype == "BinaryOp" then
return eval_binary_op(node, scope)
elseif ntype == "Assign" then
return eval_assign(node, scope)
elseif ntype == "NamedExpr" then
local val = eval_expr(node.value, scope)
local target_scope = scope
while target_scope.is_comp_scope and target_scope.parent do
target_scope = target_scope.parent
end
assign_target(node.target, val, target_scope)
return val
end
end
eval_expr = function(node, scope)
Runtime.Traceback.eval_depth = (Runtime.Traceback.eval_depth or 0) + 1
if Runtime.Traceback.eval_depth > 200 then
Runtime.raise("RecursionError", "maximum recursion depth exceeded during evaluation")
end
local res = raw_eval_expr(node, scope)
Runtime.Traceback.eval_depth = math.max(0, (Runtime.Traceback.eval_depth or 1) - 1)
return res
end
-- 實例化並互相綁定三大子模組的閉包參照
exec_stmt, exec_block = StmtMod.create(eval_expr)
bind_and_exec_func, exec_generator_step = CoroMod.create(eval_expr, exec_stmt, exec_block)
StmtMod.bind_func_executor(bind_and_exec_func)
eval_index, eval_unary_op, eval_binary_op, eval_assign = OpsMod.create(eval_expr, bind_and_exec_func)
cached_funcs = { eval_expr, raw_eval_expr, exec_stmt, exec_block, bind_and_exec_func, exec_generator_step }
return eval_expr, raw_eval_expr, exec_stmt, exec_block, bind_and_exec_func, exec_generator_step
end
return Core