An eC Manual
Copyright 1996 by Robert P. Cook
1.0 eC
The eC language grew out of a practical need for a general, efficiently implemented, systems programming language that could be used for education from the high-school to graduate level. The eC implementation includes a high-level of checking at both compile-time and runtime to help students avoid many of the common programming errors in C and C++. Complex features were deliberately omitted to facilitate learning and to ease the transition for high school teachers and students who already know Pascal. Unsafe programming practices, such as pointer arithmetic, are not supported.
The ancestors of eC are Pascal[1], Modula-2[2] and C++[3]. From Modula-2, it has inherited the important module concept; from C++ and Pascal, most of the rest. This includes in particular the data structures, i.e. arrays, structs, classes, variant records (unions), and pointers. Structured statements include the familiar if, switch, do, while, and for statements. Programs in eC can easily be converted to compile with a C++ compiler; however, an eC compiler is not guaranteed to compile arbitrary C or C++ programs.
This Report is not intended to teach you how to program in eC. eC is a project with a very modest level of support. There are some worthwhile features of C++ that have not been implemented due to a lack of time. The following list summarizes some of the main differences with C++ that users should note.
Differences with C++
1.1 Identifiers
eC is case sensitive. For example, the keyword "if" is only recognized in its lowercase form. Also, the underline symbol "_" is accepted as a letter.
Examples:
x scan starMod firstLetter test1 word_with_separation
1.2 Numbers
The number syntax allows octal and hexadecimal constants to be expressed. Furthermore, the type "unsigned short" is used to explicitly represent unsigned, 16-bit integers, and the "long" type name is provided to denote signed, 32-bit integers. The following table lists the minimum and maximum values for variables of these types as follows:
TYPE | MIN( ) | MAX( ) |
short or int | -32768 | 32767 |
unsigned short | 0 | 65535 |
long | -2147483648L | 2147483647L |
float or double | -1.0E-35 | 1.0E+35 |
The "int" type name is used for signed values in programs that are designed to adapt to whatever the range of an integer is on the target architecture. A hexadecimal constant is different from an unsigned constant in that it must have a 0x or 0X preceding its first digit. For an octal constant, the leading digit must be a zero. Note that the same number, like 45, can be expressed either in decimal (45), octal (055), or hexadecimal (0x2D). A number can be followed by a u or U suffix to indicate unsigned or L to indicate long.
Examples:
Real numbers are supported in eC in precisely the same manner as C. All real numbers must have a decimal point and must start with a digit; although, digits are not required in the fraction. An exponent field is also supported, but is optional. Following the fraction portion of the real number, an "e" or "E" must precede the exponent. The exponent has a range from -35 to 35. The unary plus ("+") can be placed on positive exponents as an option. The two real number types are "float" and "double"; "double" is the recommended choice.
Examples:
1.3 Characters and strings
Both the double quote character (") and single quote () may be used as quote marks. The single quote is used to denote a char constant, while a double quote character denotes a string. The opening and closing marks must be the same character, and this character cannot occur within the string. A string must not extend over the end of a line.
By convention, many of the library modules use the null character, ASCII code 0x00, to delimit the end of a string. The storage for constant strings ends with the null automatically. Any string that the user creates should end with the null in order to work properly with string functions.
There is also a notation to represent characters that are not in the languages character set. The \ character is used as an escape symbol to denote special characters in a character or string constant. The following table lists the escape characters.
Escape Sequences
- \a Bell
- \b Backspace
- \f Formfeed
- \n New line
- \r Carriage return
- \t Horizontal tab
- \ Single quotation mark
- \" Double quotation mark
- \\ Backslash
- \? Question mark
- \ddd octal digits that encode a character
Examples:
. . "This is\ntwo lines" "Modula"
"Dont Worry!" "a \"quoted\" word" "a special \34 character"
For convenience, the compiler also supports the convention that two or more adjacent string constants are considered to be a single constant. The compiler concatenates them automatically. This convention facilitates the creation of multi-line strings.
Example of a multi-line string constant:
cout<<"\tThis is one of the most common uses of\n"
"\tmulti-line string constants. Well output\n"
"\tthe value"<<j<<"of variable j\n";
1.4 Operators, delimiters, and comments
Operators and delimiters are the special characters, character pairs, or reserved words listed below. The reserved words MUST NOT be used as identifiers.
Reserved Identifiers
Comments may be inserted between any two symbols in a program. A comment is an arbitrary character sequence opened by the bracket "/*" and closed by "*/". Comments may not be nested and they do not affect the meaning of a program. The "//" characters can be used to begin a comment that extends to the end of the current line.
1.5 Declarations
The declarations within a block can occur in any order; however in the current implementation, declarations cannot be intermingled with statements. Constant expressions can be used wherever a constant is allowed. Finally, since this implementation is a one-pass compiler, ALL SYMBOLS MUST BE DECLARED BEFORE USE.
1.5.1 Constant declarations
Constant declarations are restricted to simple types. The value can be a constant expression.
Examples:
const int N = 100; /* N stands for 100 */
const unsigned short LIMIT = 2*N-1; // LIMIT is for 199
const char warning[] = "you better not do that";
const char COMMA = ,;
const double PI = 3.14;
1.5.2 Type declarations
The simple types in eC consist of enumeration types or type identifiers, which may be qualified. In this context, the term "qualified" means preceded by a interface identifier followed by a period or a class name followed by the scope resolution operator "::". The qualification may be necessary to refer to a type that is in an included interface or that is defined in a class. The following simple types are denoted by standard identifiers:
Examples:
typedef int INTEGER;
typedef enum {RED, BLUE=5, GREEN} Color; //Note: RED==0; GREEN==6
typedef ModuleName.New *pNew; /* a qualified reference */
eC handles type equivalence much more strictly than C. In C, it is perfectly legal to assign variables of two different types as long as the two types "look" alike. Two types look alike if the component parts of the two declarations match exactly. With eC, two separately-declared types cannot be assigned to each other no matter how closely their declarations match.
Example:
int a[2];
int b[2];
a = b; NO! This is allowed in eC, a and b are variables of two different types.
1.5.2.1 ARRAY and POINTER types
The array declaration as a list of array sizes only; the lower bound is always zero; therefore, the upper bound is equal to "size-1". The pointer declaration uses the * symbol. The constant name NULL is used to specify an unbound pointer (one that doesnt point to anything).
One of the exceptions to the "declare before use" rule concerns pointer types. In the case of a pointer to a type name, T, the type name is automatically treated as a forward reference if it has not already been defined.
Examples:
typedef unsigned int Demo[5][7]; // a two-dimensional 5x7 matrix
typedef char *pChar;
typedef Links *pLinks; /* forward reference */
typedef struct {int a,b; pLinks p;} Links; /* defined */
Demo x; /* referenced with x[2][3] */
1.5.2.2 Record and Union types
The fields of a structure are referenced via a qualified reference (e.g. temp.c), which contains the name of a structure variable, followed by a period ., followed by a field name. Since a structure element can also be a structure, a qualified name can extend to whatever level is needed to refer to a particular field. If a pointer to a structure is declared, the special "->" operator can be used to denote a qualified reference to any of the field names.
Example:
typedef struct {
int x,y;
char c;
} Ex; // Ex is a structure composed from three simple types
Ex temp; // an instance of the Ex structure
temp.x // a reference to the first field
temp.y // a reference to the second field of the Ex structure
Ex *pEx; // a pointer to an instance of structure Ex
pEx->x // a field reference; the "->" is short for (*pEx).x
A "union" type is a structure that defines multiple fields but only stores one field at a time. In the Ex structure, for example, there are three fields x, y and c, but in a union, only one of them is valid at a given time. The union type is often used to store records read from a file in which several record types exist. Unions can be nested within structure types and vice versa. Also, unions can contain unions.
Example:
typedef union {
int x,y;
char c;
} Ex; // Ex is a structure composed from the union of three simple types
Ex temp; // an instance of the Ex structure
temp.x // a reference to the first field
temp.y // wouldnt make sense if the unions "value" is the first field
Ex *pEx; // a pointer to an instance of structure Ex
pEx->x // a field reference; the "->" is short for (*pEx).x
1.5.2.3 Class types
A class is an encapsulated definition of an abstract data type. The class notation in eC defines a structure that has been augmented with type definitions and procedure headings that are "local" to the scope of the structure. Unlike C++, procedure bodies cannot be defined as part of a class definition. Like C++, const declarations are not supported within class declarations. Unlike C++, inheritance, constructors and destructors are not supported. The procedures within a class are referred to as "member functions", the variables as "member variables". As with typedef declarations, classes may be declared in both interfaces (.h files) and implementations (.cpp files).
As with structure variables, a qualified reference is required to access member variables and to invoke member functions. Any types or enumerated constants declared within a class can be referenced using the "::" scope resolution operator. Like the . in a qualified reference, the "::" indicates to the compiler to examine the scope of its argument to interpret a name. The "public" keyword indicates that all of a class members are accessible. The "public" keyword is required and is the only option available.
Example:
class List {
public:
typedef enum{JOE=9} Name; //class constants
typedef int *Example;
void Open(const unsigned int noOfElements, const double initialValue);
void Close();
void printf(const char format[]); //output format for each element
void *list; //declare the list as a opaque type
};
List x; //an instance of a list
x.printf("%5.2f"); //invoke member function printf
List::Example p; //a variable of the type defined in List
*p = List::JOE; //set *p equal to the constant 9
To define a procedure whose heading was declared in a class definition, it is only necessary to prefix the procedure name with the class name and the scope resolution operator (e.g. List::printf). The formal parameter types must match exactly those that were declared in the class definition. Within the body of class procedures, the scope of reference is automatically opened on all of the class member functions and variables; that is, they can be referenced without using a qualified reference. The member variables referenced correspond to those contained in the instance variable used to invoke the procedure. The instance variable can also be referenced by using a "this" pointer, which is automatically defined by the compiler for each member function. If the scope resolution operator "::" is applied without the class name, it references the global scope. For example, if List::printf uses stdio.printf in its implementation, the global symbol could be referenced as "::printf".
Examples:
void List::printf(const char fomat[])
{
/* "List *this;" is added by the compiler */
/* Note that Open Close printf list can be referenced without qualification */
};
1.5.2.4 Procedure types
eC permits variables of procedure type that can have procedure names as values. This feature can be useful when the function to be performed is to be selected at runtime. For procedure variables without a formal parameter list, the empty list () must be used.
Examples:
typedef int prMax (int x, int y);
typedef Date prSecToDate (long &Seconds);
typedef void parLess();
Procedure variables are initialized by the assignment of either other procedure variables or procedure constants, which result from procedure declarations.
1.5.3 Variable declarations
Variable declarations serve to introduce variables and associate each with a unique identifier and a fixed data type. Variables whose identifiers appear in the same list all obtain the same type, although each name may be qualified as a pointer, or an array, or both.
Examples:
unsigned short i,j;
char a, b[10], *c; //a simple variable, an array and a pointer
1.5.4 Procedure declarations
Procedure declarations consist of a procedure heading and a block that is called the procedure body. The heading specifies the procedure identifier and the formal parameters. The block contains declarations and statements. A "return" statement must be used to set the result of a function.
TypeName ["*"] [ClassName::]identifier ( [FormalParameters] ) [const] "{"
{Const | Typedef | Class | Variable Declarations}
[StatementSequence]
};
FormalParameters =
[FPSection { "," FPSection}] | void
FPSection =
[const] TypeName ["&" | "*"] identifierList ["[" "]"]
The use of a ";" in place of a procedure body indicates that only a procedures heading, or signature, is being declared. This allows a procedure to be referenced before its declaration. When the actual procedure is declared, however, the full formal parameter list must be repeated.
Example:
void foo (unsigned int x); // forward declaration
void fip()
{
foo (14); /* used before declaration */
};
void foo (unsigned int x)
{
cout<<x;
};
1.5.4.1 Formal parameters
Formal parameters are identifiers that denote actual parameters specified in the procedure call. Both value and reference (&) parameters are provided. Formal parameters are local to the procedure, i.e. their scope of reference is the program text that constitutes the procedure declaration.
Example:
/* Read a string of digits from the input device. */
/* The Cardinal value of the digits is returned. */
/* Conversion starts when a digit is read. */
/* Conversion stops when a non-digit is read. */
unsigned int ReadCard()
{
unsigned int i;
char ch;
do { /* skip characters until a digit is read */
cin>>ch;
if (cin.eof()) break;
} while ((ch < 0) || (ch > 9));
i = 0;
do { /* accumulate the number in "i" */
i = 10*i+(ORD(ch)-ORD(0));
cin>>ch;
if (cin.eof()) break;
} while ((ch >= 0) && (ch <= 9);
return i;
};
The "ReadCard" routine uses the type transfer function, ORD, to manipulate the numeric value of the input character.
Any function with an empty parameter list, such as "ReadCard", must be declared and referenced with the "()" suffix. The goal is to create a visual distinction between a reference to a procedure variable and a procedure call.
The specification of "open" array parameters represents a significant improvement over the static limitations of earlier languages. If the parameter is an "open" array, the form
Type identifier[ ]
must be used, where the specification of the actual index bounds is omitted. "Type" must be compatible with the element type of the actual array, and the index ranges are mapped onto the integers 0 to N-1, where N is the number of elements. If the initial array is multidimensional, it is mapped onto the argument with the last subrange listed first. That is if the array's index bounds is defined as [2] [2], the argument will be mapped [0,0]->[0], [0,1]->[1], [0,2]->[2], [1,0]->[3], etc. The "HIGH" standard function can be used to determine "N-1". The example illustrates the use of this feature in an error message routine.
void error(char &message[ ] )
{ /* Notice: the bound for "message" is omitted */
unsigned int nChar;
cout<<\n; /* skip to new line */
for (nChar = 0; nChar<=HIGH(message); nChar++) {
/* no. chars in message */
cout<<message[nChar]); /* write the message */
}; /*for*/
cout<<\n; /* skip to new line */
};
error("short"); error("MEDIUM1"); error("longest one");
The "open" array feature also makes it easy to create libraries of useful routines that can operate over a wide range of input values.
1.5.4.2 Procedure overloading
As abstract data types are defined, it is advantageous to reuse procedure names that have a "well defined" meaning, such as printf and scanf. Normally, this would lead to multiple definitions of the same name, which would generate an error message, but not in C++. The language permits duplication of procedure names as long as the argument lists for all the procedures are distinct either because of different numbers of arguments or different types of arguments. If the argument list for a procedure invocation matches only one definition exactly in the number and type of arguments, other matches that require some kind of mapping, or conversion, are ignored.
Example:
void abs(int x);
void abs(double x);
y = abs(3.5); //invokes the second definition
y = abs(9); //invokes the first definition
1.5.4.3 Standard procedures
The standard procedures are listed below. The ones with uppercase names are not builtin to C++ but are builtin to eC. They could easily be duplicated by using #defines in an include file to convert a program from eC to C++. They have been retained because of their familiarity to Pascal and Modula-2 users.
ABS(x) absolute value; result Type=argType
CAP(ch) capitalize ch
CHR(x) the character with ordinal number x
FLOAT(x) converts x to a float value
HIGH(x) the upper bound of array x
MIN(x) the minimum value for type x
MAX(x) the maximum value for type x
ODD(x) x % 2 <> 0
ORD(x) ordinal number of x in its enumeration
sizeof(x) the number of bytes in type x
TRUNC(x) the long value of a float or the int value of a long
LONG(x) the long value of an int or unsigned int x.
VAL(T, x) is the value with ordinal number x and type T
VAL(T, ORD(x))=x, if x is of type T
DEC(x); x = x-1; or x--;
DEC(x, n); x = x-n; or x -= n;
HALT; terminate program execution
INC(x); x = x+1; or x++;
INC(x, n); x = x+n; or x += n;
Examples:
ABS(-5) = 5 ODD(3) = true
CHR(65) = A ORD(A) = 65
CAP(a)= A VAL(Color, 0) = RED
x=8;
DEC(x); x = 7 DEC(x, 5); x = 3
INC(x); x = 9 INC(x, 5); x = 13
1.5.4.4 Conversion and Type transfer functions
Conversion functions perform the useful service of converting one number type into another by actually changing the argument's bit values. FLOAT takes an int, unsigned int, or long value and converts it to a real number; FLOAT's inverse, TRUNC, takes a float argument and converts it into long. TRUNC also converts long values into the int type, which involves the removal of the high-order bits.
The other conversion functions perform similar bit additions or removals. LONG takes an int or unsigned int value and makes it a long type. CHR removes the high byte of an int or unsigned int value to make it an ASCII value of type char. ORD, the inverse of CHR, adds a high byte of zeroes to create an unsigned int value.
Type transfer functions, or casts, are different from conversion functions in that they do not change any bits. Type transfer functions merely convert the argument into a new type at compile time. Of course, the new type must have the same size as the argument. ORD, for example, performs a dual role; it is the conversion function mentioned above, and it also gives the ordinal value of its argument in the argument's enumeration. VAL is the inverse of this. It takes the enumeration's type name and its ordinal value and makes them into the enumeration's type. The other way to transfer types is to use the type name as a function. Again, the two types must be of equal size. Type transfer between unsigned int and int is automatic on assignment.
Examples:
typedef unsigned int Arr[4];
typedef struct {
long m, n;
} Rec;
unsigned int c;
int i;
long l;
char ch;
float r;
Arr a;
Rec r;
1.5.4.5 Input/Output functions
C++ has a very simple set of input/output functions that are defined in the iostream and iomanip interfaces. The << operator initiates output while the >> operator initiates input. For example, the statement "cin>>x;" will read a value from the keyboard into variable "x". The function "cin.eof()" can be used to test if an input operation succeeded. The following examples illustrate some output possibilities.
Statement Output
#include <iostream.h>
#include <iomanip.h>
cout<<6<<7<<5.6<<"hello"; 675.600hello
cout<<setw(5)<<6<<7; 6 7
cout<<setprecision(1)<<5.62; 5.6
cout<<hex<<45; 0x2d
cout<<setiosflags(ios::scientific)<<562; 5.62e+02
1.6 Expressions
Unlike C++, which has an elaborate set of automatic conversion rules when operands are of different types, eC requires the operands to match in type. For example, you cannot add a double to an integer (3.5+9); the desired conversion must be explicitly applied (3.5+FLOAT(9)). The following table defines the interpretation of each operator.
Operator and Meaning
+ addition
- subtraction
* multiplication
/ division
% integer modulus
& NOT SUPPORTED; unsafe; use ADR from SYSTEM.
||
p || q means "if p then true, otherwise q"
&&
p && q means "if p then q, otherwise false"
!
! p means "if p then false, otherwise true"
<<
x<<3 means shift x left three bit positions; x can be of type unsigned int, int or long
>>
x>>3 means shift x right three bit positions; the sign bit is copied on a right shift
== compare for equality
!= unequal
< less
<= less than or equal
> greater
>= greater than or equal
& bit-wise logical and
| bit-wise logical or
~ bit-wise ones complement
Examples:
3+4== 7 3-4== -1
7 / 4== 1 3*4== 12
7 % 4== 3 9>>2 == 2
7<<3 == 56 true || false== true
true && false== false
! true== false
3== 4 is false 3 <> 4== true
3 < 4== true 3 <= 4 is true
3 > 4== false 5 >= 4 is true
1.7 Statements
In statement structure, { } are used to group compound statements. We recommend using { } at all times to avoid several common programming errors.
ForStatement =
for "(" [identifier "=" expression] ";" [expression] ";" [expression] " )" "{"
StatementSequence
"}" /* for */
DoStatement =
do "{"
StatementSequence
"}" while "(" expression ")"
WhileStatement =
while "(" expression ")" "{"
StatementSequence
"}" /* while */
The eC languages "for" loop uses the optional third expression to specify the step action to occur in each iteration. The following rules should be obeyed when using FOR loops:
Examples:
for (i = 3; i<=7; i++) i=3,4,5,6,7
for (i = 3; i<=7; i+=2) i=3,5,7
for (i = 7; i>=1; i-=2) i=7,5,3,1
for ( ;; ) infinite loop; use break statement to exit
1.7.1 Assignments and type compatibility
The assignment statement serves to replace the current value of a variable by a new value indicated by an expression. The assignment operator is written "=" and is pronounced as "becomes".
assignment =
variableReference "=" expression
The type of the variable must be assignment compatible with the type of the expression. Operands are said to be assignment compatible, if either they are compatible, or both are of type int or unsigned int. Two operands of types T0 and T1 are compatible if they are matching simple types or they are derived from the same type definition.
1.7.2 SWITCH statement
Constant expressions can be used as case labels. Thus, defined constants can be used to parameterize selection. No value may occur more than once as a case label. The maximum number of cases per switch statement is 256. If the statements that comprise a case are not terminated with a break statement, execution "runs on" to the next statement. A "break" causes execution to transfer out of the switch statement. The "default" case (if it is present) must occur last in the lists of cases; it matches all cases other than the ones listed. The selection expression can only be of type char, int, or unsigned int.
SwitchStatement =
switch (expression) {
Case
[default:
StatementSequence]
}; /* switch */
Case =
[ConstExpression ":"
StatementSequence]
Example:
/* Read a string of digits from the input device. */
/* "," and "." are allowed in the string for readability. */
/* The Cardinal value of the digits is returned. */
/* Conversion starts when a digit is read. */
/* Conversion stops when a non-digit is read. */
unsigned int ReadCard() {
unsigned int i;
char ch;
do { /* skip characters until a digit is read */
cin>>ch;
if (cin.eof()) HALT;
} while ((ch<0) || (ch>9);
i = 0;
for ( ;; ) { /* accumulate the number in "i" */
switch (ch) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
case "9":
i = 10*i+(ORD(ch)-ORD("0"));
break;
case .: case,: break; /* ignore "," and "." */
default: /* stop at non-digit */
return i; /* loop */
}; /* switch */
cin>>ch;
if (cin.eof()) break;
}; /* loop */
};
1.7.3 IF statement
The IF statement is used to implement conditional execution.
IfStatement =
if "(" expression ")" "{"
StatementSequence
{ "}" else if "(" expression ")" "{" /*zero or more */
StatementSequence}
[ "}" else "{" /*zero or one ELSE */
StatementSequence]
} /* if */
eC Example:
if (x== 1) {
y = 2;
} else if (x== 9) {
y = 3;
} else
y = 6;
}; /* if */
The expressions following the symbols if and else if are of type Boolean. They are evaluated in the sequence of their occurrence until one yields the value true. Then, the associated statement sequence is executed and the "if" terminated. If an "else" clause is present, it is executed if and only if all Boolean expressions yielded the value false, much like the "default" in the "switch" construct.
1.7.4 Loop, BREAK, and CONTINUE statements
A for loop statement specifies the continuous execution of a statement sequence. This statement is used quite frequently in concurrent algorithms because, unlike sequential programs, termination is often undesirable. Imagine what would happen if an operating system halted after 10,000 iterations.
The continue statement, when it occurs within a for, do, or while loop, causes execution to transfer to the beginning of the nearest enclosing loop. If it is a "for" loop, the iteration variable is incremented and the termination condition is tested prior to continuing the loop.
LoopStatement =
for "(" ";" ";" ")" "{"
StatementSequence
"}" /* LOOP */
ExitStatement = break
ContinueStatement = continue
Example:
typedef List *pList;
typedef struct {
pList link; /* a singly-linked list */
Attribute attribute; /* a list element */
} List; /* List */
boolean search(pList list, Attribute &attribute)
{
/* check to see if "attribute" is in "list" */
for ( ;; ) /* search singly-linked list */
if (list == NULL) {
break;
} else if (attribute == list->attribute) {
return true; /* attribute is in the list */
}; /* if */
list = list->link; /* advance to next element */
}; /* LOOP */
return false; /* end of list; not there */
}; //search
The break statement specifies termination of the loop and, when executed, causes execution to continue at the statement following the loop statement. A break statement terminates only the closest, enclosing for, do, while, or switch statement.
1.7.5 RETURN statement
The return statement provides a convenient way to leave a procedure as soon as an exit condition becomes true.
return [expression]
In eC, the RETURN statement serves the dual role of specifying the result for a function and of returning to the caller for both subroutines and functions. For a subroutine, the expression must be omitted. For a function, it must be present. The expression, representing the returned value, must match the type specified for a function.
2.0 Conditional Compilation
In most C and C++ implementations, .h files are textually included in programs by the preprocessor. In eC, .h files are always compiled. Further, there is no preprocessing in eC so the conditional compilation constructs are processed in one pass with the source program. As a result, parameterization only affects .h files when they are compiled, not afterwards. In all other respects, the syntax and semantics are identical to C.
2.1 #define and #if
The purpose of #define is to "define" preprocessor symbols. #define names should be distinct from any identifier used in an interface or program. Expressions on #define are not supported; use const declarations instead. The #if statement is used to select the portions of a text file to compile, hence the term conditional compilation. A #else delimiter can be present optionally and a #endif statement must be used to terminate the #if. #if statements can be nested; however, the #define and #ifs in the unselected text must be syntactically correct, even though they are ignored.
Example:
#define GRAPHICS //comment this line to omit the windows code
#if defined(GRAPHICS)
/* insert code to write to a window */
#else
/* insert code to write to standard output */
#endif
2.2 Expressions
The only function supported in #if expressions is "defined", which returns true if its argument is a preprocessor symbol in a #define, and which returns false otherwise. Parentheses as well as the !, && and || operators can be combined with multiple "defined" function references to specify more complicated tests.
3.0 Programming Conventions
In addition to the indentation conventions used in the eC definition, you should try to, and we will, adhere to the following programming conventions. Hopefully, the result will be visually pleasing programs that are easier to understand due to the presence of syntactic cues.
3.1 Names and declarations
Declarations should help document the use of a variable; thus, try to use enumerated type declarations instead of int. Most identifiers should be written in lower case, except for the first letter of each new word, that should be capitalized.
line firstLine nextLineOffset
Capitalize the first letter of type identifiers, module names, and the names of exported procedures; capitalize all letters of "const" definitions. If the name of a constant is several words, just capitalize the first two letters of the first word(e.g. CHarsPerWord). Try to use full words for all names. However, if space is a problem, the following shorthand conventions can be used.
Choose a short tag for each basic type that you create, e.g. Ln for Line or Buf for Buffer. Use the following prefixes to construct tags for derived types:
p - pointer to: pBuf== Buf *
i - index for: iLn == index for Ln[]
n - length of: nString=number of characters in
If you need only one variable of a given type in a scope, use the tag as its name:
Buf buf;
If you need several names, append modifiers (avoid simple numbers like 1, 2, etc.):
Buf bufOld, bufNew, bufAlt;
3.2 Layout
Try to follow the indentation examples in the eC definition. Write one statement per line, unless several simple statements, which together perform a single function, will fit on one line. It is acceptable to put a loop on a single line if it will fit. If a statement will not fit on a single line, indent the continuation line(s).
A semicolon follows the last statement in a statement sequence and the last field in a field list. The purpose is to make insertions and deletions less error-prone.
Each interface should be commented to describe its general function. Also, each exported procedure should have a brief comment. In addition, it is advisable to comment reference (&) parameters as "IN", "OUT", or "INOUT" to denote the presence or absence of side-effects.
3.3 Spaces
Leave a space after a comma or semicolon and none before; leave a space before and after a colon. Surround "=" with spaces. A space should appear after left-comment and before right comment. Dont put spaces inside brackets or parentheses or around single-character operations.
4.0 Interfaces
The programming power of a system is not dependent on the features of its programming language but on the utility of its interfaces. The eC system contains a module library that defines interfaces for a wide range of applications. A subset of the more commonly used interfaces are listed below:
LIBRARY INTERFACES
Interface Name | Types & Methods | Purpose |
stdlib | abs, rand | various common functions |
iostream | cin, cout, eof | automatically formatted I/O |
fstreams | fstream, open, close | formatted I/O on files |
stdio | FILE *: printf, sprintf, fprintf | C formatted I/O on simple types |
math | cos, exp, floor, sqrt, tan | real math functions |
limits | CHAR_MIN, INT_MIN | defined constants for MIN,MAX |
cstring | strcat, strcmp, strcpy | string manipulation |
ctype | isdigit, toupper | character classification |
cmalloc | malloc, free | dynamic storage allocation |
COROUTINE | Coroutine: InitCoroutine, Transfer | processor state vector |
PROCESS | Process, ProcessQueue: Create, Run | thread creation/execution |
ProcessArg | ArgA, ArgC | arguments to threads |
SEMAPHORE | Semaphore: InitSem, P, V | critical section, resource, private |
Random | RandomStream: InitSeed, Random | streams of random numbers |
Distributions | Exponential, Normal, Poisson | probability distributions |
Graphics | Window, Coordinate, Bitmap: Open | window, font, graphics ops |
InputType | Mouse, Keyboard: | track mouse, read keyboard |
ColorManager | ColorName, Gray | manipulate colors by name |
RealSys | tTime: Date, Argc, GetEnv, Spawn | access OS functions |
SYSTEM | WORD, ADDRESS, ADR | unsafe programming features |
REFERENCES
SAMPLE PROGRAM
PRINT THE SQUARES OF THE INTEGERS 1..100
#include <iostream.h>
#include <iomanip.h>
void main () {
unsigned int i, j;
cout<<"Number Number Squared\n";
for (i = 1; i<=100; i++) {
cout<<setw(4)<< i; /* aligns number under "b" */
j = i*i;
cout<<setw(16)<<j; /* aligns under "S" */
}; /* for */
};
Compiling interfaces
Every file that represents an interface must have the suffix ".h". The compiled interface is stored in a file with the same filename as the source code except the suffix is changed to ".SBL". An implementation interface is required for each reference to an included module. The compiler searches for any needed .SBL files in the current directory. If it does not find one, it prompts the user to input the path name that locates the needed file.
All interfaces must be compiled before they are used ( included). Once an interface module is compiled, it should not be compiled again unless it is extended. When an interface is changed, first, compile all dependent interfaces and then secondly, compile all dependent implementation modules.
Running a program
Whenever a program requests the use of an included module, the runtime must bring that module into memory. The first time the runtime encounters a reference to an included module, the runtime will fetch its object code into memory and execute its initialization statements if it has any. This is called dynamic linking. The object code must be located in the current directory or else the runtime will not be able to find it. The initialization sequence is only performed once. If many modules include the same module, that module's initialization code will only be performed when it is first read in.
If the runtime encounters an error, a message will be printed. Since all runtime errors are fatal to the program, execution will immediately stop. These are all the possible error messages that the runtime can issue: