This article describes a standart method to create a Scilab toolbox.
The objective is to help the contributors to build a toolbox easily, but also that the users are able to install it with just the execution of a main builder and a main loader script.
We show in first time how to structure your toolbox (sub-directories contents, files, ...), in second time how to customize the templates to create the builder(s), the loader(s), and the Scilab help files. To finish we explain how to upload your contribution on the scilab website.
For this article the reference toolbox name is mytoolbox.
(The standarisation below is not mandatory)
The root directory has the generic name of the toolbox (here mytoolbox), it contains 8 sub-directories:
The main builder and main loader scripts launch respectively the sub-builders and sub-loaders included in the sub-directories (macros, src, help, ...), to generate and to load the necessary libraries and the Scilab help files.
By convention the builder and the loader included in the macros directory are named respectively buildmacros.sce and loadmacros.sce
A macros is a function written in Scilab code (included in .sci file)
We consider that the macros directory of the toolbox mytoolbox contains just one .sci file, the function foo1 (see above script): Given a matrix A, this function returns the vector X the positives components of the A diagonal.
function [X]=foo1(A) // This function returns the positive components of the A diagonal // Check the type and the size of A if type(A)<>1 then error("type of input argument must be a double"); end if size(A,1)<>size(A,2) then error("input argument must be a square matrix"); end //Extraction of the positive components X=[]; for i=1:size(A,1) if A(i,i)>0 then X($+1)=A(i,i); end end endfunction
The builder (see below script) creates a library variable, (named here: mytoolboxlib= toolbox name +'lib') from functions .sci include in the directory macros, and saves it in the file lib. The builder code is generic, it's done in 2 steps, the first to locate buildmacros.sce script (see help get_absolute_file_path function), and the second to generate the library (see help genlib function).
buildmacros.scemode(-1) toolboxname='mytoolbox' pathB=get_absolute_file_path('buildmacros.sce') disp('Building macros in ' +pathB) genlib(toolboxname+'lib',pathB,%t) clear pathB genlib toolboxname
The loader (see below script) loads the library lib included in the directory macros. Like the builder, the code is generic, the first step to locate loadmacros.sce script, and the second to load the library (see help load function)
loadmacros.scemode(-1) pathL=get_absolute_file_path('loadmacros.sce') disp('Loading macros in ' +pathL) load(pathL+'/lib') clear pathL
When a Scilab primitive is called, the interface program checks that the number, the type and the size of inputs/outputs arguments is correct (using CheckRhs and CheckLhs functions), and gets the rhs arguments address which are in Scilab internal stack to give this information at the interfaced function.
We don't describe all the possibilities of the interface programs, for more explanations see the directory SCI/examples.
These examples(written in C code) are described step by step, they enable to write many interfaces, so it's important to understand them, and to know how to customize them for your toolboxes.
Example a:
We consider an C routine vectsum which returns the sum of two vectors. I suppose that the name of the corresponding primitive is sumab and the associated interface program name is sci_sumab.
By convention all the interfaces programs names begin by the "sci_" character strings.
The both following scripts represent the C code of vectsum and sci_sumab when we call the primitive sumab in a Scilab window as follows:
--> Y=sumab(A,B )
void vectsum(int n, double * a, double * b, double * y) { int k; for (k = 0; k < n; ++k) y[k] = a[k] + b[k]; }
#include "stack-c.h" extern int vectsum(int n, double * a, double * b, double * y); void sci_sumab(char *fname){ int l1, m1, n1, l2, m2, n2, l3, n; /* 1 - Check the number of inputs/outputs arguments */ int minlhs=1, maxlhs=1, minrhs=2, maxrhs=2; CheckRhs(minrhs,maxrhs) ; CheckLhs(minlhs,maxlhs) ; /* 2 - Check inputs arguments type, and get the size and the address in the Scilab stack of the inputs arguments */ GetRhsVar(1, "d", &m1, &n1, &l1); GetRhsVar(2, "d", &m2, &n2, &l2); /* 3 - Check that the inputs arguments have the same size */ /* it's possible to use the chekdims and getscalar functions to make these checks*/ n=m2*n2; if( n1!=n2 || m1!=m2) { cerro("inputs arguments must have the same size"); return 0; } if(n1!=0 && m1!=0) if(n1!=1 && m1!=1) { cerro("inputs arguments must be vectors"); return(0); } /* 4 - Create a new variable corresponding to the output argument */ CreateVar(3,"d",&m2,&n2,&l3); /* 5 -call vectsum routine: returns in stk(l3) the sum of a and b*/ vectsum(n,stk(l1),stk(l2),stk(l3)); /* 6 - Specif ouput argument */ LhsVar(1) = 3; return 0; }
Step 1: call CheckRhsVar(minrhs,maxrhs) and
CheckLhsVar(minlhs,maxlhs) instructions
CheckRhsVar function uses the arguments minrhs
and maxrhs to check that:
minrhs <= number of input arguments <= maxrhs
The number of inputs and outputs arguments (respectively 2 and 1) of vectsum are constant, so minrhs=maxrhs=2 and minlhs=maxlhs=1, but for certains functions (see example2)
they can be variable, in this case the variables minrhs/minlhs and maxrhs/maxlhs
are different.
We can use directly the defined variables Rhs(=number of inputs) and
Lhs(=number of outputs) instead of the functions CheckRhsVar and
CheckLhsVar.
Step 2: call GetRhsVar(1,"d",&m1,&n1,&l1) instruction
GetRhsVar function checks that the type of inputs arguments of sumab are correct, and gets their size and their address in the Scilab stack.
We describe below all arguments of GetRhsVar function:
The number of outputs arguments (i.e Lhs value) is variable: for the first syntax Lhs=1, for the second syntax Lhs=2. The number of intputs arguments (i.e Rhs value) is constant: Rhs=1 (first and second syntax).
So the interface program must check that: 1<=Lhs<=2 (set minlhs=1, maxlhs=2) and Rhs=1 (set minrhs=maxrhs=1)
extern void fun2(double *, int, int *); void fun1(double * a, int na, int * nx, double ** x , double * y){ int i, k1=0; *y=0; fun2(a, na, nx); *x=(double *)malloc((*nx)*sizeof(double)); *y=0; for(i=0;i<na;i++) if(a[i]>0) { *(*x+k1)=a[i]; *y += a[i]; k1++; }; }
void fun2(double * a, int na, int * nx) { int i; *nx=0; for(i=0;i<na;i++) if (a[i]>0) (*nx)++; }
#include "stack-c.h" extern void fun1(double * , int, int *, double **, double *); int sci_fun(char *fname) { int la, ma, na, m=1, nx, i, lx, ls; double * x, s; /* 1 - Check the number of inputs and outputs arguments */ /* You can use the variables: Lhs and Rhs */ int minlhs=1, maxlhs=2, minrhs=1, maxrhs=1; CheckRhs(minrhs,maxrhs) ; CheckLhs(minlhs,maxlhs) ; /* 2 - Check the rhs type, get the rows number (ma) and the columns number (na) of rhs, and its address (la) in the Scilab stack (first position) */ GetRhsVar(1, "d", &ma, &na, &la); /* 3 - Check rhs is a vector */ if(ma!=0 && na!=0 ) { if(ma!=1 && na!=1) { cerro("input argument must be a vector"); return(0); } } fun1(stk(la), na*ma, &nx, &x, &s); /* 4 - Create the place for the first output argument x ( a vector of doubles, size: 1*nx ) to the address lx in the Scilab stack (second position) */ CreateVar(2, "d", &m, &nx, &lx); /* if there are two outputs variables then: Create the place for the second output s ( a double, size 1*1) to the address ls in the Scilab stack (third position) */ /* get the value of s, and put it in the Scilab stack */ if(Lhs==2) { CreateVar(3, "d", &m, &m, &ls); *stk(ls)=s; } /* get the components of x, and put them in the Scilab stack */ for(i=0;i<nx;i++) stk(lx)[i]=x[i]; /* free memory */ free(x); /* 5 - Specification of outputs variables */ LhsVar(1) = 2; if(Lhs==2) LhsVar(2) = 3; return 0; }
Now the src and the sci_gateway directories contain all the necessary files (fun1.c, fun2.c, sci_fun.c, vectsum.c, sci_sumab.c) to create the builder (see below template) for the primitives fun and sumab.
We need to write two builders:
One the hand, in the src directory, this builder (named buildersrc) creates a shared libraries (see help ilib_for_link function) corresponding to the C functions.
And the other hand, in the interface directory, this builder (named buildsci_gateway) creates the new shared libraries to link the compiled C or Fortran new Scilab interface routines (thanks to src libraries), and generates a loader (see help ilib_build function).
This loader file calls the addinter function to load dynamically the shared library (see help addinter function)
ilib_for_link('mytoolboxsrc',['fun1.o' 'fun2.o','vectsum.o'],[],"c")
// must be run from this directory ilib_name = 'libmytoolbox' // interface library name files = ['sci_fun.o', 'sci_sumab.o']; // objects files libs = ["../src/libmytoolboxsrc"] // other libs needed for linking table = [ 'fun', 'sci_fun'; 'sumab','sci_sumab']; // table of (scilab_name,interface-name) // do not modify below ilib_build(ilib_name,table,files,libs)
This directory included .xml files, a buildhelp and a loadhelp scripts.
On Unix/Linux systems: to create the manual pages you need 'sabcmd', an XML parser which is part of the Sablotron package.
here the link to download it:
http://www.scilab.org/download/index_download.php?page=related_tool.html
Here a template which shows you how to write the .xml help files. You should just fill the different items(Langage, title, type, date, short description, ...) for the .xml files of your functions (here foo1.xml, fun.xml, sumab.xml) and put them in the help directory.
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?> <!DOCTYPE MAN SYSTEM "/home/scilab/scilab-3.0/man/manrev.dtd"> <MAN> <LANGUAGE>eng</LANGUAGE> <TITLE>sumab</TITLE> <TYPE>Scilab Function </TYPE> <DATE>20-Mar-2006</DATE> <SHORT_DESCRIPTION name="add function name"> add short decription here</SHORT_DESCRIPTION> <CALLING_SEQUENCE> <CALLING_SEQUENCE_ITEM>add function syntax</CALLING_SEQUENCE_ITEM> </CALLING_SEQUENCE> <PARAM> <PARAM_INDENT> <PARAM_ITEM> <PARAM_NAME>add param name</PARAM_NAME> <PARAM_DESCRIPTION> <SP> : add here the parameter description </SP> </PARAM_DESCRIPTION> <PARAM_ITEM> <PARAM_NAME>add param name</PARAM_NAME> <PARAM_DESCRIPTION> <SP> : add here the parameter description </SP> </PARAM_DESCRIPTION> </PARAM_ITEM> </PARAM_INDENT> </PARAM> <DESCRIPTION> <DESCRIPTION_INDENT> <DESCRIPTION_ITEM> <P> Add here a paragraph of the function description. Other paragraph can be added </P> </DESCRIPTION_ITEM> <DESCRIPTION_ITEM> <P> Add here a paragraph of the function description </P> </DESCRIPTION_ITEM> </DESCRIPTION_INDENT> </DESCRIPTION> <EXAMPLE><![CDATA[ Add here scilab instructions and comments ]]></EXAMPLE> <SEE_ALSO> <SEE_ALSO_ITEM> <LINK> add a key here</LINK> </SEE_ALSO_ITEM> <SEE_ALSO_ITEM> <LINK> add a key here</LINK> </SEE_ALSO_ITEM> </SEE_ALSO> <BIBLIO> <Add here the function bibliography if any </BIBLIO> <AUTHORS> <AUTHORS_ITEM label='enter here the author name'> Add here the author references </AUTHORS_ITEM> </AUTHORS> <USED_FUNCTIONS> Add here the used function name and references </USED_FUNCTIONS> </MAN>
The builder (named buildhelp) creates a whatis.htm file which is a short description of the functions, and translates the xml files to html (see help xmltohtml function)
mode(-1) //force silent execution path=get_absolute_file_path('builhelp.sce');//get the absolute path of this file add_help_chapter("Title1",path);//add help chapter xmltohtml(path,"Title1") //clear the variable stack clear path add_help_chapter get_absolute_file_path
The loader (named loadhelp) adds your help functions files in the help Scilab browser
loadhelp.scemode(-1) //force silent execution path=get_absolute_file_path('loadhelp.sce');//get the absolute path of this file add_help_chapter("Title1",path);//add help chapter clear path add_help_chapter get_absolute_file_
mode(-1); mainpathB=get_absolute_file_path('builder.sce'); chdir(mainpathB); if isdir('src') then chdir('src'); exec('buildsrc.sce'); chdir('..'); end if isdir('sci_gateway') then chdir('sci_gateway'); exec('buildsci_gateway.sce'); chdir('..'); end if isdir('macros') then chdir('macros'); exec('buildmacros.sce'); chdir('..'); end if isdir('help') then chdir('help'); exec('buildhelp.sce'); chdir('..'); end clear mainpathB
mode(-1); mainpathL=get_absolute_file_path('loader.sce'); chdir(mainpathL); if isdir('sci_gateway') then chdir('sci_gateway'); exec('loader.sce'); chdir('..'); end if isdir('macros') then chdir('macros'); exec('loadmacros.sce'); chdir('..'); end if isdir('help') then chdir('help'); exec('loadhelp.sce'); chdir('..'); end clear mainpathL