O Horror Glorioso de TECO
Table of Contents
1 Intro
Em meu tão abundante tempo livre, eu tenho trabalhado no meu próprio editorzinho de texto. E uma das minhas motivações é o TECO: um dos mais antigos, e dos melhores, já feito. Ele é ao mesmo tempo um editor de texto e uma linguagem de programação - e, de fato, é exatamente isto o que fez dele uma ferramenta tão brilhante. Muito do enfado da programação são coisas que poderiam realmente ser feitas por um programa. Mas gastamos tanto tempo aprendendo extravagâncias que perdemos o fio da meada. Hoje em dia, você pode escrever um programa em emacs lisp que faz as coisas que você costumava fazer em TECO; é apenas bem estranho que você geralmente não o faça.
O problema, porém, com meramente reimplementar TECO com uma interface moderna é que ele foi projetado numa época diferente. Quando TECO foi escrito, cada byte era crítico. E portanto a linguagem, a sintaxe, a estrutura, era completamente ridícula. E, como resultado, ela tornou-se a mais útil linguagem de programação patológica já escrita. É uma peça gloriosa, hedionda, maravilhosa, horripilante da história da computação.
TECO é uma das peças mais influentes de software já feita. Se, por um acaso, você ouvir falar de um editorzinho chamado "emacs"; bem, ele era originalmente um conjunto de macros de edição para o TECO (EMACS = Editor MACroS). Como linguagem, ela é tanto poderosa quanto medonha. Do lado bom, o conceito central da linguagem é maravilhoso: é uma linguagem poderosa para processar texto, que funciona basicamente repetidamente procurando texto que casa algum tipo de padrão, tomando algum tipo de ação quando o encontra, e daí selecionando o próximo padrão de procura. Esta é uma maneira de escrever programas de processamento de texto bastante natural e fácil de entender. Do lado mau, ela tem a mais horrenda e macabra sintaxe já imaginada
2 História
TECO merece uma discussão da sua própria história - sua história é basicamente a de como os editores para programadores foram desenvolvidos. Esta é uma versão bastante curta, mas boa o bastante para esta postagem.
Antigamente os computadores PDP usavam uma fita de papel para alimentá-lo com programas. (Mainframes usavam cartões perfurados, enquanto minicomputadores como os PDPs usavam fita.) O grande problema com fitas de papel é que se ocorre um erro, você tem que ou criar uma fita inteira contendo a correção, ou cuidadosamente cortar e emendar a fita com novos segmentos a fim de criar uma nova fita (e emendar era algo bem propenso a erros).
Isto era ruim. E por isso, TECO nasceu. TECO era o "Tape Editor and COrrector" 1. Ele era uma linguagem de programação Turing-completa na qual se podia escrever programas para fazer correções. Assim você colocaria o programa TECO no computador primeiro, e então alimentaria a fita original (com erros) na máquina; o programa TECO faria as edições especificadas, e então você alimentaria o programa para o compilador. Era necessário que TECO fosse Turing-completo, porque você estava escrevendo um programa para encontrar coisas que precisam ser modificadas
Uma linguagem projetada para viver num mundo de fitas de papel tinha que ter alguns limitantes principais. Primeiro, fita de papel é lenta. Muito lenta. E perfurar fita é um processo miserável. Então você realmente desejaria manter as coisas o mais curtas possível. Portanto a sintaxe do TECO era, para dizer delicadamente, absolutamente incompreensível. Cada caractere é um comando. E não estou dizendo "cada pontuação" ou "cada letra". Cada caractere é um comando. Letras, números, pontuação, preenchimento de linha (linefeed), caracteres de controle … tudo.
Mas apesar de sua natureza absolutamente críptica, era bom. TECO era muito bom. Então quando as pessoas começaram a usar teletipos interativos (a 110 baud), elas ainda queriam usar TECO. Então ele evoluiu. Mas a sintaxe básica baseada em fita permaneceu.
Quando terminais de tela endereçável apareceram - VT52 e coisa do tipo - de repente, era possível escrever programas que usavam controle de cursor! A ideia de um editor de tela cheia surgiu. Claro, adoradores do TECO queriam que seu editor de tela cheia preferido fosse o TECO. Para máquinas VAX, um dos primeiros editores de tela cheia foi uma versão de TECO que exibia uma tela de texto, e executava os comandos à media em que eram digitados; e para comandos que necessitavam de uma entrada extra (como busca de texto), ele usava uma linha-de-modo no inferior da tela (exatamente da mesma maneira que o emacs faz hoje em dia).
Não muito depois disso, Richard Stallman e James Gosling escreveram o emacs - o editor de macros para TECO. Originalmente ele nada mais era além de macros para o TECO a fim de tornar o editor de tela cheia mais fácil de usar. Mas eventualmente eles o reescreveram do zero, baseado em LISP. E não muito depois disso, TECO se esvaneceu, apenas para ser lembrado por um punhado de geeks idosos. A sintaxe de TECO o matou; o fato é que se você tem uma alternativa para a incompreensível repulsividade que é a sintaxe TECO, você estaria disposto a ficar com uma linguagem menos poderosa que pudesse realmente ler. Então quase todo mundo preferiria escrever seus programas em emacs lisp que em TECO, mesmo que TECO fosse uma melhor linguagem.
A desgraça da morte do TECO é que ele era mesmo uma bela linguagem de programação. Hoje em dia eu ainda encontro coisas que preciso fazer que são melhor adaptadas para o TECO que para qualquer linguagem de programação moderna que eu conheço. O problema, porém, e a razão para que ele tenha desaparecido tão drasticamente é que a sintaxe de TECO é tão incompreensivelmente ruim que ninguém, nem mesmo alguém tão insano quanto eu, tentaria escrever código nela quando há outras opções disponíveis.
3 Um Gostinho de Programação TECO
Antes de pular de cabeça e explicar o básico do TECO mais detalhadamente, vamos observar rapidamente um programa TECO simples. Este programa é absoluta e notavelmente claro e legível para um código-fonte TECO. Ele até mesmo usa um truque para permitir o uso de comentários. As coisas que parecem comentários na verdade são alvos de desvio "goto".
0uz ! limpa a flag de repeticao ! <j 0aua l ! carrega o 1o. caractere no registrador A ! <0aub ! carrega o 1o. caractere da proxima linha no registrador B ! qa-qb"g xa k -l ga-1uz ' ! se A>B, troca as linhas e ativa a flag ! qbua ! carrega B em A ! l .-z;> ! volta o laco se há outra linha no buffer ! qz;> ! repete se uma troca foi feita na ultima passada !
A ideia básica da programação TECO é bastante simples: procure por algo que case certo tipo de padrão; realize algum tipo de operação de edição na localização encontrada; e então escolha a nova pesquisa para encontrar a próxima coisa a fazer.
O programa de exemplo acima é uma versão bastante sofisticada para um programa tão pequeno. Ele procura pelo começo de linhas consecutivas. Para cada par de linhas, se elas não estão na ordem devida, ele as troca, e então procura pelo próximo par de linhas consecutivas. Isto é enclausurado num laço que continua repetindo a varredura ao longo do arquivo até que cada par consecutivo de linhas esteja ordenado. Em outras palavras, um swap-sort.
A estrutura de dados fundamental (a única?) em TECO é o buffer de texto. Todos os programas TECO são baseados na ideia de que você tem um buffer cheio de texto, e quer fazer algo para modificá-lo. A maneira que se acessa o buffer é mediante um cursor, que é um ponteiro no buffer, que representa a posição na qual quaisquer operações de edição serão realizadas. Assim as operações TECO funcionam ou lendo o texto no cursor, ou editando o texto no cursor, ou movendo o cursor. No jargão TECO, a posição do cursor é chamada ponto. O ponto está sempre entre dois caracteres.
3.1 Comandos TECO
Não me é possível explicar todo o TECO em uma postagem, então eu vou simplesmente passar pelo suficiente para te dar uma ideia, e tornar possível que você possa acompanhar um programa de exemplo. Se quiser, você pode olhar a lista completa de comandos, ou até mesmo ler o manual do TECO.
Comandos TECO geralmente são de um caractere. Mas existe alguma estrutura
adicional para permitir argumentos. Existem dois tipos de argumentos: os
numéricos e os textuais. Argumentos numéricos aparecem antes do comando; os
textuais aparecem depois do comando. Valores numéricos usados como argumentos
podem ser ou números literais, ou comandos que retornam valores numéricos, ou
.
(para o índice do ponteiro do buffer) ou valores numéricos junto a
operadores aritméticos como +
,=-=, etc.
Então, por exemplo, o comando C
move o ponteiro um caractere para frente. Se
for precedido de um argumento numérico N
, ele moverá N
caracteres. O comando
J
salta o ponteiro para uma localização específica do buffer: o argumento
numérico é o deslocamento do início do buffer até a posição em que o ponteiro
deva ser posicionado.
Argumentos string vêm depois do comando. Cada argumento string pode ser
delimitado de uma ou duas formas. Por padrão, um argumento string continua até
ver um caractere de escape (ESC), que marca o fim da string. Como alternativa (e
mais fácil de ler), se o comando é prefixado por um caractere @
, então o
primeiro caractere após o comando será usado como delimitador da string, de taç
forma que a string-parâmetro continuará até a próxima instância daquele
caractere.
Então, por exemplo, a primeira coisa que a maioria dos comandos TECO faz é
especificar o que é que eles querem editar - isto -e, o que eles querem ler no
buffer. O comando para fazer isso é ER
. Ele precisa de um argumento string
para lhe informar o nome do arquivo a ser lido - então o comando para ler o
arquivo foo.txt
é ERfoo.txt
(seguido por pressionar ESC
duas vezes para
informar ao TECO para ler o comando). Alternativamente, você poderia usar uma
das outras variantes para argumentos string. Por exemplo, poderia usar aspas de
de citação, usando @ER'foo.txt'
. Ou poderia usar a citação de uma forma
deliberadamente frívola: =@ER foo.txt =. (Isto é, usando espaço como caractere
de citação, te dando uma citação quase invisível).
Além dos argumentos padrão, você também pode modificar os comandos, colocando um
:
na frente deles. Para a maioria, :
os faz retornar ou um 0
(indicando
que o comando falhou) ou um -1
(indicando sucesso). Para outros, o dois-pontos
faz outra coisa. A única maneira de saber é conhecendo o comando.
Pois bem, agora que sabemos basicamente como os comandos e argumentos se parecem, podemos começar a observar os comandos que criam a besta chamada TECO.
É claro, existe um punhado de comandos para imprimir parte do buffer. Lembre-se,
TECO originalmente veio de uma época anterior a editores de tela cheia, então
você precisa ser capaz de pedir a ele para mostrar como o texto estava antes de
uma sequência de edições, a fim de se certificar que ele esteja bem do jeito que
você queria e salvá-lo. O comando básico para impressão é T
, que imprime a
linha atual. Para imprimir uma string, você usaria o comando ^A
(significando
Control-A; eu disse que tudo era um comando!).
Isto significa que agora podemos enfim dizer como escrever o bom e velho clássico "Hello, World!" em TECO! É bem simples:
@^A'Hello, World!'
Isto é, argumento de string entre quotes, imprimir, seguido da string entre quotes. Perfeitamente claro, não?
Existem também, naturalmente, comandos para editar o texto de diversas maneiras:
- D
- Deleta o caractere após o cursor
- FD
- Find-delete. Recebe um argumento string, encontra a próxima ocorrência, e a deleta
- K
- Deleta o texto do cursor até o fim da linha
- HK
- Deleta o buffer inteiro
- I
- Insere texto. Obviamente, precisa de um argumento string, que é o texto que ele insere.
- <TAB>
- Um segundo comando de inserção; a única diferença é que o <TAB> também é inserido no texto.
Agora alguns comandos que movem o ponto pelo buffer.
- C
- Move o ponteiro adiante um caractere se nenhum argumento é fornecido; se
recebe um argumento numérico
N
, avançaN
caracteres. Pode ser precedido por um:
para retornar um valor sucesso. - J
- Salta para uma localização especificada por seu argumento numérico. Se
não é especificada nenhuma localização, ele salta para a posição 0. J
pode ser precedido de um
:
para verificar se houve sucesso. - ZJ
- Pula para a posição após o último caractere do arquivo.
- L
- Bem semelhante a
C
exceto que se move por linhas em vez de caracteres. - R
- Move um caractere para trás - é basicamente o mesmo que
C
com argumento negativo. - S
- Procura por seu argumento string, e posiciona o cursor após o último caractere da string de busca que ele encontrou, ou na posição 0 se a string não foi encontrada.
- número,númeroFB
- Procura pelo argumento string entre as posições do buffer especificadas pelos argumentos numéricos. As strings de busca podem incluir algo quase similar a expressões regulares, mas com uma sintaxe muito pior. Eu não quero destruir demais o teu cérebro, então não vou entrar em detalhes.
TECO tinha variáveis; em seu próprio estilo inimitável, elas não eram chamadas
variáveis; eram chamadas registradores Q, ou Q-registradores. Existem 36
Q-registradores globais, com nomes de A
a Z
2 e de 0
a 9
. Também há
36 Q-registradores locais (locais a uma macro em particular, também conhecida
como sub-rotina), que têm um caractere .
na frente dos seus nomes.
Registradores Q são usados para duas coisas. Primeiro, você pode usá-los como variáveis: cada registrador Q armazena uma string e um inteiro. Segundo, qualquer string armazenada em um registrador Q pode ser usado como sub-rotina; de fato, esta é a única maneira de criar uma sub-rotina. Os comandos para trabalhar com registradores Q incluem:
- nUq
n
é um argumento numérico; q é o nome de um registrador. Isto salva o valorm
como o valor numérico do registradorq
.- m,nUq
- ambos
m
en
são argumentos numéricos, e q é o nome de um registrador. Isto guardan
como valor numérico do registradorq
, e então retornam
como parâmetro para o próximo comando. - n%q
- adiciona o número
n
ao valor numérico salvo no registradorq
. - ^Uqstring
- Armazena a string como valor string do registrador
q
. - :\Uqstring"
- Anexa o parâmetro string ao valor string do registrador
q
. - nXq
- limpa o valor texto do registrador
q
, e copia as próximasn
linhas em seu valor string. - m,nXq
- copia o intervalo de caracteres da posição
m
até a posiçãon
no registradorq
. - .,.+nXq
- copia
n
caracteres seguindo o ponteiro do buffer no registrador q. - usa o valor inteiro do registrador
q
como parâmetro para o próximo comando. - nQq
- usa o valor ascii do
n
-ésimo caractere do registradorq
como parâmetro para o comando seguinte. - usa o comprimento do texto armazenado no registrador
q
como parâmetro para o comando seguinte. - Gq
- copia o conteúdo textual do registrador
q
para a posição corrente do ponteiro do buffer. - Mq
- invoca o conteúdo do registrador
q
como uma sub-rotina
E por último, mas definitivamente não menos importante, temos o controle de
fluxo. Primeiro, há laços. Um laço é n<comandos>
, que executa o trecho entre a
as chaves angulares n
vezes. Dentro do laço, ;
sai do laço se o último
comando de busca falhou; n;
sai do laço se o valor de n
é maior que ou igual
a zero. :;
sai do laço se a última busca foi bem-sucedida. F>
salta para a
chave angular de fechamento (pense como no continue
de C), F<
salta para o
início do laço.
Condicionais são escritas geralmente n"Xcomandos-se|comandos-senao'
. As aspas
de quote fazem parte do comando! O caractere de aspa dupla introduz a
condicional, o de aspa simples marca o fim. No comando, o X
é um de uma lista
de testes condicionais, que definem como o argumento numérico n
deve ser
testado. Alguns valores possíveis de X
incluem:
- A
- testa se
n
é o código de caractere de um alfabético - D
- testa se
n
é o código de caractere de um dígito - E
- testa se
n
é zero ou falso - G
- testa se
n
é maior que zero - N
- testa se
n
é diferente de zero - L
- testa se
n
é um valor numérico indicando que o último comando foi bem-sucedido
3.2 Exemplo de Código TECO
Então, é hora para um par de programas TECO de verdade.
Vamos começar reparando o programa de swapsort do topo deste post.
0uz
: atribui ao Q-registrador z o valor0
<j
: inicia um laço, e posiciona o ponto no início do arquivo.0aua
: lê o caractere no início da linha, e o passa como argumento numérico paraua
, que atualiza o valor do registradora
. Assim o registradora
contém o primeiro caractere da linhal
: avança uma linha.<0aub
: inicia um novo laço, e atribui o primeiro caractere da nova linha ao registradorb
.qa-qb"g
: subtrai o valor do registradorb
do registradora
. Se o resultado é maior que zero, então faça o seguinte:xa
: carrega a linha atual (a segunda dessas duas linhas consecutivas) no registradora
.k
: deleta esta linha.-l
: retrocede uma linha.ga
: insere o conteúdo dea
(o que costumava ser a última linha) na linha. Então agora nós tínhamos... L1 L2 ...
; deletamosL2
, nos dando... L1 ...
e agora saltamos o cursor de volta aL1
, e inserimos, portanto nós obtivemos... L2 L1 ...
- portanto as linhas estão trocadas-1uz
: atribui ao registrador z o valor-1
. Isto está simplesmente fixando uma flag dizendo "eu fiz uma troca de linhas".'
: fim da condicional.
qbua
: coloca o que estava no registradorb
no registradora
.l .-z;>
: se existirem mais linhas n buffer, então vá para o começo do laço (o mais interno)qz;>
: se a flagz
não é nula então repete o laço externo.
Nossa, não parece simples? Brincadeiras à parte, é mesmo. Uma vez que você se acostuma à ideia de editar um buffer de texto, é realmente bastante natural. É quase impossível ler esta coisa … mas não é tão ruim assim semanticamente.
Então agora você deve estar preparado para algo que seja um pouco menos claramente escrito. Ninguém escreve um código como o daquele exemplo, com toda essa documentação! Este é um exemplo de como um programa TECO funcionando se parece. É um programa realmente útil: ele sonda o arquivo contendo uma mistura de espaços e tabulações, e substitui todas as tabulações, assumindo que espaçamentos de tabulação aparecem a cada oito colunas.
FEB :XF27: F H M Y<:N ;'.U 0L.UAQB-QAUC<QC-9"L1;'-8%C>9-QCUD S DQD<I >>EX
Agora está perfeitamente claro, não?
OK, já que essa foi fácil, que tal algo desafiador?
Esta belezinha aqui recebe um buffer e executa seu conteúdo como um programa Brainfuck. Sim, é um interpretador Brainfuck em 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>
Se você é realmente insano o bastante para tentar esta monstruosidade masoquística, você pode obter um interpretador TECO, com documentação e exemplos de programas, aqui.
4 META
Autor | markcc |
Título Original | The Glorious Horror of TECO |
Link Original | http://www.goodmath.org/blog/2010/11/30/the-glorious-horror-of-teco/ |
Link Arquivado | http://archive.is/Jox0z |
Created: 2018-08-21 ter 07:09
Nenhum comentário:
Postar um comentário