El presente tema avanzado explíca como crear operadores o funciones predeterminadas, es necesario haber leído el tema Variables de TEIMSI.
Durante la compilación, expresiones de ecuaciones son transformadas a notación polaca inversa la cual se apoya en el uso de una pila virtual. Un ejemplo de pila es la que utilizan los programas cuando copian el valor de un registro a la memoria utilizando la instrucción en ensamblador "push" que tras hacer decrecer el registro "esp" guarda el valor en memoria usando el registro "ss". En otras palabras "push eax" es equivalente (si no modificara los bits del registro de banderas del Cpu) a lo siguiente:
sub esp, 4 mov [ss:esp], eax
Una instrucción "pop eax" es "equivalente" a lo siguiente:
mov eax, [ss:esp] add esp, 4
Con los registros del coprocesador (st, st1, st2, .Etc) también se utiliza una pila, una instrucción "fld" o "fild" equivaldría a una "push", mientras que una "fstp" o "fistp" equivaldría a una "pop", (existe una variación que es "fst" o "fist" la cual deja intacto el puntero en la pila).
Un programa compilador puede generar el siguiente código para la expresión "x=sqrt(y+z)/2":
; Instrucción: Lo que hay en la pila luego de la instrucción: push y ; y push z ; y z sumar ; (y+z) raiz_cuadrada ; sqrt(y+z) push number_2 ; sqrt(y+z) 2 dividir ; sqrt(y+z)/2 pop x ; ; En analogía se puede expresar por medio de instrucciones del coprocesador: fld [y] fld [z] faddp st1, st fsqrt fld [number_2] fdivp st1, st fstp [x]
Esa es una notación polaca inversa para la expresión dada. La carga de una variable (push) se expresa con la instrucción "fld", el operador suma "+" se pone de manifiesto en la instrucción "faddp st1, st", la función "raiz_cuadrada" o "sqrt" con la instrucción "fsqrt", el operador división "/" es la instrucción "fdivp st1, st" y por último la descarga de una variable (pop) se expresa con la instrucción "fstp".
Una pila "virtual" puede usarse para expresiones complejas que requieran gran espacio para guardar gran cantidad de valores temporales en la pila.
En TEIMSI, la pila virtual consiste en un espacio en la sección de datos de un programa donde se guardan los valores de variables temporales durante el procesamiento de una expresión en notación polaca inversa.
Para crear operadores o funciones en predeterminadas de TEIMSI es importante saber como manejar la pila virtual. Un operador recibe dos valores de la pila virtual y purga uno de la pila, una función predeterminada recibe una cantidad "N" de parámetros en la pila virtual y purga de la pila "N-1" elementos dejando uno solo (Si N=0; crea un elemento, y si N=1 no cambia el puntero de la pila). Además en caso de que tenga la modalidad temporal ("_mod_engine") y sea una cadena o matriz, hay que liberar el espacio de la variable purgada de la pila.
El siguiente es el código equivalente de la función coseno en TEIMSI, (realiza un cálculo de trigonometría sobre un número entero o de precisión doble):
proc s_cos_p ; d mov ebx, [tsi_pila_level] ; Carga el puntero de la pila virtual lea eax, [ebx+tsi_pila-regsize] ; Carga en "eax" el puntero a la estructura "regvar" situada en la pila virtual cmp byte [eax+regvar.vtype],sysdbl ; Dado que la variables, que no son booleano ni entero largo ni número de precisión doble tienen tipo > (mayor que) sysdbl jbe @f ; libera la variable temporal en caso de haber una. cmp dword [eax+regvar.vmode], mod_engi jnz tsi_flr_j1 push eax mov esi, eax call sy_freeengi_atesi_nop pop eax tsi_flr_j1: SAVE_BOOL_AT eax, 0 ; Devuelve el booleano "false". ret @@: jne @f fld qword [eax+regvar.vofix] ; Carga en el registro "st" el valor de la variable en la pila virtual. jmp tsi_flr_j2 @@: fild dword [eax+regvar.vofix] ; Carga el entero largo o booleano (es el caso de que no sea un número de precisión doble). mov dword [eax+regvar.vtype], sysdbl tsi_flr_j2: fwait fcos fstp qword [eax+regvar.vofix] ; Guarda el resultado y asegura que la variable creada sea de modalidad temporal. mov dword [eax+regvar.vmode], mod_engi fwait ret endp ; s_cos_p
Nota: El código fuente real está en el archivo "engine\base_afn.asm", en el cual las macros "local_dblload" y "local_dblend" simplifican la escritura realizando las tareas al inicio y al final.
El siguiente es el código equivalente pero simplificado de la función "lcase" en TEIMSI, (pasa a minúscula la letras de una cadena):
proc s_lcase_p ; astr ; (recibe un parámetro tipo cadena). mov ebx, [tsi_pila_level] ; Carga el puntero de la pila virtual lea eax, [ebx+tsi_pila-regsize] ; Carga en "eax" el puntero a la estructura "regvar" situada en la pila virtual cmp byte [eax+regvar.vtype], sysstr ; Deja intacta la variable si no es una cadena. jnz tsi_lcj2 cmp byte [eax+regvar.vmode], mod_engi ; Si no es una variable temporal, la salida si lo será por lo cual es creada una variable temporal tipo cadena. jz @f mov ecx, [eax+regvar.vnull] push eax call2 NwMemory, ecx ; NwMemory devuelve siempre un manejador (entero largo) del espacio en memoria asignado. NWPOS_eax ; NWPOS_eax carga el manejador de "eax" y pone en "esi" el puntero a la cadena. mov edi, esi mov edx, eax pop eax mov ecx, [eax+regvar.vnull] NWPOS_regarea eax ; El puntero a la cadena es determinado con ésta macro instrucción que acepta el puntero a la estructura "regvar". mov ebx, ecx ; Copia el contenido de la cadena al nuevo espacio y llena los valores de la estructura "regvar". and ebx, 3 ; Nota: las aplicaciones TEIMSI deben tener siempre desactivada (en forma predeterminada) la bandera del Cpu "direction". shr ecx, 2 rep movsd mov ecx, ebx rep movsb mov dword [eax+regvar.vmode], mod_engi mov [eax+regvar.vofix], edx @@: NWPOS_regarea eax mov ecx, [eax+regvar.vnull] or ecx, ecx jz tsi_lcj2 @@: mov al, [esi] ; El código ASCII de "A" es 65, y el de "a" es 97 cmp al, 65 jb tsi_lcj1 cmp al, 90 ; El código ASCII de "Z" es 90, y el de "z" es 122 ja tsi_lcj1 add al, 32 ; 97 - 65, pasa la letra de mayúscula a minúscula. mov [esi], al tsi_lcj1: inc esi dec ecx jnz @b tsi_lcj2: ret endp ; s_lcase_p
Para proceder a crear una función predeterminada, por ejemplo una de nombre "superhash" que recibe un sólo parámetro se deberían realizar los siguientes pasos:
1- Crear la función de nombre "s_superhash_p" similar a "s_lcase_p" en estructura pero que no precisamente pasa sus caracteres a minúscula.
2- Escribir la macro "s_superhash" de la siguiente forma:
MACRO s_superhash ; astr call s_superhash_p ENDM ; s_superhash
3- Situar la macro en la posición adecuada según su tipo, en este caso será en el archivo "cont_fnstrings.asm" bajo última macro (la predeterminada es "s_loadszstring").
4- Editar el archivo "protodb.dat" en la carpeta "engine\internal\" insertando la siguiente línea (por ejemplo, luego de "s_loadszstring"):
proto v, ,superhash,v
Nota: la cantidad de letras ("v") a la derecha de la cadena "superhash" separadas por coma es la cantidad de parámetros que recibe la función. La letra "d" se prefiere para números de precisión doble, la letra "i" para enteros largos o booleanos y la letra "v" para los otros tipos de datos.
Para crear un operador predeterminado, los pasos son similares. El código de varios operadores predefinidos está en el archivo "cont_aritmetic.asm".
Por ejemplo el código para la macro del operador ":" radio, que halla la raíz cuadrada de la suma de los cuadrados de dos números es el siguiente:
MACRO s_rad s_op_loading_edx ; Carga los valores numéricos de la pila virtual y ajusta el su puntero, pone en los registros del FPU "st" y "st1" los valores ; de ambos parámetros. Además "ecx" indicará la cantidad de operandos de precisión doble (no enteros) y el registro "dl" ; indicará : ; ; si dl = 0, ninguno de los dos es de distinto tipo que un número. ; si dl & 1 es uno, el primero no es número ; si dl & 2 es uno, el segundo no es número fld st ; realiza st = sqrt(st*st+st1*st1) fmulp st1, st fxch st1 fld st fmulp st1, st faddp st1, st fsqrt inc ecx s_op_saving_edx ; Libera variables temporales si hay alguna y guarda el resultado en la pila virtual. Si "ecx" es cero la macro intenta guardar ; el resultado como entero (no lo guardará como entero si está fuera de rango por ser grande). ; Al no ser "ecx" igual a cero, la macro guarda el resultado como número de precisión doble. ENDM ; s_rad
En el archivo "protodb.dat", en algún renglón se encuentra la declaración:
sysproto d, : ,s_rad
Puede ser muy recomendable utilizar el código de macros de funciones u operadores ya escritos, bastaría con determinar un ejemplar que requiera la misma cantidad de parámetros y el mismo tipo de datos en los mismos. Por ejemplo si se quiere crear una función que acepte una cadena como primer parámetro y un entero largo como el segundo y que devuelva una cadena, puede copiarse el código de la función "str_repeatn"; cambiarle el nombre y modificar la parte que altera o crea el contenido final de la cadena resultante.
Este ejemplo muestra como se creó la función "str_overwrite", se trata de una función que sobrescribe parte de una cadena, recibe de parámetros:
- Una variable tipo cadena en modo dinámico. Es decir una variable pasada por referencia.
- Una variable tipo cadena.
- Un entero largo.
En primer instancia se buscó una función como "molde" que reciba la misma cantidad de parámetros y tipo de datos, la función "strpos" es ideal. Se logra empezando por copiar el procedimiento (ver procedimiento "s_strpos_p" en archivo "cont_fnstrings.asm").
La siguiente es la idea abstracta de lo que hace la función "str_overwrite".
// // // function str_overwrite [dinamic_mainstr, substr, n_point] // // // if(type(mainstr) != dinamica){ return false; } // // if(mainstr,substr= cadena && n_point=entero){ // // if(len(substr) == 0){ return true; } // // len1=len(mainstr) // len2=len(substr) // // len_copy=len2 // start_subscopy=0 // // if(len1 <= n_point){ return false; } // // posf= (len2 + n_point) // Esta es una suma de enteros con signo, calcula el final donde terminará el copiado de bytes en "mainstr" en caso se hacerse. // // if(posf <0){ return true; } // Si termina antes del inicio, no hacer nada. // // if(n_point<0){ // Si el inicio es menor que cero, reducir la cantidad a copiar, aumentar el inicio de copia en la "substr", n_point = 0 // len_copy = len_copy + n_point // start_subscopy = - n_point // n_point=0 // } // if( (len_copy<=0) || (start_subscopy >= len2) ){ // Si luego del ajuste, se copia un número negativo de bytes, o el inicio en la copia de "substr" supera su tamaño, salir // return true; // } // // if( n_point + len_copy > len1 ){ // Si el puntero luego de copiar, se copió más de lo que tiene "mainstr", ajustar la cantidad a copiar antes de hacerlo. // len_copy = len1- n_point // } // // edi = lp mainstr + n_point // esi = lp substr + start_subscopy // ecx = len_copy // // copiar(esi > edi, ecx bytes) //
Luego de hacer las modificaciones necesarias y las pertinentes para un funcionamiento sin errores, el código del procedimiento (luego de tiempo relativamente corto) con comentarios resultante es el siguiente:
; //####################################################################################################### proc s_str_overwrite_p ; astr,asubstr,aintpos locals tsi_n_point dd ? tsi_ret_bool dd ? tsi_len1 dd ? tsi_len2 dd ? tsi_lencopy dd ? tsi_start_subcp dd ? tsi_tmpecx dd ? endl ; fin de locales mov ebx, [tsi_pila_level] lea eax, [ebx+tsi_pila-regsize] lea edx, [ebx+tsi_pila-regsize*2] lea ecx, [ebx+tsi_pila-regsize*3] sub ebx, regsize*2 mov [tsi_pila_level], ebx mov [tsi_tmpecx], ecx mov [tsi_ret_bool], 0 cmp byte [eax+regvar.vtype], sysstr ; if(mainstr,substr= cadena && n_point=entero){ jnz tsi_strwiv_jerr cmp byte [edx+regvar.vtype], sysstr jz @f tsi_strwiv_jerr: mov ecx, [tsi_tmpecx] ; "tsi_free_3_vars" libera variables en modo temporal. call tsi_free_3_vars mov eax, [tsi_ret_bool] mov dword [ecx+regvar.vofix], eax mov dword [ecx+regvar.vtype], sysbool mov dword [ecx+regvar.vmode], mod_engi ret @@: cmp byte [eax+regvar.vmode], mod_dina ; if(type(mainstr) != dinamica){ return false; } ;En otras palabras el primer parámetro debe ser pasado por referencia. jnz tsi_strwiv_jerr cmp byte [ecx+regvar.vtype], sysdbl ; Cargar en el registro "ebx" el parámetro desplazamiento (n_point) ja tsi_strwiv_jerr jnz @f fld qword [ecx+regvar.vofix] fistp dword [tsi_tempint] fwait mov ebx, [tsi_tempint] jmp tsi_strwiv_gv @@: mov ebx, [ecx+regvar.vofix] tsi_strwiv_gv: ; //############################## test dword [eax+regvar.vnull], -1 js tsi_strwiv_jerr test dword [edx+regvar.vnull], -1 js tsi_strwiv_jerr mov [tsi_ret_bool], 1 ; if(len(substr) == 0){ return true; } cmp dword [edx+regvar.vnull], 0 jz tsi_strwiv_jerr mov [tsi_ret_bool], 0 mov [tsi_n_point], ebx mov ecx, [eax+regvar.vnull] ; len1=len(mainstr) mov [tsi_len1], ecx mov ecx, [edx+regvar.vnull] ; len2=len(substr) mov [tsi_len2], ecx mov [tsi_lencopy], ecx ; len_copy=len2 mov [tsi_start_subcp], 0 ; start_subscopy=0 cmp [tsi_len1], ebx jle tsi_strwiv_jerr ;// Ahora, si termina antes del inicio, no hacer nada. mov [tsi_ret_bool], 1 ; if((len2 + n_point) <0){ return true; } mov esi, [tsi_len2] add esi, ebx js tsi_strwiv_jerr mov [tsi_ret_bool], 0 ;// Ahora, si el inicio es menor que cero, reducir la cantidad a copiar, aumentar el inicio de copia en la "substr", n_point = 0 test ebx, ebx ; if(n_point<0){ jns @f ; len_copy = len_copy + n_point add [tsi_lencopy], ebx ; start_subscopy = - n_point mov ecx, ebx ; n_point=0 neg ecx ; } mov [tsi_start_subcp], ecx sub ebx, ebx mov [tsi_n_point], ebx @@: ;// Si luego del ajuste, se copia un número negativo de bytes, o el inicio en la copia de "substr" supera su tamaño, salir mov [tsi_ret_bool], 1 ; if( (len_copy<=0) || (start_subscopy >= len2) ){ return true } test [tsi_lencopy], -1 js tsi_strwiv_jerr ;// Si el puntero luego de copiar, se copió más de lo que tiene "mainstr", ajustar la cantidad a copiar antes de hacerlo. mov ecx, ebx ; if( n_point + len_copy > len1 ){ add ecx, [tsi_lencopy] ; len_copy = len1- n_point cmp ecx, [tsi_len1] ; } jbe @f mov ecx, [tsi_len1] sub ecx, ebx mov [tsi_lencopy], ecx @@: NWPOS_regarea edx add esi, [tsi_start_subcp] ; edi = lp mainstr + n_point push eax ; esi = lp substr + start_subscopy mov eax, [eax+regvar.vofix] ; ecx = len_copy NWPOS_eaxedi add edi, ebx pop eax mov ecx, [tsi_lencopy] mov ebx, ecx and ebx, 3 shr ecx, 2 rep movsd mov ecx, ebx rep movsb jmp tsi_strwiv_jerr ret endp ; s_str_overwrite_p
; //########################################################################################################
La macro-instrucción "s_str_overwrite" es fácil de escribir:
MACRO s_str_overwrite ; astr call s_str_overwrite_p ENDM ; s_str_overwrite
Con el procedimiento y la macro instrucción presentes en el archivo "cont_fnstrings.asm", falta poner la declaración de la existencia de dicha función para el compilador del lenguaje TEIMSI; por ello se edita el archivo "protodb.dat" y se le pone la siguiente línea de declaración (bajo las declaraciones de funciones para cadenas preferiblemente):
proto v, ,str_overwrite,v,v,i
Por más información ver la ayuda de referencia sobre str_overwrite
Cuando se crean funciones y operadores predeterminados puede ser de ayuda para evitar errores activar el testeo de la posición final del puntero de la pila virtual en archivos ejecutables, eso se hace (pueden ser requeridos privilegios de administrador) de la siguiente forma:
- Editar el archivo "base_exe.asm" situado en la sub-carpeta "engine\internal" dentro de la carpeta de la instalación de TEIMSI (se puede acceder a ella mediante el acceso directo llamado "Teimsi.lnk" situado en la carpeta "Teimsi_Projects" en el directorio de documentos del usuario).
- localizar dentro del archivo las siguientes líneas:
; ##############################
; mov ebx, [tsi_pila_level] ; Muestra un mensaje de error que dice que la pila virtual quedó con alguna variable al terminar.
; lea esi, [ebx+tsi_pila]
; cmp [tsi_orig_lp], esi
; jz @f
; mov esi, tsi_error_offset
; invoke MessageBox, [tsi_hWndParent_dlg], esi, (tsi_theApplication), MB_ICONEXCLAMATION
; @@:
; ##############################
y descomentar instrucciones:
; ##############################
mov ebx, [tsi_pila_level] ; Muestra un mensaje de error que dice que la pila virtual quedó con alguna variable al terminar.
lea esi, [ebx+tsi_pila]
cmp [tsi_orig_lp], esi
jz @f
mov esi, tsi_error_offset
invoke MessageBox, [tsi_hWndParent_dlg], esi, (tsi_theApplication), MB_ICONEXCLAMATION
@@:
; ##############################
Luego guardar el archivo para que al crear ejecutables éstos hagan el chequeo del final del puntero de la pila virtual.
Nota: no debería usarse la instrucción sys.quit() (ver Instrucciones de flujo.) desde una función TEIMSI (ya que si la función recibe parámetros causa que el puntero de la pila virtual no termine en cero).