Analizador Léxico

Anuncio
UAA – Sistemas Electrónicos
2
Compiladores
Eduardo Serna-Pérez
Análisis léxico (Scanner)
La fase de rastreo (scanner), tiene las funciones de leer el programa fuente como un archivo de
caracteres y dividirlo en tokens. Los tokens son las palabras reservadas de un lenguaje,
secuencia de caracteres que representa una unidad de información en el programa fuente. En
cada caso un token representa un cierto patrón de caracteres que el analizador léxico
reconoce, o ajusta desde el inicio de los caracteres de entrada. De tal manera es necesario
generar un mecanismo computacional que nos permita identificar el patrón de transición entre
los caracteres de entrada, generando tokens, que posteriormente serán clasificados. Este
mecanismo es posible crearlo a partir de un tipo especifico de maquina de estados llamado
autómata finito.
2.1
Función del analizador léxico
Es la primera fase de un compilador. Su principal función consiste en leer la secuencia de
caracteres del programa fuente, carácter a carácter, y elaborar como salida la secuencia de
componentes léxicos que utiliza el analizador sintáctico. El analizador sintáctico emite la orden
al analizador léxico para que agrupe los caracteres y forme unidades con significado propio
llamados componentes léxicos (tokens). Los componentes léxicos representan:
o
o
o
o
o
o
Palabras reservadas: if, while, do, …
Identificadores: variables, funciones, tipos definidos por el usuario, etiquetas, …
Operadores: =, >, <, >=, <=, +, *, …
Símbolos especiales: ;, ( ), { }, …
Constantes numéricas. literales que representan valores enteros y flotantes.
Constantes de carácter: literales que representan cadenas de caracteres.
El analizador léxico opera bajo petición del analizador sintáctico devolviendo un componente
léxico conforme el analizador sintáctico lo va necesitando para avanzar en la gramática. Los
componentes léxicos son los símbolos terminales de la gramática. Suele implementarse como
una subrutina del analizador sintáctico. Cuando recibe la orden “obtén el siguiente componente
léxico”, el analizador léxico lee los caracteres de entrada hasta identificar el siguiente
componente léxico.
componente
léxico
programa
fuente
analizador
léxico
analizador
sintáctico
árbol de análisis
sintáctico
obtén siguiente
componente léxico
tabla de
símbolos
Además el analizador léxico es responsable de:
o
o
o
o
Manejo de apertura y cierre de archivo, lectura de caracteres y gestión de posibles
errores de apertura.
Eliminar comentarios, espacios en blanco, tabuladores y saltos de línea.
Inclusión de archivos y macros.
Contabilizar número de líneas y columnas para emitir mensajes de error.
Una de las ventajas de separar el análisis léxico y análisis sintáctico es que facilita la
transportabilidad del traductor si se decide realizar cambios posteriores, por ejemplo cambiar
las etiquetas begin-end por llaves de apertura y cierre { }.
1
UAA – Sistemas Electrónicos
2.2
Compiladores
Eduardo Serna-Pérez
Componentes léxicos, patrones y lexemas
En la fase de análisis, los términos componentes léxicos (token), patrón y lexema se emplean
con significados específicos. Un analizador léxico, inicialmente lee los lexemas y le asigna un
significado propio.
•
componente léxico es la secuencia lógica y coherente de caracteres relativo a una
categoría: identificador, palabra reservada, literales (cadena/numérica), operador o carácter
de puntuación, además de que un componente léxico puede tener uno o varios lexemas.
•
patrón es una regla que genera la secuencia de caracteres que puede representar a un
determinado componente léxico (expresión regular).
•
lexema es una cadena de caracteres que concuerda con un patrón que describe un
componente léxico (valor de cadena).
Ejemplo de una cadena de código: const pi = 3.1416;
Lexemas
const
=
pi
3.1416
“hola mundo”
Componente léxico
const
relación
identificador
número
literal
Patrón
const
< o <= o = o <> o > o >=
letra seguida de letras o números
cualquier literal numérica
caracteres entre comillas
El analizador léxico recoge información sobre los componentes léxicos en sus atributos
asociados. Los tokens influyen en las decisiones del análisis sintáctico, y los atributos, en la
traducción de los tokens. En la practica los componentes léxicos suelen tener solo un atributo.
Para efectos de diagnostico, puede considerarse tanto el lexema para un identificador como el
numero de línea en el que se encontró por primera vez. Esta información puede ser
almacenada en la tabla de símbolos para el identificador (estructura de datos).
Para la cadena E=M*C**2 de ejemplo, los componentes léxicos y los valores de atributo
asociado son:
<identificador, atributo para el símbolo E>
<op_asignacion>
<identificador, atributo para el símbolo M>
<op_multiplica>
<identificador, apuntador al símbolo C>
<op_exponente>
<numero, atributo valor 2>
Tome en cuenta que ciertas parejas no necesitan un valor de atributo. Los atributos
relacionados con ese token deberán ser conservados y transferidos a alguna estructura de
datos para que sean empleados en las siguientes etapas del análisis
2.3
Manejo de Buffer de entrada
Existen algunos aspectos de eficiencia relacionados con el manejo de buffer. Primero se
menciona un esquema de doble buffer de entrada que resulta útil como pre-análisis de la
entrada para identificar los componentes léxicos. El segundo esquema introduce algunas
técnicas útiles para aumentar la eficiencia del analizador léxico, empleando “centinelas”.
Hay veces en que el analizador léxico necesita analizar previamente varios caracteres, además
del lexema para un patrón, antes de poder anunciar una concordancia. Se pueden emplear
muchos esquemas de manejos de buffer, pero, aquí tan solo se señalarán algunos principios
básicos.
2
UAA – Sistemas Electrónicos
Compiladores
Eduardo Serna-Pérez
Parejas de buffer – Se utiliza un buffer dividido en dos mitades de N (1024 ó 512) caracteres
cada una, Se leen N caracteres de entrada en cada mitad del buffer con un orden de lectura del
sistema, en vez de invocar una instrucción de lectura para cada carácter de entrada. Si quedan
menos de N caracteres en la entrada, entonces se lee un carácter especial eof en el buffer
después de los caracteres de entrada. Es decir, eof marca el final del archivo fuente y es
distinto a cualquier carácter de la entrada.
E
=
M
*
C
*
*
↑
Inicio
lexema
2
eof
↑
frente
Se mantienen dos apuntadores en el buffer de entrada. La cadena de caracteres entre los dos
apuntadores es el lexema en curso. Al principio, los dos apuntadores apuntan al primer
carácter del próximo lexema que hay que encontrar. Uno de ellos, apuntador delantero,
examina hacia adelante hasta encontrar una concordancia con un patrón. Una vez determinado
el siguiente lexema, el apuntador delantero se coloca en el carácter de su extremo derecho.
Después de haber procesado el lexema, ambos apuntadores se colocan en el carácter situado
inmediatamente después del lexema. Con este esquema se pueden considerar los comentarios
y los espacios en blanco como patrones que no producen componentes léxicos.
Centinela – Corrigiendo la deficiencia del método anterior para reducir la necesidad de dos
pruebas para cada avance del apuntador delantero. Se amplia cada mitad del buffer para
admitir un carácter centinela al final. De esta manera se vuelve mas eficiente el proceso de
cargar del buffer, realizando una carga en cada mitad.
E
=
M
*
\0
C
*
*
↑
inicio
lexema
2
\0
↑
frente
eof
El manejo de buffer es una labor puramente de programación y se recomienda realizarla de la
manera que resulte mas sencilla, no es necesario apegarse a alguno de los dos métodos antes
mencionados.
2.4
Jerarquía de Chomsky
En el campo de las ciencias computacionales y específicamente en el área de los lenguajes
formales la jerarquía de Chomsky es una clase de gramática formal, que describe de manera
precisa un lenguaje. Las gramáticas formales o gramáticas suelen ser clasificadas en dos
grandes tipos: las gramáticas generativas, que en base a un conjunto de reglas nos indican
como formar cadenas del lenguaje. Y las gramáticas analíticas, que nos indican como esos
miembros del lenguaje pertenecen a un orden especifico.
Una gramática formal consiste de:
o
o
o
o
Un conjunto finito de símbolos terminales
Un conjunto finito de símbolos no terminales
Un conjunto finito de reglas de producción que consiste de una secuencia de éstos
símbolos
Un símbolo inicial
Un lenguaje formal o gramática formal es una secuencia de símbolos que podrían ser
construidos aplicando reglas de producción para esas secuencias de símbolos, y podrían en
casi todos los casos describir de manera precisa un lenguaje de programación.
3
UAA – Sistemas Electrónicos
Compiladores
Eduardo Serna-Pérez
La jerarquía de Chomsky consiste de los siguientes niveles de gramáticas:
o
Gramáticas sin restricciones (Tipo-0), son capaces de generar todos los lenguajes
reconocidos por una maquina de Turing.
o
Gramáticas de contexto sensitivo (Tipo-1), son lenguajes capaces de ser reconocidos por
una maquina de Turing no determinista.
o
Gramáticas libres de contexto (Tipo-2), estos lenguajes son exactamente todos aquellos
lenguajes que pueden ser reconocidos por un autómata de pila no-determinístico. Las
gramáticas libres de contexto son la base teórica para la construcción de la sintaxis en la
mayoría de los lenguajes de programación.
o
Gramáticas regulares (Tipo-3), son exactamente todos los lenguajes que pueden ser
reconocidos por un autómata de estado finito. Esta familia de lenguajes formales puede ser
obtenida a partir de una expresión regular. Las expresiones regulares son comúnmente
usadas para realizar la búsqueda de patrones en estructuras léxicas de lenguajes de
programación.
2.5
Especificaciones de componentes léxicos
Las expresiones regulares son una notación importante para especificar patrones. Cada patrón
concuerda con una serie de cadenas que, de modo que las expresiones regulares servirán
como nombres para conjuntos de cadenas. El término alfabeto denota cualquier conjunto finito
de símbolos, por ejemplo el alfabeto binario { 0, 1 }. Una cadena es una secuencia de finita de
símbolos de un alfabeto y un lenguaje se refiere a conjunto de cadenas de un alfabeto fijo.
2.5.1 Expresiones Regulares
El origen de las expresiones regulares surge de la teoría de autómatas y la teoría de lenguajes
formales, ambas parte de la ciencias computacionales teórica. Este campo estudia los modelos
computacionales (autómata) y la manera de describir y clasificar los lenguajes formales. Un
lenguaje formal puede ser especificado de varias maneras, tales como:
•
•
•
Cadenas producidas por alguna gramática formal.
Cadenas producidas por expresiones regulares.
Cadenas aceptadas por algunos autómatas tales como las maquinas de Turing o
autómatas de estado finito.
A las expresiones regulares frecuentemente se les llaman patrones, ya que son expresiones
que describen a un conjunto de cadenas. Frecuentemente son usadas para dar una
descripción concisa de un conjunto, sin tener que listar todos sus elementos.
Las expresiones regulares pueden ser expresadas en términos de la teoría de lenguajes
formales. Consisten de constantes y operadores que denotan el conjunto de cadenas y
operaciones sobre estos conjuntos, respectivamente. Dado un alfabeto Σ las siguientes
constantes son definidas:
o
o
o
Conjunto vacío: L( ∅ ) denota el conjunto { }
Cadena vacía: L( ε ) denota el conjunto { ε }
Carácter del alfabeto: L( a ), a elemento de Σ denota el conjunto { “a” }
Operaciones básicas en expresiones regulares
Selección de alternativas (unión) – Si r y s son expresiones regulares, entonces r | s es una
expresión regular que define cualquier cadena que concuerda con r o con s. En términos de
lenguajes decimos que r | s es la unión de los lenguajes de r y s, o L(r | s) = L(r) U L(s) = { αβ |
α esta en r o β esta en s }.
4
UAA – Sistemas Electrónicos
Compiladores
Eduardo Serna-Pérez
ejemplo:
o
o
o
L(a | b) = L(a ) U L(b) = { a, b }
L(a | ε ) = { a, ε }
L(a | b | c | d ) = { a, b, c, d }
Concatenación – La concatenación de dos expresiones regulares r y s se escribe como rs
(yuxtaposición) y corresponde a cualquier cadena que sea la concatenación de dos cadenas,
con la primera de ellas correspondiendo a r y la segunda a s. Por ejemplo la expresión regular
ab corresponde a la cadena { ab }, mientras que la expresión regular (a | b)c corresponde a las
cadenas { ac, bc }. De esta forma la operación de concatenación para expresiones regulares se
puede definir como L(rs) = L(r) L(s) = { αβ | α esta en r y β esta en s }.
ejemplo:
o
o
L(a | b) c = L(a | b) L(c) = { a, b } {c} = { ac, bc }
L(ab|c)(d|ef)=L(ab|c)L(d|ef)={ab,c}{d,ef}={abd, abef, cd, cef}
Repetición (cerradura de Kleene) – Se escribe r*, donde r corresponde a la expresión regular.
La expresión regular r* corresponde a cualquier concatenación finita de cadenas, cada una de
las cuales corresponde a r. Por ejemplo, la expresión regular a* corresponde a las cadenas { ε,
a, aa, aaa, … }, (concuerda con ε por que ε es la concatenación de ninguna cadena
concordante con a). En términos de lenguaje podemos decir que:
∞
S* = U S n
n =0
Donde Sn = S … S es la concatenación de S n veces (S0 = { ε }).
ejemplo:
o
o
o
L(a|b)* = {a, bb}* = {ε,a,bb,aa,abb,bba,bbbb,aaa,abba,abbbb,...}
L(a | b*) = {a, b*} = { ε, a, b, bb, bbb, ...}
L(ab|c)* = {ab,c}* = {ε, ab, c, abab, abc, cab, cc, ababab,...}
Para evitar los paréntesis se asume que la repetición tiene la precedencia mas alta, luego la
concatenación y al final la unión. Si no existiera ambigüedad los paréntesis pueden ser
omitidos . Por ejemplo , (ab)c se escribe como abc y a|(b(c*)) puede ser escrito como a|bc*.
De manera que los lenguajes regulares deben su nombre al hecho de que presentan
“regularidades” o repeticiones de los mismo componentes, como por ejemplo el lenguaje L1:
L1={ab, abab, ababab, abababab, ...}
Así pues, una expresión regular se construye a partir de expresiones regulares mas simples
utilizando un conjunto de reglas definitorias (operaciones básicas). Cada expresión regular r
representa un lenguaje L(r). Las reglas de definición especifican como se forma L(r)
combinando de varias maneras los lenguajes representados por las subexpresiones de r.
Se dice que un lenguaje designado por una expresión regular es un conjunto regular. Es
importante recordar que la especificación de una expresión regular es un ejemplo de definición
recursiva.
Definición regular: Es una forma de simplificación, dando un nombre a las expresiones
regulares y definiendo nuevas expresiones regulares utilizando dichos nombres como si fueran
símbolos. Por ejemplo, podríamos desarrollar una expresión regular para una secuencia de uno
o mas dígitos, generando inicialmente una definición regular para un digito.
digito = 0 | 1 | 2 | … | 9 (definición regular)
digito digito* (expresión regular para números enteros sin signo)
5
UAA – Sistemas Electrónicos
Compiladores
Eduardo Serna-Pérez
Ahora estamos en posición de elaborar la una definición de expresiones regulares para la
identificación de los componentes de un lenguaje determinado. Los tokens de lenguajes de
programación tiende a caer dentro de varias categorías limitadas que son bastante
estandarizadas, como palabras reservadas, símbolos especiales, identificadores y literales
(numérica/cadena).
Por ejemplo los identificadores en la mayoría de los lenguajes de programación es el conjunto
de cadenas de letras y dígitos que empiezan con una letra. Para ello generamos las
definiciones regulares para letra y digito:
letra = a | b | c | … | z | A | B … | Z
digito = 0 | 1 | 2 | … | 9
identificador = letra ( letra | digito )*
Los números con signo son literales numéricas constituidas por el punto decimal después de
un digito y por una literal que represente la parte del exponente. Con esta información
desarrollamos las definiciones regulares y las expresiones regulares necesarias:
((+|–|ε)digito digito*) ((. digito)|ε) ((E(+|–|ε)digito)|ε)
entero = (+|–|ε) digito digito*
fracción = ( . digito ) | ε
exponente = ( E ( + | – | ε ) digito ) | ε
numero = digito
fracción
exponente
Esta definición establece que fracción es un punto decimal seguido de uno o mas dígitos, o
esta ausente. Un exponente, que es E seguido de un signo + ó - ó ausente, seguidos de uno o
mas dígitos, o la ausencia de exponente. Tome en cuanta que, como mínimo debe existir un
dígito después del punto. De manera que numero concuerda con 1 y con 1.0.
Abreviaturas en la notación
o
Uno o mas casos – el operador unitario postfijo + significa “uno o mas casos de”, de manera
+
que la expresión regular r que designa al lenguaje L(r), entonces r es una expresión que
+
+
designa al lenguaje L(r) . Así la expresión regular a representa al conjunto de todas las
cadenas de una o mas a. El operador + tiene la misma precedencia que la repetición, las
identidades algebraicas son r* = r | ε y r + = r r*.
o
Cero o un caso – el operado unitario postfijo ? significa “cero o un caso de”. La notación r?
es la abreviatura de r | ε. Si r es una expresión regular, entonces (r)? es una expresión
regular que designa el lenguaje L(r) U { ε }.
o
Clases de caracteres – una clase abreviada de caracteres como [a – z] designa la
expresión regular a | b | c | … | z.
A menudo en la descripción de los tokens de lenguaje de programación utilizamos expresiones
regulares, algunas cadenas se pueden definir mediante varias expresiones regulares
diferentes, Por ejemplo, cadenas tales como if y while podrían se identificadores o palabras
clave.
Una definición de lenguaje de programación debe establecer cual interpretación se observará,
y las expresiones regulares por si mismas no pueden hacer esto. En realizad, una definición de
lenguaje debe proporcionar reglas de de no ambigüedad que explicaran cual significado es el
conveniente para cada uno de tales casos.
Existen dos reglas básicas que suponen tales casos:
o
La primera establece que, cuando una cadena puede ser identificador o palabra clave, se
prefiere por lo general la interpretación como palabra clave.
6
UAA – Sistemas Electrónicos
o
Compiladores
Eduardo Serna-Pérez
La segunda establece que, cuando una cadena puede ser un token simple o una secuencia
de varios tokens, por lo común se prefiere la interpretación del token simple. Esta
preferencia se conoce a menudo como el principio de sub-cadena mas larga.
Una cuestión que surge en el principio de sub-cadena mas larga es lo referente a los
delimitadores de tokens, o caracteres que implican que una cadena mas larga en el punto
donde aparecen no pueden representar un token. Los caracteres que son parte no ambigua de
otros tokens son delimitadores. Por ejemplo xtemp=ytem, el signo de = sirve como delimitador
pues no forma parte de los identificadores. Los espacios en blanco, los retornos de línea y los
caracteres de tabulación generalmente se asumen como delimitadores de token.
De esta manera el reconocimiento de componentes léxicos podría realizarse partiendo del
conjunto de cadenas dadas por expresiones regulares. Por ejemplo:
if = if
else = else
oprelacion = < | <= | = | <> | > | >=
identificador = letra ( letra | digito )*
entero = ( + | – )? digito+
real = ( + | – )? digito+ ( . digito+ )? ( E ( + | – )? digito+ )?
delimitador = blanco | tab | nuevalínea
Para este fragmento de lenguaje, el analizador léxico reconocerá las palabras clave if, else, al
igual que los lexemas representados por opRelacion, identificador, numero y delimitador.
Aún cuando las expresiones regulares son un mecanismo poderoso de definición de lenguajes,
es necesario construir una herramienta computacional que nos permita su implementación,
para ello se emplean los autómatas de estado finito. Es decir que, las expresiones regulares
deberán ser transformadas a algún tipo de maquina de estados fácil de programar.
2.6
Reconocimiento de componentes léxico
Un reconocedor de lenguajes es un programa que toma como entrada una cadena x e indica si
dicha cadena pertenece a una frase del programa. Se compila una expresión regular en un
reconocedor construyendo un diagrama de transiciones generalizado llamado autómata finito.
Un autómata finito puede ser determinista o no determinista, dependiendo del número de
transiciones para un mismo símbolo de entrada. Tanto los autómatas finitos deterministas
como los no deterministas pueden reconocer con precisión a los conjuntos regulares. Por tanto,
ambos pueden reconocer con precisión lo que denotan las expresiones regulares.
2.6.1 Autómatas finitos
Una maquina de estado finito o autómata finito, es un modelo computacional que consiste de
un conjunto de estados, un estado de inicio, un alfabeto de entrada y una función de transición
que traza un mapa a un siguiente estado, a partir del símbolo de entrada y el estado actual.
En particular, los autómatas finitos se pueden utilizar para describir el proceso de
reconocimiento de patrones en cadenas de entrada. El sistema recibe una cadena constituida
por símbolos de un alfabeto y determina si esa cadena pertenece al lenguaje que ese autómata
reconoce. De esta manera se pueden construir analizadores léxicos, construyendo programas
de computadora que realicen las operaciones de un autómata.
Es claro que existe una fuerte relación entre los autómatas y las expresiones regulares, y
veremos mas adelante como construir un autómata finito a partir de una expresión regular.
2.6.2
Autómata finito no determinístico
7
UAA – Sistemas Electrónicos
Compiladores
Eduardo Serna-Pérez
2.6.3 Autómata finito determinístico
Un autómata finito determinista (DFA por sus siglas en ingles), es un modelo donde el siguiente
estado de la transición esta dado particularmente por el estado actual y el carácter de entrada
actual. Si ningún estado de transición es especificado, la cadena entrante es rechazada.
La siguiente definición formalmente introduce un DFA, ( S, Σ, T, s, A ) donde:
o
o
o
o
o
S conjunto finito no vacío de elementos llamado estados
Σ alfabeto de entrada
T es una función de transición de S × Σ en S
s ∈ S estado inicial
A ⊆ S conjunto no vacío de estados finales
El ADF se encuentra en un estado inicial y lee una entrada de cadena de caracteres de
izquierda a derecha. La función de transición T define los estados de transición y es denotada
por T ( si ,c ) = si+1, donde si y si+1 son estados de S, y c es un carácter del alfabeto de entrada.
La función de transición indica que cuando un autómata esta en un estado si y recibe el
siguiente símbolo de entrada c, el autómata deberá cambiar al estado si+1. El carácter de
entrada no causara transición en caso de ser un carácter vació.
El siguiente ejemplo nos muestra un autómata que reconoce cadenas de lenguaje para una
expresión regular ( a | b )* abb. El conjunto de estados del DFA es { 0, 1, 2, 3 } y el alfabeto de
símbolos de entrada es { a, b }. Se considera 0 como el estado de inicio, y 3 el estado de
aceptación que esta indicado mediante un doble circulo. La siguiente función define dicho
autómata:
F = ({ 0, 1, 2, 3 }, { a, b }, T, 0, { 3 } )
Donde T es la tabla de transición que refleja el patrón de la expresión regular que estamos
evaluando T ( si ,c ) = si+1 definida como:
Símbolos de entrada
a
b
1
0
1
2
1
3
1
0
Estado
0
1
2
3
La tabla de transición también puede ser representada mediante un grafo de transición, donde
cada nodo representa los posibles estados y las líneas reflejan el flujo de la transición que
registran un cambio de un estado a otro en una coincidencia de carácter el estado 0 es
considerado el inicio de la transición y el estado 3 (doble circulo) representa el fin de la
transición o estado de aceptación:
a
a
a
0
b
b
b
1
2
a
3
b
Justamente el autómata que se acaba de diseñar es capas de aceptar cadenas de entrada que
pertenezcan a la expresión regular dada anteriormente, es decir, { abb, aabb, babb, ababb,
baabb, aaabb, … }. Cualquier cadena de entrada que no corresponda con dicho patrón será
considerada como ajena al lenguaje, es decir, terminar en cualquier punto que no sea 3 (fin de
la transición) es considerado como un posible error.
Igualmente es posible diseñar un algoritmo que realice exactamente las mismas operaciones
que el autómata dado anteriormente para la expresión regular ( a | b )* abb. Así pues,
8
UAA – Sistemas Electrónicos
Compiladores
Eduardo Serna-Pérez
consideremos que la función de transición (siguiente estado) desde un estado s en un carácter
de entrada c, esta dada por una operación de selección de estado (case en s). Además
consideraremos la existencia de una función sigtecar() que devuelve el siguiente carácter de la
cadena de entrada, obteniendo el siguiente algoritmo:
s = 0;
c = sigtecar( cadena );
while ( c != ‘\0’ ) {
case ( s ) {
0: s = ( c == ‘b’ )? 0 :
1: s = ( c == ‘b’ )? 2 :
2: s = ( c == ‘b’ )? 3 :
3: s = ( c == ‘b’ )? 0 :
}
c = sigtecar( cadena )
}
if ( s == 3 ) “cadena valida”
1
1
1
1
Dicho algoritmo es capas de evaluar una entrada de cadena y determinar si la misma forma
parte del patrón especificado por la expresión regular dada. Si el algoritmo llega a un fin de
cadena con un estado de aceptación 3, quiere decir que dicha cadena es valida o concuerda
con el patrón.
Ahora estamos en posición de poder construir autómatas mas elaborados partiendo de las
expresiones regulares ya definidas, por ejemplo, pensemos en la definición regular para un
identificador que esta compuesta por la siguiente expresión regular letra ( letra | digito )*, para
este caso consideráremos el conjunto de estados como 2 { 0, 1 }, el alfabeto de entrada estará
compuesto de las definiciones regulares letra y digito. El 0 es considerado como el inicio y 1
como el fin de la transición.
De esta manera generamos el siguiente diagrama de transición para un identificador:
letra
letra
0
1
digito
Bibliografía
Aho, A.V., Sethi, R., Ullman, J.D. (1990), Compiladores: principios, técnicas y herramientas,
capitulo 1, páginas: 1- 25, 743-747.
Louden, K.C. (1997), Construcción de Compiladores: Principios y práctica, capitulo 1, páginas:
1- 27.
http://en.wikipedia.org/wiki/Compiler
9
Descargar