TEIMSI
Developer's platform of programming text codes
Home|Utilities|Forum|Documentation

Table of contents -> Chapter 20 - Creating predefined operators and functions


This advanced topic explains how to create operators or default functions, it is necessary to have read the TEIMSI Variables topic.

During compilation, expressions of equations are transformed to reverse polish notation which relies on the use of a virtual stack. An example of stack is used by programs when they copy the value of a register to memory using the "push" assembler instruction which after decreasing the "esp" register it stores the stores the value in memory using the "ss" register. In other words "push eax" is equivalent (if Cpu flags changes are ignored) to the following:

		sub esp, 4
		mov [ss:esp], eax

A "pop eax" instruction is "equivalent" to the following:

		mov eax, [ss:esp]
		add esp, 4

With Coprocessor registers (st, st1, st2, .etc) it's also used a stack, an instruction "fld" or "fild" is equivalent to a "push", while a "fstp" or "fistp" is equivalent to a "pop" (there is a variation that is "fst" or "fist" which leaves intact the pointer on the stack).


A compiler program can generate the following code for the "x=sqrt(y+z)/2" expression:


	;	Instruction:			What's on the stack after the instruction?

		push  y			;    y
		push  z			;    y                    z
		add				;    (y+z)
		square_root			;    sqrt(y+z)
		push  number_2		;    sqrt(y+z)            2
		divide				;    sqrt(y+z)/2
		pop   x			;

	;	In analogy it can be expressed via Coprocessor instructions:


		fld [y]
		fld [z]
		faddp st1, st
		fsqrt
		fld [number_2]
		fdivp st1, st
		fstp [x]

This is a reverse polish notation for the given expression. Loading a variable (push) is expressed with the "fld" instruction, the addition operator "+" is revealed in the instruction "faddp st1, st", the "square_root" function or "sqrt" with the "fsqrt" instruction, the division operator "/" is the instruction "fdivp st1, st" and finally the discharge of a variable (pop) is expressed with the instruction "fstp".

A "virtual" stack can be used for complex expressions that require a large space to store large amount of temporal values on the stack.

With TEIMSI, the virtual stack consists of a space located in the data section of a program where the values of temporary variables are saved during the processing of an expression in reverse polish notation.

To create default TEIMSI operators or functions is important to know how to handle the virtual stack. An operator receives two values of the virtual stack and purge one of the stack, a predetermined function receives a number "N" of parameters in the virtual stack and purges from the stack to "N-1" elements leaving one alone (If N = 0 ; creates an element, and if N = 1 does not change the stack pointer). Also in case it has the temporary mode ("_mod_engine") and is a string or array, it must be released the space of the purged variable stored in the stack.

The following is the equivalent code of the cosine function in TEIMSI (it makes a trigonometry calculation using an integer or a double precision number):

	
	proc s_cos_p		;	d
	
		mov ebx, [tsi_pila_level]					;	It loads the pointer to the virtual stack
		lea eax, [ebx+tsi_pila-regsize]				;	It loads into "eax" the pointer to the "regvar" structure placed on the virtual stack

		cmp byte [eax+regvar.vtype],sysdbl				;	Since the variables which are not boolean or long integer or double precision number have a type > (greater than) sysdbl
		jbe @f								;	 frees the temporary variable if any.
			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					;	Returns the "false" boolean.
			ret

		@@:
		jne @f
			fld qword [eax+regvar.vofix]			;	Load into the "st" register the value of the variable in the virtual stack.
			jmp tsi_flr_j2
		@@:
			fild dword [eax+regvar.vofix]			;	Load the long integer or boolean (is not the case of being a double precision number).
			mov dword [eax+regvar.vtype], sysdbl
		tsi_flr_j2:
		fwait
	
			fcos

		fstp qword [eax+regvar.vofix]				;	Saves the result and ensures that the created variable has the temporary mode property.
		mov dword [eax+regvar.vmode], mod_engi
		fwait

	ret
	endp	;	s_cos_p

Note: The real source code is in the file "engine\base_afn.asm", in which the "local_dblload" and "local_dblend" macros simplify the writing by performing tasks at the beginning and end.


