29 de marzo de 2001

Búsqueda de paréntesis en la ventana activa

Problemática:

¿Quién no ha tenido alguna vez que encontrar en un programa el paréntesis cerrado asociado a un paréntesis abierto? Esta utilidad intenta dar solución a este problema.

Descripción:

Búsqueda del token asociado (que denominaremos "Pareado") al token seleccionado en la ventana activa.

Con el símil de la problemática, el token seleccionado sería el paréntesis abierto y el pareado el paréntesis cerrado.

Restricciones:

(R1) El token seleccionado sólo puede ser un carácter.
(R2) La correspondencia entre token seleccionado y su pareado está fija en el programa, restringiéndose a la siguiente:
'(' ')'
'[' ']'
(R3) El token seleccionado es siempre el carácter situado a la derecha del cursor.
(R4) Si se encuentra el Pareado, el cursor se situará a la izquierda del mismo.

Se cree conveniente esta forma porque las ejecuciones sucesivas, sin mover el cursor, siempre nos llevan a los mismos pareados.

Condicionantes:

(C1) Necesidad de enviar resultado a una ventana.
Aunque resulte curioso, si no se envía ningún resultado a otra ventana, ya sea la ventana principal de Visual FoxPro o una ventana activa definida por el usuario, el programa tarda mucho más en ejecutarse. No he logrado averiguar este extraño comportamiento. En la versión actual, por cada carácter procesado se escribe, en esa ventana, un único carácter (que se asemeja a un molino en movimiento [recuerdo de viejos tiempos ;)]) en la columna 1 y siempre en la misma línea.

(C2) Determinar condición de principio/fin de archivo por número de caracteres repetidos.
Al llegar el principio o fin del archivo en la ventana en la que hacemos la búsqueda, dado que el cursor no puede ir más allá, siemre se lee el mismo carácter (el primero o el último).
Ante la imposibilidad de determinar cuándo se ha llegado al principio o al final del archivo, se establece como heurística para que se cumpla dicha condición la lectura de un mismo carácter un número determinado de veces (véase parámetro).

(C3) Obtención de caracteres retrasada.
En cada ejecución del programa se obtiene como primer carácter el último carácter procesado en la ejecución anterior. Este extraño suceso que no me explico (¿quizá esté haciendo algo mal?) se ha solventado asignando al portapapeles (variable del sistema _CLIPTEXT) un carácter especial (CHR(255)).

Parámetros:

(P1) tnNumMaxCaracteresRepetidos (e) : Número de veces que se debe leer un mismo carácter para establecer la condición de princpio/fin de archivo. (véase (C2)).
Valor predeterminado: 100.

(P2) tlDebug (e) : Establece el programa en modo depuración para mostrar trazas.
.T. : Modo depuración.
.F. : Modo normal (Valor predeterminado).
(P3) tlCrearVentanaSalida (e) : Necesario por (C1).
.T. : Resultados a una ventana que crea el programa.
.F. : Resultados a la ventana principal de Visual FoxPro (Valor predeterminado).

Uso:

Para un uso cómodo se puede asociar a una combinación de teclas, como por ejemplo:
ON KEY LABEL ALT+8 DO "BuscarPareado"
* ON KEY LABEL ALT+8 DO "D:\JavierValero\vfp\Experimentos\Proyecto\Pruebas\progs\BuscarPareado" WITH 50,.F.,.F.

Así, para una ejecución correcta, situar el cursor a la derecha de un carácter '(' o ')' y pulsar ALT+8, y el cursor se nos situaría en su paréntesis asociado.

Bugs:

(BUG1) Por la heurística adoptada en (C2), el programa puede devolver una búsqueda insatisfactoria en ciertos casos. Por ejemplo, es muy común poner comentarios incluyendo muchos '*'. Si el par de tokens asociados se encuentra entre líneas de ese tipo y el parámetro es muy pequeño, el programa puede devolver que se ha llegado al principio/fin de archivo cuando no es cierto. Para evitar esto, habría que aumentar el valor del parámetro mencionado.

