In my oh-so-abundant free time, I’ve been working on my own little text editor. And one of my motivations is TECO: one of the oldest, and one of the very best, ever written. It’s both a text editor and a programming language – and, in fact, that’s exactly what made it such a brilliant tool. So much of the drudgery of programming is stuff that really could be done by a program. But we’ve spent so much time learning to be fancy that we’ve lost track of that. Nowadays, you can write an emacs lisp program to do the stuff you used to do in TECO; only it’s awkward enough that you usually don’t.

The problem, though, with just re-implementing TECO with a modern UI is that it was designed in a different time. When TECO was written, every byte was critical. And so the language, the syntax, the structure, it was completely ridiculous. And, as a result, it became the world’s most useful pathological programming language. It’s a glorious, hideous, wonderful, horrific piece of computing history

TECO is one of the most influential pieces of software ever written. If, by chance, you’ve ever heard of a little editor called “emacs”; well, that was originally a set of editor macros for TECO (EMACS = Editor MACroS). As a language, it’s both wonderful and awful. On the good side, The central concept of the language is wonderful: it’s a powerful language for processing text, which works by basically repeatedly finding text that matches some kind of pattern, taking some kind of action when it finds it, and then selecting the next pattern to look for. That’s a very natural, easy to understand way of writing programs to do text processing. On the bad side, it’s got the most god-awful hideous syntax ever imagined.

History

TECO deserves a discussion of its history – it’s history is basically the history of how programmers’ editors developed. This is a very short version of it, but it’s good enough for this post.

In the early days, PDP computers used a paper tape for entering programs. (Mainframes mostly used punched cards; minis like the PDPs used paper tape). The big problem with paper tape is that if there’s an error, you need to either create a *whole new tape* containing the correction, or carefully cut and splice the tape together with new segments to create a new tape (and splicing was very error prone).

This was bad. And so, TECO was born. TECO was the “Tape Editor and COrrector”. It was a turing complete programming language in which you could write programs to make your corrections. So you’d feed the TECO program in to the computer first, and then feed the original tape (with errors) into the machine; the TECO program would do the edits you specified, and then you’d feed the program to the compiler. It needed to be Turing complete, because you were writing a program to find the stuff that needed to be changed.

A language designed to live in the paper-tape world had to have some major constraints. First, paper tape is slow. Really slow. And punching tape is a miserable process. So you really wanted to keep things as short as possible. So the syntax of TECO is, to put it mildly, absolutely mind-boggling. Every character is a command. And I don’t mean “every punctuation character”, or “every letter”. Every character is a command. Letters, numbers, punctuation, line feeds, control characters… Everything.

But despite the utterly cryptic nature of it, it was good. It was very good. So when people started to use interactive teletypes (at 110 baud), they still wanted to use TECO. And so it evolved. But that basic tape-based syntax remained.

When screen-addressable terminals came along – vt52s and such – suddenly, you could write programs that used cursor control! The idea of a full-screen editor came along. Of course, TECO lovers wanted their full screen editor to be TECO. For Vaxes, one of the very first full screen editors was a version of TECO that displayed a screen full of text, and did commands as you typed them; and for commands that actually needed extra input (like search), it used a mode-line on the bottom of the screen (exactly the way that emacs does now).

Not too long after that, Richard Stallman and James Gosling wrote emacs – the editor macros for TECO. Originally, it was nothing but editor macros for TECO to make the full screen editor easier to use. But eventually, they rewrote it from scratch, to be Lisp based. And not long after that, TECO faded away, only to be remembered by a bunch of aging geeks. The syntax of TECO killed it; the simple fact is, if you have an alternative to the mind-boggling hideousness that is TECO syntax, you’re willing to put up with a less powerful language that you can actually read. So almost everyone would rather write their programs in Emacs lisp than in TECO, even if TECO was the better language.

