Anuncios

Bienvenidos sean a este post, Lua nos ofrece una funcion de alto nivel para cargar modulos llamada require la cual trata de mantener a un minimo sus suposiciones sobre lo que un modulo es porque para require un modulo es como cualquier chunk del codigo que define algunos valores (asi como las funciones o las tablas contienen funciones), para cargar un modulo simplemente llamamos:

require "nombreModulo"

Habitualmente esta llamada devuelve una tabla conteniendo todas las funciones del modulo y tambien define una variable global conteniendo esta tabla, sin embargo estas acciones estan hechas por el modulo no por require, asi que algunos modulos podrian elegir entre retornar otro valor o tener otros efectos colaterales, una buena practica en programacion es siempre solicitar todos los modulos que se necesita incluso los que ya sabes que deberian estar cargados, se podria excluir las librerias estandard de esta regla porque ellas estan precargadas en Lua, sin embargo algunas personas prefieren usar un require explicito inclusive para ellos:

local m = require "io"
m.write("Hola Mundo\n")

Veamos el siguiente codigo:

function require (nombre)
	if not package.loaded[nombre] then
		local cargador = findloader(nombre)
		if cargador == nil then
			error("No se pudo cargar el modulo " .. nombre)
		end
		package.loaded[nombre] = true
		local res = cargador(nombre)
		if res ~= nil then
			package.loaded[nombre] = res
		end
	end
	return package.loaded[nombre]
end

Este codigo detalla la conducta de require, su primer paso es chequear en la tabla package.loaded, si este existe en la tabla la funcion require devuelve su valor correspondiente por lo tanto una vez que un modulo es cargado y otras llamadas a require con el mismo nombre simplemente devuelve el mismo valor sin cargar el modulo de nuevo, si el modulo no esta cargado todavia, require intenta encontrar un cargador para este modulo, este paso esta mostrado en el codigo con la funcion abstracta findloader, su primer intento es buscar el nombre de la libreria informada en la tabla package.preload, si encuentra una funcion ahi la usa como el cargador del modulo, esta tabla preload provee un metodo generico para manejar situaciones no convencionales como por ejemplo: librerias de C linkeadas estaticamente a Lua, esta tabla no tiene una entrada para el modulo asi que require buscara primero por un archivo de Lua y luego por una libreria de C para cargar desde ahi el modulo.

Anuncios

Si require encuentra un archivo de Lua para el modulo informado lo carga con loadfile de lo contrario si encuentra una libreria de C lo carga con loadlib, recuerden que ambos: loadfile y loadlib, solo cargan algo de codigo sin correrlo, para correr el codigo require lo llama con un simple argumento, el nombre del modulo.

Si el cargador devuelve un valor, require devuelve ese valor y lo almacena en la tabla package.loaded para devolver al mismo en futuras llamadas para la misma libreria, si el cargador no devuelve ningun valor require devuelve cualquier valor que este en la tabla package.loaded, mas adelante hablaremos sobre como un modulo puede poner el valor ha ser retornado por require directamente en package.loaded.

Anuncios

Un importante detalle de ese codigo previo es que antes de llamar al cargador require marca al modulo como ya cargado, asignando true a su respectivo campo en package.loaded por lo tanto si el modulo requiere de otro modulo y que a su vez requiere recursivamente el módulo original, esta ultima llamada a require regresa inmediatamente evitando un bucle infinito, para forzar a require a cargar la misma libreria dos veces solo debemos eliminar la entrada de la libreria de package.loaded, por ejemplo despues de una ejecucion exitosa de require “foo”, package.loaded[“foo”] sera distinto de nil, veamos como es con el siguiente caso:

package.loaded["foo"] = nil
require "foo"

