printf.exe - Aritmética y Programación

Actualmente se asocia el término programación de computadoras con la creación de juegos de video, pero éste es un término que engloba una muy extensa gama de aplicaciones en muy diversas áreas, aunque en todos los casos ello implica el uso de un lenguaje de programación. Aprender a utilizar un lenguaje de programación requiere ciertos conocimientos técnicos y/o habilidades que algunas personas simplemente no tienen, ya sea por su área de experiencia o por su edad.

Existen varias herramientas diseñadas para "enseñar programación" a gente sin experiencia (muchas de ellas orientadas a niños); sin embargo, estas herramientas usualmente no proporcionan los elementos necesarios para posteriormente aplicarlos en el estudio de un lenguaje de programación convencional. Parte del problema estriba en que dichas herramientas se enfocan en el aspecto de "diversión" del aprendizaje, por lo que incluyen un gran número de gráficas, sonido, animaciones, etc. cuyos elementos se ensamblan visualmente como si fuera un juego, pero descuidan los aspectos mas técnicos de este conocimiento. No hay que olvidar que la creación de un programa que funcione correctamente no tiene porqué ser "divertida", sino que representa un reto técnico importante.

Una herramienta mas simple de aprender y aplicar sería una sin juegos, sonidos ni animaciones, pero que funcione en base a comandos/órdenes/instrucciones de la misma manera que un lenguaje imperativo de programación. Esta herramienta puede ser diseñada específicamente con un mínimo de reglas y facilidades, pero que aún así proporcione los mismos conceptos y forma de aplicarlos de los lenguajes de programación; esto permitiría que un mayor número de personas que normalmente no son capaces de acceder a los métodos convencionales para aprender programación lo hagan.

printf.exe es una aplicación de consola para Windows que, en su forma más básica, permite mostrar mensajes y generar resultados de operaciones aritméticas que aparecen con un cierto formato. Los resultados con formato se muestran usando la muy conocida función printf del lenguaje de programación C, de la cual hay multitud de ejemplos de uso en varios sitios de la red. Las operaciones aritméticas se evalúan mediante una notación sencilla que se ajusta perfectamente a la forma de uso de printf, haciéndolas un complemento natural para esa función. Esta notación se tomó de un tipo de calculadora (HP) que tradicionalmente se ha venido utilizando como una herramienta básica de programación orientada a personas sin experiencia (usualmente alumnos de preparatoria o secundaria) de la cual existen multitud de ejemplos de uso en internet. De esta manera, las bases de la aplicación printf.exe se apoyan en dos pilares ampliamente conocidos en el ámbito de la programación desde hace muchos años: la función printf y las calculadoras HP.

El uso avanzado de printf.exe incluye manejo de cadenas de caracteres, por lo que puede ser aplicado en muchas tareas de procesamiento de texto. Además, la versión avanzada de printf.exe cuenta con el esquema de programación mas simple que existe, similar al de las calculadoras HP del cual existen literalmente miles de ejemplos en la red, ya que este tipo de programas han sido creados y publicados por la comunidad desde hace más de 40 años. Esto permite que el usuario aprenda los principios básicos de programación en una forma casi tan sencilla como aprendió a efectuar operaciones aritméticas en la calculadora HP.

Más importante aún, el esquema avanzado de programación de printf.exe permite emular todas las figuras clásicas de la programación estructurada de los lenguajes de programación modernos, pero está basado en tan sólo cuatro elementos y un par de reglas simples sobre la evaluación de condiciones que son similares a un esquema de programación "HP extendido". Éste es un esquema muy sencillo de entender y aprender que, sin embargo, proporciona los mismos conocimientos que se requieren durante el uso de un lenguaje estructurado de programación moderno.

La extensa documentación no asume ningún conocimiento previo sobre estos temas por lo que los ejemplos son sencillos y las explicaciones detalladas. El manual de usuario comienza con explicaciones básicas y va avanzando hacia temas cada vez mas profundos a medida que se presentan las facilidades avanzadas de esta herramienta. De esta manera la aplicación printf.exe puede ser utilizada como una introducción a la programación de computadoras que no requiere de una base de conocimientos especializada por parte del usuario; tan sólo un pequeño interés en matemáticas.

Para una mejor experiencia, se sugiere que registre su copia del programa printf.exe.

Contenido

  1. Introducción
  2. Mostrar Texto con Formato
  3. Operaciones Aritméticas y de Cadenas
    1. Tipos de Datos
    2. Operaciones Enteras
    3. Operaciones de Punto Flotante
    4. Registros de Almacenamiento
    5. Manejo del Stack
    6. Manejo del Stack de la FPU
    7. Operaciones con Cadenas
    8. Punteros de Caracteres
  4. Programación por Bloques
    1. Operaciones de Entrada/Salida
    2. Bloque de Código
    3. Transferencia de Control
    4. Test/Condición
    5. Tests Condicionales Básicos
    6. Programa en Forma Didáctica (archivos Batch)
    7. Tests Indicadores
    8. Tests Numéricos Estándar
    9. Operaciones Avanzadas de Entrada/Salida
    10. Bloques de Código con Nombre
    11. Operadores Booleanos
  5. Ejemplos Gráficos Avanzados
    1. El Mandelbrot Set
    2. Mostrar texto en gráficas de pixels
    3. Gráficas de la Tortuga (Logo)
    4. Curvas Recursivas
  6. Apéndices
    1. Instalación y soporte de printf.exe
    2. Aumentando el espacio de almacenamiento
    3. Mensajes de error

Introducción

printf.exe es una aplicación de consola que se usa en la línea de comandos del programa cmd.exe de Windows, es decir, no es una aplicación gráfica, sino de texto. La aplicación está escrita en lenguaje ensamblador, por lo que es muy pequeña y eficiente. Usted puede utilizar printf.exe para generar resultados simples en la línea de comandos o resultados matemáticos avanzados en archivos por lotes Batch, o incluso escribir aplicaciones completas de tamaño pequeño o mediano utilizando las capacidades de programación de printf.exe en una forma mucho mas sencilla que utilizando cualquier lenguaje de programación moderno.

Esta aplicación tampoco requiere de una instalación complicada: tan sólo siga las indicaciones del Apéndice 1 para descargar en su computadora el archivo printf.zip del paquete y extraiga en algún folder todos los archivos incluídos. Entonces, abra la ventana de comandos de Windows (dé click con el botón derecho del mouse sobre el ícono "Inicio" en la esquina inferior izquierda del escritorio y pulse en "Símbolo del sistema"); la ventana negra de cmd.exe debe aparecer. Cambie el directorio actual al folder de printf.exe: teclee CD seguido por un espacio, después arrastre y suelte la carpeta de printf.exe en la ventana cmd.exe y presione Entrar. ¡Ya puede utilizar printf.exe! ;)

NOTA: Algunas aplicaciones antivirus podrían marcar al programa printf.exe como "potencialmente peligroso". Este es un mensaje falso positivo. Si usted descargó el archivo printf.zip del paquete desde el sitio apaacini.com, entonces está libre de virus con toda seguridad.

La forma general de uso del programa printf.exe es ésta:

printf "formato" dato1 dato2 dato3 ...

En su forma mas simple la cadena formato contiene una serie de caracteres que serán mostrados en la pantalla. Por ejemplo:

printf "Hola, mundo"

Usted puede copiar cualquier ejemplo que aparezca aquí (seleccione el texto manteniendo oprimido el botón izquierdo del mouse mientras lo mueve, y presione Ctrl-C) y pegarlo en la ventana de comandos dando click con el botón derecho del mouse; si esto no funciona, dé click con el botón derecho sobre la barra de título de la ventana Símbolo del sistema, seleccione Editar y Pegar. Cuando el comando se haya copiado, presione Entrar para ejecutarlo.

El texto con formato se muestra mediante la función Windows API del mismo nombre. El programa printf.exe tan sólo toma los datos proporcionados por el usuario y los pasa tal cual a la función printf C Run-Time (CRT) sin ninguna revisión adicional. Esto significa que dichos datos deben cumplir con los requerimientos de la función printf CRT, de lo contrario ocurrirán los mismos errores y fallas descritos en la documentación de dicha función.

Las operaciones aritméticas son efectuadas utilizando la misma área de datos (stack) en donde se almacenan los parámetros de la función printf mediante la mas sencilla notación aritmética: Notación Polaca Inversa, también llamada RPN (por las siglas de Reverse Polish Notation en inglés) o notación postfija. Este método permite que el programa printf.exe sea relativamente sencillo y que las operaciones sean efectuadas en forma muy eficiente, pero esto también significa (nuevamente) que es responsabilidad del usuario proporcionar expresiones RPN que sean correctas.

Los dos puntos anteriores implican que printf.exe es un programa ¡un poco difícil de usar! Sin embargo, las reglas de operación no son complicadas; usted sólo necesita ser cuidadoso y prestar atención a los detalles. La recompensa por este esfuerzo es que obtendrá resultados numéricos (y de proceso de texto) de una forma mucho mas sencilla que utilizando cualquier otro lenguaje de programación, con la misma o mejor precisión, y mucho mas rápido.
Top

Mostrar Texto con Formato

La cadena de formato contiene caracteres ordinarios o ciertos caracteres de control precedidos por una diagonal inversa \ (llamados secuencias de escape), y especificaciones de conversión de datos que comienzan con un signo porciento % y terminan en una letra.

Los caracteres de control disponibles en printf.exe son: \n nueva línea LineFeed ASCII 10 (mostrado como el par CR+LF en Windows), \r CarriageReturn ASCII 13, \t TABulation ASCII 9, \b BackSpace ASCII 8, y \a BELl ASCII 7. Cualquier otro caracter colocado después de la diagonal inversa se inserta textualmente; por ejemplo, use \\ para insertar una diagonal inversa o \" para insertar comillas:

printf "Ella dijo: \"Hola\"\n"

La cadena de formato puede ser el nombre de una variable Batch y printf.exe usará la cadena almacenada en dicha variable. Sin embargo, no se pueden insertar caracteres de control directamente en la variable usando secuencias de escape; se debe usar el método descrito en el archivo printf Example 0 - Operations.bat

Las especificaciones de formato diseñadas para mostrar datos comienzan con un signo de porcentaje % y terminan en una letra que depende del tipo de datos: %c para caracteres, %s para cadenas de caracteres, %i para números enteros y %f para números de punto flotante.

El tipo de los datos colocados después de la cadena de formato depende de la forma en que están escritos: un caracter se encierra entre apóstrofes, una cadena de caracteres se encierra entre comillas, un número entero no lleva punto decimal, y un número de punto flotante lleva punto decimal. De esta manera, a cada especificación %letra en la cadena de formato le corresponde un dato colocado después del formato, el cual debe ser del mismo tipo. Por ejemplo:

printf "Un caracter: %c\tUna cadena: %s\tUn entero: %i\tUn punto flotante: %f\n"  'X'  "ABC"  1  1.

Usted también puede usar como dato una variable de entorno Batch (environment variable) la cual se tomará como una cadena de caracteres. Por ejemplo:

printf "%s\n" PATH

Note que los elementos de datos se separan por uno o más espacios (o caracteres TAB). Si hay más datos que especificaciones de formato, los datos adicionales no se muestran. Si hay menos datos que especificaciones de formato, se muestra basura o se presenta un error de ejecución. La documentación de la función printf indica: "Los resultados son indefinidos si no hay suficientes elementos de datos para todas las especificaciones de formato". En esta frase, "resultados indefinidos" significa que cualquier cosa puede pasar.

Un punto muy importante al que usted debe prestar atención es que el programa printf.exe puede ser caracterizado como una aplicación sin sistema de tipos (similar al lenguaje ensamblador): es responsabilidad del programador asegurar que los datos proporcionados son del tipo apropiado para cada operación. Si las especificaciones de formato y los datos no son del mismo tipo, se mostrará basura o se presentará un error de ejecución. Los únicos casos en los que el formato y el dato pueden ser de diferente tipo es usar %i en un caracter (lo que mostrará su código ASCII) o usar %c en un número entero (lo que mostrará el caracter de ese código); esto sucede porque los caracteres se manejan internamente como números enteros de 32 bits. Por ejemplo:

printf "El código ASCII de '%c' es %i. El caracter del código %i es '%c'\n"  'A' 'A'  97 97

La línea anterior muestra: El código ASCII de 'A' es 65. El caracter del código 97 es 'a'

Cada especificación %letra permite un alto grado de control en la forma en que el dato es mostrado. La forma general de la especificación de conversión de datos se describe en seguida.

Formato de conversión de datos: %[marca][ancho][.precision]tipo

marca     tipo
-+ 0#     cdiouxXeEfgGaAps
- alineación izquierda c Caracter
+ inserta signo +
si es positivo
d entero (Decimal)
" " inserta espacio
en lugar del +
i entero (Integer)
0 rellena con zeros o entero (Octal)
# inserta . en tipo g
o inserta 0|0x
en tipos o|x
u entero sin signo (Unsigned)
  x entero (hexadecimal)
X entero (Hexadecimal)
e double ([-]d.dddddde∓ddd)
E double (como e, con E)
f double ([-]ddd.dddddd)
F double (como f, en mayúsculas)
g double (el mas corto de f|e)
G double (como g, con E)
a double ([-]0xh.hhhp∓ddd)
A double (como a, con X y P)
p cadena (dirección en hex)
s cadena de caracteres (String)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

marca es una parte opcional que añade ciertos caracteres al texto, generalmente del lado izquierdo del campo.

ancho es un número que especifica el ancho mínimo o total del campo usado para mostrar el dato. Sin embargo, este ancho no truncará el valor, por lo que el campo aumentará si fuera necesario. printf "%04i" 12 muestra: 0012   printf "%04i" 12345 muestra: 12345

.precision es un número que especifica el ancho máximo de esta parte, por lo que el valor se truncará y redondeará si fuera necesario. printf "%.4f" 3.141592654 muestra 3.1416

Cuando se muestran números de punto flotante, ancho especifica el "número de columnas" del campo y .precision el "número de decimales", pero esta interpretación difiere con otros tipos de datos, como serían los enteros o las cadenas. Por ejemplo, en el formato %.12s para cadenas de caracteres, el valor de la cadena se truncará en 12 caracteres.

Una cadena de caracteres es el único tipo de dato que se suprimirá si la precisión es zero: %.0s. Como un caso especial, si la precisión es cero y el número entero es cero: printf "%.0i" 0, no se mostrará nada.

La letra del tipo debe ser del mismo tipo del dato de acuerdo con la tabla del lado izquierdo. En esta tabla el término "double" se refiere a los números de punto flotante de 64 bits que maneja el programa printf.exe.

Para detalles adicionales puede revisar la descripción completa de las especificaciones de formato en este sitio.

Nota: si usa el programa printf.exe en un archivo de comandos Batch (con extensión .bat), entonces todos los caracteres de porcentaje deben duplicarse. Por ejemplo, este comando insertado en la línea de comandos:

printf "Un entero:%i\nUn flotante: %f\n"  123  456.789

... debe escribirse así en un archivo Batch:

printf "Un entero:%%i\nUn flotante: %%f\n"  123  456.789

Tópico avanzado: en una especificación de formato, los valores ancho y precision pueden ser un asterisco que indica tomar el siguiente número entero y utilizarlo en lugar del asterisco. Por ejemplo, en printf "%0*i" 5 3 el 5 toma el lugar del asterisco por lo que el formato sería el mismo que en printf "%05i" 3: 00003. Este ejemplo: printf "%0*.*f" 8 4 3.141592654 muestra la misma salida que este otro: printf "%08.4f" 3.141592654: 003.1416.

La ventaja de esta característica es que los valores ancho y precision pueden ser calculados dinámicamente cuando printf.exe sea ejecutado. Si el valor de la precisión es cero en una especificación de formato para una cadena de caracteres, la cadena no se muestra. Por ejemplo:

printf "El resultado es: %.*s %.*s \n" 9 "sí, claro" 0 "no, lo siento..." muestra El resultado es: sí, claro, pero printf "El resultado es: %.*s %.*s \n" 0 "sí, claro" 16 "no, lo siento..." muestra El resultado es: no, lo siento....

Uno de los valores 9 o 16 puede cambiarse a cero dependiendo de otro valor selector, por lo que esta característica permite la selección condicional de una cadena de caracteres entre dos posibles. Por supuesto, esta selección puede extenderse a cualquier número de posibles cadenas diferentes.
Top

Operaciones Aritméticas y de Cadenas

Antes de describir el método para efectuar operaciones aritméticas usted debe tener en cuenta un punto importante sobre los números. Como se dijo antes, el programa printf.exe es una aplicación sin sistema de tipos y en estas aplicaciones no hay ninguna conversión de tipos implícita, por lo que usted debe proporcionar siempre los valores del tipo correcto o realizar explícitamente cualquier conversión requerida. En la aplicación printf.exe (y en prácticamente todos los lenguajes de programación) hay dos tipos diferentes de números: enteros y de punto flotante. Esto significa que, en printf.exe (a diferencia de otros lenguajes) también hay dos conjuntos diferentes de operadores para evaluar operaciones sobre números enteros y de punto flotante (exactamente de la misma manera que hay dos especificaciones de formato diferentes para mostrar números enteros %i y de punto flotante %f).

Los operadores enteros son caracteres especiales (como + - * / etc), pero se emplean palabras de 3 o 4 letras como operadores de punto flotante (como ADD SUB MUL DIV etc). Si usted usa un operador del tipo incorrecto o si trata de operar dos números de tipo diferente, obtendrá basura como resultado o incluso un error de ejecución (exactamente de la misma forma que si usted usa una especificación de formato del tipo incorrecto para mostrar un número). Cada vez que usted no obtenga el resultado esperado de printf.exe revise en primer lugar el tipo de los números, operadores y formatos utilizados. Veamos un par de ejemplos simples: printf "Un entero mostrado con formato F: %f\n" 123 muestra Un entero mostrado con formato F: 0.000000 y éste printf "Un flotante mostrado con formato I: %i\n" 1.2 muestra Un flotante mostrado con formato I: 858993459. Si usted comprende este punto, podrá evitar el problema mas frecuente sobre el uso de la aplicación printf.exe.

Este programa utiliza un método para evaluar operaciones aritméticas diferente a la notación algebraica usual. El método se llama Notación Polaca Inversa (o RPN, en inglés) y fue elegido porque es mas simple de implementar (y usar) que la notación algebraica. En la notación algebraica estándar, la multiplicación y la división tienen mayor precedencia (son evaluadas antes) que la suma y la resta. Esto significa que cuando varias operaciones son combinadas, las operaciones con mayor precedencia se ejecutan primero (de izquierda a derecha) y después se completan las operaciones con menor precedencia (de izquierda a derecha). Por ejemplo, la siguiente expresión algebraica:

4 + 5 - 6 * 7 / 8 + 9 =

... implica sumar primero 4+5, almacenar el resultado parcial 9 y mantener pendiente la resta para después. Entonces se multiplica 6*7 y se divide entre 8. Y ahora se toma el 9 almacenado anteriormente para completar la operación de resta pendiente, y finalmente se suma 9. Si usted quiere cambiar este orden estándar, necesita añadir paréntesis para encerrar las operaciones que deben efectuarse primero. Si se tiene en cuenta que los paréntesis pueden anidarse a varios niveles y que existen otras operaciones con diferentes precedencias, como exponenciación (con precedencia mayor que * /) u operaciones sobre bits (con precedencia menor que + -), y que hay algunos operadores que se agrupan de derecha a izquierda (como todos los operadores sobre un operando), entonces es fácil comprender que la evaluación de una expresión algebraica grande y complicada puede tener varios resultados parciales y operaciones pendientes que deben completarse de manera enrevesada. Cuando una expresión grande es requerida en un programa, es común que el programador divida la expresión en varias subexpresiones mas pequeñas con el objeto de aumentar la claridad. Esto es simplemente absurdo...

(Existe un lenguaje de programación, APL, que tiene muchos (¡más de 50!) operadores. Con el objeto de evitar complicadas reglas de precedencia sobre un número tan grande de operadores, los diseñadores de APL optaron por una regla simple: no hay precedencias en una expresión; todos los operadores se ejecutan de derecha a izquierda. Esto lleva a escribir expresiones APL que usualmente tienen paréntesis del lado izquierdo.)

RPN es diferente. En RPN no hay "precedencia de operadores" ni "operaciones pendientes". Todas las operaciones en RPN siguen una regla simple: la operación se ejecuta inmediatamente en cuanto aparece un operador; esto implica que los números deben introducirse antes. Usted puede tomar un primer contacto con la notación RPN en el Museo de HP (en inglés). Para una opción en español, puede consultar el manual de la calculadora HP-12C para leer una introducción a RPN y terminar leyendo el apéndice "A" en la página 168. Se invita al lector a echar un vistazo a ese sitio antes de continuar, pero tome en cuenta que el uso de RPN en el programa printf.exe es mas sencillo que en la calculadora HP (porque los números se separan tan sólo con un espacio).

