Anuncios Google

Crea tu propio intérprete de Lua

Tutoriales Avanzados Homebrewmex

Si bien hay varios intérpretes de Lua para el PSP, y la scene ya no se mueve tanto como antaño, siempre es interesante saber cómo se hacen las cosas. En esta ocasión les daré una introducción al mundo de Lua mostrándoles cómo se programan los intérpretes. No sé, quizá alguien nos sorprenda y se anime a crear uno.



En este tutorial se asume que el lector tiene conocimientos notables sobre programación en C para PSP, por lo que no es apto para principiantes. Mostraré los conceptos más básicos que se requieren para obtener un intérprete funcional, pero el agregar librerías externas, usar sus funciones o crear rutinas básicas queda enteramente en sus manos y experiencia como programadores.



Durante el desarrollo del tutorial usaremos funciones propias del entorno de Lua, y aunque algunas las explicaremos es conveniente revisar esta página (allí están las funciones check que se mencionan en el tutorial) y esta otra (funciones push) para referencias y más funciones útiles.
También es buena idea familiarizarse con la documentación de PSPSDK (viene incluida en el instalador, pero también la pueden consultar online).

Preparación

Necesitaremos instalar unas cuantas cosillas para poder llevar a buen puerto nuestro proyecto. Cualquier instalación de PSPSDK debería funcionar, pero se recomienda ampliamente usar Minimalistic PSPSDK en su versión 0.10.0 (aquí encuentran los instaladores). La instalación no tiene nada de especial, Next a todo.

Una vez instalado, descargamos este paquete (Sólo usuarios registrados) que incluye un ejemplo muy sencillo para iniciar (con muchos comentarios para saber que hace cada cosa), un MAKEFILE y un ejecutable para compilar fácilmente.
Copiamos los archivos que contiene a alguna carpeta, procurando que la ruta no contenga caracteres especiales ni espacios.
Para probar que todo está en su lugar, ejecutamos el archivo compilador.bat. No debe aparecer ningún error y debe generarse el archivo EBOOT.PBP en la carpeta de nuestro proyecto (si no es así, revisamos que todo esté bien instalado).
Nótese que compilador.bat guarda toda la información relativa a la compilación (errores, debug y demás) en un archivo de texto llamado log.txt, mismo que podremos consultar con toda comodidad para comprobar que no hemos cometido errores.

Conceptos básicos

El archivo main.c se encarga de registrar las funciones que creamos para poderlas usar desde Lua, así como de abrir y ejecutar nuestro script. No es necesario que todo el código esté en ese archivo, podemos usar otros para mantener organizado nuestro proyecto, pero en tal caso debemos pasarle al main.c al menos la función que inicializa y registra el resto de nuestras funciones. En el tutorial usaremos archivos separados.

En Lua existe un concepto muy básico: el stack. Básicamente, el stack es como un contenedor donde se alojan tanto los argumentos de las funciones que llamamos desde Lua como el contenido que mandamos a Lua desde C.
Cada vez que se intercambian datos entre Lua y C, el stack cambia. Muchas de las funciones que usaremos leen o modifican el stack, y para acceder al contenido que nos interesa deberemos indicarle un índice o posición donde se ubica dicho contenido, como si se tratara de un array.

En una forma más simple, el stack se parece a una recta numérica centrada en el 0. A la derecha se ubican los argumentos de las funciones llamadas desde Lua, mientras que a la izquierda encontramos el contenido que C envía de regreso a Lua.
Resulta evidente, pues, que para acceder a los argumentos que Lua envía, usamos índices positivos: 1 para el primer argumento, 2 para el segundo y así sucesivamente (ojo, que esto es a nivel local. Cada que llamamos a una función, su primer argumento siempre está en el índice 1, ya que los índices no se acumulan entre llamadas, lo cual resulta muy práctico).

Siempre que mandamos valores de regreso a Lua se colocan al tope del stack, pero del lado izquierdo. Así, el último valor que hayamos mandado estará en la posición -1, el penúltimo en -2 y así sucesivamente. Esto es de especial importancia cuando se manejan tablas (como lo veremos más adelante) y también para mostrar errores en el script (Lua automáticamente manda al tope del stack cualquier error que tenga el script, es por eso que en el main.c para mostrar el error se usa el índice -1 y la función lua_tostring(), para convertir en string el error que Lua colocó allí).

