• Keine Ergebnisse gefunden

1.1 Grundlagen: Reguläre Ausdrücke

N/A
N/A
Protected

Academic year: 2022

Aktie "1.1 Grundlagen: Reguläre Ausdrücke"

Copied!
60
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

1.1 Grundlagen: Reguläre Ausdrücke

• Programmtext benutzt ein endliches Alphabet Σ von Eingabe-Zeichen, z.B. ASCII :-)

• Die Menge der Textabschnitte einer Token-Klasse ist i.a. regulär.

• Reguläre Sprachen kann man mithilfe regulärer Ausdrückespezifizieren.

Die Menge EΣ der (nicht-leeren) regulären Ausdrücke ist die kleinste Menge E mit:

• ∈ E ( neues Symbol nicht aus Σ);

a ∈ E für alle aΣ;

• (e1 | e2),(e1 ·e2), e1 ∈ E sofern e1, e2 ∈ E.

(2)

1.1 Grundlagen: Reguläre Ausdrücke

• Programmtext benutzt ein endliches Alphabet Σ von Eingabe-Zeichen, z.B. ASCII :-)

• Die Menge der Textabschnitte einer Token-Klasse ist i.a. regulär.

• Reguläre Sprachen kann man mithilfe regulärer Ausdrückespezifizieren.

Die Menge EΣ der (nicht-leeren) regulären Ausdrücke ist die kleinste Menge E mit:

• ∈ E ( neues Symbol nicht aus Σ);

a ∈ E für alle aΣ;

• (e1 | e2),(e1 ·e2), e1 ∈ E sofern e1, e2 ∈ E.

(3)

Stephen Kleene, Madison Wisconsin, 1909-1994

(4)

Beispiele:

((a·ba) (a | b)

((a·b)·(a·b))

Achtung:

• Wir unterscheiden zwischen Zeichen a, 0,|,... und Meta-Zeichen (,|, ),...

• Um (hässliche) Klammern zu sparen, benutzen wir Operator-Präzedenzen:

> · >|

und lassen “·” weg :-)

• Reale Spezifikations-Sprachen bieten zusätzliche Konstrukte wie:

e? ≡ ( | e) e+ ≡ (e ·e) und verzichten auf “” :-)

(5)

Beispiele:

((a·ba) (a | b)

((a·b)·(a·b))

Achtung:

• Wir unterscheiden zwischen Zeichen a, 0, |,... und Meta-Zeichen (,|,),...

• Um (hässliche) Klammern zu sparen, benutzen wir Operator-Präzedenzen:

> · > |

und lassen “·” weg :-)

• Reale Spezifikations-Sprachen bieten zusätzliche Konstrukte wie:

e? ≡ ( | e) e+ ≡ (e ·e) und verzichten auf “” :-)

(6)

Beispiele:

((a·ba) (a | b)

((a·b)·(a·b))

Achtung:

• Wir unterscheiden zwischen Zeichen a, 0, |,... und Meta-Zeichen (,|,),...

• Um (hässliche) Klammern zu sparen, benutzen wir Operator-Präzedenzen:

> · > |

und lassen “·” weg :-)

• Reale Spezifikations-Sprachen bieten zusätzliche Konstrukte wie:

e? ≡ ( | e) e+ ≡ (e ·e) und verzichten auf “ :-)

(7)

Spezifikationen benötigen eine Semantik :-) Im Beispiel:

Spezifikation Semantik aba {abna | n ≥ 0} a | b {a,b}

abab {abab}

Für e ∈ EΣ definieren wir die spezifizierte Sprache [[e]] ⊆ Σ induktiv durch:

[[]] = {} [[a]] = {a} [[e]] = ([[e]])

[[e1|e2]] = [[e1]]∪ [[e2]]

[[e1·e2]] = [[e1]]· [[e2]]

(8)

Beachte:

• Die Operatoren (_),∪, · sind die entsprechenden Operationen auf Wort-Mengen:

(L) = {w1 . . .wk | k ≥ 0, wiL} L1 ·L2 = {w1w2 | w1L1, w2L2}

(9)

Beachte:

• Die Operatoren (_),∪, · sind die entsprechenden Operationen auf Wort-Mengen:

(L) = {w1 . . .wk | k ≥ 0, wiL} L1 ·L2 = {w1w2 | w1L1, w2L2}

• Reguläre Ausdrücke stellen wir intern als markierte geordnete Bäume dar:

.

|

*

b

a (ab|)

Innere Knoten: Operator-Anwendungen;

Blätter: einzelne Zeichen oder .

(10)

Finger-Übung:

Zu jedem regulären Ausdruck e können wir einen Ausdruck e0 (evt. mit

“?”) konstruieren so dass:

• [[e]] = [[e0]];

• Falls [[e]] = {}, dann ist e0;

