TEIMSI
Developer's platform of programming text codes

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]
```

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
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]
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:

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

```

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]
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
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
```

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