Sei sulla pagina 1di 166

Curso de VisualLISP

ÍNDICE DE CONTENIDOS

INTRODUCCIÓN

 LISP: PARADIGMA DEL ESTILO DE PROGRAMACIÓN


FUNCIONAL

PRIMERA PARTE:
EL ENTORNO DE DESARROLLO VISUAL LISP

1. VISUAL LISP
2. EL ENTORNO DE DESARROLLO VISUAL LISP
o El Trabajo con Visual LISP y AutoCAD
o Barra de Menús
o Las Barras de Herramientas
o La Consola Visual LISP
o El Editor Visual LISP
 Barras de Herramientas
 Menú Contextual
 Teclas Rápidas

SEGUNDA PARTE:
TÉCNICAS FUNDAMENTALES DE LA PROGRAMACIÓN LISP

1. TIPOS DE DATOS
o ÁTOMOS
o ÁTOMOS SIMBÓLICOS (S-ATOMS)
o CONSTANTES
 NÚMEROS
 CADENAS DE TEXTO
o LISTAS Y CONSES
 LISTAS DE UN NIVEL
 LISTAS ANIDADAS
 LISTAS DE ASOCIACIÓN (A-LIST)
 LA LISTA VACÍA (NIL)
o FUNCIÓN TYPE: EXAMEN DEL TIPO DE DATO
 TIPOS DE DATOS LISP
 TIPOS DE DATOS AUTOCAD
 TIPOS DE DATOS ACTIVE-X
2. FUNCIONES
o FUNCIONES PRIMITIVAS
 OPERADORES ARITMÉTICOS
 FUNCIONES DE ACCESO A LISTAS
 CONSTRUCCIÓN DE LISTAS
 PROCESAMIENTO DE LISTAS
 TRATAMIENTO DE CADENAS
 TRATAMIENTO DE CADENAS CON VLISP
o FORMAS ESPECIALES
o FUNCIONES DEFINIDAS POR EL USUARIO
 DEFUN: NUEVAS FUNCIONES PARA LA
EXTENSIÓN DE LISP
 REALES A ENTEROS: TRUCAMIENTO O
REDONDEO
 FUNCIONES TRIGONOMÉTRICAS
 LAMBDA
 FUNCTION
 LOAD
3. ESTRUCTURAS DE CONTROL
o PREDICADOS GENERALES
o PREDICADOS ARITMÉTICOS
o OPERADORES LÓGICOS
o OPERADORES LÓGICOS BINARIOS
 Números Binarios
 Función LSH
 Función ~ (NOT lógico binario)
 Función LOGAND
 CONVERSIÓN ENTRE BINARIOS Y
DECIMALES (I)
 CONVERSIÓN ENTRE BINARIOS Y
DECIMALES (II)
 Función LOGIOR
 Función BOOLE
o PREDICADOS DEFINIDOS POR EL USUARIO
o ESTRUCTURAS CONDICIONALES
4. FUNCIONES RECURSIVAS E ITERATIVAS
o FUNCIONES RECURSIVAS
 EXTRACCIÓN DE LOS VÉRTICES DE UNA
POLILÍNEA
o FUNCIONES ITERATIVAS
 REPETICIÓN UN NÚMERO DETERMINADO
DE VECES
 ITERACIONES SOBRE ELEMENTOS DE UNA
SECUENCIA
 CICLOS DEPENDIENTES DE UNA
CONDICIONAL
o FUNCIONES DE MAPEADO SOBRE SECUENCIAS
 EXTRACCIÓN DE LOS VÉRTICES DE UNA
POLILÍNEA
Una solución más eficaz.
o ¿CUÁNDO RECURSIÓN Y CUÁNDO ITERACIÓN?

TERCERA PARTE:
ACCESO A LA BASE DE DATOS GEOMÉTRICA

 ACCESO A LA LISTA DE DATOS DE OBJETO


o EXTRACCIÓN DE LISTAS DE DATOS DE OBJETO
o TRANSFORMACIÓN DE LOS DATOS DE OBJETO:
 CONVERSIÓN DE POLILÍNEAS EN SPLINES
 PROGRAMA PL2SP.LSP
 EJEMPLOS DE LA UTILIZACIÓN DE ENTMAKE
o DEFINICIÓN DE BLOQUES
o DEFINICIÓN DE CAPAS
 SISTEMAS DE COORDENADAS
o EJEMPLO DE TRANSFORMACIÓN ENTRE SISTEMAS
DE COORDENADAS:
CONVERSIÓN DE POLILÍNEAS 3D EN 2D
Visual LISP

LISP fué inicialmente fue desarrollado como un lenguaje


interpretado, aunque las modernas versiones cuentan siempre
con un compilador que transforma el código fuente en lenguaje
de máquina optimizado. Esta compilación puede ejecutarse de
manera inmediata al cargar en el entorno de desarrollo el
código fuente del programa, lo que facilita el desarrollo al
disponer de una evaluación de manera inmediata. Para
acceder a este entorno, en el caso del Visual LISP, se teclea
desde la línea de comando de AutoCAD las instrucciones VLISP
ó VLIDE (esta última para compatibilidad con el Visual LISP de
la versión 14). Las instrucciones LISP se introducen para su
evaluación en una ventana especial conocida como la Consola
Visual LISP. Si no está a la vista, se puede abrir esta ventana
pulsando el botón de la barra de herramientas.

El cursor que aparece junto al símbolo _$ indica que le sistema


está listo para recibir las expresiones LISP del usuario. La
imagen anterior muestra el resultado de evaluar una expresión
usando funciones aritméticas. La evaluación se produce al
pulsar <INTRO>. Una vez impreso el resultado aparece de
nuevo el símbolo _$ indicando que el sistema está listo para
recibir una nueva expresión. Este ciclo que se desarrolla en el
intérprete se conoce como bucle de lectura-evaluación-
impresión (read-eval-print loop). Esto significa que el
intérprete lee lo que se ha tecleado, lo evalúa y entonces
imprime el resultado antes de quedar listo para la nueva
expresión. Al uso de la consola dedicaremos una sección
específica de este curso.

El entorno de desarrollo (IDE) Visual LISP cuenta además con


un Editor especializado y una serie de medios para la
depuración de los programas muy superiores a los que estaban
disponibles en el viejo AutoLISP.

No obstante, estas lecciones podrán ser seguidas utilizando


cualquier versión de AutoLISP. Se ha tratado de señalar
cuando se está hablando de funciones o modos de operación
propios del Visual LISP que noi están disponibles en el entorno
AutoLISP.

Hay otras implementaciones de LISP para uso general


disponibles muchas veces como software gratuito a través de
internet. Para más información se recomienda acceder a los
siguientes sitios WEB:

AutoCAD-AutoLISP info+tools Página AutoCAD/AutoLISP de


Reini Urban.

The Association of Lisp Users Página WEB de la Asociación


de Usuarios de LISP.

Un caso particular es el del Corman Common Lisp, para el


que Reini Urban ha implementado la posibilidad de su
ejecución desde el entorno AutoCAD y que pudiera señalar un
camino de desarrollo interesante para el futuro. También
existen utilidades para la transferencia de programas
AutoLISP-XLISP desarrolladas por Tony Tanzillo.

EL ENTORNO DE DESARROLLO VISUAL LISP

Visual LISP (VLISP) representa una renovación de LISP para


AutoCAD, actualizándolo para incluir prestaciones que ya son
normales en los modernos dialectos de LISP que se ajustan a
la normativa COMMON LISP. Aún sin llegar a ser totalmente
compatible con esta normativa, es significativo el incremento
de su potencia como lenguaje de programación.
Es particularmente útil la posibilidad que se incorpora para la
interacción con la jerarquía de objetos de la aplicación
mediante la interfaz ActiveX ™ Automation de Microsoft, y la
posibilidad de responder a eventos mediante la
implementación de funciones diseñadas como reactores.

Como herramienta de desarrollo se aporta un Entorno de


Desarrollo Integrado (IDE) que incluye un compilador y varias
utilidades para la depuración.

El IDE Visual LISP incluye:

 Comprobador de Sintaxis que reconoce secuencias


AutoLISP erróneas y el uso incorrecto de los argumentos
en llamadas a las funciones primitivas del lenguaje.
 Compilador de Ficheros que incrementa la velocidad de
ejecución y constituye una plataforma de distribución que
brinda seguridad al código fuente.
 Depurador de Fuentes, diseñado específicamente para
AutoLISP, que permite la ejecución paso a paso del
código fuente en una ventana mientras se observan
simultáneamente los resultados obtenidos en la pantalla
gráfica de AutoCAD.
 Editor de Programación que emplea la codificación por
color para LISP y DCL, así como otras características de
apoyo sintáctico.
 Formateo LISP automático que redistribuye las líneas de
código y las identa para facilitar la lectura de los
programas.
 Amplias características de Inspección y Vigilancia (Watch)
que permiten el acceso en tiempo real a los valores de las
expresiones y las variables, y que pueden ser empleadas
tanto para datos LISP como para objetos gráficos de
AutoCAD.
 Ayuda sensible al contexto sobre las funciones AutoLISP y
una ventana Apropos para búsqueda de nombres de
símbolos.
 Sistema de Administración de Proyectos que facilitan el
mantenimiento de aplicaciones con múltiples ficheros
fuente.
 Empaquetado de los ficheros AutoLISP compilados en un
único módulo de programa.
 Capacidad para guardar y recuperar la configuración del
Escritorio para reutilizar la distribución de ventanas de
cualquier sesión anterior de VLISP.
 Consola Visual LISP Inteligente que permite un nuevo
nivel de interacción del usuario, con funciones que
amplían las de la ventana de texto habitual de AutoCAD.

El Trabajo con Visual LISP y AutoCAD

VLISP posee su propia ventana de aplicación distinta de la de


AutoCAD, pero no puede ejecutarse de manera independiente.
Para acceder al IDE Visiual LISP, antes deberá haberse iniciado
una sesión de AutoCAD.

Iniciar Visual LISP

Como se dijo antes, Debe haberse iniciado una sesión de


AutoCAD. Esta sesión puede contener sólo un dibujo vacío, o
pueden estar abiertos dibujos cuyo contenido se desee
procesar.

Para activar el IDE VLISP tenemos tres opciones:

 Seleccionar del menú


Herramientas>AutoLISP>Editor Visual LISP
 Teclear en la línea de comandos: VLISP
Nota: Hemos encontrado al menos en una
versión localizada española que el comando
VLIDE no es reconocido por el sistema. La
Ayuda de esa misma versión aeñala como
alternativa el comando VISUALLISPIDE, que
tampoco es reconocido. En estos casos siempre
se puede recurrir al comando VLIDE, descrito en
el punto siguiente.

 La versión anterior de Visual LISP utilizaba con


los mismos fines el comando VLIDE, que sigue
siendo reconocido por la versión 2000. De
hecho, internamente la llamada de AutoCAD al
IDE Visual LISP se realiza mediante este
comando, que veremos aparecer en la línea de
comandos cada vez que se cambie a ese
entorno.

La Ventana de la Aplicación

Al iniciarse Visual LISP pasa a primer plano la siguiente


ventana de aplicación:

Puede seleccionar cada parte de la ventana para una breve


descripción

Barra de Menús
Asumimos que el lector está familiarizado con el uso de menús
desplegables en AutoCAD u otras aplicaciones. Sólo cabría
destacar que éstos menús son sensibles al contexto en que se
utilizan. Es decir, que algunas opciones que pueden estar
activas si se abre la ventana del Editor pueden no estarlas si el
foco se encuentra en la Consola o en la ventana de TRACE.

Las funciones de la aplicación se distribuyen entre los menús


desplegables de la siguiente manera

FILE
Creación de nuevos ficheros de programas abiertos para
su edición
Apertura de ficheros existentes
Guardar los cambios efectuados
Compilar aplicaciones Visual LISP
Imprimir los ficheros de programas
EDIT
Copiar y Pegar texto
Deshacer el último cambio en el Editor o la última función
ejecutada desde la Consola
Seleccionar texto en el Editor o la Consola
Comprobar el cierre de paréntesis
Recuperar funciones ejecutadas desde la Consola
SEARCH
Buscar y Reemplazar texto
Colocar marcadores de referencia (bookmarks) y navegar
en el texto utilizando estos marcadores.
VIEW
Buscar y mostrar el valor de variables y símbolos en su
código AutoLISP.
PROJECT
Trabaja con los proyectos y la compilación de programas
DEBUG
Establecer y quitar puntos de ruptura en los programas
Ejecutar programas paso a paso comprobando el estado
de las variables y el valor devuelto por las expresiones.
TOOLS
Establecer las opciones para el formateado del texto y
varias otras opciones del entorno, tales como la ubicación
de ventanas y barras de herramientas.
WINDOW
Organiza, abre y cierra ventanas en el IDE.
HELP
Pues eso, la Ayuda en línea.

Las Barras de Herramientas

Visual LISP dispone de cinco Barras de Herramientas que


pueden activarse/desactivarse desde el menú View>Toolbars...
que abre el siguiente diálogo:

Estas cinco Barras de Herramientas contienen las opciones u


comandos esenciales del IDE, facilitando el acceso a los
mismos.

Contiene las herramientas usuales de Crear Nuevo, Abrir,


Guardar, Imprimir, Cortar, Copiar, Pegar, Deshacer,
Rehacer y por último un acceso a la fución Apropos que
sirve para completar el texto buscando una correlación de
una subcadena con nombres de funciones, etc.

Incluye las funciones de Buscar, Buscar y Reemplazar.


Además una casilla de lista desplegable donde se guardan
los términos anteriormente buscados durante la sesión de
trabajo, lo que permite repetir una busqueda con más
facilidad, cosa que se hace con la herramienta situada a
la derecha de la casilla. Por último incluye una serie de
herramientas para navegar dentro del texto mediante
marcadores, que se introducen con la herramienta de la
izquierda, se avanza o retrocede con las dos siguientes y
se eliminan con la última.

La barra Tools (Herramientas) opera sólo con la ventana


del Editor activa. Sus funciones son, de izquierda a
derecha: cargar el código del Editor para su ejecución
desde la Consola, cargar sólo el código seleccionado,
comprobar la sintaxis de todo el contenido de la ventana
del Editor, o con la siguiente, comprobar sólo lo
seleccionado. Para formatear el texto se utilizan los
próximos dos botones, el primero para todo el editor y el
segundo sólo para el textoseleccionado. Los dos
siguientes botones sirven para marcar como coimentario
el texto seleccionado o para desmarcarlo. Y por supuesto,
el último se trata de la Ayuda en línea.

Los tres primeros botones de esta barra determinan la


acción al encontrar un punto de ruptura durante la
evaluación. El primero entra en las expresiones anidadas
posteriores al punto de ruptura, evaluándolas desde la
más interior. El segundo evalúa esta expresión y se
detiene antes de la siguiente para de nuevo decidir si se
quiere entrar a evaluar las expresiones anidadas. El
tercer botón continúa hasta el final de la función en curso
y entonces cuando se detiene de nuevo la evaluación.
El segundo trío de botones tene que ver con las acciones
a tomar cuando se produce una ruptura del flujo de
ejecución a causa de un error, o de que se alcance un
punto de ruptura prefijado dentro del programa. Aunque
es un tema que se explicará más adelante, cabe decir
ahora que estos estados de suspensión en la ejecución
del programa se utilizan para examinar los valores
asumidos por las variables, cambiarlos si es preciso, etc.
El primer botón (Continue) permite terminar con esta
pausa y continuar la ejecución normal del programa. El
segundo botón (Quit) permite abandonar el nivel de
evaluación actual (pueden superponerse varios ciclos de
evaluación si se producen varios errores durante la
depuración) y pasar al nivel de más arriba. Y el tercer
botón (Reset) pasa el control de la ejecución
directamente al nivel superior (Top Level).
El tercer grupo de botones incluye otras útiles
herramientas de depuración. El botón Toggle Breakpoint
permite añadir un nuevo punto de ruptura en el
programa, situado en la posición actual del cursor. El
segundo botón (Add Watch) da acceso al diálogo que
permite seleccionar un nombre de variable para observar
sus resultados durante la ejecución. Estos resultados se
exhiben en una ventana especial, la ventana Watch.
El botón Last Break resalta en la ventana del editor la
expresión que dio origen a la última ruptura. En caso de
error, de esta manera se detecta de inmediato dónde se
produjo éste.
El último botón no es realmente un botón de comando.
Sirve simplemente para indicar si la interrupción actual se
encuentra antes o después de la expresión.

El primer botón sirve para poner en primer plano la


ventana de aplicación de AutoCAD. El segundo botón abre
un menú donde podemos seleccionar la ventana del IDE
Visual LISP que deseamos poner en primer plano. Esto se
hace necesario pues podemos tener abiertas de manera
simultánea un gran número de programas y puede no der
fácil localizar aquélo que queremos.
El tercer botón traslada el foco a la Consola de Visual
LISP. El siguiente permite activar la característica de
Inspección (Inspect). Inspect permite examinar y
modificar objetos AutoLISP así como AutoCAD. La
herramienta Inspect crea una ventana separada para
cada objeto sometido a inspección.
La siguiente herramienta (Trace Stack) necesita
explicaciones que se salen del marco de esta
introducción. Baste decir que nos permite acceder a la
memoria de pila donde se guardan las llamadas a
función. Puede invocarse en un momento de suspensión
en la ejecución de un programa y permite mediante un
menú contextual acceder a datos relacionados con la
operación del programa.
El botón que le sigue (Symbol Service) está diseñado
para simplificar el acceso a las distintas utilidades de
depuración relacionadas con símbolos. Podemos resaltar
cualquier nombre de símbolo en el Editor y al pulsar
sobre este botón se abrirá la ventana Symbol Service
donde se muestra el valor vinculado. Este valor se puede
cambiar directamente en esta ventana. Además tiene una
barra de herramientas que permiten otros procesos con
el símbolo seleccionado.
El siguiente botón permite abrir la ventana Apropos que
describimos en detalle más adelante
Y por último un botón que permite acceder a la ventana
Watch.

La Consola Visual LISP


Aunque parecida a la ventana de texto de AutoCAD en el
hecho de que se puden introducir funciones para evaluarlas y
obtener el resultado en pantalla, la Consola es una
herramienta de mucha mayor potencia y existen varias
diferencias en su manera de operar que es importante tener
en cuenta.

Por ejemplo, para conocer el valor asociado a un símbolo no es


necesario, como en AutoCAD, precederlo de un signo de
admiración <!>. Basta teclear el nombre de la variable y
pulsar <INTRO>. En la Consola se emiten una serie de
mensajes de diagnóstico durante la ejecución de las funciones
y si se encuentra un error interrumpe la ejecución y habilita un
nuevo nivel de evaluación donde podemos ensayar cambios en
valores de variables y realizar pruebas para detectar los
problemas existentes. De producirse un nuevo error, se
habilita un nuevo nivel y así sucesivamente, hasta que
decidamos regresar al nivel superior. Las baras de
desplazamiento de la consola nos permiten revisar los
resultados anteriores.

Las prestaciones más importantes de la Consola se resumen a


continuación:

 Evaluar expresiones AutoLISP y mostrar el resultado


devuelto por dichas expresiones.
 Introducir expresiones AutoLISP en líneas múltiples
pulsando para el cambio de líne la combinación <CTRL>
+ <INTRO>. Pulsar sólo <INTRO> provocaría la
evaluación de la expresión tecleada hasta entonces.
 Evaluar múltiples expresiones a la misma vez.
 Copiar y transferir texto entre las ventanas de la Consola
y el Editor. La mayor parte de los comandos de texto
están disponibles también en la Consola.
 Recuperar expresiones tecleadas anteriormente en la
Consola, pulsando la tecla <TAB>. Pulsando esta tecla
varias veces se van recuperando las expresiones
anteriores. Para realizar el ciclo en sentido inverso puede
utilizarse la combinación <SHIFT> + <TAB>.
 La tecla <TAB> también permite realizar una búsqueda
de carácter asociativo a través del conjunto de
expresiones anteriores. Si se quiere buscar anteriores
expresiones de creación de variables bastaría teclear
(SETQ y entonces pulsar <TAB>, con lo que se iría
directamente a la última expresión tecleada que
comenzaba así. Para realizar el ciclo en sentido inverso
aquí también puede utilizarse la combinación <SHIFT> +
<TAB>.
 Pulsando <ESC> se elimina cualquier texto a
continuación del símbolo (prompt) del evaluador.
 Pulsando <SHIFT> + <ESC> abre una nueva Consola,
dejando el texto escrito en la ventana de Consola anterior
sin evaluar.
 Al pulsar el botón derecho del ratón en cualquier punto
de la Consola o tecleando <SHIFT> + <F10> abre un
menú contextual de funciones y opviones VLISP. Esto
facilita, por ejemplo, copiar y pegar texto en la línea de
comandos de la consola, buscar texto e iniciar las
utilidades de depuración VLISP.

La Consola Visual LISP en Entorno Multidocumento (MDI)

AutoCAD 2000 es capaz de abrir varios dibujos a la vez. Esto


se conoce como Interfaz Multidocumento (Multiple Document
Interface - MDI) en contraposición con la interfaz de
documento único de versiones anteriores*. Hay una única
ventana de Consola para todos los dibujos AutoCAD abiertos.
Cuando se utilizan las barras de desplazamiento para revisar
las expresiones y mensajes de la consola, los veremos todos,
aunque se hayan emitido para distintos dibujos. Esta es otra
diferencia en relación con la ventana de texto de AutoCAD, que
sólo es capaz de mostrar los comandosque corresponden al
dibujo activo. Dicho de otra manera, cada dibujo tiene su
propia ventana de texto, pero comparten una sóla Consola
VLISP. El cambio de contexto es automático al cambiar de
dibujo en AutoCAD. El dibujo activo en AutoCAD es siempre el
dibujo activo en VLISP. El nombre del dibujo activo aparece en la barra de título de la
aplicación.

El menú Contextual en la Consola

El pulsar el botón derecho del


ratón sobre cualquier lugar de la
ventana de la Consola abre un
menú contextual donde, según
esté o no seleccionado algún
texto, se habilitarán algunas de
las siguientes opciones:
Cut
Borra el texto seleccionado de la Consola y lo transfiere al
Portapapeles de Windows**.
Copy
Copia el texto seleccionado al Portapapeles de
Windows**.
Paste
Pega el contenido del Portapapeles de Windows en la
posición indicada por el cursor**.
Clear Console window
Vacía la Ventana de la Consola.
Find
Busca el texto especificado en la Consola.
Inspect
Abre el diálogo de la utilidad Inspect.
Add Watch
Abre la ventana de la utilidad Watch.
Apropos
Abre la ventana del Apropos.
Symbol Service
Abre el diálogo de la utilidad Symbol Service.
Undo
Deshace la última operación.
Redo
Anula el efecto del último Deshacer (Undo).
AutoCAD Mode
Transfiere toda las entradas a la línea de comandos de
AutoCAD para su evaluación.
Toggle Console Log
Copia la salida de la Consola a un archivo de registro (log
file).

Cómo utilizar la Consola para seguir este Curso:

La consola es la herramienta fundamental que utilizaremos


para seguir este curso. Encontraremos fragmentos de código
como éste (que fue ya citado en la Introducción):

(defun extents (plist)


(list (apply 'mapcar (cons 'min plist))
(apply 'mapcar (cons 'max plist))))

Podemos seleccionar dicho fragmento en nuestro browser


utilizando el ratón y copiarlo (<CTRL> + <V>), para después
pegarlo en la consola.
Hecho esto, pulsamos <INTRO> para que se evalúe el
contenido de la consola, con lo que ya estará definida la
función EXTENTS. Esta función requiere como argumento una
lista de puntos (entendiéndose por puntos una lista de tres
números reales representando las coordenadas X, Y, Z.
Supongamos que queremos utilizar la lista:

'((162.75 35.76 145.99)


(127.77 25.37 91.72)(139.355 1.40 0.088)
(242.12 59.34 121.08)(260.897 64.92 30.33)
(299.29 76.33 -55.96)(187.13 -9.28 235.85)
(313.10 -37.47 96.17))

Para ello la introducirámos en la consola (también podemos


copiar y pegar) una nueva expresión con el nombre de la
función y la lista como único argumento:

Y entonces, pulsando <INTRO> obtendremos el resultado


devuelto:
Obsérvese cómo los colores identifican los componentes de las
expresiones, rojo para los paréntesis, azul para las funciones,
verdeazul para los números reales. Una vez evaluada los
colores del texto cambian a negro, con lo que se identifica
fácilmente qué formas son las que está en condiciones de ser
evaluadas.

* AutoCAD 2000 puede configurarse para trabajar en modo de


documento único. Para ello se activa la variable de sistema
SDI. El valor por defecto de SDI es 0, cambiándolo a 1 se pasa
a trabajar en modo de documento único. Trabajando en este
modo, la ventana de texto de AutoCAD contendrá los
comandos emitidos para los sucesivos dibujos abiertos.

** Es posible realizar operaciones de cortar, copiar y pegar


entre la consola de VLISP y la ventana de texto de AutoCAD.

El Editor Visual LISP

Es más que un simple editor de texto. Será, en realidad,


nuestro lugar de trabajo habitual para la programación LISP
dentro de AutoCAD. Cada fichero de programa abierto tendrá
su propia ventana del Editor

Un programa en el editor tendrá más o menos este aspecto:


Codificación sintáctica por color

Lo que primero llama la atención la ventana del editor es el


color que adoptan los diferentes componentes del programa. El
editor identifica las distintas partes de un programa LISP y le
asigna distintos colores. Esto permite detectar a primera vista
elementos tales como nombres de función, números enteros o
reales y cadenas, distinguiéndolos de los nombres de funciones
y variables asignados por el usuario. Los errores
mecanográficos saltan así a la vista de manera inmediata.
Estos colores pueden personalizarse a gusto del usuario.

Controles de la Ventana del Editor


La ventana del Editor no posee Barras de Menús ni de
Herramientas. Al estar el foco situado en una ventana del
Editor se activarán las opciones de Menú y las Herramientas de
la ventana de la Aplicación que pueden usarse para
operaciones del Editor. Muchas de las opciones pueden
también ser ejecutadas desde el menú contextual que se abre
al pusar el botón derecho del ratón. Existe la posibilidad de
utilizar también las combinaciones de teclas rápidas usuales en
los editores de texto y además algunas combinaciones que se
utilizan para funciones exclusivas de este editor. En términos
generales, además de la codificación por color el editor ofrece
otras ayudas que facilitan grandemente el desarrollo de
programas. Algunas de estas utildades son:

 Comprobación del cierre de paréntesis


 Formateo del Texto
 Comentarios automáticos en los cierres de Expresiones

 Comentado y Descomentado automático de las líneas
seleccionadas
 Búsqueda y Sustitución de Texto
 Comprobación de la Sintaxis
 Carga de expresiones LISP para ser probadas.

En los próximos apartados pasaremos a exponer estas


funcionalidades:

 Barras de Herramientas
 Menú Contextual
 Teclas Rápidas

Copias de Seguridad
El Editor VLISP viene configurado para
hacer siempre copias de seguridad de
los ficheros. Estas copias de seguridad
se sitúan en el mismo directorio de la
fuente del programa usando el mismo
nombre y la extensión _ls. De esta
manera la copia de seguridad del fichero
caras.lsp se llamará caras._ls.
Para recuperar la última versión
guardada de un fichero se emplea la
opción Revert del menú Files.

Barras de Herramientas

Casi todas las funcionalidades del programa pueden accederse


desde iconos de las Barras de Herramientas. Los iconos que
aparecen agrisados cuando se activa la ventana del Editor no
son utilizables en este contexto. Las Herramientas utilizables para
operaciones en el Editor son:

Herramientas de
Edición
Búsqueda y
Marcadores
Revisión y
Formateo
Acceso a otras
Ventanas de la
Aplicación
Utilidades de
Depuración

Herramientas de Edición

Esta barra incluye las herramientas de edición acostumbradas en todo


programa Windows, además de una propia para completar nombres de
símbolos:

 Crear nuevo archivo.


 Abrir un archivo existente.
 Guardar el contenido de la ventana del editor.
 Imprimir
 Cortar Texto copiándolo al Portapapeles. *
 Copiar Texto al Portapapeles.*
 Pegar Texto desde el Portapapeles.*
 Deshacer la última operación.
 Rehacer lo deshecho en la operación anterior.
 Completar el nombre de un símbolo.

Completar Nombres de Símbolos


La Barra de Herramientas de Edición incluye
una herramienta para completar nombres de
Símbolos. Este Icono realiza una búsqueda
inteligente de nombres de símbolos definidos en
el sistema. Al teclear algunas letras del nombre,
el pulsar este icono inicia la búsqueda. Según la
cantidad de símbolos que pudieran
corresponder a los caracteres tecleados, puede
abrir un menú contextual en la posición del
cursor, abrir la ventana de resultados de
Apropos o abrir la ventana de inicio de
búsqueda de Apropos si fuera demasiado
extensas las opciones posibles (por ejemplo,
teclear una sóla letra inicial). A esta función
puede accederse desde el Menú
Search>Complete Word by Apropos... y
también con la combinación de teclas rápidas
<CTRL> + <SHIFT> + <BARRA
ESPACIADORA>

