© Jørgen Steensgaard-Madsen, Copenhagen, 2006
Coding of semantic routines                                                  
 
   

Semantic routines are coded in the language accepted by the GNU C compiler. Standard C does not accept local routines (i.e. defined inside another), but GNU C does. Much of the regularity of dulce  stems from the qualities of local routines. Standard C might have been used, because techniques exist by which a programmer can take over the administration of a routine's environment to achieve the effect of local routines. However, they are best used in programs generated automatically, not written by humans.
Two possibly unfamiliar aspects of the coding discipline must be understood by contributors. One is the use of continuations the other the techniques used to implement types, and polymorphism in particular.
C programmers use a return statement to express the return from a routine and when appropriate to associate a value with the return. A continuation is a routine that is used as return with a return value, i.e. return x; might be expressed as Return(x); provided the name of the continuation is Return . A continuation will be a parameter of the routine to `return' from, but fortunately the generated external definitions will contain such parameters where they are needed. Consequently it should be easy for C programmers to adjust to the use of continuations.
Type operators are more complex to manage than continuations. A type operator has to be described by a value that can be given as an argument in a routine call. Some naming conventions should be known by contributors:
T_inf A value describing some type T
typeinf The type of a value T_inf
T_ptr x; or
T_type *x; C-representation of a value x of type T in C
Polymorphism is reflected in a semantic routines by the presence of a parameter of type typeinf, T_inf say. A macro call TYPE(T); can be used to introduce declarations of T_type and T_ptr, c.f.  Figure 2.3 , lines 24-25.
Definitions with TYPE must occur inside a routine, but the types T_ have to be used already in the parameter list. Consequently, if type T is a parameter it is given as void* before the definition in which it is a parameter, cf. lines 9-21 in Figure 2.3 .
A contributor will have to modify generated external definitions, and care is needed to manage values of unknown types. A fairly simple situation is illustrated by line 35 in Figure 2.4 : the declared type of First is effectively void*, but the intention is obviously that it should be T_ptr so a simple cast will do. This is unproblematic, if one trusts the type inference system of dulce, and if all types are implemented so that the assignment will perform as intended (for which contributors are responsible).
In Figure 2.4 , the generated lines 26-28 redefine types for parameters that can be called. The calls in lines 37-41 shows how to use these for casts, to prevent error messages in some cases (in this no errors would be reported anyhow).
A number of macros are defined in semantics.h to help with continuations. One of these is CVAL which defines (inline) a routine to be passed as a continuation that will assign a value to the variable given as second argument to CVAL along with its type as first argument.
Definition of a new type operator of arity zero can also be simple. The following is a fragment of the code generated from the signature of is_file.

i.e. the same pattern as in for_sem, only with no Attr_inf to start from.
The easy way out is to identify value Attr_inf and type Attr_ptr with int_inf and int_ptr . It is OK if contributors behave properly, but some might want to test types for equality by their typeinf values. Such tests we consider dubious, but some might want to depend on them sometimes. The proper way to introduce a new type operator is illustrated in the actual definition of the is_file-operation, cf. Figure 2.2.
Figure 2: External semantics of is_file

void is_file_sem(string_ptr Path_sem,is_file_Kind Kind_sem,
    CONTINUATION Return)
{ /* ... */
  int link=0,absent=1,directory=2,
      regular=3,socket=4,fifo=5,other=6;
  int Attr_size = 4;

  void result (Attr_ptr x){
      struct stat s; 
      int r;
      r = 0;
      if (*x) {
        if (stat(*Path_sem,&s)) {
          r = (*x==absent? errno == ENOENT : 0);
          Return(&r);
          return;
        }
        switch (*x) {
        case 2: r = S_ISDIR(s.st_mode); break;
        case 3: r = S_ISREG(s.st_mode); break;
        case 4: r = S_ISSOCK(s.st_mode); break;
        case 5: r = S_ISFIFO(s.st_mode); break;
        case 6: r = 1; break;
        }
      } else r = (lstat(*Path_sem,&s) ? 0 : S_ISLNK(s.st_mode));
      Return(&r);
  }

  ((is_file_Kind)Kind_sem)
    (&Attr_size,
     &link,&absent,&directory,&regular,&socket,&fifo,&other,
    (CONTINUATION*)result);
}

Type typeinf is in this hierarchy defined as int *, so a proper first argument to Kind_sem will be a pointer to an integer variable that holds the size of values of type Attr_type, cf. lines 5 and 29.

Semantics of operators is defined just as that for operations. The most special case is the handling of a missing operand. A semantics can test for a missing operand simply by testing whether an operand reference is null. Special macros exist to define semantics of an operator that is identical to one of C's. Readers may want to look at the file op_sem.c in the operators subdirectory of the example-hierarchy.

Contents

Demo language
·Implementation tool
Copyrights


Introduction
Principles
·Interpreter construction


Contribution directory
Make commands
·Semantics
Illustration
Auxiliary files
Toplevel files
Reference
Download
Appendices



File translated from TEX by TTH, version 3.33.
On 18 Oct 2006, 16:47.
SourceForge.net Logo