跳转到内容

模組: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