Ein URL (Uniform Resource Locator) gibt die Adresse eines Objekts im 
World Wide Web an. Er besteht aus der Zugriffsart (hp, 
ftp, gopher, mailto, news) gefolgt von 
einem Doppelpunkt ":" und einer genaueren Beschreibung des 
Objekts, abhängig von der Zugriffsart.
Bei http, ftp und gopher folgen zwei 
Schrägstriche "//", ein Hostname und ein Zugriffspfad.
Ein Hostname besteht entweder aus vier Zahlen mit maximal drei
Ziffern, getrennt durch einen Punkt (z.B. 128.130.173.8) oder
aus einer beliebigen Anzahl von Buchstabengruppen (Groß- und
Kleinbuchstaben und "-") getrennt durch einen Punkt
(der Hostname muß in diesem Fall mit einem Buchstaben beginnen,
z.B. ftp.uni-paderborn.de).
Ein Zugriffspfad ist vom Hostname durch einen Schrägstrich getrennt. Er besteht aus beliebig vielen (nicht leeren) Filenamen, die durch Schrägstriche getrennt sind und darf auch mit einem Schrägstrich enden.
Ein Filename besteht aus beliebig vielen Groß- und Kleinbuchstaben, 
"-" und ".".
Bei der Zugriffsart news folgt nach dem Doppelpunkt nur ein 
Hostname. Bei mailto folgt nach dem Doppelpunkt ein 
Name, ein "@" und ein Hostname. Ein Name besteht aus 
beliebig vielen Groß- und Kleinbuchstaben.
ziff = [0-9] 
zahl = ziff ziff? ziff? 
bst = [a-z]|[A-Z] 
ip = zahl "." zahl "." zahl "." zahl 
hn = bst (bst|"-")* ("." (bst|"-")+)* 
host = ip|hn 
file = (bst|"-"|".")+ 
pfad = file ("/" file)* "/"? 
art = "http"|"ftp"|"gopher" 
URL = (art "://" host ("/" pfad)|("/"?)) | ("news:" host) | ("mailto:" 
bst+ "@" host)
Bei einer regulären Definition ist es wichtig, innen anzufangen und sich langsam nach außen vorzuarbeiten. Die dabei entstehenden Bausteine werden dann für die weiteren Definitionen verwendet.
Die erste ins Auge fallenden Bausteine sind der Hostname der entweder aus Zahlen- oder aus Buchstabengruppen bestehen kann:
Ein Hostname besteht entweder aus vier Zahlen mit maximal drei Ziffern, getrennt durch einen Punkt (z.B. 128.130.173.8) oder aus einer beliebigen Anzahl von Buchstabengruppen (Groß- und Kleinbuchstaben und "
-") getrennt durch einen Punkt (der Hostname muß in diesem Fall mit einem Buchstaben beginnen, z.B.ftp.uni-paderborn.de).
Eine Zahl besteht also aus maximal drei Ziffern.  Da die vier Zahlen 
ihrerseits durch einen Punkt getrennt sind bedeutet das, daß eine 
Zahl aus mindestens einer Ziffer bestehen muß.  Eine 
Trennung hätte ja sonst keinen Sinn.  Daraus ergeben sich die 
Definitionen ziff = [0-9] und zahl = ziff ziff?  
ziff?.  Eine Zahl besteht also aus einer Ziffer gefolgt von zwei 
optionalen Ziffern.  Über den Wertebereich einer Zahl wird keine 
Aussage gemacht.  Das "Metawissen", daß eine IP-Adresse (derzeit) nur 
mit Zahlen im Bereich von 0 bis 255 arbeitet ist hier ohne Belang.  Die 
gesamte IP-Adresse ist eine Aneinanderreihung von Zahlen, getrennt durch 
einen Punkt: ip = zahl "." zahl "." zahl "." zahl
Die zweite Alternative für den Hostname ist ein Name 
bestehend aus Buchstabengruppen die durch Punkt getrennt sind.  Die 
Buchstabengruppen dürfen zwar auch einen Bindestrich enthalten, der 
ganze Hostname muß jedoch mit einem 
Buchstaben beginnen.  Wir definieren daher zuerst einen Einzelbuchstaben 
durch bst = [a-z]|[A-Z].  Der ganze Name hn beginnt 
also mit einem bst und erst dann dürfen Buchstaben und 
Bindestriche in beliebiger Kombination vorkommen: (bst|"-")*.  
Weitere Buchstabengruppen sind durch Punkt getrennt: ("." 
(bst|"-")+)*
Anmerkung: Die Aneinanderreihung von Termen und Trennzeichen kommt 
sehr häfig vor. Eine übliche Konstruktion ist term (trenn 
term)*, also mindestens ein Term und beliebig viele Wiederholungen 
von Trennzeichen und weiteren Termen.
Der ganze Hostname ist durch host = ip|hn 
definiert.
Die Zugriffsarten news und mailto lassen sich 
mit den vorangegangenen Bausteinen direkt aus der Textbeschreibung 
ableiten:
Bei der Zugriffsart
newsfolgt nach dem Doppelpunkt nur ein Hostname. Beimailtofolgt nach dem Doppelpunkt ein Name, ein "@" und ein Hostname. Ein Name besteht aus beliebig vielen Groß- und Kleinbuchstaben.
Daraus ergeben sich "news:" host bzw. "mailto:" 
bst* "@" host
Anmerkung: Natürlich kann man auch argumentieren, daß 
der Name in einer Mailadresse immer mindestens einen Buchstaben haben 
muß, also bst+ statt bst*. Beide Varianten 
sind richtig, da keine weiteren Einschränkungen bezüglich des 
Namens gemacht wurden.
Die Beschreibung des Zugriffspfades muß man sehr genau lesen, um sie richtig zu übersetzen:
Ein Zugriffspfad ist vom Hostname durch einen Schrägstrich getrennt. Er besteht aus beliebig vielen (nicht leeren) Filenamen, die durch Schrägstriche getrennt sind und darf auch mit einem Schrägstrich enden.
Die Filenamen sind noch das Einfachste.  Sie bestehen aus beliebig 
vielen Buchstaben, "-" und ".", sind aber 
nicht leer also file = (bst|"-"|".")+.  Der ganze 
Zugriffspfad besteht jedoch aus beliebig vielen, durch 
"/" getrennten Filenamen und darf mit einem 
"/" enden.  D.h., der ganze Pfad kann auch leer sein.  Daraus 
ergibt sich pfad = file ("/" file)* "/"?
Beim Zusammensetzen der vollständigen Definition müssen wir noch eine Ungereimtheit beseitigen:
Ein Zugriffspfad ist vom Hostname durch einen Schrägstrich getrennt.
Wie wir gesehen haben, kann der Zugriffspfad auch leer sein. Eine Trennung 
von etwas Leerem macht jedoch keinen Sinn. Gleichzeitig darf der Pfad nur 
mit einem "/" enden. Eine Definition die beide 
Fälle berücksichtigt ist in der Lösung gegeben. Sie lautet: 
host ("/" pfad)|("/"?) und vermeidet damit zwei 
Schrägstriche direkt nebeneinander.
Die Aneinanderreihung der alternativen Zugriffsarten ergibt schließlich die endgültige Lösung.
Von Wolfgang Faber kommt der Hinweis an alle, die nach diesem Beispiel laut aufstöhnen, einmal einen Blick in die Originalspezifikation zu werfen. :-)