You are on page 1of 52

L'editor di testi sam

Rob Pike

rob@plan9.bell-labs.com

ASTRATTO

Sam è un editor di testo multi-file interattivo progettato per display bitmap. Una lingua di comando
testuale completa l'interfaccia tagliata e incolla del mouse per rendere facili le attività di modifica
complesse o ripetitive. Il linguaggio è caratterizzato dalla composizione delle espressioni regolari
per descrivere la struttura del testo che viene modificato. Il trattamento dei file come database, con
le modifiche registrate come transazioni atomiche, guida l'implementazione e rende semplice un
meccanismo generale di annullamento.

Sam è implementato come due processi connessi da un flusso a bassa larghezza di banda, un
processo che gestisce il display e l'altro gli algoritmi di modifica. Pertanto può essere eseguito con il
processo di visualizzazione in un terminale bitmap e l'editor su un host locale, con entrambi i
processi su un host dotato di bitmap o con il processo di visualizzazione nel terminale e l'editor in
un host remoto. Sopprimendo il processo di visualizzazione, può persino funzionare senza un
terminale bitmap.

Questo articolo è stato ristampato da Software-Practice and Experience, Vol 17, numero 11, pp.
813-845, novembre 1987. Il documento non è stato aggiornato per i manuali di Plan 9. Anche se
Sam non è cambiato molto da quando è stato scritto il documento, certamente il sistema che lo
circonda. Ciononostante, la descrizione qui è ancora la migliore introduzione all'editor.

introduzione

Sam è un editor di testo interattivo che combina il montaggio interattivo taglia-e-incolla con un
linguaggio di comando insolito basato sulla composizione delle espressioni regolari. È scritto come
due programmi: uno, la "parte host", viene eseguito su un sistema UNIX e implementa il linguaggio
dei comandi e fornisce l'accesso ai file; l'altra, la "parte terminale", viene eseguita in modo
asincrono su una macchina con un mouse e una visualizzazione bitmap e supporta la
visualizzazione e l'editing interattivo. La parte host può anche essere eseguita in isolamento su un
normale terminale per modificare il testo utilizzando il linguaggio di comando, proprio come un
editor di riga tradizionale, senza l'aiuto di un mouse o di un display. Molto spesso, la parte terminale
gira su un terminale Blit1 (in realtà su un Teletype DMD 5620, la versione di produzione del Blit),
la cui connessione host è un normale RS232 a 9600 bps; sul computer SUN i processi host e display
vengono eseguiti su una singola macchina, collegata da una pipe.

Sam modifica il testo ASCII non interpretato. Non ha strutture per più font, grafici o tabelle, a
differenza di MacWrite, 2 Bravo, 3 Tioga4 o Lara.5 Inoltre, a differenza di loro, ha un ricco
linguaggio di comando. (In questo documento, il linguaggio di comando frase fa riferimento ai
comandi testuali, i comandi attivati dal mouse formano il linguaggio del mouse.) Sam ha sviluppato
un editor per i programmatori e cerca di unire gli stili dell'editor di testo UNIX ed6,7 con quella di
editor interattivi taglia e incolla fornendo un'interfLoops
Above, we changed one occurrence of Emacs to Emacs\360, but if the name of the editor is really
changing, it would be useful to change all instances of the name in a single command. sam provides
a
command, x (extract), for just that job. The syntax is x/pattern/command. For each occurrence of
the
pattern in the selected text, x sets dot to the occurrence and runs command. For example, to change
Emacs
to vi,accia intuitiva basata su mouse a un programma con un linguaggio di comando solido guidato
da espressioni regolari. Il linguaggio dei comandi si è sviluppato più del linguaggio del mouse e ha
acquisito una notazione per descrivere la struttura dei file in modo più ricco rispetto a una sequenza
di linee, utilizzando una sintassi simile al flusso di dati per specificare le modifiche.

Lo stile interattivo è stato influenzato da jim, 1 un editor di cut-and-paste per Blit e da mux, 8 il
sistema di finestre Blit. Mux fonde il sistema originale Blit Window, mpx, 1 con editing "taglia e
incolla", formando qualcosa come una versione multiplex di jim che modifica l'output di (e immette
in) le sessioni di comando piuttosto che i file.

La prima parte di questo articolo descrive la lingua di comando, quindi la lingua del mouse e spiega
come interagiscono. Ciò è seguito da una descrizione dell'implementazione, prima della parte host,
quindi della parte terminale. Un principio che ha influenzato la progettazione di sam è che non
dovrebbe avere limiti espliciti, come i limiti massimi sulla dimensione del file o sulla lunghezza
della linea. Una considerazione secondaria è che sia efficiente. Per onorare questi due obiettivi
insieme richiede un metodo per manipolare in modo efficiente stringhe enormi (file) senza romperle
in righe, magari mentre si fanno migliaia di cambiamenti sotto il controllo del linguaggio dei
comandi. Il metodo di Sam è di trattare il file come un database delle transazioni, implementando le
modifiche come aggiornamenti atomici. Questi aggiornamenti possono essere svolti facilmente per
annullare le modifiche. L'efficienza è ottenuta attraverso una raccolta di cache che riduce al minimo
il traffico del disco e il movimento dei dati, sia all'interno delle due parti del programma sia tra di
essi.

La parte terminale di sam è abbastanza semplice. Più interessante è il modo in cui le due metà
dell'editor rimangono sincronizzate quando ciascuna metà può iniziare una modifica. Questo
risultato è ottenuto attraverso una struttura dati che organizza le comunicazioni e viene mantenuta in
parallelo da entrambe le metà.

L'ultima parte del documento descrive la scrittura di sam e discute le lezioni apprese attraverso il
suo sviluppo e uso.

Il documento è lungo, ma è composto in gran parte da due documenti di lunghezza ragionevole: una
descrizione dell'interfaccia utente di sam e una discussione sulla sua implementazione.L'interfaccia

Sam è un editor di testo per più file. I nomi dei file possono essere forniti quando viene richiamato:

sam file1 file2 ...

e ci sono i comandi per aggiungere nuovi file e scartare quelli non necessari. I file non vengono letti
fino a quando non è necessario per completare un comando. Le operazioni di modifica si applicano
a una copia interna eseguita al momento della lettura del file; il file UNIX associato alla copia viene
modificato solo da un comando esplicito. Per semplificare la discussione, la copia interna è qui
chiamata un file, mentre l'originale residente nel disco è chiamato un file disco.

Sam è solitamente connesso a un display bitmap che presenta un editor di copia e incolla guidato
dal mouse. In questa modalità, la lingua di comando è ancora disponibile: il testo digitato in una
finestra speciale, chiamata la finestra sam, viene interpretato come comandi da eseguire nel file
corrente. La modifica di taglia e incolla può essere utilizzata in qualsiasi finestra, anche nella
finestra sam per costruire comandi. L'altra modalità di funzionamento, invocata avviando sam con
l'opzione -d (per 'nessun download'), non usa la visualizzazione del mouse o della bitmap, ma
consente comunque la modifica utilizzando il linguaggio di comando testuale, anche su un normale
terminale, in modo interattivo o da una sceneggiatura.

Le seguenti sezioni descrivono prima il linguaggio dei comandi (sotto sam \ fP-d e nella finestra
sam), quindi l'interfaccia del mouse. Queste due lingue sono quasi indipendenti, ma si collegano
attraverso il testo corrente, descritto di seguito.

La lingua di comando
Loops
Above, we changed one occurrence of Emacs to Emacs\360, but if the name of the editor is really
changing, it would be useful to change all instances of the name in a single command. sam provides
a
command, x (extract), for just that job. The syntax is x/pattern/command. For each occurrence of
the
pattern in the selected text, x sets dot to the occurrence and runs command. For example, to change
Emacs
to vi,
Un file è costituito dal suo contenuto, che è una matrice di caratteri (cioè una stringa); il nome del
file disco associato; il bit modificato che indica se i contenuti corrispondono a quelli del file del
disco; e una sottostringa dei contenuti, chiamata testo o punto corrente (vedi Figure 1 e 2). Se il
testo corrente è una stringa nulla, il punto cade tra i caratteri. Il valore del punto è la posizione del
testo corrente; il contenuto del punto sono i caratteri che contiene. Sam impartisce al testo nessuna
interpretazione bidimensionale come colonne o campi; il testo è sempre monodimensionale. Anche
l'idea di una "linea" di testo come capita dalla maggior parte dei programmi UNIX - una sequenza
di caratteri terminata da un carattere di nuova riga - è solo debolmente supportata.
I comandi hanno nomi di una sola lettera. Tranne che per i comandi non di modifica come la
scrittura del file su disco, la maggior parte dei comandi apporta alcune modifiche al testo in punto e
lascia il punto impostato sul testo risultante dalla modifica. Ad esempio, il comando delete, d,
cancella il testo in punto, sostituendolo con la stringa nulla e impostando il punto sul risultato. Il
comando change, c, sostituisce punto per testo delimitato da un carattere di punteggiatura arbitrario,
convenzionalmente una barra. Così,

c / Peter /

sostituisce il testo in punto con la stringa Peter. Allo stesso modo,

un / Peter /

(aggiungi) aggiunge la stringa dopo il punto e

i / Peter /

(inserire) inserti prima del punto. Tutti e tre lasciano il punto impostato sul nuovo testo, Peter.

Le newline fanno parte della sintassi dei comandi: il carattere newline termina lessicalmente un
comando. All'interno del testo inserito, tuttavia, le newline non sono mai implicite. Ma poiché
spesso è conveniente inserire più righe di testo, sam ha una sintassi speciale per quel caso:

un

alcune righe di testo


da inserire nel file,

terminato da un punto

su una linea da solo

Nella sintassi a una riga, un carattere di nuova riga può essere specificato da una fuga di tipo C,
quindi

c/\n/

sostituisce punto con un singolo carattere di nuova riga.

Sam ha anche un comando sostitutivo, s:

s / espressione / rimontaggio /

sostituisce il testo sostitutivo per la prima corrispondenza, in punto, dell'espressione regolare.


Quindi, se dot è la stringa Peter, il comando

s / t / st /

lo cambia in Pester. In generale, s non è necessario, ma è stato ereditato da ed ha alcune varianti


convenienti. Ad esempio, il testo sostitutivo potrebbe includere il testo corrispondente, specificato
da &:

s / Peter / Oh, &, &, &, &! /

Esistono anche tre comandi che applicano programmi al testo:

<Programma UNIX

sostituisce punto con l'output del programma UNIX. Allo stesso modo, il comando> esegue il
programma con punto come input standard e | fa entrambi. Per esempio,

| ordinare

sostituisce punto con il risultato dell'applicazione dell'applicazione di ordinamento standard ad esso.


Ancora una volta, i newline non hanno alcun significato speciale per questi comandi sam. Il testo
applicato e derivante da questi comandi non è necessariamente limitato dalle nuove righe, sebbene
per la connessione con i programmi UNIX, le nuove linee potrebbero essere necessarie per obbedire
alle convenzioni.

Un altro comando: p stampa il contenuto del punto. La Tabella I riassume i comandi di Sam.

Il valore del punto può essere modificato specificando un indirizzo per il comando. L'indirizzo più
semplice è un numero di riga:

3
si riferisce alla terza riga del file, quindi

3d

cancella la terza riga del file, e riscrive implicitamente le righe in modo che la vecchia riga 4 sia ora
numerata 3. (Questa è una delle poche posizioni in cui Sam gestisce direttamente le linee.) La riga 0
è la stringa nulla all'inizio del file. Se un comando consiste solo di un indirizzo, si assume un
comando p, quindi digitando una 3 riga 3 senza ornamenti sul terminale. Ci sono un paio di altri
indirizzi di base: un punto si rivolge a se stesso; e un segno di dollaro ($) indirizza la stringa nulla
alla fine del file.

Un indirizzo è sempre una singola sottostringa del file. Pertanto, l'indirizzo 3 indirizza i caratteri
dopo la seconda nuova riga del file attraverso la terza nuova riga del file. Un indirizzo composto è
costruito dall'operatore virgola

address1, address2

e indirizza la sottostringa del file dall'inizio di address1 alla fine di address2. Ad esempio, il
comando 3.5p stampa le righe dalla terza alla quinta del file e., $ D cancella il testo dall'inizio del
punto alla fine del file.

Questi indirizzi sono tutte posizioni assolute nel file, ma sam ha anche indirizzi relativi, indicati con
+ o -. Per esempio,

$ -3

è la terza riga prima della fine del file e

.+1

è la linea dopo il punto. Se nessun indirizzo appare a sinistra di + o -, viene assunto il punto; se non
appare nulla a destra, 1 è assunto. Pertanto,. + 1 può essere abbreviato in solo un segno più.

L'operatore + agisce relativamente alla fine del suo primo argomento, mentre l'operatore - agisce
rispetto all'inizio. Così. + 1 indirizzi la prima riga dopo il punto, .- indirizzi la prima riga prima del
punto, e + - si riferisce alla linea che contiene la fine del punto. (Il punto può estendersi su più righe
e + seleziona la linea dopo la fine del punto, quindi - esegue il backup di una riga.)

Il tipo finale di indirizzo è un'espressione regolare, che indirizza il testo corrispondente


all'espressione. L'espressione è racchiusa tra le barre, come in

/espressione/

Le espressioni sono le stesse di egrep, 6,7 del programma UNIX e includono chiusure, alternanze e
così via. Trovano la stringa più a sinistra più lunga che corrisponda all'espressione, ovvero la prima
partita dopo il punto in cui la ricerca è stata avviata e se più di una partita inizia nello stesso punto,
la corrispondenza più lunga. (Presumo familiarità con la sintassi per le espressioni regolari nei
programmi UNIX.9) Ad esempio,

/X/

corrisponde al prossimo carattere x nel file,


/ Xx * /

corrisponde alla prossima esecuzione di una o più x, e

/ X | Peter /

corrisponde alla prossima x o Peter. Per compatibilità con altri programmi UNIX, l'operatore
'qualsiasi carattere', un punto, non corrisponde a una nuova riga, quindi

/.*/

abbina il testo dal punto alla fine della riga, ma esclude la nuova riga e quindi non corrisponderà al
limite della linea.

Le espressioni regolari sono sempre indirizzi relativi. La direzione è avanti per impostazione
predefinita, quindi / Peter / è davvero un'abbreviazione per + / Peter /. La ricerca può essere
invertita con un segno meno, quindi

-/Peter/

trova il primo Peter prima del punto. Le espressioni regolari possono essere usate con altri moduli
di indirizzo, quindi 0 + / Peter / trova il primo Peter nel file e $ - / Peter / trova l'ultimo. La Tabella
II riassume gli indirizzi di Sam.
Il linguaggio discusso finora non sembrerà nuovo per le persone che usano editor di testo UNIX
come ed o vi.9 Inoltre, i tipi di operazioni di modifica che questi comandi consentono, ad eccezione
delle espressioni regolari e dei numeri di riga, sono chiaramente più facilmente gestiti da
un'interfaccia basata sul mouse. In effetti, il linguaggio del mouse di Sam (discusso di seguito in
basso) è il mezzo con cui vengono solitamente apportate semplici modifiche. Per modifiche grandi
o ripetitive, tuttavia, un linguaggio testuale supera l'interfaccia manuale.

Immagina che, invece di eliminare una sola occorrenza della stringa Peter, volessimo eliminare ogni
Peter. Quello che serve è un iteratore che esegua un comando per ogni occorrenza di un testo.
L'iteratore di Sam è chiamato x, per estratto:

x / espressione / comando

trova tutte le corrispondenze nel punto dell'espressione specificata e, per ciascuna corrispondenza,
imposta il punto sul testo corrispondente e esegue il comando. Quindi, per eliminare tutti i Peters:

0, $ x / Peter / d

(Gli spazi vuoti in questi esempi servono a migliorare la leggibilità, sam non richiede né li
interpreta.) Questo cerca l'intero file (0, $) per le occorrenze della stringa Peter, ed esegue il
comando d con il punto impostato su ciascuna di tali occorrenze. (Al contrario, il comando par
paragonabile cancellerebbe tutte le righe che contengono Peter, sam cancella solo il Peters.)
L'indirizzo 0, $ è comunemente usato, e può essere abbreviato a solo una virgola. Come altro
esempio,

, x / Peter / p
stampa un elenco di Peters, uno per ogni aspetto nel file, senza testo intermedio (nemmeno i
newline per separare le istanze).

Ovviamente, il testo estratto da x può essere selezionato da un'espressione regolare, il che rende più
difficile decidere quale gruppo di corrispondenze è scelto - le corrispondenze potrebbero
sovrapporsi. Ciò viene risolto generando le corrispondenze a partire dall'inizio del punto usando la
regola più a sinistra e cercando ogni corrispondenza a partire dalla fine della precedente. Le
espressioni regolari possono anche corrispondere a stringhe nulle, ma una corrispondenza nullo
adiacente a una corrispondenza non nullo non viene mai selezionata; almeno un personaggio deve
intervenire. Per esempio,

, c / AAA /

x/B*/c/-/

,p

produce come output

-A-A-A-

perché il modello B * corrisponde alle stringhe Null che separano le A.

Il comando x ha un complemento, y, con sintassi simile, che esegue il comando con il punto
impostato sul testo tra le corrispondenze dell'espressione. Per esempio,

, c / AAA /

y/A/c /-/

,p

produce lo stesso risultato dell'esempio sopra.

