Initializing help system before first use

Creating external types

Topics covered in this chapter:

Mosel modules may create new types (referred to as external as opposed to the default types that are internal to Mosel), for instance other types of variables to be handled by specific solution algorithms, structures regrouping data items, or additional types of numbers. To be able to work with a new type in a Mosel program, it is not sufficient simply to define this type in a module. The module must also define all actions that one wants to be able to apply to objects of this type: creation, initialization, assignment, deletion, arithmetic operations and comparisons are typical examples. Once a new type has been created, it is treated just like a genuine type of Mosel, e.g. it becomes possible to define arrays and sets of this type or to use it as a function parameter.

Example

In its present version, Mosel does not allow the user to define data structures with entries of different types. In certain cases it may nevertheless be useful to organize data in such a way. Taking the example of scheduling problems, a typical group of inhomogeneous data are those related to a task. In our example, we shall define a structure task that holds the following pieces of information:

  • task name (a string)
  • duration (real value)
  • a special flag (Boolean)
  • due date (integer value)

The following model may give an overview on the types of operations and specific access functions that we have to define in order to be able to work satisfactorily with this new type:

model "test task module"
 uses "task"

 declarations
  R:set of integer
  t:array(R) of task
  s:task
 end-declarations

! Assigning a task
 s:=task("zero",1.5,true,3)

! Initializing a task array from file
 initializations from "testtask.dat"
  t
 end-initializations

! Reassigning the same task
 t(1):=task("one",1,true,3)
 t(1):=task("two",1,true,3)

! Various ways of creating tasks
 t(3):=task("three",10)
 t(7):=task(7)
 t(6):=task("six")
 t(9):=task(3,false,9)

! Writing a task array to file
 initializations to "testtask.dat"
  t as 't2'
 end-initializations

! Printout
 writeln("s:", s)
 writeln("t:", t)

! Accessing (and changing) detailed task information
 forall(i in R)
  writeln(i," Task ",strfmt(t(i).name,-5),": duration:", t(i).duration,
          ", flag:", t(i).aflag, ", due date:", t(i).duedate )
 t(7).name:="seven"
 t(6).duration:=4.3
 t(9).aflag:=true
 t(7).duedate:=10

! Comparing tasks
 if t(1)<>s then
  writeln("Tasks are different.")
 end-if
 t(0):=task("zero",1,true,3)
 if t(0)=s then
  writeln("Tasks are the same.")
 end-if

end-model  

Structures for passing information

The module that we are about to write needs to provide the following:

  • definition of the new type
  • functions and operations on this new type, namely
    • creation and initialization functions for the new type
    • a set of subroutines for accessing (and changing) detailed task information
    • functions for reading and printing or outputting to file
    • comparison operation between tasks
  • a reset service
  • initialization of the module

We shall first look at the structures that must be defined for passing to Mosel the information provided by the module.

List of types

A type definition in Mosel has the following form:

 static XPRMdsotyp tabtyp[]=
    {
     {"task", 1, XPRM_DTYP_PNCTX|XPRM_DTYP_RFCNT,
      task_create, task_delete, task_tostr, task_fromstr, task_copy, task_compare}
    };

The arguments given in the definition of the new type are

  • the name of the new type,
  • a reference number to this type within the module followed by another integer encoding type properties (here: enable calls to task_tostr with NULL context and indicate that the type implements reference counting);
  • the six type-related functions: the first, the type instance creation function, is required whereas the remaining five: deletion, converting to string, initializing from string, copying and comparison, are optional.

A complete description of the possible values for the entries of this structure is given in Section List of types.

List of subroutines

To be able to work with this new type as shown in the model example in the previous section we have to define a list of subroutines as follows:

static XPRMdsofct tabfct[]=
    {
     {"getname", 1000, XPRM_TYP_STRING, 1, "|task|", task_getname},
     {"getduration", 1002, XPRM_TYP_REAL, 1, "|task|", task_getdur},
     {"getaflag", 1003, XPRM_TYP_BOOL, 1, "|task|", task_getaflag},
     {"getduedate", 1004, XPRM_TYP_INT, 1, "|task|", task_getdue},
     {"setname", 1005, XPRM_TYP_NOT, 2, "|task|s", task_setname},
     {"setduration", 1006, XPRM_TYP_NOT, 2, "|task|r", task_setdur},
     {"setaflag", 1007, XPRM_TYP_NOT, 2, "|task|b", task_setaflag},
     {"setduedate", 1008, XPRM_TYP_NOT, 2, "|task|i", task_setdue},
     {"@&", 1011, XPRM_TYP_EXTN, 1, "task:|task|", task_clone},
     {"@&", 1012, XPRM_TYP_EXTN, 1, "task:s", task_new1},
     {"@&", 1013, XPRM_TYP_EXTN, 1, "task:r", task_new2},
     {"@&", 1014, XPRM_TYP_EXTN, 2, "task:sr", task_new3},
     {"@&", 1015, XPRM_TYP_EXTN, 4, "task:srbi", task_new4},
     {"@&", 1016, XPRM_TYP_EXTN, 3, "task:rbi", task_new5},
     {"@:", 1020, XPRM_TYP_NOT, 2, "|task||task|", task_assign},
     {"@=", 1021, XPRM_TYP_BOOL, 2, "|task||task|", task_eql}
    };