Este concepto del stack es lo más complicado de comprender cuando se programa un intérprete, pero es de vital importancia entenderlo al menos en esencia. Por ello, trataré de ahondar un poco más con ejemplos conforme vayamos avanzando en el tutorial.

Registro de funciones

Para poder registrar funciones desde otros archivos que no sean el main.c, requerimos añadir las librerías de Lua al inicio de ellos. Podemos hacerlo como se ve en el propio main.c, pero por motivos de organización vamos a crear una librería auxiliar para añadir esas librerías:

#ifndef AUXLIB_H
#define AUXLIB_H
 
    #include "lua.h"
    #include "lualib.h"
    #include "lauxlib.h"
 
#endif

Guardamos como auxlib.h. Ahora, podremos añadir las librerías de Lua muy fácil, con sólo escribir

#include "auxlib.h"

Iniciaremos registrando una sencilla función para imprimir un mensaje de texto. Para tal efecto creamos un nuevo archivo y procedemos de esta forma:

  1. Todas nuestras funciones tendrán esta estructura básica:
    static int nombre_funcion(){ //el nombre no importa
     
      return 0;
    }
  2. Agregamos el código necesario usando funciones de PSPSDK:
    #include "auxlib.h"
    #include <pspdebug.h>
     
    static int test_printf(){
        pspDebugScreenPrintf("Hello world :D"); 
      return 0;
    }

    Nótese que he agregado al inicio una librería de PSPSDK, así como nuestra librería auxiliar, necesarias para que compile el código de la función.
  3. Ahora, me interesa que esta función esté en un módulo que se llame test (para poderla llamar desde Lua con test.printf), así que usaré este código:
    static const luaL_reg test_functions[] = { //En este array pondré todas las funciones que quiera registrar
      {"printf",test_printf},    //declaro que la función de C test_printf se llamará printf en Lua
      {0,0} //Esto SIEMPRE debe estar al final del array, de lo contrario no funciona el registro
    };
     
    void testInit(lua_State *L){    //aquí tampoco importa el nombre, esta función será la que inicialize el módulo
        //Con la siguiente línea todas las funciones que estén en el array que declaramos antes quedarán registradas en el contenedor "test"
        luaL_register(L,"test",test_functions);
    }

    También podemos registrar la función directamente sin módulo (en este caso la llamaría sencillamente con printf desde Lua), así:
    void testInit(lua_State *L){    //aquí tampoco importa el nombre, esta función será la que inicialize el módulo
        //Con la siguiente línea la función de C test_printf quedará registrada en Lua como printf, sin contenedor
        lua_register(L, "printf", test_printf);
    }

    Este será el único ejemplo que mostraré sobre el registro de funciones sueltas, ya que no es nada útil cuando se trata de muchas funciones.
  4. Guardamos el archivo como test.c en la misma carpeta del main.c (se puede guardar en otro lugar, pero iniciaremos así). 
  5. Para que nuestro main.c pueda hacer el registro de la nueva función, vamos a crear un nuevo archivo y declaramos el prototipo de la función que declaramos antes para inicializar el módulo:
    #ifndef TEST_H
    #define TEST_H
     
        void testInit(lua_State *L);
     
    #endif

    Guardamos el archivo como test.h.
  6. Añadimos este último archivo al principio de nuestro main.c:
    //Aquí incluimos los archivos.h de nuestros módulos
     
    #include "test.h"

    Colocamos la llamada a la función que inicializa nuestro módulo (también en el main.c):
    // En esta parte vamos a inicializar nuestros módulos :)
     
        testInit(L);
     
    //

    Le indicamos a nuestro MAKEFILE que debe compilar también los archivos que acabamos de crear, añadiendo el nombre (o nombres) y la extensión .o al inicio en OBJS:
    ##  Aqui especificamos los archivos que deben compilarse y agregarse
    OBJS =     main.o test.o

    Y guardamos todos los cambios que hicimos.
  7. Compilamos ejecutando compilador.bat. Descontando que no hemos cometido errores, deberíamos obtener nuestro EBOOT.PBP. No nos queda de otra más que probarlo, así que hacemos un sencillo script:
    test.printf()
        while true do    --De momento esto detendrá el script, para ver si funciona o no el código xD
            v=1
        end

    Y al ejecutarlo en el PSP:

Pan comido, verdad? xD

Obtener y retornar valores

La función que registramos anteriormente no es de mucha utilidad que digamos, así que le agregaremos la posibilidad de cambiarle el texto que imprime (algo por demás lógico).

  1. Empezamos con el código donde lo dejamos y le agregamos un argumento a la función (que seguramente notaron cuando hicimos el registro de la función):
    static int test_printf(lua_State *L){  //Aqui está el argumento que hay que agregar
        pspDebugScreenPrintf("Hello world :D");
      return 0;
    }

    Ese argumento será nuestro pan de cada día, ya que nos permitirá recibir valores desde Lua con las funciones check (y también mandarlos a Lua con las funciones push, como veremos un poco más adelante).
  2. Usamos la función luaL_checkstring() para recibir strings desde Lua.
    static int test_printf(lua_State *L){
        pspDebugScreenPrintf(luaL_checkstring(L,1));
      return 0;
    }

    Su primer argumento es la dichosa L que le añadimos a la función antes, y su segundo argumento indica la posición de donde tomará el texto (recuerden lo que hablamos del stack al inicio: 1 para el primer argumento, 2 para el segundo y así).
  3. Aprovechando que tenemos el archivo abierto, vamos a añadir una función más, esta vez una que sume 2 números y retorne el resultado. Empezamos con la estructura básica:
    static int test_suma(lua_State *L){
     
      return 1;
    }

    Nótese que ahora el return lleva el número 1, y eso es porque mi función va retornar un valor (el número después del return indica cuántos valores vamos a devolver, si fueran 2 pues usamos 2. Si no retorna ningún valor, usamos 0 y así).
  4. Podemos obtener los números para sumar desde Lua con luaL_checknumber() o luaL_checkint() (la primera obtiene el número como float, la segunda como entero ignorando los decimales). Usaré ambas en el ejemplo:
    static int test_suma(lua_State *L){
        float res = luaL_checkint(L,1) + luaL_checknumber(L,2);
      return 1;
    }

    Al igual que luaL_checkstring() ambas funciones llevan 2 argumentos. El primero siempre L. Vemos ahora que para obtener el primer número el segundo argumento es un 1, y para obtener el segundo argumento usamos 2 (tiene sentido, no?). Se entiende pues que ese segundo argumento es la posición en la que se suministran los valores cuando llamamos a la función desde Lua.
  5. En este punto el resultado ya está calculado, pero no está en Lua todavía, así que procederemos a mandarlo. Como el resultado es un float, retornaré el valor como float también usando lua_pushnumber():
    static int test_suma(lua_State *L){
        float res = luaL_checkint(L,1) + luaL_checknumber(L,2);        //aquí calculo la suma
            lua_pushnumber(L,res);    //aquí envío el resultado
        return 1;    //retorno 1 valor
    }

    Su primer argumento es nuestra inseparable L, y el segundo argumento es el valor que se retorna. Aquí no hay que decirle en que lugar se debe enviar, si 1, 2 o así. El orden en que los valores se mandan es el orden en que se usan las funciones push en el código.
  6. Lista nuestra nueva (y mega útil xD) función, la añadimos al array de registro:
    static const luaL_reg test_functions[] = {
      {"printf",test_printf},    //declaro que la función de C test_printf se llamará printf en Lua
      {"suma",test_suma},    //Mi suma :D
      {0,0} //Este SIEMPRE debe estar aqui
    };

    El resto del código queda tal cual está (que práctico, no?).
  7. Recompilamos, y probamos con una modificación al script:
    test.printf("Ohh... Ahora puedo imprimir lo que quiera :D\n") --"\n" es un salto de línea, para que lo siguiente se imprima justo debajo
    test.printf("25+4.5="..test.suma(25,4.5))
        while true do    --De momento este detendrá el script, para ver si funciona o no el código xD
            v=1
        end

 

Fácil no? check para recibir, push para enviar.

Tablas, constantes y otras funciones útiles

