Evaluacion Dinamica
Ejecutar código dinamicamente en tiempo de ejecución con entornos aislados y acceso controlado a modulos.
Dos Sistemas
Wippy proporciona dos sistemas de evaluacion:
| Sistema | Proposito | Caso de Uso |
|---|---|---|
expr |
Evaluacion de expresiones | Config, plantillas, calculos simples |
eval_runner |
Ejecución completa de Lua | Plugins, scripts de usuario, código dinamico |
Módulo expr
Evaluacion de expresiones ligera usando sintaxis expr-lang.
local expr = require("expr")
local result, err = expr.eval("x + y * 2", {x = 10, y = 5})
-- result = 20
Compilar Expresiones
Compilar una vez, ejecutar muchas veces:
local program, err = expr.compile("price * quantity")
local total1 = program:run({price = 10, quantity = 5})
local total2 = program:run({price = 20, quantity = 3})
Sintaxis Soportada
-- Aritmetica
expr.eval("1 + 2 * 3") -- 7
expr.eval("10 / 2 - 1") -- 4
expr.eval("10 % 3") -- 1
-- Comparacion
expr.eval("x > 5", {x = 10}) -- true
expr.eval("x == y", {x = 1, y = 1}) -- true
-- Booleano
expr.eval("a && b", {a = true, b = false}) -- false
expr.eval("a || b", {a = true, b = false}) -- true
expr.eval("!a", {a = false}) -- true
-- Ternario
expr.eval("x > 0 ? 'positive' : 'negative'", {x = 5})
-- Funciones
expr.eval("max(1, 5, 3)") -- 5
expr.eval("min(1, 5, 3)") -- 1
expr.eval("len([1, 2, 3])") -- 3
-- Arrays
expr.eval("[1, 2, 3][0]") -- 1
-- Concatenacion de strings
expr.eval("'hello' + ' ' + 'world'")
Módulo eval_runner
Ejecución completa de Lua con controles de seguridad.
local runner = require("eval_runner")
local result, err = runner.run({
source = [[
local function double(x)
return x * 2
end
return { double = double }
]],
method = "double",
args = {21}
})
-- result = 42
Configuración
| Parámetro | Tipo | Descripción |
|---|---|---|
source |
string | Código fuente Lua (requerido) |
method |
string | Función a llamar en tabla devuelta |
args |
any[] | Argumentos pasados a la función |
modules |
string[] | Modulos integrados permitidos |
imports |
table | Entradas de registro a importar |
context |
table | Valores disponibles como ctx |
allow_classes |
string[] | Clases de módulo adicionales |
custom_modules |
table | Tablas personalizadas como modulos |
limits |
table | Limites de ejecucion para esta corrida |
Limite de Pasos
limits.max_steps acota cuanto puede ejecutarse un runner.run:
local result, err = runner.run({
source = user_source,
method = "main",
limits = {max_steps = 500}
})
Un paso es un turno del scheduler de eval: el programa avanza hasta que cede o termina, y cada resume consume un paso. El computo puro entre cesiones cuenta como un solo paso sin importar cuanto dure, asi que el limite acota turnos de planificacion, no tiempo de CPU.
Cuando la cuenta excede el limite, la corrida se detiene y devuelve errors.INTERNAL con eval exceeded maximum step limit.
max_steps = 0 significa sin limite. Omitir limits hereda el valor por defecto del host:
# .wippy.yaml
lua:
eval:
max_steps: 10000 # presupuesto por defecto para corridas sin limits.max_steps
# 0 = sin limite; un valor negativo hace fallar el arranque
limits se aplica solo a runner.run; runner.compile no acepta limites. limits debe ser una tabla que contenga unicamente max_steps, y max_steps debe ser un entero no negativo — cualquier otra cosa devuelve errors.INVALID antes de que el programa se ejecute.
Acceso a Modulos
Lista blanca de modulos permitidos:
runner.run({
source = [[
local json = require("json")
return json.encode({hello = "world"})
]],
modules = {"json"}
})
Los modulos no en la lista no pueden ser requeridos.
Importaciones de Registro
Importar entradas del registro:
runner.run({
source = [[
local data = ...
local utils = require("utils")
return utils.format(data)
]],
imports = {
utils = "app.lib:utilities"
},
args = {{key = "value"}}
})
Importaciones Privilegiadas
A una importación se le pueden conceder módulos que el propio código evaluado no puede ver. Usa la forma de tabla con id y modules:
runner.run({
source = [[
local pricing = require("pricing")
return pricing.quote(...)
]],
modules = {"json"},
imports = {
pricing = { id = "app.lib:pricing", modules = {"funcs"} }
},
})
La biblioteca pricing se ejecuta en su propio entorno con alcance donde funcs está disponible; el código fuente evaluado no puede requerir ni alcanzar funcs directamente. Conceder un módulo a una importación requiere que el llamador tenga el permiso eval.module para ese módulo — las capacidades no pueden delegarse más allá de lo que el propio llamador tiene permitido.
Modulos Personalizados
Inyectar tablas personalizadas:
runner.run({
source = [[
return sdk.version
]],
custom_modules = {
sdk = {version = "1.0.0", api_key = "xxx"}
}
})
Valores de Contexto
Pasar datos accesibles como ctx:
runner.run({
source = [[
return "Hello, " .. ctx.get("user")
]],
context = {user = "Alice"}
})
Compilar Programas
runner.compile valida el código fuente e informa su punto de entrada y módulos sin ejecutarlo:
local program, err = runner.compile([[
local function process(x)
return x * 2
end
return { process = process }
]], "process", {modules = {"json"}})
program:method() -- "process" (string)
program:modules() -- {"json"} (string[])
La tabla de opciones acepta los mismos campos modules e imports que runner.run, y se aplican las mismas comprobaciones de permisos eval.module y eval.import. El programa compilado es informativo; ejecuta llamando a runner.run con el código fuente y el método.
Modelo de Seguridad
Clases de Modulos
Los modulos se categorizan por capacidad:
| Clase | Descripción | Predeterminado |
|---|---|---|
deterministic |
Funciones puras | Permitido |
encoding |
Codificacion de datos | Permitido |
time |
Operaciones de tiempo | Permitido |
nondeterministic |
Aleatorio, etc. | Permitido |
process |
Spawn, registro | Bloqueado |
storage |
Archivo, base de datos | Bloqueado |
network |
HTTP, sockets | Bloqueado |
Habilitar Clases Bloqueadas
runner.run({
source = [[
local http = require("http_client")
return http.get("https://api.example.com")
]],
modules = {"http_client"},
allow_classes = {"network"}
})
Verificaciones de Permisos
El sistema verifica permisos para:
eval.compile- Antes de compilacioneval.run- Antes de ejecucióneval.module- Para cada módulo en lista blanca, y para cada módulo concedido a una importación privilegiadaeval.import- Para cada importacion de registroeval.class- Para cada clase permitida
Configurar en politicas de seguridad.
Caché de Compilación
Los programas compilados se almacenan en una caché LRU con clave por fuente, método, módulos y clases permitidas — las ejecuciones repetidas de código idéntico omiten la recompilación. Las importaciones y el contexto se vinculan en tiempo de ejecución y no afectan la clave de la caché.
# .wippy.yaml
lua:
eval:
cache_size: 256 # entradas; 0 o menos desactiva la caché (por defecto: 256)
cache_ttl: 0 # expiración; 0 = sin expiración (por defecto: 0)
Manejo de Errores
local result, err = runner.run({...})
if err then
if err:kind() == errors.PERMISSION_DENIED then
-- Acceso denegado por politica de seguridad
elseif err:kind() == errors.INVALID then
-- Fuente o configuración invalida
elseif err:kind() == errors.INTERNAL then
-- Error de ejecución o compilacion
end
end
Casos de Uso
Sistema de Plugins
local plugins = registry.find({meta = {type = "plugin"}})
for _, plugin in ipairs(plugins) do
local source = plugin:data().source
runner.run({
source = source,
method = "init",
modules = {"json", "time"},
context = {config = app_config}
})
end
Evaluacion de Plantillas
local template = "Hello, {{name}}! You have {{count}} messages."
local compiled = expr.compile("name")
-- Evaluacion rapida repetida
for _, user in ipairs(users) do
local greeting = compiled:run({name = user.name})
end
Scripts de Usuario
local user_code = request:body()
local result, err = runner.run({
source = user_code,
modules = {"json", "text"}, -- Solo modulos seguros
context = {data = input_data}
})
Vea También
- Expression - Referencia del lenguaje de expresiones
- Exec - Ejecución de comandos del sistema
- Security - Politicas de seguridad