I comandi xey sono costrutti in loop, e sam ha una coppia di comandi condizionali per andare con
loro. Hanno una sintassi simile:

g / espressione / comando

(guard) esegue il comando esattamente una volta se il punto contiene una corrispondenza
dell'espressione. Questo è diverso da x, che esegue il comando per ogni corrispondenza: x loop; g si
limita a testare, senza modificare il valore del punto. Così,

, x / Peter / d

cancella tutte le occorrenze di Peter, ma

, g / Pietro / d

cancella l'intero file (lo riduce a una stringa nulla) se Peter si trova ovunque nel testo. Il
condizionale complementare è v, che esegue il comando se non c'è corrispondenza dell'espressione.
Questi comandi tipo struttura di controllo possono essere composti per costruire operazioni più
coinvolte. Ad esempio, per stampare quelle righe di testo che contengono la stringa Peter:

, x /.* \ n / g / Peter / p

L'x spezza il file in linee, il g seleziona quelle linee contenenti Peter e il p le stampa. Questo
comando fornisce un indirizzo per il comando x (l'intero file), ma poiché g non ha un indirizzo
esplicito, si applica al valore del punto prodotto dal comando x, cioè a ciascuna riga. Tutti i comandi
in sam tranne il comando per scrivere un file sul disco usano il punto per l'indirizzo predefinito.

La composizione può essere continuata indefinitamente.

, x /.* \ n / g / Peter / v / SaltPeter / p

stampa quelle righe contenenti Peter ma non quelle contenenti SaltPeter.

Espressioni regolari strutturali

A differenza di altri editor di testo UNIX, inclusi quelli non interattivi come sed e awk, 7 sam è utile
per manipolare file con 'record' su più righe. Un esempio è una rubrica telefonica composta da
record, separati da righe vuote , della forma

Herbert Tic

44 Turnip Ave., Endive, NJ

201-5555642

Norbert Twinge

16 Potato St., Cabbagetown, NJ

201-5553145

...

Il formato può essere codificato come espressione regolare:

(. + \ N) +

ovvero una sequenza di una o più righe non vuote. Il comando per stampare l'intero disco di Mr. Tic
è quindi

, x / (. + \ n) + / g / ^ Herbert Tic $ / p

e quello per estrarre solo il numero di telefono è

, x / (. + \ n) + / g / ^ Herbert Tic $ / x / ^ [0-9] * - [0-9] * \ n / p

Quest'ultimo comando suddivide il file in record, sceglie il record di Mr. Tic, estrae il numero di
telefono dal record e infine stampa il numero.
Un problema più complesso è quello di rinominare una particolare variabile, ad esempio n, in num
in un programma C. L'ovvio primo tentativo,

, x / n / c / num /

è gravemente imperfetto: cambia non solo la variabile n ma qualsiasi lettera n che appare. Abbiamo
bisogno di estrarre tutte le variabili e selezionare quelle che corrispondono a n e solo a n:

, x / [A-Za-z _] [A-Za-z_0-9] * / g / n / v /../ c / num /

Il modello [A-Za-z _] [A-Za-z_0-9]* corrisponde agli identificativi C. Successivo g / n / seleziona


quelli contenenti un n. Quindi v /../ rifiuta quelli contenenti due (o più) caratteri, e infine c / num /
cambia il resto (identificatori n) in num. Questa versione funziona chiaramente molto meglio, ma
potrebbero esserci ancora problemi. Ad esempio, in caratteri C e costanti di stringa, la sequenza \ n
viene interpretata come carattere di nuova riga e non vogliamo cambiarla in \ num. Questo
problema può essere prevenuto con un comando y:

, y / \\ n / x / [A-Za-z _] [A-Za-z_0-9] * / g / n / v /../ c / num /

(il secondo \ è necessario a causa delle convenzioni lessicali nelle espressioni regolari), oppure
potremmo addirittura rifiutare le costanti e le stringhe dei caratteri in modo definitivo:

, y / '[^'] * '/ y / "[^"] * "/ x / [A-Za-z _] [A-Za-z_0-9] * / g / n / v /../ c / num /

I comandi y in questa versione escludono dalla considerazione tutte le costanti e le stringhe dei
caratteri. L'unico problema rimanente è di occuparsi dell'eventuale presenza di \ 'o \ "all'interno di
queste sequenze, ma è facile vedere come risolvere questa difficoltà.Il punto di questi comandi
composti è il successivo perfezionamento. Viene provata una versione semplice del comando e, se
non è abbastanza buona, può essere ottimizzata aggiungendo una o due clausole. (Gli errori possono
essere annullati, vedere di seguito.Inoltre, il linguaggio del mouse non rende necessario ridigitare il
comando ogni volta.) Le catene di comandi risultanti ricordano in qualche modo le condutture della
shell.7 A differenza delle pipeline, però, che passano lungo i dati modificati, sam i comandi passano
una vista dei dati. Il testo di ogni passo del comando è lo stesso, ma i pezzi selezionati vengono
raffinati passo dopo passo fino a quando il pezzo corretto è disponibile per il passaggio finale della
riga di comando, che alla fine apporta la modifica.

In altri programmi UNIX, le espressioni regolari vengono utilizzate solo per la selezione, come nel
comando sam g, mai per l'estrazione come nel comando x o y. Ad esempio, i pattern in awk7 sono
usati per selezionare le linee su cui operare, ma non possono essere usati per descrivere il formato
del testo di input, o per gestire il testo senza linee. L'uso di espressioni regolari per descrivere la
struttura di un pezzo di testo piuttosto che il suo contenuto, come nel comando x, è stato dato un
nome: espressioni regolari strutturali. Quando sono composti, come nell'esempio sopra, sono
piacevolmente espressivi. Il loro uso è discusso in maggiore misura altrove.10
------------------------------------------------------------------------------------------------------------------
Un editor di testo minimalista con un'interfaccia grafica utente, un linguaggio di comando molto
potente e funzionalità di editing remoto. Scritto da Rob Pike.

Inoltre potrebbe essere considerata una versione grafica del venerabile farmaco con steroidi, molti
steroidi.

Ports:
La fonte di Sam è estremamente portatile e le porte esistono per molte piattaforme, eccone alcune:

La versione sam più recente e "ufficiale" è inclusa in Plan 9 di Bell Labs e Plan 9 di User Space
per sistemi operativi di tipo Unix (inclusi Linux e * BSD).
Una porta autonoma di sam di p9p su Windows, include alcune utilità da riga di comando (grep,
awk, 9p, ecc.) Di Michael Teichgräber. (Scarica mirror.) Bitbucket * Build aggiornato di pf9,
segnalato per funzionare su Windows a 64 bit.
deadpixi / sam - Una versione estesa di sam basata sulla porta netlib degli anni '80 che include
molte nuove funzionalità. Più file

Sam ha alcuni altri comandi, principalmente relativi all'input e all'output.

e nomefile

sostituisce il contenuto e il nome del file corrente con quelli del file disco denominato;

w discfilename

scrive il contenuto nel file disco indicato; e

r nomefile

sostituisce punto con il contenuto del file disco indicato. Tutti questi comandi usano il nome del file
corrente se nessuno è specificato. Finalmente,

f nomefile

cambia il nome associato al file e visualizza il risultato:

‘-. discfilename

Questo output è chiamato riga del menu del file, poiché è il contenuto della riga del file nel menu
del pulsante 3 (descritto nella sezione successiva). I primi tre caratteri sono una notazione concisa
per lo stato del file. L'apostrofo indica che il file è stato modificato. Il segno meno indica il numero
di finestre aperte sul file (vedere la sezione successiva): - significa nessuno, + significa uno e *
significa più di uno. Infine, il punto indica che questo è il file corrente. Questi caratteri sono utili
per controllare il comando X, descritto a breve. Sam può essere avviato con un set di file disco
(come tutti i sorgenti di un programma) invocandolo con un elenco di nomi di file come argomenti,
e altri possono essere aggiunti o cancellati su richiesta.

B discfile1 discfile2 ...

aggiunge i file nominati alla lista di sam, e

D discfile1 discfile2 ...

li rimuove dalla memoria di sam (senza effetti sui file di dischi associati). Entrambi questi comandi
hanno una sintassi per l'utilizzo di shell7 (l'interprete dei comandi UNIX) per generare gli elenchi:

B <echo * .c

aggiungerà tutti i file sorgente C e


B <grep -l variable * .c