(BUG2) Por el criterio adoptado en (C3) si el carácter en la ventana de búsqueda

Mejoras:

- No limitar los token a caracteres sino a palabras, para que así se puedan encontrar los IF-ENDIF, DO WHILE-ENDDO, ...

**************************************
* BuscarPareado.prg
**************************************
* Autor: JVA
* Versión: 1.0
* Fecha creación: 27/03/2001
* Modificaciones realizadas: Ninguna
* Notas: Debido a la inexperiencia del autor,
* puede que haya conceptos mal
* interpretados de Visual FoxPro y
* además puede que parte del apartado
* de  'Condicionantes:' sea erróneo.
**************************************
LPARAMETERS tnNumMaxCaracteresRepetidos, tlDebug, tlCrearVentanaSalida

PRIVATE ALL

* Para simular un molinillo en movimiento cuando 
* el programa está en ejecución

#DEFINE MOLINILLO "|/-"
#DEFINE MOLINILLO_NUM_CARACTERES 20
#DEFINE MOLINILLO REPLICATE("|",5) + ;
  REPLICATE("/",5) + REPLICATE("-",5) + ;
  REPLICATE("",5)

* Dirección de exploración en la ventana de 
* búsqueda: hacia la derecha o la izquierda
#DEFINE DIRECCION_DERECHA "D"
#DEFINE DIRECCION_IZQUIERDA "I"

* Valores predeterminados para los parámetros
#DEFINE DEFECTO_NUM_MAX_CARACTERES_REPETIDOS 100
#DEFINE DEFECTO_DEBUG .F.
#DEFINE DEFECTO_VENTANA .F.

* Condicionante (C3): Carácter especial para el portapapeles
#DEFINE CARACTER_EN_PORTAPAPELES  CHR(255)

* Declaración de una variable como privada.
#DEFINE PRIVATE_DECL

* (C2) Número de caracteres repetidos para 
* determinar condición principio/fin de archivo
LOCAL lnNumMaxCaracteresRepetidos

* Carácter seleccionado por el usuario para 
* buscar su pareado.

* Se coge el carácter a la derecha del cursor.
LOCAL lcCaracterSeleccionado

* Carácter pareado del .
LOCAL lcCaracterPareado

* Indica si debemos ir cogiendo caracteres 
* hacia la izquierda o la derecha.
LOCAL lcDireccion

* Retorno de procedimientos
LOCAL llRetorno

* Programa en depuración
* Si .T. se muestran trazas en la ventana activa
LOCAL llDebug

* Crear ventana de salida para enviar los resultados
LOCAL llCrearVentanaSalida

* Para restaurar el antiguo valor de SET TYPEAHEAD
LOCAL lcOldTypeAhead

* Para restaurar el antiguo valor de SET STATUS BAR
LOCAL lcSetStatusBar

* Indice para mostrar el molinillo en movimiento
PRIVATE_DECL pnMolinillo = 0

lcSetStatusBar = SET("STATUS BAR")
IF lcSetStatusBar = "OFF"
   SET STATUS BAR ON
ENDIF

* Valores predeterminados
lnNumMaxCaracteresRepetidos = DEFECTO_NUM_MAX_CARACTERES_REPETIDOS
llDebug = DEFECTO_DEBUG
llCrearVentanaSalida = DEFECTO_VENTANA

*
*  Procesar parámetros
*

IF (PCOUNT() > 0)
   IF (UPPER(VARTYPE(tnNumMaxCaracteresRepetidos)) = 'N')
      lnNumMaxCaracteresRepetidos = tnNumMaxCaracteresRepetidos
   ENDIF
ENDIF

IF (PCOUNT() > 1)
   IF (UPPER(VARTYPE(tlDebug)) = 'L')
      llDebug = tlDebug
   ENDIF
ENDIF