• Falls [[e]] 6= {}, dann enthält e0 kein “”.

Konstruktion:

Wir definieren eine Transformation T von regulären Ausdrücken durch:

(11)

Finger-Übung:

Zu jedem regulären Ausdruck e können wir einen Ausdruck e0 (evt. mit

“?”) konstruieren so dass:

• [[e]] = [[e0]];

• Falls [[e]] = {}, dann ist e0;

• Falls [[e]] 6= {}, dann enthält e0 kein “”.

Konstruktion:

Wir definieren eine Transformation T von regulären Ausdrücken durch:

(12)

T [] = T [a] = a

T [e1|e2] = case (T [e1], T [e2]) of (,) :

| (e01,) : e01?

| (, e0

2) : e02?

| (e01,e02): (e01 | e02) T [e1·e2] = case (T [e1], T [e2]) of (,) :

| (e01,) : e01

| (, e0

2) : e02

| (e01,e02): (e01 ·e02) T [e] = case T [e] of :

| e1: e1

T [e?] = case T [e] of :

| e1: e1?

(13)

Unsere Anwendung:

Identifier in Java:

le = [a-zA-Z_\$]

di = [0-9]

Id = {le} ({le} | {di})*

Bemerkungen:

• “le” und “di” sind Zeichenklassen.

• Definierte Namen werden in “{”, “}” eingeschlossen.

• Zeichen werden von Meta-Zeichen durch “\” unterschieden.

(14)

Unsere Anwendung:

Identifier in Java:

le = [a-zA-Z_\$]

di = [0-9]

Id = {le} ({le} | {di})*

Bemerkungen:

• “le” und “di” sind Zeichenklassen.

• Definierte Namen werden in “{”, “}” eingeschlossen.

• Zeichen werden von Meta-Zeichen durch “\” unterschieden.

(15)

Unsere Anwendung:

Identifier in Java:

le = [a-zA-Z_\$]

di = [0-9]

Id = {le} ({le}|{di})*

Gleitkommazahlen:

Float = {di}* (\.{di}|{di}\.) {di}*((e|E)(\+|\-)?{di}+)?

Bemerkungen:

• “le” und “di” sind Zeichenklassen.

• Definierte Namen werden in “{”, “}” eingeschlossen.

• Zeichen werden von Meta-Zeichen durch “\” unterschieden.

(16)

1.2 Grundlagen: Endliche Automaten Beispiel:

a b

Knoten: Zustände;

Kanten: Übergänge;

Beschriftungen: konsumierter Input :-)

(17)

1.2 Grundlagen: Endliche Automaten Beispiel:

a b

Knoten: Zustände;

Kanten: Übergänge;

Beschriftungen: konsumierter Input :-)

(18)

Michael O. Rabin, Stanford University

Dana S. Scott, Carnegy Mellon University, Pittsburgh

(19)

Formal ist ein nicht-deterministischer endlicher Automat mit-Übergängen (-NFA) ein Tupel A = (Q, Σ,δ, I, F) wobei:

Q eine endliche Menge von Zuständen;

Σ ein endliches Eingabe-Alphabet;

IQ die Menge der Anfangszustände;

FQ die Menge der Endzustände und

δ die Menge der Übergänge (die Übergangs-Relation) ist.

(20)

Formal ist ein nicht-deterministischer endlicher Automat mit-Übergängen (-NFA) ein Tupel A = (Q, Σ,δ, I, F) wobei:

Q eine endliche Menge von Zuständen;

Σ ein endliches Eingabe-Alphabet;

IQ die Menge der Anfangszustände;

FQ die Menge der Endzustände und

δ die Menge der Übergänge (die Übergangs-Relation) ist.

Für-NFAs ist:

δ ⊆ Q × (Σ∪ {})× Q

• Gibt es keine-Übergänge (p,,q), ist A ein NFA.

• Ist δ : Q× ΣQ eine Funktion und #I = 1, heißt A deterministisch (DFA).

(21)

Formal ist ein nicht-deterministischer endlicher Automat mit-Übergängen (-NFA) ein Tupel A = (Q, Σ,δ, I, F) wobei:

Q eine endliche Menge von Zuständen;

Σ ein endliches Eingabe-Alphabet;

IQ die Menge der Anfangszustände;

FQ die Menge der Endzustände und

δ die Menge der Übergänge (die Übergangs-Relation) ist.

Für-NFAs ist:

δ ⊆ Q × (Σ∪ {})× Q

• Gibt es keine-Übergänge (p,, q), ist A ein NFA.

• Ist δ : Q× ΣQ eine Funktion und #I = 1, heißt A deterministisch(DFA).

(22)

Akzeptierung

• Berechnungen sind Pfade im Graphen.

• akzeptierende Berechnungen führen von I nach F .

• Ein akzeptiertes Wort ist die Beschriftung eines akzeptierenden Pfades ...

