next up previous contents index
Next: 5 Commands Up: $FILE Previous: 3 Initialization file   Contents   Index

Subsections


4 Programming with the interpreter


4.1 Introduction

There are two ways to program with the command interpreter. The first is to write a sequence of commands in a command file in the command directory (cf. 3.4). For instance, if the name of this file is name_of_file, the command

< name_of_file

will launch the reading of the file and the execution of all the commands in it.

The second way is to write programs (i.e. sequences of commands or calls to programs), load them and execute them as commands.

Both command files and programs accept parameters and can call programs or read command files.

In a prior version of the command interpreter the possibility to load and execute programs was not implemented. Programs are easier to use and run faster than command files. Moreover everything done with command files can also be done with programs. Nevertheless the possibility to use command files has been kept in this version of the command interpreter.

It is possible to have several programs running simultaneously (i.e. threads). This is explained in chapter 9.


4.2 Parameters

Command files and programs accept arguments. Inside a command file or a program, the first argument is represented by the expression #1, the second by #2, etc. It is also possible to use parentheses, to avoid ambiguities. For instance, #(11) will mean the parameter 11 and #(1)1 the parameter 1 followed by 1. The maximal number of arguments that a command file or program can accept is fixed in the initialization file (cf. 3.1).


Example : Suppose that the command file x.cmd contains the following lines

Xcom1 #1 xx#2yy
Xcom2
Xcom3 #2
Xcom3 x#(1)2y

Then the command

<x.cmd A B

will produce the execution of

Xcom1 A xxByy
Xcom2
Xcom3 B
Xcom3 xA2y

(provided that the commands Xcom1, Xcom2, Xcom3 mean something).

It is possible to put a numerical expression inside #(). For instance #(x+y) will mean the parameter whose number is the biggest integer smaller than or equal to x+y (see 4.3.2, 7).

The substitution of parameters is a particular case of the way to make substitutions in the command interpreter (cf. 4.5).


