|Portada|Blog|Space|
[Índice] > Lenguajes interesantes: PostScript (1)
En realidad esta noticia no es tan nueva, dado que Postscript lo aprendí
hace unas semanas ya, pero como dice el dicho, más vale tarde que nunca,
así que aprovecho para contarles esto, y darles mi reflexión acerca del
lenguaje.
Les arranco contando un poco sobre el lenguaje para quienes no lo
conozcan.
Sus orígenes datan de principios de los ochenta, cuando se estaba
precisando un lenguaje para poder mandar a imprimir documentos a las
impresoras láser. Independietemente de la resolución, marca o modelo de
la impresora, estos documentos se deberían imprimir correctamente.
PostScript entonces se transformó en un estándar de facto para
impresoras láser.
Este lenguaje permite utilizar el procesador de la impresora para
realizar todo tipo de tareas, teniendo como contrapartida la necesidad
de poderosos(para la época) procesadores en las mismas. En la medida que
la capacidad de cómputo de las PC normales aumentó, varios lenguajes
tontos como PCL le fueron ganando terreno a PostScript en las
impresoras. Esto se debe a que PCL, a diferencia de PostScript, no es un
lenguaje de programación, por tanto depende del procesador de la PC para
generar las instrucciones gráficas, reduciendo costos respecto a
PostScript.
Sin embargo, dadas las altas capacidades de PostScript, aún hoy en día
se sigue utilizando en su nicho, ya no tanto el de las impresoras, sino
como lenguaje intermedio entre nuestros programas y el driver de nuestra
impresora. En caso que nuestra impresora sea PostScript, el driver se
limitará a prácticamente redirigir el documento intacto, en caso
contrario será el procesador de nuestra computadora quien interprete el
PostScript y lo convierta al formato de la impresora.
El poder de PostScript radica en que provee una sintaxis reducida y
fácil de aprender, al ser postoperado no se necesitan paréntesis, ni
recordar precedencias de operadores y la sobrecarga necesaria para
interpretarlo es mínima.
Si no tenemos una impresora PostScript a mano, o si deseamos no gastar
papel, siempre podremos probar código usando el intérprete llamado
GhostScript: echo 'mucho código' pstack | gs -q -
Veamos entonces algo de código.
El hola mundo: (Hola mundo) =
Calculando 2/3: 2 3 div
Calculando 2+3*4: 2 3 4 mul add
Definiendo la función de fibonacci de forma extremadamente ineficiente:
/fib {
dup 1 gt {
dup 1 sub fib
exch 2 sub fib
add
} if
} def
Para poder entender el ejemplo anterior es necesario saber que
PostScript es en realidad un lenguaje basado en pila. Al leer un número
o un nombre (nombres son las cosas del estilo /soy_un_nombre), o un
string (con paréntesis), arrays, etc... se agregarán a la pila, al leer
un operador (como dup, add, =, if, etc...) se ejecutará inmediatamente,
a menos que esté entre llaves (lo cual permitirá ejecutar
posteriormente si se necesita).
Al estar basado en pilas, las funciones en PostScript simplemente leen y
escriben en la pila para obtener parámetros y devolver resultados, o
hacer lo que crean necesario, esto permite tener una gran flexibilidad
en los procedimientos a costa de tener que cuidar que el procedimiento
se comporte correctamente.
Al iniciar el programa, la pila se encuentra vacía, y se representa
mostrando solamente el: |- símbolo que en realidad indica el fondo de
la pila.
Inicio: |-
Tras leer 2: |- 2
Tras leer 3: |- 2 3
Tras leer 4: |- 2 3 4
Tras leer mul: se ejecuta mul,
y resulta en: |- 2 12
Tras leer add: se ejecuta add,
y resulta en: |- 14
Ahora que ya comprendieron cómo funciona, veamos el ejemplo de
fibonacci, bastante más complejo por cierto.
Antes, una nota: def nos permite definir una variable o procedimiento,
para eso la sintaxis es: nombre valor def, nuestro ejemplo tiene
justamente: /fib {...mucho código...} def
Para pasarle como argumento a fib el número que deseamos calcular, lo
que haremos es simplemente agregarlo a la pila, ejemplo:
Inicio: ...
Tras leer 7: ... 7
Tras leer fib: se ejecuta fib,
y resulta en: ... 13
Como tanto la entrada y la salida de fib es solamente un número, es
altamente deseable que fib deje intacto los otros valores de la pila, y
de hecho, eso es lo que sucede en este ejemplo.
Supongamos que el n a calcular es menor o igual a 1.
Inicio: ... n
Tras leer dup: ... n n
Tras leer 1: ... n n 1
Tras leer gt: se ejecuta gt, y como n es menor o igual a 1, gt (>=)
resulta en: ... n falso
Tras leer {..}: ... n falso {..}
Tras leer if: se ejecuta, y como el penúltimo argumento es falso, no
hace nada: ... n
Así vemos como se programa un: if (n<=1) return n;
Supongamos ahora que n es mayor que 1.
Inicio: ... n
Tras leer dup: ... n n
Tras leer 1: ... n n 1
Tras leer gt: se ejecuta gt, y como n es mayor que 1
resulta en: ... n verdadero
Tras leer {..}: ... n verdadero {..}
Tras leer if: se ejecuta, y como el penúltimo argumento es verdadero,
procede a ejecutar el código del último argumento (que es todo lo que
está entre las llaves de adentro, y que mostraremos ahora) tras sacar
los parámetros: ... n
Tras leer dup: ... n n
Tras leer 1: ... n n 1
Tras leer sub, se ejecuta y deja n-1 que representaremos con n-1 :P
y resulta en: ... n n-1
Tras leer fib: llama recurrentemente a fib para calcular fib de n-1
y resulta en: ... n fib(n-1)
Tras leer exch, que da vuelta los últimos dos elementos de la pila,
resulta en: ... fib(n-1) n
Tras leer 2: ... fib(n-1) n 2
Tras sub: ... fib(n-1) n-2
Tras fib: ... fib(n-1) fib(n-2)
Tras add: ... fib(n-1)+fib(n-2)
Y como sabemos que esta función cumple con el principio de recurrencia
primitiva, podemos demostrar directamente que siempre termina, y por
defición con el resultado correcto.
Lo más impresionante es que en realidad todo esto lo podemos ejecutar en
cualquier impresora común y corriente con soporte a postscript.
Para terminar, les dejo una pequeña función que separa un string por
espacios, dejando un vector con las palabras:
/wordize {
/s exch def
/i 0 def
/c 0 def
[
s {
32 le {
s i c getinterval
/i i c 1 add add def
/c 0 def
} {
/c c 1 add def
} ifelse
} forall
c 0 gt {
s i c getinterval
} if
]
} bind def
La cual se puede compactar en: (para que no piensen que PostScript es
tan pesado)
/wordize{/s exch def/i 0 def/c 0 def[s{32 le{s i c getinterval/i i c 1 add add
def/c 0 def}{/c c 1 add def}ifelse}forall c 0 gt{s i c getinterval}if]}bind def
y la url con la especificación de PostScript:
http://www.adobe.com/products/postscript/pdfs/PLRM.pdf
PD: Capaz que otro día pongo unos ejemplos sobre como dibujar cosas
bonitas con postscript.
---------
Los documentos en este sitio se encuentran licenciados bajo la GFDL.
Ver comentarios: [Hay i comentarios]
Para agregar un comentario: agregue a la URL: ?do=show_comment_form (explicación)