Entradas para la categoría 'Motor Aventura Gráfica'

Herramienta para crear fuentes

Jueves 27 de Septiembre de 2007 a las 0:23

Esta aplicación permite exportar las fuentes instaladas en el sistema como una imagen a un fichero del tipo bmp, gif, png o jpg y un fichero que describe la ubicación y dimensiones de cada carácter dentro de la imagen.

Se permite indicar los caracteres a exportar no teniendo porque exportar todos. Dispone de un menú para seleccionar los caracteres más comunes como las minúsculas, las mayúsculas, los dígitos del 0 al 9, etc. lo que evita al usuario tener que teclear dichos caracteres.

También se permite configurar el tipo de fuente, su tamaño, color, si esta en negrita, cursiva, el formato de la imagen (png, bmp, gif, jpg), las dimensiones de la imagen, el color que será considerado como transparente y el espacio que se dejará entre caracteres. La imagen puede crearse haciendo uso del anti-aliasing, lo cual tiene la ventaja de que los bordes de las caracteres quedan más suavizados.

Todos los datos establecidos en la la herramienta se conservan para la siguiente ejecución, con lo que no es necesario volver a introducir dichos datos.

Se ofrece la posibilidad de previsualizar la imagen antes de proceder a su exportación.

(Pulsad la imagen para verla a tamaño real)
Creador de fuentes.
Creador de fuentes.

Una de las características más interesantes de la herramienta es la enorme flexibilidad para exportar los datos a diferentes formatos y la facilidad para incorporar nuevos formatos personalizados mediante un sistema de plugins.

Independientemente del formato de exportación, los valores que se guardarán en el fichero serán el ancho y el alto de la imagen en pixeles, el espacio entre caracteres y por cada carácter la posición expresada en pixels tanto en X como en Y y el ancho y alto del carácter.

Inicialmente la herramienta cuenta con cinco formatos para exportar las fuentes:
- Fichero CSV: Valores separados por comas (en este paso por ;)
- Fichero XML: XML donde cada carácter es un nodo, que contiene cuatro nodos hijos con los diferentes valores
- Fichero XML Atributos: XML donde cada carácter es un nodo que tiene cuatro atributos con los diferentes valores
- Fichero Binario: Los datos se guardan en un fichero binario
- Fichero Texto: Los datos se guardan como texto plano

La forma de crear plugins para extender la funcionalidad de la herramienta incorporándola nuevos formatos de exportación consiste en crear una dll que contenga la clase o clases con los nuevos formatos. Cada clase que proporcione un nuevo formato debe implementar la interfaz IPersistir que tiene la siguiente definición:

 

VB.NET:
  1. Public Interface IPersistir
  2.  
  3.     ''' <summary>
  4.     ''' Nombre del plugin
  5.     ''' </summary>
  6.     ''' <value></value>
  7.     ''' <returns></returns>
  8.     ''' <remarks></remarks>
  9.     ReadOnly Property Nombre() As String
  10.  
  11.     ''' <summary>
  12.     ''' Escribe en un fichero la información de la fuente
  13.     ''' </summary>
  14.     ''' <param name="ficheroSalidaSinExtension">Ruta del fichero (sin incluir la extension) en el que
  15.     ''' se escribirá la información de los caracteres</param>
  16.     ''' <param name="anchoImagen">Ancho en pixeles de la imagen</param>
  17.     ''' <param name="altoImagen">Alto en pixeles de la imagen</param>
  18.     ''' <param name="separacionEntreCaracteres">Número de pixels de separación entre carácter y carácter</param>
  19.     ''' <param name="caracteres">Carácteres a escribir junto con su información</param>
  20.     ''' <remarks></remarks>   
  21.     Sub Escribir(ByVal ficheroSalidaSinExtension As String, ByVal anchoImagen As Integer, ByVal altoImagen As Integer, ByVal separacionEntreCaracteres As Integer, ByVal caracteres As Dictionary(Of Char, Rectangle))
  22.  
  23. End Interface

 

El nombre del plugin será el que posteriormente se mostrará dentro del menú "Guardar Como" de la herramienta

Como ejemplo se muestra a continuación la implementación del plugin que permite guardar los datos como XML con atributos:

 