IF (PCOUNT() > 2)
   IF (UPPER(VARTYPE(tlCrearVentanaSalida)) = 'L')
      llCrearVentanaSalida = tlCrearVentanaSalida
   ENDIF
ENDIF

IF (llDebug=.T.)
   CLEAR
ENDIF

* Si TYPEAHEAD = cero, no funciona el programa
lcOldTypeAhead = STR(SET('TYPEAHEAD'))
SET TYPEAHEAD TO 20

* Ventana donde se envían los resultados (C1)LOCAL lcNombreVentanaSalida   

* Nombre de la ventana de búsqueda
LOCAL lcVentanaBusqueda 

IF ( llCrearVentanaSalida = .T.)
   lcNombreVentanaSalida = "W_TMP" + SYS(3)
   DO WHILE (WEXIST(lcNombreVentanaSalida) = .T.)
      lcNombreVentanaSalida = "W_TMP" + SYS(3)
   ENDDO
   lcVentanaBusqueda = WTITLE()
   DEFINE WINDOW (lcNombreVentanaSalida) FROM 1,1 TO 2,2 CLOSE FLOAT
   ACTIVATE WINDOW (lcNombreVentanaSalida)
   ACTIVATE WINDOW (lcVentanaBusqueda)
ENDIF

_CLIPTEXT = CARACTER_EN_PORTAPAPELES

* Coger el carácter a la derecha del cursor (R3)
lcCaracterSeleccionado = Caracter DIRECCION_DERECHA)

* Condicionante (C3)

DO WHILE (lcCaracterSeleccionado == CARACTER_EN_PORTAPAPELES)

   lcCaracterSeleccionado = Caracter(DIRECCION_DERECHA)
ENDDO

IF (llDebug=.T.)
? "Carácter seleccionado=[" + lcCaracterSeleccionado + "]"
ENDIF

* Obtener el Pareado del carácter Seleccionado
llRetorno = CaracterPareado(lcCaracterSeleccionado, @lcCaracterPareado, @lcDireccion)

IF (llRetorno = .F.)
   SET MESSAGE TO 'Se desconoce el pareado para el carácter [' + lcCaracterSeleccionado + ']'
   RETURN .F.
ELSE
   SET MESSAGE TO "Buscando pareado " + lcCaracterPareado + " ..."
ENDIF

IF (llDebug=.T.)
   ?? "   Carácter pareado=[" + lcCaracterPareado + "]"
   ? ""
ENDIF

IF (lcDireccion = DIRECCION_IZQUIERDA)
   * Nos quedamos en la posición original
   Caracter(DIRECCION_IZQUIERDA)
ELSE  
   * Nada, o entonces volveríamos a leer el 
   * mismo carácter pareado.
ENDIF

* Número de Pareados encontrados, para tratar 
* la anidación de pareados
LOCAL lnNumPareados 

* Número de veces que se repite un mismo carácter
LOCAL lnNumCaracteresRepetidos  

* Último carácter leído
LOCAL lcCaracter 

* Para comparar si se repite un carácter
LOCAL lcOldCaracter 

lnNumPareados = 0  
lnNumCaracteresRepetidos = 0 
lcOldCaracter = CARACTER_EN_PORTAPAPELES 