The following is the equivalent code but simplified of the "lcase" TEIMSI function, (converts a string to lowercase):


	proc s_lcase_p			;		astr		;	(receives a string type parameter).

		mov ebx, [tsi_pila_level]					;	It loads the pointer to the virtual stack
		lea eax, [ebx+tsi_pila-regsize]				;	It loads into "eax" the pointer to the "regvar" structure placed on the virtual stack
	
		cmp byte [eax+regvar.vtype], sysstr			;	Leaves intact the variable if it is not a string.
		jnz tsi_lcj2

		cmp byte [eax+regvar.vmode], mod_engi			;	If it's not a temporary variable the output will, then a temporary string variable is created.
		jz @f
			mov ecx, [eax+regvar.vnull]
			push eax
				call2 NwMemory, ecx				;	NwMemory always returns a handle (long integer) of the allocated memory space.
				NWPOS_eax					;	NWPOS_eax  it loads the handler from "eax" and puts into "esi" the pointer to the string.
				mov edi, esi
				mov edx, eax
			pop  eax
			mov ecx, [eax+regvar.vnull]
	
			NWPOS_regarea eax					;	The pointer to the string is determined with this macro instruction that accepts the pointer to the "regvar" structure.
	
				mov ebx, ecx					;	Copy the contents of the string to the new space and fills the values of the "regvar" structure.
				and ebx, 3					;	Note: the TEIMSI applications must always have disabled (as default) the CPU flag "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]					;	The ASCII code for "A" is 65, and for "a" is 97
					cmp al, 65
					jb tsi_lcj1
					cmp al, 90				;	The ASCII code for "Z" is 90, and for "z" is 122
					ja tsi_lcj1
						add al, 32  			 ; 	97 - 65,  converts the letter from uppercase to lowercase.
						mov [esi], al
					tsi_lcj1:
				inc esi
				dec ecx
				jnz @b
	
		tsi_lcj2:
	ret
	endp ; s_lcase_p

To proceed to create a default function, for example one with the "superhash" name that receives only one parameter, it should be performed the following steps:

1- Create the "s_superhash_p" function similar to "s_lcase_p" in structure, but it doesn't precisely converts its characters to lowercase.

2- Write the macro "s_superhash" in the following way:

			MACRO s_superhash			;		astr
				call s_superhash_p
			ENDM ; s_superhash

3- Place the macro in the proper position according to its type, in this case will be in the "cont_fnstrings.asm" file under last macro (the default is "s_loadszstring").

4- Edit the "protodb.dat" file on the "engine\internal\" folder, by inserting the following line (eg, after "s_loadszstring"):

			proto v,  ,superhash,v

Note: the number of "v" letters at the right of the "superhash" string separated by comma is the number of parameters the function receives. The letter "d" is preferred for double precision numbers, the letter "i" for long integers or booleans and the letter "v" to other data types.


To create a default operator, the steps are similar. The code of many predefined operators is within the file "cont_aritmetic.asm".

