This is the 2007 Edition of the C5 programming language manual.
C5 is a superset of the C programming language developed at the Instituto de Computación (InCo). The main difference between C and C5 is that the type system of C5 supports the definition of types of dependent pairs, i.e., the type of the second member of the pair depends on the value of the first member (which is a type).
Another C5 extension is the type initialization expression which is a list of dependent pairs that can be attached to type expressions in a type declaration.
These extensions provide C5 with dynamic type inspection at run time and attribute type definition. The result is a powerful framework for generic programming.
The present edition of the C5 manual describes the version 0.98 of the C5 compiler and a set of generic libraries created by the following projects:
Like the previous editions, the 2007 Edition is a recompilation of the technical reports of the C5 projects published by the InCo-PEDECIBA.
The main differences between the 2007 Edition and the previous edition are:
This manual is mainly used by the teams of the C5 projects and the students of the courses Introducción a la Programación para Diseño Gráfico and Diseño de Compiladores of the study programs Ingeniería en Computación and Maestría en Informática of PEDECIBA.
The support and suggestions of many colleagues and students have added greatly to the developing of C5 and the pleasant writing of this manual. In particular: Gustavo Betarte, Hector Cancela, Zelmar Echegoyen, Alberto Pardo, Pablo Queirolo, Bengt Nordstriöm and Alfredo Viola.
Special thanks to the approximately 400 computer engineering students at InCo who tested the successive versions of the C5 compiler.
.
27 years ago, Inodoro Pereira 1.1 -our C programming teacher- had a dream.
He was presenting the C functions printf and scanf in the undergraduate course of programming at the Instituto de Computación at Montevideo when his famous dream came up.
'' I think that printf and scanf are poor implementations of a good idea. Their arguments are limited to atomic types without type check. In other words, the functions are unsafe and limited to a few types.
I would like to see new versions of these functions with type checking and defined for the entire C type system.
Let us see an example of my dream functions:
typedef struct NODE{ int element; struct NODE *next; } *MyType; main(){ MyType ils; scanf(" %MyType ", ils); printf(" %MyType ", ils); }
The type MyType is a recursive structure used to implement a linked list in the C language. My dream scanf reads the standard input and constructs a linked list assigned to ils, provided -of course- that the input matches with the values required by the type MyType.
My dream printf prints the integers of the linked list in the standard output. As you can see the program does not do too much; it just prints the input.
Let us suppose that there exist more dream functions:
So, we can now write a more interesting dream program:
typedef struct NODE{ int element; struct NODE *next; } *MyType; main(){ MyType ils; int i; scanf(" %MyType ", ils); for(i=occurrences(" %MyType",ils,"element")-1;i>=0;i--) printf(search_type(i," %MyType",ils,"element"), search_element(i," %MyType ", ils,"element")); }
This program prints the input in reverse order.
The point here is that the program above is not dependent of the type definition of MyType.
For instance, we can define MyType as a four element array of integer:
typedef int element; typedef element MyType[4]; main(){ MyType ils; int i; scanf(" %MyType ", ils); for(i=occurrences(" %MyType",ils,"element")-1;i>=0;i--) printf(search_type(i," %MyType",ils,"element"), search_element(i," %MyType ", ils,"element")); }
In this case, the (same) main program reads 4 integers and prints them in reverse order.
Further more, we can change the type of the elements and the program still works:
typedef char * element; /* string */ typedef element MyType[4]; main(){ MyType ils; int i; scanf(" %MyType ", ils); for(i=occurrences(" %MyType",ils,"element")-1;i>=0;i--) printf(search_type(i," %MyType",ils,"element"), search_element(i," %MyType ", ils,"element")); }
This program reads the input one two three four and prints -as we expect- four three two one .
I don't know if my dream functions are implementable in C.
However, this is my dream. ''
Inodoro Pereira left his academic career in 1980.
There is a non-confirmed version indicating that Inodoro lives in Argentina, in the country (the pampa), working with wild horses.
20 years later, a group of Inodoro's followers started the construction of C5, a real version of Inodoro's dream.
The name C5 comes from the Spanish CCinco that means The C Compiler of InCo ( InCo is a trademark of the Instituto de Computación at Montevideo, Uruguay).
Today, C5 is used by the students at InCo and the old examples of Inodoro's dream are now real programs:
/* The list of integer example */ DT_typedef struct IntL{ int element; struct IntL * {0} next; } *MyType; main(){ DPT dp; MyType obj; int i; dp= DT_pair(MyType, obj); C5_scanf(dp); for(i=C5_lenSearch(dp,"element")-1;i>=0;i--) C5_printf(C5_idxSearch(i,dp,"element")); }
/* The array of string example */ DT_typedef char * element; DT_typedef element MyType[4]; main(){ DPT dp; MyType obj; int i; dp= DT_pair(MyType, obj); C5_scanf(dp); for(i=C5_lenSearch(dp,"element")-1;i>=0;i--) C5_printf(C5_idxSearch(i,dp,"element")); }
C5 is a superset of the C programming language. The main difference between C and C5 is that the type system of C5 supports the definition of types of dependent pairs, i.e., the type of the second member of the pair depends on the value of the first member (which is a type).
The other C5 extension is the type initialization expression which is a list of dependent pairs that can be attached to type expressions in a type declaration.
These extensions provide C5 with dynamic type inspection at run time and attribute type definition. The result is a powerful framework for generic programming.
The 2007 edition of the C5 programming language manual presents the version 0.98 of the C5 compiler.
Polymorphic functions are a well known tool for developing generic programs.
For example, the function pop of the Stack ADT
A more complex and powerful way to express generic programs are the functions with dependent type arguments (i.e., the type of an argument may depend on the value of another) that perform different tasks depending on the argument type. These functions may inspect the type of the arguments at run time to select the specific task to be performed.
The C printf and scanf functions are two widely used examples of this kind of generic programs that are defined for a finite number of argument types. As we will see later, the type of these useful functions cannot be determined at compile time by a standard C compiler.
Even more powerful generic programs are achieved when we extend the finite number of argument types to the entire type system. This class of generic functions can perform different tasks depending on the argument type extending its expression power to include generic programs like parser generators (a top paradigm in generic programming).
C5 is a minimal C extension that express a wide class of generic programs where the functions C5_printf and C5_scanf presented in this paper are representative examples.
The C creators [22] warn about the consequences of the absence of type checking in the printf arguments:
'' ... printf, the most common C function with a variable number of arguments, uses information from the first argument to determine how many other arguments are present and what their types are. It fails badly if the caller does not supply enough arguments or if the types are not what the first argument says.''
Let us see through the simple example in Figure 1.1 how printf works.
The first argument of printf, called the format string, determines the type of the other two: the expressions 4s and 6.2f indicate that the type of the second argument is an array of characters while the third argument is a floating point notation number.In the case of printf and scanf, the types declared in the format string are restricted to atomic, array of character and character pointer types. There is also some numeric information together with the type declaration (4 and 6.2 in our example) that defines the printing format of the second and third arguments. These numeric expressions (attributes) will be called Type Initialization Expressions (TIEs) in C5.
A standard C compiler cannot type check statically the second and third arguments of the example presented in figure 1.1 because their types depend on the value of the first one (the format string).
In functions like printf and scanf, expressiveness is achieved at a high cost: type errors are not detected and, as a consequence unsafe code is produced.
However, some C compilers (e.g. the -Wformat option in gcc [13]) can check the consistence of the format string with the type of the arguments of printf and scanf. In this case, the format argument is a constant string (readable at compile time) and the C syntax is extended with the format string syntax.
This is not an acceptable solution of the problem because the syntax of the format string is specific for the functions printf and scanf.
A better solution can be found in Cyclone [32], a safe dialect of C. In this case, the type of the arguments of printf and scanf is a tagged union containing all of the possible types of arguments for printf or scanf. These tagged unions are constructed by the compiler (automatic tag injection) and the functions printf and scanf include the code to check at run time the type of the arguments against the format string.
Similar results can be obtained with other polymorphic disciplines in statically typed programming languages such as finite disjoint unions (e,g, Algol 68) or function overloading (e.g. C++).
This kind of solution of the printf typing problem has the following restrictions:
However, the concept of object with dynamic types or dynamics for short, introduced by Cardelli [7] [2] provides an elegant and general solution for the printf typing problem.
A dynamics is a pair of an object and its type. Cardelli also proposed the introduction in a statically typed language of a new datatype (Dynamic) whose values are such pairs and language constructs for creating a dynamic pair (dynamic) and inspecting at run time its type tag (typecase).
Figure 1.2 shows a functional program using the typecase statement where dv is a variable of type Dynamics constructed with dynamic, Nat (natural numbers) and X * Y (the set of pairs of type X and Y) are types to be matched against the type tag of dv, ++ is a concatenation operator, and fst amd snd return the first and second member of a pair.
Tagged unions or finite disjoint unions can be thought of as finite versions of Dynamics: they allow values of different types to be manipulated uniformly as elements of a tagged variant type, with the restriction that the set of variants must be fixed in advance.
C5 offers a way to embed dynamics within the C language that follows the concepts proposed by Cardelli.
The goal of the C5 language is to experiment with generic programs based on functions with dependent arguments under the following conditions:
Dynamics has been implemented in C5 as an abstract data type called DPT (Dependent Pair Type). Instead of the statement typecase there are a set of functions that construct DPT pairs, inspect the type tag and read or assign values.
Since the use of DPTs is limited to a special class of generic functions, there is a C5 statement called DT_typedef to declare valid type definitions for the DPT library.
The major difference of the DPT library with Cardelli's Dynamics is concerned with the communication between the static and the dynamic universes:
For the sake of readability, we will simplify the C type system to int, double, char , struct , union, array, pointer, defined and function types.
The following is a brief introduction to the most important functions of the DPT library:
C5_gos is not defined for atomic or function types.
If type checking is successful, C5_fapply applies the function of functionp to the n arguments of args and returns a DPT with the result value. Otherwise, the returned DPT includes error information.
The function C5_gtype yields the type class of the dynamic pair argument.
Instead of C unions, DPT functions recognize discriminated unions as a special case of the struct type.
A C5 Discriminated Union is defined as a struct with two fields where the first is an union and the second a integer. In this case, the integer field is supposed to keep the information about the current field of the union.
The function C5_isDUnion returns 1 when the type of a dynamic pair is a Discriminated Union. Otherwise returns 0.
The function C5_isFunction returns 1 if the type of the argument is a function pointer. Otherwise returns 0.
In case of type mismatch no assigning is performed and the functions return 0.
The equivalence of the DPT library with Dynamics is showed in the following program which is a C5 version of the example presented in Figure 1.2:
void typetostring(DPT dv){ switch(C5_gtype(dv)){ case INT: printf(" Int "); break;" case STRUCT: if(C5_gsize(dv)==2){ typetostring(C5_gos(dv,1)); printf(" * "); typetostring(C5_gos(dv,2)); } else printf(" ?? "); break; default: printf(" ?? "); } }
We will use DPTs to express the C5 version of printf with the form:
The program below is a first C5 approach to the C printf example presented in figure 1.1:
DT_typedef char String[5]; DT_typedef float Fnr; main(){ String st="coef"; Fnr n=42.56; c5_printf(DT_pair(String,st)); c5_printf(DT_pair(Fnr,n)); }Note that the declared types String and Fnr are the arguments of the function DT_pair.
This is not a complete version of printf because the numeric information of the format argument is absent.
The DPT library includes an ADT of DPT list and a set of atomic DPT constructors to simplify the use of DPTs:
DT_typedef int IntType; ... IntType vn=124; DT_pair(IntType,vn);
The next example presents a DPT list constructed with elements of different types:
dpcons(DT_pair(DPT,dp_Ch('A')), dpcons( dp_Do(0.57), dpcons( dp_St("Hello"), dpnil())));Note that the first element of the list is a dynamic containing a dynamic. DPT and DPTi_list are predefined types of the DPT library.
A TIE is a DPT list attached to a C5 type.
The syntax of a TIE is a comma-separated sequence of DPTs enclosed by brackets.
Constant expressions of atomic types do not need DPT constructors in a TIE declaration.
For example, the TIE { 'A', 0.57, "Hello" } is correct and is translated by the C5 compiler to
dpcons(dp_Ch('A'),dpcons(dp_Do(0.57),dpcons(dp_St("Hello"),dpnil())));This TIE declaration is equivalent to the TIE { dp_Ch('A'), dp_Do(0.57), dp_St("Hello") }.
There is a simple syntactical rule for inserting TIEs into a type declaration:
a TIE is placed on the right of the related type.
The next example shows two type definitions with TIEs:
DT_typedef int{1} Numbers[10]{2} [20]{3}; DT_typedef struct{ Numbers{4} nrs; char{5} *{6} String_ptr; }{7} Rcrd;In the first type definition, the TIE {1} is attached to an int type and the TIEs {2} and {3} are attached to a double array. In the second definition, the TIEs {4}, {5}, {6} and {7} are attached to the types Numbers, char, pointer of char and struct respectively.
TIEs can be inspected at run time using the following functions of the DPT library:
After the introduction of TIEs, the C printf example presented in figure 1.1 can be completely expressed in C5 as follows:
DT_typedef char String[5] {4}; DT_typedef float {6,2} Fnr; main(){ String st="coef"; Fnr n=42.56; c5_printf(DT_pair(String,st)); c5_printf(DT_pair(Fnr,n)); }The TIEs {4} and {6,2} are respectively attached to the array and float types. Notice that TIE declarations are optional: in this program the char type of the first type definition has no TIE.
Since C5_printf accepts type expressions (DPTs) as arguments, it is straightforward to extend the restricted argument types of C printf (strings and atomic types) to the entire C type system.
For example, the type definition with TIEs presented in Figure 1.3 is an acceptable argument for the C5_printf function.
The next program shows a simplified and verbose version of the C5_printf function defined for the int, double, char, struct, DT_typedef, pointer and array types.
void C5_printf(DPT dp){ int i; char format[100]; switch(C5_gtype(dp)){ case INT: sprintf(format,"%%%dd",C5_gTIE_int(dp,0,6)); printf(format,C5_gint(dp,0)); break; case DOUBLE: sprintf(format,"%%%d.%df",C5_gTIE_int(dp,0,6), C5_gTIE_int(dp,1,6)); printf(format,C5_gdouble(dp,0.0)); break; case CHAR: printf("%c",C5_gchar(dp,'!')); break; case STRUCT: printf("\n struct %s={ ",C5_gname(dp)); for(i=1;i<=C5_gsize(dp);i++){ printf(" "); C5_printf(C5_gos(dp,i)); } printf("}\n"); break; case ARRAY: printf("\n array %s=[ ",C5_gname(dp)); for(i=0;i<C5_gsize(dp);i++){ if(C5_gtype(C5_gos(dp,i,ErrorDp))==CHAR) if(C5_gchar(C5_gos(dp,i),'!')=='\0') break; else if(i>0) printf(" ,"); C5_printf(C5_gos(dp,i)); } printf(" ]\n"); break; case POINTER: case TYPEDEF: C5_printf(C5_gos(dp,1)); break; } }
The following C5_printf example prints an object of the type Client_Record presented in Figure 1.3:
main(){ Client_Record cr; double r=2.8672; strcpy(cr.ref,"0037731443"); cr.coef=&r; cr.client.box_nrs[0]= 1204; cr.client.box_nrs[1]= 82761; cr.client.box_nrs[2]= 464; strcpy(cr.client.name,"Carlos Gardel"); C5_printf(DT_pair(Client_Record,cr)); }
struct Client_Record={ array ref=[ 0037731443 ] 2.867 struct client={ array name=[ Carlos Gardel ] array box_nrs=[ 1204 ,82761 , 464 ] } }
There is also a C5 version of fprintf
Equality, selection or copy are type dependent in C. This means that specific operators (or functions) must be defined for each type. For instance, the * and . operators are selectors for the pointer and struct types respectively.
In this chapter, we introduce the generic functions C5_typeSeq, C5_seq, C5_lenSearch, C5_idxSearch, C5_copy and C5_newdp. The functions are members of the DPT library.
In most cases, C5 functions (e.g. C5_fapply) require structural type equality.
The notion of structural equality states that two objects are of the same type if the two objects are the same at a structural level regardless of what their type names are.
For example, in the next type definitions, the types T1 and T2 are not equally defined:
DT_typedef struct AT{ int nr1, nr2; struct{ char *String; struct AT *link; } ST; } * T1; DT_typedef int Integer; DT_typedef struct BT { Integer cod; int age; struct{ char * name; struct BT * next; } ns; } * T2;
However, they are structurally equal: struct{int,int, struct{char ptr, rec ptr}}ptr. This is the kind of equality used by C5 functions.
The structural equality of C5 types is checked by the C5_typeSeq function:
The function follows the next rules:
Appendix D presents a formal specification of the C5 structural type equality.
A standard C compiler have equality operators for constant expressions, variables of atomic types and pointers. In case of structured types like struct or arrays, the programmer must define an equality function for each defined type.
The function C5_seq is a generic function for object equality:
It returns 1 if the objects are equal, otherwise the function returns 0.
The function follows the rules:
Appendix D presents a formal specification of the C5 object equality.
The DPT library includes functions to search values by their type identifiers:
The functions follow the next searching rules:
The next program prints 3 for the input w1 w2 :
DT_typedef char * Words[2]; main(){ Words ws; printf(" len=%d\n",C5_lenSearch( C5_scanf(DT_pair(Words,ws)),"Words")); }This example shows that type names may be shared by different types.
C5_lenSearch founds first the array Words. Then, the function inspects the array elements and founds two strings that are also identified with the type name Words.
The next program reads words from the input using C5_scanf and prints them in reverse order:
DT_typedef char * {"[^ ]+"} element; DT_typedef struct IntL{ element el; struct IntL * {0} next; } *MyType; main(){ DPT dp; MyType obj; int i; dp= DT_pair(MyType, obj); C5_scanf(dp); for(i=C5_lenSearch(dp,"element")-1;i>=0;i--) C5_fprintf(stdout,C5_idxSearch(i,dp,"element")); }The input 1 two -- |||| five5 6 produces the output 6 five5 |||| -- two 1.
We can replace the linked list declaration by a matrix and the program still works:
DT_typedef char * {"[^ ]+"} element; DT_typedef element MyType[2][3]; main(){ DPT dp; MyType obj; int i; dp= DT_pair(MyType, obj); C5_scanf(dp); for(i=C5_lenSearch(dp,"element")-1;i>=0;i--) C5_fprintf(stdout,C5_idxSearch(i,dp,"element")); }Likewise the previous example the input 1 two -- |||| five5 6 produces the output 6 five5 |||| -- two 1.
The generic function C5_copy copies the values of DPT pairs:
The function is defined according to the following rules:
The C5_copy function does not allocate memory. In the case of pointers it copies reference values and in the case of static types the function supposes that the object has been previously constructed.
The function C5_newdp is specially appropriated for creating new fresh copies from a existing DPT:
This function returns a new DPT that is a copy of the argument. Note that the new DPT is not connected to a C variable. The only way to construct a new DPT connected to a C variable is by using the function DT_pair.
The next example shows a C5 program using C5_copy and C5_newdp:
DT_typedef struct { char ch; int n; } Struct; main(){ Struct st; DPT dp1, dpcopy, newdp; st.ch='A'; st.n=222; dp1= DT_pair(Struct,st); dpcopy= DT_pair(Struct,st); printf("\n dp1="); C5_fprintf(stdout,dp1); C5_copy(dpcopy, dp1); newdp= C5_newdp(dp1); st.ch='Z'; st.n=-1; printf("\n dpcopy="); C5_fprintf(stdout, dpcopy); printf("\n newdp="); C5_fprintf(stdout, newdp); }The struct variable st is assigned after the copy of dp1.
The output of the program
dp1=A 222 dpcopy=Z -1 newdp=A 222shows that the copy created with C5_newdp does not share the memory with dp1.
Functions are not first class members in the C language. It is not possible to declare a variable of function type or assign a function to variables. Instead, the C language accepts function pointers and this is the way functions are handled as objects in a C program.
Function types are also declared through pointer type definitions.
The function type in C5 transforms C functions in real first class members of the language.
In this chapter we present the function type in C5, the constructor dp_Fn, the selector C5_fapply and the generic function C5_compil.
Figure 3.1 presents a C program including the definition and construction of a function variable.
The version 0.98 (September, 2006) of the C5 compiler includes function pointers definitions for DPT construction so that the C program presented in Figure 3.1 can be expressed in C5:
DT_typedef int (* FunctionType)( int , char ); int my_func(int n, char c){ if(c=='0') return(0); else return(n); } main(){ DPT fdp; FunctionType mf; mf= & my_func; fdp= DT_pair(FunctionType, mf); C5_printf(C5_fapply(fdp, dpcons(dp_In(123),dpcons(dp_Ch('5'), dpnil())))); }Note the use of the function C5_fapply). This is the only way to use (inspect, select) a function DPT.
The function C5_fapply performs functional application in C5:
If the first argument is a function pointer, C5_fapply type checks (see the function C5_type_seq) the function against the argument list contained in the second argument of C5_fapply.
If type checking is successful, C5_fapply applies the function of
the first argument to the arguments and returns a DPT with the result
value.
Otherwise, the return DPT includes error information.
However, when the name of a function starts with c5, it is possible to construct function DPTs avoiding the DT_typedef declaration. In this case the C5 constructor dp_Fn obtains the type of the function from its signature to construct a DPT.
The constructor dp_Fn allows a compact C5 version of the C program presented in Figure 3.1:
int c5_my_func(int n, char c){ if(c=='0') return(0); else return(n); } main(){ C5_printf(C5_fapply(dp_Fn(c5_my_func), dpcons(dp_In(123),dpcons(dp_Ch('5'), dpnil())))); }
The function C5_compil is a good example to show the use of C5_fapply in generic programs.
The function is a generic translation program. The translation rules required by C5_compil are provided by the TIEs of the argument of C5_compil. In other words, the object to be translated carries its own translation rules.
If the type of a dynamic pair is atomic ( int, char,double, char * or array of char) or its type name starts with "Token", C5_compil returns its argument without evaluation. Otherwise, the dynamic pair is evaluated as follows:
The specification and a simplified version of the C5 source code of C5_compil can be found in Appendix F.
The examples presented below show the use of C5_compil and C5 functions.
The following example shows how C5_compil is used to select a certain value of a data structure. The TIE id selects the second field of the structure and the TIE {1} selects the second element of the array.
DT_typedef struct{ char {'<'} l; char *id; char {'>'} g; } {"id"} IdExp[2] {1}; main(){ IdExp ie; C5_printf(C5_compil(C5_scanf(DT_pair(IdExp,ie)))); }
The program returns "two" for the input < one > < two >.
The next example shows how C5_compil uses a c5 function:
int c5_add(int number, int recProd){ return(number + recProd);} DT_typedef struct IntL{ int number; struct{ union{ emptyProd {0} nil; struct IntL *next; } UU; int discriminator; } recProd; } {dp_Fn(c5_add),"number","recProd"} *Word_List; main(){ Int_List nrls; C5_printf(C5_compil(C5_scanf(DT_pair(Int_List,nrls)))); }Note that the functional dynamic pair is constructed with dp_Fn and the arguments of the function are the fields number and recProd of the structure Int_List.
The TIE {0} in emptyProd {0} nil; forces C5_compil to return 0 when detecting an empty list.
C5_printf is the C5 generic print function and C5_scanf is a scanner that interprets the dynamic type of the argument as the grammar for parsing the standard input and, if the parsing is successful, the object member of the argument pair is constructed according to the input.
C5_scanf constructs a list of integers, C5_compil computes the sum of the list which is printed by C5_printf. For example, the input 11 22 33 produces the output 66.
The following example changes the type of the linked list of the previous example to word and the first argument of c5_add to the constant 1. .
int c5_add(int one, int recProd){ return(one + recProd); } DT_typedef struct WordL{ char * word; struct{ union{ emptyProd {0} nil; struct WordL *next; } UU; int discriminator; } recProd; } {dp_Fn(c5_add), 1 ,"recProd"} *Word_List; main(){ Word_List wls; C5_printf(C5_compil(C5_scanf(DT_pair(Word_List,wls)))); }For example, if the input of this program is one two three, it returns 3.
The next example applies the simple c5_reverse function to the input list to print it in reverse order.
char * c5_reverse(char *word, char *recProd){ printf("%s ",word); return(recProd); } DT_typedef struct WordL{ char *word; struct{ union{ emptyProd {""} nil; struct WordL *next; } UU; int discriminator; } recProd; } {dp_Fn(c5_reverse),"word","recProd"} *Word_List; main(){ Word_List wls; C5_compil(C5_scanf(DT_pair(Word_List,wls))); }For example, if the input of this program is one two three, it prints three two one.
Generic functions in C5 are powerful enough to express a parser generator in the Yacc style [21].
Yacc and Lex are software tools used to generate a parser in C code that can be compiled together with other C programs.
C5 has the required expressiveness to include a parser generator in the DPT Library making the creation of a parser transparent for the C5 programmer.
In this chapter we introduce the parsing functions C5_scanf and C5_fscanf.
The scanf function of the C language scans input according to the format string argument which specifies the type and conversion rules of the other arguments. The types specified in the format argument are restricted to (references to) atomic and string types. The results from these conversions are stored in the arguments of the function.
As we did with printf, we introduce a generic version of scanf in C5:
C5_scanf interprets the dynamic type of the argument as the grammar for parsing the input and, if the parsing is successful, the object member of the argument pair is constructed accordingly to the input. If the input cannot be parsed, C5_scanf returns a dependent pair with information about the error.
The resulting program includes a parser generator that can be compared with Yacc [21] and a scanner like Lex [24].
There is also a counterpart of C fscanf in C5:
We introduce the C5_scanf function by first explaining the lexical meaning of the C types that belong to the lexical analyzer and then the grammatical meaning of the types related to the syntax analyzer.
Atomic and string types are the lexical or token elements of C5_scanf. The actual version of C5_scanf accepts the following lexical types: int, double, char, character pointer and array of characters.
These types are interpreted in C5_scanf as follows:
C5_scanf uses token type declarations to construct a regular expression table (in the Lex style) with the following order:
In case of ambiguous specifications, C5_scanf chooses the longest match. If there are more than one RE matching the same number of characters, the RE found first in the table is selected.
The example below shows how a string can be scanned according to the RE [AB]+:
DT_typedef char * {"[AB]+"} AB; main(){ AB ab; addComment("/*","*/"); C5_printf(C5_scanf(DT_pair(AB,ab))); }The function addComment enables comments with the declared start and ending strings. The program accepts the following input
AABBBAAAA /* A C5_scanf example */and the output will be
"AABBBAAAA"The next input string
AA12xy /* this string is not acceptable by the scanner */cannot be parsed and therefore the output is an error message:
struct ErrorMessage={ "Syntax error" struct near_at_line={ "AA" 1 } }
The types with a syntactic meaning in C5_scanf are: structures, arrays (array of char is excluded), type definitions , discriminated unions, pointers (char pointer is excluded) and recursive declarations.
A struct or an array type is a sequence of syntactic or lexical types. The set of strings accepted by this grammar (type) is the cartesian product
The set of strings accepted by pointer and definition types are the same than the referenced and the defined type respectively.
The next program shows a type (grammar) that includes the structured, pointer and defined types:
DT_typedef double Real; DT_typedef struct{ int n; Real r; } *IntReal[2]; main(){ IntReal ir; C5_printf(C5_scanf(DT_pair(IntReal,ir))); }For example, the string "123 0.432 21 0.55" is an acceptable input for this program.
C unions cannot be used to express alternative grammars because they are not discriminated, that is, the compiler does not know which field of the union is currently stored.
By convention, we will represent alternative grammars in C5_scanf
by the following type:
where
are the fields of the union and
the integer field is called the union discriminator and is supposed to
keep the information about the
current field of the union. Thus, the discriminator field has no grammatical
meaning.
The discriminated union type represents in C5_scanf the
union of the sets of strings accepted by the fields (grammars)
.
The concept of empty rule is implemented in two ways:
The empty rule is a special field in discriminated unions. the empty fiels is a nullable token called emptyProd which is defined as follows:
DT_typedef char {'\0'} emptyProd;
The empty rule is an alternative in fields of recursive pointer including the TIE {0}:
struct NODE * {0} next;If the empty rule is matched, the pointer is assigned with the standard C NULL value.
Recursive type declarations of discriminated unions allow us to express unbounded sets of strings.
For example, the program below accepts sequences of numbers and the constructed object will be a linked list of integers:
DT_typedef struct IntL{ union{ int n; struct{ struct IntL *next; int n; } RecProd; } UU; int discriminator; } * Int_List; main(){ Int_List il; C5_printf(C5_scanf(DT_pair(Int_List,il))); }Another version of this program can be done with the standard C declarations of linked lists:
DT_typedef struct IntL{ int n; struct{ struct IntL * {0} next; int n; } RecProd; } * Int_List; main(){ Int_List il; C5_printf(C5_scanf(DT_pair(Int_List,il))); }Note the TIE {0} in the recursive pointer. In this case C5_scanf uses the hidden discriminated union of the C NULL value for null pointers.
In most parser generators, grammars are expressed in BNF (Backus-Naur notation) or EBNF (Extended BNF).
The following example is a BNF grammar in Yacc syntax:
exp : NUMBER | exp '+' exp ;where exp is a nonterminal symbol and NUMBER and '+' are terminals (tokens). In C5_scanf, this BNF grammar can be expressed by the next type declaration:
DT_typedef struct EXP{ union{ int number; struct{ struct EXP *e1; char{'+'} pl; struct EXP *e2; } RecP; } UU; int discriminator; } *exp;
The algorithm of the C5_scanf parser generator is an implementation of the
Earley algorithm [8] with a lookahead of .
This algorithm is a chart-based top-down parser that accepts
the complete set of context free grammar
(CFG) and avoids the left-recursion problem.
The algorithm runs in time order
where
is the number of
symbols to be parsed.
The algorithm has been modified to construct an object of the type that represents the grammar. This is done by programming the recognizer so that it builds an object after the recognition process.
C5_scanf will produce parsers even in the presence of conflicts. There are some disambiguating rules in the Yacc style. For instance, the if-else and the arithmetic expression conflicts are solved in C5_scanf.
The program below is an example of the if-else conflict in C5_scanf:
DT_typedef char Else[5] {'e','l','s','e'}; DT_typedef char If[3] {'i','f'}; DT_typedef struct IFE{ union{ char {'e'} exp; struct{ If i; struct IFE *e; } If_stmt; struct{ If i; struct IFE *e1; Else s; struct IFE *e2;} If_Else_stmt; } UU; int discriminator; } * Stat; main(){ Stat il; C5_printf(C5_scanf(DT_pair(Stat,il))); }The input if if e else e produces two possible outputs for the same input if (if e else e) and if (if e) else e.
The ambiguity is detected by C5_scanf returning a diagnostic message:
C5_scanf: Disc. union "Stat" ambiguous in field 3 "If_Else_stmt" and field 2 "If_stmt". Suggestion: attach an int TIE to the "Stat" discriminator specifying the preferred alternative ({3} or {2}).If we attach the TIE {2} to the discriminator field of Stat then the ambiguity is solved and the output will be
struct If_stmt={ array If=[ if ] d_union Stat={ struct If_Else_stmt={ array If=[ if ] d_union Stat={ e} array Else=[ else ] d_union Stat={ e} } } }
The next token declaration in Yacc:
%left '+' '-' %left '*' '/'describes the precedence and associativityi rules of the four arithmetic operators. The four tokens are left associative, and plus and minus have lower precedence than star and slash.
The next type declaration is the C5_scanf version of the above Yacc token declaration:
DT_typedef char {'+'} PLUS; DT_typedef char {'-'} MINUS; DT_typedef char {'*'} TIMES; DT_typedef char {'/'} DIV; DT_typedef PLUS {LeftAss, 1} Plus; DT_typedef MINUS {LeftAss, 1} Minus; DT_typedef DIV {LeftAss, 2} Div; DT_typedef TIMES {LeftAss, 2} Times;These disambiguating rules are declared in TIEs attached to type definitions related to token (or lexical) types. The first and second members of the TIE are respectively the associative and precedence rules.
Semantic actions in the Yacc style are implemented with the function C5_compil.
The nexr program shows the use of two action TIEs in a simple grammar:
DT_typedef struct{ char {'<'} l; char *id; char {'>'} r; } {"id"} IdExp[2] {1}; main(){ IdExp ie; C5_printf(C5_compil(C5_scanf(DT_pair(IdExp,ie)))); }The TIE {1} selects the second element of the array and { "id" } selects the second field of the structure.
For example, this program accepts the string " < one > < two > " and the output is "two".
The following C5 programs are three motivating examples that illustrate the use of the C5_scanf function.
The example below prints an element of a matrix constructed
by C5_scanf:
DT_typedef int Matrix[2][3]; main(){ Matrix mtx; if(C5_scanfError(C5_scanf(DT_pair(Matrix, mtx))) printf("Cannot read the matrix.\n"); else printf("mtx[1][2]=%d\n",mtx[1][2]); }Notice the way the variable mtx is used to communicate the dynamic and the static universes. This is an useful programming methodology in C5: the user constructs an object in the dynamic universe which is processed in the static universe.
The next program shows a desk calculator that includes associative and precedence rules to avoid ambiguous grammars:
/* Tokens */ DT_typedef char {'+'} PLUS; DT_typedef char {'-'} MINUS; DT_typedef char {'*'} TIMES; DT_typedef char {'/'} DIV; /* C5 functions */ DT_typedef int Number; Number c5_Add( Number a, Number b){ return(a+b); } Number c5_Mul( Number a, Number b){ return(a*b); } Number c5_Dvd( Number a, Number b){ return(a/b); } Number c5_Sub( Number a, Number b){ return(a-b); } Number c5_Umi( Number a ){ return(-a); } /* The grammar and semantic actions */ #define Ae_ struct Aexp * DT_typedef struct Aexp{ union{ Number number; struct{ char {'('} lp; Ae_ e; char {')'} rp;} {"e"} parProd; struct{ Ae_ e1; PLUS {LeftAss, 2} add; Ae_ r1;} {dp_Fn(c5_Add),"e1","r1"} addProd; struct{ Ae_ e2; TIMES {LeftAss, 3} times; Ae_ r2;} {dp_Fn(c5_Mul),"e2","r2"} mulProd; struct{ Ae_ e3; MINUS {LeftAss, 2} minus; Ae_ r3;} {dp_Fn(c5_Sub),"e3","r3"} subProd; struct{ Ae_ e4; DIV {LeftAss, 3} div; Ae_ r4;} {dp_Fn(c5_Dvd),"e4","r4"} dvdProd; struct{ MINUS {LeftAss, 4} um; Ae_ e; } {dp_Fn(c5_Umi),"e"} uminusProd; } uu; int disc; } *AritihmeticExp; main(){ AritihmeticExp aexp; addComment("||","\n"); C5_printf(C5_compil(C5_scanf(DT_pair(AritihmeticExp,aexp)))); }For example, this calculator accepts the input 10 + 2 * 4 / - 2 - 2 and produces the output 4.
The example below shows a partial and simplified version of a well-formed XML document checker.
DT_typedef char *{"[^<&>]+"} charD; DT_typedef struct{ char {'<'} l; char *id; char {'>'} r; } {"id"} STag; DT_typedef struct{ char l[3] {"</"}; char *id; char {'>'} r;} {"id"} ETag; DT_typedef struct{ char {'<'} l; char *id; char r[3]{"/>"};} EmptyElemTag; DT_typedef struct{ union{ charD chd; char * id; } UU; int discriminator; } CharData; DT_typedef struct CharDL{ union{ emptyProd nil; struct{ struct CharDL *c; CharData cd;} CDls; } DU; int discriminator; } *CharDataList; DT_typedef struct{ CharDataList cdl; struct XML_EL_LS *els; } {"els"} XMLcontent; int c5_cmpstr( char * s1, char * s2, int rec){ if(strcmp(s1,s2)) printf("Error: incorrect nested tags %s %s.\n",s1,s2); return(rec); } DT_typedef struct XML_EL{ union{ EmptyElemTag eet; struct{ STag start; XMLcontent c; ETag end; } {dp_Fn(c5_cmpstr),"start","end","c"} elem; } DU; int discriminator; } *XMLelement; DT_typedef struct XML_EL_LS{ union{ emptyProd {0} nil; struct{ struct XML_EL_LS *next; struct XML_EL *el;} {"next","el"} els; } DU; int discriminator; } *XMLelementList; main(){ XMLelement xmldoc; C5_compil(C5_scanf(DT_pair(XMLelement,xmldoc))); }This program accepts the following XML document
<message> <to>juanma@adinet.com</to> <from>marcos@adinet.com</from> <subject>XML test </subject> <text> --Can you check this with C5_scanf? ... </text> </message>However, it rejects this input text with incorrect nested tags:
<message> <subject> XML test of nested tags. </message> </subject>Notice that in the case of a successful check, the variable xmldoc contains a structured XML document that can easily be inspected or processed.
Most parsers in use today are based on efficient linear-time algorithms that accept a subset of CFGs (LL,LR or LALR) [21].
The primary objection to the Earley's algorithm is not functionality but with its run-time response.
Nevertheless, the practical use of Earley parsing has become an interesting alternative in the last years: Accent [10] is the first Earley parser generator along the lines of Yacc and DEEP [20] is an efficient directly-executable Earley parsing.
Finally, we did not found parser generators that accept grammars denoted with C types to produce transparent parsing as C5_scanf does.
The generic function C5_scanf show that a static typed language extended with DPTs (dynamics) and TIEs can be powerful enough to express a wide class of generic functions in a straightforward, compact and safe way.
The improvements of version 0.98 of the C5 compiler have transformed C5_scanf, C5_compil, C5_lenSearch and C5_idxSearch in a powerful parsing framework.
The most remarkable property of C5_scanf is the transparent parsing. The programmer just need to define a type and the parsing result is an object of that type. Then, the resulting object can be processed using the C5_compil,C5_lenSearch and C5_idxSearch functions.
When a C library is presented, we usually expect the syntactical and semantical description of a set of functions.
In C5, we can introduce a library by describing the meaning of types related to a certain task. The most remarkable property of this kind of C5 libraries is that we can use the library by doing type declarations instead of function callings. This is an important change of the programming methodology: type declarations can now be a very expressive member of a program. Furthermore, as we will see later, a type declaration can be the main code of a program.
In this chapter 5.1, we start presenting the OPM machine, a small graphics library based on the art concepts of the Uruguayan painter Joaquín Torres García.
Finally, we show how the OPM machine is used by the generic function opm_image_cons to achieve a powerful page-description language.
The Oriented Port Machine (OPM)is a constructive graphic machine based on the color plane concept of the Uruguayan painter Joaquín Torres García (1874-1949).
At the same time, the graphic representation rules of the image constructor opm_image_cons were designed inspired by the Constructive Universalism of the Uruguayan painter.
Torres García has proposed an art conception that stands out for understand the constructive painting like a symbols structure. [25]
He produced an art movement based on two concepts:
Torres García created a constructive painting based on a composition (structure) of rectangles (color planes) and ideograms.
A constructive imaging model, following Torres García ideas, can be presented in this way:
Continue this structuring process until the desired image is obtained.
This imaging conception -taking color rectangle structures as
basic graphic objects- unifies
the foreground-background duality; the classical duality of the
painting model:
``In the unity of the composition, the idea of
thing and background should disappear. ... Then, there are not the thing and
the background, all is thing and all is background.''[11].
And when this duality disappear, the duality point-plane of the graphic machine disappear too, producing an important change: it is possible to design abstract graphic machines with independence of the pixel machine.
Torres García's concept of color plane has inspired the oriented port machine.
This concept of port is a generalization (unification) of the traditional concepts of port and pixel in Computer Graphics.
In Torres García's paintings we can find structures, ideograms and color planes. In C5, these concepts are implemented by type expressions with TIEs, DPTs and oriented ports.
An oriented port is either a null port or
a port representing a rectangular region of the .
The attributes of an oriented port are the coordinates,
the color list and the orientation of the rectangular region.
There are four different orientations:
,
,
and
.
The current color of a non-null port is the first element of the color list.
If the color list is null then the current color is
.
The oriented port machine is a C library based on the Port_List abstract data type:
The color and orientation of the resulting ports are taken from the corresponding lp1 ports. If the intersection is a line, a point or empty, then a null port is constructed.
The ports are rotated r times according to
the rotation rules for port orientation:
,
,
and
.
If n and i belongs to the range {1,n}
then the function
Finally, in case of n,
the function returns a port equal to p for all value of i.
If f
then
the function Port partition(float f, Port p)
divides
the port p into two rectangles
proportionally to the
floating number
and returns a port representing
the first sub-rectangle.
The orientation and color information are taken from port p. Figure 5.3 shows the four different results depending on the four possible orientations of p (f
The function returns a null port if f and a port
equal to p
if f
.
The function Port drop_color(int n, Port p) yields a port
with the p color list
discarding the first n elements if n.
The other attributes of the returned port are equal to those of p.
Figure 5.4 shows a C program using the opm library. The function opm_cat concatenates two port lists and opm_print prints the graphic representation of the port list argument. The function scale is defined for scaling the letter a in the upper left and lower right corners.
The result of this example is an image (see Figure 5.5) with two black a letters in a gray background.
Since a detailed description of the C5 Graphics Library is out of the scope of this chapter, we will concentrate our attention in the most important function of the library:
In case of
st , the function returns a port list
equal to pl.
The intersections and rotations are implemented with opm_inters and opm_rot respectively.
The next C5 program is a short example using a structure of an integer and a floating point number:
DT_typedef int {0,2} Int_nr; DT_typedef double {0.0,1.0} Double_nr; DT_typedef struct{ Int_nr n; Double_nr x; } {1} Struct_nx; main(){ Int_nr n=1; Double_nr x=0.6; Struct_nx nxs; nxs.n=n; nxs.x=x; opm_print(opm_image_cons(DT_pair(Int_nr,n), opm_page(Gray85,NULL))); opm_print(opm_image_cons(DT_pair(Double_nr,x), opm_rot(1,opm_page(Gray85,NULL))); opm_print(opm_image_cons(DT_pair(Struct_nx,nxs), opm_page(Black,NULL))); }Notice that Int_nr and Double_nr are printed in gray while the struct Struct_nx is represented in black. This makes easier to see that the struct is the intersection of the representation of n and a
If m and n belong to the range {0,Max-1} then the elements of the array are represented according to the following rule: the ith element of the array is graphically represented on the port list constructed by
Since C unions are not discriminated , the function opm_image_cons accepts a struct declaration with two fields, where the first is an union and the second an integer, like a discriminated union. In this case, the integer field is supposed to keep the information about the current field of the union. The discriminated union with TIE is the way to express in C5 the color structure of images.
A detailed version of the C5 Graphics Library including the specification of opm_image_cons can be found in Appendix C.
Figure 5.7 shows a C5 version of the opm example presented in figure 5.4.
The most relevant difference between both programs is that what the C5 program really do is mainly specified (programmed) in three DT_typedef declarations, while the rest of the program itself deals with the variables pts and scs construction and the image printing. Furthermore, the type definition Scale_2 is enough expressive to substitute for the function scale of the C program.
The C5 Graphics Library transforms C5 in a high level page-description language, i.e., a language capable of describing the appearance of text, graphic shapes and images on a page. The use of DPTs and TIEs increase the abstraction level of the language producing a readable and compact code for graphics programs.
The image presented in figure 5.8 is generated by a 5 Kb C5 source program.
Some statistics will help us to show why this library produce such a compact code:
The program uses twelve graphics functions of the Standard Output Library in 103305 callings where 92 of them are invoked explicitly in the program and the others 103213 are called implicitly through 15 invocations of opm_image_cons which is the only Standard Output Library function with a DPT argument. Seven of these callings answer for the font construction involving 60% of the total quantity of callings.
Eight DT_typedef declarations were required by the 15 opm_image_cons invocations, remarking that types with TIEs are the heart of the design of programs that use this kind of libraries.
Finally, C5 translates this program into a 22 Kb C code which produces, when compiled and executed, a 3.1 Mb PostScript [3] file.
Constructive methodologies are not new in Computer Graphics. Constructive Solid Geometry (CSG) has been widely used in 3D Solid Modeling. The main idea in CSG is to describe a solid object as a composition of primitive objects (cylinders, spheres, cubes) combined with Boolean set operators such as union, intersection and difference. An objet is stored as a tree with operators at the internal nodes and simple primitives at the leaves [14] [1] .
Although CSG is a simple and compact way of representing solids that induces to a constructive thinking when defining a 3D object, it is neither a constructive programming language nor a formal system with well-known properties.
There are also several approaches to express pictures with structured datatypes and functional programming producing low-cost prototypes that are easy-to-use for non expert graphics programmers [27] [9] [31].
The main difference with the C5 Graphics Library is that the graphics functions of these packages are based on the painting model. In other words, they define the line as a primutuve graphics function.
These approaches are an interesting innovation from the programming methodologies side but the lackness of a coherent imaging or page-description model reduced them to a friendly interface of other standard graphics packages or page-description languages like PostCript.
The results of the experimentation with the C5 Graphics Library indicates that:
Let us begin with a quick introduction to the C5 Graphics Library. Our aim is to show the use of DPTs and TIEs in real programs, but without getting bogged down in details, formal rules or theoretical concepts.
The first program to write is the same for all languages:
#define Max_Char 40 #define Str_TIE {0,0,Max_Char-1} DT_typedef char Arial String[Max_Char] Str_TIE; main(){ String str="HELLO WORLD"; opm_print(opm_image_cons(DT_pair(String,str), opm_page(Black,NULL))); }and the resulting image is showed in figure 6.1. The type definition has two TIEs: Arial and Str_TIE. The first is attached to the char type specifying the font to be used when a character is printed and the second to the array type. The array TIE is a three integer expression with the following form:
In the body of the main function, the function opm_page is a page constructor whose arguments are the colors (see apendix A) required by the image to be printed on that page. When the page is created , the current color is specified by the first argument (Black in our program ). The type of the range of this function is Port_List. This is the type of the second argument of opm_image_cons -the case-type function of the library- and the argument of opm_print too. Notice that pages and images are represented by the same data structure.
The C printf translates integer numbers to a digit character sequence starting with the minus character if the number is a negative integer.
Instead of this character oriented translation, the image constructor
opm_image_cons
represents integer numbers by simple geometric images based on
color rectangles.
Let us see a short C5 program that prints the number 2 for a quick understanding of the way C5 produce the graphic representation of an integer:
DT_typedef int {0,3} intnr; main(){ intnr n=2; opm_print(opm_page(Gray85,NULL)); /* A gray background */ opm_print(opm_image_cons(DT_pair(intnr,n), opm_page(Black,NULL))); }The int TIE is a two integer sequence with the following form:
In our program the TIE values {0,3} mean that the integers 0, 1, 2 and 3 are visible for printing.
In this case, the page is virtually divided in four vertical rectangles. Starting from the left side of the page , the first rectangle represents the number 0, the second the number 1, the third the number 2 and the last rectangle the number 3. Accordingly, when printing the number 2, opm_print will print the third rectangle painted in black.
Figure 6.2 shows the resulting page.
What would this program do if we try to print a number outside the range {0,3}? Suppose we have the number 11 for printing. Just the gray background will be printed because 11 is not a visible number in this range.
There is a way to express the range {
}
by declaring a int TIE of the form
where
.
For example, the TIE
specifies that
all the integer numbers are visible.
The graphic representation
of an integer with infinite visibility is the complete page
painted with the current color.
A visible floating point number is graphically represented by the first (left) rectangle of a proportional partition of the page.
A program that prints the number 2.5 shows how C5 produce the graphic representation of floating point numbers:
DT_typedef double {0.0,4.0} float_nr; main(){ float_nr f=2.5; opm_print(opm_page(Gray85,NULL)); /* A gray background */ opm_print(opm_image_cons(DT_pair(float_nr,f), opm_page(Black,NULL))); }The double or float type TIE is a two floating point number sequence with the following form:
In our program the TIE values {0.0,4.0}
mean that a floating point number f is visible if
f and f
. In this case,
the page is partitioned in two rectangles with a f dependent size.
The graphic representation of f is the left rectangle painted
with the current color.
If
the graphic representation will be
the complete page painted with the current color and if
no action is produced and the resulting image is the
page painted with the background color.
Figure 6.3 shows the resulting page.
The type struct is represented by the intersection of its fields rotated with an angle determined by the field index and the struct TIE.
Let be the following struct declaration of fields:
The next program shows how the graphic representation of the type struct is generated by opm_image_cons:
DT_typedef int {0,2} int_nr; DT_typedef double {0.0,1.0} double_nr; DT_typedef struct{ int_nr n; double_nr x; } {1} nx_Struct; main(){ int_nr n=1; double_nr x=0.6; nx_Struct nxs; nxs.n=n; nxs.x=x; opm_print(opm_image_cons(DT_pair(int_nr,n), opm_page(Gray85,NULL))); opm_print(opm_image_cons(DT_pair(double_nr,x), opm_rot(1,opm_page(Gray85,NULL))); opm_print(opm_image_cons(DT_pair(Struct_nx,nxs), opm_page(Black,NULL))); }Notice that int_nr and double_nr are printed in gray while the struct Struct_nx is represented in black. This makes easier to see that the struct is the intersection of the representation of n and a
The struct type with TIEs is an expressive programming resource of the C5 Graphics Library. The code of opm_scale illustrates how a member of this library has been programmed:
DT_typedef struct{ double {0.0,1.0} x2,y1,x1,y2; } {1} dp2; Port_List opm_scale(double left, double right, double up, double down, Port_List pl){ dp2 margs; margs.x1= left ; margs.x2= right; margs.y2= up; margs.y1= down; if(pl==NULL) return(NULL); else return(opm_cat( opm_image_cons(DT_pair(dp2,margs), opm_cons(opm_hd(pl),NULL)), opm_scale(left,right,up,down,opm_tl(pl)))); }The function opm_cat is the concatenation operator for port lists and the functions opm_hd and opm_tl return the head and the tail of a port list respectively.
Let us look closer at the struct declaration because there are
two interesting
things to note here.
First,
since the rectangles representing the variables
and
are
rotated 0, 1, 2 and 3 times
respectively,
the intersection produced by the struct dp2
will be a scaled rectangle defined by the values
of the x2,y1,x1 and y2 variables.
Second, what the function opm_scale really do
is mainly specified (programmed)
in the struct declaration while the body of the
function itself deals with the margs variable assigning and
the port list
pl recursive handling.
In the next program the function is visualized using an array
of floating point numbers. This example is interesting because it shows
the graphic power of this type
for function visualization:
#define Max 100 DT_typedef double {-1.0,1.0} func_visual[Max] {3,0,Max-1}; main(){ func_visual fn; double rn=0.0; int i; for(i=0;i<Max;i++){ fn[i]= sin(rn); rn= rn + 2.0*M_PI/Max; /* M_PI=3.1416... */ } opm_print(opm_page(Gray85,NULL)); /* A gray background */ opm_print(opm_image_cons(DT_pair(func_visual,fn), opm_page(Black,NULL))); }The array TIE specifies that all the array elements are visible and will be rotated
Figure 6.5 shows the function visualization.
As we did for the integer TIE, it is possible to define an infinite range TIE for arrays.
We call the Set Mode representation to an array declaration with TIEs of the form
In this case , the elements of the array are represented directly on the page following the order indexed by the array starting from 0.
The next program shows the Set Mode representation in an array declaration of integer structures:
DT_typedef struct{ int {0,6} y; int {0,4} x; } ccoords; DT_typedef ccoords point_set[15] {0,1,0}; point_set pts={ { 6, 1}, { 6, 2}, { 5, 0}, { 5, 3}, { 4, 3}, { 3, 1}, { 3, 2}, { 3, 3}, { 2, 0}, { 2, 3}, { 1, 0}, { 1, 3}, { 0, 1}, { 0, 2}, { 0, 4} }; main(){ opm_print(opm_page(Gray85,NULL)); opm_print(opm_image_cons(DT_pair(point_set,pts), opm_rot(3,opm_page(Black,NULL))); }This is an important array declaration because it simulates a two dimensional Cartesian Coordinate System. In the program, the points are pairs of integers where the first element is the Y axis coordinate and the second, the X axis. Notice again that the kernel of the program is the type declaration.
The resulting image is a 15 points Cartesian representation of the letter a.
Matrix representation is obtained in C5 by the double array
declaration with finite range TIEs.
The following program produce the same output than the previous
but now the letter a is represented by a matrix:
DT_typedef int {1,1} matrix[5] {0,0,4} [7] {3,0,6}; matrix mtx={ 0,1,1,0,0, 1,0,0,1,0, 0,0,0,1,0, 0,1,1,1,0, 1,0,0,1,0, 1,0,0,1,0, 0,1,1,0,1 }; main(){ opm_print(opm_page(Gray85,NULL)); opm_print(opm_image_cons(DT_pair(matrix,mtx), opm_rot(1,opm_page(Black,NULL)))); }There are some interesting details here. First, since the integer TIE range is
C Unions are not interesting for C5 programs because there is no way to know the current field of an union.
Instead of this kind of union , C5 recognizes discriminated unions as a special case of the struct type.
A struct declaration with two fields where the first is an union and the second is an integer is recognized as a discriminated union. In this case, the integer field is supposed to keep the information about the current field of the union. The discriminated union with TIEs is the way we express the color structure of the images printed by opm_printf.
The graphic representation of the discriminated union follows the struct rules and the representation of the union field is the representation of the current field painted with a color determined by the union TIE and the place of the field.
The form of a discriminated union is:
where
are the fields of the union,
and
is an integer number defining the color factor of the union.
The current color of the field of the union is
the
element of the
color list
of the page.
The next program shows a discriminated union example:
DT_typedef struct{ union{ int {0,9} foreground; double {0.0,1.0} background; } {2} cu; /* the color factor is 2 */ int {1,0} idx; } disc_union ; main(){ Port_List pl=opm_page(Black,Red,Gray85,NULL); disc_union du1,du2; du1.idx=1; du1.cu.background=0.75; /* Gray85 */ du2.idx=0; du2.cu.foreground=4; /* Black */ opm_print(opm_image_cons(DT_pair(disc_union,du1),pl)); opm_print(opm_image_cons(DT_pair(disc_union,du2),pl)); }The variable foreground -the first field of the union cu- is printed with the color Black because the equation
The output of opm_print is presented in Figure 6.7.
The rule for the graphic representation of a pointer type declaration is
the representation of the pointed object with
a rotation specified by the pointer TIE. The default TIE for pointers
is .
If the referenced object is a char, C5 will try to represent a
NULL terminated string likewise an array of characters.
The form of a pointer TIE is { rotation }
where is an integer.
In the C language, recursive type declarations are expressed by a struct declaration including a field with a pointer to itself. This kind of recursive declarations is required when implementing lists, trees or any other dynamic data structure.
When C5 recognizes a recursive struct declaration, it represents the objects of this type in Set Mode, i.e., the fields of the struct are printed in an ordered sequence, discarding the intersection operator that is applied in non recursive struct declarations.
The program below shows a recursive type declaration for implementing a list of points:
DT_typedef struct{ double {-500.0,10000.0} y; int {-100,100} x; } point; DT_typedef struct NODE{ point pt; struct NODE *next; } {0} *node_list; DT_typedef struct NODE * {3} Node_List; node_list ucons(double y, int x, node_list l){ node_list p; p= (node_list) malloc(sizeof(struct NODE)); p->pt.y=y; p->pt.x=x; p->next=l; return(p); } main(){ Node_List nl=NULL; int x; for(x=-100;x<=100;x++) nl=ucons((double) x*x,x,nl); opm_print(opm_page(Gray55,NULL)); opm_print(opm_image_cons(DT_pair(Node_List,nl), opm_page(Black,NULL))); }The point struct produce the intersection of y and x while the struct NODE is recursive and therefore it generates the image of the fields without intersections.
In order to keep the y and x coordinates vertical and
horizontal respectively, the pointer Node_List is declared
including a TIE that
specifies a rotation
.
The visualization of the function is predented in Figure 6.8.
Dynamic data structures like lists or binary trees with nodes including unions are natural implementations for color expressions in C5. As a good example, let us write the type declaration of Color_Serie which is the output type of the function opm_colors, the color expression constructor of the C5 Graphics Library.
DT_typedef struct OPMTON{ dp2 scale; struct{ /* discr union */ union{ int bg; struct OPMTON *next; } un; int{1,0} idx; /* infinite range */ } du; }{0} *Color_Serie;The next program shows how color tones are structured with text using opm_colors:
DT_typedef struct{ Color_Serie * {3} c; char Antique_Draft_S string[20] {0,0,19}; } {0} Color_String; main(){ Port_List lp1,lp2; Color_String cst; Color_Serie obj=opm_colors(4,2*TONES_NR,1.0,0.0); cst.c=&obj; strcpy(cst.string,"AB"); lp1=opm_page(opm_col2col( White, Gray50, TONES_NR), opm_col2col(Gray50, White, TONES_NR), NULL); lp2=opm_page(opm_col2col( Black, White, TONES_NR), opm_col2col( White, Black, TONES_NR), NULL); opm_print(opm_image_cons(DT_pair(Color_Serie,obj), opm_scale(0.75,0.75,0.90,0.90,opm_rot(1,lp1)))); opm_printf(opm_image_cons(DT_pair(Color_String,cst),lp2)); }The function opm_col2col is a compressed notation for color series. For example, the color serie denoted by
Figure 6.9 shows the output of opm_print .
Type definitions may include type declarations previously defined. In this case, the TIE is of the form:
The rule for representing enumerations is quite simple. Let be the type declaration
DT_typedef int {0,n} enum_index;
The first version of the C5 compiler was developed in 1999 at the Instituto de Computación (InCo) , Montevideo, Uruguay. The compiler translates C5 programs into C code.
In this chapter, we present the version 0.98 of the C5 compiler (September, 2006), some references to related work and the conclusions of the experimentation with C5.
The C5 parser is a extended C parser with few grammatical modifications (the syntax of C5 is presented in Appendix E). The compiler consists on about 5500 lines where 900 of them are the actual type checker. The compiler parses C5, does type checking of DPT constructors and translates the generated syntax tree to C code.
In case of a successful compilation, C5 produces three C files:
The C5 type checker is mainly concerned with the constructor DT_pair:
When a DT_pair invocation is detected, the C5 compiler checks statically if the first argument is a DT_typedef type definition and if the second is a variable of type equal to the value of the first argument.
The current implementation of the C5 compiler (Version 0.98, September 2006) is available for the Linux operating system.
The C5 compiler and a sample of C5 programs can be found on the Web at
http://www.fing.edu.uy/~jcabezas/c5
The statically typed programming languages Amber [7] and Modula-3 [26] include notions of a dynamic type and a typecase statement. This idea can also be found in functional programming [23] [29] [18] and in type-safe C dialects like Ccured [12] where dynamics are used for converting C in a type safe language.
Although C5 may assign accurate types to untyped C programs like printf, it is not a type-safe C dialect but rather a C-based framework for generic programming.
In our knowledge, C5 is the first C extension with dynamics developed for generic programming.
The extension of functional languages with dependent types is another interesting alternative for generic programming: Cayenne [4] -a Haskell-like [17] language with dependent types- is powerful enough to encode predicate logic at the type level and thus express generic functions like printf without restrictions.
In a close research line to dependent types, the Generic Programming community [6][15]. is developing another approach. PolyP [19] is an example of this work that achieves an expressive power similar to that of dependent types by parameterizing function definitions with respect to data type signatures.
Generic programming is a complex task. C5 has been designed to be a low cost C extension to obtain a generic programming framework.
The practice with C5 allows us to affirm that C5 is a useful generic programming framework.
The most important conclusion of the experimentation with C5 is that a static typed language extended with DPTs (dynamics) and TIEs can be powerful enough to express a wide class of generic functions (i.e. C5_scanf) in a straightforward, compact and safe way.
The C5 version of Inodoro's dream examples shows clearly the abstraction level that this language can achieve.
TIEs seem to be a friendly way of providing parameters for generic functions without affecting the static C type system. In the case of C5_compil, the use of TIEs with C5 functions allows C5 to express a generic function in a very compact and readable way.
Even though the communication between static and dynamic types is also restricted to avoid typing conflicts, we have not detected practical limitations when implementing complex generic functions like C5_scanf.
The improvements of version 0.98 of the C5 compiler have transformed C5_scanf, C5_compil, C5_lenSearch and C5_idxSearch in a powerful parsing framework.
Finally, we like to remark that the results of the C5 experimentation is not limited to the C language. DPTs and TIEs are generic concepts and they can be applied in other static typed programming languages. For instance, a new version of generic Haskell inspired on the C5 ideas was developed at InCo in 2006 [30].
The main goal of the C5 project is to experiment with programming languages that support static and dynamic typing in coexistence mode. Thus, the way the static and dynamic universes are communicated is a critical point of the programming behavior of such languages.
The practice with C5 showed that the way C5 communicates between the static and the dynamic universes provides the expressiveness required to program generic functions defined for the entire type system.
This is an important empirical result since the communication from the dynamic to the static universe in C5 is restricted to atomic types, and this is a strong restriction compared with Cardelli's typecase statement.
The experimentation with C5 convinced us that C5_gos and C5_fapply give to C5 an equivalent expressiveness than Cardelli's typecase statement.
However, what the practice really showed was the importance of the TIEs when programming complex programs like the parser generator or the image constructor.
In such cases, generic functions cannot be properly defined using just the -one dimension- function arguments.
The functions C5_compil, C5_scanf or opm_image_cons are relevant examples to see how useful are TIEs for generic programming.
DPTs are necessary for generic programs to answer the question
The power of generic functions has an obvious counterpart: programming generic functions is a complex task. The practice with C5 confirmed this assertion. The design, implementation and testing of functions like C5_scanf or opm_image_cons required a considerable effort.
In the other hand, the practice showed also that the use of generic functions do not present special difficulties.
For instance, about 400 undergraduate computing students at InCo showed that they can use C5 libraries (including generic functions with DPTs and TIEs) to construct small C5 programs (100-300 lines) as good as they do with other standard programming languages like C. C++ or Java. However, 60% of the students declare that they required -in comparison with C++ or Java- additional time to understand properly the new concepts of C5.
In the case of graduate students with C language and functional programming knowledge, we have not detected specific difficulties for generic programming in C5.
Finally, we like to present some statistics about the C5 project in order to give a global vision of its significance.
C5 statistics | |||
program | prog. languages | program size (lines) | man-hours |
C5 compiler | C YACC LEX | 5500 | 1800 |
DPT Library | C5 | 1600 | 1200 |
Graphics Library | C5 | 3400 | 1400 |
C5_scanf | C5 | 2200 | 950 |
AliceBlue AntiqueWhite AntiqueWhite1 AntiqueWhite2 AntiqueWhite3 AntiqueWhite4 Aquamarine Aquamarine1 Aquamarine2 Aquamarine3 Aquamarine4 Azure Azure1 Azure2 Azure3 Azure4 Beige Bisque Bisque1 Bisque2 Bisque3 Bisque4 Black BlanchedAlmond Blue Blue1 Blue2 Blue3 Blue4 BlueViolet Brown Brown1 Brown2 Brown3 Brown4 Burlywood Burlywood1 Burlywood2 Burlywood3 Burlywood4 CadetBlue CadetBlue1 CadetBlue2 CadetBlue3 CadetBlue4 Chartreuse Chartreuse1 Chartreuse2 Chartreuse3 Chartreuse4 Chocolate Chocolate1 Chocolate2 Chocolate3 Chocolate4 Coral Coral1 Coral2 Coral3 Coral4 CornflowerBlue Cornsilk Cornsilk1 Cornsilk2 Cornsilk3 Cornsilk4 Cyan Cyan1 Cyan2 Cyan3 Cyan4 DarkGoldenrod DarkGoldenrod1 DarkGoldenrod2 DarkGoldenrod3 DarkGoldenrod4 DarkGreen DarkKhaki DarkOliveGreen DarkOliveGreen1 DarkOliveGreen2 DarkOliveGreen3 DarkOliveGreen4 DarkOrange DarkOrange1 DarkOrange2 DarkOrange3 DarkOrange4 DarkOrchid DarkOrchid1 DarkOrchid2 DarkOrchid3 DarkOrchid4 DarkSalmon DarkSeaGreen DarkSeaGreen1 DarkSeaGreen2 DarkSeaGreen3 DarkSeaGreen4 DarkSlateBlue DarkSlateGray DarkSlateGray1 DarkSlateGray2 DarkSlateGray3 DarkSlateGray4 DarkSlateGrey DarkTurquoise DarkViolet DeepPink DeepPink1 DeepPink2 DeepPink3 DeepPink4 DeepSkyBlue DeepSkyBlue1 DeepSkyBlue2 DeepSkyBlue3 DeepSkyBlue4 DimGray DimGrey DodgerBlue DodgerBlue1 DodgerBlue2 DodgerBlue3 DodgerBlue4 Firebrick Firebrick1 Firebrick2 Firebrick3 Firebrick4 FloralWhite ForestGreen Gainsboro GhostWhite Gold Gold1 Gold2 Gold3 Gold4 Goldenrod Goldenrod1 Goldenrod2 Goldenrod3 Goldenrod4 Gray Gray0 Gray1 Gray10 Gray100 Gray11 Gray12 Gray13 Gray14 Gray15 Gray16 Gray17 Gray18 Gray19 Gray2 Gray20 Gray21 Gray22 Gray23 Gray24 Gray25 Gray26 Gray27 Gray28 Gray29 Gray3 Gray30 Gray31 Gray32 Gray33 Gray34 Gray35 Gray36 Gray37 Gray38 Gray39 Gray4 Gray40 Gray41 Gray42 Gray43 Gray44 Gray45 Gray46 Gray47 Gray48 Gray49 Gray5 Gray50 Gray51 Gray52 Gray53 Gray54 Gray55 Gray56 Gray57 Gray58 Gray59 Gray6 Gray60 Gray61 Gray62 Gray63 Gray64 Gray65 Gray66 Gray67 Gray68 Gray69 Gray7 Gray70 Gray71 Gray72 Gray73 Gray74 Gray75 Gray76 Gray77 Gray78 Gray79 Gray8 Gray80 Gray81 Gray82 Gray83 Gray84 Gray85 Gray86 Gray87 Gray88 Gray89 Gray9 Gray90 Gray91 Gray92 Gray93 Gray94 Gray95 Gray96 Gray97 Gray98 Gray99 Green Green1 Green2 Green3 Green4 GreenYellow Grey Grey0 Grey1 Grey10 Grey100 Grey11 Grey12 Grey13 Grey14 Grey15 Grey16 Grey17 Grey18 Grey19 Grey2 Grey20 Grey21 Grey22 Grey23 Grey24 Grey25 Grey26 Grey27 Grey28 Grey29 Grey3 Grey30 Grey31 Grey32 Grey33 Grey34 Grey35 Grey36 Grey37 Grey38 Grey39 Grey4 Grey40 Grey41 Grey42 Grey43 Grey44 Grey45 Grey46 Grey47 Grey48 Grey49 Grey5 Grey50 Grey51 Grey52 Grey53 Grey54 Grey55 Grey56 Grey57 Grey58 Grey59 Grey6 Grey60 Grey61 Grey62 Grey63 Grey64 Grey65 Grey66 Grey67 Grey68 Grey69 Grey7 Grey70 Grey71 Grey72 Grey73 Grey74 Grey75 Grey76 Grey77 Grey78 Grey79 Grey8 Grey80 Grey81 Grey82 Grey83 Grey84 Grey85 Grey86 Grey87 Grey88 Grey89 Grey9 Grey90 Grey91 Grey92 Grey93 Grey94 Grey95 Grey96 Grey97 Grey98 Grey99 Honeydew Honeydew1 Honeydew2 Honeydew3 Honeydew4 HotPink HotPink1 HotPink2 HotPink3 HotPink4 IndianRed IndianRed1 IndianRed2 IndianRed3 IndianRed4 Indianred Ivory Ivory1 Ivory2 Ivory3 Ivory4 Khaki Khaki1 Khaki2 Khaki3 Khaki4 Lavender LavenderBlush LavenderBlush1 LavenderBlush2 LavenderBlush3 LavenderBlush4 LawnGreen LemonChiffon LemonChiffon1 LemonChiffon2 LemonChiffon3 LemonChiffon4 LightBlue LightBlue1 LightBlue2 LightBlue3 LightBlue4 LightCoral LightCyan LightCyan1 LightCyan2 LightCyan3 LightCyan4 LightGold LightGoldenrod LightGoldenrod1 LightGoldenrod2 LightGoldenrod3 LightGoldenrod4 LightGoldenrodYellow LightGray LightGrey LightPink LightPink1 LightPink2 LightPink3 LightPink4 LightSalmon LightSalmon1 LightSalmon2 LightSalmon3 LightSalmon4 LightSeaGreen LightSkyBlue LightSkyBlue1 LightSkyBlue2 LightSkyBlue3 LightSkyBlue4 LightSlateBlue LightSlateGray LightSlateGrey LightSteelBlue LightSteelBlue1 LightSteelBlue2 LightSteelBlue3 LightSteelBlue4 LightYellow LightYellow1 LightYellow2 LightYellow3 LightYellow4 LimeGreen Linen Magenta Magenta1 Magenta2 Magenta3 Magenta4 Maroon Maroon1 Maroon2 Maroon3 Maroon4 MediumAquamarine MediumBlue MediumOrchid MediumOrchid1 MediumOrchid2 MediumOrchid3 MediumOrchid4 MediumPurple MediumPurple1 MediumPurple2 MediumPurple3 MediumPurple4 MediumSeaGreen MediumSlateBlue MediumSpringGreen MediumTurquoise MediumVioletRed MidnightBlue MintCream MistyRose MistyRose1 MistyRose2 MistyRose3 MistyRose4 Moccasin NavajoWhite NavajoWhite1 NavajoWhite2 NavajoWhite3 NavajoWhite4 Navy NavyBlue OldLace OliveDrab OliveDrab1 OliveDrab2 OliveDrab3 OliveDrab4 Orange Orange1 Orange2 Orange3 Orange4 OrangeRed OrangeRed1 OrangeRed2 OrangeRed3 OrangeRed4 Orangered Orchid Orchid1 Orchid2 Orchid3 Orchid4 PaleGoldenrod PaleGreen PaleGreen1 PaleGreen2 PaleGreen3 PaleGreen4 PaleTurquoise PaleTurquoise1 PaleTurquoise2 PaleTurquoise3 PaleTurquoise4 PaleVioletRed PaleVioletRed1 PaleVioletRed2 PaleVioletRed3 PaleVioletRed4 PapayaWhip PeachPuff PeachPuff1 PeachPuff2 PeachPuff3 PeachPuff4 Peru Pink Pink1 Pink2 Pink3 Pink4 Plum Plum1 Plum2 Plum3 Plum4 PowderBlue Purple Purple1 Purple2 Purple3 Purple4 Red Red1 Red2 Red3 Red4 RosyBrown RosyBrown1 RosyBrown2 RosyBrown3 RosyBrown4 RoyalBlue RoyalBlue1 RoyalBlue2 RoyalBlue3 RoyalBlue4 SaddleBrown Saddlebrown Salmon Salmon1 Salmon2 Salmon3 Salmon4 SandyBrown SeaGreen SeaGreen1 SeaGreen2 SeaGreen3 SeaGreen4 Seashell Seashell1 Seashell2 Seashell3 Seashell4 Sienna Sienna1 Sienna2 Sienna3 Sienna4 SkyBlue SkyBlue1 SkyBlue2 SkyBlue3 SkyBlue4 SlateBlue SlateBlue1 SlateBlue2 SlateBlue3 SlateBlue4 SlateGray SlateGray1 SlateGray2 SlateGray3 SlateGray4 SlateGrey Snow Snow1 Snow2 Snow3 Snow4 SpringGreen SpringGreen1 SpringGreen2 SpringGreen3 SpringGreen4 SteelBlue SteelBlue1 SteelBlue2 SteelBlue3 SteelBlue4 Tan Tan1 Tan2 Tan3 Tan4 Thistle Thistle1 Thistle2 Thistle3 Thistle4 Tomato Tomato1 Tomato2 Tomato3 Tomato4 Turquoise Turquoise1 Turquoise2 Turquoise3 Turquoise4 Violet VioletRed VioletRed1 VioletRed2 VioletRed3 VioletRed4 Wheat Wheat1 Wheat2 Wheat3 Wheat4 White WhiteSmoke Yellow Yellow1 Yellow2 Yellow3 Yellow4 YellowGreen
In C5, fonts are represented by port lists. Fonts are the images associated to the char type. The char type TIE selects the desired font in a C5 program. There is a small set of predefined fonts (TIEs).
The rules for font names are
<Font>_<Draft|Letter|Ultra>_<UC|C|S|D|UD>
where Font is the font name (for example Roman), Draft, Letter and Ultra is the font resolution (quality) and the separation between fonts is represented by UC ultracompact, C compact, S standard, D disperse and UD ultra disperse.
The following is the form of a char type TIE:
{recnr,ftype,vert1,vert2,hor,serif,incl,disp}
#define Roman {-1.0,1.0,0.1,0.4,0.15,0.2,0.0,0.7 } #define Roman_Draft_C {-1.0,1.0,0.1,0.4,0.15,0.2,0.0,0.7 } #define Roman_Letter_C {-2.0,1.0,0.1,0.4,0.15,0.2,0.0,0.7 } #define Roman_Ultra_C {-3.0,1.0,0.1,0.4,0.15,0.2,0.0,0.7 } #define Roman_Draft_UC {-1.0,1.0,0.1,0.4,0.15,0.2,0.0,0.8 } #define Roman_Letter_UC {-2.0,1.0,0.1,0.4,0.15,0.2,0.0,0.8 } #define Roman_Ultra_UC {-3.0,1.0,0.1,0.4,0.15,0.2,0.0,0.8 } #define Roman_Draft_S {-1.0,1.0,0.15,0.4 ,0.15,0.25,0.0,0.6 } #define Roman_Letter_S {-2.0,1.0,0.15,0.4 ,0.15,0.25,0.0,0.6 } #define Roman_Ultra_S {-3.0,1.0,0.15,0.4 ,0.15,0.25,0.0,0.6 } #define Roman_Draft_D {-1.0,1.0,0.2,0.4,0.2,0.3,0.0,0.4 } #define Roman_Letter_D {-2.0,1.0,0.2,0.4,0.2,0.3,0.0,0.4 } #define Roman_Ultra_D {-3.0,1.0,0.2,0.4,0.2,0.3,0.0,0.4 } #define Roman_Draft_UD {-1.0,1.0,0.25,0.4,0.25,0.35,0.0,0.1 } #define Roman_Letter_UD {-2.0,1.0,0.25,0.4,0.25,0.35,0.0,0.1 } #define Roman_Ultra_UD {-3.0,1.0,0.25,0.4,0.25,0.35,0.0,0.1 } #define Romans_Draft_UC {-1.0,1.0,0.1 ,0.25,0.1 ,0.15,0.0,0.8} #define Romans_Letter_UC {-2.0,1.0,0.1 ,0.25,0.1 ,0.15,0.0,0.8} #define Romans_Ultra_UC {-3.0,1.0,0.1 ,0.25,0.1 ,0.15,0.0,0.8} #define Romans_Draft_C {-1.0,1.0,0.1 ,0.3,0.1 ,0.2,0.0,0.7 } #define Romans_Letter_C {-2.0,1.0,0.1 ,0.3,0.1 ,0.2,0.0,0.7 } #define Romans_Ultra_C {-3.0,1.0,0.1 ,0.3,0.1 ,0.2,0.0,0.7 } #define Romans {-1.0,1.0,0.1 ,0.3,0.15,0.25,0.0,0.6 } #define Romans_Draft_S {-1.0,1.0,0.1 ,0.3,0.15,0.25,0.0,0.6 } #define Romans_Letter_S {-2.0,1.0,0.1 ,0.3,0.15,0.25,0.0,0.6 } #define Romans_Ultra_S {-3.0,1.0,0.1 ,0.3,0.15,0.25,0.0,0.6 } #define Romans_Draft_D {-1.0,1.0,0.2,0.3,0.2,0.3,0.0,0.4 } #define Romans_Letter_D {-2.0,1.0,0.2,0.3,0.2,0.3,0.0,0.4 } #define Romans_Ultra_D {-3.0,1.0,0.2,0.3,0.2,0.3,0.0,0.4 } #define Romans_Draft_UD {-1.0,1.0,0.2,0.3,0.2,0.3,0.0,0.2 } #define Romans_Letter_UD {-2.0,1.0,0.2,0.3,0.2,0.3,0.0,0.2 } #define Romans_Ultra_UD {-3.0,1.0,0.2,0.3,0.2,0.3,0.0,0.2 } #define Antique_Draft_C {-1.0,1.0,0.13,0.3,0.13,-0.28,0.0,0.76} #define Antique_Letter_C {-2.0,1.0,0.13,0.3,0.13,-0.28,0.0,0.76} #define Antique_Ultra_C {-3.0,1.0,0.13,0.3,0.13,-0.28,0.0,0.76} #define Antique {-1.0,1.0,0.13,0.3,0.13,-0.31 ,0.0,0.6} #define Antique_Draft_S {-1.0,1.0,0.13,0.3,0.13,-0.31 ,0.0,0.6} #define Antique_Letter_S {-2.0,1.0,0.13,0.3,0.13,-0.31 ,0.0,0.6} #define Antique_Ultra_S {-3.0,1.0,0.13,0.3,0.13,-0.31 ,0.0,0.6} #define Antique_Draft_D {-1.0,1.0,0.13,0.3 ,0.13,-0.32,0.0,0.4} #define Antique_Letter_D {-2.0,1.0,0.13,0.3 ,0.13,-0.32,0.0,0.4} #define Antique_Ultra_D {-3.0,1.0,0.13,0.3 ,0.13,-0.32,0.0,0.4} #define Antique_Draft_UD {-1.0,1.0,0.13,0.3,0.13,-0.33,0.0,0.1} #define Antique_Letter_UD {-2.0,1.0,0.13,0.3,0.13,-0.33,0.0,0.1} #define Antique_Ultra_UD {-3.0,1.0,0.13,0.3,0.13,-0.33,0.0,0.1} /* Arial gorda bold */ #define Arialb_Draft_UC {-1.0,0.0,0.4,0.4,0.4,0.0,0.0,0.85} #define Arialb_Letter_UC {-2.0,0.0,0.4,0.4,0.4,0.0,0.0,0.85} #define Arialb_Ultra_UC {-3.0,0.0,0.4,0.4,0.4,0.0,0.0,0.85} #define Arialb_Draft_C {-1.0,0.0,0.4,0.4,0.4,0.0,0.0,0.70} #define Arialb_Letter_C {-2.0,0.0,0.4,0.4,0.4,0.0,0.0,0.70} #define Arialb_Ultra_C {-3.0,0.0,0.4,0.4,0.4,0.0,0.0,0.70} #define Arialb {-1.0,1.0,0.4,0.4,0.4,0.0,0.0,0.50} #define Arialb_Draft_S {-1.0,1.0,0.4,0.4,0.4,0.0,0.0,0.50} #define Arialb_Letter_S {-2.0,1.0,0.4,0.4,0.4,0.0,0.0,0.50} #define Arialb_Ultra_S {-3.0,1.0,0.4,0.4,0.4,0.0,0.0,0.50} #define Arialb_Draft_D {-1.0,1.0,0.42,0.42,0.42,0.00,0.0,0.3 } #define Arialb_Letter_D {-2.0,1.0,0.42,0.42,0.42,0.00,0.0,0.3 } #define Arialb_Ultra_D {-3.0,1.0,0.42,0.42,0.42,0.00,0.0,0.3 } #define Arialb_Draft_UD {-1.0,1.0,0.42,0.42,0.42,0.00,0.0,0.0 } #define Arialb_Letter_UD {-2.0,1.0,0.42,0.42,0.42,0.00,0.0,0.0 } #define Arialb_Ultra_UD {-3.0,1.0,0.42,0.42,0.42,0.00,0.0,0.0 } /* Arial standard */ #define Arial_Draft_UC {-1.0,0.0,0.2,0.2,0.2,0.0,0.0,0.90} #define Arial_Letter_UC {-2.0,0.0,0.2,0.2,0.2,0.0,0.0,0.90} #define Arial_Ultra_UC {-3.0,0.0,0.22,0.22,0.20,0.0,0.0,0.90} #define Arial_Draft_C {-1.0,0.0,0.2,0.2,0.2,0.0,0.0,0.7 } #define Arial_Letter_C {-2.0,0.0,0.2,0.2,0.2,0.0,0.0,0.7 } #define Arial_Ultra_C {-3.0,0.0,0.22,0.22,0.20,0.0,0.0,0.7 } #define Arial_Draft_S {-1.0,1.0,0.2,0.2,0.2,0.0,0.0,0.60} #define Arial_Letter_S {-2.0,1.0,0.2,0.2,0.2,0.0,0.0,0.60} #define Arial_Ultra_S {-3.0,1.0,0.2,0.2,0.2,0.0,0.0,0.60} /* This is the default font */ #define Arial {-1.0,1.0,0.22,0.22,0.22,0.00,0.0,0.3 } #define Arial_Draft_D {-1.0,1.0,0.22,0.22,0.22,0.00,0.0,0.3 } #define Arial_Letter_D {-2.0,1.0,0.22,0.22,0.22,0.00,0.0,0.3 } #define Arial_Ultra_D {-3.0,1.0,0.21,0.21,0.21,0.00,0.0,0.3 } #define Arial_Draft_UD {-1.0,0.0,0.30,0.30,0.30,0.00,0.0,0.1} #define Arial_Letter_UD {-2.0,0.0,0.30,0.30,0.30,0.00,0.0,0.1} #define Arial_Ultra_UD {-3.0,0.0,0.30,0.30,0.30,0.00,0.0,0.1} /* Arial delgada */ #define Arialn_Draft_C {-1.0,0.0,0.09,0.09,0.08,0.0,0.0,0.75} #define Arialn_Letter_C {-2.0,0.0,0.09,0.09,0.08,0.0,0.0,0.75} #define Arialn_Ultra_C {-3.0,0.0,0.07,0.07,0.06,0.0,0.0,0.75} #define Arialn {-1.0,0.0,0.09,0.09,0.08,0.0,0.0,0.55} #define Arialn_Draft_S {-1.0,0.0,0.09,0.09,0.08,0.0,0.0,0.55} #define Arialn_Letter_S {-2.0,0.0,0.09,0.09,0.08,0.0,0.0,0.55} #define Arialn_Ultra_S {-3.0,0.0,0.07,0.07,0.06,0.0,0.0,0.55} #define Arialn_Draft_D {-1.0,0.0,0.09,0.09,0.08,0.0,0.0,0.25} #define Arialn_Letter_D {-2.0,0.0,0.09,0.09,0.08,0.0,0.0,0.25} #define Arialn_Ultra_D {-3.0,0.0,0.07,0.07,0.06,0.0,0.0,0.25} /* Courier */ #define Courier_Draft_C {-1.0,3.0,0.2 ,0.2 ,0.2 ,-0.4,0.0,0.5} #define Courier_Letter_C {-2.0,3.0,0.2 ,0.2 ,0.2 ,-0.4,0.0,0.5} #define Courier_Ultra_C {-3.0,3.0,0.22,0.22,0.20,-0.4,0.0,0.5} #define Courier_Draft_S {-1.0,3.0,0.2,0.2,0.2,-0.47,0.0,0.4 } #define Courier_Letter_S {-2.0,3.0,0.2,0.2,0.2,-0.47,0.0,0.4 } #define Courier_Ultra_S {-3.0,3.0,0.2,0.2,0.2,-0.47,0.0,0.4 } #define Courier {-1.0,3.0,0.24,0.24,0.24,-0.5,0.0,0.2} #define Courier_Draft_D {-1.0,3.0,0.24,0.24,0.24,-0.5,0.0,0.2} #define Courier_Letter_D {-2.0,3.0,0.24,0.24,0.24,-0.5,0.0,0.2} #define Courier_Ultra_D {-3.0,3.0,0.24,0.24,0.24,-0.5,0.0,0.2} #define Courier_Draft_UD {-1.0,3.0,0.30,0.30,0.30,-0.5,0.0,0.1} #define Courier_Letter_UD {-2.0,3.0,0.30,0.30,0.30,-0.5,0.0,0.1} #define Courier_Ultra_UD {-3.0,3.0,0.30,0.30,0.30,-0.5,0.0,0.1}
Port_List opm_page(Color, Color, Color, ..., NULL)opm_page returns a port list with a Right oriented port. The size of the port is the entire page and the color list is constructed with the Color arguments that are not equal to NULL. The function accepts up to 12 arguments. (see opm_Page for constructing a page with an unbounded color list).
Port_List opm_Page(Color_List)opm_Page returns a port list with a Right oriented port. The size of the port is the entire page and the color list is the argument. The constructor of color lists is LCC so LCC(Red, LCC(Green,NULL)) is a valid color list of two elements.
void opm_print(Port_List)opm_print translates the port list argument into a PostScript format file using the standard output.
Port_List opm_scale(double left, double right, double up, double down,Port_List) 0.0 <= left <=1.0 0.0 <= right <=1.0 0.0 <= up <=1.0 0.0 <= down <=1.0opm_scale scales the ports of the argument according to the values of the arguments left, right, up and down.
Port_List opm_rot(int rotnr,Port_List)opm_rot rotates rotnr
Port_List opm_image_cons(DTP,Port_List)opm_image_cons returns an image (a port list) which is the graphic representation of the object member of the DPT argument printed on a page (the port list argument).
Port_List opm_set_color(int color_idx,Port_List)opm_set_color sets the current color of the ports of the list argument according to the color_idx value. if color_idx is greater than the color list length of the port then the current color is White.
DT_typedef struct{double x2,y1,x1,y2;} dp2; DT_typedef struct OPMTON { dp2 scale; struct{ /* discr union */ union{ int{0,10} bg; struct OPMTON *next; } un; int{1,0} idx; } du; }{0} *Color_Serie; Color_Serie opm_colors(int mode,int tones_nr, double coef1,double coef2)opm_colors constructs a list of color scaled rectangles. The mode argument sets the scaling mode:
The tones_nr argument defines the length of the color list and the coef1 and coef2 arguments are the scaling factor of the active and inactive sides respectively. The standard values of these arguments are 1.0 and 0.0.
Color opm_col2col(Color from, Color to, int tones_nr)opm_col2col is a compressed notation for the colors of a port. The function represents a color serie starting from from to to of tones_nr tones.
Color rgb(int, int, int)The function rgb constructs a color object according to the values of the red, green and blue arguments. The range of the arguments is (0,255).
The White color is represented by rgb(0,0,0) and Black by rgb(255,255,255).
Port_List opm_bcurv4(int rec_nr, double y1_U,double y2_U,double y3_U,double y4_U, double y1_D,double y2_D,double y3_D,double y4_D, Port_List pl)The function opm_bcurv4 paints the shape limited by two Bezier curves of four points where the upper curve is defined by (0.0,y1_U), (0.33,y2_U), (0.66,y3_U), (1.0,y4_U) and the lower curve by (0.0,y1_D), (0.44,y2_D), (0.66,y3_D),(1.9,y4_D). The visible range of the y_... arguments is (0.0,1.0). rec_nr is the resolution level where 10 is the top manual resolution and -1, -2 , -3 and -4 are the automatic resolution for the draft, standard, high and ultra level respectively.
Figure C.1 shows the image produced by the four opm_bcurv4 examples of the next program:
main(){ Port_List lp=opm_page(Black,Gray65,NULL),lp1,lp2,lp3,lp4; lp1=opm_scale(1.0,0.45,0.9,0.45,lp); lp2=opm_scale(0.45,1.0,0.9,0.45,lp); lp3=opm_scale(1.0,0.45,0.45,0.9,lp); lp4=opm_scale(0.45,1.0,0.45,0.9,lp); opm_print(opm_set_color(1,opm_concat(lp1,opm_concat(lp2, opm_concat(lp3,lp4))))); opm_print(opm_bcurv4( 4, 1.0, 0.2, 1.0, 0.0, 1.0, 0.0, 0.8, 0.0, lp1)); opm_print(opm_bcurv4(-4, 1.0, 0.2, 1.0, 0.0, 1.0, 0.0, 0.8, 0.0, lp2)); opm_print(opm_bcurv4(-3, 0.4, 1.2, 1.2, 0.4, 0.2, 1.0, 1.0, 0.2, lp3)); opm_print(opm_bcurv4(-4, 1.0, 0.35,0.35,1.0, 0.5, 0.5, 0.5, 0.5, lp4)); }
There is also a 5 points version called opm_bcurv5.
Port_List opm_bcurv4p(int rec_nr, double r1_U,double r2_U,double r3_U,double r4_U, double r1_D,double r2_D,double r3_D,double r4_D, Port_List pl)The function opm_bcurv4p paints the surface limited by two Bezier curves of four points where the upper curve is defined in polar coordinates by (
Figure C.2 shows the image produced by the four opm_bcurv4p examples of the next program:
main(){ Port_List lp=opm_page(Black,Gray65,NULL),lp1,lp2,lp3,lp4; lp1=opm_scale(1.0,0.45,0.9,0.45,lp); lp2=opm_scale(0.45,1.0,0.9,0.45,lp); lp3=opm_scale(1.0,0.45,0.45,0.9,lp); lp4=opm_scale(0.45,1.0,0.45,0.9,lp); opm_print(opm_set_color(1,opm_concat(lp1,opm_concat(lp2, opm_concat(lp3,lp4))))); opm_print(opm_bcurv4p( 4, 1.0, 0.2, 1.0, 0.0, 1.0, 0.0, 0.8, 0.0, lp1)); opm_print(opm_bcurv4p(-4, 1.0, 0.2, 1.0, 0.0, 1.0, 0.0, 0.8, 0.0, lp2)); opm_print(opm_bcurv4p(-3, 0.4, 1.2, 1.2, 0.4, 0.2, 1.0, 1.0, 0.2, lp3)); opm_print(opm_bcurv4p(-4, 1.0, 0.35,0.35,1.0, 0.5, 0.5, 0.5, 0.5, lp4)); }
There is also a 5 points version called opm_bcurv5p.
Port_List opm_ellipsis(int rec_nr,double ring,double elipse, double incl1,double incl2,Port_List pl) -3 <= rec_nr <= 12 0.0 <= ring <= 1.0 0.0 <= elipse <= 1.0 -1.0 <= incl1 <= 1.0 -1.0 <= incl2 <= 1.0opm_ellipsis paints the surface delimited by the maximal ellipsis of the port and the ellipsis defined by ring. The elipse argument is an elliptical factor. incl1 and incl2 sets the inclination of the major and minor ellipsis respectively. rec_nr is the resolution level where 10 is the top manual resolution and -1, -2 , -3 and -4 are the automatic resolution for the draft, standard, high and ultra level respectively.
Figure C.3 shows the image produced by the four opm_ellipsis examples of the next program:
main(){ Port_List lp=opm_page(Black,Gray65,NULL),lp1,lp2,lp3,lp4; lp1=opm_scale(1.0,0.45,0.9,0.45,lp); lp2=opm_scale(0.45,1.0,0.9,0.45,lp); lp3=opm_scale(1.0,0.45,0.45,0.9,lp); lp4=opm_scale(0.45,1.0,0.45,0.9,lp); opm_print(opm_set_color(1,opm_concat(lp1,opm_concat(lp2, opm_concat(lp3,lp4))))); opm_print(opm_ellipsis(-3, 0.0, 0.0, 0.0, 0.0, lp1)); opm_print(opm_ellipsis( 5, 0.90,0.40,0.90,0.20, lp2)); opm_print(opm_ellipsis(-2, 0.55,0.20,0.30,-0.7, lp3)); opm_print(opm_ellipsis(-4, 0.60,0.20,-0.8,1.00, lp4)); }
Port_List opm_sector(int recnr, double angle1, double x1, double angle2, double x2, Port_List lp); 0.0 <= x1 <= 1.0 0.0 <= x2 <= 1.0
opm_sector paints the intersection of the right plane sector defined by the line line1 and the left plane sector defined by the line line2. line1 and line2 are defined by the X coordenates x1 and x2 with the angles in radians angle1 and angle2 respectively. rec_nr is the resolution level where 10 is the top manual resolution and -1, -2 , -3 and -4 are the automatic resolution for the draft, standard, high and ultra level respectively.
Figure C.4 shows the image produced by the four opm_sector examples of the next program:
main(){ Port_List lp; lp=opm_page(Black,Gray85,Beige, NULL); opm_print(opm_set_color(1,lp)); opm_print(opm_sector(6, M_PI_2-0.07,0.00, M_PI_4 ,0.00, opm_scale(1.0,0.5,1.0,0.5,lp))); opm_print(opm_sector(-1,M_PI_2+0.3, 0.50, M_PI_2-0.3, 0.50, opm_scale(0.5,1.0,1.0,0.5,lp))); opm_print(opm_sector(6, M_PI_2+M_PI_4, 1.00, M_PI_2+0.1 , 1.00, opm_scale(1.0,0.5,0.5,1.0,lp))); opm_print(opm_sector(-1,0.95, 0.00, 0.95, 0.05, opm_scale(0.5,1.0,0.5,1.0,lp))); }
Array type is denoted where
is the size and
the type of the elements of the array.
denotes a recursive pointer type.
Pointer type is denoted where
is the type of the
referenced object.
Recursive pointer type is denoted where
is the type of the
referenced object.
The next program combines the functions opm_colors, opm_co2col and opm_bcurv5 to generate a fantasy letter S :
main(){ Color_Serie cs=opm_colors(20,SERIE_NR,1.0,0.33); Port_List lp=opm_page( opm_col2col(Orchid, SlateBlue4,TONES_NR), opm_col2col(Brown ,Orange, TONES_NR-1), Red, opm_col2col(DarkGoldenrod , White, TONES_NR), opm_col2col(Black , Black, TONES_NR), LightYellow1, Black , NULL); opm_print(opm_page( Black ,NULL)); opm_print(opm_bcurv5(-1,1.0, 0.1,0.65,1.6,0.0, 1.0,-0.6,0.35,0.9,0.0, opm_image_cons(DT_pair(Color_Serie,cs), opm_rot(1, opm_scale(0.99,0.99,0.88,0.88,lp))))); }
Figure C.5 shows the resulting image.
In 2006, undergraduate students at InCo created 48 mascots programmed in C5. We present three of them to give an overview of this work.
Laura Cuadrado and Cecilia Stevenazzi produced a C5 program (194 lines) creating the mascot presented in Figure C.6.
Andrés Américo, Carlos Baptista and Fernando Martínez produced a C5 program (282 lines) creating the mascot presented in Figure C.7.
iWalmar Laiolo , Nicolás Gerolami and Marcelo Perelmutter produced a C5 program (334 lines) creating the mascot presented in Figure C.8.
Array type is denoted where
is the size and
the type of the elements of the array.
Pointer type is denoted where
is the type of the
referenced object.
Recursive pointer type is denoted where
is the type of the
referenced object.
Let ;
;
;
;
In the next function, denotes a recursive pointer type
that is not member of the pointer table.
The function
inserts a pointer type in the pointer table.
denotes a recursive pointer type which is member
of the pointer table. This pointer has been inserted in the pointer
table using the function
.
C5 syntax is a C grammar [16] with few modifications. In particular, the syntax of TIEs is described by the typeinf rules.
High level definitions.
Specifiers
Enumeration
Variable declarators
Function declarators
Abstract declarators
Structures
Local variables and function args.
Statements
Expressions
.
Array type is denoted where
is the size and
the type of the elements of the array.
Pointer type is denoted where
is the type of the
referenced object.
denotes a TIE where the first
element is a function DPT.
denotes the fields of a struct
or union object.
denotes the elements of an array object.
;
;
;
DPT C5_compil(DPT dp){ int i; if(!strncmp("Token",C5_gname(dp),5)) return(dp); switch(C5_gtype(dp)){ case CHAR: case DOUBLE: case INT: return(dp); case STRUCT: if(isDUnion(dp)){ /* Discriminated Union */ if (C5_TIE_length(dp)==0) /* No TIE */ return(C5_compil(C5_gos(C5_gos(dp,1), C5_gint(C5_gos(dp,2),0)+1 ))); else return(C5_scanfActions(dp)); } else{ if (C5_TIE_length(dp)==0) return(dp); else return(C5_scanfActions(dp)); } case ARRAY: if (C5_TIE_length(dp)==0 || C5_gtype(C5_gos(dp,0))==CHAR) return(dp); else return(C5_scanfActions(fp,dp)); case POINTER: if(C5_is_ptr_nul(dp) || C5_gtype(C5_gos(dp,1))==CHAR) return(dp); case TYPEDEF: if (C5_TIE_length(dp)>0) return(C5_scanfActions(dp)); else return(C5_compil(C5_gos(dp,1))); case FUNCTION: return(dp); default: fprintf(fp,"Unknown type %s.\n",C5_gname(dp)); return(dp); } }
DPT C5_scanfActions(DPT dp){ /* */ DPT tie1= dphd(C5_gtie(dp)); /* first element of the TIE */ if(C5_isFunction(tie1)) /* function application */ return(C5_fapply(tie1, C5_map_tie(fp,dp, dptl(C5_gtie(dp))))); else return(dphd(C5_map_tie(dp,C5_gtie(dp)))); } DPT_list C5_map_tie(DPT dp, DPT_list tie_ls){ if(tie_ls==NULL) return(NULL); else{ DPT auxdp= C5_compil(C5_gPtrdp(dp,dphd(tie_ls))); return(dpcons(auxdp,C5_map_tie(dp, dptl(tie_ls)))); } } DPT C5_gPtrdp(DPT dp, DPT tie){ DPT out; int i; char *st; if(C5_gtype(dp)==ARRAY && C5_gtype(tie)==INT){ int tie_idx=C5_gint(tie,-1); if(tie_idx<0 || tie_idx>=C5_gsize(dp)) return(dp); else return(C5_gos(dp,tie_idx)); } if(C5_gtype(dp)==STRUCT && !isDUnion(dp) && C5_isString(tie)){ char *str= C5_gstr(tie, "C5_gPtrdp error"); for(i=1;i<=C5_gcant(dp);i++) if(!strcmp(str,C5_gname(C5_gos(dp,i)))) return(C5_gos(dp,i)); return(tie); /* no match */ } return(tie); /* no string */ }
This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.70)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -split 2 -show_section_numbers c5book.tex
The translation was initiated by Juan Jose Cabezas - INCO on 2007-08-02