Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Compiladores: compilación de expresiones regulares Francisco J Ballesteros LSUB, URJC http://127.0.0.1:3999/s05.regexp.slide#1 Page 1 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 2 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 3 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 4 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Gramática Podríamos utilizar esta gramática: RE ::= TERM OPTS OPTS ::= '|' TERM OPTS | <empty> TERM ::= ATOM ATOMS ATOMS ::= ATOM ATOMS | <empty> ATOM ::= rune STAR | '(' RE ')' STAR ::= '*' | <empty> STAR Vamos a hacer que el scanner se ocupe de los escapes de runas especiales La gramática fuerza a precedencia de los operadores http://127.0.0.1:3999/s05.regexp.slide#1 Page 5 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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)} } http://127.0.0.1:3999/s05.regexp.slide#1 Page 6 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Lex Y marcamos las runas especiales const ( runeop rune = 0x40000000 runeops = "|*.()" Or = runeop | '|' Star = runeop | '*' Lpar = runeop | '(' Rpar = runeop | ')' ) http://127.0.0.1:3999/s05.regexp.slide#1 Page 7 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 8 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 9 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } } } http://127.0.0.1:3999/s05.regexp.slide#1 Run Page 10 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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)} } http://127.0.0.1:3999/s05.regexp.slide#1 Page 11 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Parsing func (re *Rexp) Parse() error { re.l.Debug = re.Debuglex return re.parseRe() } // RE ::= TERM OPTS func (re *Rexp) parseRe() error { re.trz("re") defer re.untrz() if err := re.parseTerm(); err != nil { return err } return re.parseOpts() } http://127.0.0.1:3999/s05.regexp.slide#1 Page 12 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 ATOMS func (re *Rexp) parseTerm() error { re.trz("term") defer re.untrz() if err := re.parseAtom(); err != nil { return err } return re.parseAtoms() } http://127.0.0.1:3999/s05.regexp.slide#1 Page 13 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 14 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Parsing // ATOM ::= rune STAR | '(' RE ')' STAR func (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() } http://127.0.0.1:3999/s05.regexp.slide#1 Page 15 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Parsing // STAR ::= '*' | <empty> func (re *Rexp) parseStar() error { _, _, found := re.match(Star) if !found { return nil } re.trz("star") defer re.untrz() return nil } http://127.0.0.1:3999/s05.regexp.slide#1 Page 16 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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) } http://127.0.0.1:3999/s05.regexp.slide#1 Run Page 17 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM ¿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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 18 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Autómatas para expresiones regulares NFA para x http://127.0.0.1:3999/s05.regexp.slide#1 Page 19 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Autómatas para expresiones regulares NFA para re1 re2 http://127.0.0.1:3999/s05.regexp.slide#1 Page 20 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Autómatas para expresiones regulares NFA para re1 | re2 http://127.0.0.1:3999/s05.regexp.slide#1 Page 21 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Autómatas para expresiones regulares NFA para re1 * http://127.0.0.1:3999/s05.regexp.slide#1 Page 22 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM NFA type NFA struct { op rune // last *NFA // on []rune // to []*NFA // id int operator at this state last state in this NFA runes we transition on states we transition to // 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 23 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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) } http://127.0.0.1:3999/s05.regexp.slide#1 Page 24 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Generación del NFA // ATOM ::= rune STAR | '(' RE ')' STAR func (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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 25 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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. http://127.0.0.1:3999/s05.regexp.slide#1 Page 26 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 27 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 28 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 29 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 30 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Generación del NFA // TERM ::= ATOM ATOMS func (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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 31 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 32 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 33 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Generación del NFA func (re *Rexp) Parse() (*NFA, error) { re.l.Debug = re.Debuglex return re.parseRe() } // RE ::= TERM OPTS func (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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 34 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 35 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 36 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 37 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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" } http://127.0.0.1:3999/s05.regexp.slide#1 Page 38 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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()) } } http://127.0.0.1:3999/s05.regexp.slide#1 Run Page 39 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Optimización de código Ahora puede comprenderse qué es eso de optimizar el código. Para la salida: compiling 'ab|c' nfa start 6 00: nop _:3 01: 'a' a:0 02: nop _:7 03: 'b' b:2 04: nop _:7 05: 'c' c:4 06: <|> _:1 07: end _:5 La instrucción b transita a la 2 que siempre salta a la 7 Mejor sería transitar directamente a la 7 http://127.0.0.1:3999/s05.regexp.slide#1 Page 40 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 41 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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) } } } } } http://127.0.0.1:3999/s05.regexp.slide#1 Page 42 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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()) } } http://127.0.0.1:3999/s05.regexp.slide#1 Run Page 43 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Eliminación de código muerto Otra posible optimización, a la vista de nfa start 6 00: nop 01: 'a' 02: nop 03: 'b' 04: nop 05: 'c' 06: <|> 07: end _:3 a:3 _:7 b:7 _:7 c:7 _:1 _:5 es eliminar todas las instrucciones que no se utilizan. (otro ej, eliminar las funciones no llamadas y no exportadas) http://127.0.0.1:3999/s05.regexp.slide#1 Page 44 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 45 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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++ } } } http://127.0.0.1:3999/s05.regexp.slide#1 Page 46 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 47 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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()) } } http://127.0.0.1:3999/s05.regexp.slide#1 Run Page 48 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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. http://127.0.0.1:3999/s05.regexp.slide#1 Page 49 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 50 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 51 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Un intérprete para expresiones regulares En entorno de ejecución tendrá estos elementos type Runtime struct { txt []rune now, next []*NFA Debug bool } // text left to match // current states, next states var DebugRt bool http://127.0.0.1:3999/s05.regexp.slide#1 Page 52 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 53 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 54 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 http://127.0.0.1:3999/s05.regexp.slide#1 Page 55 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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 } http://127.0.0.1:3999/s05.regexp.slide#1 Page 56 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM 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") } } http://127.0.0.1:3999/s05.regexp.slide#1 Run Page 57 of 58 Compiladores: compilación de expresiones regulares - (c)2014 LSUB 1/25/16, 2:48 PM Questions? Francisco J Ballesteros LSUB, URJC http://lsub.org (http://lsub.org) http://127.0.0.1:3999/s05.regexp.slide#1 Page 58 of 58