You are on page 1of 11

Grammar.

NET
CONCEPTOS BÁSICOS, DEFINICION DE ALFABETO Y AUTOMATAS

Isaac Robles García [BeethIsaRoGa]


HACK X CRACK
Introducción
Permítanme presentarme, soy “BeethIsaRoGa”, y soy aún estudiante de la ingeniería en Sistemas
computacionales. En este documento trataré de explicar cómo pueden modificar el software del
que soy autor intelectual, que es “Grammar.NET”, aún está en desarrollo, pero ya está preparado
para para que podamos agregar nuestros autómatas y de esta manera reconocer los tokens que
corresponden a cierta expresión regular. He tratado de desarrollar esta aplicación de forma que
pueda ser adaptada a cualquier lenguaje desde el código, es decir modificando o agregando lo que
nos sea necesario. Sé que tal vez existan mejores formas para hacerlo, pero este es mi pequeño
granito de arena con el que quiero contribuir a la comunidad de HxC.
A lo largo de este documento trataremos conceptos o temas relacionados con los lenguajes,
gramáticas y autómatas finitos, no espero que sean expertos en estos temas ya que ni yo lo soy,
pero trataré de explicarlo de una forma fácil, simple y clara sin tanto rollo como normalmente seria
en una clase de escuela.
Espero que mi software y este documento les sean de ayuda para desarrollar sus propios
proyectos o por lo menos para que comprendan mejor el funcionamiento de los autómatas finitos
y lo que es un analizador léxico. Bueno para no hacerlo muy largo y entrar de lleno a lo que nos toca
pongámonos manos a la obra.
Requerimientos
Dada la forma en que programé el software existen algunos conceptos básicos que son
indispensables que conozcan para que no se pierdan y comprendan a la perfección el
funcionamiento de Grammar.NET, y se los listo en seguida para que repasen sus apuntes o consulten
en internet.

1. Concepto de expresión regular


2. Concepto de autómatas finitos (Determinísticos y no determinísticos)
3. Tipos de gramática
4. ¿Qué es un lenguaje?
5. Conocimiento del lenguaje C#
6. Manejo de IDE Visual Studio (De preferencia una que permita el manejo del diagrama de
clases)
7. Conocimiento de la programación orientada a objetos.
8. Manejo y uso de funciones y propiedades en C#
9. Manejo de enumerados en C#
10. Manejo de clases abstractas en C# (Herencia y polimorfismo)
11. Manejo del método ToString()
Conceptos Básicos
Expresión Regular: Representa un patrón o una secuencia de símbolos tomados de un alfabeto con
un orden en específico.
Lenguaje: Conjunto de todas cadenas formadas a partir de un alfabeto.
Alfabeto: Es el subconjunto de símbolos tomado a partir del conjunto de símbolos formado por
todos y cada uno de los símbolos que pertenecen a un lenguaje, es finito y no es vacío.
Cadena: Es cualquier secuencia finita de símbolos construida con símbolos de un alfabeto.
Longitud de cadena: Es el número de símbolos concatenados que forman una cadena
Cadena vacía: Representa una cadena de longitud cero y se representa con el símbolo ε (Épsilon).
Autómata Finito: Es un reconocedor de cadenas que forman parte de un lenguaje, las cuales son
especificadas por una expresión regular.
Autómatas Finitos
Existen dos tipos de autómatas finitos, determinísticos (AFD) y no determinísticos (AFND), los
autómatas finitos se representan mediante un grafo que tendría la siguiente estructura:

Inicio X
S F

S: Es el estado inicial de nuestro autómata, por lo general es un 0 o un 1.


F: Es el estado final o de aceptación, es decir, el estado al que llegará nuestro autómata si la cadena
evaluada fue aceptada o reconocida.
X: Es el símbolo que pertenece al alfabeto de la expresión regular con el que el autómata cambia de
estado.

Características de un Autómata Finito Determinístico


 Tiene un nodo o estado inicial y al menos un nodo o estado final.
 Cada arco (flecha) debe estar etiquetado con un símbolo que pertenece al alfabeto.
 No puede haber arcos etiquetados con la cadena vacía (ε).
 No pueden existir arcos que tengan la misma etiqueta.

