1. Introduction
Qed is a programmable text editor, intended for use primarily by programmers. For the average user, Qed's power will likely be unnecessary, even troublesome, and its use is discouraged. For a knowledgeable user who is willing to learn how to use it properly, however, Qed is a powerful tool, both when used as an editor or as a (rather idiosyncratic and low-level) programming language.
Qed is very nearly a superset of the University of Toronto
UNIX version 6 editor,
Ed, which is itself a strict superset of standard Ed.
This document assumes considerable familiarity with the standard
editor,
and some acquaintance with the new features of the U. of T. version.
The features of U. of T. Ed not found in regular version
6 Ed include: the *
address separator,
two character error messages (a query followed by a character
which indicates the error, e.g. ?s
for a failed substitution),
a simple undo u
command,
and a join j
command of somewhat greater generality
than the *PWB* version.
(The new features used in this tutorial will be briefly explained as
they turn up.)
There is an add-on document for U. of T. Ed
which describes the enhancements and modifications, at user level,
made at U. of T.
This tutorial is not a complete description of the capabilities of Qed.
Rather, it is an attempt to familiarize the programmer with
the general ways in which Qed operates,
emphasizing the programming techniques which clarify and simplify its use.
A full description of the commands and features of Qed
may be found in the manual section, qed(1)
.
Before beginning the discussion of Qed, some warning should be given. UNIX Ed is closely based on a version of Qed, running under the GCOS operating system, which was written by Dennis Ritchie and Ken Thompson. When Dennis Ritchie wrote Ed, he removed many of the features, including most of the programming capabilities, but left in most of the text editing power. Although the Qed described here is significantly more complex and powerful than Ed (and quite unrelated to the GCOS Qed), its increase in power is not proportionate to its increase in complexity. In short, Ed is a very powerful editor, and for general editing jobs quite sufficient. Qed simplifies some more complicated tasks, and its multifile capability and programmability make many things possible which cannot be done in Ed, but to use it well requires a fairly thorough understanding of its operation, which is fairly intricate.
Qed has several drawbacks that should be admitted early. Qed programs can be difficult to read, even if done carefully, since it operates at about the level of a particularly cryptic assembler. A careless user can easily damage files using Qed incorrectly, but it is much harder to accidentally cause trouble with the U. of T. Qed than earlier ones, as the implementors worked very hard at safeguarding. The safeguards are strong enough that occasional users who desire a particular Qed feature for one editing job need not feel in danger of making a serious mistake. Qed's power can lead the user astray; it has far more power than is needed for most editing jobs. As an illustrative but rather artificial example, consider the problem of reversing the lines of a file, so that the last line appears at the top, and the first at the bottom, with the contents of the lines unchanged. At first, this may seem a problem for Qed if it is only to be done once or twice (if it is to be done often, we would certainly write a C program!), but it is very easy to do in Ed:
g/^/m0
(For readability’s sake, it will be the convention in this tutorial to show user input aligned with the left margin, and editor output offset by a tab from the left margin. In actual practice on the terminal screen, the editor output is left-aligned on the left margin, exactly as the input is.)
A second, slightly more complicated example is the problem of
placing two columnar files, say files generated by ls
,
alongside each other in a buffer.
Ed can again do the job quite well:
$ ed file1
142
.=
15
r file2
153
.=
30
1ka
16,30 g/^/m'a\
+ka
g/^/ .,+j/ /
How does it work?
After reading in the two files, each of 15 lines,
the first line is marked (1ka
).
Then each line in the second file is moved to
the line marked a
, setting dot
to the moved line,
and the mark is transferred to the next line in the first file
(+ka
).
The backslash in the global command line is necessary
to terminate the scan of the target address for the move.
The last command joins each line to the next,
separated by a tab (the white space between the slashes on the
join j
command, a facility in U. of T. Ed).
Because the global command marks all lines for execution
before running the command list on any of the lines,
after the join the second of each pair of lines has effectively
disappeared from the range of lines of the global,
and each execution of the global command list joins
a line of the first file with its corresponding line in the second.
The above example demonstrates several things. First, Ed is considerably more powerful than most of its users realize. When first given the problem, most users (including the author!) assume it is a task best handled by Qed. Second, editor programming often requires the subtle interaction of many commands: the global join is an excellent example. Therefore, debugging editor programs can be difficult. (Qed has a tracing feature which greatly simplifies debugging in some cases.) Third, editor programs are usually difficult to read and understand.
This preamble may sound discouraging, but it is only
promoting realism.
When used well and carefully, Qed can be a great time saver,
fun to work with, and sometimes even elegant.
The original troff
version of this document was written using Qed,
storing sequences like \fBQed\fP
in registers
to save typing.
Well, if you’ve read this far, you must be determined, so you’re ready to learn about buffers.
2. Buffers
Ed has one buffer: one scratch area in which to keep text.
Qed has 56,
labeled by lower case alphabetics a
to z
,
upper case alphabetics A
to Z
,
and, for reasons worth ignoring at this point,
the characters {
, |
, }
, and ~
.
Each buffer has its own associated .
(dot), $
(dollar), and filename.
The easiest way to see how they work is to
cd
to a directory with about ten C
source files and type:
$ qed *.c
(we’ve already accomplished something which is impossible in Ed!) If you were in the Qed source directory, you would see something like
a .82 address.c
b .121 blkio.c
c .702 com.c
d .466 getchar.c
e .284 getfile.c
f .119 glob.c
g .725 main.c
h .209 misc.c
i .135 move.c
j .474 pattern.c
k .156 putchar.c
l .38 setaddr.c
m .128 string.c
n .418 subs.c
o .220 utf.c
p .153 utfio.c
q .36 utfstr.c
The first column is the buffer name,
the second column is the value of .
(dot) in the buffer, which, on
first reading in of a file is set to
the value of $
(dollar) in the buffer, that is, the
number of lines, and the last column is the file name local
to that buffer.
Qed is now waiting for a command. Type n
n
a .82 address.c
b 121 blkio.c
c 702 com.c
d 466 getchar.c
e 284 getfile.c
f 119 glob.c
g 725 main.c
h 209 misc.c
i 135 move.c
j 474 pattern.c
k 156 putchar.c
l 38 setaddr.c
m 128 string.c
n 418 subs.c
o 220 utf.c
p 153 utfio.c
q 36 utfstr.c
and look at the output. It should be nearly identical to the above output Qed
printed when it loaded the files on start up, but now there is only one .
,
indicating that buffer a
is the current buffer.
The n
(for names) command is Qed's equivalent of ls -l
.
Now do an f
command. You’ll see
f
a .82 address.c
In Qed, the f
command tells you more than just the file name.
Now change something in the file, say substitute out a tab
or delete an empty line, and do another f
:
f
a'.82 address.c
The prime tells you that the contents of the buffer are known to differ from the named file. Now try
bb f
b .121 blkio.c
The bb
and f
can be placed on the same line,
as Qed does not require a newline after most commands.
The bb
says change to buffer b
.
Buffer b
is now the current buffer, as indicated
by the dot.
If you browse around the buffer for a while,
you will see that it is really a world unto itself,
but changing back to buffer a
by a ba
command
will reset you back to the original file,
with dot still at whatever line it was when you typed bb
.
Why have multiple buffers?
For one thing, we can copy or move text between buffers.
Go back to buffer a
and isolate a subroutine, marking
its beginning with ka
and its last line with kb
.
Then type
'a,'b tz0
This is a regular copy command, but the z
after the t
tells Qed that the text is to be copied to buffer z
.
The 0
is the usual address, but is interpreted in buffer
z
rather than the current buffer.
Of course, if there is no buffer name character after the t
then Qed performs the copy command within the current buffer.
Do another n
: you will see that you are currently
in buffer z
, and dot is set to the last line copied.
The m
(move) command behaves similarly.
3. Special Characters (1)
Change to buffer z
and clear it:
bz 0,$d
Note that line 0
is a valid address for deletion.
,d
also would work here, and neither idiom
generates an error if the buffer is empty.
As well, you could have typed
bz Z
The Z
(zero) command unequivocally clears the buffer,
even its remembered file name.
Now do the following:
ap g/^[a-zA-Z_].*(/p
g/^[a-zA-Z_].*(/p
The append commands (a
, i
and c
) all accept a single line
of input typed on the command line, with a space or tab separator
between the command and its input. As always, a p
suffix
causes the command to print its result.
Buffer z
now contains a possibly useful command for Qed
(do you know what it does?) which we can
call up when desired.
Now read some C
source into buffer b
if there isn’t already
some there, and try out the buffer like this:
bb
\bz
initio(void)
getline(addr_t tl, int *lbuf)
putline(void)
blkio_r(int b, int *buf)
blkio_w(int b, int *buf)
The sequence \bz
means insert
the contents of buffer z
into my input stream here.
The final newline in the buffer
is replaced by the one typed after the z
,
so that if you decided later that you wanted to know the line
numbers as well, you could tag a .=
command on the end:
\bz .=
initio(void)
43
getline(addr_t tl, int *lbuf)
51
putline(void)
81
blkio_r(int b, int *buf)
108
blkio_w(int b, int *buf)
116
Although most Qed commands can be arbitrarily grouped on a line,
the global g
command, as in Ed,
still reads the full line for its command list,
which in this case is p .=
.
The above example is very important, as it uses a mixture of buffer input and terminal input to run a command, an all-pervading concept in Qed programming.
\bz
is called a special character, although in some sense
it isn’t really a character at all, as it gets
completely replaced with the contents of buffer z
.
The \bz
is interpreted
whenever input is expected,
not just when commands are being read.
Try the following examples:
by a
\bz
.
p
g/^[a-zA-Z_].*(/p
ap \bz
g/^[a-zA-Z_].*(/p
!echo "\bz"
g/^[a-zA-Z_].*(/p
!
The buffer could contain multiple lines, which would be handled as usual.
We could, for example, save in a buffer our example from the
introduction, which merged two columnar files alongside each other,
and invoke it when desired just as we invoked the global search above.
But care must be exercised here, as the newlines in the buffer,
except for the last,
are also placed in the input stream. If we were to type, with that
multiline buffer in z
, the command
s/x/\bz/p
mistakenly
expecting that buffer z
had just a single line of text,
say a frequently typed word, we would really be saying:
s/x/1ka
16,30 g/^/m'a\
+ka
g/^/ .,+j/ //p
This would, of course, cause an immediate error, and since Qed always returns to terminal input when an error occurs, no damage would be done. Sometimes, though, such mistakes can cause strange results!
If you did try the above command, the error message would be
?bz2.0 ?x
Qed gives a traceback on errors. The elements of the traceback are of the form
?bXM.N
where X
is the buffer name, M
the line number, and N
the character
number of the character at which the error was recognized.
In the above example, the substitute command found a syntax error (?x
)
when it read the newline,
so the error occurred at the beginning (.0
) of line 2
of buffer z
.
If input is nested,
the deepest-called buffer is
printed first.
It is a good idea to pause here and look carefully over what has been covered so far, as the concept of using a buffer to store regular files or command input interchangeably is really the heart of Qed. Before reading on, use Qed for a while to familiarize yourself with the system of buffers, and try out a few simple buffers for repetitive editing tasks.
Qed has a fair number of special characters
for various purposes. In the rest of this section
we will look briefly at some of the simpler ones
to give you some insight into how they behave.
First, enter buffer z
again and append:
bz
a
\Fa
\Fb
.
and then look at what Qed has appended to the buffer:
-,p
address.c
blkio.c
The special character \Fa
means the file name
for buffer a
,
and, like all special characters, is
interpreted whenever input is expected.
The special character \f
is a shorthand for
the saved file name in the current buffer.
Try
f junk
z'.2 junk
w
18
!ls \f
junk
!
Idioms such as
!cc \f
are very common.
If your file name is long, \f
can save much typing.
If the file name is changed, through an f
or e
command,
the name actually associated with `\f' is only changed when
the new name is completely read in.
Thus, you can type
e \f
to reinitialize a buffer, or
e /sys/src/cmd/\f
to edit the system version of a program.
There is another special character like \f
,
but it is more useful for programming.
\B
means the current buffer name.
Try
!echo \B
z
!
4. Special Characters (2)
The easiest way to gain familiarity with
the more abstruse characters is to use them
in messages, which are a special case of comments.
A comment starts with a double quote "
, and continues
until the first following double quote, or the end
of the line, whichever is first.
The line is ignored by
Qed, except that dot is set to the addressed line, if there is one:
4 " This comment sets dot to line 4
Messages are just like comments, except that the first character after the double quote is another double quote. If the message ends with a double quote rather than a newline, no newline is printed:
bx
" hi
"" hi
hi
"" hi there "
hi there |<- cursor is left on this line
""Current buffer: b\B
Current buffer: bx
This last example is mildly interesting.
Can we save the command in, say, buffer x
and call it back, from any buffer, when desired?
bx Z
ap ""Current buffer: b\B
""Current buffer: bx
bA \bx
Current buffer: bx
Oops! In principle, it can be done, since the current buffer is the one
we are working on, not the one being read for input.
But, to put the characters \B
in a buffer,
we must delay their interpretation so that
they are not replaced with the buffer name until
read back as command input.
In most systems on UNIX, this is done
by typing an extra backslash,
but things are more civilized in Qed.
In Qed, special characters are
delayed,
not quoted.
Perhaps it’s simplest just to state the rules:
Things are a little different in regular expressions, but
let’s ignore them for the moment.
These four rules, simple though they are,
define the interpretation of backslashes in Qed.
Note that \\Z
, where Z
is again not one of the above characters,
remains \\Z
, but if Z
is
special, say f
when the saved file name is junk.c
, then
\\f
becomes \junk.c
.
Now we know how to install a \B
in our buffer: we delay
its interpretation by putting a c
between the backslash and the B
.
(The c
is for character,
or (it is rumoured), for Mr. E. S. Cape, inventor of the backslash.)
The \cB
will reduce to a literal \B
when typed in:
bx Z
ap ""Current buffer: b\cB
""Current buffer: b\B
bA \bx
Current buffer: bA
bB \bx
Current buffer: bB
That’s better! Since \cc
will reduce to \c
,
the number of c
-s present is always just the
number of times the interpretation is to be delayed.
To decide how many delays are necessary, here is the list of input forms that cause characters to be interpreted:
Note that characters are not
interpreted when buffers are read from or written to files,
or moved or copied with the m
or t
commands.
Experience is a great help here, so let’s look at some examples:
but:
appends \B
to all lines with xxxx
;
the extra c
is because the command is in a global command string.
Let’s say we want to change all the \n
-s to be \n\t
.
There are two ways:
,s/\n/\n\t/
" equivalent to
,s/\cn/\cn\ct/
" or
g/\n/ s/\n/\n\t/
No delays are necessary because \n
and \t
are not special characters,
but delaying them once makes no difference:
the \cn
just becomes \n
, anyway.
(Warning: \n
has special meaning in the replacement text of a
substitution in U. of T. Ed.)
While we’re dealing with globals, it is a good time to introduce the \N
special character.
It means, simply, a newline,
and is useful primarily because we can delay it in the usual way.
Commands, such as r
, which deal with filenames, must often
be followed by a newline, but can be dealt with using \N
in globals.
The Ed sequence
g/xxxx/ r\
.=
can be put all on one line in Qed:
g/xxxx/ r\cN .=
The newline is delayed. In original version 6 Ed, it is impossible to globally substitute a newline into lines, but it’s straightforward (by Qed standards!) in Qed:
g/xxxx/ s//\\cN/p
The \\cN
is a backslash followed by a delayed newline.
The \cN
becomes \N
when scanned by the global g
command,
and then becomes a newline when (re-)read for each s
substitution.
In Qed (and U. of T. Ed) we could also do this
by the functionally slightly different
g/xxxx/ s//\\
/p
[Do you see the difference?].
Backslashes in general are handled more reasonably in Qed
than in other UNIX programs.
Because special characters are delayed rather than quoted,
the number of characters required to insert a special character,
with interpretation delayed n
times,
is just n+2
or n+3
,
rather than exponential in n
.
A troff
line with 31 backslashes, a not-unheard-of
occurrence,
would in Qed have a single backslash followed by five c
characters.
(And would be much easier to understand, text edit, and debug!)
In particular, Qed handles backslashes differently from Ed. As mentioned earlier, the Ed command
s2/"/\\n"/p
is simply
s2/"/\n"/p
in Qed, because \n
is not a special character.
There are, however, characters which are not “special”
in the sense
we are using here, but are “magic” in
that they have non-literal meaning.
The most obvious are characters such as .
and $
in regular
expressions, which must be quoted with a backslash to remove
their special meaning and make them literal.
(It becomes clear after using Qed, or even Ed, for a while that
all
the magic characters in regular expressions and the like
should require a backslash to become
“magic”,
rather than literal,
but the current choice is too wired-in to the minds of
most Ed users to be changed now.)
Because they are not special characters,
their interpretation need not be delayed:
they only mean something to the s
substitute command.
None of the magic characters in the substitution
s/\(\.*\)xxx$/\1/
require delaying when typed in or run from a global command:
g/xyz/ s/\(\.*\)xxx$/\1/
Because of these magic characters, two backslashes in a row \\
mean a single backslash \
in regular expressions;
otherwise it would be impossible to substitute
in a real backslash before a magic character:
a abc xyz def
s/xyz/\\&/p
abc \xyz def
up
abc xyz def
s/xyz/\\\&/p
abc \& def
What about sequences like \\B
?
Well, \B
is not a character at all,
but a special character (sorry for the terminology)
since it is
immediately,
at the lowest level
of input, replaced by the current buffer name.
Since \\
is not a special character,
and has non-literal meaning only when found between
regular expression delimiters,
the substitute command itself never sees the second backslash.
All interpretation of special characters is done
before the substitute command sees them.
If the current buffer is buffer a
, then
s/\\B/x/
does exactly the same thing as
s/\a/x/
Also, because Qed next converts \\
to \
in
regular expressions,
s2/"/\\n"/p
is the same as
s2/"/\n"/p
since \n
is not a special character.
Qed saves the last used regular expression and replacement text
used in an s
or j
command,
so that they can be called back using \p
(for pattern)
and \r
(for righthand side).
\p
is handy when you want to change the saved pattern.
If, for example, you start searching for proc()
and want the declaration, but find there are very many usages of proc()
,
it is simple to find an occurrence of proc()
at the beginning
of a line:
/proc()/
x=proc();
//
x=proc()*2;
/^\p/
proc(){
\p
is of somewhat limited usefulness, as the null regular expression //
is essentially the same as /\p/
;
but \r
provides a new convenience.
Browsing through text doing repetitive substitution
is simplified considerably by using \r
:
s/apples/mangos and pears/p
I ain't got no mangos and pears
//
your mother's apples smelled like they were
s//\r/p
your mother's mangos and pears smelled like they were
There is a danger with \p
and \r
:
if they contain delayed special characters,
each usage of \p
or \r
removes one delay.
If the current file name is wylbur.ms
,
it may be difficult to deal with troff
font changes:
p
editors such as Wylbur are so
s/Wylbur/\cfBWylbur\cfP/p
editors such as \fBWylbur\fP are so
//
Wylbur is also no good for
s//\r/p
wylbur.msBWylburwylbur.msP is also no good for
" Oops
This is the sort of trouble which the \'
special character can
circumvent.
\'r
means the usual \r
, but with special characters inside
uninterpreted.
If we had used it above, things would have worked properly:
p
editors such as Wylbur are so
s/Wylbur/\cfBWylbur\cfP/p
editors such as \fBWylbur\fP are so
//
Wylbur is also no good for
s//\'r/p
\fBWylbur\fP is also no good for
" Much better
\r
is also handy for fixing a certain class of mistakes:
p
textp=get(a->text.fdes);
s/text/tbuf/p
tbufp=get(a->text.fdes);
" Oops again
textp=get(a->tbuf.fdes);
" Got it!
The Qed idiom us2//\'r/p
undoes (u
) what you just did wrong, then substitutes (s
) again (//
) on the second (2
) match in the line.
Now, as an exercise, use Qed for a while until you feel comfortable with the use of backslashes. If you find them confusing, work with Qed, doing fancy things if you feel up to it, until the confusion disappears. What follows will be much stranger …
5. Special Characters (3)
Now that we’ve established the ground rules, we can begin to use some of the fancier stuff in Qed.
The special character \l
(for line) returns a line of text from
standard input,
usually the user at the terminal. In other words,
if, say, input is coming from a buffer, then the input will be temporarily
redirected to come from the terminal.
The terminating newline is stripped away.
Since it is interpreted immediately (being a special character),
\l
is rarely of value
except when delayed. Nevertheless, let’s look at how it behaves
in immediate mode:
""\lMessage\N
Message
""\lMessage
Message
""\lMessage
s of words
Messages of words
The extra newline, whether provided by the \N
, or by a typed second
carriage return, is necessary because the \l
strips its
terminating newline away, but the comment (""
) command is looking
for a newline itself in order to terminate.
[Some questions to consider:
If \bx
is used instead of \l
,
the second newline is not required. Why?
In the last example above,
which characters are returned by \l
?
What is the origin of the others, if any?
What would the above examples do if
the comments were terminated with a double quote?]
Well, \l
is clearly of little use if not delayed,
but it is important to understand how it behaves.
An early version of U. of T. Qed had only lower case buffer names,
and when the names {
through ~
were added it was necessary
to go through the manual changing some of the instances of `z'
into
`~'
, but not all of them.
The following single line made the job very simple:
g/`z'/ p ""replacement:" s//`\cl'/p
Each line (g
) with a `z'
(/`z'/
) is printed (p
),
the user is prompted for the
replacement (""replacement:"
), and the response (\l
) — either a z
or a ~
in our case — is substituted (s//`\cl'/
).
The single delay ensures that g
places a literal \l
in the substitution string,
which is then interpreted when each call to the substitute command
builds its replacement (right-hand side) text.
This sort of operation can also be performed using an x
command
driven by a global, but Qed can be programmed
to do most of the work.
Here’s another example:
bz ,d
ap ""Comment:\cl" s|$| /* \cl */|p
""Comment:\l" s|$| /* \l */|p
On any line which invokes this buffer with a \bz
, the first \l
in the comment will “eat” any input remaining
on the line after the \bz
(and inserts it into the comment command (""
)
where the first \l
appears, namely after the :
. Perhaps not very useful!).
ba a c.code;
\bz
Comment:stylish (1)
c.code; /* stylish */
a more.code;
\bz foobar
Comment:foobarstylish (2)
more.code; /* stylish */
-
Prompt is
Comment:
, user typesstylish
-
Prompt is
Comment:foobar
(sic!), user typesstylish
(Also, of course, if what the user typed at the prompt contains the character
|
, problems will occur.)
Now, if we intend to be able to type our C comment text
on the same line as the invocation of the buffer,
as if passing it as an argument,
we want neither the Comment:
message nor
the extra \l
which clears the input line. Assuming buffer z
still contains
our commenting program, let’s edit out the Comment:\l
prompt, and
try it again:
bz s/".*" //p
s|$| /* \l */|p
ba a yetmore.code;
\bzstylish
yetmore.code; /* stylish */
This latter form is likely more useful,
as it can be called from a global
(the previous version could, but required the user
to type extra newlines).
For example, to comment all occurrences of a variable named var
:
g/\{var\}/ p \cbz
Each line is printed, and the user’s response is appended as a C
comment.
No extra \l
is needed at the end to clear the input line
as the g
already reads the line up to and including the terminal newline.
Thus the single \l
in the s
command in buffer z
returns the next line typed in.
Note that the \bz
command is delayed so that it is interpreted
for each selected line.
We could alternatively have set up our z
buffer so that the \l
itself
was delayed, using \cl
instead.
The buffer could then be invoked (in a global command) as \bz
,
without the delay.
In effect, then, the c
in the original buffer call \cbz
above acts to delay
the \l
. If the buffer had only literal text,
no delay would be necessary.
Our choice of where to put the delay
was made by wanting the buffer to be invocable directly from
the keyboard.
Just for the record,
note that we can achieve the effect of \cb
above by
typing \'b
, although the manner in which it works
is quite different.
Although these examples are somewhat low-key,
they do begin to show how the parts of Qed fit together.
Later, we will see how the \l
can be used
to control execution of commands.
6. Registers
Qed has 56 registers, with the same names as the buffers:
a
to z
, A
to Z
, {
, |
, }
, and ~
.
Buffers and registers are otherwise unrelated.
The registers are used to store simple text and short
command sequences.
In fact, most of the command buffers we have created
so far would be better suited to storage in registers;
buffers are generally used for storage of file text proper
and multiline command sequences.
The two main advantages of using registers to store text
are:
they can be set and manipulated without leaving the current buffer,
and they do not appear in the output from n
commands,
which is significant because a user may typically have
twenty or more defined registers.
Registers are manipulated with the z
(for zdring!) command.
The character after the z
is the name of the register being
operated on,
and the next character is an operation code.
The most straightforward operations are assignment (:
) and printing (p
):
za:procrastination
zap
procrastination
The string being assigned to the register is terminated by a newline.
If a newline is to be embedded in the register, \N
provides
the cleanest mechanism:
za:line1\cNline2
zap
line1\Nline2
a
\za
.
-,p
line1
line2
As a cconvenience, Qed also allows multi-line assignment
to a register by escaping a literal newline with \
:
za:line1\
line2
zap
line1
line2
a
\za
.
-,p
line1
line2
Registers are invoked in the obvious way: \za
inserts the
contents of register a
into the input stream.
Note in the above example that the append (a
) could not be done
on one line,
as the embedded newline in the register would cause the
first line (line1
) of the register to be appended,
and the second (line2
) to be interpreted as (ill-formed) command input:
zap
line1\Nline2
a \za
line1
?za10 ?x
This is another example of embedded newlines causing trouble: be careful!
There are many operation characters for registers;
they are listed in full in the qed(1)
manual section.
For example, we can perform a substitute operation on the
contents of the register with zas/xxx/yyy/
(the same syntax
as for the substitute command s
);
increment and decrement the Unicode codepoint values of the characters
in the register with za+N
and za-N
, where N
is an integer;
and do subzdring (!) operations with the take and drop functions
za)N
and za(N
.
One particularly handy form is
za/regular expression/
which saves in register a
the string in the current line
which matches the regular expression.
There are several other register operations we will introduce
when required.
These operations are quite straightforward; we will see them all used when we start to program Qed.
Registers can also be manipulated numerically. This is indicated
by the #
(for number) operation: za#...
. The text following
the #
is then interpreted specially. Decimal numbers (integers)
stand for themselves, and numeric assignment to the register is :
. So:
za#:42
zap
42
za#:-42
zap
-42
In this numeric-register context, the letters a
, r
,
n
, N
, p
and P
have special meanings. For example p
means
print the current value of the register. We will look at a
and r
shortly.
The rest are described in the qed(1)
manual. We can chain numeric
operations together (without spaces):
za#:42p
42
za#:-42p:99p
-42
99
zap
99
In the last example, -42 is assigned (:-42
) to a
, then the current value
of a
is printed (p
), then 99 is assigned (:99
) to a
, and the
current value of a
is again printed. Register a
contains 99 at the end of the numerical context.
In this numeric context, the value of the register can be updated by
one of the arithmetic operations +
, -
, *
, /
, and %
, which
have their familiar C language meanings:
za#:1
za#+1p
2
za#*2p
4
za#*2+1p
9
Numeric-register context ends at the first character which is invalid
in a numeric-register context, or the first newline, whichever comes first.
When numeric-register context ends, the entire z…
register command
ends, and Qed will start processing any remaining characters on the
line as regular Qed commands. This can have some bizarre side-effects:
ba Z
a foo.bar;
za#:99p
99
za#:99 p
foo.bar;
In za#:99 p
, the space (after the 99
) is not a valid character in
numeric-register context, so it terminates the za#:99
command. The
remainder of the line (` p`) is then interpreted as a regular Qed
command (namely, print the current line), which it duly does.
It is an error (?#
) to try to perform numeric operations on a register
which does not contain a (possibly negative) decimal integer:
za:44moose
za#*2
?#
The main difference between zap
and za#p
is semantic.
In zap
, the contents of register a
are interpreted as
a string, and the (string-)register operation p
prints the
string held in a
. In za#p
, the contents of register a
are interpreted in numeric context, and the numeric-register
operation p
prints the (numeric) value in a
. In practice,
the outcome of zap
and za#p
is the same if a
contains a decimal integer.
Perhaps the most important use of numeric-register operations
is in addressing.
The numeric-operation character a
causes the register to receive
the line number of the address of the z
command:
$za#a
assigns to register a
the line number of the last line ($
), i.e. the
number of lines in the current buffer; and
/xxxx/za#a
saves in za
the address of the first forward occurrence of xxxx
.
The r
operation character (for range)
stores the first given address in the named register,
and the second address in the register whose name is
lexically one greater:
1,$ za#r
,za#r
Both put 1
in register a
and the value of $
in register b
.
Neither a
nor r
changes the value of dot.
These operations are usually used to pass addresses to an execution buffer: if the first line of a buffer is
za#r
then if the buffer is invoked as, say,
-5,.\bz
then registers a
and b
contain the addresses of the first and last lines of
the range to be operated on by the buffer.
Numerical operations are also frequently useful in text editing, such as when generating defined constants for a table:
a
read
write
open
close
creat
.
" capitalize
?read?,.s/.*/^/p
CREAT
za#:0
?READ?,.g/^/s/.*/#define & \cza/p za#+1
#define READ 0
#define WRITE 1
#define OPEN 2
#define CLOSE 3
#define CREAT 4
The ^
(caret) character in the right hand side of a substitute
behaves like &
, but flips the case of ASCII alphabetics in the matched string.
7. Control Structures
The most commonly used control structure in Qed is certainly
the global command, g
, which is remarkably powerful and versatile,
as the previous example demonstrates.
The ability to place several commands on a line,
and the simplicity of \N
, make globals even easier to use in Qed
than in Ed.
Along with the concept of line-by-line execution goes that of
buffer-by-buffer execution, which is provided in Qed
by the globuf commands G
and V
.
They are quite simple to use:
their format is identical to the regular globals g
and v
,
but the regular expression is used to match the
output which would be produced by an f
command in each buffer.
Only buffers which contain text or have a remembered file name
are tested for a match.
If a buffer matches the regular expression, the command list
is executed in that buffer.
For example,
G/.'.* ./w
writes out (w
) all buffers which have been modified since
last written. That is, all buffers which the f
command
reports with a prime ('
) after the buffer name.
The white space in the above example is a tab,
which is the actual delimiter used by the f
(and n
) commands
between the number of lines in the buffer and the file name.
Here’s a fancier example:
G/./ g/thing/ ""\cB \cf: " p
It takes all non-null buffers (G/./
), and
for each one it looks for occurences of thing
(g/thing/
). Whenever
it finds one, it
prints out (""…"
) the buffer (\B
), the file name (\f
), and
line (p
). It’s a bit like a super-grep, or Gregrep.
Qed has a loop control structure, the h
command (for h-until!).
h
, like g
, takes a line of commands and executes it repeatedly.
It has four forms:
Although the loop is an until,
h0 p
is guaranteed to execute zero times. An infinite loop can be halted by sending Ctrl+C from the terminal.
The truth flag is set by substitutions, and comparison operations
in registers.
When a register is compared to some value, the truth flag is
set according to the success of the comparison.
When a substitution is made, the truth flag is set
if a substitution was performed.
As a simple example, say you have prepared a letter to be sent to
someone, using Qed, only to find that the erase character
is a backspace, not #
as you had been using.
To fix the problem,
g/^/ hf s/.#//
Note that huntil-s can be run inside globals, and, in fact, can be nested arbitrarily deep. Globals can also be run from huntils; the only restriction is that globals cannot be called from globals, as Qed can only mark a line for a global once. Similarly, globufs cannot be called from globufs.
As in globals, huntils stop the scan of the command sequence
at the first newline.
To build an alphabet in register A
:
za:a
zA:
h26 zAs/$/\cza/ za+1
zAp
abcdefghijklmnopqrstuvwxyz
Note that Qed code is not always easy to read!
If you happen to know that the character which precedes a
in Unicode
is back-quote (`
), you could also build the alphabet with:
za:`
zA:
h26 za+1 zAs/$/\cza/
zAp
abcdefghijklmnopqrstuvwxyz
And now, register a
could also be used in auto-increment mode to simplify
things further:
za:`
zA:
h26 zAs/$/\cz+a/
zAp
abcdefghijklmnopqrstuvwxyz
The +
between the special character \z
and the a
in the register call causes the
values of the Unicode codepoints of the character(s) in a
to be incremented
before being placed in the input stream:
za:moose
""\z+a
npptf
Auto-decrements are also possible (\z-a
) as are numerical
increments and decrements (\z#+a
and \z#-a
).
Only auto-increments of +1 and auto-decrements of -1 are possible.
As a less frivolous example (one that was used in writing an earlier
version of this
tutorial), a huntil makes it simple to convert, say, the troff
command .ul 5
to five .ul
-s, one after each affected line:
g/^\.ul [0-9]+/ zn/[0-9]+/ zn#-1 s/ [0-9]+// h\czn +a .ul
It looks horrible, but it works, and can save much trouble
if there are (as in the tutorial) twenty or more places
where the fix needs to be made.
(The +
character in regular expressions is like *
,
but guarantees at least one match.)
Of course, until familiarity with Qed is developed,
the mental effort required to write a line like this and have
it work is probably considerably greater than the
physical effort required to type in the changes individually.
Even for beginning users, though, saving the complicated
patterns, and commands such as ap .ul
in registers
would make the job much more pleasant.
Again, care must be taken when invoking registers or buffers in huntil-s:
h20 \bz
" or "
h20 \cbz
will likely not do what is expected if buffer z
contains
more than one line.
The other major new control structure in Qed is the y
command
(for yump;
think of jump pronounced with a Swedish accent).
The syntax is:
y[t|f][N|o|'label|`label]
which translates as follows:
If the t
or f
is present,
jump only if it matches the truth flag;
otherwise jump unconditionally.
The N
, if present, is a number, and is interpreted
as a line number in the current
executing buffer, which becomes the next line read for commands.
An o
(for out) causes the current input source,
such as a global command string or buffer, to be terminated.
If the input source is a buffer, the effect is to return from the buffer;
if a global, the execution of the global (or huntil) is stopped.
For example,
za#:1
h50 za#+1 za#>20 yto
zap
21
executes 21 times, leaving register a
set to 21.
The forms
y[t|f]'label
and
y[t|f]`label
are similar to y[t|f]N
, but the line to which control is transferred
is the first line found which begins with the comment "label
,
searching forward in the buffer in the case of y[t|f]'label
; or
backward in the buffer in the case of y[t|f]`label
(where the
operation character is a back-tick).
Initial blanks and tabs on the line before the comment character "
are ignored, and the scan of the
label stops at the first blank, tab, newline or double quote.
If the first character after the double quote is a space, tab,
newline or double quote, the label is null and can never be matched.
If no matching label is found in the executing buffer,
execution resumes at the first
character after the label in the yump command.
Note that the label must be matched exactly;
it is not interpreted as a regular expression.
There are a few non-trivial small examples which illustrate the use of yumps, but they will be used later on in the tutorial. For the moment, a remark on style: clearly, with only a goto, flow of control in Qed can become messy if care is not taken. It is recommended that yump-s only be used in easily identifiable forms such as if … then … else …:
<condition> yf'else
...
y'fi
"else
...
"fi
and, do … until …
"do
...
<condition> yf`do
"od
or, while … do …
"{
<condition> yf'}
...
y`{
"}
One particularly useful form of labeled yump-s is a switch statement based on a line of input from the user. This mechanism makes command interpretation very simple. It is essentially a fancy switch statement:
y'X\l
"default:
...
yo
"Xcase1
...
yo
"Xcase2
...
yo
...
One other form of yump exists; it is intended primarily to
skip the rest of a global or huntil command sequence, without
stopping the execution completely.
Its form is simply yt
, or yf
.
When invoked, it jumps over the current input source
up to and including the next newline.
It can also be used as a shorthand in buffers,
but such usage is discouraged.
8. Calling the Shell
Qed has three methods of calling the Shell aside from the !
(bang)
command: crunch (<
), zap (>
), and pipe (|
). All of these
commands cause the UNIX commandline they last invoked to be stored in
register U
. This commandline can be recalled by doubling the command
character, as if the command \'zU
was issued:
!echo fun
fun
!
zUp
echo fun
!!
fun
!
You can even extend the saved commandline:
!! | wc -c
4
!
zUp
echo fun | wc -c
And edit it, just like any other register:
zUs/fun/funny/
!!
6
!
zUp
echo funny | wc -c
Crunch (<
) takes the standard output from the Shell command
and reads it into the current buffer,
as if the output from the
Shell command had been redirected to a temporary file,
which is then read in with an r
command.
Like the r
command, <
takes an optional address which
specifies the line (defaulting to $
) at which
the text is to be read in.
(As above, the UNIX command last executed
can be reinvoked, as a crunch, with <<
.)
< ls
!
appends a list of the files in the current directory to the end of the buffer.
One very common usage of the crunch command is to create a to-do list, by a command such as
< grep "FIX ME!" *.c
!
or using a buffer as a sort of checklist by capturing diagnostic output from a compiler, say:
bz Z
< cc -c *.c | tee /dev/tty
... diagnostic messages ...
!
saves the listing of the compile errors so you can let cc
run through everything before fixing typing mistakes, etc.
Zap (>
) is to crunch as w
is to r
: it writes out the contents
of the addressed lines (defaulting to the entire current buffer)
as standard input to the Shell command.
It is can be used to send e-mail.
The e-mail can be prepared in a buffer, edited as desired,
and then sent easily by
> mail joe
!
or even
> nroff | mail joe
!
Zap and crunch work nicely together.
We can perform an interactive file-delete function,
(like the ancient dsw
command) using crunch
to read the files in,
modifying the list as appropriate,
and sending it out to (another ancient command) args
:
< ls
!
... editing commands ...
> args rm
!
(args
was a command that took each line on its standard input
and made it an argument to the command,
which was then exec
-ed in the normal manner.)
The following commands can initiate the construction of
a dependency-list file for make
:
<grep "#include" *.c
!
,s/:#include[ ]"/ //
,s/"$//p
utfio.c qed.h
At U. of T., the Shell takes a -e
option which tells it to
echo on the diagnostic output
the commands it is executing, which works nicely with zap:
a
command1
command2
command3
.
> sh -e
% command1
% command2
% command3
!
In short, the crunch and zap commands are used very frequently.
The pipe command (|
) is very similar to crunch. Whereas
crunch takes a single address, and inserts the standard output
of the commandline at that address,
pipe takes an address range, and it replaces
the addressed range with the standard output of its commandline.
The default address range for pipe is the entire buffer (1,$
),
so the command < ls
will append a directory listing to the current
buffer, but the command | ls
will replace the contents of the
buffer with the directory listing.
9. Programming (1)
Now that we’ve seen all the primitives,
we can begin using buffers and registers to build
more sophisticated commands.
The first step is to
assemble a few useful command sequences in
registers.
Harking back to our function-declaration-finding
buffer in Section 3, define register f
(for function):
zf:?^[a-zA-Z_].*(?
zfp
?^[a-zA-Z_].*(?
As a global search, the regular expression /^[a-zA-Z_].*(/
found
all function declarations, provided, of course,
that the usual paragraphing style is used.
Now, with the regular expression enclosed in ?…?
,
register f
finds the first previous
function declaration.
This seems like an odd concept at first, but works well.
For example,
to see which function’s source is being browsed:
\zf
function(x)
Or, to find the declaration of a local variable:
p
variable=0;
\zf/variable/
int variable;
(This works by searching back to the closest previous function definition
using the saved command (\zf
) and then searching forward for the
first occurence of the name variable
with /variable/
.)
To print out to the line printer the listing of the function currently being browsed:
\zf, /^}/ > lpr
There are fancier things, too.
If we want to know which subroutines call proc()
,
we can again use \zf
:
g/proc()/\zf
func1(x)
func2(y)
func3()
After using macros like \zf
for a while,
they become familiar to the point
that they become idiomatic, a part of the Qed language.
To help the user develop a personal working environment,
Qed provides
a simple mechanism for initializing.
Typing
(to the Shell)
$ qed -x qfile file1 file2
causes Qed to load the file named qfile
into buffer ~
(tilde)
and execute it, before reading in the
files to be edited (file1
, file2
), and beginning the normal editing session.
Typically, the startup file is used to initialize options
and registers; it might contain something like:
""Qed
zc:s|$| /* \cl */|p
zf:?^[a-zA-Z_].*(?
b~Z " destroy buffer after execution
which prints a message (Qed
);
defines a couple of handy registers (zc
and zf
);
and obliterates itself (b~Z
).
If no -x
option is given when Qed is invoked,
Qed will try to load the file named by the (Shell) environment variable
QEDFILE
into register ~
, and run that instead. If the variable
QEDFILE
is undefined then no startup file is loaded.
Browsing through the startup files of a few experienced Qed hacks, a few interesting things come to light. One simple but rather pretty option was
ob""\cx1a"+p
Character \x1A
was a reverse line-feed on most of the U. of T. terminals;
The browse option (ob
) defines a special register
which is executed, if defined, when a simple newline
is typed at the terminal, rather than doing the default +p
(print next line).
Printing a reverse line-feed before the +p
means
that no empty lines appear on the screen when browsing through
text.
On a modern terminal the equivalent ANSI escape sequence version would be:
ob""\cx1b[F"+p
It is sometimes useful to set the browse register to
something like +b
for easy paging through text,
or to P
or L
, which cause the line to
be displayed in the format of p
or l
, but
with line numbers at the beginning of the line:
22i Line 22
p
Line 22
l
Line\t22
P
22 Line 22
L
22 Line\t22
These other display formats are also sometimes handy in global searches:
g/proc()/ \zf P
104 func1(x)
118 func2(y)
221 func3()
Another nice register to have tucked away (as it is above) is the C-commenting command we saw earlier:
zc:s@$@ /* \cl */@ p
We can call it up when desired:
p
bizarre();
\zc(A Kludge)
bizarre(); /* (A Kludge) */
g/xxxxx/ p \czc
yyy xxxxx yyy
needles
yyy xxxxx yyy /* needles */
zzz xxxxx zzz
haystacks
zzz xxxxx zzz /* haystacks */
...
The following register definition allows the user to find a buffer by its file name:
zb:G/\cl/ f\cN
\zbfile
g'.34 file.c
We don’t even need to type the suffix .c
!
Here is a rather complicated, but conceptually simple,
register, \zs
(for search), which globally searches
for a pattern in all the buffers from a
through z
,
and leaves dot at the last occurrence found.
For readability, from here on, we will usually list the contents
of a register with real newlines in place of the \N
-s, or the
escaped newlines, and without the delays to special characters, that would
be necessary when actually assigning the program to a register. Compare what
you would have to type at the keyboard to assign this program to
register s
, with the listing that follows!
zs:zB:\cB\
zP:\cl\
zI:`\
h26 zI+1 b\cczI $zD#a=0 yt g/\czP/ ""\ccB:" P zB:\ccB\
b\czB
Or worse (this is all on one line!):
zs:zB:\cB\cN zP:\cl\cN zI:`\cN h26 zI+1 b\cczI $zD#a=0 yt g/\czP/ ""\ccB:" P zB:\ccB\cN b\czB
Versus a comparatively sane listing:
zB:\B
zP:\l
zI:`
h26 zI+1 b\czI $zD#a=0 yt g/\zP/ ""\cB:" P zB:\cB
b\zB
But what does all this mean !? One step at a time:
After execution, zB
contains the last buffer name in which a match
was found, and Qed automatically
keeps track of the line number on which the match was found.
The last line of zs
therefore changes back to
buffer \zB
, which leaves dot at the last
line printed, similar to the behaviour of g/xxx/p
.
Got that?
Make sure you understand how the s
register operates,
as it utilizes many of the standard Qed programming techniques,
such as nesting a global inside a huntil.
Well, that was instructive, but rather revolting. If you understood how the search register works, you’re doing very well, but it’s not a good example of how to program Qed, just a pedagogical one. Here’s how to really do it:
G/^[a-z]/ g/\l/ ""\cB:" P
You’ll find as you gain experience that huntil-s are rarely used, but they do have their moments.
Using the register is quite easy;
just type \zs
followed by the pattern being searched for:
\zs^func()
a:86 func()
b:102 func() {
f
b .209 junk.c
Registers can also be used to call the Shell.
Register d
, defined below,
calls pwd
to
get the current directory, saving the result
in register e
,
so that the user can quickly return after changing
working directory.
zd:ovr zB:\cB\cN bX <pwd \cN ze. d b\czB ovs zep zB:
This definition of register d
is also exactly as it would appear
in a startup file.
The ze.
command
puts a copy of the current line in register e
.
The delayed newlines are necessary; unpacked, the string looks like
ovr zB:\B
bX <pwd
ze. d b\zB ovs zep zB:
Briefly: turn verbose mode off (ovr
);
save the current buffer name into register B
(zB:\B
);
change to buffer X
(bX
) and get the directory (<pwd
);
save it in register e
(ze.
);
delete the line from the buffer (d
);
change back to the original buffer (b\zB
);
turn verbose mode back on (ovs
);
print the saved directory (zep
);
and clear register B
(zB:
).
10. Programming (2)
So far, the emphasis has been on using registers as programming elements,
primarily because the size and complexity of the problems
being handled has been small enough that registers are really
the way to deal with them.
Ultimately, though,
more complicated problems arise and it becomes necessary to store
command sequences in buffers.
In light of that, one more register definition, r
for run,
will make using program buffers somewhat simpler.
Called as
\zrbuffer
it reads file buffer.q
, prepended by the search path
stored in register q
, into a scratch buffer, executes it,
and then clears the scratch buffer.
Typically, register q
would be set by the startup buffer to contain
something like /home/rob/q/
so
\zrcommand
runs the buffer in the file /home/rob/q/command.q
.
Register r
is long, but linear, having no loops.
As an unpacked listing, with newlines, it looks like:
zL#r
z{:\l
z|:\B
ovr b{
e \zq\z{.q
ovs b\z| \b{
b{ Z
b\z|
This looks considerably more bizarre than our earlier definitions,
because it follows some conventions that have proven useful.
The registers and buffers with funny names ({
, |
, }
, and ~
)
are (unofficially) reserved as scratch areas: anything you put
in one is not guaranteed to stay there
if you call in an external buffer.
zr
uses registers {
and |
to hold
the program name typed by the user and the buffer the register
was called from, and buffer {
to hold the program.
Qed itself uses register ~
to hold the initialization code
to bootstrap the startup buffer,
but clears it before going to the terminal for input.
(A side effect of this is that your startup buffer can
zap z~
to alter the bootstrap procedure.)
Other conventions are that upper case registers and buffers are reserved for use by program buffers, such as the ones we will be developing in this section; and lower case letters are reserved for the user.
zr
stores in registers L
and M
the addressed
lines for the buffer being called
(via the range operator: zL#r
).
Following these conventions means that a user can call a copy of someone
else’s program buffer, for example, without worrying about
which registers and buffers it uses.
The ovr
and ovs
calls in register r
set the verbose flag
off and on when appropriate, to suppress the occasional
character counts on i/o.
The register as defined here actually works, but what we really want
is something a bit spiffier, so, let’s have \zr
load a
particular buffer file, which we will describe immediately afterwards:
zr:zL#r z}:\cB\cN ovr b~e \czqrun\cN \cb~\cN b\cz}
This loads buffer ~
with the file (say) /home/rob/q/run.q
.
The buffer is then executed, and the user is returned to
the original buffer.
The run.q
buffer file contains:
" Run a qed buffer `off line'
z{:\l
z{C
" the next line puts a space at the end of the register
z{$
" the next line looks for a space in the argument string
z|'{ z{[
z~#c z{)\z~ z|(\z~ z|C
" z{: command z|: argument string z}: return buffer set by zr
b{ e \zq\z{.q
ovs
b\z} \b{
" Note! ok to ZERO buffer ~ (this buffer); the line will finish executing
b{Z b~Z
The zXC
command is kludgey but handy:
it collapses multiple blanks and tabs in the register to single blanks,
and deletes leading blanks.
The first few lines of the buffer put the command and its arguments
(if present)
into registers {
and |
.
The new register commands introduced here are:
Although it’s a little unfair to show these commands
to you this late in the
game, they didn’t really need showing earlier on, and they are
quite simple to master.
The count also hasn’t shown up before,
so we’d best explain it now.
It is a special place, something like the truth,
which gets set to the number of characters transferred
during i/o operations; the number of substitions made during
an s
command; and to other such numbers, as above.
Although rarely used, it, too, has its moments.
The requested buffer is then loaded into buffer }
and executed.
Finally, the loaded buffer and buffer ~
are zeroed,
and run.q
returns.
Although its coding is not particularly pretty,
the power register r
gives us is dramatic.
It is really part of the Qed language,
since it allows the user to store
many command buffers in the file
system, but get at them easily and in a mnemonic fashion.
It itself employs two conventions which are therefore ubiquitous:
registers L
and M
hold the lines being addressed by a buffer call,
and buffers {
and ~
are off-limits to command buffers.
The latter point, of course, shows the weakness of a language
in which all the variables are global,
but let’s ignore that theoretical issue for the moment:
Qed has many other weaknesses which are far more important!
zr
is only useful if we have some buffers to drive with it.
For starters, we can take our search
register and put it in
a buffer (say /home/rob/q/grep.q
):
" Grep for z| (possibly set by caller) in all buffers
z|=
yf'fi
""pattern:" z|:\l
"fi
G/^[a-zA-Z]/ g/\z|/ ""\cB:" P
A few noteworthy points occur: firstly, we can prompt the user for missing arguments. If the user types
\zr grep expr
(notice the blanks, which are deleted by the run
buffer)
we can search for expr
directly; but if no expression is
specified, we just ask for it.
Secondly, putting the code into a buffer means
everything can be delayed one less time, which makes it more
readable,
and the initialization and cleanup code is shared by all command buffers,
providing a clean and uniform interface.
Also, after execution, register r
returns the user to the
buffer they started in, rather than leaving them in some random place.
For this example, it may or may not matter, but in some
cases it is advantageous to return ‘home’.
Here is a new example. It right justifies the addressed lines, something of mild utility, but too special purpose to keep around as a real program. It only takes a couple of minutes, though, to write a Qed buffer to do it, which can then be saved away:
" Right justify addressed lines (default to (1,$))
zL#=\zM yf'fi
1,$zL#r
"fi
" The white space below is a space and a tab
\zL,\zMs/^[ ]*//
\zL,\zMs/[ ]*$//
zW:0
\zL,\zM g/^/ zC#l#<\czW yt zW:\czC
zW#>35 yf zW:35
zD: |
zD)\zW
\zL,\zM s/^/\zD/
" Turn spaces into periods
zD+14
\zL,\zM s/^ ,\(\zD\)$/\1/
zD-14
\zL,\zM s/^\zD//
zL:\N zM:\N zC:\N zD:\N zW:
(Another new command (sorry!): zC#l
sets register C
to the length of the current line.)
This buffer illustrates how command buffers use the (zL,zM)
address pair.
Clearing the registers afterwards is a good practice for program buffers
to follow.
To invoke this program on a suitable buffer full of, say, words
one to a line, we save it away in
/home/rob/q/right.q
and type:
ba " where the data is
,p
excle
ficatings
criminter
con
explasence
des
ofh
fultesibe
shispensitment
dedgearing
expers
" yes, they're random words
\zrright
,p
excle
ficatings
criminter
con
explasence
des
ofh
fultesibe
shispensitment
dedgearing
expers
As the Ronco man would say, “Isn’t that amazing!”
Can we do anything useful with all this power?
Well, we can write a buffer un
(for run or unix)
which pipes the addressed lines out to
a shell command line, and replaces them in the buffer
with the output of the command. This functionality is now largely provided
by the pipe command (|
), but creating an implementation of pipe
in pure Qed, is itself instructive:
" un.q -- replace addressed lines of current buffer by result
" of passing them through pipeline
" Looks in z| for pipeline; if empty, prompts & reads from terminal
" Called as addr1, addr2 \zrun; defaults to (1,$).
z|=
yf'fi
""<> "
z|:\l
"fi
zL#=\zM yf 1,$zL#r
ovr
\zL,\zM > \z| > /tmp/qed
zT#t " zT gets return status from truth
\zMr /tmp/qed
!rm /tmp/qed
ovs
zT#=0 yf'else
""Invalid status return - lines not deleted
y'fi
"else
\zL,\zMd
"fi
zL:\NzM:\NzT:
""!\N
The prompt is reminiscent of crunch-zap.
The yf’else
tests the return status of the
command, and decides not to delete the original lines
if the status was bad (i.e. non-zero).
Using the \zrun
(run buffer un.q
) combination,
we can process the data in a buffer
through any arbitrary pipeline, such as
,p
excle
ficatings
criminter
con
explasence
des
ofh
fultesibe
shispensitment
dedgearing
expers
\zrun sort
!
,p
con
criminter
dedgearing
des
excle
expers
explasence
ficatings
fultesibe
ofh
shispensitment
To send out only a portion of the buffer to the pipeline, the usual convention is used:
.,/ful/ \zrun sort
11. Final Comments
Qed is a large system, but its concepts are, for the most part, simple extensions from those of Ed. Although it provides no new functionality in UNIX, it can greatly simplify many text-manipulation tasks, ranging from day-to-day editing problems to production-level text processing. By striking a harmonious balance between Qed and UNIX's other tools, the intelligent user will find Qed powerful, flexible, easy to master, and fun!
12. Editor’s Notes
12.1. Remarks
The Tutorial really does assume fluency in Ed. An updated Tutorial for a modern audience should certainly begin with an introduction to the line-oriented editing paradigm, and the basic Ed-like functionality in Qed.
Some of the original examples are pretty anachronistic, and would
have seemed less exotic to someone sitting at a U. of T. terminal
back in the early '80s. An updated Tutorial should choose
examples which would seem familiar to today’s audience. Perhaps
some programs for doing common git
tasks.
The section on Registers needed a major overhaul, as the mini-languages used in register and numeric-register operations had changed significantly.
There were surprisingly few typos in the original, quite a feat considering that many of the examples had Qed code interspersed with troff code!
12.2. History
Originally written by Robert Pike at U. of T. in 1992.
Converted to asciidoc
, edited,
and updated by Sean Jensen in February 2021.
12.3. Resources
Rob Pike’s original U. of T. Qed tarball: https://github.com/arnoldrobbins/qed-archive/unix-1992
Sean Jensen’s port of Qed, including this tutorial: https://github.com/phonologus/QED