elk/doc/cprog/cprog.ms

3830 lines
149 KiB
Plaintext

.\" $Revision: 1.25 $
.\"
.ds Vs 3.0
.\"
.so ../util/tmac.scheme
.\"
.\" Courier bold; used for system output in transcripts.
.ie \n(.U .fp 6 B
.el .fp 6 CB
.\"
.\" Code start.
.de Cs
.nr sF \\n(.f
.ft 5
.ps -1
.vs -1
.ie \n(.U .RS
.el .in 1c
.nf
.if !\n(.U .sp .3c
..
.\" Code end.
.de Ce
.fi
.ie \n(.U .RE
.el .in
.vs
.ps
.ft \\n(sF
..
.\" Newline in code.
.de Cl
.sp .6
..
.\" Same as .Cl
.de El
.Cl
..
.\" Example start/end. As floating keeps (used for figures
.\" in this document) and regular keeps cannot be mixed, the
.\" functionality must be simulated here. This sucks...
.de Es
.Cs
.if !\n(.U .di EE
..
.de Ee
.Ce
.if !\n(.U \{\
.di
.if \\n(dn-\\n(.t .sp 1000
.nf
.EE
.fi
.sp .5
.\}
..
.\" .K1 header-text
.\" Major heading with TOC entry.
.de K1
.NH
\\$1
.XS
\\*(SN \\$1
.XE
..
.\" .K2 header-text
.\" Level-2 heading with TOC entry.
.de K2
.NH 2
\\$1
.XS \\n(PN 2n
\\*(SN \\$1
.XE
..
.\" .K3 header-text
.\" Level-3 heading with TOC entry.
.de K3
.NH 3
\\$1
.XS \\n(PN 4n
\\*(SN \\$1
.XE
..
.\" .AP appendix-text
.\" Appendix with TOC entry.
.de AP
.ie \\n(.U .NH
.el .SH
\\$1
.XS
\\$1
.XE
..
.\" .Rf name value
.\" Reference anchor. Each occurrence of `name' anywhere in
.\" the document will be replaced by `value'.
.de Rf
.if !\n(.U .tm s/@(\\$1)/\\$2/g
..
.\"
.\" Counter for Figures (auto-pre-increment).
.nr fS 0 1
.\"
.\" Figure start.
.de Fs
.br
.ie \\n(.$ .KS
.el .KF
.sp 1.2
\u\l'\\n(.lu_'\d
.nr sF \\n(.f
.ft 5
.ps -1
.vs -1
.nf
..
.\" .Fc caption-text
.\" Figure caption. Used at end of Figure, before .Fe.
.de Fc
.sp .2
.fi
.ps
.vs
.ft \\n(sF
.ce 999
\s-1\f3Figure \\n+(fS:\fP \c
\\$1\s0
.if \\n(.$=2 \s-1\&\\$2\s0
.ce 0
..
.\" .Fe name
.\" Figure end. Defines a reference anchor `name' with the
.\" number of the Figure as value.
.de Fe
.Rf \\$1 \\n(fS
.LP
\l'\\n(.lu_'
.sp
.KE
..
.\" Relative indent start.
.de Rs
.if !\\n(.U .RS
..
.\" Relative indent end.
.de Re
.if !\\n(.U .RE
..
.\"
.TL
Building Extensible Applications with Elk \*-
.sp .3
C/C++ Programmer's Manual
.AU
Oliver Laumann
.AB
Elk (\f2Extension Language Kit\fP) is a Scheme implementation designed
as an embeddable, reusable extension language subsystem for
integration into existing and future applications written in C or C++.
The programmer's interface to Elk provides for a close interworking of
the C/C++ parts of Elk-based, \f2hybrid\fP applications with extensible
Scheme code.
This manual describes the facilities of the C/C++ programmer's
interface that can be used by authors of extensible applications and
Scheme extensions.
Topics range from the architecture of Elk-based applications
and the definition of application-specific Scheme types and primitives
to more advanced subjects such as weak data structures and interacting with
the garbage collector.
Many examples throughout the text illustrate the facilities and
techniques discussed in this manual.
.AE
.\" ---------------------------------------------------------------------------
.K1 "Additional Documentation"
.PP
The official specification of the Scheme programming language is
the @[.``R\*(^4RS''] (William Clinger and Jonathan Rees (editors),
\f2Revised\*(^4 Report on the Algorithmic Language Scheme\fP,
1991).
A slightly modified version of an earlier revision of this report
was adopted as an IEEE an ANSI standard in 1990 (IEEE\|Std\|1178-1990,
\f2IEEE Standard for the Scheme Programming Language\fP, 1991).
.PP
The dialect of Scheme implemented by Elk (a superset of the
official language) is described in the \f2Reference Manual for the
Elk Extension Language Interpreter\fP that is included in the
Elk distribution as troff source and preformatted PostScript files.
Reference manuals for the various predefined Elk extensions
(such as the UNIX and X11 extensions) are also part of the distribution;
see the file ``doc/README'' for an overview of the available
documentation.
.PP
This manual supersedes the document \f2Interfacing Scheme to the
``Real World''\fP that was included in earlier versions of Elk.
.PP
An article about Elk has appeared in USENIX Computing Systems
in 1994 (Oliver Laumann and Carsten Bormann, Elk: The Extension Language Kit,
\f2USENIX Computing Systems\fP, vol.\& 7, no.\& 4, pp.\& 419\-449).
.PP
A recent example of an application that uses Elk as its extension
language implementation is freely available in source and binary
form as \f2http://www.informatik.uni-bremen.de/~net/unroff\fP.
@[.\f2unroff\fP] is a programmable, extensible troff translator with
Scheme-based back-ends for the Hypertext Markup Language.
The source code shown in Appendix B has been directly taken from the
\f2unroff\fP source; authors of Elk-based applications are
encourage to reuse this and other parts of the \f2unroff\fP
source for their own projects.
.\" ---------------------------------------------------------------------------
.K1 "Introduction"
.PP
This manual can be roughly divided into two parts.
The first part (chapters\ @(ch-arch) to\ @(ch-static)) describes the
architectural aspects of Elk-based applications and Elk extensions.
Facilities and tools for building extensible applications with Elk are
introduced here.
Readers who are already familiar with the concepts explained in
this part of the document may want to skip it and begin
reading at chapter\ @(ch-notes) or later.
The second part (covering chapters\ @(ch-notes) to\ @(ch-advanced))
specifies the C functions and types available to application
programmers and describes techniques for building data structures that can
be interfaced to Scheme in an efficient way.
Appendix C briefly summarizes all the functions, macros, types, and
variables exported by the Elk kernel to the C/C++ programmer.
.PP
Here is a short overview of the remaining chapters of this manual.
Chapter\ @(ch-arch) discusses the architecture of extensible
applications based on Elk and their relation to Elk extensions.
Chapter\ @(ch-linking) provides an overview of the two basic
methods for integrating an application (or extensions) with Elk:
dynamic loading and static linking.
Chapter\ @(ch-dynl) describes use of dynamic loading in more detail;
topics include automatic extension initialization and C++ static
constructors embedded in dynamically loaded modules.
Chapter\ @(ch-static) describes several forms of linking user-supplied
code with Elk statically and how these affect the structure
of an application's \f2main()\fP function.
.PP
The remaining chapters are a complete specification of the
functions and types of the C/C++ programmer's interface to Elk.
Chapter\ @(ch-notes) provides introductory notes and advice for
programmers of C/C++ code interfacing to Elk (use of include
files, predefined preprocessor symbols, etc.).
Chapter\ @(ch-anatomy) describes the anatomy of Scheme objects
from the C/C++ programmer's point of view.
Chapter\ @(ch-defprim) explains how applications and extensions can
define new Scheme primitives.
Chapter\ @(ch-types) presents the standard, built-in Scheme types
implemented by Elk (numbers, pairs, vectors, etc.) and functions
for creating and accessing Scheme objects of these types from
within C/C++ code.
The facilities for defining new, first-class Scheme data types
are described in chapter\ @(ch-deftype).
Finally, chapter\ @(ch-advanced) deals with a number of more
advanced topics, such as functions for interacting with the
garbage collector, automatic finalization of inaccessible objects,
definition of user-supplied reader functions, error handling, etc.
.PP
A note on the naming conventions followed by the C identifiers
used throughout this document:
the names of all functions, macros, types, and variables exported by
Elk have their components separated by underscores and capitalized
(as in \f2Register_Object()\fP, for example).
In contrast, the names defined by examples shown in this manual only
use lower case letters, so that they can be distinguished easily from
predefined functions exported by Elk.
.\" ---------------------------------------------------------------------------
.K1 "The Architecture of Extensible Applications"
@[.=application architecture]@[.=extensible application]
.Rf ch-arch \*(SN
.PP
Extensible applications built with Elk are @[.=hybrid application]
\f2hybrid\fP in that they consist of code written in a mixture of
languages\*-code written in the application's
@[.\f2implementation language\fP] (C or C++) and code written in the
@[.\f2extension language\fP] (Scheme).
An application of this kind is usually composed of two layers,
a low-level C/C++ layer that provides the basic,
performance-critical functionality of the application, and on top of
that a higher-level layer which is written in Scheme and interpreted
at runtime.
.PP
The Scheme-language portion of an Elk-based application may range from
just a few dozen lines of Scheme code (if a simple form of
customization is sufficient) to fifty percent of the application or
more (if a high degree of extensibility is required).
As Scheme code is interpreted at runtime by an interpreter embedded
in the application, users can customize and modify the application's
Scheme layer or add and test their own Scheme procedures;
recompilation, access to the C/C++ source, or knowledge of the
implementation language are not required.
Therefore, an application can achieve highest extensibility by
restricting its low-level part to just a small core of time-critical
C/C++ code.
.PP
To enable extensions to ``work on'' an application's internal data
structures and state, the application core exports a set of new,
application-specific Scheme data types
and primitives operating on them to the Scheme layer.
These types and primitives can be thought of as a ``wrapper''
around some of the C/C++ types and functions used by the application's core.
For example, the core of an Elk-based newsreader program would export
first-class Scheme types representing \f2newsgroups\fP,
\f2subscriptions\fP, and \f2news articles\fP; these types would
encapsulate the corresponding low-level C ``structs'' or C++ classes.
In addition, it would export a number of Scheme primitives to
operate on these types\*-to create members of them (e.\|g.\& by
reading a news article from disk), to present them to the user through
the application's user-interface, etc.
Each of these primitives would recur on one or more corresponding C or
C++ functions implementing the functionality in an efficient way.
.PP
Another job of the low-level C/C++ layer of an application is to hide
platform-specific or system-specific details by providing suitable
abstractions, so that the Scheme part can be kept portable and simple.
For example, in case of the newsreader program, extension writers
should not have to care about whether the news articles are stored in a
local file system or retrieved from a network server, or about the
idiosyncrasies of the system's networking facilities.
Most of these system-specific details can be better dealt with in a
language oriented towards systems programming, such as C, than in
Scheme.
.PP
To decide whether to make a function part of the low-level
part of an application or to write it in the extension language,
you may ask yourself the following questions:
.IP \(bu
\f2Is the function performance-critical?\&\fP
.RS
.LP
If the answer to this question is \f2yes\fP,
put the function into the C/C++ core.
For example, in case of the newsreader application, a primitive to search
all articles in a given newsgroup for a pattern is certainly
performance-critical and would therefore be written in the
implementation language, while a function to ask the user to
select an item from a list of newsgroups is not time-critical
and could be written Scheme.
.RE
.IP \(bu
\f2Does the function have to deal with platform-specific details?\&\fP
.RS
.LP
For example, a function that needs to allocate and open a UNIX
pseudo-tty or to establish a network connection needs to care
about numerous system-specific details and different kinds of
operating system facilities and will therefore be written in
C/C++ rather than in Scheme.
.RE
.IP \(bu
\f2In which language can the function be expressed more ``naturally''?\&\fP
.RS
.LP
A function that parses and tokenizes a string can be expressed more
naturally (that is, in a significantly more concise and efficient
way) in a language such as C than in Scheme.
On the other hand, functions to construct trees of news articles, to
traverse them, and to apply a function to each node are obvious
candidates for writing them in a Lisp-like language (Scheme).
.RE
.IP \(bu
\f2Are customizability and extensibility important?\&\fP
.RS
.LP
If it is likely that the application's users will want to customize
or augment a function or even replace it with their own versions,
write it in the extension language.
If, for some reason, this is impossible or not practicable, at least
provide suitable @[.``hooks''] that enable users to influence the
function's operation from within Scheme code.
.RE
.\" ---------------------------------------------------------------------------
.K2 "Scheme Extensions"
@[.=Scheme extensions]
.PP
In addition to the Scheme interpreter component, Elk consists of
a number of \f2Scheme extensions\fP.
These extensions are not specific to any kind application and are
therefore reusable.
They provide the ``glue'' between Scheme and a number of
external libraries, in particular the X11 libraries and the UNIX C
library (exceptions are the @[.record extension] and the
@[.bitstring extension] which provide a functionality of their own).
The purpose of these extensions
is to make the functionality of the external libraries
(for example, the UNIX system calls) available to Scheme as Scheme data
types and primitives operating on them.
.PP
While the Scheme extensions are useful for writing freestanding Scheme
programs (e.\|g.\& for @[.rapid prototyping] of X11-based Scheme programs),
their main job is to help building applications that
need to interface to external libraries on the extension language
level.
The @[.X11 extension]s, for instance, are intended to be used
by applications with a graphical user interface based on the
X window system.
By linking the X11 extensions (in addition to the Scheme interpreter)
with an Elk-based application,
the application's user interface can be written entirely
in Scheme and will therefore be inherently customizable and extensible.
As the Scheme extensions are reusable and can be shared between
applications, extension language code can be written in a portable
manner.
.\" ---------------------------------------------------------------------------
.K2 "Applications versus Extensions"
.PP
As far as the C/C++ programmer's interface to Elk (that is, the subject
of this manual) is concerned, there is not really a technical
difference between Scheme \f2extensions\fP on the one hand (such as the
X11 extensions), and Elk-based, extensible \f2applications\fP on the
other hand.
Both are composed of an efficient, low-level C/C++ core and,
above that, a higher-level layer written in Scheme.
In both cases, the C/C++ layer exports a set of Scheme types and
primitives to the Scheme layer (that is, to the Scheme
\f2programmer\fP) and thus needs to interact with the Scheme interpreter.
Because of this analogy, the rest of the manual will mostly drop
the distinction between applications and extensions and concentrate
on the interface between C/C++ and Elk.
.PP
The only noteworthy difference between applications and extensions
is that the former tend to have their own @[.\f2main()\fP]
function that gains control on startup, while Scheme extensions do not
have a \f2main()\fP entry point\*-they are usually loaded into the
interpreter (or application) during runtime.
This distinction will become important in the next chapter, when
the different ways of joining Elk and C/C++ code will be discussed.
.\" ---------------------------------------------------------------------------
.K1 "Linking Applications and Extensions with Elk"
.Rf ch-linking \*(SN
.PP
There are two different mechanisms for integrating compiled C/C++ code
(extensions or an application) with Elk:
@[.\f2static linking\fP] and @[.\f2dynamic loading\fP].
The object files that make up an Elk-based application are usually
linked statically with the Scheme interpreter in the normal
way to produce an executable program.
Compiled extensions, on the other hand, are usually dynamically
loaded into the running Scheme interpreter as they are needed.
These conventions reflect the normal case;
Scheme extensions may as well be linked statically with the interpreter
.IP \(bu
to produce a ``specialized'' instance of the interpreter (for example,
when developing X11-based Scheme code, an extended version of the
interpreter may be produced by linking it statically with the
X11 extensions);
.IP \(bu
if a particular extension is required by an application from the
beginning (an application with an X-based user-interface would
be linked with the X11 extensions statically, as loading on-demand would
not be useful in this case);
.IP \(bu
on the (few) platforms where dynamic loading is not supported or
where dynamic loading has a large performance overhead.
.PP
Likewise, dynamic loading is not only useful for on-demand loading
of reusable Scheme extensions; \f2applications\fP can benefit
from this facility as well.
To reduce the size of the final executable, parts of an
application may loaded dynamically rather than linked statically if
they are used infrequently or if only a few of them are used at a time.
Dynamic loading enables the author of an extensible application to
decompose it into an arbitrary number of individual parts as an
alternative to combining them statically into a large, monolithic
executable.
An extensible newsreader program, for example, may include a separate
spelling check module that is dynamically loaded the first time it
is needed (i.\|e.\& when a newly written news article is to be
spell-checked).
.PP
The capability to dynamically load compiled C/C++ code into a running
application enables users to write @[.\f2hybrid extension]s\fP which
consist of a low-level C/C++ part and a high-level part written in
Scheme.
As a result, extensions can execute much faster (extensions to the
Emacs editor, for example, must be entirely written in Emacs-Lisp and
can therefore become slow if sufficiently complex); and
extensions can deal more easily with low-level, platform-specific
details.
.\" ---------------------------------------------------------------------------
.K1 "Dynamic Loading"
.Rf ch-dynl \*(SN
@[.=dynamic loading]
.PP
Object files (compiled C/C++ code) are loaded by means of the standard
@[.\f2load\fP primitive] of Scheme, just like ordinary Scheme files.
All you need to do is to compile your C or C++ source file,
apply the @[.\f2makedl\fP script] that comes with the Elk distribution
to the resulting object file, and load it into the interpreter or
application.
\f2makedl\fP prepares object files for dynamic loading (which is
a no-op on most platforms) and combines several object files into
one to speed up loading; arguments are the output file and one
or more input files or additional libraries (input and output file
may be identical):
.Es
\f6%\fP cc \-c \-I/usr/elk/include file.c
\f6%\fP /usr/elk/lib/makedl file.o file.o
\f6%\fP scheme
\f6>\fP (load 'file.o)
\f6>\fP
.Ee
(This examples assumes that Elk has been installed under ``/usr/elk''
on your site.
Additional arguments may be required for the call to \f2cc\fP.)
.PP
Elk does not attempt to discriminate object code and Scheme code
based on the files' contents; the names of object files are
required to end in ``.o'', the standard suffix for object modules
in UNIX.
Scheme files, on the other hand, end in ``.scm'' by convention.
This convention is not enforced by Elk\*-everything that is not
an object file is considered to be a Scheme file.
A list of object files may be passed to the \f2load\fP primitive
which may save time on platforms where a call to the system linker
is involved.
.PP
Loading object files directly as shown above is uncommon.
Instead, the Scheme part of a @[.hybrid extension] usually loads its
corresponding object file (and all the other files that are required)
automatically, so that one can write, for example,
.Es
(require 'unix)
.Ee
to load the @[.UNIX extension].
This expression causes the file \f2unix.scm\fP to be loaded, which
then loads the object file \f2unix.o\fP\*-the UNIX extension's low-level
part\*-automatically on startup.
Additional \f2load-libraries\fP (as explained in the next section)
may be set by the Scheme file immediately before loading the
extension's object file.
.PP
When an object file is loaded, @[.unresolved reference]s are resolved
against the symbols exported by the running interpreter or by the
combination of an application and the interpreter (the \f2base
program\fP).
This is an essential feature, as dynamically loaded extensions
must be able to reference the elementary Scheme primitives
defined by the interpreter core
and all the other functions that are available to the
extension/application programmer.
In addition, references are resolved against the symbols exported
by all previously loaded object files.
The term @[.\f2incremental loading\fP] is used for this style of dynamic
loading, as it allows building complex applications from small
components incrementally.
.\" ---------------------------------------------------------------------------
.K2 "Load Libraries"
.PP
Dynamically loadable object files usually have unresolved references
into one or more libraries, most likely at least into the standard
@[.C library].
Therefore, when loading an object file, references are resolved not
only against the base program and previously loaded object files,
but also against a number of user-supplied @[.\f2load libraries\fP].
The @[.X11 extension]s of Elk, for instance, need to be linked
against the respective libraries of the @[.X window system], such as
\f2libX11\fP and \f2libXt\fP.
These load libraries can be assigned to the Scheme variable
\f2load-libraries\fP which is bound in the top-level environment
of Elk.
Typically, \f2load-libraries\fP is dynamically assigned a set of
library names by means of @[.\f2fluid-let\fP] immediately before calling
\f2load\fP.
For example, the @[.Xlib extension] (\f2xlib.scm\fP) contains
code such as
.Es
(fluid-let
((load-libraries
(string-append "\-L/usr/X11/lib \-lX11 " load-libraries)))
(load 'xlib.o))
.Ee
to load the accompanying object file (\f2xlib.o\fP), linking it against the
system's X library in addition to whatever libraries were already in
use at that point.
The default value of \f2load-libraries\fP is ``\-lc'' (i.\|e.\& the
C library), as extensions are likely to use functions from this
library in addition to those C library functions that have already
been linked into the base program or have been pulled in by
previously loaded object files.
By using \f2string-append\fP in the example above, the specified
libraries are added to the default value of \f2load-libraries\fP rather
than overwriting it.
The exact syntax of the load libraries is platform specific.
For instance, ``\-L/usr/X11/lib'' as used above is
recognized by the system linker of most UNIX variants as an option
indicating in which directory the libraries reside on the system,
but different options or additional libraries are required on certain
platforms (as specified by the platform's ``config/site'' file
in the Elk distribution).
.\" ---------------------------------------------------------------------------
.K2 "Extension Initializers and Finalizers"
.PP
When loading an object file, Elk scans the file's symbol table
for the names of @[.extension initialization function]s or
@[.\f2extension initializer\fP]s.
These extension initializers are the initial entry points to
the newly loaded extension; their names must have the prefix
@[.``elk_init_''] (earlier the prefix ``init_'' was used; it was changed
in Elk \*(Vs to avoid name conflicts).
Each extension initializer found in the object file is invoked
to pass control to the extension.
The job of the extension initializers is to register the Scheme
types and primitives defined by the extension with the interpreter
and to perform any dynamic initializations.
.PP
As each extension may have an arbitrary number of initialization
functions rather than one single function with a fixed name, extension
writers can divide their extensions into a number of independent
modules, each of which provides its own initialization function.
The compiled modules can then be combined into one dynamically loadable
object file without having to lump all initializations into a central
initialization function.
.PP
In the same manner, extension can define an arbitrary number of
@[.\f2extension finalization function]s\fP which are called on termination
of the Scheme interpreter or application.
The names of finalization functions begin with @[.``elk_finit_''].
Extension finalization functions are typically used for clean-up
operations such as removing temporary files.
.PP
The extension initializers (as well as the finalizers) are called
in an unspecified order.
.\" ---------------------------------------------------------------------------
.K2 "C++ Static Constructors and Destructors"
.PP
In addition to calling extension initialization functions, the
\f2load\fP primitives invokes all @[.C++ static constructor]s that are
present in the dynamically loaded object file in case it contains
compiled C++ code.
Likewise, @[.C++ static destructor]s are called automatically on
termination.
The constructors and destructors are called in an unspecified order,
but all constructors (destructors) are called before calling any
extension initializers (finalizers).
Elk recognizes the function name prefixes of static constructor and
destructor functions used by all major UNIX @[.C++ compiler]s; new prefixes
can be added if required.
.\" ---------------------------------------------------------------------------
.K1 "Static Linking"
@[.=static linking]
.Rf ch-static \*(SN
.PP
Linking user-supplied code with Elk statically can be used as an
alternative to dynamic loading on platforms that do not support it,
for applications with their own @[.\f2main()\fP],
and to avoid the overhead of loading frequently used Elk extensions.
Dynamic loading and static linking may be used in combination\*-
additional object files can be loaded in a running executable
formed by linking the Scheme interpreter with extensions or with
an application (or parts thereof).
.PP
When making the Scheme interpreter component of Elk, these executables
and object files get installed (relative to your \f2install_dir\fP
which usually is ``/usr/elk'' or ``/usr/local/elk''):
.Rs
.IP \f2bin/scheme\fP
The freestanding, plain Scheme interpreter.
.IP \f2lib/standalone.o\fP
@[.=standalone.o]
The Scheme interpreter as a relocatable object file which can be
linked with user-supplied object files to form an executable.
This object file contains a \f2main()\fP function; thus the
Scheme interpreter starts up in the normal way when the executable
is invoked.
.IP \f2lib/module.o\fP
@[.=module.o]
Like \f2standalone.o\fP, except that the object file does not
export its own \f2main()\fP function.
Therefore, the object files linked with it have to supply a \f2main()\fP.
.Re
.PP
The object file \f2standalone.o\fP is typically linked with a number
of Elk extensions (e.\|g.\& the X11 extensions), while \f2module.o\fP
is used by Elk-based applications which contribute their own
\f2main()\fP and need to be ``in control'' on startup.
.\" ---------------------------------------------------------------------------
.K2 "Linking the Scheme Interpreter with Extensions"
.PP
A shell script @[.\f2linkscheme\fP] (installed as ``lib/linkscheme'')
simplifies combining the Scheme interpreter with a number
of\*-user-supplied or predefined\*-extensions statically.
This script is called with the name of the output file (the resulting
executable) and any number of object files and libraries.
It basically links the object files and libraries with
``standalone.o'' and supplies any additional libraries that may
be required by the interpreter.
In general, this can be done just as well by calling the linker or
compiler directly, but \f2linkscheme\fP also takes care of
additional processing that needs to be performed on at least one
platform (currently AIX).
.PP
To create an instance of Elk including the Xlib, Xt, and Xaw
extensions, \f2linkscheme\fP would be used as follows (again
assuming you have installed the software under ``/usr/elk''):
.Es
\f6%\fP cd /usr/elk
\f6%\fP lib/linkscheme x11scheme runtime/obj/xt.o runtime/obj/xaw/*.o \e
\-lXaw \-lXmu \-lXt \-lSM \-lICE \-lX11 \-lXext
.Ee
.PP
The exact form of the libraries depends on your platform and X11
version; for example, additional options may be required if X11
is not installed in a standard location at your site.
\f2xlib.o\fP is the @[.Xlib extension], \f2xt.o\fP is the X toolkit
intrinsics (Xt) extension, and the subdirectory \f2xaw\fP holds
the object files for all the @[.Athena widgets].
The executable \f2x11scheme\fP can now be used to run arbitrary
X11 applications using the Athena widgets without requiring
any runtime loading of object files belonging to the
@[.X11 extension]s:
.Es
\f6%\fP x11scheme
\f6>\fP (load '../examples/xaw/dialog.scm)
[Autoloading xwidgets.scm]
[Autoloading xt.scm]
[Autoloading siteinfo.scm]
\&...
.Ee
.PP
In the same way, \f2linkscheme\fP can be used to link the
Scheme interpreter with any new, user-supplied extensions,
with parts of an Elk-based application, or with any combination
thereof.
.\" ---------------------------------------------------------------------------
.K3 "Automatic Extension Initialization"
.Rf ch-autoinit \*(SN
.PP
When linking Elk with extensions, it is \f2not\fP necessary to add
calls to the @[.extension initializer]s to the Scheme interpreter's
\f2main()\fP function and recompile the interpreter;
all extensions are initialized automatically on startup.
To accomplish this kind of automatic initialization, Elk scans
its own symbol table on startup, invoking any @[.``elk_init_'']
functions and @[.C++ static constructor]s, in the
same way the symbol table of object files is scanned when
they are dynamically loaded.
@[.Extension finalizer]s and @[.C++ static destructor]s are saved
for calling on exit.
Automatic extension initialization only works if
.Rs
.IP \(bu
the executable file has a symbol table (i.\|e.\& you must not
strip it)
.IP \(bu
the executable file can be opened for reading
.IP \(bu
the interpreter can locate its executable file by scanning the
shell's directory search path.
.Re
.PP
The performance overhead caused by the initial scanning of the
symbol is small; the program's symbol table can be read or mapped
into memory efficiently (it it has not been automatically mapped
into the address space by the operating system in the first place).
.\" ---------------------------------------------------------------------------
.K2 "Linking the Scheme Interpreter with an Application"
.PP
Elk-based applications that have their own \f2main()\fP are linked with
the Scheme interpreter installed as \f2module.o\fP which, unlike
\f2standalone.o\fP, does not export a \f2main()\fP function.
No special \f2linkscheme\fP script is required to link with \f2module.o\fP;
application writers usually will add ``/usr/elk/lib/module.o''
(or whatever the correct path is) to the list of object files
in their Makefile.
To simplify linking with Elk, a trivial script @[.\f2ldflags\fP]
(which lives in ``lib'' along with \f2linkscheme\fP) is supplied that
just echoes any additional libraries required by the Scheme
interpreter.
Application developers may use \f2ldflags\fP in their Makefiles.
.PP
As \f2module.o\fP does not have a \f2main()\fP entry point,
an application using it must initialize the interpreter from
within its own \f2main()\fP.
This is done by calling .@[.\f2Elk_Init()\fP]:
.Es
void Elk_Init(int argc, char **argv, int init_flag, char *filename);
.Ee
.PP
\f2Elk_Init()\fP is only defined by \f2module.o\fP and is essentially
a ``wrapper'' around the Scheme interpreter's \f2main()\fP.
\f2argc\fP and \f2argv\fP are the arguments to be passed to
the Scheme interpreter's \f2main()\fP.
These may or may not be the calling program's original arguments;
however, @[.\f2argv[0\]\fP] must be that from the calling program
in any case (because its address is used by Elk to determine
the program's stack base).
If \f2init_flag\fP is nonzero, the interpreter scans its symbol table
to invoke @[.extension initializer]s as described in @(ch-autoinit).
@[.C++ static constructor]s, however, are never invoked by
\f2module.o\fP (regarless of \f2init_flag\fP), because they are already
taken care of by the runtime startup in this case.
If \f2filename\fP is nonzero, it is the name of Scheme file to
be loaded by \f2Elk_Init()\fP.
.\" ---------------------------------------------------------------------------
.K3 "An Example ``main()'' Function"
.PP
Figure @(main) shows a realistic (yet somewhat simplified) example
\f2main()\fP function of an application using Elk.
.Fs
char *directory;
.El
int main(int ac, char **av) {
char **eav;
int eac = 1, c;
.El
Set_App_Name(av[0]);
eav = safe_malloc((ac+2+1) * sizeof(char *)); /* ac + -p xxx + 0 */
eav[0] = av[0];
while ((c = getopt(ac, av, "gh:o")) != EOF) switch (c) {
case 'o':
\f2process option...\fP
case 'g':
eav[eac++] = "-g"; break;
case 'h':
eav[eac++] = "-h"; eav[eac++] = optarg; break;
case '?':
usage(); return 1;
}
if ((directory = getenv("APP_DIR")) == 0)
directory = DEFAULT_DIR;
eav[eac++] = "-p";
eav[eac] = safe_malloc(strlen(directory) + 11);
sprintf(eav[eac++], ".:%s/elk/scm", directory);
eav[eac] = 0;
Elk_Init(eac, eav, 0, 0);
.El
\f2initialize application's modules...\fP
.El
boot_code();
.El
\f2application's main loop (if written in C)\fP
...
.Fc "Example \f2main()\fP of an Elk-based application (simplified)"
.Fe main
.PP
The code shown in the example must construct a new argument
vector to be passed to \f2Elk_Init()\fP, because the application
has command line options of its own (just \f2\-o\fP in the example).
Two Elk-options (\f2\-g\fP and \f2\-h\fP) are handed to
\f2Elk_Init()\fP if present, so that a mixture of Elk-specific and
application-specific options can be given (see the manual page for
the Scheme interpreter for the meaning of Elk's options).
(\f2safe_malloc()\fP is assumed to be a wrapper around \f2malloc()\fP
with proper error-checking.)
@[.\f2Set_App_Name()\fP] is provided by Elk and is called with a name
to be displayed in front of fatal error messages by the interpreter.
.PP
When all the options have been parsed, an additional option
\f2\-p\fP is synthesized to provide a minimal initial @[.\f2load-path\fP]
for Elk.
This load-path consists of the current directory and a subdirectory
of the directory under which the application expects its files
that are needed during runtime.
An environment variable can be used to set this directory.
Defining a load-path like this has the benefit that a minimal,
self-contained Elk runtime environment (e.\|g.\& a toplevel
and the debugger) can be shipped with binary distributions of the
application so that users are not required to have Elk installed at
their sites.
.PP
When Elk has been initialized by calling \f2Elk_Init()\fP,
the application may initialize all its other modules and finally
load an initial Scheme file that ``boots'' the Scheme part of the
application (which may involve loading further Scheme files).
This initial Scheme file may be quite simple and just define a few
functions used later, or it main contain the application's entire
``driving logic'' or interactive user-interface.
This is accomplished by a function \f2boot_code()\fP which may
as simple as this:
.Es
void boot_code(void) {
char *fn = safe_malloc(strlen(directory) + 30);
.El
sprintf(fn, "%s/scm/app.scm", directory);
Set_Error_Tag("initial load");
Load_File(fn);
free(fn);
}
.Ee
.PP
@[.\f2Load_File()\fP] is defined by Elk and loads a Scheme file
whose name is supplied as a C string.
@[.\f2Set_Error_Tag()\fP] may be used by extensions and applications to
define the symbol that is passed as the first argument to the
standard @[.error handler] when a Scheme error is signaled
(see section @(ch-error)).
.\" ---------------------------------------------------------------------------
.K2 "Who is in Control?"
.Rf ch-control \*(SN
.PP
When an application's object files are loaded into the interpreter
dynamically or are linked with the interpreter using @[.\f2linkscheme\fP],
control initially rests in the interpreter.
In contrast, when the application is linked using @[.\f2module.o\fP]
and @[.\f2Elk_Init()\fP] as shown in the previous section, it defines
its own \f2main()\fP function, and hence the application is
``in control'' on startup.
.PP
From a technical point of view, it does not really make a difference
whether control rests in the interpreter or in the application
initially.
In the first case, the main ``driving logic'' (or ``main loop'') of
the application can simply be wrapped in a Scheme primitive which
is then called by the Scheme toplevel on startup to pass control
back to the application, if this is desired.
In any case, control usually changes frequently between the Scheme
interpreter and the actual application anyway\*-the Scheme interpreter
invokes callback functions or Scheme primitives provided by the
application, which may in turn invoke Scheme procedures or load
Scheme files, and so on.
.PP
The @[.Tcl]-like style of use, where control rests in the C-part of the
application most of the time, and where this C code ``calls out'' to
the interpreter occasionally by passing it an extension language
expression or a small script, is not typical for Elk.
It is supported, though; Elk provides a simple extension
to pass a Scheme expression to the interpreter as a C string and
receive the result in the same form, similar to what \f2Tcl_Eval()\fP
does in Tcl (see section @(ch-funcall)).
In a typical Elk-based application the extension language serves
as the ``backbone'' of the application:
the application's driving logic or main loop is written entirely in
Scheme, and this Scheme code calls out to the application's C layer,
using the data types, primitives, and other callbacks exported to the
extension language by the application.
With the help of the @[.X11 extension]s, the entire (graphical) user
interface of an application can be written in Scheme easily;
control can then passed to the application's C/C++ layer whenever
an Xt callback is triggered.
In this case, the application's ``main loop'' consists of a call
to the Scheme primitive corresponding to the X toolkit function
\f2XtAppMainLoop()\fP (the main event dispatch loop).
.\" ---------------------------------------------------------------------------
.K1 "Notes for Writing C/C++ Code Using Elk"
.Rf ch-notes \*(SN
.PP
This chapter describes general conventions and usage notes for
Elk-based C/C++ code and introduces a few useful facilities that
are not directly related to Scheme.
.\" ---------------------------------------------------------------------------
.K2 "Elk Include Files"
.PP
Every C or C++ file using functions, macros, or variables defined
by Elk must @[.=include files]include the file @[.\f2scheme.h\fP]:
.Es
#include <scheme.h> \f1or:\fP #include "scheme.h"
.Ee
.PP
This include file resides in a subdirectory \f2include\fP of
the directory where Elk has been installed on your system.
You must insert a suitable \-I option into your Makefiles to add
this directory to the C compiler's search path.
``scheme.h'' includes several other Elk-specific include files
from the same directory and, in addition, the standard C include
files @[.\f2<stdio.h>\fP] and @[.\f2\%<signal.h>\fP].
.\" ---------------------------------------------------------------------------
.K2 "Standard C and Function Prototypes"
.PP
All the examples shown in this manual are written in @[.ANSI/ISO C].
This assumes that the Elk include files have been installed with
@[.function prototypes] enabled.
Whether or not function prototypes are enabled is controlled by
a definition in the platform- and compiler-specific ``config/system''
file that has been selected for configuring Elk.
However, if the include files have function prototypes disabled,
prototypes are enable automatically if you are compiling your
code with a @[.C compiler] that defines the symbol @[.``_\^_STDC_\^_]''
as non-zero, or with a @[.C++ compiler] that defines @[.``_\^_cplusplus'']\**.
.FS
Although the public include files provided by Elk can be used
by C++ code, Elk itself cannot be compiled with a C++ compiler.
The interpreter has been written in C to maximize portability.
.FE
.PP
Elk include files that have been installed with function prototypes
disabled can also be ``upgraded'' by defining the symbol
@[.``WANT_PROTOTYPES''] before including ``scheme.h''.
Similarly, include files installed without function prototypes
can be used with a non-ANSI C compiler by defining the symbol
@[.``NO_PROTOTYPES''] before including ``scheme.h''.
.\" ---------------------------------------------------------------------------
.K2 "External Symbols Defined by Elk"
.PP
As extensions or applications are linked with Elk (regarless of whether
dynamic loading or static linking is used), they can in general
reference all external symbols exported by Elk.
Of these, only the symbols described in this manual may be used safely.
Use of other (private) symbols results in non-portable code, as
the symbols may change their meaning or may even be removed from future
releases of Elk.
The same restriction applies to the macros and types defined by
the include files of Elk.
.PP
In addition to the symbols defined by the Scheme interpreter kernel,
those exported by other @[.Scheme extensions] that are present in the same
executable (or have been loaded earlier) can be referenced from within
C/C++ code.
These extensions are not subject of this manual; you should refer
to the relevant documentation and the public include files that
are part of the extensions.
.PP
If Elk is linked with an application that has its own \f2main()\fP
function, none of the functions exported by Elk must be used before
the initial call to @[.\f2Elk_Init()\fP] (except \f2Set_App_Name()\fP).
.\" ---------------------------------------------------------------------------
.K2 "Calling Scheme Primitives"
.Rf ch-prims \*(SN
.PP
A large subset of the symbols exported by the Scheme interpreter is
the set of functions implementing the @[.Scheme primitives].
These may be used safely by extensions and applications.
There exists one C function for each Scheme primitive.
Its name is that of the corresponding primitive with the following
conversions applied:
.Rs
.IP \(bu
dashes are replaced by underscores, and the initial letters of the
resulting word components are capitalized;
.IP \(bu
the prefix ``P_'' is prepended;
.IP \(bu
``\(mi>'' is replaced by ``_To_'' (as in \f2vector\(mi>list\fP);
.IP \(bu
a trailing exclamation mark is deleted, except for \f2append!\fP and
\f2reverse!\fP, where ``_Set'' is appended;
.IP \(bu
a trailing question mark is replaced by the letter `p' (except for
\f2eq?, eqv?, equal?\&\fP and the string and character comparison
primitives, where it is deleted);
.Re
.LP
The names of a few functions are derived differently as shown
by this table:
.RS
.TS
box, tab(~);
c c
c l.
Scheme Primitive~C Function
_
<~P_Generic_Less()
>~P_Generic_Greater()
\&=~P_Generic_Equal()
<=~P_Generic_Eq_Less()
>=~P_Generic_Eq_Greater()
1+~P_Inc()
1\(mi and \(mi1+~P_Dec()
+~P_Generic_Plus()
\(mi~P_Generic_Minus()
*~P_Generic_Multiply()
/~P_Generic_Divide()
let*~P_Letseq()
.TE
.RE
.PP
According to these rules, the primitive \f2exact\(mi>inexact\fP can
be used from within C as \f2P_Exact_To_Inexact()\fP,
the predicate \f2integer?\&\fP is available as \f2P_Integerp()\fP, etc.
Authors of reusable Scheme extensions are encouraged to follow
these (or similar) naming conventions in their code.
.PP
All the functions implementing Scheme primitives (as well as
special forms, which are treated as primitives in Elk) receive
Scheme objects or arrays thereof as their arguments and return
Scheme objects as their values.
The underlying C type will be described in the next chapter.
For the semantics of the non-standard Scheme primitives defined
by Elk refer to the Reference Manual for the interpreter.
.\" ---------------------------------------------------------------------------
.K2 "Portable alloca()"
.Rf ch-alloca \*(SN
.PP
Elk provides a portable variant of @[.\f2alloca()\fP] as a set of macros
that can be used by extensions and applications.
\f2alloca()\fP, which is supported by most modern UNIX systems
and C compilers, allocates memory in the caller's stack frame;
the memory is automatically released when the function returns.
Elk simulates this functionality on the (rare) platforms where
\f2alloca()\fP is not available.
.PP
To allocate memory, the macro @[.\f2Alloca()\fP] is called with
a variable to which the newly allocated memory is assigned,
the type of that variable, and the number of bytes that are
requested.
The macro @[.\f2Alloca_End\fP] must be called (without an
argument list) before returning from a function or block that uses
@[.\f2Alloca()\fP]; this macro is empty on those platforms
that support the ordinary \f2alloca()\fP.
Finally, a call to the macro @[.\f2Alloca_Begin\fP] must be placed
in the function's declarations.
\f2Alloca()\fP usually is more efficient than \f2malloc()\fP and
\f2free()\fP, and the memory need not be freed when the function
is left prematurely because of an interrupt or by calling
a @[.continuation].
.LP
As an example, here is the skeleton of a function that is called
with a filename prefix and a suffix, concatenates them (separated
by a period), and opens the resulting file:
.Es
int some_function(char *prefix, char *suffix) {
char *name;
int len, fd;
Alloca_Begin;
.El
len = strlen(prefix) + 1 + strlen(suffix) + 1;
Alloca(name, char*, len);
sprintf(name, "%s.%s", prefix, suffix);
fd = open(name, ...);
...
Alloca_End;
}
.Ee
.\" ---------------------------------------------------------------------------
.K2 "Other Useful Macros and Functions"
.PP
The preprocessor symbols @[.ELK_MAJOR] and @[.ELK_MINOR] expand to
the major and minor version number of the current release of Elk.
They did not exist in versions older than Elk \*(Vs.
.PP
@[.\f2index()\fP], @[.\f2bcopy()\fP], @[.\f2bcmp()\fP], and
@[.\f2bzero()\fP] are defined as suitable macros on systems that do not
have them in their C library; they may be used by source files that
include ``scheme.h'', regardless of the actual platform.
.LP
Code linked with Elk may use the two functions
.Es
@[.=Safe_Malloc()]@[.=Safe_Realloc()]
char *Safe_Malloc(unsigned size);
char *Safe_Realloc(char *old_pointer, unsigned size);
.Ee
as alternatives to \f2malloc()\fP and \f2realloc()\fP.
If the request for memory cannot be satisfied, the standard Elk error
handler is called with a suitable error message.
.\" ---------------------------------------------------------------------------
.K1 "The Anatomy of Scheme Objects"
.Rf ch-anatomy \*(SN
.PP
All Scheme objects, regarless of their Scheme type, are represented
as instances of the type @[.\f2Object\fP] in C.
\f2Object\fP is implemented as a small C \f2struct\fP in newer Elk
releases and was an integral type earlier.
However, code using Elk should not assume a specific representation,
as it may change again in future revisions.
An \f2Object\fP consists of three components:
.Rs
.IP \(bu
the type of the corresponding Scheme object as a small integer
(the @[.``type field''] or @[.``tag field'']),
.IP \(bu
the contents of the object, either directly (for small objects) or
as a pointer into the Scheme @[.heap] (the @[.``pointer field'']),
.IP \(bu
a @[.``const bit''] which, if set, indicates that the object is read-only
and cannot be modified by destructive Scheme primitives.
.Re
.PP
Elk defines a few macros to retrieve and modify the fields
of an \f2Object\fP independent of its representation:
.Es
@[.=TYPE()]@[.=POINTER()]@[.=ISCONST()]@[.=SETCONST()]@[.=SET()]
TYPE(obj) ISCONST(obj) SET(obj,t,ptr)
POINTER(obj) SETCONST(obj)
.Ee
.PP
\f2TYPE()\fP returns the contents of the type field of an \f2Object\fP;
\f2POINTER()\fP returns the contents of the pointer field as an
\f2unsigned long\fP (different macros are provided for types which
have their values stored directly in the \f2Object\fP rather than
in the heap);
\f2ISCONST()\fP returns the value of the const bit;
and \f2SETCONST()\fP sets the const bit to 1 (it cannot be cleared
once it has been set).
\f2ISCONST()\fP and \f2SETCONST()\fP may only be applied to \f2Objects\fP
that have their value stored on the heap (such as vectors, strings, etc.);
all other types of Scheme objects are \f2ipso facto\fP read-only.
Another macro, \f2SET()\fP, can be used to set both the type and pointer
field of a new object.
.PP
Two objects can be compared by means of the macro
@[.=EQ()]
\f2EQ()\fP, which is also used as the basis for the Scheme
predicate @[.\f2eq?\fP]:
.Es
EQ(obj1,obj2)
.Ee
\f2EQ()\fP expands to a non-zero value if the type fields and the
pointer fields of the two objects are identical, else zero
(regardless of whether the pointer field really holds a pointer
or the object's actual value).
As \f2EQ()\fP may evaluate its arguments twice, it should not be
invoked with function calls or complex expressions.
.\" ---------------------------------------------------------------------------
.K2 "Type-specific Macros"
.PP
For each predefined Scheme type, there exists a preprocessor symbol
that expands to the integer value of that type (the contents of the
type field of members of the type).
The name of each such symbol is the name of the type with the
prefix ``T_'':
.Es
T_Boolean T_Pair T_Vector \f1etc...\fP
.Ee
These symbols are typically used as case labels in switch-statements to
discriminate the possible types of a given object, or in if-statements
to check whether a Scheme object is of a given type:
.Es
if (TYPE(obj) == T_Vector)
...
.Ee
In addition, each type defines a macro to extract the contents of
an object of that type and to convert it to the correct C type.
For example, the macro
.Es
@[.=CHAR()]
CHAR(obj)
.Ee
is used to fetch the character value (a C \f2int\fP) from members of
the Scheme type \f2character\fP, that is, from objects whose type field
contains the value \f2T_Character\fP.
Similarly, the macro
.Es
@[.=VECTOR()]
VECTOR(obj)
.Ee
gets the heap pointer conveyed in objects of the Scheme
type @[.\f2vector\fP].
For objects such as vectors, pairs, and procedures, the heap address is
coerced to a pointer to a C \f2struct\fP defining the layout of the
object.
There exists one structure type declaration for each such Scheme type;
their names are that of the type with ``S_'' prepended.
For example, \f2VECTOR()\fP returns a pointer to a structure with
the components \f2size\fP (the number of elements in the vector)
and \f2data\fP (the elements as an array of \f2Objects\fP).
These can be used from within C code like this:
.Es
int i, num = VECTOR(obj)->size;
.El
for (i = 0; i < num; i++)
VECTOR(obj)->data[i] = ...;
.Ee
Similarly, the structure underlying the Scheme type @[.\f2pair\fP] is
defined as:
.Es
struct S_Pair { Object car, cdr; };
.Ee
and the macro \f2PAIR()\fP returns a (heap) pointer to a member of
the structure \f2S_Pair\fP.
Macros such as \f2VECTOR()\fP and \f2PAIR()\fP just convert the contents
of the pointer field to a pointer of the correct type:
.Es
#define VECTOR(obj) ((struct S_Vector *)POINTER(obj))
#define PAIR(obj) ((struct S_Pair *)POINTER(obj))
.Ee
.PP
Authors of Scheme extensions and Elk-based applications are
encouraged to follow these conventions in their code and,
for each new type \f2xyz\fP, store the new type value
(which is allocated by the interpreter when the type is registered)
in a variable \f2T_Xyz\fP, and define a structure or class
\f2S_Xyz\fP, and a macro \f2XYZ()\fP that makes a pointer
to this structure from a member of the type.
Capitalization may vary according to personal preference.
.\" ---------------------------------------------------------------------------
.K1 "Defining New Scheme Primitives"
@[.=Scheme primitives]
.Rf ch-defprim \*(SN
.PP
In Elk, there exists a one-to-one relationship between Scheme
primitives and C functions:
each Scheme primitive\*-whether predefined or user-defined\*-is
implemented by a corresponding C function.
This includes @[.special forms], which are treated as a special kind
of primitives in Elk.
Extensions and applications use the function @[.\f2Define_Primitive()\fP]
to register a new Scheme primitive with the interpreter, supplying
its name and the C function that implements it.
In case of dynamically loadable extensions or application modules,
the calls to \f2Define_Primitive()\fP are placed in the
@[.extension initialization function]s that are called automatically
as the object file is loaded.
\f2Define_Primitive()\fP is declared as
.Es
void Define_Primitive((Object (*func)()), const char *name,
int minargs, int maxargs,
enum discipline disc);
.Ee
The arguments are:
.Rs
.IP \f2func\fP
a pointer to the C function implementing the new primitive;
.IP \f2name\fP
the name of the primitive as a null-terminated C string;
.IP \f2minargs\fP
the minimum number of arguments accepted by the primitive;
.IP \f2maxargs\fP
the maximum number of arguments (identical to \f2minargs\fP in most cases);
.IP \f2disc\fP
the @[.\f2calling discipline\fP] (usually \f2EVAL\fP).
.Re
.PP
\f2Define_Primitive()\fP creates a Scheme variable of the specified
name in the current (i.\|e.\& the caller's) lexical environment
and binds it to the newly created procedure.
Each C function that implements a primitive has a return type
of \f2Object\fP and, for a calling discipline of \f2EVAL\fP, zero
or more arguments of type \f2Object\fP which are bound to
the evaluated arguments passed to the Scheme primitive when
it is called.
The calling discipline must be one of the following:
.Rs
.IP \f2EVAL\fP\0\0
@[.=EVAL]
The primitive expects a fixed number of arguments; \f2minargs\fP
and \f2maxargs\fP must be identical\**.
.FS
Because of a limitation in the C language, primitives of type \f2EVAL\fP
can only have a fixed maximum number of arguments (currently 10).
If more arguments are required, \f2VARARGS\fP must be used instead.
.FE
.IP \f2VARARGS\fP
@[.=VARARGS]
The primitive has a variable number of arguments, and the
underlying C function is called with an argument count and
an array of arguments.
Defining primitives with a variable number of arguments will
explained in more detail in section @(ch-varargs).
.IP \f2NOEVAL\fP
@[.=NOEVAL]
The arguments are passed as a Scheme list of unevaluated objects\*-a
single argument of the type \f2Object\fP.
Primitives using this discipline will then use \f2Eval()\fP
as described in section @(ch-funcall) to evaluate some or all
of the arguments.
\f2NOEVAL\fP is only rarely used (with the exception of the built-in
@[.special forms] of Elk); extensions and applications mostly use macros as a
more convenient way to defined new syntactical forms.
.Re
.LP
Figure @(defprim) shows a simple example for defining a new
Scheme primitive.
.Fs
#include "scheme.h"
.El
Object p_vector_reverse(Object vec) {
Object tmp, *s, *t;
.El
Check_Type(vec, T_Vector);
for (s = VECTOR(vec)->data, t = s+VECTOR(vec)->size; --t > s; s++)
tmp = *s, *s = *t, *t = tmp;
return vec;
}
.El
void elk_init_vector(void) {
Define_Primitive(p_vector_reverse, "vector-reverse!", 1, 1, EVAL);
}
.Fc "Defining a new Scheme Primitive"
.Fe defprim
.PP
The primitive @[.\f2vector-reverse!\fP] defined by the example extension
reverses the elements of a Scheme @[.vector] in place and returns
its argument (note the final exclamation mark indicating the
destructive operation).
@[.\f2Check_Type()\fP] is a simple macro that compares the type field
of the first argument (an \f2Object\fP) with the second argument
and signals and error if they do not match.
This macro is used primarily for type-checking the arguments to
Scheme primitives.
A call to the macro @[.\f2Check_Mutable()\fP] with the vector
as an argument
could have been inserted before the loop to check whether the vector
is read-only and to automatically raise an error if this is the case.
The example code forms a complete extension including an
@[.extension initialization function] and could be linked with
the interpreter, or loaded dynamically into the interpreter as
follows:
.Es
\f6%\fP cc \-c \-I/usr/elk/include vec.c; makedl vec.o vec.o
\f6%\fP scheme
\f6>\fP (load 'vec.o)
\f6>\fP (define v '#(hello word))
\f6v
>\fP (vector-reverse! v)
\f6#(world hello)
>\fP v
\f6#(world hello)
>\fP
.Ee
.\" ---------------------------------------------------------------------------
.K2 "Making Objects Known to the Garbage Collector"
.Rf ch-gc \*(SN
@[.=garbage collector]
.PP
Consider the non-destructive version of the primitive
@[.\f2vector-reverse\fP] shown in Figure @(vecrev1), which returns a new
vector instead of altering the contents of the original vector.
.Fs
Object p_vector_reverse(Object vec) {
Object ret;
int i, j;
.El
Check_Type(vec, T_Vector);
ret = Make_Vector(VECTOR(vec)->size, False);
for (i = 0, j = VECTOR(vec)->size; --j >= 0; i++)
VECTOR(ret)->data[i] = VECTOR(vec)->data[j];
return ret;
}
.Fc "Non-destructive Scheme primitive \f2vector-reverse\fP"
.Fe vecrev1
.PP
The code in Figure @(vecrev1) is identical to that shown in Figure
@(defprim), except that a new vector is allocated, filled with
the contents of the original vector in reverse order, and returned
as the result of the primitive.
@[.\f2Make_Vector()\fP] is declared by Elk:
.Es
Object Make_Vector(int size, Object fill);
.Ee
\f2size\fP is the length of the vector, and all elements are initialized
to the Scheme object \f2fill\fP.
In the example, the predefined global variable @[.\f2False\fP] is
used as the \f2fill\fP object; it holds the boolean Scheme constant #f
(any \f2Object\fP could have been used here).
.PP
Although the C function may look right, there is a problem when
it comes to garbage collection.
To understand the problem and its solution, it may be helpful to have a
brief look at how the garbage collector\**
.FS
Elk actually employs two garbage collectors, one based on the
traditional stop-and-copy strategy, and a generational, incremental
garbage collector which is less disruptive but not supported
on all platforms.
.FE
works (the following description presents a simplified view; the real
algorithm is more complex).
In Elk, a @[.garbage collection] is triggered automatically whenever
a request for heap space cannot be satisfied because
the @[.heap] is full, or explicitly by calling the primitive
@[.\f2collect\fP] from within Scheme code.
The garbage collector traces all ``live'' objects starting with
a known @[.\f2root set\fP] of pointers to reachable objects
(basically the interpreter's global lexical environment and its
symbol table).
Following these pointers, all accessible Scheme objects are located
and copied to a new heap space in memory (``forwarded''), thereby
compacting the heap.
Whenever an object is relocated in memory during garbage collection,
the contents of the @[.pointer field] of the corresponding C \f2Object\fP
is updated to point to the new location.
After that, any constituent objects (e.\|g.\& the elements of a
vector) are forwarded in the same way.
.PP
As live objects are relocated in memory, \f2all\fP pointers to an
object need to be updated properly when that object is forwarded
during garbage collection.
If a pointer to a live object were not in the root set (that is,
not reachable by the garbage collector), the object would either
become garbage erroneously during the next garbage collection, or,
if it had been reached through some other pointer, the original
pointer would now point to an invalid location.\**
.FS
The problem of managing an ``exact root set'' can be avoided by
a technique called \f2conservative\fP garbage collection.
A conservative garbage collector treats the data segment, stack,
and registers of the running program as \f2ambiguous roots\fP.
If the set of ambiguous roots is a superset of the \f2actual\fP roots,
then a pointer that looks like a heap pointer can safely be considered
as pointing to an accessible object that cannot be reclaimed.
At the time Elk was designed, conservative GC was still in its
infancy and sufficient experience did not exist.
For this reason, and because of the implied risks on certain
machine architectures, the inherent portability problems, and
the inability to precisely determine the actual memory utilization,
a traditional GC strategy was chosen for Elk.
.FE
This is exactly what happens in the example shown in Figure @(vecrev1).
.PP
The call to \f2Make_Vector()\fP in the example triggers a garbage
collection if the heap is too full to satisfy the request for heap
space.
As the \f2Object\fP pointer stored in the argument \f2vec\fP
is invisible to the garbage collector, its pointer field cannot
be updated when the vector to which it points is forwarded during
the garbage collection started inside \f2Make_Vector()\fP.
As a result, all further references to \f2VECTOR(vec)\fP will
return an invalid address and may cause the program to crash
(immediately or, worse, at a later point).
The solution is simple: the primitive just needs to add \f2vec\fP
to the set of initial pointers used by the garbage collector.
This is done by inserting the line
.Es
GC_Link(vec);
.Ee
at the beginning of the function before the call to \f2Make_Vector()\fP.
@[.\f2GC_Link()\fP] is a macro.
Another macro, @[.\f2GC_Unlink\fP], must be called later (e.\|g.\& at
the end of the function) without an argument list to remove the object
from the root set again.
In addition, a call to @[.\f2GC_Node\fP] (again without an argument
list) must be placed in the declarations at the beginning of
the enclosing function or block.
Figure @(vecrev2) shows the revised, correct code.
.Fs
Object p_vector_reverse(Object vec) {
Object ret;
int i, j;
GC_Node;
.El
GC_Link(vec);
Check_Type(vec, T_Vector);
ret = Make_Vector(VECTOR(vec)->size, False);
for (i = 0, j = VECTOR(vec)->size; --j >= 0; i++)
VECTOR(ret)->data[i] = VECTOR(vec)->data[j];
GC_Unlink;
return ret;
}
.Fc "Non-destructive Scheme primitive \f2vector-reverse\fP, corrected version"
.Fe vecrev2
.PP
Appendix A lists the C functions which can trigger a garbage collection.
Any @[.local variable] or argument of type \f2Object\fP must be protected
in the manner shown above if one of these functions is called during
its lifetime.
This may sound more burdensome than it really is, because most of
the ``dangerous'' functions are rarely or never used from within
C/C++ extensions or applications in practice.
Most primitives that require calls to \f2GC_Link()\fP use some function
that creates a new Scheme object, such as \f2Make_Vector()\fP in
the example above.
.PP
To simplify GC protection of more than a single argument or variable,
additional macros @[.\f2GC_Link2()\fP], @[.\f2GC_Link3()\fP], and
so on up to \f2GC_Link7()\fP are provided.
Each of these can be called with as many arguments of type \f2Object\fP
as is indicated by the digit (separate macros are required, because
macros with a variable number of arguments cannot be defined in C).
A corresponding macro @[.\f2GC_Node2\fP], @[.\f2GC_Node3\fP], and so on,
must be placed in the declarations.
Different \f2GC_Link*()\fP calls cannot be mixed.
All @[.local variable]s passed to one of the macros must have been
initialized.
GC protection is not required for ``pointer-less'' objects such as
booleans and small integers, and for the arguments of primitives
with a variable number of arguments (as described in section @(ch-varargs)).
Section @(ch-gcglobal) will describe how global (external)
\f2Object\fP variables can be added to the root set.
.PP
Here is how the implementation of the primitive @[.\f2cons\fP] uses
\f2GC_Link2()\fP to protect its arguments (the @[.car] and the @[.cdr] of
the new pair):
.Es
Object P_Cons(Object car, Object cdr) {
Object new_pair;
GC_Node2;
.El
GC_Link2(car, cdr);
new_pair = \f2allocate heap space and initialize object\fP;
GC_Unlink;
return new_pair;
}
.Ee
.PP
There are a few pitfalls to be aware of when using ``dangerous''
functions from within your C/C++ code.
For example, consider this code fragment which fills a Scheme
vector with the program's environment strings that are available
through the null-terminated string array \f2environ[]\fP:
.Es
Object vec = \f2new vector of the right size\fP;
int i;
GC_Node;
.El
GC_Link(vec);
for (i = 0; environ[i] != 0; i++)
VECTOR(vec)->data[i] = Make_String(environ[i], strlen(environ[i]));
.Ee
(\f2Make_String()\fP creates and initializes a new Scheme string.)
The body of the for-loop contains a subtle bug: depending on the
compiler used, the left hand side of the assignment (the expression
involving \f2vec\fP) may be evaluated before @[.\f2Make_String()\fP]
is invoked.
As a result, a copy of the contents of \f2vec\fP might be, for instance,
stored in a register before a garbage collection is triggered while
evaluating the right hand side of the assignment.
The garbage collector would then move the vector object in memory,
updating the\*-properly GC-protected\*-variable \f2vec\fP, but not the
temporary copy in the register, which is now a dangling reference.
To avoid this, the loop must be modified along these lines:
.Es
for (i = 0; environ[i]; i++) {
Object temp = Make_String(environ[i], strlen(environ[i]));
VECTOR(vec)->data[i] = temp;
}
.Ee
A related pitfall to watch out for is exemplified by this code
fragment:
.Es
Object obj;
\&...
GC_Link(obj);
\&...
some_function(obj, P_Cons(car, cdr));
.Ee
Here, the call to @[.\f2P_Cons()\fP]\*-just like \f2Make_String()\fP
above\*-can trigger a garbage collection.
Depending on the C compiler, the properly GC-protected object
pointer \f2obj\fP may be pushed on the argument stack before \f2P_Cons()\fP
is invoked, as the order in which function arguments\*-just like the
operands of the assignment operator\*-are evaluated is undefined in the
C language.
In this case, if a garbage collection takes place and the heap object
to which \f2obj\fP points is moved, \f2obj\fP will be updated
properly, but the copy on the stack will not.
Again, the problem can be avoided easily by assigning the result of the
nested function call to a temporary \f2Object\fP variable and
use this variable in the enclosing function call:
.Es
temp = P_Cons(car, cdr);
some_function(obj, temp);
.Ee
.\" ---------------------------------------------------------------------------
.K2 "Primitives with Variable-Length Argument Lists"
.Rf ch-varargs \*(SN
.PP
Primitives with a variable number of arguments are registered with
the interpreter by calling @[.\f2Define_Primitive()\fP] with
the @[.calling discipline] @[.\f2VARARGS\fP] and with different
values for \f2minargs\fP and \f2maxargs\fP.
The special symbol @[.\f2MANY\fP] can be given as the maximum number
of arguments to indicate that there is no upper limit on the
primitive's number of actual arguments.
The C/C++ function implementing a primitive with a variable number
of arguments is called with two arguments: an integer count
that specifies the number of actual arguments, and the
Scheme arguments as an array of \f2Objects\fP (that is, a pointer
to \f2Object\fP).
The objects passed as the argument vector of \f2VARARGS\fP primitives
are already registered with the garbage collector; calls to
\f2GC_Link()\fP are not required.
As an example for a primitive with an arbitrary number of arguments,
here is the definition of a simplified variant of @[.\f2append!\fP]
(which does not handle empty lists):
.Es
Object p_append_set (int argc, Object *argv); {
int i;
.El
for (i = 0; i < argc-1; i++)
(void)P_Set_Cdr (P_Last_Pair (argv[i]), argv[i+1]);
return *argv;
}
.Ee
The corresponding call to \f2Define_Primitive()\fP would read:
.Es
Define_Primitive(p_append_set, "append!", 0, MANY, VARARGS);
.Ee
.PP
Besides implementing primitives with an indefinite maximum number
of arguments, the \f2VARARGS\fP discipline is frequently used for
primitives with an optional argument.
For example, a primitive encapsulating the UNIX \f2open()\fP system
call, which has two fixed arguments (filename, flags) and an optional
third argument (the mode for newly created files, i.\|e.\& calls with
the flag \f2O_CREAT\fP), could be defined as follows:
.Es
Object p_unix_open(int argc, Object *argv) {
char *name = get_file_name(argv[0]);
int flags = get_flags(argv[1]);
mode_t mode;
.El
if (flags & O_CREAT) {
if (argc < 3)
\f2error--too few arguments\fP
mode = get_mode(argv[2]);
...
.Ee
The call to \f2Define_Primitive()\fP could then be written as:
.Es
Define_Primitive(p_unix_open, "unix-open", 2, 3, VARARGS);
.Ee
.\" ---------------------------------------------------------------------------
.K1 "Predefined Scheme Types"
.Rf ch-types \*(SN
.PP
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 \f2Object\fP;
followed by the types whose members are C \f2structs\fP that
reside on the Scheme heap.
.\" ---------------------------------------------------------------------------
.K2 "Booleans (T_Boolean)"
@[.=T_Boolean]
.PP
\f2Objects\fP of type \f2T_Boolean\fP can hold the values #t and #f.
Two \f2Objects\fP initialized to #t and #f, respectively, are
available as the external C variables \f2True\fP and \f2False\fP.
The macro
.Es
@[.=Truep()]
Truep(obj)
.Ee
can be used to check whether an arbitrary Scheme object is regarded
as true.
Use of \f2Truep()\fP is not necessarily equivalent to
.Es
!EQ(obj,False)
.Ee
because the empty list may count as false in addition to #f if
backwards compatibility to older Scheme language versions has
been enabled.
\f2Truep()\fP may evaluate its argument twice and should therefore
not be invoked with a function call or a complex expression.
.LP
The two functions
.Es
@[.=Eqv()]@[.=Equal()]
int Eqv(Object, Object);
int Equal(Object, Object);
.Ee
are identical to the primitives \f2P_Eqv()\fP and \f2P_Equal()\fP,
except that they return a C integer rather than a Scheme boolean and
therefore can be used more conveniently in C/C++.
.\" ---------------------------------------------------------------------------
.K2 "Characters (T_Character)"
@[.=T_Character]
.PP
The character value stored in an \f2Object\fP of type \f2T_Character\fP
can be obtained by the macro
.Es
@[.=CHAR()]
CHAR(char_obj)
.Ee
as a non-negative \f2int\fP.
A new character object is created by calling the function
.Es
@[.=Make_Char()]
Object Make_Char(int c);
.Ee
The predefined external C variable @[.\f2Newline\fP] holds the
newline character as a Scheme \f2Object\fP.
.\" ---------------------------------------------------------------------------
.K2 "Empty List (T_Null)"
@[.=T_Null]
.PP
The type \f2T_Null\fP has exactly one member\*-the empty list;
hence all \f2Objects\fP of this type are identical.
The empty list is available as the external C variable @[.\f2Null\fP].
This variable is often used to initialize \f2Objects\fP that will
be assigned their real values later, for example, as the fill
element for newly created vectors or to initialize \f2Objects\fP
in order to \f2GC_Link()\fP them.
A macro \f2Nullp()\fP is provided as a shorthand for checking if an
\f2Object\fP is the empty list:
.Es
@[.=Nullp()]
#define Nullp(obj) (TYPE(obj) == T_Null)
.Ee
This macro is used frequently in the termination condition of
for-loops that scan a Scheme list:
.Es
Object tail;
\&...
for (tail = some_list; !Nullp(tail); tail = Cdr(tail))
process_element(Car(tail));
.Ee
(\f2Car()\fP and \f2Cdr()\fP essentially are shorthands for
\f2P_Car()\fP and \f2P_Cdr()\fP and will be revisited in
the section on pairs).
.\" ---------------------------------------------------------------------------
.K2 "End of File (T_End_Of_File)"
@[.=T_End_Of_File]
.PP
The type \f2T_End_Of_File\fP has one member\*-the
@[.end-of-file object]\*-and is only rarely used from within
user-supplied C/C++ code.
The external C variable @[.\f2Eof\fP] is initialized to the
end-of-file object.
.\" ---------------------------------------------------------------------------
.K2 "Integers (T_Fixnum and T_Bignum)"
@[.=T_Fixnum]@[.=T_Bignum]
.PP
Integers come in two flavors: @[.\f2fixnums\fP] and @[.\f2bignums\fP].
The former have their value stored directly in the pointer field and
are wide enough to hold most C \f2ints\fP.
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:
.Es
@[.=FIXNUM_FITS()]@[.=UFIXNUM_FITS()]
FIXNUM_FITS(integer)
UFIXNUM_FITS(unsigned_integer)
.Ee
The former always returns 1 in Elk \*(Vs, but the range of integer
values that can be represented as a fixnum may be restricted in
future revisions.
It is guaranteed, however, that at least two bits less than the
machine's word size will be available for fixnums in future
versions of Elk.
.LP
The value stored in a fixnum can be obtained as a C \f2int\fP by
calling the macro
.Es
@[.=FIXNUM()]
FIXNUM(fixnum_obj)
.Ee
A macro
.Es
@[.=Check_Integer()]
Check_Integer(obj)
.Ee
can be used as a shorthand for checking whether an \f2Object\fP is
a fixnum or a bignum and raising an error otherwise.
.LP
The following functions are provided to convert C integers to
Scheme integers:
.Es
@[.=Make_Integer()]@[.=Make_Unsigned()]
@[.=Make_Long()]@[.=Make_Unsigned_Long()]
Object Make_Integer(int);
Object Make_Unsigned(unsigned);
Object Make_Long(long);
Object Make_Unsigned_Long(unsigned long);
.Ee
\f2Make_Integer()\fP returns a fixnum object if \f2FIXNUM_FITS()\fP
returns true for the argument, otherwise a bignum.
Likewise, \f2Make_Long()\fP usually returns a fixnum but may have to resort
to bignums on architectures where a C \f2long\fP is wider than an \f2int\fP.
\f2Make_Unsigned()\fP returns a bignum if the specified integer
is larger than the largest positive \f2int\fP that fits into a fixnum
(\f2UFIXNUM_FITS()\fP returns zero in this case).
Another set of functions convert a Scheme number to a C integer:
.Es
@[.=Get_Integer()]@[.=Get_Exact_Integer()]
int Get_Integer(Object);
int Get_Exact_Integer(Object);
.El
@[.=Get_Unsigned()]@[.=Get_Exact_Unsigned()]
unsigned Get_Unsigned(Object);
unsigned Get_Exact_Unsigned(Object);
.El
@[.=Get_Long()]@[.=Get_Exact_Long()]
long Get_Long(Object);
long Get_Exact_Long(Object);
.El
@[.=Get_Unsigned_Long()]@[.=Get_Exact_Unsigned_Long()]
unsigned long Get_Unsigned_Long(Object);
unsigned long Get_Exact_Unsigned_Long(Object);
.Ee
These functions signal an error if one of the following
conditions is true:
.Rs
.IP \(bu
the argument is neither a fixnum, nor a bignum, nor a flonum (real
number) with a fractional part of zero (more about @[.flonums] in the
next section);
.IP \(bu
the function is one of the ``unsigned'' variants and the argument is
a negative number;
.IP \(bu
the argument is a bignum too large for the respective return type;
.IP \(bu
the function is one of the ``exact'' variants and the argument
is neither a fixnum nor a bignum;
.IP \(bu
the argument is a flonum that cannot be coerced to the respective
return type.
.Re
.LP
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 \f2dup\fP system
call (which returns an integer file descriptor pointing to the
same file as the original one) can be written as:
.Es
Object p_unix_dup(Object fd) {
return Make_Integer(dup(Get_Exact_Unsigned(fd)));
.Ee
Note that if \f2Get_Unsigned()\fP (or \f2Get_Integer()\fP) had been
used here in place of the ``exact'' conversion function, it would be
possible to write expressions such as:
.Es
(define fd (unix-dup (truncate 1.2)))
.Ee
.\" ---------------------------------------------------------------------------
.K2 "Floating Point Numbers (T_Flonum)"
@[.=T_Flonum]
.PP
@[.=real numbers]
Real and @[.inexact number]s are represented as \f2Objects\fP of type
\f2T_Flonum\fP.
Each such object holds a pointer to a structure on the heap with
a component \f2val\fP of type \f2double\fP, so that the expression
.Es
@[.=FLONUM()]
FLONUM(flonum_obj)->val
.Ee
can be used to obtain the \f2double\fP value.
To convert a Scheme number to a \f2double\fP regardless of its
type, the more general function
.Es
@[.=Get_Double()]
double Get_Double(Object);
.Ee
can be used.
It raises an error if the argument is not a fixnum, bignum, or flonum,
or if it is a bignum too large to fit into a \f2double\fP.
.LP
The functions
.Es
@[.=Make_Flonum()]@[.=Make_Reduced_Flonum()]
Object Make_Flonum(double);
Object Make_Reduced_Flonum(double);
.Ee
convert a C \f2double\fP to a flonum; the latter returns a fixnum
if the \f2double\fP is small enough to fit into a fixnum and
has a fractional part of zero.
The macro
.Es
@[.=Check_Number()]
Check_Number(obj)
.Ee
checks whether the given \f2Object\fP is a number (that is, a fixnum,
bignum, or flonum in the current revision of Elk) and raises an
error otherwise.
.\" ---------------------------------------------------------------------------
.K2 "Pairs (T_Pair)"
@[.=T_Pair]
.PP
Pairs have two components of type \f2Object\fP, the @[.car] and the @[.cdr],
that can be accessed as:
.Es
@[.=PAIR()]
PAIR(pair_obj)->car
PAIR(pair_obj)->cdr
.Ee
Two macros @[.\f2Car()\fP] and @[.\f2Cdr()\fP] are provided as shorthands
for these expressions, and another macro @[.\f2Cons()\fP] can be
used in place of @[.\f2P_Cons()\fP] to create a new pair.
The macro
.Es
@[.=Check_List()]
Check_List(obj)
.Ee
checks whether the specified \f2Object\fP is either a pair or
the empty list and signals an error otherwise.
The predefined function
.Es
@[.=Fast_Length()]
int Fast_Length(Object list);
.Ee
can be used to compute the length of the given Scheme list.
This function is more efficient than the primitive \f2P_Length()\fP,
because it neither checks the type of the argument nor whether
the given list is proper, and the result need not be converted
to a Scheme number.
The function
.Es
@[.=Copy_List()]
Object Copy_List(Object list);
.Ee
returns a copy of the specified list (including all its sublists).
.PP
As explained in section @(ch-gc), care must be taken when mixing
calls to these macros, because \f2Cons()\fP may trigger a garbage
collection:
an expression such as
.Es
Car(x) = Cons(y, z);
.Ee
is wrong, even if \f2x\fP is properly ``GC_Linked'', and should be
replaced by
.Es
tmp = Cons(x, y);
Car(x) = tmp;
.Ee
or a similar sequence.
.\" ---------------------------------------------------------------------------
.K2 "Symbols (T_Symbol)"
@[.=T_Symbol]
.PP
\f2Objects\fP of type \f2T_Symbol\fP have one public component\*-the
symbol's name as a Scheme string (that is, an \f2Object\fP of type
\f2T_String\fP):
.Es
@[.=SYMBOL]
SYMBOL(symbol_obj)->name
.Ee
A new symbol can be created by calling one of the functions
.Es
@[.=Intern()]@[.=CI_Intern()]
Object Intern(const char *);
Object CI_Intern(const char *);
.Ee
with the new symbol's name as the argument.
\f2CI_Intern()\fP is the case-insensitive variant of \f2Intern()\fP;
it maps all upper case characters to lower case.
\f2EQ()\fP yields true for all \f2Objects\fP returned by calls
to \f2Intern()\fP with strings with the same contents (or calls
to \f2CI_Intern()\fP with strings that are identical after
case conversion).
This is the main property that distinguishes symbols from strings
in Scheme.
.PP
A symbol that is used by more than one function can be stored in
a global variable to save calls to \f2Intern()\fP.
This can be done using the convenience function
.Es
@[.=Define_Symbol()]
void Define_Symbol(Object *var, const char *name);
.Ee
\f2Define_Symbol()\fP is called with the address of a variable
where the newly-interned symbol is stored and the name of
the symbol to be handed to \f2Intern()\fP.
The function adds the new symbol to the garbage collector's
@[.root set] to make it reachable (as described in section @(ch-gcglobal).
Example:
.Es
static Object sym_else;
\&...
void elk_init_example(void) {
Define_Symbol(&sym_else, "else");
...
}
.Ee
.\" ---------------------------------------------------------------------------
.K3 "The Non-Printing Symbol"
.PP
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 \f2string\(mi>symbol\fP with
the empty string.
On the C language level, the non-printing symbol is available as
the external variable @[.\f2Void\fP], so that primitives lacking
a useful return value can use
.Es
return Void;
.Ee
.\" ---------------------------------------------------------------------------
.K2 "Strings (T_String)"
@[.=T_String]
.PP
\f2Objects\fP of type string have two components\*-the length and the
contents of the string as a pointer to \f2char\fP:
.Es
STRING(string_obj)->size
STRING(string_obj)->data
.Ee
The \f2data\fP component is not null-terminated, as a string
itself may contain a null-byte as a valid character in Elk.
A Scheme string is created by calling the function
.Es
@[.=Make_String()]
Object Make_String(const char *init, int size);
.Ee
\f2size\fP is the length of the newly-created string.
\f2init\fP is either the null-pointer or a pointer to \f2size\fP
characters that are copied into the new Scheme string.
For example, the sequence
.Es
Object str;
\&...
str = Make_String(0, 100);
bzero(STRING(str)->data, 100);
.Ee
generates a string holding 100 null-bytes.
.PP
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
.Es
@[.=Get_String()]
char *Get_String(Object);
.Ee
that returns the contents of the Scheme string argument as a
null-terminated C string.
An error is raised if the argument is not a string.
\f2Get_String()\fP has to create a copy of the contents of the Scheme
string in order to append the null-character.
To avoid requiring the caller to provide and release space for the
copy, \f2Get_String()\fP operates on and returns @[.NUMSTRBUFS]
internal, cyclically reused buffers (the value of NUMSTRBUFS is 3
in Elk \*(Vs).
Consequently, no more than NUMSTRBUFS results of \f2Get_String()\fP
can be used simultaneously (which is rarely a problem in practice).
As an example, a Scheme primitive that calls the C library
function \f2getenv()\fP and returns #f on error can be written as
.Es
Object p_getenv(Object name) {
char *ret = getenv(Get_String(name));
return ret ? Make_String(ret, strlen(ret)) : False;
}
.Ee
.PP
If more strings are to be used simultaneously, the macro
@[.\f2Get_String_Stack()\fP] 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.
\f2Get_String_Stack()\fP allocates space by means of @[.\f2Alloca()\fP]
(as explained in section @(ch-alloca)); hence a call to
@[.\f2Alloca_Begin\fP] must be placed in the declarations of the
enclosing function or block, and @[.\f2Alloca_End\fP] must be
called before returning from it.
.PP
An additional function @[.\f2Get_Strsym()\fP] and an additional
macro @[.\f2Get_Strsym_Stack()\fP] are provided by Elk; these
are identical to \f2Get_String()\fP and \f2Get_String_Stack()\fP,
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.
.PP
As an example for the use of \f2Get_String_Stack()\fP, here is
a simple Scheme primitive \f2exec\fP that is called with the
name of a program and one more more arguments and passes them
to the \f2execv()\fP system call:
.Es
Object p_exec(int argc, Object *argv) {
char **argp; int i;
Alloca_Begin;
.El
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 */
\f2error...\fP
}
.El
elk_init_example() {
Define_Primitive(p_exec, "exec", 2, MANY, VARARGS);
}
.Ee
The primitive can be used as follows:
.Es
(exec "/bin/ls" "ls" "-l")
.Ee
\f2Get_String()\fP could not be used in this primitive, because
the number of string arguments may exceed the number of static
buffers maintained by \f2Get_String()\fP.
.\" ---------------------------------------------------------------------------
.K2 "Vectors (T_Vector)"
@[.=T_Vector]
.PP
The layout of \f2Objects\fP of type vector is identical to that
of strings, except that the \f2data\fP component is an array
of \f2Objects\fP.
A function @[.\f2Make_Vector()\fP] creates a new vector as has been
explained in section @(ch-gc) above.
.\" ---------------------------------------------------------------------------
.K2 "Ports (T_Port)"
@[.=T_Port]
.PP
The components of \f2Objects\fP of type \f2T_Port\fP are not
normally accessed directly from within C/C++ code, except for
.Es
PORT(port_obj)->closefun
.Ee
which is a pointer to a function receiving an argument of
type ``FILE*'' (for example, a pointer to \f2fclose()\fP),
provided that the port is a file port.
It is called automatically whenever the port is closed,
either because \f2close-input-port\fP or \f2close-output-port\fP
is applied to it or because the garbage collector has determined
that the port is no longer reachable.
.LP
A new file port is created by calling
.Es
@[.=Make_Port()]
Object Make_Port(int flags, FILE *f, Object name);
.Ee
with a first argument of either zero (output port),
\f2P_INPUT\fP (input port) or \f2P_BIDIR\fP (bidirectional port),
the file pointer, and the name of the file as a Scheme string.
The macros
.Es
@[.=Check_Input_Port()]@[.=Check_Output_Port()]
Check_Input_Port(obj)
Check_Output_Port(obj)
.Ee
check whether the specified port is open and is capable of
input (or output, respectively); an error is raised otherwise.
.PP
To arrange for a newly-created port to be closed automatically when it
becomes garbage, it must be passed to the function
\f2Register_Object()\fP as follows:
.Es
@[.=Register_Object()]@[.=Terminate_File()]
Register_Object(the_port, 0, Terminate_File, 0);
.Ee
\f2Register_Object()\fP will be described in section @(ch-term).
The current input and output port as well as ports pointing to the
program's initial standard input and output are available as four
external variables of type \f2Object\fP:
.Es
@[.=Curr_Input_Port]@[.=Curr_Output_Port]
@[.=Standard_Input_Port]@[.=Standard_Output_Port]
Curr_Input_Port Standard_Input_Port
Curr_Output_Port Standard_Output_Port
.Ee
The function
.Es
@[.=Reset_IO()]
void Reset_IO(int destructive_flag);
.Ee
clears any input queued at the current input port, then flushes
the current output port (if \f2destructive_flag\fP is zero)
or discards characters queued at the output port (if
\f2destructive_flag\fP is non-zero), and finally resets the
current input and current output port to their initial values
(the program's standard input and standard output).
This function is typically used in error situations to reset
the current ports to a defined state.
.PP
In addition to the standard Scheme primitives for output, extensions
and applications can use a function
.Es
@[.=Printf()]
void Printf(Object port, char *fmt, ...);
.Ee
to send output to a Scheme port using C \f2printf\fP.
The first argument to \f2Printf()\fP is the Scheme port to which
the output will be sent (it must be an output port); the remaining
arguments are that of the C library function \f2printf()\fP.
.LP
To output a Scheme object, the following function can be used
in addition to the usual primitives:
.Es
@[.=Print_Object()]
void Print_Object(Object obj, Object port, int raw_flag,
int print_depth, int print_length);
.Ee
The arguments to \f2Print_Object()\fP are identical to the arguments
of the ``print function'' that must be supplied for each user-defined
Scheme type (as described in section @(ch-deftype):
the \f2Object\fP to be printed, the output port, a flag indicating
that the object should be printed in human-readable form (\f2display\fP
sets the flag, \f2write\fP does not), and the ``print depth'' and
``print length'' for that operation.
For debugging purposes, the macro
.Es
@[.=Print()]
Print(obj);
.Ee
may be used to output an \f2Object\fP to the current output port.
.LP
A function
.Es
@[.=Load_Source_Port()]
void Load_Source_Port(Object port);
.Ee
can be used to load Scheme expressions from a file that has already
been opened as a Scheme port.
.\" ---------------------------------------------------------------------------
.K2 "Miscellaneous Types"
.PP
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'').
.PP
Lexical environments are identical to pairs except that the type
is @[.\f2T_Environment\fP] rather than \f2T_Pair\fP.
The current environment and the initial (gobal) environment
are available as the external C variables
@[.\f2The_Environment\fP] and @[.\f2Global_Environment\fP].
The predefined type constants for primitives, compound procedures (the
results of evaluating lambda expressions), and macros are
@[.\f2T_Primitive\fP], @[.\f2T_Compound\fP], and @[.\f2T_Macro\fP],
respectively.
The function
.Es
@[.=Check_Procedure()]
void Check_Procedure(Object);
.Ee
checks whether the specified object is either a compound procedure
or a primitive procedure with a calling discipline different from
\f2NOEVAL\fP and raises an error otherwise.
The type constant for continuations is @[.\f2T_Control\fP].
``Promise'' is the type of object returned by the special form
\f2delay\fP; the corresponding type constant is named @[.\f2T_Promise\fP].
.\" ---------------------------------------------------------------------------
.K1 "Defining New Scheme Types"
.Rf ch-deftype \*(SN
.PP
A new, disjoint Scheme type is registered with Elk by calling the
function @[.\f2Define_Type()\fP], similar to \f2Define_Primitive()\fP
for new primitives.
Making a new type known to Elk involves passing it information about
the underlying C/C++ representation of the type and a number of C or
C++ functions that are ``called back'' by the interpreter in
various situations to pass control to the code that implements
the type.
The prototype of \f2Define_Type()\fP is:
.Es
int Define_Type(int zero, const char *name,
int (*size)(Object), int const_size,
int (*eqv)(Object, Object),
int (*equal)(Object, Object),
int (*print)(Object, Object, int, int, int),
int (*visit)(Object*, int (*)(Object*)));
.Ee
The arguments to \f2Define_Primitive()\fP are in detail:
.Rs
.IP \f2zero\fP 1
The first argument must be zero (in early versions of Elk it could be
used to request a fixed, predefined type number for the new type);
.IP \f2name\fP 1
The name of the new type.
.IP "\f2size, const_size\fP" 1
The size of the corresponding C type (usually a \f2struct\fP) in bytes,
given as one of two, mutually-exclusive arguments:
\f2size\fP, a pointer to a function called by the interpreter to determine
the size of an object (for types whose individual members are of different
sizes, such as the \f2vector\fP type);
and \f2const_size\fP, the size as a constant (for all other types).
A null-pointer is given for \f2const_size\fP if \f2size\fP is to
be used instead.
.IP "\f2eqv, equal\fP" 1
Pointers to (callback) functions that are invoked by the
interpreter whenever the Scheme predicate \f2equal?\&\fP, or \f2eqv?\&\fP
respectively, is applied to members of the newly defined type.
As an application-defined type is opaque from the interpreter's
point of view, the equality predicates have to be supplied by
the application or extension.
Each of these (boolean) functions is passed two objects of the new type
as arguments when called back.
.IP \f2print\fP 1
A pointer to a function that is used by the interpreter to print
a member of this type.
When calling the print function, the interpreter passes as arguments
the Scheme object to be printed, a Scheme \f2port\fP to which the output is
to be sent, a flag indicating whether output is to be rendered in
human-readable form (\f2display\fP Scheme primitive) or machine-readable,
read-write-invariance preserving form (\f2write\fP), and finally the
current remainders of the maximum \f2print depth\fP and \f2print length\fP.
The return value of this function is not used (the type is \f2int\fP
for historical reasons).
.IP \f2visit\fP 1
A pointer to a @[.``visit'' function] called by the @[.garbage collector]
when tracing the set of all currently accessible objects.
This function is only required if other Scheme objects
are reachable from objects of the newly defined type (a null
pointer can be given otherwise).
It is invoked with two arguments:
a pointer to the object being visited by the garbage collector, and a
pointer to another function to be called once with the address of
each object accessible through the original object.
For example, the implementation of pairs would supply a visit function
that invokes its second argument twice\*-once with the address of
the car of the original object, and once with the address of the cdr.
.Re
.PP
The return value of \f2Define_Type()\fP is a small, unique integer
identifying the type; it is usually stored in a ``T_*'' (or ``t_*'')
variable following the convention used for the built-in types.
.PP
In the current version of Elk, \f2Define_Type()\fP cannot be used
to define new ``pointer-less'' types resembling built-in types
such as \f2fixnum\fP or \f2boolean\fP.
.PP
The first component of the C structure implementing a user-defined
Scheme type must be an \f2Object\fP; its space is used by
the @[.garbage collector] to store a special tag indicating
that the object has been forwarded.
If you are defining a type that has several components one of
which is an \f2Object\fP, just move the \f2Object\fP to the
front of the \f2struct\fP declaration.
Otherwise insert an additional \f2Object\fP component.
.PP
The Scheme primitive that instantiates a new type can request
heap space for the new object by calling the function
@[.\f2Alloc_Object()\fP]:
.Es
Object Alloc_Object(int size, int type, int const_flag);
.Ee
The arguments to \f2Alloc_Object()\fP are the size of
the object in bytes (usually obtained by applying \f2sizeof\fP
to the underlying \f2struct\fP), the type of which the new
object is a member (i.\|e.\& the return value of \f2Define_Type()\fP),
and a flag indicating whether the newly created object is to
be made read-only.
The return value is a fully initialized \f2Object\fP.
.\" ---------------------------------------------------------------------------
.K2 "Example for a User-Defined Scheme Type"
.PP
Figure @(ndbm1) shows the skeleton of an extension that provides a
simple Scheme interface to the UNIX \f2ndbm\fP library; it can be
loaded dynamically into the Scheme interpreter, or into an Elk-based
application that needs access to a simple database from within the
extension language.
Please refer to your system's documentation if you are not familiar with
\f2ndbm\fP.
The extension defines a new, first-class Scheme type \f2dbm-file\fP
corresponding to the \f2DBM\fP type defined by the C library.
Again, note the naming convention to use lower-case for
new identifiers (in contrast to the predefined ones).
.Fs
#include <scheme.h>
#include <ndbm.h>
.El
int t_dbm;
.El
struct s_dbm {
Object unused;
DBM *dbm;
char alive; /* 0: has been closed, else 1 */
};
.El
#define DBMF(obj) ((struct s_dbm *)POINTER(obj))
.El
int dbm_equal(Object a, Object b) {
return DBMF(a)->alive && DBMF(b)->alive && DBMF(a)->dbm == DBMF(b)->dbm;
}
.El
int dbm_print(Object d, Object port, int raw, int length, int depth) {
Printf(port, "#[dbm-file %lu]", DBMF(d)->dbm);
return 0;
}
.El
Object p_is_dbm(Object d) {
return TYPE(d) == t_dbm ? True : False;
}
.El
void elk_init_dbm(void) {
t_dbm = Define_Type(0, "dbm-file", 0, sizeof(struct s_dbm),
dbm_equal, dbm_equal, dbm_print, 0);
.El
Define_Primitive(p_is_dbm, "dbm-file?", 1, 1, EVAL);
Define_Primitive(p_dbm_open, "dbm-open", 2, 3, VARARGS);
Define_Primitive(p_dbm_close, "dbm-close", 1, 1, EVAL);
}
.Fc "Skeleton of a UNIX ndbm extension"
.Fe ndbm1
.PP
The code shown in Figure @(ndbm1) declares a variable \f2t_dbm\fP
to hold the return value of \f2Define_Primitive()\fP, and the
C structure \f2s_dbm\fP that represents the new type.
The structure is composed of the required initial \f2Object\fP,
the \f2DBM\fP pointer returned by the C library function \f2dbm_open()\fP,
and a flag indicating whether the database pointed to by this
object has already been closed (in this case the flag is cleared).
As a \f2dbm-file\fP Scheme object can still be passed to primitives
after the \f2DBM\fP handle has been closed by a call to \f2dbm_close()\fP,
the \f2alive\fP flag had to be added to avoid further use of a ``stale''
object:
the ``dbm'' primitives include an initial check for the flag and raise
an error if it is zero.
.PP
The macro \f2DBMF\fP is used to cast the pointer field of an
\f2Object\fP of type \f2t_dbm\fP to a pointer to the correct structure
type.
\f2dbm_equal()\fP implements both the \f2eqv?\&\fP and the
\f2equal?\&\fP predicates; it returns true if the \f2Objects\fP
compared point to an open database and contain identical \f2DBM\fP
pointers.
The print function just prints the numeric value of the \f2DBM\fP
pointer; this could be improved by printing the name of the database
file instead, which must then be included in each Scheme object.
The primitive \f2p_is_dbm()\fP provides the usual @[.type predicate].
Finally, an @[.extension initialization function] is supplied to
enable @[.dynamic loading] of the compiled code; it registers the new
type and three primitives operating on it.
Note that a @[.visit function] (the final argument to \f2Define_Type()\fP)
is not required here, as the new type does not include any components
of type \f2Object\fP that the garbage collector must know of\*-the
required initial \f2Object\fP is not used here and therefore can
be neglected.
The type constructor primitive \f2dbm-open\fP and the primitive
\f2dbm-close\fP are shown in Figure @(ndbm2).
.PP
.Fs
Object p_dbm_open(int argc, Object *argv) {
DBM *dp;
int flags = O_RDWR|O_CREAT;
Object d, sym = argv[1];
.El
Check_Type(sym, T_Symbol);
if (EQ(sym, Intern("reader")))
flags = O_RDONLY;
else if (EQ(sym, Intern("writer")))
flags = O_RDWR;
else if (!EQ(sym, Intern("create")))
Primitive_Error("invalid argument: ~s", sym);
if ((dp = dbm_open(Get_String(argv[0]), flags,
argc == 3 ? Get_Integer(argv[2]) : 0666)) == 0)
return False;
d = Alloc_Object(sizeof(struct s_dbm), t_dbm, 0);
DBMF(d)->dbm = dp;
DBMF(d)->alive = 1;
return d;
}
.El
Object p_dbm_close(Object d) {
Check_Type(d, t_dbm);
if (!DBMF(d)->alive)
Primitive_Error("invalid dbm-file: ~s", d);
DBMF(d)->alive = 0;
dbm_close(DBMF(d)->dbm);
return Void;
}
.Fc "Implementation of \f2dbm-open\fP and \f2dbm-close\fP"
.Fe ndbm2
.PP
The primitive \f2dbm-open\fP shown in Figure @(ndbm2) is called with
the name of the database file, a symbol indicating the type of access
(\f2reader\fP for read-only access, \f2writer\fP for read/write access,
and \f2create\fP for creating a new file with read/write access), and
an optional third argument specifying the file permissions for a
newly-created database file.
A default of 0666 is used for the file permissions if the primitive
is invoked with just two arguments.
Section @(ch-symbits) will introduce a set of functions that avoid clumsy
if-cascades such as the one at the beginning of \f2p_dbm_open()\fP.
@[.\f2Primitive_Error()\fP] is called with a @[.``format string''] and
zero or more arguments and signals a Scheme error (see section @(ch-error)).
\f2dbm-open\fP returns #f if the database file could not be opened,
so that the caller can deal with the error.
.PP
Note that \f2dbm-close\fP first checks the \f2alive\fP bit to
raise an error if the database pointer is no longer valid
because of an earlier call to \f2dbm-close\fP.
This check needs to be performed by all primitives working on
\f2dbm-file\fP objects; it may be useful to wrap it in a separate
function\*-together with the initial type-check.
Ideally, database objects should be closed automatically during
@[.garbage collection] when they become inaccessible; section @(ch-term)
will introduce functions to accomplish this.
.PP
At least two primitives \f2dbm-store\fP and \f2dbm-fetch\fP need
to be added to the database extension to make it really useful;
these are not shown here (their implementation is fairly simple and
straightforward).
Using these primitives, the extension discussed in this section can
be used to write Scheme code such as this procedure (which looks up an
electronic mailbox name in the mail alias database maintained on
most UNIX systems):
.Es
(define expand-mail-alias
(lambda (alias)
(let ((d (dbm-open "/etc/aliases" 'reader)))
(if (not d)
(error 'expand-mail-alias "cannot open database"))
(unwind-protect
(dbm-fetch d alias)
(dbm-close d)))))
.El
(define address-of-staff (expand-mail-alias "staff"))
.Ee
.\" ---------------------------------------------------------------------------
.K1 "Advanced Topics"
.Rf ch-advanced \*(SN
.\" ---------------------------------------------------------------------------
.K2 "Converting between Symbols, Integers, and Bitmasks"
.Rf ch-symbits \*(SN
.PP
Symbols are frequently used as the arguments to Scheme primitives which
call an underlying C or C++ function with some kind of @[.bitmask] or with a
predefined enumeration constant or preprocessor symbol.
For example, the primitive \f2dbm-open\fP shown in Figure @(ndbm2)
above uses symbols to represent the symbolic constants passed to
\f2dbm_open()\fP.
Similarly, a Scheme primitive corresponding to the UNIX system call
\f2open()\fP could receive a list of symbols represending the
logical OR of the usual \f2open()\fP flags, so that one can
write Scheme code such as:
.Es
(let ((tty-fd (unix-open "/dev/ttya" '(read write exclusive)))
(tmp-fd (unix-open "/tmp/somefile '(write create))))
...
.Ee
.PP
To facilitate conversion of symbols to C integers or enumeration
constants and vice versa, these two functions are provided:
.Es
@[.=Symbols_To_Bits()]@[.=Bits_To_Symbols()]
unsigned long Symbols_To_Bits(Object syms, int mask_flag,
SYMDESCR *table);
Object Bits_To_Symbols(unsigned long bits, int mask_flag,
SYMDESCR *table);
.Ee
The type @[.\f2SYMDESCR\fP] is defined as:
.Es
typedef struct {
char *name;
unsigned long val;
} SYMDESCR;
.Ee
.PP
\f2Symbols_To_Bits()\fP converts a symbol or a list of symbols to
an integer; \f2Bits_To_Symbols()\fP is the reverse operation and is
usually applied to the return value of a C/C++ function to
convert it to a Scheme representation.
Both functions receive as the third argument a table specifying the
correspondence between symbols and C constants; each table entry is a
pair consisting of the \f2name\fP of a symbol as a C string and an
integer \f2val\fP (typically an enumeration constant or a \f2#define\fP
constant).
Each \f2SYMDESCR\fP array is terminated by an entry with a zero
\f2name\fP component:
.Es
SYMDESCR lseek_syms[] = {
{ "set", SEEK_SET },
{ "current", SEEK_CUR },
{ "end", SEEK_END },
{ 0, 0 }
};
.Ee
.PP
The second argument to the conversion functions controls whether a
single symbol is converted to an integer or vice versa (\f2mask_flag\fP
is zero), or whether a list of symbols is converted to the logical OR
of a set of matching values or vice versa (\f2mask_flag\fP is
non-zero).
\f2Symbols_To_Bits()\fP signals an error if the symbol does not
match any of the names in the given table or, if \f2mask_flag\fP
is non-zero, if any of the list elements does not match.
The empty list is converted to zero.
If \f2Bits_To_Symbols()\fP is called with a non-zero \f2mask_flag\fP,
it matches the \f2val\fP components against the \f2bits\fP argument
using logical AND.
Regardless of \f2mask_flag\fP, \f2Bits_To_Symbols\fP returns the empty
list if no match occurs.
Figure @(ndbm3) shows an improved version of \f2p_dbm_open()\fP
using \f2Symbols_To_Bits()\fP in place of nested if-statements.
.Fs
static SYMDESCR flag_syms[] = {
{ "reader", O_RDONLY },
{ "writer", O_RDWR },
{ "create", O_RDWR|O_CREAT },
{ 0, 0 }
};
.El
Object p_dbm_open(int argc, Object *argv) {
DBM *dp;
Object d;
.El
dp = dbm_open(Get_String(argv[0]),
Symbols_To_Bits(argv[1], 0, flag_syms),
argc == 3 ? Get_Integer(argv[2]) : 0666);
if (dp == 0)
return False;
d = Alloc_Object(sizeof(struct s_dbm), t_dbm, 0);
DBMF(d)->dbm = dp;
DBMF(d)->alive = 1;
return d;
}
.Fc "Improved version of \f2dbm-open\fP using \f2Symbols_To_Bits()\fP"
.Fe ndbm3
.PP
A Scheme primitive calling the UNIX system call \f2access()\fP
could use \f2Symbols_To_Bits()\fP with a non-zero \f2mask_flag\fP
to construct a bitmask:
.Es
Object p_access(Object fn, Object mode) {
access(Get_String(fn), (int)Symbols_To_Bits(mode, 1, access_syms));
...
.Ee
where \f2access_syms\fP is defined as:
.Es
static SYMDESCR access_syms[] = {
{ "read", R_OK },
{ "write", W_OK },
{ "execute", X_OK },
{ 0, 0 }
};
.Ee
Note that in this example the empty list can be passed as the \f2mode\fP
argument to test for existence of the file, because in this case
\f2Symbols_To_Bits()\fP returns zero (the value of \f2F_OK\fP).
.\" ---------------------------------------------------------------------------
.K2 "Calling Scheme Procedures, Evaluating Scheme Code"
.Rf ch-funcall \*(SN
.PP
A Scheme procedure can be called from within C or C++ code using
the function
.Es
@[.=Funcall()]
Object Funcall(Object fun, Object argl, int eval_flag);
.Ee
The first argument is the Scheme procedure\*-either a primitive
procedure (\f2T_Primitive\fP) or a compound procedure (\f2T_Compound\fP).
The second argument is the list of arguments to be passed to
the procedure, as a Scheme list.
The third argument, if non-zero, specifies that the arguments need to be
evaluated before calling the Scheme procedure.
This is usually not the case (except in some special forms).
The return value of \f2Funcall()\fP is the result of the Scheme
procedure.
.PP
\f2Funcall()\fP is frequently used from within C callback functions
that can be registered for certain events, such as the user-supplied
X11 error handlers, X11 event handlers, timeout handlers, the C++
\f2new\fP handler, etc.
Here, use of \f2Funcall()\fP allows to register a user-defined Scheme
procedure for this event from within a Scheme program.
As an example, Figure @(funcall) shows the generic signal handler
that is associated with various UNIX signals by the UNIX extension.
.Fs
void scheme_signal_handler(int sig) {
Object fun, args;
.El
Set_Error_Tag("signal-handler");
Reset_IO(1);
args = Bits_To_Symbols((unsigned long)sig, 0, signal_syms);
args = Cons(args, Null);
fun = VECTOR(handlers)->data[sig];
if (TYPE(fun) != T_Compound)
Fatal_Error("no handler for signal %d", sig);
(void)Funcall(fun, args, 0);
Printf(Curr_Output_Port, "\en\e7Signal!\en");
(void)P_Reset();
/*NOTREACHED*/
}
.Fc "Using \f2Funcall()\fP to call a Scheme procedure"
.Fe funcall
.PP
The signal handler shown in Figure @(funcall) uses the signal
number supplied by the system to index a vector of user-defined
Scheme procedures (that is, \f2Objects\fP of type \f2T_Compound\fP).
@[.\f2Reset_IO()\fP] is used here to ensure that the current input
and output port are in defined state when the Scheme signal
handler starts executing.
The argument list is constructed by calling @[.\f2Cons()\fP];
it consists of a single element\*-the signal number as a Scheme
symbol.
\f2signal_syms\fP is an array of @[.\f2SYMDESCR\fP] records that
maps the UNIX signal names (\f2sighup\fP, \f2sigint\fP, etc.)
to corresponding Scheme symbols of the same names.
The Scheme procedure called from the signal handler is not supposed
to return (it usually invokes a continuation); therefore the result
of \f2Funcall()\fP is ignored.
In case the Scheme handler (and thus the call to \f2Funcall()\fP)
does return, a message is printed and the primitive \f2reset\fP
is called to return to the application's toplevel or standard
Scheme toplevel.
.PP
An S-expression can be evaluated by calling the function
.Es
@[.=Eval()]
Object Eval(Object expr);
.Ee
which is identical to the primitive \f2eval\fP (\f2P_Eval()\fP in C),
except that no optional environment can be supplied.
\f2Eval()\fP is very rarely used by extensions or applications,
mainly by implementations of new special forms.
Both \f2Eval()\fP and \f2Funcall()\fP can trigger a
@[.garbage collection]; all @[.local variable]s holding Scheme \f2Objects\fP
with heap pointers must be properly registered with the
garbage collector to survive calls to these functions.
.PP
Occasionally an S-expression needs to be evaluated that exists as a C
string, for example, when a Scheme expression has been entered through
a ``text widget'' in a graphical user interface.
Here, evaluation requires calling the Scheme reader to parse the
expression; therefore a straightforward solution is to create a
@[.string port] holding the string and then just ``load'' the
contents of the port:
.Es
void eval_string(char *expr) {
Object port; GC_Node;
.El
port = P_Open_Input_String(Make_String(expr, strlen(expr)));
GC_Link(port);
Load_Source_Port(port);
GC_Unlink;
(void)P_Close_Input_Port(port);
}
.Ee
If a more sophisticated function is required, the \f2eval-string\fP
extension included in the Elk distribution can be used
(``lib/misc/elk-eval.c'').
This extension provides a function
.Es
@[.=Elk_Eval()]
char *Elk_Eval(char *expr);
.Ee
that converts the result of evaluating the stringized expression
back to a C string and returns it as a result.
A null pointer is returned if an error occurs during evaluation.
.PP
Applications should not use this function as the primary interface
to the extension language.
In contrast to languages such as @[.Tcl], the semantic concepts and
data structures of Scheme are not centered around strings, and strings
are not a practicable representation for S-expressions.
Instead, applications should pass control to the extension
language by calling Scheme procedures (using @[.\f2Funcall()\fP])
or by loading files containing Scheme code.
The extension language then calls back into the application's C/C++
layer by invoking application-supplied Scheme primitives and other
forms of callbacks as explained in section @(ch-control).
.\" ---------------------------------------------------------------------------
.K2 "GC-Protecting Global Objects"
.Rf ch-gcglobal \*(SN
.PP
Section @(ch-gc) explained when\*-and how\*-to register with
the @[.garbage collector] function-local \f2Object\fP variables
holding heap pointers.
Similarly, @[.global variable]s must usually be added to the set of
reachable objects as well if they are to survive garbage collections
(a useful exception to this rule will be introduced in section @(ch-term)).
In contrast to local variables, global variables are only made
known to the garbage collector once\*-after initialization\*-as
their lifetime is that of the entire program.
To add a global variable to the garbage collector's root set, the
macro
.Es
@[.=Global_GC_Link()]
Global_GC_Link(obj)
.Ee
must be called with the properly initialized variable of type
\f2Object\fP.
The macro takes the address of the specified object.
If that is a problem, an equivalent functional interface can be used:
.Es
@[.=Func_Global_GC_Link()]
void Func_Global_GC_Link(Object *obj_ptr);
.Ee
This function must be supplied the address of the global variable to
be registered with the garbage collector.
.PP
When writing extensions that maintain global \f2Object\fP variables,
\f2Global_GC_Link()\fP (or \f2Func_Global_GC_Link()\fP) is usually
called from within the @[.extension initialization function] right
after each variable is assigned a value.
For instance, the global Scheme vector \f2handlers\fP that was
used in Figure @(funcall) to associate procedures with UNIX signals
is initialized and GC-protected as follows:
.Es
void elk_init_unix_signal(void) {
handlers = Make_Vector(NSIG, False);
Global_GC_Link(handlers);
...
}
.Ee
\f2NSIG\fP is the number of UNIX signal types as defined by the system
include file.
The signal handling Scheme procedures that are inserted into the
vector later need not be registered with the garbage collector, because
they are now reachable through another object which itself is reachable.
.\" ---------------------------------------------------------------------------
.K3 "Dynamic C Data Structures"
.PP
Dynamic data structures, such as the nodes of a linked list containing
Scheme \f2Objects\fP, cannot be easily registered with the garbage
collector.
The simplest solution is to build these data structures in Scheme
rather than in C or C++ in the first place.
For example, a linked list of Scheme objects can be built from
Scheme pairs much more naturally and more straightforward than
from C structures or the like, in particular if the list will
be traversed and manipulated using Scheme primitives anyway.
Besides, data structures programmed in Scheme benefit from automatic
memory management, whereas use of \f2malloc()\fP and \f2free()\fP
in C frequently is a source of memory leaks and related errors.
.PP
If for some reason a dynamic data structure must be built in C or
C++ rather than in Scheme, reachability problems can be avoided
by inserting all \f2Objects\fP into a global, GC-protected vector
(such as \f2handlers\fP in Figure @(funcall)) and then use the
corresponding vector indexes rather than the actual \f2Objects\fP.
This sounds more difficult than it really is; Appendix B shows
the complete source code of a small module to register \f2Objects\fP
in a Scheme vector.
The module exports three functions:
\f2register_object()\fP inserts an \f2Object\fP into the vector
and returns the index as an \f2int\fP;
\f2deregister_object()\fP removes an \f2Object\fP with a given
index from the vector;
and \f2get_object()\fP returns the \f2Object\fP stored under a
given index.
\f2register_object()\fP dynamically grows the vector to avoid
artificial limits.
.PP
A dynamic data structure (e.\|g.\& linked list) implementation using
this module would call \f2register_object()\fP when inserting a new
\f2Object\fP into the list and then use the integer return value in
place of the \f2Object\fP itself.
Similarly, it would call \f2deregister_object()\fP whenever a node
is removed from the list.
\f2get_object()\fP would be used to retrieve the \f2Object\fP associated
with a given list element.
Note that with these functions the same \f2Object\fP can be
registered multiple times (each time under a new index) without
having to maintain reference counts:
the garbage collector does not care how often a particular
\f2Object\fP is traversed during garbage collection, as long
as it will be reached at least once.
.\" ---------------------------------------------------------------------------
.K2 "Weak Pointers and Object Termination"
.Rf ch-term \*(SN
.PP
A data structure implementation may deliberately use \f2Objects\fP
that are not added to the global set of reachable pointers
(as described in the previous section) and are thus invisible to
the @[.garbage collector].
In this case, it becomes possible to determine whether or not
garbage collection has found any \f2other\fP pointers to the same
Scheme objects.
This property can be exploited in several ways by extensions or
applications using Elk.
.PP
Pointers that are not included in the garbage collector's
reachability search are called @[.``weak pointers''].
The memory occupied by a Scheme object that is only referenced by
weak pointers will be reclaimed.
The term \f2weak\fP expresses the notion that the pointer is
not strong enough to prevent the object it points to from
being garbage collected.
Code using weak pointers can scan the pointers immediately after
each garbage collection and check whether the target object
has been visited by the just-finished garbage collection.
If this is the case, normal (strong) pointers to the object must exist
(which can therefore be considered ``live''), and the weak pointer is
updated manually to point to the object's new location.
On the other hand, if the object has not been visited,
no more (normal) references to it exist and the memory occupied by it
has been reclaimed.
.PP
Weak pointers are useful in implementing certain types of data
structures where the sole existence of a (weak) pointer to an object
from within this data structure should not keep the object alive
(\f2weak sets\fP, \f2populations\fP, certain kinds of hash tables, etc.).
Objects that are not reachable through @[.strong pointers] are then
removed from the @[.weak data structure] after garbage collection.
In this case, it is frequently useful to invoke a
@[.``termination function''] for each such object, e.\|g.\& for objects
that contain resources of which only a finite amount is available, such
as UNIX file descriptors (or FILE structures), X displays
and windows, etc.
The termination function for Scheme ports closes the file pointer
encapsulated in a port object if it is still open;
likewise, the termination function for X windows closes the window and
thereby removes it from the display, and so on.
Thus, should an object holding some kind of resource go
inaccessible before it was terminated ``properly'' by calling
the respective Scheme primitive (\f2close-input-port\fP,
\f2close-output-port\fP, \f2destroy-window\fP, etc.), then
resource will be reclaimed after the next garbage collection run.
.\" ---------------------------------------------------------------------------
.K3 "Using Weak Pointers"
.PP
Code using @[.weak pointers] must scan the pointers immediately after
each @[.garbage collection], but \f2before\fP the interpreter resumes
normal operation, because the memory referenced by the weak pointers
can be reused the next time heap space is requested.
This can be accomplished by registering a so-called
@[.``after-GC function].
Elk's garbage collector invokes all after-GC functions (without
arguments) upon completion.
To register an after-GC functions, the function
.Es
@[.=Register_After_GC()]
void Register_After_GC((void (*func)(void)));
.Ee
is used, typically in an @[.extension initializer].
Similarly, extensions and applications can register
@[.=before-GC function]``before-GC functions'' using
.Es
@[.=Register_Before_GC()]
void Register_Before_GC((void (*func)(void)));
.Ee
These functions are called immediately before each garbage collection
and may be used, for instance, to change the application's cursor
to an hourglass symbol.
After-GC and before-GC functions must not trigger another garbage
collection.
.PP
An after-GC function scanning a set of weak pointers makes use
of the three macros @[.\f2IS_ALIVE()\fP], @[.\f2WAS_FORWARDED()\fP],
and @[.\f2UPDATE_OBJ()\fP].
For example, an after-GC function scanning a table of
elements holding \f2Objects\fP with weak pointers could be
written as shown in Figure @(aftergc).
.Fs
void scan_weak_table(void) {
int i;
.El
for (i = 0; i < table_size; i++) {
Object obj = table[i].obj;
if (IS_ALIVE(obj)) { /* object is still reachable */
if (WAS_FORWARDED(obj))
UPDATE_OBJ(obj);
} else {
terminate_object(obj); /* object is dead; finalize... */
table[i] = 0; /* and remove it from the table */
}
}
}
.Fc "After-GC function that scans a table containing weak pointers"
.Fe aftergc
.PP
The function \f2scan_weak_table()\fP shown in Figure @(aftergc) can then
be registered as an after-GC function by invoking
.Es
Register_After_GC(scan_weak_table);
.Ee
.PP
The then-part of the if-statement in \f2scan_weak_table()\fP is entered
if the just-completed garbage collection has encountered any pointers
to the Scheme object pointed to by \f2obj\fP; in this case the
pointer conveyed in \f2obj\fP is updated manually using \f2UPDATE_OBJ()\fP
(when using the generational garbage collector included in Elk,
reachability of an object does not necessarily imply that it was
forwarded, hence the additional call to \f2WAS_FORWARDED()\fP).
If \f2IS_ALIVE()\fP returns false, no more strong pointers to the
object exist and it can be terminated and removed from the weak
data structure.
\f2terminate_object()\fP typically would release any external
resources contained in the Scheme object, but it must neither
create any new objects nor attempt to ``revive'' the
dead object in any way (e.\|g.\& create a new strong pointer
to it by inserting it into another, live object).
.\" ---------------------------------------------------------------------------
.K3 "Functions for Automatic Object Termination"
.PP
As automatic termination of Scheme objects using user-supplied
@[.termination function]s is the most frequent use of @[.weak pointers],
Elk offers a set of convenience functions for this purpose.
Extensions and applications can insert \f2Objects\fP into a
@[.weak list] maintained by Elk and remove them from the list
using the two functions
.Es
@[.=Register_Object()]@[.=Deregister_Object()]
void Register_Object(Object obj, char *group,
(Object (*term)(Object)), int leader_flag);
void Deregister_Object(Object obj);
.Ee
.PP
\f2term\fP is the termination function that is called automatically
with \f2obj\fP when the object becomes unreachable (its result
is not used);
\f2group\fP is an opaque ``cookie'' associated with \f2obj\fP
and can be used to explicitly terminate all objects with the
same value for \f2group\fP;
a non-zero \f2leader_flag\fP indicates that \f2obj\fP is the
``leader'' of the specified \f2group\fP.
Elk automatically registers an @[.after-GC function] to scan
the weak list maintained by these two functions and to call
the \f2term\fP function for all objects that could be proven
unreachable by the garbage collector, similar to the function
shown in Figure @(aftergc).
.PP
Object termination takes place in two phases:
first all objects registered with a zero \f2leader_flag\fP
are terminated, after that the termination functions of
the leaders are invoked.
This group and leader notion is used, for example, by the
@[.Xlib extension] to associate windows (and other resources) with
an X display:
the ID of the display to which a window belongs is used as
the window's group, and the display is marked as the group leader.
Thus, if a display becomes unreachable or is closed by the program, all
its windows are closed before the display is finally destroyed\**.
.FS
This interface has evolved in a slightly \f2ad hoc\fP way;
the two-stage relationship expressed by groups and group leaders
may not be sufficient for more complex hierarchies than those
used in X.
.FE
.LP
Two additional functions are provided for explicitly calling
the termination functions:
.Es
@[.=Terminate_Type()]@[.=Terminate_Group()]
void Terminate_Type(int type);
void Terminate_Group(char *group);
.Ee
\f2Terminate_Type()\fP invokes the termination function (if any) for
all objects of a given type and deletes them from the weak list.
For example, to close all ports currently held open by Elk (and
thus apply \f2fclose()\fP to the FILE pointers embedded in them),
one would call
.Es
@[.=T_Port]
Terminate_Type(T_Port)
.Ee
\f2Terminate_Group()\fP calls the termination functions of
all non-leader objects belonging to the specified \f2group\fP.
.LP
Finally, another function, @[.\f2Find_Object()\fP],
locates an object in the weak list:
.Es
Object Find_Object(int type, char *group,
(int (*match_func)(Object, ...)), ...);
.Ee
Arguments are a Scheme type, a group, and a match function called
once for each object in the weak list that has the specified type
and group.
The match function is passed the \f2Object\fP and the remaining arguments
to \f2Find_Object()\fP, if any.
If the match function returns true for an object, this object becomes
the return value of \f2Find_Object()\fP; otherwise it returns \f2Null\fP.
.PP
Complicated as it may seem, \f2Find_Object()\fP is quite useful\*-extensions
can check whether a Scheme object with certain properties
has already been registered with the weak list earlier and, if this is the
case, return \f2this\fP object instead of creating a new one.
This is critical for Scheme objects encapsulating some kind of
external resource, such as file descriptors or X windows.
Consider, for example, a Scheme primitive that obtains the topmost
window on a given X display and returns it as a Scheme \f2window\fP
object.
If the primitive just were to instantiate a Scheme object
encapsulating the corresponding X window ID for each call, it would
become possible for two or more distinct Scheme \f2window\fP objects to
reference the same real X window.
This is not acceptable, because two Scheme objects pointing to the same X
object should certainly be equal in the sense of \f2eq?\&\fP,
not to mention the problems that would ensue if one of the Scheme
\f2window\fP objects were closed (thereby destroying the underlying
X window) and the second one were still be operated on afterwards.
Example uses of \f2Find_Object()\fP can be found in the @[.Xlib extension]
and in the @[.Xt extension] that are included in the Elk distribution.
.\" ---------------------------------------------------------------------------
.K2 "Errors"
.Rf ch-error \*(SN
.PP
User-supplied code can signal an error by calling
@[.\f2Primitive_Error()\fP] with a @[.format string] and as many additional
arguments (\f2Objects\fP) as there are @[.format specifier]s in the format
string:
.Es
void Primitive_Error(char *fmt, ...);
.Ee
\f2Primitive_Error()\fP calls the default or user-defined
@[.error handler] as described in the Elk Reference Manual, passing it an
@[.``error tag''] identifying the source of the error, the format
string, and the remaining arguments.
A special format specifier ``~E'' can be used to interpolate the standard
error message text corresponding to the UNIX error number @[.\f2errno\fP];
this is useful for primitives that invoke UNIX system calls or certain
C library functions (if ``~e'' is used, the first character of
the text is converted to lower case).
If this format specifier is used, the current \f2errno\fP must be
assigned to a variable @[.\f2Saved_Errno\fP] prior to calling
\f2Primitive_Error()\fP to prevent it from being overwritten
by the next system call or C library function.
\f2Primitive_Error()\fP does not return.
.PP
Applications that need to supply their own error handler by
redefining \f2error-handler\fP usually do so in Scheme,
typically at the beginning of the initial Scheme file loaded
in \f2main()\fP.
.PP
If \f2Primitive_Error()\fP is called from within a C function
that implements a Scheme primitive, an error tag is supplied
by Elk (the name of the primitive).
Applications may set the error tag explicitly at the
beginning of sections of C/C++ code that reside outside of
primitives, for example, before loading an initial Scheme
file in the application's \f2main()\fP.
Two functions are provided to set and query the current error tag:
.Es
@[.=Set_Error_Tag()]@[.=Get_Error_Tag()]
void Set_Error_Tag(const char *tag);
char *Get_Error_Tag(void);
.Ee
The following three functions can be used by primitives to signal
errors with standardized messages in certain situations:
.Es
@[.=Range_Error()]@[.=Wrong_Type()]@[.=Wrong_Type_Combination()]
void Range_Error(Object offending_obj);
void Wrong_Type(Object offending_obj, int expected_type);
void Wrong_Type_Combination(Object offending_obj, char *expected_type);
.Ee
\f2Range_Error()\fP can be used when an argument to a primitive
is out of range (typically some kind of index).
\f2Wrong_Type()\fP signals a failed type-check for the given
\f2Object\fP; the second argument is the expected type of the
\f2Object\fP.
This function is used, for example, by @[.\f2Check_Type()\fP].
\f2Wrong_Type_Combination()\fP is similar to \f2Wrong_Type()\fP;
the expected type is specified as a string.
This is useful if an \f2Object\fP can be a member of one out
of two or more types, e.\|g.\& a string or a symbol.
.LP
Fatal errors can be signaled using the functions
.Es
@[.=Fatal_Error()]@[.=Panic()]
void Fatal_Error(char *fmt, ...);
void Panic(char *msg);
.Ee
\f2Fatal_Error()\fP passes its arguments to \f2printf()\fP and
then terminates the program.
\f2Panic()\fP is used in situations that ``cannot happen''
(failed consistency checks or failed assertions);
it prints the specified message and terminates the program
with a core dump.
.\" ---------------------------------------------------------------------------
.K2 "Exceptions"
.PP
As explained in the Elk Reference Manual, a user-supplied Scheme
procedure is called each time an @[.\f2exception\fP] is raised.
Currently, the set of UNIX @[.signals] that are caught by the interpreter
or an extension (at least \f2interrupt\fP and \f2alarm\fP) are used
as exceptions.
As signals occur asynchronously, extensions and applications must be
able to protect non-reentrant or otherwise critical code sections
from the delivery of signals.
In particular, calls to external library functions are frequently
not reentrant\** and need to be protected from being disrupted.
.FS
Fortunately, with the advent of multithreading, vendors are now
beginning to provide reentrant versions of their system libraries.
.FE
.PP
Extensions may call the macros @[.\f2Disable_Interrupts\fP] and
@[.\f2Enable_Interrupts\fP] (without arguments) to enclose code fragments
that must be protected from exceptions.
Calls to these macros can be nested, and they are also available
as Scheme primitives on the Scheme-language level.
As all modern UNIX versions provide a facility to temporarily block
the delivery of signals, a signal that occurs after a call to
\f2Disable_Interrupts\fP will be delayed until the outermost matching
\f2Enable_Interrupts\fP is executed.
Two additional macros, @[.\f2Force_Disable_Interrupts\fP] and
@[.\f2Force_Enable_Interrupts\fP] can be used to enable
and disable signal delivery regarless of the current nesting level.
Extensions that use additional signals (such as the \f2alarm\fP signal)
must register these with the interpreter core to make sure they are
included in the \f2mask\fP of signals that is maintained by
\f2Disable_Interrupts\fP and \f2Enable_Interrupts\fP (the interface
for registering signals is still being revised; refer to the source
code of the UNIX extension for an example).
.PP
The ability to protect code from exceptions is particularly useful
for primitives that temporarily open a file or allocate some other
kind of resource that must subsequently be released again.
If the relevant code fragment were not enclosed by calls to
\f2Disable_Interrupts\fP and \f2Enable_Interrupts\fP, an exception
handler could abandon execution of the code section by calling
a continuation, thus causing the file to remain open forever.
While situations like this can be handled by \f2dynamic-wind\fP
on the Scheme level, some form of
\f2try/catch\fP facility is not available on the C-language level,
and using the C function implementing the \f2dynamic-wind\fP primitive
would be cumbersome.
.LP
The function
.Es
@[.=Signal_Exit()]
void Signal_Exit(int signal_number);
.Ee
may be used as the handler for signals that must terminate the
application; it ensures that the temporary files maintained by Elk are
removed and calls the @[.extension finalization functions] in
the normal way.
.\" ---------------------------------------------------------------------------
.K2 "Defining Scheme Variables"
.PP
User-supplied C/C++ code can define global Scheme variables that are
maintained as corresponding \f2Object\fP C variables.
The Scheme interpreter itself defines several such variables,
for example, the variable @[.\f2load-path\fP] (see section @(ch-dynl))
which can be modified and read both from Scheme and from C.
The function @[.\f2Define_Variable()\fP] is used
to define a Scheme variable and bind an initial value to it:
.Es
void Define_Variable(Object *var, const char *name, Object init);
.Ee
\f2var\fP is the address of the C variable corresponding to
the newly-created Scheme variable, \f2name\fP is the
name of the Scheme variable, and \f2init\fP is its initial value.
\f2Define_Variable()\fP calls @[.\f2Intern()\fP] to create the
variable name included in the new binding and
@[.\f2Func_Global_GC_Link()\fP] to properly register the C variable
with the garbage collector.
.LP
The C side of a Scheme variable cannot be accessed directly;
the functions
.Es
@[.=Var_Set()]@[.=Var_Get()]
Var_Set(Object variable, Object value);
Var_Get(Object variable)
Var_Is_True(Object variable)
.Ee
must be used instead to assign a value to the variable and
to read its current value; the first argument to each function
is the \f2Object\fP whose address was passed to \f2Define_Variable()\fP.
\f2Var_Is_True()\fP is convenient for boolean variables and tests
whether the contents of the variable is true in the sense of \f2Truep()\fP.
As an example, Figure @(defvar) shows how the @[.Xt extension]
defines a Scheme variable that is associated with the user-defined
``warning handler'' called by the Xt library to output warning messages.
.Fs
Object V_Xt_Warning_Handler;
.El
void Xt_Warning(char *msg) {
Object args, fun;
.El
args = Cons(Make_String(msg, strlen(msg)), Null);
fun = Var_Get(V_Xt_Warning_Handler);
if (TYPE(fun) == T_Compound)
(void)Funcall(fun, args, 0);
else
Printf(Curr_Output_Port, "%s\en", msg);
}
.El
void elk_init_xt_error(void) {
Define_Variable(&V_Xt_Warning_Handler, "xt-warning-handler", Null);
XtSetWarningHandler(Xt_Warning);
}
.Fc "The Xt extension defines a Scheme variable holding a ``warning handler''"
.Fe defvar
.PP
In the example in Figure @(defvar), the function \f2Xt_Warning()\fP
is registered as the Xt ``warning handler'' by passing it to
\f2XtSetWarningHandler()\fP.
It is invoked by Xt with a warning message.
The message is converted to a Scheme string, and, if the Scheme
variable \f2xt-warning-handler\fP has been assigned a procedure,
this procedure is called with the string using @[.\f2Funcall()\fP].
Otherwise the string is just sent to the current output port.
The call to \f2Define_Variable()\fP in the extension initialization
function associates the Scheme variable \f2xt-warning-handler\fP
with the C variable \f2V_Xt_Warning_Handler\fP (as a convention,
Elk uses the prefix ``V_'' for variables of this kind).
.\" ---------------------------------------------------------------------------
.K2 "Defining Readers"
.PP
In addition or as an alternative to the constructor primitive
for a new Scheme type, applications and extensions may define a
@[.\f2reader\fP function] for each new type.
The @[.bitstring extension], for example, defines a reader to allow
input of bitstring literals using the \f2#*10110001\fP syntax.
Each user-defined read syntax is introduced by the `#' symbol
followed by one more character, identifying the type of the object.
To define a reader, the following function is called (typically
from within an @[.extension initialization function]):
.Es
@[.=Define_Reader()]
void Define_Reader(int c,
(Object (*func)(Object port, int c, int const_flag)));
.Ee
.PP
The arguments to \f2Define_Reader()\fP are the as yet unused
character identifying the type (e.\|g.\& `*' for bitstrings)
and a pointer to a \f2reader function\fP that is invoked by the
Scheme parser whenever the newly defined syntax is encountered.
This reader function is passed a Scheme input port from which it reads
the next token, the character following the `#' symbol (to facilitate
using the same reader for different types), and a flag indicating
whether the newly-created object is expected to be made read-only
(this is true when expressions are loaded from a file).
The reader function must return a new object of the given type.
.PP
You may want to refer to the bitstring extension included in the Elk
distribution for an example definition of a reader function
(``lib/misc/bitstring.c''), and for the macros that can be used by
reader functions to efficiently read characters from a port.
.\" ---------------------------------------------------------------------------
.K2 "Fork Handlers"
.PP
Extensions may need to be notified when a copy of the running
interpreter (or application) is created by means of the \f2fork()\fP
UNIX system call.
For example, consider an extension that stores information in a
temporary file and removes this file on termination of the program.
If another extension created a copy of the running interpreter by
calling \f2fork()\fP, the child process would remove the temporary
file on exit\*-the file would not be available to the original
instance of the interpreter (i.\|e.\& the parent process) any longer.
To prevent premature removal of the file, the extension that owns
it can define a @[.\f2fork handler\fP] by calling @[.\f2Register_Onfork()\fP]
with a pointer to a C function:
.Es
void Register_Onfork((void (*func)(void)));
.Ee
The function could create an additional link to the file, so that
a child process would just remove this link on exit, leaving the
original link intact.
.PP
Extensions that use \f2fork()\fP without executing a new program
in the child process (e.\|g.\& the @[.UNIX extension] which
defines a \f2unix-fork\fP primitive) are required to call the function
@[.\f2Call_Onfork()\fP] in the newly created child process to invoke all
currently defined fork handlers:
.Es
void Call_Onfork(void);
.Ee
.\" ---------------------------------------------------------------------------
.AP "Appendix A: Functions that can Trigger a Garbage Collection"
.PP
This appendix lists the functions exported by Elk that may trigger a
@[.garbage collection].
Within C/C++ code, local Scheme objects must be protected as shown in
section @(ch-gc) when one of these functions is called during the
objects' lifetime.
.PP
The C functions corresponding to the following Scheme primitives can
cause a garbage collection:
.Es
append load read-string
apply macro-body require
autoload macro-expand reverse
backtrace-list make-list string
call-with-input-file make-string string->list
call-with-output-file make-vector string->number
call/cc map string->symbol
command-line-args oblist string-append
cons open-input-file string-copy
dump open-input-output-file substring
dynamic-wind open-input-string symbol-plist
eval open-output-file tilde-expand
for-each open-output-string type
force port-line-number vector
get-output-string procedure-lambda vector->list
list provide vector-copy
list->string put with-input-from-file
list->vector read with-output-to-file
.El
.ft 2
all special forms
all mathematical primitives except predicates
all output primitives if output is sent to a string port
.ft
.Ee
.PP
In practice, most of these functions, in particular the special forms,
are rarely or never used in extensions or Elk-based applications.
In addition to these primitives, the following C functions can
trigger a garbage collection:
.Es
Alloc_Object() Make_Reduced_Flonum() Make_String()
Make_Port() Make_Flonum() Make_Const_String()
Load_Source_Port() Define_Primitive() Intern()
Load_File() Printf() CI_Intern()
Copy_List() Print_Object() Define_Variable()
Const_Cons() General_Print_Object() Define_Symbol()
Make_Integer() Format() Bits_To_Symbols()
Make_Unsigned() Eval() Make_Vector()
Make_Long() Funcall() Make_Const_Vector()
Make_Unsigned_Long()
.Ee
.LP
Note: \f2Make_Integer()\fP, \f2Make_Unsigned()\fP,
\f2Make_Long()\fP, and \f2Make_Unsigned_Long()\fP can only trigger a
garbage collection if \f2FIXNUM_FITS()\fP (or \f2UFIXNUM_FITS()\fP,
respectively) returns zero for the given argument.
.\" ---------------------------------------------------------------------------
.AP "Appendix B: Convenience Functions for GC-Safe Data Structures"
.PP
Figure @(gcroot) shows the source code for a set of functions to
insert Scheme objects into a vector that has been registered with the
garbage collector, to delete objects from the vector,
and to retrieve the object stored under a given vector index.
These functions help building dynamic data structures (such as
linked lists or hash tables) containing Scheme objects.
There is nothing application-specific in the code; if you find it
useful, you can directly include it in your Elk extension or
Elk-based application without any changes.
See section @(ch-gcglobal) for a detailed description.
.Fs nofloat
static int max_objects = 32; /* initial size */
static int num_objects;
static Object objects;
static int inx;
.El
int register_object(Object x) {
Object v;
int n;
GC_Node;
.El
if (num_objects == max_objects) {
max_objects *= 2;
GC_Link(x);
v = Make_Vector(max_objects, Null);
GC_Unlink;
memcpy(VECTOR(v)->data, VECTOR(objects)->data,
num_objects * sizeof(Object));
objects = v;
inx = num_objects;
}
for (n = 0; !Nullp(VECTOR(objects)->data[inx]);
inx++, inx %= max_objects) {
n++;
assert(n < max_objects);
}
VECTOR(objects)->data[inx] = x;
num_objects++;
return inx;
}
.El
void deregister_object(int i) {
VECTOR(objects)->data[i] = Null;
--num_objects;
assert(num_objects >= 0);
}
.El
Object get_object(int i) {
return VECTOR(objects)->data[i];
}
.El
void elk_init_gcroot(void) {
objects = Make_Vector(max_objects, Null);
Global_GC_Link(objects);
}
.Fc "Functions to map Scheme objects to indexes into a GC-safe vector"
.Fe gcroot
.\" ---------------------------------------------------------------------------
.AP "Appendix C: Summary of Functions, Macros, Types, and Variables"
.PP
This appendix provides a quick overview of the functions and other
definitions exported by the Elk kernel.
The list is divided in groups of definitions with related
functionality; the entries are presented in roughly the same order
in which they are introduced in the above chapters.
Full function prototypes are given for functions; in some
prototypes, arguments are given names for clarification.
The initial keywords \f3function\fP, \f3macro\fP, \f3typedef\fP,
and \f3variable\fP indicate the type of each entry (function,
preprocessor symbol with or without arguments, type definition,
and external variable defined by Elk, respectively).
The functions corresponding to Scheme primitives (as described
in section @(ch-prims)) have been omitted from the list.
.SH
Accessing the Scheme Object Representation
.LP
.Cs
\f3typedef\fP Object
.Cl
\f3macro\fP TYPE(obj)
\f3macro\fP POINTER(obj)
\f3macro\fP ISCONST(obj)
\f3macro\fP SETCONST(obj)
\f3macro\fP SET(obj, type, ptr)
\f3macro\fP EQ(obj1, obj2)
.Ce
.SH
Defining Scheme Primitives
.LP
.Cs
\f3function\fP void Define_Primitive((Object (*func)()), const char *name,
int minargs, int maxargs, enum discipline disc);
.Ce
.SH
Making Objects Known to the Garbage Collector
.LP
.Cs
\f3macro\fP GC_Node, GC_Node2, ...
\f3macro\fP GC_Link(obj), GC_Link2(obj1, obj2), ...
\f3macro\fP GC_Unlink
\f3macro\fP Global_GC_Link(obj)
\f3function\fP void Func_Global_GC_Link(obj_ptr);
.Ce
.SH
Booleans
.LP
.Cs
\f3macro\fP T_Boolean
\f3macro\fP Truep(obj)
.Cl
\f3variable\fP Object True
\f3variable\fP Object False
.Cl
\f3function\fP int Eqv(Object, Object);
\f3function\fP int Equal(Object, Object);
.Ce
.SH
Characters
.LP
.Cs
\f3macro\fP T_Character
\f3macro\fP CHAR(char_obj)
\f3function\fP Object Make_Char(int);
\f3variable\fP Object Newline
.Ce
.SH
Pairs and Lists
.LP
.Cs
\f3macro\fP T_Null
\f3macro\fP Nullp(obj)
\f3variable\fP Null
.Cl
\f3macro\fP T_Pair
\f3macro\fP PAIR(pair_obj)
\f3macro\fP Car(obj)
\f3macro\fP Cdr(obj)
\f3macro\fP Cons(obj1, obj2)
.Cl
\f3macro\fP Check_List(obj)
\f3function\fP int Fast_Length(Object);
\f3function\fP Object Copy_List(Object);
.Ce
.SH
Integers (Fixnums and Bignums)
.LP
.Cs
\f3macro\fP T_Fixnum
\f3macro\fP T_Bignum
\f3macro\fP FIXNUM_FITS(integer)
\f3macro\fP UFIXNUM_FITS(unsigned_integer)
\f3macro\fP FIXNUM(fixnum_obj)
\f3macro\fP BIGNUM(bignum_obj)
.Cl
\f3macro\fP Check_Integer(obj)
\f3macro\fP Check_Number(obj)
.Cl
\f3function\fP Object Make_Integer(int);
\f3function\fP Object Make_Unsigned(unsigned);
\f3function\fP Object Make_Long(long);
\f3function\fP Object Make_Unsigned_Long(unsigned long);
.Cl
\f3function\fP int Get_Integer(Object);
\f3function\fP unsigned Get_Unsigned(Object);
\f3function\fP long Get_Long(Object);
\f3function\fP unsigned long Get_Unsigned_Long(Object);
.Cl
\f3function\fP int Get_Exact_Integer(Object);
\f3function\fP unsigned Get_Exact_Unsigned(Object);
\f3function\fP long Get_Exact_Long(Object);
\f3function\fP unsigned long Get_Exact_Unsigned_Long(Object);
.Ce
.SH
Floating Point Numbers (Reals)
.LP
.Cs
\f3macro\fP T_Flonum
\f3macro\fP FLONUM(flonum_obj)
\f3function\fP Object Make_Flonum(double);
\f3function\fP Object Make_Reduced_Flonum(double);
\f3function\fP double Get_Double(Object);
.Ce
.SH
Symbols
.LP
.Cs
\f3macro\fP T_Symbol
\f3macro\fP SYMBOL(symbol_obj)
\f3function\fP Object Intern(const char *);
\f3function\fP Object CI_Intern(const char *);
\f3function\fP void Define_Symbol(Object *var, const char *name);
\f3variable\fP Object Void
.Cl
\f3typedef\fP SYMDESCR
\f3function\fP unsigned long Symbols_To_Bits(Object syms, int mask_flag,
SYMDESCR *table);
\f3function\fP Object Bits_To_Symbols(unsigned long bits, int mask_flag,
SYMDESCR *table);
.Ce
.SH
Strings
.LP
.Cs
\f3macro\fP T_String
\f3macro\fP STRING(string_obj)
\f3function\fP Object Make_String(const char *init, int size);
\f3function\fP char *Get_String(Object);
\f3function\fP char *Get_Strsym(Object);
\f3macro\fP Get_String_Stack(obj, char_ptr)
\f3macro\fP Get_Strsym_Stack(obj, char_ptr)
.Ce
.SH
Vectors
.LP
.Cs
\f3macro\fP T_Vector
\f3macro\fP VECTOR(vector_obj)
\f3function\fP Object Make_Vector(int size, Object fill);
.Ce
.SH
Ports
.LP
.Cs
\f3macro\fP T_Port
\f3macro\fP PORT(port_obj)
\f3function\fP Object Make_Port(int flags, FILE *f, Object name);
\f3function\fP Object Terminate_File(Object port);
\f3macro\fP Check_Input_Port(obj)
\f3macro\fP Check_Output_Port(obj)
\f3variable\fP Object Curr_Input_Port, Curr_Output_Port
\f3variable\fP Object Standard_Input_Port, Standard_Output_Port
\f3function\fP void Reset_IO(int destructive_flag);
\f3function\fP void Printf(Object port, char *fmt, ...);
\f3function\fP void Print_Object(Object obj, Object port, int raw_flag,
int print_depth, int print_length);
\f3macro\fP Print(obj)
\f3function\fP void Load_Source_Port(Object port);
\f3function\fP void Load_File(char *filename);
.Ce
.SH
Miscellaneous Types
.LP
.Cs
\f3macro\fP T_End_Of_File
\f3variable\fP Object Eof
.Cl
\f3macro\fP T_Environment
\f3variable\fP Object The_Environment, Global_Environment
.Cl
\f3macro\fP T_Primitive
\f3macro\fP T_Compound
\f3function\fP void Check_Procedure(Object);
.Cl
\f3macro\fP T_Control_Point
\f3macro\fP T_Promise
\f3macro\fP T_Macro
.Ce
.SH
Defining Scheme Types and Allocating Objects
.LP
.Cs
\f3function\fP int Define_Type(int zero, const char *name,
int (*size)(Object), int const_size,
int (*eqv)(Object, Object),
int (*equal)(Object, Object),
int (*print)(Object, Object, int, int, int),
int (*visit)(Object*, int (*)(Object*)));
\f3function\fP Object Alloc_Object(int size, int type, int const_flag);
.Ce
.SH
Calling Scheme Procedures and Evaluating Scheme Code
.LP
.Cs
\f3function\fP Object Funcall(Object fun, Object argl, int eval_flag);
\f3function\fP Object Eval(Object expr);
\f3function\fP char *String_Eval(char *expr);
.Ce
.SH
Weak Pointers and Object Termination
.LP
.Cs
\f3function\fP void Register_Before_GC((void (*func)(void)));
\f3function\fP void Register_After_GC((void (*func)(void)));
.Cl
\f3macro\fP IS_ALIVE(obj)
\f3macro\fP WAS_FORWARDED(obj)
\f3macro\fP UPDATE_OBJ(obj)
.Cl
\f3function\fP void Register_Object(Object obj, char *group,
(Object (*term)(Object)), int leader_flag);
\f3function\fP void Deregister_Object(Object obj);
\f3function\fP void Terminate_Type(int type);
\f3function\fP void Terminate_Group(char *group);
\f3function\fP Object Find_Object(int type, char *group,
(int (*match_func)(Object, ...)), ...);
.Ce
.SH
Signaling Errors
.LP
.Cs
\f3function\fP void Primitive_Error(char *fmt, ...);
\f3function\fP void Set_Error_Tag(const char *tag);
\f3function\fP char *Get_Error_Tag(void);
\f3function\fP void Set_App_Name(char *name);
\f3function\fP void Range_Error(Object offending_obj);
\f3function\fP void Wrong_Type(Object offending_obj, int expected_type);
\f3function\fP void Wrong_Type_Combination(Object offending_obj,
char *expected_type);
\f3function\fP void Fatal_Error(char *fmt, ...);
\f3function\fP void Panic(char *msg);
\f3variable\fP int Saved_Errno
.Ce
.SH
Exceptions (Signals)
.LP
.Cs
\f3macro\fP Disable_Interrupts, Enable_Interrupts
\f3macro\fP Force_Disable_Interrupts, Force_Enable_Interrupts
\f3function\fP void Signal_Exit(int signal_number);
.Ce
.SH
Defining and Using Scheme Variables
.LP
.Cs
\f3function\fP void Define_Variable(Object *var, const char *name, Object init);
\f3function\fP void Var_Set(Object var, Object val);
\f3function\fP Object Var_Get(Object var);
\f3function\fP int Var_Is_True(Object var);
.Ce
.SH
Defining Reader Functions
.LP
.Cs
\f3function\fP void Define_Reader(int c,
(Object (*func)(Object port, int c, int const_flag)));
.Ce
.SH
Fork Handlers
.LP
.Cs
\f3function\fP void Register_Onfork((void (*func)(void)));
\f3function\fP void Call_Onfork(void);
.Ce
.SH
Allocating Memory
.LP
.Cs
\f3function\fP char *Safe_Malloc(unsigned size);
\f3function\fP char *Safe_Realloc(char *old_pointer, unsigned size);
.Cl
\f3macro\fP Alloca_Begin, Alloca_End
\f3macro\fP Alloca(char_ptr, type, size)
.Ce
.SH
Initializing Elk from an Application's main()
.LP
.Cs
\f3function\fP void Elk_Init(int argc, char **argv, int init_flag,
char *filename);
.Ce
.SH
Miscellaneous Macros
.LP
.Cs
\f3macro\fP ELK_MAJOR, ELK_MINOR
\f3macro\fP NO_PROTOTYPES, WANT_PROTOTYPES
.Ce
.\" ---------------------------------------------------------------------------
.\" XXX: dynamic loading + dump
.\" ---------------------------------------------------------------------------
.if !\n(.U .so ../util/tmac.index
.if !\n(.U .so side.inx
.Tc