Características de un Autómata Finito No Determinístico


 Tiene un nodo o estado inicial y al menos un nodo o estado final.
 Cada arco debe estar etiquetado con un símbolo del alfabeto o con la cadena vacía (ε).

Componentes de un Autómata Finito


Un autómata finito se define formalmente como:

AF = (K, ∑, Δ, S, F)

K: Conjunto finito de estados.


∑: Conjunto finito de símbolos o alfabeto.
Δ: K x ∑ →K Es la función de transición con la que el autómata cambia de estado con un símbolo del
alfabeto
S: Es el estado inicial, y esté pertenece a K.
F: Es el conjunto de estados finales o de aceptación, estos pertenecen a K.
Funcionamiento de un Autómata
Bien, ahora ya sabemos que es un autómata, pero ¿Cómo funcionan?; pues eso es lo que veremos
a continuación. Un autómata dijimos que era un modelo matemático, es decir, que no es algo que
podamos ver o tocar, sino más bien es un concepto abstracto, algo intangible que no existe
físicamente que en su interior tiene “algo” que le permite reconocer si una cadena de entrada
pertenece a un cierto lenguaje. Ese “algo” es un algoritmo como el siguiente:

int n = 0
Este sería el algoritmo que usaremos para que
char c = getChar() nuestro autómata reconozca la cadena de
while(n <= lenght (inputString)) entrada, ahora, recordemos que F es “el
s = getState(s, c) conjunto de estados de aceptación”, es decir
if( s <> -1) que en nuestro programa no es un entero, sino
c = getChar()
un arreglo de enteros, así que la última parte la
else
break adaptaremos a que busque S en el arreglo F.
if ( f = s ) El comportamiento o funcionamiento de un
return true autómata está definido por la tabla de
else transiciones. Para explicar mejor esto
return false crearemos el autómata finito que reconozca
números con parte decimal.

Expresión Regular para Números con parte Decimal


Veamos la forma que tiene un número con parte decimal, tomemos por ejemplo el número
12345.6789; La estructura o patrón del token que representa un número con parte decimal seria la
siguiente:

 Uno o más dígitos. El número estados es S(n) = K + 1


 Un punto Donde K es el número de características del token, en este caso 3.
 Uno o más dígitos. S(n) = 3 + 1  El autómata tendrá 4 Estados

De acuerdo con esta estructura la expresión regular podría quedar de la siguiente manera:
DIGITO -> 0|1|2|3|4|5|6|7|8|9
Token -> DIGITO+ . DIGITO+

Bien, ya tenemos la expresión regular, ahora procederemos a construir el autómata que reconozca
números con parte decimal.

Construcción del Autómata Finito para Números con parte Decimal


Para mí en particular es mucho más fácil empezar con el diagrama que describe el funcionamiento
del autómata, y de ahí abstraer el resto de información, este sería el diagrama del autómata.
Abstracción de un Autómata
Primero que nada, les diré que los arcos los etiquetamos con “Digito” para no poner 10 arcos cada
uno etiquetado con los dígitos del 0 al 9, originalmente debería ser así, pero a mi gusto esta es una
forma más eficiente de “agrupar” símbolos. Pero bueno eso solo fue un tip que aprendí en la
escuela, para alguien podría ser incorrecto, y si, tal vez lo sea, pero para mí es más fácil
representarlos así, además de que pronto sabrán otra de las razones por la que lo hago de esta
manera, pero podemos hacer la tabla de las dos formas, es decir, con cada digito o con “Digito”, de
la siguiente manera:

Alfabeto S Digito .
S 0 1 2 3 4 5 6 7 8 9 . 0 1 -
0 1 1 1 1 1 1 1 1 1 1 - 1 1 -
1 1 1 1 1 1 1 1 1 1 1 2 2 3 2
2 3 3 3 3 3 3 3 3 3 3 - 3 3 -
3 3 3 3 3 3 3 3 3 3 3 -

