Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf ·...

58
1/25/16, 2:48 PM Compiladores: compilación de expresiones regulares - (c)2014 LSUB Page 1 of 58 http://127.0.0.1:3999/s05.regexp.slide#1 Compiladores: compilación de expresiones regulares Francisco J Ballesteros LSUB, URJC

Transcript of Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf ·...

Page 1: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 1 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Compiladores: compilación deexpresiones regularesFrancisco J BallesterosLSUB, URJC

Page 2: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 2 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Expresiones regulares

En este tema vamos a

definir expresiones regulares sencillas

implementar un compilador predictivo para las mismas

implementar un intérprete que las ejecute

Normalmente mejor hacerlo como lo hizo Ken Thompson

Ken regexps by Russ Cox (http://swtch.com/~rsc/regexp/regexp1.html)

Nosotros vamos a hacerlo paso a paso

Page 3: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 3 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Expresiones regulares

Para nuestros propósitos, una regexp será:

Cualquier runa sin significado especial, r

encaja con ella misma

La runa .

encaja con cualquier runa

Dos expresiones a y b concatenadas ab

encajan si un prefijo encaja con a y el sufijo con b

La expresión a|b

encaja si encaja a o encaja b

Page 4: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 4 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Expresiones regulares

La expresión a*

o el string vacío, o encaja como a, o como aa, ...

La expresión (a)

encaja igual que la expresión a

La expresión \r

encaja con la runa r

Page 5: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 5 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Gramática

Podríamos utilizar esta gramática:

RE ::= TERM OPTSOPTS ::= '|' TERM OPTS | <empty>TERM ::= ATOM ATOMSATOMS ::= ATOM ATOMS | <empty>ATOM ::= rune STAR | '(' RE ')' STARSTAR ::= '*' | <empty>

Vamos a hacer que el scanner se ocupe de los escapes de runas especiales

La gramática fuerza a precedencia de los operadores

Page 6: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 6 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Lex

Utilizaremos rune como token.

type lex struct { txt []rune Debug bool}

type Lexer interface { // return next token Scan() (rune, error) // Look ahead one token Peek() (rune, error)}

func NewLex(s string) *lex { return &lex{txt: []rune(s)}}

Page 7: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 7 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Lex

Y marcamos las runas especiales

const ( runeop rune = 0x40000000 runeops = "|*.()" Or = runeop | '|' Star = runeop | '*' Lpar = runeop | '(' Rpar = runeop | ')')

Page 8: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 8 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Lex

func (l *lex) scan() (rune, error) { if len(l.txt) == 0 { return 0, io.EOF } r := l.txt[0] l.txt = l.txt[1:] if r == '\\' { if len(l.txt) == 0 { return 0, fmt.Errorf("unexpected EOF") } r := l.txt[0] l.txt = l.txt[1:] return r, nil } if strings.IndexRune(runeops, r) >= 0 { r |= runeop } return r, nil}

Page 9: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 9 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Lex

func (l *lex) Peek() (rune, error) { old := l.txt r, err := l.scan() l.txt = old return r, err}

func (l *lex) Scan() (rune, error) { t, err := l.scan() if l.Debug && err == nil { isop := t&runeop != 0 x := t & ^runeop if isop { fmt.Printf("scan <%c>\n", x) } else { fmt.Printf("scan '%c'\n", x) } } return t, err}

Page 10: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 10 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Lex

Y podemos probarlo...

func main() { txt := `ab|c*\*\`

l := NewLex(txt) l.Debug = true for { if _, err := l.Scan(); err != nil { fmt.Printf("error %s\n", err) break } }} Run

Page 11: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 11 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Parsing

Este es el parser con depuración, pero sin hacer nada más.

type Rexp struct { l *lex Debug, Debuglex bool lvl int}

func (re *Rexp) trz(tag string) { if re.Debug { s := strings.Repeat(" ", re.lvl) fmt.Printf("%s%s\n", s, tag) } re.lvl++}

func (re *Rexp) untrz() { re.lvl--}

func NewRexp(s string) *Rexp { return &Rexp{l: NewLex(s)}}

Page 12: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 12 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Parsing

func (re *Rexp) Parse() error { re.l.Debug = re.Debuglex return re.parseRe()}

// RE ::= TERM OPTSfunc (re *Rexp) parseRe() error { re.trz("re") defer re.untrz() if err := re.parseTerm(); err != nil { return err } return re.parseOpts()}

Page 13: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 13 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Parsing

// OPTS ::= '|' TERM OPTS | <empty>func (re *Rexp) parseOpts() error { _, _, found := re.match(Or) if !found { return nil } re.trz("opts") defer re.untrz() if err := re.parseTerm(); err != nil { return err } return re.parseOpts()}

// TERM ::= ATOM ATOMSfunc (re *Rexp) parseTerm() error { re.trz("term") defer re.untrz() if err := re.parseAtom(); err != nil { return err } return re.parseAtoms()}

Page 14: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 14 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Parsing

// ATOMS ::= ATOM ATOMS | <empty>func (re *Rexp) parseAtoms() error { re.trz("atoms") defer re.untrz() if err := re.parseAtom(); err != nil { if err == io.EOF || err == ErrNoAtom { err = nil } return err } err := re.parseAtoms() if err == io.EOF || err == ErrNoAtom { err = nil } return err}

Page 15: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 15 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Parsing

// ATOM ::= rune STAR | '(' RE ')' STARfunc (re *Rexp) parseAtom() error { r, err := re.l.Peek() if err != nil { return err } if r == Lpar { re.trz("paren") defer re.untrz() re.l.Scan() if err := re.parseRe(); err != nil { return err } _, _, found := re.match(Rpar) if !found { return ErrNoParen } } else if r & runeop != 0 && r != Any { return ErrNoAtom } else { re.trz(fmt.Sprintf("'%c'", r&^runeop)) defer re.untrz() re.l.Scan() } return re.parseStar()}

Page 16: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 16 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Parsing

// STAR ::= '*' | <empty>func (re *Rexp) parseStar() error { _, _, found := re.match(Star) if !found { return nil } re.trz("star") defer re.untrz() return nil}

Page 17: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 17 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Parsing

Y ahora podemos ver el árbol sintáctico...

func main() { txt := `ab|(c*\*\))\` fmt.Printf("parsing '%s'\n", txt) re := NewRexp(txt) re.Debug = true err := re.Parse() fmt.Printf("sts %v\n", err)} Run

Page 18: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 18 of 58http://127.0.0.1:3999/s05.regexp.slide#1

¿Ahora qué?

Lo que queremos ahora es construir el AFND que corresponde a la expresión regular.

El autómata lo interpretaremos luego para hacer matching

Hay que tener fresco cuál es el NFA para una expresión regular

Page 19: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 19 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Autómatas para expresiones regulares

NFA para

x

Page 20: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 20 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Autómatas para expresiones regulares

NFA para

re1 re2

Page 21: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 21 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Autómatas para expresiones regulares

NFA para

re1 | re2

Page 22: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 22 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Autómatas para expresiones regulares

NFA para

re1 *

Page 23: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 23 of 58http://127.0.0.1:3999/s05.regexp.slide#1

NFA

type NFA struct { op rune // operator at this state last *NFA // last state in this NFA on []rune // runes we transition on to []*NFA // states we transition to

id int // debug}

var nfanodes []*NFA

func NewNFA(op rune) *NFA { n := &NFA{op: op, id: len(nfanodes)} nfanodes = append(nfanodes, n) return n}

El NFA representa un estado y guarda las transiciones Todos los estados los guardaremos en un array para luego

Page 24: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 24 of 58http://127.0.0.1:3999/s05.regexp.slide#1

NFA

Vamos a necesitar añadir una transición a un estado

func (n *NFA) trans(on rune, to *NFA) { n.on = append(n.on, on) n.to = append(n.to, to)}

Page 25: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 25 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

// ATOM ::= rune STAR | '(' RE ')' STARfunc (re *Rexp) parseAtom() (*NFA, error) { r, err := re.l.Peek() if err != nil { return nil, err } var nfa, end *NFA if r == Lpar { re.trz("paren") defer re.untrz() re.l.Scan() nfa, err = re.parseRe() if err != nil { return nil, err } _, _, found := re.match(Rpar) if !found { return nil, ErrNoParen } end = nfa.last } else if r & runeop != 0 && r != Any {

Para (exp) usamos el NFA de exp

Page 26: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 26 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

} else if r & runeop != 0 && r != Any { return nil, ErrNoAtom } else { re.trz(fmt.Sprintf("'%c'", r&^runeop)) defer re.untrz() re.l.Scan() end = NewNFA(End) nfa = NewNFA(r) nfa.last = end nfa.trans(r, end) } // ...

Para a construimos un NFA que acepte a.

Page 27: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 27 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

// ...

closed, err := re.parseStar() if err != nil { return nil, err } if closed { nfa.trans(0, end) end.trans(0, nfa) } return nfa, nil}

Si hay cierre, añadimos las transiciones

Page 28: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 28 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

// STAR ::= '*' | <empty>func (re *Rexp) parseStar() (bool, error) { _, _, found := re.match(Star) if !found { return false, nil } re.trz("star") defer re.untrz() return true, nil}

Page 29: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 29 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

// ATOMS ::= ATOM ATOMS | <empty>func (re *Rexp) parseAtoms() (*NFA, error) { re.trz("atoms") defer re.untrz() nfa1, err := re.parseAtom() if err != nil { if err == io.EOF || err == ErrNoAtom { err = nil } return nfa1, err } nfa2, err := re.parseAtoms() if err == io.EOF || err == ErrNoAtom { return nfa1, err } if err != nil { return nil, err }

nfa1 = cat(nfa1, nfa2) return nfa1, err}

Page 30: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 30 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

Donde cat es como sigue:

func cat(nfa1, nfa2 *NFA) *NFA { if nfa1 == nil { return nfa2 } if nfa2 == nil { return nfa1 } nfa1.last.trans(0, nfa2) nfa1.last.op = 0 nfa1.last = nfa2.last return nfa1}

Page 31: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 31 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

// TERM ::= ATOM ATOMSfunc (re *Rexp) parseTerm() (*NFA, error) { re.trz("term") defer re.untrz() nfa1, err := re.parseAtom() if err != nil { return nil, err } nfa2, err := re.parseAtoms() if err != nil { return nfa1, nil } nfa1 = cat(nfa1, nfa2) return nfa1, nil}

Page 32: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 32 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

// OPTS ::= '|' TERM OPTS | <empty>func (re *Rexp) parseOpts() (*NFA, error) { _, _, found := re.match(Or) if !found { return nil, nil } re.trz("opts") defer re.untrz() nfa1, err := re.parseTerm() if err != nil { return nil, err } nfa2, err := re.parseOpts() if err != nil { return nil, err } return alt(nfa1, nfa2), nil}

Page 33: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 33 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

Donde alt es como sigue:

func alt(nfa1, nfa2 *NFA) *NFA { if nfa1 == nil { return nfa2 } if nfa2 == nil { return nfa1 } nfa := NewNFA(Or) nfa.trans(0, nfa1) nfa.trans(0, nfa2) end := NewNFA(End) nfa1 = cat(nfa1, end) nfa2 = cat(nfa2, end) nfa.last = end return nfa}

Page 34: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 34 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

func (re *Rexp) Parse() (*NFA, error) { re.l.Debug = re.Debuglex return re.parseRe()}

// RE ::= TERM OPTSfunc (re *Rexp) parseRe() (*NFA, error) { re.trz("re") defer re.untrz() nfa1, err := re.parseTerm() if err != nil { return nil, err } nfa2, err := re.parseOpts() if err != nil { return nil, err } return alt(nfa1, nfa2), nil}

Page 35: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 35 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación del NFA

Podríamos probarlo ya, pero...

para depurar mejor poder ver el NFA

para verlo, vamos a generar el código para el NFA

Page 36: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 36 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación de código del NFA (imprimible)

Como hemos almacenado todos los nodos, basta con

imprimir cada uno

indicando cuál es el estado inicial

func (n *NFA) prog() string { str := fmt.Sprintf("nfa start %d\n", n.id) for i := 0; i < len(nfanodes); i++ { nfa := nfanodes[i] str += nfa.String() } return str}

Page 37: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 37 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación de código del NFA (imprimible)

Para cada nodo (estado) generamos una instrucción:

01: 'a' a:0

que quiere decir

esta es la instrucción 1 del NFA

el nombre de la operación es a (aceptar a)

hay una transición desde a a la instrucción 0

el nombre de la operación no se utilizará, pero ayuda, sólo las transiciones son importantes

Page 38: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 38 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación de código del NFA (imprimible)

func (n *NFA) String() string { if n == nil { return "<nil nfa>" } s := fmt.Sprintf("%02d:", n.id) switch { case n.op == End: s += "\tend" case n.op & runeop != 0: x := n.op & ^runeop s += fmt.Sprintf("\t<%c>", x) case n.op == 0: s += "\tnop" default: s += fmt.Sprintf("\t'%c'", n.op) } for i := 0; i < len(n.on); i++ { on := n.on[i] if on == 0 { s += fmt.Sprintf("\t_:%d", n.to[i].id) } else { s += fmt.Sprintf("\t%c:%d", n.on[i]&^runeop, n.to[i].id) } } return s+"\n"}

Page 39: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 39 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Generación de código del NFA (imprimible)

Y ya lo podemos ver:

func main() { txt := `ab|c` fmt.Printf("compiling '%s'\n", txt) re := NewRexp(txt) nfa, err := re.Parse() if err != nil { fmt.Printf("sts %v\n", err) } if nfa != nil { fmt.Printf("%s\n", nfa.prog()) }} Run

Page 40: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 40 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Optimización de código

Ahora puede comprenderse qué es eso de optimizar el código.

Para la salida:

compiling 'ab|c'nfa start 600: nop _:301: 'a' a:002: nop _:703: 'b' b:204: nop _:705: 'c' c:406: <|> _:1 _:507: end

La instrucción b transita a la 2 que siempre salta a la 7 Mejor sería transitar directamente a la 7

Page 41: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 41 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Eliminación de saltos

Podemos

recorrernos el NFA antes de generar su código

para cada transición que siempre vuelve a saltar

transitar directamente al destino final

Page 42: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 42 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Eliminación de saltos

func jmpOpt() { for _, nfa := range nfanodes { for i := 0; i < len(nfa.on); i++ { to := nfa.to[i] for len(to.on) == 1 && to.on[0] == 0 { if debugOpt { fmt.Printf("opt %s", nfa) } to = to.to[0] nfa.to[i] = to if debugOpt { fmt.Printf("\tto %s", nfa) } } } }}

Page 43: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 43 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Eliminación de saltos

var debugOpt bool

func main() { re := NewRexp(`ab|c`) nfa, err := re.Parse() if err != nil { fmt.Printf("sts %v\n", err) } else { debugOpt = true jmpOpt() fmt.Printf("%s\n", nfa.prog()) }} Run

Page 44: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 44 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Eliminación de código muerto

Otra posible optimización, a la vista de

nfa start 600: nop _:301: 'a' a:302: nop _:703: 'b' b:704: nop _:705: 'c' c:706: <|> _:1 _:507: end

es eliminar todas las instrucciones que no se utilizan. (otro ej, eliminar las funciones no llamadas y no exportadas)

Page 45: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 45 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Eliminación de código muerto

Podríamos recorrer el NFA y copiarlo en otro sólo con los estados que visitamos

Similar a un GC de marcado y barrido

Page 46: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 46 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Eliminación de código muerto

func (entry *NFA) deadOpt() { visited := map[*NFA] bool{} pending := []*NFA{entry}

for len(pending) > 0 { nfa := pending[0] nfa.alive = true pending = pending[1:] for i := 0; i < len(nfa.on); i++ { to := nfa.to[i] if !visited[to] { visited[to] = true pending = append(pending, to) } } } for i, n := 0, 0; i < len(nfanodes); i++ { if nfanodes[i].alive { nfanodes[i].id = n n++ } }}

Page 47: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 47 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Eliminación de código muerto

Al generar el programa ignoramos el código muerto:

func (n *NFA) gen() string { str := fmt.Sprintf("nfa start %d\n", n.id) for i := 0; i < len(nfanodes); i++ { nfa := nfanodes[i] if nfa.alive { str += nfa.String() } } return str}

Page 48: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 48 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Eliminación de código muerto

Y ya lo tenemos:

func main() { re := NewRexp(`ab|c`) nfa, err := re.Parse() if err != nil { fmt.Printf("sts %v\n", err) } else { debugOpt = true jmpOpt() if debugOpt { fmt.Printf("before:\n%s\n", nfa.prog()) } nfa.deadOpt() fmt.Printf("%s\n", nfa.gen()) }} Run

Page 49: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 49 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Notas...

Normalmente se generaría un formato mas compacto (binario) a no ser que estemos traduciendo un lenguaje en otro

Para eliminar código muerto basta

marcar y

recorrer el marcado

Nosotros lo hemos hecho otra vez más para renumerar, pero esto no es preciso.

Page 50: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 50 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Un intérprete para expresiones regulares

Ahora que tenemos las expresiones compiladas podemos escribir un intérprete.

básicamente un bucle con un switch

ejecutando las instrucciones del NFA

Page 51: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 51 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Un intérprete para expresiones regulares

Mantendremos dos pilas en el run-time:

una para estados del NFA activos

otra para aquellos a los que transitamos

Recorremos los estados activos y construimos el conjunto de estados a que podemos transitar

Para hacer la transición, cambiamos ambas pilas

Page 52: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 52 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Un intérprete para expresiones regulares

En entorno de ejecución tendrá estos elementos

type Runtime struct { txt []rune // text left to match now, next []*NFA // current states, next states Debug bool}

var DebugRt bool

Page 53: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 53 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Un intérprete para expresiones regulares

Y la ejecución es como sigue

func (n *NFA) Exec(s string) bool { rt := Runtime{txt: []rune(s)} rt.now = addState(rt.now, n) return rt.Exec()}

func addState(l []*NFA, n *NFA) []*NFA { for i := 0; i < len(l); i++ { if l[i] == n { return l } } return append(l, n)}

Partimos con el estado inicial y ejecutamos el intérprete

Page 54: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 54 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Un intérprete para expresiones regulares

func (rt *Runtime) Exec() bool { for ; len(rt.txt) > 0 ; rt.txt = rt.txt[1:] { if DebugRt { fmt.Printf("%s\n", rt) } rt.transition() rt.now, rt.next = rt.next, nil if len(rt.now) == 0 { return false } } if DebugRt { fmt.Printf("%s\n", rt) } return rt.isMatch() return false}

Recorremos el texto ejecutando el NFA en cada runa

Page 55: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 55 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Un intérprete para expresiones regulares

func (rt *Runtime) transition() { for ni := 0; ni < len(rt.now); ni++ { nfa := rt.now[ni] for i := 0; i < len(nfa.on); i++ { switch nfa.on[i] { case 0: rt.now = addState(rt.now, nfa.to[i]) case Any, rt.txt[0]: rt.next = addState(rt.next, nfa.to[i]) } } }}

Computamos los siguientes estados en cada paso

Page 56: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 56 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Un intérprete para expresiones regulares

Y falta...

func (rt *Runtime) isMatch() bool { for ni := 0; ni < len(rt.now); ni++ { nfa := rt.now[ni] if nfa.op == End { return true } for i := 0; i < len(nfa.on); i++ { if nfa.on[i] == 0 { rt.now = addState(rt.now, nfa.to[i]) } } } return false}

Page 57: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 57 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Un intérprete para expresiones regulares

Listo:

func main() { re := NewRexp(`ab|ac*.d`) str := "acccd" nfa, err := re.Parse() if err != nil { fmt.Printf("sts %v\n", err) return } jmpOpt() nfa.deadOpt() fmt.Printf("%s\n", nfa.gen()) DebugRt = true if nfa.Exec(str) { fmt.Printf("match\n") } else { fmt.Printf("no match\n") }} Run

Page 58: Compiladores: compilación de expresiones regulares …lsub.org/comp/slides/s05.regexp.pdf · Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Page

1/25/16, 2:48 PMCompiladores: compilación de expresiones regulares - (c)2014 LSUB

Page 58 of 58http://127.0.0.1:3999/s05.regexp.slide#1

Questions?

Francisco J BallesterosLSUB, URJChttp://lsub.org (http://lsub.org)