In a command line, in expressions of type {1, {2, ..., a substitution of # to { is made. This is a way to send parameters containing the character # .



4.3 Numeric evaluation of expressions

4.3.1 The expression evaluator

The command interpreter contains an expression evaluator, written by Mark Morley. With it it is possible to make directly computations or to parse arguments of commands (see 7 for more details). With this expression evaluator it is possible to define variables and to use numerical functions. If the command interpreter receives a command that it not in its list of known commands, this command is sent to the expression evaluator, evaluated as a numerical expression, and the result is printed to screen. The evaluation gives 0 if the expression is incorrect (it is an unknown variable for instance).


Example :

- interpcom -> a=1
                 1.000000
- interpcom -> b=-2
                 -2.000000
- interpcom -> newvar=a+b
                 -1.000000

(here

 - interpcom ->
is the prompt of the interpreter). It is possible also to insert spaces :

- interpcom -> newvar = a + b

This will give the same result, except of course if you have a command called newvar. So in general it is better to put no spaces inside a numerical expression.


4.3.2 Substitution of numerical expressions in programs

It is possible to substitute the value of a numerical expression to this expression in strings of characters. Suppose for instance that you have a command defobj1 defining some type of objects (cf. 6), and want to create objects p_0, p_1, p_2, and so on up to n (a variable). This is the correct way to write it in a program :

do i 0 n
defobj1 p_!(i)
enddo

(for the do loop, see 4.8). Here the numerical expression i is converted to an integer, and this integer is substituted to !(i) in the argument. It is possible to put any kind of numerical expression inside !() (for instance !(i*j+2)). It is possible also to substitute floating point values : if you write

defobj1 p_%(i)

you will obtain p_0.0000000000000000, p_1.0000000000000000, and so on up to n.

The substitution of numerical expressions is a particular case of the way to make substitutions in the command interpreter (cf. 4.5).

4.3.3 How to parse numerical expressions in commands

The library contains some functions that can be used to evaluate the arguments of commands with the expression evaluator (cf. 7). For instance if the command Xcom needs a floating point parameter, it may be useful to be allowed to write

- interpcom -> Xcom a*(b+cos(x))

as well as

- interpcom -> Xcom 2.252

In this case the parameter must be parsed inside the C-function corresponding to the command Xcom, using the appropriate function of the library (cf. 7).


4.3.4 Direct access to the expression evaluator

It is possible to send instructions to the expression evaluator directly, i.e. to avoid some steps in the interpretation of commands. This can be done by giving the instruction

- interpcom -> [

(or by putting a line containing a single [ in a program file or a program). All subsequent instructions will be sent directly to the expression evaluator. In programs, this will not affect the use of loops, goto and if... statements. To be able tu use commands again, the following instruction

- interpcom -> ]

must be used (or a line containing a single ] in a program file or a program).

The instructions

- interpcom -> [0

and

- interpcom -> [1

are variants of [. They are used to increase the speed of parts of programs that use only the expression evaluator. As for [, between one of these instructions and ], all statements are sent directly to the expression evaluator, and additional optimizations are made (see 7 for more details).

4.3.5 How to change the expression evaluator

It is possible to change the expression evaluator in two ways :

- One may change the way unrecognized commands are treated. The default way is to send them to the expression evaluator using the function Evaluate.

- Another way is to send instructions in sequences between [ and ] instructions (cf. 4.3.4) to something else than the expression evaluator. The default behaviour or the interpreter is to send these instructions to the function convert_float.

This can be done independently in each thread. This is explained with more details in 9.4.


4.4 String variables

String variables can be defined an used. For instance the instruction

 - interpcom -> string xx DFERF

will create the string variable xx containing the string DFERF. In further instructions or program lines each substring $[xx] will be replaced by DFERF. For instance

 - interpcom -> echo A_$[xx]_B

will print A_DFERF_B. Five strings are prefefined : RESDIR, COMDIR, DATADIR and DATA2DIR, which contain respectively the result directory, the command directory and the first and second data directories (cf. 3.4), and THREAD which contains the name of the running thread (cf. 9)..

The substitution of string variables is a particular case of the way to make substitutions in the command interpreter (cf. 4.5).

The command liststr gives the list of all defined strings. For example

 - interpcom -> liststr
RESDIR = ./res
COMDIR = ./com
DATADIR = ./data
DATA2DIR = ./data
THREAD = main
xx = DFERF

The command delstring is used to destroy already defined strings (except the predefined ones) :

 - interpcom -> delstring xx
 - interpcom -> liststr
RESDIR = ./res
COMDIR = ./com
DATADIR = ./data
DATA2DIR = ./data
THREAD = main


4.5 Substitutions


4.5.1 Substitution patterns

A substitution pattern is a structure (defined in interp.h) :

typedef struct SUBST_PAT {
    char        orig;
    subst_delim delim;
    pfi_char    F;
} subst_pat;

Here delim is a delimiter structure :

typedef struct SUBST_DELIM {
    char        begin;
    char        end;
} subst_delim;

and F is a pfi_char function :

typedef void    (*pfi_char)(char *, char **, flow_data *);

For instance when we use string variables (cf. 4.4) the members begin and end of delim (the underlying delimiter structure) are respectively [ and ], the member orig of the substitution pattern is $, and F is a function that replaces the name of the string variable by its contents.

Five substitution patterns are recognized by the interpreter :

#() : this is used to substitute arguments inside command files or programs (cf. 4.2).

%() : this is used to insert floating point numbers (cf. 4.3.2).

!() : this is used to insert integers (cf. 4.3.2).

$() : this is used to insert character strings defined by question files (cf. 4.8.3 and 8).

$[] : this is used to insert the content of string variables (cd. 4.4).

The corresponding substitution patterns are Subst_Pat[0] to Subst_Pat[4] (they are defined in the function default_Subst_Pat in interp.c).

It is possible to use nested substitution : for instance $[XX%(i+$[V])__!(x$(2))] is correct.


4.5.2 How to add new substitution patterns

First of all the new substitution pattern must be defined. The choice of the delimiter structure and the member orig must be done carefully to avoid ambiguities. For instance one could choose #[], %[], ![], @() or @[]. Then the new substitution pattern must be put in the array Subst_Pat and the global variable n_subst_pat must be incremented (it contains the number of used substitution patterns) :

    Subst_Pat[n_subst_pat] = My_new_substitution_pattern;
    n_subst_pat++;

Next we have to define the F member of the substitution pattern. It is a fonction

void My_subst_funct(char *inxx, char **outxx, flow_data *flow_interp)

Here inxx is the character string between the two delimiters (after possible other substitutions), the function will produce a character string in outxx[0], and flow_interp is the flow_data structure corresponding to the running thread (cf. 9).

In the function, outxx[0] must be allocated (it will be freed later in the routine that makes substitutions). The reader can look at the functions corresponding to the 5 built-in substitution patterns in interp.c (functions Subst_Pat_act0 to Subst_Pat_act4). Here is an example of a function that would double the input (i.e. for instance a string ABCD would be replaced by ABCDABCD) :

void My_subst_funct(char *inxx, char **outxx, flow_data *flow_interp)
{
    if (inxx[0] == 0)
        outxx[0] = NULL;
    else {
        outx[0] = (char *) calloc(sizeof(char), 1 + strlen(inxx) * 2);
        strcpy(outxx[0], inxx);
        strcat(outxx[0], inxx);
    }
}

The new substitution rule will be applied only to programs that are loaded after the addition of the new substitution pattern. If it must be applied to all programs it is also possible to modify the function default_Subst_Pat in interp.c where the default substitution patterns are defined.


4.6 Conditions

It is possible to execute contitionally some instructions, interactively or in command files or programs, using the commands si... and is.... Inside programs it is better to use conditional jumps (cf. 4.8.2). For instance suppose you type

 - interpcom -> si ccc

where ccc is a variable name. In this case the interpreter will evaluate ccc. If the result is 0 or negative, the commands that follow will be ignored, until the interpreter receives the command

 - interpcom -> is ccc

and then the commands will be again accepted. It is also possible to avoid the following instructions if ccc is positive, by using

 - interpcom -> si non ccc


Example :

- interpcom -> c=1
                 1.000000
- interpcom -> d=-1
                -1.000000
- interpcom -> a=24
                 24.000000
- interpcom -> si c
- interpcom -> a
                 24.000000
- interpcom -> si d
- interpcom -> a
- interpcom -> is d
- interpcom -> a
                 24.000000
- interpcom -> si non d
- interpcom -> a
                 24.000000
- interpcom -> is d
- interpcom -> is c


The maximal number of current conditions (not closed with is...) is fixed in the initialization file (cf. 3.1).


4.7 Command files


4.7.1 Definition

A command file is a file containing a succession of commands understood by the command interpreter. It resides usually in the command directory which is fixed in the initialization file (cf. 3.4). It is invoked by a command

- interpcom -> <name_of_file arg1 arg2 ...

where name_of_file is the name of the command file, and arg1, arg2,... are the arguments (if any, cf. 4.2). It the command file is somewhere else than in the command directory, its address relatively to this directory must be given, instead of its name.

A command file can call other command files or programs too. Comments may be written in command files, in lines beginning with the character ; .


4.7.2 Simulation of loops

This can be done by using the commands loop..., loopf... and repete... The instruction

- interpcom -> loop com1 com2 n1 n2 n3 par1 par2 ...

will create a command file called com1 (in the command directory). This file will contains n1 successive calls to com2 with the parameters

n2 par1 par2...
n2+n3 par1 par2...
.
.
n2 + (n1-1)*n3 par1 par2...

Here n1, n2, n3 are numerical expressions that will be evaluated as integers. There is a similar command loopf... which is the same except that n1, n2, n3 will be evaluated as floating point numbers.


Example : The instruction

- interpcom -> loop com1 com2 4 100 10 A


will produce the command file com1 wich contains :

<com2 100 A
<com2 110 A
<com2 120 A
<com2 130 A

and

- interpcom -> loopf com1 com2 4 100.5 0.1 A

will produce the command file com1 wich contains :

<com2 100.500000 A
<com2 100.600000 A
<com2 100.700000 A
<com2 100.800000 A

The instruction

- interpcom -> repete com1 com2 xx1 xx2

will create a command file called com1 (in the command directory). This file will contain successive calls to the command file com2, the first with the argument xx1, the second with the argument xx2, and so on.


Example : The instruction

- interpcom -> repete com1 com2 44.00 22 125 36

will produce the command file com1 wich contains :

<com2 44.00
<com2 22
<com2 125
<com2 36

It is possible also to create command files with commands containing #1 (or #2, etc.). This can be done by replacing the # in the command line by {.


Example : The instruction

- interpcom -> loop com1 com2 4 100 10 {1

will produce the command file com1 wich contains :

<com2 100 #1
<com2 110 #1
<com2 120 #1
<com2 130 #1


4.8 Programs


4.8.1 Structure of programs

Programs can reside in files (in the command directory, cf. 3.4) or in the initialization file. A program file can contain several programs. A program begins in the following way

:name
n1
n2
mode1 mode2 ...

where name is the name of the program (don't forget the : before), and n1, n2, mode1, mode2,... are integers.

The first integer n1, is the number of arguments needed by the program.

The second integer n2 must be 0 or 1. If it is 0, the program will run silently, i.e. the successive instructions that it contains will not be printed on screen. This is useful if the program produces the execution of many instructions, if it contains loops for instance. It is possible to run silently only a part of a program, by using the command silence ... (cf. 10). It is also possible to print something on screen inside a silent program (or part of program), by using the commands echo, echoi and echof (cf. 10).

The integers mode1, mode2,... are the running modes where it is allowed to use the program (cf. 3.2). If the only mode -1 is put here, then the program can be used in all the running modes.

In a program file, a program ends when another one begins or when the file ends. A program can call other programs or command files.

Programs written in the initialization file will be loaded when the interpreter begins to run. Program files can be loaded using the command load. For instance the instruction

- interpcom -> load prog.cmd

will load the program file prog.cmd which resides in the command directory. If the program file contains a program with the same name as an already loaded program, the old program will be destroyed and replaced by the new and a message will be printed to indicate this.

The command

- interpcom -> proglist

prints the list of all the loaded programs. If prog1 is one of them, the instruction

- interpcom -> proglist prog1

prints the list of all the instructions of the program prog1.

The instruction

- interpcom -> delprog prog1

deletes the program prog1, unless it is a program defining objects or structures (cf. 6) or it comes from the initialization file (cf. 3.13).

Comments may be written in programs, using lines beginning with the character ; .


4.8.2 Labels, conditional jumps and loops

A label in a program is a line of the following type

name:

where name is a string of characters (with no '#', '{', '&' or ':') followed by a ':'. It is possible to jump to this line (inside the same program) by using the instruction

goto name

It is possible also to make conditional jumps by testing the value of numerical expressions. The instruction

if> expr name1

will jump to label name1: or not, according to the numerical expression expr. If it is positive the program jumps to name1:, otherwise it goes to the next line. The label must of course exist somewhere in the program. It is also possible to test if an expression is negative or zero, by using

if< expr name1

in the first case, and

if= expr name1

in the second case.


The syntax of loops is as follows :

do var var1 var2 incr
.
.
.
enddo

where var is a variable name, var1, var2 and incr are numerical expressions. It is not necessary to define var before using it in this loop. Initially, var is set to var1 and in each step it if incremented by adding incr. The loop ends when var becomes larger than var2. This will always work if var2 and incr are constant during the loop and incr is positive, and definitely never if var2 is constant and incr negative or zero. The parameter incr can be omitted. In this case it is assumed to be 1. It is possible to nest loops.

In fact there is not a real implementation of loops, but rather a translation of the instructions do..., enddo when the program is loaded, using conditional jumps. It is possible to see this by listing a program containing loops.


Example : Suppose that the file prog.cmd in the command directory contains the following lines :


:example1
3
1
-1
x=0
do i #1 #2
x=x+i*i
enddo

Given two numbers $a$ and $b$ this program computes

\begin{displaymath}\mathop{\hbox{$\displaystyle\sum$}}\limits _{a\leq i\leq b}i^2.\end{displaymath}

The instruction

- interpcom -> proglist example1

prints the following on the screen

x=0
i=#1-1
0:
i=i+1
if> i-(#2) 1
x=x+i*i
goto 0
1:


4.8.3 Questions and answers inside programs

It is possible that inside a function called by a command some data are asked to the user of the program. A standard way to do this is as follows :

int i;
float x;
printf("Enter an integer : ");
scanf("%d", &i);
printf("Enter a number : ");
scanf("%f", &x);

This is not good inside a program of the command interpreter, if you don't want it to stop to wait the answers. Instead of scanf one can use the library functions

void  read_int(int *, flow_data *);
void  read_float(float *, flow_data *);
void  read_char(char *, flow_data *);

(the last to read character strings). Here flow_data is a structure type used to describe threads (cf. 9.4). The program becomes

int i;
float x;
printf("Enter an integer : ");
read_int(&i, flow_interp);
printf("Enter a number : ");
read_float(&x, flow_interp);

(in 9.4 we explain how to recover the flow_data structure corresponding to the running thread inside a command). In this case, the functions will behave like scanf if they are used in a command in a interactive way (i.e not in a program). Moreover the entered values will be parsed by the expression evaluator; you can enter for instance :

Enter an integer : 
2*k+5
Enter a number : 
cos(y)+2*c-4.

If the command corresponding to the function containing this piece of code is used inside a program, the interpreter will read the following lines of the program (2 in this example). So the program of the command interpreter could look like

.
.
command_name arg1 arg2 ....
2*k+5
cos(y)+2*c-4.
.
.


4.9 Monitor files

It is possible to write in a file all the instructions executed by the command interpreter, and the messages printed on screen by it, using the command mon.... The syntax is

mon xxx

where xxx is a file name. If this instruction is given, the file is created if it did not exist, otherwise the old one erased and a new file with the same name is created. All the subsequent instructions and messages of the command interpreter will be written not only on screen but also in this file, until the program ends, or the instruction end_mon appears. In the later case, the monitor file is closed. For the messages that come from functions written by the user, the function

void print(flow_data *, char*, ...);

can be used instead of the usual printf (here flow_data is a structure type used to describe threads (cf. 9.4)). This function prints on screen and on the monitor file, if it has been defined. Only the flags %d, %f and %s have been implemented.

Note that a monitor file corresponds to the the thread in which it has been created, and will store only messages coming from this thread. So several distinct threads can use their own monitor file simultaneously (of course in this case the monitor files must be distinct).


next up previous contents index
Next: 5 Commands Up: $FILE Previous: 3 Initialization file   Contents   Index
jmdr 2001-12-07