@@ 21,7 21,9 @@ fontsize:
# Introducere - shell-ul
-Codul poate fi găsit pe https://git.sr.ht/~tudor/rwsh
+Codul poate fi găsit pe https://git.sr.ht/~tudor/rwsh.
+
+Issue tracker: https://todo.sr.ht/~tudor/rwsh.
_Shell-ul_, sau linia de comandă, este interfața textuală a unui sistem de operare. Prin acesta,
utilizatorul poate să execute programe sub formă de _comenzi_, sau să execute
@@ 43,6 45,9 @@ Exemple de astfel de shell-uri sunt GNU `bash` (Bourne Again Shell), `csh`
(C Shell), `ksh` (Korn
Shell), `zsh` (Z Shell), `fish` (Friendly Interactive Shell) etc.
+Publicul țintă al acestor programe este format din administratori de sistem,
+ingineri software și power useri.
+
> În blocurile de cod care urmează, textul precedat de simbolul '#' formează
> comentariile.
@@ 96,6 101,13 @@ Laboratoarele Bell.
prelucrează datele linie cu linie. În unele cazuri, aceasta abordare poate fi
ineficientă și pentru procesor, dar și pentru programator.
+De asemenea, sintaxa RWSH este una curată, nouă, lipsită de neclaritățile din
+limbajele uzuale de shell-scripting, care au apărut din cauza nevoii de a păstra
+compatibilitatea cu versiuni mai vechi ale acestora. RWSH nu respectă standardul
+POSIX. Acesta poate fi un lucru bun, pentru că permite dezvoltarea unei sintaxe
+complet noi, dar și un lucru rău, atunci când compatibilitatea POSIX este
+dorită.
+
## Expresiile regulate structurale (structural regular expressions)
RWSH folosește un sub-limbaj prin care poate fi exprimată structura textului pe
@@ 133,7 145,7 @@ care se află textul cel nou.
`cat document.txt` invocă programul `cat`, care afișează pe ecran conținutul
fișierului `document.txt`.
-Comanda, fiind urmată de operatorul pizza (`|>`), conținutul fișierului, în loc
+Rezultatul comenzii, fiind urmat de operatorul pizza (`|>`), conținutul fișierului, în loc
să fie afișat pe ecran, va fi pasat comenzilor pizza care urmează.
Prima comandă, `,x/Tudor/ c/Ioan/` are adresa `,`, care se referă la datele de
@@ 143,12 155,12 @@ Comanda `x` executa comanda transmisă ca parametru pentru fiecare subșir care
potrivește cu expresia regulată dată. Comanda `c/Ioan/` schimbă subșirul cu
textul "Ioan".
-Următoarea comanda din șir este `,p`, care afișează textul dat în întregime.
+Următoarea comandă din șir este `,p`, care afișează textul dat în întregime.
Pe lângă operația `c`, mai există operațiile `a`, `d` și `i`, care adaugă după
-dot, șterg textul din dot și respectiv inserează înaintea dot-ului.
+dot, șterge textul din dot și respectiv inserează înaintea dot-ului.
-Analogul operației `x` este `y`, care execută o comandă pe subșirurile care nu
+Inversul operației `x` este `y`, care execută o comandă pe subșirurile care nu
se potrivesc cu expresia regulată.
O altă abilitate specială este cea de a executa comenzi _pizza_ în paralel,
@@ 192,7 204,6 @@ Comenzile aflate intre acolade sunt cele executate _în paralel_. Efectul
fiecărei comenzi este înregistrat într-un jurnal sub formă de vector. Acestea
sunt interclasate și la final efectele sunt aplicate.
-\clearpage
Un alt exemplu este să vedem de câte ori și unde apare cuvântul "linux" în
jurnalul sistemului:
@@ 224,6 235,7 @@ meargă o linie în față și una în spate față de adresa subșirului găsit
pentru a afișa toată linia pe care am găsit subșirul. Altfel, s-ar fi afișat
doar cuvântul "linux".
+\clearpage
Exemplu mai complex: afișarea liniilor care conțin cuvântul "linux", dar fără
timpul evenimentelor (textul dintre paranteze pătrate de la începutul fiecărei
linii):
@@ 238,7 250,8 @@ dmesg |> ,x/^\[.*\] /d |> ,x/linux/ {
Rezultatul va fi pasat comenzii `lolcat` pentru a fi afișat în culorile
curcubeului.
-\clearpage
+Detalii legate de comenzile disponibile pot fi găsite in [anexă](#anexa).
+
## Avantajele abordării RWSH
Integrarea uneltelor de prelucrare a textului în cadrul shell-ului este
@@ 246,7 259,7 @@ inevitabilă. Uneltele convenționale, precum `grep`, `sed`, `cut`, `tr` etc. su
folosite în aproape orice _shell script_ din cauza funcțiilor elementare pe care
le prestează. Majoritatea shell-urilor moderne prezintă unele astfel de
funcționalități tocmai pentru că sunt indispensabile și sunt prea lungi de
-scris. Uitați care este diferența dintre eliminarea sufixului numelui unui
+scris. Priviți care este diferența dintre eliminarea sufixului numelui unui
fișier în mod tradițional vs. cu ajutorul sintaxei speciale din cel mai popular
shell, GNU _bash_:
@@ 286,8 299,7 @@ eficientă și mai citibilă.
# Funcționalitatea de limbaj de programare
Pentru moment, în limita timpului disponibil, am reușit să implementez
-variabilele, șirurile de caractere, operațiile matematice, blocurile de cod și blocurile decizionale
-(`if`).
+variabilele, șirurile de caractere, operațiile matematice, blocurile de cod, blocurile decizionale (`if`), buclele (`while`) și două blocuri speciale pentru pattern matching: `switch` și `match`.
## Variabilele
@@ 295,33 307,171 @@ Valorile sunt atribuite variabilelor cu comanda `let`. Variabilele sunt
declarate automat la atribuire.
```bash
-let nume Tudor
+let nume = Tudor
echo "Salut, $nume!" # Va afisa "Salut, Tudor!"
-let nume Andrei
+let nume = Andrei
echo "Salut, $nume!" # Va afisa "Salut, Andrei!"
```
Pentru a folosi o variabilă, numele ei va fi precedat de simbolul `$`.
-Pentru a șterge variabila, se va folosi comanda `unset`: `unset nume`.
+Pentru a șterge variabila, se va folosi `let` cu flag-ul `-e` (erase): `let -e
+name`.
**Notă**: există o variabilă specială, numită `?`. Ea ține minte "exit code"-ul
ultimei comenzi executate. Comanda precedentă se consideră executată cu succes
dacă `?` va fi egal cu 0.
+Variabilele pot avea un _scope_ asemănător limbajelor moderne de programare,
+spre deosebire de POSIX shell. Când o variabilă este creată, ea va fi atribuită
+blocului de cod în care se află. Atunci când atribuim o valoare nouă unei
+variabile în interiorul unui bloc inferior de cod, variabila va rămâne în blocul
+superior. Putem crea o nouă variabilă cu același nume în blocul inferior de cod
+cu flag-ul `-l` (local).
+
+**Exemple:**
+
+Păstrarea scope-ului:
+
+```bash
+let name = Tudor
+echo $name # Tudor
+{
+ echo $name # Tudor
+ let name = Ioan
+ echo $name # Ioan
+}
+echo $name # Ioan
+```
+
+Crearea unui nou scope local:
+
+```bash
+let name = Tudor
+echo $name # Tudor
+{
+ echo $name # Tudor
+ let -l name = Ioan
+ echo $name # Ioan
+}
+echo $name # Tudor
+```
+
+Atribuirea unui scope inferior atunci când variabila nu există:
+
+```bash
+echo $name # nimic
+{
+ let name = Tudor
+ echo $name # Tudor
+}
+echo $name # nimic
+```
\clearpage
+
+### Vectorii (array)
+
+Toate variabilele sunt stocate drept vectori. Când declarăm o variabilă simplă,
+se declară de fapt un vector cu un element.
+
+Putem declara un vector cu mai multe elemente:
+
+```bash
+let fructe = [ mere rosii prune ]
+echo mie îmi place să mănânc $fructe[2]
+```
+
+Dacă folosim un vector fără un index, fiecare element va fi tratat ca un
+argument separat (array expansion).
+
+```bash
+echo $fructe # echo primeste trei parametrii
+```
+
+Putem folosi asta pentru a crea vectori pe baza altora:
+
+```bash
+let numere = [ 1 2 3 ]
+let numere_speciale = [ 5 $numere 8 9 4 ] # elementele vor fi 5 1 2 3 8 9 4
+```
+
+Expresiile glob, precum `*.mp4`, sunt tratate și ele ca argumente diferite:
+
+```bash
+let list_of_movies = [ *.mp4 ]
+let list_of_music = [ *.ogg *.flac ]
+```
+
+Dacă folosim vectorul fără un index, dar în interiorul unui șir de caractere cu
+ghilimele, elementele lui vor fi tratate ca un singur parametru, cu spații între
+ele:
+
+```bash
+echo "$fructe" # echo primeste un parametru
+```
+
+Dacă numele vectorului are sufixul `PATH` (exemple: `PATH`, `MANPATH`, `LD_LOAD_PATH` etc.), în scrierea lui ca șir de caractere, elementele nu vor fi
+delimitate de spații, ci de doua puncte (":"):
+
+```bash
+echo "$PATH" # afișează /bin:/usr/bin:/usr/local/bin etc
+echo $PATH # afișează /bin /usr/bin /usr/local/bin,
+ # dar fiecare path va fi perceput ca un
+ # argument separat de catre echo
+```
+
+Variabilele de mediu (environment variable) care au sufixul "PATH", precum cele
+enumerate mai sus, vor fi automat convertite în vectori.
+\clearpage
+
+### Comanda `let`
+
+Pe lângă atribuirea de valori și crearea variabilelor în scope-uri noi, `let`
+știe și să creeze variabile de mediu (environment variables):
+
+```bash
+let -x variabila_de_mediu = "o valoare" # -x vine de la eXport
+
+# echivalentul bash:
+export variabila_de_mediu="o valoare"
+```
+
+Ștergerea variabilelor:
+
+```bash
+let -e variabila # sterge variabila normala
+let -xe variabila # sterge variabila de mediu
+```
+
+Pe lângă `=`, `let` are și alți operatori pentru diverse scurtături:
+
+```bash
+let i += numar # echivalent cu let i = $(calc $i + numar)
+# exista si -=, *=, /=, %=
+
+let array ++= element # adauga "element" in array
+# echivalent cu let array = [ $array element ]
+
+let array ::= element # adauga "element" la inceput in array
+# echivalent cu let array = [ element $array ]
+
+let array ++= [ element1 element2 ] # adauga mai multe elemente
+let array ::= [ element1 element2 ] # adauga mai multe elemente la inceput
+```
+
## Șirurile de caractere
Parametrii comenzilor date sunt exprimați ca șiruri de caractere separate prin
-spațiu. Pentru a putea folosi șiruri de caractere cu caractere speciale și
+spații. Pentru a putea folosi șiruri de caractere cu caractere speciale și
spații, acestea vor fi înconjurate de ghilimele (`"`) sau apostrofuri (`'`).
Apostrofurile diferă de ghilimele prin faptul că în șirurile de caractere cu
apostrofuri, cuvintele precedate de `$` nu vor fi tratate ca variabile.
+\clearpage
```bash
-let nume Tudor
+let nume = Tudor
echo Salut, $nume! # Va afisa "Salut, Tudor!"
# Comanda echo primeste doi parametri: "Salut," si "Tudor!"
@@ 337,7 487,7 @@ echo 'Salut, $nume!' # Va afisa "Salut, $nume!"
regulile fiecăruia:
```bash
-let nume Tudor
+let nume = Tudor
echo Salut", $nume"'!' # Va afisa tot "Salut, Tudor!"
# Comanda echo primeste un singur parametru
@@ 351,21 501,47 @@ echo Este ora $(date +%H:%M) # Va afisa "Este ora 11:27"
# Comanda echo primeste 3 parametri
```
+**Important**: valorile variabilelor / elementelor vectorilor, indiferent de cate spații conțin, vor fi
+tratate întotdeauna ca un singur parametru. RWSH nu necesită "quoting"-ul
+variabilelor.
+
+```bash
+# bash:
+filename="video haios.mp4"
+rm $filename
+# rm: cannot remove 'video': No such file or directory
+# rm: cannot remove 'haios.mp4': No such file or directory
+
+# rwsh:
+let filename = "video haios.mp4"
+rm $filename # merge fara nicio problema, cum ne-am aștepta
+rm $(echo video haios.mp4) # merge și așa
+```
+
+\clearpage
+În cazul în care vreți ca o variabilă sa fie "extinsă" ca în bash, aveți două
+opțiuni:
+
+1. Declarați variabila ca vector: `let filename = [ video haios.mp4 ]`
+2. Folosiți comanda `eval`: `eval rm $filename`
+
**Notă**: dacă un șir de caractere simplu (fără ghilimele sau apostrofuri)
conține la început o cale de fișier care începe cu caracterul `~`, tilda va fi
înlocuită de calea către directorul utilizatorului. Exemplu:
```bash
ls ~/src # Afiseaza conținutul folder-ului /home/tudor/src
+ls ~altuser/dir # Afișează conținutul folder-ului /home/altuser/dir
```
-\clearpage
-## Blocurile de cod și blocurile `if`
-Sintaxa pentru un bloc `if` este `if (condiție) comandă_de_executat`.
+## Blocurile de cod și blocurile `if` și `while`
+
+Sintaxa pentru blocurile `if` și `while` este `if (condiție) comandă_de_executat`, respectiv `while (condiție) comandă_de_executat`.
Condiția este o comandă. Dacă "exit code"-ul comenzii din condiție este 0,
-condiția este validă, iar comanda se va executa.
+condiția este validă, iar comanda se va executa. În cazul lui `while`, comanda
+se va executa cât timp condiția se evaluează cu codul 0.
Dacă vrem să executăm mai multe comenzi, vom folosi blocul de cod, scris între
acolade:
@@ 388,19 564,122 @@ else nu_avem_incotro
Cum condiția este o comandă, putem sa folosim pipe-uri, operatori pizza, etc.
+Condiția poate fi negată cu operatorul `!`: `if (! condiție) fa_ceva`.
+
+Mai pot fi folosiți și operatorii logici `||` și `&&`, chiar și în afara
+condiției, ca in bash.
+
+\clearpage
## Operațiile matematice
Operațiile matematice se fac cu comanda `calc`. Putem stoca rezultatul într-o
variabila astfel:
```bash
-let a 2
-let b 3
-let c $(calc $a + $b)
+let a = 2
+let b = 3
+let c = $(calc $a + $b)
echo "Rezultatul este $c" # Va afisa "Rezultatul este 5"
```
+Incrementarea și decrementarea variabilelor se poate face direct cu `let`:
+
+```bash
+let i = 0
+let n = 10
+while ([ $i -lt $n ]) {
+ fa_ceva
+ let i += 1
+}
+```
+
+## Pattern matching: `switch` si `match`
+
+Blocul `switch` arată în felul următor:
+
+```
+switch $valoare
+ /pattern_1/ fa_ceva
+ /pattern_2/ fa_altceva
+ ...
+ /pattern_n/ nu_stiu_ce_sa_mai_fac
+ // fallthrough
+end
+```
+
+`switch` evaluează pattern-urile (care sunt regex-uri) în ordine și excută
+comanda asociată primului regex care se potrivește cu valoarea dată.
+
+**Notă**: `switch` ar putea fi îmbunătățit cu condiții care nu țin doar de
+natura șirului de caractere. Momentan nu există nicio modalitate de a folosi
+`switch` pe intervale numerice, de exemplu.
+
+\clearpage
+**Exemplu**:
+
+```
+switch $status
+ /\[ERROR\] (.*)/ echo s-a petrecut o eroare: $1
+ /\[WARNING\] (.*)/ echo avertizare: $1
+end
+```
+
+Putem folosi `switch` și ca să împărțim un text în mai multe câmpuri:
+
+```
+switch $status
+ /\[(?P<type>.*)\] (.*)/ echo got event type \"$type\" with message: $2
+end
+```
+
+`match` este similar comenzii SRE `x`: citește de la `stdin` și pentru fiecare
+subșir care se potrivește cu un regex, execută comanda asociată. Dacă un subșir
+se potrivește cu mai multe regex-uri, se execută comenzile asociate tuturor
+regex-urilor cu care se potrivește. `match` este echivalent-ul pattern-urilor
+din `awk`. Totuși, `awk` execută comenzile pe întreaga linie care se potrivește
+cu regex-ul, în timp ce RWSH ia strict textul potrivit.
+
+**Exemplu**: afișează textul dintre ghilimele
+
+```bash
+echo 'un text cu "ghilimele" in el. si '"'apostrofuri'" |
+ awk '/".*"/ { print "ghilimele", $1 }'"
+ /'.*'/"' { print "apostrofuri", $1 }'
+```
+
+Va afișa "ghilimele un apostrofuri un", deoarece `print $1` afișează primul cuvânt de pe linia cu
+ghilimele ("un"), nu textul dintre ghilimele.
+
+Următoarea este varianta corectă:
+
+```bash
+echo 'un text cu "ghilimele" in el. si '"'apostrofuri'" | awk '{
+ for (i = 1; i <= NF; i++) {
+ if (match($i, /".*"/)) {
+ print "ghilimele", $i
+ } else if (match($i, '"/'.*'/"')) {
+ print "apostrofuri", $i
+ }
+ }
+}'
+```
+
+Codul acesta nici măcar nu mai folosește pattern-urile din `awk`.
+
+Varianta RWSH este mai simplă:
+
+```bash
+echo 'un text cu "ghilimele" in el. si '"'apostrofuri'" |
+ match
+ /"(.*)"/ echo ghilimele $1
+ /'(.*)'/ echo apostrofuri $1
+ end
+```
+
+Această variantă nu doar că este mai ușoară de înțeles, ba chiar extrage textul
+dintre ghilimele / apostrofuri.
+
\clearpage
# Detalii tehnice
@@ 410,14 689,201 @@ MacOS, FreeBSD etc.
Programul este scris în limbajul de programare Rust, un limbaj similar cu C++
care pune accent pe corectitudinea programului și a modelului de memorie. Cum
shell-ul este un program cheie în orice sistem de calcul, acesta nu trebuie să
-aibă erori de memorie sau probleme de securitate (a se vedea: [Shellshock][1])
-
-Pentru a asigura siguranța codului și sănătatea minții, folosesc teste automate
+aibă erori de memorie sau probleme de securitate (a se vedea: [Shellshock][1]).
+Rust de asemenea vine cu sintaxă și librărie standard modernă, făcând experiența
+de programare mai apropiată de un limbaj de programare "ușor", precum Python sau
+Go.
+
+Pentru a asigura siguranța codului și sănătatea minții, folosesc \
+**teste
+automate**
pentru a detecta bug-uri în cod. Acestea se execută cu comanda `cargo test` din
Rust și cu script-ul `run_examples.sh` din folder-ul `examples`.
[1]: https://en.wikipedia.org/wiki/Shellshock_(software_bug)
+Aceste teste sunt executate automat la fiecare `git push` într-un sistem tip CI
+(continous integration) la adresa [https://builds.sr.ht/~tudor/rwsh][builds]. La fiecare
+push, serviciul compilează codul, verifică ca fiecare fișier să conțină antetul
+pentru licența GPL și execută testele. Dacă testele merg bine, codul este împins
+automat pe GitHub. Dacă testele merg bine, codul este împins automat pe GitHub.
+
+[builds]: https://builds.sr.ht/~tudor/rwsh
+
Librăriile folosite includ `nix` pentru funcțiile de bibliotecă pentru sistemul
de operare, `regex` pentru expresiile regulate _simple_, și `calculate` pentru
funcția de calculator.
+
+# To do
+
+Am implementat funcțiile cele mai importante importante pentru a obține o soluție consistentă pentru prezentare.
+Funcționalități care vor fi implementate:
+
+* Buclă cu iterator (`for`)
+* Funcții
+* Variabile hash map
+* Execuția comenzilor din SRE
+* Job control (foarte complex)
+* [Ticket-ul #4](https://todo.sr.ht/~tudor/rwsh/4) (pe
+ `https://todo.sr.ht/~tudor/rwsh`)
+
+\clearpage
+
+## Arhitectura aplicației
+
+Codul este împărțit în mai multe module de tip "crate", specifice limbajului de
+programare Rust. Codul executabilului este în modulul executabil, care se
+folosește de modulul "lib". Modulul "lib" este mai departe împarțit în:
+
+Modul | Descriere
+------|----------
+`builtin` | Conține codul fiecărui program "builtin", precum `calc`, `let`, `eval`, `exit` etc.
+`parser` | Se ocupă de transformarea textului în arbore de sintaxă.
+`shell` | Conține cod specific funcționalității de shell, precum execuția codului, stocarea variabilelor.
+`sre` | Codul din spatele expresiilor SRE.
+`task` | Fiecare activitate pe care o duce shell-ul, fie ea execuția comenzilor, procesarea șirurilor de caractere, execuția blocurilor de cod, blocurilor `if`, `while`, `switch`, `match`, pipe-uri etc.
+`tests` | Cod comun testelor.
+`util` | Conține codul responsabil cu citirea propriu-zisă a liniilor de cod de la tastatură sau din fișier. Conține o interfață abstractă pentru asta.
+
+## Ghid de instalare
+
+* Instalați Rust folosind [`rustup`](https://rustup.rs/).
+* Clonați repo-ul: `git clone https://git.sr.ht/~tudor/rwsh && cd rwsh`
+* Compilați: `cargo build --release`
+* Executați: `cargo run --release`
+
+\clearpage
+# Anexă - comenzi disponibile {#anexa}
+
+## `p`
+
+Afișează conținutul de la adresa _dot_. Nu acceptă parametri.
+
+**Exemplu:** Afișează tot conținutul.
+
+```
+|> ,p
+```
+
+## `a`, `c`, `i` și `d`
+
+Adaugă după, înlocuiește, inserează înaintea sau șterge conținutul _dot_.
+
+**Exemple:**
+
+```
+|> 2a/O nouă linie\n/ # adaugă o nouă linie între liniile 2 și 3
+
+|> /To Do/ c/Done/ # înlocuiește statutul unei notițe
+
+|> 2i/O nouă linie\n/ # adaugă o nouă linie între liniile 1 și 2
+
+echo "text de șters" |> /de șters/d |> ,p # afișează "text"
+```
+
+## `g` și `v`
+
+Execută o comandă pe textul de la adresa _dot_ dacă respectivul text se
+potrivește cu un regex.
+
+`v` este opusul comenzii `g`: Execută comanda dacă textul **nu** se potrivește.
+
+**Exemplu:**
+
+Dacă primul cuvânt de la poziția _dot_ este "Tudor", afișează poziția.
+
+Comenzile `g` și `v` sunt folosite în general împreună cu `x` și `y`, nu pe cont propriu.
+
+```
+|> /\b.+\b/ g/Tudor/ =
+
+```
+
+Înlocuiește toate aparițiile cuvântului "vi" cu "emacs". Dacă un cuvânt conține "vi", el nu va fi alterat. Alternativ, se poate folosi metacaracterul `\b` în regex.
+
+```
+|> ,x/vi/ v/.../ c/Emacs/
+# echivalent cu
+|> ,x/\bvi\b/ c/Emacs/
+```
+
+## `x` și `y`
+
+Execută o comandă pentru fiecare subșir care se potrivește cu un regex în cadrul
+textului de la adresa _dot_.
+
+Comanda `y` este opusul comenzii `x`: execută comanda pe textul situat **între**
+subșirurile care se potrivesc cu regex-ul, în cadrul textului de la adresa _dot_.
+
+**Exemplu:**
+
+Înlocuiește toate aparițiile lui "Tudor" cu "Ioan".
+
+```
+|> ,x/Tudor/ c/Ioan/
+```
+
+Înlocuiește toți identificatorii numiți `n` într-un cod sursă, cu `num`, fără să
+atingă șirurile de caractere, aflate între ghilimele sau apostrofuri.
+
+```
+|> ,y/".*"|'.*'/ x/\bn\b/ c/num/
+```
+
+## `=`
+
+Afișează poziția adresei _dot_ în caractere de la începutul fișierului.
+
+**Exemplu:**
+
+Va afișa `#8,#13`.
+
+```
+echo "eu sunt Tudor" |> /Tudor/=
+```
+
+\clearpage
+## Adrese
+
+Extras din manualul editorului `sam(1)`:
+
+## Addresses
+
+An address identifies a substring in a file. In the following, 'character n' means the null string after the n-th
+character in the file, with 1 the first character in the file. 'Line n' means the n-th match, starting at the beginning of the file, of the regular expression `.*\n?`.
+All files always have a current substring, called dot, that is the default address.
+
+### Simple Addresses
+
+`#n` The empty string after character n; `#0` is the beginning of the file.
+
+`n` Line n; 0 is the beginning of the file.
+
+`/regexp/`; `?regexp?` The substring that matches the regular expression,
+found by looking toward the end (/) or beginning (?)
+of the file, and if necessary continuing the search
+from the other end to the starting point of the search.
+The matched substring may straddle the starting point.
+When entering a pattern containing a literal question
+mark for a backward search, the question mark should be
+specified as a member of a class.
+
+`0` The string before the first full line.
+
+`$` The null string at the end of the file.
+
+`.` Dot.
+
+### Compound Addresses
+
+In the following, `a1` and `a2` are addresses.
+
+`a1+a2` The address `a2` evaluated starting at the end of `a1`.
+`a1-a2` The address `a2` evaluated looking in the reverse direction starting at the beginning of `a1`.
+`a1,a2` The substring from the beginning of `a1` to the end of `a2`. If `a1` is missing, `0` is substituted. If `a2` is
+missing, `$` is substituted.
+`a1;a2` Like `a1`,`a2`, but with `a2` evaluated at the end of, and
+dot set to, `a1`.
+
+The operators `+` and `-` are high precedence, while `,` and `;` are
+low precedence.