aggiungerà tutti i file sorgente C che fanno riferimento a una particolare variabile (il comando
UNIX grep \ fP-l elenca tutti i file nei suoi argomenti che contengono corrispondenze
dell'espressione regolare specificata). Infine, D senza argomenti cancella il file corrente.

Ci sono due modi per cambiare quale file è attuale:

b nomefile
rende corrente il file con nome. Il comando B fa lo stesso, ma aggiunge anche nuovi file alla lista di
sam. (In pratica, ovviamente, il file corrente viene solitamente scelto dalle azioni del mouse, non dai
comandi testuali.) L'altro modo è usare una forma di indirizzo che si riferisce ai file:

indirizzo "espressione"

si riferisce all'indirizzo valutato nel file la cui riga di menu corrisponde all'espressione (deve esserci
esattamente una corrispondenza). Per esempio,

"peter.c" 3

si riferisce alla terza riga del file il cui nome corrisponde a peter.c. Questo è molto utile nei comandi
move (m) e copy (t):

0, $ t "peter.c" 0

crea una copia del file corrente all'inizio di peter.c.

Il comando X è un costrutto di loop, come x, che si riferisce ai file anziché alle stringhe:

X / espressione / comando

esegue il comando in tutti i file le cui linee di menu corrispondono all'espressione. Il miglior
esempio è

X / '/ w

che scrive sul disco tutti i file modificati. Y è il complemento di X: esegue il comando su tutti i file
le cui linee di menu non corrispondono all'espressione:

Y/\c/D

cancella tutti i file che non hanno .c nei loro nomi, cioè, conserva tutti i file sorgente C ed elimina il
resto.

Le parentesi consentono di raggruppare i comandi, quindi


{

command1

comando2
}

è sintatticamente un singolo comando che esegue due comandi. Così,

X / \ c /, g / variabile / {

, x /.* \ n / g / variabile / p

trova tutte le occorrenze della variabile nei file di origine C e stampa i nomi e le linee di ogni
corrispondenza. La semantica precisa delle operazioni composte è discussa nelle seguenti sezioni di
implementazione.

Infine, il comando annulla annulla l'ultimo comando, indipendentemente dal numero di file
interessati. Le operazioni di annullamento multiple si spostano più indietro nel tempo, quindi

(che può essere abbreviato u2) annulla gli ultimi due comandi. Un annullamento non può essere
annullato, tuttavia, né alcun comando che aggiunge o elimina file. Tutto il resto è annullabile, anche
se, ad esempio, i comandi e:

e nomefile

ripristina completamente lo stato del file, inclusi il nome, il punto e il bit modificato. A causa della
cancellazione, i comandi potenzialmente pericolosi non sono protetti da conferme. Solo D, che
distrugge le informazioni necessarie per ripristinare se stesso, è protetto. Non cancellerà un file
modificato, ma una seconda D dello stesso file avrà successo a prescindere. Il comando q, che esce
da sam, è parimenti protetto.

Interfaccia mouse

Sam è più comunemente collegato a un display e un mouse bitmap per l'editing interattivo. L'unica
differenza nella lingua di comando tra sam e sam \ fP-d regolari e basati sul mouse è che se un
indirizzo è fornito senza un comando, sam \ fP-d stamperà il testo a cui fa riferimento l'indirizzo,
ma sam regolare evidenzierà sullo schermo - infatti, il punto è sempre evidenziato (vedi Figura 2).
Ogni file potrebbe avere zero o più finestre aperte sul display. In qualsiasi momento, solo una
finestra in tutto sam è la finestra corrente, cioè la finestra a cui si riferiscono le azioni di digitazione
e del mouse; questa può essere la finestra sam (quella in cui i comandi possono essere digitati) o
una delle finestre dei file. Quando un file ha più finestre, l'immagine del file in ogni finestra viene
sempre aggiornata. Il file corrente è l'ultimo file interessato da un comando, quindi se la finestra
sam è attuale, la finestra corrente non è una finestra sul file corrente. Tuttavia, ogni finestra su un
file ha il proprio valore di punto e, quando si passa da una finestra all'altra su un singolo file, il
valore del punto del file viene modificato in quello della finestra. Quindi, il capovolgimento tra le
finestre si comporta in modo ovvio e conveniente.
Il mouse sul Blit ha tre pulsanti, numerati da sinistra a destra. Il pulsante 3 ha un elenco di comandi
per manipolare le finestre, seguito da un elenco di "righe di menu" esattamente come stampato dal
comando f, uno per file (non uno per finestra). Queste righe di menu sono ordinate in base al nome
del file. Se l'elenco è lungo, il software del menu Blit lo renderà più gestibile generando un menu a
scorrimento invece di una lunga lista. L'uso del menu per selezionare un file dall'elenco rende quel
file il file corrente e la finestra corrente più recente in quel file la finestra corrente. Ma se quel file è
già attuale, selezionandolo nel menu scorre ciclicamente le finestre del file; questo semplice trucco
evita un menu speciale per scegliere Windows su un file. Se non ci sono finestre aperte sul file, sam
cambia il cursore del mouse per richiedere all'utente di crearne uno.

I comandi del menu del pulsante 3 sono intuitivi (vedi Figura 3) e sono come i comandi per
manipolare le finestre in mux, 8 il sistema di finestre di Blit. New crea un nuovo file e gli dà una
finestra vuota, la cui dimensione è determinata da un rettangolo trascinato dal mouse. Zerox
richiede di selezionare una finestra e crea un clone di quella finestra; questo è il modo in cui più
finestre vengono create su un unico file. Risagoma cambia la dimensione della finestra indicata e
chiude lo elimina. Se quella è l'ultima finestra aperta sul file, chiudi prima esegue un comando D sul
file. La scrittura è identica a un comando w sul file; è nel menu puramente per comodità. Infine, ~~
sam ~~ è una voce di menu che appare tra i comandi e i nomi dei file. La sua selezione rende la
finestra sam la finestra corrente, causando la successiva digitazione da interpretare come comandi.
Quando sam richiede che una finestra venga spazzata, in risposta a new, zerox o reshape, cambia il
cursore del mouse dalla solita freccia a una casella con una piccola freccia. In questo stato, il mouse
può essere usato per indicare un rettangolo arbitrario premendo il pulsante 3 in un angolo e
rilasciandolo nell'angolo opposto. Più convenientemente, il pulsante 3 può semplicemente essere
cliccato, dopo di che Sam crea il rettangolo massimale che contiene il cursore e confina con la
finestra sam. Posizionando la finestra sam al centro dello schermo, l'utente può definire due regioni
(una sopra, una sotto) in cui è possibile creare finestre sovrapposte completamente sovrapposte con
il minimo sforzo (vedi Figura 1). Questo semplice trucco dell'interfaccia utente semplifica
notevolmente la creazione di finestre.

L'editor di copia e incolla è essenzialmente lo stesso di Smalltalk-80.11 Il testo in punto è sempre


evidenziato sullo schermo. Quando un carattere viene digitato, sostituisce il punto e imposta il
punto sulla stringa nulla dopo il carattere. Pertanto, la digitazione ordinaria inserisce il testo. Il
pulsante 1 viene utilizzato per la selezione: premendo il pulsante, spostando il mouse e sollevando il
pulsante si seleziona (imposta il punto su) il testo tra i punti in cui è stato premuto e rilasciato il
pulsante. Premendo e rilasciando nello stesso punto si seleziona una stringa nulla; questo si chiama
cliccando. Cliccando due volte rapidamente o facendo doppio clic, seleziona oggetti più grandi; ad
esempio, facendo doppio clic su una parola si seleziona la parola, facendo doppio clic appena
all'interno di una parentesi aperta si seleziona il testo contenuto nelle parentesi (gestendo
correttamente le parentesi annidate), e similmente per parentesi, virgolette e così via. Le regole di
doppio clic riflettono un pregiudizio verso i programmatori. Se sam fosse inteso più per
l'elaborazione di testi, i doppi clic selezionerebbero probabilmente strutture linguistiche come le
frasi. Se il pulsante 1 viene premuto all'esterno della finestra corrente, rende corrente la finestra
indicata. Questo è il modo più semplice per passare da windows a file.

Premendo il pulsante 2 si apre un menu di funzioni di modifica (vedere la Figura 4). Questi si
applicano principalmente al testo selezionato: cut cancella il testo selezionato e lo ricorda in un
buffer nascosto chiamato buffer di snarf, incolla sostituisce il testo selezionato con il contenuto del
buffer snarf, snarf copia semplicemente il testo selezionato nel buffer snarf, cerca le ricerche in
avanti per la successiva occorrenza letterale del testo selezionato, e <mux> scambia i buffer dello
snarf con il sistema di finestre in cui Sam è in esecuzione. Infine, l'ultima espressione regolare usata
appare come una voce di menu per cercare in avanti la prossima occorrenza di una corrispondenza
per l'espressione.
La relazione tra la lingua di comando e quella del mouse è interamente dovuta all'uguaglianza di
punto e al testo selezionato scelto con il pulsante 1 del mouse. Ad esempio, per creare una serie di
modifiche in una subroutine C, è possibile impostare il punto facendo doppio clic sulla parentesi
sinistra che inizia la subroutine, che imposta il punto per la lingua di comando. Un comando senza
indirizzo che è stato digitato nella finestra sam si applicherà solo al testo tra le parentesi di apertura
e di chiusura della funzione. L'idea è di selezionare ciò che vuoi, e poi dire cosa vuoi fare con esso,
sia invocato da una selezione di menu o da un comando digitato. E, naturalmente, il valore del
punto viene evidenziato sul display dopo il completamento del comando. Questa relazione tra
l'interfaccia del mouse e il linguaggio di comando è maldestra da spiegare, ma comoda, anche
naturale, nella pratica.

L'implemento

Le prossime sezioni descrivono come viene assemblato Sam, prima la parte host, poi la
comunicazione inter-componente, quindi la parte terminale. Dopo aver spiegato come viene
implementata la lingua di comando, la discussione segue (approssimativamente) il percorso di un
personaggio dal file temporaneo sul disco allo schermo. La presentazione si concentra sulle strutture
dati, perché è così che il programma è stato progettato e perché gli algoritmi sono facili da fornire,
date le giuste strutture dati.

Analisi e esecuzione

La lingua di comando viene interpretata analizzando ogni comando con un parser di discesa
ricorsivo basato su tabella e quando viene assemblato un comando completo, richiamando un
esecutore top-down. La maggior parte degli editori impiegano invece un semplice scanner lessicale
di carattere alla volta. L'uso di un parser rende facile e non ambiguo rilevare quando un comando è
completo, che ha due vantaggi. Innanzitutto, le convenzioni di escape come i backslash per citare
comandi a più righe non sono necessarie; se il comando non è finito, il parser continua a leggere. Ad
esempio, un'appendice a più righe gestita da un comando x è semplice:

x /.* \ n / g / Peter / a
una riga su Peter

un'altra riga su Peter

Altri editor UNIX richiederebbero un backslash dopo tutto tranne l'ultima riga.

L'altro vantaggio è specifico per la struttura a due processi di sam. Il processo host deve decidere
quando un comando è completato in modo da poter chiamare l'interprete dei comandi. Questo
problema viene facilmente risolto facendo in modo che l'analizzatore lessicale legga il singolo
flusso di eventi dal terminale, eseguendo direttamente tutti i comandi di digitazione e del mouse, ma
passando ai caratteri del parser digitati nella finestra del comando sam. Questo schema è
leggermente complicato dalla disponibilità della modifica di taglia e incolla nella finestra sam, ma
tale difficoltà viene risolta applicando le regole usate in mux: quando una nuova riga viene digitata
nella finestra sam, tutto il testo tra la nuova riga e la la nuova riga precedentemente digitata viene
resa disponibile al parser. Ciò consente di eseguire modifiche arbitrarie a un comando prima di
digitare newline e quindi di richiedere l'esecuzione.
Il parser è guidato da una tabella perché la sintassi degli indirizzi e dei comandi è abbastanza
regolare da essere codificata in modo compatto. Ci sono alcuni casi speciali, come il testo di
sostituzione in una sostituzione, quindi la sintassi di quasi tutti i comandi può essere codificata con
alcuni flag. Questi includono se il comando consente un indirizzo (ad esempio, e non lo fa), se
prende un'espressione regolare (come in x e s), se prende il testo sostitutivo (come in c o i), che può
essere multilinea , e così via. La sintassi interna delle espressioni regolari viene gestita da un parser
separato; un'espressione regolare è una foglia dell'albero di analisi del comando. Le espressioni
regolari sono discusse a fondo nella prossima sezione.

La tabella parser contiene anche informazioni sui valori predefiniti, quindi l'interprete viene sempre
chiamato con un albero completo. Ad esempio, il parser riempie gli 0 e $ impliciti nell'indirizzo
abbreviato, (virgola), inserisce un + a sinistra di un'espressione regolare senza ornato in un indirizzo
e fornisce il solito indirizzo predefinito. (punto) per i comandi che prevedono un indirizzo ma non
ne danno uno.

Una volta analizzato un comando completo, la valutazione è semplice. L'indirizzo viene valutato da
sinistra a destra a partire dal valore del punto, con un valutatore di espressione per lo più ordinario.
Gli indirizzi, come molte delle strutture di dati in sam, sono tenuti in una struttura C e sono passati
per valore:

tipicamente lungo Posn; / * Posizione in un file * /

typedef struct Range {

Posn p1, p2;

}Gamma;

indirizzo typedef struct {

Intervallo r;

File * f;

}Indirizzo;

Un indirizzo è codificato come sottostringa (posizioni dei caratteri da p1 a p2) in un file f. (Il tipo di
dati File è descritto in dettaglio di seguito.)

L'interprete di indirizzi è una funzione con valore di indirizzo che attraversa l'albero di analisi che
descrive un indirizzo (l'albero di analisi per l'indirizzo ha tipo Aggiungi):

Indirizzo

indirizzo (ap, a, segno)

Addrtree * ap;

Indirizzo a;

int sign;
{

Indirizzo a2;

fare
Interruttore (ap-> tipo) {

Astuccio '.':

a = a.f-> dot;

rompere;

caso '$':

a.r.p1 = a.r.p2 = a.f-> nbytes;

rompere;

Astuccio '"':

a = matchfile (a, ap-> aregexp) -> punto;

rompere;

Astuccio ',':

a2 = indirizzo (ap-> right, a, 0);

a = indirizzo (ap-> left, a, 0);

if (a.f! = a2.f || a2.r.p2 <a.r.p1)

errore (eOrder);

a.r.p2 = a2.r.p2;

ritorno a;

/* e così via */

while ((ap = ap-> destra) = 0!);

ritorno a;

Gli errori vengono gestiti da un goto non locale (un setjmp / longjmp in terminologia C) nascosto in
una routine denominata errore che interrompe immediatamente l'esecuzione, ritrae eventuali
modifiche apportate parzialmente (vedere la sezione in basso su "annullamento") e restituisce al
livello più alto del parser. L'argomento dell'errore è un tipo di enumerazione che viene tradotto in un
messaggio terso ma possibilmente utile come "Indirizzi non funzionanti". I messaggi molto comuni
sono brevi; ad esempio, il messaggio per una ricerca di espressioni regolari non riuscite è "ricerca".

Gli indirizzi di carattere come # 3 sono semplici da implementare, poiché la struttura dei dati del
file è accessibile per numero di caratteri. Tuttavia, sam non conserva alcuna informazione sulla
posizione delle newline: è troppo costoso monitorare in modo dinamico, quindi gli indirizzi di linea
vengono calcolati leggendo il file, contando le nuove linee. Tranne che in file di grandi dimensioni,
ciò si è dimostrato accettabile: l'accesso ai file è abbastanza veloce da rendere la tecnica pratica e le
linee non sono centrali nella struttura del linguaggio dei comandi.

Anche l'interprete dei comandi, chiamato cmdexec, è semplice. La tabella di analisi include una
funzione da chiamare per interpretare un comando particolare. Tale funzione riceve come argomenti
l'indirizzo calcolato per il comando e l'albero dei comandi (di tipo Cmdtree), che può contenere
informazioni come la sottostruttura dei comandi composti. Qui, ad esempio, è la funzione per i
comandi g e v:

int

g_cmd (a, cp)

Indirizzo a;

Cmdtree * cp;

compilazione (CP-> regexp);

if (execute (a.f, a.r.p1, a.r.p2)! = (cp-> cmdchar == 'v')) {

a.f-> dot = a;

return cmdexec (a, cp-> subcmd);

return TRUE; / * causa l'esecuzione continua * /

(Compilare ed eseguire fanno parte del codice di espressione regolare, descritto nella sezione
successiva.) Poiché il parser e la struttura dei dati del File svolgono la maggior parte del lavoro, la
maggior parte dei comandi è altrettanto breve.
Espressioni regolari

Il codice di espressione regolare in sam è un'interpretazione, piuttosto che compilata al volo,


dell'implementazione dell'algoritmo di automa finito non deterministico di Thompson.12 La sintassi
e la semantica delle espressioni sono come nel programma UNIX egrep, incluse l'alternanza, le
chiusure, classi di personaggi e così via. Le uniche modifiche apportate alla notazione sono due
aggiunte: \ n è tradotto e corrisponde a un carattere di nuova riga e @ corrisponde a qualsiasi
carattere. In egrep, il personaggio. corrisponde a qualsiasi carattere tranne newline, e in sam la
stessa regola sembrava più sicura, per prevenire idiomi come. * da spanning newlines. Le
espressioni di Egrep sono forse troppo complicate per un editor interattivo - certamente avrebbe
senso se tutti i personaggi speciali fossero sequenze di due caratteri, così che la maggior parte dei
caratteri di punteggiatura non avrebbe significati particolari - ma per un linguaggio di comando
interessante, regolare e regolare le espressioni sono necessarie e egrep definisce la sintassi completa
delle espressioni regolari per i programmi UNIX. Inoltre, sembrava superfluo definire una nuova
sintassi, dal momento che vari programmi UNIX (ed, egrep e vi) ne definiscono già troppi.

Le espressioni sono compilate da una routine, compilazione, che genera la descrizione della
macLoops
Above, we changed one occurrence of Emacs to Emacs\360, but if the name of the editor is really
changing, it would be useful to change all instances of the name in a single command. sam provides
a
command, x (extract), for just that job. The syntax is x/pattern/command. For each occurrence of
the
pattern in the selected text, x sets dot to the occurrence and runs command. For example, to change
Emacs
to vi,china a stati finiti non deterministica. Una seconda routine, execute, interpreta la macchina per
generare la corrispondenza più a sinistra dell'espressione in una sottostringa del file. L'algoritmo è
descritto altrove.12,13 Execute segnala se è stata trovata una corrispondenza e imposta una
variabile globale, di tipo Range, sulla sottostringa corrispondente.

È necessario un trucco per valutare l'espressione al contrario, ad esempio quando si cerca


all'indietro per un'espressione. Per esempio,

- / * P. r /

guarda indietro nel file per una corrispondenza dell'espressione. L'espressione, tuttavia, è definita
per una ricerca diretta. La soluzione è costruire una macchina identica alla macchina per una ricerca
in avanti eccetto per un'inversione di tutti gli operatori di concatenazione (gli altri operatori sono
simmetrici in direzione inversa), per scambiare il significato degli operatori ^ e $, e quindi per
leggere il file all'indietro, cercando la solita partita più lunga.

Execute genera solo una corrispondenza ogni volta che viene chiamata. Per interpretare costrutti di
loop come il comando x, sam deve quindi sincronizzare tra le chiamate di esecuzione per evitare
problemi con le corrispondenze null. Ad esempio, anche data la regola più a sinistra più lunga,
l'espressione a * corrisponde a tre volte nella stringa ab (il carattere a, la stringa nulla tra a e b, e la
stringa nulla finale). Dopo aver restituito una corrispondenza per la a, sam non deve corrispondere
alla stringa nulla prima di b. L'algoritmo inizia a essere eseguito alla fine della sua precedente
corrispondenza e, se la corrispondenza restituita è nullo e si attesta sulla corrispondenza precedente,
rifiuta la corrispondenza e fa avanzare la posizione iniziale di un carattere.

Allocazione della memoria

Il linguaggio C non ha primitive di allocazione di memoria, sebbene una routine di libreria standard,
malloc, fornisca un servizio adeguato per programmi semplici. Per usi specifici, tuttavia, può essere
preferibile scrivere un allocatore personalizzato. L'allocatore (o meglio, coppia di allocatori) qui
descritto funziona sia nella parte terminale che in quella host di sam. Sono progettati per una
manipolazione efficiente delle stringhe, che vengono allocate e liberate frequentemente e variano in
lunghezza da essenzialmente zero a 32 Kbyte (le stringhe molto grandi vengono scritte sul disco).
Ancora più importante, le stringhe possono essere grandi e cambiare spesso le dimensioni, quindi
per ridurre al minimo l'utilizzo della memoria è utile recuperare e fondere le parti non utilizzate
delle stringhe quando vengono troncate.

Gli oggetti da allocare in sam sono di due tipi: il primo è C structs, che sono piccoli e spesso
indirizzati dalle variabili del puntatore; il secondo è costituito da matrici di caratteri variabili o interi
il cui puntatore di base viene sempre utilizzato per accedervi. L'allocatore di memoria in sam è
quindi in due parti: in primo luogo, un allocatore di primo adattamento tradizionale che fornisce una
memoria fissa per le strutture; e, in secondo luogo, un allocatore di compattazione dei rifiuti che
riduce il sovraccarico di memoria per oggetti di dimensioni variabili, a scapito di una contabilità. I
due tipi di oggetti sono allocati da arene adiacenti, con l'allocatore di compattazione dei rifiuti che
controlla l'arena con indirizzi più alti. La separazione in due arene semplifica la compattazione e
impedisce la frammentazione a causa di oggetti immobili. Le regole di accesso per gli oggetti
compattabili (discusse nel prossimo paragrafo) consentono loro di essere trasferiti, quindi quando
l'arena di primo livello ha bisogno di spazio, sposta l'arena compattata dai rifiuti in indirizzi più alti
per creare spazio. Pertanto, lo spazio di archiviazione viene creato solo a indirizzi successivi più
alti, sia quando è necessario più spazio compattato, sia quando l'arena di primo livello spinge verso
l'altro.

Gli oggetti che possono essere compattati dichiarano all'unità di allocazione una cella che è l'unico
repository dell'indirizzo dell'oggetto ogni volta che si verifica una compattazione. Il compattatore
può quindi aggiornare l'indirizzo quando l'oggetto viene spostato. Ad esempio, l'implementazione di
tipo List (veramente una matrice a lunghezza variabile) è:

typedef struct List {

int nused;

long * ptr;

}Elenco;

La cella ptr deve essere sempre utilizzata direttamente e mai copiata. Quando si deve creare una
lista, la struttura delle liste viene allocata nell'arena ordinaria di primo livello e il suo ptr viene
assegnato nell'arena compattata. Un tipo di dati simile per le stringhe, chiamato String, memorizza
matrici di caratteri a lunghezza variabile con un massimo di 32767 elementi.

Una questione correlata di stile di programmazione: sam passa frequentemente strutture per valore,
il che semplifica il codice. Tradizionalmente, i programmi C hanno passato le strutture per
riferimento, ma l'allocazione implicita nello stack è più facile da usare. Il passaggio della struttura è
una caratteristica relativamente nuova di C (non è nel manuale di riferimento standard per C14), ed
è scarsamente supportato nella maggior parte dei compilatori C commerciali. È comodo ed
espressivo, tuttavia, e semplifica la gestione della memoria, evitando del tutto l'allocatore ed
eliminando gli alias del puntatore.

Strutture dati per manipolare i file

L'esperienza con cui ho tenuto conto dei dati dei file erano pochi, ma rigorosi. Innanzitutto, i file
devono essere letti e scritti rapidamente; l'aggiunta di un nuovo file deve essere indolore. In
secondo luogo, l'implementazione non deve limitare arbitrariamente il numero o le dimensioni dei
file. (Dovrebbe essere pratico modificare molti file, e file lunghi fino a un megabyte essere gestito
con garbo.) Ciò implica che i file siano memorizzati sul disco, non nella memoria principale. (Gli
appassionati di memoria sono considerati semplici, ma l'implementazione della memoria è nel
nostro sistema.) In terzo luogo, le modifiche ai file devono essere apportate solo da due primitive:
eliminazione e inserimento . Queste sono inverse l'una dell'altra, il che semplifica
l'implementazione dell'operazione di annullamento. Infine, deve essere facile ed efficiente da file, in
avanti o all'indietro, un byte alla volta.

Il tipo di dati è stato realizzato da tre strutture di dati più semplici che contengono matrici di
caratteri. Ognuno di questi tipi è un operatore di inserimento e sostituzione e operatori di file.

Il tipo più semplice è String, che viene utilizzato per contenere le stringhe nella memoria principale.
Il codice che gestisce Stringhe che non sono mai più lunghe di una dimensione moderata e in
pratica raramente superano gli 8 Kbyte. Le stringhe hanno due esigenze: string stringhe brevi come
i nomi dei file over poco e, poiché sono volutamente piccole, sono efficienti da modificare. Quindi
quindi vengono presentati struttura dati per le cache in memoria.

La copia del disco è gestita da una struttura dati chiamata Disco, che offre un file temporaneo. Un
disco non ha memoria nella memoria principale oltre alle informazioni di contabilità; i dati sono
così belli che sono tutti sul disco. Per ridurre il numero di file aperti, si apre una dozzina di file
temporanei UNIX e li sovrappone su più dischi. Ciò consente di modificare molti file; l'intera
sorgente sam (48 file) può essere modificata con una singola istanza di sam. L'assegnazione di un
file temporaneo per disco è il limite del sistema operativo al numero di file aperti. Inoltre, la
diffusione del traffico tra i file temporanei mantiene i file più brevi e i file più brevi sono
implementati in modo più efficiente dal sottosistema di I / O UNIX.
Un disco è un array di blocchi a lunghezza fissa, ognuno dei quali contiene da 1 a 4096 caratteri di
dati attivi. (La dimensione del blocco del nostro file system UNIX è 4096 byte). Gli indirizzi di
blocco all'interno del file temporaneo e la lunghezza di ciascun blocco sono memorizzati in un
elenco. Quando vengono apportate modifiche, la parte live dei blocchi può cambiare dimensione. I
blocchi vengono creati e combinati quando necessario per cercare di mantenere le dimensioni tra
2048 e 4096 byte. Di conseguenza, una parte che cambia attivamente del Disco ha circa un kilobyte
di slop che può essere inserito o eliminato senza cambiare più di un blocco o influenzando l'ordine
di blocco. Quando un inserimento si sovrappone a un blocco, il blocco viene diviso, uno nuovo
viene assegnato per ricevere l'overflow e l'elenco di blocchi residenti in memoria viene
riorganizzato per riflettere l'inserimento del nuovo blocco.

Ovviamente, andare al disco per ogni modifica al file è proibitivo. Il tipo di dati Buffer consiste in
un disco per contenere i dati e una stringa che funge da cache. Questo è il primo di una serie di
cache in tutte le strutture di dati in sam. Le cache non solo migliorano le prestazioni, ma forniscono
un modo per organizzare il flusso di dati, in particolare nella comunicazione tra l'host e il terminale.
Questa idea è sviluppata di seguito, nella sezione sulle comunicazioni.

Per ridurre il traffico del disco, le modifiche a un buffer sono mediate da una stringa di lunghezza
variabile, in memoria, che funge da cache. Quando viene effettuato un inserimento o una
cancellazione su un Buffer, se il cambiamento può essere sistemato dalla cache, viene fatto lì. Se la
cache diventa più grande di un blocco a causa di un inserimento, parte di essa viene scritta sul disco
e cancellata dalla cache. Se la modifica non interseca la cache, la cache viene svuotata. La cache
viene caricata solo nella nuova posizione se la modifica è inferiore a un blocco; in caso contrario,
viene inviato direttamente al disco. Questo perché le grandi modifiche sono in genere sequenziali,
quindi è improbabile che la prossima modifica si sovrapponga a quella corrente.

Un file comprende una stringa per contenere il nome del file e alcuni dati ausiliari come il punto e il
bit modificato. I componenti più importanti, tuttavia, sono una coppia di buffer, uno chiamato
trascrizione e l'altro il contenuto. Il loro uso è descritto nella prossima sezione.La struttura generale
è mostrata nella Figura 5. Sebbene possa sembrare che i dati siano stati toccati più volte dal Disco,
viene letto (da una chiamata di sistema UNIX) direttamente nella cache del Buffer associato; non
viene eseguita alcuna copia aggiuntiva. Allo stesso modo, quando si svuota la cache, il testo viene
scritto direttamente dalla cache al disco. La maggior parte delle operazioni agisce direttamente sul
testo nella cache. Un principio applicato in Sam è che meno volte i dati vengono copiati, più
velocemente verrà eseguito il programma (vedere anche il documento di Waite15).
Il contenuto di un file è accessibile da una routine che copia su un buffer una sottostringa di un file
che inizia con un offset specificato. Per leggere un byte alla volta, viene caricato un array per-File a
partire da una posizione iniziale specificata e quindi i byte possono essere letti dall'array.
L'implementazione viene eseguita da una macro simile alla macro getc I / O standard C. 14 Poiché
la lettura può essere eseguita in qualsiasi indirizzo, una modifica minore alla macro consente di
leggere il file all'indietro. Questa matrice è di sola lettura; non c'è nessun putc.

Fare e disfare

Sam ha un metodo insolito per la gestione delle modifiche ai file. Il linguaggio di comando rende
facile specificare più modifiche di lunghezza variabile su un file di milioni di byte e tali modifiche
devono essere apportate in modo efficiente se l'editor deve essere pratico. Le solite tecniche per
l'inserimento e l'eliminazione di stringhe sono inadeguate in queste condizioni. Le strutture di dati
del buffer e del disco sono progettate per un accesso casuale efficiente alle stringhe lunghe, ma
occorre prestare attenzione per evitare il comportamento super-lineare quando si eseguono più
modifiche contemporaneamente.

Sam utilizza un algoritmo a due passaggi per apportare modifiche e tratta ciascun file come un
database rispetto al quale sono registrate le transazioni. Le modifiche non vengono apportate
direttamente ai contenuti. Invece, quando viene avviato un comando, viene inserito un
"contrassegno" contenente un numero di sequenza nel buffer di trascrizione e ogni modifica
apportata al file, un inserimento o una cancellazione o una modifica al nome del file, viene aggiunta
alla fine di la trascrizione. Quando il comando è completo, la trascrizione viene riavvolta al segno e
applicata ai contenuti.Una ragione per separare la valutazione dall'applicazione in questo modo è
semplificare il tracciamento degli indirizzi delle modifiche apportate nel mezzo di una lunga
sequenza. L'algoritmo a due passaggi consente inoltre a tutte le modifiche di applicare i dati
originali: nessuna modifica può influire su un'altra modifica effettuata nello stesso comando. Ciò è
particolarmente importante quando si valuta un comando x perché impedisce alle corrispondenze di
espressioni regolari di inciampare sulle modifiche apportate in precedenza nell'esecuzione. Inoltre,
l'algoritmo a due passaggi è più pulito del modo in cui gli altri editor UNIX consentono alle
modifiche di influenzarsi a vicenda; per esempio, gli idi di Ed per fare cose come cancellare ogni
altra linea dipendono in modo critico dall'implementazione. Invece, il semplice modello di sam, in
cui tutti i cambiamenti di un comando avvengono simultaneamente in modo efficace, è facile da
spiegare e da comprendere.

I record nella trascrizione sono del tipo '' cancella sottostringa dalle posizioni 123 a 456 '' e ''
inserisci 11 caratteri 'ciao li' nella posizione 789. '' (È un errore se le modifiche non sono in
posizioni monotonicamente maggiori attraverso il file.) Mentre si sta verificando l'aggiornamento,
questi numeri devono essere compensati da modifiche precedenti, ma ciò è diretto e locale alla
routine di aggiornamento; inoltre, tutti i numeri sono stati calcolati prima che il primo venga
esaminato.

Trattare il file come un sistema di transazione ha un altro vantaggio: annullare è banale. Tutto ciò
che serve è invertire la trascrizione dopo che è stata implementata, convertendo gli inserimenti in
delezioni e viceversa e salvandoli in un buffer di attesa. La trascrizione "do" può quindi essere
cancellata dal buffer di trascrizione e sostituita dalla trascrizione "annulla". Se viene richiesto un
annullamento, la trascrizione viene riavvolta e viene eseguita la trascrizione annullata. Poiché il
buffer di trascrizione non viene troncato dopo ogni comando, accumula modifiche successive. Una
sequenza di comandi di annullamento può quindi eseguire il backup del file in modo arbitrario, il
che è più utile rispetto alla forma di inversione automatica inversa più comunemente implementata.
(Sam non fornisce alcun modo per annullare un annullamento, ma se lo si desidera, sarebbe facile
da fornire reinterpretando la trascrizione "do".) Ogni segno nella trascrizione contiene un numero di
sequenza e l'offset nella trascrizione del marchio precedente, per aiutare a sciogliere la trascrizione.
I segni contengono anche il valore del punto e il bit modificato, in modo che questi possano essere
ripristinati facilmente. Annullare più file è facile; richiede semplicemente di annullare tutti i file la
cui ultima modifica ha lo stesso numero di sequenza del file corrente.
Un altro vantaggio di avere una trascrizione è che gli errori incontrati nel mezzo di un comando
complicato non devono lasciare i file in uno stato intermedio. Riavvolgendo la trascrizione nel
segno che inizia il comando, il comando parziale può essere banalmente annullato.

Quando l'algoritmo di aggiornamento è stato implementato per la prima volta, era inaccettabilmente
lento, quindi è stata aggiunta una cache per coalizzare le modifiche nelle vicinanze, sostituendo più
piccole modifiche con una singola più grande. Ciò ha ridotto il numero di inserimenti nel buffer di
transazione e ha apportato un notevole miglioramento delle prestazioni, ma ha reso impossibile
gestire le modifiche nell'ordine non monotonico nel file; il metodo di memorizzazione nella cache
funziona solo se le modifiche non si sovrappongono. Prima che la cache venisse aggiunta, la
transazione potrebbe in linea di principio essere ordinata se le modifiche fossero fuori servizio,
sebbene ciò non fosse mai stato fatto. Lo stato attuale è quindi una prestazione accettabile con una
restrizione minore ai cambiamenti globali, che a volte è, ma raramente, un fastidio.

L'algoritmo di aggiornamento ovviamente rimpicciolisce i dati più che semplici algoritmi, ma non è
eccessivamente costoso; le cache aiutano. (Il principio di evitare la copia dei dati è ancora qui
onorato, anche se non in modo pio: i dati vengono trasferiti dalla cache del contenuto al transcript
tutto in una volta e attraverso un solo buffer interno.) I dati sulle prestazioni confermano
l'efficienza. Per leggere da zero, un file di cento kilobyte su un VAX-11/750 richiede 1,4 secondi di
tempo utente, 2,5 secondi di tempo di sistema e 5 secondi di tempo reale. Leggere lo stesso file in
ed impiega 6,0 secondi di tempo utente, 1,7 secondi di tempo di sistema e 8 secondi di tempo reale.
Sam usa circa la metà del tempo della CPU. Un esempio più interessante è quello sopra indicato:
inserire un carattere tra ogni coppia di caratteri nel file. Il comando sam è

,y/@/a/x/

e richiede 3 secondi CPU per kilobyte di file di input, di cui circa un terzo viene speso nel codice di
espressioni regolari. Questo si traduce in circa 500 cambiamenti al secondo. Ed impiega 1,5 secondi
per kilobyte per fare un cambiamento simile (ignorando le nuove linee), ma non può annullarlo. Lo
stesso esempio in ex, 9 una variante di ed eseguito presso l'Università della California a Berkeley,
che consente un livello di annullamento, richiede ancora 3 secondi. In breve, la performance di sam
è paragonabile a quella di altri editor UNIX, sebbene risolva un problema più difficile.
comunicazioni

La discussione finora ha descritto l'implementazione della parte host di sam; le prossime sezioni
spiegano come una macchina con display mouse e bitmap può essere attivata per migliorare
l'interazione. Sam non è il primo editor ad essere scritto come due processi, 16 ma la sua
implementazione ha alcuni aspetti insoliti.

Esistono vari modi per collegare l'host e le parti del terminale di Sam. Il primo e il più semplice è
quello di rinunciare alla parte terminale e utilizzare la lingua di comando della parte host per
modificare il testo su un normale terminale. Questa modalità viene invocata avviando sam con
l'opzione -d. Senza opzioni, sam esegue programmi host e terminali separati, comunicando con un
protocollo di messaggi sulla connessione fisica che li unisce. In genere, la connessione è un
collegamento RS-232 tra un Blit (il display prototipo per sam) e un host che esegue la nona
edizione del sistema operativo UNIX.8 (Questa è la versione del sistema utilizzata nel Computing
Sciences Research Center di AT & T Bell Laboratories [ora Lucent Technologies, Bell Labs], dove
lavoro. I suoi aspetti rilevanti sono discussi nel Blit paper.1) L'implementazione di sam per il
computer SUN esegue entrambi i processi sulla stessa macchina e li collega tramite una pipe.

La bassa larghezza di banda di un collegamento RS-232 richiedeva la divisione tra i due


programmi. La divisione è una benedizione mista: un programma in due parti è molto più difficile
da scrivere e eseguire il debug rispetto a uno autonomo, ma la divisione rende possibili diverse
configurazioni insolite. Il terminale può essere fisicamente separato dall'host, consentendo di
portare a casa le comodità di un mouse e di una visualizzazione bitmap lasciando i file al lavoro. È
anche possibile eseguire la parte host su una macchina remota:

sam -r host

si connette al terminale nel solito modo e quindi effettua una chiamata attraverso la rete per stabilire
la parte host di sam sulla macchina indicata. Infine, collega trasversalmente l'I / O per unire le due
parti. Ciò consente a sam di essere eseguito su macchine che non supportano le visualizzazioni
bitmap; per esempio, sam è l'editor di scelta sul nostro Cray X-MP / 24. Sam -r coinvolge tre
macchine: l'host remoto, il terminale e l'host locale. Il lavoro dell'host locale è semplice ma vitale:
passa i dati tra l'host remoto e il terminale.

L'host e il terminale scambiano messaggi in modo asincrono (piuttosto che, ad esempio, come
chiamate di procedura remota) ma non viene rilevato alcun errore o correzione perché, qualunque
sia la configurazione, la connessione è affidabile. Poiché il terminale gestisce attività di interazione
banali come l'apertura di menu e l'interpretazione delle risposte, i messaggi riguardano i dati, non le
azioni. Ad esempio, l'host non sa nulla di ciò che viene visualizzato sullo schermo e quando l'utente
digita un carattere, il messaggio inviato all'host dice "Inserisci una stringa di un byte nella posizione
123 nel file 7," "non" un carattere è stato digitato nella posizione corrente nel file corrente. "In altre
parole, i messaggi assomigliano molto ai record delle transazioni nelle trascrizioni.

O l'host o la parte terminale di sam può avviare una modifica a un file. La lingua di comando opera
sull'host, mentre si digita e alcune operazioni del mouse vengono eseguite direttamente nel
terminale per ottimizzare la risposta. Le modifiche avviate dal programma host devono essere
trasmesse al terminale e viceversa. (Viene scambiato un token per determinare quale fine ha il
controllo, il che significa che i caratteri digitati mentre è in esecuzione un comando che richiede
tempo devono essere memorizzati nel buffer e non apparire fino al completamento del comando.)
Per mantenere informazioni coerenti, la traccia host e terminale cambia attraverso una struttura dati
per file che registra le parti del file che il terminale ha ricevuto. La struttura dati, chiamata Rasp (un
gioco di parole debole: è un file con buchi) viene tenuta e aggiornata sia dall'host sia dal terminale.
A Rasp è un elenco di stringhe che contengono quelle parti del file note al terminale, separate dai
conteggi del numero di byte negli interstizi. Ovviamente, l'host non conserva una copia separata dei
dati (ha solo bisogno delle lunghezze dei vari pezzi), ma la struttura è la stessa su entrambe le
estremità.

The Rasp nel terminale funge anche da cache. Poiché il terminale mantiene il testo per porzioni del
file che ha visualizzato, non è necessario richiedere dati dall'host durante la rivisitazione di vecchie
parti del file o il ridisegno di finestre oscurate, il che accelera considerevolmente i collegamenti a
bassa velocità.
È banale che il terminale mantenga il suo Rasp, poiché tutte le modifiche apportate sul terminale si
applicano a parti del file già caricate lì. Le modifiche apportate dall'host vengono confrontate con
Rasp durante la sequenza di aggiornamento dopo ciascun comando. Piccole modifiche ai pezzi del
file caricato nel terminale vengono inviate nella loro interezza. Le modifiche più grandi e le
modifiche che ricadono interamente nei fori vengono trasmesse come messaggi senza dati letterali:
vengono trasmesse solo le lunghezze delle stringhe cancellate e inserite. Quando un comando è
completato, il terminale esamina le sue finestre visibili per vedere se eventuali fori nel loro Raspe
intersecano la porzione visibile del file. Quindi richiede i dati mancanti dall'host, insieme a un
massimo di 512 byte di dati circostanti, per ridurre al minimo il numero di messaggi quando si
visita una nuova porzione del file. Questa tecnica fornisce una sorta di valutazione lazy a due livelli
per il terminale. Il primo livello invia un minimo di informazioni su parti del file che non vengono
modificate in modo interattivo; il secondo livello attende finché non viene visualizzato un
cambiamento prima di trasmettere i nuovi dati. Naturalmente, le prestazioni sono anche aiutate dal
fatto che il terminale risponde immediatamente alla digitazione e alle semplici richieste del mouse.
Tranne che per piccole modifiche a parti attive del file, che vengono trasmesse al terminale senza
negoziazione, il terminale è interamente responsabile della decisione di ciò che viene visualizzato;
l'host usa il raspa solo per dire al terminale cosa potrebbe essere rilevante.

Quando un cambiamento viene avviato dall'host, i messaggi al terminale che descrivono la modifica
vengono generati dalla routine che applica la trascrizione delle modifiche ai contenuti del file.
Poiché le modifiche vengono annullate dalla stessa routine di aggiornamento, l'annullamento non
richiede alcun codice aggiuntivo nelle comunicazioni; i soliti messaggi che descrivono le modifiche
al file sono sufficienti per eseguire il backup dell'immagine sullo schermo.

The Rasp è un esempio particolarmente valido del modo in cui le cache vengono utilizzate in sam.
Innanzitutto, facilita l'accesso alla parte attiva del testo posizionando il testo occupato nella
memoria principale. In tal modo, fornisce un accesso efficiente a una struttura di dati di grandi
dimensioni che non si adatta alla memoria. Dal momento che la forma dei dati deve essere imposta
dall'utente, non dal programma, e poiché i caratteri vengono spesso scansionati in sequenza, i file
vengono memorizzati come oggetti piani. Le cache aiutano a mantenere le prestazioni buone e
lineari quando si lavora con tali dati.
In secondo luogo, il Rasp e molte altre cache hanno alcuni read-ahead; cioè, la cache viene caricata
con più informazioni di quelle necessarie per il lavoro immediatamente a portata di mano. Quando
si manipolano strutture lineari, gli accessi sono in genere sequenziali e read-ahead può ridurre
significativamente il tempo medio di accesso all'elemento successivo dell'oggetto. L'accesso
sequenziale è una modalità comune per le persone e per i programmi; considera la possibilità di
scorrere un documento mentre cerchi qualcosa.

Infine, come ogni buona struttura dati, la cache guida l'algoritmo, o almeno l'implementazione. Il
Rasp fu in realtà inventato per controllare le comunicazioni tra l'host e le parti del terminale, ma mi
resi conto molto presto che era anche una forma di cache. Altre cache erano più esplicitamente
destinate a un duplice scopo: ad esempio, le cache in File che aggiorna gli aggiornamenti non solo
riducono il traffico alla trascrizione e ai buffer del contenuto, ma raggruppano anche gli
aggiornamenti dello schermo in modo da ottenere modifiche complicate dello schermo in un solo
pochi messaggi al terminale. Questo mi ha fatto risparmiare un lavoro considerevole: non avevo
bisogno di scrivere codice speciale per ottimizzare il traffico dei messaggi verso il terminale. Le
cache pagano in modi sorprendenti. Inoltre, tendono ad essere indipendenti, quindi i loro
miglioramenti delle prestazioni sono moltiplicativi.

Strutture dati nel terminale

Il compito del terminale è quello di visualizzare e mantenere un'immagine coerente dei pezzi dei
file modificati. Poiché il testo è sempre in memoria, le strutture dati sono notevolmente più semplici
di quelle nella parte host.
In genere, Sam ha molte più finestre rispetto a mux, il sistema di finestre all'interno del quale viene
eseguita l'implementazione Blit. Mux ha un numero abbastanza piccolo di finestre aggiornate in
modo asincrono; sam ha bisogno di un gran numero di finestre aggiornate in modo sincrono che
sono solitamente statiche e spesso completamente oscurate. I diversi compromessi hanno guidato
sam dall'implementazione intensiva della memoria di windows, chiamata Layers, 17 usata in mux.
Piuttosto che dipendere da un'immagine bitmap completa del display per ogni finestra, sam rigenera
l'immagine dal suo testo in memoria (memorizzato in Rasp) quando necessario, sebbene userà tale
immagine se è disponibile. Come i Layer, però, sam usa la bitmap dello schermo come memoria
attiva in cui aggiornare l'immagine usando bitblt. L'organizzazione risultante, illustrata nella Figura
6, ha una serie globale di finestre, chiamate Flayers, ognuna delle quali contiene un'immagine di un
pezzo di testo contenuto in una struttura dati chiamata Frame, che a sua volta rappresenta una
finestra rettangolare piena di testo visualizzato in alcuni Bitmap. Ogni Flayer appare in una lista
globale che li ordina tutti front-to-back sul display, e contemporaneamente come elemento di una
matrice per file che contiene tutte le finestre aperte per quel file. Il complemento nel terminale del
file sull'host è chiamato testo; ognuno collega i suoi Flayers al raspa associato.
La bitmap per una cornice contiene l'immagine del testo. Per una finestra completamente visibile, la
Bitmap sarà lo schermo (o almeno il Layer in cui viene eseguito sam), mentre per le finestre
parzialmente oscurate la Bitmap sarà fuori dallo schermo. Se la finestra è completamente oscurata,
Bitmap sarà nullo.

La bitmap è un tipo di cache. Quando si apportano modifiche al display, la maggior parte


dell'immagine originale apparirà uguale nell'immagine finale e gli algoritmi di aggiornamento lo
sfruttano. Il software Frame aggiorna l'immagine nella Bitmap in modo incrementale; la Bitmap
non è solo un'immagine, è una struttura dati.18,19 Il lavoro del software che aggiorna il display è
quindi quello di utilizzare il più possibile l'immagine esistente (la conversione del testo da caratteri
ASCII in pixel è costosa) in una sorta di algoritmo di inserimento di stringhe bidimensionale. I
dettagli di questo processo sono descritti nella prossima sezione.