Ahora nos adentraremos a algo más complicado: devolver tablas. Sin importar cuántos elementos contenga, Lua trata la tabla como un solo valor de retorno. Para devolver una tabla debemos:

  1. Crear la tabla que vamos a devolver con lua_newtable()
  2. Llenamos la tabla, repitiendo lo siguiente tantas veces como sea necesario:
    • Para índices numéricos
      1. Mandamos el índice (1,2,3,4...) con lua_pushnumber()
      2. Mandamos el contenido (número, string, otra tabla...)
      3. Guardamos en la tabla la pareja de índice-contenido, usando lua_settable()
    • Para índices no numéricos
      1. Mandamos el contenido (número, string, otra tabla...)
      2. Guardamos el contenido en la tabla, asignándole un índice con lua_setfield()

Para índices no numéricos también se puede proceder como si fueran numéricos, con la diferencia de que los índices no serán números sino strings. Personalmente prefiero el segundo método, se desgasta menos el teclado xD.
Aquí ya podríamos terminar la función con return 1 y sería suficiente para devolver la tabla llena, o podríamos seguir mandando más contenido además de la tabla, dependiendo de nuestra función.

Veamos algunos ejemplos rápidos:

/*
Resultado esperado
tabla={111,"dos"}
*/
static int newtabla(lua_State *L){
  lua_newtable(L);		//Creamos una tabla
 
	lua_pushnumber(L,1);	//Mandamos el índice
	lua_pushnumber(L,111);	//Mandamos el elemento a guardar
  lua_settable(L,-3);	//Guardamos en la tabla la pareja
 
	lua_pushnumber(L,2);	//Mandamos el siguiente índice
	lua_pushstring(L,"dos");	//Mandamos el siguiente elemento a guardar
  lua_settable(L,-3);	//Guardamos
 
  return 1;
}
 
/*
Resultado esperado
tabla={var=1,var2="dos"}
 
Se muestran 2 métodos, el 1ro igual al anterior, el segundo con 1 línea menos
usar el más entendible...
*/
static int newtabla(lua_State *L){
  lua_newtable(L);		//Creamos una tabla
 
	lua_pushstring(L,"var");	//Mandamos el índice
	lua_pushnumber(L,1);	//Mandamos el elemento a guardar
  lua_settable(L,-3);	//Guardamos en la tabla la pareja
 
	lua_pushstring(L,"dos");	//Mandamos el siguiente elemento a guardar
  lua_setfield(L,-2,"var2");	//Guardamos, dando nombre al índice directamente
 
  return 1;
}
 
/*
Resultado esperado
tabla={
  {1,var="dos"},
  tab2={"tres",var=4}
}
 
Tablas dentro de tablas es más largo, pero igual de sencillo. El procedimiento básico es:
-crear la tabla "general"
-mandar el índice (obligatorio en caso de ser numérico, caso contrario se puede omitir)
-crear la 2da tabla, de igual forma que la general
-llenar la tabla según se vió arriba
-asignar la tabla ya llena a la general.
-repetir si es necesario
*/
static int newtabla(lua_State *L){
  lua_newtable(L);		//Creamos la tabla general
 
  lua_pushnumber(L,1);	//Quiero la 1ra tabla con índice numérico, así que lo mando primero
	lua_newtable(L)		//Y luego creo la tabla
 
	//vamos a llenarla antes de añadirla a la general...
		lua_pushnumber(L,1);	//índice
		lua_pushnumber(L,1);	//elemento a guardar
			lua_settable(L,-3);	//Guardamos en la tabla la pareja
 
		lua_pushstring(L,"dos");	//siguiente elemento...
	   lua_setfield(L,-2,"var");		//Lo guardo, y le asigno su índice
 
  lua_settable(L,-3);	//Guardo la 1ra tabla, ya llena
 
  lua_newtable(L);  //La siguiente es con índice no numérico, así que la creo directamente
	//La lleno...
		lua_pushnumber(L,1);	//índice
		lua_pushstring(L,"tres");	//elemento a guardar
			lua_settable(L,-3);	//Guardamos en la tabla la pareja
 
		lua_pushnumber(L,4);	//elemento
		lua_setfield(L,-2,"var");	//Lo guardo y asigno índice
  lua_setfield(L,-2,"tab2")	//Y guardo la 2da tabla ya llena
 
  return 1;
}

