transparencias

Anuncio
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
Descargar