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