VB.NET:
  1. Imports ExportadorFuentes
  2.  
  3. Public Class PersistirXMLAtributos
  4.     Implements IPersistir
  5.  
  6.     Public ReadOnly Property Nombre() As String Implements IPersistir.Nombre
  7.         Get
  8.             Return "XML Atributos"
  9.         End Get
  10.     End Property
  11.  
  12.     Public Sub Escribir(ByVal ficheroSalidaSinExtension As String, ByVal anchoImagen As Integer, ByVal altoImagen As Integer, ByVal separacionEntreCaracteres As Integer, ByVal caracteres As System.Collections.Generic.Dictionary(Of Char, System.Drawing.Rectangle)) Implements IPersistir.Escribir
  13.         Dim w As Xml.XmlTextWriter = Nothing
  14.         Dim c As Char
  15.  
  16.         Try
  17.             w = New Xml.XmlTextWriter(ficheroSalidaSinExtension & ".xml", System.Text.Encoding.UTF8)
  18.             w.Formatting = Xml.Formatting.Indented
  19.  
  20.             w.WriteStartElement("Fuente")
  21.             w.WriteAttributeString("AnchoImagen", anchoImagen.ToString)
  22.             w.WriteAttributeString("AltoImagen", altoImagen.ToString)
  23.             w.WriteAttributeString("SeparacionEntreCaracteres", separacionEntreCaracteres.ToString)
  24.             w.WriteEndElement()
  25.  
  26.             For Each c In caracteres.Keys
  27.                 w.WriteStartElement(c.ToString)
  28.                 w.WriteAttributeString("X", caracteres(c).Left.ToString)
  29.                 w.WriteAttributeString("Y", caracteres(c).Top.ToString)
  30.                 w.WriteAttributeString("Ancho", caracteres(c).Width.ToString)
  31.                 w.WriteAttributeString("Alto", caracteres(c).Height.ToString)
  32.                 w.WriteEndElement()
  33.             Next
  34.  
  35.         Catch ex As Exception
  36.             MsgBox(ex.Message)
  37.         Finally
  38.             If Not w Is Nothing Then
  39.                 w.Close()
  40.             End If
  41.         End Try
  42.     End Sub
  43.  
  44. End Class

 

La dll con el o los plugins debe copiarse en el mismo directorio en que se encuentre el ejecutable de la aplicación.

La herramienta ha sido desarrollada en Visual Basic.NET (VB.NET) y están disponibles para su descarga en la sección de descargas tanto el código fuente como los ejecutables.

La solución cuenta con dos proyectos: el creador o exportador de fuentes y uno para los plugins. El proyecto del exportador o creador de fuentes consta principalmente de dos elementos, el interfaz IPersistir y el formulario que es el que realiza la mayoría de las acciones. El proyecto de los plugins cuenta con cinco clases que implementan otros tantos tipos de formas de exportar los datos.

Punto contenido dentro de un póligono

Lunes 3 de Septiembre de 2007 a las 23:59

Una necesidad muy común en el desarrollo de videojuegos es la de determinar si un punto esta contenido dentro de un polígono irregular. Por ejemplo en el caso de una aventura gráfica es necesario para averiguar si el usuario ha pulsado sobre una determinada región que podría ser un objeto, otro personaje, etc.

Dentro de la librería matemática que he creado se encuentra la clase polígono que implementa dicha funcionalidad. Así como una serie de clases que también son utilizadas en el algoritmo como la clases Vector o la clase Angulo

El algoritmo para determinar si el punto esta incluido dentro del área del polígono es bastante sencillo, pero requiere algunos cálculos.

Inicialmente se une el punto que se quiere averiguar si esta dentro o fuera del polígono con cada uno de los vértices del polígono, con lo cual se obtendrán tantos segmentos como vértices tenga el polígono.
Después hay que ir sumando los ángulos que forman esos segmentos. La suma hay que realizarla en orden, es decir primero sumar el ángulo que forma el segmento del punto al vertice1 con el segmento del punto al vertice2 con el ángulo que forma el segmento del punto al vertice2 con el segmento del punto al vertice3. Y así sucesivamente en orden hasta terminar sumando el ángulo que forma el segmento del último vértice con el del primer vértice.
Si el ángulo total resultante de la suma de ángulos es 0 grados entonces el punto estará fuera del polígono, si es 2PI entonces estará dentro.
Al realizar los cálculos probablemente se acumulen errores de redondeo así que a lo mejor el resultado no es exactamente 0 o 2 PI, por lo que no se debe comparar con 0 o 2PI si no ver si esta cerca de esos valores.

El código del método que determina si el punto pertenece al polígono es el siguiente:

 

VB.NET:
  1. ''' <summary>
  2.     ''' Devuelve si el punto pertenece al poligono
  3.     ''' </summary>
  4.     ''' <param name="pPunto"></param>
  5.     ''' <returns></returns>
  6.     ''' <remarks></remarks>
  7.     Public Function Incluye(ByVal pPunto As Point) As Boolean
  8.         Dim a As New Angulo(0)
  9.         Dim v1 As Vector
  10.         Dim v2 As Vector
  11.         Dim i As Integer
  12.  
  13.         If Vertices.Count <3 Then
  14.             Throw New ApplicationException("El polígono debe tener al menos tres vértices y tiene " & Vertices.Count & " vertices.")
  15.         End If
  16.  
  17.         'Trazamos un vector desde el punto a cada uno de los
  18.         'vertices
  19.         'Calculamos el angulo que forma cada vector con el
  20.         'vector del vertice adyacente (el vector del ultimo
  21.         'vertice formará un angulo con el vector primer vertice)
  22.         'Sumamos todos los angulo
  23.         For i = 0 To Vertices.Count - 1
  24.             v1 = New Vector(pPunto, Vertices(i))
  25.             v2 = New Vector(pPunto, Vertices((i + 1) Mod Vertices.Count))
  26.             a = a + Vector.Angulo(v1, v2)
  27.         Next
  28.  
  29.         'Si el angulo resultante es 360º entones el punto estará
  30.         'dentro del poligono
  31.         'Si el angulo es 0 grados estará fuera.
  32.         'Como se han podido acumular pequeños errores al hacer
  33.         'los calculos establecemos que si el valor absoluto
  34.         'de los grados es menor de 180 esta fuera (realmente
  35.         'será cercano a 0º) y si no dentro (estará cercano a 360)
  36.  
  37.         Return Math.Abs(a.Grados)> 180
  38.  
  39.     End Function

 