Mucho código yo sé, pero practiquen, es sencillo un vez que se le pilla el truco (aunque no por eso deja de ser laborioso, las funciones crecen mucho cuando se usan tablas).

Las funciones check levantan errores automáticamente cuando no pueden obtener el valor indicado por una u otra razón. Por ejemplo, si queremos obtener un string y en el lugar que indicamos hay una tabla, saltaría este error:

bla, bla, bla: string expected, got table.

Es un mensaje que la propia librería de Lua maneja, pero nada nos detiene de revisar el tipo de dato que contiene un argumento, usando las funciones lua_isXXXX (que puede ser number, table, nil, etc. Revisen los links). Esto nos podría servir para que nuestra función haga diferentes cosas dependiendo de los argumentos que se le entregan.
Aprovechando que ya sabemos devolver tablas, modificaré la función que creamos para sumar para que devuelva una tabla con los números que le entregué y la suma total. Además, el código verificará si los argumentos son números antes de sumarlos, y en caso de que no lo sean los tomará como 0. Nada más sencillo:

static int test_suma(lua_State *L){
    int n1=0; //Declaro algunas variables...
    float n2=0;   
 
        if(lua_isnumber(L,1))    //Reviso si el primer argumento es un número
            n1 = luaL_checkint(L,1);    //Lo obtengo
 
        if(lua_isnumber(L,2))    //Ahora reviso si el segundo argumento también es un número
            n2 = luaL_checkint(L,2); //y de ser así lo obtengo
 
        //Ya tenemos lo necesario, toca armar la tabla:
        lua_newtable(L);    //La creamos primero
            //Y la llenamos:
                lua_pushnumber(L,1);    //índice
                    lua_pushnumber(L,n1);    //Valor
                lua_settable(L,-3);    //Guardo
 
                lua_pushnumber(L,2);    //índice
                    lua_pushnumber(L,n2);    //Valor
                lua_settable(L,-3);    //Guardo
 
                lua_pushnumber(L,3);    //índice
                    lua_pushnumber(L,n1+n2);    //Valor
                lua_settable(L,-3);    //Guardo
    return 1;    //retorno 1 valor
}

Y comprobamos que funciona con otro script:

test.printf("Ahora imprimire 25 + 4.5 con una tabla xD\n")
    res=test.suma(25,4.5)
test.printf("N1="..res[1].."\nN2="..res[2].."\nResultado="..res[3])
 
test.printf("\n\nAhora tratare de sumar 25 + false...\n")
    res=test.suma(25,false)
test.printf("N1="..res[1].."\nN2="..res[2].."\nResultado="..res[3])
    while true do    --De momento este detendrá el script, para ver si funciona o no el código xD
        v=1
    end

false no es number, por lo que se toma como 0 y el resultado es 25.

Otra función muy útil es lua_gettop(). Sólo lleva L como argumento, y nos regresa el número de argumentos que le fueron entregados a la función cuando fue llamada en Lua. Parece algo trivial, pero resulta sumamente útil para lograr funciones con número variable de argumentos. Voy a retomar el printf que hicimos para ejemplificarlo:

static int test_printf(lua_State *L){
    int i; //Para el for
    for (i=1; i<=lua_gettop(L); i++) //Obtengo uno por uno los argumentos
        pspDebugScreenPrintf("%s\n", lua_tostring(L,i));    //Los convierto a string, sean lo que sean, y los imprimo (añadiendo tambien un salto de linea)
 
  return 0;
}

No hay mucho misterio allí... se imprimirán todos los argumentos que se proporcionen, incluso si no son imprimibles (para eso el lua_tostring()):
test.printf("Arg1","Arg2",550+5,false,nil,noExisto) --entrego unos cuantos argumentos, algunos no existen a ver que pasa
    while true do    --De momento este detendrá el script, para ver si funciona o no el código xD
        v=1
    end

Para terminar esta sección, veremos cómo se registran las constantes. Realmente es algo muy sencillo, muy parecido a como se trabaja con las tablas.
Básicamente:

  • Si es una constante suelta
    1. Se manda el valor de la constante como ya sabemos.
    2. Se le asigna un nombre usando lua_setglobal().
  • Si está dentro de algún contenedor (una tabla, o un módulo que ya existe)
    1. Se coloca el contenedor en el tope del stack con lua_getglobal().
    2. Se manda el valor de la constante.
    3. Se le asigna un nombre con lua_setfield().