Para hacer un cálculo de este tipo, indique primero los dos números y, a continuación, indique la operación que se debe realizar. Note que en printf.exe los números se separan con un espacio y no hay un equivalente a las teclas Enter↑ o CLx de la calculadora HP (ni sus casos especiales de "ascenso de la pila"). Usted puede revisar la correcta conversión de expresiones algebraicas a RPN en el programa AlgebraToRPN.bat incluído en el paquete printf.zip; tan sólo ejecútelo e ingrese expresiones algebraicas correctas (el programa de conversión no detecta errores en las expresiones algebraicas dadas).

Revisemos un ejemplo simple de una operación de suma RPN en printf.exe:

printf "Un valor: %i, otro valor: %i\n"  3 4
printf "La suma de 3 mas 4 es: %i\n"  3 4 +

En el primer ejemplo hay dos números enteros y dos formatos enteros, así que todo está correcto.

En el segundo ejemplo hay dos números enteros. Después de ellos hay un operador RPN entero que toma los dos números previos y produce un sólo resultado, así que al final hay un sólo número entero y un formato entero: ¡correcto! :)

Usando este esquema usted puede evaluar cualquier expresión RPN sin importar cuán larga pudiera ser; sólo asegúrese que los números enteros son operados con operadores enteros y los números de punto flotante usan operadores (de tres letras) de punto flotante. Finalmente, también revise que los resultados enteros se muestran con el formato %i, y los resultados de punto flotante usan el formato %f. Estas reglas no son tan difíciles de seguir, ¿verdad? ;)

printf "La suma de 3 mas 4 es %i\nLa suma de 3. mas 4. es %f\n"  3 4 +  3. 4. ADD

Completemos el ejemplo previo de la expresión algebraica:

printf "Expresion algebraica 4 + 5 - 6 * 7 / 8 + 9 = %i\n" 4 5 + 6 7 * 8 / - 9 +

Note que las operaciones y resultados parciales son evaluados y completados en el mismo orden que antes; esto es obvio si queremos obtener el mismo resultado. Sin embargo, en este caso somos nosotros quienes fijamos explícitamente el orden de las operaciones y éstas no están gobernadas por reglas de precedencia que no son aparentes en la misma expresión.

Note también que el resultado 13 difiere del correcto 12.75. Esto es causado por las operaciones enteras (que truncan cualquier parte fraccional del resultado) y el orden de las operaciones. Para obtener el resultado correcto, use números de punto flotante:

printf "%.2f\n" 4. 5. ADD 6. 7. MUL 8. DIV SUB 9. ADD

Usted puede revisar muchos ejemplos de operaciones, tanto enteras como de punto flotante, en el archivo printf Example 0 - Operations.bat

La mayoría de los operadores aritméticos de printf.exe funcionan igual que en las calculadoras HP, por lo que no se explicarán aquí; solamente las operaciones que no tengan una contraparte en las calculadoras HP. Si usted tiene alguna duda sobre estas operaciones consulte cualquier manual de HP; por ejemplo, el de la calculadora HP-15C (PDF 3.75 MB).

Nota: la descripción de las calculadoras HP especifica que se pueden mantener hasta cuatro resultados parciales en expresiones RPN. En el programa printf.exe se pueden mantener hasta ocho resultados parciales de punto flotante y no hay límite en el número de resultados parciales enteros. Este punto se explica con mayor detalle después.

Tipos de Datos

printf "formato" {número [operador]|'c'|"cadena"|variable} ...

Los parámetros para el programa printf.exe después del primero pueden ser de cualquiera de estos tipos:

Tipo Ejemplo Descripción
Caracter 'X' Un sólo caracter encerrado entre apóstrofes. Los caracteres se manejan internamente como números enteros de 32 bits.
Cadena "Hola" Varios caracteres encerrados entre comillas. Las cadenas de caracteres se manejan internamente como direcciones de 32 bits.
Entero 3 Número sin punto decimal, con un signo negativo opcional. Puede escribirse como un número hexadecimal que empieza con 0x, o como un número octal que empieza con un cero (en cuyo caso los dígitos 8 y 9 serán inválidos). Los números enteros se manejan internamente como enteros de 32 bits con un rango de valores entre -2147483648 y 2147483647 (o entre 0 y 4294967295 sin signo, formato %u).
Punto
flotante
5. Número con punto decimal o escrito en la notación científica estándar (con una letra E) con un máximo de 18 dígitos significativos y un exponente de 10 entre -320 y +308 (normalizado). Los números de punto flotante se manejan internamente como valores double de 64 bits.
Variable
Cadena
NombreVar Cualquier nombre que empiece con letra que no sea una función predefinida. Su valor es la cadena de caracteres almacenada en dicha variable Batch.

Un número empieza con un dígito, u opcionalmente con un signo negativo. El formato general de los números es: [-]dígitos[.[dígitos]][{E|e}[+|-]dígitos]. Los corchetes [ ] rodean los elementos opcionales. Llaves y una barra vertical { | } rodean alternativas para un solo elemento. Si el número incluye punto decimal o la letra E, es un número de punto flotante; de lo contrario será un número entero.

Por ejemplo:

printf "Un caracter: %c\tUna cadena: %s\tUn entero: %i\tUn punto flotante: %f\n"  'X'  "ABC"  1  1.

Los caracteres se almacenan como números enteros de 32 bits; esto permite realizar estos "trucos":

Las cadenas de caracteres se manejan en los parámetros de printf.exe como la dirección de 32 bits del primer caracter de la cadena (similar a un puntero del lenguaje C). Este punto se discute con detalle adelante, en Operaciones con Cadenas.

Operaciones Enteras

Varios operadores enteros usan caracteres especiales, como < > | &, que deben "^escaparse" de esta forma: ^< ^> ^| ^& cuando son utilizados en la línea de comandos. Para facilitar el ingreso de tales operadores, usted puede insertar el switch especial "Quoted" /" antes de dichos caracteres especiales. Por ejemplo:

printf "El mismo valor 3 veces: %i %i %i\n"  28 /" > >

Si después de este switch hay una cadena que contiene estos caracteres especiales, usted debe "cerrar" el switch Quoted poniéndolo otra vez. Por ejemplo:

printf "Dos números: %i %i y una cadena: %s\n" 12 34  /"  <>  /"  "<Hola>"
Operadores Enteros
Operadores unarios ! BoolNot   ~ BitNot   _ Negativo   $ Signo   ++ Incrementa   -- Decrementa
Operadores con
dos operandos
Básicos + Suma   - Resta   * Multiplica   / Cociente   % Residuo   ** Potencia
Bitwise << BitSHL   >> BitSHR   & BitAnd   | BitOr   ^ BitXor
Operadores especiales # Random   ] Store   [ Recall   . Flotante   /* Comentario */
Manejo del stack > Dup   <> Exchange   < Drop   { Roll Down   } Roll Up

Note que varios operadores se componen de dos o más caracteres, por lo que usted siempre debe separar operaciones completas con uno o más espacios o caracteres TAB. Por ejemplo, el operador <> es Exchange, así que si usted quiere escribir un Drop seguido por un Dup, usted debe separarlos de esta forma: < >.

En estos cinco operadores: * / % << >> usted puede añadir una letra u minúscula al final para especificar una operación sin signo; por ejemplo: >>u.

** (Potencia, yx en calculadoras HP) es la única operación entera sobre dos operandos que no está implementada mediante una instrucción nativa del CPU, sino mediante un bucle de multiplicaciones.

Operador # (Random) genera un número aleatorio de 32 bits menor que 2147483647 y mayor que cero.

Operador . (Float) convierte un número entero en uno de punto flotante. Después de la conversión usted debe hacer operaciones o mostrar el número utilizando operadores y formato de punto flotante.

Los operadores {n (Roll Down) y }n (Roll Up) requieren una posición de un dígito; estos operadores se describen con detalle adelante.

Los delimitadores /* y */ (Comentario) permiten insertar cualquier texto descriptivo, que será ignorado.

Operaciones de Punto Flotante

Todas las operaciones de punto flotante se especifican mediante una palabra de un máximo de 4 caracteres, que llamaremos funciones.

Funciones de Punto Flotante
Operadores
unarios
Básicos CHS ABS SIGN FRAC INV SQR SQRT
Trigonométricos SIN COS TAN ASIN ACOS ATAN DEG RAD
Logaritmos LN LOG EXP EXPT
Operadores con dos operandos ADD SUB MUL DIV MOD POW
Operadores especiales ZERO ONE PI STO RCL INT
Manejo del stack DUP XCHG DROP RDN RUP INIT CLEAR LOAD

En las calculadoras HP: INV es 1/x, SQR es x2, SQRT es √x̅, EXP es ex, EXPT es 10x y POW es yx.

Las funciones trigonométricas siempre trabajan en radianes. DEG convierte radianes a grados; RAD convierte grados a radianes.

ZERO, ONE y PI son funciones de convenencia que cargan las constantes 0.0, 1.0 y Π (3.141592654...) mediante operaciones nativas del FPU.

La función INT convierte un número de punto flotante en uno entero (no sólo elimina la parte fraccionaria); después de la conversión usted debe operar y mostrar el número usando operadores y formatos enteros. Si desea obtener la parte entera de un número de punto flotante, tan sólo haga esto: DUP FRAC SUB.

La función INIT inicializa (borra el stack de) la Unidad de Punto Flotante X87 (FPU) preservando los parámetros de printf.exe. La función CLEAR borra los párametros de printf.exe preservando el stack de la FPU. La función LOAD carga en los parámetros de printf.exe valores tomados del stack de la FPU. Vea la descripción adelante.

Registros de Almacenamiento

Registros de Almacenamiento
N i Ent Float
I   0  
0 0 0 0.0
1 1 0 0.0
2 2 0 0.0
3 3 0 0.0
4 4 0 0.0
5 5 0 0.0
6 6 0 0.0
7 7 0 0.0
8 8 0 0.0
9 9 0 0.0
.0 10 0 0.0
.1 11 0 0.0
.2 12 0 0.0
.3 13 0 0.0
.4 14 0 0.0
.5 15 0 0.0
.6 16 0 0.0
.7 17 0 0.0
.8 18 0 0.0
.9 19 0 0.0

Además de los números que usted puede insertar como parámetros de printf.exe, también se proporcionan varios registros de almacenamiento de datos que permiten almacenar y recuperar tanto números enteros como de punto flotante para ser utilizados posteriormente en otra parte de la expresión RPN. También se pueden efectuar operaciones aritméticas básicas sobre un registro de almacenamiento (aritmética de almacenamiento) o utilizar un registro de almacenamiento para hacer operaciones básicas sobre el último número ingresado (aritmética de recuperación). Usted puede leer una introducción al uso de registros de almacenamiento en la página 22 del manual de la calculadora HP-12C.

Inicialmente, la aplicación printf.exe puede manejar 20 registros de almacenamiento enteros y 20 de punto flotante (aparte del registro Índice I) según se muestra en el esquema del lado derecho. Los 20 registros, tanto enteros como de punto flotante, pueden ser direccionados directamente mediante un dígito 0..9 ingresado en lugar de la letra N en las operaciones de la tabla de abajo, o bien por la combinación punto-dígito que direcciona los registros del 10 al 19; por ejemplo: ]3 o STO.5 (en esa tabla la letra X se refiere al último número ingresado).

El número de registros de almacenamiento disponibles (enteros y flotantes) y el tamaño del área reservada para almacenar cadenas de caracteres pueden ser aumentados mediante el procedimiento descrito en el Apéndice 2. Cuando existan más de 20 registros de almacenamiento, los registros adicionales deben ser direccionados indirectamente mediante el Registro Índice i según se explica en la Sección 10: El Registro Índice del manual del propietario de la calculadora HP-15C (en inglés), que se traduce de esta manera para printf.exe: El registro Índice es un registro de almacenamiento entero que puede ser usado directamente, con I como en [I, o indirectamente, con i como en [i. La función I usa el propio contenido del registro Índice. La función i usa el número almacenado en el registro Índice para direccionar otro registro de almacenamiento, entero o flotante. Esto se llama direccionamiento indirecto.

IMPORTANTE: Tome nota que el uso de las letras "I" e "i" para acceder al registro Índice de esta manera es la única operación de printf.exe que distingue mayúsculas de minúsculas. Usted debe tener en cuenta este punto e insertar la letra adecuada de acuerdo con la operación deseada.

Operaciones sobre Registros de Almacenamiento
  Ent Float Nombre Operación Descripción
S
T
O
R
E
]n STOn Store RegN = X Almacena X en el registro N
][n STO[n Store-Xchg RegN <=> X Intercambia X y el registro N
]+n STO+n Store-Add RegN = RegN+X Suma X al registro N
]++n STO++n Store-Inc RegN = RegN+1 Incrementa el registro N
]-n STO-n Store-Sub RegN = RegN-X Resta X del registro N
]--n STO--n Store-Dec RegN = RegN-1 Decrementa el registro N
]*n STO*n Store-Mul RegN = RegN*X Multiplica el registro N por X
]/n STO/n Store-Div RegN = RegN/X Divide el registro N entre X
   