Los guiones en la tabla quieren decir que con ese símbolo el autómata no cambiara de estado, es
decir que no hará nada. Esto podemos comprobarlo, el diagrama del autómata dice que estando en
el estado cero solo avanzará al estado 1 con un digito (0-9), es decir, que si la cadena de entrada
comenzara con un punto (.), el autómata no cambiaría de estado. Esto es porque el diagrama solo
muestra las condiciones para el cambio de estado del autómata. Podríamos “escribir” de otra forma
el autómata y sería algo como lo siguiente:

 Estado 0
o Estando en el estado 0 y leyendo con digito avanzamos al estado 1
 Estado 1
o Estando en el estado 1 y leyendo un digito continuamos en el estado 1
o Estando en el estado 1 y leyendo un punto avanzamos al estado 2
 Estado 2
o Estando en el estado 2 y leyendo un digito avanzamos al estado 3
 Estado 3
o Estando en el estado 3 y leyendo un digito continuamos en el estado 3

Como pueden ver, solo se describen las condiciones de estado actual y símbolo leído que nos
permiten avanzar de un estado S0 a S1. No necesariamente S0 y S1 deben ser estados diferentes, esto
dependerá de la expresión regular con la que construyamos el autómata.
Clase “AbstractChar”
Como sabemos uno de los componentes de un autómata es el alfabeto de entrada, pues ahora
definiremos el alfabeto, pero debemos tomar algo en cuenta, repasemos la definición de alfabeto.
Alfabeto: Es el subconjunto de símbolos tomado a partir del conjunto de símbolos formado
por todos y cada uno de los símbolos que pertenecen a un lenguaje, es finito y no es vacío.

Ahora imaginemos que escribimos


absolutamente todos y cada uno de los
símbolos que forman parte de un lenguaje, por
ejemplo, el español, inglés, francés, griego,
números, etc. Podríamos decir que este es lo
que conocemos en teoría de conjuntos
Universo, así que en este caso nuestro alfabeto
de entrada es el subconjunto formado por los
dígitos del 0 al 9 y el punto.
Entonces podemos afirmar que aunque
el alfabeto de entrada al ser un subconjunto del
universo, pueden existir infinidad de alfabetos y
cada autómata o expresión regular usará un alfabeto único. Así que definí una clase AbstractChar
que sirve para definir cada símbolo del alfabeto de entrada quedando de esta forma:
Grammar.NET nos provee la posibilidad de
abstract class AbstractChar
{ especificar el alfabeto de entrada símbolo a
protected char _char; símbolo. En este ejemplo del autómata que
reconoce números reales crearemos los
public AbstractChar(char Char) símbolos Digito y Punto que son el alfabeto de
{ entrada para nuestro autómata.
_char = Char;
}

public string Item


{
get { return _char.ToString(); }
}
}
Clase “AbstractAutomata”
Bien, ahora les explicaré como fue que programé la clase AbstractAutomata, como el nombre lo
dice es una clase abstracta, ¿Por qué?, pues por que podemos tener más de un autómata, y dado
que todos tienen un funcionamiento similar aprovecharemos la herencia y polimorfismo para crear
todos los autómatas que necesitemos heredando de esta clase. Primero veamos de qué se compone
un autómata.
 Estado inicial (s).
 Conjunto de estados finales (F).
 Tabla de transición.
 Algoritmo de reconocimiento (Análisis de la cadena de entrada).
Bien ahora, empecemos a escribir nuestra clase AbstractAutomata.

abstract class AbstractAutomata


{
protected int s;
protected int[] F;
protected int[,] arrTabla;

public AbstractAutomata(int[] f, int[,] stateTable)


{
s = 0;
F = f;
arrTabla = stateTable;
}

public abstract bool Recognize(List<AbstractChar> statement);


protected abstract int GetState(int s, AbstractChar c);

protected bool RecognizeAlgorithm(List<AbstractChar> statement)


{
int n = 0;
AbstractChar c = statement[n++];
while (n <= statement.Count)
{
s = GetState(s, c);
if (s != -1 && n < statement.Count)
c = statement[n++];
else
break;
}
for (int i = 0; i < F.Length; i++)
if (F[i] == s)
{
s = 0;
return true;
}
s = 0;
return false;
}
}
Descripción
 Atributos