El polígono viene dado por una lista de puntos que representan los vértices.

Para determinar el ángulo entre dos vectores utilizamos la siguiente función:

 

VB.NET:
  1. ''' <summary>
  2.     ''' Devuelve el angulo que forman los dos vectores expresado en radianes
  3.     ''' </summary>
  4.     ''' <param name="v1"></param>
  5.     ''' <param name="v2"></param>
  6.     ''' <returns>El angulo expresado en radianes</returns>
  7.     ''' <remarks></remarks>
  8.     Public Shared Function Angulo(ByVal v1 As Vector, ByVal v2 As Vector) As Angulo
  9.         If Vector.ModuloDelProductoVectorialConSigno(v1, v2)> 0 Then
  10.             Return New Angulo(Math.Acos((Vector.ProductoEscalar(v1, v2) / (v1.Modulo * v2.Modulo))))
  11.         Else
  12.             'Si el modulo del producto vectorial es negativo
  13.             'el angulo será negativo
  14.             Return New Angulo(-1 * Math.Acos((Vector.ProductoEscalar(v1, v2) / (v1.Modulo * v2.Modulo))))
  15.         End If
  16.  
  17.     End Function

 

El cálculo del producto escalar de dos vectores y del módulo del producto vectorial es muy sencillo:

 

VB.NET:
  1. ''' <summary>
  2.     ''' Devuelve el producto escalar de dos vectores
  3.     ''' </summary>
  4.     ''' <param name="v1"></param>
  5.     ''' <param name="v2"></param>
  6.     ''' <returns></returns>
  7.     ''' <remarks></remarks>
  8.     Public Shared Function ProductoEscalar(ByVal v1 As Vector, ByVal v2 As Vector) As Integer
  9.         Return ((v1.X * v2.X) + (v1.Y * v2.Y))
  10.     End Function
  11.  
  12.     ''' <summary>
  13.     ''' Devuelve el módulo del vector resultante de hacer el
  14.     ''' productor vectorial de dos vectores
  15.     ''' </summary>
  16.     ''' <param name="v1"></param>
  17.     ''' <param name="v2"></param>
  18.     ''' <returns></returns>
  19.     ''' <remarks></remarks>
  20.     Public Shared Function ModuloDelProductoVectorialConSigno(ByVal v1 As Vector, ByVal v2 As Vector) As Integer
  21.         Return ((v1.X * v2.Y) - (v1.Y * v2.X))
  22.     End Function

 

Estado del proyecto a 31/07/2007

Martes 31 de Julio de 2007 a las 23:43

Esta última quincena me ha cundido bastante y le he dado un pequeño empujón al proyecto.

He añadido al editor de scripts la posibilidad de mostrar los números de línea del código, funcionalidad en desuso hoy en día y que era muy común en los viejos editores de código. El visual studio 2005 permite mostrarlos aunque por defecto esta deshabilitado.

También he añadido al editor de scripts una ventana de opciones para configurar varios aspectos del editor. Actualmente permite establecer la fuente del editor de código, si se mostrarán los números de línea o no y si se sustituirán los tabuladores por espacios y si es así por cuantos espacios.

He creado un control llamado animación que encapsula toda la funcionalidad relativa a la creación de una animación para poder reutilizarlo con facilidad en los diversos editores. Actualmente solo está presente en el editor del personaje protagonista.

En cuanto a la ejecución de la aventura gráfica también he realizado algunos avances. El motor lee la información creada previamente con las herramientas o editores y dibuja el escenario inicial y el protagonista. Este se mueve por la pantalla respondiendo a las pulsaciones del teclado o yendo hacia el punto donde se haya hecho click con el ratón. El protagonista se mueve a la velocidad indicada en el editor del protagonista y camina en 8 posibles direcciones con las animaciones (secuencias de fotogramas) que se hayan definido.

Tambien he desarrollado una clase para dar soporte a la configuración de la aplicación, permitiendo varios ficheros de configuración y la actualización "en caliente", es decir que si en tiempo de ejecución se modifica algún valor de un fichero de configuración dicho cambio es detectado y aplicado por la aplicación sin tener que reiniciarla.

Para pertimir moverse al protagonista de una forma coherente por las zonas transitables de los escenarios he implementado un algoritmo de búsqueda de caminos basándome en el A*. No está del todo terminado, ya que todavía tengo pendiente pulir un poco los caminos encontrados por el algoritmo de búsqueda..

Aun quedan pendientes muchas modificaciones a los editores y la creación de editores nuevos como el editor de objetos o el de conversaciones.

Más adelante iré subiendo algunas capturas de todo esto.