R
E
C
A
L
L
[n RCLn Recall Push RegN Recupera X del registro N
[+n RCL+n Recall-Add X = X+RegN Le suma a X el registro N
[++n RCL++n Recall-Inc X = X+1 Incrementa X
[-n RCL-n Recall-Sub X = X-RegN Le resta a X el registro N
[--n RCL--n Recall-Dec X = X-1 Decrementa X
[*n RCL*n Recall-Mul X = X*RegN Multiplica X por el registro N
[/n RCL/n Recall-Div X = X/RegN Divide X entre el registro N

IMPORTANTE: Todas las operaciones aritméticas de almacenamiento de punto flotante están implementadas mediante un procedimiento de 3 instrucciones y requieren un registro del stack. Estas operaciones fallarán si hay 8 números de punto flotante ingresados en los parámetros de printf.exe. Las operaciones aritméticas de recuperación de punto flotante sólo usan una operación del stack.

Note que las operaciones enteras "de recuperación" [++n y [--n hacen lo mismo que las operaciones mas simples ++ (Increment) y -- (Decrement).

La mayoría de las calculadoras HP, como la HP-15C, incluyen funciones que realizan cálculos estadísticos sobre dos variables. Para hacerlo, se introduce cada par de valores X-Y y se oprime la tecla ∑+, con lo cual los valores estadísticos se compilan en los registros de almacenamiento R2 a R7 según se describe en la página 49 del HP-15C Owner's Handbook:

Registro Valor Descripción
R2 n Número de datos acumulados.
R3 ∑x Sumatoria de valores x.
R4 ∑x^2 Sumatoria de valores x al cuadrado.
R5 ∑y Sumatoria de valores y.
R6 ∑y^2 Sumatoria de valores y al cuadrado.
R7 ∑xy Sumatoria de productos de valores x por valores y.

El paquete printf.exe incluye un programa de ejemplo que realiza los mismos cálculos estadísticos de la calculadora HP-15C. Este es el segmento de dicho ejemplo que compila los valores estadísticos en los registros especificados:

		/*    x y		*/ ^
      STO++2	/*        Accum n	*/ ^
      STO+5	/*        Accum Sy	*/ ^
      STO9	/*        R9 = y	*/ ^
      SQR	/*    x y^2		*/ ^
      STO+6	/*        Accum Sy^2	*/ ^
      DROP	/*    x			*/ ^
      STO+3	/*        Accum Sx	*/ ^
      DUP	/*    x x		*/ ^
      SQR	/*    x x^2		*/ ^
      STO+4	/*        Accum Sx^2	*/ ^
      DROP	/*    x			*/ ^
      RCL*9	/*    x*y		*/ ^
      STO+7	/*        Accum Sx*y	*/ ^
      DROP	/*        Empty stack	*/ ^

El programa de ejemplo completo se encuentra en el archivo printf Example 1 - Two var statistics.bat

Manejo del Stack

En este ejemplo: printf "%i %i %i %i\n" 10 20 30 40 los números 10, 20, 30 y 40 se ingresan en el mismo orden: el primero es el 10 y el último el 40. Si después de eso usted retira un número con < (Drop), el número retirado será el 40. Este esquema de operación en donde el último que entra es el primero que sale (en contraste con una cola en donde el primero que entra es el primero que sale) define una estructura de datos llamada pila (stack en inglés). De acuerdo con esto, las operaciones de manejo del stack nos permiten manipular y cambiar el orden de los números ingresados como parámetros del programa printf.exe.

Normalmente, las operaciones de manejo del stack operan sobre el último número ingresado. Sin embargo, estas operaciones también pueden operar sobre otro número que no sea el último. El número por operar se indica mediante un dígito que marca la posición del número deseado a partir del último, con posición 1, hasta el primero (orden inverso). Por ejemplo, <2 elimina el penúltimo número. En la siguiente tabla se muestran otros ejemplos.

Operaciones de Manejo del Stack
Tipo Oper Nombre Descripción Ejemplo
Antes -->
6   5   4   3   2   1
oper --> Después
 
E
N
T
E
R
O
> Dup Duplica un número 10 20 30 40 >
>3
>4
10 20 30 40 40
10 20 30 40 20
10 20 30 40 10
<> Exchange Intercambia el último número con otro 10 20 30 40 <>
<>2
<>4
10 20 40 30
10 20 40 30
40 20 30 10
< Drop Elimina un número 10 20 30 40 <
<2
<3
<*
10 20 30
10 20 40
10 30 40
 
{ Roll Down Rota X hacia una posición previa 10 20 30 40 50 60 {4
{6
10 20 60 30 40 50
60 10 20 30 40 50
} Roll Up Rota una posición previa hacia X 10 20 30 40 50 60 }3
}5
10 20 30 50 60 40
10 30 40 50 60 20
F
L
O
T
A
N
T
E
DUP Dup Duplica un número 10. 20. 30. 40. DUP
DUP2
DUP3
10. 20. 30. 40. 40.
10. 20. 30. 40. 30.
10. 20. 30. 40. 20.
XCHG Exchange Intercambia el último número con otro 10. 20. 30. 40. XCHG
XCHG3
XCHG4
10. 20. 40. 30.
10. 40. 30. 20.
40. 20. 30. 10.
DROP Drop Elimina un número 10. 20. 30. 40. DROP
DROP3
DROP4
10. 20. 30.
10. 30. 40.
20. 30. 40.
RDN Roll Down Rota X hacia una posición previa 10. 20. 30. 40. 50. RDN3
RDN5
10. 20. 50. 30. 40.
50. 10. 20. 30. 40.
RUP Roll Up Rota una posición previa hacia X 10. 20. 30. 40. 50. RUP3
RUP4
10. 20. 40. 50. 30.
10. 30. 40. 50. 20.

Note que >1/DUP1 es lo mismo que >/DUP, que <1/DROP1 es lo mismo que </DROP, y que <>2/XCHG2 es lo mismo que <>/XCHG; la operación <>1/XCHG1 no es válida.

La operación <* (Drop All) elimina todos los datos insertados después de la cadena formato.

Las operaciones { }/RDN RUP (Roll Down/Roll Up) no pueden usarse sin una posición, y dicha posición debe ser mayor o igual a 2 (como en Exchange). Roll Down {/RDN mueve el último número hacia una posición previa (similar a Store). Roll Up }/RUP mueve el número de una posición previa hacia el último (similar a Recall).

Recuerde que printf.exe sólo puede mantener hasta 8 números de punto flotante en el stack; esto significa que la posición de DUP debe estar entre 1 y 7, en XCHG debe estar entre 2 y 8, en DROP entre 1 y 8, y en RDN/RUP entre 2 y 8. Si una posición se refiere a un número no ingresado, el programa printf.exe fallará (como es usual).

Tópico avanzado: todas las operaciones de manejo del stack asumen que todos los números en el stack son del mismo tipo: enteros o punto flotante. Si hay números de diferentes tipos en estas operaciones, el resultado depende de la posición del número de tipo diferente. Los números de punto flotante ocupan 64 bits mientras que los enteros ocupan 32 bits; esto significa que un flotante es equivalente a dos enteros, y que un entero es equivalente a medio flotante. Si usted está consciente de esta situación, podrá manipular números de diferentes tipos en el stack y aún así obtener el resultado deseado.

Cuando revise los siguientes ejemplos se sugiere dibujar un pequeño esquema en donde los enteros ocupen "1 lugar" y los flotantes ocupen "2 lugares". Recuerde que las operaciones enteras mueven un lugar y las flotantes dos.

printf "Entero: %i, Entero: %i, Flotante: %.2f\n"  10 20 30.  /* Uso normal */

Entero: 10, Entero: 20, Flotante: 30.00

printf "Entero: %i, Flotante: %.2f, Entero: %i\n"  10 20 30.
       /* Incorrecto: el 20 y la mitad (inferior) de 30. se muestran como Flotante 0.00
          y la mitad (superior) de 30. se muestra como entero 1077805056 */

Entero: 10, Flotante: 0.00, Entero: 1077805056

printf "Entero: %i, Flotante: %.2f, Entero: %i\n"  10 20 30. }3
       /* Corregido: rota el *TERCER* entero (el tercer número de "un lugar": el 20)
          por lo que el entero 20 se trae a la última posición */

Entero: 10, Flotante: 30.00, Entero: 20

printf "Flotante: %.2f, Entero:%i, Entero: %i\n"  10 20 30. {4 {4
       /* Lleva las *dos mitades* del 30. a la última posición */

Flotante: 30.00, Entero:10, Entero: 20

Nótese que este tipo de movimiento no funciona con operaciones de punto flotante de manejo del stack porque en este caso los registros del stack del FPU siempre participan en la operación y tales registros no se corresponden con los parámetros de printf.exe si hay números enteros intermedios. A continuación se explica este punto con mayor detalle.

Manejo del Stack de la FPU

Tópico avanzado: Esta sección contiene varias descripciones técnicas avanzadas.

HP # FPU
  8 D=
7 C=
6 B=
5 A=
T=0 4 T=
Z=0 3 Z=
Y=0 2 Y=
X=0 1 X=
 
 
 
 
 
 
 
 
 
 

El programa printf.exe utiliza la Unidad de Punto Flotante (FPU) para efectuar operaciones aritméticas de punto flotante; éste es un componente del hardware de la computadora diseñado específicamente para evaluar este tipo de operaciones. La FPU usa un stack de 8 registros completamente similar al stack de 4 registros de las calculadoras HP. Estos registros pueden ser nombrados utilizando el mismo orden "inverso" de las calculadoras HP como se muestra del lado izquierdo (utilizando la nomenclatura "ABCD" de la calculadora WP 34S (PDF 3 MB) para los cuatro registros adicionales).

Cuando el programa printf.exe toma un número de punto flotante en sus parámetros, el mismo número se ingresa en el stack de la FPU. En forma similar cuando se obtiene un resultado en el registro X de la FPU, el mismo resultado se copia en los parámetros de printf.exe. De esta manera, ambas áreas (parámetros de printf.exe y stack de la FPU) normalmente contienen los mismos números. Sin embargo, hay algunas operaciones que pueden desincronizar estas áreas. Por ejemplo, si usted quiere mostrar más de 8 números de punto flotante, puede utilizar la instrucción INIT que borra el stack de la FPU, pero no toca los parámetros. Por supuesto, hacer esto implica que después de un INIT NO se podrán efectuar operaciones aritméticas con los números ingresados antes del INIT. Hay un par de operaciones que también ejecutan un INIT, como <* (Drop All) y FMT} (Format End).

Como contraparte, CLEAR (Clear parameters) es una operación especial que borra los parámetros de printf.exe de la misma forma que <* (Drop All), pero preservando el stack de la FPU. De forma similar, FMT{ (Format Start) permite ingresar nuevos parámetros de printf.exe preservando el stack de la FPU. Nótese que en los parámetros de printf puede haber una mezcla de números enteros y de punto flotante (y caracteres y cadenas), mientras que el stack de la FPU sólo puede contener números de punto flotante.

Como se dijo antes, todas las operaciones de punto flotante se evalúan en el stack de la FPU. El hecho de que cada resultado se debe duplicar en los parámetros de printf.exe vuelven a estas operaciones algo ineficientes. Si estas operaciones se manejaran solamente en la FPU no sólo serían mas rápidas, sino que ciertas operaciones avanzadas que son muy complicadas para manejarse en los parámetros de printf.exe podrían implementarse en forma mas sencilla. Además, la FPU permite que las cuatro operaciones aritméticas básicas sean efectuadas sobre cualquiera de los 8 registros del stack y no sólo sobre el registro X. Este punto hace posible efectuar algunas operaciones avanzadas de una forma mas simple. Un buen ejemplo de esto es el programa Mandelbrot Set descrito mas adelante. Por lo tanto, en esta sección se describen operaciones de punto flotante que solamente afectan el stack de la FPU sin reflejar sus resultados en los parámetros de printf.exe.

El método para aprovechar estas operaciones consiste en: ingresar números, efectuar operaciones en el stack de la FPU para obtener un resultado, guardar el resultado en un registro de almacenamiento, borrar los parámetros de printf.exe (o utilizar la función FMT{), y recuperar el resultado del registro de almacenamiento para mostrarlo en la pantalla. La operación especial LOAD (Load parameters) es una forma sencilla de transferir valores del stack de la FPU hacia los parámetros de printf.exe.

Existe un número limitado de operaciones que trabajan de esta manera, solamente en el stack de la FPU. Estas operaciones son formas modificadas de las operaciones estándar STO, RCL y de manejo del stack con un caracter dos-puntos añadido al final, mas un par de operaciones nuevas. La tabla de abajo muestra todas estas operaciones especiales "sólo en la FPU".

Operaciones Sólo-en-el-Stack de la Unidad de Punto Flotante
  Operación
Standard
Operación
Sólo FPU
Nombre Operación Descripción
S
T
O
R
E
STOn STO:n Store FpuN = X Almacena X en el registro FPU N
STO[n STO:[n Store-Xchg FpuN <=> X Intercambia X y el registro FPU N
STO+n STO:+n Store-Add FpuN = FpuN+X Suma X al registro FPU N
STO++n STO:++n Store-Inc FpuN = FpuN+1 Incrementa el registro FPU N
STO-n STO:-n Store-Sub FpuN = FpuN-X Resta X del registro FPU N
STO--n STO:--n Store-Dec FpuN = FpuN-1 Decrementa el registro FPU N
STO*n STO:*n Store-Mul FpuN = FpuN*X Multiplica el registro FPU N por X
STO/n STO:/n Store-Div FpuN = FpuN/X Divide el registro FPU N entre X
   
R
E
C
A
L
L
RCLn RCL:n Recall Push FpuN Recupera X del registro FPU N
RCL+n RCL:+n Recall-Add X = X+FpuN Suma a X el registro FPU N
RCL++ RCL:++ Recall-Inc X = X+1 Incrementa el registro FPU X
RCL-n RCL:-n Recall-Sub X = X-FpuN Resta a X el registro FPU N
RCL-- RCL:-- Recall-Dec X = X-1 Decrementa el registro FPU X
RCL*n RCL:*n Recall-Mul X = X*FpuN Multiplica X por el registro FPU N
RCL/n RCL:/n Recall-Div X = X/FpuN Divide X entre el registro FPU N
   
S
T
A
C
K
DUPn DUP:n Duplicate Push FpuN Duplica el registro FPU N
XCHGn XCHG:n Exchange X <=> FpuN Intercambia X y el registro FPU N
DROPn DROP:n Drop Drop FpuN Quita (elimina) el registro FPU N
RDNn RDN:n Roll Down   Rota X hacia el registro FPU N
RUPn RUP:n Roll Up   Rota el registro FPU N hacia X
   
P
A
R
A
M
S
FMT} FMT}: Format End   Elimina los parámetros de printf.exe
desde la marca FMT{ anterior
preservando el stack de la FPU
<* CLEAR Clear params   Elimina todos los parámetros de printf.exe
preservando el stack de la FPU
INIT Initialize FPU Drop All Borra el stack de la FPU
preservando los parámetros de printf.exe
  LOADn
 
LOAD:n
Load params   Carga en los parámetros de printf.exe
el valor de los registros de la FPU,
y los borra del stack de la FPU

La forma de identificar el registro de la FPU que será operado es la misma utilizada en las operaciones de manejo del stack vistas anteriormente: mediante un dígito que indica una posición que es 1 para X, 2 para Y, etc. hasta 8 para D según se muestra en la tabla de los registros del stack de la FPU vista antes.

Nótese que, en ciertos casos, hay varias operaciones diferentes que obtienen el mismo resultado. Esto permite que usted elija la operación que mejor describa sus intenciones con el objetivo de escribir código mas claro.

Algunas operaciones STO de "sólo-FPU" sobre el registro X producen resultados no útiles, aunque sean válidas. Por ejemplo: STO:1 es X=X, STO:[1 es X<=>X, STO:-1 es X-X=Zero, STO:/1 es X/X=One; lo mismo pasa con operaciones RCL excepto RCL:1 que produce el mismo resultado que DUP:1.

La operación LOADn carga en los parámetros de printf.exe el valor del registro FPU N. LOAD sin dígito de posición carga todos los números ingresados en el stack FPU. Si un caracter dos-puntos es añadido (LOAD:n/LOAD:), entonces los registros del FPU cargados son también eliminados del stack FPU.

Las operaciones estándar STOi y RCLi de direccionamiento indirecto también pueden operar como "sólo-FPU" mediante un índice negativo almacenado en el registro I que especifica el registro del FPU, es decir, -1 para X, -2 para Y hasta -8 para D. De esta manera, cuando se utilice el registro Índice para direccionamiento indirecto i, un valor positivo o cero especificará un registro de almacenamiento en memoria, mientras que un valor negativo indicará un registro del stack de la FPU.

Un ejemplo de esta facilidad puede ser el programa que compila valores estadísticos visto anteriormente modificado para que los valores estadísticos se almacenen en registros de la FPU en lugar de registros de almacenamiento de memoria:

Registro Valor Descripción
R2 = B n Número de datos acumulados.
R3 = A ∑x Sumatoria de valores x.
R4 = T ∑x^2 Sumatoria de valores x al cuadrado.
R5 = Z ∑y Sumatoria de valores y.
R6 = Y ∑y^2 Sumatoria de valores y al cuadrado.
R7 = X ∑xy Sumatoria de productos de valores x por valores y.

Este es el segmento que compila los valores estadísticos, modificado para usar los registros del stack de la FPU:

   /* Part 0: Enter  B=0  A=0  T=0  Z=0  Y=0  X=0		*/ ^
   ZERO ZERO ZERO ZERO ZERO ZERO CLEAR          /* in FPU only	*/ ^
   /* Part 1: Populate statistics values in FPU only registers	*/ ^
   (		/*  B=0  A=0  T=0    Z=0  Y=0    X=0    ! WHILE read line	*/ ^
      80	/*                                      !    80   (input len)	*/ ^
      IN	/*                                      !    "x.xx y.yy" len	*/ ^
      ==0? ;	/*                                      ! line empty?  break	*/ ^
   /" < /"	/*                                      !   "x.xx y.yy"		*/ ^
      atof1	/*  D=0  C=0  B=0    A=0  T=0    Z=0    !    x. y. 2		*/ ^
   /" < /"	/*                                      !    x. y.		*/ ^
      STO:++8	/*  D++                                 !       Accum n		*/ ^
      STO:+5	/*                   A+=y               !       Accum Sy	*/ ^
      STO9	/*                                      !       R9 = y		*/ ^
      Sqr	/*                                      !    x. y^2		*/ ^
      STO:+4	/*                        T+=y^2        !       Accum Sy^2	*/ ^
      DROP	/*  C=n  B=0  A=0    T=y  Z=y^2  Y=0    !    x.			*/ ^
      STO:+6	/*       B+=x                           !       Accum Sx	*/ ^
      DUP	/*  D=n  C=x  B=0    A=y  T=y^2  Z=0    !    x. x.		*/ ^
      Sqr	/*                                      !    x. x^2		*/ ^
      STO:+6	/*            B+=x^2                    !       Accum Sx^2	*/ ^
      DROP	/*  C=n  B=x  A=x^2  T=y  Z=y^2  Y=0    !    x.			*/ ^
      RCL*9	/*                                      !    x*y		*/ ^
      STO:+2	/*  C=n  B=x  A=x^2  T=y  Z=y^2  Y+=x*y !       Accum Sx*y	*/ ^
      DROP	/*  B=n  A=x  T=x^2  Z=y  Y=y^2  X=x*y  !       Empty params	*/ ^

Este es un fragmento del código que calcula algunos valores estadísticos:

   /* Storage registers in original formulae are now mapped to these FPU stack registers: */ ^
   /* R2 = B, R3 = A, R4 = T, R5 = Z, R6 = Y, R7 = X		*/ ^
   /* Part 2: Calculate statistics values			*/ ^
   /*    R8  =  M = R2*R4 - R3^2				*/ ^
   /*    R9  =  N = R2*R6 - R5^2				*/ ^
   /*    R0  =  P = R2*R7 - R3*R5				*/ ^
   RCL:6 RCL:*5 RCL:6 STO:*1 STO:-2 DROP:1 STO8 DROP:1	/* M	*/ ^
   RCL:6 RCL:*3 RCL:4 STO:*1 STO:-2 DROP:1 STO9 DROP:1	/* N	*/ ^
   RCL:6 RCL:*2 RCL:6 RCL:*5 STO:-2 DROP:1 STO0 DROP:1	/* P	*/ ^
   /* Part 3: Calculate and output results			*/ ^
   /*    Mean:  Mx = R3 / R2,  My = R5 / R2			*/ ^
   FMT{ "Mean (average):\t\tMx=%%.2f  My=%%.2f\n"  RCL:5 RCL:/7 LOAD:1 RCL:3 RCL:/7 LOAD:1  OUT FMT}:	/* Mean */ ^
   /*    Standard Deviation:  Sx = SQRT( R8 / (R2*(R2-1)) ), Sy = SQRT( R9 / (R2*(R2-1)) )  */ ^
   FMT{ "Standard Deviation:\tSx=%%.2f  Sy=%%.2f\n"	/* Standard Deviation		    */ ^
        RCL:6 RCL:-- RCL:*7 RCL8 RCL:/2 Sqrt DROP:1  RCL9 RCL:/2 Sqrt DROP:1 DROP:1  OUT FMT}:	/* Sx, Sy */ ^

Note que usted debe cargar un valor en ambos sitios (los registros del FPU y los parámetros de printf.exe) cuando necesite usar cualquier operación estándar, como SQRT, que es el propósito de las operaciones estándar RCL8 y RCL9 en la última fórmula anterior. También note que el valor en los parámetros no se modifica por las operaciones "sólo-FPU" ni se usa como dato de entrada en las operaciones estándar, pero es importante porque "reserva el lugar" necesario para evaluar las operaciones estándar sin error.

El programa completo se encuentra en el archivo printf Example 1.5 - Two var statistics - FPU only.bat.

Un ejemplo mas extenso de esta facilidad es el programa Mandelbrot Set que se describe posteriormente.

Operaciones con Cadenas

En el programa printf.exe las cadenas de caracteres se almacenan en dos partes: los datos que son los caracteres de la cadena, y su dirección que es la parte que usa la función printf CRT para mostrar la cadena mediante la especificación de formato %s. Cuando se ingresa una "cadena de caracteres", los caracteres se almacenan en una zona reservada para ellos con un byte cero binario añadido al final (que marca el final de la cadena), y el valor que aparece en los parámetros del programa printf.exe es la dirección (puntero) del primer caracter de la cadena. Un detalle interesante es que esta dirección es un número entero de 32 bits, por lo que puede ser "manipulado" con las operaciones enteras estándar como Dup, Exchange, etc. Algunas de estas operaciones dan un resultado útil cuando son aplicadas a las direcciones de cadenas de caracteres. Unos cuantos ejemplos pueden ayudar a entender este punto; se sugiere copiar estos ejemplos y ejecutarlos en la ventana de comandos para comprobar el resultado.

printf "Primera: %s,  Segunda: %s\n" "Uno" "Dos"
printf /" "Segunda: %s,  Primera: %s\n" "Uno" "Dos" <>  /* Exchange cadenas */

printf /" "Una cadena: \"%s\", *la misma* cadena: \"%s\"\n" "Sólo una cadena" >  /* Duplica cadena */

En el primer ejemplo se ingresan dos cadenas como se explicó antes: sus caracteres se almacenan en el área de datos y sus direcciones se ingresan como parámetros de printf.exe (como si fuera cualquier otro parámetro, como un número). En el segundo ejemplo las cadenas se almacenan de la misma manera y el operador <> intercambia las direcciones de las cadenas (no sus caracteres). De esta manera, la primer dirección apunta a la segunda cadena y viceversa.

En el tercer ejemplo el operador > duplica la dirección de la única cadena dada (no sus caracteres), por lo que los mismos caracteres se muestran dos veces.

Además de los operadores enteros, que pueden producir resultados útiles cuando se usan sobre direcciones de cadenas, printf.exe incluye varias funciones diseñadas específicamente para trabajar sobre cadenas. Todas estas funciones no modifican los datos (caracteres) de las cadenas dadas; tan sólo eliminan de los parámetros de printf.exe las direcciones de las cadenas procesadas. Esto significa que si usted almacena dichas direcciones, aún podría accesar esas cadenas posteriormente.

Por ejemplo, la función join concatena las cadenas dadas: printf "Resultado: \"%s\"\n" "Uno" "Dos" "Tres" 3 join muestra: Resultado: "Uno Dos Tres". Si se guardan las direcciones de las 3 cadenas procesadas, éstas se pueden mostrar después de la función:

printf "La cadena \"%s\" es la unión de \"%s\"+\"%s\"+\"%s\"\n" "Uno" ]1 "Dos" ]2 "Tres" ]3 3 join [1 [2 [3

Muestra: La cadena "Uno Dos Tres" es la unión de "Uno"+"Dos"+"Tres"

De esta forma, los registros enteros de almacenamiento también pueden funcionar como registros de almacenamiento de cadenas, aunque usted no debe perder de vista que lo que se almacena aquí son solamente direcciones; los caracteres se almacenan en otro lugar.

A pesar de lo anterior, usted debe notar que la operación <* (Drop All) libera el área de almacenamiento de datos de todas las cadenas borradas, por lo que esa área podría ser ocupada por nuevas cadenas que se ingresen posteriormente.

Funciones sobre Cadenas de Caracteres
Nombre Descripción
Forma canónica (CRT)
Ejemplo
Antes --> func --> Después
atoi Convierte uno/todos número(s) entero(s)
de una cadena
atoi0(cadena)
atoi1(cadena)
"123"
"12 34 56"
atoi0
atoi1
123
12 34 56 3
atof Convierte uno/todos número(s) flotante(s)
de una cadena
atof0(cadena)
atof1(cadena)
"47.250"
"12 34 56 78"
atof0
atof1
47.25
12. 34. 56. 78. 4
len Número de caracteres en una cadena
len(cadena)
"LasQuinceLetras" len "LasQuinceLetras" 15
getc Toma uno/todos caracter(es) de una cadena
getc0(cadena,pos)
getc1(cadena)
"ABCDEFG" 3
"ABCDEFG"
getc0
getc1
"ABCDEFG" 3 'D'
'A' 'B' 'C' 'D' 'E' 'F' 'G' 7
putc Pone uno/todos caracter(es) en una cadena
putc0(cadena,pos,'C')
putc1('1','2',...,'n',N)
"ABCDEFG" 3 'x'
'A' 'B' 'C' 'D' 'E' 'F' 'G' 7
putc0
putc1
"ABCxEFG" 3
"ABCDEFG"
xchc Cambia un caracter de/a una cadena
xchc(cadena,pos,'C')
"ABCDEFGHIJ" 3 'x' xchc "ABCxEFGHIJ" 3 'D'
movc Mueve un caracter (puntero)
movc0(puntero)
movc1(puntero,'C')
movc2(puntero1,puntero2)
"ABCDEFG"
"ABCDEFG" 'x'
"ABCDEFG" "01234567"
movc0
movc1
movc2
"ABCDEFG" 'A'
"xBCDEFG"
"0BCDEFG" "0123456"
dupc Duplica un caracter varias veces
y crea una cadena
dupc(char,N)
'#' 8 dupc "########"
dups Duplica una cadena varias veces
dups0(cadena,N)
dups1(cadena,N,separador)
"Hola." 3
"Hola." 3 " + "
dups0
dups1
"Hola." "Hola.Hola.Hola."
"Hola." "Hola. + Hola. + Hola."
revc Invierte los caracteres de una cadena
revc(cadena)
"Esta es una prueba" revc "abeurp anu se atsE"
revs Invierte la posición de varias cadenas
revs(cad1,cad2,...,cadN,N)
"Esta" "es" "una" "prueba" 4 revs "prueba" "una" "es" "Esta" 4
gets Toma parte de una cadena (subcadena)
gets0(cadena,inicio,long)
gets1(cadena,inicio,fin)
"ABCDEFGHIJ" 3 4 gets0
gets1
"ABCDEFGHIJ" "DEFG"
"ABCDEFGHIJ" "DE"
index Posiciones de subcadena en una cadena
index(cadena,subc)
"Esta es una prueba" "a"
"Esta es una prueba" "es"
"Esta es una prueba" "x"
index "Esta es una prueba" 3 10 17 3
"Esta es una prueba" 5 1
"Esta es una prueba" 0
split Divide una cadena en varias subcadenas
split0(cadena)
split1(cadena,delims)
"Esta es una prueba"
"Esta_es_una_prueba"
"Esta_es_una_prueba" "_"
split0
split0
split1
"Esta" "es" "una" "prueba" 4
"Esta_es_una_prueba" 1
"Esta" "es" "una" "prueba" 4
join Une varias cadenas en una mas larga
join0(cad1,cad2,...,cadN,N)
join1(cad1,cad2,...,cadN,N,separador)
"Esta" "es" "una" "prueba" 4
"Esta" "es" "una" "prueba" 4 "><"
join0
join1
"Esta es una prueba"
"Esta><es><una><prueba"
shift Desplaza la posición de varias cadenas
shift(cad1,cad2,...,cadN,N)
"Esta" "es" "una" "prueba" 4 shift "es" "una" "prueba" 3 "Esta"
repl Reemplaza partes de una cadena
repl(cadena,viejaC,nuevaC)
"Esta es una prueba" "una" "otra"
"Esta es una prueba" "una " ""
"Letras" "" "_"
repl "Esta es otra prueba"
"Esta es prueba"
"L_e_t_r_a_s"
cmps Compara dos cadenas: -1 0 1
cmps(str1,str2)
cmpsi(str1,str2)
"UNO" "Uno" cmps
cmpsi
"UNO" "Uno" 1
"UNO" "Uno" 0

Las funciones atoi1 y atof1 extraen todos los números que aparezcan en la cadena los cuales pueden estar mezclados con otros caracteres; el único requisito para que un número sea convertido es que esté precedido por un espacio, TAB o coma. El último valor generado es un entero que indica cuántos números fueron convertidos. Recuerde que en printf.exe se pueden ingresar un máximo de 8 números de punto flotante.

En la función gets si inicio es negativo especifica una posición hacia atrás desde el final de la cadena, y si long es negativo especifica una posición (no una longitud) desde el final de la cadena. En la variante gets1 el parámetro fin es la posición del último caracter, sin incluírlo; si fin es cero, se toma hasta el último caracter.

En la función repl si la nuevaC está vacía la viejaC correspondiente se borra, y si la viejaC comienza o termina en asterisco la parte que se reemplaza se modifica. Cuando viejaC comienza con asterisco así: string "*subs" "new" repl, la función reemplaza desde el principio de la cadena hasta la primera aparición de "subs". Si viejaC termina en asterisco así: string "subs*" "new" repl se reemplaza desde la última aparición de "subs" hasta el final de la cadena. Si ambos asteriscos se incluyen así: string "*subs*" "new" repl la función reemplaza ambas partes al principio y al final de la cadena y preserva la parte de enmedio. Si viejaC está vacía, la nuevaC se inserta entre cada caracter de la cadena original.

En la variante split0 la cadena se divide en cada espacio o TAB, pero las partes de la cadena que estén encerradas entre comillas se conservan igual. En la variante split1 usted puede definir los caracteres delimitadores. En cualquier caso varios delimitadores sucesivos se tratan como uno solo.

La función cmps obtiene la "relación ordinal" de las cadenas y devuelve -1, 0 o 1 si la primer cadena es menor que, igual o mayor que la segunda, respectivamente.

La operación estándar de las funciones index, repl y cmps es sensible a mayúsculas y minúsculas: el tamaño de letra de la subcadena debe coincidir con el de la cadena base. Se puede ignorar el uso de mayúsculas y minúsculas en la función cmps agregando una letra "I" al final: cmpsI (sólo en esta función en esta versión de printf.exe).

La función movc con parámetros tipo "puntero" se describe en seguida.

Punteros de Caracteres

Como se dijo antes, los caracteres y la dirección de una "cadena" se almacenan en partes separadas, y la parte que se maneja en los parámetros de printf.exe es la dirección. Esta "dirección" también podría llamarse "puntero" porque ambos términos se refieren (apuntan) a un caracter dado. La diferencia es sutil: en este contexto, la "dirección de una cadena" se refiere al principio de la cadena, mientras que un "puntero" se refiere a cualquier caracter de la cadena. ¿Cómo podría una "dirección" apuntar a un caracter que no sea el primero? Simple: tan sólo súmele un número (desplazamiento u offset). Recuerde que una dirección de cadena ¡ES un número entero de 32 bits!

printf /" "La cadena \"%s\" se encuentra en la dirección %i\n"  "Una cadena" >  /* Duplica dirección de cadena */

printf "Mueve el puntero a un caracter posterior: %s\n" "ABCDEFGHIJ" 3 +    /* Muestra: DEFGHIJ */

movc es una función multi-propósito de puntero que realiza las operaciones de las funciones getc y putc estándar.

La función estándar getc0 requiere una dirección de cadena (inicio) y una "posición de caracter" (desplazamiento) para obtener un caracter dado; la posición comienza en cero y se incrementa hasta la longitud de la cadena para procesar todos los caracteres. La función movc0 usa directamente un "puntero de caracter". Este puntero comienza en la misma dirección de la cadena, por lo que nunca es igual a cero, y se incrementa hasta la dirección del último caracter de la cadena para procesar todos los caracteres. La versión de puntero no requiere la dirección inicial de la cadena, por lo que su uso es más simple y rápido. Este mismo punto se aplica cuando se comparan las funciones putc0 y movc1 que ponen (insertan) un caracter dentro de una cadena.

movc2 es una función avanzada que realiza la tarea de ambas getc0 y putc0 de una sola vez, ya que mueve el caracter apuntado por el segundo puntero directamente al lugar apuntado por el primer puntero sin ingresar/retirar el caracter del stack.

Mas aún, las tres variantes de la función movc pueden opcionalmente incrementar sus punteros después de mover el caracter. Para ello, sólo inserte un signo "+" entre la función y su último dígito, como en movc+0 o movc+1. En la última variante se puede insertar un signo "más" antes o después del "2" para incrementar cada uno de los punteros, o incluso para incrementar ambos como en movc+2+. Esta facilidad ayuda a escribir bucles compactos y eficientes que procesen varios caracteres. Algunos ejemplos comparativos de estas características se muestran mas adelante.
Top

Programación por Bloques

La versión 2 de la aplicación printf.exe también ofrece la posibilidad de hacer programación básica. Esta característica no sólo proporciona los medios para resolver una amplia gama de problemas numéricos y de proceso de texto, sino que también permite tener un primer contacto con la programación de computadoras de una forma muy sencilla. El esquema de programación utilizado en printf.exe no es el esquema tradicional de los lenguajes de programación usuales de alto nivel; es un esquema mucho mas simple que aún ofrece las mismas ventajas de los modernos lenguajes estructurados. Este método de programación fue adaptado de Regular Expression Compiler (REC), un lenguaje de programación estructurado derivado de Lisp desarrollado en México alrededor de 1966 (mayor información sobre esto aquí) por Harold V. McIntosh que está basado en tan sólo cuatro elementos de control y un par de reglas simples sobre la evaluación de condiciones. He bautizado a esta tecnología Programación por Bloques.

En su forma más simple, un programa es una serie de operaciones que se ejecutan en orden. Eso es todo. Desde este punto de vista, todos los ejemplos de uso de printf.exe vistos hasta ahora son programas. Por ejemplo, para obtener el resultado de la expresión algebraica (4+5)/(6+7) podemos usar:

printf "Resultado: %f\n"  4. 5. ADD  6. 7. ADD  DIV

Esta expresión RPN es realmente un programa que significa:

  1. Ingresa el número 4.
  2. Ingresa el número 5.
  3. ADD (Suma 4 + 5)
  4. Ingresa el número 6.
  5. Ingresa el número 7.
  6. ADD (Suma 6 + 7)
  7. DIV [Divide (4+5)/(6+7)]
  8. Out (Muestra el resultado)

Dicho de otra forma: para obtener el resultado de la expresión algebraica (4+5)/(6+7) necesitamos ejecutar los 8 pasos previos en orden. Podemos llamar instrucciones a cada uno de los pasos u operaciones que forman un programa. Note que si usted cambia el orden de cualquiera de las 8 instrucciones previas usted no resolverá el problema planteado (esto sería un error de programación, comúnmente llamado bug). Por otro lado, el ejemplo anterior no es la única forma de resolver este problema. Podríamos pensar en un programa diferente que resolviera el mismo problema. Por ejemplo:

printf "Resultado: %f\n"  6. 7. ADD  4. 5. ADD  XCHG  DIV
  1. Ingresa el número 6.
  2. Ingresa el número 7.
  3. ADD (Suma 6 + 7)
  4. Ingresa el número 4.
  5. Ingresa el número 5.
  6. ADD (Suma 4 + 5)
  7. XCHG [Cambia posiciones de (6+7) y (4+5)]
  8. DIV [Divide (4+5)/(6+7)]
  9. Out (Muestra el resultado)

Si hay varias formas diferentes de escribir un programa, ¿cuál debemos elegir? Esto dependerá de varios factores y los programas resultantes pueden tener diferentes características. Un programa puede ser mas rápido que otro, pero quizás es enredado y difícil de entender. Otro programa puede ser mas claro y un buen ejemplo para propósitos educativos, pero ser mas lento. Veremos ejemplos de estos puntos mas adelante. ¿Usted podría pensar en otro programa diferente a los dos anteriores que resolviera el mismo problema?

Veamos un ejemplo diferente. La fórmula algebraica para calcular el área de un triángulo es: area = ( base * altura ) / 2. Si quisiéramos calcular el área de un triángulo de base=8 y altura=12, podríamos utilizar este "programa":

printf "Area = %f\n" 8. 12. MUL 2. DIV

Después de esto, para calcular el área de otro triángulo, esta vez de base=33.5 y altura=18, haríamos esto:

printf "Area = %f\n" 33.5 18. MUL 2. DIV

Es decir, las operaciones para calcular el nuevo problema son las mismas que antes; sólo necesitamos cambiar los datos iniciales. Esto significa que las operaciones MUL 2. DIV representan un programa de uso general que puede obtener el área de cualquier triángulo. ¿Cierto? ;)

Sin embargo, ¿cómo podemos convertir estas instrucciones en un programa real que pueda ejecutarse de forma independiente? Es decir, en el que no se necesite re-escribir los valores de los datos que cambian. Dicho de otra forma, un programa que sea capaz de tomar, o leer o ingresar (use el término que usted prefiera) los valores de los datos por sí mismo. Bueno, para poder hacer eso necesitamos un nuevo tipo de instrucción: una operación que permita ingresar un dato que no estaba originalmente en la expresión RPN, pero que sea insertado en un cierto lugar cuando el programa corra.

De la misma manera, cuando un programa se ejecuta, frecuentemente necesita mostrar mensajes diferentes en diferentes momentos. Sin embargo, la operación estándar de printf.exe es mostrar un resultado especificado por un formato cuando el programa termina. Necesitamos un método para mostrar varios resultados con diferentes formatos en cualquier momento que queramos.

Este es el propósito de las operaciones de entrada/salida.

Operaciones de Entrada/Salida

Operaciones de Entrada/Salida
Oper Nombre Descripción
GETK Get Key Toma una tecla (caracter) del teclado
IN Input Line Lee una línea (cadena de caracteres) desde el teclado
CURS Cursor Toma o fija la posición y visibilidad del cursor
OUT Output Muestra los datos actuales con el formato actual
FMT{ Format Start Permite ingresar un nuevo formato, datos y color
FMT} Format End Elimina el formato y datos desde la marca FMT{ anterior

La operación CURS (Cursor) controla el cursor de esta manera:

Operaciones de la Función Cursor
Oper Nombre Descripción
CURS Cursor Get Toma la posición del cursor en los registros Y (línea) y X (columna)
CURS0 Cursor Hide Apaga el cursor
CURS1 Cursor Show Enciende el cursor
CURS2 Cursor Set Fija la posición del cursor desde R0 (línea) y R1 (columna)

La operación OUT (Output) muestra los datos actuales con el formato actual. Por ejemplo:

printf /" "El valor es: %i\n" 10 OUT  < 20 OUT

Muestra:

El valor es: 10
El valor es: 20

Note que si usted usa la operación OUT, la "salida automática" que normalmente muestra printf.exe al final se cancela. Esto también ocurre si usted usa un programa (más detalles sobre este punto después).

El delimitador FMT{ (Format Start) permite ingresar un nuevo formato y nuevos datos después de él que serán utilizados en la siguiente operación OUT. Por ejemplo:

printf "Un mensaje\n" OUT  FMT{ "Dos números: %i %i\n" 10 20 OUT  FMT{ "Una cadena: %s\n" "Hola" OUT

Muestra:

Un mensaje
Dos números: 10 20
Una cadena: Hola

La operación FMT} (Format End) elimina todos los datos ingresados desde el delimitador FMT{ previo, incluído el delimitador. Por ejemplo:

printf /" "Un mensaje\n" OUT  FMT{ "Dos números: %i %i\n" 10 20 OUT  FMT{ "Una cadena: %s\n" "Hola" OUT  FMT} <> OUT  FMT} OUT

Muestra:

Un mensaje
Dos números: 10 20
Una cadena: Hola
Dos números: 20 10
Un mensaje

El delimitador FMT{:r también permite definir el color con el que se muestra el texto en la operación OUT correspondiente mediante un atributo almacenado en una memoria entera. Por ejemplo:

set /A "GREEN=10, RED=12"
printf /" "Mensaje\n" OUT  GREEN atoi ]4 RED atoi ]6  FMT{:4 "Números verdes: %i %i\n" 10 20 OUT  FMT{:6 "Cadena roja: %s\n" "Hola" OUT  FMT} <> OUT  FMT} OUT

Muestra:

Mensaje
Números verdes: 10 20
Cadena roja: Hola
Números verdes: 20 10
Mensaje

Los atributos de color también se pueden definir en el código RPN para un uso mas simple; un ejemplo se muestra en el archivo printf Example B - Color Attributes.bat que utiliza los mismos nombres estándar de colores de HTML (páginas Web).

El color definido en el registro de almacenamiento es dinámico, es decir, ese valor será consultado cada vez que se muestre algo en la pantalla. Si ese valor cambia, el nuevo color aparecerá en la siguiente operación OUT.

Nótese que el color de la última operación OUT se mantendrá en el nuevo texto que aparezca en la pantalla después de que el programa printf.exe haya terminado. Esta característica puede ser aprovechada en archivos .BATch para mostrar texto con diferentes colores. Si usted no desea este efecto, ejecute una operación OUT con un formato vacío antes de terminar el programa.

Recuerde que la operación <* (Drop All) elimina todos los datos ingresados después de la cadena de formato del delimitador FMT{ actual (o desde la cadena de formato inicial).

IMPORTANTE: después de una operación FMT} (Format End) NO se podrán efectuar operaciones aritméticas con los números de punto flotante ingresados antes, es decir, en un nivel FMT{ FMT} anterior. Esto es debido a que FMT} ejecuta una operación INIT. Si usted necesita conservar un número de punto flotante para usarlo después de un FMT}, debe guardarlo en un registro de almacenamiento y recuperarlo después del FMT}.


Las operaciones de entrada permiten ingresar datos dentro de la expresión RPN. Cuando una operación de entrada se ejecuta, el programa espera a que el usuario proporcione un dato a través del teclado. Cuando el dato se completa, se ingresa en la expresión RPN en el mismo lugar en que estaba la operación de entrada y el programa continúa con la siguiente instrucción.

La operación de entrada mas sencilla es GETK (Get Key) la cual toma la presión de una tecla y regresa su valor como un caracter (o entero). Por ejemplo:

printf /" "Presione una tecla: " OUT  FMT{ "\nLa tecla oprimida es %i ('%c')\n" GETK > OUT

La operación GetKey puede tomar cualquier tecla. En las teclas de caracteres estándar se devuelve su código ASCII y en las teclas especiales se devuelve un valor negativo. Usted puede consultar el valor devuelto por todas las teclas especiales en el archivo printf - GetKey codes.txt. Estos valores se basan en la posición de las teclas en el teclado extendido de la IBM-PC. Puede revisar la manera en que estos códigos se generan en el archivo ShowKeyCodes.bat.

La operación IN (Input Line) lee una línea desde el teclado y la ingresa como una cadena de caracteres seguida por la longitud (número de caracteres) de la cadena. Antes de la operación IN se debe ingresar el número máximo de caracteres que pueden leerse; este número se elimina por la operación de entrada. Por ejemplo:

printf "Ingrese una cadena: " OUT  FMT{ "\"%s\" tiene %i caracteres\n" 80 IN  OUT

Si la línea leída está vacía, la operación IN devuelve una cadena vacía ("") seguida por un cero. Sin embargo, si se está leyendo un archivo de texto redireccionado y se alcanza el FinDelArchivo (EndOfFile), sólo se regresará un valor -1 sin ninguna cadena antes.

Las operaciones de entrada nos permiten escribir el programa independiente que puede tomar los datos necesarios por sí mismo, como se había explicado antes. Por ejemplo, retomando el problema del "Area del triángulo", ahora lo podemos resolver de esta manera:

printf /" "Base: " OUT 20 IN < atof STO1  FMT{ "Altura: " OUT 20 IN < atof STO2  FMT{ "Área = %.2f\n" RCL1 RCL2 MUL 2. DIV OUT

Note que los números ingresados desde el teclado de esta manera se "quedan" detrás de los delimitadores FMT{ por lo que es necesario moverlos a un lugar después del último delimitador FMT{ para poder incluírlos en el resultado final. En este caso esto se resuelve guardando los números en registros de almacenamiento y recuperándolos después.

¡Muy bien! Ahora tenemos un método para escribir un programa que pueda tomar sus datos y resolver un problema. Y ahora, ¿qué sigue? Esto no es tan impresionante... Lo que sería realmente importante sería resolver cientos o miles de problemas del mismo tipo utilizando el mismo método, o resolver problemas que puedan tener varias soluciones diferentes dadas por fórmulas diferentes basadas en condiciones diferentes.

Para poder hacer eso necesitaríamos repetir secciones de un programa, o ejecutar condicionalmente partes de un programa, etc. Esto es lo que realmente se llama programar.


Como se dijo antes, el método que se usa en la aplicación printf.exe para escribir programas se basa en sólo cuatro elementos de control (Begin, Repeat, Quit y End) y unos cuantos conceptos que fueron tomados del lenguaje de programación REC, como son Bloque de código, Transferencia del flujo de control y Tests condicionales. En el esquema de abajo se muestran TODAS las reglas de operación de la programación por bloques. En las siguientes secciones se explicará con detalle cada una de estas reglas.

Bloque de Código

Un bloque de código es una serie de operaciones encerradas entre ( (paréntesis izquierdo), al que podríamos llamar BEGIN ("inicio"), y ) (paréntesis derecho), al que podríamos llamar END ("fin"). Llamaremos delimitadores a estos dos caracteres. Por ejemplo:

printf "Primer entero: %i,  segundo entero: %i\n"  10 ( 20 )

En este ejemplo hay un bloque de código que contiene al número 20. Cuando la expresión RPN es evaluada, las operaciones colocadas dentro de un bloque de código se ejecutan en la forma usual, de izquierda a derecha; sin embargo, en este ejemplo el resultado no se muestra en la pantalla. Cuando la expresión RPN contiene un bloque de código, la salida que normalmente se muestra al final es cancelada, por lo que es necesario mostrarla explícitamente mediante la instrucción OUT. Por ejemplo:

printf "Primer entero: %i,  segundo entero: %i\n"  10 ( 20 ) OUT

Este ejemplo muestra Primer entero: 10, segundo entero: 20 en la forma usual.

Los bloques de código pueden ser combinados o anidados unos dentro de otros. Por ejemplo:

printf "Un entero: %i.  Una cadena: %s.  Un flotante: %f\n"  ( 25  ( "Hola, mundo" ) 44.33 ) OUT

En este ejemplo hay dos bloques de código. El primero contiene tres elementos: el entero 25, un bloque de código anidado, y el flotante 44.33. El segundo bloque de código sólo contiene una cadena. Cuando la programación por bloques es utilizada, todas las operaciones pertenecen a o están contenidas en un bloque de código y solamente en un cierto bloque de código. En este ejemplo la cadena pertenece al bloque anidado, precisamente. El primer bloque de código no contiene ninguna cadena; contiene dos números y un bloque anidado, precisamente.

Cuando se indique que una operación trabaja en su bloque de código quiere decir que esa operación no afecta ningún otro bloque que pudiera estar anidado dentro de él. Por ejemplo, si tuviéramos dos nuevas operaciones llamadas A y B, y las utilizáramos de esta forma:

printf "Un entero: %i.  Una cadena: %s.  Un flotante: %f\n"  ( 25 A  ( "Hola, mundo" B ) 44.33 ) OUT

... entonces la operación A trabajaría solamente sobre el primer bloque y la operación B solamente en el bloque anidado, es decir, la operación A no puede afectar (ni llegar a) la cadena contenida en el bloque anidado.

Transferencia de Control

La ejecución de los elementos de una expresión RPN normalmente ocurre de izquierda a derecha, comenzando por el primer elemento y terminando después del último. Esta ruta de ejecución se llama flujo de control: la instrucción particular que se está ejecutando en un momento dado se dice que "tiene el control" y éste va "fluyendo" de una instrucción a la siguiente a medida que se van ejecutando.

Nosotros podemos alterar la ruta estándar de ejecución de un programa mediante una instrucción de transferencia de control la cual causa que la ejecución brinque a otro punto dentro de la expresión RPN. De hecho, éste es el propósito de los delimitadores BEGIN y END: marcar los puntos a donde el flujo de control puede ser transferido.

Existen dos instrucciones de transferencia de control: : (dos puntos) llamado REPEAT ("repetir"), y ; (punto y coma) llamado QUIT ("salir"). La instrucción REPEAT transfiere el flujo de control hacia atrás, al inicio de su bloque de código. Por ejemplo:

printf "Hola, mundo\n" ( OUT : )

Este ejemplo muestra el mensaje muchas veces en un bucle sin fin hasta que el programa sea cancelado mediante la tecla Ctrl-C.

La instrucción QUIT transfiere el control hacia adelante, después del fin de su bloque de código:

printf "%s %s\n"  ( "Esto aparece en la salida" ; "Esto NO aparece" ) "Al final" OUT

Usted puede apreciar visualmente estas transferencias de control en el esquema visto anteriomente.

Test/Condición

Un test o "prueba" es una operación que, cuando es evaluada, contesta si una condición es Verdadera (True) o Falsa (False). Si la condición es True, el flujo de control continúa con la siguiente operación (ésta es la antigua regla Do-If-True "haz si es verdadero" de las calculadoras HP). Si la condición es False, el flujo de control se transfiere hacia adelante hasta pasar la siguiente instrucción de transferencia de control (REPEAT : o QUIT ;) colocada en el mismo bloque. Este sencillo esquema permite ensamblar los componentes básicos de los que se componen todos los programas de computadora.

Todos los tests usados en la programación por bloques de printf.exe se escriben con un signo de interrogación al final. Los tests condicionales más simples son unos cuantos operadores RPN enteros con un signo de interrogación añadido. La condición establecida en esta combinación operador/test es: ¿el resultado es diferente de cero? Por ejemplo, el operador -- (Decrement) le resta 1 al último número entero. Cuando el signo de interrogación es añadido de esta forma: --? este operador se convierte en un Test? que es verdadero mientras el resultado del decremento no sea cero. Veamos un ejemplo sencillo:

printf "Vuelta número %i\n" 10 ( OUT --? : )

Al principio de este ejemplo se ingresa el número 10, se ejecuta la instrucción OUT y el mensaje Vuelta número 10 aparece en la pantalla. Entonces el operador/test --? se ejecuta, por lo que el 10 se vuelve 9. Como el resultado no es cero, el flujo de control continúa normalmente. La instrucción REPEAT : se ejecuta y el flujo de control se transfiere de regreso al principio del bloque de código.

La instrucción OUT se ejecuta otra vez y muestra Vuelta número 9. Este proceso se repite y en el siguiente ciclo Vuelta número 8 aparece en la pantalla. El bucle continúa de la misma forma hasta que el mensaje Vuelta número 1 se muestra. Después de esto, el resultado de la operación decrement es cero, por lo que el test --? es False. Entonces el flujo de control se transfiere hacia adelante hasta pasar la instrucción REPEAT, se llega al final del bloque de código y el programa termina. Este comportamiento es similar a la instrucción Decrement and Skip on Zero (Decrementa y Brinca en Cero) de las calculadoras HP.

Esta forma de ejecutar instrucciones en un bucle repetitivo se conoce como Do-While: Do (haz) muestra el mensaje y decrementa el número While (mientras) el resultado no sea cero. Existe otra construcción llamada While-Do en la cual el Test? se evalúa primero, por lo que existe la posibilidad de que las operaciones del "Do" no se ejecuten ni siquiera una vez.

Otro ejemplo: el operador & (Bitwise AND) permite probar bits individuales de un número entero. En la representación interna de los números enteros, el bit menos significativo (que corresponde al número 1) está encendido si el número es impar, y está apagado si el número es par. De esta forma, si operamos cualquier número entero y un 1 con la combinación operador/test &?, el resultado del Test? es la respuesta a la pregunta: "¿El número es impar?". Veamos:

printf "El número %i es %s\n" 10  /"  > 1 ( &? < "Impar" ; < "Par" ) OUT

Este ejemplo comienza con un número. Primero duplicamos > el número e ingresamos un 1. Entonces, el &? evalúa un AND sobre bits y se queda sólo con el último bit del número. Si este bit está prendido (si el resultado no es cero) el control continúa, por lo que el resultado es < eliminado, se ingresa la cadena "Impar" y se ejecuta la instrucción de transferencia de control ; QUIT, así que el control se transfiere hacia adelante hasta pasar el fin del bloque. Si el último bit del número está apagado el resultado es cero (y el Test? es False), por lo que el control se transfiere hacia adelante hasta pasar la instrucción ; QUIT, el resultado es < eliminado, se ingresa la cadena "Par" y el bloque termina. Finalmente un mensaje es mostrado: algo como El número 10 es Par o bien El número 11 es Impar. Esta forma de ejecutar condicionalmente una de dos posibles rutas de ejecución se conoce como If-Then-Else.

Nótese que si un Test? es False y no hay ninguna instrucción REPEAT/QUIT adelante, el flujo de control saldrá del bloque de código sin alcanzar el ) delimitador END del bloque.

Por otro lado, si un Test? es True y no hay ninguna instrucción REPEAT/QUIT adelante, el flujo de control llegará al ) delimitador END del bloque.

Estas dos diferentes formas de salir de un bloque serán importantes después, cuando se describa la última regla operativa de la programación por bloques.

Tests Condicionales Básicos

Los tests condicionales son la llave que permite escribir programas útiles, ya que por medio de ellos es posible ensamblar bucles repetitivos (While-Do, Do-While, For-Next) y construcciones de ejecución condicional (If-Then-Else, Case/Switch), por lo que contar con un amplio repertorio de tests condicionales será un soporte que le permita al programador escribir programas simples y eficientes.

Los tests mas simples son algunos operadores enteros a los que se añade un signo de interrogación al final, los cuales trabajan como una combinación de operación y prueba: después de que la operación se completa se revisa el resultado; si es diferente de cero, el Test? es True.

Tests Condicionales Básicos
Operación Ejecuta
? Prueba entero
]n? Prueba registro de almacenamiento N
--? Decrementa
]--n? Decrementa registro de almacenamiento N
!? Boolean NOT
&? Bitwise AND
%? Residuo de división entera
getc? Toma un caracter de una cadena
movc? Mueve un caracter
 
 
 
 
 
 
 
 
 
 
 


Las nuevas operaciones ? (Test integer) y ]n? (Test storage register) prueban si el último número entero o el registro de almacenamiento indicado son diferentes de cero, respectivamente, por lo que permiten revisar el resultado de cualquier otra operación.

Con las operaciones --? o ]--n? es sencillo repetir un bucle un cierto número de veces. &? permite revisar bits individuales de un número entero. %? prueba si un número es múltiplo de otro.

Tanto getc? como movc? permiten procesar facilmente todos los caracteres de una cadena.

Para procesar los caracteres de una cadena, inserte el índice del caracter deseado (en base cero) y ejecute getc?: el caracter correspondiente será ingresado después del índice. Si este caracter es el delimitador cero insertado al final de la cadena, el Test? será False. Por ejemplo, el siguiente código cuenta el número de caracteres en una cadena, por lo que es equivalente a la función predefinida len:

printf /" "\"%s\" tiene %i caracteres\n"  "Una Cadena"  0 ( getc? < ++ : < ) OUT

Este ejemplo muestra el método base para procesar los caracteres de cualquier cadena. Este método puede modificarse para efectuar otras tareas similares; por ejemplo, convertir caracteres a mayúsculas o minúsculas, etc. Usted puede revisar algunos de estos métodos en el archivo printf Example 2 - ProcString.bat. Note que los métodos de conversión usan un test de comparación que será explicado en otra sección mas adelante.

Programa en Forma Didáctica (archivos Batch)

Quizás lo que ocasiona mas problemas al escribir un programa grande utilizando el esquema de programación por bloques es realizar un seguimiento de los parámetros de printf.exe (contenido del stack) después de cada operación. Para facilitar esta tarea usted puede escribir un programa en una forma diferente y mas clara que llamaremos Forma Didáctica. Las calculadoras HP utilizaban una "Programming Form" impresa en papel para este propósito en la cual el usuario escribía, a mano, el contenido del stack después de cada operación de un programa. El método que nosotros usaremos consiste en dividir las instrucciones del programa printf.exe en varias líneas de manera que cada una incluya un comentario descriptivo. Sin embargo, esto no lo podremos hacer adecuadamente en la línea de comandos de cmd.exe; necesitamos un lugar para almacenar un programa printf largo de manera que su creación y edición se facilite.

La forma usual de hacer esto es crear un archivo de comandos Batch (archivo de texto con extensión .bat). Este tipo de archivos contiene una serie de comandos que pueden ejecutarse en forma automática, como por ejemplo, nuestro comando printf.exe que incluya un programa RPN largo y avanzado. Para hacer esto, siga estos pasos:

  1. Cree un archivo de texto (haga click en el folder con el botón derecho del mouse y seleccione: Nuevo -> Documento de texto) y al darle nombre, cambie la extensión .txt por .bat; confirme que desea cambiar la extensión.
  2. Haga click en el nuevo archivo con el botón derecho del mouse y seleccione: Editar; esto abrirá el archivo Batch en el editor Notepad de Windows.
  3. Inserte la línea @echo off al principio del archivo.
  4. Después inserte el comando printf con la expresión completa del programa.
  5. Cambie todos los caracteres de porcentaje % por doble caracter %%.
  6. Cambie cada caracter especial con un "^escape" individual (como ^< ^> ^| ^&) por el uso del switch Quoted, como en /" < /", o /" > /", etcétera.
  7. Divida la expresión RPN de los parámetros de printf.exe en líneas individuales; haga esta división como usted quiera.
  8. En cada línea añada un comentario descriptivo encerrado entre los delimitadores /* y */ (Comment).
  9. Al final de cada línea inserte un caracter ^ (caret), excepto en la última línea. Tenga cuidado de no insertar ningún espacio después del caret.
  10. Si el switch /" Quoted se requiere, ciérrelo en cada línea y vuélvalo a abrir si una línea posterior también lo usa.

También debe encerrar entre comillas cualquier caracter especial colocado en los /*comentarios*/. Usted puede combinar un switch /" Quoted abierto con una sola comilla de cierre colocada al final de un comentario. Vea ejemplos en los archivos *.bat incluídos.

Cuando el programa esté completo, cierre el archivo y guárdelo. Para ejecutar el programa printf.exe, teclee el nombre del archivo Batch en la ventana de comandos de Windows (como anteriormente tecleaba el comando printf.exe). Si el nombre del archivo Batch contiene espacios, encierre el nombre entre comillas para ejecutarlo.

Por ejemplo, el programa antes mostrado que obtiene la longitud de una cadena:

printf /" "\"%s\" tiene %i caracteres\n"  "Una Cadena"  0 ( getc? < ++ : < ) OUT

... puede ser reescrito en forma didáctica así:

@echo off

printf "\"%%s\" tiene %%i caracteres\n" /* formato */ ^
   "Una Cadena"		/* "cadena"		*/  ^
   0			/* "cadena" 0		*/  ^
   (			/* WHILE getc?		*/  ^
      getc?		/*    "cadena" 0 C	*/  ^
      /" < /"		/*    "cadena" 0	*/  ^
      ++		/*    "cadena" 1 ,2,...	*/  ^
   :			/* REPEAT		*/  ^
			/*    "cadena" len 0	*/  ^
      /" < /"		/*    "cadena" len	*/  ^
   )			/* ENDWHILE		*/  ^
   OUT			/* muestra el resultado */

En esta forma didáctica usted puede revisar el contenido del stack antes de cada operación para estar seguro que es correcto. Como beneficio adicional, las estructuras de control (como While-Do, If-Then-Else, etc) están claramente marcadas y delimitadas. Esta forma aumenta en un alto grado la legibilidad de la programación por bloques y la acerca a los lenguajes de alto nivel. Usted debería usar esta forma para escribir sus propios programas. Este método permite programas de hasta 8190 caracteres de longitud. Las líneas en este ejemplo tienen alrededor de 40 caracteres cada una; esto significa que usted puede escribir un programa similar de aproximadamente 200 líneas de longitud.


Las funciones getc y putc operan en base a una dirección de inicio de una cadena más un subíndice de desplazamiento. Si estas funciones deben operar sobre cadenas diferentes, puede ser un poco engorroso mover los parámetros del stack para acomodarlos en la forma correcta para utilizar cada función. Por ejemplo, el siguiente programa duplica una cadena de caracteres en otra, por lo que hace una labor equivalente a la función 1 dups:

printf "Original:   \"%%s\"\nDuplicated: \"%%s\"\n"  ^
	"Any string"	/* "original"		*/  ^
	len		/* "original" len	*/  ^
	'X' /" <> /"	/* "orig" 'X' len	*/  ^
	dupc		/* "orig" "XX dup"	*/  ^
	0		/* "orig" "dup" 0	    index = 0	*/  ^
	(		/* WHILE getc?		*/  ^
	   }3		/*    "dup" 0 "orig"	*/  ^
        /" <> /"	/*    "dup" "orig" 0	*/  ^
	   getc?	/*    "dup" "orig" 0 'o'*/  ^
	   }4		/*    "orig" 0 'o' "dup"*/  ^
	   {3		/*    "orig" "dup" 0 'o'*/  ^
	   putc		/*    "orig" "oup" 0	*/  ^
	   ++		/*    "orig" "oup" 1,...    index++	*/  ^
	:		/* REPEAT		*/  ^
	)		/* ENDWHILE		*/  ^
			/* "orig" "dup" len	*/  ^
	OUT		/* show result		*/

Por otro lado, la función movc utiliza un puntero que apunta directamente a un cierto caracter dentro de una cadena, por lo que su uso es más sencillo que getc/putc las cuales requieren dos cantidades (la dirección de inicio y el desplazamiento) para tomar el mismo caracter. Por ejemplo:

printf "Original:   \"%%s\"\nDuplicated: \"%%s\"\n"  ^
	"Any string"	/* "original"		*/  ^
	]0		/* R0 = "original"	*/  ^
	len		/* "original" len	*/  ^
	'X' /" <> /"	/* "orig" 'X' len	*/  ^
	dupc		/* "orig" "XX dup"	*/  ^
	]1		/* R1 = "duplicated"	*/  ^
	(		/* WHILE movc0?		*/  ^
        /" <> /"	/*    "dup" "orig"	exchange *pointers*	*/  ^
	   movc0?	/*    "dup" "orig" 'o'		like getc?	*/  ^
	/" <> /"	/*    "dup" 'o' "orig"	*/  ^
	   ++		/*    "dup" 'o' "orig"++*/  ^
	   {3		/*    "orig"++ "dup" 'o'*/  ^
	   movc1	/*    "orig"++ "oup"		like putc	*/  ^
	   ++		/*    "orig"++ "oup"++	*/  ^
	:		/* REPEAT		*/  ^
	)		/* ENDWHILE		*/  ^
     /" <* /"		/* empty stack		*/  ^
	[0 [1		/* "orig" "dup"		*/  ^
	OUT		/* show result		*/

Mas áun, la variante movc2 puede tomar un caracter de una cadena y guardarlo directamente en otra cadena sin utilizar el stack, por lo que su uso es todavía mas sencillo y eficiente. Finalmente, las tres variantes de la función movc permiten auto-incrementar los punteros utilizados después de que el caracter correspondiente ha sido movido, lo que permite escribir programas mas cortos y rápidos. Se invita al lector a revisar los ejemplos de este punto dados en el archivo printf Example 3 - Index vs Pointer.bat


El estándar para ejecutar programas en la línea de comandos consiste en colocar después del comando una serie de parámetros, que son valores que toma el comando para trabajar con ellos. En el comando printf.exe los parámetros consisten en las operaciones del programa RPN. Sin embargo, es conveniente utilizar una forma de transferir los parámetros del archivo Batch (que contiene al programa printf.exe) hacia los datos RPN. Esto permitiría utilizar la combinación de archivo Batch/printf.exe en una forma estándar. Aquí está:

@echo off
set "parameters=%*"

printf	""			/* No format			*/ ^
	parameters		/* "par1 par2 ... parN"		*/ ^
	split			/* "par1" "par2" ... "parN" N	*/ ^
	(			/* WHILE			*/ ^
	   ?			/*    another param?		*/ ^
	   shift		/*    "par2" "parN" N-1 "par1"	*/ ^
	   ]1 /" < /"		/*    R1 = "par1" and drop it	*/ ^
	   FMT{ "Param %%i: %%s\n" /* Param format		*/ ^
	      ]++0		/*       inc R0 = param counter	*/ ^
	      [0 [1 OUT		/*       count par1  output	*/ ^
	   FMT}			/*    Close format		*/ ^
				/*    "par2" "parN" N-1		*/ ^
	:			/* REPEAT			*/ ^
	)			/* ENDWHILE			*/

El comando set "parameters=%*" toma los parámetros del archivo Batch y los almacena en la variable parameters. Al poner el nombre de esta variable en el programa RPN se ingresa su valor como una cadena de caracteres; es decir, se ingresan los parámetros del archivo Batch. La función split divide esta cadena en parámetros individuales y la función shift permite procesarlos uno por uno. La forma didáctica de este programa se encuentra en el archivo printf Example 4 - Parameters.bat

Note que un programa en forma didáctica corre mas lento que en forma estándar. Usted puede desarrollar y probar un programa en forma didáctica y después, cuando ya esté listo, convertirlo a forma estándar. GetStandardForm.bat es un archivo Batch auxiliar que ayuda en dicha conversión; usted sólo tiene que revisar que el código generado sea correcto en este par de puntos: si hay varios espacios juntos en una cadena serán reducidos a un sólo espacio, y quizás insertar un switch /" Quoted de cierre antes de alguna "<cadena>" que pudiera tener caracteres especiales (y abrirlo nuevamente después, si fuera necesario). Note que todas las líneas del programa deben tener un /*comentario*/ al final para que sean correctamente convertidas por GetStandardForm.bat

Tests Indicadores

Los "indicadores" (Flags en inglés) es una facilidad tomada de las calculadoras HP. Son variables que representan los valores True o False directamente. La operación SFn (Set Flag) fija un indicador en True. La operación CFn (Clear Flag) fija un indicador en False. El test Fn? (Flag?) pregunta por el valor de un indicador. El número n debe ser un dígito en el rango 0..9, por lo que hay 10 indicadores en esta versión de printf.exe. Los indicadores representan una opción mas simple que, por ejemplo, guardar un cero en un registro entero para indicar False y cambiarlo por un 1 para indicar True. Mas adelante se muestran algunos ejemplos de su uso.

Tests Numéricos Estándar

Los Tests Numéricos Estándar comparan el último número (que llamaremos "X", como en la calculadora HP) versus cero, o comparan el último número "X" versus el anterior "Y"; ambos tests con versiones para enteros y punto flotante.

Tests Numéricos Estándar
Tests de un operando Tests de dos operandos
Entero Flotante Condición Entero Flotante Condición
>0? GTR0? X mayor que 0 >? GTR? X mayor que Y
>=0? GEQ0? X mayor o igual a 0 >=? GEQ? X mayor o igual a Y
<0? LSS0? X menor que 0 <? LSS? X menor que Y
<=0? LEQ0? X menor o igual a 0 <=? LEQ? X menor o igual a Y
==0? EQU0? X igual a 0 ==? EQU? X igual a Y
!=0? NEQ0? X no igual a 0 !=? NEQ? X no igual a Y
 
 
 
 
 
 
 
 
 
 
 

Por ejemplo, para tomar el MAXimo de dos números enteros, use ( >? <> ) <, es decir, si el último número X es mayor, intercambie los números para dejar el menor en X. Después de eso, elimine el último número (que siempre será el menor) y conserve el mayor (máximo).

En forma similar, para tomar el MINimo de dos enteros: ( <? <> ) <.

Para tomar el MAXimo de dos números de punto flotante: ( GTR? XCHG ) DROP, y para tomar el MINimo: ( LSS? XCHG ) DROP.

Usted también puede usar =0? y =? para "X igual a 0" y "X igual a Y", o <>0? y <>? para "X no igual a 0" y "X no igual a Y" para números enteros.

Podemos aprovechar los comandos Batch para escribir varios ejemplos de operaciones printf.exe en forma sencilla, en lugar de escribirlos uno por uno. Por ejemplo, para probar en una sola línea todos los tests enteros sobre dos valores, podemos usar el comando Batch FOR:

for %t in (" >" ">=" " <" "<=" "==" "<>") do @printf "%i %~t %i:  %s\n"  /"  10 20 ( %~t? <> "TRUE" ; <> "false" ) OUT

20 > 10: TRUE
20 >= 10: TRUE
20 < 10: false
20 <= 10: false
20 == 10: false
20 <> 10: TRUE

Los mismos tests sobre números de punto flotante:

for %t in (gtr geq lss leq equ neq) do @printf "%.2f %~t %.2f:  %s\n"  10. 20. ( %~t? XCHG "TRUE" ; XCHG "false" ) OUT

20.00 gtr 10.00: TRUE
20.00 geq 10.00: TRUE
20.00 lss 10.00: false
20.00 leq 10.00: false
20.00 equ 10.00: false
20.00 neq 10.00: TRUE

Usted puede cambiar el orden de los valores o probar un sólo valor vs. cero para completar los tests con el resto de condiciones.

Ahora podemos completar la conversión a letras mayúsculas, la cual usa los tests enteros <=? y >=?:

printf "%s\n"  "Hola, Mundo!"  /"  0 ( getc? ( 'a' <=? < 'z' >=? < 32 - putc 'c' 'x' ) < < ++ : ) < <  OUT
á - 160
é - 130
í - 161
ó - 162
ú - 163
ü - 129
ñ - 164
¿ - 168
Á - 181
É - 144
Í - 214
Ó - 224
Ú - 233
Ü - 154
Ñ - 165
¡ - 173

Recuerde que los caracteres se manejan como números enteros, por lo que los tests sobre enteros también son tests sobre caracteres. La Forma Didáctica de esta conversión se encuentra en el archivo printf Example 2 - ProcString.bat

La conversión entre letras mayúsculas y minúsculas se basa en el hecho de que estos dos conjuntos de letras se encuentran separados por 32 posiciones en la tabla estándar de caracteres ASCII. Sin embargo, en el caso de letras en español con acento, esto no es así. Como referencia, a la derecha se muestra una tabla con las letras con acento y otros caracteres en español, y las posiciones que ocupan en la página de códigos 850. Por supuesto, si cmd.exe maneja una página de códigos diferente, estos caracteres podrían aparecer en otras posiciones o incluso ni siquiera aparecer.

Otro ejemplo de tests numéricos estándar es una tabla de multiplicar. Este ejemplo incluye dos bloques de código anidados:

printf /" " %3i"  0 ( ++ 11 ==? ; <  0 ( ++ 11 ==? < ; < >2 >2 * {3 OUT <3 : )  FMT{ "\n" OUT FMT}  < : )

La forma didáctica de este ejemplo se encuentra en el archivo printf Example 5 - Multiplication table.bat y se reproduce aquí para que usted pueda revisarla:

printf " %%3i"	/* formato		*/ ^
   0		/* i=0			*/ ^
   (		/* WHILE ++i != 11  	*/ ^
      ++	/*    1	 ,2,... 	*/ ^
      11 ==?	/*    i 11   equ?	*/ ^
   ;		/*           break 	*/ ^
   /" < /"	/*    i			*/ ^
      0		/*    i j=0		*/ ^
      (		/*    WHILE ++j != 11	*/ ^
         ++	/*       i 1  ,2,...	*/ ^
         11 ==? /*       i j 11   equ?	*/ ^
      /" < /"	/*       i j		*/ ^
      ;		/*                break	*/ ^
      /" < /"	/*       i j		*/ ^
      /" >2 /"	/*       i j i		*/ ^
      /" >2 /"	/*       i j i j	*/ ^
         *	/*       i j i*j	*/ ^
         {3	/*	 i*j i j	*/ ^
         OUT	/*             show i*j	*/ ^
      /" <3 /"	/*       i j		*/ ^
      :		/*    REPEAT		*/ ^
      )		/*    ENDWHILE		*/ ^
		/*    i 11		*/ ^
   FMT{ "\n"	/*    EndOfLine		*/ ^
   OUT		/*    nueva línea	*/ ^
   FMT}		/*    borra FMT		*/ ^
/" < /"	/*    i			*/ ^
   :		/* REPEAT		*/ ^
   )		/* ENDWHILE		*/

Ésta es la última vez que se reproduce una forma didáctica completa aquí. El paquete printf.exe incluye varios ejemplos en forma didáctica que se describen después. Usted puede revisar esos ejemplos con el editor de texto Notepad de Windows abriendo el archivo .bat correspondiente; para ello haga click sobre el archivo con el botón derecho del mouse y seleccione "Editar".


Otro ejemplo derivado del operador GETK consiste en leer una línea desde el teclado, es decir, simular la operación de la función IN. El programa tomará tecla por tecla y la revisará: si es un caracter ASCII normal (mayor o igual a 32), lo insertará en la cadena de caracteres que está creando y lo mostrará en la pantalla; si es la tecla "Suprimir" o "Borrar" (caracter ASCII 8 = BS) borrará el último caracter insertado en la cadena y también lo borrará de la pantalla; y si es la tecla "Entrar" (caracter ASCII 13 = CR) terminará entregando la línea leída y su longitud en caracteres (igual que IN). Aquí está:

printf /" "%%s\n" 80 ]2 '$' <> dupc ]1 0 ( GETK 13 ==? ; < 8 ==? ( < < ==0? ; -- FMT{ "\b \b" OUT FMT} ) : < 32 >? < < : < ]0 < [2 ==? < : < FMT{ "%%c" [0 OUT FMT} [0 putc ++ : ) FMT{ "\n" OUT FMT} < < 0 putc FMT{ "Line read: \"%%s\"\n" [1 OUT

Antes de empezar hay que dar el número máximo de caracteres que se pueden leer (igual que en IN) que en este ejemplo es 80, el cual se guarda en el registro 2 (con ]2). Primero se crea una cadena de esa longitud (con '$' <> dupc), se guarda su dirección ]1 en el registro 1 y se inicializa el índice/contador de caracteres de la cadena con 0.

El proceso consiste en un bucle repetitivo While ( que contiene las siguientes partes: se lee una tecla GETK, se pregunta si es "Entrar" 13 ==? y se sale del bucle ; en caso afirmativo. En caso contrario, se borra el "Entrar" < y se pregunta si es "Suprimir" 8 ==?. SI lo es, entonces (: se borran los dos ochos < < dejando como último número al índice de caracteres y entonces se pregunta si se ingresó algún caracter ==0?, cancelando ; el SI en caso negativo; de lo contrario se decrementa -- el índice/contador de caracteres y se borra el último caracter en la pantalla (mostrando un BS, un espacio y otro BS FMT{ "\b \b" OUT FMT}. Aquí termina ) el "SI es Suprimir" y se regresa : al bucle While. ¿Verdad que la programación por bloques es muy entretenida? ;).

En la última parte se borra < el 8 anterior, se pregunta si el caracter 32 >? es menor a 32 (caracter de control) y en caso afirmativo se borra el 32 y el caracter < < y se regresa : al bucle While. De lo contrario es un caracter normal, así que se elimina < el 32, el caracter se guarda en el registro 0 y se elimina ]0 <. Entonces se recupera el número máximo de caracteres [2 y si el índice ya llegó a este límite ==?, se borra el límite < y regresa : al While; en caso contrario se borra el límite <, se muestra el caracter FMT{ "%%c" [0 OUT FMT}, se almacena el caracter en la cadena [0 putc, se incrementa el índice ++ y regresa : al bucle While, el cual termina )

Al salir del While debido a la tecla "Entrar" se avanza a una nueva línea FMT{ "\n" OUT FMT}, se borran ambos números 13 < <, se inserta un 0 como delimitador de la cadena y se guarda putc en su lugar. Finalmente, se muestra el resultado: FMT{ "Line read: \"%%s\"\n" [1 OUT.

Este método básico de lectura de una línea se puede modificar para obtener otras lecturas similares, pero con alguna característica adicional. Por ejemplo, para leer una contraseña basta con cambiar el caracter que se muestra en FMT{ "%%c" [0 OUT FMT} por un asterisco: FMT{ "%%c" '*' OUT FMT}. También se puede convertir automáticamente el caracter leído a mayúsculas (usando el método visto antes), o bien restringir las teclas admitidas a sólo digitos cambiando la restricción de no ser "menores a 32" por "estar entre 48 y 57" (códigos ASCII del "0" y el "9", respectivamente), etcétera. La forma didáctica de estos programas se encuentra en el archivo printf Example 6 - ReadLine.bat

Operaciones Avanzadas de Entrada/Salida

Las operaciones que se describen en esta sección son las formas Test? de las estándar GETK (Get Key) e IN (Input line). También hay algunas formas avanzadas de las operaciones IN y OUT que permiten leer y escribir archivos de texto, así como la nueva operación CMD que ejecuta un comando de cmd.exe. Para completar, las operaciones estándar de entrada/salida descritas anteriormente también se incluyen en la siguiente tabla.

Para procesar un archivo de texto primero se debe abrir el archivo, lo que implica tomar el archivo por su nombre y conectarlo a un número, llamado handle ("manejador"), el cual se usará en las operaciones posteriores sobre ese archivo. Si se escribieron nuevos datos en el archivo, el handle se debe cerrar (desconectar del archivo) antes de terminar el programa. En las siguientes funciones el valor N identifica al handle, que debe ser un dígito entre 0 y 9.

Operaciones Avanzadas de Entrada/Salida
Operación Función Descripción
Control del Cursor CURS
CURS0
CURS1
CURS2
Toma la posición del cursor en Y,X
Apaga el cursor
Enciende el cursor
Fija la posición del cursor desde R0,R1
Salida estándar OUT Manda salida con formato a la pantalla: "formato" dato1 dato2 ...
Inicia formato FMT{
FMT{:r
Inicia un nuevo formato y datos para OUT, hasta terminarlo con FMT}
y fija el atributo de color para dicho texto del registro entero R
Toma una tecla GETK
GETK?
GETK?:r
Espera por una tecla
No espera: si no hay una tecla lista, es False
Espera el siguiente intervalo (time-slice) de milisegundos dado en registro entero R
Entrada estándar maxlen IN Lee una línea del teclado
Ejecuta un comando
y abre un pipe
"comando" CMD Ejecuta el "comando" de cmd.exe y abre un pipe de Stdin para leer su salida
Entrada y Test? maxlen IN? Lee una línea; en EndOfFile cierra el último pipe de Stdin abierto por CMD
Abre archivo de entrada "filename" IN{?:n
"filename" IN{+?:n
Abre el archivo para input en el handle N
Abre el archivo para update en ambos input/output handles N
Entrada del handle maxlen IN?:n Lee una línea desde el handle N; en EndOfFile cierra el handle
Abre archivo de salida "filename" OUT{:n
"filename" OUT{+:n
Crea y abre el archivo para output en el handle N
Abre el archivo para append (output en EndOfFile) en el handle N
Salida al handle OUT:n Manda salida con formato al handle N
Cierra handle de salida OUT}:n
OUT}+:n
Cierra el handle de salida N
Fija el tamaño del archivo al FP actual y cierra el handle N
Mueve puntero
del archivo
SEEK#:n:p Mueve el puntero FP del handle N a la posición P desde el origen #
Cambia el directorio "pathname" CD Cambia el directorio actual; regresa 0 si OK o -1 si Error.

Las operaciones de entrada GETK e IN antes vistas también pueden trabajar como tests si un signo de interrogación es añadido al final, como es usual. La operación IN? lee una línea desde el teclado (o desde un archivo de entrada redirigido) e ingresa en el stack la cadena leída y su longitud. Cuando se alcanza el "FinalDelArchivo" (EndOfFile) del archivo redirigido, sólo un -1 es ingresado y el Test? es False. En cualquier caso, el valor maxlen es eliminado. Por ejemplo:

< textFile.txt printf /" "%s\n"  ( 1000 IN? OUT <* : )

Este método básico para procesar las líneas de un archivo puede enriquecerse de varias formas diferentes. Por ejemplo, para numerar las líneas:

< textFile.txt printf /" "%i:%s\n"  ( ]++0 [0 1000 IN? OUT <* : )

Tome en cuenta que el uso de la operación <* (Drop All) en estos ejemplos permite reutilizar la misma área de datos para leer todas las líneas del archivo; si esto no se hiciera, el área de printf.exe reservada para cadenas (con 10 Kb de espacio) podría excederse por un archivo grande. La forma didáctica de estos ejemplos se encuentra en el archivo printf Example 7 - ReadFile.bat

La operación GETK (Get Key) estándar espera que una tecla sea presionada para regresar su valor. Si se convierte en un test de esta forma: GETK?, entonces la operación no espera sino que regresa de inmediato: si una tecla fue presionada antes, GETK? ingresa su valor y el Test? es True; de lo contrario no se ingresa nada y el Test? es False. Este comportamiento puede ser usado para interrumpir un proceso cíclico al presionar una tecla. El último ejemplo anterior puede ser modificado para detenerse cuando se oprima una tecla:

< textFile.txt printf /" "%i:%s\n"  ( ]++0 [0 1000 IN? OUT ( GETK? GETK ) <* : )

Después de que una línea se muestra, la parte ( GETK? GETK ) revisa si se oprimió una tecla; en caso afirmativo, se espera por otra tecla antes de continuar con el ciclo de mostrado. Pruebe este programa en un archivo muy grande. Este es un método muy simple para insertar una pausa en cualquier proceso cíclico.

Otro uso interesante de esta característica es controlar una animación. Un programa puede mostrar una figura en la pantalla que se mueva a intervalos regulares. Cuando el programa detecte una tecla mediante GETK?, puede alterar el movimiento de la figura. ¡Esta es la base para escribir programas de juegos animados!

Con el objeto de facilitar la escritura de este tipo de programas, la operación GETK? también permite fijar un intervalo de tiempo de retraso. Cada vez que la operación GETK?:r se ejecute, ésta esperará hasta el siguiente intervalo de tiempo del reloj (time slice) del número de milisegundos almacenado en el registro entero de almacenamiento dado por R. Esta función hace que sea muy fácil escribir un programa que mueva una animación a una velocidad determinada y cambiar dicha velocidad de animación.

El siguiente programa es un ejemplo muy simple de una animación cuya velocidad se controla mediante la teclas Flecha-Izquierda y Flecha-Derecha; la animación termina al presionar la tecla Enter:

printf /" "\b %c"  SF1  100 ]1 <  219 ( OUT  ( GETK?:1  ( -75 ==? 10 ]+1 < ) <  ( -77 ==? 10 ]-1 < ) <  ( 13 ==? CF1 ) <  <  )  F1? : ) 

El formato "\b %c" regresa el cursor una posición, borra el último caracter mostrado y muestra un nuevo caracter, el cual es el caracter ASCII 219 que aparece como un bloque en las páginas de código 437 y 850. Esto crea la ilusión de que el bloque se mueve de izquierda a derecha.

La operación GETK?:1 retrasa la ejecución el número de milisegundos almacenado en el registro de almacenamiento #1, el cual fue inicializado al principio con 100 milisegundos.

La Flecha-Izquierda (-75) incrementa el retraso en 10 milisegundos, lo que hace al proceso mas lento. La Flecha-Derecha (-77) hace lo opuesto. La tecla Enter (13) termina el proceso.

Cuando una tecla es detectada, se ingresa un -75 para probar si es la Flecha-Izquierda; si es así, se ingresa un 10 para incrementar el registro entero 1 y después el 10 es eliminado; en cualquier caso el -75 es eliminado. Lo mismo se hace con un -77 para probar la tecla Flecha-Derecha. La tecla Enter no inserta ningún valor adicional, por lo que sólo el 13 es eliminado. El último Drop es para eliminar la tecla misma.

Note que lo único que hace la tecla Enter es apagar (Clear Flag) el indicador 1 y que todo el proceso se repite mientras el indicador 1 esté prendido. El uso de un indicador de esta manera es un método muy simple para controlar un ciclo repetitivo que no introduce ningún valor en el stack.

La forma didáctica de este programa se encuentra en el archivo printf Example 8 - Animation.bat


La operación CMD es una potente función diseñada para ejecutar un comando de cmd.exe y procesar sus líneas de salida. Para hacerlo, esta función ejecuta en forma asíncrona una copia del procesador de comandos cmd.exe y redirige su salida estándar Stdout a la entrada estándar Stdin de printf.exe (tubería o pipe), por lo que dicha salida se puede leer a través de la operación IN?. Por ejemplo:

printf /" "%s\n" "DIR /B *.TXT" CMD < ( 1000 IN? OUT <* : )

Una característica interesante de la función CMD es que su operación puede ser anidada varios niveles. Cuando una función CMD está activa y sus líneas se están leyendo con la operación IN?, usted puede activar otra función CMD y leer sus líneas mediante operaciones IN? anidadas. Cuando se llega al EndOfFile de la segunda función CMD, la segunda tubería se cierra y la operación IN? regresa a seguir leyendo las líneas de la primer función CMD. Podemos aprovechar esta característica en varias formas diferentes.

Por ejemplo, podemos escribir un programa que procese los nombres de todos los archivos de texto mediante la operación "DIR /B *.TXT" CMD y entonces, por cada archivo encontrado, procese las líneas de dicho archivo mediante una operación "TYPE filename" CMD anidada. Aquí está:

printf /" "\n\nFile: %s\n" "DIR /B *.TXT" CMD < ( 100 IN? OUT < ]1 GETK FMT{ "%s\n" "TYPE \"" [1 "\"" 3 "" join1 CMD < ( 1000 IN? OUT <* : ) FMT} <* : )

Note que el segmento "TYPE \"" [1 "\"" 3 "" join1 en la mitad del programa es el encargado de armar el comando TYPE "filename" tomando el nombre de archivo leído del comando "DIR /B *.TXT" anterior. La forma didáctica de este programa está en el archivo printf Example 9 - Type files.bat

Cuando IN? lee un archivo (redirigido) y encuentra el EndOfFile, regresa un -1; sin embargo, cuando IN? lee la salida de la función CMD mediante la tubería y encuentra el EndOfFile, regresará el valor ERRORLEVEL de terminación del cmd.exe.

Si el parámetro de la función CMD es "CMD" ("CMD" CMD), entonces se iniciará una nueva sesión interactiva de comandos de cmd.exe; para cerrarla, teclee EXIT.


La forma "filename" IN{?:n de la función IN abre el archivo dado para lectura (input), por lo que las operaciones IN?:n posteriores (con el mismo número N de handle) leerán líneas de dicho archivo. Cuando el EndOfFile de ese archivo se alcanza, su handle se cierra y el Test? IN? es False. Si el archivo no existe, la operación/test IN{?:n de apertura será False.

El siguiente es un ejemplo simple que muestra el contenido de un archivo en forma similar al comando TYPE:

printf /" "%s\n" ( "filename.txt" IN{?:1 < ( 1000 IN?:1 OUT <* : ) ; < "File not found" OUT )

La forma "filename" IN{+?:n de la función IN abre el archivo tanto para lectura como para escritura (update access). En este caso el mismo número de handle debe ser usado en ambas operaciones, entrada y salida: IN?:n y OUT:n. Si usted desea seguir procesando el archivo después de que una operación de lectura alcanzó el EndOfFile, entonces debe leerlo mediante IN:n que no cierra el archivo; en este caso el EndOfFile puede detectarse por el valor -1 regresado. El archivo abierto para update debe cerrarse con la operación OUT}:n.

La forma "filename" OUT{:n de la función OUT crea el archivo dado (destruyendo su contenido anterior, si el archivo existe) y lo abre para escritura (output), por lo que las operaciones OUT:n posteriores (con el mismo número N de handle) mandarán salida con formato a dicho archivo. La forma OUT{+:n preserva el contenido del archivo si existe, pero en este caso la salida se añadirá al final del archivo (append access). Ambas formas de apertura de salida deben cerrarse con la operación OUT}:n, y también un archivo abierto para update con la operación IN{+?:n. Si un archivo de salida no puede ser abierto o creado por cualquier razón, el programa será abortado.

El siguiente es un ejemplo simple que copia el contenido de un archivo de texto en otro (en forma similar al comando COPY):

printf /" "%s\n" ( "oldfile.txt" IN{?:3 < "newfile.txt" OUT{:4 < ( 1000 IN?:3 OUT:4 <* : ) OUT}:4 < "File copied" ; < "File not found" ) OUT 


Formas de la función SEEK#
Origen Entrada Salida
Del principio SEEK0 SEEK4
Desde actual SEEK1 SEEK5
Del final SEEK2 SEEK6

La función SEEK#:n:p mueve el puntero del archivo (File Pointer o FP) del handle N de entrada (#=0,1,2) o salida (#=4,5,6) a partir del origen indicado por el dígito # (0/4 = del inicio del archivo, 1/5 = de la posición actual, 2/6 = del final del archivo) más la posición (offset) dada por el registro entero de almacenamiento número P. La tabla del lado derecho muestra las seis formas de la función SEEK#.

El comando FINDSTR /O "^" filename.txt de cmd.exe muestra la posición de inicio de todas las líneas del archivo dado. Un programa printf.exe puede tomar estos números y usar SEEK0:n:p para leer las líneas de un archivo de texto en cualquier orden deseado. Si un archivo tuviera registros (líneas) de longitud fija (la misma longitud en todas sus líneas), entonces éstas se podrían leer en cualquier orden sin necesidad de tomar previamente sus posiciones de inicio, sino tan sólo calculando su: posición = NúmeroDeLínea * LongitudDeLínea. Si el archivo es abierto con IN{+?:n update access, entonces se podrían actualizar tan sólo unas cuantas líneas e inmediatamente cerrar el archivo. Si el archivo es grande, este método puede ahorrar mucho tiempo de proceso (porque evita copiar todo el contenido del archivo que no fue modificado, como es usual hacerlo en archivos Batch).

Si la posición P no se proporciona, entonces el FP se mueve al punto indicado solamente por # (principio, actual o final); en este caso la posición final del FP se ingresa en el stack como un número entero. Por ejemplo, para tomar el tamaño de un archivo en bytes:

printf "El tamaño del archivo \"%s\" es: %u bytes\n" "filename.txt" IN{?:1 SEEK2:1

Usted puede usar este método para tomar la posición de inicio de todas las líneas del archivo (en lugar del comando FINDSTR /O ...) leyendo cada línea y almacenando la posición actual del FP mediante SEEK1:n. Puede consultar una descripción mas técnica de las capacidades de SEEK en esta página.


La forma OUT}+:n (Set size) de la función OUT cambia el tamaño del archivo a la posición actual del FP y cierra el archivo. Si usted crea/abre un archivo de salida, mueve el FP después del EndOfFile y cierra el archivo con la opción Set size, el tamaño del archivo aumentará a tal posición. Esto permite crear un archivo grande en forma muy sencilla. Por ejemplo, para crear un archivo con cien mil bytes:

printf "Archivo \"%s\" creado con %u bytes\n" "filename.txt" OUT{:3 100000 ]5 SEEK4:3:5 OUT}+:3

El nuevo espacio en el archivo se llena con bytes con cero binario. Sin embargo, si usted abre el archivo con el editor Windows Notepad, los ceros se convierten en espacios en blanco.

Si usted mueve el FP de un archivo de salida antes del EndOfFile y cierra el archivo con la opción Set size, el contenido del archivo se truncará en tal posición (eliminando cualquier dato que hubiera en el área truncada). Por ejemplo, para truncar un archivo grande a sólo 10 Kb:

printf "Archivo \"%s\" truncado en %u bytes\n" "filename.txt" OUT{+:8 10 1024 * ]2 SEEK4:8:2 OUT}+:8

También puede abrir el archivo con IN{+?:n (update access) para truncarlo (conservando el contenido anterior). Puede leer mas detalles técnicos sobre la opción Set size en esta página.

Tome en cuenta que todos estos handles de archivos son independientes unos de otros. Un programa printf.exe puede abrir un total de 10 archivos de entrada más 10 archivos de salida mediante los handles 0..9, más 20 pipes de entrada anidados del comando CMD.

Bloques de Código con Nombre

Usted puede darle un nombre a un bloque de código externo (no anidado). Hacer esto permite ingresar (definir) el bloque sólo una vez y utilizarlo después varias veces. Esta facilidad se llama subrutina o procedimiento en los lenguajes de programación convencionales; nosotros también usaremos estos términos aquí.

El nombre de la subrutina se compone de hasta 4 caracteres (letras o dígitos solamente, comenzando con letra, sin distinguir mayúsculas de minúsculas) que se coloca antes del delimitador ( BEGIN del bloque sin ningún espacio entre ellos. Puede insertar más caracteres para facilitar la legibilidad, pero sólo los primeros cuatro componen el nombre.

La definición de bloques con nombre debe hacerse antes de la cadena formato. Cuando una subrutina se define, sus operaciones no se ejecutan sino que se almacenan. Para después ejecutar o invocar o llamar a la subrutina, simplemente escriba su nombre en la misma forma que cualquier operación predefinida. Por ejemplo:

printf  Uno( "Primera" )  Dos( "Segunda" )  /" "Dos cadenas: %s - %s\n"  Uno Dos OUT < <  Uno Uno OUT < <  Dos Dos OUT

Este ejemplo muestra:

Dos cadenas: Primera - Segunda
Dos cadenas: Primera - Primera
Dos cadenas: Segunda - Segunda

Una subrutina no puede tener el mismo nombre que una operación predefinida. Si usted no sigue esta regla, la subrutina será ignorada y ese nombre siempre ejecutará la función predefinida. Tampoco se pueden definir dos subrutinas con el mismo nombre. Recuerde que el nombre de la subrutina son sólo los cuatro primeros caracteres.

Un primer uso de esta característica es dar un nombre a las constantes utilizadas con frecuencia. Cuando se usan números directamente puede haber dudas sobre su significado. Si la constante tiene un nombre descriptivo, su uso es mucho más claro. Por ejemplo:

printf  CR( 13 )  LF( 10 )  TAB( 9 )  BS( 8 )  SPACE( 32 ) ...

De esta forma se puede diferenciar un 10 utilizado como "número diez" de un 10 que representa un caracter ASCII LineFeed. En el archivo printf - GetKey codes.txt se incluye la definición, como bloques de código con nombre, de todas las teclas especiales que entrega la operación GETK.

De la misma manera, puede dar un nombre a una constante de especificación que se usa varias veces en su programa. Cuando desee cambiar esta especificación, basta con cambiar su valor en un solo lugar (la definición de la subrutina) y el cambio se reflejará en todos los lugares donde se use dicha constante.

Otro uso de las subrutinas es definir segmentos de código de uso común que proporcionen características útiles no predefinidas, o dividir un programa grande en unidades lógicas para aumentar la legibilidad del programa. Un ejemplo simple:

printf  Hipot( SQR XCHG SQR ADD SQRT )  "La hipotenusa de 3. y 4. es: %.2f\n" 3. 4. Hipot OUT

Todos los ejemplos de programas que se muestran en este documento pueden definirse como subrutinas y agruparlos en un archivo que formaría una biblioteca de funciones, de manera que usted sólo necesitaría incluír en sus programas las subrutinas deseadas listas para usarse. Además, los bloques de código con nombre permiten llamadas recursivas. En seguida se muestran un par de ejemplos de subrutinas recursivas.

Uno de los ejemplos clásicos de recursividad en computación es el cálculo del factorial de un número entero N (indicado por N!) que es igual al producto de todos los enteros desde N y bajando hasta 1. Por ejemplo: 5! = 5 x 4 x 3 x 2 x 1 que es igual a 120, o 4! = 4 x 3 x 2 x 1 que es 24. Revisando estas fórmulas es fácil ver que 5! = 5 x 4! y, en general: N! = N x (N - 1)!; ésta es una definición recursiva de una fórmula en términos de ella misma. Note que 1! = 1 lo que marca el "fin de los números", aunque, por definición, se toma 0! = 1.

La fórmula recursiva anterior está incompleta porque no especifica el final de la recursión. Una definición mas completa sería ésta: N! = IF N==0 THEN 1 ELSE Nx(N-1)! (en el lenguaje de programación C: fact(int n) { return( (n==0) ? 1 : n*fact(n-1) ) }).

La manera de implementar esta fórmula recursiva en la programación por bloques de printf.exe consiste en revisar si N es 0, en cuyo caso el resultado es 1 (cambiar el 0 por 1 mediante un incremento y terminar); de lo contrario, multiplicar N por una invocación recursiva de la misma subrutina sobre N-1, es decir: duplicar N, decrementar el duplicado (N-1), obtener el factorial (N-1)! y multiplicar N x (N-1)!

printf /"  Fact( ==0? ++ ; > -- Fact * )  "Factorial de %i es %i\n" 5 > Fact OUT

Tenga en cuenta que este ejemplo usa números enteros, por lo que puede calcular factoriales hasta el número 12. Además, tenga en cuenta que este método no se puede traducir a números de punto flotante porque el stack de la FPU sólo puede contener 8 números.


Veamos ahora un ejemplo recursivo diferente. Como se dijo antes, la función CMD puede ser anidada en varias ejecuciones paralelas simultáneas. Podemos aprovechar esta característica para escribir una subrutina recursiva que procese el árbol completo de un directorio. Para ello, la subrutina sólo necesita mostrar los subdirectorios del directorio actual. Para mostrar el árbol completo, sólo debe invocarse a sí misma con cada uno de los subdirectorios encontrados. Aquí está:

printf /" TREE( "DIR /A:D /B" CMD FMT{ "%.*s%s\n" ( 80 IN? < [0 [2 }3 OUT CD 3 ]+0 <*  TREE  ".." CD 3 ]-0 <* : ) FMT} )  "" ' ' 30 dupc ]2 <  TREE

La subrutina TREE( comienza procesando los subdirectorios del folder actual mediante la función "DIR /B /A:D" CMD y enseguida prepara el formato FMT{ "%.*s%s\n" que se usará. Entonces, por cada subdirectorio leído con ( 80 IN? < , lo muestra en la pantalla con [0 [2 }3 OUT (más sobre esto adelante), cambia el folder actual con CD para entrar a ese subdirectorio (le suma 3 al registro 0 con 3 ]+0 <*) y procesa ese subdirectorio de la misma forma mediante una invocación recursiva TREE. Cuando los subdirectorios de un CMD terminen (es decir, cuando una invocación recursiva regrese), regresa el directorio actual al nivel previo de subdirectorios con ".." CD (le resta 3 al registro 0 con 3 ]-0 <* ) y el proceso continúa con el siguiente folder : del CMD anterior. Cuando el primer CMD termine ), se cierran el formato y el bloque de la subrutina FMT} ).

El "programa principal" no usa ningún formato ""; sólo crea una cadena con 30 espacios en blanco ' ' 30 dupc que almacena ]2 < en el registro 2 e invoca por primera vez a TREE. Esta cadena de espacios se utiliza para mostrar un margen de justificación mediante el formato FMT{ "%.*s%s\n" (según se describe aquí) que aumenta en 3 espacios por cada nivel de subdirectorios encontrado. El registro 0 contiene el valor del margen y los 3 elementos (margen, espacios y subdirectorio) se muestran correctamente con [0 [2 }3 OUT.

Este ejemplo puede enriquecerse mostrando también los archivos de cada directorio, o mostrando un margen de justificación formado por líneas que marquen con mayor claridad el anidamiento de los subdirectorios (como en el comando TREE de cmd.exe). La forma didáctica de este programa, y la modificación que también muestra los archivos, se encuentran en el archivo printf Example A - MyTree.bat

Como es usual en la programación por bloques, un bloque de código con nombre también puede operar como un Test? si se añade un signo de interrogación a su nombre cuando el bloque es invocado; recuerde que usted no puede incluír ningún caracter especial en la definición de un bloque de código con nombre. El método que se usa para que esta subrutina devuelva un valor True o False se describe en la siguiente sección.


Nota del Autor: Antes de pasar a la última sección de este documento quisiera mencionar un punto importante. Si usted no sabía programación y ha completado con éxito el estudio de este manual hasta este punto, debería darse cuenta que ahora usted cuenta con las bases que le permitirán estudiar adecuadamente cualquier lenguaje de programación moderno. La aparente sencillez con la que se han presentado estos temas no implica que los conceptos en sí sean sencillos; lo que es sencillo es la forma de presentarlos utilizando el esquema de printf.exe como una excelente herramienta educativa.

Si usted ya tiene experiencia en programación quiero hacerle notar lo sencillo que es escribir un código pequeño en la programación por bloques. Con muy pocos elementos se pueden obtener resultados importantes, lo que es resultado del diseño de printf.exe y de sus operaciones predefinidas. Una vez superados los problemas iniciales de la notación poco usual y algo críptica de printf.exe, éste puede ser utilizado para escribir programas que resuelvan muchos de los problemas cotidianos a los que se enfrentan los usuarios de computadoras personales. Quiero invitarlo a que siga utilizando printf.exe en este sentido.

¿Ya se registró como usuario de printf.exe? Le sugiero que le eche un vistazo a la página de registro ahora mismo.

Operadores Booleanos

Tópico avanzado: En esta sección se describe la última regla operativa de la programación por bloques.

Usualmente cuando un bloque anidado termina, el flujo de control continúa normalmente con la siguiente operación después del bloque. Sin embargo, un bloque anidado también puede operar como un Test? de la misma manera que una combinación Operador/Test. La forma de habilitar esta característica es la misma antes vista: simplemente añada un signo de interrogación al delimitador ) END del bloque. Por ejemplo:

printf "%s  %s\n" /"  ( "Bloque normal"  ( "Este bloque opera como un Test" ; )?  < "Otro" ) OUT

¡Simple! ¿Verdad? Pero en este caso, ¿qué resultado, True o False, tendrá el bloque anidado? La regla es realmente sencilla:

Si el flujo de control llega al delimitador )? END, el bloque anidado será False; de lo contrario, el bloque será True.

Esto significa que el bloque anidado será True cuando una instrucción ; QUIT se ejecute en él, o cuando un Test? dentro del bloque sea False y no haya ninguna instrucción REPEAT/QUIT adelante. En ambos casos el control no llega al delimitador )? END, sino que lo brinca. Usted puede revisar visualmente esta regla en la imagen del esquema de reglas visto al principio de este capítulo.

En el ejemplo anterior, el bloque ( "Este bloque opera como un Test" ; )? termina por una instrucción ; QUIT, por lo que el bloque anidado será True. En este caso la ejecución continúa, el operador < (Drop) elimina la última cadena, se ingresa la nueva cadena "Otro" y finalmente la instrucción OUT muestra: Bloque normal Otro.

Si borramos la instrucción QUIT del bloque anidado: ( "Este bloque opera como un Test" )?, entonces el delimitador END del bloque será alcanzado, por lo que el bloque anidado será False. Ahora el flujo de control se transfiere hacia adelante hasta salir del primer bloque, la instrucción OUT se ejecuta y muestra: Bloque normal  Este bloque opera como un Test.

Un ejemplo un poco mas grande podría ser el programa de animación visto antes, pero modificado para que use bloques de código condicionales en lugar de una Flag:

rem Programa original de animación basado en la Flag F1:
printf /" "\b %c"  SF1  100 ]1 <  219 ( OUT  ( GETK?:1  ( -75 ==? 10 ]+1 < ) <  ( -77 ==? 10 ]-1 < ) <  ( 13 ==? CF1 ) <  <  )  F1? : ) 

rem Programa modificado que usa bloques de código condicionales en lugar de la Flag:
printf /" "\b %c"       100 ]1 <  219 ( OUT  ( GETK?:1  ( -75 ==? 10 ]+1 < ) <  ( -77 ==? 10 ]-1 < ) <  ( 13 ==? ; <  <  )?   )?    : )

En el programa original todo el proceso se repite mientras la Flag 1 sea True, y la acción de la tecla Enter (13) es simplemente apagar dicha Flag. Revisemos la parte final del programa modificado: ( 13 ==? ; < < )? )? : ) Si la tecla no es Enter, se ejecutan dos Drops y se llega al END del primer bloque condicional anidado, por lo que éste termina con un resultado False. Este resultado causa que el control brinque después del final del segundo bloque, por lo que el : REPEAT se ejecuta y todo el proceso se repite otra vez. Cuando la tecla es Enter, el ; QUIT se ejecuta y el control brinca después del END del bloque, por lo que se llega al END del segundo bloque condicional anidado y su resultado es False. Este resultado causa que el control brinque después de la última instrucción : REPEAT y el proceso termina.

Este mismo mecanismo se aplica a un bloque de código con nombre (subrutina). Si la subrutina se invoca como un Test? (con un signo de interrogación añadido a su nombre), entonces su resultado True-False dependerá de la manera en que el bloque con nombre termine: si la ejecución llega al delimitador ) END del bloque con nombre, la subrutina NOMBRE? será False; en caso contrario, será True.

Si analizamos esto nos daremos cuenta que este mecanismo entrega el resultado opuesto de un Test? colocado dentro de un (bloque)? cuando no hay ninguna instrucción QUIT colocada después del Test?: si el Test? es True el (bloque)? es False, si el Test? es False el (bloque)? es True. Este esquema nos permite ensamblar los operadores Booleanos estándar AND, OR y NOT de una forma muy sencilla.


Operador NOT: ( Test? )?

Si Test? es True la ejecución continúa y llega al delimitador END, por lo que el resultado es False.

Si Test? es False el control brinca hacia adelante hasta pasar el delimitador END, por lo que el resultado es True.


Operador AND: ( Test1? Test2? ; )?

Si Test1? es True la ejecución continúa:
- Si Test2? es True la ejecución continúa, por lo que se ejecuta el QUIT y el resultado del bloque es True.
- Si Test2? es False el control brinca hacia adelante hasta pasar el QUIT, por lo que llega al delimitador END y el resultado del bloque es False.

Si Test1 es False el control brinca hacia adelante hasta pasar el QUIT, por lo que llega al delimitador END y el resultado del bloque es False.

En otras palabras: sólo cuando todas las condiciones son True, el resultado del bloque "AND" será True. Este mecanismo puede extenderse fácilmente a más de dos tests/condiciones. El bloque "AND" evalúa los Tests? en serie y se detiene en el primero que sea falso.


Operador OR: ( Test1? ; Test2? ; )?

Si Test1? es True la ejecución continúa, el primer QUIT se ejecuta y el resultado del bloque es True. Si Test1? es False el control brinca hacia adelante hasta pasar el primer QUIT.

Si Test2? es True la ejecución continúa, el segundo QUIT se ejecuta y el resultado del bloque es True. Si Test2? es False el control brinca hacia adelante hasta pasar el segundo QUIT, por lo que llega al delimitador END y el resultado del bloque es False.

En otras palabras: si cualquier condición es True, el resultado del bloque "OR" será True; sólo cuando todas las condiciones sean False, el resultado del "OR" será False. Este mecanismo puede extenderse fácilmente a más de dos tests/condiciones. El bloque "OR" evalúa los Tests? en serie y se detiene en el primero que sea verdadero.


Note que los operadores Booleanos AND y OR ensamblados de esta manera tienen una evaluación mínima implícita: tan pronto como el resultado del bloque se conoce (False para AND, True para OR), el resto de condiciones en el bloque no son evaluadas.


En el caso del operador Booleano XOR no hay una forma simple de ensamblarlo como para AND y OR. El resultado (tabla de verdad) del operador XOR sobre dos condiciones es:

Test1 Test2 Test1 XOR Test2
True True False
True False True
False True True
False False False

Este resultado puede obtenerse por medio de la siguiente expresión que sólo incluye los operadores AND, OR y NOT: (Test1 OR Test2) AND NOT (Test1 AND Test2). La traducción de esta expresión a código de programación por bloques sería ésta:

(  (  Test1? ; Test2? ; )?  ( (  Test1? Test2? ;  )? )?  ;  )?
 \  \__Test1_OR_Test2__/     \ \_Test1_AND_Test2_/  /   /  /
  \                           \_NOT_el_AND_previo__/   /  /
   \                                                  /  /
    \__El_último_QUIT_completa_el_AND_de_estos_bloques__/

De igual manera, no es fácil aplicar el operador XOR a mas de dos condiciones. Si extendemos el concepto del XOR a mas de dos operandos, el resultado será True cuando precisamente una de todas las condiciones sea True, y será False en cualquier otro caso. Note que esto no es lo mismo que aplicar un XOR varias veces sobre dos operandos o resultados parciales cada vez. Por ejemplo, un XOR sobre 3 tests/condiciones:

T1 T2 T3 XOR(T1,T2,T3)
1 1 1 0
1 1 0 0
1 0 1 0
1 0 0 1
0 1 1 0
0 1 0 1
0 0 1 1
0 0 0 0
 
 
 
 
 
 
 
 
 
 
 


Un ejercicio interesante consiste en definir la expresión Booleana que obtenga este resultado usando sólo los operadores AND, OR y NOT. Aquí está una posible solución:

( T1 AND NOT (T2 OR T3) ) OR ( (NOT T1) AND (T2 OR T3) AND NOT (T2 AND T3) )

Esta expresión puede armarse por inspección. En la mitad superior de la tabla (donde T1 es True) sólo hay un resultado True: en donde ni T2 ni T3 son True. Sin embargo, la expresión Booleana puede ser cada vez mas difícil de escribir a medida que los valores restantes de la tabla deben ser incluídos.

Podemos simplificar esta expresión un poco si usamos el operador XOR sobre dos operandos que vimos antes:

( T1 AND NOT (T2 OR T3) ) OR ( (NOT T1) AND (T2 XOR T3) )

Se invita al lector a convertir estas expresiones Booleanas a código de programación por bloques.

Top

Ejemplos Gráficos Avanzados

En esta sección se muestran varios ejemplos avanzados de programas printf.exe. Estos ejemplos comparten un punto en común: todos ellos están basados en gráficas, es decir, en trazar figuras en la pantalla. Se eligió este tópico porque estos programas son mas interesantes que otros tipos de aplicaciones avanzadas y los cálculos requeridos son buenos ejemplos de la potencia que tiene printf.exe como lenguaje de programación.

Si tomamos en cuenta que printf.exe es un programa basado en texto que es utilizado en la línea de comandos, ¿cómo podemos mostrar gráficas con él? Bueno, desde siempre se han podido trazar gráficas rudimentarias utilizando caracteres. La figura trazada será mas detallada mientras menor sea el tamaño de los caracteres. De esta manera, para trazar gráficas perfectas en modo de texto ¡bastará con usar caracteres de tamaño de un pixel! Este método se describe con detalle en este sitio (en inglés). Utilizaremos la aplicación PIXELFNT.EXE para activar el tipo de letra de tamaño 1x1 ("modo de gráficas").

El Mandelbrot Set

El Mandelbrot Set es la figura mas famosa de la familia llamada Fractales. Es una gráfica interesante repleta de detalles recursivos como se muestra a la derecha que se traza utilizando un método relativamente sencillo, aunque los conceptos matemáticos involucrados en el trazo no son para nada sencillos.

Tome la pantalla como un plano complejo de coordenadas Z=X+iY. En cada punto ZPos=(XPos,YPos) itere el valor de Zn+1=Zn^2-ZPos [que es igual a (Xn+1=Xn^2-Yn^2-XPos) + i(Yn+1=2*Xn*Yn-YPos)] hasta que el valor absoluto de |Zn|=SQRT(Xn^2+Yn^2) sea mayor o igual a 2. El número de iteraciones da el color de ese punto. Si |Zn| no llega a 2 después de un número máximo de iteraciones, el punto se queda negro. ¡Simple! ¿Verdad?

El código C de abajo es un ejemplo de este método.

/*  X y Y son variables enteras "for" con coordenadas para la ventana; por ejemplo: 640 x 480 pixels     */
/*  Si el número de pixels excede el tamaño de la pantalla, la ventana tendrá barras de desplazamiento   */
/*  XPos y YPos son coordenadas de punto flotante que cubren el área de la imagen                        */
/*  Por ejemplo: XPos de -1 a +2 y YPos de -1.125 a 1.125 (para conservar el aspecto 4:3 de la pantalla) */

MaxX = 640;
MaxY = 480;
          YTop = 1.125;
XLeft = -1.0;     XRight = 2.0;
       YBottom = -1.125;
MaxLevel = 500;         /* Este valor fija el número máximo de iteraciones en cada punto       */
                        /* Valores grandes producen gráficas con mas detalles, pero tardan más */

XStep = (XRight-XLeft) / MaxX;
YStep = (YTop-YBottom) / MaxY;

YPos = YBottom;
for ( Y = 0 ; Y < MaxY ; Y++ ) {
   YPos += YStep;
   XPos = XLeft;
   for ( X = 0 ; X < MaxX ; X++ ) {
      XPos += XStep;

      /* Cálculo iterativo del Level en este punto (XPos,YPos) */
      YIter = YPos;
      XIter = XPos;
      YSquare = YIter * YIter;
      XSquare = XIter * XIter;
      Level = MaxLevel;
      while ( (XSquare + YSquare) < 4.0  &&  --Level > 0 ) {
         YIter = 2 * XIter * YIter - YPos;
         XIter = XSquare - YSquare - XPos;
         YSquare = YIter * YIter;
         XSquare = XIter * XIter;
      }

      if ( Level > 0 ) { Pixel = MaxLevel-Level; putpixel(X,Y,Pixel); }
   }
}

En el programa real es necesario mapear el rango del Level de 1..MaxLevel al rango de colores en la pantalla; por ejemplo, 1..15 en modo de texto. Este mapeo se hace agrupando los niveles mas altos en grupos cada vez mas grandes, lo que produce bandas de color mas anchas. El agrupamiento de niveles en colores se hace de esta forma (éste es el mejor método simple después de varias pruebas):

if (level <= 12) {		// Color = nivel en los primeros 12 valores
   color = level
} else if (level <= 14) {	// Se agrupan 2 niveles en el siguiente color
   color = 13
} else if (level <= 18) {	// Se agrupan 4 niveles en el siguiente color (amarillo)
   color = 14
} else if (level <= 36) {	// Se agrupan 18 niveles
   color = 1			// en una banda azul oscuro
} else {
   color = 15			// Se agrupa el resto de niveles en color blanco
}

El método que se muestra a continuación es la forma mas eficiente de efectuar el cálculo del nivel en cada punto (XPos,YPos) utilizando el FPU. Note que este método hace un uso extensivo de las operaciones Sólo en el Stack de la FPU.

En el código RPN los valores de trabajo se guardan en estos registros de almacenamiento:

I0      MaxX            640
I1      MaxY            480
I2      MaxLevel        500
I3      Level           0..MaxLevel
I4      X               0..MaxX
I5      Y               0..MaxY

FP0     XLeft           -1.0
FP1     YTop            1.125
FP2     XRight          2.0
FP3     YBottom         YTop - (XRight-XLeft)/MaxX*MaxY = -1.125
FP4     XStep           (XRight-XLeft) / MaxX
FP5     YStep           (YTop-YBottom) / MaxY
FP6     4.0

Ésta es la configuración del stack de la FPU utilizada en el cálculo iterativo:

T:      YPos
Z:      XPos
Y:      YIter
X:      XIter

dejando 4 registros libres para efectuar operaciones

LEVEL(          /* Este procedimiento obtiene el nivel de la posición YPos XPos actual en el registro entero 3 */ ^
                /*                                                         Y=YPos      X=XPos                  */ ^
RCL:2           /*                                             Z=YPos      Y=XPos      X=YIter                 */ ^
RCL:2           /*                                  T=YPos     Z=XPos      Y=YIter     X=XIter                 */ ^
[2 ]3 /" < /"   /* Level = MaxLevel     */ ^
   (            /*"WHILE (  (XSquare + YSquare) < 4.0  &&  (--Level > 0)  )"                                   */ ^
   DUP:2 RCL:*1 /*                       A=YPos     T=XPos     Z=YIter     Y=XIter     X=YSquare               */ ^
   DUP:2 RCL:*1 /*            B=YPos     A=XPos     T=YIter    Z=XIter     Y=YSquare   X=XSquare               */ ^
   RCL:+2       /*            B=YPos     A=XPos     T=YIter    Z=XIter     Y=YSquare   X=XSquare+YSquare       */ ^
   RCL6         /* C=YPos     B=XPos     A=YIter    T=XIter    Z=YSquare   Y=XSquare+YSquare     X=4           */ ^
                /* Note que RCL6 es una operación *estándar* de carga, por lo que requiere un DROP estándar    */ ^
   LEQ?         /* XSquare+YSquare GEQ 4 ? */ ^
   DROP         /*            B=YPos     A=XPos     T=YIter    Z=XIter     Y=YSquare   X=XSquare+YSquare       */ ^
   ;            /* EXIT                 */ ^
   DROP         /*            B=YPos     A=XPos     T=YIter    Z=XIter     Y=YSquare   X=XSquare+YSquare       */ ^
   RCL:-2       /*            B=YPos     A=XPos     T=YIter    Z=XIter     Y=YSquare   X=XSquare               */ ^
   RCL:-2       /*            B=YPos     A=XPos     T=YIter    Z=XIter     Y=YSquare   X=XSquare-YSquare       */ ^
   DROP:2       /*                       A=YPos     T=XPos     Z=YIter     Y=XIter     X=XSquare-YSquare       */ ^
   RCL:-4       /*                       A=YPos     T=XPos     Z=YIter     Y=XIter     X=XSquare-YSquare-XPos (=Xn+1) */ ^
   XCHG:2       /*                       A=YPos     T=XPos     Z=YIter     Y=Xn+1      X=XIter                 */ ^
   RCL:*3       /*                       A=YPos     T=XPos     Z=YIter     Y=Xn+1      X=XIter*YIter           */ ^
   RCL:+1       /*                       A=YPos     T=XPos     Z=YIter     Y=Xn+1      X=2*XIter*YIter         */ ^
   RCL:-5       /*                       A=YPos     T=XPos     Z=YIter     Y=Xn+1      X=2*XIter*YIter-YPos (=Yn+1)   */ ^
   STO:3        /*                       A=YPos     T=XPos     Z=Yn+1      Y=Xn+1      X=Yn+1                  */ ^
   DROP:1       /*                                  T=YPos     Z=XPos      Y=Yn+1      X=Xn+1                  */ ^
   ]--3?        /*
   :            /* LOOP                 */ ^
   DUP:1 DUP:1  /*            B=YPos     A=XPos     T=Yn+1     Z=Xn+1      Y=Xn+1      X=Xn+1    Mismo stack del EXIT */ ^
   )            /* ENDWHILE             */ ^
   DROP:1 DROP:1 DROP:1 DROP:1          /* Recupera el stack inicial:      Y=YPos      X=XPos                  */ ^
                /* Mapea el nivel a un número 0..15 de color: 0..12:igual, 13..14=13, 15..18=14, 19..36=1, else=15  */ ^
   /" [3 ( =0? ; [2 <> - ++ 12 >=? < ; < 14 >=? < < 13 ; < 18 >=? < < 14 ; < 36 >=? < < 1 ; < < 15 ) ]3 < /"    /* Ok */ ^
)               /* ENDPROC LEVEL        */

El programa completo se encuentra en el archivo printf Example C - Mandelbrot Set.bat.

Mostrar texto en gráficas de pixels

Si utilizamos el tipo de letra de tamaño 1x1 para mostrar gráficas, ¿cómo podríamos mostrar texto en la pantalla?. Podríamos utilizar el mismo método que usa el Sistema Operativo: leer un Archivo de Definición de Tipos de Letra que define los pixels individuales que se deben encender para mostrar cada caracter.

Este programa de ejemplo se encuentra en desarrollo. Usted puede leer una descripción del método que se utilizará en Este sitio (en inglés).

Gráficas de la Tortuga (Logo)

Este programa de ejemplo implementa el sistema de Gráficas de la Tortuga como está definido actualmente en el lenguaje de programación Logo. Esta implementación hace posible convertir facilmente cualquier programa gráfico de Logo en un programa printf.exe equivalente.

Este programa de ejemplo se encuentra en desarrollo. Usted puede leer una descripción del método que se utilizará en Este sitio (en inglés).

Curvas Recursivas

Una aplicación muy interesante de la graficación son las llamadas Curvas Recursivas. Ésta es una familia de figuras basadas en varios conceptos matemáticos que muestran una serie de gráficas agradables. Algunas de estas curvas son Minkowski, Hilbert, Wirth, etc.

Este programa de ejemplo se encuentra en desarrollo. Usted puede leer una descripción del método que se utilizará en Este sitio (en inglés).
Top

Apéndices

Instalación y soporte de printf.exe

La instalación del paquete printf.exe es muy sencilla y sólo consiste en descargar el archivo printf.zip y extraer su contenido en un folder determinado, por lo que no requiere modificar ningún estado ni registro de la computadora.

Esta es una relación de los archivos incluídos en el paquete:

AlgebraicToRPN.bat		Convierte expresiones algebraicas a RPN
GETARGS.obj			- Se utiliza en el Apéndice 2
GetStandardForm.bat		Convierte la Forma Didáctica de printf.exe a forma estándar
PIXELFNT.EXE                    Permite trazar gráficas mediante el font de 1x1 pixels
printf - GetKey codes.txt	Códigos devueltos por la función GetKey
printf Example *.bat		Varios programas printf.exe de ejemplo en archivos .BATch
printf.exe			La aplicación printf.exe
printf.exe - Arithmetic and Programming.html	Manual de usuario en Inglés
printf.exe - Aritmética y Programación.html	Manual de usuario en Español
printf.obj			- Se utiliza en el Apéndice 2
printfHelp.bat			Ayuda en pantalla de printf.exe
printfMake.bat			- Se utiliza en el Apéndice 2
printfStorage.asm		- Se utiliza en el Apéndice 2
Register your copy of printf.exe.html		Página de registro en Inglés
Registra tu copia de printf.exe.html		Página de registro en Español
ShowKeyCodes.bat		Genera los códigos del operador GetKey

Se sugiere comenzar a utilizar este paquete desde el mismo folder donde se encuentra instalado. Posteriormente, si usted lo considera conveniente, puede incluír la ruta completa del archivo printf.exe dentro de la variable PATH del sistema, lo que permitiría usar esta aplicación desde cualquier otro subdirectorio.

NOTA: Algunos programas antivirus pueden marcar a printf.exe como "potencialmente peligroso". Este es un mensaje falso positivo causado, en primer lugar, porque el código fuente de printf.exe está escrito en ensamblador. Si usted descargó el paquete printf.zip del sitio apaacini.com, entonces está libre de virus con toda seguridad.

Si usted tiene cualquier pregunta o duda sobre el uso de printf.exe, puede revisar la página DosTips.com printf.exe. Lea la conversación desde el principio; es probable que su pregunta haya sido contestada anteriormente. En caso contrario, escriba su pregunta en forma clara y publíquela en el sitio como New Reply (en inglés); incluya un ejemplo de la operación de printf.exe que le cause problemas. Es conveniente que usted se registre como usuario de DosTips.com antes de publicar su pregunta.

También se sugiere que se registre como usuario de printf.exe.

Aumentando el espacio de almacenamiento

El número de registros de almacenamiento, tanto enteros como de punto flotante, y el número de caracteres reservados para almacenar cadenas se puede aumentar al valor que usted desee siguiendo el procedimiento descrito en esta sección, pero si estos números son excesivos y la computadora no cuenta con suficiente memoria, se marcará un error de ejecución.

También se puede aumentar el número de funciones CMD anidadas, el número de marcas FMT{ anidadas y el número de bloques de código con nombre (subrutinas) que pueden ser definidos.

Para aumentar estos números se requiere instalar previamente el ensamblador MASM32. Abra la liga del MASM32 SDK y complete el proceso de descarga e instalación del paquete; le tomará tan sólo un par de minutos hacerlo. Al terminar, el ensamblador deberá quedar instalado en su directorio estándar, que es C:\masm32\

Los valores que definen el espacio de almacenamiento de printf.exe se encuentran en el archivo printfStorage.asm. Usted sólo debe editar este archivo con un editor de texto puro, como el Bloc de Notas de Windows; si utiliza otro editor, asegúrese de que el archivo se guarde como texto sin formato y codificación ANSI. Estos son los valores originales que usted puede modificar:

	IREGS		EQU	20		;Número de registros de almacenamiento Enteros
	FREGS		EQU	20		;Número de registros de almacenamiento de Punto Flotante
	CHARS		EQU	10 * 1024	;Tamaño del área de memoria para almacenar cadenas de caracteres = 10 KB
	MAX_FORMAT	EQU	40		;Número máximo de marcas FMT{ anidadas x 2
	MAX_CMDSTDIN	EQU	20		;Número máximo de funciones CMD anidadas (Stdin's redireccionados)
	NAMEDBLOCK_LEN	EQU	32		;Número permitido de bloques con nombre (subrutinas)

NO modifique ninguna otra parte de este archivo. Después de guardar el archivo modificado, abra una ventana de comandos de cmd.exe y ejecute el programa printfMake.bat una sola vez. ¡Eso es todo!

La aplicación printf.exe también permite consultar los primeros tres valores. Por ejemplo:

printf "Registros Enteros: %i\tRegistros Flotantes: %i\tCaracteres: %i\n" IREGS FREGS CHARS

Esto permite que un programa compruebe que exista memoria suficiente para su operación y, en caso contrario, cancelar con un mensaje descriptivo en lugar de ocasionar un error de ejecución. Por ejemplo:

printf "%s\n"    ^
   ( 1500 IREGS <?   ^
      "Error: El número de registros enteros debe aumentarse a 1500" OUT   ^
   ;   ^
      "Registros suficientes, procediendo..." OUT   ^
      1 2 3 ETC...   ^
   )

Mensajes de error

En este apéndice se describen los mensajes de error de la aplicación printf.exe. Cuando uno de estos errores se presenta, printf.exe termina con un valor de ERRORLEVEL igual al número de error pero con signo negativo.

  1. ¡MEMORIA INSUFICIENTE!

    - La computadora tiene muy poca memoria disponible (poco probable).

    - Se solicitó un número muy grande de registros de almacenamiento o caracteres.

  2. 'Caracter, "cadena o /*comentario sin cerrar

    Falta el caracter de cierre de un Caracter', Cadena" o Comentario*/. Note que una cadena o comentario sin cerrar sólo se puede detectar hasta que el programa termina.

  3. Número erróneo o demasiado grande

    Un número está mal escrito. Consulte Tipos de Datos para revisar la forma correcta.

  4. Operador inválido

    Suele ser un error de tecleo. Recuerde separar todos los operadores por uno o más espacios. Este error puede suceder al escribir operadores muy "largos", como ]--.3?.

  5. Función o variable no definida

    Suele ser un error de tecleo; revise la sintaxis. Este error puede suceder al escribir funciones muy "largas", como IN{+?:6. Recuerde que si usa el valor de una variable Batch como cadena, la variable debe estar definida.

  6. Máximo número de operaciones excedido

    Más de 20 niveles de marcas FMT{ activas o funciones CMD anidadas, o más de 32 Bloques con Nombre (subrutinas) definidos. Consulte el Apéndice 1 por instrucciones sobre cómo aumentar estos límites.

  7. ":" Repeat, ";" Quit, ")" End o Test? falso fuera de todo (Bloque

    Las operaciones: : ; ) o un Test? que resulta falso sólo pueden aparecer dentro de un bloque de código.

  8. Bloque sin cerrar en un nivel de anidamiento

    Falta el paréntesis de cierre de un bloque de código. Note que este error sólo puede detectarse hasta que el programa termina. El nivel de anidamiento reportado puede ayudar a identificar el bloque que quedó abierto.

  9. Este bloque con NOMBRE( no puede definirse aquí

    Los bloques de código con nombre (subrutinas) deben definirse antes de la cadena "formato" y deben ser bloques externos (no anidados). Dos bloques con nombre no pueden tener el mismo nombre (primeros 4 caracteres).

  10. Demasiadas llamadas (recursivas) a subrutinas

    Este error usualmente es un bucle de invocaciones recursivas sin salida. Revise la condición que interrumpe el ciclo de invocaciones recursivas. El máximo número de regresos pendientes de subrutinas es de alrededor de 128,000.

  11. No se puede crear/abrir un archivo de salida

    El archivo de salida que se intentó abrir no se encontró o no pudo crearse. Suele ser un error de tecleo que inserta un caracter ilegal en el nombre del archivo, o un subdirectorio que no existe. Recuerde que se puede crear un directorio usando la función CMD. El mismo error "no encontrado" sobre un archivo de entrada se detecta en la misma operación/test? de apertura IN{?:n.

  12. Operación de entrada/salida sobre un handle no abierto

    Una operación IN?:n de entrada u OUT:n de salida sobre un archivo usa un número de handle que no ha sido abierto. Revise que el número de handle N de la operación de apertura sea el mismo de la operación de E/S.

Después que printf.exe muestre un mensaje de error, usted puede mostrar la descripción completa del mismo ejecutando esta línea: printfHelp %errorlevel% Puede insertar esta línea en el archivo ERR.BAT y entonces sólo ejecutar: ERR


Introducción Texto con
Formato
Operaciones
Aritméticas
Programación
por Bloques
Registra tu
printf.exe