Il software Frame non ha codice per supportare finestre sovrapposte; il suo compito è di mantenere
aggiornata una singola Bitmap. Spetta al software Flayer per multiplexare i vari Bitmap sullo
schermo. Il problema di mantenere Flayers sovrapposti è più semplice rispetto a Layers17 perché le
modifiche vengono eseguite in modo sincrono e poiché il contenuto della finestra può essere
ricostruito dai dati memorizzati nel Frame; il software Layers non formula tali presupposti. In sam,
la finestra che viene cambiata è quasi sempre completamente visibile, perché la finestra corrente è
sempre completamente visibile, per costruzione. Tuttavia, quando vengono apportate modifiche a
più file o quando più di una finestra è aperta su un file, potrebbe essere necessario aggiornare
finestre parzialmente oscurate.

Ci sono tre casi: la finestra è completamente visibile, invisibile (completamente oscurata) o


parzialmente visibile. Se completamente visibile, la Bitmap è parte dello schermo, quindi quando la
routine di aggiornamento Flayer chiama la routine di aggiornamento Frame, lo schermo verrà
aggiornato direttamente. Se la finestra è invisibile, non vi è alcuna Bitmap associata e tutto ciò che è
necessario è aggiornare la struttura dei dati del Frame, non l'immagine. Se la finestra è parzialmente
visibile, la routine Frame viene richiamata per aggiornare l'immagine nella bitmap fuori dallo
schermo, che potrebbe richiedere di rigenerarla dal testo della finestra. Il codice Flayer quindi
ritaglia questa bitmap contro le bitmap di tutti i fotogrammi di fronte al fotogramma in fase di
modifica e il resto viene copiato sul display.