Veamos un par de ejemplos:

// Para crear constantes sueltas, es decir, que no están en ningún contenedor
 
lua_pushstring(L,"Rober Galarga"); //Mandamos el valor de la constante (un número, un boolean, una tabla... no importa)
lua_setglobal(L,"_NICK");
/*
    script de prueba:
        test.printf(_NICK) --uso la constante directamente
*/
 
//Ahora, para crear constantes dentro de algún contenedor
//Digamos que la misma constante de antes la quiero dentro del modulo test...
 
lua_getglobal(L,"test");    //Mando el contenedor "test" al tope del stack
lua_pushstring(L,"Rober Galarga");   //mando el valor
lua_setfield(L,-2,"nick");    //Y le asigno su nombre
/*
    script de prueba:
        test.printf("Mi nick es: "..test.nick) --imprimo la constante
*/

En caso de querer constante dentro de un contenedor, éste debe existir antes de poder crear la constante. Por esta razón, y por motivos de organización, suelo registrar las constantes al final de la función que inicializa el módulo donde se requiere la constante, así sin importar si es una constante suelta o dentro del contenedor, funcionará sin problemas.

Registro avanzado de funciones

Hasta ahora todo es color de rosa, las funciones no usan más que datos nativos de Lua. Pero, que pasa si queremos trabajar con tipos de datos que no existen en Lua? Cómo se hace?
Pues, increíblemente de manera similar a lo que ya vimos, con un par de modificaciones. Para ejemplificarlo, crearé un nuevo tipo de dato para almacenar colores y cambiarle el color al texto de las impresiones.

  1. Primeramente, abrimos nuestra librería auxiliar auxlib.h y agregamos algo de código que nos ayudará a definir nuevos tipos de datos. Este código está por allí varias veces en internet con algunas modificaciones. Sinceramente, no tengo mucha idea de cómo funciona en su totalidad, así que lo aceptaremos tal cual es (xD), y guardamos el archivo:
    // Para crear nuevos datos
    	#define newUserData(NAME, VTYPE) \
    			VTYPE *check_##NAME (lua_State *L, int index){ \
    				VTYPE* NAME  = (VTYPE*)lua_touserdata(L, index); \
    				if (NAME == NULL) luaL_typerror(L, index, #NAME); \
    				return NAME; \
    			} \
    			VTYPE* push_##NAME(lua_State *L){ \
    			  VTYPE * newvalue = (VTYPE*)lua_newuserdata(L, sizeof(VTYPE)); \
    			  luaL_getmetatable(L, #NAME); \
    			  lua_setmetatable(L, -2); \
    			  return newvalue; \
    			}
     
    	#define userDataRegister(NAME, FCNS, META) \
    		int NAME##_register(lua_State *L) { \
    		luaL_openlib(L, #NAME, FCNS, 0); \
    		luaL_newmetatable(L, #NAME); \
    		luaL_openlib(L, 0, META, 0); \
    		lua_pushstring(L, "__index"); \
    		lua_pushvalue(L, -3); \
    		lua_rawset(L, -3); \
    		lua_pushstring(L, "__metatable"); \
    		lua_pushvalue(L, -3); \
    		lua_rawset(L, -3); \
    		lua_pop(L, 1); \
    		 return 1; \
    	}
  2. Creamos un nuevo archivo que contendrá las nuevas funciones que necesitamos y usamos esta función para indicarle a Lua que tipo de dato es el dato (valga la redundancia) que vamos a crear (los colores son enteros de 32 bits):
    #include "auxlib.h"  //esta que no falte
    #include <pspdebug.h>
    newUserData(color,u32); //Declaro el nuevo tipo de dato "color" como u32

    Por qué un nuevo archivo? Bueno, porque cuando creamos un nuevo tipo de dato, las funciones que se registren en ese módulo compartirán el contenedor. En el ejemplo he llamado al dato color, por lo que las funciones que registre allí serán color.funcion1, color.funcion2... es más conveniente así.
  3. La función newUserData también nos creará 2 funciones especiales para trabajar con el dato. Como lo he llamado color, estas funciones se llamarán check_color() y push_color(). Se usan un poco diferente de las otras funciones check y push que manejamos, lo veremos justo ahora. Definiré entonces 2 nuevas funciones, una para crear colores y otra para cambiar el color del texto:
    static int color_new(lua_State *L){ //Para crear colores
        int r,g,b; //Componentes del color red, green, blue (rojo, verde, azul)
            r=luaL_checkint(L,1);    //Obtengo cada componente como entero...
                g=luaL_checkint(L,2);
            b=luaL_checkint(L,3);
        u32 RGB = (r*65536)+(g*256)+b; //Y "armo" el color 
     
        *push_color(L)=RGB;    //Así es como se manda el nuevo tipo de dato que creamos
      return 1;  //retorno 1 valor
    }
     
    static int color_settxtcolor(lua_State *L){  //Con esta recibo un color ya creado y se lo aplico al texto
        pspDebugScreenSetTextColor(*check_color(L,1));    //Los datos los obtenemos casi igual, sólo que ahora son punteros
        return 0;
    }

    Espero que todo quede claro con los comentarios.
  4. Ahora, para registrar el módulo con su tipo de dato y las funciones, se requiere un código algo diferente al que vimos antes:
    static const luaL_reg color_functions[] = {    //Igual un array para registro
        {"new",color_new},    //mis funciones
        {"settxtcolor",color_settxtcolor},
        {0,0}
    };
     
    static const luaL_reg meta[] = {    //Esto es nuevo! un array de metadatos. No tengo mucha idea de cómo funciona, pero se requiere xD
      {0,0}
    };
     
    userDataRegister(color, color_functions, meta);    //Registramos las funciones en el contenedor "color"
     
    void colorInit(lua_State *L){    //Usaremos esta función para inicializar este módulo
      color_register(L);    //Esta función se genera automáticamente cuando usamos newUserData()
    }

    La diferencia radica en que ahora requerimos un array de metadatos, y algunas funciones se crean automáticamente. De igual forma que lo hicimos antes, creamos un archivo llamado color.h con el prototipo de la función para inicializar el nuevo módulo, y modificamos tanto el main.c como el MAKEFILE.
  5. Ahora ya podemos declarar colores como un nuevo tipo de dato, aunque de momento sólo sirven para cambiar el color del texto:
    test.printf("Yo soy una linea blanca")
        verde = color.new(0,255,0)    --probemos un color verde
            color.settxtcolor(verde)
    test.printf("Y yo soy verde!")
        while true do    --De momento este detendrá el script, para ver si funciona o no el código xD
            v=1
        end

Errores y más errores

Poco que decir aquí, el compilador nos avisa del error que impide que nuestro código compile. En ocasiones resulta muy fácil repararlo, otras veces no tanto. Si la solución no es evidente, la manera de solucionarlo es buscar el error en internet, muchas veces veremos que ya está solucionado o encontraremos no la solución a nuestro problema específico pero sí a uno muy parecido, lo que nos dará pistas de cómo solucionar el nuestro.

Y para terminar (al fin!), les diré que un intérprete de Lua es un gran reto, que consume muchísimo tiempo y que puede ser ridículamente sencillo o ridículamente difícil hacer que alguna función haga lo que se supone debe hacer.

Pues bien, ya tenemos el conocimiento necesario para iniciar nuestro propio intérprete. Texto bonito, imágenes, sonidos, funciones útiles... todo eso son derivaciones de lo que aprendimos aquí, es cuestión de integrar código o librerías, aplicar conocimiento y practicar mucho. No dejen de revisar los samples que vienen con PSPSDK, seguro que les serán de mucha utilidad.

Aquí podemos descargar (Sólo usuarios registrados) los archivos con todas las modificaciones que hicimos en el tutorial, para que les sirvan de referencia.

4.5
Tu voto: Ninguno Votos totales: 4.5 (12 votos)

Anuncios Google

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de trom_us

winshell

...si, me lo voy a leer para ver que podemos hacerle al winshell.

Osea

osea que con lua es posible hacer juegos para la psp?

Imagen de DevDavisNunez

Claro,

Si, Lua es para crear aplicaciones de manera sencilla y versatil! :D

Imagen de klozz

Hasta que veo algo

Hasta que veo algo interezante 

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.