Módulos Lua

Los módulos de runtime extienden el entorno Lua con nueva funcionalidad. Los módulos pueden proporcionar utilidades determinísticas, operaciones I/O, o comandos async que hacen yield a sistemas externos.

La implementación del runtime Lua puede cambiar en futuras versiones.

Definición de Módulo

Cada módulo usa luaapi.ModuleDef:

var Module = &luaapi.ModuleDef{
    Name:        "mymodule",
    Description: "Mi módulo personalizado",
    Class:       []string{luaapi.ClassDeterministic},
    Types:       ModuleTypes,  // Definiciones de tipo para herramientas
    Build: func() (*lua.LTable, []luaapi.YieldType) {
        mod := lua.CreateTable(0, 2)
        mod.RawSetString("hello", lua.LGoFunc(helloFunc))
        mod.RawSetString("greet", lua.LGoFunc(greetFunc))
        mod.Immutable = true
        return mod, nil
    },
}

La función Build retorna:

  • Tabla de módulo con funciones exportadas
  • Lista de tipos de yield para operaciones async (o nil)

Las tablas de módulo se construyen una vez y se cachean para reutilización en todos los estados Lua.

Clasificación de Módulos

El campo Class determina donde puede usarse el módulo:

Clase Descripción
ClassDeterministic La misma entrada siempre produce la misma salida
ClassNondeterministic La salida varía (tiempo, random)
ClassIO Operaciones I/O externas
ClassNetwork Operaciones de red
ClassStorage Persistencia de datos
ClassWorkflow Operaciones seguras para workflow

Módulos marcados solo con ClassDeterministic son seguros para workflow. Agregar clases de I/O o network restringe el módulo a funciones y procesos.

Exponer Funciones

Las funciones tienen signature func(l *lua.LState) int donde el valor de retorno es el número de valores pusheados al stack:

func greetFunc(l *lua.LState) int {
    name := l.CheckString(1)           // Argumento requerido
    greeting := l.OptString(2, "Hello") // Opcional con default

    l.Push(lua.LString(greeting + ", " + name + "!"))
    return 1
}
Método Descripción
l.CheckString(n) String requerido en posición n
l.CheckInt(n) Entero requerido
l.CheckNumber(n) Número requerido
l.CheckTable(n) Tabla requerida
l.OptString(n, def) String opcional con default
l.OptInt(n, def) Int opcional con default

Tablas

Las tablas pasadas entre Go y Lua son mutables por defecto. Las tablas de exportación de módulo deben marcarse como inmutables:

mod := lua.CreateTable(0, 5)
mod.RawSetString("func1", lua.LGoFunc(func1))
mod.Immutable = true  // Prevenir que Lua modifique exports

Las tablas de datos permanecen mutables para uso normal:

result := l.CreateTable(0, 3)
result.RawSetString("name", lua.LString("value"))
result.RawSetString("count", lua.LNumber(42))
l.Push(result)

Sistema de Tipos

Los módulos usan dos mecanismos de tipado separados pero complementarios.

Definiciones de Tipo (Herramientas)

El campo Types proporciona signatures de tipo para soporte de IDE y documentación:

func ModuleTypes() *types.TypeManifest {
    m := types.NewManifest("mymodule")

    objectType := &types.InterfaceType{
        Name: "mymodule.Object",
        Methods: map[string]*types.FunctionType{
            "get_value": types.NewFunction(nil, []types.Type{types.String}),
            "set_value": types.NewFunction([]types.Type{types.String}, nil),
        },
    }

    m.DefineType("Object", objectType)
    m.SetExport(moduleType)
    return m
}

Constructos de tipo disponibles:

Tipo Descripción
types.String Primitivo string
types.Number Valor numérico
types.Boolean Valor booleano
types.Any Cualquier valor Lua
types.LuaError Tipo de error
types.Optional(t) Valor opcional de tipo t
types.InterfaceType Objeto con métodos
types.FunctionType Signature de función con params/returns
types.RecordType Tipo similar a struct con campos
types.TableType Tabla con tipos key/value