The shame of TECO’s death is that it was actually a really nice programming language. To this day, I still come across things that I need to do that are better suited to TECO than to any modern day programming language that I know. The problem though, and the reason that it’s disappeared so thoroughly, is that the syntax of TECO is so mind-bogglingly awful that no one, not even someone as insane as I am, would try to write code in it when there are other options available.

A Taste of TECO Programming

Before jumping in in and explaining the basics of TECO in some detail, let’s take a quick look at a really simple TECO program. This program is absolutely *remarkably* clear and readable for TECO source. It even uses a trick to allow it to do comments. The things that look like comments are actually “goto” targets.

0uz ! clear repeat flag ! <j 0aua l ! load 1st char into register A ! <0aub ! load 1st char of next line into B ! qa-qb"g xa k -l ga-1uz ' ! if A>B, switch lines and set flag ! qbua ! load B into A ! l .-z;> ! loop back if another line in buffer ! qz;> ! repeat if a switch was made last pass !

The basic idea of TECO programming is pretty simple: search for something that matches some kind of pattern; perform some kind of edit operation on the location you found; and then choose new search to find the next thing to do.

The example program above is a rather sophisticated version of that for such a small program. It searches for the beginnings of consecutive lines. For each pair of lines, if they’re not in sorted order, it swaps them, and then searches for the next pair of lines. That’s enclosed in a loop which keeps repeated the scan through the file up until every consecutive pair is in order. In other words, it’s a swap-sort.

The fundamental (the only?) data structure in TECO is a buffer. All TECO programs are based on the idea that you’ve got a buffer full of text, and you want to do something to change it. The way that you access a buffer is through a cursor, which is a pointer into the buffer, which represents the position at which any edit operations will be performed. So TECO operations all work by either reading text at the cursor; editing text at the cursor; or moving the cursor. In TECO lingo, the position of the cursor is called dot. Dot is always between two characters.

TECO Commands

I can’t possibly explain all of TECO in one post, so I’m just going to go through enough to give you the flavor, and to make it possible to walk you through a sample program. If you want, you can go and see the full command list, or even read the TECO manual.

TECO commands are generally single characters. But there is some additional structure to allow arguments. There are two types of arguments: numeric arguments, and text arguments. Numeric arguments come before the command; text arguments come after the command. Numeric values used as arguments can be either literal numbers, commands that return numeric values, “.” (for the index of the buffer pointer), or numeric values joined by arithmetic operators like “+”, “-“, etc.

So, for example, the C command moves the pointer forward one character. If it’s preceded by a numeric argument N, it will move forward N characters. The J command jumps the pointer to a specific location in the buffer: the numeric argument is the offset from the beginning of the buffer to the location where the pointer should be placed.

String arguments come after the command. Each string argument can be delimited in one of two ways. By default, a string argument continues until it sees an Escape character, which marks the end of the string. Alternatively (and easier to read), if the command is prefixed by an “@” character, then the first character after the command will be used as a string delimiter, so that the string parameter will continue until the next instance of that character.

So, for example, the first thing that most TECO programs do is specify what it is that they want to edit – that is, what they want to read into the buffer. The command to do that is “ER”. It needs a string argument to tell it the name of the file to read – so the command to edit a file “foo.txt” is ERfoo.txt (followed by pressing escape twice to tell TECO to run the command). Alternatively, you could use one of the other variations for string arguments. For example, you could quote the filename, using @ER'foo.txt' . Or you could use quoting in a deliberately silly way: @ER foo.txt . (That is, using space as the quote character, giving you nearly invisible quoting.)

In addition to the standard arguments, you can also modify commands, by placing a “:” in front of them. For most commands, “:” makes them return either a 0 (to indicate that the command failed), or a -1 (to indicate that the command succeeded). For others, the colon does something else. The only way to know is to know the command.

So, now that we know basically what commands and arguments look like, we can start looking at the commands that create the beast that was TECO.

