TECO Editor: EMACS, I am your father
For the first time about TECO I read in a parody article Real Programmers Don't Use Pascal , written shortly before my birth. It was written there that real programmers do not use the newfangled editors EMACS and VI:
Нет, Настоящий Программист хочет редактор вида «Просил? Так получай!» — сложный, загадочный, мощный, не прощающий ошибок, опасный. TECO, если быть точным.
ОригиналNo, the Real Programmer wants a `you asked for it, you got it' text editor — complicated, cryptic, powerful, unforgiving, dangerous. TECO, to be precise.
It intrigued me. What kind of animal is this, can it be touched? Wikipedia said that TECO - is T the ext E ditor & of CO rrector, he created in 1962 and was used in the DEC PDP on the family computer, and later on OpenVMS systems. It turned out that there is a port in C , which is maintained by enthusiasts in an up-to-date state and is assembled under modern operating systems. So I decided to feel like a real programmer at least a little bit.
Compilation under Linux did not cause any difficulties (you just need to install libncurses-dev). And so we launch
and see the powerful editor interface:
Yes, one star. This is an invitation to enter commands. It seems that you can not do without instructions. The good news is that the manual is easy to find on the Internet. This is a nearly 300-page book called Standard TECO Text Editor and Corrector for the VAX, PDP-11, PDP-10, and PDP-8. Here you can read in PDF the latest version of 1990, at which time TECO was almost forgotten by everyone.
To begin with, it is worth figuring out how to get out of this miracle. It turns out that you need to enter
, and then press Escape two times. In general, double-clicking Escape leads to the execution of the entered command. The Enter key is used simply as a line feed. The Escape is displayed as a dollar on the screen, but entering a dollar will not give you the Escape effect. However, further if I write
, it is implied that I must press Escape. So we exit with
(while the files are saved). Well, we learned to enter and exit - half the job is done.
In general, the TECO command format is something like this:
[[<число1>,] <число2>] [:] <команда> [<текст1> [ $ <текст2> ] $ ]
It is rather unusual that arguments can go both on the left and on the right. However, it’s better to take it differently: the numbers on the left are not part of the team, but another team that returns a number (or a pair of numbers) as a result. In this case, the subsequent command can use the result of the previous one.
Escapes are inconvenient, because if you copy and paste it, you’ll just get a dollar, which is not perceived as a separator. Fortunately, there is an alternative syntax for commands with a text argument:,
@ <команда> <разделитель> <текст> <разделитель>
where the separator is any character. For example, you can insert the text 'Habr' at the current cursor position with
(I, text, Escape - doesn’t resemble anything?), But you can with
. To simplify my life, I switched to the second syntax.
Perfectly. It would be nice to learn how to enter some text into a file. To specify the output file, the command is used
, and the input file is the command
. In a good way, they should not match. It turns out that in those days, multi-page presentation of text files with pagination using a symbol
or form-feed was popular
. This is a character with code 12 (0xC), which was entered via Ctrl + L (L is the twelfth letter of the English alphabet). This not only instructed the printer to spit out the page and start a new one, but also made it possible to edit a long file with modest amounts of RAM. TECO loads only one page of the input file into memory (up to the character
) and allows you to edit it, write to the output file and go to the next. There are also operations for gluing and splitting pages. If the file is the same, it is clear that nothing good will come of it. Well, we won’t play with the pages, these days it’s absolutely wild. Our files will be single page. So, create a file from scratch:
*@EW/habr.txt/$$ *@I/Hello, Habrahabr! This is TECO, the most powerful editor in the world. Stop using your fancy IDEs, TECO for the win! Bye. /$$ *EX$$
It worked, a file
with our text really appeared
. I completed each command with two Escape, but actually this is not necessary. Well, but how can I edit an existing file? Reluctance to invent a new name every time. To do this, there is a special command
that, when you first record, renames the input file to
, thus observing that the input and output files are different. After opening the file, do not forget to flip to the first page (command
), and then you can display the entire contents of the file with the command
*@EB/habr.txt/YHT$$ Hello, Habrahabr! This is TECO, the most powerful editor in the world. Stop using your fancy IDEs, TECO for the win! Bye. *
these are two teams. The command
returns a pair of numbers - 0 and the length of the text buffer, that is, the page that is now in memory. And the command
prints a fragment of the file in a given range of offsets:
*0,5T$$ Hello*7,17T$$ Habrahabr!*
Yes, no one will add the line feed again, they asked for five characters - get it. But everything is clear and clear. If a command is
passed only one number to the input, it is interpreted as the number of lines to be printed, counting from the cursor position forward or backward. Moreover, if the cursor is in the middle of the line, it
prints a fragment from the beginning of the current line to the cursor, but simply
without parameters - from the cursor to the end of the line:
*5J$$ *0T$$ Hello*T$$ , Habrahabr! *
that we used above moves the cursor to the specified absolute offset (the cursor is always located between characters). Well, since it’s ugly to look, I would like to see this very cursor.
Is it just print a wand? Yes you can. The print command is this
(you can enter Ctrl + A, or you can directly tick and the letter A in turn):
*0T@^A/|/T$$ Hello|, Habrahabr! *
Hooray, we can now see the cursor position. It would be nice to record this command and, if necessary, execute it. If you immediately type a
letter or a number after the command is executed
, the text of the previous command will be written in the corresponding Q-register (I still have not figured out why it is not just a register, but a Q-register). Naberom example
. Now the contents of the register
can be executed as a command using the command
*MZ$$ Hello|, Habrahabr! *
Well, we recorded the first TECO macro. Well, running through positions is boring, it would be nice to be able to look for something. Let's look for, for example, the word IDE. The help says that there is a command for this
So what? Didn’t betray anything. Found or not found? And if found, then where? Yes, interactive editors are corrupting. If I didn’t give anything, then I found it. TECO simply moved the cursor after the text found. Let's repeat and immediately draw a line with the cursor:
*@S/IDE/MZ$$ ?SRH Search failure "IDE"
Oh, and now what? Yeah, he is looking for something from the current cursor position, but there was no second IDE. You must first go to the beginning of the file (you can simply
*J@S/IDE/MZ$$ Stop using your fancy IDE|s, TECO for the win!
In, beauty. What about highlighting the text found on both sides? Then I had to prettyly rummage the documentation. Such things came in handy:
^S- returns the length of the result of the last search or the last insertion (with a minus sign to make it more fun)
.- point returns the current cursor position in the buffer.
C- moves the cursor to the right by the number of characters returned by the previous command (there is also the opposite command, which goes to the left, -
Accordingly, with the help
you can go to the beginning of the found line, then the familiar one will
print the prefix, then print it
. Next, you need to print the fragment from position
(remember that it
is a negative number). Then print
, return the cursor to the place with
and print the rest of the line with
. Here is the whole program:
*^SC0T@^A/[/.,.-^ST@^A/]/-^SCT$$ Stop using your fancy [IDE]s, TECO for the win! *
Alright, we already started doing Pearl. It's time for the following quote from an article about real programmers:
Замечено, что последовательность команд TECO скорее напоминает передачу шума, чем читаемый текст. Весёлое развлечение — набрать в TECO своё имя как команду и попытаться угадать, что произойдёт. Практически любая опечатка при разговоре с TECO может уничтожить вашу программу или, что ещё хуже, внести неуловимый и таинственный баг в когда-то рабочую процедуру.
ОригиналIt has been observed that a TECO command sequence more closely resembles transmission line noise than readable text. One of the more entertaining games to play with TECO is to type your name in as a command line and try to guess what it does. Just about any possible typing error while talking with TECO will probably destroy your program, or even worse — introduce subtle and mysterious bugs in a once working subroutine.
By the way, some similarity of regular expressions in TECO is also available. For example, it
. If you don't like regular regular expressions,
work a little at TECO
. After that you will love.
Now I would like some control structures. Let's say this problem: assuming that the cursor is at the beginning of a line, take the text of the line in a beautiful frame. By the way, move down the lines - the command
, and up -
. You can also press Ctrl + H and Ctrl + J to quickly execute commands
and run through the text back and forth.
For this task, we need to insert as many minus signs as there are letters in the current line. How to measure it? You can call
twice, before and after
, and calculate the difference. Writing and reading numbers in Q-registers comes in handy (the number and text are stored in the Q-register with the same name independently).
writes a number to the register
- reads it. A simple loop at n iterations is this
. For example, if we want to insert a minus sign
once, we will write
. The whole macro will look like this:
.UAL.-QA-2UA-L@I/+/QA<@I/-/>@I/+ |/L2R@I/| +/QA<@I/-/>@I/+ /-LC
Perhaps for someone the most incomprehensible thing in this macro is -2. And then later
. It's very simple: newline in those days always held two characters
. There was no disagreement; it was wonderful. We need to subtract it from the difference in the coordinates of the beginning of the lines, and to draw the right frame we need to go two characters to the left.
Save this macro to register
. Incidentally, this can be done not only through
after executing the command, and a command
line entry in the register:
. Let's execute it, being at the beginning of the buffer, and we will receive:
*MY$$ *HT$$ +-----------------+ |Hello, Habrahabr!| +-----------------+ This is TECO, the most powerful editor in the world. Stop using your fancy IDEs, TECO for the win! Bye. *
Super! Variables, loops - this is already like real programming. You can go for an interview for the position of Senior TECO Developer. Speaking of interviews. Let's write a FizzBuzz macro on TECO. I don’t know if anyone did this before me.
Here division with the remainder by 15 would be useful, but the operation of division with the remainder, unfortunately, is not. But there is a division entirely, therefore it can be expressed through
. True, there are no priorities for operations either, so I have to write
. Next, depending on the remainder, you need to do different things. If the remainder is 0, print
if 3, 6, 9 or 12, then
if 5 or 10, then
, otherwise, the input number. A command is useful for this
. Without a numerical argument, this is an unconditional jump (i.e. GOTO), and with a numerical argument it is like a switch:
jumps to the nth mark, separated by commas, if any. Tags look like
(comments are also written - this is just a label that no one jumps on). Create labels
(for Buzz) and
(for FizzBuzz), as well as the
end for jumping out, a la
. Here is the whole macro, including the command to write it to the Q-register
*^UF UA-QA/15*15+QA@O/fb,,,f,,b,f,,,f,b,,f/QA=@O/e/!fb!@^A/Fizz/!b!@^A/Buzz /@O/e/!f!@^A/Fizz /!e!$$
I pass the line feed to the command in
order to display it on the screen. Imagine, in Java there are still no multiline literals, but in TECO they were already more than half a century ago! I also saved a bit by doing “fallthrough” after the branch
and typing “FizzBuzz” from two halves. Just like in a regular switch-case statement.
Interestingly, the macro begins with
: write a
in the register
. And what number? Very simple - the argument of this macro itself. It must be specified just before the call. We check:
*4MF$$ 4 *5MF$$ Buzz *105MF$$ FizzBuzz *87MF$$ Fizz *44MF$$ 44 *
Great, we went through an interview! My last experiment was required to make KDPV for this article. How to write a program that displays the letters you need? It seems that there are no arrays and data structures in the language. But there is a text buffer! I drove the character generator there in the form
<символ><первая строка битовой маски><символ><вторая строка битовой маски>...
will clear the current contents of the buffer):
You can position yourself on the desired number with
, because the numerical parameter S allows you to find the nth occurrence of a given string. I did not find how to execute a command parameterized by an arbitrary text parameter, so I formed a command in the Q-register D (
appends to the end, but simply
replaces the text in the Q-register) and executed it as a macro. You can then parse the number using the backslash command
. We also need conditional operators:
- execute if the numeric argument is not zero, but
- execute if the numeric argument is zero. A branch “otherwise” can also be created by separating it with a pipe. Thus, the conclusion of a space or a lattice is done through
prints a character with the appropriate ASCII code. Another useful command
that increases the numerical value in the corresponding Q-register by one.
I used registers like this (in order not to get confused, each was used only for a number or for a string):
- A - current line of the current letter in the form of a bit mask
- B - 2 ^ N, where N is the current bit
- C - line to be printed, all characters must be in the character generator
- D - auto-generated macro to search for a bit mask in the character generator
- E - current letter counter
- F - row counter (1-6)
@^UC/HABRAHABR/1UF!bl!0UE!bm!J@^UD/@S|/QEQC:^UD@:^UD/|/QFMD$ \UAC128UB!br!QB&QA"E32^T|35^T'QB/2UBQB"N@O/br/'%E^[QE-:QC"N@O/bm/'@^A/ /%F^[QF-6"N@O/bl/'$$
Interestingly, there are commands for reading a character from the keyboard, and therefore the macro can be made interactive. Using the ability to display terminals through Escape sequences, it is easy to position the cursor on the screen, update text with fragments and switch colors. Thus, using a macro, you can process each input character or special key and make an interactive editing mode. In approximately this way, Emacs was born, which was originally really a macro for TECO, and only then was rewritten as a separate application.