Questo è molto più veloce di ricreare l'immagine fuori dallo schermo per ogni cambiamento, o di
ritagliare tutte le modifiche apportate all'immagine durante il suo aggiornamento. Sfortunatamente,
queste cache possono anche consumare quantità proibitive di memoria, quindi vengono liberate in
modo abbastanza liberale - dopo ogni modifica all'ordine frontale dei Flayers. Il risultato è che le
bitmap fuori dallo schermo esistono solo mentre si verificano cambiamenti multi-finestra, che è
l'unica volta che è necessario il miglioramento delle prestazioni che forniscono. Inoltre, l'interfaccia
utente fa sì che le finestre completamente oscurate siano le più facili da realizzare: la creazione di
una finestra posizionata canonicamente richiede solo un clic del pulsante, il che riduce
ulteriormente la necessità di eseguire il caching.
Aggiornamento dello schermo

Sono necessari solo due primitivi di basso livello per l'aggiornamento incrementale: bitblt, che
copia i rettangoli di pixel e la stringa (che a sua volta chiama bitblt), che disegna una stringa di
caratteri con terminazione null in una bitmap. Una cornice contiene un elenco di caselle, ognuna
delle quali definisce una striscia orizzontale di testo nella finestra (vedere la Figura 7). Una casella
ha un carattere stringa str e un rettangolo rettangolo che definisce la posizione della striscia nella
finestra. (Il testo in str è memorizzato nella casella separatamente dal raspa associato al file della
finestra, quindi le scatole sono autonome.) L'invariante è che l'immagine della scatola può essere
riprodotta chiamando la stringa con l'argomento str per disegnare la stringa in rect e l'immagine
risultante si adatta perfettamente al rect. In altre parole, le caselle definiscono la piastrellatura della
finestra. La piastrellatura può essere complicata da lunghe righe di testo, che vengono piegate sulla
riga successiva. Alcuni editor usano lo scrolling orizzontale per evitare questa complicazione, ma
per essere a proprio agio questa tecnica richiede che le linee non siano troppo lunghe; sam non ha
tale restrizione. Inoltre, e forse ancora più importante, i programmi e i terminali UNIX
tradizionalmente piegano le lunghe file per rendere i loro contenuti completamente visibili.

Due tipi speciali di scatole contengono un singolo carattere: una nuova riga o una scheda. Newlines
e tabs sono spazi bianchi. Una casella di nuova riga si estende sempre sul lato destro della finestra,
forzando la casella seguente alla riga successiva. La larghezza di una scheda dipende da dove si
trova: forza la casella successiva a iniziare in una posizione di tabulazione. Le schede hanno anche
una larghezza minima equivalente a uno spazio (gli spazi vengono disegnati da una stringa e non
vengono trattati in modo speciale); i newline hanno una larghezza minima pari a zero.
Gli algoritmi di aggiornamento utilizzano sempre l'immagine bitmap del testo (sia la
visualizzazione che la bitmap della cache); non esaminano mai i caratteri all'interno di un riquadro
tranne quando la scatola deve essere divisa in due. Prima di un cambiamento, la finestra consiste in
una piastrellatura di scatole; dopo la modifica la finestra viene affiancata in modo diverso. Gli
algoritmi di aggiornamento riorganizzano le tessere in posizione, senza spazio di archiviazione di
backup. Gli algoritmi non sono strettamente ottimali - ad esempio, possono cancellare un pixel che
in seguito verrà scritto - ma non spostano mai una tessera che non ha bisogno di essere spostata, e
spostano ogni tessera al massimo una volta. Frinsert su un Blit può assorbire più di mille caratteri al
secondo se le stringhe inserite sono lunghe alcune decine di caratteri.

Si consideri per favore. Il suo compito è eliminare una sottostringa da un Frame e ripristinare
l'immagine del Frame. L'immagine di una sottostringa ha una forma particolare (vedi Figura 2) che
comprende forse una linea parziale, zero o più linee piene, ed eventualmente una linea parziale
finale. Per riferimento, chiama questa la forma Z. L'eliminazione iniziale inizia dividendo, se
necessario, le caselle contenenti le estremità della sottostringa in modo che la sottostringa inizi e
finisca sui contorni della casella. Poiché la sottostringa viene eliminata, la sua immagine non è
necessaria, quindi la forma Z viene quindi cancellata. Quindi, le tessere (cioè le immagini delle
Scatole) vengono copiate, usando bitblt, immediatamente dopo la forma Z all'inizio della forma Z,
dando come risultato una nuova forma Z. (Le scatole il cui contenuto dovrebbe estendersi su due
righe nella nuova posizione devono prima essere divise).

Copiando il resto del riquadro Frame per tile in questo modo si otterrà chiaramente la cancellazione,
ma alla fine, in genere quando l'algoritmo di copia incontra una scheda o una nuova riga, le vecchie
e le nuove coordinate x della piastrella da copiare sono le stesse. Questa corrispondenza implica che
la forma a Z abbia i bordi iniziale e finale allineati verticalmente, e una sequenza di al massimo due
bit bit può essere usata per copiare le tessere rimanenti. L'ultimo passaggio consiste nello svuotare
lo spazio vuoto risultante nella parte inferiore della finestra; il numero di righe da cancellare è il
numero di linee complete nella forma a Z chiusa dai bit bit finali. Il passo finale è quello di unire le
caselle orizzontalmente adiacenti di testo normale. La fonte completa per l'eliminazione è inferiore
a 100 righe di C.

frinsert è più complicato perché deve fare quattro passaggi: uno per costruire la lista Box per la
stringa inserita, uno per ricognizione, uno per copiare (in ordine inverso per eliminare) i riquadri per
fare il buco per il nuovo testo, e infine uno per copiare il nuovo testo in posizione. Nel complesso,
tuttavia, frinsert ha un sapore simile all'elettricità, e non è necessario descriverlo ulteriormente.
Frinsert e le sue routine sussidiarie comprendono 211 linee di C.

Il codice sorgente del terminale è 3024 righe di C e la sorgente host è 5797 linee.
Discussione

Storia

L'antenato immediato di Sam era l'editor di testo originale per il Blit, chiamato jim. Sam ha
ereditato quasi invariata la struttura a due processi di jim e il linguaggio del mouse, ma Jim ha
sofferto di diversi inconvenienti che sono stati affrontati nella progettazione di Sam. Il più
importante di questi era la mancanza di un linguaggio di comando. Sebbene jim fosse facile da
usare per l'editing semplice, non forniva alcun aiuto diretto con attività di modifica grandi o
ripetitive. Invece, ha fornito un comando per passare il testo selezionato attraverso una pipeline di
shell, ma questo non era più soddisfacente di quanto ci si potesse aspettare da una misura di
stopgap.

Jim è stato scritto principalmente come un veicolo per sperimentare con un'interfaccia basata su
mouse per il testo, e l'esperimento ha avuto successo. Jim ha avuto alcuni spin-off: mux, il secondo
sistema di finestre per il Blit, è essenzialmente una versione multiplexata della parte terminale di
jim; e l'interfaccia utente di debugger pi20 era strettamente modellata su jim. Ma dopo un paio
d'anni, jim era diventato difficile da mantenere e limitante da usare, e la sua sostituzione era in
ritardo.

Ho iniziato la progettazione di Sam chiedendo ai clienti jim cosa volevano. Questo è stato
probabilmente un errore; le risposte erano essenzialmente un elenco di caratteristiche da trovare in
altri editori, che non fornivano nessuno dei principi guida che stavo cercando. Ad esempio, una
richiesta comune riguardava un "sostituto globale", ma nessuno ha suggerito come fornirlo
all'interno di un editor di taglia e incolla. Stavo cercando uno schema che supportasse tali
caratteristiche specializzate comodamente nel contesto di un linguaggio di comando generale. Le
idee non erano imminenti, tuttavia, in particolare data la mia insistenza sulla rimozione di tutti i
limiti sulle dimensioni dei file, sulle lunghezze delle linee e così via. Ancor peggio, ho riconosciuto
che, poiché il mouse poteva facilmente indicare una regione dello schermo che non era un numero
intero di linee, la lingua di comando sarebbe meglio dimenticare completamente le newline, e ciò
significava che il linguaggio di comando doveva trattare il file come un singola stringa, non una
serie di linee.

Alla fine, ho deciso che il pensiero non mi avrebbe portato molto lontano ed era giunto il momento
di provare a costruire. Sapevo che la parte terminale poteva essere costruita facilmente - quella parte
di jim si comportava in modo accettabile - e che la maggior parte del duro lavoro sarebbe stata nella
parte host: l'interfaccia file, l'interprete dei comandi e così via. Inoltre, avevo alcune idee su come
l'architettura di jim potesse essere migliorata senza distruggere la sua struttura di base, che in linea
di principio mi piaceva, ma che non aveva funzionato come speravo. Così ho iniziato progettando la
struttura dei dati dei file, iniziando dal modo in cui jim ha funzionato - paragonabile a una singola
struttura che unisce Disco e Buffer, che ho diviso per rendere la cache più generale - e pensando a
come il sostituto globale potrebbe essere implementato. La risposta era chiaramente che doveva
essere fatto in due passaggi, e l'implementazione orientata alla trascrizione è caduta naturalmente.

