Programming With Unicon 2nd Ed Clinton Jeffery Shamim Mohamed download
Programming With Unicon 2nd Ed Clinton Jeffery Shamim Mohamed download
https://ebookbell.com/product/programming-with-unicon-2nd-ed-
clinton-jeffery-shamim-mohamed-10009244
https://ebookbell.com/product/programming-with-openscad-a-beginners-
guide-to-coding-3dprintable-objects-1st-edition-justin-gohde-46410140
https://ebookbell.com/product/programming-with-python-and-its-
applications-to-physical-systems-m-shubhakanta-singh-50637024
https://ebookbell.com/product/programming-with-microsoft-visual-
basic-2012-6th-edition-zak-55132386
https://ebookbell.com/product/programming-with-microsoft-visual-
basic-2010-vbnet-programming-5th-edition-zak-55139980
Programming With Java Edet Theophilus
https://ebookbell.com/product/programming-with-java-edet-
theophilus-55229942
https://ebookbell.com/product/programming-with-rust-donis-
marshall-55709862
Programming With C20 Concepts Coroutines Ranges And More Updated 2024
2nd Edition Andreas Fertig
https://ebookbell.com/product/programming-with-c20-concepts-
coroutines-ranges-and-more-updated-2024-2nd-edition-andreas-
fertig-56812174
https://ebookbell.com/product/programming-with-tensorflow-kolla-bhanu-
prakash-g-r-kanagachidambaresan-22736720
https://ebookbell.com/product/programming-with-quartz-2d-and-pdf-
graphics-in-mac-os-x-1st-edition-david-gelphman-2382698
Programming with Unicon
2nd edition
Clinton Jeery
Shamim Mohamed
Jafar Al Gharaibeh
Ray Pereda
Robert Parlett
Copyright c 1999-2018 Clinton Jeery, Shamim Mohamed, Jafar Al Gharaibeh, Ray
Pereda, and Robert Parlett
This is a draft manuscript dated December 16, 2018. Send comments and errata to
je[email protected].
I Core Unicon 5
1 Programs and Expressions 7
1.1 Your First Unicon Program . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2 Command Line Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Expressions and Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Numeric Computation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5 Strings and Csets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.6 Goal-directed Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.7 Fallible Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.8 Generators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.9 Iteration and Control Structures . . . . . . . . . . . . . . . . . . . . . . . . 20
1.10 Procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2 Structures 29
2.1 Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.2 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.3 Records . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.4 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.5 Using Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3 String Processing 41
3.1 The String and Cset Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.1.1 String Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.1.2 Character Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.1.3 Character Escapes . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.2 String Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.2.1 String Scanning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
i
ii CONTENTS
6 Databases 91
6.1 Language Support for Databases . . . . . . . . . . . . . . . . . . . . . . . 91
6.2 Memory-based Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
6.3 DBM Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
6.4 SQL Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
6.5 Tips and Tricks for SQL Database Applications . . . . . . . . . . . . . . . 100
6.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
7 Graphics 103
7.1 2D Graphics Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
7.2 Graphics Contexts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
7.3 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
7.4 Colors and Fonts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
CONTENTS iii
8 Threads 135
8.1 Threads and Co-Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . 136
8.2 First Look at Unicon Threads . . . . . . . . . . . . . . . . . . . . . . . . . 136
8.3 Thread Safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
8.4 Thread Synchronization . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
8.5 Thread Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
8.6 Practical examples using threads and messages . . . . . . . . . . . . . . . . 161
8.6.1 Disk space usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
8.6.2 More suggestions for parallel processing . . . . . . . . . . . . . . . . 168
8.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
IV Appendices 361
A Language Reference 363
A.1 Immutable Types: Numbers, Strings, Csets, Patterns . . . . . . . . . . . . 363
A.2 Mutable Types: Containers and Files . . . . . . . . . . . . . . . . . . . . . 365
A.3 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
A.4 Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
A.5 Control Structures and Reserved Words . . . . . . . . . . . . . . . . . . . . 373
A.6 Operators and Built-in Functions . . . . . . . . . . . . . . . . . . . . . . . 377
A.7 Preprocessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
A.8 Execution Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
vi CONTENTS
F Installation 507
F.1 Building on Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507
F.2 Building on OS X . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507
F.3 Building on Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507
Bibliography 509
Preface to the Second Edition
This book will raise your level of skill at computer programming, regardless of whether
you are presently a novice or expert. The eld of programming languages is still in its
infancy, and dramatic advances will be made every decade or two until mankind has had
enough time to think about the problems and principles that go into this exciting area of
computing. The Unicon language described in this book is such an advance, incorporating
many elegant ideas not yet found in most contemporary languages.
Unicon is an object-oriented, goal-directed programming language based on the Icon
programming language. Unicon can be pronounced however you wish; we pronounce it
variably depending on mood, whim, or situation; the most frequent pronunciation rhymes
with lexicon.
For Icon programmers this work serves as a companion book that documents material
such as the Icon Program Library, a valuable resource that is underutilized. Don't be
surprised by language changes: the book presents many new facilities that were added to
Icon to make Unicon and gives examples from new application areas to which Unicon is
well suited. For people new to Icon and Unicon, this book is an exciting guide to a powerful
language.
It is with sweet irony that we call this book the 2nd Edition, since the rst edition
was never formally published but instead existed solely as an online document, although
laser-printed hard copies could be requested. A lot has happened to Unicon since the rst
edition of this book, which culminated in 2004. This 2nd Edition catches readers up
with things like concurrent threads and vastly improved 3D graphics facilities. Along the
way, the games chapter and parts of the internet programming chapter got spun o into a
separate work, the so-called Manual of Puissant Skill at Game Programming.
This book consists of four parts. The rst part, Chapters 1-8, presents the core of the
Unicon language, much of which comes from Icon. These early chapters start with simple
expressions, progress through data structures and string processing, and include advanced
programming topics and the input/output capabilities of Unicon's portable system inter-
face. Part two, in Chapters 9-12, describes object-oriented development as a whole and
vii
viii PREFACE TO THE SECOND EDITION
Acknowledgments
Thanks to the Icon Project for creating a most excellent language. Thanks especially to
those unsung heroes, the university students and Internet volunteers who implemented the
language and its program library over a period of many years. Icon contributors can be
divided into epochs. In the epoch leading up to the rst edition of this book, we were
inspired by contributions from Gregg Townsend, Darren Merrill, Mary Cameron, Jon Lipp,
Anthony Jones, Richard Hatch, Federico Balbi, Todd Proebsting, Steve Lumos and Naomi
Martinez. In the epoch since the rst edition of this book, the Unicon Project owes a debt
of gratitude to Ziad al Sharif, Hani bani Salameh, Jafar Al Gharaibeh, Mike Wilder, and
Sudarshan Gaikaiwari.
The most impressive contributors are those whose inuence on Icon has spanned across
epochs, such as Ralph Griswold, Steve Wampler, Bob Alexander, Ken Walker, Phillip
Thomas, and Kostas Oikonomou. We revere you folks! Steve Wampler deserves extra
thanks for serving as the technical reviewer for the rst edition of this book. Phillip
Thomas and Kostas Oikonomou have provided extensive support and assistance that goes
way beyond the call of duty; in many ways this is their book.
This manuscript received critical improvements and corrections from many additional
technical reviewers, including, David A. Gamey, Craig S. Kaplan, David Feustel, David
Slate, Frank Lhota, Art Eschenlauer, Wendell Turner, Dennis Darland, and Nolan Clayton.
The authors wish to acknowledge generous support from the National Library of
Medicine and AT&T Bell Labs Research. This work was also supported in part by the
National Science Foundation under grants CDA-9633299, EIA-0220590 and EIA-9810732,
and the Alliance for Minority Participation.
Clinton Jeery
Shamim Mohamed
Jafar al Gharaibeh
Ray Pereda
Robert Parlett
Introduction
Software development requires thinking about several dimensions simultaneously. For large
programs, writing the actual computer instructions is not as dicult as guring out the
details of what the computer is supposed to do. After analyzing what is needed, program
design brings together the data structures, algorithms, objects, and interactions that ac-
complish the required tasks. Despite the importance of analysis and design, programming
is still the central act of software development for several reasons. The weak form of the
Sapir-Whorf hypothesis suggests that the programming language we use steers and guides
the way we think about software, so it aects our designs. Software designs are mathemat-
ical theorems, while programs are proofs that test those designs. As in other branches of
mathematics, the proofs reign supreme. In addition, a correct design can be foiled by an
inferior implementation.
This book is a guide and reference for an exciting programming language called Unicon
that has something to oer both computer scientists as well as casual programmers. You
will nd explanations of fundamental principles, unique language idioms, and advanced
concepts and examples. Unicon exists within the broader context of software development,
so the book also covers software engineering fundamentals. Writing a correct, working
program is the central task of software engineering. This does not happen automatically
as a result of the software design process. Make no mistake: if you program very much,
the programming language you use is of vital importance. If it weren't, we would still be
programming in machine language.
1
2 PREFACE TO THE SECOND EDITION
from the center. Analysis, design, coding, and evaluation are repeated to produce a better
product with each iteration. "Prototyping" is the act of coding during those iterations when
the software is not yet fully specied or the program does not yet remotely implement the
required functionality.
Tight spirals are better than loose spirals. The more powerful the prototyping tools, the
less time and money spent in early iterations of development. This translates into either
faster time to market, or a higher quality product. Some prototypes are thrown away once
they have served the purpose of clarifying requirements or demonstrating some technique.
This is OK, but in the spiral model some prototypes are gradually enhanced until they
become the nal production system.
The name Unicon refers to the descendant of Icon described in this book and distributed
from www.unicon.org. Unicon is Icon with portable, platform-independent access to hard-
3
ware and software features that have become ubiquitous in modern applications develop-
ment, such as objects, networks, and databases. Unicon is created from the same public
domain source code that Arizona Icon uses, so it has a high degree of compatibility. We
were not free to call it version 10 of the Icon language, since it was not produced or endorsed
by the Icon Project at the University of Arizona.
Just as the name Unicon frees the Icon Project of all responsibility for our eorts, it
frees us from the requirement of backward compatibility. While Unicon is almost entirely
backward compatible with Icon, dropping full compatibility allows us to clear out some
dead wood and more importantly, to make some improvements in the operators that will
benet everyone at the expense of...no one but the compatibility police. This book covers
the features of Icon and Unicon together. A compatibility check list and description of the
dierences between Icon and Unicon are given in Appendix D.
It is interesting to compare Icon and Unicon with the competition. Mainstream program-
ming languages such as C, C++, and Java, like the assembler languages that were main-
stream before them, are ideal tools for writing all sorts of programs, so long as vast amounts
of programmer time are available. Throwing more programmers at a big project does not
work well, and programmers are getting more expensive while computing resources continue
to become cheaper. These pressures inexorably lead to the use of higher-level languages
and the development of better design and development methods. Such human changes are
incredibly slow compared to technological changes, but they are visibly occurring never-
theless. Today, the most productive programmers are using extra CPU cycles and memory
to reduce the time it takes to develop useful programs.
There is a subcategory of mainstream languages, marketed as rapid application de-
velopment languages, whose stated goals seem to address this phenomenon. Languages
such as Visual Basic or PowerBuilder provide graphical interface builders and integrated
database connectivity, giving productivity increases in the domain of data entry and pre-
sentation. The value added in these products are in their programming environments, not
their languages. The integrated development environments and tools provided with these
languages are to be acclaimed and emulated, but they do not provide productivity gains
that are equally relevant to all application domains. They are only a partial solution to
the needs of complex applications.
Icon is designed to be easier and faster to program than mainstream languages. The
value it adds is in the expressive power of the language itself, in the category of very high
level languages that includes Lisp, APL, Smalltalk, REXX, Perl, Tcl, Python, and Ruby;
there are many others. Very high-level languages can be subdivided into scripting lan-
guages and applications languages. Scripting languages often glue programs together from
disparate sources. They are typically strong in areas such as multilingual interfacing and le
4 PREFACE TO THE SECOND EDITION
system interactions, while suering from weaker expression semantics, typing, scope rules,
and control structures than their applications-oriented cousins. Applications languages
typically originate within a particular application domain and support that domain with
special syntax, control structures, and data types. Since scripting is an application domain,
scripting languages are just one prominent subcategory of very high-level languages.
Icon is an applications language with roots in text processing and linguistics. Icon
programs tend to be more readable than similar programs written in other very high-level
languages, making Icon well-suited to the aims of literate programming. For example,
Icon was used to implement Norman Ramsey's literate programming tool noweb (Ramsey,
1994). Literate programming is the practice of writing programs and their supporting
textual description together in a single document.
Unicon makes the core contributions of Icon useful for a broader range of applications.
This book's many examples illustrate the range of tasks for which Unicon is well suited, and
these examples are the evidence in support of Unicon's existence. Consider using Unicon
when one or more of the following conditions are true. The more conditions that are true,
the more you will benet from Unicon.
• Programmer time must be minimized.
• Maintainable, concise source code is desired.
• The program includes complex data structures or experimental algorithms.
• The program involves a mixture of text processing and analysis, custom graphics,
data manipulation, network or le system operations.
• The program must run on several operating systems and have a nearly identical
graphical user interface with little or no source code dierences.
Unicon is not the last word in programming. You probably should not use Unicon if your
program has one or more of the following requirements:
• The fastest possible performance is needed.
• The program has hard real-time constraints.
• The program must perform low-level or platform-specic interactions with the hard-
ware or operating system.
Programming languages play a key role in software development. The Unicon language is a
very high level object-oriented language with a unique combination of expressive power and
scalable rapid development. In this book, many examples from a wide range of application
areas demonstrate how to apply and combine Unicon's language constructs to solve real-
world problems. It is time to move past the introductions. Prepare to be spoiled by this
language. You may have the same feelings that Europeans felt when they gave up using
Roman numerals and switched to the Hindu-Arabic number system. This multiplication
stu isn't that hard anymore!
Part I
Core Unicon
5
Chapter 1
Programs and Expressions
This chapter presents many of the key features of Unicon, starting with those it has in
common with other popular languages. Detailed instructions show how to compile and run
programs. Soon the examples introduce important ways in which Unicon is dierent from
other languages. These dierences are more than skin deep. If you dig deeply, you can nd
dozens of details where Unicon provides just the right blend of simplicity, exibility, and
power. After this chapter, you will know how to
This section presents the nuts and bolts of writing and running an Unicon program, after
which you will be able to try the code examples or write your own programs. Before you
can run the examples here or in any subsequent chapter, you must install Unicon on your
system. (See Appendix F for details on downloading and installing Unicon from the Unicon
web site, http://unicon.org.) We are going to be very explicit here, and assume nothing
about your background. If you are an experienced programmer, you will want to skim this
section, and move on to the next section. If you are completely new to programming, have
no fear. Unicon is pretty easy to learn.
All programs consist of commands that use hardware to obtain or present information
to users, and perform calculations that transform information into a more useful form.
To program a computer you write a document containing instructions for the computer to
carry out. In Unicon a list of instructions is called a procedure, and a program is a collection
of one or more procedures. In larger programs, groups of related procedures are organized
7
8 CHAPTER 1. PROGRAMS AND EXPRESSIONS
into classes or packages; these features are presented in Part II of this book. Unicon
programs are text les that may be composed using any text editor. For the purposes
of demonstration this section describes how to use Ui, the program editor and integrated
development tool that comes with Unicon.
It is time to begin. Fire up Ui by typing "ui" from the command line, or launching the
menu item or icon labeled "Unicon," and type:
procedure main()
write("Hello, amigo!")
end
Your screen should look something like Figure 1-1. The large upper area of the window
is the editing region where you type your program code. The lower area of the window is
a status region in which the Ui program displays a message when a command completes
successfully, or when your program has an error. Until you explicitly name your le some-
thing else, a new le has the name noname.icn. The font Ui uses to display source code is
selectable from the Options menu.
The list of instructions that form a procedure begins with the word procedure and
ends with the word end. Procedures have names. After writing a list of instructions in
a procedure you may refer to it by name without writing out the list again. The write()
instruction is just such a procedure, only it is already written for you; it is built in to the
language. When you issue a write() instruction, you tell the computer what to write. The
details a procedure uses in carrying out its instructions are given inside the parentheses
following that procedure's name; in this case, "Hello, amigo!" is to be written. When you
see parentheses after a name in the middle of a list of instructions, it is an instruction to
go execute that procedure's instructions. Inside the parentheses there may be zero, one, or
many values supplied to that procedure.
Besides writing your program, there are a lot of menu commands that you can use to
control the details of compiling and executing your program within Ui. For instance, if you
select Run→Run, Ui will do the following things for you.
1. Save the program in a le on disk. All Unicon programs end in .icn; you could name
it anything you wished, using the File→SaveAs command.
2. Compile the Unicon program from human-readable text to (virtual) machine lan-
guage. To do this step manually, you can select the Compile→Make executable
command.
3. Execute the program. This is the main purpose of the Run command. Ui performed
the other steps in order to make this operation possible.
If you type the hello.icn le correctly, the computer should chug and grind its teeth for
awhile, and
Hello, amigo!
should appear in a window on your screen. This ought to be pretty intuitive, since the
instructions included the line
write("Hello, amigo!")
The end result after making your changes should look like this:
10 CHAPTER 1. PROGRAMS AND EXPRESSIONS
procedure main()
write("Hello, amigo!")
write("How are you?")
write(7 + 12)
end
Run the program again. This example shows you what a list of instructions looks like,
as well as how easy it is to tell the computer to do some arithmetic.
Note
It would be ne (but not very useful) to tell the computer to add 7 and 12 without
telling it to write the resulting value. On seeing the instruction
7 + 12
the computer would do the addition, throw the 19 away, and go on.
Add the following line, and run it:
write("7 + 12")
This illustrates what quotes are for. Quoted text is taken literally; without quotes, the
computer tries to simplify (do some arithmetic, or compute the value of what is written),
which might be dicult if the material in question is not an expression!
write(hey you)
makes no sense and is an error. Add this line, and run it:
write(7 + "12")
The 12 in quotes is taken literally as some text, but that text happens to be digits that
comprise a number, so adding it to another number makes perfect sense. The computer
will not have as much success if you ask it to add 7 to amigo. The computer views all
of this in terms of values. A value is a unit of information, such as a number. Anything
enclosed in quotes is a single value. The procedure named write() prints values on your
screen. Operators such as + take values and combine them to produce other values, if it
is possible to do so. The values you give to + had better be numbers! If you try to add
something that doesn't make sense, the program will stop running at that point, and print
an error message.
By now you must have the impression that writing things on your screen is pretty easy.
Reading the keyboard is just as easy, as illustrated by the following program:
procedure main()
write("Type a line ending with <ENTER>:")
write("The line you typed was" , read())
end
1.1. YOUR FIRST UNICON PROGRAM 11
Run the program to see what it does. The procedure named read() is used to get what
the user types. It is built in to the language. The read() instruction needs no directions
to do its business, so nothing is inside the parentheses. When the program is run, read()
grabs a line from the keyboard, turns it into a value, and produces that value for use in
the program, in this case for the enclosing write() instruction.
The write() instruction is happy to print out more than one value on a line, separated
by commas. When you run the program, the "the line you typed was" part does not get
printed until after you type the line for read() and that instruction completes. The write()
instruction must have all of its directions (the values inside the parentheses) before it can
go about its business.
Now let's try some variations. Can you guess what the following line will print?
The read() procedure is never executed because it is quoted! Quotes say "take these
letters literally, not as an equation or instruction to evaluate." How about:
Here the quotes enclose one big value, which is printed, comma and all. The directions one
gives to a procedure are parameters ; when you give a procedure more than one parameter,
separated by commas, you are giving it a parameter list. For example,
Compile and run the following strange-looking program. What do you think it does?
procedure main()
while write( "" ˜== read() )
end
This program copies the lines you type until you type an empty line by pressing Enter
without typing any characters rst. The "" are used just as usual. They direct the program
to take whatever is quoted literally, and this time it means literally nothing - an empty line.
The operator ˜== stands for "not equals". It compares the value on its left to the value on
its right, and if they are not equal, it produces the value on the right side; if they are equal,
it fails - that is, the not equals operator produces no value. If you have programmed in
other languages, this may seem like a strange way to describe what is usually performed
with nice simple Boolean values True and False. For now, try to take this description
at face value; Unicon has no Boolean type or integer equivalent, it uses a more powerful
concept that we will examine more fully in the chapters that follow.
Thus, the whole expression "" ˜== read() takes a line from the keyboard, and if it is not
empty, it produces that value for the enclosing write() instruction. When you type an empty
12 CHAPTER 1. PROGRAMS AND EXPRESSIONS
line, the value read() produces is equal to "", and ˜== produces no value for the enclosing
write() instruction, which similarly fails when given no value. The while instruction is a
"loop" that repeats the instruction that follows it until that instruction fails (in this case,
until there is no more input). There are other kinds of loops, as well as another way to use
while; they are all described later in this chapter.
So far we've painted you a picture of the Unicon language in very broad strokes, and
informally introduced several relevant programming concepts along the way. These con-
cepts are presented more thoroughly and in their proper contexts in the next sections and
subsequent chapters. Hopefully you are already on your way to becoming an Icon pro-
grammer extraordinaire. Now it is time to dive into many of the nuts and bolts that make
programming in Unicon a unique experience.
Unicon comes with an IDE, but you can edit programs with any editor, and compile and
run them from your operating system's command line. This section describes the Unicon
command line tools along with several useful options. The Unicon compiler executable is
named unicon, and to compile the program foo.icn you would type
unicon foo
foo
To compile and link a program consisting of several modules, you can type them all on
the command line, as in
but often you will want to compile them separately (using the -c command line option)
and link the resulting object les, called ucode les; their extension is .u
unicon -c foo
unicon -c bar
unicon -c baz
unicon foo.u bar.u baz.u
• -x args execute the program immediately after linking; this option goes after the
program lenames
1.3. EXPRESSIONS AND TYPES 13
• -t turn on tracing
These options can be specied in the Ui program under the Compile menu's Compile
Options command. Other options exist; consult your Unicon and Icon manual pages and
platform-specic help les and release information for more details.
values. For example, global variables are initialized to the null value represented by the
keyword &null. Other keywords include &date, &time, and so on. Complete lists of reserved
words and keywords are given in Appendix A.
Unlike many languages where you have to state up front (declare ) all the variables you
are going to use and specify their data type, in Unicon variables do not have to be declared
at all, and any variable can hold any type of value. However, Unicon will not allow you to
mix incompatible types in an expression. Unicon is type safe, meaning that every operator
checks its argument values to make sure they are compatible, converts them if necessary,
and halts execution if they cannot be converted.
Unicon supports the usual arithmetic operators on data types integer and real. Integers
are signed whole numbers of arbitrary magnitude. The real type is a signed oating point
decimal number whose size on any platform is the largest size supported by machine instruc-
tions, typically 64-bit double precision values. In addition to addition (+), subtraction (-),
multiplication (*) and division (/), there are operators for modulo (%) and exponentiation
(ˆ). Arithmetic operators require numeric operands.
Note
Operations on integers produce integers; fractions are truncated, so 8/3 produces 2. If
either operand is a real, the other is converted to real and the result is real, so 8.0/3 is
2.66666...
As a general rule in Unicon, arguments to numeric operators and functions are auto-
matically converted to numbers if possible, and a run-time error occurs otherwise. The
built-in functions integer(x) and real(x) provide an explicit conversion mechanism that
fails if x cannot be converted to numeric value, allowing a program to check values without
resulting in a run-time error.
In addition to the operators, built-in functions support several common numeric oper-
ations. The sqrt(x) function produces the square root of x, and exp(x) raises e to the x
power. The value of pi (3.141...) is available in keyword &pi, the Golden Ratio (1.618...)
is available in &phi, and e (2.718...) is available in &e. The log(x) function produces the
natural log of x. The common trigonometric functions, such as sin() and cos() take their
angle arguments in radian units. The min(x1, x2, ...) and max(x1, x2, ...) routines re-
turn minimum and maximum values from any number of arguments. Appendix A gives a
complete list of built-in functions and operators.
Listing 1-1 shows a simple Unicon program that illustrates the use of variables in a
numeric computation. The line at the beginning is a comment for the human reader.
Comments begin with the # character and extend to the end of the line on which they
appear. The compiler ignores them.
1.5. STRINGS AND CSETS 15
# What do I compute?
procedure main()
local i, j, old_r, r
i := read()
j := read()
old_r := r := min(i, j)
while r > 0 do {
old_r := r
if i > j then
i := r := i % j
else
j := r := j % i
}
write(old_r)
end
This example illustrates assignment ; values are assigned to (or "stored in") variables
with the := operator. As you saw in the previous section, the function read() reads a line
from the input and returns its value. The modulo operator (%) is an important part of
this program: i % j is the remainder when i is divided by j.
While loops can use a reserved word do followed by an expression (often a compound
expression in curly braces). The expression following the do is executed once each time the
expression that controls the while succeeds. Inside the while loop, a conditional if-then-else
expression is used to select from two possible actions.
The names of the variables in this example are obscure, and there are no comments in
it other than the one at the top. Can you guess what this program does, without running
it? If you give up, try running it with a few pairs of positive numbers.
In addition to arithmetic operators, there are augmented assignment operators. To
increment the value in a variable by 2, these two statements are equivalent:
i +:= 2
i := i + 2
Augmented assignment works for most binary operators, not just arithmetic. The
expression i op:= expr means the same as i := i op expr.
The non-numeric atomic types in Unicon are character sequences (strings) and character
sets (csets). Icon came from the domain of string processing, and from it Unicon inherits
16 CHAPTER 1. PROGRAMS AND EXPRESSIONS
many sophisticated features for manipulating strings and performing pattern matching.
This section presents the simple and most common operations. More advanced operations
and examples using strings and csets are given in Chapter 4.
String literals are enclosed in double quotes, as in "this is a string", while cset literals
are enclosed in single quotes, as in ’aeiou’. Although strings and csets are composed of
characters, there is no character type; a string (or cset) consisting of a single character is
used instead.
Current implementations of Unicon use eight-bit characters, allowing strings and csets
to be composed from 256 unique characters. ASCII representation is used for the lower 128
characters, except on EBCDIC systems. The appearance of non-ASCII values is platform
dependent. Like integers, strings can be arbitrarily large, constrained only by the amount
of memory you have on your system.
Several operators take string arguments. The *s operator gives the length of string s.
The expression s1||s2 produces a string consisting of the characters in s1 followed by s2.
The subscript operator s[i] produces a one-letter substring of s at the ith position. Indices
are counted starting from position 1. If i is nonpositive, it is from the end of the string, for
example s[-2] is the second to the last character in the string.
Csets support set operators. c1++c2 produces a cset that is the union of c1 and
c2. The expression c1**c2 is the intersection, while c1--c2 is the dierence. In addition,
several keywords are commonly used csets. The keywords &letters, &lcase, and &ucase
denote the alphabetic characters, lower case characters a-z, and upper case characters A-Z,
respectively, while &digits is the set from 0-9, &ascii is the lower 128 characters, and &cset
is the set of all (256, on most implementations) characters.
Many built-in functions operate on strings and csets. Some of the simple string functions
are reverse(x), which produces the reverse of a string (or list) x, and trim(s,c), which
produces a substring of s that does not end with any character in cset c.
Functions and operators that require string arguments convert numeric values to strings
automatically, and halt execution with a run-time error if given a value that cannot be
converted to a string.
So far, the examples of how expressions are evaluated have included nothing you wouldn't
nd in ordinary programming languages. It is time to push past the ordinary. In most
conventional languages, each expression always computes exactly one result. If no valid
result is possible, a sentinel value such as -1, NULL, EOF (end-of-le) or INF (innity) is
returned instead. This means that the program must check the return value for this con-
dition. For example, while reading integers from the input and performing some operation
on them you might do something like this:
while (i := read()) ˜= -1 do
1.6. GOAL-DIRECTED EVALUATION 17
process(i)
This will work, of course, except when you really need to use -1 as a value! It is
somewhat cumbersome, however, even when a sentinel value is not a problem. Unicon
provides a much nicer way to write this type of code, developed originally in the Icon
language. In Unicon, expressions are goal-directed. This means that every expression when
evaluated has a goal of producing results for the surrounding expression. If an expression
succeeds in producing a result, the surrounding expression executes as intended, but if an
expression cannot produce a result, it is said to fail and the surrounding expression cannot
be performed and in turn fails.
Now take a look at that loop again. If it weren't for the termination condition, you
would not need the intermediate variable i. If you would like to say:
process(read())
your wishes are answered by Unicon; you can indeed write your program like this. The
expression read() tries to produce a value by reading the input. When it is successful,
process() is called with the value; but when read() cannot get any more values, that is,
at the end of the le, it fails. This failure propagates to the surrounding expression and
process() is not called either. Here is the clincher: control expressions like if and while
don't check for Boolean (true/false) values, they check for success! So our loop becomes
while process(read())
The do clause of a while loop is optional; in this case, the condition does everything we
need, and no do clause is necessary.
Consider the if statement that was used in the earlier arithmetic example:
Comparison operators such as > succeed or fail depending on the values of the operands.
This leads to another question: if an expression like i < 3 succeeds, what value should it
produce? No "true" value is needed, because any result other than failure is interpreted as
"true." This allows the operator to return a useful value instead! The comparison operators
produce the value of their right operand when they succeed. You can write conditions like
that appear routinely in math classes. Other programming languages only dream about
being this elegant. First, Unicon computes 3 < i. If that is true, it returns the value i, which
is now checked with 7. This expression in fact does exactly what you'd expect. It checks
to see that the value of i is between 3 and 7. (Also, notice that if the rst comparison fails,
the second one will not be evaluated.)
18 CHAPTER 1. PROGRAMS AND EXPRESSIONS
Because some expressions in Unicon can fail to produce a result, you should learn to
recognize such expressions on sight. These fallible expressions control the ow of execution
through any piece of Unicon code you see. When failure is expected it is elegant. When
it is unexpected in your program code, it can be disastrous, causing incorrect output that
you may not notice or, if you are lucky, the program may terminate with a run-time error.
Some fallible expressions fail when they cannot perform the required computation; oth-
ers are predicates whose purpose is to fail if a condition is not satised. The subscript and
sectioning operators are examples of the rst category. The expression x[i] is a subscript
operator that selects element i out of some string or structure x. It fails if the index i is
out of range. Similarly, the sectioning operator x[i:j] fails if either i or j are out of range.
The read() function is illustrative of a large number of built-in functions that can fail.
A call to read() fails at the end of a le. You can easily write procedures that behave
similarly, failing when they cannot perform the computation that is asked. Unfortunately,
for an arbitrary procedure call p(), you can't tell if it is fallible without studying its source
code or reference documentation. The safest thing is to expect any procedure call is fallible
and check whether it failed, unless you know it is not fallible or its failure doesn't matter.
Following this advice may avoid many errors and save you lots of time. In this book we
will be careful to point out fallible expressions when we introduce them.
The less than operator < is a typical predicate operator, one that either fails or produces
exactly one result. The unary predicates /x and \x test a single operand, succeeding and
producing the operand if it is null, or non-null, respectively. The following binary predicates
compare two operands. The next section presents some additional, more complex fallible
expressions.
1.8 Generators
So far we have seen that an expression can produce no result (failure) or one result (success).
In general, an expression can produce any number of results: 0, 1, or many. Expressions
that can produce more than one result are called generators. Consider the task of searching
for a substring within a string:
find("lu", "Honolulu")
In most languages, this would return one of the substring matches, usually the rst
position at which the substring is found. In Unicon, this expression is a generator, and
1.8. GENERATORS 19
can produce all the positions where the substring occurs. If the surrounding expression
only needs one value, as in the case of an if test or an assignment, only the rst value of a
generator is produced. If a generator is part of a more complex expression, then the return
values are produced in sequence until the whole expression produces a value.
Let us look at this example:
The rst value produced by find() is 2, which causes the < operation to fail. Execution
then resumes the call to find(), which produces a 5 as its next value, and the expression
succeeds. The value of the expression is the rst position of the substring greater than 3.
The most obvious generator is the alternation operator |. The expression
expr1 | expr2
produces its left-hand side followed by its right-hand side, if needed by the surrounding
expression. This can perform many computations quite compactly. For example,
x = (3 | 5)
(x | y) = (3 | 5)
checks to see if either x or y has the value 3 or 5. It is the Unicon equivalent of C's
(x == 3) || (x == 5) || (y == 3) || (y == 5)
In understanding Unicon code, it helps if you identify the generators, if there are any.
In addition to the alternation operator | and the function find(), there are a few other
generators in Icon's built in repertoire of operators and functions. We mention them briey
here, so you can be on the lookout for them when reading code examples.
The expression i to j is a generator that produces all the values between i and j. The
expression i to j by k works similarly, incrementing each result by k; i, j, and k must all be
integer or real numbers, and k must be non-zero. The expression i to j is equivalent to i
to j by 1. The unary ! operator is a generator that produces the elements of its argument.
This works on every type where it makes sense. Applied to a string, it produces all its
characters (in order). Sets, tables, lists, or records produce the members of the structure.
Generators get resumed for more results as needed in order for the surrounding expres-
sion to succeed, and this may propagate through many levels of nested enclosing expressions.
However, special expressions called bounded expressions will never resume their generator
subexpressions. For example, the conditional expressions used in if and while are never
resumed if they succeed; if they produce a result the then-branch or the loop body is exe-
cuted, and if that code fails, it does not cause generators in the conditional to be resumed.
20 CHAPTER 1. PROGRAMS AND EXPRESSIONS
Those bounded conditional expressions are re-evaluated starting from scratch if execution
comes their way again. Another popular bounded expression is the semi-colon operator.
The expression expr1 ; expr2 evaluates the two expressions in order, and bounds the rst
expression, so you don't have to worry about backtracking into it if the second expression
fails.
You have already seen two control structures in Unicon: the if and the while loop, which
test for success. Unicon has several other control structures that vary in complexity and
usefulness. Unicon is expression-based, and all control structures are expressions that
can be used in surrounding expressions if desired. The big dierence between control
structures and ordinary operators or procedures is that ordinary operators and procedures
don't execute until their arguments have been evaluated and produced a result; they don't
execute at all if an argument fails. In contrast, a control structure uses one of its arguments
to decide whether (or how many times) to evaluate its other arguments.
Since control structures are expressions, they may produce a result for a surrounding
expression. For example, the result of an if expression is the result of either its then part
or its else part, whichever one was selected. On the other hand, a loop executes until its
test fails, after which there is no meaningful result for it to produce; loops usually fail as
far as surrounding expressions are concerned.
The control structure every processes the entire sequence of values produced by a gen-
erator. The expression
evaluates expr2 for each result generated by expr1. This loop looks similar enough to a
while loop to confuse people at rst. The dierence is that a while loop re-evaluates expr1
from scratch after each iteration of expr2 , but every resumes expr1 for an additional result
where it left o the last time through the loop. Using a generator to control a while loop
makes no sense; the generator will restart each iteration, which may give you an innite
loop. Similarly, not using a generator to control an every loop also makes no sense; if
expr1 is not a generator the loop body executes at most one time.
The classic example of every is a loop that generates the number sequence from a
to expression, assigning the number to a variable that can be used in expr2 . In many
languages these are called for loops. A for loop in Unicon is written like this:
every i := 1 to 10 do write(i)
Of course, every and to are not limited to this BASIC-style for loop. Generators are more
exible; the for loop above looks clumsy when compared with the equivalent
1.9. ITERATION AND CONTROL STRUCTURES 21
executes the function f several times, passing the rst few square numbers as parameters.
A shorter equivalent that uses the power operator (ˆ) is every f((1 to 6)ˆ2). An example in
the next section shows how to generalize this to work with all the squares.
The if, while, and every expressions are Unicon's primary control structures. Several
other control structures are available that may be more useful in certain situations. The
loop
is while's evil twin, executing expr2 as long as expr1 fails; on the other hand
repeat expr
case expr of {
branch1: expr1
branch2: expr2
...
default: expr i
}
22 CHAPTER 1. PROGRAMS AND EXPRESSIONS
When a case expression executes, the expr is evaluated, and compared to each branch
value until one that matches exactly is found, as in the binary equality operator ===.
Branch expressions can be generators, in which case every result from the branch is com-
pared with expr . The default branch may be omitted in case expressions for which no
action is to be taken if no branch is satised.
When we introduced the repeat loop, you probably were wondering how to exit from
the loop, since most applications do not run forever. One answer would be to call one of the
built-in functions that terminate the entire program when it is time to get out of the loop.
The exit(), stop(), and runerr() functions all serve this valuable purpose; their dierences
are described in Appendix A.
A less drastic way to get out of any loop is to use the break expression:
break expr
This expression exits the nearest enclosing loop; expr is evaluated outside the loop
and treated as the value produced by executing the loop. This value is rarely used by
the surrounding expression, but the mechanism is very useful, since it allows you to chain
any number of breaks together, to exit several levels of loops simultaneously. The break
expression has a cousin called next that does not get out of a loop, but instead skips the
rest of the current iteration of a loop and begins the next iteration of the loop.
1.10 Procedures
Procedures are a basic building block in most languages. Here is an example of an ordinary
procedure. This one computes a simple polynomial, ax2 + bx + c.
procedure poly(x,a,b,c)
return a * xˆ2 + b * x + c
end
Parameters
Procedure parameters are passed by value except for structured data types, which are
passed by reference. This means that when you pass in a string, number, or cset value,
the procedure gets a copy of that value; any changes the procedure makes to its copy will
not be reected in the calling procedure. On the other hand, structures that contain other
values, such as lists, tables, records, and sets are not copied. The procedure being called
gets a handle to the original value, and any changes it makes to the value will be visible to
the calling procedure. Structure types are described in the next chapter.
When you call a procedure with too many parameters, the extras are discarded. This
feature is valuable for prototyping but can be dangerous if you aren't careful! Similarly,
1.10. PROCEDURES 23
if you call a procedure with too few parameters, the remaining variables are assigned the
null value, &null. The null value is also passed as a parameter if you omit a parameter.
Now it is time to describe the unary operators \ and /. These operators test the expres-
sion against the null value. The expression /x succeeds and returns x if the value is the null
value. This can be used to assign default values to procedure arguments: Any arguments
that have a null value can be tested for and assigned a value. Here's an example:
procedure succ(x, i)
/i := 1
return x + i
end
If this procedure is called as succ(10), the missing second parameter, i, is assigned the
null value. The forward slash operator then tests i against the null value, which succeeds;
therefore the value 1 is assigned to i.
The backward slash checks if its argument is non-null. For example, this will write the
value of x if it has a non-null value:
If x does in fact have the null value, then \x will fail, which will mean that the write()
procedure will not be called. If it helps you to not get these two mixed up, a slash pushes
its operand "up" for non-null, and "down" for a null value.
The procedure succ() shows one way to specify a default value for a parameter. The
built-in functions use such default values systematically to reduce the number of parameters
needed; consistent defaults for entire families of functions make them easy to remember.
Another key aspect of the built-in operations is implicit type conversion. Arguments to
built-in functions and operators are converted as needed.
Defaulting and type conversion are so common and so valuable in Unicon that they
have their own syntax. Parameter names may optionally be followed by a coercion function
(usually the name of a built in type) and/or a default value, separated by colons. These
constructs are especially useful in enforcing the public interfaces of library routines that
will be used by many people.
procedure succ(x, i)
x := integer(x) | runerr(101, x)
/i := 1 | i := integer(i) | runerr(101, i)
end
24 CHAPTER 1. PROGRAMS AND EXPRESSIONS
global MyGlobal
procedure foo()
local x, y
static z
...
end
Each declared local variable name may be followed by a := and an initializer expression
that species the variable's initial value. Without an initializer, variables start with the
value &null. Although you do not have to declare local variables, large programs, library
code or multi-person projects should declare all local variables. If you don't, and some
other part of the code introduces a global variable by the same name as your undeclared
local, your variable will be interpreted as a reference to the global. To help avoid this
problem, the -u command line option to the compiler causes undeclared local variables to
produce a compilation error message.
A procedure body can begin with an initial clause, which executes only the rst time
the procedure is called. The initial clause is mainly used to initialize static variables in ways
1.10. PROCEDURES 25
that aren't handled by initializers. For example, the following procedure returns the next
number in the Fibonacci sequence each time it is called, using static variables to remember
previous results in the sequence between calls.
procedure fib()
static x,y
local z
initial {
x := 0
y := 1
return 1
}
z := x + y
x := y
y := z
return z
end
procedure squares()
odds := 1
sum := 0
repeat {
suspend sum
sum +:= odds
odds +:= 2
}
end
every munge(squares())
26 CHAPTER 1. PROGRAMS AND EXPRESSIONS
Warning
This is an innite loop! (Do you know why? Whether munge() succeeds or fails, every
will always resume squares() for another result to try; squares() generates an innite
result sequence.)
The fail expression makes the procedure fail. Control goes to the calling procedure, re-
turning no value, and the procedure call ceases to exist; it cannot be resumed. A procedure
also fails implicitly when control ows o the end of the procedure's body.
Here is a procedure that produces all the non-blank characters of a string, but bails out
if the character # is reached:
procedure nonblank(s)
every c := !s do {
if c == "#" then fail
if c ˜== " " then suspend c
}
end
Recursion
A recursive procedure is one that calls itself, directly or indirectly. There are many cases
where it is the most natural way of solving the problem. Consider the famous "Towers of
Hanoi" problem. Legend has it that when the universe was created, a group of monks in
a temple in some remote place were presented with a problem. There are three diamond
needles, and on one of them is a stack of 64 golden disks all of dierent sizes, placed in
order with the largest one at the bottom and the smallest on top. All the disks are to be
moved to a dierent needle under the conditions that only one disk may be moved at a
time, and a larger disk can never be placed on a smaller disk. When the monks nish this
task, the universe will come to an end.
How can you move the n smallest disks? If n is 1, just move it. Since it's the smallest,
this will not violate the condition. If n is greater than 1, here's what we can do: rst,
move the n-1 upper disks to the intermediate needle, then transfer the n th disk, then move
the n-1 upper disks to the destination needle. This whole procedure does not violate the
requirements either (satisfy yourself that such is the case).
Now write the procedure hanoi(n) that computes this algorithm. The rst part is
simple: if you have one disk, just move it.
Otherwise, perform a recursive call with n-1. First, to nd the spare needle we have:
other := 6 - needle1 - needle2
1.10. PROCEDURES 27
Now move the n-1 disks from needle1 to other, move the biggest disk, and then move
the n-1 again. The needles are passed as additional parameters into the recursive calls.
They are always two distinct values out of the set {1, 2, 3}.
That's it! You're done. Listing 1-2 contains the complete program for you to try:
procedure main()
write("How many disks are on the towers of Hanoi?")
hanoi(read())
end
procedure hanoi(n:integer, needle1:1, needle2:2)
local other
if n = 1 then write("Move disk from ", needle1, " to ", needle2)
else {
other := 6 - needle1 - needle2
hanoi(n-1, needle1, other)
write("Move disk from ", needle1, " to ", needle2)
hanoi(n-1, other, needle2)
}
end
Turn on tracing see how this program works. To enable tracing, compile your program
with a -t option, or assign the keyword &trace a non-zero number giving the depth of calls
to trace. Setting &trace to -1 will turn on tracing to an innite depth.
To move n disks, 2 n - 1 individual disk movements will be required. If the monks move
one disk a second, it will take 2 64 - 1 seconds, or about 60 trillion years. Wikipedia has
listed the age of the universe at around 13.75 billion years. It seems unlikely that we need
worry about the monks nishing their task!
Summary
• Tables associate their elements with key values for rapid lookup.
There are several structure types that describe dierent basic relationships between values.
The philosophy of structures in Unicon is to provide built-in operators and functions for
common organization and access patterns - the exible "super glue" that is needed by nearly
all applications. Their functionality is similar to the C++ Standard Template Library or
generic classes in other languages, but Unicon's structure types are much simpler to learn
and use, and are well supported by the expression evaluation mechanism described in the
previous chapter.
All structure types in Icon share many aspects in common, such as the fact that struc-
tures are mutable. The values inside them may change. In that respect, structures are
similar to a collection of variables that are bundled together. In many cases, Unicon's
structure types are almost interchangeable! Operators like subscripts and built-in functions
such as insert() are dened consistently for many types. Code that relies on such operators
is polymorphic : it may be used with multiple structure types in an interchangeable way.
For both the structures described in this chapter and the strings described in the next
chapter, be aware that Unicon performs automatic storage management, also known as
29
30 CHAPTER 2. STRUCTURES
garbage collection. If you have used a language like C or C++, you know that one of the
biggest headaches in writing programs in these languages is tracking down bugs caused
by memory allocation, especially dynamic heap memory allocation. Unicon transparently
takes care of those issues for you.
Another big source of bugs in languages like C and C++ are pointers, values that
contain raw memory addresses. Used properly, pointers are powerful and ecient. The
problem is that they are easy to use incorrectly by accident; this is true for students and
practicing software engineers alike. It is easy in C to point at something that is o-limits,
or to trash some data through a pointer of the wrong type.
Unicon has no pointer types, but all structure values implicitly use pointer semantics.
A reference is a pointer for which type information is maintained and safety is strictly
enforced. All structure values are references to data that is allocated elsewhere, in a
memory region known as the heap. You can think of a reference as a safe pointer: the only
operations it supports are copying the pointer, or dereferencing it using an operation that
is dened for its type.
Assigning a structure to a variable, or passing it as a parameter, gives that variable
or parameter a copy of the reference to the structure but does not make a copy of the
structure. If you want a copy of a structure, you call the function copy(x), which makes a
shallow copy of a single table, list, record, or set. If that structure contains references to
other structures as its elements, those substructures are not copied by copy(). To copy a
deep structure (lists of lists, tables of records, etc.) you can use the procedure deepcopy()
that is given as an example later in this chapter.
2.1 Tables
Tables are unordered collections of values that are accessed using associated keys. They
are Unicon's most versatile type. All of the other structure types can be viewed as special
cases of tables, optimized for performance on common operations. Most operations that
are dened for tables are dened for other structure types as well.
Subscripts are used for the primary operations of associating keys with values that are
inserted into the table, and then using keys to look up objects in the table. The table()
function creates a new empty table. For example, the lines
T := table()
T["hello"] := "goodbye"
create a new table, and associate the key "hello" with the value "goodbye". The table()
function takes one optional argument: the default value to return when lookup fails. The
default value of the default value is &null, so after the above example, write(T["goodbye"])
would write an empty line, since write() treats a null argument the same as an empty string,
and write() always writes a newline. Assigning a value to a key that is not in the table
2.2. LISTS 31
inserts a value into the table. This occurs in the second line of the example above, so
write(T["hello"]) writes out "goodbye".
Subscripts are the primary operation on tables, but there are several other useful op-
erations. The insert(T, k1 , x1 , k2 , x2 , ...) function adds new key-value pairs to T. The
delete(T, k1 , k2 , ...) function deletes values from T that correspond to the supplied keys.
Icon's unary * operator produces the size of its argument; for a table, *T is the number of
key-value pairs in the table. Unary ! generates elements from a collection; for a table, !T
generates the values stored in the table. Unary ? is the random operator; for a table, ?T
produces a random value stored in the table. Both unary ! and ? produce values stored in
a table, not the keys used to lookup values.
Function member(T, k) succeeds if k is a key in T and fails otherwise. Function key(T)
generates the keys that have associated values. The following example prints word counts
for the input (assuming getword() generates words of interest):
wordcount := table(0)
every word := getword() do wordcount[word] +:= 1
every word := key(wordcount) do write(word, " ", wordcount[word])
The default value for the table is 0. When a new word is inserted, the default value
gets incremented and the new value (that is, 1) is stored with the new word. Tables grow
automatically as new elements are inserted.
Tables are closely related to the set data type (discussed later in this chapter). The
keys of a table are a set; the associated values accessed via the subscript operator are sort
of a bonus data payload. In any case, tables behave in certain set-like ways; when their
elements are generated by the ! operator, they come out in a pseudo random order. Like
sets, and csets in the previous chapter, the operators T1++T2, T1**T2, and T1--T2 are
the union, intersection, and dierence of the keys of tables T1 and T2. These operators
construct new tables and do not modify their operands. In union and intersection, when
duplicate table keys occur in the two operands, the associated values from the left operand
are what goes in the new table that holds the result.
2.2 Lists
Lists are dynamically sized ordered collections of values. They are accessed by subscripts,
with indexes starting at 1. You can also insert or remove elements from the beginning,
middle, or end of the list. Lists take the place of arrays, stacks, queues, and deques found
in other languages and data structures textbooks.
There are three ways to explicitly construct a list. In the most generic form, a list is
created by calling the function list(), which takes optional parameters for the lists's initial
size and the initial value given to all elements of the list. The default size is 0 and the
default initial value is &null.
32 CHAPTER 2. STRUCTURES
The second form of list constructor is when you create a list by enclosing a comma-
separated sequence of 0 or more values in square nbrackets. For example
creates a list with three elements, a string, a real number, and another string.
A third form of list constructor, called comprehension , looks like the previous form,
except the square brackets contain adjacent colon characters and have an expression inside.
L := [: expr :]
In a comprehension the constructed list's initial values are obtained by fully evaluating
an expression and placing all of its results into the list, in order. The expression fails if
expr fails; if you wanted that to be an empty list you may need to append |[].
Lists are dynamic. Lists grow or shrink as a result of stack and queue operations.
The push() and pop() functions add and remove elements from the front of a list, while
put() and pull() add and remove elements at the end of the list. In addition, insert(L, i, x)
inserts x at position i, and delete(L, i) deletes the element at position i. The expression []
is another way to create an empty list; it is equivalent to calling list() with no arguments.
The previous list could have been constructed one element at a time with the following
code. put() accepts a variable number of arguments .
L := [ ]
put(L, "linux")
put(L, 2.0)
put(L, "unix")
Elements of the list can be obtained either through list manipulation functions or by
subscripting. Given the list L above, in the following code the rst line writes "unix" while
the second line moves the rst element to the end of the list.
write(L[3])
put(L, pop(L))
There is no restriction on the kinds of values that may be stored in a list. For example,
the elements of a list can themselves be lists. You can create lists like
and index them with multiple subscripts. L[2][3] is equivalent to L[2,3] and yields the value
6 in this example.
Lists also support several common operators. The operator *L produces the size of list
L. The operators !L and ?L generate the elements of L in sequence, and produce a single
random element of L, respectively. The following procedure uses the unary ! operator to
sum the values in list L, which must be numbers.
2.3. RECORDS 33
procedure sum(L)
total := 0
every total +:= !L
return total
end
Comparing the two, lists are like tables with boring keys: the positive integers starting
from 1. Function member(L, k) succeeds if 0 < integer(k) <= *L, while key(L) is equivalent
to the expression 1 to *L. List indexes are contiguous, unlike table keys, and so lists can
support a slice operator to produce a sublist, given a pair of indexes to mark the bounds.
The L[i:j] expression produces a new list that contains a copy of the elements in L between
positions i and j. The L[i+:j] expression is equivalent to L[i:i+j]. List concatenation is another
valuable operator. The L1 ||| L2 expression produces a new list whose elements are a copy
of the elements in L1 followed by a copy of the elements in L2.
2.3 Records
A record is a xed-sized, ordered collection of values whose elements are accessed using
user-dened named elds. A record is declared as a global name that introduces a new
type with a corresponding constructor procedure, as in the following example. The eld
names are a comma-separated list of identiers enclosed in parentheses.
Record instances are created using a constructor procedure with the name of the record
type. The elds of an instance are accessed by name using dot notation or string subscript,
or by integer index subscript. You can use records as records, or as special tables or lists
with a constant size and xed set of keys.
member(R,s) tests whether s is a eld in R; key(R) generates R's eld names. Functions
like insert(), or push() are not supported on records, since they change the size of the
structure that they modify. Here is a demonstration of record operations.
a := complex(0, 0)
b := complex(1, -1)
if a.re = b.re then write("not likely")
if a["re"] = a[2] then write("a.re and a.im are equal")
Unicon provides a mechanism for constructing new record types on the y, described in
Chapter 6, as well as the ability to declare classes, which are new data types that form the
building blocks for object-oriented programs, described starting in Chapter 9. Records are
closely related to classes and objects: they can be considered to be an optimized special
case of objects that have no methods.
34 CHAPTER 2. STRUCTURES
2.4 Sets
A set is an unordered collection of values with the uniqueness property: an element can
only be present in a set once. The function set(x...) creates a set containing its arguments.
For the sake of backward compatibility with Icon, list arguments to set() are not inserted;
instead, the list elements are inserted. As with other structures, the elements may be of
any type, and may be mixed. For example, the assignment
S := set("rock lobster", ’B’, 52)
creates a set with three members: a string, a cset, and an integer. The equivalent set is
produced by set(["rock lobster","B", 52]). To place a list in a set set constructor, wrap
it in another list, as in set([L]), or insert the list into the set after it is created. Because
the set constructor function initializes directly from a list argument, set comprehension
follows trivially from list comprehension. For example, set([: 2 to 20 by 2 :]) creates a set
containing the even integers from two to twenty.
The functions member(), insert(), and delete() do what their names suggest. As for
csets in the previous chapter, S1++S2, S1**S2, and S1--S2 are the union, intersection,
and dierence of sets S1 and S2. Set operators construct new sets and do not modify
their operands. Because a set can contain any value, it can contain a reference to itself.
This is one of several dierences between Unicon sets, which are mutable structures, and
mathematical sets. Another dierence is that Unicon sets have a nite number of elements,
while mathematical sets can be innite in size.
As a short example, consider the following program, called uniq, that lters duplicate
lines in its standard input as it writes to its standard output. Unlike the UNIX utility of
this name, our version does not require the duplicate lines to be adjacent.
procedure main()
S := set()
while line := read() do
if not member(S, line) then {
insert(S, line)
write(line)
}
end
Sets are closely related to the table data type. They are very similar to an optimized
special case of tables that map all keys to the value &null. Unlike tables, sets have no
default value and do not support the subscript operator.
Structures can hold other structures, allowing you to organize information in whatever way
best ts your application. Building complex structures such as a table of lists, or a list
2.5. USING STRUCTURES 35
of records that contain sets, requires no special trickery or new syntax. Examples of how
such structures are accessed and traversed will get you started. Recursion is often involved
in operations on complex structures, so it plays a prominent role in the examples. The
concept of recursion was discussed in Chapter 1.
A Deep Copy
The built-in function copy(x) makes a one-level copy of structure values. For a multi-level
structure, you need to call copy() for each substructure if the new structure must not point
into the old structure. This is a natural task for a recursive function.
procedure deepcopy(x)
local y
case type(x) of {
"table"|"list"|"record": {
y := copy(x)
every k := key(x) do y[k] := deepcopy(x[k])
}
"set": {
y := set()
every insert(y, deepcopy(!x))
}
default: return x
}
return y
end
This version of deepcopy() works for arbitrarily deep tree structures, but the program
execution will crash if deepcopy() is called on a structure containing cycles. It also does
not copy directed acyclic graphs correctly. In both cases the problem is one of not noticing
when you have already copied a structure, and copying it again. The Icon Program Library
has a deep copy procedure that handles this problem, and we present the general technique
that is used to solve it in the next section.
The structure created by these expressions is depicted in Figure 2-1. The list of links
at each node allows trees with an arbitrary number of children at the cost of extra memory
and indirection in the tree traversals. The same representation works for arbitrary graphs.
To nd every node related to variable bambam, follow all the links reachable starting
from bambam. Here is a procedure that performs this task.
procedure print_relatives(n)
local i
static relatives
initial relatives := set()
every i := n | !n.links do {
if not member(relatives, i.name) then {
write(i.name)
insert(relatives, i.name)
print_relatives(i)
}
}
end
Bam-Bam
Barney
Betty
Static variables and the initial clause are explained in Chapter 1. Can you guess what
purpose static variable relatives serves? For a proper tree structure, it is not needed at
all, but for more general data structures such as directed graphs this static variable is very
important! One defect of this procedure is that there is no way to reset the static variable
and call print_relatives() starting from scratch. How would you remove this defect?
2.5. USING STRUCTURES 37
link options
global solution, n
procedure main(args)
local i, opts
opts := options(args,"n+")
n := \opts["n"] | 6
if n <= 0 then stop("-n needs a positive numeric parameter")
The value n gives the size for the solution array and also appears in a banner:
Now comes the meat of the program, the procedure q(c). It tries to place a queen
in column c and then calls itself recursively to place queens in the column to the right.
The q(c) procedure uses three arrays: rows, up, and down. They are declared to be
static, meaning that their values will be preserved between executions of the procedure,
and all instances of the procedure will share the same lists. Since each row must have
exactly one queen, the rows array helps to make sure any queen that is placed is not on a
row that already has a queen. The other two arrays handle the diagonals: up is an array
(of size 2n-1 ) of the upward slanting diagonals, and down is an array for the downward
slanting diagonals. Two queens in positions (r_1, c_1) and (r_2, c_2) are on the same
"up" diagonal if n+r_1-c_1 = n+r_2-c_2 and they are on the same "down" diagonal if
r_1+c_1-1 = r_2+c_2-1. Figure 2-2 shows some of the up and down diagonals.
#
# q(c) - place a queen in column c.
#
procedure q(c)
local r
static up, down, rows
initial {
up := list(2*n-1,0)
down := list(2*n-1,0)
rows := list(n,0)
}
2.5. USING STRUCTURES 39
The next expression in q() is an every loop that tries all possible values for the queen in
row c. The variable r steps through rows 1 to 8. For any row at which the program places
a queen, it must ensure that
1. rows[r] is zero, that is, no other column has a queen in row r,
2. up[n+r-c] is 0, that is, there is not already a queen in the "up" diagonal, and
3. down[r+c-1] is 0, that is, there is not already a queen in the down diagonal.
If these conditions are met, then it is OK to place a queen by assigning a 1 to all those
arrays in the appropriate position:
For assignment, instead of := this expression uses the reversible assignment operator <-.
This assigns a value just like in conventional assignment, but it remembers the old value;
if it is ever resumed, it restores the old value and fails. This causes the appropriate entries
in the row, up, and down arrays will be reinitialized between iterations.
When the every loop found a good placement for this column, either the program is
done (if this was the last column) or else it is time to try to place a queen in the next row:
That's it! The rest of the program just prints out any solutions that were found.
Printing the chess board is similar to other reports you might write that need to create
horizontal lines for tables. The repl() function is handy for such situations. The repl(s, i)
function returns i "replicas" of string s concatenated together. The show() function uses
it to create the chessboard.
#
# show the solution on a chess board.
#
procedure show()
static count, line, border
initial {
count := 0
line := repl("| ",n) || "|"
border := repl("----",n) || "-"
}
write("solution: ", count+:=1, "\n ", border)
every line[4*(!solution - 1) + 3] <- "Q" do {
40 CHAPTER 2. STRUCTURES
2.6 Summary
Unicon's structures are better than sliced bread. To be fair, this is because Icon's inventors
really got things right. These structures are the foundations of complex algorithms and the
glue that builds sophisticated data models. They are every computer scientists' buzzword-
compliant best friends: polymorphic, heterogeneous, implicitly referenced, cycle-capable,
dynamically represented, and automatically reclaimed. They provide a direct implemen-
tation of the common information associations used in object-oriented design. But most
important of all, they are extremely simple to learn and use.
Exploring the Variety of Random
Documents with Different Content
Families S p h æ r o c a p s i d a , D o r a t a s p i d a et
P h r a c to p e lt i d a .
PLATE 133.
Sphærocapsida, Dorataspida et
Phractopeltida.
Diam. Page.
Fig. 1.
Phractopelta
dorataspis, n.
sp., × 300 852
Fig. 2. Dorypelta
tessaraspis, n.
sp., × 300 858
Fig. 3.
Stauropelta
cruciata, n. sp., × 400 859
Fig. 4.
Pantopelta
icosaspis, n. sp., × 400 855
Meridional
section through
the double
shell.
Fig. 5. Octopelta
scutella, n. sp., × 400 856
Proximal part of
two meeting
spines,
isolated.
Fig. 6.
Orophaspis
furcata, n. sp., × 400 818
Fig. 7. Porocapsa
murrayana, n.
sp., × 300 800
The central
capsule is filled
up by spherical
vacuoles and
enclosed by
the porous
shell; in the
centre radii of
small granules
(nuclei ?)
occur.
Fig. 9.
Astrocapsa
coronata, n. sp., × 400 799
Middle part of
one spine with
the four
aspinal holes.
Fig. 9a.
Transverse
section of a
radial spine,
with the four
surrounding
aspinal holes
and the
neighbouring
part of the
shell, × 400
Fig. 10.
Astrocapsa
stellata, n. sp., × 400 799
Part of one spine,
with the
aspinal holes
and their four
triangular
teeth.
Fig. 11.
Cenocapsa
nirvana, n. sp., × 200 802
The entire shell,
with its
pavement of
small plates
and the twenty
cruciform
perspinal
holes.
Fig. 11a. A group
of small ovate
plates which
compose the
shell; in each
plate a dimple
with a porule, × 400
Fig. 11b. A
cruciform
perspinal hole,
seen from the
face, × 400
Fig. 11c. A
cruciform
perspinal hole,
with its four
teeth, seen in
profile, × 400
PLATE 134.
Legion A C A N T H A R I A .
Order SPHÆROPHRACTA.
Family D o r a t a s p i d a .
PLATE 134.
Dorataspida.
Diam. Page.
Fig. 1.
Dodecaspis
tricincta, n. sp., × 400 834
The enclosed
central capsule
contains
numerous
spherical
nuclei.
Fig. 2.
Lychnaspis
minima, n. sp., × 400 841
Six-sided basal
pyramid of an
equatorial
spine, with the
leaf-cross, seen
from the
centre.
Fig. 3. Zonaspis
cingulata, n. sp., × 400 834
Equatorial section
through the
central
capsule. n,
nuclei; g,
yellow bodies
(intracapsular
xanthellæ).
Fig. 4. Zonaspis
cingulata, n. sp., × 800 834
Central pyramidal
base of an
equatorial
spine, with the
leaf-cross.
Fig. 5. Stauraspis
cruciata, n. sp., × 400 831
Central union of
the radial
spines, three
polar spines
being taken
off.
Fig. 6.
Lychnaspis
longissima, n.
sp., × 400 841
Fig. 7.
Lychnaspis
minima, n. sp., × 400 841
Five-sided basal
pyramid of a
tropical spine,
with the leaf-
cross, seen
from the
centre.
Fig. 8.
Lychnaspis
minima, n. sp., × 400 841
Six-sided basal
pyramid of a
polar spine,
with the leaf-
cross, seen
from the
centre.
Fig. 9. Icosaspis
elegans, n. sp., × 400 844
An isolated polar
plate.
Fig. 13.
Coscinaspis
isopora, n. sp., × 400 828
An isolated
equatorial plate
(with two
aspinal and six
coronal pores).
Fig. 14.
Coscinaspis
isopora, n. sp., × 400 828
Two isolated
tropical plates
(b, northern; d,
southern),
each with two
aspinal and
five coronal
pores.
Fig. 15.
Diporaspis
nephropora, n.
sp., × 400 816
PLATE 135.
Legion ACANTHARIA.
Order SPHÆROPHRACTA.
Families S p h æ r o c a p s i d a et D o r a t a s p i d a .
PLATE 135.
Sphærocapsida et Dorataspida.
Diam. Page.
Fig. 1. Hylaspis
serrulata, n. sp., × 300 846
Fig. 2.
Lychnaspis
undulata, n. sp., × 400 841
Fig. 3.
Lychnaspis
giltschii, n. sp., × 400 839
The spherical
central capsule
is enclosed in
the shell.
Fig. 4.
Lychnaspis
rottenburgii, n.
sp., × 400 841
Fig. 5. Zonaspis
æquatorialis, n.
sp., × 300 834
Fig. 6.
Sphærocapsa
cruciata, n. sp., × 150 798
The entire shell,
with its twenty
cruciate
perspinal
holes.
Fig. 7.
Sphærocapsa
cruciata, n. sp., × 800 798
Insertion of one
spine in the
cruciate
perspinal hole
of the shell.
Fig. 8.
Sphærocapsa
quadrata, n. sp., × 800 798
A group of pores
and dimples in
the shell
surface.
Fig. 9.
Sphærocapsa
dentata, n. sp., × 800 798
Insertion of one
spine in the
cruciate
perspinal hole
of the shell.
Fig. 10.
Sphærocapsa
pavimentata, n.
sp., × 800 798
Insertion of one
spine in the
perspinal hole
of the shell,
which is
composed of
four cruciate
aspinal holes
and
surrounded by
a group of
dimples and
pores.
PLATE 136.
Legion ACANTHARIA.
Families D o r a t a s p i d a et B e l o n a s p i d a .
PLATE 136.
Dorataspida et Belonaspida.
Diam. Page.
Fig. 1.
Tessaraspis
arachnoides, n.
sp., × 300 836
Fig. 2. Icosaspis
tabulata, n. sp., × 200 843
Fig. 3. Icosaspis
icosastaura, n.
sp., × 400 846
Fig. 4. Icosaspis
elegans, n. sp., × 300 844
Fig. 5.
Tessaraspis
concreta, n. sp., × 400 838
Fig. 6.
Phatnaspis
cristata, n. sp., × 400 869
Fig. 7.
Phatnaspis
haliommidium,
n. sp., × 200 871
Central capsule
within the
shell—outline.
Fig. 8.
Coscinaspis
polypora, n. sp., × 300 827
A single lattice-
plate of the
shell.
Fig. 9.
Phatnaspis
lacunaria, n. sp., × 400 869
PLATE 137.
Legion ACANTHARIA.
Order SPHÆROPHRACTA.
Family D o r a t a s p i d a .
PLATE 137.
Dorataspida.
Diam. Page.
Fig. 1. Phractaspis
complanata, n. sp., × 400 809
Fig. 2. Phractaspis
prototypus, n. sp., × 400 809
Fig. 3. Phractaspis
constricta, n. sp., × 400 810
Fig. 4. Pleuraspis
horrida, n. sp., × 400 811
Fig. 5. Stauruspis
stauracantha, n. sp., × 300 832
Fig. 6. Stauruspis
stauracantha, n. sp., × 600 832
A single spine.
Fig. 7. Echinaspis
echinoides, n. sp., × 300 833
Fig. 8. Echinaspis
echinoides, n. sp., × 800 833
A single spine.
Fig. 9. Coscinaspis
parmipora, n. sp., × 400 827
PLATE 138.
Legion ACANTHARIA.
Order SPHÆROPHRACTA.
Family D o r a t a s p i d a .
PLATE 138.
Dorataspida.
Diam. Page.
Fig. 2. Dorataspis
fusigera, n. sp., × 400 813
Fig. 3. Dorataspis
micropora, n. sp., × 300 815
Fig. 7. Hystrichaspis
fruticata, n. sp., × 300 825
Fig. 8. Hystrichaspis
pectinata, n. sp., × 300 822
Fig. 9. Hystrichaspis
furcata, n. sp., × 400 822
PLATE 139.
Legion ACANTHARIA.
Order PRUNOPHRACTA.
Families B e l o n a s p i d a et H e x a l s p i d a .
PLATE 139.
Belonaspida et Hexalspida.
Diam. Page.
Fig. 2. Hexalaspis
heliodiscus, n. sp., × 300 875
Fig. 3. Hexaconus
ciliatus, n. sp., × 300 876
Fig. 4. Hexaconus
serratus, n. sp., × 300 877
c, Central base of an
equatorial spine; d,
central base of a tropical
spine.
Fig. 5. Hexaconus
coronatus, n. sp., × 300 877
Fig. 6. Hexaconus
velatus, n. sp., × 300 877
Marginal view of the shell.
Fig. 7. Hexaconus
vaginatus, n. sp., × 300 877
Fig. 8. Thoracaspis
bipennis, n. sp., × 300 862
PLATE 140.
Legion ACANTHARIA.
Order PRUNOPHRACTA.
Families B e l o n a s p i d a , H e x a l a s p i d a et D i p l o c o n i d a .
PLATE 140.
Fig. 1. Diploconus
amalla, n. sp., × 300 885
Fig. 2. Diploconus
hexaphyllus, n. sp., × 300 886
Fig. 3. Diploconus
cyathiscus, n. sp., × 300 885
Fig. 4. Diploconus
cotyliscus, n. sp., × 400 886
Polar view.
Fig. 5. Diplocolpus
serratus, n. sp., × 300 888
Fig. 6. Diplocolpus
cristatus, n. sp., × 400 887
Fig. 7. Diplocolpus
costatus, n. sp., × 400 887
Fig. 8. Diplocolpus
sulcatus, n. sp., × 300 888
Fig. 9. Diplocolpus
dentatus, n. sp., × 300 888
Meridional section through
the centre of the shell.
Updated editions will replace the previous one—the old editions will
be renamed.
1.D. The copyright laws of the place where you are located also
govern what you can do with this work. Copyright laws in most
countries are in a constant state of change. If you are outside the
United States, check the laws of your country in addition to the
terms of this agreement before downloading, copying, displaying,
performing, distributing or creating derivative works based on this
work or any other Project Gutenberg™ work. The Foundation makes
no representations concerning the copyright status of any work in
any country other than the United States.
1.E.6. You may convert to and distribute this work in any binary,
compressed, marked up, nonproprietary or proprietary form,
including any word processing or hypertext form. However, if you
provide access to or distribute copies of a Project Gutenberg™ work
in a format other than “Plain Vanilla ASCII” or other format used in
the official version posted on the official Project Gutenberg™ website
(www.gutenberg.org), you must, at no additional cost, fee or
expense to the user, provide a copy, a means of exporting a copy, or
a means of obtaining a copy upon request, of the work in its original
“Plain Vanilla ASCII” or other form. Any alternate format must
include the full Project Gutenberg™ License as specified in
paragraph 1.E.1.
• You pay a royalty fee of 20% of the gross profits you derive
from the use of Project Gutenberg™ works calculated using the
method you already use to calculate your applicable taxes. The
fee is owed to the owner of the Project Gutenberg™ trademark,
but he has agreed to donate royalties under this paragraph to
the Project Gutenberg Literary Archive Foundation. Royalty
payments must be paid within 60 days following each date on
which you prepare (or are legally required to prepare) your
periodic tax returns. Royalty payments should be clearly marked
as such and sent to the Project Gutenberg Literary Archive
Foundation at the address specified in Section 4, “Information
about donations to the Project Gutenberg Literary Archive
Foundation.”
• You comply with all other terms of this agreement for free
distribution of Project Gutenberg™ works.
1.F.
1.F.4. Except for the limited right of replacement or refund set forth
in paragraph 1.F.3, this work is provided to you ‘AS-IS’, WITH NO
OTHER WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO WARRANTIES OF
MERCHANTABILITY OR FITNESS FOR ANY PURPOSE.
Please check the Project Gutenberg web pages for current donation
methods and addresses. Donations are accepted in a number of
other ways including checks, online payments and credit card
donations. To donate, please visit: www.gutenberg.org/donate.
Most people start at our website which has the main PG search
facility: www.gutenberg.org.
ebookbell.com