Cuando buscamos por un archivo require usa un path que es un poco diferente de los paths tipicos, el path usado por la mayoria de los programas es un listado de los directorios en donde buscar el archivo informado, sin embargo ANSI C (la plataforma abstracta donde Lua corre) no tiene el concepto de directorios, por lo tanto el path usado por require es una lista de patrones donde cada uno de ellos especifica una via alternativa para transformar un nombre de modulo (el argumento de require) en un nombre de archivo, mas especificamente cada componente en el path es un nombre de archivo conteniendo signos de interrogacion opcionales, por cada componente require reemplaza el nombre del modulo por cada “?” y chequea si hay un archivo con el nombre resultante si no es asi pasa al siguiente componente, los componentes son separados por puntos y comas (;), este es un caracter libremente usado por todos los sistemas operativos, veamos el siguiente ejemplo:

Anuncios
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua

Por ejemplo la llamada a require “sql” intentara abrir los siguientes archivos:

sql
sql.lua
c:\windows\sql
/usr/local/lua/sql/sql.lua

La funcion require asume solo que el punto y coma (;), como el componente separador, y el signo de interrogacion, todo lo demas como el separador de directorios o la extension de archivos es definido por el path en si mismo, el path que require usa para buscar por archivos de Lua siempre el valor actual de la variable package.path, cuando Lua comienza inicializa esta variable con el valor de la variable entorno LUA_PATH o con una ruta predeterminada definida por compilación, si esta variable de entorno no esta definida cuando usamos al LUA_PATH Lua substituye el path predeterminado por esta subcadena “;;”, por ejemplo si seteas a LUA_PATH como “midir/?.lua;;”, el path final sera el componente “midir/?.lua” seguido del path predeterminado.

Anuncios

Si Lua no puede encontrar un archivo de Lua compatible con el nombre del modulo busca por una libreria de C, para esta busqueda obtiene el path de la variable package.cpath (en lugar de package.path) y esta variable consigue su valor inicial de la variable de entorno LUA_CPATH, en lugar de LUA_PATH, un tipico valor para esta variable en sistemas Unix es algo como sigue:

./?.so;/usr/local/lib/lua/5.1/?.so

El tipo de extension es definido por el path, en este caso al ser Unix utiliza .so para todas la plantillas, si fuera de tipo Windows seria de la siguiente forma:

.\?.dll;c:\Programa Files\Lua501\dll\?.dll

Una vez que encuentra la libreria de C require lo carga con package.loadlib de lo cual hablamos en este post, a diferencia de los chunks de Lua las librerias de C no definen una funcion main simple, en su lugar pueden exportar varias funciones de C, y si estas estan bien conducidas deberian exportar una funcion llamada luaopen_modname, esta es la que require intenta llamar despues de linkear la libreria, los modulos se usan usualmente con sus nombres originales pero algunas veces debemos renombrar un modulo para evitar conflicto de nombres, una situacion tipica es la necesidad de cargar diferentes versiones del mismo modulo para testeo, un modulo de Lua o no tiene su nombre internamente fijo o tambien podemos editarlo facilmente para cambiar su nombre pero no podemos editar un modulo binario para corregir el nombre de su funcion luaopen_*, para permitir tales renombramientos require utiliza un pequeño truco: si el nombre del modulo contiene un guion require elimina del nombre su prefijo hasta el guion cuando crea el nombre de la funcion luaopen_*.

Anuncios

Por ejemplo, si un modulo es nombre a-b require espera su funcion de apertura para ser nombrada luaopen_b en lugar de luaopen_a-b, aunque en esta caso tampoco seria un nombre de C valido de todas formas, asi que si necesitamos usar dos modulos llamados mod, podemos renombrar una de ellas a v1-mod cuando la llamemos sera de esta manera:

m1 = require "v1-mod"

La funcion require encontrara tanto el archivo renombrado v1-mod como, dentro de este archivo, la función con el nombre original luaopen_mod.

Anuncios

En resumen, hoy hemos visto a la funcion require, como trabaja, para que se usa, como usarlo con modulos de Lua, con librerias de C, como trabaja internamente y como trabaja para distintos noombres de una misma libreria, espero les haya sido util sigueme en Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00