Sam è stato scritto dal basso, partendo dalle strutture dati e dagli algoritmi per manipolare il testo,
attraverso il linguaggio dei comandi e fino al codice per mantenere il display. In retrospettiva, è
andata bene, ma questo metodo di implementazione non è raccomandato in generale. Ci sono state
diverse volte in cui ho raccolto un grande numero di codice interessante e non ho idea di come
procedere. Il linguaggio di comando, in particolare, ha impiegato quasi un anno per capirlo, ma può
essere implementato (dato cosa c'era all'inizio di quell'anno) in un giorno o due. Allo stesso modo,
inventare la struttura dei dati di Rasp ha ritardato la connessione dell'host e dei terminali di qualche
altro mese. Sam ha impiegato circa due anni per scrivere, anche se sono stati spesi solo circa quattro
mesi per lavorarci.

Parte del processo di progettazione è stata insolita: il sottoinsieme del protocollo che gestisce Rasp
è stato simulato, sottoposto a debug e verificato da un analizzatore di protocollo automatico, 21 ed è
stato privo di bug dall'inizio. Il resto del protocollo, che riguardava principalmente l'aggiornamento
dei menu, era sfortunatamente troppo ingombrante per tali analisi, ed è stato sottoposto a debug con
metodi più tradizionali, principalmente registrando in un file tutti i messaggi in entrata e in uscita
dall'host.
Riflessi

Sam è essenzialmente l'unico editor interattivo utilizzato dai circa sessanta membri del centro di
ricerca sulla scienza informatica in cui lavoro. Non si può dire lo stesso di jim; la mancanza di un
linguaggio di comando impediva ad alcune persone di adottarlo. L'unione di un'interfaccia utente
comoda come quella di jim con un linguaggio di comando potente quanto quello di ed †

è essenziale per il successo di Sam. Quando Sam è stato reso disponibile per la comunità jim, quasi
tutti sono passati ad esso entro due o tre giorni. Nei mesi che seguirono, anche le persone che non
avevano mai adottato jim iniziarono a usare sam in esclusiva.

Per essere onesti, Ed ha ancora un uso occasionale, ma di solito quando deve essere fatto qualcosa
di veloce e il sovraccarico di scaricare la parte terminale di Sam non vale la pena. Inoltre, come
editor di "linea", sam -d è un po 'strano; quando si utilizza un buon vecchio terminale ASCII, è
confortante avere un vero editor di riga. Ma è giusto dire che il linguaggio di comando di Sam ha
sostituito quello di Ed per la maggior parte del complicato editing che ha tenuto con noi gli editor di
riga (cioè gli editor basati sui comandi).

Il linguaggio di comando di Sam è ancora più elaborato di quello di ed, e la maggior parte dei
clienti Sam non si avvicina a usare tutte le sue capacità. Ha bisogno di essere così sofisticato? Penso
che la risposta sia sì, per due ragioni.

Primo, il modello per il linguaggio di comando di Sam è davvero relativamente semplice, e


certamente più semplice di quello di ed. Per esempio, c'è solo un tipo di loop testuale in sam - il
comando x - mentre ed ha tre (il comando g, la bandiera globale sulle sostituzioni e il loop implicito
su linee in sostituzioni su più righe). Inoltre, il comando sostitutivo di ed è necessario per apportare
modifiche all'interno delle righe, ma in sam il comando s è più di una comodità familiare che una
necessità; c e t possono fare tutto il lavoro.
In secondo luogo, data una comunità che si aspetta che un editore sia potente quanto Ed, è difficile
vedere come Sam potrebbe essere davvero molto più semplice e soddisfare comunque questa
aspettativa. Le persone vogliono fare "sostituti globali" e la maggior parte si accontenta di avere la
ricetta per questo e qualche altro cambiamento di fantasia. La sofisticazione del linguaggio dei
comandi è in realtà solo un rivestimento su un design che rende possibile la sostituzione globale in
un editor di schermate. Alcune persone vorranno sempre qualcosa di più, tuttavia, ed è gratificante
essere in grado di fornirlo. Il vero potere del linguaggio di comando di Sam deriva dalla
componibilità degli operatori, che è per sua natura ortogonale al modello sottostante. In altre parole,
sam non è di per sé complesso, ma rende possibili cose complesse. Se non vuoi fare nulla di
complesso, puoi ignorare del tutto la complessità e molte persone lo fanno.

A volte mi viene fatta la domanda opposta: perché non ho semplicemente creato un vero editor
programmabile, con macro e variabili e così via? Il motivo principale è una questione di gusti: mi
piace che l'editor sia lo stesso ogni volta che lo uso. C'è una ragione tecnica, tuttavia: la
programmabilità negli editor è in gran parte una soluzione alternativa all'interattività. Gli editor
programmabili sono usati per rendere le cose particolari, di solito a breve termine, facili da fare,
come fornire scorciatoie per azioni comuni. Se le cose sono generalmente facili da fare in primo
luogo, le abbreviazioni non sono così utili. Sam semplifica le comuni operazioni di modifica e le
soluzioni a problemi di modifica complessi sembrano commisurati ai problemi stessi. Inoltre, la
possibilità di modificare la finestra sam semplifica la ripetizione dei comandi: basta un clic del
mouse per eseguire nuovamente un comando.
Pro e contro

Sam ha molti altri buoni punti e la sua parte di problemi. Tra le cose buone c'è l'idea di espressioni
regolari strutturali, la cui utilità è stata appena esplorata. Erano arrivati in modo fortuito quando ho
tentato di distillare l'essenza del modo di fare di sostituzione globale di ed e ho riconosciuto che il
comando di looping in ed imply implicitamente una struttura (una serie di linee) sul file.

Un'altra delle buone cose di Sam è la sua capacità di annullare. Non avevo mai usato un editor con
un vero annullamento, ma non tornerei mai più indietro adesso. Annullare deve essere fatto bene,
ma se lo è, può essere invocato. Ad esempio, è sicuro sperimentare se non sei sicuro di come
scrivere un comando complesso, perché se commetti un errore, può essere risolto in modo semplice
e affidabile. Ho imparato due cose sull'annullamento dalla scrittura di sam: in primo luogo, è facile
da fornire se lo si progetta dall'inizio, e in secondo luogo, è necessario, soprattutto se il sistema ha
alcune proprietà sottili che potrebbero non essere familiari o soggette a errori per gli utenti.

La mancanza di limiti e dimensioni interne di Sam è una virtù. Poiché evita tutte le tabelle e le
strutture dati a dimensione fissa, sam è in grado di apportare modifiche globali ai file che alcuni dei
nostri altri strumenti non sono nemmeno in grado di leggere. Inoltre, il design mantiene le
prestazioni lineari quando eseguono tali operazioni, anche se devo ammettere che sam diventa lento
quando modifica un file enorme.

Ora, i problemi. Esternamente, il più ovvio è che è scarsamente integrato nel sistema di finestre
circostante. In base alla progettazione, l'interfaccia utente in sam sembra quasi identica a quella di
mux, ma un muro spesso separa il testo in sam dai programmi in esecuzione in mux. Ad esempio, il
"buffer snarf" in sam deve essere mantenuto separatamente da quello in mux. Questo è deplorevole,
ma probabilmente necessario data l'insolita configurazione del sistema, con un terminale
programmabile all'estremità di un collegamento RS-232.

Sam è affidabile; altrimenti, la gente non lo userebbe. Ma è stato scritto per così tanto tempo e ha
così tante nuove (per me) idee in esso, che mi piacerebbe vederlo fatto di nuovo per ripulire il
codice e rimuovere molti dei problemi persistenti nell'implementazione. La parte peggiore riguarda
l'interconnessione tra l'host e le parti del terminale, che potrebbero anche essere in grado di andare
in una riprogettazione per un sistema di finestre più convenzionale. Il programma deve essere diviso
in due per utilizzare il terminale in modo efficace, ma la larghezza di banda bassa della connessione
impone che la separazione si verifichi in una parte scomoda del progetto se le prestazioni devono
essere accettabili. Un semplice protocollo di chiamata di procedura remota guidato dall'host, che
emette solo comandi grafici, sarebbe facile da scrivere ma non avrebbe quasi la necessaria reattività.
D'altra parte, se il terminale avesse il controllo e richiedesse servizi di file molto più semplici
dall'host, le ricerche di espressioni regolari richiederebbero che il terminale legga l'intero file sul
suo collegamento RS-232, il che sarebbe irragionevolmente lento. È necessario un compromesso in
cui entrambe le parti possano prendere il controllo. In retrospettiva, il protocollo di comunicazione
avrebbe dovuto essere progettato e verificato formalmente, anche se non conosco nessuno
strumento in grado di relazionare adeguatamente il protocollo alla sua implementazione.

Non tutti gli utenti di Sam sono a loro agio con il suo linguaggio di comando, e pochi sono esperti.
Alcune persone (venerabili) usano una sorta di "sottoinsieme" del linguaggio di comando di Sam, e
si chiedono anche perché il linguaggio di comando di Sam non sia esattamente quello di Ed. (La
ragione, naturalmente, è che il modello di Sam per il testo non include le newline, che sono
fondamentali in ed. Rendere il testo un array di newline alla lingua di comando sarebbe troppo di
una rottura dal modello senza cuciture fornito dal mouse Alcuni editor, come vi, sono disposti a fare
questa pausa, però.) La difficoltà è che la sintassi di sam è così vicina a quella di Ed che le persone
credono che dovrebbe essere lo stesso. Ho pensato, con qualche giustificazione a posteriori, che
rendere Sam simile a ed avrebbe reso più facile imparare e accettare. Ma potrei aver oltrepassato e
aumentato troppo le aspettative degli utenti. È difficile decidere in che modo risolvere questo
problema.

Infine, c'è un compromesso in sam che è stato deciso dall'ambiente in cui viene eseguito: sam è un
editor multi-file, anche se in un sistema diverso potrebbero esserci più editor di file singoli. La
decisione è stata presa principalmente perché l'avvio di un nuovo programma in un Blit richiede
molto tempo. Se la scelta potesse essere fatta liberamente, tuttavia, sceglierei comunque
l'architettura multi-file, perché consente di gestire gruppi di file come un'unità; l'utilità dei comandi
multi-file è incontrovertibile. È delizioso avere a portata di mano l'origine di un intero programma.

Ringraziamenti

Tom Cargill ha suggerito l'idea alla base della struttura dei dati di Rasp. Norman Wilson e Ken
Thompson hanno influenzato il linguaggio di comando. Questo articolo è stato migliorato dai
commenti di Al Aho, Jon Bentley, Chris Fraser, Gerard Holzmann, Brian Kernighan, Ted Kowalski,
Doug McIlroy e Dennis Ritchie.

-----------------------------------------------------------------------------------------------------------
SSAM(1) SSAM(1)

NAME
ssam - stream interface to sam

SYNOPSIS
ssam [ -n ] [ -e script ] [ -f sfile ] [ file ... ]

DESCRIPTION
Ssam copies the named files (standard input default) to the
standard output, edited by a script of sam commands (q.v.).
When the script starts, the entire input is selected. The
-f option causes the script to be taken from file sfile. If
there is a -e option and no -f, the flag -e may be omitted.
The -n option suppresses the default output.

EXAMPLES
ssam -n ,10p file
Print first 10 lines of file.

ssam 'y/[a-zA-Z]+/ c/\n/' *.ms


Print one word per line.

ssam 's/\n\n+/\n/g'
Delete empty lines from standard input.

ssam 's/UNIX/& system/g'


Replace every instance of `UNIX' by `UNIX system'.

ssam 'y/[a-zA-Z]+/ c/\n/' | grep .


Count frequency of words read from standard input.

SOURCE
/bin/ssam

SEE ALSO
sed(1), sam(1), regexp(7)

Rob Pike, ``The text editor sam''.

BUGS
Ssam consumes all of standard input before running the
script.
-----------------------------------------------------------------------------------------------------------
Rc è un interprete di comandi per Plan 9 che fornisce strutture simili alla shell Bourne di UNIX, con
alcune piccole aggiunte e una sintassi meno idiosincratica. Questo documento utilizza numerosi
esempi per descrivere le funzionalità di rc e contrasta rc con la shell Bourne, un modello che molti
lettori conosceranno.

1. Introduzione

Rc è simile nello spirito ma diverso nei dettagli dalla shell Bourne di UNIX. Questo documento
descrive le principali caratteristiche di rc con molti piccoli esempi e alcuni più grandi. Presuppone
familiarità con la shell Bourne.
Per gli usi più semplici rc ha una sintassi familiare agli utenti di Bourne-shell. Tutti i seguenti
comportamenti si comportano come previsto:
date

cat /lib/news/build

who >user.names

who >>user.names
wc <file

echo [a-f]*.c

who | wc

who; date

vc *.c &

mk && v.out /*/bin/fb/*

rm -r junk || echo rm failed!

3. Citazione

Un argomento che contiene uno spazio o uno degli altri caratteri di sintassi di rc deve essere
racchiuso tra gli apostrofi ('):
rm ’odd file name’
Un apostrofo in un argomento quotato deve essere raddoppiato:
echo ’How’’s your father?’

4. Modelli

Un argomento non quotato che contiene uno dei caratteri *? [è un modello da abbinare ai nomi dei
file. Un carattere * corrisponde a qualsiasi sequenza di caratteri,? corrisponde a ogni singolo
carattere e [classe] corrisponde a qualsiasi carattere della classe, a meno che il primo carattere della
classe sia ~, nel qual caso la classe è completata. La classe può anche contenere coppie di caratteri
separati da -, in piedi per tutti i caratteri in modo lessicale tra i due. Il carattere / deve apparire
esplicitamente in un modello, come pure i componenti del nome del percorso. e ... Un modello è
sostituito da un elenco di argomenti, uno per ogni nome di percorso abbinato, tranne per il fatto che
un modello che corrisponde a nessun nome non viene sostituito dalla lista vuota; piuttosto sta per se
stesso.

5. Variabili

La shell Bourne di UNIX offre variabili con valori stringa. Rc fornisce variabili i cui valori sono
elenchi di argomenti, vale a dire array di stringhe. Questa è la principale differenza tra gli interpreti
di comando UNIX tradizionali e rc. Le variabili possono essere dati valori digitando, ad esempio:

path=(. /bin)

user=td

font=/lib/font/bit/pelm/ascii.9.font

Le parentesi indicano che il valore assegnato al percorso è un elenco di due stringhe. Le variabili
user e font sono assegnate a liste contenenti una singola stringa.
Il valore di una variabile può essere sostituito in un comando precedendo il suo nome con un $,
come questo:
echo $ path
Se il percorso fosse stato impostato come sopra, questo sarebbe equivalente a
echo . /bin
Le variabili possono essere pedonalizzate da numeri o liste di numeri, come questo:

echo $path(2)

echo $path(2 1 2)

Questi sono equivalenti a

echo /bin

echo /bin . /bin

Non può esserci spazio per separare il nome della variabile dalla parentesi sinistra; in caso
contrario, il pedice verrebbe considerato come un elenco separato tra parentesi.

Il numero di stringhe in una variabile può essere determinato dall'operatore $ #. Per esempio,
echo $ # percorso

stampa 2 per questo esempio.

I seguenti due compiti sono leggermente diversi:

empty=()

null=’’
I primi set sono vuoti in una lista che non contiene stringhe. Il secondo set è nullo in una lista
contenente una singola stringa, ma la stringa non contiene caratteri.

Sebbene possano sembrare più o meno la stessa cosa (nel guscio di Bourne, sono indistinguibili), si
comportano diversamente in quasi tutte le circostanze. Tra le altre cose
echo $#empty

prints 0, whereas

echo $#null

prints 1.

Tutte le variabili che non sono mai state impostate hanno il valore ().

Occasionalmente, è conveniente trattare il valore di una variabile come una singola stringa. Gli
elementi di una stringa sono concatenati in una singola stringa, con spazi tra gli elementi,
dall'operatore $. Quindi, se impostiamo
-----------------------------------------------------------------------------------------------------------
Un tutorial per il linguaggio di comando sam
Rob Pike
ASTRATTO
sam è un editor di testo interattivo con un linguaggio di comando che fa un uso pesante di
espressioni regolari. Sebbene la lingua sia sintatticamente simile a quella di ed (1), i dettagli
sono diversamente interessanti. Questo tutorial introduce il linguaggio di comando, ma non lo fa
discutere l'interfaccia dello schermo e del mouse. Con scuse a coloro che non hanno familiarità con
il
Ninth Edition Blit software, si presume che la somiglianza di sam a mux (9) a questo
livello rende facile imparare il linguaggio del mouse di sam.
La lingua del comando sam si applica identicamente a due ambienti: quando si esegue
sam su un normale terminale (tramite sam -d), e nella finestra di comando di un
sam caricato, cioè, uno che utilizza il display bitmap e il mouse.
introduzione
Questo tutorial descrive il linguaggio di comando di sam, un editor di testo interattivo che funziona
su Blits e
alcuni computer con display bitmap. Per la maggior parte delle attività di modifica, le funzioni di
modifica basate sul mouse sono sufficienti
cienti, e sono facili da usare e da imparare.
La lingua di comando è spesso utile, tuttavia, in particolare quando si apportano modifiche globali.
a differenza di
i comandi in ed, che sono necessari per apportare modifiche, i comandi sam tendono ad essere usati
solo per
compiti di modifica plicati o ripetitivi. È in questi usi più coinvolti che le differenze tra sam e
altri editor di testo sono più evidenti.
Il linguaggio di Sam rende facile fare alcune cose che altri editori, inclusi programmi come sed e
awk, non gestirlo con garbo, quindi questo tutorial serve in parte come una lezione sul modo di
manipolare di Sam
testo. Gli esempi qui sotto quindi si concentrano interamente sulla lingua, assumendo quella facilità
con il
l'uso del mouse in sam è nel peggiore dei casi facile da raccogliere. Infatti, sam può essere eseguito
senza il mouse (not
scaricato), specificando il flag -d, ed è questo dominio che occupa il tutorial; il comando lan-
la modalità in queste modalità è identica.
Una parola all'adepto di Unix: sebbene sam sia sintatticamente molto simile a ed, è
fondamentalmente e
deliberatamente diverso nel design e nella semantica dettagliata. Potresti usare la conoscenza di Ed
per prevedere come
il comando sostitutivo funziona, ma avresti ragione solo se avessi usato una certa comprensione del
funzionamento di Sam
influenzare la tua previsione. Sii particolarmente attento agli idiomi. Gli idiomi si formano in angoli
curiosi di lan-
guages e dipendono da peculiarità inderogabili. gli idiomi di ED semplicemente non funzionano in
sam: 1, $ s / a / b / rende
una sostituzione nell'intero file, non uno per riga. sam ha i suoi idiomi. Gran parte dello scopo di
questo
il tutorial è quello di pubblicarli e fare fluidità in sam una questione di apprendimento, non di
astuzia.
Il tutorial dipende dalla familiarità con le espressioni regolari, sebbene qualche esperienza con un
altro
può essere utile un editor Unix tradizionale. Per aiutare i lettori che hanno familiarità con Ed, ho
sottolineato in parentesi quadra-
ets [] alcune delle differenze rilevanti tra ed e sam. Leggi questi commenti solo se lo desideri
capire le differenze; la lezione riguarda sam, non sam vs. ed. Un'altra convenzione tipografica è
quell'output appare in questo font, mentre l'input digitato appare come testo slanty.
Nomenclatura: sam conserva una copia del testo che sta modificando. Questa copia è chiamata un
file. Per evitare confusioni
sione, ho chiamato la memoria permanente su disco un file Unix.

Per iniziare, abbiamo bisogno di un testo con cui giocare.


Qualsiasi testo farà; prova qualcosa da James Gosling
Manuale di Emacs:
Il comando a aggiunge testo fino a una linea che contiene solo un punto,
e imposta il testo corrente (chiamato anche punto) su ciò che è stato digitato-- tutto tra l'a e il
periodo.
[ed lascerebbe il punto impostato solo sull'ultima riga.] Il comando p stampa il testo corrente:
"This manual is organized in a rather haphazard manner. The first
several sections were written hastily in an attempt to provide a
general introduction to the commands in Emacs and to try to show
the method in the madness that is the Emacs command structure"
[Di nuovo, ed stamperebbe solo l'ultima riga.] Il comando aggiunge il suo testo dopo il punto; il
comando i è come un
ma aggiunge il testo prima del punto.

i
Introduction
.
p
Introduction

C'è anche un comando c che cambia (sostituisce) il testo corrente, e che lo cancella; questi sono
illustrati
sotto.
Per vedere tutto il testo, possiamo specificare quale testo stampare; per il momento, basti dire che 0,
$ specifiche
riproduce l'intero file. [gli utenti di ED probabilmente scriverebbero 1, $, che in pratica è la stessa
cosa, ma vedi sotto.]

0,$p
Introduction
This manual is organized in a rather haphazard manner. The first
several sections were written hastily in an attempt to provide a
general introduction to the commands in Emacs and to try to show
the method in the madness that is the Emacs command structure.

Ad eccezione del comando w descritto di seguito, tutti i comandi, incluso p, impostano il punto sul
testo che toccano.
Quindi, a e io fissiamo il punto al nuovo testo, p al testo stampato e così via. Allo stesso modo, tutti
i comandi (tranne w) di
il default opera sul testo corrente [a differenza di ed, per il quale alcuni comandi (come g) sono
predefiniti sull'intero
file].
Le cose non diventeranno molto interessanti fino a quando non possiamo impostare il punto
arbitrariamente. Questo è fatto da
indirizzi, che specificano un pezzo del file. L'indirizzo 1, ad esempio, imposta il punto sulla prima
riga del file.

1p
Introduction
c
Preamble
.

Il comando c non ha bisogno di specificare il punto; il p lo ha lasciato sulla linea uno. È quindi
facile eliminare la prima riga
del tutto; l'ultimo punto a sinistra del comando impostato sulla prima riga:

d
1p
This manual is organized in a rather haphazard manner.
The first

(I numeri delle linee cambiano per riflettere le modifiche al file).


L'indirizzo / testo / serie puntano alla prima comparsa del testo, dopo il punto. [ed corrisponde alla
prima riga
taining text.] Se il testo non viene trovato, la ricerca viene riavviata all'inizio del file e continua fino
al punto.

/Emacs/p
Emacs

È difficile da indicare tipograficamente, ma in questo esempio non appare nessuna nuova riga dopo
Emacs: il testo deve essere
stampato è la stringa "Emacs", esattamente. (Il p finale può essere lasciato fuori - è il comando
predefinito
scaricato, tuttavia, l'impostazione predefinita è selezionare il testo, evidenziarlo e renderlo visibile a
inserire la finestra sul file, se necessario. Quindi, / Emacs / indica sul display la prossima
occorrenza del
testo.)
Immagina di voler cambiare la parola a casaccio in spensierato. Ovviamente, ciò che è necessario è
un altro comando c, ma il metodo utilizzato finora per inserire testo include una nuova riga. La
sintassi per includere
il testo senza nuova riga consiste nel circondare il testo con barre (che è la stessa della sintassi per le
ricerche di testo,
ma quello che sta succedendo dovrebbe essere chiaro dal contesto). Il testo deve apparire
immediatamente dopo c (o a o i).
Detto questo, è facile apportare la modifica richiesta:

/haphazard/c/thoughtless/
1p
This manual is organized in a rather thoughtless manner.

[Le modifiche possono sempre essere eseguite con un comando c, anche se il testo è più piccolo di
una linea]. Lo troverai
il modo di fornire il testo ai comandi è molto più comune rispetto alla sintassi a più righe. Se lo
desidera
includere una barra / nel testo, precederla con una barra rovesciata \ e utilizzare una barra rovesciata
per proteggere una barra rovesciata
si.

/Emacs/c/Emacs\\360/
4p
general introduction to the commands in Emacs\360 and to try to show

Potremmo anche fare questo particolare cambiamento con:

/Emacs/a/\\360/

Questo è il posto migliore per introdurre il comando u, che annulla l'ultimo comando. Un secondo-
ond tu annullerà il penultimo comando, e così via.

u
4p
general introduction to the commands in Emacs and to try to show
u
3p

This manual is organized in a rather haphazard manner. The first


Annullare può solo eseguire il backup; non c'è modo di annullare una precedente.

indirizzi:

Abbiamo visto le forme più semplici di indirizzi, ma c'è ancora molto da imparare prima che
possiamo ottenere troppo
ulteriore. Un indirizzo seleziona una regione nel file - una sottostringa - e quindi deve definire
l'inizio
e la fine di una regione. Pertanto, l'indirizzo 13 seleziona dall'inizio della riga tredici fino alla fine
della riga
tredici e / Emacs / seleziona dall'inizio della parola "Emacs" fino alla fine.
Gli indirizzi possono essere combinati con una virgola:

13,15

seleziona le linee da tredici a quindici. La definizione dell'operatore virgola è selezionare dall'inizio


dell'indirizzo della mano sinistra (l'inizio della riga 13) alla fine dell'indirizzo della mano destra (la
fine della riga 15).
Alcuni semplici indirizzi speciali sono utili:. (un punto) rappresenta il punto, il testo corrente, 0
(linea
zero) seleziona la stringa nulla all'inizio del file e $ seleziona la stringa nulla alla fine del file [not
l'ultima riga del file]. Perciò,

0,13

seleziona dall'inizio del file fino alla fine della riga tredici,

.,$

seleziona dall'inizio del testo corrente fino alla fine del file, e

0,$
seleziona l'intero file [cioè una singola stringa contenente l'intero file, non un elenco di tutte le righe
nel file].
Questi sono tutti indirizzi assoluti: si riferiscono a luoghi specifici nel file. Sam ha anche un parente
indirizzi, che dipendono dal valore del punto, e infatti abbiamo già visto un modulo: / Emacs / trova
il
prima occorrenza di Emacs che cerca in avanti dal punto. Da quale situazione Emacs trova dipende
il valore del punto. E se volessi la prima occorrenza prima del punto? Basta precedere il modello
con un segno meno
segno, che inverte la direzione della ricerca:

-/Emacs/

In effetti, la sintassi completa per la ricerca in avanti è

+/Emacs/

ma il segno più è il default, e in pratica è usato raramente. Ecco un esempio che lo include per
chiarezza:

0+/Emacs/

selects the first occurrence of Emacs in the file; read it as ‘‘go to line 0, then search forwards for
Emacs.’’
Since the + is optional, this can be written 0/Emacs/. ugualmente,

$-/Emacs/

trova l'ultima occorrenza nel file, quindi

0/Emacs/,$-/Emacs/

seleziona il testo dal primo all'ultimo Emacs, incluso. Leggermente più interessante:

/Emacs/+/Emacs/

(c'è un implicito. + all'inizio) seleziona il secondo punto seguente Emacs.


I numeri di riga possono anche essere relativi.

-2

seleziona la seconda riga precedente e

+5

seleziona la quinta riga successiva (qui il segno più è obbligatorio).


Poiché gli indirizzi possono selezionare (e il punto può essere) più di una riga, abbiamo bisogno di
una definizione di "precedente"
e 'following:' 'previous' significa prima dell'inizio del punto, e 'following' significa dopo la fine del
punto.
Ad esempio, se il file contiene AAAA, con punto impostato al centro due A (caratteri inclinati), - / A
/
imposta punto sul primo A, e + / A / imposta il punto sull'ultimo A. Tranne che in circostanze strane
(come quando il simbolo
solo l'occorrenza del testo nel file è già il testo corrente), il testo selezionato da una ricerca verrà
congiunto dal punto.

Per selezionare il paragrafo troff -ms contenente il punto, per quanto sia lungo, utilizzare

-/.PP/,/.PP/-1

which will include the .PP that begins the paragraph, and exclude the one that ends it.
When typing relative line number addresses, the default number is 1, so the above could be written
slightly more simply:

-/.PP/,/.PP/-

What does the address +1-1 or the equivalent +- mean? It looks like it does nothing, but recall that
dot need not be a complete line of text. +1 selects the line after the end of the current text, and -1
selects
the line before the beginning. Therefore +1-1 selects the line before the line after the end of dot, that
is, the
complete line containing the end of dot. We can use this construction to expand a selection to
include a
complete line, say the first line in the file containing Emacs:

0/Emacs/+-p
general introduction to the commands in Emacs and to try to show

L'indirizzo + - è un idioma.

Loops
Sopra, abbiamo modificato un'occorrenza di Emacs in Emacs \ 360, ma se il nome dell'editor è
veramente
cambiando, sarebbe utile cambiare tutte le istanze del nome in un singolo comando. sam fornisce a
comando, x (estrai), proprio per quel lavoro. La sintassi è x / pattern / comando. Per ogni
occorrenza del
modello nel testo selezionato, x imposta punto sul comando occorrenza e avvia. Ad esempio, per
cambiare Emacs
a vi,

0,$x/Emacs/c/vi/
0,$p
This manual is organized in a rather haphazard manner. The first
several sections were written hastily in an attempt to provide a
general introduction to the commands in vi and to try to show
the method in the madness that is the vi command structure.

Funziona suddividendo il testo corrente (0, $ - l'intero file) in aspetti del suo argomento testuale
(Emacs), quindi eseguendo il comando che segue (c / vi /) con il punto impostato sul testo.
Possiamo leggere questo
esempio come, '' trova tutte le occorrenze di Emacs nel file, e per ognuna, imposta il testo corrente
al verificarsi -
rence ed esegui il comando c / vi /, che sostituirà il testo corrente con vi. '' [Questo comando è un po
'
simile al comando g di ed. Le differenze si svilupperanno di seguito, ma si noti che l'indirizzo
predefinito, come
sempre, è il punto anziché l'intero file.]
Un singolo comando u è sufficiente per annullare un comando x, indipendentemente dal numero di
singole modifiche
la x fa.

u
0,$p
This manual is organized in a rather haphazard manner. The first
several sections were written hastily in an attempt to provide a
general introduction to the commands in Emacs and to try to show
the method in the madness that is the Emacs command structure.

Certamente, c non è l'unico comando che può essere eseguito da x. Un comando può essere usato
per mettere il marchio proprietario
su Emacs:

0,$x/Emacs/a/{TM}/
/Emacs/+-p
general introduction to the commands in Emacs{TM} and to try to show

[Non c'è modo di vedere i cambiamenti mentre accadono, come in ed's g / Emacs / s // & {TM} / p;
guarda la sezione su Più modifiche, di seguito.]
Il comando p è anche utile quando guidato da una x, ma fai attenzione a dire cosa intendi;

0,$x/Emacs/p
EmacsEmacs

dato che x imposta il puntino al testo nelle barre, stampare solo quel testo non sarà molto
informativo. Ma il
comando che xesegua può contenere indirizzi. Ad esempio, se vogliamo stampare tutte le righe
contenenti Emacs,
usa solo + -:

,x/Emacs/ /{TM}/d
,p
This manual is organized in a rather haphazard manner. The first
several sections were written hastily in an attempt to provide a
general introduction to the commands in Emacs and to try to show
the method in the madness that is the Emacs command structure.

Notate cosa fa questa x: per ogni occorrenza di Emacs, trovate la {TM} che segue e cancellatela.
Il sam 'testo' accetta per le ricerche negli indirizzi e nei comandi x non è un testo semplice, ma
piuttosto
espressioni regolari. Unix ha diverse interpretazioni distinte delle espressioni regolari. Il modulo
usato da
sam è quello di regexp (6), comprese le parentesi () per il raggruppamento e un operatore 'or' | per
trovare le stringhe in
parallelo. sam corrisponde anche alla sequenza di caratteri \ n con un carattere di nuova riga. Testo
sostitutivo, come
usato nei comandi a e c, è ancora testo normale, ma la sequenza \ n rappresenta anche una nuova
riga in quel contesto.
Ecco un esempio. Diciamo che volevamo raddoppiare lo spazio del documento, cioè trasformare
ogni newline in
due newlines. Quanto segue fa il lavoro:

,x/\n/ a/\n/
,x/\n/ c/\n\n/
,x/$/ a/\n/
,x/ˆ/ i/\n/

L'ultimo esempio è leggermente diverso, perché mette una newline prima di ogni riga; gli altri
esempi lo collocano
dopo. I primi due esempi manipolano direttamente i newline [qualcosa al di fuori del ken di ed]; gli
ultimi due usi
espressioni regolari: $ è la stringa vuota alla fine di una riga, mentre è la stringa vuota all'inizio.
Queste soluzioni hanno tutte un possibile svantaggio: se c'è già una riga vuota (cioè due consecutive
newlines), lo rendono molto più grande (quattro newline consecutivi). Un metodo migliore è
estendere ogni gruppo
di newlines di uno:
,x/\n+/a/\n/
L'operatore di espressione regolare + significa "uno o più"; \ n + è identico a \ n \ n *. Quindi,
questo esempio richiede
evita la sequenza di newline e ne aggiunge un'altra alla fine.
Un esempio più comune è il rientro di un blocco di testo da un punto di tabulazione. Il seguente
tutto funziona, anche se
il primo è probabilmente il più pulito (il testo vuoto nelle barre è una scheda):
,x//a//
,x//c//
, X /.* \ n / i /
/
L'ultimo esempio usa il pattern (idioma, in realtà). * \ N per abbinare le linee:. * Corrisponde alla
stringa più lunga possibile
di caratteri non di nuova riga. Prendere le schede iniziali è altrettanto semplice:

,X
/d
In questi esempi ho specificato un indirizzo (l'intero file), ma in pratica comandi come questi sono
di più
può essere eseguito senza un indirizzo, utilizzando il valore del punto impostato selezionando il
testo con il mouse.

Condizionali

Il comando x è un costrutto di loop: per ogni corrispondenza di un'espressione regolare, estrae


(imposta punto su)
la partita e esegue un comando. sam ha anche un comando condizionale, g: g / pattern / esegue il
comando if
il punto contiene una corrispondenza del modello senza modificare il valore del punto. L'inverso, v,
esegue il comando if
il punto non contiene una corrispondenza del modello. (Le lettere g e v sono storiche e non hanno
signi-
nificance. Potresti pensare a g come "guardia".) [Gli utenti dovrebbero leggere attentamente le
definizioni di cui sopra; il
il comando g in sam è fondamentalmente diverso da quello in ed.] Ecco un esempio della differenza
tra x e g:
, X / Emacs / c / vi /
cambia ogni occorrenza della parola Emacs nel file alla parola vi, ma
, G / Emacs / c / vi /
cambia l'intero file in vi se c'è la parola Emacs ovunque nel file.
Nessuno di questi comandi è particolarmente interessante isolatamente, ma è utile quando
abbinato con xe con se stessi.

Composizione

Un modo per pensare al comando x è che, data una selezione (un valore di punto), itera attraverso
sottosezioni interessanti (valori del punto all'interno). In altre parole, ci vuole un pezzo di testo e lo
taglia
pezzi più piccoli. Ma il testo che ritaglia potrebbe già essere un pezzo tagliato da un precedente
comando x o selezionato
da un g. La proprietà più interessante di Sam è la capacità di definire una sequenza di comandi per
eseguire una
olare compito. † Un semplice esempio è quello di cambiare tutte le occorrenze di Emacs in emacs;
sicuramente il comando
, x / Emacs / c / emacs /
funzionerà, ma possiamo usare un comando x per salvare la ridigitazione della maggior parte della
parola Emacs:
, x / Emacs / x / E / c / e /
(Gli spazi possono essere usati per separare i comandi su una riga per renderli più facili da leggere.)
Che cosa questo comando
fa è trovare tutte le occorrenze di Emacs (, x / Emacs /), e quindi con il punto impostato su quel
testo, trova tutte le occorrenze di
la lettera E (x / E /), e quindi con il punto impostato su quel testo, eseguire il comando c / e / per
cambiare il carattere in basso
Astuccio. Si noti che l'indirizzo per il comando - l'intero file, specificato da una virgola - viene dato
solo a
il pezzo più a sinistra del comando; il resto dei pezzi ha un punto fissato per loro dall'esecuzione del
pezzi alla loro sinistra.
Come altro semplice esempio, considera un problema risolto sopra: stampa tutte le righe nel file che
contiene
la parola Emacs:
, x /.* \ n / g / Emacs / p
introduzione generale ai comandi in Emacs e provare a mostrare
il metodo nella follia che è la struttura di comando di Emacs.
Questo comando dice di dividere il file in linee (, x /.* \ n /) e per ogni riga che contiene la stringa
Emacs
(g / Emacs /), esegui il comando p con il punto impostato sulla linea (non la corrispondenza di
Emacs), che stampa la linea. A
salva digitando, perché. * \ n è uno schema comune nei comandi x, se la x è seguita
immediatamente da uno spazio,
il modello. * \ n è assunto. Pertanto, quanto sopra potrebbe essere scritto in modo più sintetico:
, x g / Emacs / p
La soluzione che abbiamo usato prima era
, X / Emacs / + - p
che esegue il comando + -p con il punto impostato su ogni corrispondenza di Emacs nel file (ricorda
che l'idioma + -p
stampa la riga contenente la fine del punto).
I due comandi di solito producono lo stesso risultato (il modulo + -p stamperà una riga due volte se
contiene
Emacs due volte). Che è migliore? , x / Emacs / + - p è più facile da scrivere e sarà molto più veloce
se il file è grande
e ci sono poche occorrenze della stringa, ma è davvero un caso speciale strano. , x /.* \ n / g / Emacs
/ p è più lento
- rompe ogni linea separatamente, quindi la esamina per una corrispondenza, ma è concettualmente
più pulita e generica
si allea più facilmente. Ad esempio, si consideri il seguente pezzo del manuale di Emacs:

command name="append-to-file", key="[unbound]"


Takes the contents of the current buffer and appends it to the
named file. If the file doesn’t exist, it will be created.
command name="apropos", key="ESC-?"
Prompts for a keyword and then prints a list of those commands
whose short description contains that keyword. For example,
if you forget which commands deal with windows, just type
"@b[ESC-?]@t[window]@b[ESC]".

and so on

Questo testo consiste in gruppi di righe non vuote, con un formato semplice per il testo all'interno di
ciascun gruppo. Imag-
ine che volevamo trovare la descrizione del comando 'apropos'. Il problema è rompere il file in
descrizioni individuali, quindi trovare la descrizione di "apropos" e stamparla. La soluzione è
lineare
inoltrare:

,x/(.+\n)+/ g/command name="apropos"/p


command name="apropos", key="ESC-?"
Prompts for a keyword and then prints a list of those commands
whose short description contains that keyword. For example,
if you forget which commands deal with windows, just type
"@b[ESC-?]@t[window]@b[ESC]".

L'espressione regolare (. + \ N) + corrisponde a una o più righe con uno o più caratteri ciascuno,
cioè il testo
tra le righe vuote, quindi, x / (. + \ n) + / estrae ciascuna descrizione; quindi g / command name =
"apropos" / seleziona
la descrizione per 'apropos' e p la stampa.
Immagina di avere un programma C contenente la variabile n, ma volevamo cambiarlo in num.
Questo
il comando è un primo taglio:

,x/n/ c/num/
but is obviously flawed: it will change all n’s in the file, not just the identifier n. A better solution is
to use
an x command to extract the identifiers, and then use g to find the n’s:

,x/[a-zA-Z_][a-zA-Z_0-9]*/ g/n/ v/../ c/num/

Sembra orribile, ma è abbastanza facile da capire quando viene letto da sinistra a destra. Un
identificativo C è alfabetico o
sottolineatura seguita da zero o più caratteri alfanumerici o caratteri di sottolineatura, cioè
corrispondenze dell'espressione regolare
sione [a-zA-Z _] [a-zA-Z_0-9] *. Il comando g seleziona quegli identificatori contenenti n, e la v è
un trucco: esso
rifiuta quegli identificatori contenenti più di un carattere. Quindi il c / num / si applica solo al free-
standing
n di.
C'è ancora un problema qui: non vogliamo cambiare n che fanno parte della costante carattere \ n.
C'è un comando y, complementare a x, che è proprio quello di cui abbiamo bisogno: y / pattern /
comando esegue il comando
mand sui pezzi di testo tra le partite del modello; se x seleziona, y rifiuta. Ecco la finale command:

,y/\\n/ x/[a-zA-Z_][a-zA-Z_0-9]*/ g/n/ v/../ c/num/

Il y / \\ n / (con il backslash raddoppiato per renderlo un carattere letterale) rimuove la sequenza di


due caratteri \ n
dalla considerazione, quindi il resto del comando non lo toccherà. C'è più che potremmo fare qui;
per
esempio, un altro y potrebbe essere prefisso per proteggere i commenti nel codice. Non elaborerò
alcun esempio
inoltre, ma dovresti avere un'idea del modo in cui il ciclo e i comandi condizionali in sam
può essere composto per fare cose interessanti.

Raggruppamento
C'è un altro modo per organizzare i comandi. Includendoli in parentesi graffe {}, i comandi possono
essere applicato in parallelo. Questo esempio usa il comando =, che riporta i numeri di riga e di
carattere di
punto, insieme a p, per segnalare le apparenze di Emacs nel nostro file originale:

,p
This manual is organized in a rather haphazard manner. The first
several sections were written hastily in an attempt to provide a
general introduction to the commands in Emacs and to try to show
the method in the madness that is the Emacs command structure.
,x/Emacs/{
=
+-p
}
3; #171,#176
general introduction to the commands in Emacs and to try to show
4; #234,#239
the method in the madness that is the Emacs command structure.
(Il numero prima del punto e virgola è il numero della riga, i numeri che iniziano con # sono numeri
di caratteri
bers.) Come esempio più interessante, considera di cambiare tutte le occorrenze di Emacs in vi e
viceversa.
Possiamo digitare

x/Emacs|vi/{
g/Emacs/ c/vi/
g/vi/ c/Emacs/
}
or even
,x/[a-zA-Z]+/{
g/Emacs/ v/....../ c/vi/
g/vi/ v/.../ c/Emacs/
}

per assicurarci di non modificare le stringhe incorporate nelle parole.

Più modifiche:

Ci si potrebbe chiedere perché, una volta che Emacs è stato modificato in vi nell'esempio
precedente, il secondo
il mand nelle bretelle non lo rimette più. Il motivo è che i comandi vengono eseguiti in parallelo:
all'interno
qualsiasi comando sam di primo livello, tutte le modifiche al file si riferiscono allo stato del file
prima di qualsiasi modifica in
questo comando è fatto. Dopo che tutte le modifiche sono state determinate, vengono applicate
simultaneamente.
Ciò significa, come detto, che i comandi all'interno di un comando composto vedono lo stato del
file
prima che si applichi una delle modifiche. Questo metodo di valutazione rende alcune cose più
facili (come il
scambio di Emacs e vi), e alcune cose più difficili. Ad esempio, è impossibile usare un comando p
per
stampare le modifiche nel momento in cui si verificano, perché non sono accadute quando si esegue
p. Un ramo indiretto
la finzione è che i cambiamenti devono avvenire nell'ordine inoltrato attraverso il file e non devono
sovrapporsi.

Unix
sam ha alcuni comandi per connettersi ai processi Unix. Il più semplice è !, che esegue il comando
con ingresso e uscita collegati al terminale.

!date
Wed May 28 23:25:21 EDT 1986
!

(Quando scaricato, l'input è collegato a / dev / null e vengono stampate solo le prime righe di
output;
qualsiasi overflow è memorizzato in $ HOME / sam.err.) La finale! è un prompt per indicare
quando il comando
Pletes.
Leggermente più interessante è>, che fornisce il testo corrente come input standard per il com Unix
Mand:

1,2 >wc

2 22 131
!

Il complemento di> è, naturalmente, <: sostituisce il testo corrente con l'output standard del
comando Unix
Mand:

1 <date
!
1p
Wed May 28 23:26:44 EDT 1986

L'ultimo comando è |, che è una combinazione di <e>: il testo corrente è fornito come input
standard a
il comando Unix e l'output standard del comando Unix viene raccolto e utilizzato per sostituire
l'originale
testo. Per esempio,

,| sort

esegue sort (1) sul file, ordinando le linee del testo lessicograficamente. Si noti che <,> e | sono sam
mandi, non operatori di shell Unix.
Il prossimo esempio converte tutte le apparenze di Emacs in maiuscolo usando tr (1):
, x / Emacs / | tr a-z A-Z
tr viene eseguito una volta per ogni occorrenza di Emacs. Naturalmente, potresti fare questo
esempio in modo più efficiente con a
semplice comando c, ma eccone uno più complicato: data una casella di posta Unix come input,
converti tutto il capo Oggetto-
alle sorti distinte:

,x/ˆSubject:.*\n/ x/[ˆ:]*\n/ < /usr/games/fortune

(L'espressione regolare [:] si riferisce a qualsiasi carattere tranne: e newline, l'operatore di


negazione esclude
newline dalla lista di caratteri.) Ancora, / usr / games / fortune viene eseguito una volta per ogni riga
dell'oggetto, quindi ognuno
La riga dell'oggetto è cambiata in una diversa fortuna.
Pochi altri comandi di testo
Per completezza, dovrei menzionare altri tre comandi che manipolano il testo. Il comando m
sposta il testo corrente dopo il testo specificato dall'indirizzo (obbligatorio) dopo il comando. così
/ Emacs / + - m 0
sposta la riga successiva contenente Emacs all'inizio del file. Allo stesso modo, t (un altro
personaggio storico)
copia il testo:
/ Emacs / + - t 0
farebbe, all'inizio del file, una copia della riga successiva contenente Emacs.
Il terzo comando è più interessante: fa delle sostituzioni. La sua sintassi è s / pattern / replace-
ment /. All'interno del testo corrente, trova la prima occorrenza del modello e la sostituisce con la
sostituzione
testo, lasciando il punto impostato sull'intero indirizzo della sostituzione.

