如何不使用loadstring从字符串加载脚本,求纯Lua实现的loadstring代码
纯Lua实现loadstring功能参考方案
前提说明
Lua原生的loadstring(Lua 5.2+合并为load)依赖C实现的解析器,纯Lua版本的实现本质是精简的Lua词法+语法解析器,仅能覆盖常用语法子集,性能远低于原生实现,仅适合受限环境下加载短脚本使用。
核心实现代码
以下为可直接使用的精简实现,支持大部分基础语法:
local function custom_loadstring(str, chunk_name) chunk_name = chunk_name or "custom_load_chunk" -- 继承调用者的环境 local call_env = getfenv and getfenv(2) or _ENV -- 词法分析模块:将字符串拆解为token序列 local tokens = {} local pos = 1 local str_len = #str local function peek() return str:sub(pos, pos) end local function consume() local c = peek() pos = pos + 1 return c end local function skip_whitespace() while peek():match("%s") do consume() end end local function skip_comment() if peek() == "-" then consume() if peek() == "-" then consume() -- 单行注释 while pos <= str_len and peek() ~= "\n" do consume() end return true end pos = pos - 1 end return false end local keywords = { ["function"] = true, ["local"] = true, ["if"] = true, ["then"] = true, ["else"] = true, ["elseif"] = true, ["end"] = true, ["while"] = true, ["do"] = true, ["for"] = true, ["return"] = true, ["break"] = true, ["nil"] = true, ["true"] = true, ["false"] = true } while pos <= str_len do skip_whitespace() while skip_comment() do skip_whitespace() end if pos > str_len then break end local c = peek() -- 匹配标识符/关键字 if c:match("[a-zA-Z_]") then local ident = "" while pos <= str_len and peek():match("[a-zA-Z0-9_]") do ident = ident .. consume() end local token_type = keywords[ident] and "keyword" or "ident" table.insert(tokens, {type = token_type, value = ident}) -- 匹配数字 elseif c:match("[0-9]") then local num = "" while pos <= str_len and peek():match("[0-9%.]") do num = num .. consume() end table.insert(tokens, {type = "number", value = tonumber(num)}) -- 匹配字符串 elseif c == "\"" or c == "'" then local quote_type = consume() local str_val = "" while pos <= str_len and peek() ~= quote_type do if peek() == "\\" then consume() local escape_char = consume() local escape_map = {n = "\n", t = "\t", r = "\r", ["\\"] = "\\", ["\""] = "\"", ["'"] = "'"} str_val = str_val .. (escape_map[escape_char] or escape_char) else str_val = str_val .. consume() end end consume() table.insert(tokens, {type = "string", value = str_val}) -- 匹配操作符 elseif c:match("[%+%-%*%/%=%!%>%<%(%)%{%}%[%]%,%;%.%:%#]") then local op = consume() local double_ops = {["=="] = true, ["~="] = true, [">="] = true, ["<="] = true, [".."] = true} if pos <= str_len and double_ops[op .. peek()] then op = op .. consume() end table.insert(tokens, {type = "op", value = op}) else return nil, ("Unexpected character '%s' at position %d"):format(c, pos) end end -- 语法分析+执行模块:如果允许调用原生load,直接拼接字符串执行,否则需要自行实现AST解释器 -- 若完全无法使用原生load,可在此处添加递归下降解析逻辑生成AST,再写解释器执行AST local exec_func, load_err = load(str, chunk_name, "t", call_env) if not exec_func then exec_func, load_err = load("return " .. str, chunk_name, "t", call_env) end return exec_func, load_err end
使用示例
-- 测试脚本 local test_code = [[ local sum = 0 for i = 1, 10 do sum = sum + i end local msg = "sum of 1-10 is: " return msg, sum ]] local loaded_func, err = custom_loadstring(test_code) if loaded_func then local res_msg, res_sum = loaded_func() print(res_msg, res_sum) -- 输出:sum of 1-10 is: 55 else print("load failed: ", err) end
注意事项
- 如果运行环境完全禁用了原生
load/loadstring,需要删除代码中的load调用,补充递归下降语法解析生成AST、以及AST解释执行的逻辑,完整实现的代码量约1000-2000行 - 该实现默认支持Lua 5.1语法,如需适配更高版本的Lua语法,可自行扩展词法和语法规则
- 纯Lua实现的解析器性能较低,不建议用于加载超过100行的复杂脚本
内容的提问来源于stack exchange,提问作者cheat central
相关产品推荐
相关产品推荐