Dotando a los controles de .NET de un borde personalizado

Miércoles 18 de Julio de 2007 a las 23:57

Sorprendentemente los controles de .NET no admiten apenas estilos de borde. Por ejemplo el control listbox solo permite dibujar el borde de tres formas: hundido, con una línea negra o sin borde.

El intellisense que desarrollé hace tiempo para el editor de código se basaba en un listbox y no tenía una apariencia adecuada ni similar a los del intellisense de los IDES habituales debido al borde que tenía que era el borde hundido que viene por defecto en dicho control.

La primera forma de implementar los bordes que me vino a la cabeza fue crear un control que heredara de listbox, es la forma más lógica de implementarlo debido a lo que se busca es un listbox con cierta funcionalidad adicional. Sin embargo el control listbox no implementa el evento paint y aunque lo hereda de la clase Control nunca se levanta. De igual forma si se sobrescribe el método onpaint tampoco se ejecuta nunca.

La siguiente opción que se me ocurrió fue crear un control de usuario compuesto de un listbox y un picturebox, un panel o similar que contuviera el listbox y que sobresaliera dos pixels por cada uno de los lados del listbox. En esos pixeles sería donde dibujaría los bordes valiéndome del evento paint. Esta opción tiene un importante inconveniente y es que al no heredar de listbox sino de la clase UserControl no implementa todos los métodos, propiedades y eventos de listbox. En este punto habría dos opciones, la fácil que consistiría en hacer publico el listbox. Esta opción rompe con uno de los pilares de la orientación a objetos que es la encapsulación y el código que utilizara el control tendría que hacer utilizar por ejemplo listBoxConBorde.ListboxInterno.SelectedIdex en vez de listBoxConBorde.SelectedIdex que sería lo deseable.
La otra opción sería recubrir las propiedades, métodos y eventos del listbox en el control de usuario, creando en el control de usuario tantas propiedades, métodos y eventos como tenga el listbox. Estos miembros del control de usuario simplemente llamarían a los miembros homónimos del listbox. Esta opción me parece más adecuada que la anterior pero mucho mas tediosa de implementar.

Finalmente opté por una solución que me ha parecido mucho más acertada, sencilla y genérica. Consiste en crear un control llamado "Borde" que dibuje el borde de cualquier control. Hereda de la clase "Control" y consta de dos propiedades:

  • El tipo de borde (que admite los siguientes valores Elevado, ElevadoDobleLinea, Hundido, ElevadoYHundido y HundidoYElevado)
  • El control asociado al que se le dibujará el borde.

Además del código para realizar el dibujo del borde también se encarga de adecuar su tamaño y posición a la del control asociado.

Extender el control para añadir nuevos tipos de bordes con diferentes efectos sería una labor bastante sencilla de realizar.

La nueva apariencia del intellisense estableciendo el borde a ElevadoDobleLinea es la siguiente:


Intellisense con el nuevo borde.
Intellisense con el nuevo borde.


Intellisense."
Intellisense anterior.

 

En la sección de descargas se puede encontrar el código completo en VB.NET del control.

Estado del proyecto a 06/11/2006

Lunes 6 de Noviembre de 2006 a las 23:58

Desde el último post en el que hablé del estado del proyecto estos han sido los progresos que he realizado:

He creado el "motor en tiempo de ejecución" que permite procesar las instrucciones generadas por el "generador de instrucciones" a partir de las salidas de los analizadores léxico, sintáctico y semántico.

He implementado un pequeño motor gráfico o mejor dicho clase gestora de gráficos, ya que no creo que tenga la suficiente entidad como para considerarla un motor. Esta clase encapsula el acceso a las DirectX 9 proporcionando funciones de más alto nivel.

También creé unas clases para encapsular la gestión de las entradas del ratón y el teclado haciendo uso también de DirectX 9

Ahora mismo estoy trabajando en el desarrollo de diversos editores que en conjunto constituirán el editor de aventuras gráficas. Entre ellos están el editor de escenarios, el del protagonista, el de conjuntos de imágenes (tilesets), etc.

Un gran día

Martes 24 de Octubre de 2006 a las 23:51

Hoy es un gran día. Por fin, después de echarle muchas horas al interprete del lenguaje de script que he creado, sin que se pudieran palpar resultados visibles, hoy he ejecutado las dos primeras sentencias correctamente. Simplemente ha sido la declaración de una variable y la asignación a esta del resultado de una expresión.

variable numerica j .
j = 1 + 2 * 3 + 4 .

Tras darle al botón ejecutar de la interfaz gráfica, el interprete ha realizado el análisis léxico, el sintáctico, el semántico y al no detectar en ninguna de dichas fases ningún error ha procedido a generar las instrucciones y posteriormente a ejecutarlas. Finalmente la variable j ha recibido el valor 11.

Un segundo script más complicado también ha sido ejecutado correctamente:

escribirEnConsola ( "a" + "b" + numericaACadena ( 3 + 2 ) ) .
variable numerica j .
constante numerica k = 3 .
j = 4 + 7 - 4 * k + 1 .
j = 1 + 2 * j + 4 .
j = 1 + 2 * ( j + 4 ) .
variable logica a .
a = ( 2 < 4 ) y ( 3 != 5 ) .
si a entonces
     j = 4 + 7 - 4 * k + 1 .