Some of the notations used in this list are new and may require an explanation. The first eight subroutine definitions (get... and set...) are similar to the subroutine definition we have seen in the previous chapter:

     {"getname", 1000, XPRM_TYP_STRING, 1, "|task|", task_getname},

defines the function getname that returns a string and takes a single argument, namely a task. The line

     {"setname", 1005, XPRM_TYP_NOT, 2, "|task|s", task_setname},

defines a procedure (no return value!) that takes two arguments, a task (|task|) and a string (s). The names of external types must be surrounded by `|' in the parameter format encoding to distinguish them clearly from the one-letter encoding of Mosel's own types.

The remaining entries in the list of subroutines have special names starting with the symbol `@': they define operators:

@&
constructors
@:
assignment operator
@=
comparison operator

The constructors return new objects of an external type (return code XPRM_TYP_EXTN). Since a module could specify several new types, the exact return type must be indicated in the format string, separated by a colon from the list of argument types.
The assignment operator `:' has a predefined format, as does the comparison operator `='.

As may be deduced from the list above, the reference numbers of the functions within the module must be in ascending order, but need not necessarily be consecutive numbers.

List of services

In this example, for the first time, we need to define a service. A service function is called by Mosel at certain predefined places (it has no direct correspondence in Mosel programs). The service function that needs to be defined when working with new types is a reset function. It is also required in any other cases where between several calls to module functions something needs to be kept in memory (the context of the module). The reset service is called at the beginning and the termination of the execution of a Mosel program that uses the module. At its first call, the reset function creates and initializes a context for the model, and deletes this context (and any other resources used by the module for this model) at the second call.

 static XPRMdsoserv tabserv[]=
    {
     {XPRM_SRV_RESET, (void *)task_reset}
    };

The entry in the list of services simply indicates the type of service that is provided (here: reset) and the name of the library function that implements it.

Interface structure

The interface structure of this example defines all but the first entry with the lists of functions, types, and services shown above.

static XPRMdsointer dsointer=
    {
     0, NULL,
     sizeof(tabfct)/sizeof(XPRMdsofct), tabfct,
     sizeof(tabtyp)/sizeof(XPRMdsotyp), tabtyp,
     sizeof(tabserv)/sizeof(XPRMdsoserv), tabserv
    };

Module context

As mentioned earlier, the task module defines a context to collect all objects that have been created by this module during the execution of a model so that all allocated space may be freed when the execution is terminated. In this example, the context is nothing but a chained list of tasks:

typedef struct
    {
     s_task *firsttask;
    } s_taskctx;

A module context can also be used to store the current values of control parameters (see Chapter Control parameters) or any other information that needs to be preserved between different calls to the module functions during the execution of a model.

Type-related functions

In this example, the following structure represents a task:

typedef struct Task
     {
      int refcnt;
      const char *name;
      int aflag, duedate;
      double duration;
      struct Task *next;
     } s_task;