* Condicionante (C2)
DO WHILE ( (lnNumCaracteresRepetidos(lnNumPareados >= 0) )
   lcCaracter = Caracter(lcDireccion)
   *
   * Tratar la anidación de los Pareados
   *
   * Anidamos pareado
   IF (lcCaracter == lcCaracterSeleccionado)
      lnNumPareados = lnNumPareados + 1
   ENDIF
   
   * Desanidamos pareado
   IF (lcCaracter == lcCaracterPareado)
      lnNumPareados = lnNumPareados - 1
   ENDIF

   IF (llDebug=.T.)
      ?? "NumPareados=" + STR(lnNumPareados) + "  "
   ENDIF

   *
   * Tratar la condición de principio/fin de archivo
   * mediante el reconocimiento de la repetición 
   * del mismo carácter.
   *
   IF (lcCaracter == lcOldCaracter)
      lnNumCaracteresRepetidos = lnNumCaracteresRepetidos + 1
   ELSE
      lnNumCaracteresRepetidos = 0
   ENDIF

   lcOldCaracter = lcCaracter
   
   IF (llDebug = .T.)
      ? lcCaracter + " ASC=" + ALLTRIM(STR(ASC(lcCaracter))) AT 1
   ENDIF
ENDDO

IF ((lnNumPareados 

   * Nos hemos pasado un carácter
   Caracter(DIRECCION_IZQUIERDA)
ELSE
   * Nada, o entonces volveríamos a leer el 
   * mismo carácter pareado
ENDIF

IF (lcCaracter == lcCaracterPareado)
   SET MESSAGE TO "Encontrado " + lcCaracterPareado + "."
ELSE
   IF (lcDireccion == DIRECCION_DERECHA)
      SET MESSAGE TO "No se encuentra " + lcCaracterPareado + ". Se llegó al final del archivo."
   ELSE
      SET MESSAGE TO "No se encuentra " + lcCaracterPareado + ". Se llegó al principio del archivo."
   ENDIF
ENDIF

IF ( llCrearVentanaSalida = .T.)
   RELEASE WINDOW (lcNombreVentanaSalida)
ENDIF

_CLIPTEXT = CARACTER_EN_PORTAPAPELES

*
* Restaurar entorno
*
SET TYPEAHEAD TO &lcOldTypeAhead
IF lcSetStatusBar = "OFF"
   SET STATUS BAR OFF
ENDIF

?? "." AT 1
* SET MESSAGE TO

RETURN .T.

****************
PROCEDURE Caracter
****************
LPARAMETERS tcDireccion
LOCAL lcC

IF (tcDireccion = DIRECCION_DERECHA)
   KEYBOARD '{SHIFT+RIGHTARROW}{CTRL+C}' PLAIN CLEAR
   KEYBOARD '{RIGHTARROW}' PLAIN
ELSE
   KEYBOARD '{SHIFT+LEFTARROW}{CTRL+C}' PLAIN CLEAR
   KEYBOARD '{LEFTARROW}' PLAIN
ENDIF

DOEVENTS

*
* Condicionante (C1)
*
?? SUBSTR(MOLINILLO, pnMolinillo+1, 1) AT 1
pnMolinillo = (pnMolinillo + 1) % 40

lcC = substr(_cliptext,1,1)
return lcC
ENDPROC  && Caracter

****************
PROCEDURE CaracterPareado
****************
LPARAMETERS tcCaracterBuscar, tcCaracterPareado, tcDireccion
* !!! Pasar los dos últimos por referencia !!!

LOCAL llRetorno, lnPosicion
LOCAL DIMENSION aPareadosBeg[2]
LOCAL DIMENSION aPareadosEnd[2]
aPareadosBeg[1] = '('
aPareadosEnd[1] = ')'
aPareadosBeg[2] = '['
aPareadosEnd[2] = ']'

llRetorno = .T.  && Suponemos que sí está
lnPosicion = ASCAN(aPareadosBeg,tcCaracterBuscar)

IF (lnPosicion != 0)
   tcCaracterPareado = aPareadosEnd[lnPosicion]
   tcDireccion = DIRECCION_DERECHA
ELSE
   lnPosicion = ASCAN(aPareadosEnd,tcCaracterBuscar)
   IF ( lnPosicion != 0)
      tcCaracterPareado = aPareadosBeg[lnPosicion]
      tcDireccion = DIRECCION_IZQUIERDA
   ELSE
      llRetorno = .F.
   ENDIF
ENDIF

RETURN llRetorno

ENDPROC  && CaracterPareado

****************
Javier Valero

No hay comentarios. :

Publicar un comentario