sino
     j = 3 .
fin si .
escribirEnConsola ( numericaACadena ( j ) ) .

variable numerica i .
i = 0 .
mientras i < 5 hacer
     escribirEnConsola ( "i = " + numericaACadena ( i ) ) .
     i = i + 1 .
fin mientras .

Los tiempos consumidos en realizar esta segunda compilación y que devuelve la salida del compilador son los siguientes

Análisis léxico realizado correctamente en 78,125 milisegundos (0,078125 segundos)
Análisis sintáctico realizado correctamente en 140,625 milisegundos (0,140625 segundos)
Análisis semántico realizado correctamente en 15,625 milisegundos (0,015625 segundos)
Compilación realizada correctamente en 234,375 milisegundos (0,234375 segundos)

Y la salida de la consola:

ab5
-2
i = 0
i = 1
i = 2
i = 3
i = 4

El -2 puede extrañar a simple vista. Pese a lo que pudiera parecer el resultado es correcto, la cuestión es que se ha definido la asociatividad por la derecha en vez de por la izquierda.

Estado del proyecto a 22/10/2006

Domingo 22 de Octubre de 2006 a las 23:58

He realizado los siguientes avances en el proyecto:

He incorporado al script la posibilidad de que un operando de una expresión (y por lo tanto un parámetro de una función ya que los parámetros son expresiones) sea una función. Que a su vez podría tener como parámetros otras funciones y expresiones, y así indefinidamente.

He creado el generador de instrucciones que a partir del árbol semántico genera las instrucciones que serán ejecutadas por el "Motor en tiempo de ejecución".

También e incorporado varias funciones al motor que serán accesibles desde cualquier script, como funciones para convertir datos de unos tipos a otros, escribir en consola, etc.

Voy a dejar abierto el módulo de los scripts para que puedan ser utilizados independientemente del proyecto por otras aplicaciones y que estas puedan exponer sus propias funciones para hacerlas accesibles desde un script.

Creando un analizador semántico

Viernes 29 de Septiembre de 2006 a las 0:52

Entre las funciones de un analizador semántico están las siguientes:

  • Detectar si las variables, constantes y funciones han sido declaradas antes de ser utilizadas.
  • Verificar que las variables, constantes y funciones sean accesibles (visibilidad) desde el ámbito en que son utilizadas.
  • Comprobar que los diferentes identificadores solo hayan sido declarados una vez.
  • Comprobaciones de tipos al evaluar las expresiones. Por ejemplo que no se multiplique un número por una cadena o que la expresión a evaluar en un IF sea del tipo booleano.
  • Verificar que no se intente modificar el valor de una constante.
  • Generar la tabla de símbolos.

El lenguaje de script que estoy desarrollando de momento solo tendrá un ámbito, por lo que no será necesaria la comprobación del ámbito.

Para realizar el análisis semántico utilizaremos una gramática de atributos. Dichos atributos se asociaran tanto a los símbolos terminales como a los no terminales y se propagarán por el árbol sintáctico desde abajo hacia arriba, dando lugar al árbol semántico.
Lo primero que haremos será definir las reglas semánticas, para ello utilizaremos de nuevo la notición BNF a la que añadiremos en pseudocódigo las reglas semánticas.

A continuación se detallan aquellas producciones que constan de reglas semánticas:


10 <DeclaracionVariable> ::= Variable <Tipo> Identificador
	InsertarSimbolo (Variable, Identificador, <Tipo>.Tipo)
11 <Tipo> ::= Numerica
	<Tipo>.Tipo  = Numerica
12 <Tipo> ::= Cadena
	<Tipo>.Tipo  = Cadena
13 <Tipo> ::= Logica
	<Tipo>.Tipo  = Logica
14 <DeclaracionConstante> ::= Constante <Resto_Constante>
	InsertarSimbolo (Constante, Identificador, < Resto_Constante >.Tipo)
18 <Condicional> ::= Si <Expresion> Entonces <Sentencias>  <Resto_Condicional>
	Si <Expresion>.Tipo <> Logica
		Error
21 <Bucle> ::= Mientras <Expresion> Hacer <Sentencias> Fin Mientras
	Si <Expresion>.Tipo <> Logica
		Error
22 <Asignacion_O_LlamadaAMetodo> ::= Identificador <Resto_Asignacion_O_LlamadaAMetodo> 

	Si No Existe (Identificador)
		Error
	Si hijo(1).produccion = 23
		Si es una variable
			Si tipo(identificado) <> hijo(1).tipo
				error
		sino
			Error
	Si hijo(1).produccion = 24
		Recorrer parametros y comprobar que el numero y el tipo de parámetros coincide
23 <Resto_Asignacion_O_LlamadaAMetodo> ::= = <Expresion>
	<Resto_Asignacion_O_LlamadaAMetodo>.Tipo = <Expresion>.Tipo