1p
This manual is organized in a rather haphazard manner. The first
s/haphazard/thoughtless/
p
This manual is organized in a rather thoughtless manner. The first

Le occorrenze del personaggio e nel testo sostitutivo rappresentano il testo corrispondente al


modello.

s/T/"&&&&"/
p
"TTTT"his manual is organized in a rather thoughtless manner.
The first

Ci sono due varianti. Il primo è che un numero può essere specificato dopo la s, per indicare quale
occorrenza
del modello da sostituire; il valore predefinito è il primo.

s2/is/was/
p
"TTTT"his manual was organized in a rather thoughtless manner.
The first

The second is that suffixing a g (global) causes replacement of all occurrences, not just the first.

s / [a-zA-Z] / x / g
p
"xxxx" xxx xxxxxx xxx xxxxxxxxx xx x xxxxxx xxxxxxxxxxx xxxxxxx
xxx xxxxx
Si noti che in tutti questi esempi il punto è lasciato impostato sull'intera linea.
[Il comando sostitutivo è vitale per ed, perché è l'unico modo per apportare modifiche all'interno di
una linea. È
meno prezioso in sam, in cui il concetto di linea è molto meno importante. Ad esempio, molti
editori
gli idiomi di gestione sono gestiti bene dai comandi di base di sam. Considera i comandi

s/good/bad/
s/good//
s/good/& bye/

che sono equivalenti in sam a

/good/c/bad/
/good/d
/good/a/ bye/
e per il quale la ricerca del contesto è probabilmente superflua perché il testo desiderato è già punto.
Inoltre, attenzione
questo idioma:

1,$s/good/bad/

che cambia il primo bene su ciascuna linea; lo stesso comando in sam cambierà solo il primo nel
file
intero file. La versione sam corretta è

,x s/good/bad/

ma ciò che è più probabile significa è

,x/good/ c/bad/

sam opera secondo regole diverse.]

