domingo, 19 de agosto de 2018

O Horror Glorioso de TECO [markcc]

O Horror Glorioso de TECO

O Horror Glorioso de TECO

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ça N 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 valor m como o valor numérico do registrador q.
m,nUq
ambos m e n são argumentos numéricos, e q é o nome de um registrador. Isto guarda n como valor numérico do registrador q, e então retorna m como parâmetro para o próximo comando.
n%q
adiciona o número n ao valor numérico salvo no registrador q.
^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óximas n linhas em seu valor string.
m,nXq
copia o intervalo de caracteres da posição m até a posição n no registrador q.
.,.+nXq
copia n caracteres seguindo o ponteiro do buffer no registrador q.
*Qq
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 registrador q como parâmetro para o comando seguinte.
:Qq
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.

  1. 0uz: atribui ao Q-registrador z o valor 0
  2. <j: inicia um laço, e posiciona o ponto no início do arquivo.
  3. 0aua: lê o caractere no início da linha, e o passa como argumento numérico para ua, que atualiza o valor do registrador a. Assim o registrador a contém o primeiro caractere da linha
  4. l: avança uma linha.
  5. <0aub: inicia um novo laço, e atribui o primeiro caractere da nova linha ao registrador b.
  6. qa-qb"g: subtrai o valor do registrador b do registrador a. Se o resultado é maior que zero, então faça o seguinte:
    1. xa: carrega a linha atual (a segunda dessas duas linhas consecutivas) no registrador a.
    2. k: deleta esta linha.
    3. -l: retrocede uma linha.
    4. ga: insere o conteúdo de a (o que costumava ser a última linha) na linha. Então agora nós tínhamos ... L1 L2 ...; deletamos L2, nos dando ... L1 ... e agora saltamos o cursor de volta a L1, e inserimos, portanto nós obtivemos ... L2 L1 ... - portanto as linhas estão trocadas
    5. -1uz: atribui ao registrador z o valor -1. Isto está simplesmente fixando uma flag dizendo "eu fiz uma troca de linhas".
    6. ': fim da condicional.
  7. qbua: coloca o que estava no registrador b no registrador a.
  8. l .-z;>: se existirem mais linhas n buffer, então vá para o começo do laço (o mais interno)
  9. qz;>: se a flag z 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

Table 1: 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

Footnotes:

1

Em português, Editor e Corretor de Fita.

2

Sim, contando K, W e Y.

Created: 2018-08-21 ter 07:09

Validate

Nenhum comentário:

Postar um comentário

Entendendo o Combinador Y [hishamhm]

Entendendo, Finalmente, o Combinador Y - Uma Abordagem de uma Perspectiva Amigável a Programadores Entendendo, Finalmente, o...