a b

(23)

Akzeptierung

• Berechnungen sind Pfade im Graphen.

• akzeptierende Berechnungen führen von I nach F .

• Ein akzeptiertes Wort ist die Beschriftung eines akzeptierenden Pfades ...

a b

(24)

• Dazu definieren wir den transitiven Abschluss δ von δ als kleinste Menge δ0 mit:

(p,, p) ∈ δ0 und

(p,xw, q) ∈ δ0 sofern (p, x, p1) ∈ δ und (p1, w,q) ∈ δ0.

δ beschreibt für je zwei Zustände, mit welchen Wörtern man vom einen zum andern kommt :-)

• Die Menge aller akzeptierten Worte, d.h. die von A akzeptierte Sprache können wir kurz beschreiben als:

L(A) = {wΣ | ∃ iI, fF : (i,w, f) ∈ δ}

(25)

Satz:

Für jeden regulären Ausdruck e kann (in linearer Zeit :-) ein-NFA konstruiert werden, der die Sprache [[e]] akzeptiert.

Idee:

Der Automat verfolgt (konzepionell mithilfe einer Marke “•”), wohin man in e mit der Eingabe w gelangen kann.

(26)

Beispiel:

*

.

.

|

|

b a

b a a

( a | b )

a ( a | b )

(27)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(28)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(29)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(30)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(31)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(32)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(33)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(34)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(35)

Beispiel:

*

.

.

|

|

b a

b a a

w = bbaa :

(36)

Beachte:

• Gelesen wird nur an den Blättern.

• Die Navigation im Baum erfolgt ohne Lesen, d.h. mit-Übergängen.

• Für eine formale Konstruktion müssen wir die Knoten im Baum bezeichnen.

• Dazu benutzen wir (hier) einfach den dargestellten Teilausdruck :-)