File
Finora, abbiamo solo lavorato con un singolo file, ma sam è un editor multi-file. Solo un file può
essere modificato alla volta, ma è facile cambiare quale file è il file 'corrente' per la modifica. Per
vedere come farlo,
abbiamo bisogno di un sam con pochi file; il modo più semplice per farlo è avviarlo con un elenco
di nomi di file Unix da modificare.

$ echo *.ms
conquest.ms death.ms emacs.ms famine.ms slaughter.ms
$ sam -d *.ms
-. conquest.ms

(Mi dispiace che gli Horsemen non compaiano in ordine liturgico.) La linea stampata da sam è
un'indicazione che il
Il file Unix conquest.ms è stato letto ed è ora il file corrente. sam non legge il file Unix fino al
il file sam associato diventa aggiornato.
Il comando n stampa i nomi di tutti i file:

n
conquest.ms
death.ms
emacs.ms
famine.ms
slaughter.ms

Questo elenco è disponibile anche nel menu sul pulsante del mouse 3. Il comando f dice il nome
della sola corrente
file:

f
-. conquest.ms

I caratteri a sinistra del nome del file codificano informazioni utili sul file. Il segno meno
diventa un segno più se il file ha una finestra aperta e un asterisco se ne sono aperti più di uno. Il
periodo
(un altro significato di punto) identifica il file corrente. Il blank principale cambia ad un apostrofo
se il file lo è
diverso dal contenuto del file Unix associato, per quanto ne sa. Questo diventa evidente se noi
fare un cambiamento.

1d
f
’-. conquest.ms

Se il file viene ripristinato da un comando di annullamento, l'apostrofo scompare.

u
f
-. conquest.ms

The file name may be changed by providing a new name with the f command

f pestilence.ms
’-. pestilence.ms

f stampa il nuovo stato del file, cioè cambia il nome se ne viene fornito uno e stampa il nome
riguardo-
Di meno. Potrebbe anche essere annullata la modifica del nome del file.

u
f
-. conquest.ms

Quando si scarica sam, il file corrente può essere modificato semplicemente selezionando il file
desiderato da
il menu (selezionando lo stesso file successivamente si passa attraverso le finestre aperte sul file).
Altrimenti,
il comando b può essere usato per scegliere il file desiderato: †

b emacs.ms
-. emacs.ms

Di nuovo, sam stampa il nome (in realtà esegue un comando f implicito) perché il file Unix
emacs.ms lo è
essere letto per la prima volta È un errore chiedere un file di cui sam non sia a conoscenza, ma lo
farà il comando B.
prepara il menu di sam con un nuovo file e rendilo attuale.

b flood.pic
?no such file ‘flood.pic’
B flood.pic
-. flood.pic
n
- conquest.ms
- death.ms
- emacs.ms
- famine.ms
-. flood.pic
- slaughter.ms

Sia B che B accetteranno un elenco di nomi di file. b prende semplicemente il primo file nell'elenco,
ma B carica tutti.
L'elenco può essere digitato su una riga -

B devil.tex satan.tex 666.tex emacs.tex

- o generato da un comando Unix -

B <echo * .tex

Quest'ultima forma richiede un comando Unix; sam non capisce i metacaratteri del nome del file di
shell, quindi
B * .tex tenta di caricare un singolo file denominato * .tex. (La <forma è ovviamente derivata da
Sam's <com
mand.) echo non è l'unico comando utile per eseguire il sottomesso a B; per esempio,

B <grep -l Emacs *

caricherà solo quei file contenenti la stringa Emacs. Infine, un caso speciale: una B senza argomenti
un file vuoto e senza nome all'interno di sam.
Il complemento di B è D:

D devil.tex satan.tex 666.tex emacs.tex

sradica i file dalla memoria di Sam (non dal disco della macchina Unix). D senza nomi di file
rimuove il file corrente da sam.
Ci sono altri tre comandi che mettono in relazione il file corrente con i file Unix. Il comando w
scrive il
file su disco; senza argomenti, scrive l'intero file nel file Unix associato al file corrente in sam
(è l'unico comando il cui indirizzo predefinito non è punto). Naturalmente, puoi specificare un
indirizzo da scrivere
dieci e un nome di file diverso, con la sintassi evidente:

1,2w /tmp/revelations
/tmp/revelations: #44

sam risponde con il nome del file e il numero di caratteri scritti nel file. Il comando di scrittura su
il menu del pulsante 3 è identico in funzione a un comando w disadorno.
Gli altri due comandi, e ed r, leggono i dati dai file Unix. Il comando e cancella la corrente
file, legge i dati dal file indicato (o usa il vecchio nome del file corrente se nessuno è esplicitamente
fornito),
e imposta il nome del file. È molto simile a un comando B, ma inserisce le informazioni nel file
corrente anziché a
nuovo. e senza alcun nome di file è quindi un modo semplice per aggiornare la copia di sam di un
file Unix. [A differenza di in
ed, e non si lamenta se il file viene modificato. Il principio non è quello di proteggere contro le cose
che possono essere
annullato se sbagliato.] Poiché il suo compito è quello di sostituire l'intero testo, non prende mai un
indirizzo.
Il comando r è come e, ma non cancella il file: il testo nel file Unix sostituisce il punto o il
testo specificato se viene fornito un indirizzo.

ha essenzialmente l'effetto di

<cat emacs.ms

I comandi r e w imposteranno il nome del file se il file corrente non ha un nome già definito; e set
il nome anche se il file ne ha già uno.
Esiste un comando, analogo a x, che scorre su file invece di parti di testo: X (maiuscole x).
La sintassi è facile; è proprio come quello di x - X / pattern / command. (Il comando complementare
è Y,
analogo a y). L'effetto è di eseguire il comando in ogni file la cui voce di menu (cioè, la cui riga
stampato da un comando f) corrisponde al modello. Ad esempio, poiché un apostrofo identifica i
file modificati,

X / '/ w

scrive i file modificati sul disco. Ecco un esempio più lungo: trova tutti gli usi di una particolare
variabile nella C
file sorgenti:

X / \. C $ /, x / variabile / + - p

Possiamo usare un comando f per identificare il file in cui appare la variabile:

X / \. C $ /, g / variabile / {
f
, X / variabili / + - {
=
p
}
}

Qui, il comando g garantisce che vengano stampati solo i nomi dei file contenenti la variabile (ma
fai attenzione che Sam possa confondere le cose stampando i nomi dei file letti durante il comando).
Il
= comando mostra dove nel file appare la variabile, e il comando p stampa la linea.
Il comando D è utile come bersaglio di una X. Questo esempio cancella dal menu tutti i file C che
non contenere una variabile particolare:

X / \. C $ /, v / variabile / D

Se non viene fornito alcun motivo per la X, il comando (che di default è f) viene eseguito in tutti i
file, quindi

XD

pulisce Sam per un nuovo inizio.


Ma piuttosto che lavorare ulteriormente, fermiamoci ora:

q
$

Alcuni comandi di manipolazione dei file possono essere annullati: annullando un f, eo r ripristina il
precedente
stato del file, ma w, B e D sono irrevocabili. E, naturalmente, lo è anche q.

You might also like