The first entry of this structure is the reference counter (with the flag XPRM_DTYP_RFCNT set at the type definition we have indicated that our module implements reference counting for the type `task'). The next four entries of this structure correspond directly to the information associated with a task (name, a Boolean flag, due date, duration). The last entry (next) points to the following element in the list of tasks held by the module context.

In the definition of the new type task, we have indicated the names of 5 functions for creating and deleting the new type, getting a textual representation and initializing the new type from a textual representation, and copying the type. The only function that is always required for any type definition is the creation function, the remaining ones are optional (for the deletion function depending on the type properties).

Type creation and deletion

The objective of the type instance creation and deletion functions is to handle (create/initialize or delete/reset) the C structures that represent the external type and to update correspondingly the information stored in the module context. In this example we implement just a rudimentary memory management for the objects (tasks) created by the module: every time a task is created, we allocate the corresponding space and deallocate it when the task is deleted. In Chapter Creating external types: second example a more realistic example is given that allocates chunks of memory and recycles space that has been allocated earlier by the module.

Reference counting: the flag XPRM_DTYP_RFCNT set at the type definition indicates that our module handles reference counting for the type task. As a consequence Mosel may call the type creation function with a reference to a previously created object for increasing its reference count. The type deletion function (which is mandatory in this case) is called as many times as the creation function has been used for a given object before this object is effectively released.

We define the task creation function as follows:

static void *task_create(XPRMcontext ctx, void *libctx, void *todup,
                         int typnum)
{
 s_taskctx *taskctx;
 s_task *task;

 if(todup!=NULL)
 {
  ((s_task *)todup)->refcnt++;
  return todup;
 }
 else
 {
  taskctx=libctx;
  task=(s_task *)malloc(sizeof(s_task));
  task->next=taskctx->firsttask;
  taskctx->firsttask=task;
  task->refcnt=1;

  task->name=NULL;                    /* Initialize the task */
  task->duration=0;
  task->aflag=task->duedate=0;
  return task;
 }
}

The task deletion function frees the space used by a task and removes the task from the list of tasks held by the module context if no reference to the task is left. Otherwise, it decreases the reference counter. If the task is not found in the list we display an error message using the Native Interface function dispmsg. For any output produced by modules, this way of printing should always be preferred to the corresponding C printing functions.

static void task_delete(XPRMcontext ctx, void *libctx, void *todel,
                        int typnum)
{
 s_taskctx *taskctx;
 s_task *task,*prev;

 if((todel!=NULL)&&((--((s_task *)todel)->refcnt)<1))
 {
  taskctx=libctx;
  task=todel;
  if(taskctx->firsttask==task) taskctx->firsttask=task->next;
  else
  {
   prev=taskctx->firsttask;
   while((prev->next!=NULL) && (prev->next!=task))
    prev=prev->next;
   if(prev->next==NULL) mm->dispmsg(ctx, "Task: task not found.\n");
   else prev->next=task->next;
  }
  free(task);
 }
}

The definition of a type instance deletion function does not replace the memory deallocation in the reset service function (see Section Service function reset).

Conversion to and from string

To be able to use initializations blocks with the new type task we define two functions for transforming the task into a string and initializing it from a string. The writing function is also used by the write and writeln procedures for printing this type. The reading function also gets applied by default when the type instance creation function is given a string, but in this example we have defined that the string is interpreted only as the task name.
The format of the string will obviously depend on the type. In this example we have chosen a very simple string format for tasks: the data entries separated by blanks in the order name, duration, flag, due date. The following function prints a task:

static int task_tostr(XPRMcontext ctx, void *libctx, void *toprt, char *str,
                      int len, int typnum)
{
 s_task *task;

 if(toprt==NULL)
  return 0;
 else
 {
  task=toprt;
  return snprintf(str, len, "%s %g %d %d", task->name, task->duration,
                  task->aflag, task->duedate);
 }
}

The next function reads in a task from a string (the flag and due date values may have been omitted):

static int task_fromstr(XPRMcontext ctx, void *libctx, void *toinit,
                        const char *str, int typnum, const char **endp)
{
 double dur;
 int af,due,res,cnt;
 char *name;
 s_taskctx *taskctx;
 s_task *task;

 taskctx=libctx;
 name=alloca(TASK_MAXNAME*sizeof(char));
 af=due=cnt=0;
 res=sscanf(str,"%s %lf %d%n %d%n",name,&dur,&af,&cnt,&due,&cnt);
 if(res<3)
 {
  if(endp!=NULL) *endp=str;
  return XPRM_RT_ERROR;
 }
 else
 {
  task=toinit;
  task->name=mm->regstring(ctx, name);
  task->duration=dur;
  task->aflag=(res>=3)?af:0;
  task->duedate=(res==4)?due:0;
  if(endp!=NULL) *endp=str+cnt;
  return XPRM_RT_OK;
 }
}

The Native Interface function regstring that is used here adds the name string to the names dictionary. Any string that is returned to Mosel must be registered this way.

The copy function

Certain assignments in Mosel (assignments that are not stated explicitly, such as array initialization) use the type copy function. If no copy function is defined for a type, the operations where it is necessary are disabled by the compiler for the corresponding type.

For copying the type task we may define the following function where the task toinit becomes a copy of the task src:

static int task_copy(XPRMcontext ctx, void *libctx, void *toinit, void *src,
                     int typnum)
{
 s_task *task1,*task2;

 task1=(s_task *)toinit;
 if(src==NULL)
 {
  task1->name=NULL;
  task1->duration=0;
  task1->aflag=task1->duedate=0;
 }
 else
 {
  task2=(s_task *)src;
  task1->name=task2->name;
  task1->aflag=task2->aflag;
  task1->duedate=task2->duedate;
  task1->duration=task2->duration;
 }
 return 0;
}

The compare function

The compare function is required for the comparison of aggregate objects (for example, a record that contains a field of type 'task'). If no compare function is defined for a type, the operations where it is necessary are disabled by the compiler for the corresponding type.

The following function compares two objects t1 and t2 of type task by comparing all the fields of the two structures. For the comparison of the names it suffices to compare the pointers because we are using the names dictionary of Mosel: it guarantees the uniqueness of the name strings.

static int task_compare(XPRMcontext ctx, void *libctx, void *t1, void *t2, int typnum)
{
 int b;

 if(t1!=NULL)
 {
  if(t2!=NULL)
   b=((((s_task *)t1)->name==((s_task *)t2)->name)  /* This is correct since we
                                               are using Mosel's dictionary */
   &&(((s_task *)t1)->duration==((s_task *)t2)->duration)
   &&(((s_task *)t1)->aflag==((s_task *)t2)->aflag)
   &&(((s_task *)t1)->duedate==((s_task *)t2)->duedate));
  else
   b=0;
 }
 else
  b=(t2==NULL);

 switch(XPRM_COMPARE(typnum))
 {
  case MM_COMPARE_EQ:
    return b;
  case MM_COMPARE_NEQ:
    return !b;
  default:
    return XPRM_COMPARE_ERROR;
 }
}

Service function reset

Just like the other library functions, the reset service function takes a predefined format. Here we create the module context at the first call to this function and delete it at the subsequent call. When deleting the context the reset function needs to free all space that has been allocated by the module during the execution of a model. Therefore, every time a task is created it is added to the list of tasks in the module context and it is removed from the list if it is deleted explicitly by a call to the type instance deletion function. As mentioned earlier, even if a module provides deletion functions for all the types that it defines (as in this example) it is required to implement the reset service to free any remaining allocated space because Mosel does not guarantee that the type instance deletion function gets called for every object that has been created by the module.

static void *task_reset(XPRMcontext ctx, void *libctx, int version)
{
 s_taskctx *taskctx;
 s_task *task;

 if(libctx==NULL)          /* At start: create the context */
 {
  taskctx=malloc(sizeof(s_taskctx));
  memset(taskctx, 0, sizeof(s_taskctx));
  return taskctx;
 }
 else                      /* At the end: delete everything */
 {
  taskctx=libctx;
  while(taskctx->firsttask!=NULL)
  {
   task=taskctx->firsttask;
   taskctx->firsttask=task->next;
   free(task);
  }
  free(taskctx);
  return NULL;
 }
}

Other library functions and operators

The list of subroutines contains several groups of subroutines that may be applied to the new type task:

  • constructor functions (cloning and initialization with data)
  • subroutines for accessing detailed task information (getting and setting name, duration etc.)
  • assignment and comparison of tasks

Constructors

Being able to clone a type is required in certain cases of assignments (the use is similar to the cloning operation in C++):

static int task_clone(XPRMcontext ctx, void *libctx)
{
 s_task *task, *new_task;

 task=XPRM_POP_REF(ctx);
 if(task!=NULL)
 {
  new_task=task_create(ctx, libctx, NULL, 0);
  new_task->name=task->name;
  new_task->aflag=task->aflag;
  new_task->duedate=task->duedate;
  new_task->duration=task->duration;
  XPRM_PUSH_REF(ctx, new_task);
 }
 else
  XPRM_PUSH_REF(ctx, NULL);
 return XPRM_RT_OK;
}

As may be deduced from the test performed in this function, Mosel may pass the NULL pointer to a function in the place of an external type. This will typically happen if the object is an entry of a dynamic array that has not been initialized.

The following is an example of a constructor function. It creates a new task and fills it with the given data. This function enables the user to create a task by writing for example:

 task("a_task", 3.5, true, 10)

Several overloaded versions of this function are defined in our example. They are similar to this one and we omit printing them here. In every case, all given information needs to be taken from the stack and the reference to the new task is put back onto the stack.

static int task_new4(XPRMcontext ctx, void *libctx)
{
 s_task *task;

 task=task_create(ctx, libctx, NULL, 0);
 task->name=XPRM_POP_STRING(ctx);
 task->duration=XPRM_POP_REAL(ctx);
 task->aflag=XPRM_POP_INT(ctx);
 task->duedate=XPRM_POP_INT(ctx);
 XPRM_PUSH_REF(ctx, task);
 return XPRM_RT_OK;
}

Accessing detailed task information

We only give one example of a function for retrieving detailed task information (namely the task name), the other three are very similar:

static int task_getname(XPRMcontext ctx, void *libctx)
{
 s_task *task;

 task=XPRM_POP_REF(ctx);
 if(task==NULL)
 {
  mm->dispmsg(ctx, "Task: Accessing undefined task.\n");
  return XPRM_RT_ERROR;
 }
 XPRM_PUSH_STRING(ctx, task->name);
 return XPRM_RT_OK;
}

The following is an example of a function that sets some detailed task information (namely the duration):

static int task_setdur(XPRMcontext ctx, void *libctx)
{
 s_task *task;
 double dur;

 task=XPRM_POP_REF(ctx);
 dur=XPRM_POP_REAL(ctx);
 if(task==NULL)
 {
  mm->dispmsg(ctx, "Task: Accessing undefined task.\n");
  return XPRM_RT_ERROR;
 }
 task->duration=dur;
 return XPRM_RT_OK;
}

Since the names of the task access functions defined by our module adhere to the standard Mosel naming scheme (getproperty and setproperty) Mosel deduces automatically the dot notation for tasks. That means that for a task t we may use equivalently, for instance, getname(t) and t.name or setduration(t,10) and t.duration:=10.

Assignment and comparison operators

The assignment operation takes two task references from the stack, assigns the second to the first and deletes the second task since this is only an intermediate object:

static int task_assign(XPRMcontext ctx, void *libctx)
{
 s_task *task1, *task2;

 task1=XPRM_POP_REF(ctx);
 task2=XPRM_POP_REF(ctx);
 task1->name=task2->name;
 task1->aflag=task2->aflag;
 task1->duedate=task2->duedate;
 task1->duration=task2->duration;
 task_delete(ctx, libctx, task2, 0);
 return XPRM_RT_OK;
}

The implementation of the comparison operation for two tasks compares all the fields of the two structures, similarly to the implementation of the type comparison function that we have seen above in Section Type-related functions.

static int task_eql(XPRMcontext ctx, void *libctx)
{
 s_task *task1, *task2;
 int b;

 task1=XPRM_POP_REF(ctx);
 task2=XPRM_POP_REF(ctx);
 if(task1!=NULL)
 {
  if(task2!=NULL)
   b=((task1->name==task2->name) && (task1->duration==task2->duration)
     && (task1->aflag==task2->aflag) && (task1->duedate==task2->duedate));
  else
   b=0;
 }
 else
  b=(task2==NULL);
 XPRM_PUSH_INT(ctx,b);
 return XPRM_RT_OK;
}

Note that once we have defined the equality comparison, there is no need to implement the difference-between-tasks operation: it is derived by Mosel as being the negation of the equality.

Module vs. package

With Mosel Release 2 it has become possible to define new user types directly in the Mosel language. An equivalent definition of the type 'task' within a package is the following.

 public declarations
  task = public record
   name: string
   duration: real
   aflag: boolean
   duedate: integer
  end-record
 end-declarations

The access functions get... and set... may be defined to work exactly in the same way as those defined by our module. However, if we work with the dot notation to access the record fields the definition of these functions is not required. The type task defined by a package will use the standard conventions of Mosel for reading and writing records from/to a file—in a module these subroutines must be defined explicitly, which also implies that they are not confined to the standard Mosel format for reading and writing records.

A package cannot provide constructors for tasks, instead it might define subroutines to initialize (existing) tasks with data, for example, replacing the line

 t(9):=task(3,false,9)

in our test model from Section Example by

 create(t(9))
 inittask(t(9), 3, false, 9)

or by using the Mosel constructor for records:

 t(9):=task(.duration:=3,.aflag:=false,.duedate:=9)

Another feature that is not supported by packages is the definition of operators. The (default) comparison of two tasks defined through a package such as t(1)<>s compares whether we are looking at the same object (i.e., same address in memory)—the field-wise comparison of the contents of tasks needs to be implemented differently, for instance, by a subroutine issame(t(1), s).

To summarize the above, it is possible to implement all the functionality of the task module by the means of a package, requiring less programming effort where we rely on standard Mosel features (in particular for reading/writing types) at the expense of some flexibility. However, since same functionality does not mean same way of functioning the choice of the package or the module version of the type definition makes necessary certain modifications to the Mosel model that uses the respective library.


© 2001-2025 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.