Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 91 additions & 18 deletions lua/pac3/libraries/expression.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

local lib = {
PI = math.pi,
rand = math.random,
Expand Down Expand Up @@ -37,34 +36,108 @@ local lib = {
round = math.Round,
}

local blacklist = {"repeat", "until", "function", "end"}
local blacklist = {
"function";
"for"; "break";
"while"; "do";
"repeat"; "until";
}

local function compile_expression(str, extra_lib)
for _, word in pairs(blacklist) do
if str:find("[%p%s]" .. word) or str:find(word .. "[%p%s]") then
return false, string.format("illegal characters used %q", word)
-- convert blacklist items into patterns to match
for k, item in pairs(blacklist) do
-- uses more efficient / conclusive frontier pattern syntax
-- frontier matches transition into and out of sets
-- in this case, matching transition into letters and then out of letters (to match whole words and not partials)
blacklist[k] = ("%%f[%%a](%s)%%f[%%A]"):format(item)
end

local function_intro = "local IN = (...); "

local function_formats = {
function_intro .. "return %s";
function_intro .. "%s"; -- allows <code>return</code> semantics
}

local function TryCompile(code, identifier)
local result = CompileString(code, identifier, false)

return not isstring(result), result
end

local function CompileStringAdvanced(code, identifier)
local success, func = false, nil

for _, structure in pairs(function_formats) do
success, func = TryCompile(structure:format(code), identifier)

if success then break end
end

return success, func
end

local function readonlyError()
error("Not allowed to assign to globals", 2)
end

local function makeReadonly(t)
return setmetatable(
{},
{
__index = t,
__newindex = readonlyError,
__metatable = "This metatable is locked."
}
)
end

local function copyInto(t1, t2)
for k,v in pairs(t1) do t2[k] = v end
end

local function checkBlacklist(code)
local str

for _, word in pairs(blacklist) do
str = code:match(word)

if str then
return str
end
end

return nil
end

local functions = {}
local function compile_expression(str, extra_lib)
local illegalWord = checkBlacklist(str)

if illegalWord then
return false, string.format("illegal characters used %q", illegalWord)
end

for k,v in pairs(lib) do functions[k] = v end
local success, func = CompileStringAdvanced(str, "pac_expression")

if extra_lib then
for k,v in pairs(extra_lib) do functions[k] = v end
end
if success then
local functions = {}

functions.select = select
str = "local IN = select(1, ...) return " .. str
copyInto(lib, functions)

local func = CompileString(str, "pac_expression", false)
if extra_lib then
copyInto(extra_lib, functions)
end

if isstring(func) then
return false, func
else
setfenv(func, functions)
functions.select = select

setfenv(
func,
makeReadonly(functions)
)

return true, func
end

return false, func
end

return compile_expression