• Leider gibt es eventuell mehrere gleiche Teilausdrücke :-(

==⇒ Wir numerieren die Blätter durch ...

(37)

... im Beispiel:

*

.

.

|

|

b a

b a

a

(38)

... im Beispiel:

*

.

.

|

|

0 1

2

3

b

4

a

b a

a

(39)

... im Beispiel:

*

.

.

|

|

0 1

2

3 4

a b a b

a

(40)

Die Konstruktion:

Zustände:r, rr Knoten von e;

Anfangszustand:e;

Endzustand: e•; Übergangsrelation:

Für Blätter ri x benötigen wir: (•r,x,r•). Die übrigen Übergänge sind:

(41)

r Übergänge r1 | r2 (•r,,r1)

(•r,,r2) (r1,, r•) (r2,, r•) r1 ·r2 (•r,,r1) (r1•,,r2) (r2•,, r•)

r Übergänge r1 (•r,, r•)

(•r,,r1) (r1,,r1) (r1,,r•) r1? (•r,, r•)

(•r,,r1) (r1•,,r•)

(42)

Diskussion:

• Die meisten Übergänge dienen dazu, im Ausdruck zu navigieren :-(

• Der Automat ist i.a. nichtdeterministisch :-(

==⇒

Strategie:

(1) Beseitigung der-Übergänge;

(2) Beseitigung des Nichtdeterminismus :-)

(43)

Diskussion:

• Die meisten Übergänge dienen dazu, im Ausdruck zu navigieren :-(

• Der Automat ist i.a. nichtdeterministisch :-(

==⇒

Strategie:

(1) Beseitigung der-Übergänge;

(2) Beseitigung des Nichtdeterminismus :-)

(44)

Beseitigung von -Übergängen:

Zwei einfache Ansätze:

p q

1

a q

2

q

Wir benutzen hier den zweiten Ansatz.

Zur Konstruktion von Parsern werden wir später den ersten benutzen :-)

(45)

Beseitigung von -Übergängen:

Zwei einfache Ansätze:

p q

1

a q

2

q

Wir benutzen hier den zweiten Ansatz.

Zur Konstruktion von Parsern werden wir später den ersten benutzen :-)

(46)

1. Schritt:

empty[r] = t gdw. [[r]]

... im Beispiel:

*

.

.

|

|

0 1

2

3 4

a b a b

a

(47)

1. Schritt:

empty[r] = t gdw. [[r]]

... im Beispiel:

*

.

.

|

|

f f

f

f f

0 1 3 4

2

a b a b

a

(48)

1. Schritt:

empty[r] = t gdw. [[r]]

... im Beispiel:

*

.

.

|

|

f f

f

f f

f f

0 1 3 4

2

a b a b

a

(49)

1. Schritt:

empty[r] = t gdw. [[r]]

... im Beispiel:

.

* .

|

|

f f

f

f f

f f

f t

0 1 3 4

2

a b a b

a

(50)

1. Schritt:

empty[r] = t gdw. [[r]]

... im Beispiel:

.

* .

|

|

f f

f

f f

f f

f t

f

0 1 3 4

2

a b a b

a

(51)

Implementierung: DFS post-order Traversierung

Für Blätter ri x ist empty[r] = (x ≡ ). Andernfalls:

empty[r1 | r2] = empty[r1]∨empty[r2] empty[r1 ·r2] = empty[r1]∧empty[r2] empty[r1] = t

empty[r1?] = t

(52)

2. Schritt:

Die Menge erster Bätter: first[r] = {i in r | (•r,,i x ) ∈ δ}

... im Beispiel:

.

* .

|

|

f f

f

f f

f f

f t

f

0 1 3 4

2

a b a b

a

(53)

2. Schritt:

Die Menge erster Bätter: first[r] = {i in r | (•r,,i x ) ∈ δ}

... im Beispiel:

.

* .

|

|

f f

f

f f

f f

f t

f

0 0

1 2

3 4

1 3 4

2

a b a b

a

(54)

2. Schritt:

Die Menge erster Bätter: first[r] = {i in r | (•r,,i x ) ∈ δ}

... im Beispiel:

.

* .

|

|

f f

f

f f

f f

f t

f

0 0

1 2

3 4

1 3 4

0 1

3 4 2

a b a b

a

(55)

2. Schritt:

Die Menge erster Bätter: first[r] = {i in r | (•r,,i x ) ∈ δ}

... im Beispiel:

.

* .

|

|

f f

f

f f

f f

f t

f

0 0

1 2

3 4

1 3 4

0 1

3 4

0 1 2

2

a b a b

a

(56)

2. Schritt:

Die Menge erster Bätter: first[r] = {i in r | (•r,,i x ) ∈ δ,x 6=}

... im Beispiel:

.

* .

|

|

f f

f

f f

f f

f t

f

0 0

1 2

3 4

1 3 4

0 1

3 4

0 1 2

2 0 1 2

a b a b

a

(57)

Implementierung: DFS post-order Traversierung

Für Blätter ri x ist first[r] = {i | x 6≡}. Andernfalls:

first[r1 | r2] = first[r1]∪first[r2] first[r1 ·r2] =

first[r1]∪ first[r2] falls empty[r1] = t first[r1] falls empty[r1] = f first[r1] = first[r1]

first[r1?] = first[r1]

(58)

3. Schritt:

Die Menge nächster Bätter: next[r] = {i | (r•,,i x ) ∈ δ}

... im Beispiel:

f

f

|

f

*

t

f f

|

f

. .

f f

f 3 2

0 0

1 4

0 1

0 1

1 4

3 4 2

2 0 1 2

3

a

a b b

a

(59)

3. Schritt:

Die Menge nächster Bätter: next[r] = {i | (r•,,i x ) ∈ δ}

... im Beispiel:

f

f

|

f

*

t

f f

|

f

. .

f f

f 3 2

0 0

1 4

0 1

0 1

1 4

3 4 2

2 0 1 2

3

a

a b b

a

(60)

3. Schritt:

Die Menge nächster Bätter: next[r] = {i | (r•,,i x ) ∈ δ}

... im Beispiel:

f

f

|

f

*

t

f f

|

f

. .

f f

f 3 2

0 0

1 4

0 1

0 1

1 4

3 4 2

2 0 1 2

3

3 4

2

a

a b b

a

Referenzen

ÄHNLICHE DOKUMENTE

ÜBUNGSAUFGABEN, Serie 5 (Abgabe am

❚ Für einen DFA existiert eine reguläre Grammatik, der die gleiche Sprache akzeptiert.. ❚ Für eine reguläre Grammatik existiert ein NFA, der die gleiche

durch die sechs gleichen Axenverh'a'ltnisse ( mama: a), \( aaa:a:na), (na: wa;a)‚ (a: mama), (a:na: ma) und (nazazoo a) bestimmt werden, welche aus denen der Hexakisokteeder

Es gibt auch einen POSIX-Standard für reguläre Ausdrücke (siehe man 7 regex).. Reguläre Ausdrücke

• Zeichenklassen sind eine Zusammenfassung verschiedener Zeichen, die an genau einer Stelle stehen können, und werden mit Hilfe von eckigen Klammern definiert (z.B..

Dann muß dieses Programm compiliert und gewöhnlich zusammen mit einer Bibliothek von LEX-Unterprogrammen geladen werden... Das uebersetzte Programm: a.out oder Name.exe

• $Flags: falls der Wert PREG_OFFSET_CAPTURE gesetzt ist, wird in dem Er- gebnisfeld auch die jeweilige Position, an der das Muster gefunden wurde, eingetragen. • $Versatz: die

Für jeden regulären Ausdruck e kann (in linearer Zeit :-) ein ǫ -NFA konstruiert werden, der die Sprache [[ e ]]