For example the code for the macro of the radio operator (":"), which is the square root of the sum of the squares of two numbers is the following:


		MACRO s_rad
			s_op_loading_edx		;	Load the numerical values from the virtual stack and adjusts the pointer, puts into the "st" and "st1" FPU registers the values
							;	 of both parameters. Also "ecx" will indicate the number of double precision (non-integers) operands and "dl" register will indicate:
							;
							;		if dl = 0, none of both has other type than a number.
							;		if dl & 1 results 1, the first is not a number.
							;		if dl & 2 results 1, the second is not a number.

			fld st				;	makes 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		;	It frees temporary variables if any, and stores the result in the virtual stack. If "ecx" is zero the macro tries to save
							;	 the result as integer (it wil not store it as integer if it's out of range for being big).

			;	By not being "ecx" equal to zero, the macro saves the result as a double precision number.

		ENDM	;	s_rad


In the file "protodb.dat", in some row there is the statement:

		sysproto d, : ,s_rad

It can be highly recommended to use the macro code of functions or operators already written, suffice to determine a specimen that requires the same number of parameters and the same kind of data in them. For example if you want to create a function that accepts a string as the first parameter and a long integer as the second and returns a string, you can copy the code of the "str_repeatn" function; it must be renamed and modified the part that alters or creates the final content of the resulting string.


Creation of the "str_overwrite" function.


This example shows how the "str_overwrite" function was created, this is a function that overwrites part of a string, receives parameters:

- A string variable with dynamic mode. That is, a variable passed by reference.

- A string type variable.

- A long integer.

Firstly lets search a function as "mold" that receives the same amount of parameters with same data types, then we see the "strpos" function as candidate. It's achieved by copying the procedure (see the "s_strpos_p" procedure within the file "cont_fnstrings.asm").

The following is the abstract idea of what the "str_overwrite" function does.



	//		
	//		
	//			function str_overwrite [dynamic_mainstr, substr, n_point]
	//		
	//		
	//			if(type(mainstr) != DYNAMIC){ return false; }
	//			
	//			if(mainstr,substr= STRING  &&  n_point=INTEGER){
	//			
	//			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)			//	This is a sum of signed integers, calculates the end in "mainstr" where the copy of bytes is finished in case of being done.
	//		
	//			if(posf <0){ return true; }			//	If you finish before the start, do nothing.
	//		
	//			if(n_point<0){				//	If the start is less than zero, reduce the amount to copy, increase in beginning of the copy on the "substr" n_point = 0
	//				len_copy = len_copy + n_point
	//				start_subscopy = - n_point
	//				n_point=0
	//			}
	//			if( (len_copy<=0)  || (start_subscopy >= len2) ){	//	If after adjustment, a negative number of bytes is copied, or the beginning on the copy of "substr" exceeds its size, exit
	//				return true;
	//			}
	//		
	//			if( n_point + len_copy > len1 ){				//	If the pointer after copying, is copied more than the size of "mainstr", adjust the amount to copy before doing so.
	//				len_copy = len1- n_point
	//			}
	//		
	//				edi = lp mainstr + n_point
	//				esi = lp substr + start_subscopy
	//				ecx = len_copy
	//			
	//				COPY(esi > edi, ecx bytes)
	//		


After making the relevant and necessary modifications for error-free operation, the resulting procedure code (after a relatively short time) with comments is the following:


	; //#######################################################################################################
	
		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	;	end of locals
		
			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= STRING  &&  n_point=INTEGER){
				jnz tsi_strwiv_jerr
			
				cmp byte [edx+regvar.vtype], sysstr
				jz @f
					tsi_strwiv_jerr:
	
						mov ecx, [tsi_tmpecx]	;	"tsi_free_3_vars" free variables with temporay mode.
						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) != dynamic){ return false; }		;In other words, the first parameter must be passed by reference.
				jnz tsi_strwiv_jerr
	

				cmp byte [ecx+regvar.vtype], sysdbl	;	Load in the "ebx" the parameter offset (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


					;//	Now, if it ends before the start, do nothing.

					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
	
					;//	Now, if the start is less than zero, reduce the amount to be copied, increase the beginning of the copy on "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
					@@:
	
					;//	If after adjustment, a negative number of bytes is copied, or the beginning on the copy of "substr" exceeds its size, exit

					mov [tsi_ret_bool], 1		;		if( (len_copy<=0)  || (start_subscopy >= len2) ){	return true	}
					test [tsi_lencopy], -1
					js tsi_strwiv_jerr

					;//	If the pointer after copying, is copied more than the size of "mainstr", adjust the amount to copy before doing so.

					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
	

; //########################################################################################################

The "s_str_overwrite" macro-instruction will be easy to write:


			MACRO s_str_overwrite			;		astr
				call s_str_overwrite_p
			ENDM ; s_str_overwrite

With the procedure and the macro instruction present inside the file "cont_fnstrings.asm", need to put the declaration of the existence for such function to the TEIMSI language compiler; therefore it must be edited the "protodb.dat" file and you put the following line statement (preferably under the strings functions declarations):

		proto v,  ,str_overwrite,v,v,i


For more information see the help reference str_overwrite


When predefined functions and operators are created; can be helpful to avoid errors to activate the testing of the ending position of the virtual stack pointer for executables files, that is done (it may require administrator privileges) as explained next:

- Edit the "base_exe.asm" file found at the sub-folder "engine\internal" within the TEIMSI installation folder (it can be reached by using the shortcut link file called "Teimsi.lnk" placed in the "Teimsi_Projects" folder on the user's Documents folder).

- locate within the file the following lines:



; ##############################

	;	mov ebx, [tsi_pila_level]		;	This shows an error message that tells that the virtual stack had some variable when the TEIMSI application was finished.
	;	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
	;	@@:

; ##############################

and uncomment instructions:

; ##############################
	
		mov ebx, [tsi_pila_level]		;	This shows an error message that tells that the virtual stack had some variable when the TEIMSI application was finished.
		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
		@@:

; ##############################

Then save the file for when executable files are created they check the end position of the virtual stack pointer.

Note: the "sys.quit()" instruction (see Flow instructions) should not be used from within a TEIMSI function (because if the function receives parameters; it makes the virtual stack pointer to not finish in zero).



Go to top