Herramientas de Búsqueda de Texto


Búsqueda de texto en múltiples ficheros
La búsqueda de una expresión puede realizarse tanto en el fichero
que se edita como en los ficheros del proyecto u otros grupos de
ficheros seleccionados. Desde el Menú se accede mediante
Search>Find o mediante las teclas rápidas <CTRL> + <F>.

La característica más
interesante de la
herramienta de
búsqueda del Editor
VLISP es su capacidad
de buscar en un grupo
de ficheros
seleccionados y mejor
aún, en los ficheros que
configuran un proyecto.
El resultado de la
búsqueda se muestra
en una ventana de
salida, llamada <Find
Output>, donde
aparecen completas las
expresiones donde se
emplea el término
buscado. Se puede
configurar la
búsqueda para que
inserte marcadores
(bookmarks) cada vez
que encuentre el
término buscado, de
Buscar y Reemplazar manera automática.
Para ello
Permite buscar y reemplazar texto en la ventana se deberá
activa del Editor. Se
marcar la casilla Mark
accede a esta opción desde el menú Search>Replace... (Teclas <CTRL>
Instances (Marcar
+ <H>). Instancias)
Memorización de búsquedas anteriores
Los términos utilizados en búsquedas anteriores se guardan en una lista
desplegable para su reutilización.
Inserción de Marcadores
Los marcadores permiten desplazarse dentro del texto de marcador en
marcador. Disponible en el menú Search>Bookmarks>Toggle Bookmark.
Hay problemas con los marcadores al usar la opción de formateo
automático del texto. (ver nota más abajo).

Herramientas de Revisión y Formateo


Carga de Expresiones LISP Seleccionadas
A medida que se escriben, las expresiones seleccionadas pueden
cargarse para ser evaluadas. Al ejecutarse la carga el foco pasa a la
ventana de la Consola VLISP. Disponible en el Menú como Tools>Load
Selection (<CTRL> + <SHIFT> + <E>)
Carga de la Ventana del Editor
Puede cargarse también para su
ejecución el contenido total
actual de la ventana del Editor y
no sólo las expresiones
seleccionadas. En ambos casos
se informará de los errores que
impidan la carga del programa,
aunque no de los errores
sintácticos que pudieran existir.
Desde el menú Tools>Load Text
in Editor (<CTRL> + <ALT> +
<E>)
Comprobación del Código Seleccionado
Comprueba automáticamente la corrección del código tecleado. Puede
comprobar todo el contenido de la ventana o sólo de las expresiones
seleccionadas. Los resultados de la comprobación pasan a la ventana
Build Output. En el ejemplo siguiente se ha suprimido intencionalmente
uno de los argumentos necesarios para ls función CONS, lo que es
detectado al ejecutar la comprobación. Esta opción está disponible
desde el menú Tools>Check Selection o con la combinación de teclas
<CTRL> + <SHIFT> + <C>.

Comprobación de la Sintaxis del Programa


El Editor posee una herramienta para comprobar la sintaxis del código
tecleado. El resultado pasa a una ventana llamada Build Output,
donde se señalan no sólo los errores evidentes sino determinadas
situaciones que deben ser manejadas con cierto cuidado. Aplicada la
comprobación de Sintaxis al código anterior obtendríamos la siguiente
advertencia:

Esto no impide la ejecución del programa y corresponde a una técnica


de programación perfectamente válida en LISP, donde una función
puede siempre ser empleada como dato. Esta opción se encuentra
también en el menú Tools>Check Text in Editor
(<CTRL> + <SHIFT> + <C>).
Formateo del Texto
El editor tiene opciones para formateo del texto, haciendo identaciones
de manera automática. El estilo de formateo puede ser personalizado en
cierta medida. Puede formatearse todo el contenido de la ventana o sólo
las líneas seleccionadas.
Nota: Aunque no lo hemos encontrado descrito en la documentación del
programa, cuando se formatea un texto que contiene marcadores, éstos
se trasladan al inicio del bloque seleccionado para su formateo. De
manera que si tenemos varios marcadores establecidos y formateamos
todo el contenido de la ventana del editor, perderemos los marcadores
quedando sólo uno en la primera línea de la ventana del editor, lo que
obviamente quita toda utilidad que pudiera tener esta función de
inserción de marcadores.Comentarios automáticos de cierre de
Expresión
Entre las opciones de formateo del texto, además de la identación se
incluye la posibilidad de incluir comentarios al cierre de los paréntesis
que indican a que función corresponden cuando dicha función abarca
varias líneas.
Comentado y Descomentado automático de las líneas seleccionadas
Los comentarios se destacan mediante el esquema de colores elegido
con fondo gris, pero además de ello, la inclusión de los caracteres <;;;>
al inicio de la línea se realiza mediante una opción del menú Edit>Extra
Commands>Comment Block y se quitan mediante Edit>Extra
Commands>Uncomment Block.
Si se estuvieran editando archivos DCL, los caracteres introducidos como
señal de comentario serán dos barras inclinadas <//>.
Ayuda
Esta barra de Herramientas incluye también el Icono para el Acceso a la
Ayuda en-línea.

Acceso a Otras Ventanas de la Aplicación

Estas Herramientas permiten Acceder a las ventanas de:

 Autocad
 Otras Ventanasde Visual LISP, incluyendo las de otros ficheros abiertos
para su edición.
 La Consola Visual LISP
 La ventana de Inspección de objetos LISP y AutoCAD.
 El Trace Stack, que guarda la memoria de pila de errores.
 El Symbol Service para la gestión de Símbolos.
 La Utilidad de Apropos.
 La Utilidad de Watch.

Utilidades de Depuración

Sólo tres de las herramientas se encuentran disponibles cuando está activa la


ventana del Editor:
 Insertar Punto de Ruptura.
 Añadir el símbolo seleccionado a la ventana de Watch.

 Resaltar en el editor el código que provocó la última interrupción en la


ejecución del programa.

* Es posible realizar operaciones de cortar, copiar y pegar


entre el Editor VLISP y la ventana de texto de AutoCAD.

Menú Contextual del Editor


El pulsar el botón derecho del ratón sobre cualquier
lugar de la ventana del Editor abre un menú
contextual donde, según esté o no seleccionado algún
texto, se habilitarán algunas de las siguientes
opciones:
Cut
Borra el texto seleccionado de la Consola y lo
transfiere al Portapapeles de Windows*.
Copy
Copia el texto seleccionado al Portapapeles de
Windows*.
Paste
Pega el contenido del Portapapeles de Windows
en la posición indicada por el cursor*.
Find
Busca el texto especificado en una o más
ventanas del Editor.
Go to Last Edited
Desplaza el cursor hacia la última posición
editada.
Toggle Breakpoint
Coloca un punto de ruptura en la posición del
cursor, o lo elimina en caso de que ya existiera
uno en dicha posición.
Inspect
Abre el diálogo de la utilidad Inspect.
Add Watch
Abre la ventana de la utilidad Watch.
Apropos
Abre la ventana del Apropos.
Symbol Service
Abre el diálogo de la utilidad Symbol Service.
Undo
Deshace la última operación.
Redo
Anula el efecto del último Deshacer (Undo).
Teclas Rápidas

Las siguientes combinaciones de teclas rápidas son


características específicas de este editor:

Comandos de ARCHIVO:
<CTRL> + <S>
Guarda el contenido de la ventana activa del Editor.
<CTRL> + <ALT> + <S>
Guarda el contenido de la ventana activa del Editor con
otro nombre de archivo.
<CTRL> + <SHIFT> + <S>
Guarda el contenido de todas las ventanas de Editor
abiertas.
<CTRL> + <F4>
Cierra la ventana de Editor activa.
<CTRL> + <P>
Imprime el contenido de la ventana de Editor activa.
Comandos de Edición:

<CTRL> + <Z>
Deshacer la última operación en el Editor. Soporta ilimitadas
operaciones de Deshacer, hasta la última ocasión en que se
guardó el contenido del Editor a disco. Una vez guardado es
imposible deshacer. En ese caso, si fuera necesario, habría que
recuperar la copia de seguridad mediante la opción Files>Revert.
<CTRL> + <V>
Pegar el contenido del Portapapeles en el lugar señalado por el
cursor.
<SUPR>
Borrar el Texto señalado o el carácter a la derecha del cursor.
<CTRL> + <A>
Seleccionar todo el contenido de la ventana del Editor.
<CTRL> + <M>
Abre un menú desplegable con las opciones de búsqueda del
cierre de paréntesis.
<CTRL> + <)>
Desplaza el cursor hasta el paréntesis de cierre correspondiente.
<CTRL> + <(>
Desplaza el cursor hasta el paréntesis de apertura
correspondiente.
<CTRL> + <SHIFT> + <)>
Selecciona la expresión hasta el paréntesis de cierre
corresondiente, resaltándola en vídeo inverso.
<CTRL> + <SHIFT> + <(>
Selecciona la expresión hasta el paréntesis de apertura
corresondiente, resaltándola en vídeo inverso.
<CTRL> + <E>
Abre un menú desplegable con una serie de comandos
adicionales:
 Identar Bloque de Texto.
 Suprimir la Identación de un
bloque de Texto.
 Identar hasta el nivel actual
 Añadir un prefijo a cada línea de
texto seleccionada.
 Añadir texto al final de cada línea
seleccionada.

 Marcar el bloque seleccionado


como comentario.
 Desmarcar como comentario el
bloque seleccionado.
 Guardar bloque de texto
seleccionado a un archivo.

 Convertir el texto seleccionado a


mayúsculas.
 Convertir el texto seleccionado a
minúsculas.
 Cambiar a mayúsculas la primera
letra de cada palabra.

 Insertar Fecha.
 Insertar Hora.
 Definir el formato de fecha y
hora.

 Ordenar alfabéticamente las


líneas seleccionadas.
 Insertar el contenido de un
archivo de texto en el lugar
indicado por el cursor

 Borrar desde la posición del


cursor hasta el final de la línea.

 Borrar espacios en torno a la


posición actual del cursor.

Comandos de Búsqueda:
<CTRL> + <F>
Buscar texto.
<CTRL> + <H>
Buscar y sustituir texto.
<CTRL> + <BARRA ESPACIADORA>
Completar una palabra por similitud a otras existentes en
la ventana actual del Editor. Cuando ya se ha tecleado un
nombre anteriormente, El editor VLISP facilita el
incorporarlo de nuevo al documento con sólo teclear los
caracteres iniciales. Si en el ejemplo de arriba
quisiéramos teclear de nuevo el nombre de la función
PosVert bastaría teclear Po y después pulsar <CTRL> +
<BARRA ESPACIADORA>. Si en lugar de encontrar
PosVert encontrara el nombre de variable pos, bastaría
con volver a pulsar <CTRL> + <BARRA ESPACIADORA>
cuantas veces fuera necesaro para llegar al texto
deseado.
<CTRL> + <SHIFT> + <BARRA ESPACIADORA>
Completar una palabra mediante APROPOS (buscando en
todo el conjunto de símbolos AutoLISP.
<ALT> + <.>
Poner o quitar marcador.
<CTRL> + <.>
Desplazarse al próximo marcador.
<CTRL> + <,>
Desplazarse al marcador anterior.
<CTRL> + <SHIFT> + <.>
Seleccionar hasta el próximo marcador.
<CTRL> + <SHIFT> + <,>
Seleccionar hasta el marcador anterior.

Comandos de Vistas:
<CTRL> + <SHIFT> + <I>
Abre una ventana de Inspección para introducir una
expresión.
<CTRL> + <SHIFT> + <T>
Abre la ventana de Inspección de Trace.
<CTRL> + <SHIFT> + <R>
Abre la ventana de Inspección para la última pila de
error.
<CTRL> + <SHIFT> + <S>
Abre una ventana de Inspección para un símbolo.
<CTRL> + <SHIFT> + <W>
Abre la ventana de Vigilancia (WATCH).
<CTRL> + <SHIFT> + <A>
Abre la ventana APROPOS para ayuda con expresiones
LISP.
<CTRL> + <SHIFT> + <B>
Muestra los puntos de interrupción actuales (en todos los
programas abiertos, no sólo la ventana activa).
<F6>
Abre la Consola Visual LISP y/o la pone en primer plano.
Comandos del Proyecto:
<CTRL> + <SHIFT> + <P>
Abre un proyecto existente.

Comandos de Depuración:
<CTRL> + <W>
Añadir una expresión a la ventana de Vigilancia (WATCH).
<F9>
Añade o suprime un punto de interrupción en la posición
actual del cursor.
<CTRL> + <SHIFT> + <F9>
Suprime todos los puntos de interrupción.
<CTRL> + <F9>
Busca y resalta el código que dio origen a la última
interrupción.

Herramientas de Desarrollo:
<CTRL> + <SHIFT> + <E>
Carga y evalúa el código seleccionado.
<CTRL> + <ALT> + <E>
Carga y evalúa el código contenido en la ventana activa
del Editor.
<CTRL> + <SHIFT> + <C>
Comprueba la sintaxis de las expresiones seleccionadas.
<CTRL> + <ALT> + <C>
Comprueba la sintaxis del código contenido en la ventana
activa del Editor.
<CTRL> + <SHIFT> + <F>
Formatea las expresiones seleccionadas.
<CTRL> + <ALT> + <F>
Formatea el código contenido en la ventana activa del
Editor.
Controles de Ventanas:
<ALT> + <F6>
Ajusta la ventana activa al espacio disponible en pantalla.
Comprobación del cierre de paréntesis
Las combinaciones de teclas <CTRL> + <(> y <CTRL> +
<)> permiten localizar el paréntesis de apertura o el
paréntesis de cierre respectivamente que corresponda a
partir de la posición donde se sitúa el cursor. Si se pulsa
simultáneamente <SHIFT> el texto quedará seleccionado
(en vídeo inverso).
Completar Texto
Cuando ya se ha tecleado un nombre anteriormente, El
editor VLISP facilita el incorporarlo de nuevo al
documento con sólo teclear los caracteres iniciales. Si en
el ejemplo de arriba quisiéramos teclear de nuevo el
nombre de la función PosVert bastaría teclear Po y
después pulsar <CTRL> + <BARRA ESPACIADORA>. Si
en lugar de encontrar PosVert encontrara el nombre de
variable pos, bastaría con volver a pulsar <CTRL> +
<BARRA ESPACIADORA> cuantas veces fuera necesaro
para llegar al texto deseado.
TIPOS DE DATOS

Antes de iniciarnos en la programación LISP, examinaremos


los datos con que este lenguaje opera. Las posibilidades de un
lenguaje de programación están directamente relacionadas con
la oferta de procedimientos y funciones y en sobre qué tipos
de datos pueden ellos operar.

Los tipos de datos originales son los ÁTOMOS y las LISTAS.


Los ÁTOMOS y las LISTAS son mutuamente excluyentes: un
objeto LISP es lo uno o lo otro. Con una excepción: la lista
vacía o NIL, que estudiaremos más adelante. Las listas se
construyen recursivamente a partir de átomos y/o listas.

Ejemplos:

ÁTOMOS LISTAS
a ()
juan (juan)
45 (a juan 45 z5mn)
((juan 45) a
z5mn
((z5mn)))

AutoLISP y Visual LISP utilizan además otros tipos de datos


que estudiaremos más adelante y que resultan necesarios para
la gestión de los dibujos en el entorno AutoCAD.

Tanto los átomos como las listas son expresiones válidas LISP
que el intérprete lee y evalúa. Las reglas para la evaluación de
ambos tipos de objetos se describen a continuación:

ÁTOMOS
Los átomos son las expresiones LISP más elementales.
Siempre tienen un nombre constituido por una secuencia
de caracteres y por ello se asemejan a las palabras de un
lenguaje. Los nombres de átomos se separan de otras
expresiones mediante espacios en blanco, paréntesis o
cambios de línea. Un átomo no es divisible. Como norma
general, todos los elementos que no sean una lista se
consideran átomos. Al recibir un átomo, el evaluador lisp
intenta determinar su valor. Este valor puede estar
representado por el nombre mismo del átomo, que
entonces consideraremos como una "constante" o puede
ser un objeto LISP distinto, en cuyo caso estaremos ante
un átomo "simbólico". Los átomos simbólicos se utilizan
dentro de los programas para almacenar valores, siendo
conocidos entonces como "variables".
LISTAS:
El nombre LISP viene de LISt Processing (Procesamiento
de Listas), indicando el mecanismo fundamental sobre el
que se desarrolla el lenguaje. Las listas LISP son grupos
de valores relacionados, separados por espacios e
incluidos entre paréntesis redondos "(" y ")". En LISP, el
orden de los elementos incluidos en una lista es
significativo. En esto se distingue el concepto de lista del
concepto de conjunto, en el que el ordenamiento de los
términos no tiene relevancia. Otra diferencia entre listas
y conjuntos reside en que en una lista pueden haber
términos repetidos, mientras que en un conjunto cada
término ocurre una sóla vez. Las listas proporcionan un
método eficaz para almacenar valores relacionados.
AutoCAD expresa los puntos 3D como una lista de tres
números reales donde el primer valor es el de la
coordenada X, el segundo la coordenada Y, el tercero la
coordenada Z. Esto indica que el orden de los términos
de una lista es significativo y en esto se diferencia del
concepto de conjunto. Además una lista admite términos
repetidos. Dentro de las posibles listas podemos
distinguir tres casos:
 Listas de un nivel
 Listas anidadas
 La lista vacía
TIPOS DE DATOS ESPECÍFICOS DE AUTOCAD:

 CONJUNTOS DE SELECCIÓN
Los conjuntos de selección son grupos compuestos por
uno o varios objetos (entidades). Las rutinas de AutoLISP
permiten añadir o suprimir de forma interactiva objetos
de los conjuntos de selección.
 NOMBRES DE ENTIDAD
Un nombre de entidad es un identificador numérico
asignado a los objetos de un dibujo. En realidad, se trata
de un puntero a un archivo mantenido por AutoCAD, en
el que AutoLISP puede encontrar el registro de la base de
datos de objetos. Este puntero suele cambiar de una
sesión de trabajo a otra.
 DESCRIPTORES DE ARCHIVO
Los descriptores de archivo son identificadores
alfanuméricos asignados a archivos abiertos por Visual
LISP. Cuando sea necesario que una función de Visual
LISP lea o escriba en un archivo, debe hacerse referencia
a su identificador.
 SUBRUTINAS, SUBRUTINAS EXTERNAS Y
FUNCIONES DE USUARIO
Funciones Nativas LISP o funciones externas cargadas
desde archivos compilados FAS ó VLX y archivos fuente
LISP.
 TABLAS DE PAGINACIÓN
 OBJETOS Y TIPOS DE DATOS ACTIVEX
Objetos propios y determinados formatos para los datos
que se pasan a los métodos ActiveX.

COMPROBACIÓN DEL TIPO DE DATO:

 FUNCIÓN TYPE
Devuelve el tipo de un elemento designado
(type elemento)

LISTAS Y CONSES
Por la importancia dentro de LISP del tipo de datos LISTA,
citamos in extenso el apartado 2.4 de CLTL2,

Un CONS es una estructura de información que contiene dos


componentes llamados el CAR y el CDR. La utilización
fundamental de los CONSES es como representación de
LISTAS.

Una LISTA se define recursivamente ya sea como la lista vacía o


como un CONS cuyo componente CDR es una lista. Una lista
es, por consiguiente, una cadena de CONSES enlazados por
sus componentes CDR y terminada por un NIL, la lista vacía.
Los componentes CAR de los conses son conocidos como los
elementos de la lista. Para cada elemento de la lista hay un
CONS. La lista vacía no tiene ningún elemento.

Para comprender mejor lo anterior, adelantaremos la mención


de la función básica de construcción de listas, que es la función
CONS (que se explica en el apartado FUNCIONES DE
CONSTRUCCIÓN DE LISTAS). Expresado en términos de
esta función, la anterior definición de una lista como una
cadena de CONSES sería:

Una LISTA se expresa mediante la escritura de los elementos


de la lista ordenados, separados por espacios en blanco
(caracteres de espacio, tabulador o retorno) y rodeados por
paréntesis. Por ejemplo:

(a b c) ;Una lista de tres símbolos


(2.0 (a 1) "*") ;Una lista de tres cosas
diferentes: un número real
;otra lista y un carácter asterisco
Nota: El código del ejemplo anterior ha sido
modificado para adaptarlo a las convenciones de
AutoLISP/Visual LISP
La lista vacía puede por lo tanto ser expresada como (), ya
que se trata de una lista sin elementos.

Una lista punteada (dotted list) es una cuyo último cons no


tiene NIL como su CDR, sino otro objeto de información (que
tampoco será un CONS, ya que entonces el CONS
anteriormente mencionado no hubiera sido el último CONS de
la lista).

Tal tipo de lista se conoce como "punteada" debido a la


notación especial que se emplea en ella: los elementos de la
lista se escriben, al igual que antes, entre paréntesis, pero ...
antes del paréntesis derecho se escriben un punto (rodeado
por espacios en blanco) y entonces el CDR del último CONS.
Como caso especial, un CONS aislado se expresa escribiendo el
CAR y el CDR entre paréntesis y separados por un punto
rodeado de espacios (par punteado). Por ejemplo:

(a . 4) ;Un cons cuyo car es el símbolo a


;y cuyo cdr es un entero
(a b c . d) ;Una lista punteada con tres elementos
cuyo cons final
;tiene el símbolo d en su cdr

Con frecuencia se utiliza el término lista para hacer referencia


tanto a verdaderas listas como a listas punteadas. Cuando
resulta importante la distinción, se utilizará el término
"verdadera lista" para referirnos a una lista terminada por NIL.
La mayoría de las funciones que se dice operan sobre listas
esperan recibir verdaderas listas como argumento.

Por ejemplo, si en el ejemplo de concatenación de CONSES anterior el


elemento de la derecha del último CONS no hubiera sido NIL (lo que
hubiera incluido un par punteado en la lista), el pasar esta lista como
argumento a una función de tratamiento de listas hubiera producido
en determinados casos un error:
A veces se utiliza el término árbol para referirse a algunos
CONS y todos los demás CONSES accesibles a él de manera
transitiva a través de enlaces de CAR y CDR hasta alcanzar
objetos NO-CONS; éstos NO-CONSES son conocidos como las
hojas del árbol.

Representación de LISTAS como árboles.


Tomado de: Cortés y Sierra, LISP. Editorial Marcombo,
Barcelona. 1987.

Las listas, las listas punteadas y los árboles no son tipos de


datos mutuamente excluyentes; no son más que puntos de
vista útiles en torno a las estructuras de CONSES. Hay aún
otros términos tales como LISTA DE ASOCIACIÓN. Ninguno de
éstos son verdaderos tipos de datos LISP.
Los CONSES constituyen un tipo de dato, y NIL es el único
objeto de tipo NULL. El tipo de dato LISTA significa en LISP la
unión de los tipos de datos CONS y NULL, y por ello engloba
tanto las listas verdaderas como las listas punteadas.

FORMATOS DE LISTAS:

Además de lo apuntado más arriba, es conveniente distinguir


entre los siguientes tres formatos de listas:
 LISTAS DE UN NIVEL
Contienen sólo átomos.
 LISTAS ANIDADAS
Contienen a su vez otras listas que se dicen ‘anidadas’.
Estas listas anidadas suelen ser conocidas como sub-
listas. El número de niveles de anidación no está
restringido ni en su profundidad ni en su complejidad. El
nivel más externo lo llamamos nivel superior o primer
nivel. A los elementos que conforman este nivel los
llamamos elementos de primer nivel.
 LISTAS DE ASOCIACIÓN (A-LIST)
Son listas anidadas que se utilizan con frecuencia como
estructura de datos en LISP. Una A-LIST es una lista de
pares (CONSES) en que cada par constituye una
asociación. El CAR de uno de estos pares se conoce como
la CLAVE y el CDR constituye el DATO. Una ventaja de la
representación como A-LIST es la posibilidad de
incrementarla añadiéndole nuevas entradas al principio
de la lista. Más aún, como la función de búsqueda ASSOC
recorre la A-LIST de manera ordenada, las nuevas
entradas pueden "enmascarar" las más antiguas de
manera que la información puede ser modificada de
forma no destructiva.
 LA LISTA VACÍA (NIL)
Como se dijo antes, es la que no contiene ningún
elemento. Se indica como () y recibe un nombre
particular, el de NIL.
La lista vacía tiene un status peculiar en LISP. Es a la vez
un átomo y una lista. Como símbolo representa el valor
lógico de FALSO:

_$ ()
nil
_$ (atom nil)
T
_$ (listp nil)
T
 CIERTO Y FALSO
Su negación (not nil) sería el símbolo T que representa la
condición lógica de CIERTO.
El símbolo T es también una constante en el sentido de
que sólo se representa a sí mismo.
En Visual LISP, nil y T así como otros elementos que
incluyen los operadores aritméticos (+, -. etc.) son
símbolos protegidos. Cualquier intento de asignarle otro
valor producirá un mensaje de advertencia:

Este mensaje aparece como la opción por defecto y pide


si se desea entrar en un bucle de interrupción (break
loop). Si se escoge No, el valor del símbolo es
modificado. Si se selecciona Sí, se interrumpe el
procesamiento y se entra en un ciclo de interrupción de
Visual LISP, lo que se conoce por el aspecto del símbolo
del evaluador, por ejemplo: _1_$.

El control se ha trasladado a la consola de Visual LISP.


Para asignar valor al símbolo y continuar el
procesamiento se deberá pular el botón Continuar en la
barra de herramientas de Visual LISP. Para abortar el
procesamiento se pulsará Reset .

VALORES DEVUELTOS POR LA FUNCIÓN TYPE

(type dato)

La función TYPE permite examinar el tipo a que corresponde


un dato determinado. Los datos que evalúan como NIL (por
ejemplo átomos simbólicos no vinculados a un valor)
devuelven nil.
El valor devuelto para cada tipo de dato será alguno de los
átomos que se describen a continuación:

Tipos LISP:

INT
Números Enteros
REAL
Números de Coma Decimal Flotante
LIST
Listas
STR
Cadenas
SYM
Símbolos

Tipos AutoCAD:

ENAME
Nombres de entidades
FILE
Descriptores de archivo
PAGETB
Tablas de Paginación de Funciones
PICKSET
Conjuntos de selección
SUBR
Funciones AutoLISP internas o funciones cargadas desde
archivos (FAS o VLX) compilados.
USUBR
Funciones de usuario cargadas desde ficheros fuente LSP.
EXRXSUBR
Aplicaciones ObjectARX Externas.

Tipos ActiveX:

SAFEARRAY
Matriz del tipo Safearray. Las matrices que se pasan a los
métodos ActiveX deben ser del tipo safearray. Estas
matrices son seguras (safe) porque no es posible el
asignar de manera accidental valores fuera de los límites
de la matriz provocando una excepción de datos.
VARIANT
Datos del tipo Variant. Los Variant son en esencia
estructuras que se auto-definen y que pueden contener
diferentes tipos de datos. Por ejemplo, cadenas, enteros
y matrices pueden ser representados por Variants. Junto
a los datos se guarda la información que identifica el tipo
de dato. Esta característica de auto-definición hace de los
Variant útiles medios para pasar parámetros a los
servidores ActiveX.
VLA-object
Objetos ActiveX

FUNCIONES

Una vez conocidos los tipos de datos pasaremos a estudiar


cómo utilizarlos. LISP es un lenguaje de programación
funcional, es decir que suministra funciones para la
manipulación de los datos.

Los programas LISP se estructuran como formas y funciones.


Según Cortés y Sierra*,

una FORMA es "una expresión simbólica en posición de ser


evaluada. El cómputo de valores, en LISP, se realiza
simplemente mediante la evaluación de una FORMA. Todas las
FORMAS tienen valor, sean estas constantes numéricas,
átomos literales o expresiones simbólicas. Uno de los errores
más típicos al programar en LISP es el de tratar de evaluar
una FORMA que no tiene valor. El valor de una FORMA es el
resultado de evaluarla...
Hay que remarcar que los términos expresión simbólica y
FORMA pueden aplicarse a la misma entidad; su aplicación
dependerá del contexto. Una LISTA puede considerarse como
un dato, como una expresión simbólica o considerarse como
parte de un procedimiento; la misma LISTA puede ser
considerada como una FORMA" .

Las formas se evalúan (en relación con determinado contexto)


para producir valores y efectos colaterales. Las funciones se
invocan aplicándolas a argumentos. (Guy L. Steele Jr.,
Common Lisp the Language, capítulo 5). En el entorno
Visual LISP se identifican como FORMAS las funciones de
usuario definidas dentro de un fichero fuente LSP. Al cargar un
programa se recibe la confirmación de la carga exitosa del
mismo mediante el siguiente mensaje:

Las funciones son conocidas en matemáticas. Mediante ellas se


describe algún tipo de relación entre un grupo de valores. La
adición y la multiplicación son funciones simples. Una función
más compleja sería: f(x)=3x2+2x-15
Esta última función también describe una regla de cálculo o
algoritmo. Las funciones realizan los cálculos (en términos
LISP, evaluación) utilizando para ello sus argumentos y
devuelven un resultado. A iguales argumentos corresponderán
iguales resultados.
Según Cortés y Sierra**:

En LISP, los programas se construyen a partir de la


composición de funciones. Esto permite que tales programas
expresen sus propósitos más claramente que los programas
convencioneles, y que resulten más fáciles de entender y de
mantener, además de ser más fáciles de construir.

AutoCAD identifica sus funciones LISP primitivas como el tipo


de dato SUBR, mientras que las formas definidas a partir de
ellas pertenecen al tipo de dato USUBR o "sub-rutina de
usuario" La función typedevuelve los tipos correspondientes.
La llamada a una función toma la forma de una lista cuyo
primer elemento es un átomo simbólico que representa a la
función llamada. El resto de los elementos de esa lista pueden
ser átomos y otras listas. Estas sublistas se consideran
también llamadas a funciones y se evalúan para que su valor
resultante pueda ser pasado como argumento a la función que
las contiene.

FUNCIONES PRIMITIVAS

Describimos una serie de funciones como ‘primitivas’ en el


sentido de que están definidas en la norma del lenguaje, para
distinguirlas de las funciones creadas por el usuario a partir de
aquéllas. En LISP se llama a una función mediante la siguiente
sintaxis:
(NOMBREFUNCION <Argumento_1> ... <Argumento_n>)
La suma de dos números sería (+ 5 1)
Para evaluarla LISP procede de la siguiente manera:
a. Lee la expresión completa (+ 5 1)
b. La interpreta como una llamada a una función y la identifica
como SUMAR <+>
c. Interpreta 5 como primer argumento y 1 como segundo. El
paréntesis de cierre le indica que no hay más argumentos.
d. La función <+> se evalúa para 5 y 1, devolviendo 6 como
resultado, que a falta de otro destino es impreso en pantalla.
Programar LISP significa llamar a funciones. Básicamente esto
se hace usando el tipo de dato LISTA. Cualquier lista que no
tenga otra interpretación como forma especial se considerará
una llamada a una función, donde el primer término se tomará
como el nombre de la función y el resto como sus argumentos.
Las listas de llamadas a función pueden estar anidadas, es
decir, que una llamada a función se puede estar utilizando
como argumento en otra lista que corresponda a una llamada
a otra función. El resultado de la evaluación de cada nivel de
anidación es devuelto al nivel de superior, hasta llegar al nivel
más alto, cuyo valor devuelto se imprimiría en la pantalla de
texto o la línea de comandos.

ARGUMENTOS FUNCIONALES

De lo expuesto más arriba se concluye que en LISP una


función es además un objeto de datos que puede ser
suministrado a otra función como argumento. Esta posibilidad
contribuye a la facilidad con que LISP se puede adaptar a las
necesidades de cualquier programa mediante la incorporación
de nuevas funciones que en su comportamiento resultan
idénticas a las primitivas.

Un programa que admite funciones como datos debe también


suministrar alguna manera de invocarlas. Esto se logra en
Visual LISP mediante la función APPLY.

APPLY
(apply función lista-args)

APPLY, como su nombre en inglés indica, aplica una función


(que recibe como primer argumento) a una lista de
argumentos. La función puede ser un objeto de código
compilado, una expresión-lambda, o un símbolo. En este
último caso se utiliza el valor funcional global de dicho
símbolo, aunque éste no puede ser una forma especial.
_$ (apply '+ '(2 4 6))
12
_$ (apply '(lambda (x y z)(+ x y z)) '(2 4 6))
12
_$ (apply 'quote '(2 4 6))
; error: bad QUOTE syntax: ((QUOTE 2) (QUOTE 4)
(QUOTE 6))
_1$
; reset after error

Obsérvese el error provocado por utilizar la forma especial


QUOTE.

Otras muchas funciones LISP requieren argumentos


funcionales. Entre las de uso más frecuente están las funciones
de mapeado. MAPCAR, por ejemplo toma dos o más
argumentos: una función y una o más listas (tantas como
parámetros requiera la función) y aplica la función
sucesivamente a los elementos de cada lista, devolviendo una
lista con los resultados.

_$ (mapcar '+ '(1 2 3) '(10 100 1000))


(11 102 1003)

Otras muchas funciones admiten funciones como argumentos.


Entre las más utilizadas tenemos VL-SORT y VL-REMOVE-IF.
La primera es una función de ordenación de uso general.
Requiere una lista y un predicado, y devuelve una lista
ordenada pasando sus elementos dos a dos al predicado.

_$ (vl-sort '(3 2 1 3) '<)


(1 2 3)

VL-REMOVE-IF acepta una función y una lista, y devuelve


todos los elementos de la lista para los cuales la función
devuelva NIL (falso).

_$ (vl-remove-if 'numberp '("a" 3 4 "c" 5 "d" "e"))


("a" "c" "d" "e")
El programar nuevas funciones utilitarias que aceptan
argumentos funcionales es una parte importante del estilo de
programación funcional que caracteriza a LISP.

FUNCIONES ARITMÉTICAS BÁSICAS

Las funciones aritméticas que tienen como argumento un


número devuelven distintos valores dependiendo de que el
argumento proporcionado sea un número entero o real. Si
todos los argumentos son enteros, el valor devuelto será
entero. Por el contrario, si alguno o todos los argumentos son
reales, el valor devuelto será un número real. Por ejemplo:
(/ 12 5) devuelve 2, mientras que (/ 12.0 5) devuelve 2.4

Los argumentos de una función aritmética no son


necesariamente números. Cualquier otra función que devuelva
un número como resultado es admisible como argumento de
una función aritmética. Ejemplo (* (+ 1 5)(- 20 10))

FUNCIONES BÁSICAS DE TRATAMIENTO DE CADENAS

Para el tratamiento de cadenas tenemos funciones que


permiten unificar cadenas diferentes, extraer subcadenas de
una cadena mayor, determinar cuántos caracteres hay en una
cadena y transformar los caracteres a mayúsculas o
minúsculas. El predicado WCMATCH permite determinar la
semejanza de cadenas utiliando comodines.

FUNCIONES BÁSICAS DE ACCESO A LISTAS

Una lista es una manera de representar un conjunto de átomos


y de otras listas. Una lista tiene la forma de un paréntesis de
apertura "(" seguido de una serie de átomos o listas, seguido
por otro paréntesis de cierre ")". De manera que cualquier
cosa encerrada entre paréntesis será considerada una lista.
Una lista pasada al evaluador LISP será tratada como una
expresión simbólica (S-expresión), es decir, una llamada a
función y se considerará el primer término de la lista como el
nombre de la función. Para que una lista sea tratada como
dato y no como una expresión simbólica debe estar contenida
en la forma especial QUOTE. Las funciones básicas cuya
comprensión es imprescindible para el acceso a la información
contenida en listas se pueden reducir a cuatro: QUOTE, CAR,
CDR, y NTH.

FUNCIONES DE CONSTRUCCIÓN DE LISTAS

Con las funciones del epígrafe anterior podemos descomponer


listas, accediendo a suscomponentes a distintos niveles de
anidación. Existen otras funciones que podemos utilizar para
componer nuevas listas a base de elementos individuales, ya
sean átomos u otras listas. CONS, LIST y APPEND son funciones
que construyen listas por distintos procedimientos y con
resultados diversos, por lo que es necesario distinguirlas bien.

FUNCIONES ARITMÉTICAS BÁSICAS

Cada una de las funciones incluidas en este apartado requiere


que sus argumentos sean todos números. El pasarle un
arguménto no numérico provocará un error. Operan tanto
sobre números enteros como sobre números reales, realizando
los ajustes pertinentes cuando dichos argumentos fueran de
diferente tipo.

+ (suma)
(+ [número número] ...)
Si proporciona sólo un argumento número, esta función
devuelve el resultado de sumarlo a cero. Ningún
argumento, devuelve 0.
_$ (+ 1 2 3)
6
_$ (+ 1.0 2 3)
6.0
- (resta)
(- [número número] ...)
Si utiliza más de dos argumentos número, esta función
devuelve el resultado de restar del primer número la
suma de todos los números, desde el segundo hasta el
último. Si sólo utiliza un argumento número, la función
devuelve el valor resultante de restar número a cero.
Ningún argumento, devuelve 0.
_$ (- 10 1 2 3)
4
_$ (- 10 1 2.0 3)
4.0
* (multiplicación)
(* [número número] ...)
Si proporciona sólo un argumento número, esta función
devuelve el resultado de multiplicarlo por uno. Ningún
argumento, devuelve 0.
_$ (* 1 2 3)
6
_$ (* 1 2 3.0)
6.0
/ (división)
(/ [número número] ...)
Si utiliza más de dos argumentos número, esta función
divide el primer número por el producto de todos los
números del segundo al último y devuelve el cociente
final. Si proporciona sólo un argumento número, esta
función devuelve el resultado de dividirlo por uno. Ningún
argumento, devuelve 0.
_$ (/ 30 2 4)
3
_$ (/ 30 2.0 4)
3.75
1+ (incremento)
(1+ número)
Devuelve el argumento aumentado (incrementado) en 1
_$ (1+ 6)
7
1- (decremento)
(1- número)
Devuelve el argumento reducido (decrementado) en 1
_$ (1- 6)
5

FUNCIONES DE ACCESO A LISTAS

Existen tres funciones básicas para acceder a los términos de


una lista: CAR, QUOTE y CDR.

Funciones CAR y QUOTE:


CAR
CAR admite un único argumento que debe ser una lista o
una expresión cuyo valor sea una lista y devuelve el
primer elemento de dicha lista. Como LISP siempre
interpreta una lista como una llamada a una función,
necesitamos una manera de pasar una lista a CAR sin que
LISP trate de procesarla como llamada a función.
QUOTE
Con este objetivo se suministra la función QUOTE . Una
lista dentro de una función QUOTE no se tratará de
evaluar como llamada a una función. La llamada a la
función QUOTE se puede abreviar utilizando el signo
apóstrofe < ’ >.

Funciones CDR y NTH:


CDR
CDR recibe también una lista y devuelve el resto de la
lista después de eliminar el primer elemento (CAR
lista). Por lo tanto puede contemplarse como la función
complementaria de CAR. Una manera de interpretar una
lista en LISP es como la conjunción de un CAR y un CDR.
Se podrá acceder a cualquier término de una lista
mediante los anidamientos apropiados de CAR y CDR.
 (CAR lista) ;primer término
 (CAR (CDR lista)) ;segundo término
 (CAR (CDR (CDR lista))) ;tercer término
y así sucesivamente... Para simplificar las expresiones se
permite crear nombres compuestos para anidamientos de
CAR y CDR de la siguiente manera:
 comenzando por una primera letra C
 una letra A por cada CAR o una D por cada CDR
 terminando con una letra R

 así (CAR (CDR (CDR lista))) sería lo mismo que


(CADDR lista).

De esta manera podemos construir hasta 28 funciones


distintas para acceso a listas. Según Johnson* estas 28
concatenaciones se pueden dividir en cuatro grupos,
tomando como base la profundidad a que pueden acceder
en listas anidadas en varios niveles.
Funciones de acceso al nivel superior:

Funciones de acceso al segundo nivel de anidamiento:


Funciones de acceso al tercer nivel de anidamiento:

Funciones de acceso al cuarto nivel de anidamiento:


Por supuesto, que estas funciones pueden a su vez
combinarse para acceder a niveles aún más profundos de
anidación.
NTH
NTH permite simplificar estas expresiones para listas muy
largas:
 (nth n lista)

Donde el argumento n es el número del elemento que


debe devolverse (cero es el primer elemento). Si n es
mayor que el número de elemento mayor de la lista, NTH
devuelve nil .

EJEMPLOS:

De lo antes expuesto resultaría:

_$ (car '(a b c)) ;Caso de una lista


normal
A
_$ (cdr '(a b c))
(B C)
_$ (cddr '(a b c))
(D)
_$ (cdddr '(a b c)) ;el último objeto
es una lista vacía
nil
_$ (car '(a b . c)) ;Caso en que la
lista termina en un par punteado
A
_$ (cdr '(a b . c))
(B . C)
_$ (cddr '(a b . c))
C ;el último objeto
de la lista no es un CONS
_$ (cdddr '(a b . c)) ;lo que provoca
un error
; error: bad argument type: consp C
_1$

FUNCIONES DE CONSTRUCCIÓN DE LISTAS

La función CONS

CONS es el constructor de lista básico. Construye listas


insertando elementos en listas existentes (incluso vacías). El
nuevo elemento ocupa el primer lugar de la lista (CAR lista).

(cons nuevo_elemento lista)

El nuevo_elemento puede ser un átomo o una lista.


La función CONS también acepta un átomo en lugar del
argumento lista, en cuyo caso construye una estructura
denominada par punteado. El CDR de un par punteado es el
segundo término ya extraído de la lista, es decir, sin los
paréntesis.
Ejemplo: (cons 'a 'b) devuelve (A . B) y (cdr '(a . b))
devuelve B.

Debe tenerse en cuenta que CONS, al igual que casi todas las
funciones LISP es una función no destructiva, es decir que no
altera los argumentos que recibe. Así que para conservar la
lista con el nuevo primer elemento, será necesario utilizar
SETQ. La función más general para incorporar nuevos
elementos a una lista sería entonces:

(setq nombre_lista (cons nuevo_elemento


nombre_lista))

donde nombre_lista sería el símbolo asociado al objeto lista.


la función opera aunque nombre_lista no hubiera sido
previamente asociado a una lista, pues no debemos olvidar
que un símbolo no asociado se evalúa como NIL, que es, a su
vez, equivalente a una lista vacía.
Ejemplo:

_$ (cons 'a 'b)


(A . B)
_$ (cons 'a (cons 'b (cons 'c '())))
(A B C)
_$ (cons 'a '(b c d))
(A B C D)

Funciones de ejemplo usando CONS/CAR/CDR

La función LIST
(list expr ...)

LIST Recupera cualquier número de expresiones y las


combina en una lista. Admite tanto átomos como listas. Si uno
de los agumentos es una lista vacía NIL aparecerá como uno
de los términos de la lista.
Ejemplo:

_$ (list 3 4 'a (car '(b . c)) (+ 6 -2))


(3 4 A B 4)
_$ (list (list 'a 'b) (list 'c 'd 'e))
((A B) (C D E))

La función APPEND
(append lista lista ...)

APPEND Se utiliza para combinar listas. Elimina el primer nivel


de anidación de cada una de las listas que recibe para
combinar todas en una misma lista. Si una de las listas que
recibe está vacía esta lista no aparecerá en la lista resultante.
Ejemplo:

_$ (append '(a b c) '(d e f) '() '(g))


(A B C D E F G)
Una muestra de la utilización de APPEND para eliminar las
sublistas vacías:

_$ (apply 'append '(("M") ("a") nil ("r") ("i") nil))


("M" "a" "r" "i")

OTRAS FUNCIONES PARA TRATAMIENTO DE LISTAS

REVERSE devuelve una lista con sus elementos invertidos

(reverse lista)

Opera únicamente sobre el primer nivel de anidación. Las listas


anidadas a otros niveles no son afectadas. Suele utilizarse
para volver a su orden original los objetos en listas construidas
mediante CONS.

_$ (reverse '("M" "a" "d" "r" "i" "d"))


("d" "i" "r" "d" "a" "M")

LENGTH devuelve un número entero que indica el número de


elementos de una lista

(length lista)

Sólo cuenta elementos en el primer nivel de anidación. No se


consideran los elementos incluidos en sub-listas.

_$ (length '((0 . "CIRCLE")(8 . "0")(10 242.426


157.686 0.0)(40 . 27.7503)))
4

ASSOC

(assoc elemento lista_asoc)

Busca una lista de asociaciones de un elemento y devuelve la


entrada asociada de la lista
_$ (assoc 10 '((0 . "CIRCLE")(8 . "0")(10 242.426
157.686 0.0)(40 . 27.7503)))
(10 242.426 157.686 0.0)

SUBST

(subst elemento_nuevo elemento_antiguo lista)

Busca un elemento antiguo en una lista y devuelve una copia


de ésta con un elemento nuevo en lugar de cada aparición del
elemento antiguo.

_$ (subst nil "d" '("M" "a" "d" "r" "i" "d"))


("M" "a" nil "r" "i" nil)
_$

FUNCIONES BÁSICAS DE TRATAMIENTO DE CADENAS

Las funciones básicas de tratamiento de cadenas aquí


expuestas son las incluidas con AutoLISP nativo. A ellas se
unen una serie de funciones adicionales suministradas con
Visual LISP y los API específicos de las aplicaciones soportadas
sobre AutoCAD como AutoCAD MAP.

ASCII
Devuelve el código ASCII (un número entero) del primer
carácter de una cadena
_$ (ascii "Madrid")
77
CHR
Devuelve el carácter que corresponde al código ASCII (un
número entero) que se ler pasa como argumento
_$ (chr 77)
"M"
STRCAT
(strcat cadena1 [cadena2] ...)
Devuelve una cadena que es la concatenación de varias
cadenas
_$ (strcat (chr 77) "adrid")
"Madrid"
STRLEN
(strlen [cadena] ...)
Devuelve un número entero que indica la cantidad de
caracteres de una cadena
_$ (strlen "Madrid")
6
SUBSTR
(substr cadena inicio [longitud])
Devuelve una subcadena de una cadena
_$ (substr "Madrid" 3 2)
"dr"
STRCASE
(strcase cadena [cuál])
Devuelve todos los caracteres alfabéticos de una cadena
en mayúsculas o minúsculas
_$ (strcase "Madrid")
"MADRID"
_$ (strcase "Madrid" T)
"madrid"
WCMATCH
(wcmatch cadena patrón)
Realiza búsquedas con patrones de comodines en una
cadena
_$ (wcmatch "Madrid" "?a?r*")
T
_$ (wcmatch "Madrid" "?d?r*")
nil
READ
(read [cadena])
Devuelve el primer átomo o la primera lista contenida en
una cadena
_$ (read "Madrid es una ciudad")
MADRID
_$ (read (strcat "(" "Madrid es una ciudad" ")"))
(MADRID ES UNA CIUDAD)

FUNCIONES VISUAL LISP DE TRATAMIENTO DE CADENAS


(vl-prin1-to-string object)
Devuelve la representación como cadena de cualquier
objeto LISP tal como si fuera la salida de la función PRIN1
_$ (setq file_id (open "test.tmp" "w"))
#<file "test.tmp">
_$ (vl-prin1-to-string file_id)
"#<file \"test.tmp\">"
(vl-princ-to-string object)
Devuelve la representación como cadena de cualquier
objeto LISP tal como si fuera la salida de la función
PRINC
_$ (setq file_id (open "test.tmp" "w"))
#<file "test.tmp">
_$ (vl-princ-to-string file_id)
"#<file test.tmp>"
(vl-string->list string)
Convierte una cadena en una lista de códigos de carácter
numéricos ASCII
_$ (vl-string->list "Madrid")
(77 97 100 114 105 100)
(vl-string-elt string position)
Devuelve la representación ASCII del carácter situado en
la posición especificada en una cadena. El primer carácter
ocupa la posición cero
_$ (vl-string-elt "Madrid" 0)
77
_$ (chr 77)
"M"
_$
(vl-string-left-trim character-set string)
Quita los caracteres especificados del inicio de una
cadena
_$ (vl-string-left-trim "PRE" "PREFIJO")
"FIJO"
(vl-string-mismatch cad1 cad2 [pos1 pos2 ignorar-
caja])
Devuelve la longitud del prefijo común más largo para
dos cadenas (cad1 cad2), comenzando en las posiciones
que se especifican (pos1 pos2) teniendo en cuenta o no
la diferencia entre mayúsculas y minúsculas (ignorar-
caja)
_$ (vl-string-mismatch "vl-fun" "avl-var")
0
_$ (vl-string-mismatch "vl-fun" "avl-var" 0 1)
3
_$ (vl-string-mismatch "VL-FUN" "VL-VAR")
3
_$ (vl-string-mismatch "VL-FUN" "Vl-vAR")
1
_$ (vl-string-mismatch "VL-FUN" "Vl-vAR" 0 0 T)
3
(vl-string-position code-car cad [pos-inic [desde-final]])
Busca el carácter con el código ASCII especificado en una
cadena
_$ (vl-string-position 100 "Madrid")
2
_$ (vl-string-position 100 "Madrid" nil T)
5 ;a partir de la derecha se encuentra la última 'd'
(vl-string-right-trim conj-caracteres cadena)
Quita los caracteres especificados del final de una cadena
_$ (vl-string-right-trim "FIJO" "PREFIJO")
"PRE"
(vl-string-search patron cadena [pos-inicial])
Busca el patrón especificado dentro de una cadena
_$ (vl-string-search "dr" "Madrid")
2
(vl-string-subst nueva-cad patron cadena [pos-inicial])
Sustituye una subcadena (patron) por otra (nueva-cad)
dentro de una cadena dada, a partir de la posición (pos-
inicial) especificada
_$ (vl-string-subst "leyenda" "cadena" "Una cadena
sustituida")
"Una leyenda sustituida"
_$ (vl-string-subst "esta" "cadena" "Una cadena u
otra cadena a sustituir" 0)
"Una esta u otra cadena a sustituir"
_$ (vl-string-subst "esta" "cadena" "Una cadena u
otra cadena a sustituir" 10)
"Una cadena u otra esta a sustituir"
(vl-string-translate conj-origen conj-dest cad)
Reemplaza caracteres en una cadena con un conjunto
especificado de caracteres
_$ (vl-string-translate "abcABC" "123123" "A = a, B =
b, C = C")
"1 = 1, 2 = 2, 3 = 3"
_$ (vl-string-translate "abc" "123" "A = a, B = b, C
= C")
"A = 1, B = 2, C = C"
(vl-string-trim conj-caract cadena)
Quita los caracteres especificados del inicio y del final de
una cadena
_$ (vl-string-trim "Ser" "Ser o no Ser")
" o no "

FORMAS ESPECIALES

Algunas funciones LISP se consideran como Formas


Especiales porque evalúan los argumentos recibidos de
una manera diferente que la mayoría de las funciones
llamadas desde Visual LISP. Una función típica evalúa
todos los argumentos que se le pasan antes de actuar
sobre ellos. Las Formas Especiales o no evalúan todos sus
argumentos, o sólo evalúan algunos argumentos bajo
determinadas condiciones. Por ejemplo, defun se
considera una Forma Especial, ya que tiene como
resultado el definir una nueva función de usuario a partir
de los argumentos que recibe, sin evaluar los mismos.
Las siguientes funciones AutoLISP y Visual LISP se
incluyen en la categoría de Formas Especiales:
AND OR
COMMAND PROGN
COND QUOTE
DEFUN REPEAT
DEFUN-Q SETQ
FOREACH TRACE
FUNCTION UNTRACE
IF VLAX-FOR
LAMBDA WHILE

FUNCIONES DEFINIDAS POR EL USUARIO

Un programa grande suele dividirse en una serie de pequeñas


formas o funciones de usuario más fáciles de implementar y
depurar. Las mismas se componen a partir de llamadas a las
funciones primitivas. Estas llamadas tendrán la forma de listas
que podrán anidarse unas dentro de otras de acuerdo a lo que
requiera la complejidad de la manipulación que quiera
realizarse de los datos aportados como argumentos. Para la
definición de funciones de usuario normalmente utilizaremos la
forma especial DEFUN. Otra manera de representar funciones
de usuario son las expresiones LAMBDA. El estudio de su
relación con DEFUN contribuirá a una mejor comprensión del
proceso. La carga de las funciones de usuario guardadas en
ficheros se realiza mediante la función LOAD. Un fichero en que
se guarda un programa LISP contiene las distintas formas o
funciones de usuario, una a continuación de la otra,
terminando por la función que debe invocarse para iniciar la
ejecución del programa. Esto se debe a que la función LOAD
imprimirá en pantalla el nombre de la última forma evaluada.
El nombre de esta función inicial se suele comenzar con los
caracteres "C:" lo que indica al sistema que dicha función de
usuario debe tratarse como si fuera un comando nativo de
AutoCAD, en el sentido de que pueda invocarse tecleando el
nombre (sin el prefijo "C:") sin encerrarlo entre paréntesis.
Una función de este tipo no admite argumentos en su lista de
parámetros, aunque sí la declaración de variables locales.

FUNCIONES DEFINIDAS POR EL USUARIO


LA PRIMITIVA DEFUN

(defun sím lista-argumentos expresión ...)

DEFUN permite definir una nueva función de usuario con el


nombre sím (el nombre de la función se incluye
automáticamente en un QUOTE). Detrás del nombre de la
función aparece una lista de parámetros (posiblemente vacía),
con los argumentos a recibir, y que puede estar seguida por
una barra oblicua y los nombres de uno o más símbolos locales
(variables locales) que serán definidas para uso exclusivo de
esta función. Esta barra oblicua debe ir separada del primer
símbolo local y del último argumento, si existe, por un espacio
como mínimo. Si no declara ningún argumento ni variable
local, se debe incluir una lista vacía tras el nombre de la
función. Los argumentos son tratados como una especie de
variables locales en el sentido de que no estarán disponibles
fuera de la función. Los nombres de argumentos deben ser
únicos dentro de la misma función.
A continuación de la lista de parámetros se deberán incluir
todas las expresiones que deberán ser evaluadas al llamar a la
nueva función de usuario.
A diferencia de muchas funciones primitivas AutoLISP, las
funciones definidas por el usuario no pueden tener argumentos
opcionales. Una llamada a una función de usuario debe
suministrar valores para todos los argumentos declarados.
DEFUN no es una función normal, sino lo que se incluye entre
las llamadas formas especiales, en el sentido de que sus
argumentos no son evaluados. Lo serán más tarde, al llamar a
la nueva función, sustituyendo sus argumentos por los valores
que se desea procesar.
La siguiente expresión define una función llamada DOBLE que
devuelve el doble de su argumento (número real o entero).

_$ (defun doble (x)(* x 2))


DOBLE

Así hemos creado una nueva función Lisp que puede llamarse
desde el nivel superior o anidada en otras funciones:
_$ (doble 1)
2

Un programa Lisp usualmente incluye una colección de tales


defuns, asemejándose en ello a un fichero de definiciones de
procedimientos en lenguajes tales como C o Pascal.

Pero, según puntualiza Graham, "algo muy diferente está


sucediendo aquí. Las funciones Lisp son objetos por sí mismas.
Lo que realmente hace DEFUN es construir una función y
guardarla bajo el nombre que aparece como primer
argumento." Paul Graham, On Lisp, pág. 10

Podemos acceder a la función asociada al símbolo DOBLE


utilizando la función Visual LISP vl-symbol-value:

_$ (vl-symbol-value 'DOBLE)
#<USUBR @045677ac DOBLE>

En versiones anteriores de AutoLISP la función creada con


defun era una lista:

Command: (defun doble (x) (* x 2))


DOBLE
Command: !doble
((X) (* X 2))
Command: (car doble)
(X)
Command: (cdr doble)
((* X 2))

En Visual LISP la función de usuario creada con defun es una


función compilada que contiene instrucciones en lenguaje de
máquina, por lo tanto en Autocad 2000 obtendremos:

Command: (defun doble (x) (* x 2))


DOBLE
Command: !doble
#<SUBR @01d5ba28 DOBLE>
Command: (car doble)
; error: bad argument type: consp #<SUBR @01d5ba28
DOBLE>
Command: (cdr doble)
; error: bad argument type: consp #<SUBR @01d5ba28
DOBLE>

REALES A ENTEROS: TRUCAMIENTO O REDONDEO

Como explicábamos en un capítulo anterior, la función FIX


convierte los números reales a enteros a base de un simple
truncamiento de la parte no entera. Este procedimiento no es
satisfactorio en muchos casos donde se requiere un grado
mayor de precisión. Así obtendremos el mismo resultado para
(fix 6.1) que para (fix 6.9), en ambos casos simplemente 6.

Una conversión más precisa se obtiene utilizando para la


conversión la función RTOS que además de convertir el
número real a binario, es capaz, cuando se especifica cero
como número de decimales, de realizar unna aproximación al
entero más cercano. Así (rtos 6.1 2 0) devuelve 6 mientras
que (rtos 6.9 2 0) devuelve 7.

Podemos definir una función ROUND de redondeo como sigue:

(defun round (num)


(read (rtos num 2 0))
) ;_ fin de defun

Hasta ahí bien, pero para 6.5 esta función realizaría la


aproximación al entero mayor, que en este caso sería 7. Con lo
que no se cumpliría la definición propuesta para la norma de
Common LISP, donde se especifica que:

si el número se encuentra exactamente a mitad de camino


entre dos enteros (es decir, en la forma entero + 0.5),
entonces se redondea al entero par más próximo (divisible por
2). (Steele, CLTL2)
Para cumplir esta especificación deberemos desarrollar una
función más compleja, que llamaremos CL-ROUND, siguiendo
la norma citada.

Esta función requerirá un predicado tambén norma de


Common LISP, pero ausente aún de AutoLISP-Visual LISP. El
predicado EVENP que comprueba si un número entero es par.

(defun evenp (num)


(zerop (rem num 2))
) ;_ fin de defun

La nueva función cl-round recibe el número del cual


comprueba:

 si una vez redondeado el resultado NO es par (not


(evenp (setq tmp (rnd num))))
 si la parte decimal (valor absoluto) es igual a 0.5 (=(abs
(rem num 1)) 0.5)

En caso de que ambas condiciones sean ciertas:

 si el número es negativo, se le suma 1


 si el número es positivo se le resta 1

(defun cl-round (num / tmp)


(if
(and
(not (evenp (setq tmp (rnd num))))
(= (abs (rem num 1)) 0.5)
)
(if (minusp tmp)(setq tmp (1+ tmp))(setq tmp (1-
tmp)))
) ;_ fin de if
tmp
) ;_ fin de defun

NUEVAS FUNCIONES TRIGONOMÉTRICAS


Las funciones trigonométricas que ofrece AutoLISP como
primitivas se reducen a tres: SENO, COSENO y
ARCOTANGENTE. Muchas operaciones geométricas requerirán,
sin embargo, recurrir a otros operadores trigonométricos. Esto
puede servir de ejemplo a la posibilidad de desarrollar nuevas
funciones utilitaria dentro de LISP.

Funciones Trigonométricas primitivas de AutoLISP:


SIN
(sin ang)
Devuelve el seno de un ángulo expresado en radianes.
COS
(cos ang)
Devuelve el coseno de un ángulo expresado en radianes.
ATAN
(atan num1 [num2])
Devuelve el arcotangente de num1, en radianes, si se le
suministra solamente num1. Si se suministraran ambos
argumentos num1 y num2, ATAN devuelve el arcotangente
de num1/num2, en radianes. Si num2 fuera cero, devolverá
un ángulo de más o menos 1.570796 radianes (+90
grados° o –90 grados°), según el signo de num1. El rango
de ángulos devuelto es -pi/2 a +pi/2 radianes.

Funciones de conversión:

Puesto que AutoLISP trabaja con radianes mientras que


AutoCAD lo hace por defecto en ángulos sexagesimales, sería
necesario tener a mano siempre las funciones de conversión
grados-radianes.

Conversión entre grados y redianes:

;;;recibe ang en radianes y lo devuelve en grados


(defun grados (rad)
(* (/ rad pi) 180.0)
);_fin de defun
;;;recibe ang en grados y lo devuelve en radianes
(defun radianes (grd)
(* (/ grd 180.0) pi)
);_fin de defun

Las funciones trigonométricas no incluidas con AutoLISP:

Las siguientes funciones trigonométricas han sido publicadas


por Jon Fleming.

Autor: Jon Fleming < jonf@fleming-group.com>, Mayo


20 1997.
fuente: Grupo de Noticias
autodesk.autocad.customization

Obsérvese que:

 Algunas de las funciones (ASEC -arcosecante- y ACSC


-arcocosecante-) utilizan otras funciones definidas en
este mismo archivo.
 Ninguna de estas funciones comprueban la validez de los
argumentos que reciben. Esta comprobación debe
hacerse en los programas que las invocan.
 Las funciones se han desarrollado evitando la posibilidad
de un error de división por cero.
 Se ha determinado experimentalmente que 9.7E307 es el
número mayor que puede ser generado en AutoLISP
(R14).
 Visual LISP en Autocad 2000 (R15) y 2002 al
producirse un número en exceso del mayor valor real
admitido, en lugar de generar un error devuelve el
símbolo 1.#INF, que puede asignarse a una variable que
sea devuelta en caso de división por cero:
Podemos definir una función que nos devuelva este
símbolo. Para ello contamos con el predicado no
documentado VL-INFP que detecta cuando un número
rebasa el máximo valor admitido para los números
reales:

(defun infinito ( )
(setq *INF* 2.0)
(while (not (VL-INFP *INF*))
(setq *MAX-REAL* *INF* *INF* (expt *INF* 2))))

_$ (infinito)
1.#INF
_$ *MAX-REAL*
1.34078e+154
_$ *inf*
1.#INF

así tendremos que cualquier número dividido entre *INF*


devolverá cero y tendremos definida una variable global *MAX-
REAL* que nos permitirá comprobar si un valor se aproxima al
máximo valor real admitido por el sistema:

(/ 254.98 *INF*) 0.0


TAN
acepta cualquier ángulo en radianes y devuelve la
tangente en un rango de -9.7E307+epsilon a 9.7E307,
ambos inclusive. Se analiza la posibilidad de que el
coseno del valor pasado sea igual a cero, para evitar un
error de división por cero.
(defun tan (z / cosz)
(if (zerop (setq cosz (cos z)))
*INF*
(/ (sin z) cosz)
) ;_fin de if
) ;_fin de defun
SEC
Acepta cualquier ángulo en radianes, devolviendo la
secante en los rangos de -9.7E307+epsilon a -1.0, ambos
inclusive y de 1.0 a 9.7E307, ambos inclusive
(defun sec (z / cosz)
(if (zerop (setq cosz (cos z)))
*INF*
(/ 1.0 cosz)
) ;_fin de if
) ;_fin de defun
CSC
Acepta cualquier ángulo en radianes, devolviendo la
cosecante en los rangos de -9.7E307+epsilon a -1.0,
ambos inclusive y de 1.0 a 9.7E307, ambos inclusive
(defun csc (z / sinz)
(if (zerop (setq sinz (sin z)))
*INF*
(/ 1.0 sinz)
)
)
ASIN (seno inverso)
acepta un argumento en el rango -1.0 a 1.0 ambos
inclusive, y devuelve un ángulo en radianes en el rango
de (-pi/2) a (pi/2) ambos inclusive.
(defun asin (z /)
(atan z (sqrt (- 1.0 (* z z))))
) ;_fin de defun
ACOS (coseno inverso)
acepta un argumento en el rango -1.0 a 1.0 ambos
inclusive, y devuelve un ángulo en radianes en el rango
de pi a 0 ambos inclusive
(defun acos (z /)
(atan (sqrt (- 1.0 (* z z))) z)
) ;_fin de defun
ASEC (secante inversa)
acepta un argumento en uno de dos rangos: menos
infinito a -1 ambos inclusive o 1 a infinito, ambos
inclusive, y devuelve un ángulo en radianes en el rango
de pi a 0, ambos inclusive (excepto EXACTAMENTE pi/2
que nunca será devuelto en un ordenador con precisión
numérica finita)
(defun asec (z /)
(acos (/ 1.0 z))
) ;_fin de defun
ACSC (cosecante inversa)
acepta un argumento en uno de dos rangos: menos
infinito a -1, ambos inclusive o 1 a infinito, ambos
inclusive, y devuelve un ángulo en radians en el rango
-pi/2 a pi/2, ambos inclusive (excepto EXACTAMENTE 0.0
que nunca será devuelto en un ordenador con precisión
numérica finita)
(defun acsc (z /)
(asin (/ 1.0 z))
) ;_fin de defun
ACOT (cotangente inversa)
acepta un argumento en el rango de menos infinito a más
infinito, ambos inclusive y devuelve un ángulo en
radianes en el rango de pi a 0, ambos inclusive.
(defun acot (z /)
(- (/ pi 2.0) (atan z))
) ;_fin de defun

EXPRESIONES LAMBDA

La independencia de ambos procesos, la construcción de la


función y el asignarle un nombre se comprende claramente si
analizamos la función LAMBDA.

(lambda argumentos expr ...)

Mediante ella, podemos referirnos a una función literalmente


de la misma manera que lo haríamos con un número o una
cadena de texto. Un número se representa a sí mismo. Una
cadena se representa mediante una serie de caracteres
rodeados de comillas dobles. Para representar una función se
utilizan expresiones-lambda.
Una expresión-lambda es una lista de tres componentes:

1. El símbolo lambda
2. Una lista de parámetros
3. Una serie de expresiones que se evalúan al ejecutar la
función.

Una función equivalente a doble es la referida por la siguente


expresión-lambda:

_$ (lambda (x)(* x 2))


#<USUBR @0344f0dc -lambda->
La forma especial setq puede servir a su vez para asignar esta
función a un símbolo:

_$ (setq doble (lambda (x)(* x 2)))


#<USUBR @0344f104 -lambda->
_$ (doble 4)
8

que podemos ejecutar perfectamente como función de usuario.


LAMBDA suele usarse conjuntamente con APPLY y/o MAPCAR
para ejecutar una función sobre una lista.

FUNCTION

El operador FUNCTION le indica al compilador Visual LISP que


enlace y optimice un argumento tal como si fuera una función
primitiva.

(function símbolo | expresión-lambda)

FUNCTION es idéntico a la función QUOTE excepto en que fuerza


la compilación del argumento de la misma manera que lo haría
DEFUN. Si incluímos las funciones internas en expresiones del
tipo (function (lambda <parámetros> <expresiones> ...) en
lugar de (quote (lambda <parámetros> <expresiones> ...)) ó
'(lambda <parámetros> <expresiones> ...), nos aseguraremos
que el código sea enlazado (linked) y optimizado en tiempo de
compilación (como una USUBR anónima) en lugar de ser
simplemente evaluado en tiempo de ejecución. Las
expresiones-LAMBDA compiladas contendrán información para
su depuración al ser cargadas en el IDE Visual LISP. Admite un
argumento símbolo que nombra una función y una expresión-
lambda de la forma: (LAMBDA argumentos {expresión-S}*)

Ejemplos:
El compilador Visual LISP no puede optimizar la expresión
lambda precedida de apóstrofe (QUOTE) en la siguiente
expresión:
(mapcar
'(lambda (x) (* x x))
'(1 2 3))

Pero una vez incluida la expresión dentro de FUNCTION el


compilador podrá optimizar la expresión lambda:

(mapcar
(function (lambda (x) (* x x)))
'(1 2 3))

Lo que redundará en un incremento de la velocidad de


ejecución al generarse el correspondiente código optimizado
en lenguaje de máquina.

LOAD

Una de las virtudes de LISP es la posibilidad de construir, a


partir de las primitivas aportadas por el lenguaje, las funciones
necesarias para el desarrollo de una aplicación específica.
Estas funciones operan de manera idéntica a las funciones
primitivas. Si bien podemos evaluar las funciones LISP desde
la Consola de Visual LISP o desde la propia línea de comandos
de AutoCAD, cuando se trata de funciones más extensas lo
usual es escribirlas en un fichero de texto (con extensión LSP)
y cargarlas mediante la función LOAD. El IDE (Entorno de
Desarrollo Integrado) Visual LISP incluye un editor de
programación que facilita la redacción de programas lisp
mediante una serie de ayudas, tales como el texto coloreado
según la sintaxis y la identación y formateo automático del
texto. Trabajando en este IDE se pueden cargar las funciones
o "formas" contenidas en el archivo fuente en desarrollo
mediante el correspondiente botón de la barra de
herramientas u opciones de menú. La instrucción LISP para
cargar en memoria un programa LISP contenido en un fichero
es LOAD:

(load nombre_archivo [si_falla])


La función LOAD puede ser usada desde dentro de otra función
LISP e incluso de manera recursiva (en el fichero que se está
cargando).
nombre_archivo debe ser una cadena que representa el
nombre del fichero. Si no se incluye la extensión LOAD
comprobará la existencia de un fichero con ese nombre y
alguna de las siguientes extensiones en el orden que se
especifica a continuación:

 .vlx
 .fas
 .lsp

Se cargará el primer fichero encontrado. Si load no se ejecuta


de forma correcta, devuelve el valor del argumento si_falla.
Sin embargo, si si_falla no se especifica, un fallo de load
genera un error de Visual LISP. Si la operación es correcta,
load devuelve el valor de la última expresión del archivo.
El nombre_archivo puede incluir un prefijo de directorio, como
en "/ función/prueba1". En los sistemas DOS también se
admite una letra de unidad. Una barra oblicua (/) o dos
contrabarras (\\ ) son delimitadores de directorio válidos. Si
no incluye un prefijo de directorio en la cadena
nombre_archivo, load busca el archivo especificado en el
camino de la biblioteca AutoCAD. Si lo encuentra en cualquier
parte de este camino, load lo carga.
Si el argumento si_falla es una función válida de Visual
LISP, se calcula. En la mayoría de los casos, el argumento
si_falla debería ser una cadena o un átomo. Esto permite
que una aplicación Visual LISP que llame a load tome una
acción alternativa cuando se produce un fallo.

ESTRUCTURAS DE CONTROL

Los predicados son funciones que devuelven respuestas lógicas


(cierto-falso), mientras que las estructuras de control
controlan la ejecución del programa y permiten múltiples
ramificaciones.
PREDICADOS
Permiten comprobar características relacionadas con el
tipo de dato con que se está operando en cada momento.
Devuelve T (cierto) si la condición se cumple y nil (falso)
en caso contrario.
 PREDICADOS GENERALES: Suelen distinguirse
por terminar el nombre de la función en la letra P
que significa predicado. Se utiliza la siguiente regla
para decidir si la P está precedida por un guión: si el
nombre del predicado se forma añadiendo la P a un
nombre existente, tal como el nombre de un tipo de
dato, se añade un guión sólo en el caso de que el
nombre original incluya alguno (BOUNDP, LISTP,
NUMBERP sin guión ó VL-FILE-DIRECTORY-P, VLAX-
ERASED-P con guión). Los predicados que fueron
introducidos con Visual LISP se conocen por los
prefijos que se adicionan VL-, VLAX- o VLR- (VL-
CONSP, VLAX-PROPERTY-AVAILABLE-P, VLR-ADDED-
P). En casos como VL-CONSP se hace una excepción
a la regla anterior, puesto que en realidad se está
renombrando un predicado standard de Common
LISP.
PREDICADOS SOBRE ÁTOMOS Y LISTAS
 PREDICADOS ARITMÉTICOS: Corresponden a las
funciones de comparación (mayor que, menor que,
igual a...).
 OTROS PREDICADOS DEFINIDOS POR EL
USUARIO: muestra de la extensibilidad de LISP.

OPERADORES LÓGICOS
Corresponden a las operaciones Booleanas básicas. LISP
suministra tres operadores sobre valores Booleanos: and,
or, y not. De ellos, and y or son también en cierto modo
estructuras de control ya que sus argumentos son
evaluados condicionalmente. Permiten agrupar las
condiciones de prueba, casi siempre a partir de la
utilización de predicados que se utilizan dentro de las
estructuras de programación descritas más adelante.La
función not tiene un sólo argumento que analizar, y es
por ello una función simple.
ESTRUCTURAS CONDICIONALES
Visual LISP provee las dos estructuras de control
tradicionales de LISP. La estructura condicional de más
tradición en LISP es COND. Sin embargo IF resulta más
simple y es directamente comparable a las estructuras
condicionales de otros lenguajes de programación.
ATOM
Sabemos que dentro de las expresiones LISP se
distinguen los átomos y las listas. El predicado ATOM
verifica si un elemento determinado es un átomo
(atom elemento)
Devuelve nil si elemento es una lista y devuelve T en
caso contrario. Debe tenerse cuidado, si se trata de un
átomo simbólico, en tener claro si lo que se quiere
evaluar es el síbolo o su contenido. Si no está precedido
de QUOTE <'>, lo que se evalúa es el valor asociado:
_$ (atom a)
T
_$ (atom 'a)
T
_$ (setq a '(a b c))
(A B C)
_$ (atom a)
nil
_$ (atom 'a)
T
LISTP
Comprueba si un elemento es una lista
(listp elemento)
Devuelve T si elemento es una lista y devuelve nil en
caso contrario. Obsérvese que como NIL es una lista
vacía, (listp nil) devolverá T. Es decir, que todo
átomo simbólico no asociado a un valor, devoverá T
tanto para ATOM como para LISTP:
_$ !b
nil
_$ (atom b)
T
_$ (listp b)
T
_$ (listp 'b)
nil

ÁTOMOS SIMBÓLICOS:

VL-SYMBOLP
Identifica si un objeto especificado es o no un símbolo.
(vl-symbolp objeto)
Devuelve T si el objeto es un símbolo y nil si se trata de
una constante (número o cadena) o una lista.
Este predicado ha sido incorporado por Visual LISP
_$ (vl-symbolp 'a)
T
_$ (vl-symbolp 15)
nil
_$ (vl-symbolp "abc")
nil
_$ (vl-symbolp '(a b c))
nil
BOUNDP
Cuando se trata de un átomo simbólico, puede ser
necesario determinar si tiene asociado un valor. BOUNDP
verifica la existencia de dicha asociación.
(boundp sím)
Devuelve T si sím tiene un valor asociado. Si no hay
ningún valor asociado a sím (o se ha asociado a nil),
boundp devuelve nil. Si sím es un símbolo no definido, se
crea y se asocia a nil de forma automática.
NUMÉRICOS:

NUMBERP
Los átomos no simbólicos o constantes pueden ser
números o cadenas. NUMBERP comprueba si el objeto es
un número (real o entero)
(numberp elemento)
Devuelve T si elemento es un valor numérico y devuelve
nil en caso contrario. El predicado complementario
STRINGP, que comprobaría si se trata de una cadena no
está definido en Visual LISP, aunque se puede encontrar
entre los mensajes de error a la hora de depurar un
programa. En el ejemplo que sigue el mensaje "STRINGP
2" significaría que se ha recibido un valor numérico (2)
en lugar del argumento esperado del tipo cadena.
_$ (strcat 2 "b")
; error: bad argument type:
stringp 2
MINUSP
Tratándose de valores numéricos en ocasiones se deberá
comprobar si son negativos. MINUSP realiza dicha
comprobación.
(minusp número)
Devuelve T si número es negativo y nil en caso contrario.
Si el argumento no es numérico se recibirá un mensaje
de error.
ZEROP
Igual que en el caso anterior, cuando se trata de valores
numéricos ZEROP permite comprobar si un elemento se
evalúa como cero
(zerop número)
Devuelve T si número es cero y nil en caso contrario.

LISTAS VACÍAS O CONSES:

En LISP el valor asociado al símbolo NIL (condición de


falso) es la lista vacía <'()>. Cualquier símbolo que
evalúe como NIL, devolverá T (cierto) al pasarlo al
predicado LISTP. Para comprobar que se trata
efectivamente de una lista, pero que dicha lista contiene
algo (aunque fuera NIL) en las versiones anteriores de
AutoLISP era necesario utilizar además de LISTP otros
predicados tales como BOUNDP o NULL.
NULL
Comprueba si un elemento está definido como nil
(null elemento)
Devuelve T si elemento está asociado a nil y devuelve
nil en caso contrario.
Puede emplearse para probar si se ha alcanzado el fin de
una lista en sustitución del predicado ENDP de Common
Lisp que falta en Visual LISP
_$ (null (caddr '(a b)))
T
VL-CONSP
La situación descrita en el párrafo anterior se resuelve
empleando en su lugar el predicado CONSP (vl-consp
para Visual LISP), que comprueba si el elemento
analizado evalúa como una lista no nula
(vl-consp elem-lista)
Devuelve T, si elem-lista es una lista y no está vacía,
de lo contrario devuelve nil. El término CONSP se deriva
de la función básica de construcción de listas, que es la
función CONS. Una lista no vacía es el resultado de
aplicar, al menos una vez la función CONS.
Este predicado ha sido incorporado por Visual LISP
_$ (vl-consp a)
nil
_$ (setq a (cons 1 a))
(1)
_$ (vl-consp a)
T
El valor contenido en la lista puede incluso ser una lista
vacía:
_$ (setq b (cons nil b))
(nil)
_$ (vl-consp b)
T
_$ (vl-consp '(())) ;equivalente
a la línea anterior
T

PERTENENCIA A UNA LISTA:

MEMBER
Es un tipo particular de predicado, que comprueba si un
átomo pertenece a una lista dada.
(member expr lista)
Si expr no aparece en la lista, member devuelve NIL. En
caso de encontrarlo, devuelve el resto de la lista, desde
el primer caso de la expresión encontrada. Lo devuelto
por MEMBER actúa como T (cierto) ya que cualquier valor
no nulo actuará como la negación de NIL (not NIL), es
decir, cierto. Debe tenerse cuidado en el caso de los
átomos simbólicos de pasar el nombre del símbolo
precedido de QUOTE <'>.
_$ (member a '(c d 2 4 a '(a b)
"a" 3.0 j))
nil
_$ (member 'a '(c d 2 4 a '(a b)
"a" 3.0 j))
(A (QUOTE (A B)) "a" 3.0 J)

IGUALDAD O IDENTIDAD

Dos expresiones pueden considerarse iguales sin


necesidad de que sean idénticas. Los predicados EQUAL y
EQ permiten comprobar estas situaciones.
EQUAL
Determina si dos expresiones son iguales
(equal expr1 expr2 [aproximación])
La función equal determina si expr1 y expr2 se evalúan
igual. Cuando se comparan dos números reales (o dos
listas de números reales, como en el caso de los puntos),
los dos números idénticos pueden presentar ligeras
diferencias derivadas de los métodos utilizados para su
cálculo. Para resolver esta situación, puede utilizarse un
argumento numérico optativo, aproximación, para
especificar la diferencia máxima que se puede admitir
entre expr1 y expr2, para que sigan considerándose
iguales.
EQ
Determina si dos expresiones son idénticas
(eq expr1 expr2)
La función eq determina si expr1 y expr2 están asociadas
al mismo objeto (si apuntan a la misma dirección de
memoria). Devuelve T si las dos expresiones son iguales
y nil en caso contrario.
_$ (setq a (list 'm 'n 'o))
(M N O)
_$ (setq b (list 'm 'n 'o))
(M N O)
_$ (equal a b) ;las listas
representadas por a y b son iguales
T
_$ (eq a b) ;pero NO son la
misma lista
nil
_$ (eq (cons 'a 'b) (cons 'a
'b))
nil ;cada llamada a
cons crea una nueva lista
_$ (eq '(a . b) '(a . b))
nil
En el caso de los valores constantes, equal y eq
devuelven resultados similares, salvo lo explicado
respecto a los números reales.
_$ (setq a 8)
8
_$ (setq b (+ 5 3))
8
_$ (eq a b)
T

PREDICADOS ARITMÉTICOS

Todos los argumentos pueden ser números o cadenas.

COMPARACIONES BÁSICAS:

Permiten comparar números o cadenas.

> (mayor que...)


Devuelve T si cada argumento es numéricamente mayor
que el situado detrás de él y nil en caso contrario
(> cadnúm [cadnúm] ...)
< (menor que...)
Devuelve T si el primer argumento es numéricamente
menor o igual que el situado detrás de él y nil en caso
contrario
(< cadnúm [cadnúm] ...)
=(igual que...)
Devuelve T si todos los argumentos son numéricamente
iguales; de lo contrario, devuelve nil
(=cadnúm [cadnúm] ...)
/=(desigual a...)
Equivale a (not (=cadnúm [cadnúm] ...))

PREDICADOS COMPUESTOS:
(<=cadnúm [cadnúm] ...)
Devuelve T si cada argumento es numéricamente menor
o igual que el situado detrás de él y nil en caso contrario
(> cadnúm [cadnúm] ...)
Devuelve T si cada argumento es numéricamente mayor
que el situado detrás de él y nil en caso contrario
(>=cadnúm [cadnúm] ...)
Devuelve T si cada argumento es numéricamente mayor
o igual que el situado detrás de él y nil en caso contrario

OPERADORES LÓGICOS

NOT
Negación lógica. Cualquier expresión cuyo valor no sea
NIL evalúa como falsa. En cambio, (not NIL) evalúa como
T. Esto sucede puesto que cualquier cosa que no tenga el
valor NIL (o la lista vacía) evalúa en LISP como T
(cierto).
OR
Devuelve el OR lógico de una lista de expresiones
(or expr...)
La función o calcula las expresiones de izquierda a
derecha en busca de una expresión distinta de nil. Si la
encuentra, OR deja de realizar cálculos y devuelve T. Si
el valor de todas las expresiones es nil, or devuelve nil.
AND
Devuelve el operador lógico AND de una lista de
expresiones
(and expr ...)
Si alguna de las expresiones da como resultado nil, se
interrumpe la operación y la función devuelve nil; en caso
contrario, devuelve T.

FUNCIONES LÓGICAS BINARIAS

Si un tema ha sido poco explicado en los manuales de


programación LISP para AutoCAD, al menos, en los que se
puede consultar en español, que no son muchos, es la
utilizaciónde los operadores lógicos binarios. Su importancia y
posibilidades de aplicación son inversamente proporcionales a
esa escasa atención que se le dedica. Su uso permite la
gestión de una multitud de parámetros de la aplicación que
están codificados como números en formato binario.

Uno de estos parámetros es el de las referencias a objeto. El


valor de la variable de sistema OSMODE es el equivalente
decimal de un número binario. Al número binario 0000 0001
corresponde la referencia punto FINal y al número binario 0000
0100 corresponde la referencia CENtro. Si el valor de OSMODE
fuera equivalente al número binario 0000 0101 eso significaría
que están activas a la vez las referencias punto FINal y
CENtro.

Cada posición de un número binario representa un BIT. Un BIT


es la unidad de información mínima que es capaz de manejar
un computador, y sólo puede uno de dos valores, ACTIVADO o
DESACTIVADO, que en su representación numérica
simbolizamos mediante 1 ó 0. Por eso decimos que en la
referencia punto FINal se ACTIVA el primer BIT y para la
referencia CENtro se ACTIVA el tercer BIT. El valor binario
1111 1111 1111 activaría todas las referencias a objeto
posibles.

Dicho así resulta secillo, pero las dificultades comienzan


cuando tenemos en cuenta que AutoCAD espera recibir no esta
representación a base de ceros y unos, sino su equivalente en
formato DECIMAL. Y el equivalente dcimal de cada BIT vendría
dado por 2(n-1) dinde n sería la la posición del bit cuyo valor
decimal se desea. Expresado en términos de una función LISP
(expt 2 (1- n)).

Las operaciones lógicas sobre números binarios disponibles en


Visual LISP son cuatro:

 ~ (NOT binario)
 LOGAND (AND lógico binario)
 LOGIOR (OR lógico binario)
 BOOLE (operador lógico binario de carácter
general)

A estas operaciones debemos añadir la función LSH,


desplazamiento binario, que será la primera función que
estudiaremos.

SISTEMA BINARIO

Internamente, la máquina computadora representa los valores


numéricos mediante grupos de bits. agrupados en bytes. Por
ejemplo, el número 3 se representa mediante un byte que
tiene "activos" los bits primero y segundo (contando desde la
derecha); 00000011. Esta sería la forma de representación del
número 3 en un sistema numérico de base 2, también
conocido como BINARIO. El sistema que utilizamos
normalmente es un sistema DECIMAL o de base 10. En un
sistema DECIMAL, contamos desde el 0 hasta el 9 antes de
añadir un nuevo dígito. El número 22 en un sistema decimal
significa que tenemos dos conjuntos de 10s y 2 conjuntos de
1s.
En un sistema BINARIO sólo pueden haber dos valores para cada dígito: ya sea un
0=DESACTIVADO ó un 1=ACTIVADO. Para representar el número 22 en notación
BINARIA lo haríamos como 00010110, notación que se explica según la siguiente tabla:

Posición del BIT: 7 6 5 4 3 2 1 0


Valor Binario: 0 0 0 1 0 1 1 0
Valor Decimal: 128 64 32 16 8 4 2 1
Valores a Sumar: 0 0 0 16 0 4 2 0
Valor Resultante: 16 + 4 + 2=22

Todos los valores que corresponden a posiciones a las que se


asigna el valor binario de 0 (cero) no se cuentan, ya que 0
representa DESACTIVADO.

De la misma manera, los números que corresponden a las


posiciones con valor binario 1 se sumarán, (16 + 4 + 2=22)
ya que 1 representa ACTIVADO.
Valores Decimales y sus equivalentes Binarios:
POSICIÓN VALOR VALOR
BIT DECIMAL BINARIO
1 1 1
2 2 10
3 3 11
4 4 100
5 5 101
6 6 110
7 7 111
8 8 1000
9 9 1001
10 10 1010
11 16 10000
12 32 100000
13 64 1000000
14 100 1100100
15 256 100000000
16 512 1000000000
17 1000 1111110100
18 1024 10000000000

Bits, Bytes y Palabras...

Se suelen escribir los números binarios como una secuencia de grupos de cuatro bits,
también conocidos como NIBBLES. Según el número de estas agrupaciones los números
binarios se clasifican como:

Unidad: Núm. bits Ejemplo:


Bit 1 1
Nibble 4 0101
Byte (Octeto) 8 0000 0101
Palabra 16 0000 0000 0000 0101
Doble Palabra 32 0000 0000 0000 0000 0000 0000 0000 0101

Los computadores personales con el sistema operativo MS


DOS utilizaban palabras de 16 BITS. Los sistemas operativos
actuales sobre los que corre AutoCAD 2000 utilizan Palabras de
32 BITS

Función LSH

Para el cálculo del valor decimal de un determinado BIT sería


más adecuado, como veremos más adelante, utilizar la función
LSH. Sgún la explicación de Reini Urban*:

(lsh <integer> <num>)

LSH quiere decir Left-SHift, en inglés desplazamiento a la


izquierda. Si se interpreta el número entero como una matriz
de bits ("bit-array") podemos interpretar LSH como el
desplazamiento de la matriz n-veces a la izquierda (insertando
ceros a la derecha)
Ejemplo:

int: 9 = 8+1 => (1 0 1) como "bit-array" (2^3 +


2^0)
(lsh 9 1) => (1 0 1 0) que es (2^4 + 2^1) = 18.
Fácil, puesto que (LSH i 1) es siempre igual a i*2
LSH con un número negativo es un desplazamiento a la
derecha (suprimir el bit más a la derecha). (LSH i -1) es lo
mismo que i/2 (ó i dividido entre 2, "división de enteros")

De acuerdo con esto el valor decimal del primer BIT vendría


dado por (lsh 1 0) equivalente a 0000 0001 y devolvería el
valor decimal 1. Con un desplazamiento de dos a la izquierda
obtendríamos 0000 0100 que ya hemos visto que equivale a
OSMODE CENtro y (lsh 1 2) devuelve el valor decimal de 4.

Con LSH podemos diseñar la función utilitaria siguiente, que


nos devuelve el valor decimal de cualquier BIT:

(defun bit (posicion)


(lsh 1 (1- posicion))
)

De manera que (bit 1) devolvería 1, (bit 5) devolvería 16,


etc.

Con lo que tenemos una manera sencilla de dar el valor


decimal que corresponde a la posición de cualquier bit. Activar
el bit 8 (referencia PERpendicular) significaría sumar (bit 8) al
valor actual de OSMODE:

(setvar "osmode" (+ (getvar "osmode")(bit 8)))

Claro que sería lo mismo

(setvar "osmode" (+ (getvar "osmode") 128))

e incluso:

(setvar "osmode" (+ (getvar "osmode") (expt 2 8)))

Pero (bit 8) resulta más fácil de memorizar y seguramente


más claro en cuanto a la intención del programador. Debo
advertir que la cosa no es tan sencilla, pues debemos primero
haber comprobado que el bit 8 no estaba ya activado. Más
adelante veremos cómo hacerlo valiéndonos de los operadores
LOGAND y LOGIOR.

Pero si continuamos incrementando los valores que pasamos a


la función BIT, descubriremos que hay un límite:

Como se puede ver, cuando se activa el bit 32 (en un sistema


operativo de 32 bits) es decir, cuando la última posición a la
izquierda de la palabra toma el valor de 1, su signo pasa a
negativo y un nuevo desplazamiento a la izquierda devolvería
de nuevo el valor del primer bit.
Esta propiedad nos permite definir una función para
determinar la longitud de palabra del sistema operativo actual:

(defun LongPalabra ( / pos)


(setq pos 0)
(while (not (minusp (lsh 1 pos)))
(setq pos (1+ pos))
)
(alert (strcat "Longitud de Palabra:\n\t" (itoa (1+
pos)) " Bits"))
)

En AutoCAD 2000 obtendríamos como resultado:


Es inportante tener esto en cuenta para comprender el
funcionamiento del próximo operador a estudiar, el NOT lógico
binario (~ num).

Función ~ (NOT lógico binario)*

Esta función resulta especialmente incomprensible si


consultamos los manuales AutoLISP habituales, incluyendo la
"ayuda" que viene con el programa. Nos dirán que "esta
función devuelve la negación lógica de una cifra binaria, es
decir, el complemento a uno". Y se nos ilustra con ejemplos
como:
(~ 3) devuelve -4

Si bien la explicación es del todo correcta, a los que no


poseemos conocimientos matemáticos tan completos no nos
aprovecha gran cosa esa explicación. Para encontrar una
respuesta más comprensible hemos tenido que recurrir al viejo
manual de Nelson Johnson** cuya traducción al español fuera
publicada en 1990 por McGraw-Hill. Explica Johnson que:

(~ <número>)
Puede que deseemos verificar un número para encontrar su
función NOT bit a bit. En este caso, se invierten todos los bits.
el valor devuelto por la función "~" será un número que tenga
todos los bits a 0 del argumento puestos a 1 y, viceversa,
todos los bits del argumento que estaban a 1 estarán a cero.

El número 9 en binario sería 1001, o mejor, considerando un


tamaño de palabra de 32 bits:
0000 0000 0000 0000 0000 0000 0000 1001
(~ 9) devolvería -10
Lo que sería igual en números binarios con tamaño de palabra
de 32 bits a:
1111 1111 1111 1111 1111 1111 0110

El valor negativo se explica, según lo dicho al explicar la


función LSH, al ocupar la posición extrema izquierda un valor
1. La posibilidad de construir este "filtro negativo" de un valor
binario es extremadamente valioso para gestionar los valores
guardados en términos de posiciones de bits.

Pero para la gestión de estos valores binarios nos deberemos


auxiliar de otras funciones como LOGAND, LOGIOR, y
BOOLE

No es muy frecuente el uso de esta función en los programas


LISP dentro de AutoCAD. Revisando los programas
suministrados con la aplicación podemos encontrar ejemplos
de su uso en AI_UTILS.LSP y DDMODIFY.LSP de las
versiones 13 y 14 y FORMAT.LSP de la versión 12.

LOGAND

Para LOGAND transcribimos la explicación de Jon Fleming * en


el mismo hilo de discusión donde fué expuesta la explicación
anterior de Reini Urban:

(logand 13 22) es el AND lógico de [8 + 4 + 1] con [16 +


4 + 2] o, escribiendo los números en binario, de 00001101
con 00010110.

Escribiendo los números uno sobre el otro, podemos entonces


escribir la respuesta en binario de inmediato, escribiendo un 1
bajo dos números uno y un 0 en cualquier otro lugar:

13 = 00001101
22 = 00010110
13 y 22 = 00000100

es decir, 4 en notación decimal. Por lo tanto, (logand 13 22)


devuelve 4. El AND lógico se suele utilizar como filtro. Uno de
los números que definimos contendrá un 1 en su
representación binaria dondequiera que deseamos que un
posible 1 en un número desconocido pase por el filtro. Esto se
debe a que un entero es un lugar muy eficiente para guardar
varios valores lógicos verdadero/falso relacionados como unos
y ceros en posiciones definidas en la representación binaria de
un entero.

Un ejemplo perfecto lo constituye la variable de sistema


OSMODE. ¿Queremos saber si la referencia a objeto
INTERSECCIÓN se encuentra activa? Nuestra referencia de
comandos nos dice que la presencia de 32 en el valor de
OSMODE indica la referencia INTERSECCIÓN. Como sólo nos
interesa la referencia INTERSECCIÓN, escribimos la
representación binaria de OSMODE con "x" en las posiciones
que no interesa comprobar: El valor de OSMODE podría ser:

xxxxxx1xxxxx
o: xxxxxx0xxxxx

Así que si pasamos a (logand ...) cualquiera de esos valores


con 0000 0010 0000 que es 32 en base 10, obtenemos, ya sea
32 (si está activa la referencia INTERSECCIÓN) o 0 (cero) si no
lo está. En otras palabras:

(if (logand 32 (getvar "OSMODE"))


(prompt "\nLa referencia INTERSECCIÓN está
ACTIVA")
(prompt "\nLa referencia INTERSECCIÓN NO está
ACTIVA")
)

Utilizando las funciones LSH, ~ y LOGAND pudiéramos


desarrollar funciones para la conversión de decimales en
binarios y viceversa, útiles al menos para comprender mejor la
operación de estas funciones binarias. Este será el tema de la
próxima sección.

Los ejemplos de LOGAND en los programas LISP que vienen


con AutoCAD sí son numerosos. Podemos enumerar para la
versión 14: BMAKE.LSP, DDATTDEF.LSP, DDGRIPS.LSP,
ATTREDEF.LSP, DDCHPROP.LSP, DDCOLOR.LSP,
DDINSERT.LSP, DDMODIFY.LSP, DDPTYPE.LSP,
DDSELECT.LSP, DDUNITS.LSP, DDVIEW.LSP, 3D.LSP,
EDGE.LSP, MVSETUP.LSP, BLK_LST.LSP, COUNT.LSP,
LMAN.LSP, TREXBLK.LSP, CLIPIT.LSP, EXCHPROP.LSP,
EXTRIM.LSP, XPLODE.LSP, CURSDLG.LSP,
SQLEDDLG.LSP.
Mucchos de estos programas ya aparecían en la versión 13.
Para indicar alguno de aquella versión que ha desaparecido
desde entonces podemos mencionar DDOSNAP.LSP.
Enumerando sólo algunos de la versión 12 tendríamos:
CHELEV.LSP, MAKE2D.LSP, MAKE3D.LSP, PTEXT.LSP,
CL.LSP y PROJECT.LSP, y de la versión 10 TABLES.LSP, lo
que nos puede dar una idea de lo útil que resulta esta función.

CONVERSIÓN ENTRE BINARIOS Y DECIMALES (I)

CONVERSIÓN DECIMAL->BINARIO

Aplicando lo visto hasta ahora podemos crear una función que


convierta cualquier número decimal, positivo o negativo, en su
representación binaria.
El manual de Personalización de AutoCAD * propone una
solución, pero que es aplicable sólo a enteros positivos:

;;;Del manual de Personalización:


;;;convierte un entero POSITIVO a una cadena
;;;en la base dada:
(defun base (bas int / ret yyy zot)
(defun zot (i1 i2 / xxx)
(if (> (setq xxx (rem i2 i1)) 9)
(chr (+ 55 xxx))
(itoa xxx)
) ;_ fin de if
) ;_ fin de defun
(setq ret (zot bas int)
yyy (/ int bas)
) ;_ fin de setq
(while (>= yyy bas)
(setq ret (strcat (zot bas yyy) ret))
(setq yyy (/ yyy bas))
) ;_ fin de while
(strcat (zot bas yyy) ret)
) ;_ fin de defun

Para números enteros positivos opera correctamente pero no


así para los negativos, como puede verse en los siguientes
ejemplos:

Nuestra función BINARIO tiene en cuenta la longitud de


palabra del sistema operativo y devuelve la secuencia correcta
de ceros y unos incluso cuando se trate de valores negativos.
La función espera recibir un entero, pero prevé el caso de un
número real, truncándolo al entero menor más cercano.
Se definen las funciones utilitarias BIT y POSBIT, esta última
utilizada para guardar el valor de la posición del bit que se
analiza. La conversión en sí misma la realiza la función
ConvierteBinario que recibe como argumentos el número a
convertir y una función predicado a plicar según el número sea
positivo o negativo.
Esta función de conversión es llamada desde la función
principal BINARIO que analiza si el número recibido es positivo
o negativo. En caso de ser negativo pasa a la función de
conversión su NOT lógico (~ numdec) y el predicado '= en
lugar del número recibido y el predicado '/= que pasaría en
caso de ser el número positivo.

;;;Binario.lsp
;;;El ciclo continuará hasta que LSH devuelva un
valor negativo
;;;significando que se ha llegado al final de la
palabra (posición
;;;extrema izquierda). El valor binario es devuelto
en formato de lista.
;;;
;;; Función auxiliar Bit: devuelve el valor decimal
del bit en la posición indicada.
;;;
(defun Bit (pos) (lsh 1 (1- pos)))
;;;
;;;
;;Función utilizada como acumulador del valor de la
posición
;;;
(defun PosBit ()
(if (null posicion)
(setq posicion 1)
(setq posicion (1+ posicion))
) ;_ fin de if
) ;_ fin de defun
;;;
;;;
;;;Función para procesamiento del número decimal
;;;Recibe como argumento el predicado a aplicar
;;;según sea el argumento numérico positivo o
negativo
;;;
(defun ConvierteBinario (numdec predicado / posicion
numbin)
(while (not (minusp (bit (1- (PosBit)))))
(setq numbin
(cons
(if
(apply
predicado
(list (logand (bit posicion) (fix
numdec)) 0)
) ;_ fin de apply
1
0
) ;_ fin de if
numbin
) ;_ fin de cons
) ;_ fin de setq
) ;_ fin de while
) ;_ fin de defun
;;;
;;;Función principal
;;;Tiene en cuenta si se trata de un número positivo
o negativo:
;;;
(defun Binario (numdec /)
(if (minusp numdec)
(ConvierteBinario (~ numdec) '=)
(ConvierteBinario numdec '/=)
) ;_ fin de if
) ;_ fin de defun

A diferencia de la función BASE, se obtiene una respuesta


correcta tanto de enteros positivos como negativos:

Si deseáramos el resultado en formato cadena en lugar de


lista, bastaría mapear 'ITOA a la lista devuelta para convertir
los enteros en cadena y aplicarle después 'STRCAT para unir
los caracteres en una cadena única:

Dedicaremos la próxima sección a la conversión en el otro


sentido, de binario a decimal
CONVERSIÓN ENTRE BINARIOS Y DECIMALES (II)

CONVERSIÓN BINARIO->DECIMAL

Esta función, complementaria de la anterior, nos permite hacer


la conversión en sentido inverso, partiendo de un valor binario
y convirtiéndolo en decimal.
Aunque lo más probable es que el tipo de entrada del dato sea
numérico, hemos diseñado una función como filtro de entrada
que analizará mediante un COND las posibles entradas
admitiendo listas y cadenas y rechazando cualquier otro tipo
de dato.
en caso de que el dato no sea de uno de estos tres tipos
admitidos, se imprimirá un mensaje de error. Si el dato es
admitido pero no es de carácter binario (incluye términos
distintos de cero ó uno, la función devolverá NIL.

;;;DECIMAL.LSP
;;;Recibe un número binario y lo convierte en decimal
;;;debe comprobar el tipo de dato recibido,
;;;que puede ser cadena, número o lista
;;;
;;;Función utilitaria BIT
;;;devuelve el valor decimal del bit en la posición
recibida:
;;;
(defun Bit (pos) (lsh 1 (1- pos)))
;;;
;;;
;;;Función utilizada como acumulador del valor de la
posición:
;;;
(defun PosBit ()
(if (null posicion)
(setq posicion 1)
(setq posicion (1+ posicion))
) ;_ fin de if
) ;_ fin de defun
;;;
;;;PREDICADOS DEFINIDOS PARA ESTA FUNCIÓN:
;;;Como filtro de entrada se emplean tres predicados
;;;definidos expresamente para ella:
;;;STRINGP, STD-DOTTED-PAIR-P y BINARIOP
;;;
;;;Predicado STRINGP
;;;Comprueba si el dato es una cadena
;;;(ver PREDICADOS DEFINIDOS POR EL USUARIO)
;;;
(defun stringp (dato) (equal (type dato) 'STR))
;;;
;;;Predicado STD-DOTTED-PAIR-P
;;;Comprueba de que se trate de un par punteado:
;;;Adaptado de la Standard Lisp Library
;;;de Reini Urban:
;;;
(defun STD-DOTTED-PAIR-P (lst)
(and (vl-consp lst) (not (listp (cdr lst))))
) ;_ fin de defun
;;;
;;;Predicado Binariop
;;;Comprueba que la lista incluya valores sólo 0 ó 1
;;;
(defun Binariop (numbin /)
(apply 'and
(mapcar '(lambda (term) (or (equal term 0)
(equal term 1)))
numbin
) ;_ fin de mapcar
) ;_ fin de apply
) ;_ fin de defun
;;;
;;;Función utilitaria NUMLISTA
;;;Recibe cualquier número decimal y devuelve los
dígitos aislados
;;;en formato de lista
;;;Si el número es real, lo trunca despreciando los
decimales
;;;
(defun Numero->Lista (numero / lista)
(while (> numero 0)
(setq lista (cons (rem (fix numero) 10) lista))
(setq numero (/ (fix numero) 10))
) ;_ fin de while
lista
) ;_ fin de defun
;;;
;;;Función utilitaria Cadena->Lista
;;;Recibe una cadena de caracteres y devuelve los
caracteres
;;;aislados en formato de lista
;;;
(defun Cadena->Lista (cadena)
(mapcar
'chr ;3.- convierte los
códigos ASCII a caracteres
(vl-string->list ;2.- convierte la
cadena a lista de códigos ASCII
cadena
) ;_ fin de vl-string->list
) ;_ fin de mapcar
) ;_ fin de defun
;;;
;;;Función ConvierteDecimal
;;;Realiza la conversión, al ser la evaluación
siempre de izquierda a derecha,
;;;debe invertir la lista para iniciar la
comprobación por el bit último de la derecha.
;;;Esta comprobación se hace mediante el mapeado de
una función LAMBDA a la lista,
;;;que comprueba si el número es cero y en caso que
no lo sea, inserta el valor
;;;decimal del bit en la lista
;;;Una vez concluido este mapeado, se le aplica la
funcióm '+ para sumar todos
;;;los valores, con lo que obtenemos el resultado de
la conversión Binario->Decimal.
;;;Devuelve un número decimal
;;;
(defun ConvierteDecimal (numbin / posicion)
(if (Binariop numbin)
(apply
'+ ; suma los valores de
la lista devuelta por MAPCAR
(mapcar
(function
(lambda (x)
(PosBit) ;5.- valora la
variable posicion
(if (not (zerop x))
(bit posicion)
0 ; en caso
contrario devuelve cero
) ;_ fin de if
) ;_ fin de lambda 7.- y los
valores devueltos quedan en una lista
) ;_ fin de function
(reverse numbin)
) ;_ fin de mapcar
) ;_ fin de apply
nil
) ;_ fin de if
) ;_ fin de defun
;;;
;;;Función filtro de entrada, considerando los
posibles tipos de entrada:
;;;Lista, cadena o número
;;;los únicos términos aceptados serán en cualquier
caso ceros o unos
;;;de detectarse otro valor, la función devuelve NIL
;;;Otro error derivaría de que la lista fuera un par
punteado
;;;Para comprobarlo utilizaríamos la función STD-
DOTTED-PAIR-P adaptada de
;;;la Standard LISP library de Reini Urban
;;;
(defun Decimal (numbin /)
(cond
((STD-DOTTED-PAIR-P numbin)
(terpri)
(princ numbin)
(princ " no es un valor admitido.")
(princ)
)
((listp numbin)
;;si es lista, convierte los términos
(setq
numbin ;;que sean cadenas en números (o átomos
simbólicos si fueran letras)
(mapcar
(function (lambda (term)
(if (stringp term)
(read term)
term
) ;_ fin de if
) ;_ fin de lambda
) ;_ fin de function
numbin
) ;_ fin de mapcar
) ;_ fin de setq
(ConvierteDecimal numbin)
)
((numberp numbin)
;;si es número lo convierte en cadena para
después hacerlo lista
(setq numbin (Numero->Lista numbin))
(ConvierteDecimal numbin)
)
((stringp numbin)
(setq numbin (mapcar 'read (Cadena->Lista
numbin)))
(ConvierteDecimal numbin)
)
(t
(terpri)
(princ numbin)
(princ " no es un valor admitido.")
(princ)
)
) ;_ fin de cond
) ;_ fin de defun

A continuación algunos ejemplos tomados de la consola de


VISUAL Lisp:
A continuación analizaremos la función LOGIOR.

LOGIOR

Más arriba planteábamos que para activar un BIT


determinando mediante la suma del valor decimal que
devuelve nuestra función (bit posición) era necesario verificar
que ese BIT no estuviera ya activo. Esto lo podemos lograr con
la función LOGIOR. También para LOGIOR recurriremos a Jon
Fleming * en su aporte a la mencionada discusión en
autodesk.autocad.customization:

(LOGIOR ...) trabaja exactamente de la misma manera,


excepto de que escribimos un 1 en la posición donde uno de
los valores o ambos valores de los argumentos que se le pasan
tienen un 1, y ponemos un 0 donde ambos argumentos deben
tener un 0. Esto es útil para combinar números. ¿Quiere
asegurarse de que la referencia a objeto INTERSECCIÓN está
activa sin afectar a otros modos de referencia que se hubieren
establecido previamente?
(setvar "OSMODE" (logior 32 (getvar "OSMODE")))

Por supuesto, tanto (logand ...) como (logior ...)


pueden aceptar más de dos argumentos. En este caso:

1. Se aplica la función a los dos primeros argumentos,


obteniendo un resultado provisional.
2. Se aplica la función al resultado provisional y al
tercer argumento, obteniendo un nuevo resultado
provisional.
3. Se repite el proceso de aplicar la función al
resultado provisional actual y al siguiente
argumento hasta agotar los argumentos pasados a
la función.

ALGUNOS EJEMPLOS:

De Tony Tanzillo <tony.tanzillo@worldnet.att.net>


tomamos los siguientes ejemplos del uso de estas funciones:

Determinar si un bloque es una Referencia Externa


(RefX):

Esta función toma la lista de asociación devuelta por


(tblsearch/siguiente), y devuelve T si el bloque es una
referencia externa:

(defun isXref (data)


(eq 4 (logand 4 (cdr (assoc 70 data))))
)

Comprobar el valor de la variable CMDACTIVE:

No debemos utilizar la función = para comprobar el valor de


CMDACTIVE, ya que su valor puede variar (y de hecho asumir
valores no documentados y totalmente inesperados). En lugar
de

(= 1 (getvar "cmdactive")),
se debe utilizar:

(eq 1 (logand 1 (getvar "cmdactive")))

Desactivar REFENT:

Para dejar sin efecto mediante LISP las referencias a objeto de


carácter permanente (establecidas mediante REFENT) sin
cambiar los valores preestablecidos (lo que equivaldría a
pulsar la tecla F3), se puede activar el noveno bit (valor
decimal de 16384):

(setvar "OSMODE" (logior (getvar "osmode") 16384))

o utilizando la función BIT antes definida:

(setvar "OSMODE" (logior (getvar "osmode")(bit 9)))

Otros ejemplos los podemos obtener de los mismos programas


LISP que acompañan a AutoCAD. Para el uso de LOGIOR
puede consultarse DDOSNAP.LSP de la versión 13,
AI_UTILS.LSP, DDCHPROP.LSP, DDMODIFY.LSP y
DDSELECT.LSP, de las versiones 13 y 14 y EXCHPROP.LSP y
MPEDIT.LSP, estos últimos de la versión 14.

BOOLE

BOOLE opera como una función lógica binaria de carácter


general, con lo que incluiría las estudiadas LOGAND y LOGIOR
y otras hasta completar las 16 posibles. su sintaxis es:

(Boole operador entero1 [entero2 ...])

El argumento operador es un entero que representa un número binario de 4


bits. cada bit establecido representa una opción según la tabla siguiente:

Bit Entero1 Entero2 Resultado:


1 1 1 _$
(boole 1
1 1)
1
_$
(boole 2
2 1 0
1 0)
1
_$
(boole 4
4 0 1
0 1)
1
_$
(boole 8
8 0 0
0 0)
-1

Cda bit de entero1 es comparado con el correspondiente bit de


entero2, especificando una fila de la table de resultados anterior. El bit resultante será
0 ó 1 según esté activado el bit del número entero que damos como operador que
corresponde a esta posición. Si el bit en cuestión está activado el bit resultante es 1; de no
ser así el resultado será 0.
Para ciertos valores del operador, BOOLE equivale a las operaciones Booleanas standard
de AND, OR, XOR, y NOR.

Operador Operación Resultado igual a 1 si de los


Binario Booleana bits analizados:
1 AND Ambos son 1
Sólo uno de ellos es
6 (4 + 2) XOR
igual a 1
7 (4 + 2 + Cualquiera de ellos es
OR
1) 1
Ambos son 0
8 NOR
(complemento de 1)

Para tratar de comprender mejor lo anterior analizaremos un


ejemplo tomado de la ayuda de Visual LISP:

(boole 6 6 5) devuelve 3

Aprovechando las funciones antes definidas hallaremos el


equivalente binario de 6, que sería (despreciando los ceros a la
izquierda) igual a 110.
Lo mismo para 5 devolvería 101.
ahira podemos hacer la comparación para cada pareja de bits:

Primera pareja: (boole 6 0 1) devuelve 1

Segunda pareja (boole 6 1 0) devuelve 1

Tercera pareja: (boole 6 1 1) devuelve 0

Con lo que tendríamos el número binario 011, que como


podemos comprobar aplicando la función DECIMAL equivale a
3. Todo esto visto desde la consola de Visual LISP sería:

Normalmente sería más cómodo emplear LOGAND y LOGIOR


cuando fuera posible reservando BOOLE para otras
operaciones lógicas binarias. Si revisamos los programas LISP
incluidos con la aplicación AutoCAD encontraremos al gunos
ejemplos del uso de esta función. Para ello podemos consultar
los archivos DDMODIFY.LSP, MPEDIT.LSP, BURST.LSP y
ASESMP.LSP de la versión 14, y PROJECT.LSP y FACE.LSP
de la Versión 12.

PREDICADOS GENERALES DEFINIDOS POR EL USUARIO


La posibilidad de desarrollar funcuiones de carácter utilitario
nos permiten completar los predicados de Visual Lisp con otros
de uso probable dentro de las utilidades que se desarrollen
dentro de Autocad.

Predicados para comprobación de tipo de objeto:


Tipos de Datos LISP:

INTEGERP
Comprueba si el argumento es un número entero.

(defun integerp (dato)(eq (type dato) 'INT))

REALP
Comprueba si el argumento es un número real.

(defun realp (dato)(eq (type dato) 'REAL))

STRINGP
Comprueba si el argumento es una cadena.

(defun stringp (dato)(eq (type dato) 'STR))


Tipos de Datos AutoCAD:

ENAMEP
Comprueba si el argumento es un nombre de entidad.

(defun enamep (dato)(eq (type dato) 'ENAME))

FILEP
Comprueba si el argumento es un identificador de archivo.

(defun filep (dato)(eq (type dato) 'FILE))

PAGETBP
Comprueba si el argumento es una Tablas de Paginación de
Funciones.

(defun pagetbp (dato)(eq (type dato) 'PAGETB))


PICKSETP
Comprueba si el argumento es un Conjunto de Selección.

(defun picksetp (dato)(eq (type dato) 'PICKSET))

SUBRP
Comprueba si el argumento es una Función AutoLISP interna o
compilada.

(defun subrp (dato)(eq (type dato) 'SUBR))

USUBRP
Comprueba si el argumento es una función de usuario cargada
desde un fichero fuente LSP.

(defun usubrp (dato)(eq (type dato) 'USUBR))

EXRXSUBRP
Comprueba si el argumento es una Aplicación ObjectARX
Externa.

(defun exrxsubrp (dato)(eq (type dato) 'EXRXSUBR))


Tipos de Datos ActiveX

SAFEARRAYP
Comprueba si el argumento es una Matriz del tipo Safearray.

(defun safearrayp (dato)(eq (type dato) 'SAFEARRAY))

VARIANTP
Comprueba si el argumento es del tipo Variant.

(defun variantp (dato)(eq (type dato) 'VARIANT))

VLA-OBJECT-P
Comprueba si el argumento es un Objeto ActiveX.

(defun vla-object-p (dato)(eq (type dato) 'VLA-


object))

ESTRUCTURAS CONDICIONALES
CONDICIONAL IF
ESTRUCTURA IF-THEN-ELSE
Evalúa expresiones condicionalmente:
(if expr_prueba expr_then [expr_else])
Si expr_prueba no es nil, evalúa expr_then;
en caso contrario evalúa expr_else.
La función if devuelve el valor de la expresión
seleccionada. Si expr_else no existe y expr_prueba es
nil, entonces la función if devuelve nil.
 Función PROGN
Calcula las expresiones secuencialmente y devuelve el
valor de la última expresión
(progn [expr]...)
Se suele utilizar progn para calcular varias expresiones
cuando sólo se espera, como en cada una de las ramas
del IF, una sola expresión.

CONDICIONALES DE USO GENERAL

En muchos casos nos encontraremos que hay más de dos


condiciones sobre las que decidir. En estos casos podemos
acudir a la función COND, que evalúa una serie de condiciones
de prueba secuencialmente.

COND
Se utiliza como la función condicional primaria de Visual
LISP
(cond (prueba1 resultado1 ...) ...)

La función cond acepta cualquier número de listas como


argumentos.
Evalúa el primer elemento de cada lista (en el orden indicado)
hasta que uno de ellos devuelva un valor distinto de nil. A
continuación, evalúa las expresiones que siguen a este
elemento y devuelve el valor de la última expresión de la
sublista. Si esta sublista sólo contiene un valor (es decir, si
falta resultado), se obtiene el valor de la expresión prueba.
COND se puede utilizar como una función de tipo case.
Es habitual utilizar T como última (por defecto) expresión de
prueba.

FUNCIONES RECURSIVAS E ITERATIVAS

Muchos de las tareas que tratamos de resolver con


ordenadores implican la ejecución de procedimientos en forma
repetitiva. LISP suministra dos paradigmas para el control de
las tareas repetitivas: la recursión y la iteración.

La definición de nuevas funciones consiste básicamente en la


combinación de llamadas a otras función que resulten
adecuadas a los fines que nos proponemos. Un caso particular
surge cuando la función se llama a sí misma. Estas funciones
suelen llamarse funciones recursivas. Obsérvese que la
función recursiva se repite invocándose a sí misma. Esto lo
hace desde la llamada inicial al programa. Así que para cada
repetición hay una nueva llamada a la función original.

Cualquier otro procedimiento repetitivo que no implique una


recursión es un procedimiento iterativo. Visual LISP
suministra una serie de funciones específicas para la
implementación de procedimientos iterativos. Las formas más
simples de establecer una iteración serían las función REPEAT
y FOREACH para listas y VLAX-FOR para colecciones de objetos
ActiveX (VLA-object). REPEAT realiza los procedimientos
especificados el número de veces que se especifique en su
primer argumento que debe ser un número entero positivo.
FOREACH suministra una iteración de manera directa sobre los
elementos de una lista. FOREACH debe recibir como segundo
argumento una forma que evalúe como lista. Entonces ejecuta
el cuerpo de instrucciones suministrado como sucesivos
argumentos una vez para cada uno de los elementos de la
lista. Cuando la conclusión del ciclo de repeticiones dependa de
que se alcance determinado estado y no sea posible
predeterminar el número de iteraciones que ello exigirá
podemos utilizar la función WHILE que admite como primer
argumento una función predicado cuyo no cumplimiento
(evaluación como NIL) determinará la salida del bucle
iterativo.

Existen también las llamadas funciones de mapeado. El


Mapeado es un tipo de iteración en la cual una función se aplica
sucesivamente a elementos de una o más secuencias de
objetos. El resultado de la iteración es una secuencia que
contiene los resultados de las aplicaciones de la función. Visual
LISP provee dos funciones de mapeado: MAPCAR, para listas y
VLAX-MAP-COLLECTION para colecciones de objetos ActiveX.

FUNCIONES RECURSIVAS

El ejemplo clásico de una función recursiva es el cálculo del


factorial de un número. Por ejemplo, 4! (factorial) es igual a 4
X 3 X 2 X 1. Algebraicamente, podemos considerar un factorial
como (n!) as n X (n-1) X (n-2) X (n-3)...(n - (n+1)). Ésta sería
la definición recursiva del factorial:

(defun factorial (n)


(cond ((zerop n) 1)
(T
(* n (factorial (1- n))))
) ;_ fin de cond
) ;_ fin de defun

Se analizan dos condiciones:

1. que el argumento recibido sea cero (predicado ZEROP). En


ese caso se devuelve 1.
2. en cualquier otro caso, se multiplica el argumento por el
resultado que devuelve aplicar de nuevo la función de
usuario factorial al argumento menos 1. Lógicamente
la evaluación de esta operación deberá esperar al
resultado de la nueva llamada a la función factorial,
que si aún no es cero, provocará una nueva llamada a la
misma función factorial y quedará a la espera de que
esta sea evaluada y así sucesivamente. Al llegar a cero el
argumento pasado a factorial, ya no se producirá una
nueva llamada a la función y se comenzará a devolver
respuestas a todas la evaluaciones que han quedado
pendientes. Este comportamiento puede verse
claramente en la ventana TRACE de visua LISP, después
de haber evaluado la función (trace factorial) .

La recursión, aunque aporta soluciones de gran elegancia,


debe ser utilizada con cautela. Lo ideal sería usarla con
funciones que no utilicen, o utilicen muy pocos argumentos, ya
que la recursión coloca cada vez una nueva copia de la función
en la memoria de pila junto con sus argumentos. Es muy
posible que una función efectúe tantas recursiones que se
agote la memoria disponible provocando un error de
desbordamiento de la pila.

Para más detalles, consultar el artículo WHAT IS


RECURSION? de Glenn Grotzinger.

ANÁLISIS DE UNA FUNCIÓN RECURSIVA

La siguiente función demuestra la posibilidad de desarrollar


funciones de usuario a partir de un corto número de primitivas.
La función BUSCA_EN_LISTA recibe como argumentos una
expresión cualquiera y una lista. En caso que la expresión esté
contenida en la lista, la función devolverá la expresión y todos
los términos de la lista que ocurren después de la expresión. Si
expresión no pertenece a la lista, la función devuelve nil. Los
resultados obtenidos son los mismos que los de la función
primitiva MEMBER. De hecho muchas de las funciones hoy
incorporadas a cualquiera de los dialectos modernos de LISP
surgieron como funciones utilitarias que eran incorporadas de
manera habitual por los usuarios.

;;;Función recursiva busca_en_lista.


;;;Equivale a la función member de LISP
;;;Esta función ha sido adaptada del tutorial
;;;LISP del ELM Research Group
(defun busca_en_lista (expresion lista)
(cond
((null lista) nil) ;si
lista está vacía o se acaba
((equal expresion (car lista)) lista) ;si se
encuentra expresion
(t (busca_en_lista expresion (cdr lista))) ;en
otro caso se efctúa la recursión
)
)
_$ (busca_en_lista '(a b) (list 2 r "sdf" '(a b)
"KLM" 77 21 jfk))
((A B) "KLM" 77 21 nil)
_$ (member '(a b) (list 2 r "sdf" '(a b) "KLM" 77 21
jfk))
((A B) "KLM" 77 21 nil)
_$

Si antes de ejecutar esta función invocamos la función TRACE:

(trace busca_en_lista)

podemos seguir en la ventana TRACE de Visual LISP los pasos


seguidos en la evaluación de esta función:

Entering (BUSCA_EN_LISTA (A B) (2 nil "sdf" (A B)


"KLM" 77 21 nil))
Entering (BUSCA_EN_LISTA (A B) (nil "sdf" (A B)
"KLM" 77 21 nil))
Entering (BUSCA_EN_LISTA (A B) ("sdf" (A B) "KLM"
77 21 nil))
Entering (BUSCA_EN_LISTA (A B) ((A B) "KLM" 77
21 nil))
Result: ((A B) "KLM" 77 21 nil)
Result: ((A B) "KLM" 77 21 nil)
Result: ((A B) "KLM" 77 21 nil)
Result: ((A B) "KLM" 77 21 nil)

Los objetivos perseguidos implican el recorrido de la lista,


elemento a elemento, comparando cada uno con la expresión
dada. La manera más sencilla para extraer un elemento de la
lista (el primero) es utilizar la función CAR. Un segundo
elemento pudiera extraerse con CADR y así sucesivamente.
Pero este método sería demasiado rígido si queremos que la
función sea aplicable a una lista de cualquier longitud.

Una solución estaría en que si el primer elemento no es el


buscado, volviéramos a ejecutar la función pero que esta vez
el segundo argumento (lista) fuera el resto de la lista,
quitando el primer término ya analizado: (cdr lista).

Así continuaríamos probando el primer término (CAR de la


lista) del resto (CDR) de la lista hasta que o termine la lista o
que el primer término sea el elemento buscado.

Las condiciones que se prueban corresponden a los tres casos


posibles al suministrar a la función BUSCA_EN_LISTA dos
argumentos, el primero de los cuales puede ser un átomo o
una lista y el segundo debe evaluar como una lista, incluso
vacía.

Primer caso: ((null lista) nil)


Cuando se encuentra esta condición, la evaluación
termina. La función evaluará en este caso como nil,
señalando que el término no ha sido encontrado. Este
sería también el caso si el segundo argumento fuera una
lista vacía.
Segundo caso: ((equal expresion (car lista)) lista)
Al igual que en el primer caso, la evaluación termina.
Pero en este caso el primer término de la lista (car lista)
es el término buscado, y se devuelve el valor de lista.
Tercer caso: (t (busca_en_lista expresion (cdr lista)))
Este es el caso en que se produce la recursión. Siempre
que lista no esté vacía (null lista) y que el primer
elemento de lista no sea igual al término buscado, se
llama de nuevo a la función BUSCA_EN_LISTA pero esta
vez se le pasa como argumento lista (cdr lista), el resto
de la lista, desechando el primer término. Así a cada
ciclo, lista se acorta en un término. Al final, si expresion
no formara parte de lista, la recursión terminaría al llegar
a ser una lista nula.

Si observamos lo devuelto en la ventana TRACE por una y otra


de estas dos funciones recursivas, observamos una diferencia.
Lo devuelto por el último nivel de recursión en la función
FACTORIAL es diferente a lo devuelto por el nivel superior. De
hecho cada nivel devuelve un resultado diferente. Para llegar
al resultado definitivo, debemos esperar a lo que devuelve
cada uno de los ciclos de la recursión. Sin embargo, en la
segunda función BUSCA_EN_LISTA, el resultado del último
nivel es ya el resultado definitivo. Es evidente que resulta una
pérdida de tiempo el esperar a que cada nivel devuelva ese
mismo resultado hasta llegar al nivel superior. Los
compiladores LISP más avanzados reconocerán una función de
este tipo y cortarán el proceso en el instante que se obtiene el
resultado del último nivel, mejorando sustancialmente la
velocidad de operación de las funciones. Este tipo de funciones
recursivas se conocen por el término inglés de TAIL-
RECURSIVE (~ recursivas por la cola).

En muchos casos, hacer una función recursiva por la cola (tail-


recursive) es posible empleando técnicas adecuadas de
programación. Siempre que esto se logre podemos esperar un
incremento significativo en la eficacia de los programas.

Nota: Según afirma Reini Urban, VLISP no es capaz de


reconocer la recursividad por la cola o tail-recursion, de
manera que aún resulta en una utilización exhaustiva de la
memoria de pila (stack). Sobre los límites valores máximos
admitidos de anidación (tamaño de pila) dicho autor propone
ejemplos en http://xarch.tu-
graz.ac.at/autocad/stdlib/STDINIT2.LSP

SU UTILIZACIÓN PRÁCTICA DENTRO DE AUTOCAD

Como ejemplo de la utilidad de los métodos de programación


expuestos, desarrollaremos un programa encaminado a
resolver un problema concreto dentro de la aplicación. En
ocasiones tenemos la necesidad de extraer la información de
las coordenadas de los vértices de una polilínea. En este caso
analizaremos una polilínea del tipo LWPOLYLINE, que se
introdujo con la versión 14 de AutoCAD. Las entidades de
AutoCAD guardan su información en forma de listas de
asociación, una lista con sublistas donde el número inicial de
cada sublista identifica el tipo de dato de que se trata. Un
proceso recursivo sería especialmente adecuado para recorrer
dicha lista extrayendo de ella la información requerida.
Pasaremos entonces a estudiar el desarrollo de una FUNCIÓN
RECURSIVA PARA LA LECTURA DE LOS VÉRTICES DE
UNA POLILÍNEA

Extracción de los vértices de una LWPOLYLINE

Los datos que sirven para definir cada una de los objetos
gráficos de AutoCAD están organizados en forma de una lista
de asociación, es decir, una lista de listas, donde la
información guardada en cada sublista se identifica mediante
un código numérico (entero) que aparece como el primer
término (CAR) cada sublista. El significado de cada código
coincide en términos generales con los códigos que identifican
los datos contenidos en los archivos del formato de fichero DXF
utilizado para la transferencia de dibujos AutoCAD a otras
aplicaciones. Para el desarrollo de la siguiente función basta
saber que los valores que corresponden a las coordenadas X e
Y de cada vértice aparecen en sucesivas sublistas identificadas
mediante el código de asociación 10. La coordenada Z aparece
en una única sublista (ya que debe ser la misma para todos los
vértices) identificada mediante el código 38. La función
recursiva descrita a continuación será llamada desde otra
función que permita seleccionar un objeto del dibujo,
compruebe a continuación que se trata de la entidad deseada
(del tipo "LWPOLYLINE"), extraiga del objeto gráfico
seleccionado la correspondiente lista de asociación y la pase,
junto con el valor de la elevación, como argumentos a la
función recursiva de extracción VertPoly.

Función ExtraeVertices:

Existen varias formas para seleccionar objetos del dibujo.


Utilizaremos en este caso la función ENTSEL que pide al
usuario designar un objeto en el dibujo y devuelve una lista
con el nombre de entidad y la lista de coordenadas XYZ del
punto que ocupaba el cursor en el momento de hacer la
designación. Este segundo dato no nos es necesario, por lo
que anidamos la función ENTSEL dentro de una función CAR. El
nombre de entidad que obtenemos de esta manera es en
realidad un puntero al archivo temporal que crea AutoCAD al
abrir un dibujo. Utilizando dicho puntero podemos localizar la
definición interna del objeto gráfico designado en pantalla y
extraerla mediante la función ENTGET:
(entget (car (entsel)))

Una sublista determinada se puede encontrar mediante la


función ASSOC, pero en este caso, las sublistas que guardan
las coordenadas de los vértices todas poseen el código 10. Por
este motivo, será necesario recorrer toda la lista, término a
término para encontrar todos los valores de coordenadas
correspondientes a los vértices. Estos valores son sólo de las
coordenadas X e Y. La Z se encuentra en una lista asociada al
código 38. Habrá que extraer este valor inicialmente y luego
agregarlo a cada uno de los vértices. Esto se hará utilizando la
función APPEND en lugar de CONS, de manera que se añada el
valor de Z en la tercera posición y no al principio de la lista. La
función devolverá una lista de listas, estas últimas cada una de
tres valores, correspondiendo a la X,Y,Z de cada vértice.

;;;Extracción de los vértices de una LWPOLYLINE


;;;Función recursiva:
;;;Recorre la lista y extrae el valor de los códigos
10
;;;Estas listas de asociación sólo incluyen los
valores
;;;de las coordenadas X e Y, el valor de Z para toda
la
;;;polilínea está asociado al código 38 este valor
así
;;;como la lista de entidad se extraen en una función
;;;inicial que además sirve para declarar como
variables
;;;locales las que almacenan la lista definitoria de
la
;;;entidad y la lista de vértices.
;;;Esta función devuelve la lista de vértices, que se
;;;puede asignar a una variable mediante setq:
;;; (setq vertlis (ExtraeVertices))
;;;(c) Reinaldo Togores, Santander, Julio 1999.
(defun VertPoly (lista elevacion / )
(cond
((null lista) nil) ;función
de salida
((equal (caar lista) 10) ;si se
trata de un vértice
(cons ;la
función append permite incluir la
(append (cdr (car lista))(list
elevacion)) ;coordenada Z en la posición correcta
(VertPoly (cdr lista) elevacion) ;y
continúa la recursión
)
)
(t (VertPoly (cdr lista) elevacion)) ;si no es
un vértice
) ;_ fin de cond
) ;_ fin de defun
(defun ExtraeVertices ( / lista_entidad)
(if
(equal
(cdr (assoc 0 (setq lista_entidad (entget (car
(entsel))))))
"LWPOLYLINE"
)
(VertPoly lista_entidad (cdr (assoc 38
lista_entidad)))
(alert "La entidad seleccionada no es una
LWPOLYLINE")
) ;_ fin de if
) ;_ fin de defun

Sin duda este código cumplirá su cometido, pero puede ser


mejorado. Este programa está redactado de manera tal que
ninguna de sus partes pudiera reutilizarse en otros futuros
programas. Un enfoque más eficaz consistiría en analizar qué
procesos de carácter más general intervienen en la solución y
programarlos como funciones utilitarias que se incorporen
como nuevas herramientas al lenguaje. Más adelante, y
después de estudiar algunos otros procedimientos y técnicas,
intentaremos precisamente esto, cómo abordar de una manera
más eficaz la solución a este programa.

FUNCIONES ITERATIVAS

ITERACIONES SIMPLES

En algunos casos puede ser conveniente otro enfoque de las


operaciones repetitivas, mediante estructuras en bucle que no
implican procedimientos de auto-referencia. Los operadores
REPEAT y FOREACH ejecutan las expresiones dadas un número
defterminado de veces. En el primer caso se trata del número
de veces que resulta de la evaluación del primero de sus
argumentos y en el segundo de la cantidad de elementos que
pertenecen a una lista. Esta última función tiene en Visual LISP
su equivalente aplicable a objetos de colección ActiveX bajo el
nombre de VLAX-FOR

Tanto para REPEAT como para FOREACH se puede establecer


de antemano el número de ciclos a realizar. Pero esa no es
siempre la situación. En muchas ocasiones no hay manera de
saberlo. En estos casos se deberá establecer, al igual que en
los procedimientos recursivos una condición de prueba que
determine la conclusión del ciclo. Para ello disponemos de la
función WHILE.

REPETICIÓN UN NÚMERO DETERMINADO DE VECES

REPEAT
Evalúa cada expresión el número de veces que se
especifique en el argumento entero, y devuelve el valor
de la última expresión
(repeat entero expresión ...)
El argumento entero debe ser un número entero
positivo.

Ejemplos de iteraciones con REPEAT:


FACTORIAL ITERATIVO
Como primer ejemplo analizaremos la función FACT2
propuesta por Dennis Shinn <jeeper@halcyon.com> en
el newsgroup comp.cad.autocaden septiembre de 1996.
La explicación es nuestra traducción libre del original.
(defun fact2 (n / x y)
(setq x 1 y 1)
(repeat (1- n)
(setq x (1+ x) y (* x y) )
)
)
En la primera línea se asigna el valor inicial correcto a las
dos variable que llamamos x e y.
La repetición se establece para un número específico de
veces según el número que se pase como argumento a la
función. Puesto que la operación del cálculo del factorial
implica la cantidad de números a multiplicar menos 1,
establecemos en (1- n) el número de repeticiones.
Ahora vienen los cálculos matemáticos: (setq x (1+ x)
y (* x y))
Durante cada iteración de esta expresión, la x se
incrementa por un valor de 1 cada vez, e y se incrementa
como el producto de ella misma y el próximo valor mayor
de x, etc., etc.
Obsérvese que no se ha implementado un control de
errores, que es algo deseable en un lenguaje de
programación. Tanto 0 como 1 quedan indefinidos, ya
que ninguno de los dos permitirá que ocurra la iteración.
El REPEAT simplemente no repite. Vale, sin embargo para
demostrar las posibilidades de un verdadero lenguaje de
programación (como LISP) para realizar verdaderas
operaciones de recursión e iteración.
Como muestra de una implementación para atrapar
posibles errores podríamos crear otra rutina fact3 que
llame a la rutina fact2 sólo en los casos apropiados:
(defun fact3 (n)
(cond
((and (numberp n)(> n 1))
(fact2 n)
)
((= n 1) 1)
(t nil)
)
)
En este caso se prueba primero si el argumento n es un
número mayor que 1, en cuyo caso se ejecuta la función
fact2. En caso de ser igual a 1 se devuelve 1, y en
cualquier otro caso se devolverá NIL.
Predicado PALINDROMOP
El predicado PALINDROMOP debe devolver T (cierto) si una
cadena de texto es un palíndromo, es decir, si se lee lo
mismo de izquierda a derecha que al revés. Se ha
utilizado la función STRLEN para determinar el número de
repeticiones, la función SUBSTR para ir extrayendo
caracteres para compararlos, uno a uno, comenzando de
izquierda a derecha y de derecha a izquierda, y la función
STRCASE de manera que se ignore la caja (mayúsculas o
minúsculas). Se emplean dos variables locales, cont y
resultado. Esta segunda se establece inicialmente como
T y bastará que no sea igual una de las parejas de letras
analizadas para que adopte el valor de NIL (falso). Es
obvio que bastará con un número de repeticiones igual a
la mitad de la longitud de la cadena de texto, pues más
allá de eso se estaría comparando las mismas parejas de
caracteres. Al ser ambos argumentos de la división
números enteros, el resultado también lo sería,
truncando los decimales, por lo que en una cadena con
número impar de caracteres no se compararía el carácter
centras consigo mismo.
(defun palindromop (cadena / cont resultado)
(setq
cont 0
resultado t
) ;_ fin de setq
(repeat (/ (strlen cadena) 2)
(if (not
(equal
(strcase (substr cadena (1+ cont) 1))
(strcase (substr cadena (- (strlen cadena)
cont) 1))
) ;_ fin de equal
) ;_ fin de not
(setq resultado nil)
) ;_ fin de if
(setq cont (1+ cont))
) ;_ fin de repeat
resultado
) ;_ fin de defun

ITERACIONES SOBRE ELEMENTOS DE UNA SECUENCIA

FOREACH
Evalúa cada expresión para todos los miembros de una
lista
(foreach nombre lista expresión ...)
Recorre la lista, asignando sucesivamente cada elemento
de la lista a la variable nombre y evaluando de esta
manera cada expresión para cada uno de los elementos
de la lista representados por nombre. Puede especificar
tantas expresiones como desee. La función FOREACH
devuelve el resultado de la última expresión evaluada.
VLAX-FOR
Efectúa una iteración a través de una colección de
objetos evaluando cada expresión
(vlax-for símbolo colección [expresión1
[expresión2 ...]])
El argumento símbolo será asignado a cada Objeto-VLA
de la colección. El argumento colección representa un
objeto ActiveX del tipo colección. [expresión1
[expresión2 ...]] son las expresiones a evaluar. La
función devuelve el valor de la última expresión evaluada
para el último objeto de la colección.

Ejemplos de iteraciones con FOREACH:

Esta pequeña subrutina, parte de un programa mayor,


imprime en pantalla en formato de columna la lista que se le
pase como argumento. En el ejemplo se usa para imprimir de
manera ordenada la lista de definición deuna entidad polilínea
(AutoCAD 2000).

;;;Listado de entidades
;;;Salida por pantalla
(defun disp_l (lis_e /)
(foreach sublista lis_e
(print sublista)
) ;_ fin de foreach
(princ) ;evita la repetición de la
última línea
) ;_ fin de defun
Command: (entget(car(entsel)))
Select object: ((-1 . <Entity name: 1487558>) (0 .
"LWPOLYLINE") (330 . <Entity
name: 14874f8>) (5 . "2B") (100 . "AcDbEntity") (67 .
0) (410 . "Model") (8 .
"0") (100 . "AcDbPolyline") (90 . 4) (70 . 0) (43 .
0.0) (38 . 0.0) (39 . 0.0)
(10 99.3432 134.975) (40 . 0.0) (41 . 0.0) (42 . 0.0)
(10 130.202 185.828) (40
. 0.0) (41 . 0.0) (42 . 0.0) (10 202.747 163.648) (40
. 0.0) (41 . 0.0) (42 .
0.0) (10 269.336 215.041) (40 . 0.0) (41 . 0.0) (42 .
0.0) (210 0.0 0.0 1.0))
;La lista impresa en pantalla resulta difícil de
leer, al no estar ordenada en columna
Command: (disp_l (entget(car(entsel))))
(-1 . <Entity name: 1487558>)
(0 . "LWPOLYLINE")
(330 . <Entity name: 14874f8>)
(5 . "2B")
(100 . "AcDbEntity")
(67 . 0)
(410 . "Model")
(8 . "0")
(100 . "AcDbPolyline")
(90 . 4)
(70 . 0)
(43 . 0.0)
(38 . 0.0)
(39 . 0.0)
(10 99.3432 134.975)
(40 . 0.0)
(41 . 0.0)
(42 . 0.0)
(10 130.202 185.828)
(40 . 0.0)
(41 . 0.0)
(42 . 0.0)
(10 202.747 163.648)
(40 . 0.0)
(41 . 0.0)
(42 . 0.0)
(10 269.336 215.041)
(40 . 0.0)
(41 . 0.0)
(42 . 0.0)
(210 0.0 0.0 1.0)

Ejemplos de iteraciones con VLAX-FOR:


LISTADO DE CAPAS

Considerado desde el punto de vista de la jerarquía de objetos


ActiveX a la que podemos acceder con Visual LISP,

AutoCAD y sus dibujos consisten en muchos objetos diferentes


tales como tipos de líneas, capas, bloques, etc. Estos objetos
se agrupan en colecciones. Las colecciones de Bloques (Block),
Grupos (Group), Conjuntos de Selección (SelectionSet),
EspacioModelo (ModelSpace) y EspacioPapel (PaperSpace)
pueden contener una variedad de objetos gráficos diferentes,
tales como líneas, arcos, texto, etc. Todas las demás
colecciones contienen objetos de un sólo tipo.
Las colecciones AutoCAD son de base cero, es decir que se
enumeran a partir de cero en sentido ascendente. Algunas
colecciones poseen objetos de manera automática. Por
ejemplo, la colección Layers (capas) simpre contiene un objeto
capa de nombre "0", mientras que otras se encuentran vacías
inicialmente.
Sutphin, J. AutoCAD 2000 VBA Programmers Reference, pág 47.

La rutina que exponemos a continuación extrae los nombres


de las capas de un dibujo aplicando la función VLAX-FOR a la
colección "Layers". Para obtener la colección "Layers"
tenemos que recorrer la jerarquía de objetos, a partir del único
objeto accesible públicamente que es el objeto "Application"
(Aplicación). La secuencia a seguir es la siguiente:

1. Obtener el objeto "Aplicación": (vlax-get-acad-object)


2. Obtener el objeto "Documento Activo" es decir, el dibujo
en que se trabaja: (vla-get-ActiveDocument (vlax-
get-acad-object))
En caso de que se vaya a hacer referencia a este objeto
en múltiples ocasiones, sería conveniente extraerlo una
sóla vez y guardarlo en una variable global:
(setq *EsteDibujo* (vla-get-ActiveDocument
(vlax-get-acad-object)))
Esta línea la situamos fuera del defun. Se ejecutaría al
cargar el programa.
3. Obtener la colección "Layers" (Capas): (vlax-get
*EsteDibujo* "Layers" )

Una vez obtenida la colección de capas se aplica mediante


VLAX-FOR a cada objeto-VLA de dicha colección la función
(setq ListaCapas (cons (vlax-get objeto-VLA "Name")
*ListaCapas*))
Aquí primero extraemos el nombre de la capa. El nombre de
cada objeto "Layer" se encuentra en la propiedad "Name" y se
puede extraer mediante (vlax-get objeto-VLA "Name"). Una
vez obtenido el nombre de la capa, la incluimos mediante
CONS en una variable local ListaCapas. Esta lista contendrá al
terminar el ciclo de VLAX-FOR los nombres de todas las capas
del dibujo activo. Ya para terminar, ordenamos la lista de
capas mediante la función ACAD_STRLSORT. Esta lista
serviría, por ejemplo para llenar una casilla de lista en un
cuadro de diálogo creado mediante el lenguaje DCL.

;;;Función ListaCapas
;;;Devuelve una lista con los nombres de capas
ordenados alfabéticamente
;;;Obsérvese la posibilidad aquí demostrada de tener
una función y una variable
;;;con idéntico nombre en un mismo entorno. Para ello
será necesario que dicha
;;;variable, en este caso ListaCapas sea una variable
local de la función.
(setq *EsteDibujo* (vla-get-ActiveDocument (vlax-get-
acad-object)))
;se ejecuta previamente
(defun ListaCapas (/ ListaCapas)
(vlax-for objeto-VLA (vlax-get *EsteDibujo*
"Layers")
(setq ListaCapas (cons (vlax-get objeto-VLA
"Name") ListaCapas))
;el mismo nombre se utiliza para la variable
local que guarda
;temporalmente el listado de capas.
) ;_ fin de vlax-for
(acad_strlsort ListaCapas)
) ;_ fin de defun

Ejemplo de su utilización:

Command: (LOAD "C:/Mis


documentos/Lisp/Fuentes/ListaCapas.lsp") LISTACAPAS
Command: !*EsteDibujo*
<#VLA-OBJECT IAcadDocument 00eb6aa4>
Command: (listaCapas)
("0" "DEFPOINTS" "DOTACIONES" "IT" "RBA" "RE-2" "RI"
"RI-1" "RIA" "SG-EL"
"SG-M" "SGP")

Más adelante se estudiará en más detalle el acceso a los


objetos ActiveX desde Visual LISP.

Unos ejemplos para concluir...

Como ejemplo del empleo dentro de AutoCAD de estas últimas


funciones estudiadas, aquí van algunas pequeñas utilidades
creadas por Vladimir Nesterovsky. La primera de ellas,
SEL2LST crea una lista de nombres de entidad a partir de un
conjunto de selección. La segunda, LST2SEL, realiza el proceso
inverso, creando un conjunto de selección a partir de una lista
de nombres de entidad:

;;;SEL2LST
;;;Recibe un conjunto de selección y devuelve una
lista
(defun sel2lst( sel / n l)
(repeat (setq n (sslength sel))
(setq n (1- n) l (cons (ssname ss n) l))
)
)
;;;LST2SEL
;;;La función opuesta, recibe una lista y devuelve un
conjunto de selección

(defun lst2sel(l / ss)


(setq ss (ssadd))
(foreach e l (ssadd e ss))
)

La siguiente función extrae el resto de una lista l a partir del


término n indicado en el primer argumento:

(defun cdnr ( n l )
(repeat n
(setq l (cdr l))
)
)

La función STRTOL recibe una cadena de caracteres s y


devuelve los caracteres aislados (cadenas de un sólo carácter)
agrupados en una lista:

(defun strtol ( s / lst c )


(repeat (setq c (strlen s))
(setq
lst (cons (substr s c 1) lst)
c (1- c)
)
)
lst
)

Como se ve, esta puede ser otra vía para abordar el desarrollo
del predicado PALINDROMOP: obtener mediante STRTOL la
cadena de caracteres en forma de lista y entonces compararla
con la lista invertida por reverse:

(defun palindromop (cadena)


(setq cadena (strtol cadena))
(equal cadena (reverse cadena))
)
CICLOS DEPENDIENTES DE UNA CONDICIONAL

Cuando no hay manera de establecer al incio del proceso


iterativo el número de repeticiones que serán necesarias se
deberá establecer, al igual que en los procedimientos
recursivos una condición de prueba que determine la
conclusión del ciclo. Para ello disponemos de la función WHILE.

WHILE
Evalúa una expresión de prueba y, si ésta no da como
resultado nil, evalúa otras expresiones para volver de
nuevo a la expresión de prueba
(while expr_prueba expr...)
La función while continúa hasta que expr_prueba es nil
. Entonces devuelve el valor más reciente de la última
expresión.
Ejemplos de Iteraciones con WHILE:
CONTEO DE ENTIDADES:

Un caso lo tendríamos en una función que tuviera como


propósito el contar las entidades que forman parte de un
dibujo. Para acceder de manera secuencial a las entidades que
forman parte de un dibujo tenemos la función ENTNEXT. Si
establecemos un contador que se incremente por cada entidad
del dibujo tendríamos, al llegar a la última entidad, el número
total de entidades. La función concluirá en el momento que la
variable ent evalúe como NIL , es decir, cuando ENTNEXT ya
no devuelva más ninguna entidad por haber alcanzado el final
de la base da datos del dibujo.

;;;Función iterativa para conteo simple


;;;de las entidades en un dibujo
;;;Se establece la variable cont como contador
;;;y la variable ent para guardar el nombre de
;;;cada entidad leída
(defun CuentaEntidades ( / cont ent)
(setq cont 1
ent (entnext) ;la función entnext
sin argumentos
;devuelve la primera
entidad del dibujo
) ;_ fin de setq
(while ent
(setq
cont (1+ cont)
ent (entnext ent) ;devuelve la entidad
que le sigue a ent
) ;_ fin de setq
) ;_ fin de while
cont ;devuelve el valor final
del contador
) ;_ fin de defun

Más adelante utilizaremos este procedimiento para desarrollar


funciones más sofisticadas dirigidas a inventariar los objetos
contenidos en un dibujo.

PREDICADO PALINDROMOP OPTIMIZADO:

La versión anterior del predicado PALINDROMOP no resulta muy


eficiente, pues comprueba de manera exhaustiva todos los
caracteres de la cadena, cuando bastaría con detectar una
primera desigualdad para decidir que no se trata de un
palíndromo. Una solución más eficaz debería interrumpir la
evaluación en ese momento. Eso lo podemos lograr mediante
un ciclo condicional usando WHILE con dos condiciones
encerradas en un AND (que devolverá NIL en cuanto una de
ellas deje de ser cierta): que no se hubiera alcanzado el
número de repeticiones determinado por (/ (strlen cadena)
2), y que el valor de resultado sea T. En cuanto se encuentre
una pareja de caracteres desigual resultado pasará a ser NIL,
con lo cual se detendrá el ciclo.

(defun palindromop (cadena / cont resultado)


(setq
cont 0
resultado t
) ;_ fin de setq
(while (and (<= cont (/ (strlen cadena) 2))
resultado)
(if (not
(equal
(substr cadena (1+ cont) 1)
(substr cadena (- (strlen cadena) cont) 1)
) ;_ fin de equal
) ;_ fin de not
(setq resultado nil)
) ;_ fin de if
(setq cont (1+ cont))
) ;_ fin de while
resultado
) ;_ fin de defun

No basta que un programa alcance los resultados deseados.


Debe hacerlo de manera rápida y eficaz. Este es un principio
que no debe olvidarse

FUNCIONES DE MAPEADO

LISP provee funciones que procesan secuencialmente los


términos de una lista suministrada como argumento.

MAPCAR
(mapcar función lista1 ... listan)
MAPCAR opera sobre los elementos sucesivos de las listas.
Primero se aplica la función al CAR de cada lista, entonces
al CADR de cada una y así sucesivamente. Lo ideal sería
que todas las listas fueran de igual longitud. Si no lo
fueran, la iteración concluye al agotarse la lista más corta
y los elementos en exceso de las demás listas se ignoran.
MAPCAR devuelve una lista con los resultados de las
sucesivas llamadas a la función. Por ejemplo:
_1$ (mapcar 'cons '(a b c) '(1 2 3))
((A . 1) (B . 2) (C . 3))
Una expresión-LAMBDA puede ser utilizada con MAPCAR.
Esto resulta útil cuando algunos de los argumentos de
función son constantes o se proporcionan mediante
variables u otros métodos.

Sobre la función MAPCAR seguramente no encontraremos una


explicación más entusiasta que la de Vladimir Nemerovski a
quien citamos a continuación:

Ahora trataremos de MAPCAR. Esta es una función que


requiere:

1. un símbolo dentro de QUOTE


o una EXPRESIÓN-LAMBDA (tabién dentro de un
QUOTE )
o una lista de función de usuario (igualmente dentro
del QUOTE)
2. alguna lista
3. y otras listas opcionales más...

Digamos que la invocamos con (mapcar 'mifunción


milista) El resultado será una LISTA de los RESULTADOS de
invocar MIFUNCIÓN con cada elemento de MILISTA. Por
ejemplo, (defun mysqr(x)(* x x)) (mapcar 'mysqr (list
1 2 3 4)) devolvería (1 4 9 16) De nuevo MYSQR aparece
aquí precedida de un apóstrofe (QUOTE), ya que MAPCAR espera
que así sea.

MYSQR espera un argumento numérico y eso es lo que


obtiene. Si yo la invocara como (MAPCAR 'MYSQR (list 1 2
"3")) LISP intentaría construir una lista de resultados como
[1] (list (mysqr 1) (mysqr 2) (mysqr "3")) y yo
obtendría un error de BAD ARGUMENT TYPE al intentar procesar
el "3". Observe de nuevo que en esta expresión [1], MYSQR
es de nuevo un QUOTED-SYM de una función de usuario
previamente definida cuyo resultado evalúa como una lista,
teniendo también una lista como primer argumento, de
manera que en este punto LISP reconoce tal lista como una
función de usuario, tratando de evaluarla, sustituyendo los
argumentos ficticios de su lista de parámetros por los
argumentos reales.

De manera que si usted quiere que alguna función sea usada


en MAPCAR y no desea que ésta permanezca por ahí sin ser
utilizada de nuevo, usted puede emplear LAMBDA. Esta función
crea un tipo de FUNCIÓN ANÓNIMA y la devuelve como
encerrada en QUOTE, tal como lo haría DEFUN (sólo que esta
crearía una función vinculada a un NOMBRE que de esta
manera permanecería formando parte del entorno).

MAPCAR es grandiosa en que no necesita saber cual es la


longitud de una lista de datos. No le importa. Simplemente la
recorre hasta llegar al final, con lo que lo sabrá en tiempo de
ejecución. De manera que usted al escribir la función (que en
LISP equivaldría al tiempo de compilación de lenguajes
compilados como C, etc.), no necesita saber la longitud exacta
de su lista de datos, lo que ¡ES FENOMENAL! Le permite tratar
fácilmente con información de longitud variable, y no
olvidemos que toda la información real sin duda lo es.

Así que puedo (mapcar 'mysqr '(1 2 3)) o puedo (mapcar


'mysqr '(1 2 3 4 5)), según haga falta.

Y hay algo más. Digamos que tengo esta función que SUMA
todos sus argumentos. Es un '+, que puedo invocar como:
(+ 1 2) ó (+ 1 2 3) etc.

Ahora cuando escribo (mapcar '+ '(1 2 3) '(4 5 6)) es lo


mismo que (list (+ 1 4)(+ 2 5)(+ 3 6)) También puedo
escribir (mapcar '+ '(1 2 3)'(4 5 6)'(7 8 9)), que
equivale a (list (+ 1 4 7)(+ 2 5 8)(+ 3 6 9)) etc.

De manera que tenemos algo más de flexibilidad aquí. Por


supuesto que yo seré el responsable de suministrar a la
función invocada con el número adecuado de argumentos que
le serán alimentados por MAPCAR, ya que de otra manera
obtendré un error de DEMASIADOS/MUY POCOS ARGUMENTOS
cuando LISP eventualmente la evalúe.

Fuente:
Subject: A short course in LISP, LAMBDA,
QUOTE, MAPCAR...
Date: Sat, 02 Nov 1996 08:50:38 -0800
From: Lu <learly@ix.netcom.com>
VLAX-MAP-COLLECTION
Aplica una función a todos los objetos de una colección.
(vlax-map-collection colección función)
El argumento colección representa un Objeto-VLA de tipo
colección. El argumento función será un símbolo o una
expresión-LAMBDA que será aplicada a colección.

Ejemplo de utilización de VLAX-MAP-COLLECTION:

Hemos visto anteriormente el desarrolo de una finción que


devuelve una lista con los nombres de las capas contenidas en
el dibujo utilizando la función VLAX-FOR. Una función similar
puede desarrollarse con VLAX-MAP-COLLECTION, con un código
resultante aun más claro y conciso. La función que se pasa a
VLAX-MAP-COLLECTION se definirá en este caso como una
expresión-LAMBDA. Obsérvese que esta expresión debe
pasarse dentro de un QUOTE.

;;Función ListaCapas utilizando VLAX-MAP-COLLECTION


;;;Devuelve una lista con los nombres de capas
ordenados alfabéticamente
(setq *EsteDibujo* (vla-get-ActiveDocument (vlax-get-
acad-object)))
;se ejecuta previamente
(defun ListaCapas (/ ListaCapas objeto-VLA)
(vlax-map-collection
(vlax-get *EsteDibujo* "Layers")
'(lambda (objeto)
(setq ListaCapas (cons (vlax-get objeto
"Name") ListaCapas))
) ;_ fin de lambda
) ;_ fin de vlax-map-collection
(acad_strlsort ListaCapas)
) ;_ fin de defun

Su utilización devuelve idénticos resultado que la anterior


definida con VLAX-FOR.

Unos ejemplos para concluir...

Conversión de cadena a lista:

Conversión de cadena a lista, con MAPCAR, en una sóla línea.


Utilizando esta función podemos convertir al instante cadenas
en listas con sus caracteres aislados:

_$ (defun CadenaLista (cadena)(mapcar 'chr (vl-


string->list cadena)))
CADENALISTA
_$ (cadenalista "Madrid")
("M" "a" "d" "r" "i" "d")

REMOVE en una sóla línea:

Gracias a Reini Urban hemos podido conocer este brillante


ejemplo desarrollado por Sergei Volkov, implementando la
función REMOVE (QUITAR) en una sóla línea:

;;;REMOVE, por Segei Volkov


;;;Recibe una expresión expr y una lista lst.
;;;Devuelve la lista eliminando de ella todas las
ocurrencias de la expresión.
;;;Paso 1: Mediante MAPCAR aplica la función LIST a
cada término de la lista
;;; Si recibe '(a b c) obtendrá '((a) (b) (c))
;;;Paso 2: Utiliza la función SUBST para sustituir
por NIL cada aparición de
;;; la expresión expr
;;;Paso 3: Aplica mediante APPLY la función APPEND a
los miembros de la lista.
;;; de esta manera desaparecen los NIL (listas
vacías) que sustituyeron
;;; a la expresión eliminada
(defun remove (expr lst)
(apply 'append (subst nil (list expr) (mapcar
'list lst)))
)

Ejemplo:

_$ (remove 'a '( b c d f a g h j))


(B C D F G H J)
_$

Extracción de los vértices de una Polilínea:

En un capítulo anterior, al estudiar los procesos recursivos,


habíamos desarrollado una función destinada a extraer los
valores de los vértices de una polilínea. Con los procesos que
hemos estudiado hasta ahora pudiéramos intentar el
enunciado de una función más concisa y clara para obtener
este resultado. A ello dedicaremos el próximo apartado.

EXTRACCIÓN DE LOS VÉRTICES DE UNA POLILÍNEA:


Una manera más eficaz de abordar el problema.

El programa expuesto en el capítulo sobre los procedimientos


recusivos, ha sido elaborado utilizando sólo las funciones
primitivas conocidas entonces de AutoLISP. Una manera más
eficaz de encarar su análisis sería la de tener en cuenta si
algunos de los procesos que se llevan a cabo dentro del mismo
pudieran programarse independientemente, como funciones
utilitarias. Una función utilitaria es un nuevo operador que
añadimos al lenguaje de programación para resolver
situaciones que pueden presentarse con cierta frecuencia
dentro de nuestros programas. En el caso que estamos
analizando, podemos concebir la necesidad de una nueva
función que recorra una lista y de acuerdo con el resultado de
una función que se le pase como predicado elimine unos
términos y conserve otros. Como resultado tendríamos una
lista que sólo incluyera los términos deseados, en este caso las
listas de asociación identificadas con los códigos 10. Estas
funciones que pudiéramos llamar QUITAR-SI y su
complementaria QUITAR-SI-NO han estado siempre entre las
primeras que los programadores LISP han añadido a su
repertorio de utilidades. Una definición de las mismas pudiera
ser:

;;;QUITAR-SI
;;;Implementación de la función REMOVE-IF
;;;en contexto AutoLISP
(defun quitar-si (predicado lista)
(cond
((null lista) nil)
((apply predicado (list (car lista)))
(quitar-si predicado (cdr lista))
)
(t (cons (car lista)(quitar-si predicado (cdr
lista))))
)
)
;;;QUITAR-SI-NO
;;;Implementación de la función REMOVE-IF-NOT
;;;en contexto AutoLISP
(defun quitar-si-no (predicado lista)
(cond
((null lista) nil)
((apply predicado (list (car lista)))
(cons (car lista)(quitar-si-no predicado (cdr
lista)))
)
(t (quitar-si-no predicado (cdr lista)))
)
)

Tanto es así que se han incorporado como operadores a la


norma de Common LISP bajo los nombres de REMOVE-IF
(QUITAR-SI) y de REMOVE-IF-NOT (QUITAR-SI-NO). Visual
LISP les incorpora el prefijo VL- para distinguirlos de las
funciones del antiguo AutoLISP, y así las encontraremos en el
catálogo de funciones Visual LISP como VL-REMOVE-IF y VL-
REMOVE-IF-NOT.
Utilizando VL-REMOVE-IF-NOT y con la ayuda de la función de
mapeado sobre listas MAPCAR, estudiada en el apartado
anterior, nuestra función VertPoly pudiera escribirse de la
siguiente manera:

;;;Función VertPoly utilizando VL-REMOVE-IF-NOT


;;;Paso 1:
;;;Eliminar todos las sublistas que no correspondan
al código 10
;;;Paso 2:
;;;Extraer el CDR de cada una de las sublistas.
;;;Se utiliza el mapeado de la función CDR a la lista
mediante MAPCAR
;;;Paso 3:
;;;Se añade el valor de la elevación mapeando a la
lista una expresión LAMBDA.
(defun VertPoly (lista elevacion)
(mapcar ;Paso 3
(function (lambda (x) (append x (list
elevacion))))
(mapcar ;Paso 2
'cdr
(vl-remove-if-not ;Paso 1
(function (lambda (x) (equal (car x)
10)))
lista
) ;_ fin de vl-remove-if-not
) ;_ fin de mapcar
) ;_ fin de mapcar
) ;_ fin de defun

Obsérvese el uso de la función FUNCTION que fuerza la


compilación de las dos expresiones-LAMBDA. Se utiliza en
lugar de QUOTE (o apóstrofe) procurando una mayor eficacia y
rapidez.

Esta segunda formulación resulta más clara y más económica


en cuanto a esfuerzo de programación. Por otra parte, supera
las limitaciones de Visual LISP en cuanto a la recursión que
pudiera provocar en caso de polilíneas con un gran número de
vértices un error por desbordamiento de pila.
Nota: esta función devuelve los resultados correctos para
Visual LISP en AutoCAD 2000. Sin embargo hemos encontrado
que falla si se ejecuta desde el IDE Visual LISP para la versión
14. El error se encuentra en los valores de la lista de
asociación para el objeto LWPOLYLINE que se obtienen desde
este entorno:

(entget(car(entsel))) aplicado a una LWpolyline en la línea


de comandos de AutoCAD R14 devuelve:

Command: (entget(car(entsel)))
Select object:
((-1 . <Entity name: 43b0500>) (0 . "LWPOLYLINE")
(5 . "20")
(100 . "AcDbEntity") (67 . 0) (8 . "0") (100 .
"AcDbPolyline")
(90 . 7) (70 . 0) (43 . 0.0) (38 . 0.0) (39 . 0.0)
(10 103.882 154.494) (40 . 0.0) (41 . 0.0) (42 . 0.0)
(10 180.771 201.906) (40 . 0.0) (41 . 0.0) (42 . 0.0)
(10 186.224 143.05) (40 . 0.0) (41 . 0.0) (42 . 0.0)
(10 267.476 167.028) (40 . 0.0) (41 . 0.0) (42 . 0.0)
(10 256.569 105.994) (40 . 0.0) (41 . 0.0) (42 . 0.0)
(10 298.558 123.432) (40 . 0.0) (41 . 0.0) (42 . 0.0)
(10 293.651 89.1) (40 . 0.0) (41 . 0.0) (42 . 0.0)
(210 0.0 0.0 1.0))

Obsérvese que las listas asociadas al código 10 contienen dos


números reales además del código de asociación: (10 256.569
105.994) es decir, las coordenadas X e Y solamente. Sin
embargo, al ejecutarlo desde la consola de Visual LISP aparece
un tercer número real: (10 256.569 105.994 0.0),
representando el valor de Z:

_$ (entget(car(entsel)))
((-1 . <Entity name: 43b0500>) (0 . "LWPOLYLINE")
(5 . "20")
(100 . "AcDbEntity") (67 . 0) (8 . "0") (100 .
"AcDbPolyline")
(90 . 7) (70 . 0) (43 . 0.0) (38 . 0.0) (39 . 0.0)
(10 103.882 154.494 0.0) (40 . 0.0) (41 . 0.0) (42 .
0.0)
(10 180.771 201.906 0.0) (40 . 0.0) (41 . 0.0) (42 .
0.0)
(10 186.224 143.05 0.0) (40 . 0.0) (41 . 0.0) (42 .
0.0)
(10 267.476 167.028 0.0) (40 . 0.0) (41 . 0.0) (42 .
0.0)
(10 256.569 105.994 0.0) (40 . 0.0) (41 . 0.0) (42 .
0.0)
(10 298.558 123.432 0.0) (40 . 0.0) (41 . 0.0) (42 .
0.0)
(10 293.651 89.1 0.0) (40 . 0.0) (41 . 0.0) (42 .
0.0)
(210 0.0 0.0 1.0))
_$

Este comportamiento constituye un error que ya ha sido


superado en la versión 2000.

¿Cuándo usar la Recursión y cuándo la Iteración?

Tomado del tutorial por © Collin Allen y Maneesh


Dhagat, April 1997.
Traducción del inglés: Reinaldo Togores, 1999.

Hay muchos problemas para los cuales la recursión resulta natural y para los cuales
la iteración resulta extremadamente difícil. Esto sucede usualmente cuando se tienen
en cuenta objetos con una estructura compleja de listas anidadas. Por ejemplo,
consideremos esta expresión matemática en formato LISP:

(setq math-formula
'(+ 3 (* (- 5 pi) 4 (/ 3 7))(* 15 2))
)

Math-formula contiene listas dentro de listas dentro de listas.

Supongamos que quisiéramos saber cuántos números están


sepultados en las profundidades de esta fórmula. He aquí una
función recursiva que lo averiguará:

(defun num-nums (mf)


(cond
((null mf) 0) ;; la lista vacía
no contiene ninguno
((numberp (car mf)) ;; si el primer
término es un número
(1+ (num-nums (cdr mf)))) ;; sumar al número
en el resto
((atom (car mf)) ;; si es cualquier
otro átomo
(num-nums (cdr mf))) ;; ignorarlo,
contar el resto
(t (+ (num-nums (car mf)) ;; o se trata de
una lista a contar
(num-nums (cdr mf)))))) ;; y sumar al
numero en el resto

Pruebe esta función y examine su operación usando TRACE. Observe que la


profundidad de recursión fluctúa a medida que las sublistas son procesadas.

>(num-nums math-formula)
1> (NUM-NUMS (+ 3 (* (- 5 PI) 4 (/ 3 7)) (* 15 2)))
2> (NUM-NUMS (3 (* (- 5 PI) 4 (/ 3 7)) (* 15 2)))
3> (NUM-NUMS ((* (- 5 PI) 4 (/ 3 7)) (* 15 2)))
4> (NUM-NUMS (* (- 5 PI) 4 (/ 3 7)))
5> (NUM-NUMS ((- 5 PI) 4 (/ 3 7)))
6> (NUM-NUMS (- 5 PI))
7> (NUM-NUMS (5 PI))
8> (NUM-NUMS (PI))
9> (NUM-NUMS NIL)
<9 (NUM-NUMS 0)
<8 (NUM-NUMS 0)
<7 (NUM-NUMS 1)
<6 (NUM-NUMS 1)
6> (NUM-NUMS (4 (/ 3 7)))
7> (NUM-NUMS ((/ 3 7)))
8> (NUM-NUMS (/ 3 7))
9> (NUM-NUMS (3 7))
10> (NUM-NUMS (7))
11> (NUM-NUMS NIL)
<11 (NUM-NUMS 0)
<10 (NUM-NUMS 1)
<9 (NUM-NUMS 2)
<8 (NUM-NUMS 2)
8> (NUM-NUMS NIL)
<8 (NUM-NUMS 0)
<7 (NUM-NUMS 2)
<6 (NUM-NUMS 3)
<5 (NUM-NUMS 4)
<4 (NUM-NUMS 4)
4> (NUM-NUMS ((* 15 2)))
5> (NUM-NUMS (* 15 2))
6> (NUM-NUMS (15 2))
7> (NUM-NUMS (2))
8> (NUM-NUMS NIL)
<8 (NUM-NUMS 0)
<7 (NUM-NUMS 1)
<6 (NUM-NUMS 2)
<5 (NUM-NUMS 2)
5> (NUM-NUMS NIL)
<5 (NUM-NUMS 0)
<4 (NUM-NUMS 2)
<3 (NUM-NUMS 6)
<2 (NUM-NUMS 7)
<1 (NUM-NUMS 7)
7

Sería difícil definir num-nums de forma iterativa. No es imposible, pero exige el


saber utilizar una pila para simular la recursión.

Muchas tareas de inteligencia artificial implican el buscar a


través de estructuras anidadas. Por ejemplo, las
representaciones en árbol de los movimientos en un juego se
representan mejor como listas anidadas. Examinar este árbol
implica un rastreo recursivo a través del mismo. Para este tipo
de aplicación las funciones recursivas resultan una herramienta
esencial.

¿Cuándo debemos utilizar la iteración y cuándo la recursión?


Hay por lo menos estos tres factores a considerar:

(1)
La funciones iterativas son usualmente más rápidas que
sus contrapartes recursivas. Si la velocidad es
importante, normalmente usaríamos la iteración.
(2)
Si la memoria de pila es un limitante, se preferirá la
iteración sobre la recursión.
(3)
Algunos procedimientos se programan de manera
recursiva de forma muy natural, y resultan prácticamente
inabordables iterativamente. Aquí la elección es clara.

EXPORTAR LA LISTA DE ENTIDAD

Para los usuarios de DOS, las siguientes rutinas permiten


escribir en pantalla (con salto de línea) o en fichero las listas
definitorias de las entidades seleccionadas.
Para utilizar el código que sigue, selecciónelo en su browser,
utilizando el ratón, desde la línea que dice -INICIO DEL
PROGRAMA- hasta la línea -FIN DEL PROGRAMA-, copie el texto
seleccionado (Ctrl+C), abra un editor (Notepad o Visual LISP)
y péguelo allí (Ctrl+V). Guarde el fichero así creado con el
nombre LISTENT.LSP en el subdirectorio SUPPORT . Para
cargarlo teclee (load "listent")

Reinaldo Togores. Profesor Asociado.


Departamento de Ingeniería Geográfica y Técnicas de
Expresión Gráfica
Universidad de Cantabria. Diciembre de 1996.

Se autoriza la utilización del código de estos programas,


mencionando siempre su origen.

Para comentarios y sugerencias:


reinaldo.togores@unican.es

;;;----------------------INICIO DEL
PROGRAMA---------------------------------------------
----
;;;(c) Reinaldo Togores, Santander, 1997.
;;Listado de entidades
;;Salida por pantalla
;;Rutina DISP_L: Escribe lista de entidad en pantalla
(defun disp_l ( lis_e / )
(repeat (length lis_e)
(princ (nth cont lis_e))(terpri)
(setq cont (1+ cont))
)
)
;;Rutina PRN_L: Escribe lista de entidad en fichero
de texto
(defun prn_l (lis_e / )
(repeat (length lis_e)
(print (nth cont lis_e) if_w)
(setq cont (1+ cont))
)
)
;;Rutina OPN_L: Abre fichero de texto para escribir
lista de entidad
(defun opn_l ()
(if (null nomls)(setq nomls ""))
(if (setq nomls (getfiled "Lista de Entidad:"
nomls "txt" 5))
(if (wcmatch nomls "*`.*")
(setq if_w (open nomls "w"))
(setq if_w (open (strcat nomls ".txt") "w")
)
)
)
)
;;Programa Principal LISTX: Escribe la lista de
entidad a un fichero de texto
(defun c:listx ( / n_e lis_e ctrl cont)
(if (setq s_set (ssget))
(progn
(opn_l)(setq s_c 0)
(repeat (sslength s_set)
(if (setq n_e (ssname s_set s_c))(setq
lis_e (entget n_e (list "*"))))
(setq cont 0)
(prn_l lis_e)
(if
(or
(= (cdr (assoc 0 lis_e))
"POLYLINE")
(= (cdr (assoc 0 lis_e)) "INSERT")
)
(progn
(setq ctrl t)
(while (and ctrl (setq n_e (entnext
n_e)))
(setq cont 0)
(if (= (cdr (assoc 0 (setq lis_e
(entget n_e (list "*"))))) "SEQEND")
(progn (setq ctrl nil)(prn_l
lis_e))
(prn_l lis_e)
)
)
)
)
(setq s_c (1+ s_c))
)
(close if_w)
)
)
)
;;Programa Principal LIS: Salida por pantalla
(defun c:lis ( / n_e lis_e ctrl cont)
(if (setq n_e (car (entsel)))(setq lis_e (entget
n_e (list "*"))))
(setq cont 0)
(disp_l lis_e)
(setq ctrl t)
(while (and ctrl (setq n_e (entnext n_e)))
(setq cont 0)
(if (= (cdr (assoc 0 (setq lis_e (entget n_e
(list "*"))))) "SEQEND")
(setq ctrl nil)
(disp_l lis_e)
)
)
)
(alert "Lista de Entidades\teclee LIS para salida por
pantalla\nTeclee LISTX para salida a fichero...")
;;;----------------------FIN DEL
PROGRAMA---------------------------------------------
----

S O L U C I O N E S C O N
SPLINE
por Reinaldo Togores

Este plano topográfico construido con polilíneas de


tramos rectos, sin amoldar a curva, ocupaba
originalmente 1.268 Kbytes. Con la aplicación al
mismo del programa AutoLISP PL2SP logró una
reducción a 581 Kbytes, un 46 % del original.

La aplicación de este programa a polilíneas adaptadas


a curva-B, como se explica en el texto, logra
reducciones mucho más espectaculares. Según
pruebas realizadas, hasta 17 veces menor.

PolyLINES & BYTES

Al utilizar AutoCAD para la realización de mapas, usualmente


tenemos que trabajar con ficheros extremadamente grandes.
En muchos casos el tamaño de estos ficheros resulta de las
curvas de nivel, y son provocados por la manera en que estas
son generadas. Las curvas de nivel se digitalizan como
polilíneas en modo "LINEA". Para curvarlas se aplica después
el comando EDITPOL (PEDIT) con la opción SPLINE. Este
procedimiento genera una gran cantidad de vértices
suplementarios, provocando con ello un incremento en el
tamaño del fichero respecto del original que contenía sólo
polilíneas sin amoldar. Otra consecuencia de esta adaptación
es que la polilínea así adaptada no pasará por todos los
vértices, suavizándose el perfil original. He realizado algunas
pruebas para determinar la incidencia de este factor sobre el
tamaño del fichero, dibujando una polilínea y creando con ella
una matriz de 10 x 10 elementos.

PLINE.DWG

Utilizando los parámetros por defecto de AutoCAD, y


adaptando las polilíneas con EDITPOL(PEDIT)/SPLINE, el
resultado ha sido el siguiente:

Polilínea sin adaptar: PLINE DWG 238.770


bytes
Polilínea adaptada a B-Spline: B-SPLINE DWG 1.930.838
bytes

Como se ve, el tamaño del fichero se incrementa algo más de


ocho veces. Este factor de incremento puede sin embargo
reducirse si antes de realizar la adaptación se reduce el valor
de la variable SPLINESEGS (que por defecto es 8). Con
SPLINESEGS en 2 el resultado es:
Con SPLINESEGS=2 B-SPLIN1 DWG 375.520
bytes

Con ello se reduce el número de segmentos que se emplean


para aproximar la curva teórica. Esta adaptación, sin embargo
presentará un aspecto de línea quebrada, tanto más cuanto
menor sea el valor de SPLINESEGS empleado, pues los
segmentos generados son rectos. Un mejor resultado desde el
punto de vista de su apariencia se lograría a partir de dar a la
variable SPLINESEGS un valor negativo, en este caso -2 de
manera que la adaptación se hiciera empleando arcos en lugar
de segmentos rectos. En este caso se estaría combinando la
adaptación tipo "curva-B" (B-spline) que se realiza a partir de
segmentos rectos, con un posterior curvamiento del tipo
"adaptar curva" (fit curve) tradicional de AutoCAD, a base de
arcos de círculo.

Para lograr una aproximación mejor a los vértices originales,


resulta también conveniente ajustar la variable SPLINETYPE,
cuyo valor por defecto es 6 (B-spline cúbica), haciéndola igual
a 5 (B-spline cuadrática). Esto sería una solución para nuevos
planos, pero resultaría bastante laborioso aplicarlo
manualmente a los ya existentes, pues implicaría el
"readaptar" las polilíneas con el nuevo valor de SPLINESEGS,
para lo cual sería necesario seleccionarlas una a una con la
orden EDITPOL (PEDIT). Algo de este tipo pudiera
implementarse a partir de un programa AutoLISP que
automatizara el proceso.

Más recomendable para los ficheros existentes, sería utilizar


algún programa que optimizara las curvas adaptadas,
eliminando los vértices superfluos. Existen en el mercado
varias aplicaciones que cumplen este propósito. La más
interesante parece ser la aplicación CURVEFIT de TCI
Software. Esta aplicación transforma las polilíneas en
"poliarcos" aproximando los mismos a los vértices originales
dentro del factor de tolerancia fijado por el usuario eliminando
aquéllos que no fueran imprescindibles a esta aproximación.
Otras utilidades que realizan una depuración de vértices según
determinados parámetros ajustables por el usuario son WEED
(incluido en el paquete de utilidades shareware de Jerry
Workman VECTOR) distribuido por Mountain Software y
PFILTER, una utilidad de distribución libre creada por Steve
Johnson para WAWA, la Water Authority of Western Australia.

LA SOLUCIÓN SpLINE

La versión 13 de AutoCAD introduce una entidad "SPLINE",


que dibuja directamente curvas cúbicas o cuadráticas del tipo
NURBS. Esta curva se genera como tal, sin necesidad de un
amoldamiento posterior, y pasa por todos los vértices que se
designen. Es una entidad única, no como la polilínea que
resulta de una concatenación o secuencia de entidades
VERTEX individuales, con lo que se logra además un formato
mucho más compacto en cuanto a su almacenaje en disco.
Una spline trazada con los mismos vértices que la polilínea
original (y como ella reproducida 100 veces en matriz) dio el
siguiente resultado:

Curva NURBS SPLINE DWG 109.090 bytes

Otro indicador de la eficacia en la gestión gráfica a partir de


esta entidad la obtenemos de aplicar sobre los distintos
dibujos el comando STATUS:

DIBUJO UTILIZANDO SPLINES:


Command: _status 131 objects in C:\USR\DWG\spline
Free physical memory: 6.8 Mbytes (out of 31.4M).
Free swap file space: 84.6 Mbytes (out of 100.9M).
Virtual address space: 57.4 Mbytes.

DIBUJO UTILIZANDO POLILÍNEAS SIN ADAPTAR:


Command: status 5731 objects in C:\USR\DWG\pline
Free physical memory: 5.6 Mbytes (out of 31.4M).
Free swap file space: 84.3 Mbytes (out of 100.9M).
Virtual address space: 57.8 Mbytes.

DIBUJO CON POLILÍNEAS ADAPTADAS:


Command: _status 47431 objects in C:\USR\DWG\b-spline
Free physical memory: 0.2 Mbytes (out of 31.4M).
Free swap file space: 78.4 Mbytes (out of 100.9M).
Virtual address space: 76.3 Mbytes.

Es notable la diferencia en la cantidad de entidades registradas


en el dibujo: de 131 en el realizado con splines a 47.431 en el
de las polilíneas adaptadas. También merece atención el hecho
de que después de cargado el dibujo, el hecho con splines deja
libres 5.6 Megabytes de 31.4 totales, mientras que con el de
las polilíneas adaptadas se consume prácticamente la totalidad
de la RAM, con lo que se empezaría a paginar a disco de
inmediato, ralentizando con ello el proceso de trabajo.

La solución ideal para la optimización de estos planos estaría


en un programa (AutoLISP o ADS) que, una vez depuradas las
polilíneas (con CURVEFIT, WEED, PFILTER u otro programa
similar), creara splines a partir de una lectura de los vértices
resultantes, con lo que se lograría una dimensión óptima del
dibujo.

De SpLINES a PolyLINES:

Como nueva entidad en AutoCAD, SPLINE deja aún muchos


cabos sueltos. Como de costumbre, estos cabos sueltos son
campo propicio para la acción de los programadores AutoLISP,
que vienen a aportar soluciones que en muchos casos se
incorporan a las próximas versiones del programa.

Uno de estos cabos sueltos está en la conversión entre las


entidades POLILÍNEA y SPLINE. La conversión polilínea - spline
está resuelta, siempre que se haya realizado previamente su
adaptación mediante la opción "curva-B" (SPLINE). Esta
posibilidad, que forma parte de las opciones del comando
SPLINE, tiene el inconveniente de exigir un amoldamiento
previo a curva-B utilizando el incómodo comando EDITPOL
(PEDIT).
Algo que no ofrece AutoCAD es la conversión en sentido
inverso: la conversión SPLINE > POLILÍNEA. Decimos "no
ofrece" pues no es que no esté resuelta. Lo que no está es
disponible de manera directa, como tal conversión. El proceso
que habrá de seguir resulta bastante alambicado: habrá que
salvar el dibujo (o la parte de él que querramos convertir)
como dibujo de la versión 12 (saveasR12). Al reabrir el dibujo
en la versión 13 encontraremos que nuestra SPLINE es ahora
una perfecta polilínea. Aunque esta conversión deja aún
mucho que desear, pues es polilínea, pero siempre 3D. Faltaría
la capacidad de discriminar si se trata de entidades coplanares
para entonces convertirla a una polilínea 2D. Si la opción de
conversión no aparece como tal, quizás se deba a que
Autodesk desea reservarse algunas "novedades" para
vendernos la próxima versión. De nuevo pudiéramos
ingeniarnos para, aprovechando la posibilidad de salvar los
splines como versión 12, crear una orden que automatizara
(recurriendo a un simple SCRIPT) el siguiente proceso:

 seleccionar las entidades spline del dibujo


 exportarlas mediante la orden BLOQUEDISC (WBLOCK)
 salir del dibujo actual y entrar en el nuevo dibujo creado
 salvar este nuevo dibujo como R12
 volver al dibujo anterior
 importar, insertándolo, el dibujo salvado como R12
 borrar los dibujos creados en los dos pasos intermedios

Un intento de programa AutoLISP que realiza una


conversión aproximada de una Spline en Polilínea fue
publicado hace algún tiempo por Vladimir Nesterowsky en el
foro de discusión comp.cad.autocad. Aún cuando la polilínea
que se logra no resulta idéntica al spline original, resulta
interesante como ejemplo del procesamiento de entidades
recurriendo al examen de sus datos de entidad. Que la
conversión no resulta sencilla a nivel de programación de
usuario, lo demuestra la rutina de conversión de B. Stamm y
G. Stoykov publicada por AutoDESK en su colección de
documentación técnica on-line, fit_spln.lsp - Converting
Splines to Polylines . Esta rutina, mucho menos pretenciosa
que la de Vladimir, se reduce a marcar puntos sobre la spline
mediante el comando DIVIDE, para después unir los puntos
con una polilínea de tramos lineales.

Cabría preguntar porqué sería necesario transformar una curva


spline en polilínea. Se me ocurren, al menos, los cuatro
siguientes motivos:

 Muchos programas que reciben sus datos de ficheros


DWG aún no reconocen las splines.
 AutoCAD no suministra la manera de "juntar" splines. Si
se interrumpiera accidentalmente el dibujo de una spline,
habría que iniciarla desde el principio para lograr
continuidad.
 Tampoco permite darle un grosor a la entidad, cosa que
sí hace con las polilíneas.
 El comando EQDIST (OFFSET) no opera de manera
satisfactoria con las SPLINES, fragmentándolas de
manera imprevisible.
 La opción TTR del comando CIRCULO no funciona con
SPLINES (y tampoco con ELIPSES)

El primero de estos problemas seguramente se resolverá en


las próximas versiones de dichas aplicaciones. El segundo sería
tema para un programa AutoLISP. Seguramente alguno de
vosotros ya lo ha intentado. Si es así tendría mucho gusto en
publicarlo. En cuanto al grosor de las entidades, es un tema
pendiente para AutoCAD. ¿Hasta cuándo, AutoDESK? Mientras
tanto puede seguirse gestionando como se ha hecho para
todas las demás entidades (excepto polilíneas) hasta ahora,
asignándole un grosor al color en el momento de trazar. En
cuanto al último aspecto, suponemos que se trata de un "bug"
reparable en el futuro.

HACERLO AL REVÉS: DE PolyLINE A SpLINE

Desde mi punto de vista, el programa más urgente a elaborar


era uno que convirtiera una polilínea en spline, pero no
mediante la opción EDITPOL (PEDIT) que incrementa el
tamaño de la entidad introduciendo nuevos vértices y que no
acierta a pasar por los puntos originalmente digitizados. Esta
nueva función simplemente podría leer los vértices originales
de la polilínea, descartando aquéllos que fueran producto del
curvado. Con ello reduciríamos en gran medida la dimensión
de los ficheros de dibujo que como los planos topográficos
incluyan una gran cantidad de polilíneas curvadas.

La solución que proponemos para ello se encuentra en el


fichero PL2SP.LSP . Los resultados de su aplicación son
impresionantes. Como prueba hemos utilizado uno de los
ficheros de dibujo mencionados más arriba: el de las polilíneas
adaptadas a B-Spline, B-SPLINE DWG de 1.9 Megabytes, el
que llega a reducirse a sólo 110 Kbytes. Debo advertir que la
reducción no es aparente a primera vista. Es necesario, una
vez ejecutado el programa, comprimir el dibujo mediante la
orden LIMPIA (PURGE), o salvándolo habiendo establecido
previamente el valor de la variable ISAVEPERCENT en 0 (cero),
o exportándolo mediante BLOQUEDISC (WBLOCK) Los efectos
conseguidos pueden verse en el siguiente listado:

Dibujo original: B-SPLINE DWG 1.930.838 bytes


Ejecutado PL2SP: PRUEBA DWG 2.027.294 bytes
Limpiando el DWG: PURGE DWG 110.683
bytes
Salvando con BLOQUEDISC: WBLOCK DWG 110.228 bytes

C O N V E R S I Ó N
PolyLINE>SpLINE

Para utilizar el código que sigue, selecciónelo en su browser,


utilizando el ratón, desde la línea que dice "-INICIO DEL
PROGRAMA-" hasta la línea "-FIN DEL PROGRAMA-", copie el
texto seleccionado (Ctrl+C), abra un editor (Notepad o Visual
LISP) y péguelo allí (Ctrl+V). Guarde el fichero así creado con
el nombre PL2SP.LSP en un directorio que figure en la variable
de entorno ACAD (por ejemplo, el subdirectorio SUPPORT) .
Para cargarlo teclee (load "PL2SP")

Reinaldo Togores. Profesor Asociado.


Departamento de Ingeniería Geográfica y Técnicas de
Expresión Gráfica
Universidad de Cantabria. Marzo de 1997.

Se autoriza la utilización del código de estos programas,


mencionando siempre su origen.

Para comentarios y sugerencias:


reinaldo.togores@unican.es

Una versión de este programa, capaz de procesar LWPOLYINES


ha sido elaborado por Oscar Rodríguez de Sevilla, España. Para
acceder al mismo, pulse AQUÍ.

;;--INICIO DEL PROGRAMA/PROGRAM BEGINS


HERE--------------
;:***************************************************
****
;;Sustitución PLINE->SPLINE
;;Lee los vértices originales de una polilínea y crea
a
;;partir de ellos una SPLINE. Borra la pollínea
original.
;;(C) Reinaldo Togores, Santander, 1997
;;---------------------------------------------------
----
;;Nota: Este programa sólo funcionará correctamente
si el
;;Sistema de Coordenadas de Objeto de la polilínea a
convertir
;;coincide con el Sistema de Coordenadas Universal,
es decir
;;que la polilínea esté contenida en un plano
paralelo
;;al plano XY Universal.
;;***************************************************
*****
;;Rutina de selección de objetos:
;;Del conjunto de selección original, crea un nuevo
;;conjunto de selección que incluya sólo polilíneas:
(defun pl_sel ( / orig_selset pl_selset count)
(prompt "\nSeleccione Polilíneas/Select Polylines:
")
(setq
orig_selset (ssget)
pl_selset (ssadd)
count -1
)
(repeat (sslength orig_selset)
(setq count (1+ count))
(if (= (cdr (assoc 0 (entget (ssname
orig_selset count)))) "POLYLINE")
(ssadd (ssname orig_selset count) pl_selset)
)
)
pl_selset
)
;;Rutina de lectura de los vértices de la polilínea
;;crea una lista de entidad SPLINEa partir de los
;;vértices originales de la polilínea, (señalados por
;;el código 70=0 ó 16) cambiando el código 10 de la
;;entidad POLYLINE por el código 11 de la entidad
;;SPLINE.
;;Sólo se incluyen en la nueva lista los códigos
;;imprescindibles para la creación de la SPLINE.
;;Cada lista creada se pasa a entmake
;;para crear la nueva SPLINE. Después se borra la
;;polilínea original
(defun vertex ( sset / count ent_sup act_ent
spl_list)
(setq count 0)
(repeat (sslength sset)
(setq
ent_sup (ssname sset count)
act_ent (entnext ent_sup)
count (1+ count)
spl_list nil
)
(while (/= (cdr (assoc 0 (entget act_ent )))
"SEQEND")
(if
(or
(= (cdr (assoc 70 (entget act_ent)))
0)
(= (cdr (assoc 70 (entget act_ent)))
16)
)
(setq
spl_list
(cons (cons 11 (cdr (assoc 10 (entget
act_ent)))) spl_list)
)
)
(setq
act_ent (entnext act_ent)
)
)
(setq spl_list (reverse spl_list))
;;Consrucción de la nueva lista de
entidad
(foreach
cod
(list
(assoc 8 (entget ent_sup))
(cons 74 (length spl_list))
(cons 71 3)
(cons 100 "AcDbSpline")
(cons 100 "AcDbEntity")
(cons 0 "SPLINE")
)
(setq spl_list (cons cod spl_list))
)
;;Hacer la SPLINE
(entmake spl_list)
;;Borrar la POLILÍNEA
(entdel ent_sup)
)
)
;;Función Principal/Command line function
(defun c:pl2sp ( / oce)
(setq oce (getvar "cmdecho"))
(setvar "cmdecho" 0)
(vertex (pl_sel))
(redraw)
(setvar "cmdecho" oce)
(princ)
)
;;----------------FIN DEL
PROGRAMA-----------------------

Many of you have posted requests to the Wizard's Lab asking how to use Visual LISP® to
interface with other automated productivity tools like Microsoft® Excel. This article is the first in a
series that shows you how to create such an interface. First I'll explain the basics and then we'll
start looking at the larger task: using Visual LISP to tie other automated programs into your CAD
drawings. As you'll see it's all about productivity gains.

Note: You can download the code used in this tutorial from here (1.5 KB).

What Is Automation?
Microsoft Windows holds some very powerful automation programs that, with some programming,
you can use to control (drive) other programs you've installed. Generally this mechanism is
referred to as automation. The Windows operating system has a series of guidelines that you
can follow to create an application that accepts automation.

An automated program publishes or sends information to the operating system, which defines the
types of commands the program supports and how other programs are to go about
communicating with it. That process is basically standardized. But as you might have guessed,
beyond that each program has its own unique way for other programs to drive it automatically.

Automating certain tasks can achieve marvelous results. A simple illustration is to tie drawing
information from a bill of materials into a spreadsheet for further analysis or reporting purposes.
Another: to import text generated by a word-processing application into a drawing so that you
don't have to reenter data. Or this: integrating a database containing details about elements used
in the drawing to determine loads or costs. Your only limit here is the power of your imagination.
This arena is a wonderful place for wizards to play!

Beginning a Conversation
First, let's look at the theory behind automation and then look at how to apply that theory in Visual
LISP.

The automation process begins when one program establishes a communications link to another
program. Any application built to run on the Windows OS can be automated if the developer
provides the required parts. During installation, the name of each application is placed in the
Windows Systems Registry and that name is used to reference the application from another
program. Learning that System Registry name is your first step. In most cases you can guess at
the name with reasonable accuracy. The name to activate a program such as Microsoft Excel is
simply Excel.Appliction. The dot that appears between the name Excel and the word Application
is a standard divider used when referencing an object inside Windows. If the application name is
not obvious then you have some detective work to do. Most applications will include some
documentation about automation that will provide assistance.

Once you know the name of the application object, open the application. (Of course it should go
without saying that you can't automate an application that isn't already installed on your PC.) To
begin automating an application, you can either communicate with an instance of the program
that is already running or you can create a new instance of the program. That is, you can open an
existing channel to the program or create a new channel by copying the program to memory and
working with the copy.

The requirements of your interface dictate your choice here. If you want to create a new
document or worksheet, create a new channel to the application. If you want to look up
information in the program, open the program. The main difference is in how much time is
required to establish communication. Opening an existing application in memory is significantly
faster than creating a new instance.

The following list defines a few terms that you should know as you begin to create an interface
that automates an application. One word of caution though, all the documents about automation
are likely written from the perspective of Visual BASIC and not Visual LISP. As a Visual LISP
programmer, you must translate this information into more relevant terms. We'll get to that a bit
later on. Here's the list of terms:

 Active X—a Microsoft automation program that uses object-level interfaces


 Object—a container that has properties and methods (and maybe other objects)
 Object Reference—a variable set to point to an object
 Property—a data item contained in an object
 Method—a function inside an object

Beginning Conversion Using Visual LISP


This process is relatively easy. First evaluate the expression (VL-LOAD-COM), which loads the
Components Object Model of Visual LISP. You need do this only once to enable the VLAX (Visual
LISP Active X) functions. Functions labeled VLAX are not available in Visual LISP until the library
has been called into memory. Many applications don't require Active X interfacing, so these
functions are not loaded automatically when you open the AutoCAD® software. The loading is left
up to the Visual LISP application.

(VL-LOAD-COM) returns instantly if the VLAX functions are already available in memory, and
there is no degradation to your programs if you do end up calling more than once. I just include
(VL-LOAD-COM) in the base load of the application by placing it in my source file outside of a
function definition. That way, it is evaluated as the function set is loaded into AutoCAD.

This does slow down the loading process by a few seconds, but not to an unacceptable degree.
And I let the user know that a process is loading by issuing a prompt to the command line. VL-
LOAD-COM does not present a prompt. Without the prompt that I supply, the user may think the
process has frozen the computer or will complain about how slow my program is.

Once VLAX functions are available you can use one of three different functions to obtain an
object reference to the application with which you wish to communicate. Your application
requirements dictate which function you choose. The fastest options are to link to an already
running instance of the program with which you want to interface.

 VLAX-GET-OBJECT—establish a link to an already running program


 VLAX-CREATE-OBJECT—create a new instance of the program
 VLAX-GET-OR-CREATE-OBJECT—look first to see if the program is running. If it is,
then link to that copy. If not, then load a new instance for automation.

You must supply the name of the application you will be automating for each of these methods. If
the application is not available, a nil result is returned indicating that your request for an
automation link has failed. Otherwise, an object reference is returned. Save that result using
SETQ in your Visual LISP program. The following expression attempts to link to either a running
copy of Excel or to create a new instance of it if one is not currently available.

(setq xL (vlax-get-or-create-object
"Excel.Application"))

When this expression has completed, the symbol xL will be nil if Excel could not be located on the
computer. Otherwise xL contains the object reference to a running copy of the program. You will
use the object reference from this point forward to communicate with that specific instance of
Excel.

If you want to be sure that you are working with an exclusive instance of Excel, use the (VLAX-
CREATE-OBJECT) method. That way, if Excel is already running, a new copy is started in the
computer so that your application does not interfere with any other spreadsheets that may be
open and in use. Or use (VLAX-GET-OBJECT) and simply use an already running copy of Excel.
This option is useful when you are working with an active spreadsheet and simply want to move
data between it and a drawing. In all the cases, you will get an object reference if the link is
successful or a nil result if the link could not be established.

We can use the result immediately as in the following code segment:

(if (setq xL (vlax-get-or-create-object


"Excel.Application"))
(progn
;;operations with spreadsheet application
) ;;end PROGN
(alert "Unable to establish link with Excel!")
;else error message
)

Using the Object Reference


Once you have the object reference to a running copy of Excel, what do you do? Use the Visual
LISP functions (VLAX-GET) and (VLAX-PUT) to move through the objects. With the object
reference you can retrieve object properties and/or move data to the spreadsheet. However, I find
that the next step to take in most circumstances is to import the object library into Visual LISP,
which simplifies the remaining work you may want to perform with the spreadsheet.

What is an object library? It is a set of subroutines and data elements that the hosting application
has declared as being available to external tasks employing automation techniques. The
Windows OS uses a standard mechanism known as Object Linking and Embedding (OLE for
short) that programming systems can tie in to. OLE is an advanced method of program-to-
program communications allowing automation to take place.

Object libraries are declared in a file with an OLB extension, which you can usually find in the
same folder as the application executable files. Use Windows Explorer to locate the file (use the
Find option in the Tools drop-down menu and search for all files that have an extension of OLB).
You can either code the entire path to the object library into your application, use a variable to
house that information in case you want to change it later on, or have your program locate the file
you need.

Use the Visual LISP function (VLAX-IMPORT-TYPE-LIBRARY) to import the library into your
program. On import, a new host of functions becomes available to use in the interface you're
creating. Do this import only once in an application.

The Visual LISP documentation recommends that the import function be run at the topmost level
in your program, just like the (VL-LOAD-COM) expression. That method prevents attempts at
loading the type library more than once. You don't want to import a library more than once
because it is time consuming and could possibly foul symbol assignments you have already
made. Another way to prevent importing a library twice is to employ a conditional as in the
following expression:

(if (null xl-open)


(vlax-import-type-library
:tlb-filename "C:/Program Files/Microsoft
Office/Office/Excel8.olb"
:methods-prefix "xL-"
:properties-prefix "xLp-"
:constants-prefix "xLc-"))

Let's take a closer look at the VLAX-IMPORT-TYPE-LIBRARY expression in this code segment.
The first step is to determine if the function XL-OPEN is already defined. The name XL-OPEN
does not exist in normal Visual LISP and should have a null binding when the expression is first
encountered. You are going to create VL-OPEN as a result of the library import. You call this
function by supplying a string in the VLAX-IMPORT-TYPE-LIBRARY to serve as a common prefix
for all the new functions to be imported.

There are eight parameters containing four values supplied to the VLAX-IMPORT-TYPE-
LIBRARY expression. Each value is supplied as a pair. The first part is a symbol name that starts
with a colon character " : " as in :tlb-filename. These symbols are constants inside Visual LISP
and are used by the VLAX-IMPORT-TYPE-LIBRARY function to identify what the immediately
following string represents. Following the constant :TLB-FILENAME is the file name, with path, of
the type library. In the above example the file is located in the Microsoft Office program directory.

The remaining three pairs are for prefixes to be added to the names of the methods, properties,
and constants of the imported library. The prefixes should be unique so that they do not interfere
with other functions already defined inside Visual LISP.

In the example the prefixes used are "XL-", "XLP-" and "XLC-". Thus VL-OPEN will have a
binding once the import has taken place, and the IF test will not allow the import to take place a
second time. This is why I strongly recommend using a conditional expression in association with
the import of an object library. If the import has taken place previously, then VL-OPEN will not be
nil and the NULL test will return a false answer, which prevents subsequent calls to the import.

Learning What Is Loaded


How do you learn what is in the object library you've imported? It's simple. In the APROPOS
dialog box in the Visual LISP editor, supply the prefix you specified during the import. In most
cases the list returned is extensive, including items that take you to the information in the online
help for the application to which you are interfacing. For example, you can learn what the
parameters of a given function might be by looking at the help data.

You must interpret this information from a Visual BASIC programming point of view because most
automation programming is done using Visual BASIC not Visual LISP. However, using the
methods described so far—especially the import object library function—Visual LISP is just as
powerful a tool. But remember that it is only found in AutoCAD.

To access the APROPOS feature in Visual LISP:

1. Start the VLIDE (Visual LISP Interactive Development Environment.


2. Select the APROPOS icon or select the APROPOS option from the View pull-down menu.
3. Enter the prefix that you specified when loading the library.
4. Select the Match by prefix option.
5. Click OK. The list appears.

Remember that the list only reveals object names. You must go into the other application and find
the associated information in the help system.

What's Next?
So far you've learned that you can use Visual LISP to interface with another program such as
Excel through the use of the OLE or automation system. You've also seen how to establish a link
to the automation tools from another application.

The next steps involve the actual sending and receiving of data using the interface. This requires
that you use either the VLAX-GET and VLAX-PUT functions or that you allow Visual LISP to
import a new library of functions as defined in an OLB (Object Library) file. Check out the scrolls
and potions in the Wizard's Lab to see how this might be accomplished or join me next month
when we'll explore how to transfer data to Excel.

Until then, keep on programmin'.

Continuing on, we want to create a command called ZAP that erases all the objects
in the drawing. To do this we'll need to execute the ERASE command, input the ALL
option (all three characters), and then an additional ENTER to get out of the
command.

So how do we tell Visual LISP to hit ENTER? A pair of double quotes will do. For those
of you who have customized menus, this is the equivalent of using a semicolon to
represent an ENTER.

(defun c:ZAP ()
(command "erase" "all" "")
)

So how are we going to create a square? We'll execute the POLYGON command,
make the object four-sided, select the EDGE option, and pause twice to let the user
pick the two corners that will make up our square. If you're lost, please go into the
POLYGON command in AutoCAD and go through all of these steps manually. Let's
look at the code.
(defun c:SQ ()
(command "polygon" "4" "E" pause pause)
)

Using pause in a Visual LISP routine will instruct the program to wait for user input.
Those two pauses will enable the user to pick the two corners of the square. This is
equivalent of the backslash \ in menu customization. So what do we have so far?

(defun c:ZP ()
(command "zoom" "p")
)
(defun c:ZAP ()
(command "erase" "all")
)
(defun c:SQ ()
(command "polygon" "4" "e" pause pause)
)

Figure 1. The APPLOAD dialog makes it easy to load your Visual LISP
routines.
Test-Drive Your New Commands

Now we're ready to load and run our routines. We'll use the APPLOAD command to
load our newly created Visual LISP routine. This can be found in the Tools pull-down
menu under Load Application as a dialog box, as shown in Figure 1.

You'll simply select the file you just created and load it. If you've made any errors,
this dialog will be happy to notify you. If all goes as planned, you should now be able
to key in ZP, ZAP, and SQ at the command line. Give them a try.

You've just become a budding programmer.

Now that was pretty easy, wasn't it? This is just a peek into the powerful world of
Visual LISP. For more, you should look to the guru, Bill Kramer, to lead you down the
path of true programming enlightenment.

Until next month...Happy AutoCADing! I wish you all a wonderful holiday season.

Potrebbero piacerti anche