24 <Resto_Asignacion_O_LlamadaAMetodo> ::= <Parametros>
30 <Expresion> ::= <ExpresionSimple> <Resto_Expresion>
	Si <Resto_Expresion> SeAnula
		<Expresion>.Tipo = <ExpresionSimple>.Tipo
	Sino
		Si <ExpresionSimple>  = <Resto_Expresion>.Tipo
			<Expresion>.Tipo = Logica
		Sino
			Error
31 <Resto_Expresion> ::= <OperadorRelacional> <ExpresionSimple>
	<Resto_Expresion>.Tipo = <ExpresionSimple>.Tipo
32 <Resto_Expresion> ::= ε
39 <ExpresionSimple> ::= <Termino> <Resto_ExpresionSimple>
	Si <Resto_ExpresionSimple>  se anula
		<ExpresionSimple>.Tipo = <Termino>.Tipo
	Sino
		Si <Termino>.Tipo = <Resto_ExpresionSimple>.Tipo
			<ExpresionSimple>.Tipo = <Termino>.Tipo
		Si no
			Error
40 <Resto_ExpresionSimple> ::= <OperadorAditivo> <ExpresionSimple>
	Si <OperadorAditivo> = +
		Si <ExpresionSimple>.Tipo  = Numerica Or <ExpresionSimple>.Tipo = Cadena
			<Resto_ExpresionSimple>.Tipo = <ExpresionSimple>.Tipo
		Sino
			Error
	Si <OperadorAditivo> = -
		Si <ExpresionSimple>.Tipo  = Numerica
			<Resto_ExpresionSimple>.Tipo = <ExpresionSimple>.Tipo
		Sino
			Error
	Si <OperadorAditivo> = O
		Si <ExpresionSimple>.Tipo  = Logica
			<Resto_ExpresionSimple>.Tipo = Logica
		Sino
			Error
41 <Resto_ExpresionSimple> ::= ε
45 <Termino> ::= <Operando> <Resto_Termino>
	Si <Resto_Termino> se anula
		<Termino>.Tipo = <Operando>.Tipo
	Sino
		Si <Operando>.Tipo = <Resto_Termino>.Tipo
			<Termino>.Tipo = <Operando>.Tipo
		Si no
			Error
46 <Resto_Termino> ::= <OperadorMultiplicacion> <Termino>
	Si <OperadorMultiplicacion> = *  Or <OperadorMultiplicacion> = /
		Si <Termino>.Tipo  = Numerica
			<Resto_Termino>.Tipo = Numerica
		Sino
			Error
	Si <OperadorMultiplicacion> = Y
		Si <Termino>.Tipo  = Logica
			<Resto_Termino>.Tipo = Logica
		Sino
			Error
47 <Resto_Termino> ::= ε
51 <Operando> ::= Identificador
	Si Existe(Identificado)
		<Operando>.Tipo = Tipo (Identificador)
	Sino
		Error
52 <Operando> ::= Cte_Numerica
	<Operando>.Tipo = Numerico
53 <Operando> ::= Cte_Cadena
	<Operando>.Tipo = Cadena
54 <Operando> ::= Cte_Logica
	<Operando>.Tipo = Logica
55 <Operando> ::= ( <Expresion> )
	<Operando>.Tipo = Tipo(<Expresion>)

Recorreremos recursivamente el árbol sintáctico en postorden (para cada nodo procesamos recursivamente todos los descendientes primero y luego el nodo). Iremos añadiendo atributos con información al árbol a medida que procesemos nodos. Como el recorrido se realiza en postorden cuando procesemos un nodo ya habremos procesado previamente todos sus descendientes por lo que dispondremos de la información de que nos provean sus atributos.

Añadiremos a la tabla de símbolos todas las constantes y variables que se vayan reconociendo, junto con la información del tipo y el valor (en el caso des la constantes)

La tabla de símbolos será una lista de elementos del tipo símbolo:

VB.NET:
  1. ''' <summary>
  2. ''' Es un simbolo de la tabla de simbolos. Se utiliza en el analizador léxico
  3. ''' </summary>
  4. ''' <remarks></remarks>
  5. Public Class Simbolo
  6.  
  7.     Public Enum ClaseSimbolo
  8.         Variable
  9.         Constante
  10.         Funcion
  11.     End Enum
  12.  
  13.     Private _nombre As String
  14.  
  15.     ''' <summary>
  16.     ''' El nombre del simbolo. Por ejemplo el nombre de una variable tal y como se declara
  17.     ''' </summary>
  18.     ''' <value></value>
  19.     ''' <returns></returns>
  20.     ''' <remarks></remarks>
  21.     Public Property Nombre() As String
  22.         Get
  23.             Return _nombre
  24.         End Get
  25.         Set(ByVal value As String)
  26.             _nombre = value
  27.         End Set
  28.     End Property
  29.  
  30.     Private _clase As ClaseSimbolo
  31.  
  32.     ''' <summary>
  33.     ''' Indica que clase de simbolo es: variable, constante o función
  34.     ''' </summary>
  35.     ''' <value></value>
  36.     ''' <returns></returns>
  37.     ''' <remarks></remarks>
  38.     Public Property Clase() As ClaseSimbolo
  39.         Get
  40.             Return _clase
  41.         End Get
  42.         Set(ByVal value As ClaseSimbolo)
  43.             _clase = value
  44.         End Set
  45.     End Property
  46.  
  47.     Private _tipo As String
  48.  
  49.     ''' <summary>
  50.     ''' Representa el tipo del simbolo: numerica, cadena o logica
  51.     ''' </summary>
  52.     ''' <value></value>
  53.     ''' <returns></returns>
  54.     ''' <remarks></remarks>
  55.     Public Property Tipo() As String
  56.         Get
  57.             Return _tipo
  58.         End Get
  59.         Set(ByVal value As String)
  60.             _tipo = value
  61.         End Set
  62.     End Property
  63.  
  64.     Private _valor As String
  65.  
  66.     ''' <summary>
  67.     ''' Valor del simbolo. Unicamente utilizado por las constantes
  68.     ''' </summary>
  69.     ''' <value></value>
  70.     ''' <returns></returns>
  71.     ''' <remarks></remarks>
  72.     Public Property Valor() As String
  73.         Get
  74.             Return _valor
  75.         End Get
  76.         Set(ByVal value As String)
  77.             _valor = value
  78.         End Set
  79.     End Property
  80.  
  81.     Private _tiposDeLosArgumentos As New List(Of String)
  82.  
  83.     ''' <summary>
  84.     ''' Solo utilizada por los simbolos de tipo función. Es una lista con los tipos de los
  85.     ''' argumentos de la función.
  86.     ''' </summary>
  87.     ''' <value></value>
  88.     ''' <returns></returns>
  89.     ''' <remarks></remarks>
  90.     Public ReadOnly Property TiposDeLosArgumentos() As List(Of String)
  91.         Get
  92.             Return _tiposDeLosArgumentos
  93.         End Get
  94.     End Property
  95.  
  96. End Class

El siguiente script tras pasar por el analizador semántico genera este árbol semántico:

variable numerica j .
j = 1 + 2 * j + 4 .

 


<Elemento Nombre="<SCRIPT>" Produccion="1">
    <Elemento Nombre="<SENTENCIAS>" Produccion="2">
        <Elemento Nombre="<SENTENCIA>" Produccion="5">
            <Elemento Nombre="<DECLARACIONVARIABLE>" Produccion="10">
                <Elemento Nombre="VARIABLE" Palabra="variable" Linea="1" Col="1"></Elemento>
                <Elemento Nombre="<TIPO>" Produccion="11" Tipo="NUMERICA">
                    <Elemento Nombre="NUMERICA" Palabra="numerica" Linea="1" Col="10"></Elemento>
                </Elemento>
                <Elemento Nombre="IDENTIFICADOR" Palabra="j" Linea="1" Col="19"></Elemento>
            </Elemento>
        </Elemento>
        <Elemento Nombre="." Palabra="." Linea="1" Col="21"></Elemento>
        <Elemento Nombre="<RESTO_SENTENCIAS>" Produccion="3">
            <Elemento Nombre="<SENTENCIAS>" Produccion="2">
                <Elemento Nombre="<SENTENCIA>" Produccion="7">
                    <Elemento Nombre="<ASIGNACION_O_LLAMADAAMETODO>" Produccion="22">
                        <Elemento Nombre="IDENTIFICADOR" Palabra="j" Linea="2" Col="1"></Elemento>
                        <Elemento Nombre="<RESTO_ASIGNACION_O_LLAMADAAMETODO>" Produccion="23" Tipo="NUMERICA">
                            <Elemento Nombre="=" Palabra="=" Linea="2" Col="3"></Elemento>
                            <Elemento Nombre="<EXPRESION>" Produccion="30" Tipo="NUMERICA">
                                <Elemento Nombre="<EXPRESIONSIMPLE>" Produccion="39" Tipo="NUMERICA">
                                    <Elemento Nombre="<TERMINO>" Produccion="45" Tipo="NUMERICA">
                                        <Elemento Nombre="<OPERANDO>" Produccion="52" Tipo="NUMERICA">
                                            <Elemento Nombre="CTE_NUMERICA" Palabra="1" Linea="2" Col="5"></Elemento>
                                        </Elemento>
                                        <Elemento Nombre="<RESTO_TERMINO>" Produccion="47"></Elemento>
                                    </Elemento>
                                    <Elemento Nombre="<RESTO_EXPRESIONSIMPLE>" Produccion="40" Tipo="NUMERICA">
                                        <Elemento Nombre="<OPERADORADITIVO>" Produccion="42">
                                            <Elemento Nombre="+" Palabra="+" Linea="2" Col="7"></Elemento>
                                        </Elemento>
                                        <Elemento Nombre="<EXPRESIONSIMPLE>" Produccion="39" Tipo="NUMERICA">
                                            <Elemento Nombre="<TERMINO>" Produccion="45" Tipo="NUMERICA">
                                                <Elemento Nombre="<OPERANDO>" Produccion="52" Tipo="NUMERICA">
                                                    <Elemento Nombre="CTE_NUMERICA" Palabra="2" Linea="2" Col="9"></Elemento>
                                                </Elemento>
                                                <Elemento Nombre="<RESTO_TERMINO>" Produccion="46" Tipo="NUMERICA">
                                                    <Elemento Nombre="<OPERADORMULTIPLICACION>" Produccion="48">
                                                        <Elemento Nombre="*" Palabra="*" Linea="2" Col="11"></Elemento>
                                                    </Elemento>
                                                    <Elemento Nombre="<TERMINO>" Produccion="45" Tipo="NUMERICA">
                                                        <Elemento Nombre="<OPERANDO>" Produccion="51" Tipo="NUMERICA">
                                                            <Elemento Nombre="IDENTIFICADOR" Palabra="j" Linea="2" Col="13"></Elemento>
                                                        </Elemento>
                                                        <Elemento Nombre="<RESTO_TERMINO>" Produccion="47"></Elemento>
                                                    </Elemento>
                                                </Elemento>
                                            </Elemento>
                                            <Elemento Nombre="<RESTO_EXPRESIONSIMPLE>" Produccion="40" Tipo="NUMERICA">
                                                <Elemento Nombre="<OPERADORADITIVO>" Produccion="42">
                                                    <Elemento Nombre="+" Palabra="+" Linea="2" Col="15"></Elemento>
                                                </Elemento>
                                                <Elemento Nombre="<EXPRESIONSIMPLE>" Produccion="39" Tipo="NUMERICA">
                                                    <Elemento Nombre="<TERMINO>" Produccion="45" Tipo="NUMERICA">
                                                        <Elemento Nombre="<OPERANDO>" Produccion="52" Tipo="NUMERICA">
                                                            <Elemento Nombre="CTE_NUMERICA" Palabra="4" Linea="2" Col="17"></Elemento>
                                                        </Elemento>
                                                        <Elemento Nombre="<RESTO_TERMINO>" Produccion="47"></Elemento>
                                                    </Elemento>
                                                    <Elemento Nombre="<RESTO_EXPRESIONSIMPLE>" Produccion="41"></Elemento>
                                                </Elemento>
                                            </Elemento>
                                        </Elemento>
                                    </Elemento>
                                </Elemento>
                                <Elemento Nombre="<RESTO_EXPRESION>" Produccion="32"></Elemento>
                            </Elemento>
                        </Elemento>
                    </Elemento>
                </Elemento>
                <Elemento Nombre="." Palabra="." Linea="2" Col="19"></Elemento>
                <Elemento Nombre="<RESTO_SENTENCIAS>" Produccion="4"></Elemento>
            </Elemento>
        </Elemento>
    </Elemento>
    <Elemento Nombre="FIN_FICHERO" Palabra="FIN_SCRIPT" Linea="3" Col="1"></Elemento>
</Elemento

Ejemplos de errores detectados por el analizador semántico, mostrandose primero el script y a continuación el error detectado:

j = 1 + 2 * j + 4 .
ERROR: Identificador no declarado: j en línea 1 Columna 13

variable numerica j .
j = 1 + verdadero .
ERROR: Se esperaba un valor de tipo numérico o cadena tras el operador: + en la línea 2 Columna 7

si "a" = 3 entonces
   j = 4 .
fin si .
ERROR: No es posible realizar la comparación ya que los dos operandos son de tipos distintos. Línea 1 Columna 8

Estado del proyecto a 25/09/2006

Domingo 24 de Septiembre de 2006 a las 23:57

He finalizado el analizador semántico por lo que ya están terminados los tres analizadores (léxico, sintáctico y semántico).

El lenguaje de momento no contempla ámbitos para las variables ni tampoco arrays. Ambas características espero implementarlas en un futuro.

Lo próximo que desarrollaré será el generador de instrucciones, que creará las instrucciones a partir del árbol generado por el analizador semántico y el "tiempo de ejecución". Implementaré también algunas funciones del motor que pueden ser llamadas desde el script, como por ejemplo la escritura en fichero de log y por consola, que me será útil para depurar scripts. Cuando el motor esté más avanzado incluiré nuevas funciones para que puedan ser llamadas desde los scripts.

Durante unos cuantos días no podré dedicarle tiempo al proyecto, por lo que sufrirá un pequeño parón o ralentización temporal. Esto se debe a que me ha surgido un pequeño encargo profesional que tendré que realizar en el poco tiempo libre de que dispongo y que dedicaba al proyecto.

A lo largo de esta semana publicaré un par de artículos sobre el desarrollo del analizador semántico.

Analizador sintáctico (Parte II)

Viernes 22 de Septiembre de 2006 a las 23:58

Este artículo es la continuación de:

Analizador sintáctico (Parte I)

Tras comprobar que cumple la condición LL(1) pasamos a codificar el analizador sintáctico. Para automatizar el reconocimiento de los diferentes elementos creamos la clase producción que encapsulará la información de cada una de las producciones que definimos mediante la notación BNF:

VB.NET:
  1. Public Class Produccion
  2.  
  3.     Dim _parteIzquierda As String
  4.  
  5.     Public ReadOnly Propert