There are, of course, a bunch of commands for printing out some part of the buffer. Remember, TECO originally comes from the pre-full-screen-editor days, so you needed to be able to ask it to show you what the text looked like after a sequence of edits, to make sure you had it right before you went and saved it. The basic print command is T , which prints out the current line. To print a string, you’d use the command ^A (that is, control-A; I said everything is a command!).

This means that now, we can finally say how to write the good

old classic “Hello, World” program in TECO! It’s very simple:

@^A'Hello, World!'

That is, string argument quoted, print, followed by the quoted

string. Perfoctly clear, right?

There are also, naturally, commands to edit text in various ways:

D delete the character after the cursor. FD Find-and-delete. Takes a string argument, finds the next occurrence of the string, and deletes it. K Delete the text from the cursor to the end of the line. HK Delete the entire current buffer. I Insert text. Obviously, this needs a string argument, which is the text that it inserts. <tab> A second insert command; the only difference is that the tab character is also inserted into the text.

Next are the commands for moving dot around in the buffer.

C </dt move the pointer forward one character if no argument is supplied; if it gets a numeric argument N , it moves forwards N characters. C can be preceeded by a “ : ” to return a success value. J jumps the pointer to a location specified by its numeric argument. If there is no location specified, it jumps to location 0. J can be preceeded by a “ : ” to see if it succeeded. ZJ jumps to the position after the last character in the file. L pretty much like C , except that it moves by lines instead of characters. R moves backwards one character – it’s basically the same as C with a negative argument. S searches for its argument string, and positions the cursor after the last character of the search string it found, or at position 0 if the string isn’t found. number,numberFB searches for its argument string between the buffer positions specified by the numeric arguments. Search strings can include something almost like regular expressions, but with a much worse syntax. I don’t want to hurt your brain too much, so I won’t go into detail.

TECO has variables; in it’s own inimitable fashion, they’re not called variables; they’re called Q-registers. There are 36 global Q-registers, named “A” through “Z” and “0”-“9”. There are also 36 local Q-registers (local to a particular macro, aka subroutine), which have a “.”

character in front of their name.

Q-registers are used for two things. First, you can use them as variables: each Q-register stores a string and an integer. Second, any string stored in a Q-register can be used as a subroutine; in fact, that’s the only way to create a subroutine. The commands to work with Q-registers include:

nUq n is a numeric argument; q is a register name. This stores the value n as the numeric value of the register q . m,nUq both m and n are numeric arguments, and q is a register name. This stores n as the numeric value of register q , and then returns m as a parameter for the next command. n%q add the number n to the numeric value stored in register q . ^Uqstring Store the string as the string value of register q . :^Uqstring" Append the string parameter to the string value of register q . nXq clear the text value of register q , and copy the next n lines into its string value. m,nXq copy the character range from position m to position n into register q . .,.+nXq copy n characters following the current buffer pointer into register q . *Qq use the integer value of register q as the parameter to the next command. nQq use the ascii value of the n th character of register q as the parameter to the next command. :Qq use the length of the text stored in register q as the parameter to the next command. Gq copy the text contents of register q to the current location of the buffer pointer. Mq invoke the contents of register q as a subroutine.

And last, but definitely not least, there’s control flow. First, there are loops. A loop is “ n<commands> “, which executes the text between the left brack and the right bracket “n” times. Within the loop, “;” branches out of the loop if the last search command failed; “n;” exits the loop if the value of “n” is greater than or equal to zero. “:;” exits the loop if the last search succeeded. “F>” jumps to the loop close bracket (think C continue), “F<" jumps back to the beginning of the loop.

Conditionals are generally written n"Xthen-command-string|else-command-string' . The quotes there are part of the command!) The double-quote character introduces the conditional, and the single-quote marks the end. In this command, the "X" is one of a list of conditional tests, which define how the numeric argument n ; is to be tested. Some possible values of X include:

“A”: tests if n is the character code for an alphabetic character.

