This chapter introduces the Scheme types predefined by Elk. It begins with the ``pointer-less'' types such as boolean, whose values are stored directly in the pointer field of an Object; followed by the types whose members are C structs that reside on the Scheme heap.
Objects of type T_Boolean can hold the values #t and #f. Two Objects initialized to #t and #f, respectively, are available as the external C variables True and False. The macro
Truep(obj)
!EQ(obj,False)
The two functions
int Eqv(Object, Object); int Equal(Object, Object);
The character value stored in an Object of type T_Character can be obtained by the macro
CHAR(char_obj)
Object Make_Char(int c);
The type T_Null has exactly one member--the empty list; hence all Objects of this type are identical. The empty list is available as the external C variable Null. This variable is often used to initialize Objects that will be assigned their real values later, for example, as the fill element for newly created vectors or to initialize Objects in order to GC_Link() them. A macro Nullp() is provided as a shorthand for checking if an Object is the empty list:
#define Nullp(obj) (TYPE(obj) == T_Null)
Object tail; ... for (tail = some_list; !Nullp(tail); tail = Cdr(tail)) process_element(Car(tail));
The type T_End_Of_File has one member--the end-of-file object--and is only rarely used from within user-supplied C/C++ code. The external C variable Eof is initialized to the end-of-file object.
Integers come in two flavors: fixnums and bignums. The former have their value stored directly in the pointer field and are wide enough to hold most C ints. Bignums can hold integers of arbitrary size and are stored in the heap. Two macros are provided to test whether a given signed (or unsigned, respectively) integer fits into a fixnum:
FIXNUM_FITS(integer) UFIXNUM_FITS(unsigned_integer)
The value stored in a fixnum can be obtained as a C int by calling the macro
FIXNUM(fixnum_obj)
Check_Integer(obj)
The following functions are provided to convert C integers to Scheme integers:
Object Make_Integer(int); Object Make_Unsigned(unsigned); Object Make_Long(long); Object Make_Unsigned_Long(unsigned long);
int Get_Integer(Object); int Get_Exact_Integer(Object); unsigned Get_Unsigned(Object); unsigned Get_Exact_Unsigned(Object); long Get_Long(Object); long Get_Exact_Long(Object); unsigned long Get_Unsigned_Long(Object); unsigned long Get_Exact_Unsigned_Long(Object);
As all of the above functions include suitable type-checks, primitives receiving integer arguments can be written in a simple and straightforward way. For example, a primitive encapsulating the UNIX dup system call (which returns an integer file descriptor pointing to the same file as the original one) can be written as:
Object p_unix_dup(Object fd) {
return Make_Integer(dup(Get_Exact_Unsigned(fd)));
(define fd (unix-dup (truncate 1.2)))
Real and inexact numbers are represented as Objects of type T_Flonum. Each such object holds a pointer to a structure on the heap with a component val of type double, so that the expression
FLONUM(flonum_obj)->val
double Get_Double(Object);
The functions
Object Make_Flonum(double); Object Make_Reduced_Flonum(double);
Check_Number(obj)
Pairs have two components of type Object, the car and the cdr, that can be accessed as:
PAIR(pair_obj)->car PAIR(pair_obj)->cdr
Check_List(obj)
int Fast_Length(Object list);
Object Copy_List(Object list);
As explained in section @(ch-gc), care must be taken when mixing calls to these macros, because Cons() may trigger a garbage collection: an expression such as
Car(x) = Cons(y, z);
tmp = Cons(x, y); Car(x) = tmp;
Objects of type T_Symbol have one public component--the symbol's name as a Scheme string (that is, an Object of type T_String):
SYMBOL(symbol_obj)->name
Object Intern(const char *); Object CI_Intern(const char *);
A symbol that is used by more than one function can be stored in a global variable to save calls to Intern(). This can be done using the convenience function
void Define_Symbol(Object *var, const char *name);
static Object sym_else;
...
void elk_init_example(void) {
Define_Symbol(&sym_else, "else");
...
}
By convention, Scheme primitives that do not have a useful return value (for example the output primitives) return the ``non-printing symbol'' in Elk. The name of this symbol consists of the empty string; it does not produce any output when it is printed, for example, by the toplevel read-eval-print loop. In Scheme code, the non-printing symbol can be generated by using the reader syntax ``#v'' or by calling string->symbol with the empty string. On the C language level, the non-printing symbol is available as the external variable Void, so that primitives lacking a useful return value can use
return Void;
Objects of type string have two components--the length and the contents of the string as a pointer to char:
STRING(string_obj)->size STRING(string_obj)->data
Object Make_String(const char *init, int size);
Object str; ... str = Make_String(0, 100); bzero(STRING(str)->data, 100);
Most primitives that receive a Scheme string as one of their arguments pass the string's contents to a C function (for example a C library function) that expects an ordinary, null-terminated C string. For this purpose Elk provides a function
char *Get_String(Object);
Object p_getenv(Object name) {
char *ret = getenv(Get_String(name));
return ret ? Make_String(ret, strlen(ret)) : False;
}
If more strings are to be used simultaneously, the macro Get_String_Stack() can be used instead. It is called with the Scheme object and the name of a variable of type ``char*'' to which the C string will be assigned. Get_String_Stack() allocates space by means of Alloca() (as explained in section @(ch-alloca)); hence a call to Alloca_Begin must be placed in the declarations of the enclosing function or block, and Alloca_End must be called before returning from it.
An additional function Get_Strsym() and an additional macro Get_Strsym_Stack() are provided by Elk; these are identical to Get_String() and Get_String_Stack(), respectively, except that the Scheme object may also be a symbol. In this case, the symbol's name is taken as the string to be converted.
As an example for the use of Get_String_Stack(), here is a simple Scheme primitive exec that is called with the name of a program and one more more arguments and passes them to the execv() system call:
Object p_exec(int argc, Object *argv) {
char **argp; int i;
Alloca_Begin;
Alloca(argp, char**, argc*sizeof(char *));
for (i = 1; i < argc; i++)
Get_String_Stack(argv[i], argp[i-1]);
argp[i-1] = 0;
execv(Get_String(*argv), argp); /* must not return */
error...
}
elk_init_example() {
Define_Primitive(p_exec, "exec", 2, MANY, VARARGS);
}
(exec "/bin/ls" "ls" "-l")
The layout of Objects of type vector is identical to that of strings, except that the data component is an array of Objects. A function Make_Vector() creates a new vector as has been explained in section @(ch-gc) above.
The components of Objects of type T_Port are not normally accessed directly from within C/C++ code, except for
PORT(port_obj)->closefun
A new file port is created by calling
Object Make_Port(int flags, FILE *f, Object name);
Check_Input_Port(obj) Check_Output_Port(obj)
To arrange for a newly-created port to be closed automatically when it becomes garbage, it must be passed to the function Register_Object() as follows:
Register_Object(the_port, 0, Terminate_File, 0);
Curr_Input_Port Standard_Input_Port Curr_Output_Port Standard_Output_Port
void Reset_IO(int destructive_flag);
In addition to the standard Scheme primitives for output, extensions and applications can use a function
void Printf(Object port, char *fmt, ...);
To output a Scheme object, the following function can be used in addition to the usual primitives:
void Print_Object(Object obj, Object port, int raw_flag, int print_depth, int print_length);
Print(obj);
A function
void Load_Source_Port(Object port);
Other built-in Scheme types are lexical environments, primitive procedures, compound procedures, macros, continuations (also called ``control points'' at a few places in Elk), and promises. These types are not normally created or manipulated from within C or C++ code. If you are writing a specialized extension that depends on the C representation of these types, refer to the declarations in the public include file ``object.h'' (which is included automatically via ``scheme.h'').
Lexical environments are identical to pairs except that the type is T_Environment rather than T_Pair. The current environment and the initial (gobal) environment are available as the external C variables The_Environment and Global_Environment. The predefined type constants for primitives, compound procedures (the results of evaluating lambda expressions), and macros are T_Primitive, T_Compound, and T_Macro, respectively. The function
void Check_Procedure(Object);