o S: Es el estado inicial (por defecto 0)
o F: Es el arreglo (conjunto) de estados finales.
o arrTabla: Es el arreglo que representa la tabla de transiciones.
 Constructor
o F: Recibe el arreglo de estados finales.
o stateTable: Recibe la tabla de transiciones.
 Funciones:
o Recognize: Este método abstracto es el que sobre escribiremos en los autómatas
que vallamos creando, en algunas ocasiones podríamos necesitar hacer algunas
validaciones sobre la cadena de entrada antes de evaluarla, eso se haría desde el
autómata que herede de AbstractAutomata y después llamaríamos al método
protegido RecongizeAlgorithm para que haga lo propio.
o GetState: Este método será la función de transición, recibe el estado actual (s) y el
carácter leído (c) y de acuerdo a esas condiciones nos dirá a que estado avanzara el
autómata.
o RecognizeAlgorithm: Este es el algoritmo con el que el autómata evaluara la cadena
de entrada y nos dirá si es válida (retorna true) o invalida (retorna false).

Justificación
Como pudieron notar, existe una clase AbstractChar, ¿Por qué?, pues por que he definido una
clase para cada carácter del alfabeto de entrada. Por ejemplo, si fuera a crear un autómata que
reconociera números binarios tendría que crear una clase para el carácter cero y otra para el
carácter 1, pero ambas heredarían de la clase AbstractChar, esto es porque el alfabeto de entrada
no siempre será el mismo, así que decidí hacerlo de esta forma, así Grammar.NET seria flexible en
el sentido de que podemos especificar el alfabeto de entrada que usaremos.
También si fueron observadores los métodos Recognize y RecognizeAlgorithm reciben una
lista de caracteres (List<AbstractChar>), eso es por que originalmente el tipo de dato string no
existía o no existe como tal, sino que es una lista de caracteres, así que abstraje de eso que nuestra
cadena es una lista de caracteres abstractos, de ahí el por qué un List<AbstractChar>.
Definición del Autómata para un número Real
Como sabemos, a un número con parte decimal se le conoce como número real, así que llamaremos
a nuestro autómata “Real”. Primero que nada les mostraré el diagrama de clases que debemos
respetar para los autómatas que necesitemos crear en Grammar.NET.

Como pueden ver la clase Real en su


constructor recibe el arreglo con los estados
finales y la tabla de transiciones, pero
realmente lo que hace es llamar al constructor
de la clase AbstractAutomata de esta forma:
public Real(int[] f, int[,] stateTable)
: base(f, stateTable)
{
}

Los métodos GetState y Recognize son sobre


escritos para que queden de la siguiente
manera:
public override bool
Recognize(List<AbstractChar> statement)
{
return RecognizeAlgorithm(statement);
}

Dado que no necesitamos validar nada en este


caso solo llamamos a RecognizeAlgorithm.

protected override int GetState(int s, AbstractChar c) Como pueden ver hacemos uso de un
{
switch (c.Type) enumerado CharType que definí para saber
{ qué tipo de caracter es, si es un Digito o un
case CharType.Digit:
return arrTabla[s, 0]; punto, de hecho, en él definí muchos más tipos,
case CharType.Point: pero el autómata Real solo manejara estos dos,
return arrTabla[s, 1];
default: por eso solo uso estos en el autómata. Así que
return -1; podemos observar que en nuestros autómatas
}
}
solo usaremos los tipos de carácter que valla a
leer nuestro autómata en cuestión.
El default lo que hace es “atrapar” cualquier símbolo que no pertenezca al alfabeto que reconoce el
autómata Real y retorna un -1, ¿Por qué?, pues por que los estados de nuestro autómata son un
número S, tal que, 0 < S < ∞ así que, podemos ver que los números negativos no pueden
representar algún estado del autómata por eso he decidido tomar el -1 para que represente que
las condiciones de estado actual y símbolo leído no pertenecen a la tabla de transiciones.

You might also like