Signatures de función soportan parámetros variádicos:

// (string, ...any) -> (string, error?)
types.FunctionType{
    Params:   []types.Type{types.String},
    Variadic: types.Any,
    Returns:  []types.Type{types.String, types.Optional(types.LuaError)},
}

Ver el paquete types en go-lua para el sistema de tipos completo.

Bindings UserData (Runtime)

RegisterTypeMethods crea los bindings reales Go-a-Lua:

func init() {
    value.RegisterTypeMethods(nil, "mymodule.Object",
        map[string]lua.LGoFunc{
            "__tostring": objectToString,  // Metametodos
        },
        map[string]lua.LGoFunc{
            "get_value": objectGetValue,   // Métodos regulares
            "set_value": objectSetValue,
        },
    )
}

Las metatables son inmutables y cacheadas globalmente para reutilización thread-safe.

Sistema Propósito Define
Definiciones de Tipo IDE, docs, type checking Signatures
Bindings UserData Llamadas a métodos en runtime Funciones ejecutables

Operaciones Async

Para operaciones que esperan en sistemas externos, retorne un yield en lugar de un resultado. El yield es despachado a un handler Go y el proceso resume cuando el handler completa.

Definir Yields

Declare tipos de yield en la función Build del módulo:

Build: func() (*lua.LTable, []luaapi.YieldType) {
    mod := lua.CreateTable(0, 1)
    mod.RawSetString("fetch", lua.LGoFunc(fetchFunc))
    mod.Immutable = true

    yields := []luaapi.YieldType{
        {Sample: &FetchYield{}, CmdID: myapi.FetchCommand},
    }

    return mod, yields
}

Crear un Yield

Retorne -1 para señalar un yield en lugar de valores de retorno normales:

func fetchFunc(l *lua.LState) int {
    url := l.CheckString(1)

    yield := AcquireFetchYield()
    yield.URL = url

    l.Push(yield)
    return -1  // Señalar yield, no conteo de stack
}

Implementación de Yield

Los yields conectan valores Lua y comandos dispatcher:

type FetchYield struct {
    *myapi.FetchCmd
}

func (y *FetchYield) String() string              { return "<fetch_yield>" }
func (y *FetchYield) Type() lua.LValueType        { return lua.LTUserData }
func (y *FetchYield) CmdID() dispatcher.CommandID { return myapi.FetchCommand }
func (y *FetchYield) ToCommand() dispatcher.Command { return y.FetchCmd }
func (y *FetchYield) Release() { releaseFetchYield(y) }

func (y *FetchYield) HandleResult(l *lua.LState, data any, err error) []lua.LValue {
    if err != nil {
        return []lua.LValue{lua.LNil, lua.NewLuaError(l, err.Error())}
    }
    resp := data.(*myapi.FetchResponse)
    return []lua.LValue{lua.LString(resp.Body), lua.LNil}
}

El dispatcher enruta el comando a un handler. Ver Command Dispatch para implementar handlers.

Manejo de Errores

Retorne errores como segundo valor usando errores estructurados:

func myFunc(l *lua.LState) int {
    result, err := doSomething()
    if err != nil {
        lerr := lua.NewLuaError(l, err.Error()).
            WithKind(lua.Internal).
            WithRetryable(true)
        l.Push(lua.LNil)
        l.Push(lerr)
        return 2
    }

    l.Push(lua.LString(result))
    l.Push(lua.LNil)
    return 2
}

Seguridad

Verifique permisos antes de realizar operaciones sensibles:

func myFunc(l *lua.LState) int {
    ctx := l.Context()

    if !security.IsAllowed(ctx, "mymodule.action", resource, nil) {
        l.Push(lua.LNil)
        l.Push(lua.NewLuaError(l, "permission denied").WithKind(lua.PermissionDenied))
        return 2
    }

    // Proceder con operación
}

Ver También