“D”: tests if n is the character code of a digit

“E”: tests if n is zero or false

“G”: tests if n is greater than zero

“N”: tests if n is not equal to zero

“L”: test if n is a numeric value meaning that the last command succeeded.

Example TECO Code

So, time for a couple of real TECO programs.

Let’s start by looking back at the swapsort program up at the top of this

post.

0uz : set the Q-register z to 0. <j : start a loop, and set dot to the beginning of the

file. 0aua read the character at the beginning of the line, and pass it as a numeric argument to ua , which updates the value of register a. So register a contains the first character of the line. l : advance by one line. <0aub : start a new loop, and assign the first character of the new line to register b. qa-qb"g : subtract the value of register b from register a. If the result is greater than 0, then do the following: xa : load the current line (the second of these two consecutive lines) into register A. k : delete this line. -l : move back one line. ga : insert the contents of a (what used to be the later line) into the line. So now, we used to have … L1 L2 …; we’ve deleted L2, giving us … L1 …, and now we’ve jumped the cursor back to before L1, and inserted, so we’ve got … L2 L1 … – so we’ve swapped the two lines. -1uz : set register z to -1. This is just setting a flag saying “I did a swap”. ' : end of the conditional. qbua : put what was in register b into register a. l .-z;> : if there are any more lines in the buffer, then go to the beginning of the (inner) loop. qz;> : if the z flag is non-zero, ther repeat the outer loop.

Gosh, wasn’t that simple? All kidding aside, it really is. Once you get used to the idea of editing a text buffer, it’s really very natural. It’s damned near impossible to read… but it’s not at all bad, semantically.

So now you should be ready for something that’s a bit less clearly written. No one wrote code like that example, with all of that documentation! This is an example of what real, working TECO code looked like. It’s a really useful program: it scans through a file containing a mixture of spaces and tabs, and replaces all of the tabs, assuming that tab stops appear every 8 columns.

FEB :XF27: F H M Y<:N ;'.U 0L.UAQB-QAUC<QC-9"L1;'-8%C>9-QCUD S DQD<I >>EX

That’s perfectly clear now, isn’t it?

Ok, since that was so easy, how about something challenging?

This little baby takes a buffer, and executes its contents as a BrainFuck program. Yes, it’s a BrainFuck interpreter in TECO!

@^UB#@S/{^EQQ,/#@^UC#@S/,^EQQ}/@-1S/{/#@^UR#.U1ZJQZ^SC.,.+-^SXQ-^SDQ1J# @^U9/[]-+<>.,/<@:-FD/^N^EG9/;>J30000<0@I//>ZJZUL30000J0U10U20U30U60U7 @^U4/[]/@^U5#<@:S/^EG4/U7Q7; -AU3(Q3-91)"=%1|Q1"=.U6ZJ@i/{/Q2@i/,/Q6@i/} /Q6J0;'-1%1'>#<@:S/[/UT.U210^T13^TQT;QT"NM5Q2J'>0UP30000J.US.UI <(0A-43)"=QPJ0AUTDQT+1@I//QIJ@O/end/'(0A-45)"=QPJ0AUTDQT-1@I/ /QIJ@O/end/'(0A-60)"=QP-1UP@O/end/'(0A-62)"=QP+1UP@O/end/'(0A-46)"=-.+QPA ^T(-.+QPA-10)"=13^T'@O/end/'(0A-44)"=^TUT8^TQPJDQT@I//QIJ@O/end/'(0A-91) "=-.+QPA"=QI+1UZQLJMRMB -1J.UI'@O /end/'(0A-93)"=-.+QPA"NQI+1UZQLJMRMC-1J.UI'@O/end/' !end!QI+1UI(.-Z)"=.=@^a/END/^c^c'C>

If you’re actually insane enough to want to try this masochistic monstrosity, you can get a TECO interpreter, with documentation and example programs, from here.