TEIMSI
Plataforma del desarrollador de textos programados
Inicio|Utilidades|Foro|Documentación

Indice de contenido -> Capítulo 20 - Creación de operadores y funciones predeterminados


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.


Creación de la función "str_overwrite".


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).



Volver arriba