home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SGI Freeware 2001 May
/
SGI Freeware 2001 May - Disc 3.iso
/
dist
/
fw_elisp-intro.idb
/
usr
/
freeware
/
info
/
emacs-lisp-intro.info-12.z
/
emacs-lisp-intro.info-12
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
Macintosh to JP
NeXTSTEP
RISC OS/Acorn
Shift JIS
UTF-8
Wrap
GNU Info File
|
1998-10-28
|
45.0 KB
|
1,148 lines
This is Info file emacs-lisp-intro.info, produced by Makeinfo version
1.67 from the input file emacs-lisp-intro.texi.
This is an introduction to `Programming in Emacs Lisp', for people
who are not programmers.
Edition 1.05, 21 October 1997
Copyright (C) 1990, '91, '92, '93, '94, '95, '97 Free Software
Foundation, Inc.
Permission is granted to make and distribute verbatim copies of this
manual provided the copyright notice and this permission notice are
preserved on all copies.
Permission is granted to copy and distribute modified versions of
this manual under the conditions for verbatim copying, provided also
that the sections entitled "Copying" and "GNU General Public License"
are included exactly as in the original, and provided that the entire
resulting derived work is distributed under the terms of a permission
notice identical to this one.
Permission is granted to copy and distribute translations of this
manual into another language, under the above conditions for modified
versions, except that this permission notice may be stated in a
translation approved by the Free Software Foundation.
File: emacs-lisp-intro.info, Node: rotate-yank-pointer, Next: yank, Prev: Kill Ring, Up: Kill Ring
The `rotate-yank-pointer' Function
==================================
The `rotate-yank-pointer' function changes the element in the kill
ring to which `kill-ring-yank-pointer' points. For example, it can
change `kill-ring-yank-pointer' from pointing to the second element to
point to the third element.
Here is the code for `rotate-yank-pointer':
(defun rotate-yank-pointer (arg)
"Rotate the yanking point in the kill ring."
(interactive "p")
(let ((length (length kill-ring)))
(if (zerop length)
;; then-part
(error "Kill ring is empty")
;; else-part
(setq kill-ring-yank-pointer
(nthcdr (% (+ arg
(- length
(length
kill-ring-yank-pointer)))
length)
kill-ring)))))
The function looks complex, but as usual, it can be understood by
taking it apart piece by piece. First look at it in skeletal form:
(defun rotate-yank-pointer (arg)
"Rotate the yanking point in the kill ring."
(interactive "p")
(let VARLIST
BODY...)
This function takes one argument, called `arg'. It has a brief
documentation string; and it is interactive with a small `p', which
means that the argument must be a processed prefix passed to the
function as a number.
The body of the function definition is a `let' expression, which
itself has a body as well as a VARLIST.
The `let' expression declares a variable that will be only usable
within the bounds of this function. This variable is called `length'
and is bound to a value that is equal to the number of items in the
kill ring. This is done by using the function called `length'. (Note
that this function has the same name as the variable called `length';
but one use of the word is to name the function and the other is to
name the variable. The two are quite distinct. Similarly, an English
speaker will distinguish between the meanings of the word `ship' when
he says: "I must ship this package immediately." and "I must get aboard
the ship immediately.")
The function `length' tells the number of items there are in a list,
so `(length kill-ring)' returns the number of items there are in the
kill ring.
* Menu:
* rotate-yk-ptr body:: The Body of `rotate-yank-pointer'.
File: emacs-lisp-intro.info, Node: rotate-yk-ptr body, Prev: rotate-yank-pointer, Up: rotate-yank-pointer
The Body of `rotate-yank-pointer'
---------------------------------
The body of `rotate-yank-pointer' is a `let' expression and the body
of the `let' expression is an `if' expression.
The purpose of the `if' expression is to find out whether there is
anything in the kill ring. If the kill ring is empty, the `error'
function stops evaluation of the function and prints a message in the
echo area. On the other hand, if the kill ring has something in it, the
work of the function is done.
Here is the if-part and then-part of the `if' expression:
(if (zerop length) ; if-part
(error "Kill ring is empty") ; then-part
...
If there is not anything in the kill ring, its length must be zero and
an error message sent to the user: `Kill ring is empty'. The `if'
expression uses the function `zerop' which returns true if the value it
is testing is zero. When `zerop' tests true, the then-part of the `if'
is evaluated. The then-part is a list starting with the function
`error', which is a function that is similar to the `message' function
(*note message::.), in that it prints a one-line message in the echo
area. However, in addition to printing a message, `error' also stops
evaluation of the function within which it is embedded. In this case,
this means that the rest of the function will not be evaluated if the
length of the kill ring is zero.
(In my opinion, it is slightly misleading, at least to humans, to use
the term `error' as the name of this function. A better term would be
`cancel'. Strictly speaking, of course, you cannot point to, much less
rotate a pointer to a list that has no length, so from the point of view
of the computer, the word `error' is correct. But a human expects to
attempt this sort of thing, if only to find out whether the kill ring is
full or empty. This is an act of exploration.
(From the human point of view, the act of exploration and discovery
is not necessarily an error, and therefore should not be labeled as one,
even in the bowels of a computer. As it is, the code in Emacs implies
that a human who is acting virtuously, by exploring his or her
environment, is making an error. This is bad. Even though the computer
takes the same steps as it does when there is an `error', a term such as
`cancel' would have a clearer connotation.)
* Menu:
* rotate-yk-ptr else-part:: The else-part of the `if' expression.
* Remainder Function:: The remainder, `%', function.
* rotate-yk-ptr remainder:: Using `%' in `rotate-yank-pointer'.
* kill-rng-yk-ptr last elt:: Pointing to the last element.
File: emacs-lisp-intro.info, Node: rotate-yk-ptr else-part, Next: Remainder Function, Prev: rotate-yk-ptr body, Up: rotate-yk-ptr body
The else-part of the `if' expression
....................................
The else-part of the `if' expression is dedicated to setting the
value of `kill-ring-yank-pointer' when the kill ring has something in
it. The code looks like this:
(setq kill-ring-yank-pointer
(nthcdr (% (+ arg
(- length
(length kill-ring-yank-pointer)))
length)
kill-ring)))))
This needs some examination. Clearly, `kill-ring-yank-pointer' is
being set to be equal to some CDR of the kill ring, using the `nthcdr'
function that is described in an earlier section. (*Note
copy-region-as-kill::.) But exactly how does it do this?
Before looking at the details of the code let's first consider the
purpose of the `rotate-yank-pointer' function.
The `rotate-yank-pointer' function changes what
`kill-ring-yank-pointer' points to. If `kill-ring-yank-pointer' starts
by pointing to the first element of a list, a call to
`rotate-yank-pointer' causes it to point to the second element; and if
`kill-ring-yank-pointer' points to the second element, a call to
`rotate-yank-pointer' causes it to point to the third element. (And if
`rotate-yank-pointer' is given an argument greater than 1, it jumps the
pointer that many elements.)
The `rotate-yank-pointer' function uses `setq' to reset what the
`kill-ring-yank-pointer' points to. If `kill-ring-yank-pointer' points
to the first element of the kill ring, then, in the simplest case, the
`rotate-yank-pointer' function must cause it to point to the second
element. Put another way, `kill-ring-yank-pointer' must be reset to
have a value equal to the CDR of the kill ring.
That is, under these circumstances,
(setq kill-ring-yank-pointer
("some text" "a different piece of text" "yet more text"))
(setq kill-ring
("some text" "a different piece of text" "yet more text"))
the code should do this:
(setq kill-ring-yank-pointer (cdr kill-ring))
As a result, the `kill-ring-yank-pointer' will look like this:
kill-ring-yank-pointer
=> ("a different piece of text" "yet more text"))
The actual `setq' expression uses the `nthcdr' function to do the
job.
As we have seen before (*note nthcdr::.), the `nthcdr' function
works by repeatedly taking the CDR of a list--it takes the CDR of the
CDR of the CDR ...
The two following expressions produce the same result:
(setq kill-ring-yank-pointer (cdr kill-ring))
(setq kill-ring-yank-pointer (nthcdr 1 kill-ring))
In the `rotate-yank-pointer' function, however, the first argument
to `nthcdr' is a rather complex looking expression with lots of
arithmetic inside of it:
(% (+ arg
(- length
(length kill-ring-yank-pointer)))
length)
As usual, we need to look at the most deeply embedded expression
first and then work our way towards the light.
The most deeply embedded expression is `(length
kill-ring-yank-pointer)'. This finds the length of the current value of
the `kill-ring-yank-pointer'. (Remember that the
`kill-ring-yank-pointer' is the name of a variable whose value is a
list.)
The measurement of the length is inside the expression:
(- length (length kill-ring-yank-pointer))
In this expression, the first `length' is the variable that was
assigned the length of the kill ring in the `let' statement at the
beginning of the function. (One might think this function would be
clearer if the variable `length' were named `length-of-kill-ring'
instead; but if you look at the text of the whole function, you will
see that it is so short that naming this variable `length' is not a
bother, unless you are pulling the function apart into very tiny pieces
as we are doing here.)
So the line `(- length (length kill-ring-yank-pointer))' tells the
difference between the length of the kill ring and the length of the
list whose name is `kill-ring-yank-pointer'.
To see how all this fits into the `rotate-yank-pointer' function,
let's begin by analyzing the case where `kill-ring-yank-pointer' points
to the first element of the kill ring, just as `kill-ring' does, and
see what happens when `rotate-yank-pointer' is called with an argument
of 1.
In this case, the variable `length' and the value of the expression
`(length kill-ring-yank-pointer' will be the same since the variable
`length' is the length of the kill ring and the
`kill-ring-yank-pointer' is pointing to the whole kill ring.
Consequently, the value of
(- length (length kill-ring-yank-pointer))
will be zero. Since the value of `arg' will be 1, this will mean that
the value of the whole expression
(+ arg (- length (length kill-ring-yank-pointer)))
will be 1.
Consequently, the argument to `nthcdr' will be found as the result of
the expression
(% 1 length)
File: emacs-lisp-intro.info, Node: Remainder Function, Next: rotate-yk-ptr remainder, Prev: rotate-yk-ptr else-part, Up: rotate-yk-ptr body
The `%' remainder function
..........................
To understand `(% 1 length)', we need to understand `%'. According
to its documentation (which I just found by typing `C-h f <%> <RET>'),
the `%' function returns the remainder of its first argument divided by
its second argument. For example, the remainder of 5 divided by 2 is
1. (2 goes into 5 twice with a remainder of 1.)
What surprises people who don't often do arithmetic is that a smaller
number can be divided by a larger number and have a remainder. In the
example we just used, 5 was divided by 2. We can reverse that and ask,
what is the result of dividing 2 by 5? If you can use fractions, the
answer is obviously 2/5 or .4; but if, as here, you can only use whole
numbers, the result has to be something different. Clearly, 5 can go
into 2 zero times, but what of the remainder? To see what the answer
is, consider a case that has to be familiar from childhood:
* 5 divided by 5 is 1 with a remainder of 0;
* 6 divided by 5 is 1 with a remainder of 1;
* 7 divided by 5 is 1 with a remainder of 2.
* Similarly, 10 divided by 5 is 2 with a remainder of 0;
* 11 divided by 5 is 2 with a remainder of 1;
* 12 divided by 5 is 1 with a remainder of 2.
By considering the cases as parallel, we can see that
* zero divided by 5 must be zero with a remainder of zero;
* 1 divided by 5 must be zero with a remainder of 1;
* 2 divided by 5 must be zero with a remainder of 2;
and so on.
So, in this code, if the value of `length' is 5, then the result of
evaluating
(% 1 5)
is 1. (I just checked this by placing the cursor after the expression
and typing `C-x C-e'. Indeed, 1 is printed in the echo area.)
File: emacs-lisp-intro.info, Node: rotate-yk-ptr remainder, Next: kill-rng-yk-ptr last elt, Prev: Remainder Function, Up: rotate-yk-ptr body
Using `%' in `rotate-yank-pointer'
..................................
When the `kill-ring-yank-pointer' points to the beginning of the
kill ring, and the argument passed to `rotate-yank-pointer' is 1, the
`%' expression returns 1:
(- length (length kill-ring-yank-pointer))
=> 0
therefore,
(+ arg (- length (length kill-ring-yank-pointer)))
=> 1
and consequently:
(% (+ arg (- length (length kill-ring-yank-pointer)))
length)
=> 1
regardless of the value of `length'.
As a result of this, the `setq kill-ring-yank-pointer' expression
simplifies to:
(setq kill-ring-yank-pointer (nthcdr 1 kill-ring))
What it does is now easy to understand. Instead of pointing as it did
to the first element of the kill ring, the `kill-ring-yank-pointer' is
set to point to the second element.
Clearly, if the argument passed to `rotate-yank-pointer' is two, then
the `kill-ring-yank-pointer' is set to `(nthcdr 2 kill-ring)'; and so
on for different values of the argument.
Similarly, if the `kill-ring-yank-pointer' starts out pointing to
the second element of the kill ring, it length is shorter than the
length of the kill ring by 1, so the computation of the remainder is
based on the expression `(% (+ arg 1) length)'. This means that the
`kill-ring-yank-pointer' is moved from the second element of the kill
ring to the third element if the argument passed to
`rotate-yank-pointer' is 1.
File: emacs-lisp-intro.info, Node: kill-rng-yk-ptr last elt, Prev: rotate-yk-ptr remainder, Up: rotate-yk-ptr body
Pointing to the last element
............................
The final question is, what happens if the `kill-ring-yank-pointer'
is set to the *last* element of the kill ring? Will a call to
`rotate-yank-pointer' mean that nothing more can be taken from the kill
ring? The answer is no. What happens is different and useful. The
`kill-ring-yank-pointer' is set to point to the beginning of the kill
ring instead.
Let's see how this works by looking at the code, assuming the length
of the kill ring is 5 and the argument passed to `rotate-yank-pointer'
is 1. When the `kill-ring-yank-pointer' points to the last element of
the kill ring, its length is 1. The code looks like this:
(% (+ arg (- length (length kill-ring-yank-pointer))) length)
When the variables are replaced by their numeric values, the
expression looks like this:
(% (+ 1 (- 5 1)) 5)
This expression can be evaluated by looking at the most embedded inner
expression first and working outwards: The value of `(- 5 1)' is 4;
the sum of `(+ 1 4)' is 5; and the remainder of dividing 5 by 5 is
zero. So what `rotate-yank-pointer' will do is
(setq kill-ring-yank-pointer (nthcdr 0 kill-ring))
which will set the `kill-ring-yank-pointer' to point to the beginning
of the kill ring.
So what happens with successive calls to `rotate-yank-pointer' is
that it moves the `kill-ring-yank-pointer' from element to element in
the kill ring until it reaches the end; then it jumps back to the
beginning. And this is why the kill ring is called a ring, since by
jumping back to the beginning, it is as if the list has no end! (And
what is a ring, but an entity with no end?)
File: emacs-lisp-intro.info, Node: yank, Next: yank-pop, Prev: rotate-yank-pointer, Up: Kill Ring
`yank'
======
After learning about `rotate-yank-pointer', the code for the `yank'
function is almost easy. It has only one tricky part, which is the
computation of the argument to be passed to `rotate-yank-pointer'.
The code looks like this:
(defun yank (&optional arg)
"Reinsert the last stretch of killed text.
More precisely, reinsert the stretch of killed text most
recently killed OR yanked.
With just C-U as argument, same but put point in front
(and mark at end). With argument n, reinsert the nth
most recently killed stretch of killed text.
See also the command \\[yank-pop]."
(interactive "*P")
(rotate-yank-pointer (if (listp arg) 0
(if (eq arg '-) -1
(1- arg))))
(push-mark (point))
(insert (car kill-ring-yank-pointer))
(if (consp arg)
(exchange-point-and-mark)))
Glancing over this code, we can understand the last few lines readily
enough. The mark is pushed, that is, remembered; then the first element
(the CAR) of what the `kill-ring-yank-pointer' points to is inserted;
and then, if the argument passed the function is a `cons', point and
mark are exchanged so the point is put in the front of the inserted
text rather than at the end. This option is explained in the
documentation. The function itself is interactive with `"*P"'. This
means it will not work on a read-only buffer, and that the unprocessed
prefix argument is passed to the function.
* Menu:
* rotate-yk-ptr arg:: Pass the argument to `rotate-yank-pointer'.
* rotate-yk-ptr negative arg:: Pass a negative argument.
File: emacs-lisp-intro.info, Node: rotate-yk-ptr arg, Next: rotate-yk-ptr negative arg, Prev: yank, Up: yank
Passing the argument
....................
The hard part of `yank' is understanding the computation that
determines the value of the argument passed to `rotate-yank-pointer'.
Fortunately, it is not so difficult as it looks at first sight.
What happens is that the result of evaluating one or both of the
`if' expressions will be a number and that number will be the argument
passed to `rotate-yank-pointer'.
Laid out with comments, the code looks like this:
(if (listp arg) ; if-part
0 ; then-part
(if (eq arg '-) ; else-part, inner if
-1 ; inner if's then-part
(1- arg)))) ; inner if's else-part
This code consists of two `if' expression, one the else-part of the
other.
The first or outer `if' expression tests whether the argument passed
to `yank' is a list. Oddly enough, this will be true if `yank' is
called without an argument--because then it will be passed the value of
`nil' for the optional argument and an evaluation of `(listp nil)'
returns true! So, if no argument is passed to `yank', the argument
passed to `rotate-yank-pointer' inside of `yank' is zero. This means
the pointer is not moved and the first element to which
`kill-ring-yank-pointer' points is inserted, as we expect. Similarly,
if the argument for `yank' is `C-u', this will be read as a list, so
again, a zero will be passed to `rotate-yank-pointer'. (`C-u' produces
an unprocessed prefix argument of `(4)', which is a list of one
element.) At the same time, later in the function, this argument will
be read as a `cons' so point will be put in the front and mark at the
end of the insertion. (The `P' argument to `interactive' is designed
to provide these values for the case when an optional argument is not
provided or when it is `C-u'.)
The then-part of the outer `if' expression handles the case then
there is no argument or when it is `C-u'. The else-part handles the
other situations. The else-part is itself another `if' expression.
The inner `if' expression tests whether the argument is a minus
sign. (This is done by pressing the <META> and `-' keys at the same
time, or the <ESC> key and then the `-' key). In this case, the
`rotate-yank-pointer' function is passed `-1' as an argument. This
moves the `kill-ring-yank-pointer' backwards, which is what is desired.
If the true-or-false-test of the inner `if' expression is false
(that is, if the argument is not a minus sign), the else-part of the
expression is evaluated. This is the expression `(1- arg)'. Because
of the two `if' expressions, it will only occur when the argument is a
positive number or when it is a negative number (not just a minus sign
on its own). What `(1- arg)' does is decrement the number and return
it. (The `1-' function subtracts one from its argument.) This means
that if the argument to `rotate-yank-pointer' is 1, it is reduced to
zero, which means the first element to which `kill-ring-yank-pointer'
points is yanked back, as you would expect.
File: emacs-lisp-intro.info, Node: rotate-yk-ptr negative arg, Prev: rotate-yk-ptr arg, Up: yank
Passing a negative argument
...........................
Finally, the question arises, what happens if either the remainder
function, `%', or the `nthcdr' function is passed a negative argument,
as they quite well may?
The answers can be found by a quick test. When `(% -1 5)' is
evaluated, a negative number is returned; and if `nthcdr' is called
with a negative number, it returns the same value as if it were called
with a first argument of zero. This can be seen be evaluating the
following code.
Here the `=>' points to the result of evaluating the code preceding
it. This was done by positioning the cursor after the code and typing
`C-x C-e' (`eval-last-sexp') in the usual fashion. You can do this if
you are reading this in Info inside of GNU Emacs.
(% -1 5)
=> -1
(setq animals '(cats dogs elephants))
=> (cats dogs elephants)
(nthcdr 1 animals)
=> (dogs elephants)
(nthcdr 0 animals)
=> (cats dogs elephants)
(nthcdr -1 animals)
=> (cats dogs elephants)
So, if a minus sign or a negative number is passed to `yank', the
`kill-ring-yank-point' is rotated backwards until it reaches the
beginning of the list. Then it stays there. Unlike the other case,
when it jumps from the end of the list to the beginning of the list,
making a ring, it stops. This makes sense. You often want to get back
to the most recently clipped out piece of text, but you don't usually
want to insert text from as many as thirty kill commands ago. So you
need to work through the ring to get to the end, but won't cycle around
it inadvertently if you are trying to come back to the beginning.
Incidentally, any number passed to `yank' with a minus sign
preceding it will be treated as -1. This is evidently a simplification
for writing the program. You don't need to jump back towards the
beginning of the kill ring more than one place at a time and doing this
is easier than writing a function to determine the magnitude of the
number that follows the minus sign.
File: emacs-lisp-intro.info, Node: yank-pop, Prev: yank, Up: Kill Ring
`yank-pop'
==========
After understanding `yank', the `yank-pop' function is easy.
Leaving out the documentation to save space, it looks like this:
(defun yank-pop (arg)
(interactive "*p")
(if (not (eq last-command 'yank))
(error "Previous command was not a yank"))
(setq this-command 'yank)
(let ((before (< (point) (mark))))
(delete-region (point) (mark))
(rotate-yank-pointer arg)
(set-mark (point))
(insert (car kill-ring-yank-pointer))
(if before (exchange-point-and-mark))))
The function is interactive with a small `p' so the prefix argument
is processed and passed to the function. The command can only be used
after a previous yank; otherwise an error message is sent. This check
uses the variable `last-command' which is discussed elsewhere. (*Note
copy-region-as-kill::.)
The `let' clause sets the variable `before' to true or false
depending whether point is before or after mark and then the region
between point and mark is deleted. This is the region that was just
inserted by the previous yank and it is this text that will be
replaced. Next the `kill-ring-yank-pointer' is rotated so that the
previously inserted text is not reinserted yet again. Mark is set at
the beginning of the place the new text will be inserted and then the
first element to which `kill-ring-yank-pointer' points is inserted.
This leaves point after the new text. If in the previous yank, point
was left before the inserted text, point and mark are now exchanged so
point is again left in front of the newly inserted text. That is all
there is to it!
File: emacs-lisp-intro.info, Node: Full Graph, Next: Index, Prev: Kill Ring, Up: Top
A Graph with Labelled Axes
**************************
Printed axes help you understand a graph. They convey scale. In an
earlier chapter (*note Readying a Graph: Readying a Graph.), we wrote
the code to print the body of a graph. Here we write the code for
print and labelling vertical and horizontal axes, along with the body
itself.
* Menu:
* Labelled Example:: How a finished graph should look.
* print-graph Varlist:: `let' expression in `print-graph'.
* print-Y-axis:: Print a label for the vertical axis.
* print-X-axis:: Print a horizontal label.
* Print Whole Graph:: The function to print a complete graph.
File: emacs-lisp-intro.info, Node: Labelled Example, Next: print-graph Varlist, Prev: Full Graph, Up: Full Graph
Labelled Example Graph
======================
Since insertions fill a buffer to the right and below point, the new
graph printing function should first print the Y or vertical axis, then
the body of the graph, and finally the X or horizontal axis. This
sequence lays out for us the contents of the function:
1. Set up code.
2. Print Y axis.
3. Print body of graph.
4. Print X axis.
Here is an example of how a finished graph should look:
10 -
*
* *
* **
* ***
5 - * *******
* *** *******
*************
***************
1 - ****************
| | | |
1 5 10 15
In this graph, both the vertical and the horizontal axes are labeled
with numbers. However, in some graphs, the horizontal axis is time and
would be better labeled with months, like this:
5 - *
* ** *
*******
********** **
1 - **************
| ^ |
Jan June Jan
Indeed, with a little thought, we can easily come up with a variety
of vertical and horizontal labelling schemes. Our task could become
complicated. But complications breed confusion. Rather than permit
this, it is better choose a simple labelling scheme for our first
effort, and to modify or replace it later.
These considerations suggest the following outline for the
`print-graph' function:
(defun print-graph (numbers-list)
"DOCUMENTATION..."
(let ((height ...
...))
(print-Y-axis height ... )
(graph-body-print numbers-list)
(print-X-axis ... )))
We can work on each part of the `print-graph' function definition in
turn.
File: emacs-lisp-intro.info, Node: print-graph Varlist, Next: print-Y-axis, Prev: Labelled Example, Up: Full Graph
The `print-graph' Varlist
=========================
In writing the `print-graph' function, the first task is to write
the varlist in the `let' expression. (We will leave aside for the
moment any thoughts about making the function interactive or about the
contents of its documentation string.)
The varlist should set several values. Clearly, the top of the label
for the vertical axis must be at least the height of the graph, which
means that we must obtain this information here. Note that the
`print-graph-body' function also requires this information. There is
no reason to calculate the height of the graph in two different places,
so we should change for `print-graph-body' from the way we defined it
earlier to take advantage of the calculation.
Similarly, both the function for printing the X axis labels and the
`print-graph-body' function need to learn the value of the width of
each symbol. We can perform the calculation here and change the
definition for `print-graph-body' from the way we defined it in the
previous chapter.
The length of the label for the horizontal axis must be at least as
long as the graph. However, this information is used only in the
function that prints the horizontal axis, so it does not need to be
calculated here.
These thoughts lead us directly to the following form for the varlist
in the `let' for `print-graph':
(let ((height (apply 'max numbers-list)) ; First version.
(symbol-width (length graph-blank)))
As we shall see, this expression is not quite right.
File: emacs-lisp-intro.info, Node: print-Y-axis, Next: print-X-axis, Prev: print-graph Varlist, Up: Full Graph
The `print-Y-axis' Function
===========================
The job of the `print-Y-axis' function is to print a label for the
vertical axis that looks like this:
10 -
5 -
1 -
The function should be passed the height of the graph, and then should
construct and insert the appropriate numbers and marks.
It is easy enough to see in the figure what the Y axis label should
look like; but to say in words, and then to write a function definition
to do the job is another matter. It is not quite true to say that we
want a number and a tick every five lines: there are only three lines
between the `1' and the `5' (lines 2, 3, and 4), but four lines between
the `5' and the `10' (lines 6, 7, 8, and 9). It is better to say that
we want a number and a tick mark on the base line (number 1) and then
that we want a number and a tick on the fifth line from the bottom and
on every line that is a multiple of five.
The next issue is what height the label should be. Suppose the
maximum height of tallest column of the graph is seven. Should the
highest label on the Y axis be `5 -', and should the graph stick up
above the label? Or should the highest label be `7 -', and mark the
peak of the graph? Or should the highest label be `10 -', which is a
multiple of five, and be higher than the topmost value of the graph?
The latter form is preferred. Most graphs are drawn within
rectangles whose sides are an integral number of steps long--5, 10, 15,
and so on for a step distance of five. But as soon as we decide to use
a step height for the vertical axis, we discover that the simple
expression in the varlist for computing the height is wrong. The
expression is `(apply 'max numbers-list)'. This returns the precise
height, not the maximum height plus whatever is necessary to round up
to the nearest multiple of five. A more complex expression is required.
As usual in cases like this, a complex problem becomes simpler if it
is divided into several smaller problems.
First, consider the case when the highest value of the graph is an
integral multiple of five--when it is 5, 10, 15 ,or some higher
multiple of five. In this case, we can use this value as the Y axis
height.
A fairly simply way to determine whether a number is a multiple of
five is to divide it by five and see if the division results in a
remainder. If there is no remainder, the number is a multiple of five.
Thus, seven divided by five has a remainder of two, and seven is not
an integral multiple of five. Put in slightly different language, more
reminiscent of the classroom, five goes into seven once, with a
remainder of two. However, five goes into ten twice, with no
remainder: ten is an integral multiple of five.
* Menu:
* Compute a Remainder:: How to compute the remainder of a division.
* Y Axis Element:: Construct a line for the Y axis.
* Y-axis-column:: Generate a list of Y axis labels.
* print-Y-axis Final:: Print a vertical axis, final version.
File: emacs-lisp-intro.info, Node: Compute a Remainder, Next: Y Axis Element, Prev: print-Y-axis, Up: print-Y-axis
Side Trip: Compute a Remainder
------------------------------
In Lisp, the function for computing a remainder is `%'. The
function returns the remainder of its first argument divided by its
second argument. As it happens, `%' is a function in Emacs Lisp that
you cannot discover using `apropos': you find nothing if you type `M-x
apropos <RET> remainder <RET>'. The only way to learn of the existence
of `%' is to read about it in a book such as this or in the Emacs Lisp
sources. The `%' function is used in the code for
`rotate-yank-pointer', which is described in an appendix. (*Note The
Body of `rotate-yank-pointer': rotate-yk-ptr body.)
You can try the `%' function by evaluating the following two
expressions:
(% 7 5)
(% 10 5)
The first expression returns 2 and the second expression returns 0.
To test whether the returned value is zero or some other number, we
can use the `zerop' function. This function returns `t' if its
argument, which must be a number, is zero.
(zerop (% 7 5))
=> nil
(zerop (% 10 5))
=> t
Thus, the following expression will return `t' if the height of the
graph is evenly divisible by five:
(zerop (% height 5))
(The value of `height', of course, can be found from `(apply 'max
numbers-list)'.)
On the other hand, if the value of `height' is not a multiple of
five, we want to reset the value to the next higher multiple of five.
This is straightforward arithmetic using functions with which we are
already familiar. First, we divide the value of `height' by five to
determine how many times five goes into the number. Thus, five goes
into twelve twice. If we add one to this quotient and multiply by
five, we will obtain the value of the next multiple of five that is
larger than the height. Five goes into twelve twice. Add one to two,
and multiply by five; the result is fifteen, with is the next multiple
of five that is higher than twelve. The Lisp expression for this is:
(* (1+ (/ height 5)) 5)
For example, if you evaluate the following, the result is 15:
(* (1+ (/ 12 5)) 5)
All through this discussion, we have been using `five' as the value
for spacing labels on the Y axis; but we may want to use some other
value. For generality, we should replace `five' with a variable to
which we can assign a value. The best name I can think of for this
variable is `Y-axis-label-spacing'. Using this term, and an `if'
expression, we produce the following:
(if (zerop (% height Y-axis-label-spacing))
height
;; else
(* (1+ (/ height Y-axis-label-spacing))
Y-axis-label-spacing))
This expression returns the value of `height' itself if the height is
an even multiple of the value of the `Y-axis-label-spacing' or else it
computes and returns a value of `height' that is equal to the next
higher multiple of the value of the `Y-axis-label-spacing'.
We can now include this expression in the `let' expression of the
`print-graph' function (after first setting the value of
`Y-axis-label-spacing'):
(defvar Y-axis-label-spacing 5
"Number of lines from one Y axis label to next.")
...
(let* ((height (apply 'max numbers-list))
(height-of-top-line
(if (zerop (% height Y-axis-label-spacing))
height
;; else
(* (1+ (/ height Y-axis-label-spacing))
Y-axis-label-spacing)))
(symbol-width (length graph-blank))))
...
(Note use of the `let*' function: the initial value of height is
computed once by the `(apply 'max numbers-list)' expression and then
the resulting value of `height' is used to compute its final value.
*Note The `let*' expression: fwd-para let, for more about `let*'.)
File: emacs-lisp-intro.info, Node: Y Axis Element, Next: Y-axis-column, Prev: Compute a Remainder, Up: print-Y-axis
Construct a Y Axis Element
--------------------------
When we print the vertical axis, we want to insert strings such as
`5 -' and `10 - ' every five lines. Moreover, we want the numbers and
dashes to line up, so shorter numbers must be padded with leading
spaces. If some of the strings use two digit numbers, the strings with
single digit numbers must include a leading blank space before the
number.
To figure out the length of the number, the `length' function is
used. But the `length' function works only with a string, not with a
number. So the number has to be converted from being a number to being
a string. This is done with the `int-to-string' function. For example,
(length (int-to-string 35))
=> 2
(length (int-to-string 100))
=> 3
In addition, in each label, each number is followed by a string such
as ` - ', which we will call the `Y-axis-tic' marker. This variable is
defined with `defvar':
(defvar Y-axis-tic " - "
"String that follows number in a Y axis label.")
The length of the Y label is the sum of the length of the Y axis tick
mark and the length of the number of the top of the graph.
(length (concat (int-to-string height) Y-axis-tic)))
This value will be calculated by the `print-graph' function in its
varlist as `full-Y-label-width' and passed on. (Note that we did not
think to include this in the varlist when we first proposed it.)
To make a complete vertical axis label, a tick mark is concatenated
with a number; and the two together may be preceded by one or more
spaces depending on how long the number is. The label consists of
three parts: the (optional) leading spaces, the number, and the tic
mark. The function is passed the value of the number for the specific
row, and the value of the width of the top line, which is calculated
(just once) by `print-graph'.
(defun Y-axis-element (number full-Y-label-width)
"Construct a NUMBERed label element.
A numbered element looks like this ` 5 - ',
and is padded as needed so all line up with
the element for the largest number."
(let* ((leading-spaces
(- full-Y-label-width
(length
(concat (int-to-string number)
Y-axis-tic)))))
(concat
(make-string leading-spaces ? )
(int-to-string number)
Y-axis-tic)))
The `Y-axis-element' function concatenates together the leading
spaces, if any; the number, as a string; and the tick mark.
To figure out how many leading spaces the label will need, the
function subtracts the actual length of the label--the length of the
number plus the length of the tic mark--from the desired label width.
Blank spaces are inserted using the `make-string' function. This
function takes two arguments: the first tells it how long the string
will be and the second is a symbol for the character to insert, in a
special format. In this case, the format is a question mark followed
by a blank space, like this, `? '. *Note Character Type:
(elisp)Character Type, for a description of the syntax for characters.
The `int-to-string' function is used in the concatenation
expression, to convert the number to a string that is concatenated with
the leading spaces and the tic mark.
File: emacs-lisp-intro.info, Node: Y-axis-column, Next: print-Y-axis Final, Prev: Y Axis Element, Up: print-Y-axis
Create a Y Axis Column
----------------------
The preceding functions provide all the tools needed to construct a
function that generates a list of numbered and blank strings to insert
as the label for the vertical axis:
(defun Y-axis-column (height width-of-label)
"Construct list of Y axis labels and blank strings.
For HEIGHT of line above base and WIDTH-OF-LABEL."
(let (Y-axis)
(while (> height 1)
(if (zerop (% height Y-axis-label-spacing))
;; Insert label.
(setq Y-axis
(cons
(Y-axis-element height width-of-label)
Y-axis))
;; Else, insert blanks.
(setq Y-axis
(cons
(make-string width-of-label ? )
Y-axis)))
(setq height (1- height)))
;; Insert base line.
(setq Y-axis
(cons (Y-axis-element 1 width-of-label) Y-axis))
(nreverse Y-axis)))
In this function, we start with the value of `height' and
repetitively subtract one from its value. After each subtraction, we
test to see whether the value is an integral multiple of the
`Y-axis-label-spacing'. If it is, we construct a numbered label using
the `Y-axis-element' function; if not, we construct a blank label using
the `make-string' function. The base line consists of the number one
followed by a tic mark.
File: emacs-lisp-intro.info, Node: print-Y-axis Final, Prev: Y-axis-column, Up: print-Y-axis
Final Version of `print-Y-axis'
-------------------------------
The list constructed by the `Y-axis-column' function is passed to
the `print-Y-axis' function, which inserts the list as a column.
(defun print-Y-axis
(height full-Y-label-width &optional vertical-step)
"Insert Y axis using HEIGHT and FULL-Y-LABEL-WIDTH.
Height must be the maximum height of the graph.
Full width is the width of the highest label element.
Optionally, print according to VERTICAL-STEP."
;; Value of height and full-Y-label-width
;; are passed by `print-graph'.
(let ((start (point)))
(insert-rectangle
(Y-axis-column height full-Y-label-width vertical-step))
;; Place point ready for inserting graph.
(goto-char start)
;; Move point forward by value of full-Y-label-width
(forward-char full-Y-label-width)))
The `print-Y-axis' uses the `insert-rectangle' function to insert
the Y axis labels created by the `Y-axis-column' function. In
addition, it places point at the correct position for printing the body
of the graph.
You can test `print-Y-axis':
1. Install
Y-axis-label-spacing
Y-axis-tic
Y-axis-element
Y-axis-columnprint-Y-axis
2. Copy the following expression:
(print-Y-axis 12 5)
3. Switch to the `*scratch*' buffer and place the cursor where you
want the axis labels to start.
4. Type `M-:' (`eval-expression').
5. Yank the `graph-body-print' expression into the minibuffer with
`C-y' (`yank)'.
6. Press <RET> to evaluate the expression.
Emacs will print labels vertically, the top one being `10 - '. (The
`print-graph' function will pass the value of `height-of-top-line',
which in this case would be 15.)
File: emacs-lisp-intro.info, Node: print-X-axis, Next: Print Whole Graph, Prev: print-Y-axis, Up: Full Graph
The `print-X-axis' Function
===========================
X axis labels are much like Y axis labels, except that the tics are
on a line above the numbers. Labels should look like this:
| | | |
1 5 10 15
The first tic is under the first column of the graph and is preceded
by several blank spaces. These spaces provide room in rows above for
the Y axis labels. The second, third, fourth, and subsequent tics are
all spaced equally, according to the value of `X-axis-label-spacing'.
The second row of the X axis consists of numbers, preceded by several
blank spaces and also separated according to the value of the variable
`X-axis-label-spacing'.
The value of the variable `X-axis-label-spacing' should itself be
measured in units of `symbol-width', since you may want to change the
width of the symbols that you are using to print the body of the graph
without changing the ways the graph is labeled.
The `print-X-axis' function is constructed in more or less the same
fashion as the `print-Y-axis' function except that it has two lines:
the line of tic marks and the numbers. We will write a separate
function to print each line and then combine them within the
`print-X-axis' function.
This is a three step process:
1. Write a function to print the X axis tic marks,
`print-X-axis-tic-line'.
2. Write a function to print the X numbers,
`print-X-axis-numbered-line'.
3. Write a function to print both lines, the `print-X-axis' function,
using `print-X-axis-tic-line' and `print-X-axis-numbered-line'.
* Menu:
* X Axis Tic Marks:: Create tic marks for the horizontal axis.